implement Handler; include "sys.m"; sys: Sys; sprint: import sys; include "draw.m"; include "arg.m"; arg: Arg; include "string.m"; str: String; include "regex.m"; regex: Regex; include "sh.m"; include "exception.m"; exc: Exception; include "bufio.m"; include "readdir.m"; readdir: Readdir; include "daytime.m"; daytime: Daytime; include "names.m"; names: Names; include "http.m"; http: Http; HConn, Query: import http; headers: Headers; HeaderMap: import headers; include "lib/helpers.m"; sendfile: Sendfile; badprogramming: con "assertion:bad programming"; webdir: string; R403, R404: regex->Re; init(m: Http, args: list of string) { sys = load Sys Sys->PATH; arg = load Arg Arg->PATH; str = load String String->PATH; regex = load Regex Regex->PATH; exc = load Exception Exception->PATH; readdir = load Readdir Readdir->PATH; daytime = load Daytime Daytime->PATH; names = load Names Names->PATH; http = m; headers = http->headers; sendfile = load Sendfile Sendfile->PATH; arg->init(args); arg->setusage(sprint("%s [dir]", arg->progname())); args = arg->argv(); if(len args > 1) arg->usage(); if(len args == 1) webdir = hd args; err: string; (R403, err) = regex->compile("(^|[^a-zA-Z])permission denied([^a-zA-Z]|$)", 0); if(err != nil) { sys->fprint(sys->fildes(2), "%s: R403: %s\n", arg->progname(), err); raise "fail:R403"; } (R404, err) = regex->compile("(^|[^a-zA-Z])(not found|does not exist)([^a-zA-Z]|$)", 0); if(err != nil) { sys->fprint(sys->fildes(2), "%s: R404: %s\n", arg->progname(), err); raise "fail:R404"; } sendfile->init(http); } errstrtono(s: string): int { if(regex->execute(R403, s) != nil) return 403; if(regex->execute(R404, s) != nil) return 404; return 500; } handle(h: ref HConn) { r := h.req; u := h.req.url; (query, qerr) := Query.parse(u.query); if(qerr != nil) { h.err(400, "malformed query: " + qerr); return; } if(r.method != "GET" && r.method != "HEAD") { h.err(405, nil); return; } if(query.exists("tar") && query.exists("ls")) { h.err(404, "tar and ls are mutually exclusive"); return; } if(query.exists("tar")) { if((err := tar(h)) != nil) h.err(500, sprint("%s: tar: %s", arg->progname(), err)); return; } if(webdir != nil) { if(sys->bind(webdir, "/", Sys->MREPL) < 0 || sys->chdir("/") < 0 ) { h.err(500, sprint("%s: chroot: %r", arg->progname())); return; } } sys->pctl(Sys->NODEVS | Sys->FORKFD | Sys->FORKENV, nil); fd := sys->open(u.path, Sys->OREAD); if(fd == nil) { err := sprint("%r"); errno := errstrtono(err); h.err(errno, err); return; } (failed, dir) := sys->fstat(fd); if(failed) { h.err(500, sprint("%s: stat %s: %r", arg->progname(), u.path)); return; } if(dir.mode & Sys->DMDIR) listing(h, query, fd); else sendfile->sendfile(h, fd, dir); } strip(p, s: string): string { if(len s < len p || s[:len p] != p) return s; return s[len p:]; } tar(h: ref HConn): string { if((dirname := names->basename(names->cleanname(h.req.url.path), nil)) == nil) dirname = h.req.url.host; # dirname can still be nil if a 1.0 client didn't send a host header h.rsp.hmap.add("Content-Type", "application/tar, application/x-tar"); h.rsp.hmap.add("Content-Disposition", "attachment; filename=" + dirname + ".tar" ); case h.req.method { "HEAD" => return nil; "GET" => ; * => h.err(405, nil); return nil; } cmd := load Command "/dis/puttar.dis"; if(cmd == nil) return "load puttar"; p := array[2] of ref Sys->FD; if(sys->pipe(p) < 0) return sprint("pipe: %r"); sys->pctl(Sys->NEWPGRP, nil); if(exc->setexcmode(Exception->NOTIFYLEADER) < 0) return "setexcmode"; { done := chan[1] of int; spawn tarproc(cmd, p[0], names->cleanname(h.req.url.path)); spawn copyproc(h, p[1], done); p = nil; cmd = nil; <-done; } exception e { "*" => return strip("fail:", e); } return nil; } tarproc(cmd: Command, out: ref Sys->FD, path: string) { sys->pctl(Sys->NEWFD | Sys->FORKNS | Sys->NODEVS | Sys->NEWENV, out.fd :: nil); if(sys->chdir(webdir + names->dirname(path)) < 0) raise sprint("fail:chdir: %r"); if(sys->dup(out.fd, 1) < 0) raise sprint("fail:dup: %r"); p := names->basename(path, nil); if(len p == 0) p = "."; cmd->init(nil, "puttar" :: p :: nil); } copyproc(h: ref HConn, in: ref Sys->FD, done: chan of int) { buf := array[Sys->ATOMICIO] of byte; n: int; while((n = sys->read(in, buf, len buf)) > 0) { if(h.write(buf, n) != n) raise "fail:copy: write"; # bufio doesn't give useful error messages } if(n < 0) raise sprint("fail:copy: read: %r"); done <-= 69105; } mtab := array[] of { "---", "--x", "-w-", "-wx", "r--", "r-x", "rw-", "rwx" }; modestr(mode: int): string { s: string; if(mode & Sys->DMDIR) s = "d"; else if(mode & Sys->DMAPPEND) s = "a"; else if(mode & Sys->DMAUTH) s = "A"; else s = "-"; if(mode & Sys->DMEXCL) s += "l"; else s += "-"; s += mtab[(mode>>6)&7]+mtab[(mode>>3)&7]+mtab[mode&7]; return s; } parsels(flags: string): int { x := 0; for(i := 0; i < len flags; i++) { case flags[i] { 'u' => x |= Readdir->ATIME; 'n' => x |= Readdir->NONE; 't' => x |= Readdir->MTIME; 's' => x |= Readdir->SIZE; 'r' => x |= Readdir->DESCENDING; } } if(x & ~(Readdir->DESCENDING | Readdir->COMPACT) == 0) x |= Readdir->NAME; return x; } sortgen(mode: int, flag, label: string): string { if((mode & ~(Readdir->DESCENDING | Readdir->COMPACT)) == parsels(flag)) mode ^= Readdir->DESCENDING; else mode &= ~Readdir->DESCENDING; if(mode & Readdir->DESCENDING) flag += "r"; return "" + label + ""; } htmlencode(s: string): string { r: string; for(i := 0; i < len s; i++) { c := s[i]; if(str->in(c, "\"&'<>")) r += sprint("&#%ud;", c); else r[len r] = c; } return r; } listing(h: ref HConn, query: ref Query, fd: ref Sys->FD) { r := h.req; u := h.req.url; if(len u.path < 1 || u.path[len u.path - 1] != '/') { h.rsp.hmap.add("Location", u.path + "/"); h.err(301, nil); return; } sortmode := parsels(query.find("ls")); (dirs, n) := readdir->readall(fd, sortmode | Readdir->COMPACT); if(n < 0) { h.err(500, sprint("%s: readdir: %r", arg->progname())); return; } h.rsp.hmap.add("Content-Type", "text/html; charset=utf-8"); if(r.method == "HEAD") return; if(r.method != "GET") raise badprogramming; h.puts("\n"); if(u.path != "/") h.puts("\n"); h.puts("\n" ); for(i:=0; i < n; i++) { file := dirs[i]; name := file.name; if(file.mode & Sys->DMDIR) name += "/"; name = htmlencode(name); tm := daytime->gmt(file.mtime); h.puts("\n"); h.puts("\n"); } h.puts("
..
muidmodeuidgid" + sortgen(sortmode, "s", "length") + sortgen(sortmode, "t", "mtime") + sortgen(sortmode, "", "name") + "
[" + htmlencode(file.muid) + "]\n"); h.puts(""+modestr(file.mode)+"\n"); h.puts(""+string file.dev+"\n"); h.puts("" + htmlencode(file.uid) + "" + htmlencode(file.gid) + "\n"); h.puts(sprint("%bd\n", file.length)); h.puts(sprint("%.4d-%.2d-%.2d %.2d:%.2d\n", tm.year+1900, tm.mon+1, tm.mday, tm.hour, tm.min )); h.puts(""+name+"\n"); h.puts("
\n"); }