Subversion Repositories planix.SVN

Rev

Rev 2 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 - 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**)((char*)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*)((char*)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**)((char*)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**)((char*)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**)((char*)&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*)((char*)&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
	if((d = dirstat(jar->file)) != nil){
430
		jar->qid = d->qid;
431
		free(d);
432
	}
433
	return 0;
434
}
435
 
436
Jar*
437
readjar(char *file)
438
{
439
	char *lock, *p;
440
	Jar *jar;
441
 
442
	jar = newjar();
443
	lock = emalloc9p(strlen(file)+10);
444
	strcpy(lock, file);
445
	if((p = strrchr(lock, '/')) != nil)
446
		p++;
447
	else
448
		p = lock;
449
	memmove(p+2, p, strlen(p)+1);
450
	p[0] = 'L';
451
	p[1] = '.';
452
	jar->lockfile = lock;
453
	jar->file = file;
454
	jar->dirty = 1;
455
 
456
	if(syncjar(jar) < 0){
457
		free(jar->file);
458
		free(jar->lockfile);
459
		free(jar);
460
		return nil;
461
	}
462
	return jar;
463
}
464
 
465
void
466
closejar(Jar *jar)
467
{
468
	int i;
469
 
470
	expirejar(jar, 0);
471
	if(syncjar(jar) < 0)
472
		fprint(2, "warning: cannot rewrite cookie jar: %r\n");
473
 
474
	for(i=0; i<jar->nc; i++)
475
		freecookie(&jar->c[i]);
476
 
477
	free(jar->file);
478
	free(jar);	
479
}
480
 
481
/*
482
 * Domain name matching is per RFC2109, section 2:
483
 *
484
 * Hosts names can be specified either as an IP address or a FQHN
485
 * string.  Sometimes we compare one host name with another.  Host A's
486
 * name domain-matches host B's if
487
 *
488
 * * both host names are IP addresses and their host name strings match
489
 *   exactly; or
490
 *
491
 * * both host names are FQDN strings and their host name strings match
492
 *   exactly; or
493
 *
494
 * * A is a FQDN string and has the form NB, where N is a non-empty name
495
 *   string, B has the form .B', and B' is a FQDN string.  (So, x.y.com
496
 *   domain-matches .y.com but not y.com.)
497
 *
498
 * Note that domain-match is not a commutative operation: a.b.c.com
499
 * domain-matches .c.com, but not the reverse.
500
 *
501
 * (This does not verify that IP addresses and FQDN's are well-formed.)
502
 */
503
int
504
isdomainmatch(char *name, char *pattern)
505
{
506
	int lname, lpattern;
507
 
508
	if(cistrcmp(name, pattern)==0)
509
		return 1;
510
 
511
	if(strcmp(ipattr(name), "dom")==0 && pattern[0]=='.'){
512
		lname = strlen(name);
513
		lpattern = strlen(pattern);
514
		if(lname >= lpattern && cistrcmp(name+lname-lpattern, pattern)==0)
515
			return 1;
516
	}
517
 
518
	return 0;
519
}
520
 
521
/*
522
 * RFC2109 4.3.4:
523
 *	- domain must match
524
 *	- path in cookie must be a prefix of request path
525
 *	- cookie must not have expired
526
 */
527
int
528
iscookiematch(Cookie *c, char *dom, char *path, uint now)
529
{
530
	return isdomainmatch(dom, c->dom)
531
		&& strncmp(c->path, path, strlen(c->path))==0
532
		&& c->expire >= now;
533
}
534
 
535
/* 
536
 * Produce a subjar of matching cookies.
537
 * Secure cookies are only included if secure is set.
538
 */
