implement CharonUtils; include "common.m"; include "transport.m"; include "date.m"; include "translate.m"; date: Date; me: CharonUtils; sys: Sys; D: Draw; S: String; U: Url; T: StringIntTab; Font : import D; Parsedurl: import U; convcs : Convcs; trans : Translate; Dict : import trans; dict : ref Dict; 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"; THTTP, TFTP, TFILE, TMAX: con iota; transports := array[TMAX] of Transport; tpaths := array [TMAX] of { THTTP => Transport->HTTPPATH, TFTP => Transport->FTPPATH, TFILE => Transport->FILEPATH, }; schemes := array [] of { ("http", THTTP), ("https", THTTP), ("ftp", TFTP), ("file", TFILE), }; 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" }; 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 { HCContinue => ans = X("Continue", "http"); HCSwitchProto => ans = X("Switching Protocols", "http"); HCOk => ans = X("Ok", "http"); HCCreated => ans = X("Created", "http"); HCAccepted => ans = X("Accepted", "http"); HCOkNonAuthoritative => ans = X("Non-Authoratative Information", "http"); HCNoContent => ans = X("No content", "http"); HCResetContent => ans = X("Reset content", "http"); HCPartialContent => ans = X("Partial content", "http"); HCMultipleChoices => ans = X("Multiple choices", "http"); HCMovedPerm => ans = X("Moved permanently", "http"); HCMovedTemp => ans = X("Moved temporarily", "http"); HCSeeOther => ans = X("See other", "http"); HCNotModified => ans = X("Not modified", "http"); HCUseProxy => ans = X("Use proxy", "http"); HCBadRequest => ans = X("Bad request", "http"); HCUnauthorized => ans = X("Unauthorized", "http"); HCPaymentRequired => ans = X("Payment required", "http"); HCForbidden => ans = X("Forbidden", "http"); HCNotFound => ans = X("Not found", "http"); HCMethodNotAllowed => ans = X("Method not allowed", "http"); HCNotAcceptable => ans = X("Not Acceptable", "http"); HCProxyAuthRequired => ans = X("Proxy authentication required", "http"); HCRequestTimeout => ans = X("Request timed-out", "http"); HCConflict => ans = X("Conflict", "http"); HCGone => ans = X("Gone", "http"); HCLengthRequired => ans = X("Length required", "http"); HCPreconditionFailed => ans = X("Precondition failed", "http"); HCRequestTooLarge => ans = X("Request entity too large", "http"); HCRequestURITooLarge => ans = X("Request-URI too large", "http"); HCUnsupportedMedia => ans = X("Unsupported media type", "http"); HCRangeInvalid => ans = X("Requested range not valid", "http"); HCExpectFailed => ans = X("Expectation failed", "http"); HCServerError => ans = X("Internal server error", "http"); HCNotImplemented => ans = X("Not implemented", "http"); HCBadGateway => ans = X("Bad gateway", "http"); HCServiceUnavailable => ans = X("Service unavailable", "http"); HCGatewayTimeout => ans = X("Gateway time-out", "http"); HCVersionUnsupported => ans = X("HTTP version not supported", "http"); HCRedirectionFailed => ans = X("Redirection failed", "http"); * => ans = X("Unknown code", "http"); } 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 # 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, evc: chan of ref E->Event, cksrv: Cookiesrv, ckc: ref Cookiesrv->Client) : 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; trans = load Translate Translate->PATH; if (trans != nil) { trans->init(); (dict, nil) = trans->opendict(trans->mkdictname(nil, "charon")); } # 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); dbg = int config.dbg['d']; 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); convcs = load Convcs Convcs->PATH; if (convcs == nil) return loadpath(Convcs->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); err := convcs->init(nil); if (err != nil) return err; if(J != nil) { err = J->init(me); if (err != nil) { # non-fatal: just don't handle javascript J = nil; if (dbg) sys->print("%s\n", err); } } B->init(me); LX->init(me); date->init(me); if (config.docookies) { CK = cksrv; ckclient = ckc; if (CK == nil) { path := loadpath(Cookiesrv->PATH); CK = load Cookiesrv path; if (CK == nil) sys->print("cookies: cannot load server %s: %r\n", path); else ckclient = CK->start(config.userdir + "/cookies", 0); } } # preload some transports gettransport("http"); gettransport("file"); progresschan = chan of (int, int, int, string); imcache = ref ImageCache; ctype = C->ctype; dbgproto = int config.dbg['p']; ngchan = chan of (int, list of ref ByteSource, ref Netconn, chan of ref ByteSource); 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); G->progress <-= (bs.id, G->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, # eof 0, # lim 0 # seenhdr ); G->progress <-= (bs.id, G->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; for(n = 0; n < len netconns; n++) netconns[n] = Netconn.new(n); # capture netget chan to prevent abortgo() reset of # ngchan from breaking us (channel hungup) before kill() does its job ngc := ngchan; mainloop: for(;;) { (msg,bsl,nc,c) = <- ngc; 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 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) { 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"); for (scanlist := bsl; scanlist != nil; scanlist = tl scanlist) { bs := hd scanlist; if (bs.refnc == 0) { # string ByteSource or completed or error if (bs.err != nil || bs.edata >= bs.lim) { c <-= bs; continue mainloop; } continue; } # netcon based bytesource if ((bs.hdr != nil && !bs.seenhdr && bs.hdr.mtype != UnknownType) || bs.edata > bs.lim) { 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(); } } # 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); } (t, nil) := gettransport(nc.scheme); spawn runnetconn(nc, t); } 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; retry := 4; # retry := 0; 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 != "") { if (retry) { retry--; bs.err = ""; sys->sleep(100); continue eloop; } break eloop; } nc.state = NCgethdr; } assert(nc.state == NCgethdr && nc.connected); if(nc.scheme == "https") G->progress <-= (bs.id, G->Psslconnected, 0, ""); else G->progress <-= (bs.id, G->Pconnected, 0, ""); t->writereq(nc, bs); nc.reqsent++; if (bs.err != "") { if (retry) { retry--; bs.err = ""; nc.state = NCconnect; sys->sleep(100); continue eloop; } break eloop; } # managed to write the request # do not retry if we are doing form POSTs # See RFC1945 section 12.2 "Safe Methods" if (bs.req.method == HPost) retry = 0; # Get the header t->gethdr(nc, bs); if(bs.err != "") { if (retry) { retry--; bs.err = ""; nc.state = NCconnect; sys->sleep(100); continue eloop; } break eloop; } assert(bs.hdr != nil); G->progress <-= (bs.id, G->Phavehdr, 0, ""); nc.state = NCgetdata; # read enough data to guess media type while (bs.hdr.mtype == UnknownType && ncgetdata(t, nc, bs)) bs.hdr.setmediatype(bs.hdr.actual.path, bs.data[:bs.edata]); if (bs.hdr.mtype == UnknownType) { bs.hdr.mtype = TextPlain; bs.hdr.chset = "utf8"; } ngchan <-= (NGstatechg,nil,nc,ach); <- ach; while (ncgetdata(t, nc, bs)) { ngchan <-= (NGstatechg,nil,nc,ach); <- ach; } nc.state = NCdone; G->progress <-= (bs.id, G->Phavedata, 100, ""); break; } if(bs.err != "") { nc.state = NCerr; nc.connected = 0; G->progress <-= (bs.id, G->Perr, 0, bs.err); } bs.eof = 1; ngchan <-= (NGstatechg, nil, nc, ach); <- ach; } ncgetdata(t: Transport, nc: ref Netconn, bs: ref ByteSource): int { hdr := bs.hdr; if (bs.data == nil) { blen := hdr.length; if (blen <= 0) { if(hdr.code == HCOk || hdr.code == HCOkNonAuthoritative) blen = UBufsize; else blen = UEBufsize; } bs.data = array[blen] of byte; } nr := 0; if (hdr.length > 0) { if (bs.edata == hdr.length) return 0; nr = t->getdata(nc, bs); if (nr <= 0) return 0; } else { # don't know data length - keep growing input buffer as needed if (bs.edata == len bs.data) { nd := array [2*len bs.data] of byte; nd[:] = bs.data; bs.data = nd; } nr = t->getdata(nc, bs); if (nr <= 0) { # assume EOF bs.data = bs.data[0:bs.edata]; bs.err = ""; hdr.length = bs.edata; nc.connected = 0; return 0; } } bs.edata += nr; G->progress <-= (bs.id, G->Phavedata, 100*bs.edata/len bs.data, ""); return 1; } Netconn.new(id: int) : ref Netconn { return ref Netconn( id, # id "", # host 0, # port "", # 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.tstate = 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(bs.err == "") G->progress <-= (bs.id, G->Pdone, 100, ""); else G->progress <-= (bs.id, G->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 "utf8", # 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 1, # eof - edata is final 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; } schemeid(s: string): int { for (i := 0; i < len schemes; i++) { (n, id) := schemes[i]; if (n == s) return id; } return -1; } schemeok(s: string): int { return schemeid(s) != -1; } gettransport(scheme: string) : (Transport, string) { err := ""; transport: Transport = nil; tindex := schemeid(scheme); if (tindex == -1) return (nil, "Unknown scheme"); transport = transports[tindex]; if (transport == nil) { transport = load Transport loadpath(tpaths[tindex]); if(transport == nil) return (nil, sys->sprint("Can't load transport %s: %r", tpaths[tindex])); transport->init(me); transports[tindex] = 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 nil, # 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 "