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/webfs/cookies.c |
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/webfs/cookies.c')
-rwxr-xr-x | sys/src/cmd/webfs/cookies.c | 1173 |
1 files changed, 1173 insertions, 0 deletions
diff --git a/sys/src/cmd/webfs/cookies.c b/sys/src/cmd/webfs/cookies.c new file mode 100755 index 000000000..6028bde92 --- /dev/null +++ b/sys/src/cmd/webfs/cookies.c @@ -0,0 +1,1173 @@ +#include <u.h> +#include <libc.h> +#include <bio.h> +#include <ndb.h> +#include <fcall.h> +#include <thread.h> +#include <9p.h> +#include <ctype.h> +#include "dat.h" +#include "fns.h" + +int cookiedebug; + +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 */ +static int +jarfmt(Fmt *fp) +{ + int i; + Jar *jar; + + jar = va_arg(fp->args, Jar*); + + if(jar == nil || jar->nc == 0) + return 0; + + fmtstrcpy(fp, "Cookie: "); + if(jar->c[0].version) + fmtprint(fp, "$Version=%s; ", jar->c[0].version); + for(i=0; i<jar->nc; i++) + fmtprint(fp, "%s%s=%s", i ? "; ": "", jar->c[i].name, jar->c[i].value); + fmtstrcpy(fp, "\r\n"); + return 0; +} + +/* individual cookie */ +static int +cookiefmt(Fmt *fp) +{ + int j, k, first; + char *t; + Cookie *c; + + c = va_arg(fp->args, Cookie*); + + first = 1; + for(j=0; j<nelem(stab); j++){ + t = *(char**)((uintptr)c+stab[j].offset); + if(t == nil) + continue; + if(first) + first = 0; + else + fmtstrcpy(fp, " "); + fmtprint(fp, "%s=%q", stab[j].s, t); + } + for(j=0; j<nelem(itab); j++){ + k = *(int*)((uintptr)c+itab[j].offset); + if(k == 0) + continue; + if(first) + first = 0; + else + fmtstrcpy(fp, " "); + fmtprint(fp, "%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) + */ +static 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; +} + +static 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; +} + +static void +freecookie(Cookie *c) +{ + int i; + + for(i=0; i<nelem(stab); i++) + free(*(char**)((uintptr)c+stab[i].offset)); +} + +static void +copycookie(Cookie *c) +{ + int i; + char **ps; + + for(i=0; i<nelem(stab); i++){ + ps = (char**)((uintptr)c+stab[i].offset); + if(*ps) + *ps = estrdup9p(*ps); + } +} + +static 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; +} + +static 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(cookiedebug) + fprint(2, "add %K\n", c); + + for(i=0; i<j->nc; i++) + if(cookiecmp(&j->c[i], c) == 0){ + if(cookiedebug) + fprint(2, "cookie %K matches %K\n", &j->c[i], c); + if(exactcookiecmp(&j->c[i], c) == 0){ + if(cookiedebug) + 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++; +} + +static 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]; + } +} + +static 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**)((uintptr)&c+stab[j].offset); + *pstr = val; + } + } + /* integer attributes */ + for(j=0; j<nelem(itab); j++){ + if(strcmp(itab[j].s, attr) == 0){ + pint = (int*)((uintptr)&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(cookiedebug) + fprint(2, "ignoring fractional cookie %K\n", &c); + return; + } + addcookie(jar, &c); +} + +static Jar* +newjar(void) +{ + Jar *jar; + + jar = emalloc9p(sizeof(Jar)); + return jar; +} + +static 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; +} + +static void +dumpjar(Jar *jar, char *desc) +{ + int i; + Biobuf *b; + char *s; + + print("%s\n", desc); + print("\tin memory:\n"); + + for(i=0; i<jar->nc; i++) + print("\t%K%s%s%s\n", &jar->c[i], + jar->c[i].ondisk ? " ondisk" : "", + jar->c[i].deleted ? " deleted" : "", + jar->c[i].mark ? " mark" : ""); + print("\n\ton disk:\n"); + if((b = Bopen(jar->file, OREAD)) == nil){ + print("\tno file\n"); + }else{ + while((s = Brdstr(b, '\n', 1)) != nil){ + print("\t%s\n", s); + free(s); + } + Bterm(b); + } + print("\n"); +} + +static 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(cookiedebug) + 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(cookiedebug) + 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 && jar->c[i].expire != ~0) + delcookie(jar, &jar->c[i]); + + purgejar(jar); + + b = Bopen(jar->file, OWRITE); + if(b == nil){ + if(cookiedebug) + 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; +} + +static 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; +} + +static void +closejar(Jar *jar) +{ + int i; + + if(jar == nil) + return; + 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.) + */ +static 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); + /* e.g., name: www.google.com && pattern: .google.com */ + if(lname >= lpattern && cistrcmp(name+lname-lpattern, pattern)==0) + return 1; + /* e.g., name: google.com && pattern: .google.com */ + if(lpattern > lname && + cistrcmp(pattern+lpattern-lname, name) == 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 + */ +static 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 == 0 || c->expire >= now); +} + +/* + * Produce a subjar of matching cookies. + * Secure cookies are only included if secure is set. + */ +static Jar* +cookiesearch(Jar *jar, char *dom, char *path, int issecure) +{ + int i; + Jar *j; + uint now; + + if(cookiedebug) + fprint(2, "cookiesearch %s %s %d\n", dom, path, issecure); + now = time(0); + j = newjar(); + for(i=0; i<jar->nc; i++){ + if(cookiedebug) + fprint(2, "\ttry %s %s %d %s\n", jar->c[i].dom, + jar->c[i].path, jar->c[i].secure, + jar->c[i].name); + if((issecure || !jar->c[i].secure) && + iscookiematch(&jar->c[i], dom, path, now)){ + if(cookiedebug) + fprint(2, "\tmatched\n"); + 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(*)(void*, void*))cookiecmp); + return j; +} + +/* + * RFC2109 4.3.2 security checks + */ +static char* +isbadcookie(Cookie *c, char *dom, char *path) +{ + int lcdom, ldom; + + if(strncmp(c->path, path, strlen(c->path)) != 0) + return "cookie path is not a prefix of the request path"; + + /* + * fgb says omitting this test is necessary to get some sites to work, + * but it seems dubious. + */ + if(c->explicitdom && c->dom[0] != '.') + return "cookie domain doesn't start with dot"; + + lcdom = strlen(c->dom); + if(memchr(c->dom+1, '.', lcdom-1-1) == nil) + return "cookie domain doesn't have embedded dots"; + + if(!isdomainmatch(dom, c->dom)) + return "request host does not match cookie domain"; + + ldom = strlen(dom); + if(strcmp(ipattr(dom), "dom")==0 && lcdom > ldom && + memchr(dom, '.', lcdom - ldom) != 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 + */ +static int +isleap(int year) +{ + return year%4==0 && (year%100!=0 || year%400==0); +} + +static 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(cookiedebug) + fprint(2, "bad wday (%s)\n", os); + return -1; + } + if(*s++ != ',' || *s++ != ' '){ + if(cookiedebug) + fprint(2, "bad wday separator (%s)\n", os); + return -1; + } + + /* 25- */ + if(!isdigit(s[0]) || !isdigit(s[1]) || (s[2]!='-' && s[2]!=' ')){ + if(cookiedebug) + 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(cookiedebug) + fprint(2, "bad month (%s)\n", os); + return -1; + } + if(s[0] != '-' && s[0] != ' '){ + if(cookiedebug) + fprint(2, "bad month separator (%s)\n", os); + return -1; + } + s++; + + /* 2002 */ + if(!isdigit(s[0]) || !isdigit(s[1])){ + if(cookiedebug) + 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(cookiedebug) + fprint(2, "invalid day of month (%s)\n", os); + return -1; + } + tm.year -= 1900; + if(*s++ != ' '){ + if(cookiedebug) + 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(cookiedebug) + 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(cookiedebug) + fprint(2, "invalid time (%s)\n", os); + return -1; + } + s += 9; + + if(cistrcmp(s, "GMT") != 0){ + if(cookiedebug) + 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. + */ +static 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 + */ +static 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. + */ +static char* parsecookie(Cookie*, char*, char**, int, char*, char*); +static 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(cistrncmp(p, setcookie, strlen(setcookie)) != 0) + continue; + if(cookiedebug) + 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(cookiedebug) + fprint(2, "parse cookie: %s\n", e); + break; + } + if((e = isbadcookie(&c, dom, path)) != nil){ + if(cookiedebug) + 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 + */ +static char* +parsecookie(Cookie *c, char *p, char **e, int isns, char *dom, char *path) +{ + int i, done; + char *t, *u, *attr, *val; + + c->expire = ~0; + memset(c, 0, sizeof *c); + + /* 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'; + val = p = 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(cookiedebug) + 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**)((uintptr)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; + +typedef struct Aux Aux; +struct Aux +{ + char *dom; + char *path; + char *inhttp; + char *outhttp; + char *ctext; + int rdoff; +}; +enum +{ + AuxBuf = 4096, + MaxCtext = 16*1024*1024, +}; + +void +cookieopen(Req *r) +{ + char *s, *es; + int i, sz; + Aux *a; + + 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]); + } + respond(r, nil); +} + +void +cookieread(Req *r) +{ + Aux *a; + + a = r->fid->aux; + readstr(r, a->ctext); + respond(r, nil); +} + +void +cookiewrite(Req *r) +{ + Aux *a; + int sz; + + a = r->fid->aux; + 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); +} + +void +cookieclunk(Fid *fid) +{ + char *p, *nextp; + Aux *a; + int i; + + a = fid->aux; + if(a == nil) + return; + 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]); + syncjar(jar); + free(a->dom); + free(a->path); + free(a->inhttp); + free(a->outhttp); + free(a->ctext); + free(a); +} + +void +closecookies(void) +{ + closejar(jar); +} + +void +initcookies(char *file) +{ + char *home; + + 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"); + } + jar = readjar(file); + if(jar == nil) + sysfatal("readjar: %r"); +} + +void +httpsetcookie(char *hdr, char *dom, char *path) +{ + if(path == nil) + path = "/"; + + parsehttp(jar, hdr, dom, path); + syncjar(jar); +} + +char* +httpcookies(char *dom, char *path, int issecure) +{ + char buf[1024]; + Jar *j; + + syncjar(jar); + j = cookiesearch(jar, dom, path, issecure); + snprint(buf, sizeof buf, "%J", j); + closejar(j); + return estrdup(buf); +} |