539
Jar*
540
cookiesearch(Jar *jar, char *dom, char *path, int issecure)
541
{
542
	int i;
543
	Jar *j;
544
	uint now;
545
 
546
	now = time(0);
547
	j = newjar();
548
	for(i=0; i<jar->nc; i++)
549
		if((issecure || !jar->c[i].secure) && iscookiematch(&jar->c[i], dom, path, now))
550
			addcookie(j, &jar->c[i]);
551
	if(j->nc == 0){
552
		closejar(j);
553
		werrstr("no cookies found");
554
		return nil;
555
	}
556
	qsort(j->c, j->nc, sizeof(j->c[0]), (int(*)(const void*, const void*))cookiecmp);
557
	return j;
558
}
559
 
560
/*
561
 * RFC2109 4.3.2 security checks
562
 */
563
char*
564
isbadcookie(Cookie *c, char *dom, char *path)
565
{
566
	if(strncmp(c->path, path, strlen(c->path)) != 0)
567
		return "cookie path is not a prefix of the request path";
568
 
569
	if(c->explicitdom && c->dom[0] != '.')
570
		return "cookie domain doesn't start with dot";
571
 
572
	if(memchr(c->dom+1, '.', strlen(c->dom)-1-1) == nil)
573
		return "cookie domain doesn't have embedded dots";
574
 
575
	if(!isdomainmatch(dom, c->dom))
576
		return "request host does not match cookie domain";
577
 
578
	if(strcmp(ipattr(dom), "dom")==0
579
	&& memchr(dom, '.', strlen(dom)-strlen(c->dom)) != nil)
580
		return "request host contains dots before cookie domain";
581
 
582
	return 0;
583
}
584
 
585
/*
586
 * Sunday, 25-Jan-2002 12:24:36 GMT
587
 * Sunday, 25 Jan 2002 12:24:36 GMT
588
 * Sun, 25 Jan 02 12:24:36 GMT
589
 */
590
int
591
isleap(int year)
592
{
593
	return year%4==0 && (year%100!=0 || year%400==0);
594
}
595
 
596
uint
597
strtotime(char *s)
598
{
599
	char *os;
600
	int i;
601
	Tm tm;
602
 
603
	static int mday[2][12] = {
604
		31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31,
605
		31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31,
606
	};
607
	static char *wday[] = {
608
		"Sunday", "Monday", "Tuesday", "Wednesday",
609
		"Thursday", "Friday", "Saturday",
610
	};
611
	static char *mon[] = {
612
		"Jan", "Feb", "Mar", "Apr", "May", "Jun",
613
		"Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
614
	};
615
 
616
	os = s;
617
	/* Sunday, */
618
	for(i=0; i<nelem(wday); i++){
619
		if(cistrncmp(s, wday[i], strlen(wday[i])) == 0){
620
			s += strlen(wday[i]);
621
			break;
622
		}
623
		if(cistrncmp(s, wday[i], 3) == 0){
624
			s += 3;
625
			break;
626
		}
627
	}
628
	if(i==nelem(wday)){
629
		if(debug)
630
			fprint(2, "bad wday (%s)\n", os);
631
		return -1;
632
	}
633
	if(*s++ != ',' || *s++ != ' '){
634
		if(debug)
635
			fprint(2, "bad wday separator (%s)\n", os);
636
		return -1;
637
	}
638
 
639
	/* 25- */
640
	if(!isdigit(s[0]) || !isdigit(s[1]) || (s[2]!='-' && s[2]!=' ')){
641
		if(debug)
642
			fprint(2, "bad day of month (%s)\n", os);
643
		return -1;
644
	}
645
	tm.mday = strtol(s, 0, 10);
646
	s += 3;
647
 
648
	/* Jan- */
649
	for(i=0; i<nelem(mon); i++)
650
		if(cistrncmp(s, mon[i], 3) == 0){
651
			tm.mon = i;
652
			s += 3;
653
			break;
654
		}
655
	if(i==nelem(mon)){
656
		if(debug)
657
			fprint(2, "bad month (%s)\n", os);
658
		return -1;
659
	}
660
	if(s[0] != '-' && s[0] != ' '){
661
		if(debug)
662
			fprint(2, "bad month separator (%s)\n", os);
663
		return -1;
664
	}
665
	s++;
666
 
667
	/* 2002 */
668
	if(!isdigit(s[0]) || !isdigit(s[1])){
669
		if(debug)
670
			fprint(2, "bad year (%s)\n", os);
671
		return -1;
672
	}
673
	tm.year = strtol(s, 0, 10);
674
	s += 2;
675
	if(isdigit(s[0]) && isdigit(s[1]))
676
		s += 2;
677
	else{
678
		if(tm.year <= 68)
679
			tm.year += 2000;
680
		else
681
			tm.year += 1900;
682
	}
683
	if(tm.mday==0 || tm.mday > mday[isleap(tm.year)][tm.mon]){
684
		if(debug)
685
			fprint(2, "invalid day of month (%s)\n", os);
686
		return -1;
687
	}
688
	tm.year -= 1900;
689
	if(*s++ != ' '){
690
		if(debug)
691
			fprint(2, "bad year separator (%s)\n", os);
692
		return -1;
693
	}
694
 
695
	if(!isdigit(s[0]) || !isdigit(s[1]) || s[2]!=':'
696
	|| !isdigit(s[3]) || !isdigit(s[4]) || s[5]!=':'
697
	|| !isdigit(s[6]) || !isdigit(s[7]) || s[8]!=' '){
698
		if(debug)
699
			fprint(2, "bad time (%s)\n", os);
700
		return -1;
701
	}
702
 
703
	tm.hour = atoi(s);
704
	tm.min = atoi(s+3);
705
	tm.sec = atoi(s+6);
706
	if(tm.hour >= 24 || tm.min >= 60 || tm.sec >= 60){
707
		if(debug)
708
			fprint(2, "invalid time (%s)\n", os);
709
		return -1;
710
	}
711
	s += 9;
712
 
713
	if(cistrcmp(s, "GMT") != 0){
714
		if(debug)
715
			fprint(2, "time zone not GMT (%s)\n", os);
716
		return -1;
717
	}
718
	strcpy(tm.zone, "GMT");
719
	tm.yday = 0;
720
	return tm2sec(&tm);
721
}
722
 
