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