diff options
Diffstat (limited to 'appl/svc/httpd')
| -rw-r--r-- | appl/svc/httpd/alarms.b | 45 | ||||
| -rw-r--r-- | appl/svc/httpd/alarms.m | 11 | ||||
| -rw-r--r-- | appl/svc/httpd/cache.b | 188 | ||||
| -rw-r--r-- | appl/svc/httpd/cache.m | 9 | ||||
| -rw-r--r-- | appl/svc/httpd/cgiparse.b | 258 | ||||
| -rw-r--r-- | appl/svc/httpd/cgiparse.m | 22 | ||||
| -rw-r--r-- | appl/svc/httpd/contents.b | 192 | ||||
| -rw-r--r-- | appl/svc/httpd/contents.m | 16 | ||||
| -rw-r--r-- | appl/svc/httpd/date.b | 264 | ||||
| -rw-r--r-- | appl/svc/httpd/date.m | 11 | ||||
| -rw-r--r-- | appl/svc/httpd/echo.b | 89 | ||||
| -rw-r--r-- | appl/svc/httpd/httpd.b | 721 | ||||
| -rw-r--r-- | appl/svc/httpd/httpd.debug | 27 | ||||
| -rw-r--r-- | appl/svc/httpd/httpd.log | 0 | ||||
| -rw-r--r-- | appl/svc/httpd/httpd.m | 49 | ||||
| -rw-r--r-- | appl/svc/httpd/httpd.rewrite | 10 | ||||
| -rw-r--r-- | appl/svc/httpd/httpd.suff | 110 | ||||
| -rw-r--r-- | appl/svc/httpd/imagemap.b | 251 | ||||
| -rw-r--r-- | appl/svc/httpd/mkfile | 49 | ||||
| -rw-r--r-- | appl/svc/httpd/parser.b | 861 | ||||
| -rw-r--r-- | appl/svc/httpd/parser.m | 16 | ||||
| -rw-r--r-- | appl/svc/httpd/redirect.b | 130 | ||||
| -rw-r--r-- | appl/svc/httpd/redirect.m | 7 | ||||
| -rw-r--r-- | appl/svc/httpd/stats.b | 85 |
24 files changed, 3421 insertions, 0 deletions
diff --git a/appl/svc/httpd/alarms.b b/appl/svc/httpd/alarms.b new file mode 100644 index 00000000..5142906d --- /dev/null +++ b/appl/svc/httpd/alarms.b @@ -0,0 +1,45 @@ +implement Alarms; +include "sys.m"; + sys: Sys; + +include "alarms.m"; + +Alarm.stop(a: self Alarm) +{ + a.alchan <-= -1; + fd:=sys->open("#p/"+string a.pid+"/ctl",sys->OWRITE); + sys->fprint(fd, "killgrp"); +} + +Alarm.alarm(time: int): Alarm +{ + if (sys == nil) + sys = load Sys Sys->PATH; + + pid := sys->pctl(sys->NEWPGRP|sys->FORKNS,nil); + a:=Alarm(chan of int,pid); + spawn listener(a.alchan); + spawn sleeper(a.alchan,time,pid); + return a; +} + +sleeper(ch: chan of int, time, pid: int) +{ + sys->sleep(time); + alt{ + ch <-= pid => + ; + * => + exit; + } +} + +listener(ch: chan of int) +{ + a := <-ch; + if (a==-1) + exit; + fd := sys->open("#p/"+string a+"/ctl",sys->OWRITE); + if (fd != nil) + sys->fprint(fd, "killgrp"); +} diff --git a/appl/svc/httpd/alarms.m b/appl/svc/httpd/alarms.m new file mode 100644 index 00000000..b422c48f --- /dev/null +++ b/appl/svc/httpd/alarms.m @@ -0,0 +1,11 @@ +Alarms: module{ + PATH: con "/dis/svc/httpd/alarms.dis"; + + Alarm: adt{ + alchan: chan of int; + pid: int; + stop: fn(a: self Alarm); + alarm: fn(time: int): Alarm; + }; + +}; diff --git a/appl/svc/httpd/cache.b b/appl/svc/httpd/cache.b new file mode 100644 index 00000000..3d4549e4 --- /dev/null +++ b/appl/svc/httpd/cache.b @@ -0,0 +1,188 @@ +implement Cache; + +include "sys.m"; + sys : Sys; + +include "bufio.m"; + bufio : Bufio; +Iobuf : import bufio; + +include "lock.m"; + locks: Lock; + Semaphore: import locks; + +dbg_log : ref Sys->FD; + +include "cache.m"; + +HASHSIZE : con 1019; + +lru ,cache_size : int; # lru link, and maximum size of cache. +cur_size, cur_tag : int; # current size of cache and current number. + +lock: ref Semaphore; + +Cache_link : adt{ + name : string; # name of file + contents : array of byte; # contents + length : int; # length of file + qid:Sys->Qid; + tag : int; +}; + +tab := array[HASHSIZE] of list of Cache_link; + +hashasu(key : string,n : int): int +{ + i, h : int; + h=0; + i=0; + while(i<len key){ + h = 10*h + key[i]; + h = h%n; + i++; + } + return h%n; +} + + +insert(name: string, ctents: array of byte , length : int, qid:Sys->Qid) : int +{ + tmp : Cache_link; + hash : int; + lock.obtain(); + hash = hashasu(name,HASHSIZE); + if (dbg_log!=nil){ + sys->fprint(dbg_log,"current size is %d, adding %s\n", cur_size,name); + } + while (cur_size+length > cache_size) + throw_out(); + tmp.name =name; + tmp.contents = ctents; + tmp.length = length; + tmp.qid = qid; + tmp.tag = cur_tag; + cur_size+=length; + cur_tag++; + if (cur_tag<0) cur_tag=0; + tab[hash]= tmp :: tab[hash]; + lock.release(); + return 1; +} + +find(name : string, qid:Sys->Qid) : (int, array of byte) +{ + hash,flag,stale : int; + nlist : list of Cache_link; + retval : array of byte; + flag=0; + nlist=nil; + retval=nil; + stale=0; + lock.obtain(); + hash = hashasu(name,HASHSIZE); + tmp:=tab[hash]; + for(;tmp!=nil;tmp = tl tmp){ + link:=hd tmp; + if (link.name==name){ + if(link.qid.path==qid.path && link.qid.vers==qid.vers){ + link.tag=cur_tag; + cur_tag++; + flag = 1; + retval = (hd tmp).contents; + } else { # cache is stale + lru--; if(lru<0) lru = 0; + link.tag = lru; + stale = 1; + } + } + nlist = link :: nlist; + } + tab[hash]=nlist; + if (flag && (dbg_log!=nil)) + sys->fprint(dbg_log,"Found %s in cache, cur_tag is %d\n",name,cur_tag); + if (stale){ + if (dbg_log!=nil) + sys->fprint(dbg_log,"Stale %s in cache\n",name); + throw_out(); + } + lock.release(); + return (flag,retval); +} + +throw_out() +{ + nlist : list of Cache_link; + for(i:=0;i<HASHSIZE;i++){ + tmp:=tab[i]; + for(;tmp!=nil;tmp = tl tmp) + if ((hd tmp).tag==lru) + break; + if (tmp!=nil) + break; + } + if (i==HASHSIZE && (dbg_log!=nil)) + sys->fprint(dbg_log,"LRU not found!!!\n"); + # now, the lru is in tab[i]... + nlist=nil; + for(;tab[i]!=nil;tab[i]=tl tab[i]){ + if ((hd tab[i]).tag==lru){ + if (dbg_log!=nil) + sys->fprint(dbg_log,"Throwing out %s\n",(hd tab[i]).name); + cur_size-=(hd tab[i]).length; + tab[i] = tl tab[i]; + } + if (tab[i]!=nil) + nlist = (hd tab[i]) :: nlist; + if (tab[i]==nil) break; + } + lru=find_lru(); + if (dbg_log!=nil) + sys->fprint(dbg_log,"New lru is %d",lru); + tab[i] = nlist; +} + +find_lru() : int +{ + min := cur_tag; + for(i:=0;i<HASHSIZE;i++){ + tmp:=tab[i]; + for(;tmp!=nil;tmp = tl tmp) + if ((hd tmp).tag<min) + min=(hd tmp).tag; + } + return min; +} + +cache_init(log : ref Sys->FD, csize : int) +{ + n : int; + for(n=0;n<HASHSIZE;n++) + tab[n]= nil; + lru=0; + cur_size=0; + cache_size = csize*1024; + sys = load Sys Sys->PATH; + locks = load Lock Lock->PATH; + locks->init(); + lock = Semaphore.new(); + dbg_log = log; + if (dbg_log!=nil) + sys->fprint(dbg_log,"Cache initialised, max size is %d K\n",cache_size); +} + +dump() : list of (string,int,int) +{ + retval: list of (string,int,int); + lock.obtain(); + for(i:=0;i<HASHSIZE;i++){ + tmp:=tab[i]; + while(tmp!=nil){ + retval = ((hd tmp).name, (hd tmp).length, + (hd tmp).tag) :: retval; + tmp = tl tmp; + } + } + lock.release(); + return retval; +} diff --git a/appl/svc/httpd/cache.m b/appl/svc/httpd/cache.m new file mode 100644 index 00000000..15c6c59e --- /dev/null +++ b/appl/svc/httpd/cache.m @@ -0,0 +1,9 @@ +Cache : module +{ + PATH: con "/dis/svc/httpd/cache.dis"; + + cache_init: fn(log : ref Sys->FD, size : int); + insert : fn(name: string, ctents: array of byte, length : int, qid:Sys->Qid) : int; + find: fn(name : string, qid:Sys->Qid) : (int,array of byte); + dump : fn() : list of (string,int,int); +}; diff --git a/appl/svc/httpd/cgiparse.b b/appl/svc/httpd/cgiparse.b new file mode 100644 index 00000000..4ff0d87c --- /dev/null +++ b/appl/svc/httpd/cgiparse.b @@ -0,0 +1,258 @@ +implement CgiParse; + +include "sys.m"; + sys: Sys; +include "draw.m"; +include "string.m"; + str: String; +include "bufio.m"; +include "daytime.m"; + daytime : Daytime; +include "parser.m"; + parser : Parser; +include "contents.m"; +include "cache.m"; +include "httpd.m"; + Private_info: import Httpd; +include "cgiparse.m"; + +stderr : ref Sys->FD; + +cgiparse(g: ref Private_info, req: Httpd->Request): ref CgiData +{ + ret: ref CgiData; + (ok, err) := loadmodules(); + if(ok == -1) { + sys->fprint(stderr, "CgiParse: %s\n", err ); + return nil; + } + + (ok, err, ret) = parse(g, req); + + if(ok < 0){ + sys->fprint( stderr, "CgiParse: %s\n", err ); + return nil; + } + return ret; +} + +badmod(p: string): (int, string) +{ + return (-1, sys->sprint("cannot load %s: %r", p)); +} + +loadmodules(): (int, string) +{ + if( sys == nil ) + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + if(daytime == nil) + daytime = load Daytime Daytime->PATH; + if(daytime == nil) + return badmod(Daytime->PATH); + if(str == nil) + str = load String String->PATH; + if(str == nil) + return badmod(String->PATH); + if( parser == nil ) + parser = load Parser Parser->PATH; + if( parser == nil ) + return badmod(Parser->PATH); + return (0, nil); +} + +parse(g: ref Private_info, req: Httpd->Request) : (int, string, ref CgiData) +{ + bufio := g.bufio; + Iobuf: import bufio; + + host, remote, referer, httphd : string; + form: list of (string, string); + + tmstamp := daytime->time(); + + (method, version, uri, search) := (req.method, req.version, req.uri, req.search); + + if(version != ""){ + if( g.version == nil ) + return (-1, "version unknown.", nil); + if( g.bout == nil ) + return (-1, "internal error, g.bout is nil.", nil); + if( g.bin == nil ) + return (-1, "internal error, g.bin is nil.", nil); + httphd = g.version + " 200 OK\r\n" + + "Server: Inferno-Httpd\r\n" + + "MIME-version: 1.0\r\n" + + "Date: " + tmstamp + "\r\n" + + "Content-type: text/html\r\n" + + "\r\n"; + } + + hstr := ""; + lastnl := 1; + eof := 0; + while((c := g.bin.getc()) != bufio->EOF ) { + if (c == '\r' ) { + hstr[len hstr] = c; + c = g.bin.getb(); + if( c == bufio->EOF ){ + eof = 1; + break; + } + } + hstr[len hstr] = c; + if(c == '\n' ){ + if( lastnl ) + break; + lastnl = 1; + } + else + lastnl = 0; + } + host = g.host; + remote = g.remotesys; + referer = g.referer; + (cnt, header) := parseheader( hstr ); + method = str->toupper( method); + if (method == "POST") { + s := ""; + while(!eof && cnt && (c = g.bin.getc()) != '\n' ) { + s[len s] = c; + cnt--; + if( c == '\r' ) + eof = 1; + } + form = parsequery(s); + } + for (ql := parsequery(req.search); ql != nil; ql = tl ql) + form = hd ql :: form; + return (0, nil, + ref CgiData(method, version, uri, search, tmstamp, host, remote, referer, + httphd, header, form)); +} + +parseheader(hstr: string): (int, list of (string, string)) +{ + header : list of (string, string); + cnt := 0; + if( hstr == nil || len hstr == 0 ) + return (0, nil); + (n, sl) := sys->tokenize( hstr, "\r\n" ); + if( n <= 0 ) + return (0, nil); + while( sl != nil ){ + s := hd sl; + sl = tl sl; + for( i := 0; i < len s; i++ ){ + if( s[i] == ':' ){ + tag := s[0:i+1]; + val := s[i+1:]; + if( val[len val - 1] == '\r' ) + val[len val - 1] = ' '; + if( val[len val - 1] == '\n' ) + val[len val - 1] = ' '; + header = (tag, val) :: header; + if(str->tolower( tag ) == "content-length:" ){ + if( val != nil && len val > 0 ) + cnt = int val; + else + cnt = 0; + } + break; + } + } + } + return (cnt, listrev( header )); +} + +listrev(s: list of (string, string)): list of (string, string) +{ + tmp : list of (string, string); + while( s != nil ) { + tmp = hd s :: tmp; + s = tl s; + } + return tmp; +} + +getbaseip() : string +{ + buf : array of byte; + fd := sys->open( "/net/bootp", Sys->OREAD ); + if( fd != nil ){ + (n, d) := sys->fstat( fd ); + if( n >= 0 ){ + if(int d.length > 0 ) + buf = array [int d.length] of byte; + else + buf = array [128] of byte; + n = sys->read( fd, buf, len buf ); + if( n > 0 ){ + (nil, sl) := sys->tokenize( string buf[0:n], " \t\n" ); + while( sl != nil ){ + if( hd sl == "ipaddr" ){ + sl = tl sl; + break; + } + sl = tl sl; + } + if( sl != nil ) + return "http://" + (hd sl); + } + } + } + return "http://beast2"; +} + +getbase() : string +{ + fd := sys->open( "/dev/sysname", Sys->OREAD ); + if( fd != nil ){ + buf := array [128] of byte; + n := sys->read( fd, buf, len buf ); + if( n > 0 ) + return "http://" + string buf[0:n]; + } + return "http://beast2"; +} + +gethost() : string +{ + fd := sys->open( "/dev/sysname", Sys->OREAD ); + if(fd != nil) { + buf := array [128] of byte; + n := sys->read( fd, buf, len buf ); + if( n > 0 ) + return string buf[0:n]; + } + return "none"; +} + +# parse a search string of the form +# tag=val&tag1=val1... +parsequery(search : string): list of (string, string) +{ + q: list of (string, string); + tag, val : string; + if (contains(search, '?')) + (nil,search) = str->splitr(search,"?"); + while(search!=nil){ + (tag,search) = str->splitl(search,"="); + if (search != nil) { + search=search[1:]; + (val,search) = str->splitl(search,"&"); + if (search!=nil) + search=search[1:]; + q = (parser->urlunesc(tag), parser->urlunesc(val)) :: q; + } + } + return q; +} + +contains(s: string, c: int): int +{ + for (i := 0; i < len s; i++) + if (s[i] == c) + return 1; + return 0; +} diff --git a/appl/svc/httpd/cgiparse.m b/appl/svc/httpd/cgiparse.m new file mode 100644 index 00000000..af521125 --- /dev/null +++ b/appl/svc/httpd/cgiparse.m @@ -0,0 +1,22 @@ +CgiData : adt { + method : string; + version : string; + uri : string; + search : string; + tmstamp : string; + host : string; + remote : string; + referer : string; + httphd : string; + header : list of (string, string); + form : list of (string, string); +}; + +CgiParse : module +{ + PATH : con "/dis/svc/httpd/cgiparse.dis"; + cgiparse : fn( g : ref Httpd->Private_info, req: Httpd->Request): ref CgiData; + getbase : fn() : string; + gethost : fn() : string; +}; + diff --git a/appl/svc/httpd/contents.b b/appl/svc/httpd/contents.b new file mode 100644 index 00000000..5e57dd25 --- /dev/null +++ b/appl/svc/httpd/contents.b @@ -0,0 +1,192 @@ +implement Contents; + +include "sys.m"; + sys: Sys; + dbg_log : ref Sys->FD; +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; +Iobuf : import bufio; + +include "contents.m"; + +include "cache.m"; + +include "httpd.m"; + +include "string.m"; + str : String; + +Suffix: adt{ + suffix : string; + generic : string; + specific : string; + encoding : string; +}; + +suffixes: list of Suffix; + +#internal functions... +parsesuffix : fn(nil:string): (int,Suffix); + +mkcontent(generic,specific : string): ref Content +{ + c:= ref Content; + c.generic = generic; + c.specific = specific; + c.q = real 1; + return c; +} + +badmod(m: string) +{ + sys->fprint(stderr(), "contents: cannot load %s: %r\n", m); + raise "fail:bad module"; +} + +contentinit(log: ref Sys->FD) +{ + if(suffixes != nil) + return; + + sys = load Sys Sys->PATH; + + bufio = load Bufio Bufio->PATH; + if (bufio == nil) badmod(Bufio->PATH); + + str = load String String->PATH; + if (str == nil) badmod(String->PATH); + + iob := bufio->open(Httpd->HTTP_SUFF, bufio->OREAD); + if (iob==nil) { + sys->fprint(stderr(), "contents: cannot open %s: %r\n", Httpd->HTTP_SUFF); + raise "fail:no suffix file";; + } + while((s := iob.gets('\n'))!=nil) { + (i, su) := parsesuffix(s); + if (i != 0) + suffixes = su :: suffixes; + } + dbg_log = log; +} + +# classify by file name extensions + +uriclass(name : string): (ref Content, ref Content) +{ + s : Suffix; + typ, enc: ref Content; + p : string; + lis := suffixes; + typ=nil; + enc=nil; + uri:=name; + (nil,p) = str->splitr(name,"/"); + if (p!=nil) name=p; + + if(str->in('.',name)){ + (nil,p) = str->splitl(name,"."); + for(s = hd lis; lis!=nil; lis = tl lis){ + if(p == s.suffix){ + if(s.generic != nil && typ==nil) + typ = mkcontent(s.generic, s.specific); + if(s.encoding != nil && enc==nil) + enc = mkcontent(s.encoding, ""); + } + s = hd lis; + } + } + if(typ == nil && enc == nil){ + buff := array[64] of byte; + fd := sys->open(uri, sys->OREAD); + n := sys->read(fd, buff, len buff); + if(n > 0){ + tmp := string buff[0:n]; + (typ, enc) = dataclass(tmp); + } + } + return (typ, enc); +} + + +parsesuffix(line: string): (int, Suffix) +{ + s : Suffix; + if (str->in('#',line)) + (line,nil) = str->splitl(line, "#"); + if (line!=nil){ + (n,slist):=sys->tokenize(line,"\n\t "); + if (n!=4 && n!=0){ + if (dbg_log!=nil) + sys->fprint(dbg_log,"Error in suffixes file!, n=%d\n",n); + sys->print("Error in suffixes file!, n=%d\n",n); + exit; + } + s.suffix = hd slist; + slist = tl slist; + s.generic = hd slist; + if (s.generic == "-") s.generic=""; + slist = tl slist; + s.specific = hd slist; + if (s.specific == "-") s.specific=""; + slist = tl slist; + s.encoding = hd slist; + if (s.encoding == "-") s.encoding=""; + + } + if (((s.generic == "")||(s.specific == "")) && s.encoding=="") + return (0,s); + return (1,s); +} + +#classify by initial contents of file +dataclass(buf : string): (ref Content,ref Content) +{ + c,n : int; + c=0; + n = len buf; + for(; n > 0; n --){ + if(buf[c] < 16r80) + if(buf[c] < 32 && buf[c] != '\n' && buf[c] != '\r' + && buf[c] != '\t' && buf[c] != '\v') + return (nil,nil); + c++; + } + return (mkcontent("text", "plain"),nil); +} + +checkcontent(me: ref Content,oks :list of ref Content, clist : string): int +{ + ok:=oks; + try : ref Content; + if(oks == nil || me == nil) + return 1; + for(; ok != nil; ok = tl ok){ + try = hd ok; + if((try.generic==me.generic || try.generic=="*") + && (try.specific==me.specific || try.specific=="*")){ + return 1; + } + } + + sys->fprint(dbg_log,"%s/%s not found", + me.generic, me.specific); + logcontent(clist, oks); + return 1; +} + +logcontent(name : string, c : list of ref Content) +{ + buf : string; + if (dbg_log!=nil){ + for(; c!=nil; c = tl c) + buf+=sys->sprint("%s/%s ", (hd c).generic,(hd c).specific); + sys->fprint(dbg_log,"%s: %s: %s", "client", name, buf); + } +} + +stderr(): ref Sys->FD +{ + return sys->fildes(2); +} diff --git a/appl/svc/httpd/contents.m b/appl/svc/httpd/contents.m new file mode 100644 index 00000000..e8a3927b --- /dev/null +++ b/appl/svc/httpd/contents.m @@ -0,0 +1,16 @@ +Contents: module +{ + PATH: con "/dis/svc/httpd/contents.dis"; + + Content: adt{ + generic: string; + specific: string; + q: real; + }; + + contentinit: fn(log : ref Sys->FD); + mkcontent: fn(specific,generic : string): ref Content; + uriclass: fn(name : string): (ref Content, ref Content); + checkcontent: fn(me: ref Content,oks :list of ref Content, + clist : string): int; +}; diff --git a/appl/svc/httpd/date.b b/appl/svc/httpd/date.b new file mode 100644 index 00000000..9a8ff10a --- /dev/null +++ b/appl/svc/httpd/date.b @@ -0,0 +1,264 @@ +implement Date; + +include "sys.m"; + sys: Sys; + +include "daytime.m"; + daytime : Daytime; + +Tm: import daytime; + +include "date.m"; + + # print dates in the format + # Wkd, DD Mon YYYY HH:MM:SS GMT + # parse dates of formats + # Wkd, DD Mon YYYY HH:MM:SS GMT + # Weekday, DD-Mon-YY HH:MM:SS GMT + # Wkd Mon ( D|DD) HH:MM:SS YYYY + # plus anything similar + +SEC2MIN: con 60; +SEC2HOUR: con (60*SEC2MIN); +SEC2DAY: con (24*SEC2HOUR); + +# days per month plus days/year + +dmsize := array[] of { + 365, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 +}; + +ldmsize := array[] of { + 366, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 +}; + +# return the days/month for the given year + + +weekdayname := array[] of { + "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" +}; + +wdayname := array[] of { + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" +}; + + +monname := array[] of { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" +}; + + +init() +{ + sys = load Sys Sys->PATH; + daytime = load Daytime Daytime->PATH; + if (daytime==nil){ + sys->print("daytime load: %r\n"); + exit; + } +} + +yrsize(yr : int): array of int +{ + if(yr % 4 == 0 && (yr % 100 != 0 || yr % 400 == 0)) + return ldmsize; + else + return dmsize; +} + +tolower(c: int): int +{ + if(c >= 'A' && c <= 'Z') + return c - 'A' + 'a'; + return c; +} + + +isalpha(c: int): int +{ + return c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z'; +} + + +isdig(c: int): int +{ + return c >= '0' && c <= '9'; +} + + +dateconv(t: int): string +{ + tm : ref Tm; + tm = daytime->gmt(t); + return sys->sprint("%s, %.2d %s %.4d %.2d:%.2d:%.2d GMT", + wdayname[tm.wday], tm.mday, monname[tm.mon], tm.year+1900, + tm.hour, tm.min, tm.sec); +} + + +dateword(date : string): (string,string) +{ + p : string; + i:=0; + p = ""; + while((i<len date) && !isalpha(date[i]) && !isdig(date[i])) + i++; + while((i<len date) && isalpha(date[i])){ + p[len p] = tolower(date[i]); + i++; + } + return (date[i:],p); +} + + +datenum(date : string): (string, int) +{ + n, i : int; + i=0; + while((i<len date) && !isdig(date[i])) + i++; + if(i == len date) + return (nil, -1); + n = 0; + while((i<len date) && isdig(date[i])){ + n = n * 10 + date[i] - '0'; + i++; + } + return (date[i:], n); +} + + + # parse a date and return the seconds since the epoch + # return 0 for a failure + +# could be big? +date2sec(date : string): int +{ + tm : Tm; + buf : string; + + # Weekday|Wday + + (date,buf) = dateword(date); + tm.wday = dateindex(buf, wdayname); + if(tm.wday < 0) + tm.wday = dateindex(buf, weekdayname); + + if(tm.wday < 0) + return 0; + + # check for the two major formats + + (date,buf) = dateword(date); + tm.mon = dateindex(buf, monname); + if(tm.mon >= 0){ + # MM + (date, tm.mday) = datenum(date); + if(tm.mday < 1 || tm.mday > 31) + return 0; + + # HH:MM:SS + (date, tm.hour) = datenum(date); + if(tm.hour < 0 || tm.hour >= 24) + return 0; + (date, tm.min) = datenum(date); + if(tm.min < 0 || tm.min >= 60) + return 0; + (date, tm.sec) = datenum(date); + if(tm.sec < 0 || tm.sec >= 60) + return 0; + + + # YYYY + (nil, tm.year) = datenum(date); + if(tm.year < 70 || tm.year > 99 && tm.year < 1970) + return 0; + if(tm.year >= 1970) + tm.year -= 1900; + }else{ + # MM-Mon-(YY|YYYY) + (date, tm.mday) = datenum(date); + if(tm.mday < 1 || tm.mday > 31) + return 0; + (date,buf) = dateword(date); + tm.mon = dateindex(buf, monname); + if(tm.mon < 0 || tm.mon >= 12) + return 0; + (date, tm.year) = datenum(date); + if(tm.year < 70 || tm.year > 99 && tm.year < 1970) + return 0; + if(tm.year >= 1970) + tm.year -= 1900; + + # HH:MM:SS + (date, tm.hour) = datenum(date); + if(tm.hour < 0 || tm.hour >= 24) + return 0; + (date, tm.min) = datenum(date); + if(tm.min < 0 || tm.min >= 60) + return 0; + (date, tm.sec) = datenum(date); + if(tm.sec < 0 || tm.sec >= 60) + return 0; + + # timezone + (date,buf)=dateword(date); + if(buf[0:3]!="gmt") + return 0; + } + + tm.zone="GMT"; + return gmtm2sec(tm); +} + +lowercase(name:string): string +{ + p: string; + for(i:=0;i<len name;i++) + p[i]=tolower(name[i]); + return p; +} + +dateindex(d : string, tab : array of string): int +{ + for(i := 0; i < len tab; i++) + if (lowercase(tab[i]) == d) + return i; + return -1; +} + + +# compute seconds since Jan 1 1970 GMT + +gmtm2sec(tm:Tm): int +{ + secs,i : int; + d2m: array of int; + secs=0; + + #seconds per year + tm.year += 1900; + if(tm.year < 1970) + return 0; + for(i = 1970; i < tm.year; i++){ + d2m = yrsize(i); + secs += d2m[0] * SEC2DAY; + } + + + #seconds per month + d2m = yrsize(tm.year); + for(i = 0; i < tm.mon; i++) + secs += d2m[i+1] * SEC2DAY; + + #secs in last month + secs += (tm.mday-1) * SEC2DAY; + + #hours, minutes, seconds + secs += tm.hour * SEC2HOUR; + secs += tm.min * SEC2MIN; + secs += tm.sec; + + return secs; +} diff --git a/appl/svc/httpd/date.m b/appl/svc/httpd/date.m new file mode 100644 index 00000000..eafd6b49 --- /dev/null +++ b/appl/svc/httpd/date.m @@ -0,0 +1,11 @@ + +Date: module{ + PATH : con "/dis/svc/httpd/date.dis"; + + init: fn(); + dateconv: fn(secs :int): string; # returns an http formatted + # date representing secs. + date2sec: fn(foo:string): int; # parses a date and returns + # number of secs since the + # epoch that it represents. +}; diff --git a/appl/svc/httpd/echo.b b/appl/svc/httpd/echo.b new file mode 100644 index 00000000..686a86d2 --- /dev/null +++ b/appl/svc/httpd/echo.b @@ -0,0 +1,89 @@ +implement echo; + +include "sys.m"; + sys: Sys; +stderr: ref Sys->FD; +include "bufio.m"; + +include "draw.m"; +draw : Draw; + +include "cache.m"; +include "contents.m"; +include "httpd.m"; + Private_info: import Httpd; + +include "cgiparse.m"; +cgiparse: CgiParse; + +echo: module +{ + init: fn(g: ref Private_info, req: Httpd->Request); +}; + +init(g: ref Private_info, req: Httpd->Request) +{ + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + cgiparse = load CgiParse CgiParse->PATH; + if( cgiparse == nil ) { + sys->fprint( stderr, "echo: cannot load %s: %r\n", CgiParse->PATH); + return; + } + + send(g, cgiparse->cgiparse(g, req)); +} + +send(g: ref Private_info, cgidata: ref CgiData ) +{ + bufio := g.bufio; + Iobuf: import bufio; + if( cgidata == nil ){ + g.bout.flush(); + return; + } + + g.bout.puts( cgidata.httphd ); + + g.bout.puts("<head><title>Echo</title></head>\r\n"); + g.bout.puts("<body><h1>Echo</h1>\r\n"); + g.bout.puts(sys->sprint("You requested a %s on %s", + cgidata.method, cgidata.uri)); + if(cgidata.search!=nil) + g.bout.puts(sys->sprint(" with search string %s", cgidata.search)); + g.bout.puts(".\n"); + + g.bout.puts("Your client sent the following headers:<p><pre>"); + g.bout.puts( "Client: " + cgidata.remote + "\n" ); + g.bout.puts( "Date: " + cgidata.tmstamp + "\n" ); + g.bout.puts( "Version: " + cgidata.version + "\n" ); + while( cgidata.header != nil ){ + (tag, val) := hd cgidata.header; + g.bout.puts( tag + " " + val + "\n" ); + cgidata.header = tl cgidata.header; + } + + g.bout.puts("</pre>\n"); + if (cgidata.form != nil){ + i := 0; + g.bout.puts("</pre>"); + g.bout.puts("Your client sent the following form data:<p>"); + g.bout.puts("<table>\n"); + while(cgidata.form!=nil){ + (tag, val) := hd cgidata.form; + g.bout.puts(sys->sprint("<tr><td>%d</td><td><I> ",i)); + g.bout.puts(tag); + g.bout.puts("</I></td> "); + g.bout.puts("<td><B> "); + g.bout.puts(val); + g.bout.puts("</B></td></tr>\n"); + g.bout.puts("\n"); + cgidata.form = tl cgidata.form; + i++; + } + g.bout.puts("</table>\n"); + } + g.bout.puts("</body>\n"); + g.bout.flush(); +} + diff --git a/appl/svc/httpd/httpd.b b/appl/svc/httpd/httpd.b new file mode 100644 index 00000000..e8cf84ea --- /dev/null +++ b/appl/svc/httpd/httpd.b @@ -0,0 +1,721 @@ +implement Httpd; + +include "sys.m"; + sys: Sys; + +Dir: import sys; +FD : import sys; + +include "draw.m"; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "string.m"; + str: String; + +include "readdir.m"; + readdir: Readdir; + +include "daytime.m"; + daytime : Daytime; + +include "cache.m"; + cache : Cache; + +include "contents.m"; + contents: Contents; + Content: import contents; + +include "httpd.m"; + +include "parser.m"; + parser : Parser; + +include "date.m"; + date: Date; + +include "redirect.m"; + redir: Redirect; + +include "alarms.m"; + alarms: Alarms; + Alarm: import alarms; + +# globals + +cache_size: int; +port := "80"; +addr: string; +stderr : ref FD; +dbg_log, logfile: ref FD; +debug: int; +my_domain: string; + +usage() +{ + sys->fprint(stderr, "usage: httpd [-c num] [-D] [-a servaddr]\n"); + raise "fail:usage"; +} + +atexit(g: ref Private_info) +{ + debug_print(g,"At exit from httpd, closing fds. \n"); + g.bin.close(); + g.bout.close(); + g.bin=nil; + g.bout=nil; + exit; +} + +debug_print(g : ref Private_info,message : string) +{ + if (g.dbg_log!=nil) + sys->fprint(g.dbg_log,"%s",message); +} + +parse_args(args : list of string) +{ + while(args!=nil){ + case (hd args){ + "-c" => + args = tl args; + cache_size = int hd args; + "-D" => + debug=1; + "-p" => + args = tl args; + port = hd args; + "-a" => + args = tl args; + addr = hd args; + } + args = tl args; + } +} + +badmod(m: string) +{ + sys->fprint(stderr, "httpd: cannot load %s: %r\n", m); + raise "fail:bad module"; +} + +init(nil: ref Draw->Context, argv: list of string) +{ + # Load global modules. + sys = load Sys Sys->PATH; + stderr = sys->fildes(2); + + bufio = load Bufio Bufio->PATH; + if (bufio==nil) badmod(Bufio->PATH); + + str = load String String->PATH; + if (str == nil) badmod(String->PATH); + + date = load Date Date->PATH; + if(date == nil) badmod(Date->PATH); + + readdir = load Readdir Readdir->PATH; + if(readdir == nil) badmod(Readdir->PATH); + + daytime = load Daytime Daytime->PATH; + if(daytime == nil) badmod(Daytime->PATH); + + contents = load Contents Contents->PATH; + if(contents == nil) badmod(Contents->PATH); + + cache = load Cache Cache->PATH; + if(cache == nil) badmod(Cache->PATH); + + alarms = load Alarms Alarms->PATH; + if(alarms == nil) badmod(Alarms->PATH); + + redir = load Redirect Redirect->PATH; + if(redir == nil) badmod(Redirect->PATH); + + parser = load Parser Parser->PATH; + if(parser == nil) badmod(Parser->PATH); + + logfile=sys->create(HTTPLOG,Sys->ORDWR,8r666); + if (logfile==nil) { + sys->fprint(stderr, "httpd: cannot open %s: %r\n", HTTPLOG); + raise "cannot open http log"; + } + + # parse arguments to httpd. + + cache_size=5000; + debug = 0; + parse_args(argv); + if (debug==1){ + dbg_log=sys->create(DEBUGLOG,Sys->ORDWR,8r666); + if (dbg_log==nil){ + sys->print("debug log open: %r\n"); + exit; + } + }else + dbg_log=nil; + sys->fprint(dbg_log,"started at %s \n",daytime->time()); + + # initialisation routines + contents->contentinit(dbg_log); + cache->cache_init(dbg_log,cache_size); + redir->redirect_init(REWRITE); + date->init(); + parser->init(); + my_domain=sysname(); + if(addr == nil){ + if(port != nil) + addr = "tcp!*!"+port; + else + addr = "tcp!*!80"; + } + (ok, c) := sys->announce(addr); + if(ok < 0) { + sys->fprint(stderr, "can't announce %s: %r\n", addr); + exit; + } + sys->fprint(logfile,"************ Charon Awakened at %s\n", + daytime->time()); + for(;;) + doit(c); + exit; +} + + +doit(c: Sys->Connection) +{ + (ok, nc) := sys->listen(c); + if(ok < 0) { + sys->fprint(stderr, "listen: %r\n"); + exit; + } + if (dbg_log!=nil) + sys->fprint(dbg_log,"spawning connection.\n"); + spawn service_req(nc); +} + +service_req(nc : Sys->Connection) +{ + buf := array[64] of byte; + l := sys->open(nc.dir+"/remote", sys->OREAD); + n := sys->read(l, buf, len buf); + if(n >= 0) + if (dbg_log!=nil) + sys->fprint(dbg_log,"New client http: %s %s", nc.dir, + string buf[0:n]); + # wait for a call (or an error) + # start a process for the service + g:= ref Private_info; + g.bufio = bufio; + g.dbg_log=dbg_log; + g.logfile = logfile; + g.modtime=0; + g.entity = parser->initarray(); + g.mydomain = my_domain; + g.version = "HTTP/1.0"; + g.cache = cache; + g.okencode=nil; + g.oktype=nil; + g.getcerr=""; + g.parse_eof=0; + g.eof=0; + g.remotesys=getendpoints(nc.dir); + debug_print(g,"opening in for "+string buf[0:n]+"\n"); + g.bin= bufio->open(nc.dir+"/data",bufio->OREAD); + if (g.bin==nil){ + sys->print("bin open: %r\n"); + exit; + } + debug_print(g,"opening out for "+string buf[0:n]+"\n"); + g.bout= bufio->open(nc.dir+"/data",bufio->OWRITE); + if (g.bout==nil){ + sys->print("bout open: %r\n"); + exit; + } + debug_print(g,"calling parsereq for "+string buf[0:n]+"\n"); + parsereq(g); + atexit(g); +} + +parsereq(g: ref Private_info) +{ + meth, v,magic,search,uri,origuri,extra : string; + # 15 minutes to get request line + a := Alarm.alarm(15*1000*60); + meth = getword(g); + if(meth == nil){ + parser->logit(g,sys->sprint("no method%s", g.getcerr)); + a.stop(); + parser->fail(g,Syntax,""); + } + uri = getword(g); + if(uri == nil || len uri == 0){ + parser->logit(g,sys->sprint("no uri: %s%s", meth, g.getcerr)); + a.stop(); + parser->fail(g,Syntax,""); + } + v = getword(g); + extra = getword(g); + a.stop(); + if(extra != nil){ + parser->logit(g,sys->sprint( + "extra header word '%s'%s", + extra, g.getcerr)); + parser->fail(g,Syntax,""); + } + case v { + "" => + if(meth!="GET"){ + parser->logit(g,sys->sprint("unimplemented method %s%s", meth, g.getcerr)); + parser->fail(g,Unimp, meth); + } + + "HTTP/V1.0" or "HTTP/1.0" or "HTTP/1.1" => + if((meth != "GET") && (meth!= "HEAD") && (meth!="POST")){ + parser->logit(g,sys->sprint("unimplemented method %s", meth)); + parser->fail(g,Unimp, meth); + } + * => + parser->logit(g,sys->sprint("method %s uri %s%s", meth, uri, g.getcerr)); + parser->fail(g,UnkVers, v); + } + + # the fragment is not supposed to be sent + # strip it because some clients send it + + (uri,extra) = str->splitl(uri, "#"); + if(extra != nil) + parser->logit(g,sys->sprint("fragment %s", extra)); + + # munge uri for search, protection, and magic + (uri, search) = stripsearch(uri); + uri = compact_path(parser->urlunesc(uri)); +# if(uri == SVR_ROOT) +# parser->fail(g,NotFound, "no object specified"); + (uri, magic) = stripmagic(uri); + debug_print(g,"stripmagic=("+uri+","+magic+")\n"); + + # normal case is just file transfer + if(magic == nil || (magic == "httpd")){ + if (meth=="POST") + parser->fail(g,Unimp,meth); # /magic does handles POST + g.host = g.mydomain; + origuri = uri; + parser->httpheaders(g,v); + uri = redir->redirect(origuri); + # must change this to implement proxies + if(uri==nil){ + send(g,meth, v, origuri, search); + }else{ + g.bout.puts(sys->sprint("%s 301 Moved Permanently\r\n", g.version)); + g.bout.puts(sys->sprint("Date: %s\r\n", daytime->time())); + g.bout.puts("Server: Charon\r\n"); + g.bout.puts("MIME-version: 1.0\r\n"); + g.bout.puts("Content-type: text/html\r\n"); + g.bout.puts(sys->sprint("URI: <%s>\r\n",parser->urlconv(uri))); + g.bout.puts(sys->sprint("Location: %s\r\n",parser->urlconv(uri))); + g.bout.puts("\r\n"); + g.bout.puts("<head><title>Object Moved</title></head>\r\n"); + g.bout.puts("<body><h1>Object Moved</h1>\r\n"); + g.bout.puts(sys->sprint( + "Your selection moved to <a href=\"%s\"> here</a>.<p></body>\r\n", + parser->urlconv(uri))); + g.bout.flush(); + } + atexit(g); + } + + # for magic we init a new program + do_magic(g,magic,uri,origuri,Request(meth, v, uri, search)); +} + +do_magic(g: ref Private_info,file, uri, origuri: string, req: Request) +{ + buf := sys->sprint("%s%s.dis", MAGICPATH, file); + debug_print(g,"looking for "+buf+"\n"); + c:= load Cgi buf; + if (c==nil){ + parser->logit(g,sys->sprint("no magic %s uri %s", file, uri)); + parser->fail(g,NotFound, origuri); + } + { + c->init(g, req); + } + exception{ + "fail:*" => + return; + } +} + +send(g: ref Private_info,name, vers, uri, search : string) +{ + typ,enc : ref Content; + w : string; + n, bad, force301: int; + if(search!=nil) + parser->fail(g,NoSearch, uri); + + # figure out the type of file and send headers + debug_print( g, "httpd->send->open(" + uri + ")\n" ); + fd := sys->open(uri, sys->OREAD); + if(fd == nil){ + dbm := sys->sprint( "open failed: %r\n" ); + debug_print( g, dbm ); + notfound(g,uri); + } + (i,dir):=sys->fstat(fd); + if(i< 0) + parser->fail(g,Internal,""); + if(dir.mode & Sys->DMDIR){ + (nil,p) := str->splitr(uri, "/"); + if(p == nil){ + w=sys->sprint("%sindex.html", uri); + force301 = 0; + }else{ + w=sys->sprint("%s/index.html", uri); + force301 = 1; + } + fd1 := sys->open(w, sys->OREAD); + if(fd1 == nil){ + parser->logit(g,sys->sprint("%s directory %s", name, uri)); + if(g.modtime >= dir.mtime) + parser->notmodified(g); + senddir(g,vers, uri, fd, ref dir); + } else if(force301 != 0 && vers != ""){ + g.bout.puts(sys->sprint("%s 301 Moved Permanently\r\n", g.version)); + g.bout.puts(sys->sprint("Date: %s\r\n", daytime->time())); + g.bout.puts("Server: Charon\r\n"); + g.bout.puts("MIME-version: 1.0\r\n"); + g.bout.puts("Content-type: text/html\r\n"); + (nil, reluri) := str->splitstrr(parser->urlconv(w), SVR_ROOT); + g.bout.puts(sys->sprint("URI: </%s>\r\n", reluri)); + g.bout.puts(sys->sprint("Location: http://%s/%s\r\n", + parser->urlconv(g.host), reluri)); + g.bout.puts("\r\n"); + g.bout.puts("<head><title>Object Moved</title></head>\r\n"); + g.bout.puts("<body><h1>Object Moved</h1>\r\n"); + g.bout.puts(sys->sprint( + "Your selection moved to <a href=\"/%s\"> here</a>.<p></body>\r\n", + reluri)); + atexit(g); + } + fd = fd1; + uri = w; + (i,dir)=sys->fstat(fd); + if(i < 0) + parser->fail(g,Internal,""); + } + parser->logit(g,sys->sprint("%s %s %d", name, uri, int dir.length)); + if(g.modtime >= dir.mtime) + parser->notmodified(g); + n = -1; + if(vers != ""){ + (typ, enc) = contents->uriclass(uri); + if(typ == nil) + typ = contents->mkcontent("application", "octet-stream"); + bad = 0; + if(!contents->checkcontent(typ, g.oktype, "Content-Type")){ + bad = 1; + g.bout.puts(sys->sprint("%s 406 None Acceptable\r\n", g.version)); + parser->logit(g,"no content-type ok"); + }else if(!contents->checkcontent(enc, g.okencode, "Content-Encoding")){ + bad = 1; + g.bout.puts(sys->sprint("%s 406 None Acceptable\r\n", g.version)); + parser->logit(g,"no content-encoding ok"); + }else + g.bout.puts(sys->sprint("%s 200 OK\r\n", g.version)); + g.bout.puts("Server: Charon\r\n"); + g.bout.puts(sys->sprint("Last-Modified: %s\r\n", date->dateconv(dir.mtime))); + g.bout.puts(sys->sprint("Version: %uxv%ux\r\n", int dir.qid.path, dir.qid.vers)); + g.bout.puts(sys->sprint("Message-Id: <%uxv%ux@%s>\r\n", + int dir.qid.path, dir.qid.vers, g.mydomain)); + g.bout.puts(sys->sprint("Content-Type: %s/%s", typ.generic, typ.specific)); + +# if(typ.generic== "text") +# g.bout.puts(";charset=unicode-1-1-utf-8"); + + g.bout.puts("\r\n"); + if(enc != nil){ + g.bout.puts(sys->sprint("Content-Encoding: %s", enc.generic)); + g.bout.puts("\r\n"); + } + g.bout.puts(sys->sprint("Content-Length: %d\r\n", int dir.length)); + g.bout.puts(sys->sprint("Date: %s\r\n", daytime->time())); + g.bout.puts("MIME-version: 1.0\r\n"); + g.bout.puts("\r\n"); + if(bad) + atexit(g); + } + if(name == "HEAD") + atexit(g); + # send the file if it's a normal file + g.bout.flush(); + # find if its in hash.... + # if so, retrieve, if not add.. + conts : array of byte; + (i,conts) = cache->find(uri, dir.qid); + if (i==0){ + # add to cache... + conts = array[int dir.length] of byte; + sys->seek(fd,big 0,0); + n = sys->read(fd, conts, len conts); + cache->insert(uri,conts, len conts, dir.qid); + } + sys->write(g.bout.fd, conts, len conts); +} + + + +# classify a file +classify(d: ref Dir): (ref Content, ref Content) +{ + typ, enc: ref Content; + + if(d.qid.qtype&sys->QTDIR) + return (contents->mkcontent("directory", nil),nil); + (typ, enc) = contents->uriclass(d.name); + if(typ == nil) + typ = contents->mkcontent("unknown ", nil); + return (typ, enc); +} + +# read in a directory, format it in html, and send it back +senddir(g: ref Private_info,vers,uri: string, fd: ref FD, mydir: ref Dir) +{ + myname: string; + myname = uri; + if (myname[len myname-1]!='/') + myname[len myname]='/'; + (a, n) := readdir->readall(fd, Readdir->NAME); + if(vers != ""){ + parser->okheaders(g); + g.bout.puts("Content-Type: text/html\r\n"); + g.bout.puts(sys->sprint("Date: %s\r\n", daytime->time())); + g.bout.puts(sys->sprint("Last-Modified: %d\r\n", + mydir.mtime)); + g.bout.puts(sys->sprint("Message-Id: <%d%d@%s>\r\n", + int mydir.qid.path, mydir.qid.vers, g.mydomain)); + g.bout.puts(sys->sprint("Version: %d\r\n", mydir.qid.vers)); + g.bout.puts("\r\n"); + } + g.bout.puts(sys->sprint("<head><title>Contents of directory %s.</title></head>\n", + uri)); + g.bout.puts(sys->sprint("<body><h1>Contents of directory %s.</h1>\n", + uri)); + g.bout.puts("<table>\n"); + for(i := 0; i < n; i++){ + (typ, enc) := classify(a[i]); + g.bout.puts(sys->sprint("<tr><td><a href=\"%s%s\">%s</A></td>", + myname, a[i].name, a[i].name)); + if(typ != nil){ + if(typ.generic!=nil) + g.bout.puts(sys->sprint("<td>%s", typ.generic)); + if(typ.specific!=nil) + g.bout.puts(sys->sprint("/%s", + typ.specific)); + typ=nil; + } + if(enc != nil){ + g.bout.puts(sys->sprint(" %s", enc.generic)); + enc=nil; + } + g.bout.puts("</td></tr>\n"); + } + if(n == 0) + g.bout.puts("<td>This directory is empty</td>\n"); + g.bout.puts("</table></body>\n"); + g.bout.flush(); + atexit(g); +} + +stripmagic(uri : string): (string, string) +{ + prog,newuri : string; + prefix := SVR_ROOT+"magic/"; + if (!str->prefix(prefix,uri) || len newuri == len prefix) + return(uri,nil); + uri=uri[len prefix:]; + (prog,newuri)=str->splitl(uri,"/"); + return (newuri,prog); +} + +stripsearch(uri : string): (string,string) +{ + search : string; + (uri,search) = str->splitl(uri, "?"); + if (search!=nil) + search=search[1:]; + return (uri, search); +} + +# get rid of "." and ".." path components; make absolute +compact_path(origpath:string): string +{ + if(origpath == nil) + origpath = ""; + (origpath,nil) = str->splitl(origpath, "`;| "); # remove specials + (nil,olpath) := sys->tokenize(origpath, "/"); + rlpath : list of string; + for(p := olpath; p != nil; p = tl p) { + if(hd p == "..") { + if(rlpath != nil) + rlpath = tl rlpath; + } else if(hd p != ".") + rlpath = (hd p) :: rlpath; + } + cpath := ""; + if(rlpath!=nil){ + cpath = hd rlpath; + rlpath = tl rlpath; + while( rlpath != nil ) { + cpath = (hd rlpath) + "/" + cpath; + rlpath = tl rlpath; + } + } + return SVR_ROOT + cpath; +} + +getword(g: ref Private_info): string +{ + c: int; + while((c = getc(g)) == ' ' || c == '\t' || c == '\r') + ; + if(c == '\n') + return nil; + buf := ""; + for(;;){ + case c{ + ' ' or '\t' or '\r' or '\n' => + return buf; + } + buf[len buf] = c; + c = getc(g); + } +} + +getc(g : ref Private_info): int +{ + # do we read buffered or unbuffered? + # buf : array of byte; + n : int; + if(g.eof){ + debug_print(g,"eof is set in httpd\n"); + return '\n'; + } + n = g.bin.getc(); + if (n<=0) { + if(n == 0) + g.getcerr=": eof"; + else + g.getcerr=sys->sprint(": n == -1: %r"); + g.eof = 1; + return '\n'; + } + n &= 16r7f; + if(n == '\n') + g.eof = 1; + return n; +} + +# couldn't open a file +# figure out why and return and error message +notfound(g : ref Private_info,url : string) +{ + buf := sys->sprint("%r!"); + (nil,chk):=str->splitstrl(buf, "file does not exist"); + if (chk!=nil) + parser->fail(g,NotFound, url); + (nil,chk)=str->splitstrl(buf,"permission denied"); + if(chk != nil) + parser->fail(g,Unauth, url); + parser->fail(g,NotFound, url); +} + +sysname(): string +{ + n : int; + fd : ref FD; + buf := array[128] of byte; + + fd = sys->open("#c/sysname", sys->OREAD); + if(fd == nil) + return ""; + n = sys->read(fd, buf , len buf); + if(n <= 0) + return ""; + + return string buf[0:n]; +} + +sysdom(): string +{ + dn : string; + dn = csquery("sys" , sysname(), "dom"); + if(dn == nil) + dn = "who cares"; + return dn; +} + +# query the connection server +csquery(attr, val, rattr : string): string +{ + token : string; + buf := array[4096] of byte; + fd : ref FD; + n: int; + if(val == "" ){ + return nil; + } + fd = sys->open("/net/cs", sys->ORDWR); + if(fd == nil) + return nil; + sys->fprint(fd, "!%s=%s", attr, val); + sys->seek(fd, big 0, 0); + token = sys->sprint("%s=", rattr); + for(;;){ + n = sys->read(fd, buf, len buf); + if(n <= 0) + break; + name:=string buf[0:n]; + (nil,p) := str->splitstrl(name, token); + if(p != nil){ + (p,nil) = str->splitl(p, " \n"); + if(p == nil) + return nil; + return p[4:]; + } + } + return nil; +} + +getendpoint(dir, file: string): (string, string) +{ + sysf := serv := ""; + fto := sys->sprint("%s/%s", dir, file); + fd := sys->open(fto, sys->OREAD); + + if(fd !=nil) { + buf := array[128] of byte; + n := sys->read(fd, buf, len buf); + if(n>0) { + buf = buf[0:n-1]; + (sysf, serv) = str->splitl(string buf, "!"); + if (serv != nil) + serv = serv[1:]; + } + } + if(serv == nil) + serv = "unknown"; + if(sysf == nil) + sysf = "unknown"; + return (sysf, serv); +} + +getendpoints(dir: string): string +{ + (lsys, lserv) := getendpoint(dir, "local"); + (rsys, rserv) := getendpoint(dir, "remote"); + return rsys; +} diff --git a/appl/svc/httpd/httpd.debug b/appl/svc/httpd/httpd.debug new file mode 100644 index 00000000..fdd46a73 --- /dev/null +++ b/appl/svc/httpd/httpd.debug @@ -0,0 +1,27 @@ +started at Wed Jun 26 10:40:00 EDT 1996 +domain name is...william.research.bell-labs.com +Cache initialised, max size is 5120000 K +spawning connection. +New client http: /net/tcp/1 135.104.117.197!1069 +calling parsereq +In abspath, origpath = /locale/Australia_Broken-Hill, curdir = / +hitting parsejump. wordval is accept +hitting parsejump. wordval is accept-language +hitting parsejump. wordval is user-agent +hitting parsejump. wordval is connection +hitting parsejump. wordval is referer +current size is 0, adding /locale/Australia_Broken-Hill +At exit from httpd, closing fds. +spawning connection. +New client http: /net/tcp/1 135.104.117.197!1070 +calling parsereq +In abspath, origpath = /, curdir = / +At exit from httpd, closing fds. +tralia_ACT, curdir = / +current size is 0, adding /locale/Australia_ACT +At exit from httpd, closing fds. +spawning connection. +New client http: /net/tcp/4 135.104.117.197!1068 +calling parsereq +In abspath, origpath = /locale/, curdir = / +At exit from httpd, closing fds. diff --git a/appl/svc/httpd/httpd.log b/appl/svc/httpd/httpd.log new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/appl/svc/httpd/httpd.log diff --git a/appl/svc/httpd/httpd.m b/appl/svc/httpd/httpd.m new file mode 100644 index 00000000..e96008e9 --- /dev/null +++ b/appl/svc/httpd/httpd.m @@ -0,0 +1,49 @@ +Httpd: module { + Entity: adt{ + name : string; + value : int; + }; + + Internal, TempFail, Unimp, UnkVers, BadCont, BadReq, Syntax, + BadSearch, NotFound, NoSearch , OnlySearch, Unauth, OK : con iota; + + SVR_ROOT : con "/services/httpd/root/"; + HTTPLOG : con "/services/httpd/httpd.log"; + DEBUGLOG : con "/services/httpd/httpd.debug"; + HTTP_SUFF : con "/services/httpd/httpd.suff"; + REWRITE : con "/services/httpd/httpd.rewrite"; + MAGICPATH : con "/dis/svc/httpd/"; # must end in / + + Private_info : adt{ + # used in parse and httpd + bufio: Bufio; + bin,bout : ref Bufio->Iobuf; + logfile,dbg_log : ref Sys->FD; + cache : Cache; + eof : int; + getcerr : string; + version : string; + okencode, oktype : list of ref Contents->Content; + host : string; # initialized to mydomain just + # before parsing header + remotesys, referer : string; + modtime : int; + # used by /magic for reading body + clength : int; + ctype : string; + #only used in parse + wordval : string; + tok,parse_eof : int; + mydomain,client : string; + entity: array of Entity; + oklang : list of ref Contents->Content; + }; + Request: adt { + method, version, uri, search: string; + }; + init: fn(ctxt: ref Draw->Context, argv: list of string); +}; + +Cgi: module{ + init: fn(g: ref Httpd->Private_info, req: Httpd->Request); +}; diff --git a/appl/svc/httpd/httpd.rewrite b/appl/svc/httpd/httpd.rewrite new file mode 100644 index 00000000..73d49c84 --- /dev/null +++ b/appl/svc/httpd/httpd.rewrite @@ -0,0 +1,10 @@ +# syntax: pattern replacement +# The most specific pattern wins, and is applied once (no rescanning). +# Leave off trailing slash if pattern is a directory. +# Replacements starting with http:// return "Permanently moved" +# message. +# e.g the following line aliases requests for / to requests for +# /usr/mig/html + +# / /usr/mig/html + diff --git a/appl/svc/httpd/httpd.suff b/appl/svc/httpd/httpd.suff new file mode 100644 index 00000000..dbc599d2 --- /dev/null +++ b/appl/svc/httpd/httpd.suff @@ -0,0 +1,110 @@ +#suffix generic type specific type encoding +.C text plain - # C++ program +.Z - - x-compress +.a application octet-stream - # [Mosaic] +.ada text plain - # ada program +.ai application postscript - # [Mosaic] +.aif audio x-aiff - +.aifc audio x-aiff - +.aiff audio x-aiff - +.au audio basic - # sun audio +.avi video x-msvideo - # [Mosaic] +.awk text plain - # awk program +.bas text plain - # basic program +.bbl text plain - # BibTex output +.bcpio application x-bcpio - # [Mosaic] +.bib text plain - # BibTex input +.c text plain - # C program +.c++ text plain - # C++ program +.cc text plain - # [Mosaic] +.cdf application x-netcdf - +.cpio application x-cpio - +.cpp text plain - # DOS C++ program +.dat text plain - # AMPL et al. +.diff text plain - +.dvi application x-dvi - # TeX output +.enc application octet-stream - # encrypted file +.eps application postscript - +.etx text x-setext - # [Mosaic] +.exe application octet-stream - # DOS executable +.executable application octet-stream - # DOS executable +.exz application octet-stream x-gzip # gziped DOS executable +.f text plain - # fortran-77 program +.flc video x-flc - +.fli video x-fli - +.gif image gif - +.gtar application x-gtar - # [Mosaic] +.gz - - x-gzip # gziped file +.h text plain - # C header file +.hdf application x-hdf - +.hqx application octet-stream - # Mac BinHex +.htm text html - +.html text html - +.ief image ief - # [Mosaic] +.jfif image jpeg - # [Mosaic] +.jfif-tbnl image jpeg - # [Mosaic] +.jpe image jpeg - # [Mosaic] +.jpeg image jpeg - +.jpg image jpeg - +.latex application x-latex - # [Mosaic] +.ltx application x-latex - +.man application x-troff-man - # [Mosaic] +.me application x-troff-me - # [Mosaic] +.mime message rfc822 - # [Mosaic] +.mod text plain - # AMPL et al. +.mov video quicktime - # [Mosaic] +.movie video x-sgi-movie - # [Mosaic] +.mpe video mpeg - # [Mosaic] +.mpeg video mpeg - +.mpg video mpeg - +.ms application x-troff-ms - # [Mosaic] +.mv video x-sgi-movie - # [Mosaic] +.nc application x-netcdf - # [Mosaic] +.o application octet-stream - # [Mosaic] +.oda application oda - # [Mosaic] +.pbm image x-portable-bitmap - # [Mosaic] +.pdf application pdf - # Adobe Portable Document Format +.pgm image x-portable-graymap - # [Mosaic] +.pl text plain - # [Mosaic] +.pnm image x-portable-anymap - # [Mosaic] +.ppm image x-portable-pixmap - # [Mosaic] +.ps application postscript - +.qt video quicktime - # [Mosaic] +.r text plain - # ratfor program +.ras image x-cmu-rast - # [Mosaic] +.rc text plain - # rc +.rfr text plain - # refer +.rgb image x-rgb - # [Mosaic] +.roff application x-troff - # [Mosaic] +.rtf application rtf - # [Mosaic] +.rtx text richtext - # MIME richtext [Mosaic] +.sh application x-shar - +.shar application x-shar - +.snd audio basic - +.sv4cpio application x-sv4cpio - # [Mosaic] +.sv4crc application x-sv4crc - # [Mosaic] +.t application x-troff - # [Mosaic] +.tar application x-tar - # [Mosaic] +.taz application x-tar x-compress +.tcl application x-tcl - +.tex application x-tex - # Tex input +.texi application x-texinfo - # [Mosaic] +.texinfo application x-texinfo - # [Mosaic] +.text text plain - # [Mosaic] +.tgz application x-tar x-gzip +.tif image tiff - +.tiff image tiff - +.toc text plain - # table of contents +.tr application x-troff - # [Mosaic] +.trz application x-tar x-compress +.tsv text tab-separated-values - # [Mosaic] +.txt text plain - +.ustar application x-ustar - # [Mosaic] +.wav audio x-wav - +.wsrc application x-wais-source - # [Mosaic] +.xbm image x-xbitmap - # X bitmap +.xpm image x-xpixmap - # [Mosaic] +.xwd image x-xwindowdump - # [Mosaic] +.z - - x-compress +.Z - - x-compress +.zip application zip - diff --git a/appl/svc/httpd/imagemap.b b/appl/svc/httpd/imagemap.b new file mode 100644 index 00000000..34a08edc --- /dev/null +++ b/appl/svc/httpd/imagemap.b @@ -0,0 +1,251 @@ +implement imagemap; + +include "sys.m"; + sys : Sys; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "draw.m"; + draw: Draw; + +include "cache.m"; +include "contents.m"; + +include "httpd.m"; + Private_info: import Httpd; + OnlySearch, BadSearch, NotFound: import Httpd; + g : ref Private_info; + +include "parser.m"; + parser : Parser; + +include "daytime.m"; + daytime: Daytime; + +include "string.m"; + str : String; + +imagemap : module +{ + init: fn(g : ref Private_info, req: Httpd->Request); +}; + +Point : adt { + x,y : int; +}; + +me : string; + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "imagemap: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init(k : ref Private_info, req: Httpd->Request) +{ + dest, s : string; + sys = load Sys "$Sys"; + draw = load Draw "$Draw"; + + daytime = load Daytime Daytime->PATH; + if(daytime == nil) badmod(Daytime->PATH); + + parser = load Parser Parser->PATH; + if(parser == nil) badmod(Parser->PATH); + + str = load String String->PATH; + if (str == nil) badmod(String->PATH); + + me = "imagemap"; + + g=k; + bufio=g.bufio; + parser->init(); + parser->httpheaders(g,req.version); + dest = translate(req.uri, req.search); + if(dest == nil){ + if(req.version!= ""){ + parser->okheaders(g); + g.bout.puts(sys->sprint("Date: %s\r\n", daytime->time())); + g.bout.puts("Content-type: text/html\r\n"); + g.bout.puts("\r\n"); + } + g.bout.puts("<head><title>Nothing Found</title></head><body>\n"); + g.bout.puts("Nothing satisfying your search request could be found.\n</body>\n"); + return; + } + + g.bout.puts(sys->sprint("%s 301 Moved Permanently\r\n", g.version)); + g.bout.puts(sys->sprint("Date: %s\r\n", daytime->time())); + g.bout.puts("Server: Charon\r\n"); + g.bout.puts("MIME-version: 1.0\r\n"); + g.bout.puts("Content-type: text/html\r\n"); + (s,nil)=str->splitl(dest, ":"); + if(s!=nil){ + g.bout.puts(sys->sprint("URI: <%s>\r\n", parser->urlconv(dest))); + g.bout.puts(sys->sprint("Location: %s\r\n", parser->urlconv(dest))); + }else if(dest[0] == '/'){ + g.bout.puts(sys->sprint("URI: <%s>\r\n",parser->urlconv(dest))); + g.bout.puts(sys->sprint("Location: http://%s%s\r\n", parser->urlconv(g.mydomain), parser->urlconv(dest))); + } else { + (req.uri,s) = str->splitr(req.uri, "/"); + g.bout.puts(sys->sprint("URI: <%s/%s>\r\n", parser->urlconv(req.uri), parser->urlconv(dest))); + g.bout.puts(sys->sprint("Location: http://%s%s/%s\r\n", parser->urlconv(g.mydomain), parser->urlconv(req.uri), parser->urlconv(dest))); + } + g.bout.puts("\r\n"); + g.bout.puts("<head><title>Object Moved</title></head>\r\n"); + g.bout.puts("<body><h1>Object Moved</h1></body>\r\n"); + if(dest[0] == '/') + g.bout.puts(sys->sprint("Your selection mapped to <a href=\"%s\"> here</a>.<p>\r\n", parser->urlconv(dest))); + else + g.bout.puts(sys->sprint("Your selection mapped to <a href=\"%s/%s\"> here</a>.<p>\r\n", parser->urlconv(req.uri), parser->urlconv(dest))); +} + + +translate(uri, search : string) : string +{ + b : ref Iobuf; + p, c, q, start : Point; + close, d : real; + line, To, def, s : string; + ok, n, inside, r : int; + (pth,nil):=str->splitr(uri,"/"); + # sys->print("pth is %s",pth); + if(search == nil) + parser->fail(g,OnlySearch, me); + (p, ok) = pt(search); + if(!ok) + parser->fail(g,BadSearch, me); + + b = bufio->open(uri, bufio->OREAD); + if(b == nil){ + sys->fprint(sys->fildes(2), "imagemap: cannot open %s: %r\n", uri); + parser->fail(g, NotFound, uri); + } + To = ""; + def = ""; + close = 0.; + while((line = b.gets('\n'))!=nil){ + line=line[0:len line-1]; + + (s, line) = getfield(line); + if(s== "rect"){ + (s, line) = getfield(line); + (q, ok) = pt(s); + if(!ok || q.x > p.x || q.y > p.y) + continue; + (s, line) = getfield(line); + (q, ok) = pt(s); + if(!ok || q.x < p.x || q.y < p.y) + continue; + (s, nil) = getfield(line); + return pth+s; + }else if(s== "circle"){ + (s, line) = getfield(line); + (c, ok) = pt(s); + if(!ok) + continue; + (s, line) = getfield(line); + (r,nil) = str->toint(s,10); + (s, line) = getfield(line); + d = real (r * r); + if(d >= dist(p, c)) + return pth+s; + }else if(s=="poly"){ + (s, line) = getfield(line); + (start, ok) = pt(s); + if(!ok) + continue; + inside = 0; + c = start; + for(n = 1; ; n++){ + (s, line) = getfield(line); + (q, ok) = pt(s); + if(!ok) + break; + inside = polytest(inside, p, c, q); + c = q; + } + inside = polytest(inside, p, c, start); + if(n >= 3 && inside) + return pth+s; + }else if(s== "point"){ + (s, line) = getfield(line); + (q, ok) = pt(s); + if(!ok) + continue; + d = dist(p, q); + (s, line) = getfield(line); + if(d == 0.) + return pth+s; + if(close == 0. || d < close){ + close = d; + To = s; + } + }else if(s == "default"){ + (def, line) = getfield(line); + } + } + if(To == nil) + To = def; + return pth+To; +} + + +polytest(inside : int,p, b, a : Point) : int{ + pa, ba : Point; + + if(b.y>a.y){ + pa=sub(p, a); + ba=sub(b, a); + }else{ + pa=sub(p, b); + ba=sub(a, b); + } + if(0<=pa.y && pa.y<ba.y && pa.y*ba.x<=pa.x*ba.y) + inside = !inside; + return inside; +} + + +sub(p, q : Point) : Point { + return (Point)(p.x-q.x, p.y-q.y); +} + + +dist(p, q : Point) : real { + p.x -= q.x; + p.y -= q.y; + return real (p.x * p.x + p.y *p.y); +} + + +pt(s : string) : (Point, int) { + p : Point; + x, y : string; + + if(s[0] == '(') + s=s[1:]; + (s,nil)=str->splitl(s, ")"); + p = Point(0, 0); + (x,y) = str->splitl(s, ","); + if(x == s) + return (p, 0); + (p.x,nil) = str->toint(x,10); + if(y==nil) + return (p, 0); + y=y[1:]; + (p.y,nil) = str->toint(y, 10); + return (p, 1); +} + + +getfield(s : string) : (string,string) { + i:=0; + while(s[i] == '\t' || s[i] == ' ') + i++; + return str->splitl(s[i:],"\t "); +} diff --git a/appl/svc/httpd/mkfile b/appl/svc/httpd/mkfile new file mode 100644 index 00000000..caba5c47 --- /dev/null +++ b/appl/svc/httpd/mkfile @@ -0,0 +1,49 @@ +<../../../mkconfig + +TARG= cache.dis\ + contents.dis\ + date.dis\ + echo.dis\ + httpd.dis\ + imagemap.dis\ + parser.dis\ + redirect.dis\ + stats.dis\ + alarms.dis\ + cgiparse.dis\ + + +MODULES=\ + cache.m\ + contents.m\ + date.m\ + httpd.m\ + parser.m\ + redirect.m\ + alarms.m\ + cgiparse.m\ + +SYSMODULES= + +LOGS= httpd.debug\ + httpd.log\ + httpd.rewrite\ + httpd.suff\ + +DISBIN=$ROOT/dis/svc/httpd + +<$ROOT/mkfiles/mkdis + +install:V: install-logs-$SHELLTYPE + +install-logs-rc install-logs-nt:V: + for (i in $LOGS) + rm -f $ROOT/services/httpd/$i && cp $i $ROOT/services/httpd/$i + # chmod 644 $ROOT/services/httpd/httpd.log + +install-logs-sh:V: + for i in $LOGS + do + rm -f $ROOT/services/httpd/$i && cp $i $ROOT/services/httpd/$i + done + # chmod 644 $ROOT/services/httpd/httpd.log diff --git a/appl/svc/httpd/parser.b b/appl/svc/httpd/parser.b new file mode 100644 index 00000000..a109d022 --- /dev/null +++ b/appl/svc/httpd/parser.b @@ -0,0 +1,861 @@ +implement Parser; + +include "sys.m"; + sys: Sys; +include "draw.m"; + draw: Draw; +include "bufio.m"; +include "string.m"; + str: String; +include "daytime.m"; + daytime: Daytime; +include "contents.m"; + contents : Contents; + Content: import contents; +include "cache.m"; +include "httpd.m"; + Entity, Private_info: import Httpd; + Internal, TempFail, Unimp, UnkVers, BadCont, BadReq, Syntax, + BadSearch, NotFound, NoSearch , OnlySearch, Unauth, OK : import Httpd; +include "parser.m"; +include "date.m"; + date : Date; +include "alarms.m"; + alarms: Alarms; + Alarm: import alarms; +include "lock.m"; + locks: Lock; + Semaphore: import locks; + +Error: adt { + num : string; + concise: string; + verbose: string; +}; + +errormsg := array[] of { + Internal => Error("500 Internal Error", "Internal Error", + "This server could not process your request due to an interal error."), + TempFail => Error("500 Internal Error", "Temporary Failure", + "The object %s is currently inaccessible.<p>Please try again later."), + Unimp => Error("501 Not implemented", "Command not implemented", + "This server does not implement the %s command."), + UnkVers => Error("501 Not Implemented", "Unknown http version", + "This server does not know how to respond to http version %s."), + BadCont => Error("501 Not Implemented", "Impossible format", + "This server cannot produce %s in any of the formats your client accepts."), + BadReq => Error("400 Bad Request", "Strange Request", + "Your client sent a query that this server could not understand."), + Syntax => Error("400 Bad Request", "Garbled Syntax", + "Your client sent a query with incoherent syntax."), + BadSearch =>Error("400 Bad Request", "Inapplicable Search", + "Your client sent a search that cannot be applied to %s."), + NotFound =>Error("404 Not Found", "Object not found", + "The object %s does not exist on this server."), + NoSearch => Error("403 Forbidden", "Search not supported", + "The object %s does not support the search command."), + OnlySearch =>Error("403 Forbidden", "Searching Only", + "The object %s only supports the searching methods."), + Unauth => Error("401 Unauthorized", "Unauthorized", + "You are not authorized to see the object %s."), + OK => Error("200 OK", "everything is fine","Groovy man"), +}; + +latin1 := array[] of { + '¡', + '¢', + '£', + '¤', + '¥', + '¦', + '§', + '¨', + '©', + 'ª', + '«', + '¬', + '', + '®', + '¯', + '°', + '±', + '²', + '³', + '´', + 'µ', + '¶', + '·', + '¸', + '¹', + 'º', + '»', + '¼', + '½', + '¾', + '¿', + 'À', + 'Á', + 'Â', + 'Ã', + 'Ä', + 'Å', + 'Æ', + 'Ç', + 'È', + 'É', + 'Ê', + 'Ë', + 'Ì', + 'Í', + 'Î', + 'Ï', + 'Ð', + 'Ñ', + 'Ò', + 'Ó', + 'Ô', + 'Õ', + 'Ö', + '×', + 'Ø', + 'Ù', + 'Ú', + 'Û', + 'Ü', + 'Ý', + 'Þ', + 'ß', + 'à', + 'á', + 'â', + 'ã', + 'ä', + 'å', + 'æ', + 'ç', + 'è', + 'é', + 'ê', + 'ë', + 'ì', + 'í', + 'î', + 'ï', + 'ð', + 'ñ', + 'ò', + 'ó', + 'ô', + 'õ', + 'ö', + '÷', + 'ø', + 'ù', + 'ú', + 'û', + 'ü', + 'ý', + 'þ', + 'ÿ', + 0, +}; + +entities :=array[] of { + Entity( "¡", '¡' ), + Entity( "¢", '¢' ), + Entity( "£", '£' ), + Entity( "¤", '¤' ), + Entity( "¥", '¥' ), + Entity( "¦", '¦' ), + Entity( "§", '§' ), + Entity( "¨", '¨' ), + Entity( "©", '©' ), + Entity( "ª", 'ª' ), + Entity( "«", '«' ), + Entity( "¬", '¬' ), + Entity( "­", '' ), + Entity( "®", '®' ), + Entity( "¯", '¯' ), + Entity( "°", '°' ), + Entity( "±", '±' ), + Entity( "²", '²' ), + Entity( "³", '³' ), + Entity( "´", '´' ), + Entity( "µ", 'µ' ), + Entity( "¶", '¶' ), + Entity( "·", '·' ), + Entity( "¸", '¸' ), + Entity( "¹", '¹' ), + Entity( "º", 'º' ), + Entity( "»", '»' ), + Entity( "¼", '¼' ), + Entity( "½", '½' ), + Entity( "¾", '¾' ), + Entity( "¿", '¿' ), + Entity( "À", 'À' ), + Entity( "Á", 'Á' ), + Entity( "Â", 'Â' ), + Entity( "Ã", 'Ã' ), + Entity( "Ä", 'Ä' ), + Entity( "Å", 'Å' ), + Entity( "Æ", 'Æ' ), + Entity( "Ç", 'Ç' ), + Entity( "È", 'È' ), + Entity( "É", 'É' ), + Entity( "Ê", 'Ê' ), + Entity( "Ë", 'Ë' ), + Entity( "Ì", 'Ì' ), + Entity( "Í", 'Í' ), + Entity( "Î", 'Î' ), + Entity( "Ï", 'Ï' ), + Entity( "Ð", 'Ð' ), + Entity( "Ñ", 'Ñ' ), + Entity( "Ò", 'Ò' ), + Entity( "Ó", 'Ó' ), + Entity( "Ô", 'Ô' ), + Entity( "Õ", 'Õ' ), + Entity( "Ö", 'Ö' ), + Entity( "&215;", '×' ), + Entity( "Ø", 'Ø' ), + Entity( "Ù", 'Ù' ), + Entity( "Ú", 'Ú' ), + Entity( "Û", 'Û' ), + Entity( "Ü", 'Ü' ), + Entity( "Ý", 'Ý' ), + Entity( "Þ", 'Þ' ), + Entity( "ß", 'ß' ), + Entity( "à", 'à' ), + Entity( "á", 'á' ), + Entity( "â", 'â' ), + Entity( "ã", 'ã' ), + Entity( "ä", 'ä' ), + Entity( "å", 'å' ), + Entity( "æ", 'æ' ), + Entity( "ç", 'ç' ), + Entity( "è", 'è' ), + Entity( "é", 'é' ), + Entity( "ê", 'ê' ), + Entity( "ë", 'ë' ), + Entity( "ì", 'ì' ), + Entity( "í", 'í' ), + Entity( "î", 'î' ), + Entity( "ï", 'ï' ), + Entity( "ð", 'ð' ), + Entity( "ñ", 'ñ' ), + Entity( "ò", 'ò' ), + Entity( "ó", 'ó' ), + Entity( "ô", 'ô' ), + Entity( "õ", 'õ' ), + Entity( "ö", 'ö' ), + Entity( "&247;", '÷' ), + Entity( "ø", 'ø' ), + Entity( "ù", 'ù' ), + Entity( "ú", 'ú' ), + Entity( "û", 'û' ), + Entity( "ü", 'ü' ), + Entity( "ý", 'ý' ), + Entity( "þ", 'þ' ), + Entity( "ÿ", 'ÿ' ), + + Entity( "&#SPACE;", ' ' ), + Entity( "&#RS;", '\n' ), + Entity( "&#RE;", '\r' ), + Entity( """, '"' ), + Entity( "&", '&' ), + Entity( "<", '<' ), + Entity( ">", '>' ), + + Entity( "CAP-DELTA", 'Δ' ), + Entity( "ALPHA", 'α' ), + Entity( "BETA", 'β' ), + Entity( "DELTA", 'δ' ), + Entity( "EPSILON", 'ε' ), + Entity( "THETA", 'θ' ), + Entity( "MU", 'μ' ), + Entity( "PI", 'π' ), + Entity( "TAU", 'τ' ), + Entity( "CHI", 'χ' ), + + Entity( "<-", '←' ), + Entity( "^", '↑' ), + Entity( "->", '→' ), + Entity( "v", '↓' ), + Entity( "!=", '≠' ), + Entity( "<=", '≤' ), + Entity( nil, 0 ), + }; + + +initarray() : array of Entity +{ + return entities; +} + +badmodule(p: string) +{ + sys->fprint(sys->fildes(2), "parse: cannot load %s: %r", p); + raise "fail:bad module"; +} + +lock: ref Semaphore; + +init() +{ + sys = load Sys Sys->PATH; + + date = load Date Date->PATH; + if (date==nil) badmodule(Date->PATH); + + daytime = load Daytime Daytime->PATH; + if(daytime == nil) badmodule(Daytime->PATH); + + contents = load Contents Contents->PATH; + if(contents == nil) badmodule(Contents->PATH); + + str = load String String->PATH; + if(str == nil) badmodule(String->PATH); + + alarms = load Alarms Alarms->PATH; + if(alarms == nil) badmodule(Alarms->PATH); + + locks = load Lock Lock->PATH; + if(locks == nil) badmodule(Lock->PATH); + locks->init(); + lock = Semaphore.new(); + date->init(); +} + +atexit(g: ref Private_info) +{ + if (g.dbg_log!=nil){ + sys->fprint(g.dbg_log,"At exit from parse, closing fds. \n"); + } + if (g.bin!=nil) + g.bufio->g.bin.close(); + if (g.bout!=nil) + g.bufio->g.bout.close(); + g.bin=nil; + g.bout=nil; + exit; +} + + +httpheaders(g: ref Private_info,vers : string) +{ + if(vers == "") + return; + g.tok = '\n'; + # 15 minutes to get request line + a := Alarm.alarm(15*1000*60); + while(lex(g) != '\n'){ + if(g.tok == Word && lex(g) == ':'){ + if (g.dbg_log!=nil) + sys->fprint(g.dbg_log,"hitting parsejump. wordval is %s\n", + g.wordval); + parsejump(g,g.wordval); + } + while(g.tok != '\n') + lex(g); + } + a.stop(); +} + + +mimeok(g: ref Private_info,name : string,multipart : int,head : list of ref Content): list of ref Content +{ + + generic, specific, s : string; + v : real; + + while(lex(g) != Word) + if(g.tok != ',') + return head; + + generic = g.wordval; + lex(g); + if(g.tok == '/' || multipart){ + if(g.tok != '/') + return head; + if(lex(g) != Word) + return head; + specific = g.wordval; + lex(g); + }else + specific = "*"; + tmp := contents->mkcontent(generic, specific); + head = tmp::head; + for(;;){ + case g.tok { + ';' => + if(lex(g) == Word){ + s = g.wordval; + if(lex(g) != '=' || lex(g) != Word) + return head; + v = 3.14; # should be strtof(g.wordval, nil); + if(s=="q") + tmp.q = v; + else + logit(g,sys->sprint( + "unknown %s param: %s %s", + name, s, g.wordval)); + } + break; + ',' => + return mimeok(g,name, multipart,head); + * => + return head; + } + lex(g); + } + return head; +} + +mimeaccept(g: ref Private_info,name : string) +{ + g.oktype = mimeok(g,name, 1, g.oktype); +} + +mimeacceptenc(g: ref Private_info,name : string) +{ + g.okencode = mimeok(g,name, 0, g.okencode); +} + +mimeacceptlang(g: ref Private_info,name : string) +{ + g.oklang = mimeok(g,name, 0, g.oklang); +} + +mimemodified(g: ref Private_info,name : string) +{ + lexhead(g); + g.modtime = date->date2sec(g.wordval); + if (g.dbg_log!=nil){ + sys->fprint(g.dbg_log,"modtime %d\n",g.modtime); + } + if(g.modtime == 0) + logit(g,sys->sprint("%s: %s", name, g.wordval)); +} + + +mimeagent(g: ref Private_info,nil : string) +{ + lexhead(g); + g.client = g.wordval; +} + +mimefrom(g: ref Private_info,nil : string) +{ + lexhead(g); +} + + +mimehost(g: ref Private_info,nil : string) +{ + h : string; + lexhead(g); + (nil,h)=str->splitr(g.wordval," \t"); + g.host = h; +} + +mimereferer(g: ref Private_info,nil : string) +{ + h : string; + lexhead(g); + (nil,h)=str->splitr(g.wordval," \t"); + g.referer = h; +} + +mimeclength(g: ref Private_info,nil : string) +{ + h : string; + lexhead(g); + (nil,h)=str->splitr(g.wordval," \t"); + g.clength = int h; +} + +mimectype(g: ref Private_info,nil : string) +{ + h : string; + lexhead(g); + (nil,h)=str->splitr(g.wordval," \t"); + g.ctype = h; +} + + +mimeignore(g: ref Private_info,nil : string) +{ + lexhead(g); +} + + +mimeunknown(g: ref Private_info,name : string) +{ + lexhead(g); + if(g.client!="") + logit(g,sys->sprint("agent %s: ignoring header %s: %s ", + g.client, name, g.wordval)); + else + logit(g,sys->sprint("ignoring header %s: %s", name, g.wordval)); +} + + +parsejump(g: ref Private_info,k : string) +{ + case k { + + "from" => + mimefrom(g,k); + "if-modified-since" => + mimemodified(g,k); + "accept" => + mimeaccept(g,k); + "accept-encoding" => + mimeacceptenc(g,k); + "accept-language" => + mimeacceptlang(g,k); + "user-agent" => + mimeagent(g,k); + "host" => + mimehost(g,k); + "referer" => + mimereferer(g,k); + "content-length" => + mimeclength(g,k); + "content-type" => + mimectype(g,k); + "authorization" or "chargeto" or "connection" or "forwarded" or + "pragma" or "proxy-agent" or "proxy-connection" or + "x-afs-tokens" or "x-serial-number" => + mimeignore(g,k); + * => + mimeunknown(g,k); + }; +} + +lex(g: ref Private_info): int +{ + g.tok = lex1(g); + return g.tok; +} + + +# rfc 822/rfc 1521 lexical analyzer +lex1(g: ref Private_info): int +{ + level, c : int; + if(g.parse_eof) + return '\n'; + +# top: + for(;;){ + c = getc(g); + case c { + '(' => + level = 1; + while((c = getc(g)) != Bufio->EOF){ + if(c == '\\'){ + c = getc(g); + if(c == Bufio->EOF) + return '\n'; + continue; + } + if(c == '(') + level++; + else if(c == ')' && level == 1){ + level--; + break; + } + else if(c == '\n'){ + c = getc(g); + if(c == Bufio->EOF) + return '\n'; + break; + if(c != ' ' && c != '\t'){ + ungetc(g); + return '\n'; + } + } + } + ' ' or '\t' or '\r' => + break; + '\n' => + if(g.tok == '\n'){ + g.parse_eof = 1; + return '\n'; + } + c = getc(g); + if(c == Bufio->EOF) + return '\n'; + if(c != ' ' && c != '\t'){ + ungetc(g); + return '\n'; + } + ')' or '<' or '>' or '[' or ']' or '@' or '/' or ',' + or ';' or ':' or '?' or '=' => + return c; + + '"' => + word(g,"\""); + getc(g); # skip the closing quote + return Word; + + * => + ungetc(g); + word(g,"\"()<>@,;:/[]?=\r\n \t"); + return Word; + } + } + return 0; +} + +# return the rest of an rfc 822, not including \r or \n +# do not map to lower case + +lexhead(g: ref Private_info) +{ + c, n: int; + n = 0; + while((c = getc(g)) != Bufio->EOF){ + if(c == '\r') + c = wordcr(g); + else if(c == '\n') + c = wordnl(g); + if(c == '\n') + break; + if(c == '\\'){ + c = getc(g); + if(c == Bufio->EOF) + break; + } + g.wordval[n++] = c; + } + g.tok = '\n'; + g.wordval= g.wordval[0:n]; +} + +word(g: ref Private_info,stop : string) +{ + c : int; + n := 0; + while((c = getc(g)) != Bufio->EOF){ + if(c == '\r') + c = wordcr(g); + else if(c == '\n') + c = wordnl(g); + if(c == '\\'){ + c = getc(g); + if(c == Bufio->EOF) + break; + }else if(str->in(c,stop)){ + ungetc(g); + g.wordval = g.wordval[0:n]; + return; + } + if(c >= 'A' && c <= 'Z') + c += 'a' - 'A'; + g.wordval[n++] = c; + } + g.wordval = g.wordval[0:n]; + # sys->print("returning from word"); +} + + +wordcr(g: ref Private_info): int +{ + c := getc(g); + if(c == '\n') + return wordnl(g); + ungetc(g); + return ' '; +} + + +wordnl(g: ref Private_info): int +{ + c := getc(g); + if(c == ' ' || c == '\t') + return c; + ungetc(g); + return '\n'; +} + + +getc(g: ref Private_info): int +{ + c := g.bufio->g.bin.getc(); + if(c == Bufio->EOF){ + g.parse_eof = 1; + return c; + } + return c & 16r7f; +} + +ungetc(g: ref Private_info) { + # this is a dirty hack, I am tacitly assuming that characters read + # from stdin will be ASCII..... + g.bufio->g.bin.ungetc(); +} + +# go from url with latin1 and escapes to utf + +urlunesc(s : string): string +{ + c, n : int; + t : string; + for(i := 0;i<len s ; i++){ + c = int s[i]; + if(c == '%'){ + n = int s[i+1]; + if(n >= '0' && n <= '9') + n = n - '0'; + else if(n >= 'A' && n <= 'F') + n = n - 'A' + 10; + else if(n >= 'a' && n <= 'f') + n = n - 'a' + 10; + else + break; + c = n; + n = int s[i+2]; + if(n >= '0' && n <= '9') + n = n - '0'; + else if(n >= 'A' && n <= 'F') + n = n - 'A' + 10; + else if(n >= 'a' && n <= 'f') + n = n - 'a' + 10; + else + break; + i += 2; + c = c * 16 + n; + } + else if( c == '+' ) + c = ' '; + t[len t] = c; + } + return t; +} + + +# go from http with latin1 escapes to utf, +# we assume that anything >= Runeself is already in utf + +httpunesc(g: ref Private_info,s : array of byte): string +{ + t,v: string; + c,i : int; + # convert bytes to a string. + v = string s; + for(i=0; i < len v;i++){ + c = v[i]; + if(c == '&'){ + if(v[1] == '#' && v[2] && v[3] && v[4] && v[5] == ';'){ + c = 100*(v[2])+10*(v[3])+(v[4]); + if(c < Runeself){ + t[len t] = c; + i += 6; + continue; + } + if(c < 256 && c >= 161){ + t[len t] = g.entity[c-161].value; + i += 6; + continue; + } + } else { + for(j:= 0;g.entity[j].name != nil; j++) + if(g.entity[j].name == v[i+1:]) + # problem here cvert array of byte to string? + break; + if(g.entity[j].name != nil){ + i += len g.entity[j].name; + t[len t] = g.entity[j].value; + continue; + } + } + } + t[len t] = c; + } + return t; +} + + +# write a failure message to the net and exit +fail(g: ref Private_info,reason : int, message : string) +{ + verb : string; + title:=sys->sprint("<head><title>%s</title></head>\n<body bgcolor=#ffffff>\n", + errormsg[reason].concise); + body1:= "<h1> Error </h1>\n<P>" + + "Sorry, Charon is unable to process your request. The webserver reports"+ + " the following error <P><b>"; + #concise error + body2:="</b><p>for the URL\n<P><b>"; + #message + body3:="</b><P>with the following reason:\n<P><b>"; + #reason + if (str->in('%',errormsg[reason].verbose)){ + (v1,v2):=str->splitl(errormsg[reason].verbose,"%"); + verb=v1+message+v2[2:]; + }else + verb=errormsg[reason].verbose; + body4:="</b><hr> This Webserver powered by <img src=\"/inferno.gif\">. <P>"+ + "For more information click <a href=\"http://inferno.lucent.com\"> here </a>\n"+ + "<hr><address>\n"; + dtime:=sys->sprint("This information processed at %s.\n",daytime->time()); + body5:="</address>\n</body>\n"; + strbuf:=title+body1+errormsg[reason].concise+body2+message+body3+ + verb+body4+dtime+body5; + if (g.bout!=nil && reason!=2){ + g.bufio->g.bout.puts(sys->sprint("%s %s\r\n", g.version, errormsg[reason].num)); + g.bufio->g.bout.puts(sys->sprint("Date: %s\r\n", daytime->time())); + g.bufio->g.bout.puts(sys->sprint("Server: Charon\r\n")); + g.bufio->g.bout.puts(sys->sprint("MIME-version: 1.0\r\n")); + g.bufio->g.bout.puts(sys->sprint("Content-Type: text/html\r\n")); + g.bufio->g.bout.puts(sys->sprint("Content-Length: %d\r\n", len strbuf)); + g.bufio->g.bout.puts(sys->sprint("\r\n")); + g.bufio->g.bout.puts(strbuf); + g.bufio->g.bout.flush(); + } + logit(g,sys->sprint("failing: %s", errormsg[reason].num)); + atexit(g); +} + + +# write successful header + +okheaders(g: ref Private_info) +{ + g.bufio->g.bout.puts(sys->sprint("%s 200 OK\r\n", g.version)); + g.bufio->g.bout.puts("Server: Charon\r\n"); + g.bufio->g.bout.puts("MIME-version: 1.0\r\n"); +} + +notmodified(g: ref Private_info) +{ + g.bufio->g.bout.puts(sys->sprint("%s 304 Not Modified\r\n", g.version)); + g.bufio->g.bout.puts("Server: Charon\r\n"); + g.bufio->g.bout.puts("MIME-version: 1.0\r\n\r\n"); + atexit(g); +} + +logit(g: ref Private_info,message : string ) +{ + lock.obtain(); + sys->fprint(g.logfile,"%s %s\n", g.remotesys, message); + lock.release(); +} + +urlconv(p : string): string +{ + c : int; + t : string; + for(i:=0;i<len p ;i++){ + c = p[i]; + if(c == 0) + break; + if(c <= ' ' || c == '%' || c >= Runeself){ + t += sys->sprint("%%%2.2x", c); + } else { + t[len t] = c; + } + } + return t; +} diff --git a/appl/svc/httpd/parser.m b/appl/svc/httpd/parser.m new file mode 100644 index 00000000..0c1a5829 --- /dev/null +++ b/appl/svc/httpd/parser.m @@ -0,0 +1,16 @@ +Parser: module { + Runeself : con 16r80; + Word : con 1; + + PATH: con "/dis/svc/httpd/parser.dis"; + + init: fn(); + initarray: fn(): array of Httpd->Entity; + urlunesc: fn(s: string): string; + fail: fn(g: ref Httpd->Private_info,reason: int, message: string); + logit: fn(g: ref Httpd->Private_info, message: string ); + notmodified: fn(g: ref Httpd->Private_info); + httpheaders: fn(g: ref Httpd->Private_info, vers: string); + urlconv: fn(url : string): string; + okheaders: fn(g: ref Httpd->Private_info); +}; diff --git a/appl/svc/httpd/redirect.b b/appl/svc/httpd/redirect.b new file mode 100644 index 00000000..cb3ed478 --- /dev/null +++ b/appl/svc/httpd/redirect.b @@ -0,0 +1,130 @@ +implement Redirect; + +include "sys.m"; + sys : Sys; + +include "bufio.m"; + bufio : Bufio; +Iobuf : import bufio; + +include "string.m"; + str : String; + +include "redirect.m"; + +HASHSIZE : con 1019; + + +Redir: adt{ + pat, repl : string; +}; + +tab := array[HASHSIZE] of list of Redir; + + +hashasu(key : string,n : int): int +{ + i,h : int; + i=0; + h=0; + while(i<len key){ + h = 10*h + key[i]; + h%= n; + i++; + } + return h; +} + +insert(pat, repl : string) +{ + hash := hashasu(pat,HASHSIZE); + tab[hash]= Redir(pat, repl) :: tab[hash]; +} + +redirect_init(file : string) +{ + sys = load Sys Sys->PATH; + line : string; + flist : list of string; + n : int; + bb : ref Iobuf; + for(n=0;n<HASHSIZE;n++) + tab[n]= nil; + stderr := sys->fildes(2); + bufio = load Bufio Bufio->PATH; + if (bufio==nil){ + sys->fprint(stderr,"redirect: cannot load %s: %r\n", Bufio->PATH); + raise "fail:bad module"; + } + str = load String String->PATH; + if (str==nil){ + sys->fprint(stderr,"redirect: cannot load %s: %r\n", String->PATH); + raise "fail:bad module"; + } + bb = bufio->open(file,bufio->OREAD); + if (bb==nil) + return; + while((line = bb.gets('\n'))!=nil){ + line = line[0:len line -1]; #chop newline + if (str->in('#',line)){ + (line,nil) = str->splitl(line, "#"); + if (line!=nil){ + n = len line; + while(line[n]==' '||line[n]=='\t') n--; + # and preceeding blanks + line = line[0:n]; + } + } + if (line!=nil){ + (n,flist)=sys->tokenize(line,"\t "); + if (n==2) + insert(hd flist,hd tl flist); + } + } + +} + +lookup(pat : string): ref Redir +{ + srch : list of Redir; + tmp : Redir; + hash : int; + hash = hashasu(pat,HASHSIZE); + for(srch = tab[hash]; srch!=nil; srch = tl srch){ + tmp = hd srch; + if(tmp.pat==nil) + return nil; + if(pat==tmp.pat) + return ref tmp; + } + return nil; +} + + +redirect(path : string): string { + redir : ref Redir; + newpath, oldp : string; + s : int; + if((redir = lookup(path))!=nil) + if(redir.repl==nil) + return nil; + else + return redir.repl; + for(s = len path - 1; s>0; s--){ + if(path[s]=='/'){ + oldp = path[s+1:]; + path = path[0:s]; + if((redir = lookup(path))!=nil){ + if(redir.repl!=nil) + newpath=sys->sprint("%s/%s", + redir.repl,oldp); + else + newpath = nil; + path = path+"/"+oldp; + return newpath; + } + path = path+"/"+oldp; + } + } + return nil; +} diff --git a/appl/svc/httpd/redirect.m b/appl/svc/httpd/redirect.m new file mode 100644 index 00000000..4a9a878d --- /dev/null +++ b/appl/svc/httpd/redirect.m @@ -0,0 +1,7 @@ +Redirect: module +{ + PATH: con "/dis/svc/httpd/redirect.dis"; + + redirect_init: fn(file : string); + redirect: fn(path : string): string; +}; diff --git a/appl/svc/httpd/stats.b b/appl/svc/httpd/stats.b new file mode 100644 index 00000000..3e285ef8 --- /dev/null +++ b/appl/svc/httpd/stats.b @@ -0,0 +1,85 @@ +implement Stats; + +include "sys.m"; + sys : Sys; + +include "bufio.m"; + bufio: Bufio; + Iobuf: import bufio; + +include "draw.m"; + draw: Draw; + +include "contents.m"; +include "cache.m"; + cache : Cache; + +include "httpd.m"; + Private_info: import Httpd; + +include "date.m"; + date : Date; + +include "parser.m"; + pars : Parser; + +include "daytime.m"; + daytime: Daytime; + +Stats: module +{ + init: fn(g : ref Private_info, req: Httpd->Request); +}; + +badmod(p: string) +{ + sys->fprint(sys->fildes(2), "stats: cannot load %s: %r\n", p); + raise "fail:bad module"; +} + +init(k : ref Private_info, req: Httpd->Request) +{ + sys = load Sys "$Sys"; + draw = load Draw "$Draw"; + + daytime = load Daytime Daytime->PATH; + if(daytime == nil) badmod(Daytime->PATH); + + pars = load Parser Parser->PATH; + if(pars == nil) badmod(Parser->PATH); + + date = load Date Date->PATH; + if(date == nil) badmod(Date->PATH); + + date->init(); + bufio=k.bufio; + send(k, req.method, req.version, req.uri, req.search); +} + +send(g: ref Private_info, meth, vers, uri, search : string) +{ + if(meth==""); + if(uri==""); + if(search==""); + if(vers != ""){ + if (g.version == nil) + sys->print("stats: version is unknown.\n"); + g.bout.puts(sys->sprint("%s 200 OK\r\n", g.version)); + g.bout.puts("Server: Charon\r\n"); + g.bout.puts("MIME-version: 1.0\r\n"); + g.bout.puts(sys->sprint("Date: %s\r\n", date->dateconv(daytime->now()))); + g.bout.puts("Content-type: text/html\r\n"); + g.bout.puts(sys->sprint("Expires: %s\r\n", date->dateconv(daytime->now()))); + g.bout.puts("\r\n"); + } + g.bout.puts("<head><title>Cache Information</title></head>\r\n"); + g.bout.puts("<body><h1>Cache Information</h1>\r\n"); + g.bout.puts("These are the pages stored in the server cache:<p>\r\n"); + lis:=(g.cache)->dump(); + while (lis!=nil){ + (a,b,d):=hd lis; + g.bout.puts(sys->sprint("<a href=\"%s\"> %s</a> \t size %d \t tag %d.<p>\r\n",a,a,b,d)); + lis = tl lis; + } + g.bout.flush(); +} |
