#include <u.h>
#include <libc.h>
#include <bio.h>
#include <ip.h>
#include <plumb.h>
#include <thread.h>
#include <fcall.h>
#include <9p.h>
#include <libsec.h>
#include <auth.h>
#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];
int ischunked;
uvlong length;
Ibuf b;
};
static void
location(HttpState *hs, char *value)
{
if(hs->location == nil)
hs->location = estrdup(value);
}
static void
transferencoding(HttpState *hs, char *value)
{
if(cistrcmp(value, "chunked") == 0)
hs->ischunked = 1;
}
static void
contenttype(HttpState *hs, char *value)
{
if(hs->c->contenttype != nil)
free(hs->c->contenttype);
hs->c->contenttype = estrdup(value);
}
static void
contentlength(HttpState *hs, char *value)
{
if(hs->c->contentlength != nil)
free(hs->c->contentlength);
hs->c->contentlength = estrdup(value);
hs->length = strtoull(value, nil, 0);
}
static void
contentdisposition(HttpState *hs, char *value)
{
if(hs->c->contentdisposition != nil)
free(hs->c->contentdisposition);
hs->c->contentdisposition = 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, "Negotiate ", 9) == 0){
fprint(2, "skip %s\n", line);
goto error;
}
if(cistrncmp(line, "Basic ", 6) != 0){
werrstr("unknown auth: %s", line);
goto error;
}
line += 6;
if(cistrncmp(line, "realm=", 6) != 0){
fprint(2, "ERR: missing realm: %s", line);
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 },
{ "transfer-encoding:", transferencoding },
{ "content-type:", contenttype },
{ "content-type:", contenttype },
{ "content-length:", contentlength },
{ "content-disposition:", contentdisposition },
{ "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 *request, *content, *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;
request = "GET";
if(c->havepostbody)
request = "POST";
if(c->request)
request = c->request;
if(httpdebug)
fprint(2, "<- %s %s HTTP/1.1\n<- Host: %s\n",
request, url->http.page_spec, url->host);
ioprint(io, fd, "%s %s HTTP/1.1\r\nHost: %s\r\n",
request, 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(cookies && cookies[0] && httpdebug)
fprint(2, "<- %s", cookies);
free(cookies);
}
if(c->headers){
ioprint(io, fd, "%s\r\n", c->headers);
if(httpdebug)
fprint(2, "<- %s\n", c->headers);
}
if(c->havepostbody){
content = PostContentType;
if(c->content)
content = c->content;
ioprint(io, fd, "Content-type: %s\r\n", content);
ioprint(io, fd, "Content-length: %ud\r\n", c->npostbody);
if(httpdebug){
fprint(2, "<- Content-type: %s\n", content);
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\r\n", c->authenticate);
}
ioprint(io, fd, "\r\n");
if(httpdebug)
fprint(2, "<- \r\n");
if(c->havepostbody){
if(httpdebug)
fprint(2, "<- %.*s", c->npostbody, c->postbody);
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 */
case 207: /* Multi status (WevDAV) */
case 206: /* Partial Content */
#ifdef NOT_DEFINED
if(ofile == nil && r->start != 0)
sysfatal("page changed underfoot");
#endif
break;
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;
}
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)
{
int n;
char buf[16];
HttpState *hs;
hs = c->aux;
if(hs->ischunked){
if(r->ifcall.offset >= hs->length){
if(hs->length != 0){
/*
* all chunk size lines, except the first,
* are preceeded by a blank line. we skip it.
*/
readline(&hs->b, buf, sizeof(buf));
}
if(readline(&hs->b, buf, sizeof(buf)) < 1){
fprint(2, "missing next chunk length\n");
return -1;
}
hs->length += strtoul(buf, nil, 16);
}
}
if(hs->length){
if(r->ifcall.offset >= hs->length)
return -1;
if(r->ifcall.count + r->ifcall.offset > hs->length)
r->ifcall.count = hs->length - r->ifcall.offset;
}
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;
}
|