1 /* 2 * Cookie file system. Allows hget and multiple webfs's to collaborate. 3 * Conventionally mounted on /mnt/webcookies. 4 */ 5 6 #include <u.h> 7 #include <libc.h> 8 #include <bio.h> 9 #include <ndb.h> 10 #include <fcall.h> 11 #include <thread.h> 12 #include <9p.h> 13 #include <ctype.h> 14 15 int debug = 0; 16 17 typedef struct Cookie Cookie; 18 typedef struct Jar Jar; 19 20 struct Cookie 21 { 22 /* external info */ 23 char* name; 24 char* value; 25 char* dom; /* starts with . */ 26 char* path; 27 char* version; 28 char* comment; /* optional, may be nil */ 29 30 uint expire; /* time of expiration: ~0 means when webcookies dies */ 31 int secure; 32 int explicitdom; /* dom was explicitly set */ 33 int explicitpath; /* path was explicitly set */ 34 int netscapestyle; 35 36 /* internal info */ 37 int deleted; 38 int mark; 39 int ondisk; 40 }; 41 42 struct Jar 43 { 44 Cookie *c; 45 int nc; 46 int mc; 47 48 Qid qid; 49 int dirty; 50 char *file; 51 char *lockfile; 52 }; 53 54 struct { 55 char *s; 56 int offset; 57 int ishttp; 58 } stab[] = { 59 "domain", offsetof(Cookie, dom), 1, 60 "path", offsetof(Cookie, path), 1, 61 "name", offsetof(Cookie, name), 0, 62 "value", offsetof(Cookie, value), 0, 63 "comment", offsetof(Cookie, comment), 1, 64 "version", offsetof(Cookie, version), 1, 65 }; 66 67 struct { 68 char *s; 69 int offset; 70 } itab[] = { 71 "expire", offsetof(Cookie, expire), 72 "secure", offsetof(Cookie, secure), 73 "explicitdomain", offsetof(Cookie, explicitdom), 74 "explicitpath", offsetof(Cookie, explicitpath), 75 "netscapestyle", offsetof(Cookie, netscapestyle), 76 }; 77 78 #pragma varargck type "J" Jar* 79 #pragma varargck type "K" Cookie* 80 81 /* HTTP format */ 82 int 83 jarfmt(Fmt *fmt) 84 { 85 int i; 86 Jar *jar; 87 88 jar = va_arg(fmt->args, Jar*); 89 if(jar == nil || jar->nc == 0) 90 return fmtstrcpy(fmt, ""); 91 92 fmtprint(fmt, "Cookie: "); 93 if(jar->c[0].version) 94 fmtprint(fmt, "$Version=%s; ", jar->c[0].version); 95 for(i=0; i<jar->nc; i++) 96 fmtprint(fmt, "%s%s=%s", i ? "; ":"", jar->c[i].name, jar->c[i].value); 97 fmtprint(fmt, "\r\n"); 98 return 0; 99 } 100 101 /* individual cookie */ 102 int 103 cookiefmt(Fmt *fmt) 104 { 105 int j, k, first; 106 char *t; 107 Cookie *c; 108 109 c = va_arg(fmt->args, Cookie*); 110 111 first = 1; 112 for(j=0; j<nelem(stab); j++){ 113 t = *(char**)((ulong)c+stab[j].offset); 114 if(t == nil) 115 continue; 116 if(first) 117 first = 0; 118 else 119 fmtprint(fmt, " "); 120 fmtprint(fmt, "%s=%q", stab[j].s, t); 121 } 122 for(j=0; j<nelem(itab); j++){ 123 k = *(int*)((ulong)c+itab[j].offset); 124 if(k == 0) 125 continue; 126 if(first) 127 first = 0; 128 else 129 fmtprint(fmt, " "); 130 fmtprint(fmt, "%s=%ud", itab[j].s, k); 131 } 132 return 0; 133 } 134 135 /* 136 * sort cookies: 137 * - alpha by name 138 * - alpha by domain 139 * - longer paths first, then alpha by path (RFC2109 4.3.4) 140 */ 141 int 142 cookiecmp(Cookie *a, Cookie *b) 143 { 144 int i; 145 146 if((i = strcmp(a->name, b->name)) != 0) 147 return i; 148 if((i = cistrcmp(a->dom, b->dom)) != 0) 149 return i; 150 if((i = strlen(b->path) - strlen(a->path)) != 0) 151 return i; 152 if((i = strcmp(a->path, b->path)) != 0) 153 return i; 154 return 0; 155 } 156 157 int 158 exactcookiecmp(Cookie *a, Cookie *b) 159 { 160 int i; 161 162 if((i = cookiecmp(a, b)) != 0) 163 return i; 164 if((i = strcmp(a->value, b->value)) != 0) 165 return i; 166 if(a->version || b->version){ 167 if(!a->version) 168 return -1; 169 if(!b->version) 170 return 1; 171 if((i = strcmp(a->version, b->version)) != 0) 172 return i; 173 } 174 if(a->comment || b->comment){ 175 if(!a->comment) 176 return -1; 177 if(!b->comment) 178 return 1; 179 if((i = strcmp(a->comment, b->comment)) != 0) 180 return i; 181 } 182 if((i = b->expire - a->expire) != 0) 183 return i; 184 if((i = b->secure - a->secure) != 0) 185 return i; 186 if((i = b->explicitdom - a->explicitdom) != 0) 187 return i; 188 if((i = b->explicitpath - a->explicitpath) != 0) 189 return i; 190 if((i = b->netscapestyle - a->netscapestyle) != 0) 191 return i; 192 193 return 0; 194 } 195 196 void 197 freecookie(Cookie *c) 198 { 199 int i; 200 201 for(i=0; i<nelem(stab); i++) 202 free(*(char**)((ulong)c+stab[i].offset)); 203 } 204 205 void 206 copycookie(Cookie *c) 207 { 208 int i; 209 char **ps; 210 211 for(i=0; i<nelem(stab); i++){ 212 ps = (char**)((ulong)c+stab[i].offset); 213 if(*ps) 214 *ps = estrdup9p(*ps); 215 } 216 } 217 218 void 219 delcookie(Jar *j, Cookie *c) 220 { 221 int i; 222 223 j->dirty = 1; 224 i = c - j->c; 225 if(i < 0 || i >= j->nc) 226 abort(); 227 c->deleted = 1; 228 } 229 230 void 231 addcookie(Jar *j, Cookie *c) 232 { 233 int i; 234 235 if(!c->name || !c->value || !c->path || !c->dom){ 236 fprint(2, "not adding incomplete cookie\n"); 237 return; 238 } 239 240 if(debug) 241 fprint(2, "add %K\n", c); 242 243 for(i=0; i<j->nc; i++) 244 if(cookiecmp(&j->c[i], c) == 0){ 245 if(debug) 246 fprint(2, "cookie %K matches %K\n", &j->c[i], c); 247 if(exactcookiecmp(&j->c[i], c) == 0){ 248 if(debug) 249 fprint(2, "\texactly\n"); 250 j->c[i].mark = 0; 251 return; 252 } 253 delcookie(j, &j->c[i]); 254 } 255 256 j->dirty = 1; 257 if(j->nc == j->mc){ 258 j->mc += 16; 259 j->c = erealloc9p(j->c, j->mc*sizeof(Cookie)); 260 } 261 j->c[j->nc] = *c; 262 copycookie(&j->c[j->nc]); 263 j->nc++; 264 } 265 266 void 267 purgejar(Jar *j) 268 { 269 int i; 270 271 for(i=j->nc-1; i>=0; i--){ 272 if(!j->c[i].deleted) 273 continue; 274 freecookie(&j->c[i]); 275 --j->nc; 276 j->c[i] = j->c[j->nc]; 277 } 278 } 279 280 void 281 addtojar(Jar *jar, char *line, int ondisk) 282 { 283 Cookie c; 284 int i, j, nf, *pint; 285 char *f[20], *attr, *val, **pstr; 286 287 memset(&c, 0, sizeof c); 288 c.expire = ~0; 289 c.ondisk = ondisk; 290 nf = tokenize(line, f, nelem(f)); 291 for(i=0; i<nf; i++){ 292 attr = f[i]; 293 if((val = strchr(attr, '=')) != nil) 294 *val++ = '\0'; 295 else 296 val = ""; 297 /* string attributes */ 298 for(j=0; j<nelem(stab); j++){ 299 if(strcmp(stab[j].s, attr) == 0){ 300 pstr = (char**)((ulong)&c+stab[j].offset); 301 *pstr = val; 302 } 303 } 304 /* integer attributes */ 305 for(j=0; j<nelem(itab); j++){ 306 if(strcmp(itab[j].s, attr) == 0){ 307 pint = (int*)((ulong)&c+itab[j].offset); 308 if(val[0]=='\0') 309 *pint = 1; 310 else 311 *pint = strtoul(val, 0, 0); 312 } 313 } 314 } 315 if(c.name==nil || c.value==nil || c.dom==nil || c.path==nil){ 316 if(debug) 317 fprint(2, "ignoring fractional cookie %K\n", &c); 318 return; 319 } 320 addcookie(jar, &c); 321 } 322 323 Jar* 324 newjar(void) 325 { 326 Jar *jar; 327 328 jar = emalloc9p(sizeof(Jar)); 329 return jar; 330 } 331 332 int 333 expirejar(Jar *jar, int exiting) 334 { 335 int i, n; 336 uint now; 337 338 now = time(0); 339 n = 0; 340 for(i=0; i<jar->nc; i++){ 341 if(jar->c[i].expire < now || (exiting && jar->c[i].expire==~0)){ 342 delcookie(jar, &jar->c[i]); 343 n++; 344 } 345 } 346 return n; 347 } 348 349 int 350 syncjar(Jar *jar) 351 { 352 int i, fd; 353 char *line; 354 Dir *d; 355 Biobuf *b; 356 Qid q; 357 358 if(jar->file==nil) 359 return 0; 360 361 memset(&q, 0, sizeof q); 362 if((d = dirstat(jar->file)) != nil){ 363 q = d->qid; 364 if(d->qid.path != jar->qid.path || d->qid.vers != jar->qid.vers) 365 jar->dirty = 1; 366 free(d); 367 } 368 369 if(jar->dirty == 0) 370 return 0; 371 372 fd = -1; 373 for(i=0; i<50; i++){ 374 if((fd = create(jar->lockfile, OWRITE, DMEXCL|0666)) < 0){ 375 sleep(100); 376 continue; 377 } 378 break; 379 } 380 if(fd < 0){ 381 if(debug) 382 fprint(2, "open %s: %r", jar->lockfile); 383 werrstr("cannot acquire jar lock: %r"); 384 return -1; 385 } 386 387 for(i=0; i<jar->nc; i++) /* mark is cleared by addcookie */ 388 jar->c[i].mark = jar->c[i].ondisk; 389 390 if((b = Bopen(jar->file, OREAD)) == nil){ 391 if(debug) 392 fprint(2, "Bopen %s: %r", jar->file); 393 werrstr("cannot read cookie file %s: %r", jar->file); 394 close(fd); 395 return -1; 396 } 397 for(; (line = Brdstr(b, '\n', 1)) != nil; free(line)){ 398 if(*line == '#') 399 continue; 400 addtojar(jar, line, 1); 401 } 402 Bterm(b); 403 404 for(i=0; i<jar->nc; i++) 405 if(jar->c[i].mark) 406 delcookie(jar, &jar->c[i]); 407 408 purgejar(jar); 409 410 b = Bopen(jar->file, OWRITE); 411 if(b == nil){ 412 if(debug) 413 fprint(2, "Bopen write %s: %r", jar->file); 414 close(fd); 415 return -1; 416 } 417 Bprint(b, "# webcookies cookie jar\n"); 418 Bprint(b, "# comments and non-standard fields will be lost\n"); 419 for(i=0; i<jar->nc; i++){ 420 if(jar->c[i].expire == ~0) 421 continue; 422 Bprint(b, "%K\n", &jar->c[i]); 423 jar->c[i].ondisk = 1; 424 } 425 Bterm(b); 426 427 jar->dirty = 0; 428 close(fd); 429 jar->qid = q; 430 return 0; 431 } 432 433 Jar* 434 readjar(char *file) 435 { 436 char *lock, *p; 437 Jar *jar; 438 439 jar = newjar(); 440 lock = emalloc9p(strlen(file)+10); 441 strcpy(lock, file); 442 if((p = strrchr(lock, '/')) != nil) 443 p++; 444 else 445 p = lock; 446 memmove(p+2, p, strlen(p)+1); 447 p[0] = 'L'; 448 p[1] = '.'; 449 jar->lockfile = lock; 450 jar->file = file; 451 jar->dirty = 1; 452 453 if(syncjar(jar) < 0){ 454 free(jar->file); 455 free(jar->lockfile); 456 free(jar); 457 return nil; 458 } 459 return jar; 460 } 461 462 void 463 closejar(Jar *jar) 464 { 465 int i; 466 467 expirejar(jar, 0); 468 if(syncjar(jar) < 0) 469 fprint(2, "warning: cannot rewrite cookie jar: %r\n"); 470 471 for(i=0; i<jar->nc; i++) 472 freecookie(&jar->c[i]); 473 474 free(jar->file); 475 free(jar); 476 } 477 478 /* 479 * Domain name matching is per RFC2109, section 2: 480 * 481 * Hosts names can be specified either as an IP address or a FQHN 482 * string. Sometimes we compare one host name with another. Host A's 483 * name domain-matches host B's if 484 * 485 * * both host names are IP addresses and their host name strings match 486 * exactly; or 487 * 488 * * both host names are FQDN strings and their host name strings match 489 * exactly; or 490 * 491 * * A is a FQDN string and has the form NB, where N is a non-empty name 492 * string, B has the form .B', and B' is a FQDN string. (So, x.y.com 493 * domain-matches .y.com but not y.com.) 494 * 495 * Note that domain-match is not a commutative operation: a.b.c.com 496 * domain-matches .c.com, but not the reverse. 497 * 498 * (This does not verify that IP addresses and FQDN's are well-formed.) 499 */ 500 int 501 isdomainmatch(char *name, char *pattern) 502 { 503 int lname, lpattern; 504 505 if(cistrcmp(name, pattern)==0) 506 return 1; 507 508 if(strcmp(ipattr(name), "dom")==0 && pattern[0]=='.'){ 509 lname = strlen(name); 510 lpattern = strlen(pattern); 511 if(lname >= lpattern && cistrcmp(name+lname-lpattern, pattern)==0) 512 return 1; 513 } 514 515 return 0; 516 } 517 518 /* 519 * RFC2109 4.3.4: 520 * - domain must match 521 * - path in cookie must be a prefix of request path 522 * - cookie must not have expired 523 */ 524 int 525 iscookiematch(Cookie *c, char *dom, char *path, uint now) 526 { 527 return isdomainmatch(dom, c->dom) 528 && strncmp(c->path, path, strlen(c->path))==0 529 && c->expire >= now; 530 } 531 532 /* 533 * Produce a subjar of matching cookies. 534 * Secure cookies are only included if secure is set. 535 */ 536 Jar* 537 cookiesearch(Jar *jar, char *dom, char *path, int issecure) 538 { 539 int i; 540 Jar *j; 541 uint now; 542 543 now = time(0); 544 j = newjar(); 545 for(i=0; i<jar->nc; i++) 546 if((issecure || !jar->c[i].secure) && iscookiematch(&jar->c[i], dom, path, now)) 547 addcookie(j, &jar->c[i]); 548 if(j->nc == 0){ 549 closejar(j); 550 werrstr("no cookies found"); 551 return nil; 552 } 553 qsort(j->c, j->nc, sizeof(j->c[0]), (int(*)(const void*, const void*))cookiecmp); 554 return j; 555 } 556 557 /* 558 * RFC2109 4.3.2 security checks 559 */ 560 char* 561 isbadcookie(Cookie *c, char *dom, char *path) 562 { 563 if(strncmp(c->path, path, strlen(c->path)) != 0) 564 return "cookie path is not a prefix of the request path"; 565 566 if(c->dom[0] != '.') 567 return "cookie domain doesn't start with dot"; 568 569 if(memchr(c->dom+1, '.', strlen(c->dom)-1-1) == nil) 570 return "cookie domain doesn't have embedded dots"; 571 572 if(!isdomainmatch(dom, c->dom)) 573 return "request host does not match cookie domain"; 574 575 if(strcmp(ipattr(dom), "dom")==0 576 && memchr(dom, '.', strlen(dom)-strlen(c->dom)) != nil) 577 return "request host contains dots before cookie domain"; 578 579 return 0; 580 } 581 582 /* 583 * Sunday, 25-Jan-2002 12:24:36 GMT 584 * Sunday, 25 Jan 2002 12:24:36 GMT 585 * Sun, 25 Jan 02 12:24:36 GMT 586 */ 587 int 588 isleap(int year) 589 { 590 return year%4==0 && (year%100!=0 || year%400==0); 591 } 592 593 uint 594 strtotime(char *s) 595 { 596 char *os; 597 int i; 598 Tm tm; 599 600 static int mday[2][12] = { 601 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 602 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 603 }; 604 static char *wday[] = { 605 "Sunday", "Monday", "Tuesday", "Wednesday", 606 "Thursday", "Friday", "Saturday", 607 }; 608 static char *mon[] = { 609 "Jan", "Feb", "Mar", "Apr", "May", "Jun", 610 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", 611 }; 612 613 os = s; 614 /* Sunday, */ 615 for(i=0; i<nelem(wday); i++){ 616 if(cistrncmp(s, wday[i], strlen(wday[i])) == 0){ 617 s += strlen(wday[i]); 618 break; 619 } 620 if(cistrncmp(s, wday[i], 3) == 0){ 621 s += 3; 622 break; 623 } 624 } 625 if(i==nelem(wday)){ 626 if(debug) 627 fprint(2, "bad wday (%s)\n", os); 628 return -1; 629 } 630 if(*s++ != ',' || *s++ != ' '){ 631 if(debug) 632 fprint(2, "bad wday separator (%s)\n", os); 633 return -1; 634 } 635 636 /* 25- */ 637 if(!isdigit(s[0]) || !isdigit(s[1]) || (s[2]!='-' && s[2]!=' ')){ 638 if(debug) 639 fprint(2, "bad day of month (%s)\n", os); 640 return -1; 641 } 642 tm.mday = strtol(s, 0, 10); 643 s += 3; 644 645 /* Jan- */ 646 for(i=0; i<nelem(mon); i++) 647 if(cistrncmp(s, mon[i], 3) == 0){ 648 tm.mon = i; 649 s += 3; 650 break; 651 } 652 if(i==nelem(mon)){ 653 if(debug) 654 fprint(2, "bad month (%s)\n", os); 655 return -1; 656 } 657 if(s[0] != '-' && s[0] != ' '){ 658 if(debug) 659 fprint(2, "bad month separator (%s)\n", os); 660 return -1; 661 } 662 s++; 663 664 /* 2002 */ 665 if(!isdigit(s[0]) || !isdigit(s[1])){ 666 if(debug) 667 fprint(2, "bad year (%s)\n", os); 668 return -1; 669 } 670 tm.year = strtol(s, 0, 10); 671 s += 2; 672 if(isdigit(s[0]) && isdigit(s[1])) 673 s += 2; 674 else{ 675 if(tm.year <= 68) 676 tm.year += 2000; 677 else 678 tm.year += 1900; 679 } 680 if(tm.mday==0 || tm.mday > mday[isleap(tm.year)][tm.mon]){ 681 if(debug) 682 fprint(2, "invalid day of month (%s)\n", os); 683 return -1; 684 } 685 tm.year -= 1900; 686 if(*s++ != ' '){ 687 if(debug) 688 fprint(2, "bad year separator (%s)\n", os); 689 return -1; 690 } 691 692 if(!isdigit(s[0]) || !isdigit(s[1]) || s[2]!=':' 693 || !isdigit(s[3]) || !isdigit(s[4]) || s[5]!=':' 694 || !isdigit(s[6]) || !isdigit(s[7]) || s[8]!=' '){ 695 if(debug) 696 fprint(2, "bad time (%s)\n", os); 697 return -1; 698 } 699 700 tm.hour = atoi(s); 701 tm.min = atoi(s+3); 702 tm.sec = atoi(s+6); 703 if(tm.hour >= 24 || tm.min >= 60 || tm.sec >= 60){ 704 if(debug) 705 fprint(2, "invalid time (%s)\n", os); 706 return -1; 707 } 708 s += 9; 709 710 if(cistrcmp(s, "GMT") != 0){ 711 if(debug) 712 fprint(2, "time zone not GMT (%s)\n", os); 713 return -1; 714 } 715 strcpy(tm.zone, "GMT"); 716 tm.yday = 0; 717 return tm2sec(&tm); 718 } 719 720 /* 721 * skip linear whitespace. we're a bit more lenient than RFC2616 2.2. 722 */ 723 char* 724 skipspace(char *s) 725 { 726 while(*s=='\r' || *s=='\n' || *s==' ' || *s=='\t') 727 s++; 728 return s; 729 } 730 731 /* 732 * Try to identify old netscape headers. 733 * The old headers: 734 * - didn't allow spaces around the '=' 735 * - used an 'Expires' attribute 736 * - had no 'Version' attribute 737 * - had no quotes 738 * - allowed whitespace in values 739 * - apparently separated attr/value pairs with ';' exclusively 740 */ 741 int 742 isnetscape(char *hdr) 743 { 744 char *s; 745 746 for(s=hdr; (s=strchr(s, '=')) != nil; s++){ 747 if(isspace(s[1]) || (s > hdr && isspace(s[-1]))) 748 return 0; 749 if(s[1]=='"') 750 return 0; 751 } 752 if(cistrstr(hdr, "version=")) 753 return 0; 754 return 1; 755 } 756 757 /* 758 * Parse HTTP response headers, adding cookies to jar. 759 * Overwrites the headers. 760 */ 761 char* parsecookie(Cookie*, char*, char**, int, char*, char*); 762 int 763 parsehttp(Jar *jar, char *hdr, char *dom, char *path) 764 { 765 static char setcookie[] = "Set-Cookie:"; 766 char *e, *p, *nextp; 767 Cookie c; 768 int isns, n; 769 770 isns = isnetscape(hdr); 771 n = 0; 772 for(p=hdr; p; p=nextp){ 773 p = skipspace(p); 774 if(*p == '\0') 775 break; 776 nextp = strchr(p, '\n'); 777 if(nextp != nil) 778 *nextp++ = '\0'; 779 if(debug) 780 fprint(2, "?%s\n", p); 781 if(cistrncmp(p, setcookie, strlen(setcookie)) != 0) 782 continue; 783 if(debug) 784 fprint(2, "%s\n", p); 785 p = skipspace(p+strlen(setcookie)); 786 for(; *p; p=skipspace(p)){ 787 if((e = parsecookie(&c, p, &p, isns, dom, path)) != nil){ 788 if(debug) 789 fprint(2, "parse cookie: %s\n", e); 790 break; 791 } 792 if((e = isbadcookie(&c, dom, path)) != nil){ 793 if(debug) 794 fprint(2, "reject cookie; %s\n", e); 795 continue; 796 } 797 addcookie(jar, &c); 798 n++; 799 } 800 } 801 return n; 802 } 803 804 static char* 805 skipquoted(char *s) 806 { 807 /* 808 * Sec 2.2 of RFC2616 defines a "quoted-string" as: 809 * 810 * quoted-string = ( <"> *(qdtext | quoted-pair ) <"> ) 811 * qdtext = <any TEXT except <">> 812 * quoted-pair = "\" CHAR 813 * 814 * TEXT is any octet except CTLs, but including LWS; 815 * LWS is [CR LF] 1*(SP | HT); 816 * CHARs are ASCII octets 0-127; (NOTE: we reject 0's) 817 * CTLs are octets 0-31 and 127; 818 */ 819 if(*s != '"') 820 return s; 821 822 for(s++; 32 <= *s && *s < 127 && *s != '"'; s++) 823 if(*s == '\\' && *(s+1) != '\0') 824 s++; 825 return s; 826 } 827 828 static char* 829 skiptoken(char *s) 830 { 831 /* 832 * Sec 2.2 of RFC2616 defines a "token" as 833 * 1*<any CHAR except CTLs or separators>; 834 * CHARs are ASCII octets 0-127; 835 * CTLs are octets 0-31 and 127; 836 * separators are "()<>@,;:\/[]?={}", double-quote, SP (32), and HT (9) 837 */ 838 while(32 <= *s && *s < 127 && strchr("()<>@,;:[]?={}\" \t\\", *s)==nil) 839 s++; 840 841 return s; 842 } 843 844 static char* 845 skipvalue(char *s, int isns) 846 { 847 char *t; 848 849 /* 850 * An RFC2109 value is an HTTP token or an HTTP quoted string. 851 * Netscape servers ignore the spec and rely on semicolons, apparently. 852 */ 853 if(isns){ 854 if((t = strchr(s, ';')) == nil) 855 t = s+strlen(s); 856 return t; 857 } 858 if(*s == '"') 859 return skipquoted(s); 860 return skiptoken(s); 861 } 862 863 /* 864 * RMID=80b186bb64c03c65fab767f8; expires=Monday, 10-Feb-2003 04:44:39 GMT; 865 * path=/; domain=.nytimes.com 866 */ 867 char* 868 parsecookie(Cookie *c, char *p, char **e, int isns, char *dom, char *path) 869 { 870 int i, done; 871 char *t, *u, *attr, *val; 872 873 memset(c, 0, sizeof *c); 874 875 /* NAME=VALUE */ 876 t = skiptoken(p); 877 c->name = p; 878 p = skipspace(t); 879 if(*p != '='){ 880 Badname: 881 return "malformed cookie: no NAME=VALUE"; 882 } 883 *t = '\0'; 884 p = skipspace(p+1); 885 t = skipvalue(p, isns); 886 if(*t) 887 *t++ = '\0'; 888 c->value = p; 889 p = skipspace(t); 890 if(c->name[0]=='\0' || c->value[0]=='\0') 891 goto Badname; 892 893 done = 0; 894 for(; *p && !done; p=skipspace(p)){ 895 attr = p; 896 t = skiptoken(p); 897 u = skipspace(t); 898 switch(*u){ 899 case ';': 900 *t = '\0'; 901 val = ""; 902 p = u+1; 903 break; 904 case '=': 905 *t = '\0'; 906 val = skipspace(u+1); 907 p = skipvalue(val, isns); 908 if(*p==',') 909 done = 1; 910 if(*p) 911 *p++ = '\0'; 912 break; 913 case ',': 914 if(!isns){ 915 val = ""; 916 p = u; 917 *p++ = '\0'; 918 done = 1; 919 break; 920 } 921 default: 922 if(debug) 923 fprint(2, "syntax: %s\n", p); 924 return "syntax error"; 925 } 926 for(i=0; i<nelem(stab); i++) 927 if(stab[i].ishttp && cistrcmp(stab[i].s, attr)==0) 928 *(char**)((ulong)c+stab[i].offset) = val; 929 if(cistrcmp(attr, "expires") == 0){ 930 if(!isns) 931 return "non-netscape cookie has Expires tag"; 932 if(!val[0]) 933 return "bad expires tag"; 934 c->expire = strtotime(val); 935 if(c->expire == ~0) 936 return "cannot parse netscape expires tag"; 937 } 938 if(cistrcmp(attr, "max-age") == 0) 939 c->expire = time(0)+atoi(val); 940 if(cistrcmp(attr, "secure") == 0) 941 c->secure = 1; 942 } 943 944 if(c->dom) 945 c->explicitdom = 1; 946 else 947 c->dom = dom; 948 if(c->path) 949 c->explicitpath = 1; 950 else 951 c->path = path; 952 c->netscapestyle = isns; 953 *e = p; 954 955 return nil; 956 } 957 958 Jar *jar; 959 960 enum 961 { 962 Xhttp = 1, 963 Xcookies, 964 965 NeedUrl = 0, 966 HaveUrl, 967 }; 968 969 typedef struct Aux Aux; 970 struct Aux 971 { 972 int state; 973 char *dom; 974 char *path; 975 char *inhttp; 976 char *outhttp; 977 char *ctext; 978 int rdoff; 979 }; 980 enum 981 { 982 AuxBuf = 4096, 983 MaxCtext = 16*1024*1024, 984 }; 985 986 void 987 fsopen(Req *r) 988 { 989 char *s, *es; 990 int i, sz; 991 Aux *a; 992 993 switch((int)r->fid->file->aux){ 994 case Xhttp: 995 syncjar(jar); 996 a = emalloc9p(sizeof(Aux)); 997 r->fid->aux = a; 998 a->inhttp = emalloc9p(AuxBuf); 999 a->outhttp = emalloc9p(AuxBuf); 1000 break; 1001 1002 case Xcookies: 1003 syncjar(jar); 1004 a = emalloc9p(sizeof(Aux)); 1005 r->fid->aux = a; 1006 if(r->ifcall.mode&OTRUNC){ 1007 a->ctext = emalloc9p(1); 1008 a->ctext[0] = '\0'; 1009 }else{ 1010 sz = 256*jar->nc+1024; /* BUG should do better */ 1011 a->ctext = emalloc9p(sz); 1012 a->ctext[0] = '\0'; 1013 s = a->ctext; 1014 es = s+sz; 1015 for(i=0; i<jar->nc; i++) 1016 s = seprint(s, es, "%K\n", &jar->c[i]); 1017 } 1018 break; 1019 } 1020 respond(r, nil); 1021 } 1022 1023 void 1024 fsread(Req *r) 1025 { 1026 Aux *a; 1027 1028 a = r->fid->aux; 1029 switch((int)r->fid->file->aux){ 1030 case Xhttp: 1031 if(a->state == NeedUrl){ 1032 respond(r, "must write url before read"); 1033 return; 1034 } 1035 r->ifcall.offset = a->rdoff; 1036 readstr(r, a->outhttp); 1037 a->rdoff += r->ofcall.count; 1038 respond(r, nil); 1039 return; 1040 1041 case Xcookies: 1042 readstr(r, a->ctext); 1043 respond(r, nil); 1044 return; 1045 1046 default: 1047 respond(r, "bug in webcookies"); 1048 return; 1049 } 1050 } 1051 1052 void 1053 fswrite(Req *r) 1054 { 1055 Aux *a; 1056 int i, sz, hlen, issecure; 1057 char buf[1024], *p; 1058 Jar *j; 1059 1060 a = r->fid->aux; 1061 switch((int)r->fid->file->aux){ 1062 case Xhttp: 1063 if(a->state == NeedUrl){ 1064 if(r->ifcall.count >= sizeof buf){ 1065 respond(r, "url too long"); 1066 return; 1067 } 1068 memmove(buf, r->ifcall.data, r->ifcall.count); 1069 buf[r->ifcall.count] = '\0'; 1070 issecure = 0; 1071 if(cistrncmp(buf, "http://", 7) == 0) 1072 hlen = 7; 1073 else if(cistrncmp(buf, "https://", 8) == 0){ 1074 hlen = 8; 1075 issecure = 1; 1076 }else{ 1077 respond(r, "url must begin http:// or https://"); 1078 return; 1079 } 1080 if(buf[hlen]=='/'){ 1081 respond(r, "url without host name"); 1082 return; 1083 } 1084 p = strchr(buf+hlen, '/'); 1085 if(p == nil) 1086 a->path = estrdup9p("/"); 1087 else{ 1088 a->path = estrdup9p(p); 1089 *p = '\0'; 1090 } 1091 a->dom = estrdup9p(buf+hlen); 1092 a->state = HaveUrl; 1093 j = cookiesearch(jar, a->dom, a->path, issecure); 1094 if(debug){ 1095 fprint(2, "search %s %s got %p\n", a->dom, a->path, j); 1096 if(j){ 1097 fprint(2, "%d cookies\n", j->nc); 1098 for(i=0; i<j->nc; i++) 1099 fprint(2, "%K\n", &j->c[i]); 1100 } 1101 } 1102 snprint(a->outhttp, AuxBuf, "%J", j); 1103 if(j) 1104 closejar(j); 1105 }else{ 1106 if(strlen(a->inhttp)+r->ifcall.count >= AuxBuf){ 1107 respond(r, "http headers too large"); 1108 return; 1109 } 1110 memmove(a->inhttp+strlen(a->inhttp), r->ifcall.data, r->ifcall.count); 1111 } 1112 r->ofcall.count = r->ifcall.count; 1113 respond(r, nil); 1114 return; 1115 1116 case Xcookies: 1117 sz = r->ifcall.count+r->ifcall.offset; 1118 if(sz > strlen(a->ctext)){ 1119 if(sz >= MaxCtext){ 1120 respond(r, "cookie file too large"); 1121 return; 1122 } 1123 a->ctext = erealloc9p(a->ctext, sz+1); 1124 a->ctext[sz] = '\0'; 1125 } 1126 memmove(a->ctext+r->ifcall.offset, r->ifcall.data, r->ifcall.count); 1127 r->ofcall.count = r->ifcall.count; 1128 respond(r, nil); 1129 return; 1130 1131 default: 1132 respond(r, "bug in webcookies"); 1133 return; 1134 } 1135 } 1136 1137 void 1138 fsdestroyfid(Fid *fid) 1139 { 1140 char *p, *nextp; 1141 Aux *a; 1142 int i; 1143 1144 a = fid->aux; 1145 if(a == nil) 1146 return; 1147 switch((int)fid->file->aux){ 1148 case Xhttp: 1149 parsehttp(jar, a->inhttp, a->dom, a->path); 1150 break; 1151 case Xcookies: 1152 for(i=0; i<jar->nc; i++) 1153 jar->c[i].mark = 1; 1154 for(p=a->ctext; *p; p=nextp){ 1155 if((nextp = strchr(p, '\n')) != nil) 1156 *nextp++ = '\0'; 1157 else 1158 nextp = ""; 1159 addtojar(jar, p, 0); 1160 } 1161 for(i=0; i<jar->nc; i++) 1162 if(jar->c[i].mark) 1163 delcookie(jar, &jar->c[i]); 1164 break; 1165 } 1166 syncjar(jar); 1167 free(a->dom); 1168 free(a->path); 1169 free(a->inhttp); 1170 free(a->outhttp); 1171 free(a->ctext); 1172 free(a); 1173 } 1174 1175 void 1176 fsend(Srv*) 1177 { 1178 closejar(jar); 1179 } 1180 1181 Srv fs = 1182 { 1183 .open= fsopen, 1184 .read= fsread, 1185 .write= fswrite, 1186 .destroyfid= fsdestroyfid, 1187 .end= fsend, 1188 }; 1189 1190 void 1191 usage(void) 1192 { 1193 fprint(2, "usage: webcookies [-f file] [-m mtpt] [-s service]\n"); 1194 exits("usage"); 1195 } 1196 1197 void 1198 main(int argc, char **argv) 1199 { 1200 char *file, *mtpt, *home, *srv; 1201 1202 file = nil; 1203 srv = nil; 1204 mtpt = "/mnt/webcookies"; 1205 ARGBEGIN{ 1206 case 'D': 1207 chatty9p++; 1208 break; 1209 case 'd': 1210 debug = 1; 1211 break; 1212 case 'f': 1213 file = EARGF(usage()); 1214 break; 1215 case 's': 1216 srv = EARGF(usage()); 1217 break; 1218 case 'm': 1219 mtpt = EARGF(usage()); 1220 break; 1221 default: 1222 usage(); 1223 }ARGEND 1224 1225 if(argc != 0) 1226 usage(); 1227 1228 quotefmtinstall(); 1229 fmtinstall('J', jarfmt); 1230 fmtinstall('K', cookiefmt); 1231 1232 if(file == nil){ 1233 home = getenv("home"); 1234 if(home == nil) 1235 sysfatal("no cookie file specified and no $home"); 1236 file = emalloc9p(strlen(home)+30); 1237 strcpy(file, home); 1238 strcat(file, "/lib/webcookies"); 1239 } 1240 if(access(file, AEXIST) < 0) 1241 close(create(file, OWRITE, 0666)); 1242 1243 jar = readjar(file); 1244 if(jar == nil) 1245 sysfatal("readjar: %r"); 1246 1247 fs.tree = alloctree("cookie", "cookie", DMDIR|0555, nil); 1248 closefile(createfile(fs.tree->root, "http", "cookie", 0666, (void*)Xhttp)); 1249 closefile(createfile(fs.tree->root, "cookies", "cookie", 0666, (void*)Xcookies)); 1250 1251 postmountsrv(&fs, srv, mtpt, MREPL); 1252 exits(nil); 1253 } 1254