diff options
Diffstat (limited to 'sys/src/cmd/mothra/http.c')
| -rw-r--r-- | sys/src/cmd/mothra/http.c | 486 |
1 files changed, 486 insertions, 0 deletions
diff --git a/sys/src/cmd/mothra/http.c b/sys/src/cmd/mothra/http.c new file mode 100644 index 000000000..deb36f391 --- /dev/null +++ b/sys/src/cmd/mothra/http.c @@ -0,0 +1,486 @@ +#include <u.h> +#include <libc.h> +#include <draw.h> +#include <event.h> +#include <panel.h> + +#include <libsec.h> /* tlsClient */ + +#include "mothra.h" +typedef struct Cache Cache; +struct Cache{ + int fd; /* file descriptor on which to write cached data */ + ulong hash; /* hash of url, used to compute cache file name */ + int modtime; /* time at which cache entry was created */ + int type; /* url->type of cached entry */ +}; +void httpheader(Url *, char *); +int httpresponse(char *); +static char *proxyserver; /* name of proxy server */ +void exitnow(void*, char*){ + noted(NDFLT); +} +void hashname(char *name, int n, char *stem, Cache *c){ + snprint(name, n, "/sys/lib/mothra/cache/%s.%.8lux", stem, c->hash); +} +// #define CacheEnabled +/* + * Returns fd of cached file, if found (else -1) + * Fills in Cache data structure for caller + * If stale is set, caller has determined that the existing + * cache entry for this url is stale, so we shouldn't bother re-examining it. + */ +int cacheopen(Url *url, Cache *c, int stale){ +#ifdef CacheEnabled + int fd, n; + char name[NNAME+1], *s, *l; + /* + * If we're using a proxy server or the url contains a ? or =, + * don't even bother. + */ + if(proxyserver || strchr(url->reltext, '?')!=0 || strchr(url->reltext, '=')!=0){ + c->fd=-1; + return -1; + } + c->hash=0; + for(s=url->fullname,n=0;*s;s++,n++) c->hash=c->hash*n+(*s&255); + if(stale) + fd=-1; + else{ + hashname(name, sizeof(name), "cache", c); + fd=open(name, OREAD); + } + if(fd==-1){ + hashname(name, sizeof(name), "write", c); + c->fd=create(name, OWRITE, 0444); + if(c->fd!=-1) + fprint(c->fd, "%s %10ld\n", url->fullname, time(0)); + return -1; + } + c->fd=-1; + for(l=name;l!=&name[NNAME];l+=n){ + n=&name[NNAME]-l; + n=read(fd, l, n); + if(n<=0) break; + } + *l='\0'; + s=strchr(name, ' '); + if(s==0){ + close(fd); + return -1; + } + *s='\0'; + if(strcmp(url->fullname, name)!=0){ + close(fd); + return -1; + } + c->modtime=atol(++s); + s=strchr(s, '\n'); + if(s==0){ + close(fd); + return -1; + } + s++; + if(strncmp(s, "type ", 5)!=0){ + close(fd); + return -1; + } + c->type=atoi(s+5); + s=strchr(s+5, '\n'); + if(s==0){ + close(fd); + return -1; + } + + seek(fd, s-name+1, 0); + return fd; +#else + c->fd=-1; + return -1; +#endif +} +/* + * Close url->fd and either rename the cache file or + * remove it, depending on success + */ +void cacheclose(Cache *c, int success){ + char wname[NNAME+1], cname[NNAME+1], *celem; + Dir *wdir; + if(c->fd==-1) return; + close(c->fd); + hashname(wname, sizeof(wname), "write", c); + if(!success){ + remove(wname); + return; + } + if((wdir = dirstat(wname)) == 0) + return; + hashname(cname, sizeof(cname), "cache", c); + if(access(cname, 0) == 0){ + if(remove(cname)==-1){ + remove(wname); + free(wdir); + return; + } + /* + * This looks implausible, but it's what the mv command does + */ + do; while(remove(cname)!=-1); + } + celem=strrchr(cname, '/'); + if(celem==0) celem=cname; + else celem++; + strcpy(wdir->name, celem); + if(dirwstat(wname, wdir)==-1) + remove(wname); + free(wdir); +} +static char *wkday[]={ + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" +}; +static char *month[]={ + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" +}; +/* + * Sun, 06 Nov 1994 08:49:38 GMT + * 123456789 123456789 123456789 + */ +char *rfc1123date(long time){ + static char buf[50]; + Tm *t; + t=gmtime(time); + snprint(buf, sizeof(buf), "%s, %2.2d %s %4.4d %2.2d:%2.2d:%2.2d GMT", + wkday[t->wday], t->mday, month[t->mon], t->year+1900, + t->hour, t->min, t->sec); + return buf; +} +/* + * Given a url, return a file descriptor on which caller can + * read an http document. As a side effect, we parse the + * http header and fill in some fields in the url. + * The caller is responsible for processing redirection loops. + * Method can be either GET or POST. If method==post, body + * is the text to be posted. + */ +int http(Url *url, int method, char *body){ + char *addr, *com; + int fd, n, nnl, len; + int ncom, m; + int pfd[2]; + char buf[1024], *bp, *ebp; + char line[1024+1], *lp, *elp; + char authstr[NAUTH], *urlname; + int gotresponse; + int response; + Cache cache; + int cfd, cookiefd; + static int firsttime=1; + static int gotcookies; + + if(firsttime){ + proxyserver=getenv("httpproxy"); + gotcookies=(access("/mnt/webcookies/http", AREAD|AWRITE)==0); + firsttime=0; + } + *authstr = 0; +Authorize: + cfd=-1; + cookiefd=-1; + if(proxyserver && proxyserver[0]!='\0'){ + addr=strdup(proxyserver); + urlname=url->fullname; + } + else{ + addr=emalloc(strlen(url->ipaddr)+100); + sprint(addr, "tcp!%s!%d", url->ipaddr, url->port); + urlname=url->reltext; + } + fd=dial(addr, 0, 0, 0); + free(addr); + if(fd==-1) goto ErrReturn; + if(url->ssl){ + int tfd; + TLSconn conn; + + memset(&conn, 0, sizeof conn); + tfd = tlsClient(fd, &conn); + if(tfd < 0){ + close(fd); + goto ErrReturn; + } + /* BUG: check cert here? */ + if(conn.cert) + free(conn.cert); + close(fd); + fd = tfd; + } + ncom=strlen(urlname)+sizeof(buf); + com=emalloc(ncom+2); + cache.fd=-1; + switch(method){ + case GET: + cfd=cacheopen(url, &cache, 0); + if(cfd==-1) + n=sprint(com, + "GET %s HTTP/1.0\r\n%s" + "Accept: */*\r\n" + "User-agent: mothra/%s\r\n" + "Host: %s\r\n", + urlname, authstr, version, url->ipaddr); + else + n=sprint(com, + "GET %s HTTP/1.0\r\n%s" + "If-Modified-since: %s\r\n" + "Accept: */*\r\n" + "User-agent: mothra/%s\r\n" + "Host: %s\r\n", + urlname, authstr, rfc1123date(cache.modtime), version, url->ipaddr); + break; + case POST: + len=strlen(body); + n=sprint(com, + "POST %s HTTP/1.0\r\n%s" + "Content-type: application/x-www-form-urlencoded\r\n" + "Content-length: %d\r\n" + "User-agent: mothra/%s\r\n", + urlname, authstr, len, version); + break; + } + if(gotcookies && (cookiefd=open("/mnt/webcookies/http", ORDWR)) >= 0){ + if(fprint(cookiefd, "%s", url->fullname) > 0){ + while((m=read(cookiefd, buf, sizeof buf)) > 0){ + if(m+n>ncom){ + if(write(fd, com, n)!= n){ + free(com); + goto fdErrReturn; + } + n=0; + com[0] = '\0'; + } + strncat(com, buf, m); + n += m; + } + }else{ + close(cookiefd); + cookiefd=-1; + } + } + strcat(com, "\r\n"); + n += 2; + switch(method){ + case GET: + if(write(fd, com, n)!=n){ + free(com); + goto fdErrReturn; + } + break; + case POST: + if(write(fd, com, n)!=n + || write(fd, body, len)!=len){ + free(com); + goto fdErrReturn; + } + break; + } + free(com); + if(pipe(pfd)==-1) goto fdErrReturn; + n=read(fd, buf, 1024); + if(n<=0){ + EarlyEof: + if(n==0){ + fprint(2, "%s: EOF in header\n", url->fullname); + werrstr("EOF in header"); + } + pfdErrReturn: + close(pfd[0]); + close(pfd[1]); + fdErrReturn: + close(fd); + ErrReturn: + if(cookiefd>=0) + close(cookiefd); + cacheclose(&cache, 0); + return -1; + } + bp=buf; + ebp=buf+n; + url->type=0; + if(strncmp(buf, "HTTP/", 5)==0){ /* hack test for presence of header */ + SET(response); + gotresponse=0; + url->redirname[0]='\0'; + nnl=0; + lp=line; + elp=line+1024; + while(nnl!=2){ + if(bp==ebp){ + n=read(fd, buf, 1024); + if(n<=0) goto EarlyEof; + ebp=buf+n; + bp=buf; + } + if(*bp!='\r'){ + if(nnl==1 && (!gotresponse || (*bp!=' ' && *bp!='\t'))){ + *lp='\0'; + if(gotresponse){ + if(cookiefd>=0 && cistrncmp(line, "Set-Cookie:", 11) == 0) + fprint(cookiefd, "%s\n", line); + httpheader(url, line); + }else{ + response=httpresponse(line); + gotresponse=1; + } + lp=line; + } + if(*bp=='\n') nnl++; + else{ + nnl=0; + if(lp!=elp) *lp++=*bp; + } + } + bp++; + } + if(gotresponse) switch(response){ + case 200: /* OK */ + case 201: /* Created */ + case 202: /* Accepted */ + break; + case 204: /* No Content */ + werrstr("URL has no content"); + goto pfdErrReturn; + case 301: /* Moved Permanently */ + case 302: /* Moved Temporarily */ + if(url->redirname[0]){ + url->type=FORWARD; + werrstr("URL forwarded"); + goto pfdErrReturn; + } + break; + case 304: /* Not Modified */ + if(cfd!=-1){ + url->type=cache.type; + close(pfd[0]); + close(pfd[1]); + close(fd); + if(cookiefd>=0) + close(cookiefd); + return cfd; + } + werrstr("Not modified!"); + goto pfdErrReturn; + case 400: /* Bad Request */ + werrstr("Bad Request to server"); + goto pfdErrReturn; + case 401: /* Unauthorized */ + case 402: /* ??? */ + if(*authstr == 0){ + close(pfd[0]); + close(pfd[1]); + close(fd); + if(auth(url, authstr, sizeof(authstr)) == 0){ + if(cfd!=-1) + close(cfd); + goto Authorize; + } + goto ErrReturn; + } + break; + case 403: /* Forbidden */ + werrstr("Forbidden by server"); + goto pfdErrReturn; + case 404: /* Not Found */ + werrstr("Not found on server"); + goto pfdErrReturn; + case 500: /* Internal server error */ + werrstr("Server choked"); + goto pfdErrReturn; + case 501: /* Not implemented */ + werrstr("Server can't do it!"); + goto pfdErrReturn; + case 502: /* Bad gateway */ + werrstr("Bad gateway"); + goto pfdErrReturn; + case 503: /* Service unavailable */ + werrstr("Service unavailable"); + goto pfdErrReturn; + } + } + if(cfd!=-1){ + close(cfd); + cfd=cacheopen(url, &cache, 1); + } + if(cookiefd>=0){ + close(cookiefd); + cookiefd=-1; + } + if(url->type==0) + url->type=suffix2type(url->fullname); + if(cache.fd!=-1) fprint(cache.fd, "type %d\n", url->type); + switch(rfork(RFFDG|RFPROC|RFNOWAIT)){ + case -1: + werrstr("Can't fork"); + goto pfdErrReturn; + case 0: + notify(exitnow); /* otherwise write on closed pipe below may cause havoc */ + close(pfd[0]); + if(bp!=ebp){ + write(pfd[1], bp, ebp-bp); + if(cache.fd!=-1) write(cache.fd, bp, ebp-bp); + } + while((n=read(fd, buf, 1024))>0){ + write(pfd[1], buf, n); + if(cache.fd!=-1) write(cache.fd, buf, n); + } + cacheclose(&cache, 1); + _exits(0); + default: + if(cache.fd!=-1) close(cache.fd); + close(pfd[1]); + close(fd); + return pfd[0]; + } +} +/* + * Process a header line for this url + */ +void httpheader(Url *url, char *line){ + char *name, *arg, *s, *arg2; + name=line; + while(*name==' ' || *name=='\t') name++; + for(s=name;*s!=':';s++) if(*s=='\0') return; + *s++='\0'; + while(*s==' ' || *s=='\t') s++; + arg=s; + while(*s!=' ' && *s!='\t' && *s!=';' && *s!='\0') s++; + while(*s == ' ' || *s == '\t' || *s == ';') + *s++ = '\0'; + arg2 = s; + if(cistrcmp(name, "Content-Type")==0){ + url->type|=content2type(arg, url->reltext); + if(cistrncmp(arg2, "charset=", 8) == 0){ + strncpy(url->charset, arg2+8, sizeof(url->charset)); + } else { + url->charset[0] = '\0'; + } + } + else if(cistrcmp(name, "Content-Encoding")==0) + url->type|=encoding2type(arg); + else if(cistrcmp(name, "WWW-authenticate")==0){ + strncpy(url->authtype, arg, sizeof(url->authtype)); + strncpy(url->autharg, arg2, sizeof(url->autharg)); + } + else if(cistrcmp(name, "URI")==0){ + if(*arg!='<') return; + ++arg; + for(s=arg;*s!='>';s++) if(*s=='\0') return; + *s='\0'; + strncpy(url->redirname, arg, sizeof(url->redirname)); + } + else if(cistrcmp(name, "Location")==0) + strncpy(url->redirname, arg, sizeof(url->redirname)); +} +int httpresponse(char *line){ + while(*line!=' ' && *line!='\t' && *line!='\0') line++; + return atoi(line); +} |
