#include #include #include #include #include #include #include #include <9p.h> #include #include #include "dat.h" #include "fns.h" char PostContentType[] = "application/x-www-form-urlencoded"; int httpdebug; typedef struct HttpState HttpState; struct HttpState { int fd; Client *c; char *location; char *setcookie; char *netaddr; char *credentials; char autherror[ERRMAX]; Ibuf b; }; static void location(HttpState *hs, char *value) { if(hs->location == nil) hs->location = estrdup(value); } static void contenttype(HttpState *hs, char *value) { if(hs->c->contenttype != nil) free(hs->c->contenttype); hs->c->contenttype = estrdup(value); } static void setcookie(HttpState *hs, char *value) { char *s, *t; Fmt f; s = hs->setcookie; fmtstrinit(&f); if(s) fmtprint(&f, "%s", s); fmtprint(&f, "set-cookie: "); fmtprint(&f, "%s", value); fmtprint(&f, "\n"); t = fmtstrflush(&f); if(t){ free(s); hs->setcookie = t; } } static char* unquote(char *s, char **ps) { char *p; if(*s != '"'){ p = strpbrk(s, " \t\r\n"); *p++ = 0; *ps = p; return s; } for(p=s+1; *p; p++){ if(*p == '\"'){ *p++ = 0; break; } if(*p == '\\' && *(p+1)){ p++; continue; } } memmove(s, s+1, p-(s+1)); s[p-(s+1)] = 0; *ps = p; return s; } static char* servername(char *addr) { char *p; if(strncmp(addr, "tcp!", 4) == 0 || strncmp(addr, "net!", 4) == 0) addr += 4; addr = estrdup(addr); p = addr+strlen(addr); if(p>addr && *(p-1) == 's') p--; if(p>addr+5 && strcmp(p-5, "!http") == 0) p[-5] = 0; return addr; } void wwwauthenticate(HttpState *hs, char *line) { char cred[64], *user, *pass, *realm, *s, *spec, *name; Fmt fmt; UserPasswd *up; spec = nil; up = nil; cred[0] = 0; hs->autherror[0] = 0; if(cistrncmp(line, "basic ", 6) != 0){ werrstr("unknown auth: %s", line); goto error; } line += 6; if(cistrncmp(line, "realm=", 6) != 0){ werrstr("missing realm: %s", line); goto error; } line += 6; user = hs->c->url->user; pass = hs->c->url->passwd; if(user==nil || pass==nil){ realm = unquote(line, &line); fmtstrinit(&fmt); name = servername(hs->netaddr); fmtprint(&fmt, "proto=pass service=http server=%q realm=%q", name, realm); free(name); if(hs->c->url->user) fmtprint(&fmt, " user=%q", hs->c->url->user); spec = fmtstrflush(&fmt); if(spec == nil) goto error; if((up = auth_getuserpasswd(nil, "%s", spec)) == nil) goto error; user = up->user; pass = up->passwd; } if((s = smprint("%s:%s", user, pass)) == nil) goto error; free(up); enc64(cred, sizeof(cred), (uchar*)s, strlen(s)); memset(s, 0, strlen(s)); free(s); hs->credentials = smprint("Basic %s", cred); if(hs->credentials == nil) goto error; return; error: free(up); free(spec); snprint(hs->autherror, sizeof hs->autherror, "%r"); fprint(2, "%s: Authentication failed: %r\n", argv0); } struct { char *name; /* Case-insensitive */ void (*fn)(HttpState *hs, char *value); } hdrtab[] = { { "location:", location }, { "content-type:", contenttype }, { "set-cookie:", setcookie }, { "www-authenticate:", wwwauthenticate }, }; static int httprcode(HttpState *hs) { int n; char *p; char buf[256]; n = readline(&hs->b, buf, sizeof(buf)-1); if(n <= 0) return n; if(httpdebug) fprint(2, "-> %s\n", buf); p = strchr(buf, ' '); if(memcmp(buf, "HTTP/", 5) != 0 || p == nil){ werrstr("bad response from server"); return -1; } buf[n] = 0; return atoi(p+1); } /* * read a single mime header, collect continuations. * * this routine assumes that there is a blank line twixt * the header and the message body, otherwise bytes will * be lost. */ static int getheader(HttpState *hs, char *buf, int n) { char *p, *e; int i; n--; p = buf; for(e = p + n; ; p += i){ i = readline(&hs->b, p, e-p); if(i < 0) return i; if(p == buf){ /* first line */ if(strchr(buf, ':') == nil) break; /* end of headers */ } else { /* continuation line */ if(*p != ' ' && *p != '\t'){ unreadline(&hs->b, p); *p = 0; break; /* end of this header */ } } } if(httpdebug) fprint(2, "-> %s\n", buf); return p-buf; } static int httpheaders(HttpState *hs) { char buf[2048]; char *p; int i, n; for(;;){ n = getheader(hs, buf, sizeof(buf)); if(n < 0) return -1; if(n == 0) return 0; // print("http header: '%.*s'\n", n, buf); for(i = 0; i < nelem(hdrtab); i++){ n = strlen(hdrtab[i].name); if(cistrncmp(buf, hdrtab[i].name, n) == 0){ /* skip field name and leading white */ p = buf + n; while(*p == ' ' || *p == '\t') p++; (*hdrtab[i].fn)(hs, p); break; } } } } int httpopen(Client *c, Url *url) { int fd, code, redirect, authenticate; char *cookies; Ioproc *io; HttpState *hs; char *service; if(httpdebug) fprint(2, "httpopen\n"); io = c->io; hs = emalloc(sizeof(*hs)); hs->c = c; if(url->port) service = url->port; else service = url->scheme; hs->netaddr = estrdup(netmkaddr(url->host, 0, service)); c->aux = hs; if(httpdebug){ fprint(2, "dial %s\n", hs->netaddr); fprint(2, "dial port: %s\n", url->port); } fd = iotlsdial(io, hs->netaddr, 0, 0, 0, url->ischeme==UShttps); if(fd < 0){ Error: if(httpdebug) fprint(2, "iodial: %r\n"); free(hs->location); free(hs->setcookie); free(hs->netaddr); free(hs->credentials); if(fd >= 0) ioclose(io, hs->fd); hs->fd = -1; free(hs); c->aux = nil; return -1; } hs->fd = fd; if(httpdebug) fprint(2, "<- %s %s HTTP/1.0\n<- Host: %s\n", c->havepostbody? "POST": "GET", url->http.page_spec, url->host); ioprint(io, fd, "%s %s HTTP/1.0\r\nHost: %s\r\n", c->havepostbody? "POST" : "GET", url->http.page_spec, url->host); if(httpdebug) fprint(2, "<- User-Agent: %s\n", c->ctl.useragent); if(c->ctl.useragent) ioprint(io, fd, "User-Agent: %s\r\n", c->ctl.useragent); if(c->ctl.sendcookies){ /* should we use url->page here? sometimes it is nil. */ cookies = httpcookies(url->host, url->http.page_spec, url->ischeme == UShttps); if(cookies && cookies[0]) ioprint(io, fd, "%s", cookies); if(httpdebug) fprint(2, "<- %s", cookies); free(cookies); } if(c->havepostbody){ ioprint(io, fd, "Content-type: %s\r\n", PostContentType); ioprint(io, fd, "Content-length: %ud\r\n", c->npostbody); if(httpdebug){ fprint(2, "<- Content-type: %s\n", PostContentType); fprint(2, "<- Content-length: %ud\n", c->npostbody); } } if(c->authenticate){ ioprint(io, fd, "Authorization: %s\r\n", c->authenticate); if(httpdebug) fprint(2, "<- Authorization: %s\n", c->authenticate); } ioprint(io, fd, "\r\n"); if(c->havepostbody) if(iowrite(io, fd, c->postbody, c->npostbody) != c->npostbody) goto Error; redirect = 0; authenticate = 0; initibuf(&hs->b, io, fd); code = httprcode(hs); switch(code){ case -1: /* connection timed out */ goto Error; /* case Eof: werrstr("EOF from HTTP server"); goto Error; */ case 200: /* OK */ case 201: /* Created */ case 202: /* Accepted */ case 204: /* No Content */ case 205: /* Reset Content */ #ifdef NOT_DEFINED if(ofile == nil && r->start != 0) sysfatal("page changed underfoot"); #endif break; case 206: /* Partial Content */ werrstr("Partial Content (206)"); goto Error; case 301: /* Moved Permanently */ case 302: /* Moved Temporarily */ case 303: /* See Other */ case 307: /* Temporary Redirect */ redirect = 1; break; case 304: /* Not Modified */ break; case 400: /* Bad Request */ werrstr("Bad Request (400)"); goto Error; case 401: /* Unauthorized */ if(c->authenticate){ werrstr("Authentication failed (401)"); goto Error; } authenticate = 1; break; case 402: /* Payment Required */ werrstr("Payment Required (402)"); goto Error; case 403: /* Forbidden */ werrstr("Forbidden by server (403)"); goto Error; case 404: /* Not Found */ werrstr("Not found on server (404)"); goto Error; case 405: /* Method Not Allowed */ werrstr("Method not allowed (405)"); goto Error; case 406: /* Not Acceptable */ werrstr("Not Acceptable (406)"); goto Error; case 407: /* Proxy auth */ werrstr("Proxy authentication required (407)"); goto Error; case 408: /* Request Timeout */ werrstr("Request Timeout (408)"); goto Error; case 409: /* Conflict */ werrstr("Conflict (409)"); goto Error; case 410: /* Gone */ werrstr("Gone (410)"); goto Error; case 411: /* Length Required */ werrstr("Length Required (411)"); goto Error; case 412: /* Precondition Failed */ werrstr("Precondition Failed (412)"); goto Error; case 413: /* Request Entity Too Large */ werrstr("Request Entity Too Large (413)"); goto Error; case 414: /* Request-URI Too Long */ werrstr("Request-URI Too Long (414)"); goto Error; case 415: /* Unsupported Media Type */ werrstr("Unsupported Media Type (415)"); goto Error; case 416: /* Requested Range Not Satisfiable */ werrstr("Requested Range Not Satisfiable (416)"); goto Error; case 417: /* Expectation Failed */ werrstr("Expectation Failed (417)"); goto Error; case 500: /* Internal server error */ werrstr("Server choked (500)"); goto Error; case 501: /* Not implemented */ werrstr("Server can't do it (501)"); goto Error; case 502: /* Bad gateway */ werrstr("Bad gateway (502)"); goto Error; case 503: /* Service unavailable */ werrstr("Service unavailable (503)"); goto Error; default: /* Bogus: we should treat unknown code XYZ as code X00 */ werrstr("Unknown response code %d", code); goto Error; } if(httpheaders(hs) < 0) goto Error; if(c->ctl.acceptcookies && hs->setcookie) httpsetcookie(hs->setcookie, url->host, url->path); if(authenticate){ if(!hs->credentials){ if(hs->autherror[0]) werrstr("%s", hs->autherror); else werrstr("unauthorized; no www-authenticate: header"); goto Error; } c->authenticate = hs->credentials; hs->credentials = nil; }else if(c->authenticate) c->authenticate = 0; if(redirect){ if(!hs->location){ werrstr("redirection without Location: header"); goto Error; } c->redirect = hs->location; hs->location = nil; } return 0; } int httpread(Client *c, Req *r) { HttpState *hs; long n; hs = c->aux; n = readibuf(&hs->b, r->ofcall.data, r->ifcall.count); if(n < 0) return -1; r->ofcall.count = n; return 0; } void httpclose(Client *c) { HttpState *hs; hs = c->aux; if(hs == nil) return; if(hs->fd >= 0) ioclose(c->io, hs->fd); hs->fd = -1; free(hs->location); free(hs->setcookie); free(hs->netaddr); free(hs->credentials); free(hs); c->aux = nil; }