implement CharonUtils; include "common.m"; include "transport.m"; #####################international include "charon_gui.m"; include "date.m"; date: Date; me: CharonUtils; sys: Sys; D: Draw; S: String; U: Url; T: StringIntTab; Font : import D; charon_gui: Charon_gui; ParsedUrl: import U; NCTimeout : con 100000; # free NC slot after 100 seconds UBufsize : con 40*1024; # initial buffer size for unknown lengths UEBufsize : con 1024; # initial buffer size for unknown lengths, error responses botchexception := "EXInternal: ByteSource protocol botch"; bytesourceid := 0; crlf : con "\r\n"; ctype : array of byte; # local ref to C->ctype[] dbgproto : int; dbg: int; netconnid := 0; netconns := array[10] of ref Netconn; sptab : con " \t"; transports := array[Transport->Tmax] of Transport; ngchan : chan of (int, list of ref ByteSource, ref Netconn, chan of ref ByteSource); # must track HTTP methods in chutils.m # (upper-case, since that's required in HTTP requests) hmeth = array[] of { "GET", "POST" }; # following array must track media type def in chutils.m # keep in alphabetical order mnames = array[] of { "application/msword", "application/octet-stream", "application/pdf", "application/postscript", "application/rtf", "application/vnd.framemaker", "application/vnd.ms-excel", "application/vnd.ms-powerpoint", "application/x-unknown", "audio/32kadpcm", "audio/basic", "image/cgm", "image/g3fax", "image/gif", "image/ief", "image/jpeg", "image/png", "image/tiff", "image/x-bit", "image/x-bit2", "image/x-bitmulti", "image/x-inferno-bit", "image/x-xbitmap", "model/vrml", "multipart/digest", "multipart/mixed", "text/css", "text/enriched", "text/html", "text/javascript", "text/plain", "text/richtext", "text/sgml", "text/tab-separated-values", "text/xml", "video/mpeg", "video/quicktime" }; # track Charsets in chutils.b #(cf rfc1945 for other names, those we don't bother handling) chsetnames = array[] of { "unknown", "us-ascii", "iso-8859-1", "unicode-1-1-utf-8", "unicode-1-1" }; ncstatenames = array[] of { "free", "idle", "connect", "gethdr", "getdata", "done", "err" }; hsnames = array[] of { "none", "information", "ok", "redirect", "request error", "server error" }; hcphrase(code: int) : string { ans : string; case code { #####################international HCContinue => ans = charon_gui->iContinue; HCSwitchProto => ans = charon_gui->iSwProtocol; HCOk => ans = charon_gui->iOk; HCCreated => ans = charon_gui->iCreated; HCAccepted => ans = charon_gui->iAccepted; HCOkNonAuthoritative => ans = charon_gui->iNonAuth; HCNoContent => ans = charon_gui->iNoContent; HCResetContent => ans = charon_gui->iResetContent; HCPartialContent => ans = charon_gui->iPartContent; HCMultipleChoices => ans = charon_gui->iMultiChoice; HCMovedPerm => ans = charon_gui->iMovedPerm; HCMovedTemp => ans = charon_gui->iMovedTemp; HCSeeOther => ans = charon_gui->iSeeOther; HCNotModified => ans = charon_gui->iNotModify; HCUseProxy => ans = charon_gui->iUseProxy; HCBadRequest => ans = charon_gui->iBadReq; HCUnauthorized => ans = charon_gui->iUnauth; HCPaymentRequired => ans = charon_gui->iPayRequired; HCForbidden => ans = charon_gui->iForbidden; HCNotFound => ans = charon_gui->iNotFound; HCMethodNotAllowed => ans = charon_gui->iNotAllowed; HCNotAcceptable => ans = charon_gui->iNotAccpt; HCProxyAuthRequired => ans = charon_gui->iProxyAuth; HCRequestTimeout => ans = charon_gui->iReqTimeout; HCConflict => ans = charon_gui->iConflict; HCGone => ans = charon_gui->iGone; HCLengthRequired => ans = charon_gui->iLenRequired; HCPreconditionFailed => ans = charon_gui->iPrecondFailed; HCRequestTooLarge => ans = charon_gui->iReqTooLarge; HCRequestURITooLarge => ans = charon_gui->iUriTooLarge; HCUnsupportedMedia => ans = charon_gui->iUnsuppMedia; HCRangeInvalid => ans = charon_gui->iRangeInvalid; HCExpectFailed => ans = charon_gui->iExpectFailed; HCServerError => ans = charon_gui->iServerError; HCNotImplemented => ans = charon_gui->iNotImplement; HCBadGateway => ans = charon_gui->iBadGateway; HCServiceUnavailable => ans = charon_gui->iServUnavail; HCGatewayTimeout => ans = charon_gui->iGatewayTimeout; HCVersionUnsupported => ans = charon_gui->iVerUnsupp; HCRedirectionFailed => ans = charon_gui->iRedirFailed; * => ans = charon_gui->iUnknownCode; } return ans; } # This array should be kept sorted fileexttable := array[] of { T->StringInt ("ai", ApplPostscript), ("au", AudioBasic), # ("bit", ImageXBit), ("bit", ImageXInfernoBit), ("bit2", ImageXBit2), ("bitm", ImageXBitmulti), ("eps", ApplPostscript), ("gif", ImageGif), ("htm", TextHtml), ("html", TextHtml), ("jpe", ImageJpeg), ("jpeg", ImageJpeg), ("jpg", ImageJpeg), ("pdf", ApplPdf), ("png", ImagePng), ("ps", ApplPostscript), ("shtml", TextHtml), ("text", TextPlain), ("tif", ImageTiff), ("tiff", ImageTiff), ("txt", TextPlain) }; # argl is command line # underwm is true if running under the window manager. # Return string that is empty if all ok, else path of module # that failed to load. init(ch: Charon, c: CharonUtils, argl: list of string, underwm: int, evc : chan of ref E->Event) : string { me = c; sys = load Sys Sys->PATH; startres = ResourceState.cur(); D = load Draw Draw->PATH; CH = ch; S = load String String->PATH; if(S == nil) return String->PATH; U = load Url Url->PATH; if(U == nil) return Url->PATH; U->init(); T = load StringIntTab StringIntTab->PATH; if(T == nil) return StringIntTab->PATH; #####################international charon_gui = load Charon_gui Charon_gui->PATH; if(charon_gui==nil) raise(sys->sprint("EXinternal:couldn't load Charon_gui:%r")); charon_gui->init(); # Now have all the modules needed to process command line # (hereafter can use our loadpath() function to substitute the # build directory version if dbg['u'] is set) setconfig(argl, underwm); if(underwm) { G = load Gui loadpath(Gui->GUIWMPATH); if(G == nil) return loadpath(Gui->GUIWMPATH); } else { G = load Gui loadpath(Gui->PATH); if(G == nil) return loadpath(Gui->PATH); } C = load Ctype loadpath(Ctype->PATH); if(C == nil) return loadpath(Ctype->PATH); E = load Events Events->PATH; if(E == nil) return loadpath(Events->PATH); J = load Script loadpath(Script->JSCRIPTPATH); # don't report an error loading JavaScript, handled elsewhere LX = load Lex loadpath(Lex->PATH); if(LX == nil) return loadpath(Lex->PATH); B = load Build loadpath(Build->PATH); if(B == nil) return loadpath(Build->PATH); I = load Img loadpath(Img->PATH); if(I == nil) return loadpath(Img->PATH); L = load Layout loadpath(Layout->PATH); if(L == nil) return loadpath(Layout->PATH); date = load Date loadpath(Date->PATH); if (date == nil) return loadpath(Date->PATH); # Intialize all modules after loading all, so that each # may cache pointers to the other modules # (G will be initialized in main charon routine, and L has to # be inited after that, because it needs G's display to allocate fonts) E->init(evc); I->init(me); B->init(me); LX->init(me); if(J != nil) J->init(me); date->init(me); # preload some transports gettransport(Url->HTTP); gettransport(Url->FILE); if(config.showprogress) progresschan = chan of (int, int, int, string); imcache = ref ImageCache; ctype = C->ctype; dbgproto = int config.dbg['p']; dbg = int config.dbg['d']; ngchan = chan of (int, list of ref ByteSource, ref Netconn, chan of ref ByteSource); flushanim = 1; return ""; } # like startreq() but special case for a string ByteSource # which doesn't need an associated netconn stringreq(s : string) : ref ByteSource { bs := ByteSource.stringsource(s); if(config.showprogress) progresschan <-= (bs.id, Pstart, 0, "text"); anschan := chan of ref ByteSource; ngchan <-= (NGstartreq, bs :: nil, nil, anschan); <-anschan; return bs; } # Make a ByteSource for given request, and make sure # that it is on the queue of some Netconn. # If don't have a transport for the request's scheme, # the returned bs will have err set. startreq(req: ref ReqInfo) : ref ByteSource { bs := ref ByteSource( bytesourceid++, req, # req nil, # hdr nil, # data 0, # edata "", # err nil, # net 1, # refgo 1, # refnc 0, # lim 0 # seenhdr ); if(config.showprogress) progresschan <-= (bs.id, Pstart, 0, req.url.tostring()); anschan := chan of ref ByteSource; ngchan <-= (NGstartreq, bs::nil, nil, anschan); <-anschan; return bs; } # Wait for some ByteSource in current go generation to # have a state change that goproc hasn't seen yet. waitreq(bsl: list of ref ByteSource) : ref ByteSource { anschan := chan of ref ByteSource; ngchan <-= (NGwaitreq, bsl, nil, anschan); return <-anschan; } # Notify netget that goproc is finished with bs. freebs(bs: ref ByteSource) { anschan := chan of ref ByteSource; ngchan <-= (NGfreebs, bs::nil, nil, anschan); <-anschan; } abortgo(gopgrp: int) { if(int config.dbg['d']) sys->print("abort go\n"); kill(gopgrp, 1); freegoresources(); # renew the channels so that receives/sends by killed threads don't # muck things up ngchan = chan of (int, list of ref ByteSource, ref Netconn, chan of ref ByteSource); } freegoresources() { for(i := 0; i < len netconns; i++) { nc := netconns[i]; nc.makefree(); } } # This runs as a separate thread. # It acts as a monitor to synchronize access to the Netconn data # structures, as a dispatcher to start runnetconn's as needed to # process work on Netconn queues, and as a notifier to let goproc # know when any ByteSources have advanced their state. netget() { msg, n, i: int; bsl : list of ref ByteSource; nc: ref Netconn; waitix := 0; c : chan of ref ByteSource; waitpending : list of (list of ref ByteSource, chan of ref ByteSource); maxconn := config.nthreads; gncs := array[maxconn] of int; sbs : list of ref ByteSource; # string ByteSources for(n = 0; n < len netconns; n++) netconns[n] = Netconn.new(n); mainloop: for(;;) { (msg,bsl,nc,c) = <- ngchan; case msg { NGstartreq => bs := hd bsl; # bs has req filled in, and is otherwise in its initial state. # Find a suitable Netconn and add bs to its queue of work, # then send nil along c to let goproc continue. # if ReqInfo is nil then assume this is a string ByteSource # in which case we don't need a netconn to service it as we have all # data already if (bs.req == nil) { sbs = bs :: sbs; c <- = nil; continue; } if(dbgproto) sys->print("Startreq BS=%d for %s\n", bs.id, bs.req.url.tostring()); scheme := bs.req.url.scheme; host := bs.req.url.host; (transport, err) := gettransport(scheme); if(err != "") bs.err = err; else { sport :=bs.req.url.port; if(sport == "") port := transport->defaultport(scheme); else port = int sport; i = 0; freen := -1; for(n = 0; n < len netconns && (i < maxconn || freen == -1); n++) { nc = netconns[n]; if(nc.state == NCfree) { if(freen == -1) freen = n; } else if(nc.host == host && nc.port == port && nc.scheme == scheme && i < maxconn) { gncs[i++] = n; } } if(i < maxconn) { # use a new netconn for this bs if(freen == -1) { freen = len netconns; newncs := array[freen+10] of ref Netconn; newncs[0:] = netconns; for(n = freen; n < freen+10; n++) newncs[n] = Netconn.new(n); netconns = newncs; } nc = netconns[freen]; nc.host = host; nc.port = port; nc.scheme = scheme; nc.qlen = 0; nc.ngcur = 0; nc.gocur = 0; nc.reqsent = 0; nc.pipeline = 0; nc.connected = 0; } else { # use existing netconn with fewest outstanding requests nc = netconns[gncs[0]]; if(maxconn > 1) { minqlen := nc.qlen - nc.gocur; for(i = 1; i < maxconn; i++) { x := netconns[gncs[i]]; if(x.qlen-x.gocur < minqlen) { nc = x; minqlen = x.qlen-x.gocur; } } } } if(nc.qlen == len nc.queue) { nq := array[nc.qlen+10] of ref ByteSource; nq[0:] = nc.queue; nc.queue = nq; } nc.queue[nc.qlen++] = bs; bs.net = nc; if(dbgproto) sys->print("Chose NC=%d for BS %d, qlen=%d\n", nc.id, bs.id, nc.qlen); if(nc.state == NCfree || nc.state == NCidle) { if(nc.connected) { nc.state = NCgethdr; if(dbgproto) sys->print("NC %d: starting runnetconn in gethdr state\n", nc.id); } else { nc.state = NCconnect; if(dbgproto) sys->print("NC %d: starting runnetconn in connect state\n", nc.id); } spawn runnetconn(nc, transport); } } c <-= nil; NGwaitreq => # goproc wants to be notified when some ByteSource # changes to a state that the goproc hasn't seen yet. # Send such a ByteSource along return channel c. if(dbgproto) sys->print("Waitreq\n"); # check all string ByteSources for (l := sbs; l != nil; l = tl l) { bs := hd l; if (bs.edata > bs.lim) { for (lookfor := bsl; lookfor != nil; lookfor = tl lookfor) { if (hd lookfor == bs) { c <-= bs; continue mainloop; } } } } nnc := len netconns; for(n = 0; n < nnc; n++) { waitix = (waitix + 1)%nnc; nc = netconns[waitix]; if(nc.gocur < nc.qlen) { bs := nc.queue[nc.gocur]; assert(bs.refgo != 0); if(bs.err != "" || (bs.hdr != nil && !bs.seenhdr) || (bs.edata > bs.lim) || (nc.gocur < nc.ngcur)) { for (lookfor := bsl; lookfor != nil; lookfor = tl lookfor) { if (hd lookfor == bs) { c <-= bs; continue mainloop; } } } } } if(dbgproto) sys->print("Waitpending\n"); waitpending = (bsl, c) :: waitpending; NGfreebs => # goproc is finished with bs. bs := hd bsl; if(dbgproto) sys->print("Freebs BS=%d\n", bs.id); nc = bs.net; bs.refgo = 0; if(bs.refnc == 0) { bs.free(); if(nc != nil) nc.queue[nc.gocur] = nil; } if(nc != nil) { # can be nil if no transport was found nc.gocur++; if(dbgproto) sys->print("NC %d: gocur=%d, ngcur=%d, qlen=%d\n", nc.id, nc.gocur, nc.ngcur, nc.qlen); if(nc.gocur == nc.qlen && nc.ngcur == nc.qlen) { if(!nc.connected) nc.makefree(); } } else if (bs.req == nil) { # search for bs in sbs list (string ByteSources) nsbs : list of ref ByteSource; for (; sbs != nil; sbs = tl sbs) if ((hd sbs).id != bs.id) nsbs = hd sbs :: nsbs; sbs = nsbs; } # don't need to check waitpending fro NGwait requests involving bs # the only thread doing a freebs() should be the only thread that # can do a waitreq() on the same bs. Same thread cannot be in both states. c <-= nil; NGstatechg => # Some runnetconn is telling us tht it changed the # state of nc. Send a nil along c to let it continue. bs : ref ByteSource; if(dbgproto) sys->print("Statechg NC=%d, state=%s\n", nc.id, ncstatenames[nc.state]); sendtopending : ref ByteSource = nil; pendingchan : chan of ref ByteSource; if(waitpending != nil && nc.gocur < nc.qlen) { bs = nc.queue[nc.gocur]; if(dbgproto) { totlen := 0; if(bs.hdr != nil) totlen = bs.hdr.length; sys->print("BS %d: havehdr=%d seenhdr=%d edata=%d lim=%d, length=%d\n", bs.id, bs.hdr != nil, bs.seenhdr, bs.edata, bs.lim, totlen); if(bs.err != "") sys->print (" err=%s\n", bs.err); } if(bs.refgo && (bs.err != "" || (bs.hdr != nil && !bs.seenhdr) || (nc.gocur == nc.ngcur && nc.state == NCdone) || (bs.edata > bs.lim))) { nwp : list of (list of ref ByteSource, chan of ref ByteSource) = nil; for (waitlist := waitpending; waitlist != nil; waitlist = tl waitlist) { (bslist, anschan) := hd waitlist; if (sendtopending != nil) { nwp = (bslist, anschan) :: nwp; continue; } for (look := bslist; look != nil; look = tl look) { if (bs == hd look) { sendtopending = bs; pendingchan = anschan; break; } } if (sendtopending == nil) nwp = (bslist, anschan) :: nwp; } waitpending = nwp; } } if(nc.state == NCdone || nc.state == NCerr) { if(dbgproto) sys->print("NC %d: runnetconn finishing\n", nc.id); assert(nc.ngcur < nc.qlen); bs = nc.queue[nc.ngcur]; bs.refnc = 0; if(bs.refgo == 0) { bs.free(); nc.queue[nc.ngcur] = nil; } nc.ngcur++; if(dbgproto) sys->print("NC %d: ngcur=%d\n", nc.id, nc.ngcur); nc.state = NCidle; if(dbgproto) sys->print("NC %d: idle\n", nc.id); if(nc.ngcur < nc.qlen) { if(nc.connected) { nc.state = NCgethdr; if(dbgproto) sys->print("NC %d: starting runnetconn in gethdr state\n", nc.id); } else { nc.state = NCconnect; if(dbgproto) sys->print("NC %d: starting runnetconn in connect state\n", nc.id); } spawn runnetconn(nc, transports[nc.scheme]); } else if(nc.gocur == nc.qlen && !nc.connected) nc.makefree(); } c <-= nil; if(sendtopending != nil) { if(dbgproto) sys->print("Send BS %d to pending waitreq\n", bs.id); pendingchan <-= sendtopending; sendtopending = nil; } } } } # A separate thread, to handle ngcur request of transport. # If nc.gen ever goes < gen, we have aborted this go. runnetconn(nc: ref Netconn, t: Transport) { ach := chan of ref ByteSource; err := ""; assert(nc.ngcur < nc.qlen); bs := nc.queue[nc.ngcur]; # dummy loop, just for breaking out of in error cases eloop: for(;;) { # Make the connection, if necessary if(nc.state == NCconnect) { t->connect(nc, bs); if(bs.err != "") break eloop; nc.state = NCgethdr; } assert(nc.state == NCgethdr && nc.connected); if(config.showprogress) { if(nc.scheme == Url->HTTPS) progresschan <-= (bs.id, Psslconnected, 0, ""); else progresschan <-= (bs.id, Pconnected, 0, ""); } t->writereq(nc, bs); nc.reqsent++; if (bs.err != "") break eloop; # # Write enough requests so that nc.ngcur header # # will be next to be retrieved # while(nc.reqsent < nc.qlen) { # rbs := nc.queue[nc.reqsent]; # if(rbs != nil){ # t->writereq(nc, rbs); # nc.reqsent++; # if(rbs.err != "") { # if(nc.reqsent > nc.ngcur+1) { # rbs.err = ""; # break; # } # else # break eloop; # } # } else # nc.reqsent++; # if(nc.reqsent >= nc.ngcur+1 || !nc.pipeline) # break; # } # assert(nc.reqsent > nc.ngcur); # Get the header t->gethdr(nc, bs); if(bs.err != "") break eloop; assert(bs.hdr != nil); if(config.showprogress) progresschan <-= (bs.id, Phavehdr, 0, ""); totlen := bs.hdr.length; if(totlen > 0) { nc.state = NCgetdata; ngchan <-= (NGstatechg,nil,nc,ach); <- ach; bs.data = array[totlen] of byte; while(bs.edata < totlen) { t->getdata(nc, bs); if(bs.err != "") break eloop; if(config.showprogress) progresschan <-= (bs.id, Phavedata, 100*bs.edata/totlen, ""); ngchan <-= (NGstatechg, nil, nc, ach); <- ach; } } else if(totlen == -1) { # Unknown length. # To simplify consumer semantics, we want bs.data to # not change underfoot, so for now, simply accumlate # everything before telling consumer about a state change. # # Report progress percentage based on current totlen (wrong # of course, but at least shows trend) if(bs.hdr.code == HCOk || bs.hdr.code ==HCOkNonAuthoritative) totlen = UBufsize; else totlen = UEBufsize; nc.state = NCgetdata; bs.hdr.length = 100000000; # avoid BS free during following loop ngchan <-= (NGstatechg,nil,nc,ach); <- ach; bs.data = array[totlen] of byte; for(;;) { t->getdata(nc, bs); if(config.showprogress) progresschan <-= (bs.id, Phavedata, 100*bs.edata/totlen, ""); if(nc.connected == 0 || bs.err != "") { # assume EOF bs.data = bs.data[0:bs.edata]; bs.err = ""; bs.hdr.length = bs.edata; nc.connected = 0; break; } if(bs.edata == totlen) { newbuf := array[totlen+totlen] of byte; newbuf[0:] = bs.data; bs.data = newbuf; totlen += totlen; } } } nc.state = NCdone; if(config.showprogress) progresschan <-= (bs.id, Phavedata, 100, ""); break; } if(bs.err != "") { nc.state = NCerr; nc.connected = 0; if(config.showprogress) progresschan <-= (bs.id, Perr, 0, bs.err); } ngchan <-= (NGstatechg, nil, nc, ach); <- ach; } Netconn.new(id: int) : ref Netconn { return ref Netconn( id, # id "", # host 0, # port 0, # scheme sys->Connection(nil, nil, ""), # conn nil, # ssl context 0, # undetermined ssl version NCfree, # state array[10] of ref ByteSource, # queue 0, # qlen 0,0,0, # gocur, ngcur, reqsent 0, # pipeline 0, # connected 0, # tstate nil, # tbuf 0 # idlestart ); } Netconn.makefree(nc: self ref Netconn) { if(dbgproto) sys->print("NC %d: free\n", nc.id); nc.state = NCfree; nc.host = ""; nc.conn.dfd = nil; nc.conn.cfd = nil; nc.conn.dir = ""; nc.qlen = 0; nc.gocur = 0; nc.ngcur = 0; nc.reqsent = 0; nc.pipeline = 0; nc.connected = 0; nc.tbuf = nil; for(i := 0; i < len nc.queue; i++) nc.queue[i] = nil; } ByteSource.free(bs: self ref ByteSource) { if(dbgproto) sys->print("BS %d freed\n", bs.id); if(config.showprogress) if(bs.err == "") progresschan <-= (bs.id, Pdone, 100, ""); else progresschan <-= (bs.id, Perr, 0, bs.err); bs.req = nil; bs.hdr = nil; bs.data = nil; bs.err = ""; bs.net = nil; } # Return an ByteSource that is completely filled, from string s ByteSource.stringsource(s: string) : ref ByteSource { a := array of byte s; n := len a; hdr := ref Header( HCOk, # code nil, # actual nil, # base nil, # location n, # length TextHtml, # mtype UTF_8, # chset "", # msg "", # refresh "", # chal "", # warn "" # last-modified ); bs := ref ByteSource( bytesourceid++, nil, # req hdr, # hdr a, # data n, # edata "", # err nil, # net 1, # refgo 0, # refnc 0, # lim 1 # seenhdr ); return bs; } MaskedImage.free(mim: self ref MaskedImage) { mim.im = nil; mim.mask = nil; } CImage.new(src: ref U->ParsedUrl, lowsrc: ref U->ParsedUrl, width, height: int) : ref CImage { return ref CImage(src, lowsrc, nil, strhash(src.host + "/" + src.path), width, height, nil, nil, 0); } # Return true if Cimages a and b represent the same image. # As well as matching the src urls, the specified widths and heights must match too. # (Widths and heights are specified if at least one of those is not zero.) # # BUG: the width/height matching code isn't right. If one has width and height # specified, and the other doesn't, should say "don't match", because the unspecified # one should come in at its natural size. But we overwrite the width and height fields # when the actual size comes in, so we can't tell whether width and height are nonzero # because they were specified or because they're their natural size. CImage.match(a: self ref CImage, b: ref CImage) : int { if(a.imhash == b.imhash) { if(urlequal(a.src, b.src)) { return (a.width == 0 || b.width == 0 || a.width == b.width) && (a.height == 0 || b.height == 0 || a.height == b.height); # (above is not quite enough: should also check that don't have # situation where one has width set, not height, and the other has reverse, # but it is unusual for an image to have a spec in only one dimension anyway) } } return 0; } # Return approximate number of bytes in image memory used # by ci. CImage.bytes(ci: self ref CImage) : int { tot := 0; for(i := 0; i < len ci.mims; i++) { mim := ci.mims[i]; dim := mim.im; if(dim != nil) tot += ((dim.r.max.x-dim.r.min.x)>>(3-dim.ldepth)) * (dim.r.max.y-dim.r.min.y); dim = mim.mask; if(dim != nil) tot += ((dim.r.max.x-dim.r.min.x)>>(3-dim.ldepth)) * (dim.r.max.y-dim.r.min.y); } return tot; } # Call this after initial windows have been made, # so that resetlimits() will exclude the images for those # windows from the available memory. ImageCache.init(ic: self ref ImageCache) { ic.imhd = nil; ic.imtl = nil; ic.n = 0; ic.memused = 0; ic.resetlimits(); } # Call resetlimits when amount of non-image-cache image # memory might have changed significantly (e.g., on main window resize). ImageCache.resetlimits(ic: self ref ImageCache) { res := ResourceState.cur(); avail := res.imagelim - (res.image-ic.memused); # (res.image-ic.memused) is used memory not in image cache avail = 8*avail/10; # allow 20% slop for other applications, etc. ic.memlimit = config.imagecachemem; if(ic.memlimit > avail) ic.memlimit = avail; ic.nlimit = config.imagecachenum; ic.need(0); # if resized, perhaps need to shed some images } # Look for a CImage matching ci, and if found, move it # to the tail position (i.e., MRU) ImageCache.look(ic: self ref ImageCache, ci: ref CImage) : ref CImage { ans : ref CImage = nil; prev : ref CImage = nil; for(i := ic.imhd; i != nil; i = i.next) { if(i.match(ci)) { if(ic.imtl != i) { # remove from current place in cache chain # and put at tail if(prev != nil) prev.next = i.next; else ic.imhd = i.next; i.next = nil; ic.imtl.next = i; ic.imtl = i; } ans = i; break; } prev = i; } return ans; } # Call this to add ci as MRU of cache chain (should only call if # it is known that a ci with same image isn't already there). # Update ic.memused. # Assume ic.need has been called to ensure that neither # memlimit nor nlimit will be exceeded. ImageCache.add(ic: self ref ImageCache, ci: ref CImage) { ci.next = nil; if(ic.imhd == nil) ic.imhd = ci; else ic.imtl.next = ci; ic.imtl = ci; ic.memused += ci.bytes(); ic.n++; } # Delete least-recently-used image in image cache # and update memused and n. ImageCache.deletelru(ic: self ref ImageCache) { ci := ic.imhd; if(ci != nil) { ic.imhd = ci.next; if(ic.imhd == nil) { ic.imtl = nil; ic.memused = 0; } else ic.memused -= ci.bytes(); for(i := 0; i < len ci.mims; i++) ci.mims[i].free(); ci.mims = nil; ic.n--; } } ImageCache.clear(ic: self ref ImageCache) { while(ic.imhd != nil) ic.deletelru(); } # Call this just before allocating an Image that will used nbytes # of image memory, to ensure that if the image were to be # added to the image cache then memlimit and nlimit will be ok. # LRU images will be shed if necessary. # Return 0 if it will be impossible to make enough memory. ImageCache.need(ic: self ref ImageCache, nbytes: int) : int { while(ic.n >= ic.nlimit || ic.memused+nbytes > ic.memlimit) { if(ic.imhd == nil) return 0; ic.deletelru(); } return 1; } strhash(s: string) : int { prime: con 8388617; hash := 0; n := len s; for(i := 0; i < n; i++) { hash = hash % prime; hash = (hash << 7) + s[i]; } return hash; } gettransport(scheme: int) : (Transport, string) { err := ""; transport : Transport = nil; if(scheme < 0 || scheme >= Transport->Tmax) err = "Unknown scheme"; else { transport = transports[scheme]; if(transport == nil) { tpath := ""; case scheme { Url->HTTP => tpath = Transport->HTTPPATH; Url->HTTPS => tpath = Transport->HTTPPATH; Url->FTP => tpath = Transport->FTPPATH; Url->FILE => tpath = Transport->FILEPATH; } if(tpath == "") err = "Unsupported scheme"; else { transport = load Transport loadpath(tpath); if(transport == nil) err = "Can't load transport"; else { transport->init(me); transports[scheme] = transport; } } } } return (transport, err); } # Return new Header with default values for fields Header.new() : ref Header { return ref Header( HCOk, # code nil, # actual nil, # base nil, # location -1, # length UnknownType, # mtype UnknownCharset, # chset "", # msg "", # refresh "", # chal "", # warn "" # last-modified ); } jpmagic := array[] of {byte 16rFF, byte 16rD8, byte 16rFF, byte 16rE0, byte 0, byte 0, byte 'J', byte 'F', byte 'I', byte 'F', byte 0}; pngsig := array[] of { byte 137, byte 80, byte 78, byte 71, byte 13, byte 10, byte 26, byte 10 }; # Set the mtype (and possibly chset) fields of h based on (in order): # first bytes of file, if unambigous # file name extension # first bytes of file, even if unambigous (guess) # if all else fails, then leave as UnknownType. # If it's a text type, also set the chset. # (HTTP Transport will try to use Content-Type first, and call this if that # doesn't work; other Transports will have to rely on this "guessing" function.) Header.setmediatype(h: self ref Header, name: string, first: array of byte) { # Look for key signatures at beginning of file (perhaps after whitespace) n := len first; mt := UnknownType; for(i := 0; i < n; i++) if(ctype[int first[i]] != C->W) break; if(n - i >= 6) { s := string first[i:i+6]; case S->tolower(s) { "" or "
" or "