#include #include #include #include #include #include #include "httpd.h" #include "httpsrv.h" enum { Tpl = 7, // # thumbnails per line Lpp = 4, // # thumbnails lines per page Twid = 100, // thumbnail width (pixels) Downwid = 16, // width of down arrow on directories // file types in directory scan Tdull = 0, // boring file Tdir, // real directory Tphoto, // virtual dir from photosrv Timage // image (known extension) }; Hio *hout; static Hio houtb; static HConnect *connect; static int vermaj; static char *Magic = "exif is over-complex"; static char *Imgexts[] = { ".jpg", ".gif", ".png" }; char *Strapcolor = "#EFFFEF"; // acme title bar green char *Rulercolor = "#428a42"; // acme ruler green char *Textcolor = "black"; char *Bodycolor = "white"; /*********************************************************/ char * path2text(char *path) { int i; char *p, *s, *text; if((s = strrchr(path, '/')) == nil) s = path; else s++; text = strdup(s); if((p = strrchr(text, '.')) != nil) for(i = 0; i < nelem(Imgexts); i++) if(cistrcmp(p, Imgexts[i]) == 0){ *p = 0; break; } if(isalpha(*text)) *text = toupper(*text); return text; } char * path2parent(char *path) { char *p, *parent; parent = strdup(path); if(strcmp(parent, "/") == 0) return parent; if((p = strrchr(parent, '/')) != nil){ if(p != parent) p[0] = 0; else p[1] = 0; } return parent; } char * mkuri(char *fmt, ...) { va_list a; char *r, *w, *p, *s, *t; va_start(a, fmt); s = vsmprint(fmt, a); va_end(a); // strip duplicate slashes p = ""; for(w = r = s; *r; r++){ *w = *r; if(*p == '/' && *w == '/') continue; p = w++; } *w = 0; t = smprint("%U", s); free(s); return t; } int filetype(Dir *d) { int i; char *p; DigestState *ds; uchar hash[SHA1dlen]; char muid[SHA1dlen*2]; ds = sha1((uchar *)Magic, strlen(Magic), nil, nil); sha1((uchar *)d->name, strlen(d->name), hash, ds); snprint(muid, sizeof(muid), "%.*[", 8, hash); if(strcmp(muid, d->muid) == 0) return Tphoto; if((p = strrchr(d->name, '.')) != nil) for(i = 0; i < nelem(Imgexts); i++) if(cistrcmp(p, Imgexts[i]) == 0) return Timage; if((d->mode & DMDIR) == 0) return Tdull; return Tdir; } /*********************************************************/ /* * couldn't open a file * figure out why and return and error message */ static int notfound(char *path) { connect->xferbuf[0] = 0; errstr(connect->xferbuf, sizeof connect->xferbuf); if(strstr(connect->xferbuf, "file does not exist") != nil) return hfail(connect, HNotFound, path); if(strstr(connect->xferbuf, "permission denied") != nil) return hfail(connect, HUnauth, path); return hfail(connect, HNotFound, path); } static void prelude(char *title, char *icon) { if (vermaj) { hokheaders(connect); hprint(hout, "Content-type: text/html\n"); hprint(hout, "\n"); } hprint(hout, "\n"); hprint(hout, "\n"); hprint(hout, "\n"); hprint(hout, "%s\n", title); if(icon) hprint(hout, " \n", icon); hprint(hout, "\n"); hprint(hout, "\n", Textcolor, Bodycolor); /* * A crude attempt at stopping people from snatching our pictures * doesn't really work but discourages the dull masses. */ hprint(hout, "\n"); hprint(hout, "\n"); hprint(hout, "
\n"); hprint(hout, "\n"); hprint(hout, "
\n"); } static void prologue(void) { hprint(hout, "\n"); hprint(hout, "\n"); } static void framenav(char *path, char *names[3], int page, int npages) { char *title, *uri; hprint(hout, "\n"); hprint(hout, "\n", Rulercolor, Strapcolor); hprint(hout, " \n"); title = path2text(path); hprint(hout, " \n"); free(title); hprint(hout, " \n"); hprint(hout, " \n"); uri = mkuri("/magic/webphoto?dir=%s", path); hprint(hout, " \n"); free(uri); hprint(hout, " \n"); hprint(hout, " \n"); hprint(hout, "
\n"); hprint(hout, " \n"); hprint(hout, " %s\n", title); hprint(hout, " \n"); hprint(hout, " \n"); hprint(hout, " \n"); hprint(hout, " %d/%d\n", page, npages); hprint(hout, " \n"); hprint(hout, " \n"); if(names[0]){ uri = mkuri("/magic/webphoto?frame=%s/%s", path, names[0]); hprint(hout, " \n", uri); hprint(hout, " Previous\n"); hprint(hout, " \n"); free(uri); } else hprint(hout, "  \n"); hprint(hout, " \n"); hprint(hout, " \n", uri, (page + ((Tpl * Lpp)-1)) /(Tpl * Lpp)); hprint(hout, " Index\n"); hprint(hout, " \n"); hprint(hout, " \n"); if(names[2]){ uri = mkuri("/magic/webphoto?frame=%s/%s", path, names[2]); hprint(hout, " \n", uri); hprint(hout, " Next\n"); hprint(hout, " \n"); free(uri); } else hprint(hout, "  \n"); hprint(hout, "
\n"); hprint(hout, "
\n"); } static void doframe(char *path, char *file, int type) { char *p, *name, *alt, *uri, *nuri; /* top edge of drop shadow */ hprint(hout, "\n"); hprint(hout, "
\n"); hprint(hout, "\n"); hprint(hout, "\n"); hprint(hout, " \n"); hprint(hout, " \n"); hprint(hout, " \n"); hprint(hout, "\n"); alt = "Click for metadata"; name = path2text(file); if(type == Tphoto){ if((p = strrchr(file, '.')) == nil) p = ""; uri = mkuri("/magic/webphoto?image=%s/%s/fullsize%s", path, file, p); } else uri = mkuri("/magic/webphoto?image=%s/%s", path, file); nuri = mkuri("/magic/webphoto?metadata=%s/%s", path, file); /* image and sides of drop shadow */ hprint(hout, "\n"); hprint(hout, "\n"); hprint(hout, " \n"); hprint(hout, " \n"); hprint(hout, " \n"); hprint(hout, "\n"); free(name); free(uri); free(nuri); /* bottom edge of drop shadow */ hprint(hout, "\n"); hprint(hout, "\n"); hprint(hout, " \n"); hprint(hout, " \n"); hprint(hout, " \n"); hprint(hout, "\n"); hprint(hout, "
\n"); hprint(hout, "

