Warning: Attempt to read property "date" on null in /usr/local/www/websvn.planix.org/blame.php on line 247

Warning: Attempt to read property "msg" on null in /usr/local/www/websvn.planix.org/blame.php on line 247
WebSVN – planix.SVN – Blame – /os/branches/planix-v0/sys/src/cmd/webfs/cookies.c – Rev 2

Subversion Repositories planix.SVN

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

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