summaryrefslogtreecommitdiff
path: root/appl/svc
diff options
context:
space:
mode:
Diffstat (limited to 'appl/svc')
-rw-r--r--appl/svc/auth.sh13
-rw-r--r--appl/svc/httpd/alarms.b45
-rw-r--r--appl/svc/httpd/alarms.m11
-rw-r--r--appl/svc/httpd/cache.b188
-rw-r--r--appl/svc/httpd/cache.m9
-rw-r--r--appl/svc/httpd/cgiparse.b258
-rw-r--r--appl/svc/httpd/cgiparse.m22
-rw-r--r--appl/svc/httpd/contents.b192
-rw-r--r--appl/svc/httpd/contents.m16
-rw-r--r--appl/svc/httpd/date.b264
-rw-r--r--appl/svc/httpd/date.m11
-rw-r--r--appl/svc/httpd/echo.b89
-rw-r--r--appl/svc/httpd/httpd.b721
-rw-r--r--appl/svc/httpd/httpd.debug27
-rw-r--r--appl/svc/httpd/httpd.log0
-rw-r--r--appl/svc/httpd/httpd.m49
-rw-r--r--appl/svc/httpd/httpd.rewrite10
-rw-r--r--appl/svc/httpd/httpd.suff110
-rw-r--r--appl/svc/httpd/imagemap.b251
-rw-r--r--appl/svc/httpd/mkfile49
-rw-r--r--appl/svc/httpd/parser.b861
-rw-r--r--appl/svc/httpd/parser.m16
-rw-r--r--appl/svc/httpd/redirect.b130
-rw-r--r--appl/svc/httpd/redirect.m7
-rw-r--r--appl/svc/httpd/stats.b85
-rw-r--r--appl/svc/mkfile24
-rw-r--r--appl/svc/net.sh6
-rw-r--r--appl/svc/registry.sh8
-rw-r--r--appl/svc/rstyx.sh4
-rw-r--r--appl/svc/styx.sh4
-rw-r--r--appl/svc/webget/date.b266
-rw-r--r--appl/svc/webget/date.m12
-rw-r--r--appl/svc/webget/file.b67
-rw-r--r--appl/svc/webget/ftp.b227
-rw-r--r--appl/svc/webget/http.b602
-rw-r--r--appl/svc/webget/image2enc.b1070
-rw-r--r--appl/svc/webget/image2enc.m7
-rw-r--r--appl/svc/webget/message.b249
-rw-r--r--appl/svc/webget/message.m28
-rw-r--r--appl/svc/webget/mkfile40
-rw-r--r--appl/svc/webget/transport.m5
-rw-r--r--appl/svc/webget/webget.b464
-rw-r--r--appl/svc/webget/webget.log0
-rw-r--r--appl/svc/webget/wgutils.b298
-rw-r--r--appl/svc/webget/wgutils.m53
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( "&#161;", '¡' ),
+ Entity( "&#162;", '¢' ),
+ Entity( "&#163;", '£' ),
+ Entity( "&#164;", '¤' ),
+ Entity( "&#165;", '¥' ),
+ Entity( "&#166;", '¦' ),
+ Entity( "&#167;", '§' ),
+ Entity( "&#168;", '¨' ),
+ Entity( "&#169;", '©' ),
+ Entity( "&#170;", 'ª' ),
+ Entity( "&#171;", '«' ),
+ Entity( "&#172;", '¬' ),
+ Entity( "&#173;", '­' ),
+ Entity( "&#174;", '®' ),
+ Entity( "&#175;", '¯' ),
+ Entity( "&#176;", '°' ),
+ Entity( "&#177;", '±' ),
+ Entity( "&#178;", '²' ),
+ Entity( "&#179;", '³' ),
+ Entity( "&#180;", '´' ),
+ Entity( "&#181;", 'µ' ),
+ Entity( "&#182;", '¶' ),
+ Entity( "&#183;", '·' ),
+ Entity( "&#184;", '¸' ),
+ Entity( "&#185;", '¹' ),
+ Entity( "&#186;", 'º' ),
+ Entity( "&#187;", '»' ),
+ Entity( "&#188;", '¼' ),
+ Entity( "&#189;", '½' ),
+ Entity( "&#190;", '¾' ),
+ Entity( "&#191;", '¿' ),
+ Entity( "&Agrave;", 'À' ),
+ Entity( "&Aacute;", 'Á' ),
+ Entity( "&Acirc;", 'Â' ),
+ Entity( "&Atilde;", 'Ã' ),
+ Entity( "&Auml;", 'Ä' ),
+ Entity( "&Aring;", 'Å' ),
+ Entity( "&AElig;", 'Æ' ),
+ Entity( "&Ccedil;", 'Ç' ),
+ Entity( "&Egrave;", 'È' ),
+ Entity( "&Eacute;", 'É' ),
+ Entity( "&Ecirc;", 'Ê' ),
+ Entity( "&Euml;", 'Ë' ),
+ Entity( "&Igrave;", 'Ì' ),
+ Entity( "&Iacute;", 'Í' ),
+ Entity( "&Icirc;", 'Î' ),
+ Entity( "&Iuml;", 'Ï' ),
+ Entity( "&ETH;", 'Ð' ),
+ Entity( "&Ntilde;", 'Ñ' ),
+ Entity( "&Ograve;", 'Ò' ),
+ Entity( "&Oacute;", 'Ó' ),
+ Entity( "&Ocirc;", 'Ô' ),
+ Entity( "&Otilde;", 'Õ' ),
+ Entity( "&Ouml;", 'Ö' ),
+ Entity( "&215;", '×' ),
+ Entity( "&Oslash;", 'Ø' ),
+ Entity( "&Ugrave;", 'Ù' ),
+ Entity( "&Uacute;", 'Ú' ),
+ Entity( "&Ucirc;", 'Û' ),
+ Entity( "&Uuml;", 'Ü' ),
+ Entity( "&Yacute;", 'Ý' ),
+ Entity( "&THORN;", 'Þ' ),
+ Entity( "&szlig;", 'ß' ),
+ Entity( "&agrave;", 'à' ),
+ Entity( "&aacute;", 'á' ),
+ Entity( "&acirc;", 'â' ),
+ Entity( "&atilde;", 'ã' ),
+ Entity( "&auml;", 'ä' ),
+ Entity( "&aring;", 'å' ),
+ Entity( "&aelig;", 'æ' ),
+ Entity( "&ccedil;", 'ç' ),
+ Entity( "&egrave;", 'è' ),
+ Entity( "&eacute;", 'é' ),
+ Entity( "&ecirc;", 'ê' ),
+ Entity( "&euml;", 'ë' ),
+ Entity( "&igrave;", 'ì' ),
+ Entity( "&iacute;", 'í' ),
+ Entity( "&icirc;", 'î' ),
+ Entity( "&iuml;", 'ï' ),
+ Entity( "&eth;", 'ð' ),
+ Entity( "&ntilde;", 'ñ' ),
+ Entity( "&ograve;", 'ò' ),
+ Entity( "&oacute;", 'ó' ),
+ Entity( "&ocirc;", 'ô' ),
+ Entity( "&otilde;", 'õ' ),
+ Entity( "&ouml;", 'ö' ),
+ Entity( "&247;", '÷' ),
+ Entity( "&oslash;", 'ø' ),
+ Entity( "&ugrave;", 'ù' ),
+ Entity( "&uacute;", 'ú' ),
+ Entity( "&ucirc;", 'û' ),
+ Entity( "&uuml;", 'ü' ),
+ Entity( "&yacute;", 'ý' ),
+ Entity( "&thorn;", 'þ' ),
+ Entity( "&yuml;", 'ÿ' ),
+
+ Entity( "&#SPACE;", ' ' ),
+ Entity( "&#RS;", '\n' ),
+ Entity( "&#RE;", '\r' ),
+ Entity( "&quot;", '"' ),
+ Entity( "&amp;", '&' ),
+ Entity( "&lt;", '<' ),
+ Entity( "&gt;", '>' ),
+
+ 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);
+};