723
/*
724
 * skip linear whitespace.  we're a bit more lenient than RFC2616 2.2.
725
 */
726
char*
727
skipspace(char *s)
728
{
729
	while(*s=='\r' || *s=='\n' || *s==' ' || *s=='\t')
730
		s++;
731
	return s;
732
}
733
 
734
/*
735
 * Try to identify old netscape headers.
736
 * The old headers:
737
 *	- didn't allow spaces around the '='
738
 *	- used an 'Expires' attribute
739
 *	- had no 'Version' attribute
740
 *	- had no quotes
741
 *	- allowed whitespace in values
742
 *	- apparently separated attr/value pairs with ';' exclusively
743
 */
744
int
745
isnetscape(char *hdr)
746
{
747
	char *s;
748
 
749
	for(s=hdr; (s=strchr(s, '=')) != nil; s++){
750
		if(isspace(s[1]) || (s > hdr && isspace(s[-1])))
751
			return 0;
752
		if(s[1]=='"')
753
			return 0;
754
	}
755
	if(cistrstr(hdr, "version="))
756
		return 0;
757
	return 1;
758
}
759
 
760
/*
761
 * Parse HTTP response headers, adding cookies to jar.
762
 * Overwrites the headers.  May overwrite path.
763
 */
764
char* parsecookie(Cookie*, char*, char**, int, char*, char*);
765
int
766
parsehttp(Jar *jar, char *hdr, char *dom, char *path)
767
{
768
	static char setcookie[] = "Set-Cookie:";
769
	char *e, *p, *nextp;
770
	Cookie c;
771
	int isns, n;
772
 
773
	isns = isnetscape(hdr);
774
	n = 0;
775
	for(p=hdr; p; p=nextp){
776
		p = skipspace(p);
777
		if(*p == '\0')
778
			break;
779
		nextp = strchr(p, '\n');
780
		if(nextp != nil)
781
			*nextp++ = '\0';
782
		if(debug)
783
			fprint(2, "?%s\n", p);
784
		if(cistrncmp(p, setcookie, strlen(setcookie)) != 0)
785
			continue;
786
		if(debug)
787
			fprint(2, "%s\n", p);
788
		p = skipspace(p+strlen(setcookie));
789
		for(; *p; p=skipspace(p)){
790
			if((e = parsecookie(&c, p, &p, isns, dom, path)) != nil){
791
				if(debug)
792
					fprint(2, "parse cookie: %s\n", e);
793
				break;
794
			}
795
			if((e = isbadcookie(&c, dom, path)) != nil){
796
				if(debug)
797
					fprint(2, "reject cookie; %s\n", e);
798
				continue;
799
			}
800
			addcookie(jar, &c);
801
			n++;
802
		}
803
	}
804
	return n;
805
}
806
 
