xref: /inferno-os/appl/charon/chutils.b (revision e45fa0eb0763b57d6fb0649c064bc3b95ccdea6c)
1implement CharonUtils;
2
3include "common.m";
4include "transport.m";
5include "date.m";
6include "translate.m";
7
8	date: Date;
9	me: CharonUtils;
10	sys: Sys;
11	D: Draw;
12	S: String;
13	U: Url;
14	T: StringIntTab;
15
16Font : import D;
17Parsedurl: import U;
18convcs : Convcs;
19trans : Translate;
20	Dict : import trans;
21dict : ref Dict;
22
23NCTimeout : con 100000;		# free NC slot after 100 seconds
24UBufsize : con 40*1024;		# initial buffer size for unknown lengths
25UEBufsize : con 1024;		# initial buffer size for unknown lengths, error responses
26
27botchexception := "EXInternal: ByteSource protocol botch";
28bytesourceid := 0;
29crlf : con "\r\n";
30ctype : array of byte;	# local ref to C->ctype[]
31dbgproto : int;
32dbg: int;
33netconnid := 0;
34netconns := array[10] of ref Netconn;
35sptab : con " \t";
36
37THTTP, TFTP, TFILE, TMAX: con iota;
38transports := array[TMAX] of Transport;
39tpaths := array [TMAX] of {
40	THTTP =>	Transport->HTTPPATH,
41	TFTP =>	Transport->FTPPATH,
42	TFILE =>	Transport->FILEPATH,
43};
44
45schemes := array [] of {
46	("http", 	THTTP),
47	("https",	THTTP),
48	("ftp",	TFTP),
49	("file",	TFILE),
50};
51
52ngchan : chan of (int, list of ref ByteSource, ref Netconn, chan of ref ByteSource);
53
54# must track HTTP methods in chutils.m
55# (upper-case, since that's required in HTTP requests)
56hmeth = array[] of { "GET", "POST" };
57
58# following array must track media type def in chutils.m
59# keep in alphabetical order
60mnames = array[] of {
61	"application/msword",
62	"application/octet-stream",
63	"application/pdf",
64	"application/postscript",
65	"application/rtf",
66	"application/vnd.framemaker",
67	"application/vnd.ms-excel",
68	"application/vnd.ms-powerpoint",
69	"application/x-unknown",
70	"audio/32kadpcm",
71	"audio/basic",
72	"image/cgm",
73	"image/g3fax",
74	"image/gif",
75	"image/ief",
76	"image/jpeg",
77	"image/png",
78	"image/tiff",
79	"image/x-bit",
80	"image/x-bit2",
81	"image/x-bitmulti",
82	"image/x-inferno-bit",
83	"image/x-xbitmap",
84	"model/vrml",
85	"multipart/digest",
86	"multipart/mixed",
87	"text/css",
88	"text/enriched",
89	"text/html",
90	"text/javascript",
91	"text/plain",
92	"text/richtext",
93	"text/sgml",
94	"text/tab-separated-values",
95	"text/xml",
96	"video/mpeg",
97	"video/quicktime"
98};
99
100ncstatenames = array[] of {
101	"free", "idle", "connect", "gethdr", "getdata",
102	"done", "err"
103};
104
105hsnames = array[] of {
106	"none", "information", "ok", "redirect", "request error", "server error"
107};
108
109hcphrase(code: int) : string
110{
111	ans : string;
112	case code {
113	HCContinue =>				ans = X("Continue", "http");
114	HCSwitchProto =>			ans = X("Switching Protocols", "http");
115	HCOk =>					ans = X("Ok", "http");
116	HCCreated =>				ans = X("Created", "http");
117	HCAccepted =>				ans = X("Accepted", "http");
118	HCOkNonAuthoritative =>		ans = X("Non-Authoratative Information", "http");
119	HCNoContent =>			ans = X("No content", "http");
120	HCResetContent =>			ans = X("Reset content", "http");
121	HCPartialContent =>			ans = X("Partial content", "http");
122	HCMultipleChoices =>		ans = X("Multiple choices", "http");
123	HCMovedPerm =>			ans = X("Moved permanently", "http");
124	HCMovedTemp =>			ans = X("Moved temporarily", "http");
125	HCSeeOther =>				ans = X("See other", "http");
126	HCNotModified =>			ans = X("Not modified", "http");
127	HCUseProxy =>				ans = X("Use proxy", "http");
128	HCBadRequest =>			ans = X("Bad request", "http");
129	HCUnauthorized =>			ans = X("Unauthorized", "http");
130	HCPaymentRequired =>		ans = X("Payment required", "http");
131	HCForbidden =>			ans = X("Forbidden", "http");
132	HCNotFound =>			ans = X("Not found", "http");
133	HCMethodNotAllowed =>		ans = X("Method not allowed", "http");
134	HCNotAcceptable =>			ans = X("Not Acceptable", "http");
135	HCProxyAuthRequired =>		ans = X("Proxy authentication required", "http");
136	HCRequestTimeout =>		ans = X("Request timed-out", "http");
137	HCConflict =>				ans = X("Conflict", "http");
138	HCGone =>				ans = X("Gone", "http");
139	HCLengthRequired =>		ans = X("Length required", "http");
140	HCPreconditionFailed =>		ans = X("Precondition failed", "http");
141	HCRequestTooLarge =>		ans = X("Request entity too large", "http");
142	HCRequestURITooLarge =>	ans = X("Request-URI too large", "http");
143	HCUnsupportedMedia =>		ans = X("Unsupported media type", "http");
144	HCRangeInvalid =>			ans = X("Requested range not valid", "http");
145	HCExpectFailed =>			ans = X("Expectation failed", "http");
146	HCServerError =>			ans = X("Internal server error", "http");
147	HCNotImplemented =>		ans = X("Not implemented", "http");
148	HCBadGateway =>			ans = X("Bad gateway", "http");
149	HCServiceUnavailable =>		ans = X("Service unavailable", "http");
150	HCGatewayTimeout =>		ans = X("Gateway time-out", "http");
151	HCVersionUnsupported =>	ans = X("HTTP version not supported", "http");
152	HCRedirectionFailed =>		ans = X("Redirection failed", "http");
153	* =>						ans = X("Unknown code", "http");
154	}
155	return ans;
156}
157
158# This array should be kept sorted
159fileexttable := array[] of { T->StringInt
160	("ai", ApplPostscript),
161	("au", AudioBasic),
162# ("bit", ImageXBit),
163	("bit", ImageXInfernoBit),
164	("bit2", ImageXBit2),
165	("bitm", ImageXBitmulti),
166	("eps", ApplPostscript),
167	("gif", ImageGif),
168	("gz",	ApplOctets),
169	("htm", TextHtml),
170	("html", TextHtml),
171	("jpe", ImageJpeg),
172	("jpeg", ImageJpeg),
173	("jpg", ImageJpeg),
174	("pdf", ApplPdf),
175	("png", ImagePng),
176	("ps", ApplPostscript),
177	("shtml", TextHtml),
178	("text", TextPlain),
179	("tif", ImageTiff),
180	("tiff", ImageTiff),
181	("txt", TextPlain),
182	("zip", ApplOctets)
183};
184
185# argl is command line
186# Return string that is empty if all ok, else path of module
187# that failed to load.
188init(ch: Charon, c: CharonUtils, argl: list of string, evc: chan of ref E->Event, cksrv: Cookiesrv, ckc: ref Cookiesrv->Client) : string
189{
190	me = c;
191	sys = load Sys Sys->PATH;
192	startres = ResourceState.cur();
193	D = load Draw Draw->PATH;
194	CH = ch;
195	S = load String String->PATH;
196	if(S == nil)
197		return String->PATH;
198
199	U = load Url Url->PATH;
200	if(U == nil)
201		return Url->PATH;
202	U->init();
203
204	DI = load Dial Dial->PATH;
205	if(DI == nil)
206		return Dial->PATH;
207
208	T = load StringIntTab StringIntTab->PATH;
209	if(T == nil)
210		return StringIntTab->PATH;
211
212	trans = load Translate Translate->PATH;
213	if (trans != nil) {
214		trans->init();
215		(dict, nil) = trans->opendict(trans->mkdictname(nil, "charon"));
216	}
217
218	# Now have all the modules needed to process command line
219	# (hereafter can use our loadpath() function to substitute the
220	# build directory version if dbg['u'] is set)
221
222	setconfig(argl);
223	dbg = int config.dbg['d'];
224
225	G = load Gui loadpath(Gui->PATH);
226	if(G == nil)
227		return loadpath(Gui->PATH);
228
229	C = load Ctype loadpath(Ctype->PATH);
230	if(C == nil)
231		return loadpath(Ctype->PATH);
232
233	E = load Events Events->PATH;
234	if(E == nil)
235		return loadpath(Events->PATH);
236
237	J = load Script loadpath(Script->JSCRIPTPATH);
238	# don't report an error loading JavaScript, handled elsewhere
239
240	LX = load Lex loadpath(Lex->PATH);
241	if(LX == nil)
242		return loadpath(Lex->PATH);
243
244	B = load Build loadpath(Build->PATH);
245	if(B == nil)
246		return loadpath(Build->PATH);
247
248	I = load Img loadpath(Img->PATH);
249	if(I == nil)
250		return loadpath(Img->PATH);
251
252	L = load Layout loadpath(Layout->PATH);
253	if(L == nil)
254		return loadpath(Layout->PATH);
255	date = load Date loadpath(Date->PATH);
256	if (date == nil)
257		return loadpath(Date->PATH);
258
259	convcs = load Convcs Convcs->PATH;
260	if (convcs == nil)
261		return loadpath(Convcs->PATH);
262
263
264	# Intialize all modules after loading all, so that each
265	# may cache pointers to the other modules
266	# (G will be initialized in main charon routine, and L has to
267	# be inited after that, because it needs G's display to allocate fonts)
268
269	E->init(evc);
270	I->init(me);
271	err := convcs->init(nil);
272	if (err != nil)
273		return err;
274	if(J != nil) {
275		err = J->init(me);
276		if (err != nil) {
277			# non-fatal: just don't handle javascript
278			J = nil;
279			if (dbg)
280				sys->print("%s\n", err);
281		}
282	}
283	B->init(me);
284	LX->init(me);
285	date->init(me);
286
287	if (config.docookies) {
288		CK = cksrv;
289		ckclient = ckc;
290		if (CK == nil) {
291			path := loadpath(Cookiesrv->PATH);
292			CK = load Cookiesrv path;
293			if (CK == nil)
294				sys->print("cookies: cannot load server %s: %r\n", path);
295			else
296				ckclient = CK->start(config.userdir + "/cookies", 0);
297		}
298	}
299
300	# preload some transports
301	gettransport("http");
302	gettransport("file");
303
304	progresschan = chan of (int, int, int, string);
305	imcache = ref ImageCache;
306	ctype = C->ctype;
307	dbgproto = int config.dbg['p'];
308	ngchan = chan of (int, list of ref ByteSource, ref Netconn, chan of ref ByteSource);
309	return "";
310}
311
312# like startreq() but special case for a string ByteSource
313# which doesn't need an associated netconn
314stringreq(s : string) : ref ByteSource
315{
316	bs := ByteSource.stringsource(s);
317
318	G->progress <-= (bs.id, G->Pstart, 0, "text");
319	anschan := chan of ref ByteSource;
320	ngchan <-= (NGstartreq, bs :: nil, nil, anschan);
321	<-anschan;
322	return bs;
323}
324
325# Make a ByteSource for given request, and make sure
326# that it is on the queue of some Netconn.
327# If don't have a transport for the request's scheme,
328# the returned bs will have err set.
329startreq(req: ref ReqInfo) : ref ByteSource
330{
331	bs := ref ByteSource(
332			bytesourceid++,
333			req,		# req
334			nil,		# hdr
335			nil,		# data
336			0,		# edata
337			"",		# err
338			nil,		# net
339			1,		# refgo
340			1,		# refnc
341			0,		# eof
342			0,		# lim
343			0		# seenhdr
344		);
345
346	G->progress <-= (bs.id, G->Pstart, 0, req.url.tostring());
347	anschan := chan of ref ByteSource;
348	ngchan <-= (NGstartreq, bs::nil, nil, anschan);
349	<-anschan;
350	return bs;
351}
352
353# Wait for some ByteSource in current go generation to
354# have a state change that goproc hasn't seen yet.
355waitreq(bsl: list of ref ByteSource) : ref ByteSource
356{
357	anschan := chan of ref ByteSource;
358	ngchan <-= (NGwaitreq, bsl, nil, anschan);
359	return <-anschan;
360}
361
362# Notify netget that goproc is finished with bs.
363freebs(bs: ref ByteSource)
364{
365	anschan := chan of ref ByteSource;
366	ngchan <-= (NGfreebs, bs::nil, nil, anschan);
367	<-anschan;
368}
369
370abortgo(gopgrp: int)
371{
372	if(int config.dbg['d'])
373		sys->print("abort go\n");
374	kill(gopgrp, 1);
375	freegoresources();
376	# renew the channels so that receives/sends by killed threads don't
377	# muck things up
378	ngchan = chan of (int, list of ref ByteSource, ref Netconn, chan of ref ByteSource);
379}
380
381freegoresources()
382{
383	for(i := 0; i < len netconns; i++) {
384		nc := netconns[i];
385		nc.makefree();
386	}
387}
388
389# This runs as a separate thread.
390# It acts as a monitor to synchronize access to the Netconn data
391# structures, as a dispatcher to start runnetconn's as needed to
392# process work on Netconn queues, and as a notifier to let goproc
393# know when any ByteSources have advanced their state.
394netget()
395{
396	msg, n, i: int;
397	bsl : list of ref ByteSource;
398	nc: ref Netconn;
399	waitix := 0;
400	c : chan of ref ByteSource;
401	waitpending : list of (list of ref ByteSource, chan of ref ByteSource);
402	maxconn := config.nthreads;
403	gncs := array[maxconn] of int;
404
405	for(n = 0; n < len netconns; n++)
406		netconns[n] = Netconn.new(n);
407
408	# capture netget chan to prevent abortgo() reset of
409	# ngchan from breaking us (channel hungup) before kill() does its job
410	ngc := ngchan;
411mainloop:
412	for(;;) {
413		(msg,bsl,nc,c) = <- ngc;
414		case msg {
415		NGstartreq =>
416			bs := hd bsl;
417			# bs has req filled in, and is otherwise in its initial state.
418			# Find a suitable Netconn and add bs to its queue of work,
419			# then send nil along c to let goproc continue.
420
421			# if ReqInfo is nil then this is a string ByteSource
422			# in which case we don't need a netconn to service it as we have all
423			# data already
424			if (bs.req == nil) {
425				c <- = nil;
426				continue;
427			}
428
429			if(dbgproto)
430				sys->print("Startreq BS=%d for %s\n", bs.id, bs.req.url.tostring());
431			scheme := bs.req.url.scheme;
432			host := bs.req.url.host;
433			(transport, err) := gettransport(scheme);
434			if(err != "")
435				bs.err = err;
436			else {
437				sport :=bs.req.url.port;
438				if(sport == "")
439					port := transport->defaultport(scheme);
440				else
441					port = int sport;
442				i = 0;
443				freen := -1;
444				for(n = 0; n < len netconns && (i < maxconn || freen == -1); n++) {
445					nc = netconns[n];
446					if(nc.state == NCfree) {
447						if(freen == -1)
448							freen = n;
449					}
450					else if(nc.host == host
451					   && nc.port == port && nc.scheme == scheme && i < maxconn) {
452						gncs[i++] = n;
453					}
454				}
455				if(i < maxconn) {
456					# use a new netconn for this bs
457					if(freen == -1) {
458						freen = len netconns;
459						newncs := array[freen+10] of ref Netconn;
460						newncs[0:] = netconns;
461						for(n = freen; n < freen+10; n++)
462							newncs[n] = Netconn.new(n);
463						netconns = newncs;
464					}
465					nc = netconns[freen];
466					nc.host = host;
467					nc.port = port;
468					nc.scheme = scheme;
469					nc.qlen = 0;
470					nc.ngcur = 0;
471					nc.gocur = 0;
472					nc.reqsent = 0;
473					nc.pipeline = 0;
474					nc.connected = 0;
475				}
476				else {
477					# use existing netconn with fewest outstanding requests
478					nc = netconns[gncs[0]];
479					if(maxconn > 1) {
480						minqlen := nc.qlen - nc.gocur;
481						for(i = 1; i < maxconn; i++) {
482							x := netconns[gncs[i]];
483							if(x.qlen-x.gocur < minqlen) {
484								nc = x;
485								minqlen = x.qlen-x.gocur;
486							}
487						}
488					}
489				}
490				if(nc.qlen == len nc.queue) {
491					nq := array[nc.qlen+10] of ref ByteSource;
492					nq[0:] = nc.queue;
493					nc.queue = nq;
494				}
495				nc.queue[nc.qlen++] = bs;
496				bs.net = nc;
497				if(dbgproto)
498					sys->print("Chose NC=%d for BS %d, qlen=%d\n", nc.id, bs.id, nc.qlen);
499				if(nc.state == NCfree || nc.state == NCidle) {
500					if(nc.connected) {
501						nc.state = NCgethdr;
502						if(dbgproto)
503							sys->print("NC %d: starting runnetconn in gethdr state\n", nc.id);
504					}
505					else {
506						nc.state = NCconnect;
507						if(dbgproto)
508							sys->print("NC %d: starting runnetconn in connect state\n", nc.id);
509					}
510					spawn runnetconn(nc, transport);
511				}
512			}
513			c <-= nil;
514
515		NGwaitreq =>
516			# goproc wants to be notified when some ByteSource
517			# changes to a state that the goproc hasn't seen yet.
518			# Send such a ByteSource along return channel c.
519
520			if(dbgproto)
521				sys->print("Waitreq\n");
522
523			for (scanlist := bsl; scanlist != nil; scanlist = tl scanlist) {
524				bs := hd scanlist;
525				if (bs.refnc == 0) {
526					# string ByteSource or completed or error
527					if (bs.err != nil || bs.edata >= bs.lim) {
528						c <-= bs;
529						continue mainloop;
530					}
531					continue;
532				}
533				# netcon based bytesource
534				if ((bs.hdr != nil && !bs.seenhdr && bs.hdr.mtype != UnknownType) || bs.edata > bs.lim) {
535					c <-= bs;
536					continue mainloop;
537				}
538			}
539
540			if(dbgproto)
541				sys->print("Waitpending\n");
542			waitpending = (bsl, c) :: waitpending;
543
544		NGfreebs =>
545			# goproc is finished with bs.
546			bs := hd bsl;
547
548			if(dbgproto)
549				sys->print("Freebs BS=%d\n", bs.id);
550			nc = bs.net;
551			bs.refgo = 0;
552			if(bs.refnc == 0) {
553				bs.free();
554				if(nc != nil)
555					nc.queue[nc.gocur] = nil;
556			}
557			if(nc != nil) {
558				# can be nil if no transport was found
559				nc.gocur++;
560				if(dbgproto)
561					sys->print("NC %d: gocur=%d, ngcur=%d, qlen=%d\n", nc.id, nc.gocur, nc.ngcur, nc.qlen);
562				if(nc.gocur == nc.qlen && nc.ngcur == nc.qlen) {
563					if(!nc.connected)
564						nc.makefree();
565				}
566			}
567			# don't need to check waitpending fro NGwait requests involving bs
568			# the only thread doing a freebs() should be the only thread that
569			# can do a waitreq() on the same bs.  Same thread cannot be in both states.
570
571			c <-= nil;
572
573		NGstatechg =>
574			# Some runnetconn is telling us tht it changed the
575			# state of nc.  Send a nil along c to let it continue.
576			bs : ref ByteSource;
577			if(dbgproto)
578				sys->print("Statechg NC=%d, state=%s\n",
579					nc.id, ncstatenames[nc.state]);
580			sendtopending : ref ByteSource = nil;
581			pendingchan : chan of ref ByteSource;
582			if(waitpending != nil && nc.gocur < nc.qlen) {
583				bs = nc.queue[nc.gocur];
584				if(dbgproto) {
585					totlen := 0;
586					if(bs.hdr != nil)
587						totlen = bs.hdr.length;
588					sys->print("BS %d: havehdr=%d seenhdr=%d edata=%d lim=%d, length=%d\n",
589						bs.id, bs.hdr != nil, bs.seenhdr, bs.edata, bs.lim, totlen);
590					if(bs.err != "")
591						sys->print ("   err=%s\n", bs.err);
592				}
593				if(bs.refgo &&
594				   (bs.err != "" ||
595				   (bs.hdr != nil && !bs.seenhdr) ||
596				   (nc.gocur == nc.ngcur && nc.state == NCdone) ||
597				   (bs.edata > bs.lim))) {
598					nwp: list of (list of ref ByteSource, chan of ref ByteSource) = nil;
599					for (waitlist := waitpending; waitlist != nil; waitlist = tl waitlist) {
600						(bslist, anschan) := hd waitlist;
601						if (sendtopending != nil) {
602							nwp = (bslist, anschan) :: nwp;
603							continue;
604						}
605						for (look := bslist; look != nil; look = tl look) {
606							if (bs == hd look) {
607								sendtopending = bs;
608								pendingchan = anschan;
609								break;
610							}
611						}
612						if (sendtopending == nil)
613							nwp = (bslist, anschan) :: nwp;
614					}
615					waitpending = nwp;
616				}
617			}
618			if(nc.state == NCdone || nc.state == NCerr) {
619				if(dbgproto)
620					sys->print("NC %d: runnetconn finishing\n", nc.id);
621				assert(nc.ngcur < nc.qlen);
622				bs = nc.queue[nc.ngcur];
623				bs.refnc = 0;
624				if(bs.refgo == 0) {
625					bs.free();
626					nc.queue[nc.ngcur] = nil;
627				}
628				nc.ngcur++;
629				if(dbgproto)
630					sys->print("NC %d: ngcur=%d\n", nc.id, nc.ngcur);
631				nc.state = NCidle;
632				if(dbgproto)
633					sys->print("NC %d: idle\n", nc.id);
634				if(nc.ngcur < nc.qlen) {
635					if(nc.connected) {
636						nc.state = NCgethdr;
637						if(dbgproto)
638							sys->print("NC %d: starting runnetconn in gethdr state\n", nc.id);
639					}
640					else {
641						nc.state = NCconnect;
642						if(dbgproto)
643							sys->print("NC %d: starting runnetconn in connect state\n", nc.id);
644					}
645					(t, nil) := gettransport(nc.scheme);
646					spawn runnetconn(nc, t);
647				}
648				else if(nc.gocur == nc.qlen && !nc.connected)
649					nc.makefree();
650			}
651			c <-= nil;
652			if(sendtopending != nil) {
653				if(dbgproto)
654					sys->print("Send BS %d to pending waitreq\n", bs.id);
655				pendingchan <-= sendtopending;
656				sendtopending = nil;
657			}
658		}
659	}
660}
661
662# A separate thread, to handle ngcur request of transport.
663# If nc.gen ever goes < gen, we have aborted this go.
664runnetconn(nc: ref Netconn, t: Transport)
665{
666	ach := chan of ref ByteSource;
667	retry := 4;
668#	retry := 0;
669	err := "";
670
671	assert(nc.ngcur < nc.qlen);
672	bs := nc.queue[nc.ngcur];
673
674	# dummy loop, just for breaking out of in error cases
675eloop:
676	for(;;) {
677		# Make the connection, if necessary
678		if(nc.state == NCconnect) {
679			t->connect(nc, bs);
680			if(bs.err != "") {
681				if (retry) {
682					retry--;
683					bs.err = "";
684					sys->sleep(100);
685					continue eloop;
686				}
687				break eloop;
688			}
689			nc.state = NCgethdr;
690		}
691		assert(nc.state == NCgethdr && nc.connected);
692		if(nc.scheme == "https")
693			G->progress <-= (bs.id, G->Psslconnected, 0, "");
694		else
695			G->progress <-= (bs.id, G->Pconnected, 0, "");
696
697		t->writereq(nc, bs);
698		nc.reqsent++;
699		if (bs.err != "") {
700			if (retry) {
701				retry--;
702				bs.err = "";
703				nc.state = NCconnect;
704				sys->sleep(100);
705				continue eloop;
706			}
707			break eloop;
708		}
709		# managed to write the request
710		# do not retry if we are doing form POSTs
711		# See RFC1945 section 12.2 "Safe Methods"
712		if (bs.req.method == HPost)
713			retry = 0;
714
715		# Get the header
716		t->gethdr(nc, bs);
717		if(bs.err != "") {
718			if (retry) {
719				retry--;
720				bs.err = "";
721				nc.state = NCconnect;
722				sys->sleep(100);
723				continue eloop;
724			}
725			break eloop;
726		}
727		assert(bs.hdr != nil);
728		G->progress <-= (bs.id, G->Phavehdr, 0, "");
729
730		nc.state = NCgetdata;
731
732		# read enough data to guess media type
733		while (bs.hdr.mtype == UnknownType && ncgetdata(t, nc, bs))
734			bs.hdr.setmediatype(bs.hdr.actual.path, bs.data[:bs.edata]);
735		if (bs.hdr.mtype == UnknownType) {
736			bs.hdr.mtype = TextPlain;
737			bs.hdr.chset = "utf8";
738		}
739		ngchan <-= (NGstatechg,nil,nc,ach);
740		<- ach;
741		while (ncgetdata(t, nc, bs)) {
742			ngchan <-= (NGstatechg,nil,nc,ach);
743			<- ach;
744		}
745		nc.state = NCdone;
746		G->progress <-= (bs.id, G->Phavedata, 100, "");
747		break;
748	}
749	if(bs.err != "") {
750		nc.state = NCerr;
751		nc.connected = 0;
752		G->progress <-= (bs.id, G->Perr, 0, bs.err);
753	}
754	bs.eof = 1;
755	ngchan <-= (NGstatechg, nil, nc, ach);
756	<- ach;
757}
758
759ncgetdata(t: Transport, nc: ref Netconn, bs: ref ByteSource): int
760{
761	hdr := bs.hdr;
762	if (bs.data == nil) {
763		blen := hdr.length;
764		if (blen <= 0) {
765			if(hdr.code == HCOk || hdr.code == HCOkNonAuthoritative)
766				blen = UBufsize;
767			else
768				blen = UEBufsize;
769		}
770		bs.data = array[blen] of byte;
771	}
772	nr := 0;
773	if (hdr.length > 0) {
774		if (bs.edata == hdr.length)
775			return 0;
776		nr = t->getdata(nc, bs);
777		if (nr <= 0)
778			return 0;
779	} else {
780		# don't know data length - keep growing input buffer as needed
781		if (bs.edata == len bs.data) {
782			nd := array [2*len bs.data] of byte;
783			nd[:] = bs.data;
784			bs.data = nd;
785		}
786		nr = t->getdata(nc, bs);
787		if (nr <= 0) {
788			# assume EOF
789			bs.data = bs.data[0:bs.edata];
790			bs.err = "";
791			hdr.length = bs.edata;
792			nc.connected = 0;
793			return 0;
794		}
795	}
796	bs.edata += nr;
797	G->progress <-= (bs.id, G->Phavedata, 100*bs.edata/len bs.data, "");
798	return 1;
799}
800
801Netconn.new(id: int) : ref Netconn
802{
803	return ref Netconn(
804			id,		# id
805			"",		# host
806			0,		# port
807			"",		# scheme
808			ref Dial->Connection(nil, nil, ""),	# conn
809			nil,		# ssl context
810			0,		# undetermined ssl version
811			NCfree,	# state
812			array[10] of ref ByteSource,	# queue
813			0,		# qlen
814			0,0,0,	# gocur, ngcur, reqsent
815			0,		# pipeline
816			0,		# connected
817			0,		# tstate
818			nil,		# tbuf
819			0		# idlestart
820			);
821}
822
823Netconn.makefree(nc: self ref Netconn)
824{
825	if(dbgproto)
826		sys->print("NC %d: free\n", nc.id);
827	nc.state = NCfree;
828	nc.host = "";
829	nc.conn = nil;
830	nc.qlen = 0;
831	nc.gocur = 0;
832	nc.ngcur = 0;
833	nc.reqsent = 0;
834	nc.pipeline = 0;
835	nc.connected = 0;
836	nc.tstate = 0;
837	nc.tbuf = nil;
838	for(i := 0; i < len nc.queue; i++)
839		nc.queue[i] = nil;
840}
841
842ByteSource.free(bs: self ref ByteSource)
843{
844	if(dbgproto)
845		sys->print("BS %d freed\n", bs.id);
846	if(bs.err == "")
847		G->progress <-= (bs.id, G->Pdone, 100, "");
848	else
849		G->progress <-= (bs.id, G->Perr, 0, bs.err);
850	bs.req = nil;
851	bs.hdr = nil;
852	bs.data = nil;
853	bs.err = "";
854	bs.net = nil;
855}
856
857# Return an ByteSource that is completely filled, from string s
858ByteSource.stringsource(s: string) : ref ByteSource
859{
860	a := array of byte s;
861	n := len a;
862	hdr := ref Header(
863			HCOk,		# code
864			nil,			# actual
865			nil,			# base
866			nil,			# location
867			n,			# length
868			TextHtml, 	# mtype
869			"utf8",		# chset
870			"",			# msg
871			"",			# refresh
872			"",			# chal
873			"",			# warn
874			""			# last-modified
875		);
876	bs := ref ByteSource(
877			bytesourceid++,
878			nil,		# req
879			hdr,		# hdr
880			a,		# data
881			n,		# edata
882			"",		# err
883			nil,		# net
884			1,		# refgo
885			0,		# refnc
886			1,		# eof	- edata is final
887			0,		# lim
888			1		# seenhdr
889		);
890	return bs;
891}
892
893MaskedImage.free(mim: self ref MaskedImage)
894{
895	mim.im = nil;
896	mim.mask = nil;
897}
898
899CImage.new(src: ref U->Parsedurl, lowsrc: ref U->Parsedurl, width, height: int) : ref CImage
900{
901	return ref CImage(src, lowsrc, nil, strhash(src.host + "/" + src.path), width, height, nil, nil, 0);
902}
903
904# Return true if Cimages a and b represent the same image.
905# As well as matching the src urls, the specified widths and heights must match too.
906# (Widths and heights are specified if at least one of those is not zero.)
907#
908# BUG: the width/height matching code isn't right.  If one has width and height
909# specified, and the other doesn't, should say "don't match", because the unspecified
910# one should come in at its natural size.  But we overwrite the width and height fields
911# when the actual size comes in, so we can't tell whether width and height are nonzero
912# because they were specified or because they're their natural size.
913CImage.match(a: self ref CImage, b: ref CImage) : int
914{
915	if(a.imhash == b.imhash) {
916		if(urlequal(a.src, b.src)) {
917			return (a.width == 0 || b.width == 0 || a.width == b.width) &&
918				(a.height == 0 || b.height == 0 || a.height == b.height);
919			# (above is not quite enough: should also check that don't have
920			# situation where one has width set, not height, and the other has reverse,
921			# but it is unusual for an image to have a spec in only one dimension anyway)
922		}
923	}
924	return 0;
925}
926
927# Return approximate number of bytes in image memory used
928# by ci.
929CImage.bytes(ci: self ref CImage) : int
930{
931	tot := 0;
932	for(i := 0; i < len ci.mims; i++) {
933		mim := ci.mims[i];
934		dim := mim.im;
935		if(dim != nil)
936			tot += ((dim.r.max.x-dim.r.min.x)*dim.depth/8) *
937					(dim.r.max.y-dim.r.min.y);
938		dim = mim.mask;
939		if(dim != nil)
940			tot += ((dim.r.max.x-dim.r.min.x)*dim.depth/8) *
941					(dim.r.max.y-dim.r.min.y);
942	}
943	return tot;
944}
945
946# Call this after initial windows have been made,
947# so that resetlimits() will exclude the images for those
948# windows from the available memory.
949ImageCache.init(ic: self ref ImageCache)
950{
951	ic.imhd = nil;
952	ic.imtl = nil;
953	ic.n = 0;
954	ic.memused = 0;
955	ic.resetlimits();
956}
957
958# Call resetlimits when amount of non-image-cache image
959# memory might have changed significantly (e.g., on main window resize).
960ImageCache.resetlimits(ic: self ref ImageCache)
961{
962	res := ResourceState.cur();
963	avail := res.imagelim - (res.image-ic.memused);
964		# (res.image-ic.memused) is used memory not in image cache
965	avail = 8*avail/10;	# allow 20% slop for other applications, etc.
966	ic.memlimit = config.imagecachemem;
967	if(ic.memlimit > avail)
968		ic.memlimit = avail;
969#	ic.nlimit = config.imagecachenum;
970	ic.nlimit = 10000;	# let's try this
971	ic.need(0);	# if resized, perhaps need to shed some images
972}
973
974# Look for a CImage matching ci, and if found, move it
975# to the tail position (i.e., MRU)
976ImageCache.look(ic: self ref ImageCache, ci: ref CImage) : ref CImage
977{
978	ans : ref CImage = nil;
979	prev : ref CImage = nil;
980	for(i := ic.imhd; i != nil; i = i.next) {
981		if(i.match(ci)) {
982			if(ic.imtl != i) {
983				# remove from current place in cache chain
984				# and put at tail
985				if(prev != nil)
986					prev.next = i.next;
987				else
988					ic.imhd = i.next;
989				i.next = nil;
990				ic.imtl.next = i;
991				ic.imtl = i;
992			}
993			ans = i;
994			break;
995		}
996		prev = i;
997	}
998	return ans;
999}
1000
1001# Call this to add ci as MRU of cache chain (should only call if
1002# it is known that a ci with same image isn't already there).
1003# Update ic.memused.
1004# Assume ic.need has been called to ensure that neither
1005# memlimit nor nlimit will be exceeded.
1006ImageCache.add(ic: self ref ImageCache, ci: ref CImage)
1007{
1008	ci.next = nil;
1009	if(ic.imhd == nil)
1010		ic.imhd = ci;
1011	else
1012		ic.imtl.next = ci;
1013	ic.imtl = ci;
1014	ic.memused += ci.bytes();
1015	ic.n++;
1016}
1017
1018# Delete least-recently-used image in image cache
1019# and update memused and n.
1020ImageCache.deletelru(ic: self ref ImageCache)
1021{
1022	ci := ic.imhd;
1023	if(ci != nil) {
1024		ic.imhd = ci.next;
1025		if(ic.imhd == nil) {
1026			ic.imtl = nil;
1027			ic.memused = 0;
1028		}
1029		else
1030			ic.memused -= ci.bytes();
1031		for(i := 0; i < len ci.mims; i++)
1032			ci.mims[i].free();
1033		ci.mims = nil;
1034		ic.n--;
1035	}
1036}
1037
1038ImageCache.clear(ic: self ref ImageCache)
1039{
1040	while(ic.imhd != nil)
1041		ic.deletelru();
1042}
1043
1044# Call this just before allocating an Image that will used nbytes
1045# of image memory, to ensure that if the image were to be
1046# added to the image cache then memlimit and nlimit will be ok.
1047# LRU images will be shed if necessary.
1048# Return 0 if it will be impossible to make enough memory.
1049ImageCache.need(ic: self ref ImageCache, nbytes: int) : int
1050{
1051	while(ic.n >= ic.nlimit || ic.memused+nbytes > ic.memlimit) {
1052		if(ic.imhd == nil)
1053			return 0;
1054		ic.deletelru();
1055	}
1056	return 1;
1057}
1058
1059strhash(s: string) : int
1060{
1061	prime: con 8388617;
1062	hash := 0;
1063	n := len s;
1064	for(i := 0; i < n; i++) {
1065		hash = hash % prime;
1066		hash = (hash << 7) + s[i];
1067	}
1068	return hash;
1069}
1070
1071schemeid(s: string): int
1072{
1073	for (i := 0; i < len schemes; i++) {
1074		(n, id) := schemes[i];
1075		if (n == s)
1076			return id;
1077	}
1078	return -1;
1079}
1080
1081schemeok(s: string): int
1082{
1083	return schemeid(s) != -1;
1084}
1085
1086gettransport(scheme: string) : (Transport, string)
1087{
1088	err := "";
1089	transport: Transport = nil;
1090	tindex := schemeid(scheme);
1091	if (tindex == -1)
1092		return (nil, "Unknown scheme");
1093	transport = transports[tindex];
1094	if (transport == nil) {
1095		transport = load Transport loadpath(tpaths[tindex]);
1096		if(transport == nil)
1097			return (nil, sys->sprint("Can't load transport %s: %r", tpaths[tindex]));
1098		transport->init(me);
1099		transports[tindex] = transport;
1100	}
1101	return (transport, err);
1102}
1103
1104# Return new Header with default values for fields
1105Header.new() : ref Header
1106{
1107	return ref Header(
1108		HCOk,		# code
1109		nil,		# actual
1110		nil,		# base
1111		nil,		# location
1112		-1,		# length
1113		UnknownType,	# mtype
1114		nil,		# chset
1115		"",		# msg
1116		"",		# refresh
1117		"",		# chal
1118		"",		# warn
1119		""		# last-modified
1120	);
1121}
1122
1123jpmagic := array[] of {byte 16rFF, byte 16rD8, byte 16rFF, byte 16rE0,
1124		byte 0, byte 0, byte 'J', byte 'F', byte 'I', byte 'F', byte 0};
1125pngsig := array[] of { byte 137, byte 80, byte 78, byte 71, byte 13, byte 10, byte 26, byte 10 };
1126
1127# Set the mtype (and possibly chset) fields of h based on (in order):
1128#	first bytes of file, if unambigous
1129#	file name extension
1130#	first bytes of file, even if unambigous (guess)
1131#	if all else fails, then leave as UnknownType.
1132# If it's a text type, also set the chset.
1133# (HTTP Transport will try to use Content-Type first, and call this if that
1134# doesn't work; other Transports will have to rely on this "guessing" function.)
1135Header.setmediatype(h: self ref Header, name: string, first: array of byte)
1136{
1137	# Look for key signatures at beginning of file (perhaps after whitespace)
1138	n := len first;
1139	mt := UnknownType;
1140	for(i := 0; i < n; i++)
1141		if(ctype[int first[i]] != C->W)
1142			break;
1143	if(n - i >= 6) {
1144		s := string first[i:i+6];
1145		case S->tolower(s) {
1146		"<html " or "<html\t" or "<html>" or "<head>" or "<title" =>
1147			mt = TextHtml;
1148		"<!doct" =>
1149			if(n - i >= 14 && string first[i+6:i+14] == "ype html")
1150				mt = TextHtml;
1151		"gif87a" or "gif89a" =>
1152			if(i == 0)
1153				mt = ImageGif;
1154		"#defin" =>
1155			# perhaps should check more definitively...
1156			mt = ImageXXBitmap;
1157		}
1158
1159		if (mt == UnknownType && n > 0) {
1160			if (first[0] == jpmagic[0] && n >= len jpmagic) {
1161				for(i++; i<len jpmagic; i++)
1162					if(jpmagic[i]>byte 0 && first[i]!=jpmagic[i])
1163						break;
1164				if (i == len jpmagic)
1165					mt = ImageJpeg;
1166			} else if (first[0] == pngsig[0] && n >= len pngsig) {
1167				for(i++; i<len pngsig; i++)
1168					if (first[i] != pngsig[i])
1169						break;
1170				if (i == len pngsig)
1171					mt = ImagePng;
1172			}
1173		}
1174	}
1175
1176	if(mt == UnknownType) {
1177		# Try file name extension
1178		(nil, file) := S->splitr(name, "/");
1179		if(file != "") {
1180			(f, ext) := S->splitr(file, ".");
1181			if(f != "" && ext != "") {
1182				(fnd, val) := T->lookup(fileexttable, S->tolower(ext));
1183				if(fnd)
1184					mt = val;
1185			}
1186		}
1187	}
1188
1189#	if(mt == UnknownType) {
1190#		mt = TextPlain;
1191#		h.chset = "utf8";
1192#	}
1193	h.mtype = mt;
1194}
1195
1196Header.print(h: self ref Header)
1197{
1198	mtype := "?";
1199	if(h.mtype >= 0 && h.mtype < len mnames)
1200		mtype = mnames[h.mtype];
1201	chset := "?";
1202	if(h.chset != nil)
1203		chset = h.chset;
1204	# sys->print("code=%d (%s) length=%d mtype=%s chset=%s\n",
1205	#	h.code, hcphrase(h.code), h.length, mtype, chset);
1206	if(h.base != nil)
1207		sys->print("  base=%s\n", h.base.tostring());
1208	if(h.location != nil)
1209		sys->print("  location=%s\n", h.location.tostring());
1210	if(h.refresh != "")
1211		sys->print("  refresh=%s\n", h.refresh);
1212	if(h.chal != "")
1213		sys->print("  chal=%s\n", h.chal);
1214	if(h.warn != "")
1215		sys->print("  warn=%s\n", h.warn);
1216}
1217
1218
1219mfd : ref sys->FD = nil;
1220ResourceState.cur() : ResourceState
1221{
1222	ms := sys->millisec();
1223	main := 0;
1224	mainlim := 0;
1225	heap := 0;
1226	heaplim := 0;
1227	image := 0;
1228	imagelim := 0;
1229	if(mfd == nil)
1230		mfd = sys->open("/dev/memory", sys->OREAD);
1231	if (mfd == nil)
1232		raise sys->sprint("can't open /dev/memory: %r");
1233
1234	sys->seek(mfd, big 0, Sys->SEEKSTART);
1235
1236	buf := array[400] of byte;
1237	n := sys->read(mfd, buf, len buf);
1238	if (n <= 0)
1239		raise sys->sprint("can't read /dev/memory: %r");
1240
1241	(nil, l) := sys->tokenize(string buf[0:n], "\n");
1242	# p->cursize, p->maxsize, p->hw, p->nalloc, p->nfree, p->nbrk, poolmax(p), p->name)
1243	while(l != nil) {
1244		s := hd l;
1245		cur_size := int s[0:12];
1246		max_size := int s[12:24];
1247		case s[7*12:] {
1248		"main" =>
1249			main = cur_size;
1250			mainlim = max_size;
1251		"heap" =>
1252			heap = cur_size;
1253			heaplim = max_size;
1254		"image" =>
1255			image = cur_size;
1256			imagelim = max_size;
1257		}
1258		l = tl l;
1259	}
1260
1261	return ResourceState(ms, main, mainlim, heap, heaplim, image, imagelim);
1262}
1263
1264ResourceState.since(rnew: self ResourceState, rold: ResourceState) : ResourceState
1265{
1266	return (rnew.ms - rold.ms,
1267		rnew.main - rold.main,
1268		rnew.heaplim,
1269		rnew.heap - rold.heap,
1270		rnew.heaplim,
1271		rnew.image - rold.image,
1272		rnew.imagelim);
1273}
1274
1275ResourceState.print(r: self ResourceState, msg: string)
1276{
1277	sys->print("%s:\n\ttime: %d.%#.3ds; memory: main %dk, mainlim %dk, heap %dk, heaplim %dk, image %dk, imagelim %dk\n",
1278				msg, r.ms/1000, r.ms % 1000, r.main / 1024, r.mainlim / 1024,
1279				r.heap / 1024, r.heaplim / 1024, r.image / 1024, r.imagelim / 1024);
1280}
1281
1282# Decide what to do based on Header and whether this is
1283# for the main entity or not, and the number of redirections-so-far.
1284# Return tuple contains:
1285#	(use, error, challenge, redir)
1286# and action to do is:
1287#	If use==1, use the entity else drain its byte source.
1288#	If error != nil, mesg was put in progress bar
1289#	If challenge != nil, get auth info and make new request with auth
1290#	Else if redir != nil, make a new request with redir for url
1291#
1292# (if challenge or redir is non-nil, use will be 0)
1293hdraction(bs: ref ByteSource, ismain: int, nredirs: int) : (int, string, string, ref U->Parsedurl)
1294{
1295	use := 1;
1296	error := "";
1297	challenge := "";
1298	redir : ref U->Parsedurl = nil;
1299
1300	h := bs.hdr;
1301	assert(h != nil);
1302	bs.seenhdr = 1;
1303	code := h.code;
1304	case code/100 {
1305	HSOk =>
1306		if(code != HCOk)
1307			error = "unexpected code: " + hcphrase(code);
1308	HSRedirect =>
1309		if(h.location != nil) {
1310			redir = h.location;
1311			# spec says url should be absolute, but some
1312			# sites give relative ones
1313			if(redir.scheme == nil)
1314				redir = U->mkabs(redir, h.base);
1315			if(dbg)
1316				sys->print("redirect %s to %s\n", h.actual.tostring(), redir.tostring());
1317			if(nredirs >= Maxredir) {
1318				redir = nil;
1319				error = "probable redirect loop";
1320			}
1321			else
1322				use = 0;
1323		}
1324	HSError =>
1325		if(code == HCUnauthorized && h.chal != "") {
1326			challenge = h.chal;
1327			use = 0;
1328		}
1329		else {
1330			error = hcphrase(code);
1331			use = ismain;
1332		}
1333	HSServererr =>
1334		error = hcphrase(code);
1335		use = ismain;
1336	* =>
1337		error = "unexpected code: " + string code;
1338		use = 0;
1339
1340	}
1341	if(error != "")
1342		G->progress <-= (bs.id, G->Perr, 0, error);
1343	return (use, error, challenge, redir);
1344}
1345
1346# Use event when only care about time stamps on events
1347event(s: string, data: int)
1348{
1349	sys->print("%s: %d %d\n", s, sys->millisec()-startres.ms, data);
1350}
1351
1352kill(pid: int, dogroup: int)
1353{
1354	msg : array of byte;
1355	if(dogroup)
1356		msg = array of byte "killgrp";
1357	else
1358		msg = array of byte "kill";
1359	ctl := sys->open("#p/" + string pid + "/ctl", sys->OWRITE);
1360	if(ctl != nil)
1361		if (sys->write(ctl, msg, len msg) < 0)
1362			sys->print("charon: kill write failed (pid %d, grp %d): %r\n", pid, dogroup);
1363}
1364
1365# Read a line up to and including cr/lf (be tolerant and allow missing cr).
1366# Look first in buf[bstart:bend], and if that isn't sufficient to get whole line,
1367# refill buf from fd as needed.
1368# Return values:
1369#	array of byte: the line, not including cr/lf
1370#	eof, true if there was no line to get or a read error
1371#	bstart', bend': new valid portion of buf (after cr/lf).
1372getline(fd: ref sys->FD, buf: array of byte, bstart, bend: int) :
1373		(array of byte, int, int, int)
1374{
1375	ans : array of byte = nil;
1376	last : array of byte = nil;
1377	eof := 0;
1378mainloop:
1379	for(;;) {
1380		for(i := bstart; i < bend; i++) {
1381			if(buf[i] == byte '\n') {
1382				k := i;
1383				if(k > bstart && buf[k-1] == byte '\r')
1384					k--;
1385				last = buf[bstart:k];
1386				bstart = i+1;
1387				break mainloop;
1388			}
1389		}
1390		if(bend > bstart)
1391			ans = append(ans, buf[bstart:bend]);
1392		last = nil;
1393		bstart = 0;
1394		bend = sys->read(fd, buf, len buf);
1395		if(bend <= 0) {
1396			eof = 1;
1397			bend = 0;
1398			break mainloop;
1399		}
1400	}
1401	return (append(ans, last), eof, bstart, bend);
1402}
1403
1404# Append copy of second array to first, return (possibly new)
1405# address of the concatenation.
1406append(a: array of byte, b: array of byte) : array of byte
1407{
1408	if(b == nil)
1409		return a;
1410	na := len a;
1411	nb := len b;
1412	ans := realloc(a, nb);
1413	ans[na:] = b;
1414	return ans;
1415}
1416
1417# Return copy of a, but incr bytes bigger
1418realloc(a: array of byte, incr: int) : array of byte
1419{
1420	n := len a;
1421	newa := array[n + incr] of byte;
1422	if(a != nil)
1423		newa[0:] = a;
1424	return newa;
1425}
1426
1427# Look (linearly) through a for s; return its index if found, else -1.
1428strlookup(a: array of string, s: string) : int
1429{
1430	n := len a;
1431	for(i := 0; i < n; i++)
1432		if(s == a[i])
1433			return i;
1434	return -1;
1435}
1436
1437# Set up config global to defaults, then try to read user-specifiic
1438# config data from /usr/<username>/charon/config, then try to
1439# override from command line arguments.
1440setconfig(argl: list of string)
1441{
1442	# Defaults, in absence of any other information
1443	config.userdir = "";
1444	config.srcdir = "/appl/cmd/charon";
1445	config.starturl = "file:/services/webget/start.html";
1446	config.homeurl = config.starturl;
1447	config.change_homeurl = 1;
1448	config.helpurl = "file:/services/webget/help.html";
1449	config.usessl = SSLV3;	# was NOSSL
1450	config.devssl = 0;
1451	config.custbkurl = "/services/config/bookmarks.html";
1452	config.dualbkurl = "/services/config/dualdisplay.html";
1453	config.httpproxy = nil;
1454	config.noproxydoms = nil;
1455	config.buttons = "help,resize,hide,exit";
1456	config.framework = "all";
1457	config.defaultwidth = 640;
1458	config.defaultheight = 480;
1459	config.x = -1;
1460	config.y = -1;
1461	config.nocache = 0;
1462	config.maxstale = 0;
1463	config.imagelvl = ImgFull;
1464	config.imagecachenum = 120;
1465	config.imagecachemem = 100000000;	# 100Meg, will get lowered later
1466	config.docookies = 1;
1467	config.doscripts = 1;
1468	config.httpminor = 0;
1469	config.agentname = "Mozilla/4.08 (Charon; Inferno)";
1470	config.nthreads = 4;
1471	config.offersave = 1;
1472	config.charset = "windows-1252";
1473	config.plumbport = "web";
1474	config.wintitle = "Charon";	# tkclient->titlebar() title, used by GUI
1475	config.dbgfile = "";
1476	config.dbg = array[128] of { * => byte 0 };
1477
1478	# Reading default config file
1479	readconf("/services/config/charon.cfg");
1480
1481	# Try reading user config file
1482	user := "";
1483	fd := sys->open("/dev/user", sys->OREAD);
1484	if(fd != nil) {
1485		b := array[40] of byte;
1486		n := sys->read(fd, b, len b);
1487		if(n > 0)
1488			user = string b[0:n];
1489	}
1490	if(user != "") {
1491		config.userdir = "/usr/" + user + "/charon";
1492		readconf(config.userdir + "/config");
1493	}
1494
1495	if(argl == nil)
1496		return;
1497	# Try command line arguments
1498	# All should be 'key=val' or '-key' or '-key val', except last which can be url to start
1499	for(l := tl argl; l != nil; l = tl l) {
1500		s := hd l;
1501		if(s == "")
1502			continue;
1503		if (s[0] != '-')
1504			break;
1505		a := s[1:];
1506		b := "";
1507		if(tl l != nil) {
1508			b = hd tl l;
1509			if(S->prefix("-", b))
1510				b = "";
1511			else
1512				l = tl l;
1513		}
1514		if(!setopt(a, b)) {
1515			if (b != nil)
1516				s += " "+b;
1517			sys->print("couldn't set option from arg '%s'\n", s);
1518		}
1519	}
1520	if(l != nil) {
1521		if (tl l != nil)
1522			# usage error
1523			sys->print("too many URL's\n");
1524		else
1525			if(!setopt("starturl", hd l))
1526				sys->print("couldn't set starturl from arg '%s'\n", hd l);
1527	}
1528}
1529
1530readconf(fname: string)
1531{
1532	cfgio := sys->open(fname, sys->OREAD);
1533	if(cfgio != nil) {
1534		buf := array[sys->ATOMICIO] of byte;
1535		i := 0;
1536		j := 0;
1537		aline : array of byte;
1538		eof := 0;
1539		for(;;) {
1540			(aline, eof, i, j) = getline(cfgio, buf, i, j);
1541			if(eof)
1542				break;
1543			line := string aline;
1544			if(len line == 0 || line[0]=='#')
1545				continue;
1546			(key, val) := S->splitl(line, " \t=");
1547			if(key != "") {
1548				val = S->take(S->drop(val, " \t="), "^#\r\n");
1549				if(!setopt(key, val))
1550					sys->print("couldn't set option from line '%s'\n", line);
1551			}
1552		}
1553	}
1554}
1555
1556# Set config option named 'key' to val, returning 1 if OK
1557setopt(key: string, val: string) : int
1558{
1559	ok := 1;
1560	if(val == "none")
1561		val = "";
1562	v := int val;
1563	case key {
1564	"userdir" =>
1565		config.userdir = val;
1566	"srcdir" =>
1567		config.srcdir = val;
1568	"starturl" =>
1569		if(val != "")
1570			config.starturl = val;
1571		else
1572			ok = 0;
1573	"change_homeurl" =>
1574		config.change_homeurl = v;
1575	"homeurl" =>
1576		if(val != "")
1577			if(config.change_homeurl) {
1578				config.homeurl = val;
1579				# order dependent
1580				config.starturl = config.homeurl;
1581			}
1582		else
1583			ok = 0;
1584	"helpurl" =>
1585		if(val != "")
1586			config.helpurl = val;
1587		else
1588			ok = 0;
1589 	"usessl" =>
1590 		if(val == "v2")
1591 			config.usessl |= SSLV2;
1592 		if(val == "v3")
1593 			config.usessl |= SSLV3;
1594 	"devssl" =>
1595 		if(v == 0)
1596 			config.devssl = 0;
1597 		else
1598 			config.devssl = 1;
1599#	"custbkurl" =>
1600#	"dualbkurl" =>
1601	"httpproxy" =>
1602		if(val != "")
1603			config.httpproxy = makeabsurl(val);
1604		else
1605			config.httpproxy = nil;
1606	"noproxy" or "noproxydoms" =>
1607		(nil, config.noproxydoms) = sys->tokenize(val, ";, \t");
1608	"buttons" =>
1609		config.buttons = S->tolower(val);
1610	"framework" =>
1611		config.framework = S->tolower(val);
1612	"defaultwidth" or "width" =>
1613		if(v > 200)
1614			config.defaultwidth = v;
1615		else
1616			ok = 0;
1617	"defaultheight" or "height" =>
1618		if(v > 100)
1619			config.defaultheight = v;
1620		else
1621			ok = 0;
1622	"x" =>
1623		config.x = v;
1624	"y" =>
1625		config.y = v;
1626	"nocache" =>
1627		config.nocache = v;
1628	"maxstale" =>
1629		config.maxstale = v;
1630	"imagelvl" =>
1631		config.imagelvl = v;
1632	"imagecachenum" =>
1633		config.imagecachenum = v;
1634	"imagecachemem" =>
1635		config.imagecachemem = v;
1636	"docookies" =>
1637		config.docookies = v;
1638	"doscripts" =>
1639		config.doscripts = v;
1640	"http" =>
1641		if(val == "1.1")
1642			config.httpminor = 1;
1643		else
1644			config.httpminor = 0;
1645	"agentname" =>
1646		config.agentname = val;
1647	"nthreads" =>
1648		if (v < 1)
1649			ok = 0;
1650		else
1651			config.nthreads = v;
1652	"offersave" =>
1653		if (v < 1)
1654			config.offersave = 0;
1655		else
1656			config.offersave = 1;
1657	"charset" =>
1658		config.charset = val;
1659	"plumbport" =>
1660		config.plumbport = val;
1661	"wintitle" =>
1662		config.wintitle = val;
1663	"dbgfile" =>
1664		config.dbgfile = val;
1665	"dbg" =>
1666		for(i := 0; i < len val; i++) {
1667			c := val[i];
1668			if(c < len config.dbg)
1669				config.dbg[c]++;
1670			else {
1671				ok = 0;
1672				break;
1673			}
1674		}
1675	* =>
1676		ok = 0;
1677	}
1678	return ok;
1679}
1680
1681saveconfig(): int
1682{
1683	fname := config.userdir + "/config";
1684	buf := array [Sys->ATOMICIO] of byte;
1685	fd := sys->create(fname, Sys->OWRITE, 8r600);
1686	if(fd == nil)
1687		return -1;
1688
1689	nbyte := savealine(fd, buf, "# Charon user configuration\n", 0);
1690	nbyte = savealine(fd, buf, "userdir=" + config.userdir + "\n", nbyte);
1691	nbyte = savealine(fd, buf, "srcdir=" + config.srcdir +"\n", nbyte);
1692	if(config.change_homeurl){
1693		nbyte = savealine(fd, buf, "starturl=" + config.starturl + "\n", nbyte);
1694 		nbyte = savealine(fd, buf, "homeurl=" + config.homeurl + "\n", nbyte);
1695	}
1696	if(config.httpproxy != nil)
1697		nbyte = savealine(fd, buf, "httpproxy=" + config.httpproxy.tostring() + "\n", nbyte);
1698 	if(config.usessl & SSLV23) {
1699 		nbyte = savealine(fd, buf, "usessl=v2\n", nbyte);
1700 		nbyte = savealine(fd, buf, "usessl=v3\n", nbyte);
1701	}
1702	else {
1703 		if(config.usessl & SSLV2)
1704 			nbyte = savealine(fd, buf, "usessl=v2\n", nbyte);
1705 		if(config.usessl & SSLV3)
1706 			nbyte = savealine(fd, buf, "usessl=v3\n", nbyte);
1707 	}
1708	if(config.devssl == 0)
1709		nbyte = savealine(fd, buf, "devssl=0\n", nbyte);
1710	else
1711		nbyte = savealine(fd, buf, "devssl=1\n", nbyte);
1712	if(config.noproxydoms != nil) {
1713		doms := "";
1714		doml := config.noproxydoms;
1715		while(doml != nil) {
1716			doms += hd doml + ",";
1717			doml = tl doml;
1718		}
1719		nbyte = savealine(fd, buf, "noproxy=" + doms + "\n", nbyte);
1720	}
1721	nbyte = savealine(fd, buf, "defaultwidth=" + string config.defaultwidth + "\n", nbyte);
1722	nbyte = savealine(fd, buf, "defaultheight=" + string config.defaultheight + "\n", nbyte);
1723	if(config.x >= 0)
1724		nbyte = savealine(fd, buf, "x=" + string config.x + "\n", nbyte);
1725	if(config.y >= 0)
1726		nbyte = savealine(fd, buf, "y=" + string config.y + "\n", nbyte);
1727	nbyte = savealine(fd, buf, "nocache=" + string config.nocache + "\n", nbyte);
1728	nbyte = savealine(fd, buf, "maxstale=" + string config.maxstale + "\n", nbyte);
1729	nbyte = savealine(fd, buf, "imagelvl=" + string config.imagelvl + "\n", nbyte);
1730	nbyte = savealine(fd, buf, "imagecachenum=" + string config.imagecachenum + "\n", nbyte);
1731	nbyte = savealine(fd, buf, "imagecachemem=" + string config.imagecachemem + "\n", nbyte);
1732	nbyte = savealine(fd, buf, "docookies=" + string config.docookies + "\n", nbyte);
1733	nbyte = savealine(fd, buf, "doscripts=" + string config.doscripts + "\n", nbyte);
1734	nbyte = savealine(fd, buf, "http=" + "1." + string config.httpminor + "\n", nbyte);
1735	nbyte = savealine(fd, buf, "agentname=" + string config.agentname + "\n", nbyte);
1736	nbyte = savealine(fd, buf, "nthreads=" + string config.nthreads + "\n", nbyte);
1737	nbyte = savealine(fd, buf, "charset=" + config.charset + "\n", nbyte);
1738	#for(i := 0; i < len config.dbg; i++)
1739		#nbyte = savealine(fd, buf, "dbg=" + string config.dbg[i] + "\n", nbyte);
1740
1741	if(nbyte > 0)
1742		sys->write(fd, buf, nbyte);
1743
1744	return 0;
1745}
1746
1747savealine(fd: ref Sys->FD, buf: array of byte, s: string, n: int): int
1748{
1749	if(Sys->ATOMICIO < n + len s) {
1750		sys->write(fd, buf, n);
1751		buf[0:] = array of byte s;
1752		return len s;
1753	}
1754	buf[n:] = array of byte s;
1755	return n + len s;
1756}
1757
1758# Make a StringInt table out of a, mapping each string
1759# to its index.  Check that entries are in alphabetical order.
1760makestrinttab(a: array of string) : array of T->StringInt
1761{
1762	n := len a;
1763	ans := array[n] of T->StringInt;
1764	for(i := 0; i < n; i++) {
1765		ans[i].key = a[i];
1766		ans[i].val = i;
1767		if(i > 0 && a[i] < a[i-1])
1768			raise "EXInternal: table out of alphabetical order";
1769	}
1770	return ans;
1771}
1772
1773# Should really move into Url module.
1774# Don't include fragment in test, since we are testing if the
1775# pointed to docs are the same, not places within docs.
1776urlequal(a, b: ref U->Parsedurl) : int
1777{
1778	return a.scheme == b.scheme
1779		&& a.host == b.host
1780		&& a.port == b.port
1781		&& a.user == b.user
1782		&& a.passwd == b.passwd
1783		&& a.path == b.path
1784		&& a.query == b.query;
1785}
1786
1787# U->makeurl, but add http:// if not an absolute path already
1788makeabsurl(s: string) : ref Parsedurl
1789{
1790	if (s == "")
1791		return nil;
1792	u := U->parse(s);
1793	if (u.scheme != nil)
1794		return u;
1795	if (s[0] == '/')
1796		# try file:
1797		s = "file://localhost" + s;
1798	else
1799		# try http
1800		s = "http://" + s;
1801	u = U->parse(s);
1802	return u;
1803}
1804
1805# Return place to load from, given installed-path name.
1806# (If config.dbg['u'] is set, change directory to config.srcdir.)
1807loadpath(s: string) : string
1808{
1809	if(config.dbg['u'] == byte 0)
1810		return s;
1811	(nil, f) := S->splitr(s, "/");
1812	return config.srcdir + "/" + f;
1813}
1814
1815color_tab := array[] of { T->StringInt
1816	("aqua",	16r00FFFF),
1817	("black",	Black),
1818	("blue",	Blue),
1819	("fuchsia",	16rFF00FF),
1820	("gray",	16r808080),
1821	("green",	16r008000),
1822	("lime",	16r00FF00),
1823	("maroon",	16r800000),
1824	("navy",	Navy),
1825	("olive",	16r808000),
1826	("purple",	16r800080),
1827	("red",	Red),
1828	("silver",	16rC0C0C0),
1829	("teal",	16r008080),
1830	("white",	White),
1831	("yellow",	16rFFFF00)
1832};
1833# Convert HTML color spec to RGB value, returning dflt if can't.
1834# Argument is supposed to be a valid HTML color, or "".
1835# Return the RGB value of the color, using dflt if s
1836# is "" or an invalid color.
1837color(s: string, dflt: int) : int
1838{
1839	if(s == "")
1840		return dflt;
1841	s = S->tolower(s);
1842	c := s[0];
1843	if(c < C->NCTYPE && ctype[c] == C->L) {
1844		(fnd, v) := T->lookup(color_tab, s);
1845		if(fnd)
1846			return v;
1847	}
1848	if(s[0] == '#')
1849		s = s[1:];
1850	(v, rest) := S->toint(s, 16);
1851	if(rest == "")
1852		return v;
1853	# s was invalid, so choose a valid one
1854	return dflt;
1855}
1856
1857max(a,b: int) : int
1858{
1859	if(a > b)
1860		return a;
1861	return b;
1862}
1863
1864min(a,b: int) : int
1865{
1866	if(a < b)
1867		return a;
1868	return b;
1869}
1870
1871assert(i: int)
1872{
1873	if(!i) {
1874		raise "EXInternal: assertion failed";
1875#		sys->print("assertion failed\n");
1876#		s := hmeth[-1];
1877	}
1878}
1879
1880getcookies(host, path: string, secure: int): string
1881{
1882	if (CK == nil || ckclient == nil)
1883		return nil;
1884	Client: import CK;
1885	return ckclient.getcookies(host, path, secure);
1886}
1887
1888setcookie(host, path, cookie: string)
1889{
1890	if (CK == nil || ckclient == nil)
1891		return;
1892	Client: import CK;
1893	ckclient.set(host, path, cookie);
1894}
1895
1896ex_mkdir(dirname: string): int
1897{
1898	(ok, nil) := sys->stat(dirname);
1899	if(ok < 0) {
1900		f := sys->create(dirname, sys->OREAD, sys->DMDIR + 8r777);
1901		if(f == nil) {
1902			sys->print("mkdir: can't create %s: %r\n", dirname);
1903			return 0;
1904		}
1905		f = nil;
1906	}
1907	return 1;
1908}
1909
1910stripscript(s: string): string
1911{
1912	# strip leading whitespace and SGML comment start symbol '<!--'
1913	if (s == nil)
1914		return nil;
1915	cs := "<!--";
1916	ci := 0;
1917	for (si := 0; si < len s; si++) {
1918		c := s[si];
1919		if (c == cs[ci]) {
1920			if (++ci >= len cs)
1921				ci = 0;
1922		} else {
1923			ci = 0;
1924			if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
1925				continue;
1926			break;
1927		}
1928	}
1929	# strip trailing whitespace and SGML comment terminator '-->'
1930	cs = "-->";
1931	ci = len cs -1;
1932	for (se := len s - 1; se > si; se--) {
1933		c := s[se];
1934		if (c == cs[ci]) {
1935			if (ci-- == 0)
1936				ci = len cs -1;
1937		} else {
1938			ci = len cs - 1;
1939			if (c == ' ' || c == '\t' || c == '\r' || c == '\n')
1940				continue;
1941			break;
1942		}
1943	}
1944	if (se < si)
1945		return nil;
1946	return s[si:se+1];
1947}
1948
1949# Split a value (guaranteed trimmed) into sep-separated list of one of
1950# 	token
1951#	token = token
1952#	token = "quoted string"
1953# and put into list of Namevals (lowercase the first token)
1954Nameval.namevals(s: string, sep: int) : list of Nameval
1955{
1956	ans : list of Nameval = nil;
1957	n := len s;
1958	i := 0;
1959	while(i < n) {
1960		tok : string;
1961		(tok, i) = gettok(s, i, n);
1962		if(tok == "")
1963			break;
1964		tok = S->tolower(tok);
1965		val := "";
1966		while(i < n && ctype[s[i]] == C->W)
1967			i++;
1968		if(i == n || s[i] == sep)
1969			i++;
1970		else if(s[i] == '=') {
1971			i++;
1972			while(i < n && ctype[s[i]] == C->W)
1973				i++;
1974			if (i == n)
1975				break;
1976			if(s[i] == '"')
1977				(val, i) = getqstring(s, i, n);
1978			else
1979				(val, i) = gettok(s, i, n);
1980		}
1981		else
1982			break;
1983		ans = Nameval(tok, val) :: ans;
1984	}
1985	return ans;
1986}
1987
1988gettok(s: string, i,n: int) : (string, int)
1989{
1990	while(i < n && ctype[s[i]] == C->W)
1991		i++;
1992	if(i == n)
1993		return ("", i);
1994	is := i;
1995	for(; i < n; i++) {
1996		c := s[i];
1997		ct := ctype[c];
1998		if(!(int (ct&(C->D|C->L|C->U|C->N|C->S))))
1999			if(int (ct&(C->W|C->C)) || S->in(c, "()<>@,;:\\\"/[]?={}"))
2000				break;
2001	}
2002	return (s[is:i], i);
2003}
2004
2005# get quoted string; return it without quotes, and index after it
2006getqstring(s: string, i,n: int) : (string, int)
2007{
2008	while(i < n && ctype[s[i]] == C->W)
2009		i++;
2010	if(i == n || s[i] != '"')
2011		return ("", i);
2012	is := ++i;
2013	for(; i < n; i++) {
2014		c := s[i];
2015		if(c == '\\')
2016			i++;
2017		else if(c == '"')
2018			return (s[is:i], i+1);
2019	}
2020	return (s[is:i], i);
2021}
2022
2023# Find value corresponding to key (should be lowercase)
2024# and return (1, value) if found or (0, "")
2025Nameval.find(l: list of Nameval, key: string) : (int, string)
2026{
2027	for(; l != nil; l = tl l)
2028		if((hd l).key == key)
2029			return (1, (hd l).val);
2030	return (0, "");
2031}
2032
2033# this should be a converter cache
2034getconv(chset : string) : Btos
2035{
2036	(btos, err) := convcs->getbtos(chset);
2037	if (err != nil)
2038		sys->print("Converter error: %s\n", err);
2039	return btos;
2040}
2041
2042X(s, note : string) : string
2043{
2044	if (dict == nil)
2045		return s;
2046	return dict.xlaten(s, note);
2047}
2048