xref: /inferno-os/appl/lib/rfc822.b (revision 0e96539ff7cff23233d3f0a64bb285b385a3a1f4)
1*0e96539fSCharles.Forsythimplement RFC822;
2*0e96539fSCharles.Forsyth
3*0e96539fSCharles.Forsythinclude "sys.m";
4*0e96539fSCharles.Forsyth	sys: Sys;
5*0e96539fSCharles.Forsyth
6*0e96539fSCharles.Forsythinclude "bufio.m";
7*0e96539fSCharles.Forsyth	bufio: Bufio;
8*0e96539fSCharles.Forsyth	Iobuf: import bufio;
9*0e96539fSCharles.Forsyth
10*0e96539fSCharles.Forsythinclude "rfc822.m";
11*0e96539fSCharles.Forsyth
12*0e96539fSCharles.Forsythinclude "string.m";
13*0e96539fSCharles.Forsyth	str: String;
14*0e96539fSCharles.Forsyth
15*0e96539fSCharles.Forsythinclude "daytime.m";
16*0e96539fSCharles.Forsyth	daytime: Daytime;
17*0e96539fSCharles.Forsyth	Tm: import daytime;
18*0e96539fSCharles.Forsyth
19*0e96539fSCharles.ForsythMinrequest: con 512;	# more than enough for most requests
20*0e96539fSCharles.Forsyth
21*0e96539fSCharles.ForsythSuffix: adt {
22*0e96539fSCharles.Forsyth	suffix: string;
23*0e96539fSCharles.Forsyth	generic: string;
24*0e96539fSCharles.Forsyth	specific: string;
25*0e96539fSCharles.Forsyth	encoding: string;
26*0e96539fSCharles.Forsyth};
27*0e96539fSCharles.Forsyth
28*0e96539fSCharles.ForsythSuffixFile: con "/lib/mimetype";
29*0e96539fSCharles.Forsythmtime := 0;
30*0e96539fSCharles.Forsythqid: Sys->Qid;
31*0e96539fSCharles.Forsyth
32*0e96539fSCharles.Forsythsuffixes: list of ref Suffix;
33*0e96539fSCharles.Forsyth
34*0e96539fSCharles.Forsythnomod(s: string)
35*0e96539fSCharles.Forsyth{
36*0e96539fSCharles.Forsyth	raise sys->sprint("internal: can't load %s: %r", s);
37*0e96539fSCharles.Forsyth}
38*0e96539fSCharles.Forsyth
39*0e96539fSCharles.Forsythinit(b: Bufio)
40*0e96539fSCharles.Forsyth{
41*0e96539fSCharles.Forsyth	sys = load Sys Sys->PATH;
42*0e96539fSCharles.Forsyth	bufio = b;
43*0e96539fSCharles.Forsyth	str = load String String->PATH;
44*0e96539fSCharles.Forsyth	if(str == nil)
45*0e96539fSCharles.Forsyth		nomod(String->PATH);
46*0e96539fSCharles.Forsyth	daytime = load Daytime Daytime->PATH;
47*0e96539fSCharles.Forsyth	if(daytime == nil)
48*0e96539fSCharles.Forsyth		nomod(Daytime->PATH);
49*0e96539fSCharles.Forsyth	readsuffixfile();
50*0e96539fSCharles.Forsyth}
51*0e96539fSCharles.Forsyth
52*0e96539fSCharles.Forsythreadheaders(fd: ref Iobuf, limit: int): array of (string, array of byte)
53*0e96539fSCharles.Forsyth{
54*0e96539fSCharles.Forsyth	n := 0;
55*0e96539fSCharles.Forsyth	s := 0;
56*0e96539fSCharles.Forsyth	b := array[Minrequest] of byte;
57*0e96539fSCharles.Forsyth	nline := 0;
58*0e96539fSCharles.Forsyth	lines: list of array of byte;
59*0e96539fSCharles.Forsyth	while((c := fd.getb()) >= 0){
60*0e96539fSCharles.Forsyth		if(c == '\r'){
61*0e96539fSCharles.Forsyth			c = fd.getb();
62*0e96539fSCharles.Forsyth			if(c < 0)
63*0e96539fSCharles.Forsyth				break;
64*0e96539fSCharles.Forsyth			if(c != '\n'){
65*0e96539fSCharles.Forsyth				fd.ungetb();
66*0e96539fSCharles.Forsyth				c = '\r';
67*0e96539fSCharles.Forsyth			}
68*0e96539fSCharles.Forsyth		}
69*0e96539fSCharles.Forsyth		if(n >= len b){
70*0e96539fSCharles.Forsyth			if(len b >= limit)
71*0e96539fSCharles.Forsyth				return nil;
72*0e96539fSCharles.Forsyth			ab := array[n+512] of byte;
73*0e96539fSCharles.Forsyth			ab[0:] = b;
74*0e96539fSCharles.Forsyth			b = ab;
75*0e96539fSCharles.Forsyth		}
76*0e96539fSCharles.Forsyth		b[n++] = byte c;
77*0e96539fSCharles.Forsyth		if(c == '\n'){
78*0e96539fSCharles.Forsyth			if(n == 1 || b[n-2] == byte '\n')
79*0e96539fSCharles.Forsyth				break;	# empty line
80*0e96539fSCharles.Forsyth			c = fd.getb();
81*0e96539fSCharles.Forsyth			if(c < 0)
82*0e96539fSCharles.Forsyth				break;
83*0e96539fSCharles.Forsyth			if(c != ' ' && c != '\t'){	# not continued
84*0e96539fSCharles.Forsyth				fd.ungetb();
85*0e96539fSCharles.Forsyth				lines = b[s: n] :: lines;
86*0e96539fSCharles.Forsyth				nline++;
87*0e96539fSCharles.Forsyth				s = n;
88*0e96539fSCharles.Forsyth			}else
89*0e96539fSCharles.Forsyth				b[n-1] = byte ' ';
90*0e96539fSCharles.Forsyth		}
91*0e96539fSCharles.Forsyth	}
92*0e96539fSCharles.Forsyth	if(n == 0)
93*0e96539fSCharles.Forsyth		return nil;
94*0e96539fSCharles.Forsyth	b = b[0: n];
95*0e96539fSCharles.Forsyth	if(n != s){
96*0e96539fSCharles.Forsyth		lines = b[s:n] :: lines;
97*0e96539fSCharles.Forsyth		nline++;
98*0e96539fSCharles.Forsyth	}
99*0e96539fSCharles.Forsyth	a := array[nline] of (string, array of byte);
100*0e96539fSCharles.Forsyth	for(; lines != nil; lines = tl lines){
101*0e96539fSCharles.Forsyth		b = hd lines;
102*0e96539fSCharles.Forsyth		name := "";
103*0e96539fSCharles.Forsyth		for(i := 0; i < len b; i++)
104*0e96539fSCharles.Forsyth			if(b[i] == byte ':'){
105*0e96539fSCharles.Forsyth				name = str->tolower(string b[0:i]);
106*0e96539fSCharles.Forsyth				b = b[i+1:];
107*0e96539fSCharles.Forsyth				break;
108*0e96539fSCharles.Forsyth			}
109*0e96539fSCharles.Forsyth		a[--nline] = (name, b);
110*0e96539fSCharles.Forsyth	}
111*0e96539fSCharles.Forsyth	return a;
112*0e96539fSCharles.Forsyth}
113*0e96539fSCharles.Forsyth
114*0e96539fSCharles.Forsyth#
115*0e96539fSCharles.Forsyth# *(";" parameter) used in transfer-extension, media-type and media-range
116*0e96539fSCharles.Forsyth# parameter = attribute "=" value
117*0e96539fSCharles.Forsyth# attribute = token
118*0e96539fSCharles.Forsyth# value = token | quoted-string
119*0e96539fSCharles.Forsyth#
120*0e96539fSCharles.Forsythparseparams(ps: ref Rfclex): list of (string, string)
121*0e96539fSCharles.Forsyth{
122*0e96539fSCharles.Forsyth	l: list of (string, string);
123*0e96539fSCharles.Forsyth	do{
124*0e96539fSCharles.Forsyth		if(ps.lex() != Word)
125*0e96539fSCharles.Forsyth			break;
126*0e96539fSCharles.Forsyth		attr := ps.wordval;
127*0e96539fSCharles.Forsyth		if(ps.lex() != '=' || ps.lex() != Word && ps.tok != QString)
128*0e96539fSCharles.Forsyth			break;
129*0e96539fSCharles.Forsyth		l = (attr, ps.wordval) :: l;
130*0e96539fSCharles.Forsyth	}while(ps.lex() == ';');
131*0e96539fSCharles.Forsyth	ps.unlex();
132*0e96539fSCharles.Forsyth	return rev(l);
133*0e96539fSCharles.Forsyth}
134*0e96539fSCharles.Forsyth
135*0e96539fSCharles.Forsyth#
136*0e96539fSCharles.Forsyth# 1#transfer-coding
137*0e96539fSCharles.Forsyth#
138*0e96539fSCharles.Forsythmimefields(ps: ref Rfclex): list of (string, list of (string, string))
139*0e96539fSCharles.Forsyth{
140*0e96539fSCharles.Forsyth	rf: list of (string, list of (string, string));
141*0e96539fSCharles.Forsyth	do{
142*0e96539fSCharles.Forsyth		if(ps.lex() == Word){
143*0e96539fSCharles.Forsyth			w := ps.wordval;
144*0e96539fSCharles.Forsyth			if(ps.lex() == ';'){
145*0e96539fSCharles.Forsyth				rf = (w, parseparams(ps)) :: rf;
146*0e96539fSCharles.Forsyth				ps.lex();
147*0e96539fSCharles.Forsyth			}else
148*0e96539fSCharles.Forsyth				rf = (w, nil) :: rf;
149*0e96539fSCharles.Forsyth		}
150*0e96539fSCharles.Forsyth	}while(ps.tok == ',');
151*0e96539fSCharles.Forsyth	ps.unlex();
152*0e96539fSCharles.Forsyth	f: list of (string, list of (string, string));
153*0e96539fSCharles.Forsyth	for(; rf != nil; rf = tl rf)
154*0e96539fSCharles.Forsyth		f = hd rf :: f;
155*0e96539fSCharles.Forsyth	return f;
156*0e96539fSCharles.Forsyth}
157*0e96539fSCharles.Forsyth
158*0e96539fSCharles.Forsyth#	#(media-type | (media-range [accept-params]))	; Content-Type and Accept
159*0e96539fSCharles.Forsyth#
160*0e96539fSCharles.Forsyth#       media-type     = type "/" subtype *( ";" parameter )
161*0e96539fSCharles.Forsyth#       type           = token
162*0e96539fSCharles.Forsyth#       subtype        = token
163*0e96539fSCharles.Forsyth#	LWS must not be used between type and subtype, nor between attribute and value (in parameter)
164*0e96539fSCharles.Forsyth#
165*0e96539fSCharles.Forsyth#	media-range = ("*/*" | type "/*" | type "/" subtype ) *(";' parameter)
166*0e96539fSCharles.Forsyth#    	accept-params  = ";" "q" "=" qvalue *( accept-extension )
167*0e96539fSCharles.Forsyth#	accept-extension = ";" token [ "=" ( token | quoted-string ) ]
168*0e96539fSCharles.Forsyth#
169*0e96539fSCharles.Forsyth#	1#( ( charset | "*" )[ ";" "q" "=" qvalue ] )		; Accept-Charset
170*0e96539fSCharles.Forsyth#	1#( codings [ ";" "q" "=" qvalue ] )			; Accept-Encoding
171*0e96539fSCharles.Forsyth#	1#( language-range [ ";" "q" "=" qvalue ] )		; Accept-Language
172*0e96539fSCharles.Forsyth#
173*0e96539fSCharles.Forsyth#	codings = ( content-coding | "*" )
174*0e96539fSCharles.Forsyth#
175*0e96539fSCharles.Forsythparsecontent(ps: ref Rfclex, multipart: int, head: list of ref Content): list of ref Content
176*0e96539fSCharles.Forsyth{
177*0e96539fSCharles.Forsyth	do{
178*0e96539fSCharles.Forsyth		if(ps.lex() == Word){
179*0e96539fSCharles.Forsyth			generic := ps.wordval;
180*0e96539fSCharles.Forsyth			specific := "*";
181*0e96539fSCharles.Forsyth			if(ps.lex() == '/'){
182*0e96539fSCharles.Forsyth				if(ps.lex() != Word)
183*0e96539fSCharles.Forsyth					break;
184*0e96539fSCharles.Forsyth				specific = ps.wordval;
185*0e96539fSCharles.Forsyth				if(!multipart && specific != "*")
186*0e96539fSCharles.Forsyth					break;
187*0e96539fSCharles.Forsyth			}else if(multipart)
188*0e96539fSCharles.Forsyth				break;	# syntax error
189*0e96539fSCharles.Forsyth			else
190*0e96539fSCharles.Forsyth				ps.unlex();
191*0e96539fSCharles.Forsyth			params: list of (string, string) = nil;
192*0e96539fSCharles.Forsyth			if(ps.lex() == ';'){
193*0e96539fSCharles.Forsyth				params = parseparams(ps);
194*0e96539fSCharles.Forsyth				ps.lex();
195*0e96539fSCharles.Forsyth			}
196*0e96539fSCharles.Forsyth			head = Content.mk(generic, specific, params) :: head;	# order reversed, but doesn't matter
197*0e96539fSCharles.Forsyth		}
198*0e96539fSCharles.Forsyth	}while(ps.tok == ',');
199*0e96539fSCharles.Forsyth	ps.unlex();
200*0e96539fSCharles.Forsyth	return head;
201*0e96539fSCharles.Forsyth}
202*0e96539fSCharles.Forsyth
203*0e96539fSCharles.Forsythrev(l: list of (string, string)): list of (string, string)
204*0e96539fSCharles.Forsyth{
205*0e96539fSCharles.Forsyth	rl: list of (string, string);
206*0e96539fSCharles.Forsyth	for(; l != nil; l = tl l)
207*0e96539fSCharles.Forsyth		rl = hd l :: rl;
208*0e96539fSCharles.Forsyth	return rl;
209*0e96539fSCharles.Forsyth}
210*0e96539fSCharles.Forsyth
211*0e96539fSCharles.ForsythRfclex.mk(a: array of byte): ref Rfclex
212*0e96539fSCharles.Forsyth{
213*0e96539fSCharles.Forsyth	ps := ref Rfclex;
214*0e96539fSCharles.Forsyth	ps.fd = bufio->aopen(a);
215*0e96539fSCharles.Forsyth	ps.tok = '\n';
216*0e96539fSCharles.Forsyth	ps.eof = 0;
217*0e96539fSCharles.Forsyth	return ps;
218*0e96539fSCharles.Forsyth}
219*0e96539fSCharles.Forsyth
220*0e96539fSCharles.ForsythRfclex.getc(ps: self ref Rfclex): int
221*0e96539fSCharles.Forsyth{
222*0e96539fSCharles.Forsyth	c := ps.fd.getb();
223*0e96539fSCharles.Forsyth	if(c < 0)
224*0e96539fSCharles.Forsyth		ps.eof = 1;
225*0e96539fSCharles.Forsyth	return c;
226*0e96539fSCharles.Forsyth}
227*0e96539fSCharles.Forsyth
228*0e96539fSCharles.ForsythRfclex.ungetc(ps: self ref Rfclex)
229*0e96539fSCharles.Forsyth{
230*0e96539fSCharles.Forsyth	if(!ps.eof)
231*0e96539fSCharles.Forsyth		ps.fd.ungetb();
232*0e96539fSCharles.Forsyth}
233*0e96539fSCharles.Forsyth
234*0e96539fSCharles.ForsythRfclex.lex(ps: self ref Rfclex): int
235*0e96539fSCharles.Forsyth{
236*0e96539fSCharles.Forsyth	if(ps.seen != nil){
237*0e96539fSCharles.Forsyth		(ps.tok, ps.wordval) = hd ps.seen;
238*0e96539fSCharles.Forsyth		ps.seen = tl ps.seen;
239*0e96539fSCharles.Forsyth	}else
240*0e96539fSCharles.Forsyth		ps.tok = lex1(ps, 0);
241*0e96539fSCharles.Forsyth	return ps.tok;
242*0e96539fSCharles.Forsyth}
243*0e96539fSCharles.Forsyth
244*0e96539fSCharles.ForsythRfclex.unlex(ps: self ref Rfclex)
245*0e96539fSCharles.Forsyth{
246*0e96539fSCharles.Forsyth	ps.seen = (ps.tok, ps.wordval) :: ps.seen;
247*0e96539fSCharles.Forsyth}
248*0e96539fSCharles.Forsyth
249*0e96539fSCharles.ForsythRfclex.skipws(ps: self ref Rfclex): int
250*0e96539fSCharles.Forsyth{
251*0e96539fSCharles.Forsyth	return lex1(ps, 1);
252*0e96539fSCharles.Forsyth}
253*0e96539fSCharles.Forsyth
254*0e96539fSCharles.Forsyth#
255*0e96539fSCharles.Forsyth# rfc 2822/rfc 1521 lexical analyzer
256*0e96539fSCharles.Forsyth#
257*0e96539fSCharles.Forsythlex1(ps: ref Rfclex, skipwhite: int): int
258*0e96539fSCharles.Forsyth{
259*0e96539fSCharles.Forsyth	ps.wordval = nil;
260*0e96539fSCharles.Forsyth	while((c := ps.getc()) >= 0){
261*0e96539fSCharles.Forsyth		case c {
262*0e96539fSCharles.Forsyth		 '(' =>
263*0e96539fSCharles.Forsyth			level := 1;
264*0e96539fSCharles.Forsyth			while((c = ps.getc()) != Bufio->EOF && c != '\n'){
265*0e96539fSCharles.Forsyth				if(c == '\\'){
266*0e96539fSCharles.Forsyth					c = ps.getc();
267*0e96539fSCharles.Forsyth					if(c == Bufio->EOF)
268*0e96539fSCharles.Forsyth						return '\n';
269*0e96539fSCharles.Forsyth					continue;
270*0e96539fSCharles.Forsyth				}
271*0e96539fSCharles.Forsyth				if(c == '(')
272*0e96539fSCharles.Forsyth					level++;
273*0e96539fSCharles.Forsyth				else if(c == ')' && --level == 0)
274*0e96539fSCharles.Forsyth					break;
275*0e96539fSCharles.Forsyth			}
276*0e96539fSCharles.Forsyth 		' ' or '\t' or '\r' or 0 =>
277*0e96539fSCharles.Forsyth			;
278*0e96539fSCharles.Forsyth 		'\n' =>
279*0e96539fSCharles.Forsyth			return '\n';
280*0e96539fSCharles.Forsyth		')' or '<' or '>' or '[' or ']' or '@' or '/' or ',' or
281*0e96539fSCharles.Forsyth		';' or ':' or '?' or '=' =>
282*0e96539fSCharles.Forsyth			if(skipwhite){
283*0e96539fSCharles.Forsyth				ps.ungetc();
284*0e96539fSCharles.Forsyth				return c;
285*0e96539fSCharles.Forsyth			}
286*0e96539fSCharles.Forsyth			return c;
287*0e96539fSCharles.Forsyth
288*0e96539fSCharles.Forsyth 		'"' =>
289*0e96539fSCharles.Forsyth			if(skipwhite){
290*0e96539fSCharles.Forsyth				ps.ungetc();
291*0e96539fSCharles.Forsyth				return c;
292*0e96539fSCharles.Forsyth			}
293*0e96539fSCharles.Forsyth			word(ps,"\"");
294*0e96539fSCharles.Forsyth			ps.getc();		# skip the closing quote
295*0e96539fSCharles.Forsyth			return QString;
296*0e96539fSCharles.Forsyth
297*0e96539fSCharles.Forsyth 		* =>
298*0e96539fSCharles.Forsyth			ps.ungetc();
299*0e96539fSCharles.Forsyth			if(skipwhite)
300*0e96539fSCharles.Forsyth				return c;
301*0e96539fSCharles.Forsyth			word(ps,"\"()<>@,;:/[]?={}\r\n \t");
302*0e96539fSCharles.Forsyth			return Word;
303*0e96539fSCharles.Forsyth		}
304*0e96539fSCharles.Forsyth	}
305*0e96539fSCharles.Forsyth	return '\n';
306*0e96539fSCharles.Forsyth}
307*0e96539fSCharles.Forsyth
308*0e96539fSCharles.Forsyth# return the rest of an rfc 822 line, not including \r or \n
309*0e96539fSCharles.Forsyth# do not map to lower case
310*0e96539fSCharles.Forsyth
311*0e96539fSCharles.ForsythRfclex.line(ps: self ref Rfclex): string
312*0e96539fSCharles.Forsyth{
313*0e96539fSCharles.Forsyth	s := "";
314*0e96539fSCharles.Forsyth	while((c := ps.getc()) != Bufio->EOF && c != '\n' && c != '\r'){
315*0e96539fSCharles.Forsyth		if(c == '\\'){
316*0e96539fSCharles.Forsyth			c = ps.getc();
317*0e96539fSCharles.Forsyth			if(c == Bufio->EOF)
318*0e96539fSCharles.Forsyth				break;
319*0e96539fSCharles.Forsyth		}
320*0e96539fSCharles.Forsyth		s[len s] = c;
321*0e96539fSCharles.Forsyth	}
322*0e96539fSCharles.Forsyth	ps.tok = '\n';
323*0e96539fSCharles.Forsyth	ps.wordval = s;
324*0e96539fSCharles.Forsyth	return s;
325*0e96539fSCharles.Forsyth}
326*0e96539fSCharles.Forsyth
327*0e96539fSCharles.Forsythword(ps: ref Rfclex, stop: string)
328*0e96539fSCharles.Forsyth{
329*0e96539fSCharles.Forsyth	w := "";
330*0e96539fSCharles.Forsyth	while((c := ps.getc()) != Bufio->EOF){
331*0e96539fSCharles.Forsyth		if(c == '\r')
332*0e96539fSCharles.Forsyth			c = ' ';
333*0e96539fSCharles.Forsyth		if(c == '\\'){
334*0e96539fSCharles.Forsyth			c = ps.getc();
335*0e96539fSCharles.Forsyth			if(c == Bufio->EOF)
336*0e96539fSCharles.Forsyth				break;
337*0e96539fSCharles.Forsyth		}else if(str->in(c,stop)){
338*0e96539fSCharles.Forsyth			ps.ungetc();
339*0e96539fSCharles.Forsyth			break;
340*0e96539fSCharles.Forsyth		}
341*0e96539fSCharles.Forsyth		if(c >= 'A' && c <= 'Z')
342*0e96539fSCharles.Forsyth			c += 'a' - 'A';
343*0e96539fSCharles.Forsyth		w[len w] = c;
344*0e96539fSCharles.Forsyth	}
345*0e96539fSCharles.Forsyth	ps.wordval = w;
346*0e96539fSCharles.Forsyth}
347*0e96539fSCharles.Forsyth
348*0e96539fSCharles.Forsythreadsuffixfile(): string
349*0e96539fSCharles.Forsyth{
350*0e96539fSCharles.Forsyth	iob := bufio->open(SuffixFile, Bufio->OREAD);
351*0e96539fSCharles.Forsyth	if(iob == nil)
352*0e96539fSCharles.Forsyth		return sys->sprint("cannot open %s: %r", SuffixFile);
353*0e96539fSCharles.Forsyth	for(n := 1; (line := iob.gets('\n')) != nil; n++){
354*0e96539fSCharles.Forsyth		(s, nil) := parsesuffix(line);
355*0e96539fSCharles.Forsyth		if(s != nil)
356*0e96539fSCharles.Forsyth			suffixes =  s :: suffixes;
357*0e96539fSCharles.Forsyth	}
358*0e96539fSCharles.Forsyth	return nil;
359*0e96539fSCharles.Forsyth}
360*0e96539fSCharles.Forsyth
361*0e96539fSCharles.Forsythparsesuffix(line: string): (ref Suffix, string)
362*0e96539fSCharles.Forsyth{
363*0e96539fSCharles.Forsyth	(line, nil) = str->splitstrl(line, "#");
364*0e96539fSCharles.Forsyth	if(line == nil)
365*0e96539fSCharles.Forsyth		return (nil, nil);
366*0e96539fSCharles.Forsyth	(n, slist) := sys->tokenize(line,"\n\t ");
367*0e96539fSCharles.Forsyth	if(n == 0)
368*0e96539fSCharles.Forsyth		return (nil, nil);
369*0e96539fSCharles.Forsyth	if(n < 4)
370*0e96539fSCharles.Forsyth		return (nil, "too few fields");
371*0e96539fSCharles.Forsyth	s := ref Suffix;
372*0e96539fSCharles.Forsyth	s.suffix = hd slist;
373*0e96539fSCharles.Forsyth	slist = tl slist;
374*0e96539fSCharles.Forsyth	s.generic = hd slist;
375*0e96539fSCharles.Forsyth	if (s.generic == "-")
376*0e96539fSCharles.Forsyth		s.generic = "";
377*0e96539fSCharles.Forsyth	slist = tl slist;
378*0e96539fSCharles.Forsyth	s.specific = hd slist;
379*0e96539fSCharles.Forsyth	if (s.specific == "-")
380*0e96539fSCharles.Forsyth		s.specific = "";
381*0e96539fSCharles.Forsyth	slist = tl slist;
382*0e96539fSCharles.Forsyth	s.encoding = hd slist;
383*0e96539fSCharles.Forsyth	if (s.encoding == "-")
384*0e96539fSCharles.Forsyth		s.encoding = "";
385*0e96539fSCharles.Forsyth	if((s.generic == nil || s.specific == nil) && s.encoding == nil)
386*0e96539fSCharles.Forsyth		return (nil, nil);
387*0e96539fSCharles.Forsyth	return (s, nil);
388*0e96539fSCharles.Forsyth}
389*0e96539fSCharles.Forsyth
390*0e96539fSCharles.Forsyth#
391*0e96539fSCharles.Forsyth# classify by file suffix
392*0e96539fSCharles.Forsyth#
393*0e96539fSCharles.Forsythsuffixclass(name: string): (ref Content, ref Content)
394*0e96539fSCharles.Forsyth{
395*0e96539fSCharles.Forsyth	typ, enc: ref Content;
396*0e96539fSCharles.Forsyth
397*0e96539fSCharles.Forsyth	p := str->splitstrr(name, "/").t1;
398*0e96539fSCharles.Forsyth	if(p != nil)
399*0e96539fSCharles.Forsyth		name = p;
400*0e96539fSCharles.Forsyth
401*0e96539fSCharles.Forsyth	for(;;){
402*0e96539fSCharles.Forsyth		(name, p) = suffix(name);	# TO DO: match below is case sensitive
403*0e96539fSCharles.Forsyth		if(p == nil)
404*0e96539fSCharles.Forsyth			break;
405*0e96539fSCharles.Forsyth		for(l := suffixes; l != nil; l = tl l){
406*0e96539fSCharles.Forsyth			s := hd l;
407*0e96539fSCharles.Forsyth			if(p == s.suffix){
408*0e96539fSCharles.Forsyth				if(s.generic != nil && typ == nil)
409*0e96539fSCharles.Forsyth					typ = Content.mk(s.generic, s.specific, nil);
410*0e96539fSCharles.Forsyth				if(s.encoding != nil && enc == nil)
411*0e96539fSCharles.Forsyth					enc = Content.mk(s.encoding, "", nil);
412*0e96539fSCharles.Forsyth				if(typ != nil && enc != nil)
413*0e96539fSCharles.Forsyth					break;
414*0e96539fSCharles.Forsyth			}
415*0e96539fSCharles.Forsyth		}
416*0e96539fSCharles.Forsyth	}
417*0e96539fSCharles.Forsyth	return (typ, enc);
418*0e96539fSCharles.Forsyth}
419*0e96539fSCharles.Forsyth
420*0e96539fSCharles.Forsythsuffix(s: string): (string, string)
421*0e96539fSCharles.Forsyth{
422*0e96539fSCharles.Forsyth	for(n := len s; --n >= 0;)
423*0e96539fSCharles.Forsyth		if(s[n] == '.')
424*0e96539fSCharles.Forsyth			return (s[0: n], s[n:]);
425*0e96539fSCharles.Forsyth	return (s, nil);
426*0e96539fSCharles.Forsyth}
427*0e96539fSCharles.Forsyth
428*0e96539fSCharles.Forsyth#
429*0e96539fSCharles.Forsyth#  classify by initial contents of file
430*0e96539fSCharles.Forsyth#
431*0e96539fSCharles.Forsythdataclass(a: array of byte): (ref Content, ref Content)
432*0e96539fSCharles.Forsyth{
433*0e96539fSCharles.Forsyth	utf8 := 0;
434*0e96539fSCharles.Forsyth	for(i := 0; i < len a;){
435*0e96539fSCharles.Forsyth		c := int a[i];
436*0e96539fSCharles.Forsyth		if(c < 16r80){
437*0e96539fSCharles.Forsyth			if(c < 32 && c != '\n' && c != '\r' && c != '\t' && c != '\v' && c != '\f')
438*0e96539fSCharles.Forsyth				return (nil, nil);
439*0e96539fSCharles.Forsyth			i++;
440*0e96539fSCharles.Forsyth		}else{
441*0e96539fSCharles.Forsyth			utf8 = 1;
442*0e96539fSCharles.Forsyth			(r, l, nil) := sys->byte2char(a, i);
443*0e96539fSCharles.Forsyth			if(r == Sys->UTFerror)
444*0e96539fSCharles.Forsyth				return (nil, nil);
445*0e96539fSCharles.Forsyth			i += l;
446*0e96539fSCharles.Forsyth		}
447*0e96539fSCharles.Forsyth	}
448*0e96539fSCharles.Forsyth	if(utf8)
449*0e96539fSCharles.Forsyth		params := ("charset", "utf-8") :: nil;
450*0e96539fSCharles.Forsyth	return (Content.mk("text", "plain", params), nil);
451*0e96539fSCharles.Forsyth}
452*0e96539fSCharles.Forsyth
453*0e96539fSCharles.ForsythContent.mk(generic, specific: string, params: list of (string, string)): ref Content
454*0e96539fSCharles.Forsyth{
455*0e96539fSCharles.Forsyth	c := ref Content;
456*0e96539fSCharles.Forsyth	c.generic = generic;
457*0e96539fSCharles.Forsyth	c.specific = specific;
458*0e96539fSCharles.Forsyth	c.params = params;
459*0e96539fSCharles.Forsyth	return c;
460*0e96539fSCharles.Forsyth}
461*0e96539fSCharles.Forsyth
462*0e96539fSCharles.ForsythContent.check(me: self ref Content, oks: list of ref Content): int
463*0e96539fSCharles.Forsyth{
464*0e96539fSCharles.Forsyth	if(oks == nil)
465*0e96539fSCharles.Forsyth		return 1;
466*0e96539fSCharles.Forsyth	g := str->tolower(me.generic);
467*0e96539fSCharles.Forsyth	s := str->tolower(me.specific);
468*0e96539fSCharles.Forsyth	for(; oks != nil; oks = tl oks){
469*0e96539fSCharles.Forsyth		ok := hd oks;
470*0e96539fSCharles.Forsyth		if((ok.generic == g || ok.generic=="*") &&
471*0e96539fSCharles.Forsyth		   (s == nil || ok.specific == s || ok.specific=="*"))
472*0e96539fSCharles.Forsyth			return 1;
473*0e96539fSCharles.Forsyth	}
474*0e96539fSCharles.Forsyth	return 0;
475*0e96539fSCharles.Forsyth}
476*0e96539fSCharles.Forsyth
477*0e96539fSCharles.ForsythContent.text(c: self ref Content): string
478*0e96539fSCharles.Forsyth{
479*0e96539fSCharles.Forsyth	if((s := c.specific) != nil)
480*0e96539fSCharles.Forsyth		s = c.generic+"/"+s;
481*0e96539fSCharles.Forsyth	else
482*0e96539fSCharles.Forsyth		s = c.generic;
483*0e96539fSCharles.Forsyth	for(l := c.params; l != nil; l = tl l){
484*0e96539fSCharles.Forsyth		(n, v) := hd l;
485*0e96539fSCharles.Forsyth		s += sys->sprint(";%s=%s", n, quote(v));
486*0e96539fSCharles.Forsyth	}
487*0e96539fSCharles.Forsyth	return s;
488*0e96539fSCharles.Forsyth}
489*0e96539fSCharles.Forsyth
490*0e96539fSCharles.Forsyth#
491*0e96539fSCharles.Forsyth# should probably be in a Mime or HTTP module
492*0e96539fSCharles.Forsyth#
493*0e96539fSCharles.Forsyth
494*0e96539fSCharles.ForsythQuotable: con "()<>@,;:\\\"/[]?={} \t";
495*0e96539fSCharles.Forsyth
496*0e96539fSCharles.Forsythquotable(s: string): int
497*0e96539fSCharles.Forsyth{
498*0e96539fSCharles.Forsyth	for(i := 0; i < len s; i++)
499*0e96539fSCharles.Forsyth		if(str->in(s[i], Quotable))
500*0e96539fSCharles.Forsyth			return 1;
501*0e96539fSCharles.Forsyth	return 0;
502*0e96539fSCharles.Forsyth}
503*0e96539fSCharles.Forsyth
504*0e96539fSCharles.Forsythquote(s: string): string
505*0e96539fSCharles.Forsyth{
506*0e96539fSCharles.Forsyth	if(!quotable(s))
507*0e96539fSCharles.Forsyth		return s;
508*0e96539fSCharles.Forsyth	q :=  "\"";
509*0e96539fSCharles.Forsyth	for(i := 0; i < len s; i++){
510*0e96539fSCharles.Forsyth		if(str->in(s[i], Quotable))
511*0e96539fSCharles.Forsyth			q[len q] = '\\';
512*0e96539fSCharles.Forsyth		q[len q] = s[i];
513*0e96539fSCharles.Forsyth	}
514*0e96539fSCharles.Forsyth	q[len q] = '"';
515*0e96539fSCharles.Forsyth	return q;
516*0e96539fSCharles.Forsyth}
517*0e96539fSCharles.Forsyth
518*0e96539fSCharles.Forsythweekdays := array[] of {
519*0e96539fSCharles.Forsyth	"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
520*0e96539fSCharles.Forsyth};
521*0e96539fSCharles.Forsyth
522*0e96539fSCharles.Forsythmonths := array[] of {
523*0e96539fSCharles.Forsyth	"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
524*0e96539fSCharles.Forsyth};
525*0e96539fSCharles.Forsyth
526*0e96539fSCharles.Forsyth# print dates in the format
527*0e96539fSCharles.Forsyth# Wkd, DD Mon YYYY HH:MM:SS GMT
528*0e96539fSCharles.Forsyth
529*0e96539fSCharles.Forsythsec2date(t: int): string
530*0e96539fSCharles.Forsyth{
531*0e96539fSCharles.Forsyth	tm := daytime->gmt(t);
532*0e96539fSCharles.Forsyth	return sys->sprint("%s, %.2d %s %.4d %.2d:%.2d:%.2d GMT",
533*0e96539fSCharles.Forsyth		weekdays[tm.wday], tm.mday, months[tm.mon], tm.year+1900,
534*0e96539fSCharles.Forsyth		tm.hour, tm.min, tm.sec);
535*0e96539fSCharles.Forsyth}
536*0e96539fSCharles.Forsyth
537*0e96539fSCharles.Forsyth# parse dates of formats
538*0e96539fSCharles.Forsyth# Wkd, DD Mon YYYY HH:MM:SS GMT
539*0e96539fSCharles.Forsyth# Weekday, DD-Mon-YY HH:MM:SS GMT
540*0e96539fSCharles.Forsyth# Wkd Mon ( D|DD) HH:MM:SS YYYY
541*0e96539fSCharles.Forsyth# plus anything similar
542*0e96539fSCharles.Forsyth
543*0e96539fSCharles.Forsythdate2sec(date: string): int
544*0e96539fSCharles.Forsyth{
545*0e96539fSCharles.Forsyth	tm := daytime->string2tm(date);
546*0e96539fSCharles.Forsyth	if(tm == nil || tm.year < 70 || tm.zone != "GMT")
547*0e96539fSCharles.Forsyth		t := 0;
548*0e96539fSCharles.Forsyth	else
549*0e96539fSCharles.Forsyth		t = daytime->tm2epoch(tm);
550*0e96539fSCharles.Forsyth	return t;
551*0e96539fSCharles.Forsyth}
552*0e96539fSCharles.Forsyth
553*0e96539fSCharles.Forsythnow(): int
554*0e96539fSCharles.Forsyth{
555*0e96539fSCharles.Forsyth	return daytime->now();
556*0e96539fSCharles.Forsyth}
557*0e96539fSCharles.Forsyth
558*0e96539fSCharles.Forsythtime(): string
559*0e96539fSCharles.Forsyth{
560*0e96539fSCharles.Forsyth	return sec2date(daytime->now());
561*0e96539fSCharles.Forsyth}
562