807
static char*
808
skipquoted(char *s)
809
{
810
	/*
811
	 * Sec 2.2 of RFC2616 defines a "quoted-string" as:
812
	 *
813
	 *  quoted-string  = ( <"> *(qdtext | quoted-pair ) <"> )
814
	 *  qdtext         = <any TEXT except <">>
815
	 *  quoted-pair    = "\" CHAR
816
	 *
817
	 * TEXT is any octet except CTLs, but including LWS;
818
	 * LWS is [CR LF] 1*(SP | HT);
819
	 * CHARs are ASCII octets 0-127;  (NOTE: we reject 0's)
820
	 * CTLs are octets 0-31 and 127;
821
	 */
822
	if(*s != '"')
823
		return s;
824
 
825
	for(s++; 32 <= *s && *s < 127 && *s != '"'; s++)
826
		if(*s == '\\' && *(s+1) != '\0')
827
			s++;
828
	return s;
829
}
830
 
831
static char*
832
skiptoken(char *s)
833
{
834
	/*
835
	 * Sec 2.2 of RFC2616 defines a "token" as
836
 	 *  1*<any CHAR except CTLs or separators>;
837
	 * CHARs are ASCII octets 0-127;
838
	 * CTLs are octets 0-31 and 127;
839
	 * separators are "()<>@,;:\/[]?={}", double-quote, SP (32), and HT (9)
840
	 */
841
	while(32 <= *s && *s < 127 && strchr("()<>@,;:[]?={}\" \t\\", *s)==nil)
842
		s++;
843
 
844
	return s;
845
}
846
 
847
static char*
848
skipvalue(char *s, int isns)
849
{
850
	char *t;
851
 
852
	/*
853
	 * An RFC2109 value is an HTTP token or an HTTP quoted string.
854
	 * Netscape servers ignore the spec and rely on semicolons, apparently.
855
	 */
856
	if(isns){
857
		if((t = strchr(s, ';')) == nil)
858
			t = s+strlen(s);
859
		return t;
860
	}
861
	if(*s == '"')
862
		return skipquoted(s);
863
	return skiptoken(s);
864
}
865
 
866
/*
867
 * RMID=80b186bb64c03c65fab767f8; expires=Monday, 10-Feb-2003 04:44:39 GMT; 
868
 *	path=/; domain=.nytimes.com
869
 */
