diff options
author | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
---|---|---|
committer | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
commit | e5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch) | |
tree | d8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/cmd/webcookies.c |
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/webcookies.c')
-rwxr-xr-x | sys/src/cmd/webcookies.c | 1266 |
1 files changed, 1266 insertions, 0 deletions
diff --git a/sys/src/cmd/webcookies.c b/sys/src/cmd/webcookies.c new file mode 100755 index 000000000..1ae358cff --- /dev/null +++ b/sys/src/cmd/webcookies.c @@ -0,0 +1,1266 @@ +/* + * Cookie file system. Allows hget and multiple webfs's to collaborate. + * Conventionally mounted on /mnt/webcookies. + */ + +#include <u.h> +#include <libc.h> +#include <bio.h> +#include <ndb.h> +#include <fcall.h> +#include <thread.h> +#include <9p.h> +#include <ctype.h> + +int debug = 0; + +typedef struct Cookie Cookie; +typedef struct Jar Jar; + +struct Cookie +{ + /* external info */ + char* name; + char* value; + char* dom; /* starts with . */ + char* path; + char* version; + char* comment; /* optional, may be nil */ + + uint expire; /* time of expiration: ~0 means when webcookies dies */ + int secure; + int explicitdom; /* dom was explicitly set */ + int explicitpath; /* path was explicitly set */ + int netscapestyle; + + /* internal info */ + int deleted; + int mark; + int ondisk; +}; + +struct Jar +{ + Cookie *c; + int nc; + int mc; + + Qid qid; + int dirty; + char *file; + char *lockfile; +}; + +struct { + char *s; + int offset; + int ishttp; +} stab[] = { + "domain", offsetof(Cookie, dom), 1, + "path", offsetof(Cookie, path), 1, + "name", offsetof(Cookie, name), 0, + "value", offsetof(Cookie, value), 0, + "comment", offsetof(Cookie, comment), 1, + "version", offsetof(Cookie, version), 1, +}; + +struct { + char *s; + int offset; +} itab[] = { + "expire", offsetof(Cookie, expire), + "secure", offsetof(Cookie, secure), + "explicitdomain", offsetof(Cookie, explicitdom), + "explicitpath", offsetof(Cookie, explicitpath), + "netscapestyle", offsetof(Cookie, netscapestyle), +}; + +#pragma varargck type "J" Jar* +#pragma varargck type "K" Cookie* + +/* HTTP format */ +int +jarfmt(Fmt *fmt) +{ + int i; + Jar *jar; + + jar = va_arg(fmt->args, Jar*); + if(jar == nil || jar->nc == 0) + return fmtstrcpy(fmt, ""); + + fmtprint(fmt, "Cookie: "); + if(jar->c[0].version) + fmtprint(fmt, "$Version=%s; ", jar->c[0].version); + for(i=0; i<jar->nc; i++) + fmtprint(fmt, "%s%s=%s", i ? "; ":"", jar->c[i].name, jar->c[i].value); + fmtprint(fmt, "\r\n"); + return 0; +} + +/* individual cookie */ +int +cookiefmt(Fmt *fmt) +{ + int j, k, first; + char *t; + Cookie *c; + + c = va_arg(fmt->args, Cookie*); + + first = 1; + for(j=0; j<nelem(stab); j++){ + t = *(char**)((char*)c+stab[j].offset); + if(t == nil) + continue; + if(first) + first = 0; + else + fmtprint(fmt, " "); + fmtprint(fmt, "%s=%q", stab[j].s, t); + } + for(j=0; j<nelem(itab); j++){ + k = *(int*)((char*)c+itab[j].offset); + if(k == 0) + continue; + if(first) + first = 0; + else + fmtprint(fmt, " "); + fmtprint(fmt, "%s=%ud", itab[j].s, k); + } + return 0; +} + +/* + * sort cookies: + * - alpha by name + * - alpha by domain + * - longer paths first, then alpha by path (RFC2109 4.3.4) + */ +int +cookiecmp(Cookie *a, Cookie *b) +{ + int i; + + if((i = strcmp(a->name, b->name)) != 0) + return i; + if((i = cistrcmp(a->dom, b->dom)) != 0) + return i; + if((i = strlen(b->path) - strlen(a->path)) != 0) + return i; + if((i = strcmp(a->path, b->path)) != 0) + return i; + return 0; +} + +int +exactcookiecmp(Cookie *a, Cookie *b) +{ + int i; + + if((i = cookiecmp(a, b)) != 0) + return i; + if((i = strcmp(a->value, b->value)) != 0) + return i; + if(a->version || b->version){ + if(!a->version) + return -1; + if(!b->version) + return 1; + if((i = strcmp(a->version, b->version)) != 0) + return i; + } + if(a->comment || b->comment){ + if(!a->comment) + return -1; + if(!b->comment) + return 1; + if((i = strcmp(a->comment, b->comment)) != 0) + return i; + } + if((i = b->expire - a->expire) != 0) + return i; + if((i = b->secure - a->secure) != 0) + return i; + if((i = b->explicitdom - a->explicitdom) != 0) + return i; + if((i = b->explicitpath - a->explicitpath) != 0) + return i; + if((i = b->netscapestyle - a->netscapestyle) != 0) + return i; + + return 0; +} + +void +freecookie(Cookie *c) +{ + int i; + + for(i=0; i<nelem(stab); i++) + free(*(char**)((char*)c+stab[i].offset)); +} + +void +copycookie(Cookie *c) +{ + int i; + char **ps; + + for(i=0; i<nelem(stab); i++){ + ps = (char**)((char*)c+stab[i].offset); + if(*ps) + *ps = estrdup9p(*ps); + } +} + +void +delcookie(Jar *j, Cookie *c) +{ + int i; + + j->dirty = 1; + i = c - j->c; + if(i < 0 || i >= j->nc) + abort(); + c->deleted = 1; +} + +void +addcookie(Jar *j, Cookie *c) +{ + int i; + + if(!c->name || !c->value || !c->path || !c->dom){ + fprint(2, "not adding incomplete cookie\n"); + return; + } + + if(debug) + fprint(2, "add %K\n", c); + + for(i=0; i<j->nc; i++) + if(cookiecmp(&j->c[i], c) == 0){ + if(debug) + fprint(2, "cookie %K matches %K\n", &j->c[i], c); + if(exactcookiecmp(&j->c[i], c) == 0){ + if(debug) + fprint(2, "\texactly\n"); + j->c[i].mark = 0; + return; + } + delcookie(j, &j->c[i]); + } + + j->dirty = 1; + if(j->nc == j->mc){ + j->mc += 16; + j->c = erealloc9p(j->c, j->mc*sizeof(Cookie)); + } + j->c[j->nc] = *c; + copycookie(&j->c[j->nc]); + j->nc++; +} + +void +purgejar(Jar *j) +{ + int i; + + for(i=j->nc-1; i>=0; i--){ + if(!j->c[i].deleted) + continue; + freecookie(&j->c[i]); + --j->nc; + j->c[i] = j->c[j->nc]; + } +} + +void +addtojar(Jar *jar, char *line, int ondisk) +{ + Cookie c; + int i, j, nf, *pint; + char *f[20], *attr, *val, **pstr; + + memset(&c, 0, sizeof c); + c.expire = ~0; + c.ondisk = ondisk; + nf = tokenize(line, f, nelem(f)); + for(i=0; i<nf; i++){ + attr = f[i]; + if((val = strchr(attr, '=')) != nil) + *val++ = '\0'; + else + val = ""; + /* string attributes */ + for(j=0; j<nelem(stab); j++){ + if(strcmp(stab[j].s, attr) == 0){ + pstr = (char**)((char*)&c+stab[j].offset); + *pstr = val; + } + } + /* integer attributes */ + for(j=0; j<nelem(itab); j++){ + if(strcmp(itab[j].s, attr) == 0){ + pint = (int*)((char*)&c+itab[j].offset); + if(val[0]=='\0') + *pint = 1; + else + *pint = strtoul(val, 0, 0); + } + } + } + if(c.name==nil || c.value==nil || c.dom==nil || c.path==nil){ + if(debug) + fprint(2, "ignoring fractional cookie %K\n", &c); + return; + } + addcookie(jar, &c); +} + +Jar* +newjar(void) +{ + Jar *jar; + + jar = emalloc9p(sizeof(Jar)); + return jar; +} + +int +expirejar(Jar *jar, int exiting) +{ + int i, n; + uint now; + + now = time(0); + n = 0; + for(i=0; i<jar->nc; i++){ + if(jar->c[i].expire < now || (exiting && jar->c[i].expire==~0)){ + delcookie(jar, &jar->c[i]); + n++; + } + } + return n; +} + +int +syncjar(Jar *jar) +{ + int i, fd; + char *line; + Dir *d; + Biobuf *b; + Qid q; + + if(jar->file==nil) + return 0; + + memset(&q, 0, sizeof q); + if((d = dirstat(jar->file)) != nil){ + q = d->qid; + if(d->qid.path != jar->qid.path || d->qid.vers != jar->qid.vers) + jar->dirty = 1; + free(d); + } + + if(jar->dirty == 0) + return 0; + + fd = -1; + for(i=0; i<50; i++){ + if((fd = create(jar->lockfile, OWRITE, DMEXCL|0666)) < 0){ + sleep(100); + continue; + } + break; + } + if(fd < 0){ + if(debug) + fprint(2, "open %s: %r", jar->lockfile); + werrstr("cannot acquire jar lock: %r"); + return -1; + } + + for(i=0; i<jar->nc; i++) /* mark is cleared by addcookie */ + jar->c[i].mark = jar->c[i].ondisk; + + if((b = Bopen(jar->file, OREAD)) == nil){ + if(debug) + fprint(2, "Bopen %s: %r", jar->file); + werrstr("cannot read cookie file %s: %r", jar->file); + close(fd); + return -1; + } + for(; (line = Brdstr(b, '\n', 1)) != nil; free(line)){ + if(*line == '#') + continue; + addtojar(jar, line, 1); + } + Bterm(b); + + for(i=0; i<jar->nc; i++) + if(jar->c[i].mark) + delcookie(jar, &jar->c[i]); + + purgejar(jar); + + b = Bopen(jar->file, OWRITE); + if(b == nil){ + if(debug) + fprint(2, "Bopen write %s: %r", jar->file); + close(fd); + return -1; + } + Bprint(b, "# webcookies cookie jar\n"); + Bprint(b, "# comments and non-standard fields will be lost\n"); + for(i=0; i<jar->nc; i++){ + if(jar->c[i].expire == ~0) + continue; + Bprint(b, "%K\n", &jar->c[i]); + jar->c[i].ondisk = 1; + } + Bterm(b); + + jar->dirty = 0; + close(fd); + if((d = dirstat(jar->file)) != nil){ + jar->qid = d->qid; + free(d); + } + return 0; +} + +Jar* +readjar(char *file) +{ + char *lock, *p; + Jar *jar; + + jar = newjar(); + lock = emalloc9p(strlen(file)+10); + strcpy(lock, file); + if((p = strrchr(lock, '/')) != nil) + p++; + else + p = lock; + memmove(p+2, p, strlen(p)+1); + p[0] = 'L'; + p[1] = '.'; + jar->lockfile = lock; + jar->file = file; + jar->dirty = 1; + + if(syncjar(jar) < 0){ + free(jar->file); + free(jar->lockfile); + free(jar); + return nil; + } + return jar; +} + +void +closejar(Jar *jar) +{ + int i; + + expirejar(jar, 0); + if(syncjar(jar) < 0) + fprint(2, "warning: cannot rewrite cookie jar: %r\n"); + + for(i=0; i<jar->nc; i++) + freecookie(&jar->c[i]); + + free(jar->file); + free(jar); +} + +/* + * Domain name matching is per RFC2109, section 2: + * + * Hosts names can be specified either as an IP address or a FQHN + * string. Sometimes we compare one host name with another. Host A's + * name domain-matches host B's if + * + * * both host names are IP addresses and their host name strings match + * exactly; or + * + * * both host names are FQDN strings and their host name strings match + * exactly; or + * + * * A is a FQDN string and has the form NB, where N is a non-empty name + * string, B has the form .B', and B' is a FQDN string. (So, x.y.com + * domain-matches .y.com but not y.com.) + * + * Note that domain-match is not a commutative operation: a.b.c.com + * domain-matches .c.com, but not the reverse. + * + * (This does not verify that IP addresses and FQDN's are well-formed.) + */ +int +isdomainmatch(char *name, char *pattern) +{ + int lname, lpattern; + + if(cistrcmp(name, pattern)==0) + return 1; + + if(strcmp(ipattr(name), "dom")==0 && pattern[0]=='.'){ + lname = strlen(name); + lpattern = strlen(pattern); + if(lname >= lpattern && cistrcmp(name+lname-lpattern, pattern)==0) + return 1; + } + + return 0; +} + +/* + * RFC2109 4.3.4: + * - domain must match + * - path in cookie must be a prefix of request path + * - cookie must not have expired + */ +int +iscookiematch(Cookie *c, char *dom, char *path, uint now) +{ + return isdomainmatch(dom, c->dom) + && strncmp(c->path, path, strlen(c->path))==0 + && c->expire >= now; +} + +/* + * Produce a subjar of matching cookies. + * Secure cookies are only included if secure is set. + */ +Jar* +cookiesearch(Jar *jar, char *dom, char *path, int issecure) +{ + int i; + Jar *j; + uint now; + + now = time(0); + j = newjar(); + for(i=0; i<jar->nc; i++) + if((issecure || !jar->c[i].secure) && iscookiematch(&jar->c[i], dom, path, now)) + addcookie(j, &jar->c[i]); + if(j->nc == 0){ + closejar(j); + werrstr("no cookies found"); + return nil; + } + qsort(j->c, j->nc, sizeof(j->c[0]), (int(*)(const void*, const void*))cookiecmp); + return j; +} + +/* + * RFC2109 4.3.2 security checks + */ +char* +isbadcookie(Cookie *c, char *dom, char *path) +{ + if(strncmp(c->path, path, strlen(c->path)) != 0) + return "cookie path is not a prefix of the request path"; + + if(c->explicitdom && c->dom[0] != '.') + return "cookie domain doesn't start with dot"; + + if(memchr(c->dom+1, '.', strlen(c->dom)-1-1) == nil) + return "cookie domain doesn't have embedded dots"; + + if(!isdomainmatch(dom, c->dom)) + return "request host does not match cookie domain"; + + if(strcmp(ipattr(dom), "dom")==0 + && memchr(dom, '.', strlen(dom)-strlen(c->dom)) != nil) + return "request host contains dots before cookie domain"; + + return 0; +} + +/* + * Sunday, 25-Jan-2002 12:24:36 GMT + * Sunday, 25 Jan 2002 12:24:36 GMT + * Sun, 25 Jan 02 12:24:36 GMT + */ +int +isleap(int year) +{ + return year%4==0 && (year%100!=0 || year%400==0); +} + +uint +strtotime(char *s) +{ + char *os; + int i; + Tm tm; + + static int mday[2][12] = { + 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, + 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, + }; + static char *wday[] = { + "Sunday", "Monday", "Tuesday", "Wednesday", + "Thursday", "Friday", "Saturday", + }; + static char *mon[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", + }; + + os = s; + /* Sunday, */ + for(i=0; i<nelem(wday); i++){ + if(cistrncmp(s, wday[i], strlen(wday[i])) == 0){ + s += strlen(wday[i]); + break; + } + if(cistrncmp(s, wday[i], 3) == 0){ + s += 3; + break; + } + } + if(i==nelem(wday)){ + if(debug) + fprint(2, "bad wday (%s)\n", os); + return -1; + } + if(*s++ != ',' || *s++ != ' '){ + if(debug) + fprint(2, "bad wday separator (%s)\n", os); + return -1; + } + + /* 25- */ + if(!isdigit(s[0]) || !isdigit(s[1]) || (s[2]!='-' && s[2]!=' ')){ + if(debug) + fprint(2, "bad day of month (%s)\n", os); + return -1; + } + tm.mday = strtol(s, 0, 10); + s += 3; + + /* Jan- */ + for(i=0; i<nelem(mon); i++) + if(cistrncmp(s, mon[i], 3) == 0){ + tm.mon = i; + s += 3; + break; + } + if(i==nelem(mon)){ + if(debug) + fprint(2, "bad month (%s)\n", os); + return -1; + } + if(s[0] != '-' && s[0] != ' '){ + if(debug) + fprint(2, "bad month separator (%s)\n", os); + return -1; + } + s++; + + /* 2002 */ + if(!isdigit(s[0]) || !isdigit(s[1])){ + if(debug) + fprint(2, "bad year (%s)\n", os); + return -1; + } + tm.year = strtol(s, 0, 10); + s += 2; + if(isdigit(s[0]) && isdigit(s[1])) + s += 2; + else{ + if(tm.year <= 68) + tm.year += 2000; + else + tm.year += 1900; + } + if(tm.mday==0 || tm.mday > mday[isleap(tm.year)][tm.mon]){ + if(debug) + fprint(2, "invalid day of month (%s)\n", os); + return -1; + } + tm.year -= 1900; + if(*s++ != ' '){ + if(debug) + fprint(2, "bad year separator (%s)\n", os); + return -1; + } + + if(!isdigit(s[0]) || !isdigit(s[1]) || s[2]!=':' + || !isdigit(s[3]) || !isdigit(s[4]) || s[5]!=':' + || !isdigit(s[6]) || !isdigit(s[7]) || s[8]!=' '){ + if(debug) + fprint(2, "bad time (%s)\n", os); + return -1; + } + + tm.hour = atoi(s); + tm.min = atoi(s+3); + tm.sec = atoi(s+6); + if(tm.hour >= 24 || tm.min >= 60 || tm.sec >= 60){ + if(debug) + fprint(2, "invalid time (%s)\n", os); + return -1; + } + s += 9; + + if(cistrcmp(s, "GMT") != 0){ + if(debug) + fprint(2, "time zone not GMT (%s)\n", os); + return -1; + } + strcpy(tm.zone, "GMT"); + tm.yday = 0; + return tm2sec(&tm); +} + +/* + * skip linear whitespace. we're a bit more lenient than RFC2616 2.2. + */ +char* +skipspace(char *s) +{ + while(*s=='\r' || *s=='\n' || *s==' ' || *s=='\t') + s++; + return s; +} + +/* + * Try to identify old netscape headers. + * The old headers: + * - didn't allow spaces around the '=' + * - used an 'Expires' attribute + * - had no 'Version' attribute + * - had no quotes + * - allowed whitespace in values + * - apparently separated attr/value pairs with ';' exclusively + */ +int +isnetscape(char *hdr) +{ + char *s; + + for(s=hdr; (s=strchr(s, '=')) != nil; s++){ + if(isspace(s[1]) || (s > hdr && isspace(s[-1]))) + return 0; + if(s[1]=='"') + return 0; + } + if(cistrstr(hdr, "version=")) + return 0; + return 1; +} + +/* + * Parse HTTP response headers, adding cookies to jar. + * Overwrites the headers. May overwrite path. + */ +char* parsecookie(Cookie*, char*, char**, int, char*, char*); +int +parsehttp(Jar *jar, char *hdr, char *dom, char *path) +{ + static char setcookie[] = "Set-Cookie:"; + char *e, *p, *nextp; + Cookie c; + int isns, n; + + isns = isnetscape(hdr); + n = 0; + for(p=hdr; p; p=nextp){ + p = skipspace(p); + if(*p == '\0') + break; + nextp = strchr(p, '\n'); + if(nextp != nil) + *nextp++ = '\0'; + if(debug) + fprint(2, "?%s\n", p); + if(cistrncmp(p, setcookie, strlen(setcookie)) != 0) + continue; + if(debug) + fprint(2, "%s\n", p); + p = skipspace(p+strlen(setcookie)); + for(; *p; p=skipspace(p)){ + if((e = parsecookie(&c, p, &p, isns, dom, path)) != nil){ + if(debug) + fprint(2, "parse cookie: %s\n", e); + break; + } + if((e = isbadcookie(&c, dom, path)) != nil){ + if(debug) + fprint(2, "reject cookie; %s\n", e); + continue; + } + addcookie(jar, &c); + n++; + } + } + return n; +} + +static char* +skipquoted(char *s) +{ + /* + * Sec 2.2 of RFC2616 defines a "quoted-string" as: + * + * quoted-string = ( <"> *(qdtext | quoted-pair ) <"> ) + * qdtext = <any TEXT except <">> + * quoted-pair = "\" CHAR + * + * TEXT is any octet except CTLs, but including LWS; + * LWS is [CR LF] 1*(SP | HT); + * CHARs are ASCII octets 0-127; (NOTE: we reject 0's) + * CTLs are octets 0-31 and 127; + */ + if(*s != '"') + return s; + + for(s++; 32 <= *s && *s < 127 && *s != '"'; s++) + if(*s == '\\' && *(s+1) != '\0') + s++; + return s; +} + +static char* +skiptoken(char *s) +{ + /* + * Sec 2.2 of RFC2616 defines a "token" as + * 1*<any CHAR except CTLs or separators>; + * CHARs are ASCII octets 0-127; + * CTLs are octets 0-31 and 127; + * separators are "()<>@,;:\/[]?={}", double-quote, SP (32), and HT (9) + */ + while(32 <= *s && *s < 127 && strchr("()<>@,;:[]?={}\" \t\\", *s)==nil) + s++; + + return s; +} + +static char* +skipvalue(char *s, int isns) +{ + char *t; + + /* + * An RFC2109 value is an HTTP token or an HTTP quoted string. + * Netscape servers ignore the spec and rely on semicolons, apparently. + */ + if(isns){ + if((t = strchr(s, ';')) == nil) + t = s+strlen(s); + return t; + } + if(*s == '"') + return skipquoted(s); + return skiptoken(s); +} + +/* + * RMID=80b186bb64c03c65fab767f8; expires=Monday, 10-Feb-2003 04:44:39 GMT; + * path=/; domain=.nytimes.com + */ +char* +parsecookie(Cookie *c, char *p, char **e, int isns, char *dom, char *path) +{ + int i, done; + char *t, *u, *attr, *val; + + memset(c, 0, sizeof *c); + c->expire = ~0; + + /* NAME=VALUE */ + t = skiptoken(p); + c->name = p; + p = skipspace(t); + if(*p != '='){ + Badname: + return "malformed cookie: no NAME=VALUE"; + } + *t = '\0'; + p = skipspace(p+1); + t = skipvalue(p, isns); + if(*t) + *t++ = '\0'; + c->value = p; + p = skipspace(t); + if(c->name[0]=='\0' || c->value[0]=='\0') + goto Badname; + + done = 0; + for(; *p && !done; p=skipspace(p)){ + attr = p; + t = skiptoken(p); + u = skipspace(t); + switch(*u){ + case '\0': + *t = '\0'; + p = val = u; + break; + case ';': + *t = '\0'; + val = ""; + p = u+1; + break; + case '=': + *t = '\0'; + val = skipspace(u+1); + p = skipvalue(val, isns); + if(*p==',') + done = 1; + if(*p) + *p++ = '\0'; + break; + case ',': + if(!isns){ + val = ""; + p = u; + *p++ = '\0'; + done = 1; + break; + } + default: + if(debug) + fprint(2, "syntax: %s\n", p); + return "syntax error"; + } + for(i=0; i<nelem(stab); i++) + if(stab[i].ishttp && cistrcmp(stab[i].s, attr)==0) + *(char**)((char*)c+stab[i].offset) = val; + if(cistrcmp(attr, "expires") == 0){ + if(!isns) + return "non-netscape cookie has Expires tag"; + if(!val[0]) + return "bad expires tag"; + c->expire = strtotime(val); + if(c->expire == ~0) + return "cannot parse netscape expires tag"; + } + if(cistrcmp(attr, "max-age") == 0) + c->expire = time(0)+atoi(val); + if(cistrcmp(attr, "secure") == 0) + c->secure = 1; + } + + if(c->dom) + c->explicitdom = 1; + else + c->dom = dom; + if(c->path) + c->explicitpath = 1; + else{ + c->path = path; + if((t = strchr(c->path, '?')) != 0) + *t = '\0'; + if((t = strrchr(c->path, '/')) != 0) + *t = '\0'; + } + c->netscapestyle = isns; + *e = p; + + return nil; +} + +Jar *jar; + +enum +{ + Xhttp = 1, + Xcookies, + + NeedUrl = 0, + HaveUrl, +}; + +typedef struct Aux Aux; +struct Aux +{ + int state; + char *dom; + char *path; + char *inhttp; + char *outhttp; + char *ctext; + int rdoff; +}; +enum +{ + AuxBuf = 4096, + MaxCtext = 16*1024*1024, +}; + +void +fsopen(Req *r) +{ + char *s, *es; + int i, sz; + Aux *a; + + switch((uintptr)r->fid->file->aux){ + case Xhttp: + syncjar(jar); + a = emalloc9p(sizeof(Aux)); + r->fid->aux = a; + a->inhttp = emalloc9p(AuxBuf); + a->outhttp = emalloc9p(AuxBuf); + break; + + case Xcookies: + syncjar(jar); + a = emalloc9p(sizeof(Aux)); + r->fid->aux = a; + if(r->ifcall.mode&OTRUNC){ + a->ctext = emalloc9p(1); + a->ctext[0] = '\0'; + }else{ + sz = 256*jar->nc+1024; /* BUG should do better */ + a->ctext = emalloc9p(sz); + a->ctext[0] = '\0'; + s = a->ctext; + es = s+sz; + for(i=0; i<jar->nc; i++) + s = seprint(s, es, "%K\n", &jar->c[i]); + } + break; + } + respond(r, nil); +} + +void +fsread(Req *r) +{ + Aux *a; + + a = r->fid->aux; + switch((uintptr)r->fid->file->aux){ + case Xhttp: + if(a->state == NeedUrl){ + respond(r, "must write url before read"); + return; + } + r->ifcall.offset = a->rdoff; + readstr(r, a->outhttp); + a->rdoff += r->ofcall.count; + respond(r, nil); + return; + + case Xcookies: + readstr(r, a->ctext); + respond(r, nil); + return; + + default: + respond(r, "bug in webcookies"); + return; + } +} + +void +fswrite(Req *r) +{ + Aux *a; + int i, sz, hlen, issecure; + char buf[1024], *p; + Jar *j; + + a = r->fid->aux; + switch((uintptr)r->fid->file->aux){ + case Xhttp: + if(a->state == NeedUrl){ + if(r->ifcall.count >= sizeof buf){ + respond(r, "url too long"); + return; + } + memmove(buf, r->ifcall.data, r->ifcall.count); + buf[r->ifcall.count] = '\0'; + issecure = 0; + if(cistrncmp(buf, "http://", 7) == 0) + hlen = 7; + else if(cistrncmp(buf, "https://", 8) == 0){ + hlen = 8; + issecure = 1; + }else{ + respond(r, "url must begin http:// or https://"); + return; + } + if(buf[hlen]=='/'){ + respond(r, "url without host name"); + return; + } + p = strchr(buf+hlen, '/'); + if(p == nil) + a->path = estrdup9p("/"); + else{ + a->path = estrdup9p(p); + *p = '\0'; + } + a->dom = estrdup9p(buf+hlen); + a->state = HaveUrl; + j = cookiesearch(jar, a->dom, a->path, issecure); + if(debug){ + fprint(2, "search %s %s got %p\n", a->dom, a->path, j); + if(j){ + fprint(2, "%d cookies\n", j->nc); + for(i=0; i<j->nc; i++) + fprint(2, "%K\n", &j->c[i]); + } + } + snprint(a->outhttp, AuxBuf, "%J", j); + if(j) + closejar(j); + }else{ + if(strlen(a->inhttp)+r->ifcall.count >= AuxBuf){ + respond(r, "http headers too large"); + return; + } + memmove(a->inhttp+strlen(a->inhttp), r->ifcall.data, r->ifcall.count); + } + r->ofcall.count = r->ifcall.count; + respond(r, nil); + return; + + case Xcookies: + sz = r->ifcall.count+r->ifcall.offset; + if(sz > strlen(a->ctext)){ + if(sz >= MaxCtext){ + respond(r, "cookie file too large"); + return; + } + a->ctext = erealloc9p(a->ctext, sz+1); + a->ctext[sz] = '\0'; + } + memmove(a->ctext+r->ifcall.offset, r->ifcall.data, r->ifcall.count); + r->ofcall.count = r->ifcall.count; + respond(r, nil); + return; + + default: + respond(r, "bug in webcookies"); + return; + } +} + +void +fsdestroyfid(Fid *fid) +{ + char *p, *nextp; + Aux *a; + int i; + + a = fid->aux; + if(a == nil) + return; + switch((uintptr)fid->file->aux){ + case Xhttp: + parsehttp(jar, a->inhttp, a->dom, a->path); + break; + case Xcookies: + for(i=0; i<jar->nc; i++) + jar->c[i].mark = 1; + for(p=a->ctext; *p; p=nextp){ + if((nextp = strchr(p, '\n')) != nil) + *nextp++ = '\0'; + else + nextp = ""; + addtojar(jar, p, 0); + } + for(i=0; i<jar->nc; i++) + if(jar->c[i].mark) + delcookie(jar, &jar->c[i]); + break; + } + syncjar(jar); + free(a->dom); + free(a->path); + free(a->inhttp); + free(a->outhttp); + free(a->ctext); + free(a); +} + +void +fsend(Srv*) +{ + closejar(jar); +} + +Srv fs = +{ +.open= fsopen, +.read= fsread, +.write= fswrite, +.destroyfid= fsdestroyfid, +.end= fsend, +}; + +void +usage(void) +{ + fprint(2, "usage: webcookies [-f file] [-m mtpt] [-s service]\n"); + exits("usage"); +} + +void +main(int argc, char **argv) +{ + char *file, *mtpt, *home, *srv; + + file = nil; + srv = nil; + mtpt = "/mnt/webcookies"; + ARGBEGIN{ + case 'D': + chatty9p++; + break; + case 'd': + debug = 1; + break; + case 'f': + file = EARGF(usage()); + break; + case 's': + srv = EARGF(usage()); + break; + case 'm': + mtpt = EARGF(usage()); + break; + default: + usage(); + }ARGEND + + if(argc != 0) + usage(); + + quotefmtinstall(); + fmtinstall('J', jarfmt); + fmtinstall('K', cookiefmt); + + if(file == nil){ + home = getenv("home"); + if(home == nil) + sysfatal("no cookie file specified and no $home"); + file = emalloc9p(strlen(home)+30); + strcpy(file, home); + strcat(file, "/lib/webcookies"); + } + if(access(file, AEXIST) < 0) + close(create(file, OWRITE, 0666)); + + jar = readjar(file); + if(jar == nil) + sysfatal("readjar: %r"); + + fs.tree = alloctree("cookie", "cookie", DMDIR|0555, nil); + closefile(createfile(fs.tree->root, "http", "cookie", 0666, (void*)Xhttp)); + closefile(createfile(fs.tree->root, "cookies", "cookie", 0666, (void*)Xcookies)); + + postmountsrv(&fs, srv, mtpt, MREPL); + exits(nil); +} |