summaryrefslogtreecommitdiff
path: root/appl/svc/httpd
diff options
context:
space:
mode:
authorCharles.Forsyth <devnull@localhost>2006-12-22 17:07:39 +0000
committerCharles.Forsyth <devnull@localhost>2006-12-22 17:07:39 +0000
commit37da2899f40661e3e9631e497da8dc59b971cbd0 (patch)
treecbc6d4680e347d906f5fa7fca73214418741df72 /appl/svc/httpd
parent54bc8ff236ac10b3eaa928fd6bcfc0cdb2ba46ae (diff)
20060303a
Diffstat (limited to 'appl/svc/httpd')
-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
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( "&#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();
+}