870
char*
871
parsecookie(Cookie *c, char *p, char **e, int isns, char *dom, char *path)
872
{
873
	int i, done;
874
	char *t, *u, *attr, *val;
875
 
876
	memset(c, 0, sizeof *c);
877
	c->expire = ~0;
878
 
879
	/* NAME=VALUE */
880
	t = skiptoken(p);
881
	c->name = p;
882
	p = skipspace(t);
883
	if(*p != '='){
884
	Badname:
885
		return "malformed cookie: no NAME=VALUE";
886
	}
887
	*t = '\0';
888
	p = skipspace(p+1);
889
	t = skipvalue(p, isns);
890
	if(*t)
891
		*t++ = '\0';
892
	c->value = p;
893
	p = skipspace(t);
894
	if(c->name[0]=='\0' || c->value[0]=='\0')
895
		goto Badname;
896
 
897
	done = 0;
898
	for(; *p && !done; p=skipspace(p)){
899
		attr = p;
900
		t = skiptoken(p);
901
		u = skipspace(t);
902
		switch(*u){
903
		case '\0':
904
			*t = '\0';
905
			p = val = u;
906
			break;
907
		case ';':
908
			*t = '\0';
909
			val = "";
910
			p = u+1;
911
			break;
912
		case '=':
913
			*t = '\0';
914
			val = skipspace(u+1);
915
			p = skipvalue(val, isns);
916
			if(*p==',')
917
				done = 1;
918
			if(*p)
919
				*p++ = '\0';
920
			break;
921
		case ',':
922
			if(!isns){
923
				val = "";
924
				p = u;
925
				*p++ = '\0';
926
				done = 1;
927
				break;
928
			}
929
		default:
930
			if(debug)
931
				fprint(2, "syntax: %s\n", p);
932
			return "syntax error";
933
		}
934
		for(i=0; i<nelem(stab); i++)
935
			if(stab[i].ishttp && cistrcmp(stab[i].s, attr)==0)
936
				*(char**)((char*)c+stab[i].offset) = val;
937
		if(cistrcmp(attr, "expires") == 0){
938
			if(!isns)
939
				return "non-netscape cookie has Expires tag";
940
			if(!val[0])
941
				return "bad expires tag";
942
			c->expire = strtotime(val);
943
			if(c->expire == ~0)
944
				return "cannot parse netscape expires tag";
945
		}
946
		if(cistrcmp(attr, "max-age") == 0)
947
			c->expire = time(0)+atoi(val);
948
		if(cistrcmp(attr, "secure") == 0)
949
			c->secure = 1;
950
	}
951
 
952
	if(c->dom)
953
		c->explicitdom = 1;
954
	else
955
		c->dom = dom;
956
	if(c->path)
957
		c->explicitpath = 1;
958
	else{
959
		c->path = path;
960
		if((t = strchr(c->path, '?')) != 0)
961
			*t = '\0';
962
		if((t = strrchr(c->path, '/')) != 0)
963
			*t = '\0';
964
	}
965
	c->netscapestyle = isns;
966
	*e = p;
967
 
968
	return nil;
969
}
970
 
971
Jar *jar;
972
 
973
enum
974
{
975
	Xhttp = 1,
976
	Xcookies,
977
 
978
	NeedUrl = 0,
979
	HaveUrl,
980
};
981
 
982
typedef struct Aux Aux;
983
struct Aux
984
{
985
	int state;
986
	char *dom;
987
	char *path;
988
	char *inhttp;
989
	char *outhttp;
990
	char *ctext;
991
	int rdoff;
992
};
993
enum
994
{
995
	AuxBuf = 4096,
996
	MaxCtext = 16*1024*1024,
997
};
998
 
999
void
1000
fsopen(Req *r)
1001
{
1002
	char *s, *es;
1003
	int i, sz;
1004
	Aux *a;
1005
 
1006
	switch((uintptr)r->fid->file->aux){
1007
	case Xhttp:
1008
		syncjar(jar);
1009
		a = emalloc9p(sizeof(Aux));
1010
		r->fid->aux = a;
1011
		a->inhttp = emalloc9p(AuxBuf);
1012
		a->outhttp = emalloc9p(AuxBuf);
1013
		break;
1014
 
1015
	case Xcookies:
1016
		syncjar(jar);
1017
		a = emalloc9p(sizeof(Aux));
1018
		r->fid->aux = a;
1019
		if(r->ifcall.mode&OTRUNC){
1020
			a->ctext = emalloc9p(1);
1021
			a->ctext[0] = '\0';
1022
		}else{
1023
			sz = 256*jar->nc+1024;	/* BUG should do better */
1024
			a->ctext = emalloc9p(sz);
1025
			a->ctext[0] = '\0';
1026
			s = a->ctext;
1027
			es = s+sz;
1028
			for(i=0; i<jar->nc; i++)
1029
				s = seprint(s, es, "%K\n", &jar->c[i]);
1030
		}
1031
		break;
1032
	}
1033
	respond(r, nil);
1034
}
1035
 
