diff options
Diffstat (limited to 'appl/svc')
45 files changed, 6868 insertions, 0 deletions
diff --git a/appl/svc/auth.sh b/appl/svc/auth.sh new file mode 100644 index 00000000..25c8d520 --- /dev/null +++ b/appl/svc/auth.sh @@ -0,0 +1,13 @@ +#!/dis/sh.dis -n +load std +or {ftest -e /net/dns} {ftest -e /env/emuhost} {ndb/dns} +or {ftest -e /net/cs} {ndb/cs} +or {ftest -f /keydb/signerkey} {echo 'auth: need to use createsignerkey(8)' >[1=2]; exit nosignerkey} +or {ftest -f /keydb/keys} {echo 'auth: need to create /keydb/keys' >[1=2]; exit nokeys} +and {auth/keyfs} { + listen -v -t -A 'tcp!*!inflogin' {auth/logind&} + listen -v -t -A 'tcp!*!infkey' {auth/keysrv&} + listen -v -t -A 'tcp!*!infsigner' {auth/signer&} + listen -v -t -A 'tcp!*!infcsigner' {auth/countersigner&} +} +# run svc/registry separately if desired diff --git a/appl/svc/httpd/alarms.b b/appl/svc/httpd/alarms.b new file mode 100644 index 00000000..5142906d --- /dev/null +++ b/appl/svc/httpd/alarms.b @@ -0,0 +1,45 @@ +implement Alarms; +include "sys.m"; + sys: Sys; + +include "alarms.m"; + +Alarm.stop(a: self Alarm) +{ + a.alchan <-= -1; + fd:=sys->open("#p/"+string a.pid+"/ctl",sys->OWRITE); + sys->fprint(fd, "killgrp"); +} + +Alarm.alarm(time: int): Alarm +{ + if (sys == nil) + sys = load Sys Sys->PATH; + + pid := sys->pctl(sys->NEWPGRP|sys->FORKNS,nil); + a:=Alarm(chan of int,pid); + spawn listener(a.alchan); + spawn sleeper(a.alchan,time,pid); + return a; +} + +sleeper(ch: chan of int, time, pid: int) +{ + sys->sleep(time); + alt{ + ch <-= pid => + ; + * => + exit; + } +} + +listener(ch: chan of int) +{ + a := <-ch; + if (a==-1) + exit; + fd := sys->open("#p/"+string a+"/ctl",sys->OWRITE); + if (fd != nil) + sys->fprint(fd, "killgrp"); +} diff --git a/appl/svc/httpd/alarms.m b/appl/svc/httpd/alarms.m new file mode 100644 index 00000000..b422c48f --- /dev/null +++ b/appl/svc/httpd/alarms.m @@ -0,0 +1,11 @@ +Alarms: module{ + PATH: con "/dis/svc/httpd/alarms.dis"; + + Alarm: adt{ + alchan: chan of int; + pid: int; + stop: fn(a: self Alarm); + alarm: fn(time: int): Alarm; + }; + +}; diff --git a/appl/svc/httpd/cache.b b/appl/svc/httpd/cache.b new file mode 100644 index 00000000..3d4549e4 --- /dev/null +++ b/appl/svc/httpd/cache.b @@ -0,0 +1,188 @@ +implement Cache; + +include "sys.m"; + sys : Sys; + +include "bufio.m"; + bufio : Bufio; +Iobuf : import bufio; + +include "lock.m"; + locks: Lock; + Semaphore: import locks; + +dbg_log : ref Sys->FD; + +include "cache.m"; + +HASHSIZE : con 1019; + +lru ,cache_size : int; # lru link, and maximum size of cache. +cur_size, cur_tag : int; # current size of cache and current number. + +lock: ref Semaphore; + +Cache_link : adt{ + name : string; # name of file + contents : array of byte; # contents + length : int; # length of file + qid:Sys->Qid; + tag : int; +}; + +tab := array[HASHSIZE] of list of Cache_link; + +hashasu(key : string,n : int): int +{ + i, h : int; + h=0; + i=0; + while(i<len key){ + h = 10*h + key[i]; + h = h%n; + i++; + } + return h%n; +} + + +insert(name: string, ctents: array of byte , length : int, qid:Sys->Qid) : int +{ + tmp : Cache_link; + hash : int; + lock.obtain(); + hash = hashasu(name,HASHSIZE); + if (dbg_log!=nil){ + sys->fprint(dbg_log,"current size is %d, adding %s\n", cur_size,name); + } + while (cur_size+length > cache_size) + throw_out(); + tmp.name =name; + tmp.contents = ctents; + tmp.length = length; + tmp.qid = qid; + tmp.tag = cur_tag; + cur_size+=length; + cur_tag++; + if (cur_tag<0) cur_tag=0; + tab[hash]= tmp :: tab[hash]; + lock.release(); + return 1; +} + +find(name : string, qid:Sys->Qid) : (int, array of byte) +{ + hash,flag,stale : int; + nlist : list of Cache_link; + retval : array of byte; + flag=0; + nlist=nil; + retval=nil; + stale=0; + lock.obtain(); + hash = hashasu(name,HASHSIZE); + tmp:=tab[hash]; + for(;tmp!=nil;tmp = tl tmp){ + link:=hd tmp; + if (link.name==name){ + if(link.qid.path==qid.path && link.qid.vers==qid.vers){ + link.tag=cur_tag; + cur_tag++; + flag = 1; + retval = (hd tmp).contents; + } else { # cache is stale + lru--; if(lru<0) lru = 0; + link.tag = lru; + stale = 1; + } + } + nlist = link :: nlist; + } + tab[hash]=nlist; + if (flag && (dbg_log!=nil)) + sys->fprint(dbg_log,"Found %s in cache, cur_tag is %d\n",name,cur_tag); + if (stale){ + if (dbg_log!=nil) + sys->fprint(dbg_log,"Stale %s in cache\n",name); + throw_out(); + } + lock.release(); + return (flag,retval); +} + +throw_out() +{ + nlist : list of Cache_link; + for(i:=0;i<HASHSIZE;i++){ + tmp:=tab[i]; + for(;tmp!=nil;tmp = tl tmp) + if ((hd tmp).tag==lru) + break; + if (tmp!=nil) + break; + } + if (i==HASHSIZE && (dbg_log!=nil)) + sys->fprint(dbg_log,"LRU not found!!!\n"); + # now, the lru is in tab[i]... + nlist=nil; + for(;tab[i]!=nil;tab[i]=tl tab[i]){ + if ((hd tab[i]).tag==lru){ + if (dbg_log!=nil) + sys->fprint(dbg_log,"Throwing out %s\n",(hd tab[i]).name); + cur_size-=(hd tab[i]).length; + tab[i] = tl tab[i]; + } + if (tab[i]!=nil) + nlist = (hd tab[i]) :: nlist; + if (tab[i]==nil) break; + } + lru=find_lru(); + if (dbg_log!=nil) + sys->fprint(dbg_log,"New lru is %d",lru); + tab[i] = nlist; +} + +find_lru() : int +{ + min := cur_tag; + for(i:=0;i<HASHSIZE;i++){ + tmp:=tab[i]; + for(;tmp!=nil;tmp = tl tmp) + if ((hd tmp).tag<min) + min=(hd tmp).tag; + } + return min; +} + +cache_init(log : ref Sys->FD, csize : int) +{ + n : int; + for(n=0;n<HASHSIZE;n++) + tab[n]= nil; + lru=0; + cur_size=0; + cache_size = csize*1024; + sys = load Sys Sys->PATH; + locks = load Lock Lock->PATH; + locks->init(); + lock = Semaphore.new(); + dbg_log = log; + if (dbg_log!=nil) + sys->fprint(dbg_log,"Cache initialised, max size is %d K\n",cache_size); +} + +dump() : list of (string,int,int) +{ + retval: list of (string,int,int); + lock.obtain(); + for(i:=0;i<HASHSIZE;i++){ + tmp:=tab[i]; + while(tmp!=nil){ + retval = ((hd tmp).name, (hd tmp).length, + (hd tmp).tag) :: retval; + tmp = tl tmp; + } + } + lock.release(); + return retval; +} diff --git a/appl/svc/httpd/cache.m b/appl/svc/httpd/cache.m new file mode 100644 index 00000000..15c6c59e --- /dev/null +++ b/appl/svc/httpd/cache.m @@ -0,0 +1,9 @@ +Cache : module +{ + PATH: con "/dis/svc/httpd/cache.dis"; + + cache_init: fn(log : ref Sys->FD, size : int); + insert : fn(name: string, ctents: array of byte, length : int, qid:Sys->Qid) : int; + find: fn(name : string, qid:Sys->Qid) : (int,array of byte); + dump : fn() : list of (string,int,int); +}; diff --git a/appl/svc/httpd/cgiparse.b b/appl/svc/httpd/cgiparse.b new file mode 100644 index 00000000..4ff0d87c --- /dev/null +++ b/appl/svc/httpd/cgiparse.b @@ -0,0 +1,258 @@ +implement CgiParse; + +include "sys.m"; + sys: Sys; +include "draw.m"; +include "string.m"; + str: String; +include "bufio.m"; +include "daytime.m"; + daytime : Daytime; +include "parser.m"; + parser : Parser; +include "contents.m"; +include "cache.m"; +include "httpd.m"; + Private_info: import Httpd; +include "cgiparse.m"; + +stderr : ref Sys->FD; + +cgiparse(g: ref Private_info, req: Httpd->Request): ref CgiData +{ + ret: ref CgiData; + (ok, err) := loadmodules(); + if(ok == -1) { + sys->fprint(stderr, "CgiParse: %s\n", err ); + return nil; + } + + (ok, err, ret) = parse(g, req); + + if(ok < 0){ + sys->fprint( stderr, "CgiParse: %s\n", err ); + return nil; + } + return ret; +} + +badmod(p: string): (int, string) +{ + return (-1, sys->sprint("cannot load %s: %r", p)); +} + +loadmodules(): (int, string) +{ + if( sys == nil ) + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + if(daytime == nil) + daytime = load Daytime Daytime->PATH; + if(daytime == nil) + return badmod(Daytime->PATH); + if(str == nil) + str = load String String->PATH; + if(str == nil) + return badmod(String->PATH); + if( parser == nil ) + parser = load Parser Parser->PATH; + if( parser == nil ) + return badmod(Parser->PATH); + return (0, nil); +} + +parse(g: ref Private_info, req: Httpd->Request) : (int, string, ref CgiData) +{ + bufio := g.bufio; + Iobuf: import bufio; + + host, remote, referer, httphd : string; + form: list of (string, string); + + tmstamp := daytime->time(); + + (method, version, uri, search) := (req.method, req.version, req.uri, req.search); + + if(version != ""){ + if( g.version == nil ) + return (-1, "version unknown.", nil); + if( g.bout == nil ) + return (-1, "internal error, g.bout is nil.", nil); + if( g.bin == nil ) + return (-1, "internal error, g.bin is nil.", nil); + httphd = g.version + " 200 OK\r\n" + + "Server: Inferno-Httpd\r\n" + + "MIME-version: 1.0\r\n" + + "Date: " + tmstamp + "\r\n" + + "Content-type: text/html\r\n" + + "\r\n"; + } + + hstr := ""; + lastnl := 1; + eof := 0; + while((c := g.bin.getc()) != bufio->EOF ) { + if (c == '\r' ) { + hstr[len hstr] = c; + c = g.bin.getb(); + if( c == bufio->EOF ){ + eof = 1; + break; + } + } + hstr[len hstr] = c; + if(c == '\n' ){ + if( lastnl ) + break; + lastnl = 1; + } + else + lastnl = 0; + } + host = g.host; + remote = g.remotesys; + referer = g.referer; + (cnt, header) := parseheader( hstr ); + method = str->toupper( method); + if (method == "POST") { + s := ""; + while(!eof && cnt && (c = g.bin.getc()) != '\n' ) { + s[len s] = c; + cnt--; + if( c == '\r' ) + eof = 1; + } + form = parsequery(s); + } + for (ql := parsequery(req.search); ql != nil; ql = tl ql) + form = hd ql :: form; + return (0, nil, + ref CgiData(method, version, uri, search, tmstamp, host, remote, referer, + httphd, header, form)); +} + +parseheader(hstr: string): (int, list of (string, string)) +{ + header : list of (string, string); + cnt := 0; + if( hstr == nil || len hstr == 0 ) + return (0, nil); + (n, sl) := sys->tokenize( hstr, "\r\n" ); + if( n <= 0 ) + return (0, nil); + while( sl != nil ){ + s := hd sl; + sl = tl sl; + for( i := 0; i < len s; i++ ){ + if( s[i] == ':' ){ + tag := s[0:i+1]; + val := s[i+1:]; + if( val[len val - 1] == '\r' ) + val[len val - 1] = ' '; + if( val[len val - 1] == '\n' ) + val[len val - 1] = ' '; + header = (tag, val) :: header; + if(str->tolower( tag ) == "content-length:" ){ + if( val != nil && len val > 0 ) + cnt = int val; + else + cnt = 0; + } + break; + } + } + } + return (cnt, listrev( header )); +} + +listrev(s: list of (string, string)): list of (string, string) +{ + tmp : list of (string, string); + while( s != nil ) { + tmp = hd s :: tmp; + s = tl s; + } + return tmp; +} + +getbaseip() : string +{ + buf : array of byte; + fd := sys->open( "/net/bootp", Sys->OREAD ); + if( fd != nil ){ + (n, d) := sys->fstat( fd ); + if( n >= 0 ){ + if(int d.length > 0 ) + buf = array [int d.length] of byte; + else + buf = array [128] of byte; + n = sys->read( fd, buf, len buf ); + if( n > 0 ){ + (nil, sl) := sys->tokenize( string buf[0:n], " \t\n" ); + while( sl != nil ){ + if( hd sl == "ipaddr" ){ + sl = tl sl; + break; + } + sl = tl sl; + } + if( sl != nil ) + return "http://" + (hd sl); + } + } + } + return "http://beast2"; +} + +getbase() : string +{ + fd := sys->open( "/dev/sysname", Sys->OREAD ); + if( fd != nil ){ + buf := array [128] of byte; + n := sys->read( fd, buf, len buf ); + if( n > 0 ) + return "http://" + string buf[0:n]; + } + return "http://beast2"; +} + +gethost() : string +{ + fd := sys->open( "/dev/sysname", Sys->OREAD ); + if(fd != nil) { + buf := array [128] of byte; + n := sys->read( fd, buf, len buf ); + if( n > 0 ) + return string buf[0:n]; + } + return "none"; +} + +# parse a search string of the form +# tag=val&tag1=val1... +parsequery(search : string): list of (string, string) +{ + q: list of (string, string); + tag, val : string; + if (contains(search, '?')) + (nil,search) = str->splitr(search,"?"); + while(search!=nil){ + (tag,search) = str->splitl(search,"="); + if (search != nil) { + search=search[1:]; + (val,search) = str->splitl(search,"&"); + if (search!=nil) + search=search[1:]; + q = (parser->urlunesc(tag), parser->urlunesc(val)) :: q; + } + } + return q; +} + +contains(s: string, c: int): int +{ + for (i := 0; i < len s; i++) + if (s[i] == c) + return 1; + return 0; +} diff --git a/appl/svc/httpd/cgiparse.m b/appl/svc/httpd/cgiparse.m new file mode 100644 index 00000000..af521125 --- /dev/null +++ b/appl/svc/httpd/cgiparse.m @@ -0,0 +1,22 @@ +CgiData : adt { + method : string; + version : string; + uri : string; + search : string; + tmstamp : string; + host : string; + remote : string; + referer : string; + httphd : string; + header : list of (string, string); + form : list of (string, string); +}; + +CgiParse : module +{ + PATH : con "/dis/svc/httpd/cgiparse.dis"; + cgiparse : fn( g : ref Httpd->Private_info, req: Httpd->Request): ref CgiData; + getbase : fn() : string; + gethost : fn() : string; +}; + diff --git a/appl/svc/httpd/contents.b b/appl/svc/httpd/contents.b new file mode 100644 index 00000000..5e57dd25 --- /dev/null +++ b/appl/svc/httpd/contents.b @@ -0,0 +1,192 @@ +implement Contents; + +include "sys.m"; + sys: Sys; + dbg_log : ref Sys->FD; +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; +Iobuf : import bufio; + +include "contents.m"; + +include "cache.m"; + +include "httpd.m"; + +include "string.m"; + str : String; + +Suffix: adt{ + suffix : string; + generic : string; + specific : string; + encoding : string; +}; + +suffixes: list of Suffix; + +#internal functions... +parsesuffix : fn(nil:string): (int,Suffix); + +mkcontent(generic,specific : string): ref Content +{ + c:= ref Content; + c.generic = generic; + c.specific = specific; + c.q = real 1; + return c; +} + +badmod(m: string) +{ + sys->fprint(stderr(), "contents: cannot load %s: %r\n", m); + raise "fail:bad module"; +} + +contentinit(log: ref Sys->FD) +{ + if(suffixes != nil) + return; + + sys = load Sys Sys->PATH; + + bufio = load Bufio Bufio->PATH; + if (bufio == nil) badmod(Bufio->PATH); + + str = load String String->PATH; + if (str == nil) badmod(String->PATH); + + iob := bufio->open(Httpd->HTTP_SUFF, bufio->OREAD); + if (iob==nil) { + sys->fprint(stderr(), "contents: cannot open %s: %r\n", Httpd->HTTP_SUFF); + raise "fail:no suffix file";; + } + while((s := iob.gets('\n'))!=nil) { + (i, su) := parsesuffix(s); + if (i != 0) + suffixes = su :: suffixes; + } + dbg_log = log; +} + +# classify by file name extensions + +uriclass(name : string): (ref Content, ref Content) +{ + s : Suffix; + typ, enc: ref Content; + p : string; + lis := suffixes; + typ=nil; + enc=nil; + uri:=name; + (nil,p) = str->splitr(name,"/"); + if (p!=nil) name=p; + + if(str->in('.',name)){ + (nil,p) = str->splitl(name,"."); + for(s = hd lis; lis!=nil; lis = tl lis){ + if(p == s.suffix){ + if(s.generic != nil && typ==nil) + typ = mkcontent(s.generic, s.specific); + if(s.encoding != nil && enc==nil) + enc = mkcontent(s.encoding, ""); + } + s = hd lis; + } + } + if(typ == nil && enc == nil){ + buff := array[64] of byte; + fd := sys->open(uri, sys->OREAD); + n := sys->read(fd, buff, len buff); + if(n > 0){ + tmp := string buff[0:n]; + (typ, enc) = dataclass(tmp); + } + } + return (typ, enc); +} + + +parsesuffix(line: string): (int, Suffix) +{ + s : Suffix; + if (str->in('#',line)) + (line,nil) = str->splitl(line, "#"); + if (line!=nil){ + (n,slist):=sys->tokenize(line,"\n\t "); + if (n!=4 && n!=0){ + if (dbg_log!=nil) + sys->fprint(dbg_log,"Error in suffixes file!, n=%d\n",n); + sys->print("Error in suffixes file!, n=%d\n",n); + exit; + } + s.suffix = hd slist; + slist = tl slist; + s.generic = hd slist; + if (s.generic == "-") s.generic=""; + slist = tl slist; + s.specific = hd slist; + if (s.specific == "-") s.specific=""; + slist = tl slist; + s.encoding = hd slist; + if (s.encoding == "-") s.encoding=""; + + } + if (((s.generic == "")||(s.specific == "")) && s.encoding=="") + return (0,s); + return (1,s); +} + +#classify by initial contents of file +dataclass(buf : string): (ref Content,ref Content) +{ + c,n : int; + c=0; + n = len buf; + for(; n > 0; n --){ + if(buf[c] < 16r80) + if(buf[c] < 32 && buf[c] != '\n' && buf[c] != '\r' + && buf[c] != '\t' && buf[c] != '\v') + return (nil,nil); + c++; + } + return (mkcontent("text", "plain"),nil); +} + +checkcontent(me: ref Content,oks :list of ref Content, clist : string): int +{ + ok:=oks; + try : ref Content; + if(oks == nil || me == nil) + return 1; + for(; ok != nil; ok = tl ok){ + try = hd ok; + if((try.generic==me.generic || try.generic=="*") + && (try.specific==me.specific || try.specific=="*")){ + return 1; + } + } + + sys->fprint(dbg_log,"%s/%s not found", + me.generic, me.specific); + logcontent(clist, oks); + return 1; +} + +logcontent(name : string, c : list of ref Content) +{ + buf : string; + if (dbg_log!=nil){ + for(; c!=nil; c = tl c) + buf+=sys->sprint("%s/%s ", (hd c).generic,(hd c).specific); + sys->fprint(dbg_log,"%s: %s: %s", "client", name, buf); + } +} + +stderr(): ref Sys->FD +{ + return sys->fildes(2); +} diff --git a/appl/svc/httpd/contents.m b/appl/svc/httpd/contents.m new file mode 100644 index 00000000..e8a3927b --- /dev/null +++ b/appl/svc/httpd/contents.m @@ -0,0 +1,16 @@ +Contents: module +{ + PATH: con "/dis/svc/httpd/contents.dis"; + + Content: adt{ + generic: string; + specific: string; + q: real; + }; + + contentinit: fn(log : ref Sys->FD); + mkcontent: fn(specific,generic : string): ref Content; + uriclass: fn(name : string): (ref Content, ref Content); + checkcontent: fn(me: ref Content,oks :list of ref Content, + clist : string): int; +}; diff --git a/appl/svc/httpd/date.b b/appl/svc/httpd/date.b new file mode 100644 index 00000000..9a8ff10a --- /dev/null +++ b/appl/svc/httpd/date.b @@ -0,0 +1,264 @@ +implement Date; + +include "sys.m"; + sys: Sys; + +include "daytime.m"; + daytime : Daytime; + +Tm: import daytime; + +include "date.m"; + + # print dates in the format + # Wkd, DD Mon YYYY HH:MM:SS GMT + # parse dates of formats + # Wkd, DD Mon YYYY HH:MM:SS GMT + # Weekday, DD-Mon-YY HH:MM:SS GMT + # Wkd Mon ( D|DD) HH:MM:SS YYYY + # plus anything similar + +SEC2MIN: con 60; +SEC2HOUR: con (60*SEC2MIN); +SEC2DAY: con (24*SEC2HOUR); + +# days per month plus days/year + +dmsize := array[] of { + 365, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 +}; + +ldmsize := array[] of { + 366, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 +}; + +# return the days/month for the given year + + +weekdayname := array[] of { + "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" +}; + +wdayname := array[] of { + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" +}; + + +monname := array[] of { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" +}; + + +init() +{ + sys = load Sys Sys->PATH; + daytime = load Daytime Daytime->PATH; + if (daytime==nil){ + sys->print("daytime load: %r\n"); + exit; + } +} + +yrsize(yr : int): array of int +{ + if(yr % 4 == 0 && (yr % 100 != 0 || yr % 400 == 0)) + return ldmsize; + else + return dmsize; +} + +tolower(c: int): int +{ + if(c >= 'A' && c <= 'Z') + return c - 'A' + 'a'; + return c; +} + + +isalpha(c: int): int +{ + return c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z'; +} + + +isdig(c: int): int +{ + return c >= '0' && c <= '9'; +} + + +dateconv(t: int): string +{ + tm : ref Tm; + tm = daytime->gmt(t); + return sys->sprint("%s, %.2d %s %.4d %.2d:%.2d:%.2d GMT", + wdayname[tm.wday], tm.mday, monname[tm.mon], tm.year+1900, + tm.hour, tm.min, tm.sec); +} + + +dateword(date : string): (string,string) +{ + p : string; + i:=0; + p = ""; + while((i<len date) && !isalpha(date[i]) && !isdig(date[i])) + i++; + while((i<len date) && isalpha(date[i])){ + p[len p] = tolower(date[i]); + i++; + } + return (date[i:],p); +} + + +datenum(date : string): (string, int) +{ + n, i : int; + i=0; + while((i<len date) && !isdig(date[i])) + i++; + if(i == len date) + return (nil, -1); + n = 0; + while((i<len date) && isdig(date[i])){ + n = n * 10 + date[i] - '0'; + i++; + } + return (date[i:], n); +} + + + # parse a date and return the seconds since the epoch + # return 0 for a failure + +# could be big? +date2sec(date : string): int +{ + tm : Tm; + buf : string; + + # Weekday|Wday + + (date,buf) = dateword(date); + tm.wday = dateindex(buf, wdayname); + if(tm.wday < 0) + tm.wday = dateindex(buf, weekdayname); + + if(tm.wday < 0) + return 0; + + # check for the two major formats + + (date,buf) = dateword(date); + tm.mon = dateindex(buf, monname); + if(tm.mon >= 0){ + # MM + (date, tm.mday) = datenum(date); + if(tm.mday < 1 || tm.mday > 31) + return 0; + + # HH:MM:SS + (date, tm.hour) = datenum(date); + if(tm.hour < 0 || tm.hour >= 24) + return 0; + (date, tm.min) = datenum(date); + if(tm.min < 0 || tm.min >= 60) + return 0; + (date, tm.sec) = datenum(date); + if(tm.sec < 0 || tm.sec >= 60) + return 0; + + + # YYYY + (nil, tm.year) = datenum(date); + if(tm.year < 70 || tm.year > 99 && tm.year < 1970) + return 0; + if(tm.year >= 1970) + tm.year -= 1900; + }else{ + # MM-Mon-(YY|YYYY) + (date, tm.mday) = datenum(date); + if(tm.mday < 1 || tm.mday > 31) + return 0; + (date,buf) = dateword(date); + tm.mon = dateindex(buf, monname); + if(tm.mon < 0 || tm.mon >= 12) + return 0; + (date, tm.year) = datenum(date); + if(tm.year < 70 || tm.year > 99 && tm.year < 1970) + return 0; + if(tm.year >= 1970) + tm.year -= 1900; + + # HH:MM:SS + (date, tm.hour) = datenum(date); + if(tm.hour < 0 || tm.hour >= 24) + return 0; + (date, tm.min) = datenum(date); + if(tm.min < 0 || tm.min >= 60) + return 0; + (date, tm.sec) = datenum(date); + if(tm.sec < 0 || tm.sec >= 60) + return 0; + + # timezone + (date,buf)=dateword(date); + if(buf[0:3]!="gmt") + return 0; + } + + tm.zone="GMT"; + return gmtm2sec(tm); +} + +lowercase(name:string): string +{ + p: string; + for(i:=0;i<len name;i++) + p[i]=tolower(name[i]); + return p; +} + +dateindex(d : string, tab : array of string): int +{ + for(i := 0; i < len tab; i++) + if (lowercase(tab[i]) == d) + return i; + return -1; +} + + +# compute seconds since Jan 1 1970 GMT + +gmtm2sec(tm:Tm): int +{ + secs,i : int; + d2m: array of int; + secs=0; + + #seconds per year + tm.year += 1900; + if(tm.year < 1970) + return 0; + for(i = 1970; i < tm.year; i++){ + d2m = yrsize(i); + secs += d2m[0] * SEC2DAY; + } + + + #seconds per month + d2m = yrsize(tm.year); + for(i = 0; i < tm.mon; i++) + secs += d2m[i+1] * SEC2DAY; + + #secs in last month + secs += (tm.mday-1) * SEC2DAY; + + #hours, minutes, seconds + secs += tm.hour * SEC2HOUR; + secs += tm.min * SEC2MIN; + secs += tm.sec; + + return secs; +} diff --git a/appl/svc/httpd/date.m b/appl/svc/httpd/date.m new file mode 100644 index 00000000..eafd6b49 --- /dev/null +++ b/appl/svc/httpd/date.m @@ -0,0 +1,11 @@ + +Date: module{ + PATH : con "/dis/svc/httpd/date.dis"; + + init: fn(); + dateconv: fn(secs :int): string; # returns an http formatted + # date representing secs. + date2sec: fn(foo:string): int; # parses a date and returns + # number of secs since the + # epoch that it represents. +}; diff --git a/appl/svc/httpd/echo.b b/appl/svc/httpd/echo.b new file mode 100644 index 00000000..686a86d2 --- /dev/null +++ b/appl/svc/httpd/echo.b @@ -0,0 +1,89 @@ +implement echo; + +include "sys.m"; + sys: Sys; +stderr: ref Sys->FD; +include "bufio.m"; + +include "draw.m"; +draw : Draw; + +include "cache.m"; +include "contents.m"; +include "httpd.m"; + Private_info: import Httpd; + +include "cgiparse.m"; +cgiparse: CgiParse; + +echo: module +{ + init: fn(g: ref Private_info, req: Httpd->Request); +}; + +init(g: ref Private_info, req: Httpd->Request) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + cgiparse = load CgiParse CgiParse->PATH; + if( cgiparse == nil ) { + sys->fprint( stderr, "echo: cannot load %s: %r\n", CgiParse->PATH); + return; + } + + send(g, cgiparse->cgiparse(g, req)); +} + +send(g: ref Private_info, cgidata: ref CgiData ) +{ + bufio := g.bufio; + Iobuf: import bufio; + if( cgidata == nil ){ + g.bout.flush(); + return; + } + + g.bout.puts( cgidata.httphd ); + + g.bout.puts("<head><title>Echo</title></head>\r\n"); + g.bout.puts("<body><h1>Echo</h1>\r\n"); + g.bout.puts(sys->sprint("You requested a %s on %s", + cgidata.method, cgidata.uri)); + if(cgidata.search!=nil) + g.bout.puts(sys->sprint(" with search string %s", cgidata.search)); + g.bout.puts(".\n"); + + g.bout.puts("Your client sent the following headers:<p><pre>"); + g.bout.puts( "Client: " + cgidata.remote + "\n" ); + g.bout.puts( "Date: " + cgidata.tmstamp + "\n" ); + g.bout.puts( "Version: " + cgidata.version + "\n" ); + while( cgidata.header != nil ){ + (tag, val) := hd cgidata.header; + g.bout.puts( tag + " " + val + "\n" ); + cgidata.header = tl cgidata.header; + } + + g.bout.puts("</pre>\n"); + if (cgidata.form != nil){ + i := 0; + g.bout.puts("</pre>"); + g.bout.puts("Your client sent the following form data:<p>"); + g.bout.puts("<table>\n"); + while(cgidata.form!=nil){ + (tag, val) := hd cgidata.form; + g.bout.puts(sys->sprint("<tr><td>%d</td><td><I> ",i)); + g.bout.puts(tag); + g.bout.puts("</I></td> "); + g.bout.puts("<td><B> "); + g.bout.puts(val); + g.bout.puts("</B></td></tr>\n"); + g.bout.puts("\n"); + cgidata.form = tl cgidata.form; + i++; + } + g.bout.puts("</table>\n"); + } + g.bout.puts("</body>\n"); + g.bout.flush(); +} + diff --git a/appl/svc/httpd/httpd.b b/appl/svc/httpd/httpd.b new file mode 100644 index 00000000..e8cf84ea --- /dev/null +++ b/appl/svc/httpd/httpd.b @@ -0,0 +1,721 @@ +implement Httpd; + +include "sys.m"; + sys: Sys; + +Dir: import sys; +FD : import sys; + +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "string.m"; + str: String; + +include "readdir.m"; + readdir: Readdir; + +include "daytime.m"; + daytime : Daytime; + +include "cache.m"; + cache : Cache; + +include "contents.m"; + contents: Contents; + Content: import contents; + +include "httpd.m"; + +include "parser.m"; + parser : Parser; + +include "date.m"; + date: Date; + +include "redirect.m"; + redir: Redirect; + +include "alarms.m"; + alarms: Alarms; + Alarm: import alarms; + +# globals + +cache_size: int; +port := "80"; +addr: string; +stderr : ref FD; +dbg_log, logfile: ref FD; +debug: int; +my_domain: string; + +usage() +{ + sys->fprint(stderr, "usage: httpd [-c num] [-D] [-a servaddr]\n"); + raise "fail:usage"; +} + +atexit(g: ref Private_info) +{ + debug_print(g,"At exit from httpd, closing fds. \n"); + g.bin.close(); + g.bout.close(); + g.bin=nil; + g.bout=nil; + exit; +} + +debug_print(g : ref Private_info,message : string) +{ + if (g.dbg_log!=nil) + sys->fprint(g.dbg_log,"%s",message); +} + +parse_args(args : list of string) +{ + while(args!=nil){ + case (hd args){ + "-c" => + args = tl args; + cache_size = int hd args; + "-D" => + debug=1; + "-p" => + args = tl args; + port = hd args; + "-a" => + args = tl args; + addr = hd args; + } + args = tl args; + } +} + +badmod(m: string) +{ + sys->fprint(stderr, "httpd: cannot load %s: %r\n", m); + raise "fail:bad module"; +} + +init(nil: ref Draw->Context, argv: list of string) +{ + # Load global modules. + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + + bufio = load Bufio Bufio->PATH; + if (bufio==nil) badmod(Bufio->PATH); + + str = load String String->PATH; + if (str == nil) badmod(String->PATH); + + date = load Date Date->PATH; + if(date == nil) badmod(Date->PATH); + + readdir = load Readdir Readdir->PATH; + if(readdir == nil) badmod(Readdir->PATH); + + daytime = load Daytime Daytime->PATH; + if(daytime == nil) badmod(Daytime->PATH); + + contents = load Contents Contents->PATH; + if(contents == nil) badmod(Contents->PATH); + + cache = load Cache Cache->PATH; + if(cache == nil) badmod(Cache->PATH); + + alarms = load Alarms Alarms->PATH; + if(alarms == nil) badmod(Alarms->PATH); + + redir = load Redirect Redirect->PATH; + if(redir == nil) badmod(Redirect->PATH); + + parser = load Parser Parser->PATH; + if(parser == nil) badmod(Parser->PATH); + + logfile=sys->create(HTTPLOG,Sys->ORDWR,8r666); + if (logfile==nil) { + sys->fprint(stderr, "httpd: cannot open %s: %r\n", HTTPLOG); + raise "cannot open http log"; + } + + # parse arguments to httpd. + + cache_size=5000; + debug = 0; + parse_args(argv); + if (debug==1){ + dbg_log=sys->create(DEBUGLOG,Sys->ORDWR,8r666); + if (dbg_log==nil){ + sys->print("debug log open: %r\n"); + exit; + } + }else + dbg_log=nil; + sys->fprint(dbg_log,"started at %s \n",daytime->time()); + + # initialisation routines + contents->contentinit(dbg_log); + cache->cache_init(dbg_log,cache_size); + redir->redirect_init(REWRITE); + date->init(); + parser->init(); + my_domain=sysname(); + if(addr == nil){ + if(port != nil) + addr = "tcp!*!"+port; + else + addr = "tcp!*!80"; + } + (ok, c) := sys->announce(addr); + if(ok < 0) { + sys->fprint(stderr, "can't announce %s: %r\n", addr); + exit; + } + sys->fprint(logfile,"************ Charon Awakened at %s\n", + daytime->time()); + for(;;) + doit(c); + exit; +} + + +doit(c: Sys->Connection) +{ + (ok, nc) := sys->listen(c); + if(ok < 0) { + sys->fprint(stderr, "listen: %r\n"); + exit; + } + if (dbg_log!=nil) + sys->fprint(dbg_log,"spawning connection.\n"); + spawn service_req(nc); +} + +service_req(nc : Sys->Connection) +{ + buf := array[64] of byte; + l := sys->open(nc.dir+"/remote", sys->OREAD); + n := sys->read(l, buf, len buf); + if(n >= 0) + if (dbg_log!=nil) + sys->fprint(dbg_log,"New client http: %s %s", nc.dir, + string buf[0:n]); + # wait for a call (or an error) + # start a process for the service + g:= ref Private_info; + g.bufio = bufio; + g.dbg_log=dbg_log; + g.logfile = logfile; + g.modtime=0; + g.entity = parser->initarray(); + g.mydomain = my_domain; + g.version = "HTTP/1.0"; + g.cache = cache; + g.okencode=nil; + g.oktype=nil; + g.getcerr=""; + g.parse_eof=0; + g.eof=0; + g.remotesys=getendpoints(nc.dir); + debug_print(g,"opening in for "+string buf[0:n]+"\n"); + g.bin= bufio->open(nc.dir+"/data",bufio->OREAD); + if (g.bin==nil){ + sys->print("bin open: %r\n"); + exit; + } + debug_print(g,"opening out for "+string buf[0:n]+"\n"); + g.bout= bufio->open(nc.dir+"/data",bufio->OWRITE); + if (g.bout==nil){ + sys->print("bout open: %r\n"); + exit; + } + debug_print(g,"calling parsereq for "+string buf[0:n]+"\n"); + parsereq(g); + atexit(g); +} + +parsereq(g: ref Private_info) +{ + meth, v,magic,search,uri,origuri,extra : string; + # 15 minutes to get request line + a := Alarm.alarm(15*1000*60); + meth = getword(g); + if(meth == nil){ + parser->logit(g,sys->sprint("no method%s", g.getcerr)); + a.stop(); + parser->fail(g,Syntax,""); + } + uri = getword(g); + if(uri == nil || len uri == 0){ + parser->logit(g,sys->sprint("no uri: %s%s", meth, g.getcerr)); + a.stop(); + parser->fail(g,Syntax,""); + } + v = getword(g); + extra = getword(g); + a.stop(); + if(extra != nil){ + parser->logit(g,sys->sprint( + "extra header word '%s'%s", + extra, g.getcerr)); + parser->fail(g,Syntax,""); + } + case v { + "" => + if(meth!="GET"){ + parser->logit(g,sys->sprint("unimplemented method %s%s", meth, g.getcerr)); + parser->fail(g,Unimp, meth); + } + + "HTTP/V1.0" or "HTTP/1.0" or "HTTP/1.1" => + if((meth != "GET") && (meth!= "HEAD") && (meth!="POST")){ + parser->logit(g,sys->sprint("unimplemented method %s", meth)); + parser->fail(g,Unimp, meth); + } + * => + parser->logit(g,sys->sprint("method %s uri %s%s", meth, uri, g.getcerr)); + parser->fail(g,UnkVers, v); + } + + # the fragment is not supposed to be sent + # strip it because some clients send it + + (uri,extra) = str->splitl(uri, "#"); + if(extra != nil) + parser->logit(g,sys->sprint("fragment %s", extra)); + + # munge uri for search, protection, and magic + (uri, search) = stripsearch(uri); + uri = compact_path(parser->urlunesc(uri)); +# if(uri == SVR_ROOT) +# parser->fail(g,NotFound, "no object specified"); + (uri, magic) = stripmagic(uri); + debug_print(g,"stripmagic=("+uri+","+magic+")\n"); + + # normal case is just file transfer + if(magic == nil || (magic == "httpd")){ + if (meth=="POST") + parser->fail(g,Unimp,meth); # /magic does handles POST + g.host = g.mydomain; + origuri = uri; + parser->httpheaders(g,v); + uri = redir->redirect(origuri); + # must change this to implement proxies + if(uri==nil){ + send(g,meth, v, origuri, search); + }else{ + g.bout.puts(sys->sprint("%s 301 Moved Permanently\r\n", g.version)); + g.bout.puts(sys->sprint("Date: %s\r\n", daytime->time())); + g.bout.puts("Server: Charon\r\n"); + g.bout.puts("MIME-version: 1.0\r\n"); + g.bout.puts("Content-type: text/html\r\n"); + g.bout.puts(sys->sprint("URI: <%s>\r\n",parser->urlconv(uri))); + g.bout.puts(sys->sprint("Location: %s\r\n",parser->urlconv(uri))); + g.bout.puts("\r\n"); + g.bout.puts("<head><title>Object Moved</title></head>\r\n"); + g.bout.puts("<body><h1>Object Moved</h1>\r\n"); + g.bout.puts(sys->sprint( + "Your selection moved to <a href=\"%s\"> here</a>.<p></body>\r\n", + parser->urlconv(uri))); + g.bout.flush(); + } + atexit(g); + } + + # for magic we init a new program + do_magic(g,magic,uri,origuri,Request(meth, v, uri, search)); +} + +do_magic(g: ref Private_info,file, uri, origuri: string, req: Request) +{ + buf := sys->sprint("%s%s.dis", MAGICPATH, file); + debug_print(g,"looking for "+buf+"\n"); + c:= load Cgi buf; + if (c==nil){ + parser->logit(g,sys->sprint("no magic %s uri %s", file, uri)); + parser->fail(g,NotFound, origuri); + } + { + c->init(g, req); + } + exception{ + "fail:*" => + return; + } +} + +send(g: ref Private_info,name, vers, uri, search : string) +{ + typ,enc : ref Content; + w : string; + n, bad, force301: int; + if(search!=nil) + parser->fail(g,NoSearch, uri); + + # figure out the type of file and send headers + debug_print( g, "httpd->send->open(" + uri + ")\n" ); + fd := sys->open(uri, sys->OREAD); + if(fd == nil){ + dbm := sys->sprint( "open failed: %r\n" ); + debug_print( g, dbm ); + notfound(g,uri); + } + (i,dir):=sys->fstat(fd); + if(i< 0) + parser->fail(g,Internal,""); + if(dir.mode & Sys->DMDIR){ + (nil,p) := str->splitr(uri, "/"); + if(p == nil){ + w=sys->sprint("%sindex.html", uri); + force301 = 0; + }else{ + w=sys->sprint("%s/index.html", uri); + force301 = 1; + } + fd1 := sys->open(w, sys->OREAD); + if(fd1 == nil){ + parser->logit(g,sys->sprint("%s directory %s", name, uri)); + if(g.modtime >= dir.mtime) + parser->notmodified(g); + senddir(g,vers, uri, fd, ref dir); + } else if(force301 != 0 && vers != ""){ + g.bout.puts(sys->sprint("%s 301 Moved Permanently\r\n", g.version)); + g.bout.puts(sys->sprint("Date: %s\r\n", daytime->time())); + g.bout.puts("Server: Charon\r\n"); + g.bout.puts("MIME-version: 1.0\r\n"); + g.bout.puts("Content-type: text/html\r\n"); + (nil, reluri) := str->splitstrr(parser->urlconv(w), SVR_ROOT); + g.bout.puts(sys->sprint("URI: </%s>\r\n", reluri)); + g.bout.puts(sys->sprint("Location: http://%s/%s\r\n", + parser->urlconv(g.host), reluri)); + g.bout.puts("\r\n"); + g.bout.puts("<head><title>Object Moved</title></head>\r\n"); + g.bout.puts("<body><h1>Object Moved</h1>\r\n"); + g.bout.puts(sys->sprint( + "Your selection moved to <a href=\"/%s\"> here</a>.<p></body>\r\n", + reluri)); + atexit(g); + } + fd = fd1; + uri = w; + (i,dir)=sys->fstat(fd); + if(i < 0) + parser->fail(g,Internal,""); + } + parser->logit(g,sys->sprint("%s %s %d", name, uri, int dir.length)); + if(g.modtime >= dir.mtime) + parser->notmodified(g); + n = -1; + if(vers != ""){ + (typ, enc) = contents->uriclass(uri); + if(typ == nil) + typ = contents->mkcontent("application", "octet-stream"); + bad = 0; + if(!contents->checkcontent(typ, g.oktype, "Content-Type")){ + bad = 1; + g.bout.puts(sys->sprint("%s 406 None Acceptable\r\n", g.version)); + parser->logit(g,"no content-type ok"); + }else if(!contents->checkcontent(enc, g.okencode, "Content-Encoding")){ + bad = 1; + g.bout.puts(sys->sprint("%s 406 None Acceptable\r\n", g.version)); + parser->logit(g,"no content-encoding ok"); + }else + g.bout.puts(sys->sprint("%s 200 OK\r\n", g.version)); + g.bout.puts("Server: Charon\r\n"); + g.bout.puts(sys->sprint("Last-Modified: %s\r\n", date->dateconv(dir.mtime))); + g.bout.puts(sys->sprint("Version: %uxv%ux\r\n", int dir.qid.path, dir.qid.vers)); + g.bout.puts(sys->sprint("Message-Id: <%uxv%ux@%s>\r\n", + int dir.qid.path, dir.qid.vers, g.mydomain)); + g.bout.puts(sys->sprint("Content-Type: %s/%s", typ.generic, typ.specific)); + +# if(typ.generic== "text") +# g.bout.puts(";charset=unicode-1-1-utf-8"); + + g.bout.puts("\r\n"); + if(enc != nil){ + g.bout.puts(sys->sprint("Content-Encoding: %s", enc.generic)); + g.bout.puts("\r\n"); + } + g.bout.puts(sys->sprint("Content-Length: %d\r\n", int dir.length)); + g.bout.puts(sys->sprint("Date: %s\r\n", daytime->time())); + g.bout.puts("MIME-version: 1.0\r\n"); + g.bout.puts("\r\n"); + if(bad) + atexit(g); + } + if(name == "HEAD") + atexit(g); + # send the file if it's a normal file + g.bout.flush(); + # find if its in hash.... + # if so, retrieve, if not add.. + conts : array of byte; + (i,conts) = cache->find(uri, dir.qid); + if (i==0){ + # add to cache... + conts = array[int dir.length] of byte; + sys->seek(fd,big 0,0); + n = sys->read(fd, conts, len conts); + cache->insert(uri,conts, len conts, dir.qid); + } + sys->write(g.bout.fd, conts, len conts); +} + + + +# classify a file +classify(d: ref Dir): (ref Content, ref Content) +{ + typ, enc: ref Content; + + if(d.qid.qtype&sys->QTDIR) + return (contents->mkcontent("directory", nil),nil); + (typ, enc) = contents->uriclass(d.name); + if(typ == nil) + typ = contents->mkcontent("unknown ", nil); + return (typ, enc); +} + +# read in a directory, format it in html, and send it back +senddir(g: ref Private_info,vers,uri: string, fd: ref FD, mydir: ref Dir) +{ + myname: string; + myname = uri; + if (myname[len myname-1]!='/') + myname[len myname]='/'; + (a, n) := readdir->readall(fd, Readdir->NAME); + if(vers != ""){ + parser->okheaders(g); + g.bout.puts("Content-Type: text/html\r\n"); + g.bout.puts(sys->sprint("Date: %s\r\n", daytime->time())); + g.bout.puts(sys->sprint("Last-Modified: %d\r\n", + mydir.mtime)); + g.bout.puts(sys->sprint("Message-Id: <%d%d@%s>\r\n", + int mydir.qid.path, mydir.qid.vers, g.mydomain)); + g.bout.puts(sys->sprint("Version: %d\r\n", mydir.qid.vers)); + g.bout.puts("\r\n"); + } + g.bout.puts(sys->sprint("<head><title>Contents of directory %s.</title></head>\n", + uri)); + g.bout.puts(sys->sprint("<body><h1>Contents of directory %s.</h1>\n", + uri)); + g.bout.puts("<table>\n"); + for(i := 0; i < n; i++){ + (typ, enc) := classify(a[i]); + g.bout.puts(sys->sprint("<tr><td><a href=\"%s%s\">%s</A></td>", + myname, a[i].name, a[i].name)); + if(typ != nil){ + if(typ.generic!=nil) + g.bout.puts(sys->sprint("<td>%s", typ.generic)); + if(typ.specific!=nil) + g.bout.puts(sys->sprint("/%s", + typ.specific)); + typ=nil; + } + if(enc != nil){ + g.bout.puts(sys->sprint(" %s", enc.generic)); + enc=nil; + } + g.bout.puts("</td></tr>\n"); + } + if(n == 0) + g.bout.puts("<td>This directory is empty</td>\n"); + g.bout.puts("</table></body>\n"); + g.bout.flush(); + atexit(g); +} + +stripmagic(uri : string): (string, string) +{ + prog,newuri : string; + prefix := SVR_ROOT+"magic/"; + if (!str->prefix(prefix,uri) || len newuri == len prefix) + return(uri,nil); + uri=uri[len prefix:]; + (prog,newuri)=str->splitl(uri,"/"); + return (newuri,prog); +} + +stripsearch(uri : string): (string,string) +{ + search : string; + (uri,search) = str->splitl(uri, "?"); + if (search!=nil) + search=search[1:]; + return (uri, search); +} + +# get rid of "." and ".." path components; make absolute +compact_path(origpath:string): string +{ + if(origpath == nil) + origpath = ""; + (origpath,nil) = str->splitl(origpath, "`;| "); # remove specials + (nil,olpath) := sys->tokenize(origpath, "/"); + rlpath : list of string; + for(p := olpath; p != nil; p = tl p) { + if(hd p == "..") { + if(rlpath != nil) + rlpath = tl rlpath; + } else if(hd p != ".") + rlpath = (hd p) :: rlpath; + } + cpath := ""; + if(rlpath!=nil){ + cpath = hd rlpath; + rlpath = tl rlpath; + while( rlpath != nil ) { + cpath = (hd rlpath) + "/" + cpath; + rlpath = tl rlpath; + } + } + return SVR_ROOT + cpath; +} + +getword(g: ref Private_info): string +{ + c: int; + while((c = getc(g)) == ' ' || c == '\t' || c == '\r') + ; + if(c == '\n') + return nil; + buf := ""; + for(;;){ + case c{ + ' ' or '\t' or '\r' or '\n' => + return buf; + } + buf[len buf] = c; + c = getc(g); + } +} + +getc(g : ref Private_info): int +{ + # do we read buffered or unbuffered? + # buf : array of byte; + n : int; + if(g.eof){ + debug_print(g,"eof is set in httpd\n"); + return '\n'; + } + n = g.bin.getc(); + if (n<=0) { + if(n == 0) + g.getcerr=": eof"; + else + g.getcerr=sys->sprint(": n == -1: %r"); + g.eof = 1; + return '\n'; + } + n &= 16r7f; + if(n == '\n') + g.eof = 1; + return n; +} + +# couldn't open a file +# figure out why and return and error message +notfound(g : ref Private_info,url : string) +{ + buf := sys->sprint("%r!"); + (nil,chk):=str->splitstrl(buf, "file does not exist"); + if (chk!=nil) + parser->fail(g,NotFound, url); + (nil,chk)=str->splitstrl(buf,"permission denied"); + if(chk != nil) + parser->fail(g,Unauth, url); + parser->fail(g,NotFound, url); +} + +sysname(): string +{ + n : int; + fd : ref FD; + buf := array[128] of byte; + + fd = sys->open("#c/sysname", sys->OREAD); + if(fd == nil) + return ""; + n = sys->read(fd, buf , len buf); + if(n <= 0) + return ""; + + return string buf[0:n]; +} + +sysdom(): string +{ + dn : string; + dn = csquery("sys" , sysname(), "dom"); + if(dn == nil) + dn = "who cares"; + return dn; +} + +# query the connection server +csquery(attr, val, rattr : string): string +{ + token : string; + buf := array[4096] of byte; + fd : ref FD; + n: int; + if(val == "" ){ + return nil; + } + fd = sys->open("/net/cs", sys->ORDWR); + if(fd == nil) + return nil; + sys->fprint(fd, "!%s=%s", attr, val); + sys->seek(fd, big 0, 0); + token = sys->sprint("%s=", rattr); + for(;;){ + n = sys->read(fd, buf, len buf); + if(n <= 0) + break; + name:=string buf[0:n]; + (nil,p) := str->splitstrl(name, token); + if(p != nil){ + (p,nil) = str->splitl(p, " \n"); + if(p == nil) + return nil; + return p[4:]; + } + } + return nil; +} + +getendpoint(dir, file: string): (string, string) +{ + sysf := serv := ""; + fto := sys->sprint("%s/%s", dir, file); + fd := sys->open(fto, sys->OREAD); + + if(fd !=nil) { + buf := array[128] of byte; + n := sys->read(fd, buf, len buf); + if(n>0) { + buf = buf[0:n-1]; + (sysf, serv) = str->splitl(string buf, "!"); + if (serv != nil) + serv = serv[1:]; + } + } + if(serv == nil) + serv = "unknown"; + if(sysf == nil) + sysf = "unknown"; + return (sysf, serv); +} + +getendpoints(dir: string): string +{ + (lsys, lserv) := getendpoint(dir, "local"); + (rsys, rserv) := getendpoint(dir, "remote"); + return rsys; +} diff --git a/appl/svc/httpd/httpd.debug b/appl/svc/httpd/httpd.debug new file mode 100644 index 00000000..fdd46a73 --- /dev/null +++ b/appl/svc/httpd/httpd.debug @@ -0,0 +1,27 @@ +started at Wed Jun 26 10:40:00 EDT 1996 +domain name is...william.research.bell-labs.com +Cache initialised, max size is 5120000 K +spawning connection. +New client http: /net/tcp/1 135.104.117.197!1069 +calling parsereq +In abspath, origpath = /locale/Australia_Broken-Hill, curdir = / +hitting parsejump. wordval is accept +hitting parsejump. wordval is accept-language +hitting parsejump. wordval is user-agent +hitting parsejump. wordval is connection +hitting parsejump. wordval is referer +current size is 0, adding /locale/Australia_Broken-Hill +At exit from httpd, closing fds. +spawning connection. +New client http: /net/tcp/1 135.104.117.197!1070 +calling parsereq +In abspath, origpath = /, curdir = / +At exit from httpd, closing fds. +tralia_ACT, curdir = / +current size is 0, adding /locale/Australia_ACT +At exit from httpd, closing fds. +spawning connection. +New client http: /net/tcp/4 135.104.117.197!1068 +calling parsereq +In abspath, origpath = /locale/, curdir = / +At exit from httpd, closing fds. diff --git a/appl/svc/httpd/httpd.log b/appl/svc/httpd/httpd.log new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/appl/svc/httpd/httpd.log diff --git a/appl/svc/httpd/httpd.m b/appl/svc/httpd/httpd.m new file mode 100644 index 00000000..e96008e9 --- /dev/null +++ b/appl/svc/httpd/httpd.m @@ -0,0 +1,49 @@ +Httpd: module { + Entity: adt{ + name : string; + value : int; + }; + + Internal, TempFail, Unimp, UnkVers, BadCont, BadReq, Syntax, + BadSearch, NotFound, NoSearch , OnlySearch, Unauth, OK : con iota; + + SVR_ROOT : con "/services/httpd/root/"; + HTTPLOG : con "/services/httpd/httpd.log"; + DEBUGLOG : con "/services/httpd/httpd.debug"; + HTTP_SUFF : con "/services/httpd/httpd.suff"; + REWRITE : con "/services/httpd/httpd.rewrite"; + MAGICPATH : con "/dis/svc/httpd/"; # must end in / + + Private_info : adt{ + # used in parse and httpd + bufio: Bufio; + bin,bout : ref Bufio->Iobuf; + logfile,dbg_log : ref Sys->FD; + cache : Cache; + eof : int; + getcerr : string; + version : string; + okencode, oktype : list of ref Contents->Content; + host : string; # initialized to mydomain just + # before parsing header + remotesys, referer : string; + modtime : int; + # used by /magic for reading body + clength : int; + ctype : string; + #only used in parse + wordval : string; + tok,parse_eof : int; + mydomain,client : string; + entity: array of Entity; + oklang : list of ref Contents->Content; + }; + Request: adt { + method, version, uri, search: string; + }; + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +Cgi: module{ + init: fn(g: ref Httpd->Private_info, req: Httpd->Request); +}; diff --git a/appl/svc/httpd/httpd.rewrite b/appl/svc/httpd/httpd.rewrite new file mode 100644 index 00000000..73d49c84 --- /dev/null +++ b/appl/svc/httpd/httpd.rewrite @@ -0,0 +1,10 @@ +# syntax: pattern replacement +# The most specific pattern wins, and is applied once (no rescanning). +# Leave off trailing slash if pattern is a directory. +# Replacements starting with http:// return "Permanently moved" +# message. +# e.g the following line aliases requests for / to requests for +# /usr/mig/html + +# / /usr/mig/html + diff --git a/appl/svc/httpd/httpd.suff b/appl/svc/httpd/httpd.suff new file mode 100644 index 00000000..dbc599d2 --- /dev/null +++ b/appl/svc/httpd/httpd.suff @@ -0,0 +1,110 @@ +#suffix generic type specific type encoding +.C text plain - # C++ program +.Z - - x-compress +.a application octet-stream - # [Mosaic] +.ada text plain - # ada program +.ai application postscript - # [Mosaic] +.aif audio x-aiff - +.aifc audio x-aiff - +.aiff audio x-aiff - +.au audio basic - # sun audio +.avi video x-msvideo - # [Mosaic] +.awk text plain - # awk program +.bas text plain - # basic program +.bbl text plain - # BibTex output +.bcpio application x-bcpio - # [Mosaic] +.bib text plain - # BibTex input +.c text plain - # C program +.c++ text plain - # C++ program +.cc text plain - # [Mosaic] +.cdf application x-netcdf - +.cpio application x-cpio - +.cpp text plain - # DOS C++ program +.dat text plain - # AMPL et al. +.diff text plain - +.dvi application x-dvi - # TeX output +.enc application octet-stream - # encrypted file +.eps application postscript - +.etx text x-setext - # [Mosaic] +.exe application octet-stream - # DOS executable +.executable application octet-stream - # DOS executable +.exz application octet-stream x-gzip # gziped DOS executable +.f text plain - # fortran-77 program +.flc video x-flc - +.fli video x-fli - +.gif image gif - +.gtar application x-gtar - # [Mosaic] +.gz - - x-gzip # gziped file +.h text plain - # C header file +.hdf application x-hdf - +.hqx application octet-stream - # Mac BinHex +.htm text html - +.html text html - +.ief image ief - # [Mosaic] +.jfif image jpeg - # [Mosaic] +.jfif-tbnl image jpeg - # [Mosaic] +.jpe image jpeg - # [Mosaic] +.jpeg image jpeg - +.jpg image jpeg - +.latex application x-latex - # [Mosaic] +.ltx application x-latex - +.man application x-troff-man - # [Mosaic] +.me application x-troff-me - # [Mosaic] +.mime message rfc822 - # [Mosaic] +.mod text plain - # AMPL et al. +.mov video quicktime - # [Mosaic] +.movie video x-sgi-movie - # [Mosaic] +.mpe video mpeg - # [Mosaic] +.mpeg video mpeg - +.mpg video mpeg - +.ms application x-troff-ms - # [Mosaic] +.mv video x-sgi-movie - # [Mosaic] +.nc application x-netcdf - # [Mosaic] +.o application octet-stream - # [Mosaic] +.oda application oda - # [Mosaic] +.pbm image x-portable-bitmap - # [Mosaic] +.pdf application pdf - # Adobe Portable Document Format +.pgm image x-portable-graymap - # [Mosaic] +.pl text plain - # [Mosaic] +.pnm image x-portable-anymap - # [Mosaic] +.ppm image x-portable-pixmap - # [Mosaic] +.ps application postscript - +.qt video quicktime - # [Mosaic] +.r text plain - # ratfor program +.ras image x-cmu-rast - # [Mosaic] +.rc text plain - # rc +.rfr text plain - # refer +.rgb image x-rgb - # [Mosaic] +.roff application x-troff - # [Mosaic] +.rtf application rtf - # [Mosaic] +.rtx text richtext - # MIME richtext [Mosaic] +.sh application x-shar - +.shar application x-shar - +.snd audio basic - +.sv4cpio application x-sv4cpio - # [Mosaic] +.sv4crc application x-sv4crc - # [Mosaic] +.t application x-troff - # [Mosaic] +.tar application x-tar - # [Mosaic] +.taz application x-tar x-compress +.tcl application x-tcl - +.tex application x-tex - # Tex input +.texi application x-texinfo - # [Mosaic] +.texinfo application x-texinfo - # [Mosaic] +.text text plain - # [Mosaic] +.tgz application x-tar x-gzip +.tif image tiff - +.tiff image tiff - +.toc text plain - # table of contents +.tr application x-troff - # [Mosaic] +.trz application x-tar x-compress +.tsv text tab-separated-values - # [Mosaic] +.txt text plain - +.ustar application x-ustar - # [Mosaic] +.wav audio x-wav - +.wsrc application x-wais-source - # [Mosaic] +.xbm image x-xbitmap - # X bitmap +.xpm image x-xpixmap - # [Mosaic] +.xwd image x-xwindowdump - # [Mosaic] +.z - - x-compress +.Z - - x-compress +.zip application zip - diff --git a/appl/svc/httpd/imagemap.b b/appl/svc/httpd/imagemap.b new file mode 100644 index 00000000..34a08edc --- /dev/null +++ b/appl/svc/httpd/imagemap.b @@ -0,0 +1,251 @@ +implement imagemap; + +include "sys.m"; + sys : Sys; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "draw.m"; + draw: Draw; + +include "cache.m"; +include "contents.m"; + +include "httpd.m"; + Private_info: import Httpd; + OnlySearch, BadSearch, NotFound: import Httpd; + g : ref Private_info; + +include "parser.m"; + parser : Parser; + +include "daytime.m"; + daytime: Daytime; + +include "string.m"; + str : String; + +imagemap : module +{ + init: fn(g : ref Private_info, req: Httpd->Request); +}; + +Point : adt { + x,y : int; +}; + +me : string; + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "imagemap: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init(k : ref Private_info, req: Httpd->Request) +{ + dest, s : string; + sys = load Sys "$Sys"; + draw = load Draw "$Draw"; + + daytime = load Daytime Daytime->PATH; + if(daytime == nil) badmod(Daytime->PATH); + + parser = load Parser Parser->PATH; + if(parser == nil) badmod(Parser->PATH); + + str = load String String->PATH; + if (str == nil) badmod(String->PATH); + + me = "imagemap"; + + g=k; + bufio=g.bufio; + parser->init(); + parser->httpheaders(g,req.version); + dest = translate(req.uri, req.search); + if(dest == nil){ + if(req.version!= ""){ + parser->okheaders(g); + g.bout.puts(sys->sprint("Date: %s\r\n", daytime->time())); + g.bout.puts("Content-type: text/html\r\n"); + g.bout.puts("\r\n"); + } + g.bout.puts("<head><title>Nothing Found</title></head><body>\n"); + g.bout.puts("Nothing satisfying your search request could be found.\n</body>\n"); + return; + } + + g.bout.puts(sys->sprint("%s 301 Moved Permanently\r\n", g.version)); + g.bout.puts(sys->sprint("Date: %s\r\n", daytime->time())); + g.bout.puts("Server: Charon\r\n"); + g.bout.puts("MIME-version: 1.0\r\n"); + g.bout.puts("Content-type: text/html\r\n"); + (s,nil)=str->splitl(dest, ":"); + if(s!=nil){ + g.bout.puts(sys->sprint("URI: <%s>\r\n", parser->urlconv(dest))); + g.bout.puts(sys->sprint("Location: %s\r\n", parser->urlconv(dest))); + }else if(dest[0] == '/'){ + g.bout.puts(sys->sprint("URI: <%s>\r\n",parser->urlconv(dest))); + g.bout.puts(sys->sprint("Location: http://%s%s\r\n", parser->urlconv(g.mydomain), parser->urlconv(dest))); + } else { + (req.uri,s) = str->splitr(req.uri, "/"); + g.bout.puts(sys->sprint("URI: <%s/%s>\r\n", parser->urlconv(req.uri), parser->urlconv(dest))); + g.bout.puts(sys->sprint("Location: http://%s%s/%s\r\n", parser->urlconv(g.mydomain), parser->urlconv(req.uri), parser->urlconv(dest))); + } + g.bout.puts("\r\n"); + g.bout.puts("<head><title>Object Moved</title></head>\r\n"); + g.bout.puts("<body><h1>Object Moved</h1></body>\r\n"); + if(dest[0] == '/') + g.bout.puts(sys->sprint("Your selection mapped to <a href=\"%s\"> here</a>.<p>\r\n", parser->urlconv(dest))); + else + g.bout.puts(sys->sprint("Your selection mapped to <a href=\"%s/%s\"> here</a>.<p>\r\n", parser->urlconv(req.uri), parser->urlconv(dest))); +} + + +translate(uri, search : string) : string +{ + b : ref Iobuf; + p, c, q, start : Point; + close, d : real; + line, To, def, s : string; + ok, n, inside, r : int; + (pth,nil):=str->splitr(uri,"/"); + # sys->print("pth is %s",pth); + if(search == nil) + parser->fail(g,OnlySearch, me); + (p, ok) = pt(search); + if(!ok) + parser->fail(g,BadSearch, me); + + b = bufio->open(uri, bufio->OREAD); + if(b == nil){ + sys->fprint(sys->fildes(2), "imagemap: cannot open %s: %r\n", uri); + parser->fail(g, NotFound, uri); + } + To = ""; + def = ""; + close = 0.; + while((line = b.gets('\n'))!=nil){ + line=line[0:len line-1]; + + (s, line) = getfield(line); + if(s== "rect"){ + (s, line) = getfield(line); + (q, ok) = pt(s); + if(!ok || q.x > p.x || q.y > p.y) + continue; + (s, line) = getfield(line); + (q, ok) = pt(s); + if(!ok || q.x < p.x || q.y < p.y) + continue; + (s, nil) = getfield(line); + return pth+s; + }else if(s== "circle"){ + (s, line) = getfield(line); + (c, ok) = pt(s); + if(!ok) + continue; + (s, line) = getfield(line); + (r,nil) = str->toint(s,10); + (s, line) = getfield(line); + d = real (r * r); + if(d >= dist(p, c)) + return pth+s; + }else if(s=="poly"){ + (s, line) = getfield(line); + (start, ok) = pt(s); + if(!ok) + continue; + inside = 0; + c = start; + for(n = 1; ; n++){ + (s, line) = getfield(line); + (q, ok) = pt(s); + if(!ok) + break; + inside = polytest(inside, p, c, q); + c = q; + } + inside = polytest(inside, p, c, start); + if(n >= 3 && inside) + return pth+s; + }else if(s== "point"){ + (s, line) = getfield(line); + (q, ok) = pt(s); + if(!ok) + continue; + d = dist(p, q); + (s, line) = getfield(line); + if(d == 0.) + return pth+s; + if(close == 0. || d < close){ + close = d; + To = s; + } + }else if(s == "default"){ + (def, line) = getfield(line); + } + } + if(To == nil) + To = def; + return pth+To; +} + + +polytest(inside : int,p, b, a : Point) : int{ + pa, ba : Point; + + if(b.y>a.y){ + pa=sub(p, a); + ba=sub(b, a); + }else{ + pa=sub(p, b); + ba=sub(a, b); + } + if(0<=pa.y && pa.y<ba.y && pa.y*ba.x<=pa.x*ba.y) + inside = !inside; + return inside; +} + + +sub(p, q : Point) : Point { + return (Point)(p.x-q.x, p.y-q.y); +} + + +dist(p, q : Point) : real { + p.x -= q.x; + p.y -= q.y; + return real (p.x * p.x + p.y *p.y); +} + + +pt(s : string) : (Point, int) { + p : Point; + x, y : string; + + if(s[0] == '(') + s=s[1:]; + (s,nil)=str->splitl(s, ")"); + p = Point(0, 0); + (x,y) = str->splitl(s, ","); + if(x == s) + return (p, 0); + (p.x,nil) = str->toint(x,10); + if(y==nil) + return (p, 0); + y=y[1:]; + (p.y,nil) = str->toint(y, 10); + return (p, 1); +} + + +getfield(s : string) : (string,string) { + i:=0; + while(s[i] == '\t' || s[i] == ' ') + i++; + return str->splitl(s[i:],"\t "); +} diff --git a/appl/svc/httpd/mkfile b/appl/svc/httpd/mkfile new file mode 100644 index 00000000..caba5c47 --- /dev/null +++ b/appl/svc/httpd/mkfile @@ -0,0 +1,49 @@ +<../../../mkconfig + +TARG= cache.dis\ + contents.dis\ + date.dis\ + echo.dis\ + httpd.dis\ + imagemap.dis\ + parser.dis\ + redirect.dis\ + stats.dis\ + alarms.dis\ + cgiparse.dis\ + + +MODULES=\ + cache.m\ + contents.m\ + date.m\ + httpd.m\ + parser.m\ + redirect.m\ + alarms.m\ + cgiparse.m\ + +SYSMODULES= + +LOGS= httpd.debug\ + httpd.log\ + httpd.rewrite\ + httpd.suff\ + +DISBIN=$ROOT/dis/svc/httpd + +<$ROOT/mkfiles/mkdis + +install:V: install-logs-$SHELLTYPE + +install-logs-rc install-logs-nt:V: + for (i in $LOGS) + rm -f $ROOT/services/httpd/$i && cp $i $ROOT/services/httpd/$i + # chmod 644 $ROOT/services/httpd/httpd.log + +install-logs-sh:V: + for i in $LOGS + do + rm -f $ROOT/services/httpd/$i && cp $i $ROOT/services/httpd/$i + done + # chmod 644 $ROOT/services/httpd/httpd.log diff --git a/appl/svc/httpd/parser.b b/appl/svc/httpd/parser.b new file mode 100644 index 00000000..a109d022 --- /dev/null +++ b/appl/svc/httpd/parser.b @@ -0,0 +1,861 @@ +implement Parser; + +include "sys.m"; + sys: Sys; +include "draw.m"; + draw: Draw; +include "bufio.m"; +include "string.m"; + str: String; +include "daytime.m"; + daytime: Daytime; +include "contents.m"; + contents : Contents; + Content: import contents; +include "cache.m"; +include "httpd.m"; + Entity, Private_info: import Httpd; + Internal, TempFail, Unimp, UnkVers, BadCont, BadReq, Syntax, + BadSearch, NotFound, NoSearch , OnlySearch, Unauth, OK : import Httpd; +include "parser.m"; +include "date.m"; + date : Date; +include "alarms.m"; + alarms: Alarms; + Alarm: import alarms; +include "lock.m"; + locks: Lock; + Semaphore: import locks; + +Error: adt { + num : string; + concise: string; + verbose: string; +}; + +errormsg := array[] of { + Internal => Error("500 Internal Error", "Internal Error", + "This server could not process your request due to an interal error."), + TempFail => Error("500 Internal Error", "Temporary Failure", + "The object %s is currently inaccessible.<p>Please try again later."), + Unimp => Error("501 Not implemented", "Command not implemented", + "This server does not implement the %s command."), + UnkVers => Error("501 Not Implemented", "Unknown http version", + "This server does not know how to respond to http version %s."), + BadCont => Error("501 Not Implemented", "Impossible format", + "This server cannot produce %s in any of the formats your client accepts."), + BadReq => Error("400 Bad Request", "Strange Request", + "Your client sent a query that this server could not understand."), + Syntax => Error("400 Bad Request", "Garbled Syntax", + "Your client sent a query with incoherent syntax."), + BadSearch =>Error("400 Bad Request", "Inapplicable Search", + "Your client sent a search that cannot be applied to %s."), + NotFound =>Error("404 Not Found", "Object not found", + "The object %s does not exist on this server."), + NoSearch => Error("403 Forbidden", "Search not supported", + "The object %s does not support the search command."), + OnlySearch =>Error("403 Forbidden", "Searching Only", + "The object %s only supports the searching methods."), + Unauth => Error("401 Unauthorized", "Unauthorized", + "You are not authorized to see the object %s."), + OK => Error("200 OK", "everything is fine","Groovy man"), +}; + +latin1 := array[] of { + '¡', + '¢', + '£', + '¤', + '¥', + '¦', + '§', + '¨', + '©', + 'ª', + '«', + '¬', + '', + '®', + '¯', + '°', + '±', + '²', + '³', + '´', + 'µ', + '¶', + '·', + '¸', + '¹', + 'º', + '»', + '¼', + '½', + '¾', + '¿', + 'À', + 'Á', + 'Â', + 'Ã', + 'Ä', + 'Å', + 'Æ', + 'Ç', + 'È', + 'É', + 'Ê', + 'Ë', + 'Ì', + 'Í', + 'Î', + 'Ï', + 'Ð', + 'Ñ', + 'Ò', + 'Ó', + 'Ô', + 'Õ', + 'Ö', + '×', + 'Ø', + 'Ù', + 'Ú', + 'Û', + 'Ü', + 'Ý', + 'Þ', + 'ß', + 'à', + 'á', + 'â', + 'ã', + 'ä', + 'å', + 'æ', + 'ç', + 'è', + 'é', + 'ê', + 'ë', + 'ì', + 'í', + 'î', + 'ï', + 'ð', + 'ñ', + 'ò', + 'ó', + 'ô', + 'õ', + 'ö', + '÷', + 'ø', + 'ù', + 'ú', + 'û', + 'ü', + 'ý', + 'þ', + 'ÿ', + 0, +}; + +entities :=array[] of { + Entity( "¡", '¡' ), + Entity( "¢", '¢' ), + Entity( "£", '£' ), + Entity( "¤", '¤' ), + Entity( "¥", '¥' ), + Entity( "¦", '¦' ), + Entity( "§", '§' ), + Entity( "¨", '¨' ), + Entity( "©", '©' ), + Entity( "ª", 'ª' ), + Entity( "«", '«' ), + Entity( "¬", '¬' ), + Entity( "­", '' ), + Entity( "®", '®' ), + Entity( "¯", '¯' ), + Entity( "°", '°' ), + Entity( "±", '±' ), + Entity( "²", '²' ), + Entity( "³", '³' ), + Entity( "´", '´' ), + Entity( "µ", 'µ' ), + Entity( "¶", '¶' ), + Entity( "·", '·' ), + Entity( "¸", '¸' ), + Entity( "¹", '¹' ), + Entity( "º", 'º' ), + Entity( "»", '»' ), + Entity( "¼", '¼' ), + Entity( "½", '½' ), + Entity( "¾", '¾' ), + Entity( "¿", '¿' ), + Entity( "À", 'À' ), + Entity( "Á", 'Á' ), + Entity( "Â", 'Â' ), + Entity( "Ã", 'Ã' ), + Entity( "Ä", 'Ä' ), + Entity( "Å", 'Å' ), + Entity( "Æ", 'Æ' ), + Entity( "Ç", 'Ç' ), + Entity( "È", 'È' ), + Entity( "É", 'É' ), + Entity( "Ê", 'Ê' ), + Entity( "Ë", 'Ë' ), + Entity( "Ì", 'Ì' ), + Entity( "Í", 'Í' ), + Entity( "Î", 'Î' ), + Entity( "Ï", 'Ï' ), + Entity( "Ð", 'Ð' ), + Entity( "Ñ", 'Ñ' ), + Entity( "Ò", 'Ò' ), + Entity( "Ó", 'Ó' ), + Entity( "Ô", 'Ô' ), + Entity( "Õ", 'Õ' ), + Entity( "Ö", 'Ö' ), + Entity( "&215;", '×' ), + Entity( "Ø", 'Ø' ), + Entity( "Ù", 'Ù' ), + Entity( "Ú", 'Ú' ), + Entity( "Û", 'Û' ), + Entity( "Ü", 'Ü' ), + Entity( "Ý", 'Ý' ), + Entity( "Þ", 'Þ' ), + Entity( "ß", 'ß' ), + Entity( "à", 'à' ), + Entity( "á", 'á' ), + Entity( "â", 'â' ), + Entity( "ã", 'ã' ), + Entity( "ä", 'ä' ), + Entity( "å", 'å' ), + Entity( "æ", 'æ' ), + Entity( "ç", 'ç' ), + Entity( "è", 'è' ), + Entity( "é", 'é' ), + Entity( "ê", 'ê' ), + Entity( "ë", 'ë' ), + Entity( "ì", 'ì' ), + Entity( "í", 'í' ), + Entity( "î", 'î' ), + Entity( "ï", 'ï' ), + Entity( "ð", 'ð' ), + Entity( "ñ", 'ñ' ), + Entity( "ò", 'ò' ), + Entity( "ó", 'ó' ), + Entity( "ô", 'ô' ), + Entity( "õ", 'õ' ), + Entity( "ö", 'ö' ), + Entity( "&247;", '÷' ), + Entity( "ø", 'ø' ), + Entity( "ù", 'ù' ), + Entity( "ú", 'ú' ), + Entity( "û", 'û' ), + Entity( "ü", 'ü' ), + Entity( "ý", 'ý' ), + Entity( "þ", 'þ' ), + Entity( "ÿ", 'ÿ' ), + + Entity( "&#SPACE;", ' ' ), + Entity( "&#RS;", '\n' ), + Entity( "&#RE;", '\r' ), + Entity( """, '"' ), + Entity( "&", '&' ), + Entity( "<", '<' ), + Entity( ">", '>' ), + + Entity( "CAP-DELTA", 'Δ' ), + Entity( "ALPHA", 'α' ), + Entity( "BETA", 'β' ), + Entity( "DELTA", 'δ' ), + Entity( "EPSILON", 'ε' ), + Entity( "THETA", 'θ' ), + Entity( "MU", 'μ' ), + Entity( "PI", 'π' ), + Entity( "TAU", 'τ' ), + Entity( "CHI", 'χ' ), + + Entity( "<-", '←' ), + Entity( "^", '↑' ), + Entity( "->", '→' ), + Entity( "v", '↓' ), + Entity( "!=", '≠' ), + Entity( "<=", '≤' ), + Entity( nil, 0 ), + }; + + +initarray() : array of Entity +{ + return entities; +} + +badmodule(p: string) +{ + sys->fprint(sys->fildes(2), "parse: cannot load %s: %r", p); + raise "fail:bad module"; +} + +lock: ref Semaphore; + +init() +{ + sys = load Sys Sys->PATH; + + date = load Date Date->PATH; + if (date==nil) badmodule(Date->PATH); + + daytime = load Daytime Daytime->PATH; + if(daytime == nil) badmodule(Daytime->PATH); + + contents = load Contents Contents->PATH; + if(contents == nil) badmodule(Contents->PATH); + + str = load String String->PATH; + if(str == nil) badmodule(String->PATH); + + alarms = load Alarms Alarms->PATH; + if(alarms == nil) badmodule(Alarms->PATH); + + locks = load Lock Lock->PATH; + if(locks == nil) badmodule(Lock->PATH); + locks->init(); + lock = Semaphore.new(); + date->init(); +} + +atexit(g: ref Private_info) +{ + if (g.dbg_log!=nil){ + sys->fprint(g.dbg_log,"At exit from parse, closing fds. \n"); + } + if (g.bin!=nil) + g.bufio->g.bin.close(); + if (g.bout!=nil) + g.bufio->g.bout.close(); + g.bin=nil; + g.bout=nil; + exit; +} + + +httpheaders(g: ref Private_info,vers : string) +{ + if(vers == "") + return; + g.tok = '\n'; + # 15 minutes to get request line + a := Alarm.alarm(15*1000*60); + while(lex(g) != '\n'){ + if(g.tok == Word && lex(g) == ':'){ + if (g.dbg_log!=nil) + sys->fprint(g.dbg_log,"hitting parsejump. wordval is %s\n", + g.wordval); + parsejump(g,g.wordval); + } + while(g.tok != '\n') + lex(g); + } + a.stop(); +} + + +mimeok(g: ref Private_info,name : string,multipart : int,head : list of ref Content): list of ref Content +{ + + generic, specific, s : string; + v : real; + + while(lex(g) != Word) + if(g.tok != ',') + return head; + + generic = g.wordval; + lex(g); + if(g.tok == '/' || multipart){ + if(g.tok != '/') + return head; + if(lex(g) != Word) + return head; + specific = g.wordval; + lex(g); + }else + specific = "*"; + tmp := contents->mkcontent(generic, specific); + head = tmp::head; + for(;;){ + case g.tok { + ';' => + if(lex(g) == Word){ + s = g.wordval; + if(lex(g) != '=' || lex(g) != Word) + return head; + v = 3.14; # should be strtof(g.wordval, nil); + if(s=="q") + tmp.q = v; + else + logit(g,sys->sprint( + "unknown %s param: %s %s", + name, s, g.wordval)); + } + break; + ',' => + return mimeok(g,name, multipart,head); + * => + return head; + } + lex(g); + } + return head; +} + +mimeaccept(g: ref Private_info,name : string) +{ + g.oktype = mimeok(g,name, 1, g.oktype); +} + +mimeacceptenc(g: ref Private_info,name : string) +{ + g.okencode = mimeok(g,name, 0, g.okencode); +} + +mimeacceptlang(g: ref Private_info,name : string) +{ + g.oklang = mimeok(g,name, 0, g.oklang); +} + +mimemodified(g: ref Private_info,name : string) +{ + lexhead(g); + g.modtime = date->date2sec(g.wordval); + if (g.dbg_log!=nil){ + sys->fprint(g.dbg_log,"modtime %d\n",g.modtime); + } + if(g.modtime == 0) + logit(g,sys->sprint("%s: %s", name, g.wordval)); +} + + +mimeagent(g: ref Private_info,nil : string) +{ + lexhead(g); + g.client = g.wordval; +} + +mimefrom(g: ref Private_info,nil : string) +{ + lexhead(g); +} + + +mimehost(g: ref Private_info,nil : string) +{ + h : string; + lexhead(g); + (nil,h)=str->splitr(g.wordval," \t"); + g.host = h; +} + +mimereferer(g: ref Private_info,nil : string) +{ + h : string; + lexhead(g); + (nil,h)=str->splitr(g.wordval," \t"); + g.referer = h; +} + +mimeclength(g: ref Private_info,nil : string) +{ + h : string; + lexhead(g); + (nil,h)=str->splitr(g.wordval," \t"); + g.clength = int h; +} + +mimectype(g: ref Private_info,nil : string) +{ + h : string; + lexhead(g); + (nil,h)=str->splitr(g.wordval," \t"); + g.ctype = h; +} + + +mimeignore(g: ref Private_info,nil : string) +{ + lexhead(g); +} + + +mimeunknown(g: ref Private_info,name : string) +{ + lexhead(g); + if(g.client!="") + logit(g,sys->sprint("agent %s: ignoring header %s: %s ", + g.client, name, g.wordval)); + else + logit(g,sys->sprint("ignoring header %s: %s", name, g.wordval)); +} + + +parsejump(g: ref Private_info,k : string) +{ + case k { + + "from" => + mimefrom(g,k); + "if-modified-since" => + mimemodified(g,k); + "accept" => + mimeaccept(g,k); + "accept-encoding" => + mimeacceptenc(g,k); + "accept-language" => + mimeacceptlang(g,k); + "user-agent" => + mimeagent(g,k); + "host" => + mimehost(g,k); + "referer" => + mimereferer(g,k); + "content-length" => + mimeclength(g,k); + "content-type" => + mimectype(g,k); + "authorization" or "chargeto" or "connection" or "forwarded" or + "pragma" or "proxy-agent" or "proxy-connection" or + "x-afs-tokens" or "x-serial-number" => + mimeignore(g,k); + * => + mimeunknown(g,k); + }; +} + +lex(g: ref Private_info): int +{ + g.tok = lex1(g); + return g.tok; +} + + +# rfc 822/rfc 1521 lexical analyzer +lex1(g: ref Private_info): int +{ + level, c : int; + if(g.parse_eof) + return '\n'; + +# top: + for(;;){ + c = getc(g); + case c { + '(' => + level = 1; + while((c = getc(g)) != Bufio->EOF){ + if(c == '\\'){ + c = getc(g); + if(c == Bufio->EOF) + return '\n'; + continue; + } + if(c == '(') + level++; + else if(c == ')' && level == 1){ + level--; + break; + } + else if(c == '\n'){ + c = getc(g); + if(c == Bufio->EOF) + return '\n'; + break; + if(c != ' ' && c != '\t'){ + ungetc(g); + return '\n'; + } + } + } + ' ' or '\t' or '\r' => + break; + '\n' => + if(g.tok == '\n'){ + g.parse_eof = 1; + return '\n'; + } + c = getc(g); + if(c == Bufio->EOF) + return '\n'; + if(c != ' ' && c != '\t'){ + ungetc(g); + return '\n'; + } + ')' or '<' or '>' or '[' or ']' or '@' or '/' or ',' + or ';' or ':' or '?' or '=' => + return c; + + '"' => + word(g,"\""); + getc(g); # skip the closing quote + return Word; + + * => + ungetc(g); + word(g,"\"()<>@,;:/[]?=\r\n \t"); + return Word; + } + } + return 0; +} + +# return the rest of an rfc 822, not including \r or \n +# do not map to lower case + +lexhead(g: ref Private_info) +{ + c, n: int; + n = 0; + while((c = getc(g)) != Bufio->EOF){ + if(c == '\r') + c = wordcr(g); + else if(c == '\n') + c = wordnl(g); + if(c == '\n') + break; + if(c == '\\'){ + c = getc(g); + if(c == Bufio->EOF) + break; + } + g.wordval[n++] = c; + } + g.tok = '\n'; + g.wordval= g.wordval[0:n]; +} + +word(g: ref Private_info,stop : string) +{ + c : int; + n := 0; + while((c = getc(g)) != Bufio->EOF){ + if(c == '\r') + c = wordcr(g); + else if(c == '\n') + c = wordnl(g); + if(c == '\\'){ + c = getc(g); + if(c == Bufio->EOF) + break; + }else if(str->in(c,stop)){ + ungetc(g); + g.wordval = g.wordval[0:n]; + return; + } + if(c >= 'A' && c <= 'Z') + c += 'a' - 'A'; + g.wordval[n++] = c; + } + g.wordval = g.wordval[0:n]; + # sys->print("returning from word"); +} + + +wordcr(g: ref Private_info): int +{ + c := getc(g); + if(c == '\n') + return wordnl(g); + ungetc(g); + return ' '; +} + + +wordnl(g: ref Private_info): int +{ + c := getc(g); + if(c == ' ' || c == '\t') + return c; + ungetc(g); + return '\n'; +} + + +getc(g: ref Private_info): int +{ + c := g.bufio->g.bin.getc(); + if(c == Bufio->EOF){ + g.parse_eof = 1; + return c; + } + return c & 16r7f; +} + +ungetc(g: ref Private_info) { + # this is a dirty hack, I am tacitly assuming that characters read + # from stdin will be ASCII..... + g.bufio->g.bin.ungetc(); +} + +# go from url with latin1 and escapes to utf + +urlunesc(s : string): string +{ + c, n : int; + t : string; + for(i := 0;i<len s ; i++){ + c = int s[i]; + if(c == '%'){ + n = int s[i+1]; + if(n >= '0' && n <= '9') + n = n - '0'; + else if(n >= 'A' && n <= 'F') + n = n - 'A' + 10; + else if(n >= 'a' && n <= 'f') + n = n - 'a' + 10; + else + break; + c = n; + n = int s[i+2]; + if(n >= '0' && n <= '9') + n = n - '0'; + else if(n >= 'A' && n <= 'F') + n = n - 'A' + 10; + else if(n >= 'a' && n <= 'f') + n = n - 'a' + 10; + else + break; + i += 2; + c = c * 16 + n; + } + else if( c == '+' ) + c = ' '; + t[len t] = c; + } + return t; +} + + +# go from http with latin1 escapes to utf, +# we assume that anything >= Runeself is already in utf + +httpunesc(g: ref Private_info,s : array of byte): string +{ + t,v: string; + c,i : int; + # convert bytes to a string. + v = string s; + for(i=0; i < len v;i++){ + c = v[i]; + if(c == '&'){ + if(v[1] == '#' && v[2] && v[3] && v[4] && v[5] == ';'){ + c = 100*(v[2])+10*(v[3])+(v[4]); + if(c < Runeself){ + t[len t] = c; + i += 6; + continue; + } + if(c < 256 && c >= 161){ + t[len t] = g.entity[c-161].value; + i += 6; + continue; + } + } else { + for(j:= 0;g.entity[j].name != nil; j++) + if(g.entity[j].name == v[i+1:]) + # problem here cvert array of byte to string? + break; + if(g.entity[j].name != nil){ + i += len g.entity[j].name; + t[len t] = g.entity[j].value; + continue; + } + } + } + t[len t] = c; + } + return t; +} + + +# write a failure message to the net and exit +fail(g: ref Private_info,reason : int, message : string) +{ + verb : string; + title:=sys->sprint("<head><title>%s</title></head>\n<body bgcolor=#ffffff>\n", + errormsg[reason].concise); + body1:= "<h1> Error </h1>\n<P>" + + "Sorry, Charon is unable to process your request. The webserver reports"+ + " the following error <P><b>"; + #concise error + body2:="</b><p>for the URL\n<P><b>"; + #message + body3:="</b><P>with the following reason:\n<P><b>"; + #reason + if (str->in('%',errormsg[reason].verbose)){ + (v1,v2):=str->splitl(errormsg[reason].verbose,"%"); + verb=v1+message+v2[2:]; + }else + verb=errormsg[reason].verbose; + body4:="</b><hr> This Webserver powered by <img src=\"/inferno.gif\">. <P>"+ + "For more information click <a href=\"http://inferno.lucent.com\"> here </a>\n"+ + "<hr><address>\n"; + dtime:=sys->sprint("This information processed at %s.\n",daytime->time()); + body5:="</address>\n</body>\n"; + strbuf:=title+body1+errormsg[reason].concise+body2+message+body3+ + verb+body4+dtime+body5; + if (g.bout!=nil && reason!=2){ + g.bufio->g.bout.puts(sys->sprint("%s %s\r\n", g.version, errormsg[reason].num)); + g.bufio->g.bout.puts(sys->sprint("Date: %s\r\n", daytime->time())); + g.bufio->g.bout.puts(sys->sprint("Server: Charon\r\n")); + g.bufio->g.bout.puts(sys->sprint("MIME-version: 1.0\r\n")); + g.bufio->g.bout.puts(sys->sprint("Content-Type: text/html\r\n")); + g.bufio->g.bout.puts(sys->sprint("Content-Length: %d\r\n", len strbuf)); + g.bufio->g.bout.puts(sys->sprint("\r\n")); + g.bufio->g.bout.puts(strbuf); + g.bufio->g.bout.flush(); + } + logit(g,sys->sprint("failing: %s", errormsg[reason].num)); + atexit(g); +} + + +# write successful header + +okheaders(g: ref Private_info) +{ + g.bufio->g.bout.puts(sys->sprint("%s 200 OK\r\n", g.version)); + g.bufio->g.bout.puts("Server: Charon\r\n"); + g.bufio->g.bout.puts("MIME-version: 1.0\r\n"); +} + +notmodified(g: ref Private_info) +{ + g.bufio->g.bout.puts(sys->sprint("%s 304 Not Modified\r\n", g.version)); + g.bufio->g.bout.puts("Server: Charon\r\n"); + g.bufio->g.bout.puts("MIME-version: 1.0\r\n\r\n"); + atexit(g); +} + +logit(g: ref Private_info,message : string ) +{ + lock.obtain(); + sys->fprint(g.logfile,"%s %s\n", g.remotesys, message); + lock.release(); +} + +urlconv(p : string): string +{ + c : int; + t : string; + for(i:=0;i<len p ;i++){ + c = p[i]; + if(c == 0) + break; + if(c <= ' ' || c == '%' || c >= Runeself){ + t += sys->sprint("%%%2.2x", c); + } else { + t[len t] = c; + } + } + return t; +} diff --git a/appl/svc/httpd/parser.m b/appl/svc/httpd/parser.m new file mode 100644 index 00000000..0c1a5829 --- /dev/null +++ b/appl/svc/httpd/parser.m @@ -0,0 +1,16 @@ +Parser: module { + Runeself : con 16r80; + Word : con 1; + + PATH: con "/dis/svc/httpd/parser.dis"; + + init: fn(); + initarray: fn(): array of Httpd->Entity; + urlunesc: fn(s: string): string; + fail: fn(g: ref Httpd->Private_info,reason: int, message: string); + logit: fn(g: ref Httpd->Private_info, message: string ); + notmodified: fn(g: ref Httpd->Private_info); + httpheaders: fn(g: ref Httpd->Private_info, vers: string); + urlconv: fn(url : string): string; + okheaders: fn(g: ref Httpd->Private_info); +}; diff --git a/appl/svc/httpd/redirect.b b/appl/svc/httpd/redirect.b new file mode 100644 index 00000000..cb3ed478 --- /dev/null +++ b/appl/svc/httpd/redirect.b @@ -0,0 +1,130 @@ +implement Redirect; + +include "sys.m"; + sys : Sys; + +include "bufio.m"; + bufio : Bufio; +Iobuf : import bufio; + +include "string.m"; + str : String; + +include "redirect.m"; + +HASHSIZE : con 1019; + + +Redir: adt{ + pat, repl : string; +}; + +tab := array[HASHSIZE] of list of Redir; + + +hashasu(key : string,n : int): int +{ + i,h : int; + i=0; + h=0; + while(i<len key){ + h = 10*h + key[i]; + h%= n; + i++; + } + return h; +} + +insert(pat, repl : string) +{ + hash := hashasu(pat,HASHSIZE); + tab[hash]= Redir(pat, repl) :: tab[hash]; +} + +redirect_init(file : string) +{ + sys = load Sys Sys->PATH; + line : string; + flist : list of string; + n : int; + bb : ref Iobuf; + for(n=0;n<HASHSIZE;n++) + tab[n]= nil; + stderr := sys->fildes(2); + bufio = load Bufio Bufio->PATH; + if (bufio==nil){ + sys->fprint(stderr,"redirect: cannot load %s: %r\n", Bufio->PATH); + raise "fail:bad module"; + } + str = load String String->PATH; + if (str==nil){ + sys->fprint(stderr,"redirect: cannot load %s: %r\n", String->PATH); + raise "fail:bad module"; + } + bb = bufio->open(file,bufio->OREAD); + if (bb==nil) + return; + while((line = bb.gets('\n'))!=nil){ + line = line[0:len line -1]; #chop newline + if (str->in('#',line)){ + (line,nil) = str->splitl(line, "#"); + if (line!=nil){ + n = len line; + while(line[n]==' '||line[n]=='\t') n--; + # and preceeding blanks + line = line[0:n]; + } + } + if (line!=nil){ + (n,flist)=sys->tokenize(line,"\t "); + if (n==2) + insert(hd flist,hd tl flist); + } + } + +} + +lookup(pat : string): ref Redir +{ + srch : list of Redir; + tmp : Redir; + hash : int; + hash = hashasu(pat,HASHSIZE); + for(srch = tab[hash]; srch!=nil; srch = tl srch){ + tmp = hd srch; + if(tmp.pat==nil) + return nil; + if(pat==tmp.pat) + return ref tmp; + } + return nil; +} + + +redirect(path : string): string { + redir : ref Redir; + newpath, oldp : string; + s : int; + if((redir = lookup(path))!=nil) + if(redir.repl==nil) + return nil; + else + return redir.repl; + for(s = len path - 1; s>0; s--){ + if(path[s]=='/'){ + oldp = path[s+1:]; + path = path[0:s]; + if((redir = lookup(path))!=nil){ + if(redir.repl!=nil) + newpath=sys->sprint("%s/%s", + redir.repl,oldp); + else + newpath = nil; + path = path+"/"+oldp; + return newpath; + } + path = path+"/"+oldp; + } + } + return nil; +} diff --git a/appl/svc/httpd/redirect.m b/appl/svc/httpd/redirect.m new file mode 100644 index 00000000..4a9a878d --- /dev/null +++ b/appl/svc/httpd/redirect.m @@ -0,0 +1,7 @@ +Redirect: module +{ + PATH: con "/dis/svc/httpd/redirect.dis"; + + redirect_init: fn(file : string); + redirect: fn(path : string): string; +}; diff --git a/appl/svc/httpd/stats.b b/appl/svc/httpd/stats.b new file mode 100644 index 00000000..3e285ef8 --- /dev/null +++ b/appl/svc/httpd/stats.b @@ -0,0 +1,85 @@ +implement Stats; + +include "sys.m"; + sys : Sys; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "draw.m"; + draw: Draw; + +include "contents.m"; +include "cache.m"; + cache : Cache; + +include "httpd.m"; + Private_info: import Httpd; + +include "date.m"; + date : Date; + +include "parser.m"; + pars : Parser; + +include "daytime.m"; + daytime: Daytime; + +Stats: module +{ + init: fn(g : ref Private_info, req: Httpd->Request); +}; + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "stats: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init(k : ref Private_info, req: Httpd->Request) +{ + sys = load Sys "$Sys"; + draw = load Draw "$Draw"; + + daytime = load Daytime Daytime->PATH; + if(daytime == nil) badmod(Daytime->PATH); + + pars = load Parser Parser->PATH; + if(pars == nil) badmod(Parser->PATH); + + date = load Date Date->PATH; + if(date == nil) badmod(Date->PATH); + + date->init(); + bufio=k.bufio; + send(k, req.method, req.version, req.uri, req.search); +} + +send(g: ref Private_info, meth, vers, uri, search : string) +{ + if(meth==""); + if(uri==""); + if(search==""); + if(vers != ""){ + if (g.version == nil) + sys->print("stats: version is unknown.\n"); + g.bout.puts(sys->sprint("%s 200 OK\r\n", g.version)); + g.bout.puts("Server: Charon\r\n"); + g.bout.puts("MIME-version: 1.0\r\n"); + g.bout.puts(sys->sprint("Date: %s\r\n", date->dateconv(daytime->now()))); + g.bout.puts("Content-type: text/html\r\n"); + g.bout.puts(sys->sprint("Expires: %s\r\n", date->dateconv(daytime->now()))); + g.bout.puts("\r\n"); + } + g.bout.puts("<head><title>Cache Information</title></head>\r\n"); + g.bout.puts("<body><h1>Cache Information</h1>\r\n"); + g.bout.puts("These are the pages stored in the server cache:<p>\r\n"); + lis:=(g.cache)->dump(); + while (lis!=nil){ + (a,b,d):=hd lis; + g.bout.puts(sys->sprint("<a href=\"%s\"> %s</a> \t size %d \t tag %d.<p>\r\n",a,a,b,d)); + lis = tl lis; + } + g.bout.flush(); +} diff --git a/appl/svc/mkfile b/appl/svc/mkfile new file mode 100644 index 00000000..092427ba --- /dev/null +++ b/appl/svc/mkfile @@ -0,0 +1,24 @@ +<../../mkconfig + +DIRS=\ + httpd\ + webget\ + +SHTARG=\ + auth.sh\ + net.sh\ + registry.sh\ + rstyx.sh\ + styx.sh\ + +BIN=$ROOT/dis/svc + +<$ROOT/mkfiles/mksubdirs + +SHFILES=${SHTARG:%.sh=$BIN/%} +install:V: $SHFILES +%.install:V: $BIN/% +%.installall:V: $BIN/% + +$BIN/%: %.sh + cp $stem.sh $target && chmod a+rx $target diff --git a/appl/svc/net.sh b/appl/svc/net.sh new file mode 100644 index 00000000..437c631e --- /dev/null +++ b/appl/svc/net.sh @@ -0,0 +1,6 @@ +#!/dis/sh.dis -n +load std +or {ftest -e /net/dns} {ftest -e /env/emuhost} {ndb/dns} +or {ftest -e /net/cs} {ndb/cs} +svc/registry +svc/styx diff --git a/appl/svc/registry.sh b/appl/svc/registry.sh new file mode 100644 index 00000000..4d183cb3 --- /dev/null +++ b/appl/svc/registry.sh @@ -0,0 +1,8 @@ +#!/dis/sh.dis -n +load std +or {ftest -f /mnt/registry/new} { + db=() + and {ftest -f /lib/ndb/registry} {db=(-f /lib/ndb/registry)} + mount -A -c {ndb/registry $db} /mnt/registry +} +listen -v 'tcp!*!registry' {export /mnt/registry&} # -n? diff --git a/appl/svc/rstyx.sh b/appl/svc/rstyx.sh new file mode 100644 index 00000000..22f1dd09 --- /dev/null +++ b/appl/svc/rstyx.sh @@ -0,0 +1,4 @@ +#!/dis/sh.dis -n +load std +listen 'tcp!*!rstyx' {runas $user auxi/rstyxd&} +#and {ftest -d /net/il} {listen 'il!*!rstyx' {runas $user auxi/rstyxd&}} diff --git a/appl/svc/styx.sh b/appl/svc/styx.sh new file mode 100644 index 00000000..3c5fde8f --- /dev/null +++ b/appl/svc/styx.sh @@ -0,0 +1,4 @@ +#!/dis/sh.dis -n +load std +listen -v 'tcp!*!styx' {export /&} # -n? +#and {ftest -d /net/il} {listen -v 'il!*!styx' {export /&}} # -n? diff --git a/appl/svc/webget/date.b b/appl/svc/webget/date.b new file mode 100644 index 00000000..71248954 --- /dev/null +++ b/appl/svc/webget/date.b @@ -0,0 +1,266 @@ +implement Date; + +include "sys.m"; + sys: Sys; + +include "daytime.m"; + daytime : Daytime; + +Tm: import daytime; + +include "date.m"; + + # print dates in the format + # Wkd, DD Mon YYYY HH:MM:SS GMT + # parse dates of formats + # Wkd, DD Mon YYYY HH:MM:SS GMT + # Weekday, DD-Mon-YY HH:MM:SS GMT + # Wkd Mon ( D|DD) HH:MM:SS YYYY + # plus anything similar + +SEC2MIN: con 60; +SEC2HOUR: con (60*SEC2MIN); +SEC2DAY: con (24*SEC2HOUR); + +# days per month plus days/year + +dmsize := array[] of { + 365, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 +}; + +ldmsize := array[] of { + 366, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 +}; + + +# return the days/month for the given year + + +weekdayname := array[] of { + "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" +}; + +wdayname := array[] of { + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" +}; + + +monname := array[] of { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" +}; + +init() +{ + daytime = load Daytime Daytime->PATH; + sys = load Sys "$Sys"; + if (daytime==nil) + sys->print("daytime load: %r\n"); +} + +# internals.... +dateindex : fn(nil: string, nill:array of string): int; +gmtm2sec : fn(tm: Tm): int; + + +yrsize(yr : int): array of int { + if(yr % 4 == 0 && (yr % 100 != 0 || yr % 400 == 0)) + return ldmsize; + else + return dmsize; +} + +tolower(c: int): int { + if(c >= 'A' && c <= 'Z') + return c - 'A' + 'a'; + return c; +} + + +isalpha(c: int): int{ + return c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z'; +} + + +isdig(c: int): int { + return c >= '0' && c <= '9'; +} + + +dateconv(t: int): string { + tm : ref Tm; + tm = daytime->gmt(t); + return sys->sprint("%s, %.2d %s %.4d %.2d:%.2d:%.2d GMT", + wdayname[tm.wday], tm.mday, monname[tm.mon], tm.year+1900, + tm.hour, tm.min, tm.sec); +} + + +dateword(date : string): (string,string) { + p : string; + i:=0; + p = ""; + while((i<len date) && !isalpha(date[i]) && !isdig(date[i])) + i++; + while((i<len date) && isalpha(date[i])){ + p[len p] = tolower(date[i]); + i++; + } + rest := ""; + if(i < len date) + rest = date[i:]; + return (rest,p); +} + + +datenum(date : string): (string, int){ + n, i : int; + i=0; + while((i<len date) && !isdig(date[i])) + i++; + if(i == len date) + return (nil, -1); + n = 0; + while((i<len date) && isdig(date[i])){ + n = n * 10 + date[i] - '0'; + i++; + } + return (date[i:], n); +} + + + # parse a date and return the seconds since the epoch + # return 0 for a failure + +# could be big? +date2sec(date : string): int { + tm : Tm; + buf : string; + + # Weekday|Wday + + (date,buf) = dateword(date); + tm.wday = dateindex(buf, wdayname); + if(tm.wday < 0) + tm.wday = dateindex(buf, weekdayname); + + if(tm.wday < 0) + return 0; + + # check for the two major formats + + (date,buf) = dateword(date); + tm.mon = dateindex(buf, monname); + if(tm.mon >= 0){ + # MM + (date, tm.mday) = datenum(date); + if(tm.mday < 1 || tm.mday > 31) + return 0; + + # HH:MM:SS + (date, tm.hour) = datenum(date); + if(tm.hour < 0 || tm.hour >= 24) + return 0; + (date, tm.min) = datenum(date); + if(tm.min < 0 || tm.min >= 60) + return 0; + (date, tm.sec) = datenum(date); + if(tm.sec < 0 || tm.sec >= 60) + return 0; + + + # YYYY + (nil, tm.year) = datenum(date); + if(tm.year < 70 || tm.year > 99 && tm.year < 1970) + return 0; + if(tm.year >= 1970) + tm.year -= 1900; + }else{ + # MM-Mon-(YY|YYYY) + (date, tm.mday) = datenum(date); + if(tm.mday < 1 || tm.mday > 31) + return 0; + (date,buf) = dateword(date); + tm.mon = dateindex(buf, monname); + if(tm.mon < 0 || tm.mon >= 12) + return 0; + (date, tm.year) = datenum(date); + if(tm.year < 70 || tm.year > 99 && tm.year < 1970) + return 0; + if(tm.year >= 1970) + tm.year -= 1900; + + # HH:MM:SS + (date, tm.hour) = datenum(date); + if(tm.hour < 0 || tm.hour >= 24) + return 0; + (date, tm.min) = datenum(date); + if(tm.min < 0 || tm.min >= 60) + return 0; + (date, tm.sec) = datenum(date); + if(tm.sec < 0 || tm.sec >= 60) + return 0; + + # timezone + (date,buf)=dateword(date); + if(len buf >= 3 && lowercase(buf[0:3])!="gmt") + return 0; + } + + tm.zone="GMT"; + return gmtm2sec(tm); +} + +lowercase(name:string): string { + p: string; + for(i:=0;i<len name;i++) + p[i]=tolower(name[i]); + return p; +} + +dateindex(d : string, tab : array of string): int{ + for(i := 0; i < len tab; i++) + if (lowercase(tab[i]) == d) + return i; + return -1; +} + + +# compute seconds since Jan 1 1970 GMT + +gmtm2sec(tm:Tm): int +{ + secs,i : int; + d2m: array of int; + sys = load Sys "$Sys"; + secs=0; + + #seconds per year + tm.year += 1900; + if(tm.year < 1970) + return 0; + for(i = 1970; i < tm.year; i++){ + d2m = yrsize(i); + secs += d2m[0] * SEC2DAY; + } + + + #seconds per month + d2m = yrsize(tm.year); + for(i = 0; i < tm.mon; i++) + secs += d2m[i+1] * SEC2DAY; + + #secs in last month + secs += (tm.mday-1) * SEC2DAY; + + #hours, minutes, seconds + secs += tm.hour * SEC2HOUR; + secs += tm.min * SEC2MIN; + secs += tm.sec; + + return secs; +} + +now(): int +{ + return daytime->now(); +} diff --git a/appl/svc/webget/date.m b/appl/svc/webget/date.m new file mode 100644 index 00000000..61c31a48 --- /dev/null +++ b/appl/svc/webget/date.m @@ -0,0 +1,12 @@ + +Date: module{ + PATH : con "/dis/svc/webget/date.dis"; + + dateconv: fn(secs :int): string; # returns an http formatted + # date representing secs. + date2sec: fn(foo:string): int; # parses a date and returns + # number of secs since the + # epoch that it represents. + now: fn(): int; # so don't have to load daytime too + init: fn(); # to load needed modules +}; diff --git a/appl/svc/webget/file.b b/appl/svc/webget/file.b new file mode 100644 index 00000000..49c51423 --- /dev/null +++ b/appl/svc/webget/file.b @@ -0,0 +1,67 @@ +implement Transport; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "string.m"; + S: String; + +include "bufio.m"; + B : Bufio; + Iobuf: import Bufio; + +include "message.m"; + M: Message; + Msg, Nameval: import M; + +include "url.m"; + U: Url; + ParsedUrl: import U; + +include "webget.m"; + +include "wgutils.m"; + W: WebgetUtils; + Fid, Req: import WebgetUtils; + +include "transport.m"; + +init(w: WebgetUtils) +{ + sys = load Sys Sys->PATH; + W = w; + M = W->M; + S = W->S; + B = W->B; + U = W->U; +} + +connect(c: ref Fid, r: ref Req, donec: chan of ref Fid) +{ + u := r.url; + mrep: ref Msg = nil; + if(!(u.host == "" || u.host == "localhost")) + mrep = W->usererr(r, "no remote file system to " + u.host); + else { + f := u.pstart + u.path; + io := B->open(f, sys->OREAD); + if(io == nil) + mrep = W->usererr(r, sys->sprint("can't open %s: %r\n", f)); + else { + mrep = Msg.newmsg(); + e := W->getdata(io, mrep, W->fixaccept(r.types), u); + B->io.close(); + if(e != "") + mrep = W->usererr(r, e); + else + W->okprefix(r, mrep); + } + } + if(mrep != nil) { + W->log(c, "file: reply ready for " + r.reqid + ": " + mrep.prefixline); + r.reply = mrep; + donec <-= c; + } +} diff --git a/appl/svc/webget/ftp.b b/appl/svc/webget/ftp.b new file mode 100644 index 00000000..9162fcb7 --- /dev/null +++ b/appl/svc/webget/ftp.b @@ -0,0 +1,227 @@ +implement Transport; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "string.m"; + S: String; + +include "bufio.m"; + B : Bufio; + Iobuf: import Bufio; + +include "message.m"; + M: Message; + Msg, Nameval: import M; + +include "url.m"; + U: Url; + ParsedUrl: import U; + +include "webget.m"; + +include "wgutils.m"; + W: WebgetUtils; + Fid, Req: import WebgetUtils; + +include "transport.m"; + +FTPPORT: con "21"; +DEBUG: con 1; + +# Return codes +Extra, Success, Incomplete, TempFail, PermFail : con (1+iota); + +init(w: WebgetUtils) +{ + sys = load Sys Sys->PATH; + W = w; + M = W->M; + S = W->S; + B = W->B; + U = W->U; +} + +connect(c: ref Fid, r: ref Req, donec: chan of ref Fid) +{ + mrep: ref Msg = nil; + io, dio: ref Iobuf = nil; + err := ""; + u := r.url; + port := u.port; + if(port == "") + port = FTPPORT; + addr := "tcp!" + u.host + "!" + port; + +dummyloop: # just for breaking out of on error + for(;;) { + W->log(c, sys->sprint("ftp: dialing %s", addr)); + (ok, net) := sys->dial(addr, nil); + if(ok < 0) { + err = sys->sprint("dial error: %r"); + break dummyloop; + } + io = B->fopen(net.dfd, sys->ORDWR); + if(io == nil) { + err = "cannot open network via bufio"; + break dummyloop; + } + + # look for Hello + (code, msg) := getreply(c, io); + if(code != Success) { + err = "instead of hello: " + msg; + break dummyloop; + } + # logon + err = sendrequest(c, io, "USER anonymous"); + if(err != "") + break dummyloop; + (code, msg) = getreply(c, io); + if(code != Success) { + if(code == Incomplete) { + # need password + err = sendrequest(c, io, "PASS webget@webget.com"); + (code, msg) = getreply(c, io); + if(code != Success) { + err = "login failed: " + msg; + break dummyloop; + } + } + else { + err = "login failed: " + msg; + break dummyloop; + } + } + # image type + err = sendrequest(c, io, "TYPE I"); + (code, msg) = getreply(c, io); + if(code != Success) { + err = "can't set type I: " + msg; + break dummyloop; + } + # passive mode + err = sendrequest(c, io, "PASV"); + (code, msg) = getreply(c, io); + if(code != Success) { + err = "can't use passive mode: " + msg; + break dummyloop; + } + (paddr, pport) := passvap(msg); + if(paddr == "") { + err = "passive mode protocol botch: " + msg; + break dummyloop; + } + # dial data port + daddr := "tcp!" + paddr + "!" + pport; + W->log(c, sys->sprint("ftp: dialing data %s", daddr)); + (ok2, dnet) := sys->dial(daddr, nil); + if(ok2 < 0) { + err = sys->sprint("data dial error: %r"); + break dummyloop; + } + dio = B->fopen(dnet.dfd, sys->ORDWR); + if(dio == nil) { + err = "cannot open network via bufio"; + break dummyloop; + } + # tell remote to send file + err = sendrequest(c, io, "RETR " + u.path); + (code, msg) = getreply(c, io); + if(code != Extra) { + err = "passive mode retrieve failed: " + msg; + break dummyloop; + } + + mrep = Msg.newmsg(); +W->log(c, "reading from dio now"); + err = W->getdata(dio, mrep, W->fixaccept(r.types), u); +W->log(c, "done reading from dio now, err=" + err); + B->dio.close(); + if(err == "") + W->okprefix(r, mrep); + break dummyloop; + } + if(io != nil) + B->io.close(); + if(dio != nil) + B->dio.close(); + if(err != "") + mrep = W->usererr(r, err); + if(mrep != nil) { + W->log(c, "ftp: reply ready for " + r.reqid + ": " + mrep.prefixline); + r.reply = mrep; + donec <-= c; + } +} + +getreply(c: ref Fid, io: ref Iobuf) : (int, string) +{ + for(;;) { + line := B->io.gets('\n'); + n := len line; + if(n == 0) + break; + if(DEBUG) + W->log(c, "ftp: got reply: " + line); + if(line[n-1] == '\n') { + if(n > 2 && line[n-2] == '\r') + line = line[0:n-2]; + else + line = line[0:n-1]; + } + rv := int line; + if(rv >= 100 && rv < 600) { + # if line is like '123-stuff' + # then there will be more lines until + # '123 stuff' + if(len line<4 || line[3]==' ') + return (rv/100, line); + } + } + return (-1, ""); +} + +sendrequest(c: ref Fid, io: ref Iobuf, cmd: string) : string +{ + if(DEBUG) + W->log(c, "ftp: send request: " + cmd); + cmd = cmd + "\r\n"; + buf := array of byte cmd; + n := len buf; + if(B->io.write(buf, n) != n) + return sys->sprint("write error: %r"); + return ""; +} + +passvap(s: string) : (string, string) +{ + # Parse reply to PASSV to find address and port numbers. + # This is AI + addr := ""; + port := ""; + (nil, v) := S->splitl(s, "("); + if(v != "") + s = v[1:]; + else + (nil, s) = S->splitl(s, "0123456789"); + if(s != "") { + (n, l) := sys->tokenize(s, ","); + if(n >= 6) { + addr = hd l + "."; + l = tl l; + addr += hd l + "."; + l = tl l; + addr += hd l + "."; + l = tl l; + addr += hd l; + l = tl l; + p1 := int hd l; + p2 := int hd tl l; + port = string (((p1&255)<<8)|(p2&255)); + } + } + return (addr, port); +} diff --git a/appl/svc/webget/http.b b/appl/svc/webget/http.b new file mode 100644 index 00000000..35b52966 --- /dev/null +++ b/appl/svc/webget/http.b @@ -0,0 +1,602 @@ +implement Transport; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "string.m"; + S: String; + +include "bufio.m"; + B : Bufio; + Iobuf: import B; + +include "date.m"; + D: Date; + +include "message.m"; + M: Message; + Msg, Nameval: import M; + +include "url.m"; + U: Url; + ParsedUrl: import U; + +include "webget.m"; + +include "wgutils.m"; + W: WebgetUtils; + Fid, Req: import WebgetUtils; + +include "keyring.m"; +include "asn1.m"; +include "pkcs.m"; +include "sslsession.m"; +include "ssl3.m"; + ssl3: SSL3; + Context: import ssl3; +# Inferno supported cipher suites: RSA_EXPORT_RC4_40_MD5 +ssl_suites := array [] of {byte 0, byte 16r03}; +ssl_comprs := array [] of {byte 0}; + +include "transport.m"; + +HTTPD: con "80"; # Default IP port +HTTPSD: con "443"; # Default IP port for HTTPS +Version: con "1.0"; # Client ID +MAXREDIR: con 10; + +HTTPheader: adt +{ + vers: string; + code: int; + length: int; + content: string; +}; + +Resp: adt +{ + code: int; + action: int; + cacheable: int; + name: string; +}; + +DODATA, ERROR, REDIR, UNAUTH, HTMLERR: con iota; + +usecache := 1; +cachedir: con "/services/webget/cache"; + +httpproxy: ref ParsedUrl; +agent := "Inferno-webget/" + Version; + +responses := array[] of { + (Resp)(100, DODATA, 0, "Continue" ), + (Resp)(101, ERROR, 0, "Switching Protocols" ), + (Resp)(200, DODATA, 1, "Ok" ), + (Resp)(201, DODATA, 0, "Created" ), + (Resp)(202, DODATA, 0, "Accepted" ), + (Resp)(203, DODATA, 1, "Non-Authoratative Information" ), + (Resp)(204, DODATA, 0, "No content" ), + (Resp)(205, DODATA, 0, "Reset content" ), + (Resp)(206, DODATA, 0, "Partial content" ), + (Resp)(300, ERROR, 1, "Multiple choices" ), + (Resp)(301, REDIR, 1, "Moved permanently" ), + (Resp)(302, REDIR, 0, "Moved temporarily" ), + (Resp)(303, ERROR, 0, "See other" ), + (Resp)(304, ERROR, 0, "Not modified" ), + (Resp)(305, ERROR, 0, "Use proxy" ), + (Resp)(400, HTMLERR, 0, "Bad request" ), + (Resp)(401, UNAUTH, 0, "Unauthorized" ), + (Resp)(402, HTMLERR, 0, "Payment required" ), + (Resp)(403, HTMLERR, 0, "Forbidden" ), + (Resp)(404, HTMLERR, 0, "Not found" ), + (Resp)(405, HTMLERR, 0, "Method not allowed" ), + (Resp)(406, HTMLERR, 0, "Not Acceptable" ), + (Resp)(407, HTMLERR, 0, "Proxy authentication required" ), + (Resp)(408, HTMLERR, 0, "Request timed-out" ), + (Resp)(409, HTMLERR, 0, "Conflict" ), + (Resp)(410, HTMLERR, 1, "Gone" ), + (Resp)(411, HTMLERR, 0, "Length required" ), + (Resp)(412, HTMLERR, 0, "Precondition failed" ), + (Resp)(413, HTMLERR, 0, "Request entity too large" ), + (Resp)(414, HTMLERR, 0, "Request-URI too large" ), + (Resp)(415, HTMLERR, 0, "Unsupported media type" ), + (Resp)(500, ERROR, 0, "Internal server error"), + (Resp)(501, ERROR, 0, "Not implemented"), + (Resp)(502, ERROR, 0, "Bad gateway"), + (Resp)(503, ERROR, 0, "Service unavailable"), + (Resp)(504, ERROR, 0, "Gateway time-out"), + (Resp)(505, ERROR, 0, "HTTP version not supported"), +}; + +init(w: WebgetUtils) +{ + sys = load Sys Sys->PATH; + D = load Date Date->PATH; + D->init(); + W = w; + M = W->M; + S = W->S; + B = W->B; + U = W->U; + ssl3 = nil; # load on demand + readconfig(); +} + +readconfig() +{ + cfgio := B->open("/services/webget/config", sys->OREAD); + if(cfgio != nil) { + for(;;) { + line := B->cfgio.gets('\n'); + if(line == "") { + B->cfgio.close(); + break; + } + if(line[0]=='#') + continue; + (key, val) := S->splitl(line, " \t"); + val = S->take(S->drop(val, " \t"), "^\r\n"); + if(val == "") + continue; + if(key == "httpproxy" && val != "none") { + # val should be host or host:port + httpproxy = U->makeurl("http://" + val); + W->log(nil, "Using http proxy " + httpproxy.tostring()); + usecache = 0; + } + if(key == "agent") { + agent = val; + W->log(nil, sys->sprint("User agent specfied as '%s'\n", agent)); + } + } + } +} + +connect(c: ref Fid, r: ref Req, donec: chan of ref Fid) +{ + method := r.method; + u := r.url; + accept := W->fixaccept(r.types); + mrep, cachemrep: ref Msg = nil; + validate : string; + io: ref Iobuf = nil; + redir := 1; + usessl := 0; + sslx : ref Context; + + redirloop: + for(nredir := 0; redir && nredir < MAXREDIR; nredir++) { + redir = 0; + mrep = nil; + cachemrep = nil; + io = nil; + validate = ""; + if(u.scheme == Url->HTTPS) { + usessl = 1; + if(ssl3 == nil) { + ssl3 = load SSL3 SSL3->PATH; + ssl3->init(); + sslx = ssl3->Context.new(); + } + } + cacheit := usecache; + if(r.cachectl == "no-cache" || usessl) + cacheit = 0; + resptime := 0; + # + # Perhaps try the cache + # + if(usecache && method == "GET") { + (cachemrep, validate) = cacheread(c, u, r); + if(cachemrep != nil && validate == "") + cacheit = 0; + } + else + cacheit = 0; + + if(cachemrep == nil || validate != "") { + # + # Find the port and dial the network + # + dialu := u; + if(httpproxy != nil) + dialu = httpproxy; + port := dialu.port; + if(port == "") { + if(usessl) + port = HTTPSD; + else + port = HTTPD; + } + addr := "tcp!" + dialu.host + "!" + port; + + W->log(c, sys->sprint("http: dialing %s", addr)); + (ok, net) := sys->dial(addr, nil); + if(ok < 0) { + mrep = W->usererr(r, sys->sprint("%r")); + break redirloop; + } + W->log(c, "http: connected"); + e: string; + if(usessl) { + vers := 3; + info := ref SSL3->Authinfo(ssl_suites, ssl_comprs, nil, 0, nil, nil, nil); + (e, vers) = sslx.client(net.dfd, addr, vers, info); + if(e != "") { + mrep = W->usererr(r, e); + break redirloop; + } + W->log(c, "https: ssl handshake complete"); + } + + # + # Issue the request + # + m := Msg.newmsg(); + requ: string; + if(httpproxy != nil) + requ = u.tostring(); + else { + requ = u.pstart + u.path; + if(u.query != "") + requ += "?" + u.query; + } + m.prefixline = method + " " + requ + " HTTP/1.0"; + hdrs := Nameval("Host", u.host) :: + Nameval("User-agent", agent) :: + Nameval("Accept", accept) :: nil; + m.addhdrs(hdrs); + if(validate != "") + m.addhdrs(Nameval("If-Modified_Since", validate) :: nil); + if(r.auth != "") { + m.addhdrs(Nameval("Authorization", "Basic " + r.auth) :: nil); + cacheit = 0; + } + if(method == "POST") { + m.body = r.body; + m.bodylen = len m.body; + m.addhdrs(Nameval("Content-Length", string (len r.body)) :: + Nameval("Content-type", "application/x-www-form-urlencoded") :: + nil); + } + io = B->fopen(net.dfd, sys->ORDWR); + if(io == nil) { + mrep = W->usererr(r, "cannot open network via bufio"); + break redirloop; + } + e = m.writemsg(io); + if(e != "") { + mrep = W->usererr(r, e); + break redirloop; + } + (mrep, e) = Msg.readhdr(io, 1); + if(e!= "") { + mrep = W->usererr(r, e); + break redirloop; + } + resptime = D->now(); + } + else + mrep = cachemrep; + status := mrep.prefixline; + W->log(c, "http: response from network or cache: " + status + + "\n" + mrep.header() + ); + + if(!S->prefix("HTTP/", status)) { + mrep = W->usererr(r, "expected http got something else"); + break redirloop; + } + code := getcode(status); + + if(validate != "" && code == 304) { + # response: "Not Modified", so use cached response + mrep = cachemrep; + B->io.close(); + io = nil; + + # let caching happen with new response time + # (so age will be small next time) + status = mrep.prefixline; + W->log(c, "http: validate ok, using cache: " + status); + code = getcode(status); + } + + for(i := 0; i < len responses; i++) { + if(responses[i].code == code) + break; + } + + if(i >= len responses) { + mrep = W->usererr(r, "Unrecognized HTTP response code"); + break redirloop; + } + + (nil, conttype) := mrep.fieldval("content-type"); + cacheit = cacheit && responses[i].cacheable; + case responses[i].action { + DODATA => + e := W->getdata(io, mrep, accept, u); + if(e != "") + mrep = W->usererr(r, e); + else { + if(cacheit) + cachewrite(c, mrep, u, resptime); + W->okprefix(r, mrep); + } + ERROR => + mrep = W->usererr(r, responses[i].name); + UNAUTH => + (cok, chal) := mrep.fieldval("www-authenticate"); + if(cok && r.auth == "") + mrep = W->usererr(r, "Unauthorized: " + chal); + else { + if(conttype == "text/html" && htmlok(accept)) { + e := W->getdata(io, mrep, accept, u); + if(e != "") + mrep = W->usererr(r, "Authorization needed"); + else + W->okprefix(r, mrep); + } + else + mrep = W->usererr(r, "Authorization needed"); + } + REDIR => + (nil, newloc) := mrep.fieldval("location"); + if(newloc == "") { + e := W->getdata(io, mrep, accept, u); + if(e != "") + mrep = W->usererr(r, e); + else + W->okprefix(r, mrep); + } + else { + if(cacheit) + cachewrite(c, mrep, u, resptime); + if(method == "POST") { + # this is called "erroneous behavior of some + # HTTP 1.0 clients" in the HTTP 1.1 spec, + # but servers out there rely on this... + method = "GET"; + } + oldu := u; + u = U->makeurl(newloc); + u.frag = ""; + u.makeabsolute(oldu); + W->log(c, "http: redirect to " + u.tostring()); + if(io != nil) { + B->io.close(); + io = nil; + } + redir = 1; + } + HTMLERR => + if(cacheit) + cachewrite(c, mrep, u, resptime); + if(conttype == "text/html" && htmlok(accept)) { + e := W->getdata(io, mrep, accept, u); + if(e != "") + mrep = W->usererr(r, responses[i].name); + else + W->okprefix(r, mrep); + } + else + mrep = W->usererr(r, responses[i].name); + } + } + if(io != nil) + B->io.close(); + if(nredir == MAXREDIR) + mrep = W->usererr(r, "redirect loop"); + if(mrep != nil) { + W->log(c, "http: reply ready for " + r.reqid + ": " + mrep.prefixline); + r.reply = mrep; + donec <-= c; + } +} + +getcode(status: string) : int +{ + (vers, scode) := S->splitl(status, " "); + scode = S->drop(scode, " "); + return int scode; +} + +htmlok(accept: string) : int +{ + (nil,y) := S->splitstrl(accept, "text/html"); + return (y != ""); +} + +mkhtml(msg: string) : ref Msg +{ + m := Msg.newmsg(); + m.body = array of byte sys->sprint("<HTML>\n"+ + "<BODY>\n"+ + "<H1>HTTP Reported Error</H1>\n"+ + "<P>\n"+ + "The server reported an error: %s\n"+ + "</BODY>\n"+ + "</HTML>\n", msg); + m.bodylen = len m.body; + m.update("content-type", "text/html"); + m.update("content-location", "webget-internal-message"); + return m; +} + +cacheread(c: ref Fid, u: ref Url->ParsedUrl, r: ref Req) : (ref Msg, string) +{ + ctl := r.cachectl; + if(ctl == "no-cache") + return (nil, ""); + uname := u.tostring(); + hname := hashname(uname); + io := B->open(hname, sys->OREAD); + if(io == nil) + return (nil, ""); + (mrep, e) := Msg.readhdr(io, 1); + if(e != "") { + B->io.close(); + return (nil, ""); + } + + # see if cache validation is necessary + validate := ""; + cmaxstale := 0; + cmaxage := -1; + (nl, l) := sys->tokenize(ctl, ","); + for(i := 0; i < nl; i++) { + s := hd l; + if(S->prefix("max-stale=", s)) + cmaxstale = int s[10:]; + else if (S->prefix("max-age=", s)) + cmaxage = int s[8:]; + l = tl l; + } + # we wrote x-resp-time and x-url, so they should be there + (srst, sresptime) := mrep.fieldval("x-resp-time"); + (su, surl) := mrep.fieldval("x-url"); + if(!srst || !su) { + cacheremove(hname); + B->io.close(); + return (nil, ""); + } + if(surl != uname) { + B->io.close(); + return (nil, ""); + } + (se, sexpires) := mrep.fieldval("expires"); + (sd, sdate) := mrep.fieldval("date"); + (slm, slastmod) := mrep.fieldval("last-modified"); + (sa, sage) := mrep.fieldval("age"); + + # calculate response age (in seconds), as of time received + respt := int sresptime; + datet := D->date2sec(sdate); + nowt := D->now(); + + age := nowt - respt; + if(sa) + age += (int sage); + freshness_lifetime := 0; + (sma, smaxage) := mrep.fieldval("max-age"); + if(sma) + freshness_lifetime = int smaxage; + else if(sd && se) { + exp := D->date2sec(sexpires); + freshness_lifetime = exp - datet; + } + else if(slm){ + # use heuristic: 10% of time since last modified + lastmod := D->date2sec(slastmod); + if(lastmod == 0) + lastmod = respt; + freshness_lifetime = (nowt - lastmod) / 10; + } + if(age - freshness_lifetime > cmaxstale || + (cmaxage != -1 && age >= cmaxage)) { + W->log(c, sys->sprint("must revalidate, age=%d, lifetime=%d, cmaxstale=%d, cmaxage=%d\n", + age, freshness_lifetime, cmaxstale, cmaxage)); + if(slm) + validate = slastmod; + else + return (nil, ""); + } + e = mrep.readbody(io); + B->io.close(); + if(e != "") { + cacheremove(hname); + return (nil, ""); + } + if(validate == "") + W->log(c, "cache hit " + hname); + else + W->log(c, "cache hit " + hname + " if not modified after " + validate); + return (mrep, validate); +} + +cachewrite(c: ref Fid, m: ref Msg, u: ref Url->ParsedUrl, respt: int) +{ + (sp, spragma) := m.fieldval("pragma"); + if(sp && spragma == "no-cache") + return; + (scc, scachectl) := m.fieldval("cache-control"); + if(scc) { + (snc, nil) := attrval(scachectl, "no-cache"); + (sns, nil) := attrval(scachectl, "no-store"); + (smv, nil) := attrval(scachectl, "must-revalidate"); + if(snc || sns || smv) + return; + } + uname := u.tostring(); + hname := hashname(uname); + m.update("x-resp-time", string respt); + m.update("x-url", uname); + m.update("content-length", string m.bodylen); + io := B->create(hname, sys->OWRITE, 8r666); + if(io != nil) { + W->log(c, "cache writeback to " + hname); + m.writemsg(io); + B->io.close(); + } +} + +cacheremove(hname: string) +{ + sys->remove(hname); +} + +attrval(avs, aname: string) : (int, string) +{ + (nl, l) := sys->tokenize(avs, ","); + for(i := 0; i < nl; i++) { + s := hd l; + (lh, rh) := S->splitl(s, "="); + lh = trim(lh); + if(lh == aname) { + if(rh != "") + rh = trim(rh[1:]); + return (1, rh); + } + l = tl l; + } + return (0, ""); +} + +trim(s: string) : string +{ + is := 0; + ie := len s; + while(is < ie) { + if(!S->in(s[is], " \t\n\r")) + break; + is++; + } + if(is == ie) + return ""; + if(s[is] == '"') + is++; + while(ie > is) { + if(!S->in(s[ie-1], " \t\n\r")) + break; + ie--; + } + if(is >= ie) + return ""; + return s[is:ie]; +} + +hashname(uname: string) : string +{ + hash := 0; + prime: con 8388617; + # start after "http:" + for(i := 5; i < len uname; i++) { + hash = hash % prime; + hash = (hash << 7) + uname[i]; + } + return sys->sprint(cachedir + "/%.8ux", hash); +} diff --git a/appl/svc/webget/image2enc.b b/appl/svc/webget/image2enc.b new file mode 100644 index 00000000..8345bd40 --- /dev/null +++ b/appl/svc/webget/image2enc.b @@ -0,0 +1,1070 @@ +implement Image2enc; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "bufio.m"; + +include "imagefile.m"; + Rawimage: import RImagefile; + +include "image2enc.m"; + +closest:= array[16*16*16] of { + byte 255,byte 255,byte 255,byte 254,byte 254,byte 237,byte 220,byte 203, + byte 253,byte 236,byte 219,byte 202,byte 252,byte 235,byte 218,byte 201, + byte 255,byte 255,byte 255,byte 254,byte 254,byte 237,byte 220,byte 203, + byte 253,byte 236,byte 219,byte 202,byte 252,byte 235,byte 218,byte 201, + byte 255,byte 255,byte 255,byte 250,byte 250,byte 250,byte 220,byte 249, + byte 249,byte 249,byte 232,byte 248,byte 248,byte 248,byte 231,byte 201, + byte 251,byte 251,byte 250,byte 250,byte 250,byte 250,byte 249,byte 249, + byte 249,byte 249,byte 232,byte 248,byte 248,byte 248,byte 231,byte 201, + byte 251,byte 251,byte 250,byte 250,byte 250,byte 233,byte 233,byte 249, + byte 249,byte 232,byte 215,byte 215,byte 248,byte 231,byte 214,byte 197, + byte 234,byte 234,byte 250,byte 250,byte 233,byte 233,byte 216,byte 216, + byte 249,byte 232,byte 215,byte 198,byte 198,byte 231,byte 214,byte 197, + byte 217,byte 217,byte 217,byte 246,byte 233,byte 216,byte 216,byte 199, + byte 199,byte 215,byte 215,byte 198,byte 198,byte 198,byte 214,byte 197, + byte 200,byte 200,byte 246,byte 246,byte 246,byte 216,byte 199,byte 199, + byte 245,byte 245,byte 198,byte 244,byte 244,byte 244,byte 227,byte 197, + byte 247,byte 247,byte 246,byte 246,byte 246,byte 246,byte 199,byte 245, + byte 245,byte 245,byte 228,byte 244,byte 244,byte 244,byte 227,byte 193, + byte 230,byte 230,byte 246,byte 246,byte 229,byte 229,byte 212,byte 245, + byte 245,byte 228,byte 228,byte 211,byte 244,byte 227,byte 210,byte 193, + byte 213,byte 213,byte 229,byte 229,byte 212,byte 212,byte 212,byte 195, + byte 228,byte 228,byte 211,byte 211,byte 194,byte 227,byte 210,byte 193, + byte 196,byte 196,byte 242,byte 242,byte 212,byte 195,byte 195,byte 241, + byte 241,byte 211,byte 211,byte 194,byte 194,byte 240,byte 210,byte 193, + byte 243,byte 243,byte 242,byte 242,byte 242,byte 195,byte 195,byte 241, + byte 241,byte 241,byte 194,byte 194,byte 240,byte 240,byte 239,byte 205, + byte 226,byte 226,byte 242,byte 242,byte 225,byte 225,byte 195,byte 241, + byte 241,byte 224,byte 224,byte 240,byte 240,byte 239,byte 239,byte 205, + byte 209,byte 209,byte 225,byte 225,byte 208,byte 208,byte 208,byte 224, + byte 224,byte 223,byte 223,byte 223,byte 239,byte 239,byte 222,byte 205, + byte 192,byte 192,byte 192,byte 192,byte 207,byte 207,byte 207,byte 207, + byte 206,byte 206,byte 206,byte 206,byte 205,byte 205,byte 205,byte 205, + byte 255,byte 255,byte 255,byte 254,byte 254,byte 237,byte 220,byte 203, + byte 253,byte 236,byte 219,byte 202,byte 252,byte 235,byte 218,byte 201, + byte 255,byte 238,byte 221,byte 221,byte 254,byte 237,byte 220,byte 203, + byte 253,byte 236,byte 219,byte 202,byte 252,byte 235,byte 218,byte 201, + byte 255,byte 221,byte 221,byte 221,byte 204,byte 250,byte 220,byte 249, + byte 249,byte 249,byte 232,byte 248,byte 248,byte 248,byte 231,byte 201, + byte 251,byte 221,byte 221,byte 204,byte 250,byte 250,byte 249,byte 249, + byte 249,byte 249,byte 232,byte 248,byte 248,byte 248,byte 231,byte 201, + byte 251,byte 251,byte 204,byte 250,byte 250,byte 233,byte 233,byte 249, + byte 249,byte 232,byte 215,byte 215,byte 248,byte 231,byte 214,byte 197, + byte 234,byte 234,byte 250,byte 250,byte 233,byte 233,byte 216,byte 216, + byte 249,byte 232,byte 215,byte 198,byte 198,byte 231,byte 214,byte 197, + byte 217,byte 217,byte 217,byte 246,byte 233,byte 216,byte 216,byte 199, + byte 199,byte 215,byte 215,byte 198,byte 198,byte 198,byte 214,byte 197, + byte 200,byte 200,byte 246,byte 246,byte 246,byte 216,byte 199,byte 199, + byte 245,byte 245,byte 198,byte 244,byte 244,byte 244,byte 227,byte 197, + byte 247,byte 247,byte 246,byte 246,byte 246,byte 246,byte 199,byte 245, + byte 245,byte 245,byte 228,byte 244,byte 244,byte 244,byte 227,byte 193, + byte 230,byte 230,byte 246,byte 246,byte 229,byte 229,byte 212,byte 245, + byte 245,byte 228,byte 228,byte 211,byte 244,byte 227,byte 210,byte 193, + byte 213,byte 213,byte 229,byte 229,byte 212,byte 212,byte 212,byte 195, + byte 228,byte 228,byte 211,byte 211,byte 194,byte 227,byte 210,byte 193, + byte 196,byte 196,byte 242,byte 242,byte 212,byte 195,byte 195,byte 241, + byte 241,byte 211,byte 211,byte 194,byte 194,byte 240,byte 210,byte 193, + byte 243,byte 243,byte 242,byte 242,byte 242,byte 195,byte 195,byte 241, + byte 241,byte 241,byte 194,byte 194,byte 240,byte 240,byte 239,byte 205, + byte 226,byte 226,byte 242,byte 242,byte 225,byte 225,byte 195,byte 241, + byte 241,byte 224,byte 224,byte 240,byte 240,byte 239,byte 239,byte 205, + byte 209,byte 209,byte 225,byte 225,byte 208,byte 208,byte 208,byte 224, + byte 224,byte 223,byte 223,byte 223,byte 239,byte 239,byte 222,byte 205, + byte 192,byte 192,byte 192,byte 192,byte 207,byte 207,byte 207,byte 207, + byte 206,byte 206,byte 206,byte 206,byte 205,byte 205,byte 205,byte 205, + byte 255,byte 255,byte 255,byte 191,byte 191,byte 191,byte 220,byte 190, + byte 190,byte 190,byte 173,byte 189,byte 189,byte 189,byte 172,byte 201, + byte 255,byte 221,byte 221,byte 221,byte 204,byte 191,byte 220,byte 190, + byte 190,byte 190,byte 173,byte 189,byte 189,byte 189,byte 172,byte 201, + byte 255,byte 221,byte 221,byte 204,byte 204,byte 204,byte 186,byte 186, + byte 186,byte 186,byte 186,byte 185,byte 185,byte 185,byte 168,byte 201, + byte 188,byte 221,byte 204,byte 204,byte 204,byte 187,byte 186,byte 186, + byte 186,byte 186,byte 232,byte 185,byte 185,byte 185,byte 168,byte 201, + byte 188,byte 204,byte 204,byte 204,byte 187,byte 187,byte 186,byte 186, + byte 186,byte 186,byte 169,byte 185,byte 185,byte 185,byte 168,byte 197, + byte 188,byte 188,byte 204,byte 187,byte 187,byte 233,byte 216,byte 186, + byte 186,byte 186,byte 215,byte 185,byte 185,byte 185,byte 168,byte 197, + byte 217,byte 217,byte 183,byte 183,byte 183,byte 216,byte 216,byte 199, + byte 182,byte 182,byte 215,byte 198,byte 198,byte 181,byte 214,byte 197, + byte 184,byte 184,byte 183,byte 183,byte 183,byte 183,byte 199,byte 182, + byte 182,byte 182,byte 182,byte 181,byte 181,byte 181,byte 181,byte 197, + byte 184,byte 184,byte 183,byte 183,byte 183,byte 183,byte 182,byte 182, + byte 182,byte 182,byte 182,byte 181,byte 181,byte 181,byte 164,byte 193, + byte 184,byte 184,byte 183,byte 183,byte 183,byte 183,byte 182,byte 182, + byte 182,byte 228,byte 165,byte 181,byte 181,byte 164,byte 164,byte 193, + byte 167,byte 167,byte 183,byte 229,byte 166,byte 212,byte 212,byte 182, + byte 182,byte 165,byte 211,byte 211,byte 181,byte 164,byte 210,byte 193, + byte 180,byte 180,byte 179,byte 179,byte 179,byte 179,byte 195,byte 178, + byte 178,byte 178,byte 211,byte 194,byte 177,byte 177,byte 177,byte 193, + byte 180,byte 180,byte 179,byte 179,byte 179,byte 179,byte 195,byte 178, + byte 178,byte 178,byte 178,byte 177,byte 177,byte 177,byte 177,byte 205, + byte 180,byte 180,byte 179,byte 179,byte 179,byte 179,byte 178,byte 178, + byte 178,byte 161,byte 161,byte 177,byte 177,byte 177,byte 160,byte 205, + byte 163,byte 163,byte 162,byte 162,byte 162,byte 162,byte 208,byte 178, + byte 161,byte 161,byte 223,byte 177,byte 177,byte 160,byte 160,byte 205, + byte 192,byte 192,byte 192,byte 192,byte 207,byte 207,byte 207,byte 207, + byte 206,byte 206,byte 206,byte 206,byte 205,byte 205,byte 205,byte 205, + byte 176,byte 176,byte 191,byte 191,byte 191,byte 191,byte 190,byte 190, + byte 190,byte 190,byte 173,byte 189,byte 189,byte 189,byte 172,byte 201, + byte 176,byte 221,byte 221,byte 204,byte 191,byte 191,byte 190,byte 190, + byte 190,byte 190,byte 173,byte 189,byte 189,byte 189,byte 172,byte 201, + byte 188,byte 221,byte 204,byte 204,byte 204,byte 187,byte 186,byte 186, + byte 186,byte 186,byte 173,byte 185,byte 185,byte 185,byte 168,byte 201, + byte 188,byte 204,byte 204,byte 204,byte 187,byte 187,byte 186,byte 186, + byte 186,byte 186,byte 169,byte 185,byte 185,byte 185,byte 168,byte 201, + byte 188,byte 188,byte 204,byte 187,byte 187,byte 187,byte 186,byte 186, + byte 186,byte 186,byte 169,byte 185,byte 185,byte 185,byte 168,byte 197, + byte 188,byte 188,byte 187,byte 187,byte 187,byte 170,byte 170,byte 186, + byte 186,byte 169,byte 169,byte 185,byte 185,byte 168,byte 168,byte 197, + byte 184,byte 184,byte 183,byte 183,byte 183,byte 170,byte 170,byte 182, + byte 182,byte 169,byte 152,byte 152,byte 181,byte 168,byte 151,byte 197, + byte 184,byte 184,byte 183,byte 183,byte 183,byte 183,byte 182,byte 182, + byte 182,byte 182,byte 182,byte 181,byte 181,byte 181,byte 164,byte 197, + byte 184,byte 184,byte 183,byte 183,byte 183,byte 183,byte 182,byte 182, + byte 182,byte 182,byte 165,byte 181,byte 181,byte 181,byte 164,byte 193, + byte 184,byte 184,byte 183,byte 183,byte 183,byte 166,byte 166,byte 182, + byte 182,byte 165,byte 165,byte 181,byte 181,byte 164,byte 164,byte 193, + byte 167,byte 167,byte 167,byte 166,byte 166,byte 166,byte 149,byte 182, + byte 165,byte 165,byte 165,byte 148,byte 181,byte 164,byte 147,byte 193, + byte 180,byte 180,byte 179,byte 179,byte 179,byte 179,byte 149,byte 178, + byte 178,byte 178,byte 148,byte 177,byte 177,byte 177,byte 147,byte 193, + byte 180,byte 180,byte 179,byte 179,byte 179,byte 179,byte 178,byte 178, + byte 178,byte 178,byte 178,byte 177,byte 177,byte 177,byte 160,byte 205, + byte 180,byte 180,byte 179,byte 179,byte 179,byte 162,byte 162,byte 178, + byte 178,byte 161,byte 161,byte 177,byte 177,byte 160,byte 160,byte 205, + byte 163,byte 163,byte 162,byte 162,byte 162,byte 162,byte 145,byte 161, + byte 161,byte 161,byte 144,byte 144,byte 160,byte 160,byte 160,byte 205, + byte 192,byte 192,byte 192,byte 192,byte 207,byte 207,byte 207,byte 207, + byte 206,byte 206,byte 206,byte 206,byte 205,byte 205,byte 205,byte 205, + byte 176,byte 176,byte 191,byte 191,byte 191,byte 174,byte 174,byte 190, + byte 190,byte 173,byte 156,byte 156,byte 189,byte 172,byte 155,byte 138, + byte 176,byte 176,byte 204,byte 191,byte 191,byte 174,byte 174,byte 190, + byte 190,byte 173,byte 156,byte 156,byte 189,byte 172,byte 155,byte 138, + byte 188,byte 204,byte 204,byte 204,byte 187,byte 187,byte 186,byte 186, + byte 186,byte 186,byte 169,byte 185,byte 185,byte 185,byte 168,byte 138, + byte 188,byte 188,byte 204,byte 187,byte 187,byte 187,byte 186,byte 186, + byte 186,byte 186,byte 169,byte 185,byte 185,byte 185,byte 168,byte 138, + byte 188,byte 188,byte 187,byte 187,byte 187,byte 170,byte 170,byte 186, + byte 186,byte 169,byte 169,byte 185,byte 185,byte 168,byte 151,byte 134, + byte 171,byte 171,byte 187,byte 187,byte 170,byte 170,byte 170,byte 186, + byte 186,byte 169,byte 152,byte 152,byte 185,byte 168,byte 151,byte 134, + byte 171,byte 171,byte 183,byte 183,byte 170,byte 170,byte 170,byte 153, + byte 182,byte 169,byte 152,byte 135,byte 135,byte 168,byte 151,byte 134, + byte 184,byte 184,byte 183,byte 183,byte 183,byte 183,byte 153,byte 182, + byte 182,byte 182,byte 182,byte 181,byte 181,byte 181,byte 164,byte 134, + byte 184,byte 184,byte 183,byte 183,byte 183,byte 183,byte 182,byte 182, + byte 182,byte 182,byte 165,byte 181,byte 181,byte 181,byte 164,byte 130, + byte 167,byte 167,byte 183,byte 183,byte 166,byte 166,byte 166,byte 182, + byte 182,byte 165,byte 165,byte 181,byte 181,byte 164,byte 147,byte 130, + byte 150,byte 150,byte 166,byte 166,byte 166,byte 149,byte 149,byte 182, + byte 165,byte 165,byte 148,byte 148,byte 164,byte 164,byte 147,byte 130, + byte 150,byte 150,byte 179,byte 179,byte 179,byte 149,byte 132,byte 178, + byte 178,byte 178,byte 148,byte 131,byte 177,byte 177,byte 147,byte 130, + byte 180,byte 180,byte 179,byte 179,byte 179,byte 179,byte 132,byte 178, + byte 178,byte 178,byte 161,byte 177,byte 177,byte 177,byte 160,byte 142, + byte 163,byte 163,byte 179,byte 179,byte 162,byte 162,byte 162,byte 178, + byte 178,byte 161,byte 161,byte 177,byte 177,byte 160,byte 160,byte 142, + byte 146,byte 146,byte 162,byte 162,byte 145,byte 145,byte 145,byte 161, + byte 161,byte 144,byte 144,byte 144,byte 160,byte 160,byte 159,byte 142, + byte 129,byte 129,byte 129,byte 129,byte 128,byte 128,byte 128,byte 128, + byte 143,byte 143,byte 143,byte 143,byte 142,byte 142,byte 142,byte 142, + byte 175,byte 175,byte 191,byte 191,byte 174,byte 174,byte 157,byte 157, + byte 190,byte 173,byte 156,byte 139,byte 139,byte 172,byte 155,byte 138, + byte 175,byte 175,byte 191,byte 191,byte 174,byte 174,byte 157,byte 157, + byte 190,byte 173,byte 156,byte 139,byte 139,byte 172,byte 155,byte 138, + byte 188,byte 188,byte 204,byte 187,byte 187,byte 187,byte 157,byte 186, + byte 186,byte 186,byte 156,byte 185,byte 185,byte 185,byte 168,byte 138, + byte 188,byte 188,byte 187,byte 187,byte 187,byte 170,byte 170,byte 186, + byte 186,byte 169,byte 169,byte 185,byte 185,byte 168,byte 168,byte 138, + byte 171,byte 171,byte 187,byte 187,byte 170,byte 170,byte 170,byte 186, + byte 186,byte 169,byte 152,byte 152,byte 185,byte 168,byte 151,byte 134, + byte 171,byte 171,byte 187,byte 170,byte 170,byte 170,byte 170,byte 153, + byte 169,byte 169,byte 152,byte 135,byte 135,byte 168,byte 151,byte 134, + byte 154,byte 154,byte 154,byte 170,byte 170,byte 170,byte 153,byte 153, + byte 169,byte 152,byte 152,byte 135,byte 135,byte 135,byte 151,byte 134, + byte 154,byte 154,byte 183,byte 183,byte 183,byte 153,byte 153,byte 153, + byte 182,byte 182,byte 135,byte 135,byte 181,byte 181,byte 164,byte 134, + byte 184,byte 184,byte 183,byte 183,byte 183,byte 166,byte 166,byte 182, + byte 182,byte 165,byte 165,byte 181,byte 181,byte 164,byte 164,byte 130, + byte 167,byte 167,byte 183,byte 166,byte 166,byte 166,byte 149,byte 182, + byte 165,byte 165,byte 165,byte 148,byte 181,byte 164,byte 147,byte 130, + byte 150,byte 150,byte 150,byte 166,byte 149,byte 149,byte 149,byte 132, + byte 165,byte 165,byte 148,byte 148,byte 131,byte 147,byte 147,byte 130, + byte 133,byte 133,byte 179,byte 179,byte 149,byte 132,byte 132,byte 132, + byte 178,byte 148,byte 148,byte 131,byte 131,byte 131,byte 130,byte 130, + byte 133,byte 133,byte 179,byte 179,byte 179,byte 132,byte 132,byte 178, + byte 178,byte 178,byte 131,byte 131,byte 131,byte 177,byte 160,byte 142, + byte 163,byte 163,byte 179,byte 162,byte 162,byte 162,byte 132,byte 178, + byte 161,byte 161,byte 144,byte 131,byte 177,byte 160,byte 160,byte 142, + byte 146,byte 146,byte 162,byte 162,byte 145,byte 145,byte 145,byte 161, + byte 161,byte 144,byte 144,byte 143,byte 160,byte 160,byte 159,byte 142, + byte 129,byte 129,byte 129,byte 129,byte 128,byte 128,byte 128,byte 128, + byte 143,byte 143,byte 143,byte 143,byte 142,byte 142,byte 142,byte 142, + byte 158,byte 158,byte 158,byte 112,byte 174,byte 157,byte 157,byte 140, + byte 140,byte 156,byte 156,byte 139,byte 139,byte 139,byte 155,byte 138, + byte 158,byte 158,byte 158,byte 112,byte 174,byte 157,byte 157,byte 140, + byte 140,byte 156,byte 156,byte 139,byte 139,byte 139,byte 155,byte 138, + byte 158,byte 158,byte 124,byte 124,byte 124,byte 157,byte 157,byte 140, + byte 123,byte 123,byte 156,byte 139,byte 139,byte 122,byte 155,byte 138, + byte 125,byte 125,byte 124,byte 124,byte 124,byte 170,byte 170,byte 123, + byte 123,byte 169,byte 152,byte 152,byte 122,byte 168,byte 151,byte 138, + byte 171,byte 171,byte 124,byte 124,byte 170,byte 170,byte 170,byte 153, + byte 123,byte 169,byte 152,byte 135,byte 135,byte 168,byte 151,byte 134, + byte 154,byte 154,byte 154,byte 170,byte 170,byte 170,byte 153,byte 153, + byte 169,byte 152,byte 152,byte 135,byte 135,byte 135,byte 151,byte 134, + byte 154,byte 154,byte 154,byte 170,byte 170,byte 153,byte 153,byte 153, + byte 136,byte 152,byte 135,byte 135,byte 135,byte 135,byte 134,byte 134, + byte 137,byte 137,byte 137,byte 120,byte 153,byte 153,byte 153,byte 136, + byte 136,byte 136,byte 135,byte 135,byte 135,byte 118,byte 164,byte 134, + byte 137,byte 137,byte 120,byte 120,byte 120,byte 166,byte 136,byte 136, + byte 136,byte 165,byte 165,byte 118,byte 118,byte 164,byte 147,byte 130, + byte 150,byte 150,byte 120,byte 166,byte 166,byte 149,byte 149,byte 136, + byte 165,byte 165,byte 148,byte 148,byte 118,byte 164,byte 147,byte 130, + byte 150,byte 150,byte 150,byte 149,byte 149,byte 149,byte 132,byte 132, + byte 165,byte 148,byte 148,byte 131,byte 131,byte 147,byte 147,byte 130, + byte 133,byte 133,byte 133,byte 149,byte 132,byte 132,byte 132,byte 132, + byte 115,byte 148,byte 131,byte 131,byte 131,byte 131,byte 130,byte 130, + byte 133,byte 133,byte 133,byte 116,byte 132,byte 132,byte 132,byte 132, + byte 115,byte 115,byte 131,byte 131,byte 131,byte 131,byte 160,byte 142, + byte 133,byte 133,byte 116,byte 162,byte 162,byte 132,byte 132,byte 115, + byte 161,byte 161,byte 144,byte 131,byte 131,byte 160,byte 160,byte 142, + byte 146,byte 146,byte 146,byte 145,byte 145,byte 145,byte 128,byte 161, + byte 144,byte 144,byte 144,byte 143,byte 160,byte 160,byte 159,byte 142, + byte 129,byte 129,byte 129,byte 129,byte 128,byte 128,byte 128,byte 128, + byte 143,byte 143,byte 143,byte 143,byte 142,byte 142,byte 142,byte 142, + byte 141,byte 141,byte 112,byte 112,byte 112,byte 157,byte 140,byte 140, + byte 140,byte 127,byte 139,byte 126,byte 126,byte 126,byte 109,byte 138, + byte 141,byte 141,byte 112,byte 112,byte 112,byte 157,byte 140,byte 140, + byte 140,byte 127,byte 139,byte 126,byte 126,byte 126,byte 109,byte 138, + byte 125,byte 125,byte 124,byte 124,byte 124,byte 124,byte 140,byte 123, + byte 123,byte 123,byte 123,byte 122,byte 122,byte 122,byte 122,byte 138, + byte 125,byte 125,byte 124,byte 124,byte 124,byte 124,byte 123,byte 123, + byte 123,byte 123,byte 123,byte 122,byte 122,byte 122,byte 105,byte 138, + byte 125,byte 125,byte 124,byte 124,byte 124,byte 124,byte 153,byte 123, + byte 123,byte 123,byte 152,byte 122,byte 122,byte 122,byte 105,byte 134, + byte 154,byte 154,byte 124,byte 124,byte 124,byte 153,byte 153,byte 153, + byte 123,byte 123,byte 135,byte 135,byte 122,byte 122,byte 105,byte 134, + byte 137,byte 137,byte 137,byte 120,byte 153,byte 153,byte 153,byte 136, + byte 136,byte 136,byte 135,byte 135,byte 135,byte 118,byte 105,byte 134, + byte 137,byte 137,byte 120,byte 120,byte 120,byte 153,byte 136,byte 136, + byte 136,byte 119,byte 119,byte 118,byte 118,byte 118,byte 118,byte 134, + byte 137,byte 137,byte 120,byte 120,byte 120,byte 120,byte 136,byte 136, + byte 119,byte 119,byte 119,byte 118,byte 118,byte 118,byte 101,byte 130, + byte 121,byte 121,byte 120,byte 120,byte 120,byte 120,byte 136,byte 119, + byte 119,byte 119,byte 102,byte 118,byte 118,byte 118,byte 101,byte 130, + byte 133,byte 133,byte 120,byte 120,byte 149,byte 132,byte 132,byte 119, + byte 119,byte 102,byte 148,byte 131,byte 131,byte 101,byte 101,byte 130, + byte 117,byte 117,byte 116,byte 116,byte 116,byte 132,byte 132,byte 115, + byte 115,byte 115,byte 131,byte 131,byte 114,byte 114,byte 114,byte 130, + byte 117,byte 117,byte 116,byte 116,byte 116,byte 116,byte 132,byte 115, + byte 115,byte 115,byte 131,byte 114,byte 114,byte 114,byte 114,byte 142, + byte 117,byte 117,byte 116,byte 116,byte 116,byte 116,byte 115,byte 115, + byte 115,byte 115,byte 98,byte 114,byte 114,byte 114,byte 97,byte 142, + byte 100,byte 100,byte 116,byte 99,byte 99,byte 99,byte 99,byte 115, + byte 98,byte 98,byte 98,byte 114,byte 114,byte 97,byte 97,byte 142, + byte 129,byte 129,byte 129,byte 129,byte 128,byte 128,byte 128,byte 128, + byte 143,byte 143,byte 143,byte 143,byte 142,byte 142,byte 142,byte 142, + byte 113,byte 113,byte 112,byte 112,byte 112,byte 112,byte 140,byte 140, + byte 127,byte 127,byte 110,byte 126,byte 126,byte 126,byte 109,byte 75, + byte 113,byte 113,byte 112,byte 112,byte 112,byte 112,byte 140,byte 140, + byte 127,byte 127,byte 110,byte 126,byte 126,byte 126,byte 109,byte 75, + byte 125,byte 125,byte 124,byte 124,byte 124,byte 124,byte 123,byte 123, + byte 123,byte 123,byte 123,byte 122,byte 122,byte 122,byte 105,byte 75, + byte 125,byte 125,byte 124,byte 124,byte 124,byte 124,byte 123,byte 123, + byte 123,byte 123,byte 106,byte 122,byte 122,byte 122,byte 105,byte 75, + byte 125,byte 125,byte 124,byte 124,byte 124,byte 124,byte 123,byte 123, + byte 123,byte 123,byte 106,byte 122,byte 122,byte 122,byte 105,byte 71, + byte 125,byte 125,byte 124,byte 124,byte 124,byte 107,byte 107,byte 123, + byte 123,byte 106,byte 106,byte 122,byte 122,byte 105,byte 105,byte 71, + byte 137,byte 137,byte 120,byte 120,byte 120,byte 107,byte 136,byte 136, + byte 136,byte 106,byte 106,byte 118,byte 118,byte 105,byte 88,byte 71, + byte 137,byte 137,byte 120,byte 120,byte 120,byte 120,byte 136,byte 136, + byte 119,byte 119,byte 119,byte 118,byte 118,byte 118,byte 101,byte 71, + byte 121,byte 121,byte 120,byte 120,byte 120,byte 120,byte 136,byte 119, + byte 119,byte 119,byte 102,byte 118,byte 118,byte 118,byte 101,byte 67, + byte 121,byte 121,byte 120,byte 120,byte 120,byte 103,byte 103,byte 119, + byte 119,byte 102,byte 102,byte 118,byte 118,byte 101,byte 101,byte 67, + byte 104,byte 104,byte 120,byte 103,byte 103,byte 103,byte 103,byte 119, + byte 102,byte 102,byte 102,byte 118,byte 118,byte 101,byte 84,byte 67, + byte 117,byte 117,byte 116,byte 116,byte 116,byte 116,byte 115,byte 115, + byte 115,byte 115,byte 115,byte 114,byte 114,byte 114,byte 114,byte 67, + byte 117,byte 117,byte 116,byte 116,byte 116,byte 116,byte 115,byte 115, + byte 115,byte 115,byte 115,byte 114,byte 114,byte 114,byte 97,byte 79, + byte 117,byte 117,byte 116,byte 116,byte 116,byte 99,byte 99,byte 115, + byte 115,byte 98,byte 98,byte 114,byte 114,byte 97,byte 97,byte 79, + byte 100,byte 100,byte 99,byte 99,byte 99,byte 99,byte 82,byte 98, + byte 98,byte 98,byte 81,byte 114,byte 97,byte 97,byte 97,byte 79, + byte 66,byte 66,byte 66,byte 66,byte 65,byte 65,byte 65,byte 65, + byte 64,byte 64,byte 64,byte 64,byte 79,byte 79,byte 79,byte 79, + byte 96,byte 96,byte 112,byte 112,byte 111,byte 111,byte 94,byte 127, + byte 127,byte 110,byte 110,byte 93,byte 126,byte 109,byte 92,byte 75, + byte 96,byte 96,byte 112,byte 112,byte 111,byte 111,byte 94,byte 127, + byte 127,byte 110,byte 110,byte 93,byte 126,byte 109,byte 92,byte 75, + byte 125,byte 125,byte 124,byte 124,byte 124,byte 124,byte 123,byte 123, + byte 123,byte 123,byte 106,byte 122,byte 122,byte 105,byte 105,byte 75, + byte 125,byte 125,byte 124,byte 124,byte 124,byte 107,byte 107,byte 123, + byte 123,byte 106,byte 106,byte 122,byte 122,byte 105,byte 105,byte 75, + byte 108,byte 108,byte 124,byte 124,byte 107,byte 107,byte 107,byte 123, + byte 123,byte 106,byte 106,byte 122,byte 122,byte 105,byte 88,byte 71, + byte 108,byte 108,byte 124,byte 107,byte 107,byte 107,byte 90,byte 123, + byte 106,byte 106,byte 106,byte 89,byte 122,byte 105,byte 88,byte 71, + byte 91,byte 91,byte 120,byte 107,byte 107,byte 90,byte 90,byte 136, + byte 106,byte 106,byte 89,byte 89,byte 118,byte 105,byte 88,byte 71, + byte 121,byte 121,byte 120,byte 120,byte 120,byte 120,byte 136,byte 119, + byte 119,byte 119,byte 102,byte 118,byte 118,byte 118,byte 101,byte 71, + byte 121,byte 121,byte 120,byte 120,byte 120,byte 103,byte 103,byte 119, + byte 119,byte 102,byte 102,byte 118,byte 118,byte 101,byte 101,byte 67, + byte 104,byte 104,byte 120,byte 103,byte 103,byte 103,byte 103,byte 119, + byte 102,byte 102,byte 102,byte 118,byte 118,byte 101,byte 84,byte 67, + byte 104,byte 104,byte 103,byte 103,byte 103,byte 103,byte 86,byte 102, + byte 102,byte 102,byte 85,byte 85,byte 101,byte 101,byte 84,byte 67, + byte 87,byte 87,byte 116,byte 116,byte 116,byte 86,byte 86,byte 115, + byte 115,byte 115,byte 85,byte 85,byte 114,byte 114,byte 84,byte 67, + byte 117,byte 117,byte 116,byte 116,byte 116,byte 116,byte 115,byte 115, + byte 115,byte 115,byte 98,byte 114,byte 114,byte 114,byte 97,byte 79, + byte 100,byte 100,byte 99,byte 99,byte 99,byte 99,byte 99,byte 115, + byte 98,byte 98,byte 98,byte 114,byte 114,byte 97,byte 97,byte 79, + byte 83,byte 83,byte 99,byte 99,byte 82,byte 82,byte 82,byte 98, + byte 98,byte 81,byte 81,byte 81,byte 97,byte 97,byte 80,byte 79, + byte 66,byte 66,byte 66,byte 66,byte 65,byte 65,byte 65,byte 65, + byte 64,byte 64,byte 64,byte 64,byte 79,byte 79,byte 79,byte 79, + byte 95,byte 95,byte 111,byte 111,byte 94,byte 94,byte 94,byte 77, + byte 110,byte 110,byte 93,byte 93,byte 76,byte 109,byte 92,byte 75, + byte 95,byte 95,byte 111,byte 111,byte 94,byte 94,byte 94,byte 77, + byte 110,byte 110,byte 93,byte 93,byte 76,byte 109,byte 92,byte 75, + byte 108,byte 108,byte 124,byte 111,byte 107,byte 94,byte 94,byte 123, + byte 123,byte 106,byte 93,byte 93,byte 122,byte 105,byte 92,byte 75, + byte 108,byte 108,byte 108,byte 107,byte 107,byte 107,byte 90,byte 123, + byte 106,byte 106,byte 106,byte 89,byte 122,byte 105,byte 88,byte 75, + byte 91,byte 91,byte 107,byte 107,byte 107,byte 90,byte 90,byte 123, + byte 106,byte 106,byte 89,byte 89,byte 105,byte 105,byte 88,byte 71, + byte 91,byte 91,byte 91,byte 107,byte 90,byte 90,byte 90,byte 73, + byte 106,byte 106,byte 89,byte 89,byte 72,byte 88,byte 88,byte 71, + byte 91,byte 91,byte 91,byte 90,byte 90,byte 90,byte 73,byte 73, + byte 106,byte 89,byte 89,byte 72,byte 72,byte 88,byte 88,byte 71, + byte 74,byte 74,byte 120,byte 120,byte 120,byte 73,byte 73,byte 119, + byte 119,byte 102,byte 89,byte 72,byte 72,byte 101,byte 101,byte 71, + byte 104,byte 104,byte 120,byte 103,byte 103,byte 103,byte 103,byte 119, + byte 102,byte 102,byte 102,byte 118,byte 118,byte 101,byte 84,byte 67, + byte 104,byte 104,byte 103,byte 103,byte 103,byte 103,byte 86,byte 102, + byte 102,byte 102,byte 85,byte 85,byte 101,byte 101,byte 84,byte 67, + byte 87,byte 87,byte 87,byte 103,byte 86,byte 86,byte 86,byte 86, + byte 102,byte 85,byte 85,byte 85,byte 85,byte 84,byte 84,byte 67, + byte 87,byte 87,byte 87,byte 86,byte 86,byte 86,byte 69,byte 69, + byte 115,byte 85,byte 85,byte 85,byte 68,byte 68,byte 67,byte 67, + byte 70,byte 70,byte 116,byte 116,byte 99,byte 69,byte 69,byte 69, + byte 115,byte 98,byte 85,byte 68,byte 68,byte 97,byte 97,byte 79, + byte 100,byte 100,byte 99,byte 99,byte 99,byte 82,byte 82,byte 98, + byte 98,byte 98,byte 81,byte 68,byte 97,byte 97,byte 97,byte 79, + byte 83,byte 83,byte 83,byte 82,byte 82,byte 82,byte 82,byte 98, + byte 81,byte 81,byte 81,byte 64,byte 97,byte 97,byte 80,byte 79, + byte 66,byte 66,byte 66,byte 66,byte 65,byte 65,byte 65,byte 65, + byte 64,byte 64,byte 64,byte 64,byte 79,byte 79,byte 79,byte 79, + byte 78,byte 78,byte 49,byte 49,byte 94,byte 77,byte 77,byte 48, + byte 48,byte 93,byte 93,byte 76,byte 76,byte 63,byte 92,byte 75, + byte 78,byte 78,byte 49,byte 49,byte 94,byte 77,byte 77,byte 48, + byte 48,byte 93,byte 93,byte 76,byte 76,byte 63,byte 92,byte 75, + byte 62,byte 62,byte 61,byte 61,byte 61,byte 61,byte 77,byte 60, + byte 60,byte 60,byte 93,byte 76,byte 59,byte 59,byte 59,byte 75, + byte 62,byte 62,byte 61,byte 61,byte 61,byte 61,byte 90,byte 60, + byte 60,byte 60,byte 89,byte 59,byte 59,byte 59,byte 88,byte 75, + byte 91,byte 91,byte 61,byte 61,byte 61,byte 90,byte 73,byte 60, + byte 60,byte 60,byte 89,byte 72,byte 59,byte 59,byte 88,byte 71, + byte 74,byte 74,byte 61,byte 61,byte 90,byte 73,byte 73,byte 73, + byte 60,byte 89,byte 89,byte 72,byte 72,byte 72,byte 71,byte 71, + byte 74,byte 74,byte 74,byte 90,byte 73,byte 73,byte 73,byte 73, + byte 56,byte 89,byte 72,byte 72,byte 72,byte 72,byte 71,byte 71, + byte 58,byte 58,byte 57,byte 57,byte 57,byte 73,byte 73,byte 56, + byte 56,byte 56,byte 72,byte 72,byte 55,byte 55,byte 55,byte 71, + byte 58,byte 58,byte 57,byte 57,byte 57,byte 57,byte 56,byte 56, + byte 56,byte 56,byte 56,byte 55,byte 55,byte 55,byte 55,byte 67, + byte 87,byte 87,byte 57,byte 57,byte 57,byte 86,byte 86,byte 56, + byte 56,byte 56,byte 85,byte 85,byte 55,byte 55,byte 84,byte 67, + byte 87,byte 87,byte 87,byte 86,byte 86,byte 86,byte 69,byte 69, + byte 56,byte 85,byte 85,byte 85,byte 68,byte 68,byte 67,byte 67, + byte 70,byte 70,byte 70,byte 53,byte 69,byte 69,byte 69,byte 69, + byte 52,byte 85,byte 85,byte 68,byte 68,byte 68,byte 67,byte 67, + byte 70,byte 70,byte 53,byte 53,byte 53,byte 69,byte 69,byte 52, + byte 52,byte 52,byte 68,byte 68,byte 68,byte 51,byte 51,byte 79, + byte 54,byte 54,byte 53,byte 53,byte 53,byte 69,byte 69,byte 52, + byte 52,byte 52,byte 68,byte 68,byte 51,byte 51,byte 80,byte 79, + byte 83,byte 83,byte 53,byte 82,byte 82,byte 65,byte 65,byte 52, + byte 52,byte 81,byte 64,byte 64,byte 51,byte 80,byte 80,byte 79, + byte 66,byte 66,byte 66,byte 66,byte 65,byte 65,byte 65,byte 65, + byte 64,byte 64,byte 64,byte 64,byte 79,byte 79,byte 79,byte 79, + byte 50,byte 50,byte 49,byte 49,byte 49,byte 77,byte 77,byte 48, + byte 48,byte 48,byte 76,byte 76,byte 63,byte 63,byte 46,byte 12, + byte 50,byte 50,byte 49,byte 49,byte 49,byte 77,byte 77,byte 48, + byte 48,byte 48,byte 76,byte 76,byte 63,byte 63,byte 46,byte 12, + byte 62,byte 62,byte 61,byte 61,byte 61,byte 61,byte 77,byte 60, + byte 60,byte 60,byte 60,byte 59,byte 59,byte 59,byte 59,byte 12, + byte 62,byte 62,byte 61,byte 61,byte 61,byte 61,byte 60,byte 60, + byte 60,byte 60,byte 60,byte 59,byte 59,byte 59,byte 42,byte 12, + byte 62,byte 62,byte 61,byte 61,byte 61,byte 61,byte 73,byte 60, + byte 60,byte 60,byte 43,byte 59,byte 59,byte 59,byte 42,byte 8, + byte 74,byte 74,byte 61,byte 61,byte 61,byte 73,byte 73,byte 60, + byte 60,byte 60,byte 72,byte 72,byte 72,byte 59,byte 42,byte 8, + byte 74,byte 74,byte 74,byte 57,byte 73,byte 73,byte 73,byte 73, + byte 56,byte 56,byte 72,byte 72,byte 72,byte 72,byte 42,byte 8, + byte 58,byte 58,byte 57,byte 57,byte 57,byte 57,byte 73,byte 56, + byte 56,byte 56,byte 72,byte 55,byte 55,byte 55,byte 55,byte 8, + byte 58,byte 58,byte 57,byte 57,byte 57,byte 57,byte 56,byte 56, + byte 56,byte 56,byte 56,byte 55,byte 55,byte 55,byte 38,byte 4, + byte 58,byte 58,byte 57,byte 57,byte 57,byte 57,byte 56,byte 56, + byte 56,byte 56,byte 39,byte 55,byte 55,byte 55,byte 38,byte 4, + byte 70,byte 70,byte 57,byte 57,byte 40,byte 69,byte 69,byte 69, + byte 56,byte 39,byte 85,byte 68,byte 68,byte 38,byte 38,byte 4, + byte 70,byte 70,byte 53,byte 53,byte 53,byte 69,byte 69,byte 52, + byte 52,byte 52,byte 68,byte 68,byte 68,byte 51,byte 51,byte 4, + byte 54,byte 54,byte 53,byte 53,byte 53,byte 69,byte 69,byte 52, + byte 52,byte 52,byte 68,byte 68,byte 51,byte 51,byte 51,byte 0, + byte 54,byte 54,byte 53,byte 53,byte 53,byte 53,byte 69,byte 52, + byte 52,byte 52,byte 35,byte 51,byte 51,byte 51,byte 34,byte 0, + byte 37,byte 37,byte 53,byte 36,byte 36,byte 36,byte 36,byte 52, + byte 35,byte 35,byte 35,byte 51,byte 51,byte 34,byte 34,byte 0, + byte 3,byte 3,byte 3,byte 3,byte 2,byte 2,byte 2,byte 2, + byte 1,byte 1,byte 1,byte 1,byte 0,byte 0,byte 0,byte 0, + byte 33,byte 33,byte 49,byte 49,byte 32,byte 32,byte 77,byte 48, + byte 48,byte 47,byte 47,byte 63,byte 63,byte 46,byte 46,byte 12, + byte 33,byte 33,byte 49,byte 49,byte 32,byte 32,byte 77,byte 48, + byte 48,byte 47,byte 47,byte 63,byte 63,byte 46,byte 46,byte 12, + byte 62,byte 62,byte 61,byte 61,byte 61,byte 61,byte 60,byte 60, + byte 60,byte 43,byte 43,byte 59,byte 59,byte 59,byte 42,byte 12, + byte 62,byte 62,byte 61,byte 61,byte 61,byte 44,byte 44,byte 60, + byte 60,byte 43,byte 43,byte 59,byte 59,byte 42,byte 42,byte 12, + byte 45,byte 45,byte 61,byte 61,byte 44,byte 44,byte 44,byte 60, + byte 60,byte 43,byte 43,byte 59,byte 59,byte 42,byte 42,byte 8, + byte 45,byte 45,byte 61,byte 44,byte 44,byte 44,byte 73,byte 60, + byte 43,byte 43,byte 26,byte 72,byte 59,byte 42,byte 42,byte 8, + byte 74,byte 74,byte 57,byte 44,byte 44,byte 73,byte 73,byte 56, + byte 43,byte 43,byte 26,byte 72,byte 72,byte 42,byte 42,byte 8, + byte 58,byte 58,byte 57,byte 57,byte 57,byte 57,byte 56,byte 56, + byte 56,byte 56,byte 39,byte 55,byte 55,byte 55,byte 38,byte 8, + byte 58,byte 58,byte 57,byte 57,byte 57,byte 40,byte 40,byte 56, + byte 56,byte 39,byte 39,byte 55,byte 55,byte 38,byte 38,byte 4, + byte 41,byte 41,byte 40,byte 40,byte 40,byte 40,byte 40,byte 56, + byte 39,byte 39,byte 39,byte 55,byte 55,byte 38,byte 38,byte 4, + byte 41,byte 41,byte 40,byte 40,byte 40,byte 23,byte 23,byte 39, + byte 39,byte 39,byte 22,byte 68,byte 38,byte 38,byte 38,byte 4, + byte 54,byte 54,byte 53,byte 53,byte 53,byte 69,byte 69,byte 52, + byte 52,byte 52,byte 68,byte 68,byte 51,byte 51,byte 21,byte 4, + byte 54,byte 54,byte 53,byte 53,byte 53,byte 53,byte 69,byte 52, + byte 52,byte 52,byte 35,byte 51,byte 51,byte 51,byte 34,byte 0, + byte 37,byte 37,byte 53,byte 36,byte 36,byte 36,byte 36,byte 52, + byte 35,byte 35,byte 35,byte 51,byte 51,byte 34,byte 34,byte 0, + byte 37,byte 37,byte 36,byte 36,byte 36,byte 36,byte 36,byte 35, + byte 35,byte 35,byte 35,byte 18,byte 34,byte 34,byte 34,byte 0, + byte 3,byte 3,byte 3,byte 3,byte 2,byte 2,byte 2,byte 2, + byte 1,byte 1,byte 1,byte 1,byte 0,byte 0,byte 0,byte 0, + byte 16,byte 16,byte 32,byte 32,byte 31,byte 31,byte 31,byte 47, + byte 47,byte 30,byte 30,byte 30,byte 46,byte 46,byte 29,byte 12, + byte 16,byte 16,byte 32,byte 32,byte 31,byte 31,byte 31,byte 47, + byte 47,byte 30,byte 30,byte 30,byte 46,byte 46,byte 29,byte 12, + byte 45,byte 45,byte 44,byte 44,byte 44,byte 44,byte 31,byte 60, + byte 43,byte 43,byte 30,byte 59,byte 59,byte 42,byte 42,byte 12, + byte 45,byte 45,byte 44,byte 44,byte 44,byte 44,byte 27,byte 43, + byte 43,byte 43,byte 26,byte 26,byte 42,byte 42,byte 42,byte 12, + byte 28,byte 28,byte 44,byte 44,byte 27,byte 27,byte 27,byte 43, + byte 43,byte 26,byte 26,byte 26,byte 42,byte 42,byte 25,byte 8, + byte 28,byte 28,byte 44,byte 44,byte 27,byte 27,byte 27,byte 43, + byte 43,byte 26,byte 26,byte 9,byte 42,byte 42,byte 25,byte 8, + byte 28,byte 28,byte 28,byte 27,byte 27,byte 27,byte 10,byte 43, + byte 26,byte 26,byte 26,byte 9,byte 42,byte 42,byte 25,byte 8, + byte 41,byte 41,byte 57,byte 40,byte 40,byte 40,byte 40,byte 56, + byte 39,byte 39,byte 39,byte 55,byte 55,byte 38,byte 38,byte 8, + byte 41,byte 41,byte 40,byte 40,byte 40,byte 40,byte 23,byte 39, + byte 39,byte 39,byte 22,byte 55,byte 38,byte 38,byte 38,byte 4, + byte 24,byte 24,byte 40,byte 40,byte 23,byte 23,byte 23,byte 39, + byte 39,byte 22,byte 22,byte 22,byte 38,byte 38,byte 21,byte 4, + byte 24,byte 24,byte 24,byte 23,byte 23,byte 23,byte 23,byte 39, + byte 22,byte 22,byte 22,byte 5,byte 38,byte 38,byte 21,byte 4, + byte 24,byte 24,byte 53,byte 23,byte 23,byte 6,byte 6,byte 52, + byte 52,byte 22,byte 5,byte 5,byte 51,byte 21,byte 21,byte 4, + byte 37,byte 37,byte 53,byte 36,byte 36,byte 36,byte 36,byte 52, + byte 35,byte 35,byte 35,byte 51,byte 51,byte 34,byte 34,byte 0, + byte 37,byte 37,byte 36,byte 36,byte 36,byte 36,byte 36,byte 35, + byte 35,byte 35,byte 35,byte 18,byte 34,byte 34,byte 34,byte 0, + byte 20,byte 20,byte 36,byte 36,byte 19,byte 19,byte 19,byte 35, + byte 35,byte 18,byte 18,byte 18,byte 34,byte 34,byte 17,byte 0, + byte 3,byte 3,byte 3,byte 3,byte 2,byte 2,byte 2,byte 2, + byte 1,byte 1,byte 1,byte 1,byte 0,byte 0,byte 0,byte 0, + byte 15,byte 15,byte 15,byte 15,byte 14,byte 14,byte 14,byte 14, + byte 13,byte 13,byte 13,byte 13,byte 12,byte 12,byte 12,byte 12, + byte 15,byte 15,byte 15,byte 15,byte 14,byte 14,byte 14,byte 14, + byte 13,byte 13,byte 13,byte 13,byte 12,byte 12,byte 12,byte 12, + byte 15,byte 15,byte 15,byte 15,byte 14,byte 14,byte 14,byte 14, + byte 13,byte 13,byte 13,byte 13,byte 12,byte 12,byte 12,byte 12, + byte 15,byte 15,byte 15,byte 15,byte 14,byte 14,byte 14,byte 14, + byte 13,byte 13,byte 13,byte 13,byte 12,byte 12,byte 12,byte 12, + byte 11,byte 11,byte 11,byte 11,byte 10,byte 10,byte 10,byte 10, + byte 9,byte 9,byte 9,byte 9,byte 8,byte 8,byte 8,byte 8, + byte 11,byte 11,byte 11,byte 11,byte 10,byte 10,byte 10,byte 10, + byte 9,byte 9,byte 9,byte 9,byte 8,byte 8,byte 8,byte 8, + byte 11,byte 11,byte 11,byte 11,byte 10,byte 10,byte 10,byte 10, + byte 9,byte 9,byte 9,byte 9,byte 8,byte 8,byte 8,byte 8, + byte 11,byte 11,byte 11,byte 11,byte 10,byte 10,byte 10,byte 10, + byte 9,byte 9,byte 9,byte 9,byte 8,byte 8,byte 8,byte 8, + byte 7,byte 7,byte 7,byte 7,byte 6,byte 6,byte 6,byte 6, + byte 5,byte 5,byte 5,byte 5,byte 4,byte 4,byte 4,byte 4, + byte 7,byte 7,byte 7,byte 7,byte 6,byte 6,byte 6,byte 6, + byte 5,byte 5,byte 5,byte 5,byte 4,byte 4,byte 4,byte 4, + byte 7,byte 7,byte 7,byte 7,byte 6,byte 6,byte 6,byte 6, + byte 5,byte 5,byte 5,byte 5,byte 4,byte 4,byte 4,byte 4, + byte 7,byte 7,byte 7,byte 7,byte 6,byte 6,byte 6,byte 6, + byte 5,byte 5,byte 5,byte 5,byte 4,byte 4,byte 4,byte 4, + byte 3,byte 3,byte 3,byte 3,byte 2,byte 2,byte 2,byte 2, + byte 1,byte 1,byte 1,byte 1,byte 0,byte 0,byte 0,byte 0, + byte 3,byte 3,byte 3,byte 3,byte 2,byte 2,byte 2,byte 2, + byte 1,byte 1,byte 1,byte 1,byte 0,byte 0,byte 0,byte 0, + byte 3,byte 3,byte 3,byte 3,byte 2,byte 2,byte 2,byte 2, + byte 1,byte 1,byte 1,byte 1,byte 0,byte 0,byte 0,byte 0, + byte 3,byte 3,byte 3,byte 3,byte 2,byte 2,byte 2,byte 2, + byte 1,byte 1,byte 1,byte 1,byte 0,byte 0,byte 0,byte 0, +}; + +rgbvmap := array[3*256] of { + byte 255,byte 255,byte 255, byte 255,byte 255,byte 170, + byte 255,byte 255,byte 85, byte 255,byte 255,byte 0, + byte 255,byte 170,byte 255, byte 255,byte 170,byte 170, + byte 255,byte 170,byte 85, byte 255,byte 170,byte 0, + byte 255,byte 85,byte 255, byte 255,byte 85,byte 170, + byte 255,byte 85,byte 85, byte 255,byte 85,byte 0, + byte 255,byte 0,byte 255, byte 255,byte 0,byte 170, + byte 255,byte 0,byte 85, byte 255,byte 0,byte 0, + byte 238,byte 0,byte 0, byte 238,byte 238,byte 238, + byte 238,byte 238,byte 158, byte 238,byte 238,byte 79, + byte 238,byte 238,byte 0, byte 238,byte 158,byte 238, + byte 238,byte 158,byte 158, byte 238,byte 158,byte 79, + byte 238,byte 158,byte 0, byte 238,byte 79,byte 238, + byte 238,byte 79,byte 158, byte 238,byte 79,byte 79, + byte 238,byte 79,byte 0, byte 238,byte 0,byte 238, + byte 238,byte 0,byte 158, byte 238,byte 0,byte 79, + byte 221,byte 0,byte 73, byte 221,byte 0,byte 0, + byte 221,byte 221,byte 221, byte 221,byte 221,byte 147, + byte 221,byte 221,byte 73, byte 221,byte 221,byte 0, + byte 221,byte 147,byte 221, byte 221,byte 147,byte 147, + byte 221,byte 147,byte 73, byte 221,byte 147,byte 0, + byte 221,byte 73,byte 221, byte 221,byte 73,byte 147, + byte 221,byte 73,byte 73, byte 221,byte 73,byte 0, + byte 221,byte 0,byte 221, byte 221,byte 0,byte 147, + byte 204,byte 0,byte 136, byte 204,byte 0,byte 68, + byte 204,byte 0,byte 0, byte 204,byte 204,byte 204, + byte 204,byte 204,byte 136, byte 204,byte 204,byte 68, + byte 204,byte 204,byte 0, byte 204,byte 136,byte 204, + byte 204,byte 136,byte 136, byte 204,byte 136,byte 68, + byte 204,byte 136,byte 0, byte 204,byte 68,byte 204, + byte 204,byte 68,byte 136, byte 204,byte 68,byte 68, + byte 204,byte 68,byte 0, byte 204,byte 0,byte 204, + byte 170,byte 255,byte 170, byte 170,byte 255,byte 85, + byte 170,byte 255,byte 0, byte 170,byte 170,byte 255, + byte 187,byte 187,byte 187, byte 187,byte 187,byte 93, + byte 187,byte 187,byte 0, byte 170,byte 85,byte 255, + byte 187,byte 93,byte 187, byte 187,byte 93,byte 93, + byte 187,byte 93,byte 0, byte 170,byte 0,byte 255, + byte 187,byte 0,byte 187, byte 187,byte 0,byte 93, + byte 187,byte 0,byte 0, byte 170,byte 255,byte 255, + byte 158,byte 238,byte 238, byte 158,byte 238,byte 158, + byte 158,byte 238,byte 79, byte 158,byte 238,byte 0, + byte 158,byte 158,byte 238, byte 170,byte 170,byte 170, + byte 170,byte 170,byte 85, byte 170,byte 170,byte 0, + byte 158,byte 79,byte 238, byte 170,byte 85,byte 170, + byte 170,byte 85,byte 85, byte 170,byte 85,byte 0, + byte 158,byte 0,byte 238, byte 170,byte 0,byte 170, + byte 170,byte 0,byte 85, byte 170,byte 0,byte 0, + byte 153,byte 0,byte 0, byte 147,byte 221,byte 221, + byte 147,byte 221,byte 147, byte 147,byte 221,byte 73, + byte 147,byte 221,byte 0, byte 147,byte 147,byte 221, + byte 153,byte 153,byte 153, byte 153,byte 153,byte 76, + byte 153,byte 153,byte 0, byte 147,byte 73,byte 221, + byte 153,byte 76,byte 153, byte 153,byte 76,byte 76, + byte 153,byte 76,byte 0, byte 147,byte 0,byte 221, + byte 153,byte 0,byte 153, byte 153,byte 0,byte 76, + byte 136,byte 0,byte 68, byte 136,byte 0,byte 0, + byte 136,byte 204,byte 204, byte 136,byte 204,byte 136, + byte 136,byte 204,byte 68, byte 136,byte 204,byte 0, + byte 136,byte 136,byte 204, byte 136,byte 136,byte 136, + byte 136,byte 136,byte 68, byte 136,byte 136,byte 0, + byte 136,byte 68,byte 204, byte 136,byte 68,byte 136, + byte 136,byte 68,byte 68, byte 136,byte 68,byte 0, + byte 136,byte 0,byte 204, byte 136,byte 0,byte 136, + byte 85,byte 255,byte 85, byte 85,byte 255,byte 0, + byte 85,byte 170,byte 255, byte 93,byte 187,byte 187, + byte 93,byte 187,byte 93, byte 93,byte 187,byte 0, + byte 85,byte 85,byte 255, byte 93,byte 93,byte 187, + byte 119,byte 119,byte 119, byte 119,byte 119,byte 0, + byte 85,byte 0,byte 255, byte 93,byte 0,byte 187, + byte 119,byte 0,byte 119, byte 119,byte 0,byte 0, + byte 85,byte 255,byte 255, byte 85,byte 255,byte 170, + byte 79,byte 238,byte 158, byte 79,byte 238,byte 79, + byte 79,byte 238,byte 0, byte 79,byte 158,byte 238, + byte 85,byte 170,byte 170, byte 85,byte 170,byte 85, + byte 85,byte 170,byte 0, byte 79,byte 79,byte 238, + byte 85,byte 85,byte 170, byte 102,byte 102,byte 102, + byte 102,byte 102,byte 0, byte 79,byte 0,byte 238, + byte 85,byte 0,byte 170, byte 102,byte 0,byte 102, + byte 102,byte 0,byte 0, byte 79,byte 238,byte 238, + byte 73,byte 221,byte 221, byte 73,byte 221,byte 147, + byte 73,byte 221,byte 73, byte 73,byte 221,byte 0, + byte 73,byte 147,byte 221, byte 76,byte 153,byte 153, + byte 76,byte 153,byte 76, byte 76,byte 153,byte 0, + byte 73,byte 73,byte 221, byte 76,byte 76,byte 153, + byte 85,byte 85,byte 85, byte 85,byte 85,byte 0, + byte 73,byte 0,byte 221, byte 76,byte 0,byte 153, + byte 85,byte 0,byte 85, byte 85,byte 0,byte 0, + byte 68,byte 0,byte 0, byte 68,byte 204,byte 204, + byte 68,byte 204,byte 136, byte 68,byte 204,byte 68, + byte 68,byte 204,byte 0, byte 68,byte 136,byte 204, + byte 68,byte 136,byte 136, byte 68,byte 136,byte 68, + byte 68,byte 136,byte 0, byte 68,byte 68,byte 204, + byte 68,byte 68,byte 136, byte 68,byte 68,byte 68, + byte 68,byte 68,byte 0, byte 68,byte 0,byte 204, + byte 68,byte 0,byte 136, byte 68,byte 0,byte 68, + byte 0,byte 255,byte 0, byte 0,byte 170,byte 255, + byte 0,byte 187,byte 187, byte 0,byte 187,byte 93, + byte 0,byte 187,byte 0, byte 0,byte 85,byte 255, + byte 0,byte 93,byte 187, byte 0,byte 119,byte 119, + byte 0,byte 119,byte 0, byte 0,byte 0,byte 255, + byte 0,byte 0,byte 187, byte 0,byte 0,byte 119, + byte 51,byte 51,byte 51, byte 0,byte 255,byte 255, + byte 0,byte 255,byte 170, byte 0,byte 255,byte 85, + byte 0,byte 238,byte 79, byte 0,byte 238,byte 0, + byte 0,byte 158,byte 238, byte 0,byte 170,byte 170, + byte 0,byte 170,byte 85, byte 0,byte 170,byte 0, + byte 0,byte 79,byte 238, byte 0,byte 85,byte 170, + byte 0,byte 102,byte 102, byte 0,byte 102,byte 0, + byte 0,byte 0,byte 238, byte 0,byte 0,byte 170, + byte 0,byte 0,byte 102, byte 34,byte 34,byte 34, + byte 0,byte 238,byte 238, byte 0,byte 238,byte 158, + byte 0,byte 221,byte 147, byte 0,byte 221,byte 73, + byte 0,byte 221,byte 0, byte 0,byte 147,byte 221, + byte 0,byte 153,byte 153, byte 0,byte 153,byte 76, + byte 0,byte 153,byte 0, byte 0,byte 73,byte 221, + byte 0,byte 76,byte 153, byte 0,byte 85,byte 85, + byte 0,byte 85,byte 0, byte 0,byte 0,byte 221, + byte 0,byte 0,byte 153, byte 0,byte 0,byte 85, + byte 17,byte 17,byte 17, byte 0,byte 221,byte 221, + byte 0,byte 204,byte 204, byte 0,byte 204,byte 136, + byte 0,byte 204,byte 68, byte 0,byte 204,byte 0, + byte 0,byte 136,byte 204, byte 0,byte 136,byte 136, + byte 0,byte 136,byte 68, byte 0,byte 136,byte 0, + byte 0,byte 68,byte 204, byte 0,byte 68,byte 136, + byte 0,byte 68,byte 68, byte 0,byte 68,byte 0, + byte 0,byte 0,byte 204, byte 0,byte 0,byte 136, + byte 0,byte 0,byte 68, byte 0,byte 0,byte 0, +}; + +clamp: array of int; + +# Remap pixels according to standard Inferno colormap, +# then convert to Inferno compressed image encoding. +# If second component of return is non-nil, it is a compressed mask. +image2enc(i: ref Rawimage, errdiff: int): (array of byte, array of byte, string) +{ + if(sys == nil) + sys = load Sys Sys->PATH; + + j: int; + dx := i.r.max.x-i.r.min.x; + dy := i.r.max.y-i.r.min.y; + cmap := i.cmap; + + if(clamp == nil){ + clamp = array[64+256+64] of int; + for(j=0; j<64; j++) + clamp[j] = 0; + for(j=0; j<256; j++) + clamp[64+j] = (j>>4); + for(j=0; j<64; j++) + clamp[64+256+j] = (255>>4); + } + + pic := i.chans[0]; + npic := len pic; + + maski : ref Rawimage = nil; + if(i.transp) { + mpic := array[npic] of byte; + maski = ref Rawimage ( i.r, nil, 0, byte 0, 1, array[1] of {mpic}, 0, 0 ); + for(j = 0; j < npic; j++) + if(pic[j] == i.trindex) + mpic[j] = byte 0; + else + mpic[j] = byte 1; + } + + + case i.chandesc{ + RImagefile->CRGB1 => + if(cmap == nil) + return (nil, nil, "image has no color map"); + if(i.nchans != 1) + return (nil, nil, sys->sprint("can't handle nchans %d", i.nchans)); + for(j=1; j<=8; j++) + if(len cmap == 3*(1<<j)) + break; + if(j > 8) + return (nil, nil, sys->sprint("can't understand colormap size 3*%d", len cmap/3)); + if(len cmap != 3*256){ + # to avoid a range check in inner loop below, make a full-size cmap + cmap1 := array[3*256] of byte; + cmap1[0:] = cmap[0:]; + cmap = cmap1; + errdiff = 0; # why not? + } + if(errdiff == 0){ + map := array[256] of byte; + k := 0; + for(j=0; j<256; j++){ + r := int cmap[k]>>4; + g := int cmap[k+1]>>4; + b := int cmap[k+2]>>4; + k += 3; + map[j] = byte closest[b+16*(g+16*r)]; + } + for(j=0; j<npic; j++) + pic[j] = map[int pic[j]]; + }else{ + # modified floyd steinberg, coefficients (1 0) 3/16, (0, 1) 3/16, (1, 1) 7/16 + ered := array[dx+1] of int; + egrn := array[dx+1] of int; + eblu := array[dx+1] of int; + for(j=0; j<=dx; j++) + ered[j] = 0; + egrn[0:] = ered[0:]; + eblu[0:] = ered[0:]; + p := 0; + for(y:=0; y<dy; y++){ + er := 0; + eg := 0; + eb := 0; + for(x:=0; x<dx; x++){ + in := 3*int pic[p]; + r := int cmap[in+0]+ered[x]; + g := int cmap[in+1]+egrn[x]; + b := int cmap[in+2]+eblu[x]; + r1 := clamp[r+64]; + g1 := clamp[g+64]; + b1 := clamp[b+64]; + col := int closest[b1+16*(g1+16*r1)]; + pic[p++] = byte col; + + col *= 3; + r -= int rgbvmap[col+0]; + t := (3*r)>>4; + ered[x] = t+er; + ered[x+1] += t; + er = r-3*t; + + g -= int rgbvmap[col+1]; + t = (3*g)>>4; + egrn[x] = t+eg; + egrn[x+1] += t; + eg = g-3*t; + + b -= int rgbvmap[col+2]; + t = (3*b)>>4; + eblu[x] = t+eb; + eblu[x+1] += t; + eb = b-3*t; + } + } + } + RImagefile->CRGB => + if(i.nchans != 3) + return (nil, nil, sys->sprint("RGB image has %d channels", i.nchans)); + rpic := i.chans[0]; + gpic := i.chans[1]; + bpic := i.chans[2]; + if(errdiff == 0){ + for(j=0; j<len rpic; j++){ + r := int rpic[j]>>4; + g := int gpic[j]>>4; + b := int bpic[j]>>4; + pic[j] = byte closest[b+16*(g+16*r)]; + } + }else{ + # modified floyd steinberg, coefficients (1 0) 3/16, (0, 1) 3/16, (1, 1) 7/16 + ered := array[dx+1] of int; + egrn := array[dx+1] of int; + eblu := array[dx+1] of int; + for(j=0; j<=dx; j++) + ered[j] = 0; + egrn[0:] = ered[0:]; + eblu[0:] = ered[0:]; + p := 0; + for(y:=0; y<dy; y++){ + er := 0; + eg := 0; + eb := 0; + for(x:=0; x<dx; x++){ + r := int rpic[p]+ered[x]; + g := int gpic[p]+egrn[x]; + b := int bpic[p]+eblu[x]; + r1 := clamp[r+64]; + g1 := clamp[g+64]; + b1 := clamp[b+64]; + col := int closest[b1+16*(g1+16*r1)]; + pic[p++] = byte col; + + col *= 3; + r -= int rgbvmap[col+0]; + t := (3*r)>>4; + ered[x] = t+er; + ered[x+1] += t; + er = r-3*t; + + g -= int rgbvmap[col+1]; + t = (3*g)>>4; + egrn[x] = t+eg; + egrn[x+1] += t; + eg = g-3*t; + + b -= int rgbvmap[col+2]; + t = (3*b)>>4; + eblu[x] = t+eb; + eblu[x+1] += t; + eb = b-3*t; + } + } + } + RImagefile->CY => + if(i.nchans != 1) + return (nil, nil, sys->sprint("Y image has %d chans", i.nchans)); + rpic := i.chans[0]; + if(errdiff == 0){ + for(j=0; j<npic; j++){ + r := int rpic[j]>>4; + pic[j] = byte closest[r+16*(r+16*r)]; + } + }else{ + # modified floyd steinberg, coefficients (1 0) 3/16, (0, 1) 3/16, (1, 1) 7/16 + ered := array[dx+1] of int; + for(j=0; j<=dx; j++) + ered[j] = 0; + p := 0; + for(y:=0; y<dy; y++){ + er := 0; + for(x:=0; x<dx; x++){ + r := int rpic[p]+ered[x]; + r1 := clamp[r+64]; + col := int closest[r1+16*(r1+16*r1)]; + pic[p++] = byte col; + + col *= 3; + r -= int rgbvmap[col+0]; + t := (3*r)>>4; + ered[x] = t+er; + ered[x+1] += t; + er = r-3*t; + } + } + } + } + + (encim, estr) := i2e(i); + encmask : array of byte = nil; + if(i.transp && estr == "") + (encmask, estr) = i2e(maski); + return (encim, encmask, estr); +} + +# Some defs from /usr/inferno/include/image.h +NMATCH: con 3; # shortest match possible +NRUN: con (NMATCH+31); # longest match possible +NMEM: con 1024; # window size +NDUMP: con 128; # maximum length of dump +NCBLOCK: con 6000; # size of compressed blocks +MAXCB: con 200; # maximum number of blocks + +HSHIFT: con 3; +NHASH: con (1<<(HSHIFT*NMATCH)); +HMASK: con (NHASH-1); + +Hlist: adt { + index: int; + next: cyclic ref Hlist; + prev: cyclic ref Hlist; +}; + +Outbuf: adt { + hdr: array of byte; + buf: array of byte; + buflen: int; +}; + +i2e(im: ref Rawimage): (array of byte, string) +{ + i : int; + pic := im.chans[0]; + bpl := im.r.max.x - im.r.min.x; + nl := im.r.max.y - im.r.min.y; + edata := bpl * nl; + linei := 0; + hash := array[NHASH] of ref Hlist; + chain := array[NMEM] of ref Hlist; + dumpbuf := array[NDUMP] of byte; + blocks := array[MAXCB] of ref Outbuf; + hdr := array of byte sys->sprint("compressed\n%11d %11d %11d %11d %11d ", + 3, im.r.min.x, im.r.min.y, im.r.max.x, im.r.max.y); + blocks[0] = ref Outbuf(hdr, nil, 0); + outbnum := 1; + y := im.r.min.y; + for(i = 0; i < NHASH; i++) + hash[i] = ref Hlist(0, nil, nil); + for(i = 0; i < NMEM; i++) + chain[i] = ref Hlist(0, nil, nil); + + # if a line is too narrow, we don't compress at all + nmatch := NMATCH; + if(nmatch >= bpl) + nmatch = 0; + + while(linei < edata) { + curblock := ref Outbuf(nil, array[NCBLOCK] of byte, 0); + blocks[outbnum] = curblock; + outbuf := curblock.buf; + cp := 0; + h := 0; + for(i = 0; i < NHASH; i++) { + hi := hash[i]; + hi.next = nil; + hi.prev = nil; + } + for(i = 0; i < NMEM; i++) { + ci := chain[i]; + ci.next = nil; + ci.prev = nil; + } + + outp := 0; + for(i = 0; i <= nmatch; i++) + h = (((h<<HSHIFT)^(int pic[linei+i]))&HMASK); + loutp := 0; + + blockloop: + while(linei < edata) { + ndump := 0; + eline := linei + bpl; + for(p := linei; p < eline;) { + es : int; + if(eline - p < NRUN) + es = eline; + else + es = p + NRUN; + q := 0; + runlen := 2; + hp := hash[h]; + matchloop: + for(hp=hp.next; hp != nil; hp = hp.next) { + s := p + runlen; + if(s >= es) + continue matchloop; + t := hp.index + runlen; + for(; s >= p; s--){ + if(pic[s] != pic[t]) + continue matchloop; + t--; + } + t += runlen+2; + s += runlen+2; + for(; s < es; s++) { + if(pic[s] != pic[t]) + break; + t++; + } + n := s - p; + if(n > runlen) { + runlen = n; + q = hp.index; + if(n == NRUN) + break; + } + } + if(runlen < NMATCH) { + if(ndump == NDUMP) { + if(NCBLOCK-outp < NDUMP+1) + break blockloop; + outbuf[outp++] = byte (NDUMP-1+128); + outbuf[outp:] = dumpbuf; + outp += NDUMP; + ndump = 0; + } + dumpbuf[ndump++] = pic[p]; + runlen = 1; + } + else { + if(ndump != 0) { + if(NCBLOCK-outp < ndump+1) + break blockloop; + outbuf[outp++] = byte (ndump-1+128); + outbuf[outp:] = dumpbuf[0:ndump]; + outp += ndump; + ndump = 0; + } + offs := p - q - 1; + if(NCBLOCK-outp < 2) + break blockloop; + outbuf[outp++] = byte (((runlen-NMATCH)<<2)+(offs>>8)); + outbuf[outp++] = byte offs; + } + for(q = p+runlen; p < q; p++) { + c := chain[cp]; + if(c.prev != nil) + c.prev.next = nil; + c.next = hash[h].next; + c.prev = hash[h]; + if(c.next != nil) + c.next.prev = c; + c.prev.next = c; + c.index = p; + cp++; + if(cp == NMEM) + cp = 0; + if(edata-p > NMATCH) + h = (((h<<HSHIFT)^(int pic[p+NMATCH]))&HMASK); + } + } + if(ndump != 0) { + if(NCBLOCK-outp < ndump+1) + break blockloop; + outbuf[outp++] = byte (ndump-1+128); + outbuf[outp:] = dumpbuf[0:ndump]; + outp += ndump; + } + linei = eline; + loutp = outp; + y++; + } + + # current block output buffer full + if(loutp == 0) + return (nil, "buffer too small for line"); + curblock.hdr = array of byte sys->sprint("%11d %11d ", y, loutp); + curblock.buflen = loutp; + outbnum++; + if(outbnum >= MAXCB) + return (nil, "too many output blocks"); + } + ntot := 0; + for(i = 0; i < outbnum; i++) { + b := blocks[i]; + ntot += len b.hdr + b.buflen; + } + a := array[ntot] of byte; + ai := 0; + for(i = 0; i < outbnum; i++) { + b := blocks[i]; + a[ai:] = b.hdr; + ai += len b.hdr; + if(i > 0) { + a[ai:] = b.buf[0:b.buflen]; + ai += b.buflen; + } + } + return (a, ""); +} diff --git a/appl/svc/webget/image2enc.m b/appl/svc/webget/image2enc.m new file mode 100644 index 00000000..62be121d --- /dev/null +++ b/appl/svc/webget/image2enc.m @@ -0,0 +1,7 @@ +Image2enc: module +{ + PATH: con "/dis/svc/webget/image2enc.dis"; + + image2enc: fn(i: ref RImagefile->Rawimage, errdiff: int): (array of byte, array of byte, string); +}; + diff --git a/appl/svc/webget/message.b b/appl/svc/webget/message.b new file mode 100644 index 00000000..3bf7778b --- /dev/null +++ b/appl/svc/webget/message.b @@ -0,0 +1,249 @@ +implement Message; + +include "sys.m"; + sys: Sys; + +include "string.m"; + S : String; + +include "bufio.m"; + B : Bufio; + Iobuf: import B; + +include "message.m"; + msg: Message; + +msglog: ref Sys->FD; + +init(bufio: Bufio, smod: String) +{ + sys = load Sys Sys->PATH; + S = smod; + B = bufio; +} + +sptab : con " \t"; +crlf : con "\r\n"; + +Msg.newmsg() : ref Msg +{ + return ref Msg("", nil, nil, nil, 0); +} + +# Read a message header from fd and return a Msg +# the header fields. +# If withprefix is true, read one line first and put it +# in the prefixline field of the Msg (without terminating \r\n) +# Return nil if there is a read error or eof before the +# header is completely read. +Msg.readhdr(io: ref Iobuf, withprefix: int) : (ref Msg, string) +{ + m := Msg.newmsg(); + l : list of Nameval = nil; + needprefix := withprefix; + for(;;) { + line := getline(io); + n := len line; + if(n == 0) { + if(withprefix && m.prefixline != "") + break; + return(nil, "msg read hdr error: no header"); + } + if(line[n-1] != '\n') + return (m, "msg read hdr error: incomplete header"); + if(n >= 2 && line[n-2] == '\r') + line = line[0:n-2]; + else + line = line[0:n-1]; + if(needprefix) { + m.prefixline = line; + needprefix = 0; + } + else { + if(line == "") + break; + if(S->in(line[0], sptab)) { + if(l == nil) + continue; + nv := hd l; + l = Nameval(nv.name, nv.value + " " + S->drop(line, sptab)) :: tl l; + } + else { + (nam, val) := S->splitl(line, ":"); + if(val == nil) + continue; # no colon + l = Nameval(S->tolower(nam), S->drop(val[1:], sptab)) :: l; + } + } + } + nh := len l; + if(nh > 0) { + m.fields = array[nh] of Nameval; + for(i := nh-1; i >= 0; i--) { + m.fields[i] = hd l; + l = tl l; + } + } + return (m, ""); +} + +glbuf := array[300] of byte; + +# Like io.gets('\n'), but assume Latin-1 instead of UTF encoding +getline(io: ref Iobuf): string +{ + imax := len glbuf - 1; + for(i := 0; i < imax; ) { + c := io.getb(); + if(c < 0) + break; + if(c < 128) + glbuf[i++] = byte c; + else + i += sys->char2byte(c, glbuf, i); + if(c == '\n') + break; + if(i == imax) { + imax += 100; + if(imax > 1000) + break; # Header lines aren't supposed to be > 1000 + newglbuf := array[imax] of byte; + newglbuf[0:] = glbuf[0:i]; + glbuf = newglbuf; + } + } + ans := string glbuf[0:i]; + return ans; +} + +Bbufsize: con 8000; + +# Read the body of the message, assuming the header has been processed. +# If content-length has been specified, read exactly that many bytes +# or until eof; else read until done. +# Return "" if all is OK, else return an error string. +Msg.readbody(m: self ref Msg, io: ref Iobuf) : string +{ + (clfnd, cl) := m.fieldval("content-length"); + if(clfnd) { + clen := int cl; + if(clen > 0) { + m.body = array[clen] of byte; + n := B->io.read(m.body, clen); + m.bodylen = n; + if(n != clen) + return "short read"; + } + } + else { + m.body = array[Bbufsize] of byte; + curlen := 0; + for(;;) { + avail := len m.body - curlen; + if(avail <= 0) { + newa := array[len m.body + Bbufsize] of byte; + if(curlen > 0) + newa[0:] = m.body[0:curlen]; + m.body = newa; + avail = Bbufsize; + } + n := B->io.read(m.body[curlen:], avail); + if(n < 0) + return sys->sprint("readbody error %r"); + if(n == 0) + break; + else + curlen += n; + } + m.bodylen = curlen; + } + return ""; +} + +# Look for name (lowercase) in the fields of m +# and (1, field value) if found, or (0,"") if not. +# If multiple fields with the same name exist, +# the value is defined as the comma separated list +# of all such values. +Msg.fieldval(m: self ref Msg, name: string) : (int, string) +{ + n := len m.fields; + ans := ""; + found := 0; + for(i := 0; i < n; i++) { + if(m.fields[i].name == name) { + v := m.fields[i].value; + if(found) + ans = ans + ", " + v; + else + ans = v; + found = 1; + } + } + return (found, ans); +} + +Msg.addhdrs(m: self ref Msg, hdrs: list of Nameval) +{ + nh := len hdrs; + if(nh == 0) + return; + onh := len m.fields; + newa := array[nh + onh] of Nameval; + newa[0:] = m.fields; + i := onh; + while(hdrs != nil) { + newa[i++] = hd hdrs; + hdrs = tl hdrs; + } + m.fields = newa; +} + +Msg.update(m: self ref Msg, name, value: string) +{ + for(i := 0; i < len m.fields; i++) + if(m.fields[i].name == name) { + m.fields[i] = Nameval(name, value); + return; + } + m.addhdrs(Nameval(name, value) :: nil); +} + +Msg.header(m: self ref Msg) : string +{ + s := ""; + for(i := 0; i < len m.fields; i++) { + nv := m.fields[i]; + s += nv.name + ": " + nv.value + "\n"; + } + return s; +} + +Msg.writemsg(m: self ref Msg, io: ref Iobuf) : string +{ + n := 0; + if(m.prefixline != nil) { + n = B->io.puts(m.prefixline); + if(n >= 0) + n = B->io.puts(crlf); + } + for(i := 0; i < len m.fields; i++) { + nv := m.fields[i]; + if(n >= 0) + n = B->io.puts(nv.name); + if(n >= 0) + n = B->io.puts(": "); + if(n >= 0) + n = B->io.puts(nv.value); + if(n >= 0) + n = B->io.puts(crlf); + } + if(n >= 0) + n = B->io.puts(crlf); + if(n >= 0 && m.bodylen > 0) + n = B->io.write(m.body, m.bodylen); + if(n < 0) + return sys->sprint("msg write error: %r"); + B->io.flush(); + return ""; +} diff --git a/appl/svc/webget/message.m b/appl/svc/webget/message.m new file mode 100644 index 00000000..04ccfbb3 --- /dev/null +++ b/appl/svc/webget/message.m @@ -0,0 +1,28 @@ +Message: module +{ + PATH: con "/dis/svc/webget/message.dis"; + + init: fn(bufio: Bufio, smod: String); + + Nameval: adt { + name: string; + value: string; + }; + + Msg: adt { + prefixline: string; + prefixbytes: array of byte; + fields: array of Nameval; + body: array of byte; + bodylen: int; + + readhdr: fn(io: ref Bufio->Iobuf, withprefix: int) : (ref Msg, string); + readbody: fn(m: self ref Msg, io: ref Bufio->Iobuf) : string; + writemsg: fn(m: self ref Msg, io: ref Bufio->Iobuf) : string; + header: fn(m: self ref Msg) : string; + addhdrs: fn(m: self ref Msg, hdrs: list of Nameval); + newmsg: fn() : ref Msg; + fieldval: fn(m: self ref Msg, name: string) : (int, string); + update: fn(m: self ref Msg, name, value: string); + }; +}; diff --git a/appl/svc/webget/mkfile b/appl/svc/webget/mkfile new file mode 100644 index 00000000..dcb51917 --- /dev/null +++ b/appl/svc/webget/mkfile @@ -0,0 +1,40 @@ +<../../../mkconfig + +TARG=\ + date.dis\ + file.dis\ + ftp.dis\ + http.dis\ + image2enc.dis\ + message.dis\ + webget.dis\ + wgutils.dis\ + +MODULES=\ + date.m\ + image2enc.m\ + message.m\ + transport.m\ + wgutils.m\ + +SYSMODULES=\ + bufio.m\ + daytime.m\ + draw.m\ + imagefile.m\ + ssl3.m\ + string.m\ + strinttab.m\ + sys.m\ + url.m\ + webget.m\ + +DISBIN=$ROOT/dis/svc/webget + +<$ROOT/mkfiles/mkdis + +install:V: install-logs + +install-logs:V: + rm -f $ROOT/services/webget/webget.log && cp webget.log $ROOT/services/webget/webget.log + # chmod 644 $ROOT/services/webget/webget.log diff --git a/appl/svc/webget/transport.m b/appl/svc/webget/transport.m new file mode 100644 index 00000000..d932f906 --- /dev/null +++ b/appl/svc/webget/transport.m @@ -0,0 +1,5 @@ +Transport: module +{ + init: fn(w: WebgetUtils); + connect: fn(c: ref Fid, r: ref Req, donec: chan of ref Fid); +}; diff --git a/appl/svc/webget/webget.b b/appl/svc/webget/webget.b new file mode 100644 index 00000000..6821303f --- /dev/null +++ b/appl/svc/webget/webget.b @@ -0,0 +1,464 @@ +implement Webget; + +# Protocol +# +# Client opens /chan/webget and writes one of +# GET 0 reqid url types cachectl authcookie\n +# or +# POST bodylength reqid url types cachectl authcookie\n +# body +# +# The possibilities for cachectl are +# max-stale=seconds +# client is willing to accept a response whose age exceeds +# its freshness lifetime (by at most specified seconds) +# without revalidation +# max-age=seconds +# client is unwilling to accept a response whose age +# (now - generation time) exceeds specified seconds +# without revalidiation +# no-cache +# unconditional reload +# Both max-stale and max-age may be specified (separated by comma), +# but no-cache must appear by itself. +# +# Authcookie is optional. If present, it goes in an Authorization: header. +# +# The appropriate transport mechanism gets the entity and +# responds with one of +# OK bodylength reqid type url\n +# body +# or +# ERROR reqid message\n +# +# (In the ERROR case, the message might be "Unauthorized: challenge\n", +# where challenge is of the form "BASIC realm=xxx (param, param, ...)\n". +# The user can be prompted for a name:password, and the request repeated +# with authcookie containing the base64 encoding of name:password). + +include "sys.m"; + sys: Sys; + FD: import sys; + +include "draw.m"; + +include "string.m"; + S: String; + +include "bufio.m"; + B: Bufio; + +include "message.m"; + M: Message; + Msg: import M; + +include "url.m"; + U: Url; + ParsedUrl: import U; + +include "webget.m"; + +include "wgutils.m"; + W: WebgetUtils; + Fid, Req: import W; + +include "transport.m"; + +fhash := array[128] of ref Fid; + +Transports: adt +{ + scheme: int; + m: Transport; +}; +transports: array of ref Transports; + +transtab := array[] of { + (Url->HTTP, "/dis/svc/webget/http.dis"), + (Url->HTTPS, nil), # nil means: same as previous + (Url->FILE, "/dis/svc/webget/file.dis"), + (Url->FTP, "/dis/svc/webget/ftp.dis") +}; + +Transpreq: adt +{ + index: int; + fid: ref Fid; + r: ref Req; + next: cyclic ref Transpreq; +}; + +Rchunk: con 30; +# Transpmax: con 5; # max number of simultaneously spawned transports +Transpmax: con 1; # max number of simultaneously spawned transports + +logfile: con "/services/webget/webget.log"; +DO_LOG: con 1; + +stderr: ref FD; + +# to start ever-present webget +init(nil: ref Draw->Context, nil: list of string) +{ + dummyctl := chan of int; + spawn start(dummyctl); + <- dummyctl; + <- dummyctl; +} + +# sends a 1 on ctl when ready to serve, +# 0 if there was some problem starting. +start(ctl: chan of int) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + ok := 1; + ntransp := 0; + tqueuehd: ref Transpreq = nil; + tqueuetl: ref Transpreq = nil; + + log : ref Sys->FD; + if(DO_LOG) + log = sys->create(logfile, Sys->OWRITE, 8r666); + + io := sys->file2chan("/chan", "webget"); + if(io == nil) { + sys->fprint(stderr, "webget: failed to post: %r\n"); + ok = 0; + } + + B = load Bufio Bufio->PATH; + if(B == nil) { + sys->fprint(stderr, "webget: failed to load Bufio: %r\n"); + ok = 0; + } + S = load String String->PATH; + if(S == nil) { + sys->fprint(stderr, "webget: failed to load String: %r\n"); + ok = 0; + } + M = load Message Message->PATH; + if(M == nil) { + sys->fprint(stderr, "webget: failed to load Message: %r\n"); + ok = 0; + } + M->init(B, S); + U = load Url Url->PATH; + if(U == nil) { + sys->fprint(stderr, "webget: failed to load Url: %r\n"); + ok = 0; + } + U->init(); + W = load WebgetUtils WebgetUtils->PATH; + if(W == nil) { + sys->fprint(stderr, "webget: failed to load WebgetUtils: %r\n"); + ok = 0; + } + if(!ok) { + ctl <-= 0; + return; + } + W->init(M, S, B, U, log); + + loadtransmod(); + + donec := chan of ref Fid; + ctl <-= 1; + + + altloop: + for(;;) alt { + (nil, data, fid, wc) := <-io.write => + if(wc == nil) { + finish(fid); + continue altloop; + } + ndata := len data; + c := lookup(fid); + W->log(c, "\nREQUEST: " + string data); + iw := c.writer; + n := len c.reqs; + if(iw == n) { + newrs := array[n + Rchunk] of ref Req; + newrs[0:] = c.reqs[0:n]; + c.reqs = newrs; + } + r := c.reqs[iw]; + err := ""; + if(r == nil) { + # initial part (or all) of a request + r = ref Req(iw, "", 0, "", "", "", "", "", nil, nil, nil); + c.reqs[iw] = r; + + # expect at least the prefix line to be in data + prefix := ""; + for(i := 0; i < ndata; i++) { + if(int data[i] == '\n') { + prefix = string data[0:i]; + if(i+1 < ndata) { + r.body = array[ndata-i-1] of byte; + r.body[0:] = data[i:ndata]; + } + break; + } + } + if(prefix == "") + err = "no prefix line"; + else if(prefix == "FINISH") { + writereply(wc, len data, ""); + finish(fid); + continue altloop; + } + else { + (nl, l) := sys->tokenize(prefix, "∎"); + if(nl != 6 && nl != 7) + err = "wrong number of fields in " + prefix; + else { + r.method = hd l; + l = tl l; + r.bodylen = int hd(l); + l = tl l; + r.reqid = hd l; + l = tl l; + r.loc = hd l; + l = tl l; + r.types = hd l; + l = tl l; + r.cachectl = hd l; + l = tl l; + if(l != nil) + r.auth = hd l; + locurl := U->makeurl(r.loc); + if(locurl.scheme == U->MAILTO) + err = "webget shouldn't get mailto"; + else if(locurl.scheme == U->NOSCHEME || + (locurl.scheme != U->FILE && (locurl.host == "" || locurl.pstart != "/"))) + err = "url not absolute: " + r.loc; + r.url = locurl; + } + } + if(err != "") + err = "webget protocol error: " + err; + } + else { + # continuation of request: more body + olen := len r.body; + newa := array[olen + ndata] of byte; + newa[0:] = r.body[0:olen]; + newa[olen:] = data[0:ndata]; + r.body = newa; + } + if(err == "" && len r.body == r.bodylen) { + # request complete: spawn off transport handler + c.writer++; + scheme := r.url.scheme; + found := 0; + for(i := 0; i < len transports; i++) { + if(transports[i].scheme == scheme) { + found = 1; + break; + } + } + if(found == 0) + err = "don't know how to fetch " + r.loc; + else { + if(ntransp < Transpmax) { + W->log(c, "transport " + string scheme + ": get " + r.loc); + spawn transports[i].m->connect(c, r, donec); + ntransp++; + } + else { + # too many active transports: queue this until later + tr := ref Transpreq(i, c, r, nil); + if(tqueuetl == nil) + tqueuehd = tqueuetl = tr; + else { + tqueuetl.next = tr; + tqueuetl = tr; + } + } + } + } + if(err != "") { + writereply(wc, -1, err); + W->log(c, err); + c.reqs[iw] = nil; + } + else + writereply(wc, ndata, ""); + + (nil, nbyte, fid, rc) := <-io.read => + if(rc == nil) { + finish(fid); + continue altloop; + } + c := lookup(fid); + c.nbyte = nbyte; + c.rc = rc; + readans(c); + c := <- donec => + ntransp--; + if(tqueuehd != nil) { + tr := tqueuehd; + tqueuehd = tr.next; + if(tqueuehd == nil) + tqueuetl = nil; + W->log(c, "transport: get " + tr.r.loc); + spawn transports[tr.index].m->connect(tr.fid, tr.r, donec); + ntransp++; + } + readans(c); + c = nil; + } +} + +loadtransmod() +{ + transports = array[len transtab] of ref Transports; + j := 0; + prevt : ref Transports = nil; + for(i := 0; i < len transtab; i++) { + (scheme, path) := transtab[i]; + if(path == nil) { + if(prevt != nil) + transports[j++] = ref Transports(scheme, prevt.m); + } + else { + t := load Transport path; + if(t == nil) { + sys->fprint(stderr, "failed to load %s: %r\n", path); + continue; + } + + t->init(W); + + ts := ref Transports(scheme, t); + transports[j++] = ts; + prevt = ts; + } + } +} + +# Answer a read request c.nbyte bytes, reply to go to c.rc. +# If c.readr is not -1, it is the index of a req with the currently +# being consumed reply. +# c.nread contains the number of bytes of this message read so far. +readans(c: ref Fid) +{ + n := c.nbyte; + if(n <= 0) + return; + ir := c.readr; + if(ir == -1) { + # look for ready reply + for(i := 0; i < c.writer; i++) { + r := c.reqs[i]; + if(r != nil && r.reply != nil) + break; + } + if(i == c.writer) { + return; + } + ir = i; + } + r := c.reqs[ir]; + m := r.reply; + if(m == nil) { + W->log(c, "readans bad state: nil reply"); + readreply(c, nil, ""); + return; + } + if(m.prefixbytes == nil && m.prefixline != "") + m.prefixbytes = array of byte m.prefixline; + plen := len m.prefixbytes; + blen := m.bodylen; + ntot := plen + blen; + nread := c.nread; + if(nread == 0) + W->log(c, "\nREPLY: " + m.prefixline); + nrest := ntot - nread; + if(nrest <= 0) { + W->log(c, "readans bad state: 0 left"); + readreply(c, nil, ""); + return; + } + if(n > nrest) + n = nrest; + n1 := plen - nread; + if(n1 > 0) { + if(n1 > n) + n1 = n; + readreply(c, m.prefixbytes[nread:nread + n1], ""); + c.nread += n1; + } + else { + bpos := nread - plen; + n2 := blen - bpos; + if(n > n2) + n = n2; + readreply(c, m.body[bpos:bpos+n], ""); + c.nread += n; + } + if(c.nread >= ntot) { + c.reqs[ir] = nil; + c.readr = -1; + c.nbyte = 0; + c.nread = 0; + c.rc = nil; + # move back write pointer as far as possible + if(c.writer == ir+1) { + while(ir >= 0 && c.reqs[ir] == nil) + ir--; + c.writer = ir+1; + } + } + else + c.readr = ir; +} + +# Reply to a write command. +writereply(wc: Sys->Rwrite, n: int, err: string) +{ + wc <-= (n, err); +} + +readreply(c: ref Fid, a: array of byte, err: string) +{ + rc := c.rc; + if(rc != nil) + rc <-= (a, err); + c.nbyte = 0; +} + +lookup(fid: int): ref Fid +{ + h := fid%len fhash; + for(f := fhash[h]; f != nil; f = f.link) + if(f.fid == fid) + return f; + f = ref Fid(fid, fhash[h], array[Rchunk] of ref Req, 0, -1, 0, 0, nil); + fhash[h] = f; + + W->log(f, "\nNEW CLIENT"); + + return f; +} + +finish(fid: int) +{ + W->log(nil, "finish"); + h := fid%len fhash; + + prev: ref Fid; + for(f := fhash[h]; f != nil; f = f.link) { + if(f.fid == fid) { + f.rc = nil; + W->log(f, "client finished"); + if(prev == nil) + fhash[h] = f.link; + else + prev.link = f.link; + return; + } + } +} diff --git a/appl/svc/webget/webget.log b/appl/svc/webget/webget.log new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/appl/svc/webget/webget.log diff --git a/appl/svc/webget/wgutils.b b/appl/svc/webget/wgutils.b new file mode 100644 index 00000000..fdf5c375 --- /dev/null +++ b/appl/svc/webget/wgutils.b @@ -0,0 +1,298 @@ +implement WebgetUtils; + +include "sys.m"; + sys: Sys; + +include "draw.m"; + +include "string.m"; + +include "bufio.m"; + +include "imagefile.m"; + readgif, readjpg, readxbitmap: RImagefile; + +include "image2enc.m"; + image2enc: Image2enc; + +include "message.m"; + +include "url.m"; + +include "wgutils.m"; + Iobuf: import B; + +include "strinttab.m"; + T: StringIntTab; + +Msg, Nameval: import M; +ParsedUrl: import U; + +logfd: ref Sys->FD; + +# return from acceptmatch; and conv arg to readbody +BadConv, NoConv, Gif2xcompressed, Jpeg2xcompressed, Xbm2xcompressed: con iota; + +# Both extensions and Content-Types can be in same table. +# This array should be kept sorted +mtypes := array[] of { T->StringInt + ("ai", ApplPostscript), + ("application/html", TextHtml), + ("application/pdf", ApplPdf), + ("application/postscript", ApplPostscript), + ("application/rtf", ApplRtf), + ("application/x-html", TextHtml), + ("au", AudioBasic), + ("audio/au", AudioBasic), + ("audio/basic", AudioBasic), + ("bit", ImageXCompressed), + ("bit2", ImageXCompressed2), + ("eps", ApplPostscript), + ("gif", ImageGif), + ("htm", TextHtml), + ("html", TextHtml), + ("image/gif", ImageGif), + ("image/ief", ImageIef), + ("image/jpeg", ImageJpeg), + ("image/tiff", ImageTiff), + ("image/x-compressed", ImageXCompressed), + ("image/x-compressed2", ImageXCompressed2), + ("image/x-xbitmap", ImageXXBitmap), + ("jpe", ImageJpeg), + ("jpeg", ImageJpeg), + ("jpg", ImageJpeg), + ("pdf", ApplPdf), + ("ps", ApplPostscript), + ("text", TextPlain), + ("text/html", TextHtml), + ("text/plain", TextPlain), + ("text/x-html", TextHtml), + ("tif", ImageTiff), + ("tiff", ImageTiff), + ("txt", TextPlain), + ("video/mpeg", VideoMpeg), + ("video/quicktime", VideoQuicktime), +}; + +# following array must track media type def in wgutils.m +mnames := array[] of { + "application/x-unknown", + "text/plain", + "text/html", + "application/postscript", + "application/rtf", + "application/pdf", + "image/jpeg", + "image/gif", + "image/ief", + "image/tiff", + "image/x-compressed", + "image/x-compressed2", + "image/x-xbitmap", + "audio/basic", + "video/mpeg", + "video/quicktime" +}; + +init(m: Message, s: String, b: Bufio, u: Url, lfd: ref Sys->FD) +{ + sys = load Sys Sys->PATH; + + M = m; + S = s; + B = b; + U = u; + logfd = lfd; + T = load StringIntTab StringIntTab->PATH; + readgif = load RImagefile RImagefile->READGIFPATH; + readjpg = load RImagefile RImagefile->READJPGPATH; + readxbitmap = load RImagefile RImagefile->READXBMPATH; + image2enc = load Image2enc Image2enc->PATH; + if(T == nil || readgif == nil || readjpg == nil || readxbitmap == nil || image2enc == nil) { + sys->fprint(sys->fildes(2), "webget: failed to load T, readgif, readjpg, readxbitmap, or imageremap: %r\n"); + return; + } + readgif->init(B); + readjpg->init(B); + readxbitmap->init(B); +} + +# Return msg with error provoked by bad user action +usererr(r: ref Req, msg: string) : ref Msg +{ + m := Msg.newmsg(); + m.prefixline = sys->sprint("ERROR %s %s", r.reqid, msg); + m.bodylen = 0; + return m; +} + +okprefix(r: ref Req, mrep: ref Msg) +{ + (nil, sctype) := mrep.fieldval("content-type"); + if(sctype == "") + sctype = "text/html"; + else + sctype = canon_mtype(sctype); + (nil, cloc) := mrep.fieldval("content-location"); + if(cloc == "") + cloc = "unknown"; + mrep.prefixline = "OK " + string mrep.bodylen + " " + r.reqid + " " + sctype + " " + cloc; +} + +canon_mtype(s: string) : string +{ + # lowercase, and remove possible parameter + ls := S->tolower(s); + (ts, nil) := S->splitl(ls, "; "); + return ts; +} + +mediatype(s: string, dflt: int) : int +{ + (fnd, val) := T->lookup(mtypes, canon_mtype(s)); + if(!fnd) + val = dflt; + return val; +} + +acceptmatch(ctype: int, accept: string) : int +{ + conv := BadConv; + (nil,l) := sys->tokenize(accept, ","); + while(l != nil) { + a := S->drop(hd l, " \t"); + mt := mediatype(a, -1); + match := (ctype == mt) || (a == "*/*") + || ((ctype == ImageXCompressed || ctype == ImageXCompressed2) + && (mt == ImageJpeg || mt == ImageGif || mt == ImageXXBitmap)); + if(match) { + if(ctype == ImageGif) + conv = Gif2xcompressed; + else if(ctype == ImageJpeg) + conv = Jpeg2xcompressed; + else if(ctype == ImageXXBitmap) + conv = Xbm2xcompressed; + else + conv = NoConv; + break; + } + l = tl l; + } + return conv; +} + +# Get the body of the message whose header is in mrep, +# if io != nil. +# First check that the content type is acceptable. +# Image types will get converted into Inferno compressed format. +# If there is an error, return error string, else "". +# If no error, mrep will contain content-encoding, content-location, +# and content-type fields (guessed if they weren't orignally there). + +getdata(io: ref Iobuf, m: ref Msg, accept: string, url: ref ParsedUrl) : string +{ + (efnd, etype) := m.fieldval("content-encoding"); + if(efnd) + return "content is encoded: " + etype; + ctype := UnknownType; + (tfnd, sctype) := m.fieldval("content-type"); + if(tfnd) + ctype = mediatype(sctype, UnknownType); + else { + # try to guess type from extension + sctype = "(unknown)"; + (nil, name) := S->splitr(url.path, "/"); + if(name != "") { + (f, ext) := S->splitr(name, "."); + if(f != "" && ext != "") { + ctype = mediatype(ext, UnknownType); + if(ctype != UnknownType) { + sctype = mnames[ctype]; + m.update("content-type", sctype); + } + } + } + } + transform := acceptmatch(ctype, accept); + if(transform == BadConv) + return "Unacceptable media type: " + sctype; + (clfnd, cloc) := m.fieldval("content-location"); + if(!clfnd) { + cloc = url.tostring(); + m.update("content-location", cloc); + } + if(transform != NoConv) { + rawimg: ref RImagefile->Rawimage; + err: string; + if(transform == Gif2xcompressed) + (rawimg, err) = readgif->read(io); + else if(transform == Jpeg2xcompressed) + (rawimg, err) = readjpg->read(io); + else if(transform == Xbm2xcompressed) + (rawimg, err) = readxbitmap->read(io); + # if gif file has multiple images, we are supposed to animate, + # but the first one should be there + if(err != "" && err != "ReadGIF: can't handle multiple images in file") + return "error converting image file: " + err; + (data, mask, e) := image2enc->image2enc(rawimg, 1); + if(e != "") + return "error remapping and encoding image file: " + e; + if(mask == nil) + sctype = "image/x-compressed"; + else { + sctype = "image/x-compressed2"; + newdata := array[len data + len mask] of byte; + newdata[0:] = data[0:]; + newdata[len data:] = mask[0:]; + data = newdata; + } + m.body = data; + m.bodylen = len data; + m.update("content-type", sctype); + m.update("content-length", string m.bodylen); + } + else { + # io will be nil if m came from cache + if(io != nil) { + e := m.readbody(io); + if(e != "") + return "reading body: " + e; + } + } + return ""; +} + +# Change an accept spec from webget client into one we can send +# to http server. This means image/x-compressed must be +# changed into image formats we can handle: i.e., gif or jpeg +fixaccept(a: string) : string +{ + (nil,l) := sys->tokenize(a, ","); + ans := ""; + sep := ""; + while(l != nil) { + s := S->drop(hd l, " \t"); + if(s == "image/x-compressed") + ans += sep + "image/gif,image/jpeg,image/x-xbitmap"; + else + ans += sep + s; + sep = ","; + l = tl l; + } + if(ans == "") + ans = "*/*"; + return ans; +} + +log(c: ref Fid, msg: string) +{ + if(logfd != nil) { + # don't use print in case msg is longer than buf + s := ""; + if(c != nil) + s += (string c.fid) + ": "; + s += msg + "\n"; + b := array of byte s; + sys->write(logfd, b, len b); + } +} diff --git a/appl/svc/webget/wgutils.m b/appl/svc/webget/wgutils.m new file mode 100644 index 00000000..5b2458fe --- /dev/null +++ b/appl/svc/webget/wgutils.m @@ -0,0 +1,53 @@ +WebgetUtils: module +{ + PATH: con "/dis/svc/webget/wgutils.dis"; + + Req: adt + { + index: int; + method: string; + bodylen: int; + reqid: string; + loc: string; + types: string; + cachectl: string; + auth: string; + body: array of byte; + url: ref Url->ParsedUrl; + reply: ref Message->Msg; + }; + + Fid: adt + { + fid: int; + link: cyclic ref Fid; + reqs: array of ref Req; + writer: int; + readr: int; + nbyte: int; + nread: int; + rc: Sys->Rread; + }; + + M: Message; + B: Bufio; + S: String; + U: Url; + + # media types (must track mnames array in wgutils.b) + UnknownType, + TextPlain, TextHtml, + ApplPostscript, ApplRtf, ApplPdf, + ImageJpeg, ImageGif, ImageIef, ImageTiff, + ImageXCompressed, ImageXCompressed2, ImageXXBitmap, + AudioBasic, + VideoMpeg, VideoQuicktime: con iota; + + init : fn(m: Message, s: String, b: Bufio, u: Url, logfd: ref Sys->FD); + usererr: fn(r: ref Req, msg: string) : ref Message->Msg; + okprefix: fn(r: ref Req, mrep: ref Message->Msg); + getdata: fn(io: ref Bufio->Iobuf, m: ref Message->Msg, + accept: string, url: ref Url->ParsedUrl) : string; + fixaccept: fn(a: string) : string; + log: fn(c: ref Fid, msg: string); +}; |