\n"); hprint(hout, "
\n"); hprint(hout, "

 

\n"); hprint(hout, "
\n"); hprint(hout, "

\n"); hprint(hout, "
\n"); hprint(hout, "

 

\n"); hprint(hout, "
\n"); hprint(hout, " \n"); hprint(hout, " %s\n", nuri, uri, alt); hprint(hout, " \n"); hprint(hout, "
\n"); hprint(hout, "

"); hprint(hout, " \n"); hprint(hout, " %s", name); hprint(hout, " \n"); hprint(hout, "

\n"); hprint(hout, "
\n"); hprint(hout, "

 

\n"); hprint(hout, "
\n"); hprint(hout, "

\n"); hprint(hout, "
\n"); hprint(hout, "

 

\n"); hprint(hout, "
\n"); hprint(hout, "

\n"); hprint(hout, "
\n"); hprint(hout, "
\n"); } char * probedir(char *path, char *dir) { Dir *d; int i, type, n, fd; char *p, *fn, *first; fn = smprint("%s/%s", path, dir); if((fd = open(fn, OREAD)) == -1){ free(fn); return nil; } if((n = dirreadall(fd, &d)) < 1){ free(fn); close(fd); return nil; } type = -1; for(i = 0; i < n; i++){ type = filetype(&d[i]); if(type == Tphoto || type == Timage) break; } if(i < n){ if(type == Tphoto){ if((p = strrchr(d[i].name, '.')) == nil) p = ""; first = mkuri("/magic/webphoto?thumb=%s/%s/%s/thumbnail%s", path, dir, d[i].name, p); } else first = mkuri("/magic/webphoto?thumb=%s/%s/%s", path, dir, d[i].name); } else first = mkuri("/nav/query.jpg"); free(d); free(fn); return first; } void dodir(char *path, char **files, int *types) { int i; char *p, *alt, *name, *img, *uri, *nuri; hprint(hout, "\n"); hprint(hout, ""); hprint(hout, "\n"); hprint(hout, " \n"); for(i = 0; i < Tpl; i++){ if(files[i] == nil) continue; hprint(hout, " \n"); free(name); } hprint(hout, " \n"); hprint(hout, "\n"); hprint(hout, ""); hprint(hout, "
\n"); hprint(hout, " \n"); name = path2text(files[i]); switch(types[i]){ case Tdir: alt = "Click for album contents"; if((img = probedir(path, files[i])) == nil) img = strdup("/nav/query.jpg"); uri = mkuri("/magic/webphoto?dir=%s/%s", path, files[i]); hprint(hout, " \n", uri); hprint(hout, " %s
%s\n", alt, Twid, img, name); hprint(hout, " %s\n", name, Downwid); hprint(hout, "
\n"); free(uri); break; case Timage: alt = "Click for larger image"; uri = mkuri("/magic/webphoto?frame=%s/%s", path, files[i]); nuri = mkuri("/magic/webphoto?thumb=%s/%s", path, files[i]); hprint(hout, " \n", uri); hprint(hout, " %s
%s", alt, Twid, nuri, name); hprint(hout, "
\n"); free(uri); free(nuri); break; case Tphoto: alt = "Click for larger image"; if((p = strrchr(files[i], '.')) == nil) p = ""; uri = mkuri("/magic/webphoto?frame=%s/%s", path, files[i]); nuri = mkuri("/magic/webphoto?image=%s/%s/thumbnail%s", path, files[i], p); hprint(hout, " \n", uri); hprint(hout, " %s
%s", alt, Twid, nuri, name); hprint(hout, "
\n"); free(uri); free(nuri); break; default: alt = "Unknown"; uri = mkuri("/magic/webphoto?image=%s/%s", path, files[i]); hprint(hout, " ", uri); hprint(hout, " %s
%s", alt, Twid, name); hprint(hout, "
\n"); free(uri); break; } hprint(hout, "
\n"); hprint(hout, "
\n"); } static void dirnav(char *path, char *parent, int page, int npages) { char *title, *uri; hprint(hout, "\n"); hprint(hout, "\n"); hprint(hout, "\n", Rulercolor, Strapcolor); hprint(hout, " \n"); title = path2text(path); hprint(hout, " \n"); free(title); hprint(hout, " \n"); hprint(hout, " \n"); hprint(hout, " \n"); hprint(hout, " \n"); hprint(hout, "
\n"); hprint(hout, " \n"); hprint(hout, " %s\n", title); hprint(hout, " \n"); hprint(hout, " \n"); hprint(hout, " \n"); hprint(hout, " %d/%d\n", page, npages); hprint(hout, " \n"); hprint(hout, " \n"); if(page > 1){ uri = mkuri("/magic/webphoto?dir=%s", path); hprint(hout, " \n", uri, page-1); hprint(hout, " Previous\n"); hprint(hout, " \n"); free(uri); } else hprint(hout, "  \n"); hprint(hout, " \n"); uri = mkuri("/magic/webphoto?dir=%s", parent); hprint(hout, " \n", uri); hprint(hout, " Index\n"); hprint(hout, " \n"); free(uri); hprint(hout, " \n"); if(page < npages){ uri = mkuri("/magic/webphoto?dir=%s", path); hprint(hout, " \n", uri, page+1); hprint(hout, " Next\n"); hprint(hout, " \n"); free(uri); } else hprint(hout, "  \n"); hprint(hout, "
\n"); hprint(hout, "
\n"); } /*************************************************/ int frame(char *path) { Dir *d; int i, n, tot, idx, fd, t, types[3]; char *title, *file, *names[3]; if((file = strrchr(path, '/')) == nil){ hfail(connect, HBadReq); exits("missing slash"); } if(file == path){ // root dir is special file++; path = "/"; } else{ *file++ = 0; } if((fd = open(path, OREAD)) == -1){ notfound(path); exits("not found"); } if((n = dirreadall(fd, &d)) < 1){ notfound(path); exits("not found"); } title = path2text(path); prelude(title, "/favicon.ico"); free(title); tot = 0; for(i = 0; i < n; i++) switch(filetype(&d[i])){ case Tphoto: case Timage: tot++; } idx = 0; memset(types, 0, sizeof(types)); memset(names, 0, sizeof(names)); for(i = 0; i < n; i++){ t = filetype(&d[i]); if(t != Timage && t != Tphoto) continue; types[2] = t; names[2] = d[i].name; if(names[1] && strcmp(names[1], file) == 0){ framenav(path, names, idx, tot); doframe(path, names[1], types[1]); break; } memcpy(types, &types[1], sizeof(types) - sizeof(types[0])); memcpy(names, &names[1], sizeof(names) - sizeof(names[0])); names[2] = nil; types[2] = 0; idx++; } if(i >= n && names[1] && strcmp(names[1], file) == 0){ framenav(path, names, idx, tot); doframe(path, names[1], types[1]); } prologue(); close(fd); free(d); return 0; } void dir(char *path) { Dir *d; char *str, *title, *parent, *names[Tpl]; int i, j, n, fd, page, npages, types[Tpl]; page = 1; if((str = strchr(path, '?')) != 0){ *str++ = 0; if(strncmp(str, "page=", 5) != 0){ hfail(connect, HSyntax); exits("request syntax"); } str = hurlunesc(connect, str+5); page = atoi(str); } if((fd = open(path, OREAD)) == -1){ notfound(path); exits("not found"); } if((n = dirreadall(fd, &d)) == -1){ notfound(path); exits("not found"); } npages = (n + ((Tpl * Lpp)-1)) /(Tpl * Lpp); if(page < 1 || page > npages){ hfail(connect, HBadReq); exits("bad request"); } title = path2text(path); prelude(title, "/favicon.ico"); free(title); parent = path2parent(path); dirnav(path, parent, page, npages); free(parent); for(i = (page-1)*Tpl*Lpp; i < n && i < page*Tpl*Lpp;){ memset(names, 0, sizeof(names)); memset(types, 0, sizeof(types)); for(j = 0; j < Tpl && i < n; i++){ names[j] = d[i].name; types[j] = filetype(&d[i]); if(types[j] == Tdull) continue; j++; } dodir(path, names, types); } prologue(); close(fd); free(d); } void image(char *path) { int fd; Dir *d; if((fd = open(path, OREAD)) == -1){ notfound(path); exits("not found"); } if((d = dirstat(path)) == nil){ notfound(path); exits("not found"); } /* generate mime type from file extension in request */ connect->req.uri = connect->req.search; sendfd(connect, fd, d, nil, nil); } void metadata(char *path) { Dir *d; Biobuf *bp; int type, lines; char *p, *q; char *alt, *title, *parent, *uri, *meta; if((d = dirstat(path)) == nil){ notfound(path); exits("not found"); } type = filetype(d); free(d); alt = "Full size image"; title = path2text(path); parent = path2parent(path); prelude(title, "/favicon.ico"); /* navigation bar */ hprint(hout, "\n"); hprint(hout, "\n", Rulercolor, Strapcolor); hprint(hout, " \n"); hprint(hout, " \n"); hprint(hout, " \n"); hprint(hout, " \n"); hprint(hout, " \n"); hprint(hout, "
\n"); hprint(hout, " \n"); hprint(hout, " %H\n", title); hprint(hout, " \n"); hprint(hout, " \n"); hprint(hout, " \n"); uri = mkuri("/magic/webphoto?frame=%s", path); hprint(hout, " \n", uri); hprint(hout, " %s\n", alt); hprint(hout, " \n"); free(uri); hprint(hout, " \n"); hprint(hout, "
\n"); hprint(hout, "
\n"); free(parent); free(title); /* page body */ hprint(hout, "\n", Bodycolor); hprint(hout, " \n"); hprint(hout, " \n"); hprint(hout, " \n"); hprint(hout, " \n"); hprint(hout, "
\n"); if(type == Tphoto) uri = mkuri("/magic/webphoto?image=%s/thumbnail.jpg", path); else uri = mkuri("/magic/webphoto?image=%s", path); hprint(hout, " \n", Twid, uri); hprint(hout, " \n"); hprint(hout, " \n", Bodycolor); hprint(hout, " \n"); hprint(hout, " \n"); hprint(hout, " \n"); hprint(hout, " \n"); lines = 0; meta = smprint("%s/metadata", path); if((bp = Bopen(meta, OREAD)) != nil){ while((p = Brdline(bp, '\n')) != nil){ lines++; p[Blinelen(bp) -1] = 0; if((q = strchr(p, ':')) != nil) *q++ = 0; hprint(hout, " \n"); hprint(hout, " \n"); if(q){ while(*q && isspace(*q)) q++; hprint(hout, " \n"); } hprint(hout, " \n"); } Bterm(bp); } free(meta); if(lines == 0){ hprint(hout, " \n"); hprint(hout, " \n"); hprint(hout, " \n"); } hprint(hout, " \n"); hprint(hout, "
\n"); hprint(hout, "  \n"); hprint(hout, "
\n"); hprint(hout, " %s\n", p); hprint(hout, " \n"); hprint(hout, " %s\n", q); hprint(hout, "
\n"); hprint(hout, " No EXIF metadata\n"); hprint(hout, "
\n"); hprint(hout, "
\n"); prologue(); hflush(hout); } void photo(char *str) { if (strncmp(str, "frame=", 6) == 0){ str = hurlunesc(connect, str+6); frame(str); return; } if (strncmp(str, "dir=", 4) == 0){ str = hurlunesc(connect, str+4); dir(str); return; } if (strncmp(str, "image=", 6) == 0){ str = hurlunesc(connect, str+6); image(str); return; } if (strncmp(str, "thumb=", 6) == 0){ str = hurlunesc(connect, str+6); image(str); return; } if (strncmp(str, "metadata=", 9) == 0){ str = hurlunesc(connect, str+9); metadata(str); return; } hfail(connect, HBadReq); } void main(int argc, char *argv[]) { fmtinstall('H', httpfmt); fmtinstall('U', hurlfmt); fmtinstall('M', dirmodefmt); fmtinstall('[', encodefmt); close(2); connect = init(argc, argv); hout = &connect->hout; vermaj = connect->req.vermaj; if(hparseheaders(connect, HSTIMEOUT) < 0) exits("failed"); if(connect->head.expectother || connect->head.expectcont){ hfail(connect, HExpectFail, nil); exits("failed"); } contentinit(); bind("/usr/photos", "/", MREPL); if(connect->req.search != nil) photo(connect->req.search); else hfail(connect, HSyntax); hflush(hout); writelog(connect, "200 webphoto %ld %ld\n", hout->seek, hout->seek); exits(nil); }