1036
void
1037
fsread(Req *r)
1038
{
1039
	Aux *a;
1040
 
1041
	a = r->fid->aux;
1042
	switch((uintptr)r->fid->file->aux){
1043
	case Xhttp:
1044
		if(a->state == NeedUrl){
1045
			respond(r, "must write url before read");
1046
			return;
1047
		}
1048
		r->ifcall.offset = a->rdoff;
1049
		readstr(r, a->outhttp);
1050
		a->rdoff += r->ofcall.count;
1051
		respond(r, nil);
1052
		return;
1053
 
1054
	case Xcookies:
1055
		readstr(r, a->ctext);
1056
		respond(r, nil);
1057
		return;
1058
 
1059
	default:
1060
		respond(r, "bug in webcookies");
1061
		return;
1062
	}
1063
}
1064
 
1065
void
1066
fswrite(Req *r)
1067
{
1068
	Aux *a;
1069
	int i, sz, hlen, issecure;
1070
	char buf[1024], *p;
1071
	Jar *j;
1072
 
1073
	a = r->fid->aux;
1074
	switch((uintptr)r->fid->file->aux){
1075
	case Xhttp:
1076
		if(a->state == NeedUrl){
1077
			if(r->ifcall.count >= sizeof buf){
1078
				respond(r, "url too long");
1079
				return;
1080
			}
1081
			memmove(buf, r->ifcall.data, r->ifcall.count);
1082
			buf[r->ifcall.count] = '\0';
1083
			issecure = 0;
1084
			if(cistrncmp(buf, "http://", 7) == 0)
1085
				hlen = 7;
1086
			else if(cistrncmp(buf, "https://", 8) == 0){
1087
				hlen = 8;
1088
				issecure = 1;
1089
			}else{
1090
				respond(r, "url must begin http:// or https://");
1091
				return;
1092
			}
1093
			if(buf[hlen]=='/'){
1094
				respond(r, "url without host name");
1095
				return;
1096
			}
1097
			p = strchr(buf+hlen, '/');
1098
			if(p == nil)
1099
				a->path = estrdup9p("/");
1100
			else{
1101
				a->path = estrdup9p(p);
1102
				*p = '\0';
1103
			}
1104
			a->dom = estrdup9p(buf+hlen);
1105
			a->state = HaveUrl;
1106
			j = cookiesearch(jar, a->dom, a->path, issecure);
1107
			if(debug){
1108
				fprint(2, "search %s %s got %p\n", a->dom, a->path, j);
1109
				if(j){
1110
					fprint(2, "%d cookies\n", j->nc);
1111
					for(i=0; i<j->nc; i++)
1112
						fprint(2, "%K\n", &j->c[i]);
1113
				}
1114
			}
1115
			snprint(a->outhttp, AuxBuf, "%J", j);
1116
			if(j)
1117
				closejar(j);
1118
		}else{
1119
			if(strlen(a->inhttp)+r->ifcall.count >= AuxBuf){
1120
				respond(r, "http headers too large");
1121
				return;
1122
			}
1123
			memmove(a->inhttp+strlen(a->inhttp), r->ifcall.data, r->ifcall.count);
1124
		}
1125
		r->ofcall.count = r->ifcall.count;
1126
		respond(r, nil);
1127
		return;
1128
 
1129
	case Xcookies:
1130
		sz = r->ifcall.count+r->ifcall.offset;
1131
		if(sz > strlen(a->ctext)){
1132
			if(sz >= MaxCtext){
1133
				respond(r, "cookie file too large");
1134
				return;
1135
			}
1136
			a->ctext = erealloc9p(a->ctext, sz+1);
1137
			a->ctext[sz] = '\0';
1138
		}
1139
		memmove(a->ctext+r->ifcall.offset, r->ifcall.data, r->ifcall.count);
1140
		r->ofcall.count = r->ifcall.count;
1141
		respond(r, nil);
1142
		return;
1143
 
1144
	default:
1145
		respond(r, "bug in webcookies");
1146
		return;
1147
	}
1148
}
1149
 
1150
void
1151
fsdestroyfid(Fid *fid)
1152
{
1153
	char *p, *nextp;
1154
	Aux *a;
1155
	int i;
1156
 
1157
	a = fid->aux;
1158
	if(a == nil)
1159
		return;
1160
	switch((uintptr)fid->file->aux){
1161
	case Xhttp:
1162
		parsehttp(jar, a->inhttp, a->dom, a->path);
1163
		break;
1164
	case Xcookies:
1165
		for(i=0; i<jar->nc; i++)
1166
			jar->c[i].mark = 1;
1167
		for(p=a->ctext; *p; p=nextp){
1168
			if((nextp = strchr(p, '\n')) != nil)
1169
				*nextp++ = '\0';
1170
			else
1171
				nextp = "";
1172
			addtojar(jar, p, 0);
1173
		}
1174
		for(i=0; i<jar->nc; i++)
1175
			if(jar->c[i].mark)
1176
				delcookie(jar, &jar->c[i]);
1177
		break;
1178
	}
1179
	syncjar(jar);
1180
	free(a->dom);
1181
	free(a->path);
1182
	free(a->inhttp);
1183
	free(a->outhttp);
1184
	free(a->ctext);
1185
	free(a);
1186
}
1187
 
1188
void
1189
fsend(Srv*)
1190
{
1191
	closejar(jar);
1192
}
1193
 
1194
Srv fs = 
1195
{
1196
.open=		fsopen,
1197
.read=		fsread,
1198
.write=		fswrite,
1199
.destroyfid=	fsdestroyfid,
1200
.end=		fsend,
1201
};
1202
 
1203
void
1204
usage(void)
1205
{
1206
	fprint(2, "usage: webcookies [-f file] [-m mtpt] [-s service]\n");
1207
	exits("usage");
1208
}
1209
 
1210
void
1211
main(int argc, char **argv)
1212
{
1213
	char *file, *mtpt, *home, *srv;
1214
 
1215
	file = nil;
1216
	srv = nil;
1217
	mtpt = "/mnt/webcookies";
1218
	ARGBEGIN{
1219
	case 'D':
1220
		chatty9p++;
1221
		break;
1222
	case 'd':
1223
		debug = 1;
1224
		break;
1225
	case 'f':
1226
		file = EARGF(usage());
1227
		break;
1228
	case 's':
1229
		srv = EARGF(usage());
1230
		break;
1231
	case 'm':
1232
		mtpt = EARGF(usage());
1233
		break;
1234
	default:
1235
		usage();
1236
	}ARGEND
1237
 
1238
	if(argc != 0)
1239
		usage();
1240
 
1241
	quotefmtinstall();
1242
	fmtinstall('J', jarfmt);
1243
	fmtinstall('K', cookiefmt);
1244
 
1245
	if(file == nil){
1246
		home = getenv("home");
1247
		if(home == nil)
1248
			sysfatal("no cookie file specified and no $home");
1249
		file = emalloc9p(strlen(home)+30);
1250
		strcpy(file, home);
1251
		strcat(file, "/lib/webcookies");
1252
	}
1253
	if(access(file, AEXIST) < 0)
1254
		close(create(file, OWRITE, 0666));
1255
 
1256
	jar = readjar(file);
1257
	if(jar == nil)
1258
		sysfatal("readjar: %r");
1259
 
1260
	fs.tree = alloctree("cookie", "cookie", DMDIR|0555, nil);
1261
	closefile(createfile(fs.tree->root, "http", "cookie", 0666, (void*)Xhttp));
1262
	closefile(createfile(fs.tree->root, "cookies", "cookie", 0666, (void*)Xcookies));
1263
 
1264
	postmountsrv(&fs, srv, mtpt, MREPL);
1265
	exits(nil);
1266
}