diff options
author | cinap_lenrek <cinap_lenrek@rei2.9hal> | 2012-01-11 16:17:54 +0100 |
---|---|---|
committer | cinap_lenrek <cinap_lenrek@rei2.9hal> | 2012-01-11 16:17:54 +0100 |
commit | 75e1ef0ab60acb6bccc54254b82770aec5786ead (patch) | |
tree | d273fc755a20e67801aa0a13df30ab75b2883419 | |
parent | 62fb4f97177d8e76f1fd49bb9d0073007b7c9bcc (diff) | |
download | plan9front-75e1ef0ab60acb6bccc54254b82770aec5786ead.tar.xz |
new webfs, rc based hget
-rwxr-xr-x | rc/bin/hget | 69 | ||||
-rw-r--r-- | sys/lib/dist/usr/glenda/lib/profile | 10 | ||||
-rwxr-xr-x | sys/lib/newuser | 5 | ||||
-rw-r--r-- | sys/man/1/hget | 64 | ||||
-rw-r--r-- | sys/man/4/webcookies | 14 | ||||
-rw-r--r-- | sys/man/4/webfs | 316 | ||||
-rw-r--r-- | sys/src/cmd/hget.c | 1480 | ||||
-rw-r--r-- | sys/src/cmd/webfs/buf.c | 89 | ||||
-rw-r--r-- | sys/src/cmd/webfs/buq.c | 263 | ||||
-rw-r--r-- | sys/src/cmd/webfs/client.c | 411 | ||||
-rw-r--r-- | sys/src/cmd/webfs/cookies.c | 1179 | ||||
-rw-r--r-- | sys/src/cmd/webfs/dat.h | 139 | ||||
-rw-r--r-- | sys/src/cmd/webfs/fns.h | 97 | ||||
-rw-r--r-- | sys/src/cmd/webfs/fs.c | 1103 | ||||
-rw-r--r-- | sys/src/cmd/webfs/http.c | 1223 | ||||
-rw-r--r-- | sys/src/cmd/webfs/io.c | 86 | ||||
-rw-r--r-- | sys/src/cmd/webfs/main.c | 67 | ||||
-rw-r--r-- | sys/src/cmd/webfs/mkfile | 31 | ||||
-rw-r--r-- | sys/src/cmd/webfs/plumb.c | 165 | ||||
-rw-r--r-- | sys/src/cmd/webfs/sub.c | 119 | ||||
-rw-r--r-- | sys/src/cmd/webfs/url.c | 1236 | ||||
-rw-r--r-- | sys/src/cmd/webfs/util.c | 86 | ||||
-rw-r--r-- | sys/src/cmd/webfs/webget.c | 87 | ||||
-rw-r--r-- | sys/src/cmd/webfsget.c | 85 |
24 files changed, 2319 insertions, 6105 deletions
diff --git a/rc/bin/hget b/rc/bin/hget new file mode 100755 index 000000000..5c0b1e19b --- /dev/null +++ b/rc/bin/hget @@ -0,0 +1,69 @@ +#!/bin/rc +argv0=$0 +fn usage { + echo usage: $argv0 [ -o file ] [ -p body ] [ -r header ] [ -m method ] [ -b baseurl ] url >[2=1] + exit usage +} +s=0 +o=() +p=() +r=() +m=() +b=() +while(~ $1 -*){ + switch($1){ + case -o + o=$2 + shift + case -p + p=$2 + shift + case -r + r=($r $2) + shift + case -m + m=$2 + shift + case -b + b=$2 + shift + case * + usage + } + shift +} +if(! ~ $#* 1) + usage +if(! ~ $#o 0){ + if(! ~ $#o 1) + usage + if(test -s $o) + s=`{ls -l $o | awk '{print $6}'} +} +if(! ~ $s 0) + r=($r 'Range: bytes='^$s^'-') +<>/mnt/web/clone { + d=/mnt/web/^`{sed 1q} + if(~ $#b 1) + echo -n baseurl $b >[1=0] + echo -n url $1 >[1=0] + for(i in $r) + echo -n headers $i >[1=0] + if(~ $#m 1) + echo -n request $m >[1=0] + if(~ $#p 1) + cat <$p >$d/postbody + <$d/body { + if(~ $#o 1){ + l=`{cat $d/contentlength >[2]/dev/null} + x=`{awk 'BEGIN{FS=" |-"}/^bytes ([0-9]+)\-/{print $2}' \ + $d/contentrange >[2]/dev/null} + if(~ $s $l && ~ $#x 0) + exit + if(~ $s $x) + exec cat >>$o + exec cat >$o + } + exec cat + } +} diff --git a/sys/lib/dist/usr/glenda/lib/profile b/sys/lib/dist/usr/glenda/lib/profile index 79aebb7e0..64e301fcf 100644 --- a/sys/lib/dist/usr/glenda/lib/profile +++ b/sys/lib/dist/usr/glenda/lib/profile @@ -9,9 +9,15 @@ font = /lib/font/bit/pelm/euro.9.font fn cd { builtin cd $* && awd } # for acme switch($service){ case terminal + if(! test -w $home/lib/webcookies){ + touch /tmp/webcookies + webcookies -f /tmp/webcookies + } + if not { + webcookies + } + webfs plumber - touch /tmp/webcookies - webfs -c /tmp/webcookies echo -n accelerated > '#m/mousectl' echo -n 'res 3' > '#m/mousectl' prompt=('term% ' ' ') diff --git a/sys/lib/newuser b/sys/lib/newuser index b06b8e1fd..3fae08d4d 100755 --- a/sys/lib/newuser +++ b/sys/lib/newuser @@ -10,10 +10,11 @@ cd $home x='$' mkdir bin bin/rc bin/mips bin/386 bin/power bin/arm mkdir lib tmp +touch lib/webcookies +chmod 600 lib/webcookies chmod +t tmp bind -qc /n/other/usr/$user/tmp $home/tmp bind -c $home/tmp /tmp - mail -c auth/cron -c @@ -26,6 +27,8 @@ bind -c $x^home/tmp /tmp font = /lib/font/bit/pelm/euro.9.font switch($x^service){ case terminal + webcookies + webfs plumber startupasfs echo -n accelerated > '#m/mousectl' diff --git a/sys/man/1/hget b/sys/man/1/hget index 7cc8c97c5..9e6a4ed3f 100644 --- a/sys/man/1/hget +++ b/sys/man/1/hget @@ -4,19 +4,20 @@ hget \- retrieve a web page corresponding to a url .SH SYNOPSIS .B hget [ -.B -dhv -] [ .B -o -.I ofile +.I file ] [ .B -p .I body ] [ -.B -x -.I netmntpt -] [ .B -r .I header +] [ +.B -m +.I method +] [ +.B -b +.I baseurl ] .I url .SH DESCRIPTION @@ -26,7 +27,16 @@ retrieves the web page specified by the URL and writes it, absent the .B -o option, to standard output. -The known URL types are: http and ftp. +.PP +The +.I url +can be a relative path like +.B ../index.html +if a absolute +.I baseurl +was specified with the +.B -b +option. .PP If .I url @@ -47,40 +57,22 @@ but incomplete, will fetch the missing bytes. .PP Option -.B -h -causes HTTP headers to be printed to standard output -in addition to the transferred web page. -.PP -Option .B -r sends an arbitrary HTTP .IR header . .PP Option -.B -d -turns on debugging written to standard error. -.PP -Normally, -.I hget -uses the IP stack mounted under -.BR /net . -The -.B -x -option can be used to specify the mount point of -a different IP stack to use. -.PP -Option -.B -v -writes progress lines to standard error once a second. -Each line contains two numbers, the bytes transferred so -far and the total length to be transferred. -.PP -If the environment variable -.B httpproxy -is set, it is used as a URL denoting an HTTP proxy server. -All HTTP accesses use this server to get the page instead of -calling the destination server. +.B -m +overrides the HTTP method used for the request. .SH SOURCE -.B /sys/src/cmd/hget.c +.B /rc/bin/hget .SH "SEE ALSO" +.IR webfs (4), .IR ftpfs (4) +.SH DIAGNOSTICS +.I Hget +requires +.IR webfs (4) +service mounted on +.B /mnt/web +to work. diff --git a/sys/man/4/webcookies b/sys/man/4/webcookies index 63842e90f..b4c6c2168 100644 --- a/sys/man/4/webcookies +++ b/sys/man/4/webcookies @@ -147,20 +147,8 @@ If .B cookiefs decides not to accept the cookie (as outlined in RFC2109, section 4.3.4), no indication is given. -.PP -.IR Hget (1) -uses -.BR /mnt/webcookies/http , -when it exists, to manage cookie state. -.I Webfs -does not (yet). .SH SOURCE .B /sys/src/cmd/webcookies.c .SH SEE ALSO +.IR webfs (4), .IR hget (1) -.SH BUGS -It's not clear what the relationship between -.I cookiefs -and something like -.I webfs -should be. diff --git a/sys/man/4/webfs b/sys/man/4/webfs index 5f0d40720..630f0911f 100644 --- a/sys/man/4/webfs +++ b/sys/man/4/webfs @@ -4,10 +4,6 @@ webfs \- world wide web file system .SH SYNOPSIS .B webfs [ -.B -c -.I cookiefile -] -[ .B -m .I mtpt ] @@ -26,10 +22,15 @@ mounts itself at .BR /mnt/web ), and, if .I service -is specified, will post a service file descriptor -in +is specified, will post a service file descriptor in .BR /srv/\fIservice . .PP +If the enviroment variable +.B httpproxy +is set, all HTTP request initiated by +.I webfs +will be made thru that proxy url. +.PP .I Webfs presents a three-level file system suggestive of the network protocol hierarchies @@ -37,13 +38,12 @@ of the network protocol hierarchies and .IR ether (3). .PP -The top level contains three files: +The top level contains the files files: .BR ctl , -.BR cookies , and .BR clone . .PP -The +The top level .B ctl file is used to maintain parameters global to the instance of .IR webfs . @@ -53,72 +53,6 @@ file yields the current values of the parameters. Writing strings of the form .RB `` attr " " value '' sets a particular attribute. -Attributes are: -.TP -.B chatty9p -The -.B chatty9p -flag used by the 9P library, discussed in -.IR 9p (2). -.B 0 -is no debugging, -.B 1 -prints 9P message traces on standard error, -and values above -.B 1 -present more debugging, at the whim of the library. -The default for this and the following debug flags is -.BR 0 . -.TP -.B fsdebug -This variable is the level of debugging output about the file system module. -.TP -.B cookiedebug -This variable is the level of debugging output about the cookie module. -.TP -.B urldebug -This variable is the level of debugging output about URL parsing. -.TP -.B acceptcookies -This flag controls whether to accept cookies presented by remote web servers. -(Cookies are described below, in the discussion of the -.B cookies -file.) -The values -.B on -and -.B off -are synonymous with -.B 1 -and -.BR 0 . -The default is -.BR on . -.TP -.B sendcookies -This flag controls whether to present stored cookies to remote web servers. -The default is -.BR on . -.TP -.B redirectlimit -Web servers can respond to a request with a message -redirecting to another page. -.I Webfs -makes no effort to determine whether it is in an infinite -redirect loop. -Instead, it gives up after this many redirects. -The default is -.BR 10 . -.TP -.B useragent -.I Webfs -sends the value of this attribute in its -.B User-Agent: -header in its HTTP requests. -The default is -.RB `` "webfs/2.0 (plan 9)" .'' -.PD -.PP The top-level directory also contains numbered directories corresponding to connections, which may be used to fetch a single URL. @@ -131,23 +65,10 @@ After opening, the .B clone file is equivalent to the file .IB n /ctl \fR. -A connection is assumed closed once all files in its directory -have been closed, and is then will be reallocated. -.PP -Each connection has its own private set of -.BR acceptcookies , -.BR sendcookies , -.BR redirectlimit , -and -.B useragent -variables, initialized to the defaults set in the -root's -.B ctl -file. The per-connection -.B ctl -file allows editing the variables for this particular connection. +A connection is assumed closed once all files in its +directory have been closed, and is then will be reallocated. .PP -Each connection also has a URL string variable +Each connection has a URL attribute .B url associated with it. This URL may be an absolute URL such as @@ -156,153 +77,96 @@ or a relative URL such as .IR ../index.html . The .B baseurl -string variable sets the URL against which relative URLs +attribute sets the URL against which relative URLs are interpreted. -Once the URL has been set, -its pieces can be retrieved via individual files in the -.B parsed -directory. -.I Webfs -parses the following URL syntaxes; names in italics are -the names of files in the +Once the URL has been set by wrting to the +.B ctl +file of the connetcion, its pieces can be retrieved via +individual files in the .B parsed -directory. -.IP -\fIscheme\f5:\fIschemedata -.br -\f5http://\fIhost\f5/\fIpath\fR[\f5?\fIquery\fR][\f5#\fIfragment\fR] -.br -\f5ftp://\fR[\fIuser\fR[\f5:\fIpassword\fR]\f5@\fR]\fP\f5\fIhost\f5/\fIpath\fR[\f5;type=\fIftptype\fR] -.br -\f5file:\fIpath -.LP -If there is associated data to be -posted with the request, it can be written to +directory: +.de UU +.TP +.B parsed/\fI\\$1 +\\$2 +.. +.UU url http://pete:secret@www.example.com:8000/cgi/search?q=kittens#results +.UU scheme http +.UU user pete +.UU pass secret +.UU host www.example.com +.UU port 8000 +.UU path /cgi/search +.UU query q=kittens +.UU fragment results +.PP +If there is associated data to be posted with the request, +it can be written to .BR postbody . -Finally, opening +Opening +.B postbody +or .B body -initiates the request. -The resulting data may be read from +initiates the request. If the request fails, +then opening the .B body -as it arrives. -After the request has been executed, the MIME content type -may be read from the +or writing to +.B postbody +file will fail and return a error string. +.PP +When the +.B body +file has been opend, response headers appear +as files in the connection directory. For example +reading the .B contenttype -file. +file yields the MIME content type of the body data. +If the request was redirected, the URL represended +by the +.B parsed +directory will change to the final destination. .PP -The top-level -.B cookies -file contains the internal set of HTTP cookies, which -are used by HTTP servers to associate requests with persistent -state such as user profiles. -It may be edited as an ordinary text file. -Multiple instances of -.I webfs -and -.IR webcookies (4) -share cookies by keeping their internal set -consistent with the -.I cookiefile -(default -.BR $home/lib/webcookies ), -which has the same format. +The resulting data may be read from +.B body +as it arrives. .PP -These files contain one line per cookie; -each cookie comprises some number of -.IB attr = value -pairs. -Cookie attributes are: +The following is a list of attributes that can be +set to to a connection prior initiating the request: .TP -.BI name= name -The name of the cookie on the remote server. +.B url,baseurl +See above. .TP -.BI value= value -The value associated with that name on the remote server. -The actual data included when a cookie is sent back -to the server is -.IB \fR``\fIname = value\fR'' -(where, confusingly, -.I name -and -.I value -are the values associated with the -.B name -and -.B value -attributes. -.TP -.BI domain= domain -If -.I domain -is an IP address, the cookie can only be used for URLs -with -.I host -equal to that IP address. -Otherwise, -.I domain -must be a pattern beginning with a dot, and -the cookie can only be used for URLs with a -.I host -having -.I domain -as a suffix. -For example, a cookie with -.B domain=.bell-labs.com -may be used on hosts -.I www.bell-labs.com -and -.IR www.research.bell-labs.com -(but not -.IR www.not-bell-labs.com ). -.TP -.BI path= path -The cookie can only be used for URLs with a path -beginning with -.IR path . -.TP -.BI version= version -The version of the HTTP cookie specification, specified by the server. -.TP -.BI comment= comment -A comment, specified by the server. -.TP -.BI expire= expire -The cookie expires at time -.IR expire , -which is a decimal number of seconds since the epoch. -.TP -.B secure=1 -The cookie may only be used over secure -.RB ( https ) -connections. -Secure connections are currently unimplemented. -.TP -.B explicitdomain=1 -The domain associated with this cookie was set by -the server (rather than inferred from a URL). -.TP -.B explicitpath=1 -The path associated with this cookie was set by the -server (rather than inferred from a URL). +.B useragent +Sets a custom useragent string to be used with the request. .TP -.B netscapestyle=1 -The server presented the cookie in ``Netscape style,'' which -does not conform to the cookie standard, RFC2109. -It is assumed that when presenting the cookie to the server, -it must be sent back in Netscape style as well. -.PD +.B contenttype +Sets the MIME content type of the postbody. +.TP +.B request +Usualy, the HTTP method used is +.B POST +when +.B postbody +file is opend first or +.B GET +otherwise. This can be overriden with the +.B request +attribute so send arbitrary HTTP requests. +.TP +.B headers +Adds arbitrary HTTP headers to be send with +the request. .SH EXAMPLE -.B /sys/src/cmd/webfs/webget.c +.B /rc/bin/hget is a simple client. .SH SOURCE .B /sys/src/cmd/webfs -.SH SEE ALSO -.IR hget (1), -.IR webcookies (4) -.SH BUGS -It's not clear what the relationship between -.IR hget , -.I webcookies -and -.I webfs -should be. +.SH "SEE ALSO" +.IR webcookies (4), +.IR hget (1) +.SH DIAGNOSTICS +For cookies to work, +.IR webcookies (4), +should be running and mounted on +.B /mnt/webcookies +otherwise cookies will be ignored. diff --git a/sys/src/cmd/hget.c b/sys/src/cmd/hget.c deleted file mode 100644 index c1c310e1e..000000000 --- a/sys/src/cmd/hget.c +++ /dev/null @@ -1,1480 +0,0 @@ -#include <u.h> -#include <libc.h> -#include <ctype.h> -#include <bio.h> -#include <ip.h> -#include <libsec.h> -#include <auth.h> - -typedef struct URL URL; -struct URL -{ - int method; - char *host; - char *port; - char *page; - char *etag; - char *redirect; - char *postbody; - char *cred; - char *rhead; - long mtime; -}; - -typedef struct Range Range; -struct Range -{ - long start; /* only 2 gig supported, tdb */ - long end; -}; - -typedef struct Out Out; -struct Out -{ - int fd; - int offset; /* notional current offset in output */ - int written; /* number of bytes successfully transferred to output */ - DigestState *curr; /* digest state up to offset (if known) */ - DigestState *hiwat; /* digest state of all bytes written */ -}; - -enum -{ - Other, - Http, - Https, - Ftp, -}; - -enum -{ - Eof = 0, - Error = -1, - Server = -2, - Changed = -3, -}; - -int debug; -char *ofile; - - -int doftp(URL*, URL*, Range*, Out*, long); -int dohttp(URL*, URL*, Range*, Out*, long); -int crackurl(URL*, char*); -Range* crackrange(char*); -int getheader(int, char*, int); -int httpheaders(int, int, URL*, Range*); -int httprcode(int); -int cistrncmp(char*, char*, int); -int cistrcmp(char*, char*); -void initibuf(void); -int readline(int, char*, int); -int readibuf(int, char*, int); -int dfprint(int, char*, ...); -void unreadline(char*); -int output(Out*, char*, int); -void setoffset(Out*, int); - -int verbose; -char *net; -char tcpdir[NETPATHLEN]; -int headerprint; - -struct { - char *name; - int (*f)(URL*, URL*, Range*, Out*, long); -} method[] = { - [Http] { "http", dohttp }, - [Https] { "https", dohttp }, - [Ftp] { "ftp", doftp }, - [Other] { "_______", nil }, -}; - -void -usage(void) -{ - fprint(2, "usage: %s [-dhv] [-o outfile] [-p body] [-x netmtpt] [-r header] url\n", argv0); - exits("usage"); -} - -void -main(int argc, char **argv) -{ - URL u; - Range r; - int errs, n; - ulong mtime; - Dir *d; - char postbody[4096], *p, *e, *t, *hpx; - URL px; // Proxy - Out out; - - ofile = nil; - p = postbody; - e = p + sizeof(postbody); - r.start = 0; - r.end = -1; - mtime = 0; - memset(&u, 0, sizeof(u)); - memset(&px, 0, sizeof(px)); - hpx = getenv("httpproxy"); - - ARGBEGIN { - case 'o': - ofile = EARGF(usage()); - break; - case 'd': - debug = 1; - break; - case 'h': - headerprint = 1; - break; - case 'v': - verbose = 1; - break; - case 'x': - net = EARGF(usage()); - break; - case 'r': - u.rhead = EARGF(usage()); - break; - case 'p': - t = EARGF(usage()); - if(p != postbody) - p = seprint(p, e, "&%s", t); - else - p = seprint(p, e, "%s", t); - u.postbody = postbody; - - break; - default: - usage(); - } ARGEND; - - if(net != nil){ - if(strlen(net) > sizeof(tcpdir)-5) - sysfatal("network mount point too long"); - snprint(tcpdir, sizeof(tcpdir), "%s/tcp", net); - } else - snprint(tcpdir, sizeof(tcpdir), "tcp"); - - if(argc != 1) - usage(); - - - out.fd = 1; - out.written = 0; - out.offset = 0; - out.curr = nil; - out.hiwat = nil; - if(ofile != nil){ - d = dirstat(ofile); - if(d == nil){ - out.fd = create(ofile, OWRITE, 0664); - if(out.fd < 0) - sysfatal("creating %s: %r", ofile); - } else { - out.fd = open(ofile, OWRITE); - if(out.fd < 0) - sysfatal("can't open %s: %r", ofile); - r.start = d->length; - mtime = d->mtime; - free(d); - } - } - - errs = 0; - - if(crackurl(&u, argv[0]) < 0) - sysfatal("%r"); - if(hpx && crackurl(&px, hpx) < 0) - sysfatal("%r"); - - for(;;){ - setoffset(&out, 0); - /* transfer data */ - werrstr(""); - n = (*method[u.method].f)(&u, &px, &r, &out, mtime); - - switch(n){ - case Eof: - exits(0); - break; - case Error: - if(errs++ < 10) - continue; - sysfatal("too many errors with no progress %r"); - break; - case Server: - sysfatal("server returned: %r"); - break; - } - - /* forward progress */ - errs = 0; - r.start += n; - if(r.start >= r.end) - break; - } - - exits(0); -} - -int -crackurl(URL *u, char *s) -{ - char *p; - int i; - - if(u->page != nil){ - free(u->page); - u->page = nil; - } - - /* get type */ - for(p = s; *p; p++){ - if(*p == '/'){ - p = s; - if(u->method == Other){ - werrstr("missing method"); - return -1; - } - if(u->host == nil){ - werrstr("missing host"); - return -1; - } - u->page = strdup(p); - return 0; - } - if(*p == ':' && *(p+1)=='/' && *(p+2)=='/'){ - *p = 0; - p += 3; - for(i = 0; i < nelem(method); i++){ - if(cistrcmp(s, method[i].name) == 0){ - u->method = i; - break; - } - } - break; - } - } - - if(u->method == Other){ - werrstr("unsupported URL type %s", s); - return -1; - } - - /* get system */ - free(u->host); - s = p; - p = strchr(s, '/'); - if(p == nil){ - u->host = strdup(s); - u->page = strdup("/"); - } else { - u->page = strdup(p); - *p = 0; - u->host = strdup(s); - *p = '/'; - } - - if(p = strchr(u->host, ':')) { - *p++ = 0; - u->port = p; - } else - u->port = method[u->method].name; - - if(*(u->host) == 0){ - werrstr("bad url, null host"); - return -1; - } - - return 0; -} - -char *day[] = { - "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" -}; - -char *month[] = { - "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" -}; - -struct -{ - int fd; - long mtime; -} note; - -void -catch(void*, char*) -{ - Dir d; - - nulldir(&d); - d.mtime = note.mtime; - if(dirfwstat(note.fd, &d) < 0) - sysfatal("catch: can't dirfwstat: %r"); - noted(NDFLT); -} - -int -dohttp(URL *u, URL *px, Range *r, Out *out, long mtime) -{ - int fd, cfd; - int redirect, auth, loop; - int n, rv, code; - long tot, vtime; - Tm *tm; - char buf[1024]; - char err[ERRMAX]; - - - /* always move back to a previous 512 byte bound because some - * servers can't seem to deal with requests that start at the - * end of the file - */ - if(r->start) - r->start = ((r->start-1)/512)*512; - - /* loop for redirects, requires reading both response code and headers */ - fd = -1; - for(loop = 0; loop < 32; loop++){ - if(px->host == nil){ - fd = dial(netmkaddr(u->host, tcpdir, u->port), 0, 0, 0); - } else { - fd = dial(netmkaddr(px->host, tcpdir, px->port), 0, 0, 0); - } - if(fd < 0) - return Error; - - if(u->method == Https){ - int tfd; - TLSconn conn; - - memset(&conn, 0, sizeof conn); - tfd = tlsClient(fd, &conn); - if(tfd < 0){ - fprint(2, "tlsClient: %r\n"); - close(fd); - return Error; - } - /* BUG: check cert here? */ - if(conn.cert) - free(conn.cert); - close(fd); - fd = tfd; - } - - /* write request, use range if not start of file */ - if(u->postbody == nil){ - if(px->host == nil){ - dfprint(fd, "GET %s HTTP/1.0\r\n" - "Host: %s\r\n" - "User-agent: Plan9/hget\r\n" - "Cache-Control: no-cache\r\n" - "Pragma: no-cache\r\n", - u->page, u->host); - } else { - dfprint(fd, "GET http://%s%s HTTP/1.0\r\n" - "Host: %s\r\n" - "User-agent: Plan9/hget\r\n" - "Cache-Control: no-cache\r\n" - "Pragma: no-cache\r\n", - u->host, u->page, u->host); - } - } else { - dfprint(fd, "POST %s HTTP/1.0\r\n" - "Host: %s\r\n" - "Content-type: application/x-www-form-urlencoded\r\n" - "Content-length: %d\r\n" - "User-agent: Plan9/hget\r\n", - u->page, u->host, strlen(u->postbody)); - } - if(u->cred) - dfprint(fd, "Authorization: Basic %s\r\n", u->cred); - if(u->rhead) - dfprint(fd, "%s\r\n", u->rhead); - if(r->start != 0){ - dfprint(fd, "Range: bytes=%d-\n", r->start); - if(u->etag != nil){ - dfprint(fd, "If-range: %s\n", u->etag); - } else { - tm = gmtime(mtime); - dfprint(fd, "If-range: %s, %d %s %d %2d:%2.2d:%2.2d GMT\n", - day[tm->wday], tm->mday, month[tm->mon], - tm->year+1900, tm->hour, tm->min, tm->sec); - } - } - if((cfd = open("/mnt/webcookies/http", ORDWR)) >= 0){ - if(fprint(cfd, "http://%s%s", u->host, u->page) > 0){ - while((n = read(cfd, buf, sizeof buf)) > 0){ - if(debug) - write(2, buf, n); - write(fd, buf, n); - } - }else{ - close(cfd); - cfd = -1; - } - } - - dfprint(fd, "\r\n", u->host); - if(u->postbody) - dfprint(fd, "%s", u->postbody); - - auth = 0; - redirect = 0; - initibuf(); - code = httprcode(fd); - switch(code){ - case Error: /* connection timed out */ - case Eof: - close(fd); - close(cfd); - return code; - - case 200: /* OK */ - case 201: /* Created */ - case 202: /* Accepted */ - if(ofile == nil && r->start != 0) - sysfatal("page changed underfoot"); - break; - - case 204: /* No Content */ - sysfatal("No Content"); - - case 206: /* Partial Content */ - setoffset(out, r->start); - break; - - case 301: /* Moved Permanently */ - case 302: /* Moved Temporarily (actually Found) */ - case 303: /* See Other */ - case 307: /* Temporary Redirect (HTTP/1.1) */ - redirect = 1; - u->postbody = nil; - break; - - case 304: /* Not Modified */ - break; - - case 400: /* Bad Request */ - sysfatal("Bad Request"); - - case 401: /* Unauthorized */ - if (auth) - sysfatal("Authentication failed"); - auth = 1; - break; - - case 402: /* ??? */ - sysfatal("Unauthorized"); - - case 403: /* Forbidden */ - sysfatal("Forbidden by server"); - - case 404: /* Not Found */ - sysfatal("Not found on server"); - - case 407: /* Proxy Authentication */ - sysfatal("Proxy authentication required"); - - case 500: /* Internal server error */ - sysfatal("Server choked"); - - case 501: /* Not implemented */ - sysfatal("Server can't do it!"); - - case 502: /* Bad gateway */ - sysfatal("Bad gateway"); - - case 503: /* Service unavailable */ - sysfatal("Service unavailable"); - - default: - sysfatal("Unknown response code %d", code); - } - - if(u->redirect != nil){ - free(u->redirect); - u->redirect = nil; - } - - rv = httpheaders(fd, cfd, u, r); - close(cfd); - if(rv != 0){ - close(fd); - return rv; - } - - if(!redirect && !auth) - break; - - if (redirect){ - if(u->redirect == nil) - sysfatal("redirect: no URL"); - if(crackurl(u, u->redirect) < 0) - sysfatal("redirect: %r"); - } - } - - /* transfer whatever you get */ - if(ofile != nil && u->mtime != 0){ - note.fd = out->fd; - note.mtime = u->mtime; - notify(catch); - } - - tot = 0; - vtime = 0; - for(;;){ - n = readibuf(fd, buf, sizeof(buf)); - if(n <= 0) - break; - if(output(out, buf, n) != n) - break; - tot += n; - if(verbose && (vtime != time(0) || r->start == r->end)) { - vtime = time(0); - fprint(2, "%ld %ld\n", r->start+tot, r->end); - } - } - notify(nil); - close(fd); - - if(ofile != nil && u->mtime != 0){ - Dir d; - - rerrstr(err, sizeof err); - nulldir(&d); - d.mtime = u->mtime; - if(dirfwstat(out->fd, &d) < 0) - fprint(2, "couldn't set mtime: %r\n"); - errstr(err, sizeof err); - } - - return tot; -} - -/* get the http response code */ -int -httprcode(int fd) -{ - int n; - char *p; - char buf[256]; - - n = readline(fd, buf, sizeof(buf)-1); - if(n <= 0) - return n; - if(debug) - fprint(2, "%d <- %s\n", fd, buf); - p = strchr(buf, ' '); - if(strncmp(buf, "HTTP/", 5) != 0 || p == nil){ - werrstr("bad response from server"); - return -1; - } - buf[n] = 0; - return atoi(p+1); -} - -/* read in and crack the http headers, update u and r */ -void hhetag(char*, URL*, Range*); -void hhmtime(char*, URL*, Range*); -void hhclen(char*, URL*, Range*); -void hhcrange(char*, URL*, Range*); -void hhuri(char*, URL*, Range*); -void hhlocation(char*, URL*, Range*); -void hhauth(char*, URL*, Range*); - -struct { - char *name; - void (*f)(char*, URL*, Range*); -} headers[] = { - { "etag:", hhetag }, - { "last-modified:", hhmtime }, - { "content-length:", hhclen }, - { "content-range:", hhcrange }, - { "uri:", hhuri }, - { "location:", hhlocation }, - { "WWW-Authenticate:", hhauth }, -}; -int -httpheaders(int fd, int cfd, URL *u, Range *r) -{ - char buf[2048]; - char *p; - int i, n; - - for(;;){ - n = getheader(fd, buf, sizeof(buf)); - if(n <= 0) - break; - if(cfd >= 0) - fprint(cfd, "%s\n", buf); - for(i = 0; i < nelem(headers); i++){ - n = strlen(headers[i].name); - if(cistrncmp(buf, headers[i].name, n) == 0){ - /* skip field name and leading white */ - p = buf + n; - while(*p == ' ' || *p == '\t') - p++; - - (*headers[i].f)(p, u, r); - break; - } - } - } - return n; -} - -/* - * read a single mime header, collect continuations. - * - * this routine assumes that there is a blank line twixt - * the header and the message body, otherwise bytes will - * be lost. - */ -int -getheader(int fd, char *buf, int n) -{ - char *p, *e; - int i; - - n--; - p = buf; - for(e = p + n; ; p += i){ - i = readline(fd, p, e-p); - if(i < 0) - return i; - - if(p == buf){ - /* first line */ - if(strchr(buf, ':') == nil) - break; /* end of headers */ - } else { - /* continuation line */ - if(*p != ' ' && *p != '\t'){ - unreadline(p); - *p = 0; - break; /* end of this header */ - } - } - } - if(headerprint) - print("%s\n", buf); - - if(debug) - fprint(2, "%d <- %s\n", fd, buf); - return p-buf; -} - -void -hhetag(char *p, URL *u, Range*) -{ - if(u->etag != nil){ - if(strcmp(u->etag, p) != 0) - sysfatal("file changed underfoot"); - } else - u->etag = strdup(p); -} - -char* monthchars = "janfebmaraprmayjunjulaugsepoctnovdec"; - -void -hhmtime(char *p, URL *u, Range*) -{ - char *month, *day, *yr, *hms; - char *fields[6]; - Tm tm, now; - int i; - - i = getfields(p, fields, 6, 1, " \t"); - if(i < 5) - return; - - day = fields[1]; - month = fields[2]; - yr = fields[3]; - hms = fields[4]; - - /* default time */ - now = *gmtime(time(0)); - tm = now; - tm.yday = 0; - - /* convert ascii month to a number twixt 1 and 12 */ - if(*month >= '0' && *month <= '9'){ - tm.mon = atoi(month) - 1; - if(tm.mon < 0 || tm.mon > 11) - tm.mon = 5; - } else { - for(p = month; *p; p++) - *p = tolower(*p); - for(i = 0; i < 12; i++) - if(strncmp(&monthchars[i*3], month, 3) == 0){ - tm.mon = i; - break; - } - } - - tm.mday = atoi(day); - - if(hms) { - tm.hour = strtoul(hms, &p, 10); - if(*p == ':') { - p++; - tm.min = strtoul(p, &p, 10); - if(*p == ':') { - p++; - tm.sec = strtoul(p, &p, 10); - } - } - if(tolower(*p) == 'p') - tm.hour += 12; - } - - if(yr) { - tm.year = atoi(yr); - if(tm.year >= 1900) - tm.year -= 1900; - } else { - if(tm.mon > now.mon || (tm.mon == now.mon && tm.mday > now.mday+1)) - tm.year--; - } - - strcpy(tm.zone, "GMT"); - /* convert to epoch seconds */ - u->mtime = tm2sec(&tm); -} - -void -hhclen(char *p, URL*, Range *r) -{ - r->end = atoi(p); -} - -void -hhcrange(char *p, URL*, Range *r) -{ - char *x; - vlong l; - - l = 0; - x = strchr(p, '/'); - if(x) - l = atoll(x+1); - if(l == 0) { - x = strchr(p, '-'); - if(x) - l = atoll(x+1); - } - if(l) - r->end = l; -} - -void -hhuri(char *p, URL *u, Range*) -{ - if(*p != '<') - return; - u->redirect = strdup(p+1); - p = strchr(u->redirect, '>'); - if(p != nil) - *p = 0; -} - -void -hhlocation(char *p, URL *u, Range*) -{ - u->redirect = strdup(p); -} - -void -hhauth(char *p, URL *u, Range*) -{ - char *f[4]; - UserPasswd *up; - char *s, cred[64]; - - if (cistrncmp(p, "basic ", 6) != 0) - sysfatal("only Basic authentication supported"); - - if (gettokens(p, f, nelem(f), "\"") < 2) - sysfatal("garbled auth data"); - - if ((up = auth_getuserpasswd(auth_getkey, "proto=pass service=http server=%q realm=%q", - u->host, f[1])) == nil) - sysfatal("cannot authenticate"); - - s = smprint("%s:%s", up->user, up->passwd); - if(enc64(cred, sizeof(cred), (uchar *)s, strlen(s)) == -1) - sysfatal("enc64"); - free(s); - - assert(u->cred = strdup(cred)); -} - -enum -{ - /* ftp return codes */ - Extra= 1, - Success= 2, - Incomplete= 3, - TempFail= 4, - PermFail= 5, - - Nnetdir= 64, /* max length of network directory paths */ - Ndialstr= 64, /* max length of dial strings */ -}; - -int ftpcmd(int, char*, ...); -int ftprcode(int, char*, int); -int hello(int); -int logon(int); -int xfertype(int, char*); -int passive(int, URL*); -int active(int, URL*); -int ftpxfer(int, Out*, Range*); -int terminateftp(int, int); -int getaddrport(char*, uchar*, uchar*); -int ftprestart(int, Out*, URL*, Range*, long); - -int -doftp(URL *u, URL *px, Range *r, Out *out, long mtime) -{ - int pid, ctl, data, rv; - Waitmsg *w; - char msg[64]; - char conndir[NETPATHLEN]; - char *p; - - /* untested, proxy doesn't work with ftp (I think) */ - if(px->host == nil){ - ctl = dial(netmkaddr(u->host, tcpdir, u->port), 0, conndir, 0); - } else { - ctl = dial(netmkaddr(px->host, tcpdir, px->port), 0, conndir, 0); - } - - if(ctl < 0) - return Error; - if(net == nil){ - p = strrchr(conndir, '/'); - *p = 0; - snprint(tcpdir, sizeof(tcpdir), conndir); - } - - initibuf(); - - rv = hello(ctl); - if(rv < 0) - return terminateftp(ctl, rv); - - rv = logon(ctl); - if(rv < 0) - return terminateftp(ctl, rv); - - rv = xfertype(ctl, "I"); - if(rv < 0) - return terminateftp(ctl, rv); - - /* if file is up to date and the right size, stop */ - if(ftprestart(ctl, out, u, r, mtime) > 0){ - close(ctl); - return Eof; - } - - /* first try passive mode, then active */ - data = passive(ctl, u); - if(data < 0){ - data = active(ctl, u); - if(data < 0) - return Error; - } - - /* fork */ - switch(pid = rfork(RFPROC|RFFDG|RFMEM)){ - case -1: - close(data); - return terminateftp(ctl, Error); - case 0: - ftpxfer(data, out, r); - close(data); - _exits(0); - default: - close(data); - break; - } - - /* wait for reply message */ - rv = ftprcode(ctl, msg, sizeof(msg)); - close(ctl); - - /* wait for process to terminate */ - w = nil; - for(;;){ - free(w); - w = wait(); - if(w == nil) - return Error; - if(w->pid == pid){ - if(w->msg[0] == 0){ - free(w); - break; - } - werrstr("xfer: %s", w->msg); - free(w); - return Error; - } - } - - switch(rv){ - case Success: - return Eof; - case TempFail: - return Server; - default: - return Error; - } -} - -int -ftpcmd(int ctl, char *fmt, ...) -{ - va_list arg; - char buf[2*1024], *s; - - va_start(arg, fmt); - s = vseprint(buf, buf + (sizeof(buf)-4) / sizeof(*buf), fmt, arg); - va_end(arg); - if(debug) - fprint(2, "%d -> %s\n", ctl, buf); - *s++ = '\r'; - *s++ = '\n'; - if(write(ctl, buf, s - buf) != s - buf) - return -1; - return 0; -} - -int -ftprcode(int ctl, char *msg, int len) -{ - int rv; - int i; - char *p; - - len--; /* room for terminating null */ - for(;;){ - *msg = 0; - i = readline(ctl, msg, len); - if(i < 0) - break; - if(debug) - fprint(2, "%d <- %s\n", ctl, msg); - - /* stop if not a continuation */ - rv = strtol(msg, &p, 10); - if(rv >= 100 && rv < 600 && p==msg+3 && *p == ' ') - return rv/100; - } - *msg = 0; - - return -1; -} - -int -hello(int ctl) -{ - char msg[1024]; - - /* wait for hello from other side */ - if(ftprcode(ctl, msg, sizeof(msg)) != Success){ - werrstr("HELLO: %s", msg); - return Server; - } - return 0; -} - -int -getdec(char *p, int n) -{ - int x = 0; - int i; - - for(i = 0; i < n; i++) - x = x*10 + (*p++ - '0'); - return x; -} - -int -ftprestart(int ctl, Out *out, URL *u, Range *r, long mtime) -{ - Tm tm; - char msg[1024]; - long x, rmtime; - - ftpcmd(ctl, "MDTM %s", u->page); - if(ftprcode(ctl, msg, sizeof(msg)) != Success){ - r->start = 0; - return 0; /* need to do something */ - } - - /* decode modification time */ - if(strlen(msg) < 4 + 4 + 2 + 2 + 2 + 2 + 2){ - r->start = 0; - return 0; /* need to do something */ - } - memset(&tm, 0, sizeof(tm)); - tm.year = getdec(msg+4, 4) - 1900; - tm.mon = getdec(msg+4+4, 2) - 1; - tm.mday = getdec(msg+4+4+2, 2); - tm.hour = getdec(msg+4+4+2+2, 2); - tm.min = getdec(msg+4+4+2+2+2, 2); - tm.sec = getdec(msg+4+4+2+2+2+2, 2); - strcpy(tm.zone, "GMT"); - rmtime = tm2sec(&tm); - if(rmtime > mtime) - r->start = 0; - - /* get size */ - ftpcmd(ctl, "SIZE %s", u->page); - if(ftprcode(ctl, msg, sizeof(msg)) == Success){ - x = atol(msg+4); - if(r->start == x) - return 1; /* we're up to date */ - r->end = x; - } - - /* seek to restart point */ - if(r->start > 0){ - ftpcmd(ctl, "REST %lud", r->start); - if(ftprcode(ctl, msg, sizeof(msg)) == Incomplete){ - setoffset(out, r->start); - }else - r->start = 0; - } - - return 0; /* need to do something */ -} - -int -logon(int ctl) -{ - char msg[1024]; - - /* login anonymous */ - ftpcmd(ctl, "USER anonymous"); - switch(ftprcode(ctl, msg, sizeof(msg))){ - case Success: - return 0; - case Incomplete: - break; /* need password */ - default: - werrstr("USER: %s", msg); - return Server; - } - - /* send user id as password */ - sprint(msg, "%s@closedmind.org", getuser()); - ftpcmd(ctl, "PASS %s", msg); - if(ftprcode(ctl, msg, sizeof(msg)) != Success){ - werrstr("PASS: %s", msg); - return Server; - } - - return 0; -} - -int -xfertype(int ctl, char *t) -{ - char msg[1024]; - - ftpcmd(ctl, "TYPE %s", t); - if(ftprcode(ctl, msg, sizeof(msg)) != Success){ - werrstr("TYPE %s: %s", t, msg); - return Server; - } - - return 0; -} - -int -passive(int ctl, URL *u) -{ - char msg[1024]; - char ipaddr[32]; - char *f[6]; - char *p; - int fd; - int port; - char aport[12]; - - ftpcmd(ctl, "PASV"); - if(ftprcode(ctl, msg, sizeof(msg)) != Success) - return Error; - - /* get address and port number from reply, this is AI */ - p = strchr(msg, '('); - if(p == nil){ - for(p = msg+3; *p; p++) - if(isdigit(*p)) - break; - } else - p++; - if(getfields(p, f, 6, 0, ",)") < 6){ - werrstr("ftp protocol botch"); - return Server; - } - snprint(ipaddr, sizeof(ipaddr), "%s.%s.%s.%s", - f[0], f[1], f[2], f[3]); - port = ((atoi(f[4])&0xff)<<8) + (atoi(f[5])&0xff); - sprint(aport, "%d", port); - - /* open data connection */ - fd = dial(netmkaddr(ipaddr, tcpdir, aport), 0, 0, 0); - if(fd < 0){ - werrstr("passive mode failed: %r"); - return Error; - } - - /* tell remote to send a file */ - ftpcmd(ctl, "RETR %s", u->page); - if(ftprcode(ctl, msg, sizeof(msg)) != Extra){ - werrstr("RETR %s: %s", u->page, msg); - return Error; - } - return fd; -} - -int -active(int ctl, URL *u) -{ - char msg[1024]; - char dir[40], ldir[40]; - uchar ipaddr[4]; - uchar port[2]; - int lcfd, dfd, afd; - - /* announce a port for the call back */ - snprint(msg, sizeof(msg), "%s!*!0", tcpdir); - afd = announce(msg, dir); - if(afd < 0) - return Error; - - /* get a local address/port of the annoucement */ - if(getaddrport(dir, ipaddr, port) < 0){ - close(afd); - return Error; - } - - /* tell remote side address and port*/ - ftpcmd(ctl, "PORT %d,%d,%d,%d,%d,%d", ipaddr[0], ipaddr[1], ipaddr[2], - ipaddr[3], port[0], port[1]); - if(ftprcode(ctl, msg, sizeof(msg)) != Success){ - close(afd); - werrstr("active: %s", msg); - return Error; - } - - /* tell remote to send a file */ - ftpcmd(ctl, "RETR %s", u->page); - if(ftprcode(ctl, msg, sizeof(msg)) != Extra){ - close(afd); - werrstr("RETR: %s", msg); - return Server; - } - - /* wait for a connection */ - lcfd = listen(dir, ldir); - if(lcfd < 0){ - close(afd); - return Error; - } - dfd = accept(lcfd, ldir); - if(dfd < 0){ - close(afd); - close(lcfd); - return Error; - } - close(afd); - close(lcfd); - - return dfd; -} - -int -ftpxfer(int in, Out *out, Range *r) -{ - char buf[1024]; - long vtime; - int i, n; - - vtime = 0; - for(n = 0;;n += i){ - i = read(in, buf, sizeof(buf)); - if(i == 0) - break; - if(i < 0) - return Error; - if(output(out, buf, i) != i) - return Error; - r->start += i; - if(verbose && (vtime != time(0) || r->start == r->end)) { - vtime = time(0); - fprint(2, "%ld %ld\n", r->start, r->end); - } - } - return n; -} - -int -terminateftp(int ctl, int rv) -{ - close(ctl); - return rv; -} - -/* - * case insensitive strcmp (why aren't these in libc?) - */ -int -cistrncmp(char *a, char *b, int n) -{ - while(n-- > 0){ - if(tolower(*a++) != tolower(*b++)) - return -1; - } - return 0; -} - -int -cistrcmp(char *a, char *b) -{ - while(*a || *b) - if(tolower(*a++) != tolower(*b++)) - return -1; - - return 0; -} - -/* - * buffered io - */ -struct -{ - char *rp; - char *wp; - char buf[4*1024]; -} b; - -void -initibuf(void) -{ - b.rp = b.wp = b.buf; -} - -/* - * read a possibly buffered line, strip off trailing while - */ -int -readline(int fd, char *buf, int len) -{ - int n; - char *p; - int eof = 0; - - len--; - - for(p = buf;;){ - if(b.rp >= b.wp){ - n = read(fd, b.wp, sizeof(b.buf)/2); - if(n < 0) - return -1; - if(n == 0){ - eof = 1; - break; - } - b.wp += n; - } - n = *b.rp++; - if(len > 0){ - *p++ = n; - len--; - } - if(n == '\n') - break; - } - - /* drop trailing white */ - for(;;){ - if(p <= buf) - break; - n = *(p-1); - if(n != ' ' && n != '\t' && n != '\r' && n != '\n') - break; - p--; - } - *p = 0; - - if(eof && p == buf) - return -1; - - return p-buf; -} - -void -unreadline(char *line) -{ - int i, n; - - i = strlen(line); - n = b.wp-b.rp; - memmove(&b.buf[i+1], b.rp, n); - memmove(b.buf, line, i); - b.buf[i] = '\n'; - b.rp = b.buf; - b.wp = b.rp + i + 1 + n; -} - -int -readibuf(int fd, char *buf, int len) -{ - int n; - - n = b.wp-b.rp; - if(n > 0){ - if(n > len) - n = len; - memmove(buf, b.rp, n); - b.rp += n; - return n; - } - return read(fd, buf, len); -} - -int -dfprint(int fd, char *fmt, ...) -{ - char buf[4*1024]; - va_list arg; - - va_start(arg, fmt); - vseprint(buf, buf+sizeof(buf), fmt, arg); - va_end(arg); - if(debug) - fprint(2, "%d -> %s", fd, buf); - return fprint(fd, "%s", buf); -} - -int -getaddrport(char *dir, uchar *ipaddr, uchar *port) -{ - char buf[256]; - int fd, i; - char *p; - - snprint(buf, sizeof(buf), "%s/local", dir); - fd = open(buf, OREAD); - if(fd < 0) - return -1; - i = read(fd, buf, sizeof(buf)-1); - close(fd); - if(i <= 0) - return -1; - buf[i] = 0; - p = strchr(buf, '!'); - if(p != nil) - *p++ = 0; - v4parseip(ipaddr, buf); - i = atoi(p); - port[0] = i>>8; - port[1] = i; - return 0; -} - -void -md5free(DigestState *state) -{ - uchar x[MD5dlen]; - md5(nil, 0, x, state); -} - -DigestState* -md5dup(DigestState *state) -{ - char *p; - - p = md5pickle(state); - if(p == nil) - sysfatal("md5pickle: %r"); - state = md5unpickle(p); - if(state == nil) - sysfatal("md5unpickle: %r"); - free(p); - return state; -} - -void -setoffset(Out *out, int offset) -{ - md5free(out->curr); - if(offset == 0) - out->curr = md5(nil, 0, nil, nil); - else - out->curr = nil; - out->offset = offset; - out->written = offset; - if(ofile != nil) - if(seek(out->fd, offset, 0) != offset) - sysfatal("seek: %r"); -} - -/* - * write some output, discarding it (but keeping track) - * if we've already written it. if we've gone backwards, - * verify that everything previously written matches - * that which would have been written from the current - * output. - */ -int -output(Out *out, char *buf, int nb) -{ - int n, d; - uchar m0[MD5dlen], m1[MD5dlen]; - - n = nb; - d = out->written - out->offset; - assert(d >= 0); - if(d > 0){ - if(n < d){ - if(out->curr != nil) - md5((uchar*)buf, n, nil, out->curr); - out->offset += n; - return n; - } - if(out->curr != nil){ - md5((uchar*)buf, d, m0, out->curr); - out->curr = nil; - md5(nil, 0, m1, md5dup(out->hiwat)); - if(memcmp(m0, m1, MD5dlen) != 0){ - fprint(2, "integrity check failure at offset %d\n", out->written); - return -1; - } - } - buf += d; - n -= d; - out->offset += d; - } - if(n > 0){ - out->hiwat = md5((uchar*)buf, n, nil, out->hiwat); - n = write(out->fd, buf, n); - if(n > 0){ - out->offset += n; - out->written += n; - } - } - return n + d; -} - diff --git a/sys/src/cmd/webfs/buf.c b/sys/src/cmd/webfs/buf.c deleted file mode 100644 index ffd249407..000000000 --- a/sys/src/cmd/webfs/buf.c +++ /dev/null @@ -1,89 +0,0 @@ -#include <u.h> -#include <libc.h> -#include <bio.h> -#include <ip.h> -#include <plumb.h> -#include <thread.h> -#include <fcall.h> -#include <9p.h> -#include "dat.h" -#include "fns.h" - -void -initibuf(Ibuf *b, Ioproc *io, int fd) -{ - b->fd = fd; - b->io = io; - b->rp = b->wp = b->buf; -} - -int -readibuf(Ibuf *b, char *buf, int len) -{ - int n; - - n = b->wp - b->rp; - if(n > 0){ - if(n > len) - n = len; - memmove(buf, b->rp, n); - b->rp += n; - return n; - } - return ioreadn(b->io, b->fd, buf, len); -} - -void -unreadline(Ibuf *b, char *line) -{ - int i, n; - - i = strlen(line); - n = b->wp - b->rp; - memmove(&b->buf[i+1], b->rp, n); - memmove(b->buf, line, i); - b->buf[i] = '\n'; - b->rp = b->buf; - b->wp = b->rp+i+1+n; -} - -int -readline(Ibuf *b, char *buf, int len) -{ - int n; - char *p; - - len--; - - for(p = buf;;){ - if(b->rp >= b->wp){ - n = ioread(b->io, b->fd, b->wp, sizeof(b->buf)/2); - if(n < 0) - return -1; - if(n == 0) - break; - b->wp += n; - } - n = *b->rp++; - if(len > 0){ - *p++ = n; - len--; - } - if(n == '\n') - break; - } - - /* drop trailing white */ - for(;;){ - if(p <= buf) - break; - n = *(p-1); - if(n != ' ' && n != '\t' && n != '\r' && n != '\n') - break; - p--; - } - - *p = 0; - return p-buf; -} - diff --git a/sys/src/cmd/webfs/buq.c b/sys/src/cmd/webfs/buq.c new file mode 100644 index 000000000..cbf5c9672 --- /dev/null +++ b/sys/src/cmd/webfs/buq.c @@ -0,0 +1,263 @@ +#include <u.h> +#include <libc.h> +#include <ctype.h> +#include <fcall.h> +#include <thread.h> +#include <9p.h> + +#include "dat.h" +#include "fns.h" + +static void +matchreq(Buq *q) +{ + Req *r; + Buf *b; + int l; + + while(r = q->rh){ + if((b = q->bh) == nil){ + if(q->closed){ + if((q->rh = r->aux) == nil) + q->rt = &q->rh; + if(r->ifcall.type == Tread) + r->ofcall.count = 0; + respond(r, q->error); + continue; + } + break; + } + if((q->rh = r->aux) == nil) + q->rt = &q->rh; + if(r->ifcall.type == Topen){ + respond(r, nil); + continue; + } + l = b->ep - b->rp; + if(l > r->ifcall.count) + l = r->ifcall.count; + memmove(r->ofcall.data, b->rp, l); + r->ofcall.count = l; + respond(r, nil); + b->rp += l; + q->size -= l; + if(b->rp >= b->ep){ + if((q->bh = b->next) == nil) + q->bt = &q->bh; + if(r = b->wreq){ + r->ofcall.count = r->ifcall.count; + respond(r, nil); + } + free(b); + } + } + rwakeupall(&q->rz); +} + +int +buread(Buq *q, void *v, int l) +{ + Req *r; + Buf *b; + + qlock(q); + while((b = q->bh) == nil){ + if(q->closed){ + l = 0; + if(q->error){ + werrstr("%s", q->error); + l = -1; + } + qunlock(q); + return l; + } + rsleep(&q->rz); + } + if(l > (b->ep - b->rp)) + l = b->ep - b->rp; + memmove(v, b->rp, l); + b->rp += l; + q->size -= l; + rwakeup(&q->rz); + if(b->rp < b->ep){ + qunlock(q); + return l; + } + if((q->bh = b->next) == nil) + q->bt = &q->bh; + qunlock(q); + if(r = b->wreq){ + r->ofcall.count = r->ifcall.count; + respond(r, nil); + } + free(b); + return l; +} + +int +buwrite(Buq *q, void *v, int l) +{ + Buf *b; + + b = emalloc(sizeof(*b) + l); + b->wreq = nil; + b->rp = b->end; + b->ep = b->rp + l; + memmove(b->rp, v, l); + b->next = nil; + qlock(q); + if(q->closed){ + l = 0; + if(q->error){ + werrstr("%s", q->error); + l = -1; + } + qunlock(q); + free(b); + return l; + } + *q->bt = b; + q->bt = &b->next; + q->size += l; + matchreq(q); + while(!q->closed && q->size >= q->limit) + rsleep(&q->rz); + qunlock(q); + return l; +} + +void +buclose(Buq *q, char *error) +{ + if(q == nil) + return; + qlock(q); + if(!q->closed){ + if(error) + q->error = estrdup9p(error); + q->closed = 1; + matchreq(q); + } + qunlock(q); +} + +Buq* +bualloc(int limit) +{ + Buq *q; + + q = emalloc(sizeof(*q)); + q->limit = limit; + q->rt = &q->rh; + q->bt = &q->bh; + q->rz.l = q; + incref(q); + return q; +} + +void +bufree(Buq *q) +{ + Buf *b; + Key *k; + + if(q == nil || decref(q)) + return; + while(b = q->bh){ + q->bh = b->next; + free(b); + } + freeurl(q->url); + while(k = q->hdr){ + q->hdr = k->next; + free(k); + } + free(q->error); + free(q); +} + +void +bureq(Buq *q, Req *r) +{ + Buf *b; + int l; + + switch(r->ifcall.type){ + default: + respond(r, "bug in bureq"); + return; + case Twrite: + l = r->ifcall.count; + if((q->size + l) < q->limit){ + r->ofcall.count = buwrite(q, r->ifcall.data, r->ifcall.count); + respond(r, nil); + return; + } + b = emalloc(sizeof(*b)); + b->wreq = r; + b->rp = (uchar*)r->ifcall.data; + b->ep = b->rp + l; + b->next = nil; + qlock(q); + *q->bt = b; + q->bt = &b->next; + q->size += l; + break; + case Tread: + case Topen: + r->aux = nil; + qlock(q); + *q->rt = r; + q->rt = (Req**)&r->aux; + break; + } + matchreq(q); + qunlock(q); +} + +void +buflushreq(Buq *q, Req *r) +{ + Buf **bb, *b; + Req **rr; + int l; + + switch(r->ifcall.type){ + default: + respond(r, "bug in bufflushreq"); + return; + case Twrite: + qlock(q); + for(bb = &q->bh; b = *bb; bb = &b->next){ + if(b->wreq != r) + continue; + /* fake successfull write */ + l = b->ep - b->rp; + b = realloc(b, sizeof(*b) + l); + memmove(b->end, b->rp, l); + b->rp = b->end; + b->ep = b->rp + l; + b->wreq = nil; + *bb = b; + if(b->next == nil) + q->bt = &b->next; + r->ofcall.count = r->ifcall.count; + respond(r, nil); + break; + } + break; + case Topen: + case Tread: + qlock(q); + for(rr = &q->rh; *rr; rr = (Req**)&((*rr)->aux)){ + if(*rr != r) + continue; + if((*rr = r->aux) == nil) + q->rt = rr; + respond(r, "interrupted"); + break; + } + break; + } + qunlock(q); +} diff --git a/sys/src/cmd/webfs/client.c b/sys/src/cmd/webfs/client.c deleted file mode 100644 index c0469f72f..000000000 --- a/sys/src/cmd/webfs/client.c +++ /dev/null @@ -1,411 +0,0 @@ -#include <u.h> -#include <libc.h> -#include <bio.h> -#include <ip.h> -#include <plumb.h> -#include <thread.h> -#include <fcall.h> -#include <9p.h> -#include "dat.h" -#include "fns.h" - -int nclient; -Client **client; - -static void clientthread(void*); -int -newclient(int plumbed) -{ - int i; - Client *c; - - for(i=0; i<nclient; i++) - if(client[i]->ref==0) - return i; - - c = emalloc(sizeof(Client)); - c->plumbed = plumbed; - c->creq = chancreate(sizeof(Req*), 8); - threadcreate(clientthread, c, STACK); - - c->io = ioproc(); - c->num = nclient; - c->ctl = globalctl; - clonectl(&c->ctl); - if(nclient%16 == 0) - client = erealloc(client, (nclient+16)*sizeof(client[0])); - client[nclient++] = c; - return nclient-1; -} - -void -closeclient(Client *c) -{ - if(--c->ref == 0){ - if(c->bodyopened){ - if(c->url && c->url->close) - (*c->url->close)(c); - c->bodyopened = 0; - } - free(c->contenttype); - c->contenttype = nil; - free(c->postbody); - c->postbody = nil; - freeurl(c->url); - c->url = nil; - freeurl(c->baseurl); - c->baseurl = nil; - free(c->redirect); - c->redirect = nil; - free(c->authenticate); - c->authenticate = nil; - c->npostbody = 0; - c->havepostbody = 0; - c->bodyopened = 0; - } -} - -void -clonectl(Ctl *c) -{ - if(c->useragent) - c->useragent = estrdup(c->useragent); -} - -void -clientbodyopen(Client *c, Req *r) -{ - char e[ERRMAX], *next, *frag; - int i, nauth; - Url *u; - - nauth = 0; - next = nil; - for(i=0; i<=c->ctl.redirectlimit; i++){ - if(c->url == nil){ - werrstr("nil url"); - goto Error; - } - if(c->url->open == nil){ - werrstr("unsupported url type"); - goto Error; - } - if(fsdebug) - fprint(2, "try %s\n", c->url->url); - if(c->url->open(c, c->url) < 0){ - Error: - rerrstr(e, sizeof e); - if(next) - fprint(2, "next %s (but for error)\n", next); - free(next); - c->iobusy = 0; - if(r != nil) - r->fid->omode = -1; - closeclient(c); /* not opening */ - if(r != nil) - respond(r, e); - return; - } - free(next); - next = c->redirect; - c->redirect = nil; - if(c->authenticate && nauth++ < 1){ - if(c->url->close) - (*c->url->close)(c); - continue; - } - if(next == nil) - break; - if(i==c->ctl.redirectlimit){ - werrstr("redirect limit reached"); - goto Error; - } - if((u = parseurl(next, c->url)) == nil) - goto Error; - /* if there was a redirect, carry over the fragment */ - if((frag = c->url->fragment) && u->fragment == nil){ - u->fragment = estrdup(frag); - rewriteurl(u); - } - if(urldebug) - fprint(2, "parseurl %s got scheme %d\n", next, u->ischeme); - if(u->ischeme == USunknown){ - werrstr("redirect with unknown URL scheme"); - goto Error; - } - if(u->ischeme == UScurrent){ - werrstr("redirect to URL relative to current document"); - goto Error; - } - if(c->url->close) - (*c->url->close)(c); - freeurl(c->url); - c->url = u; - } - free(next); - c->iobusy = 0; - if(r != nil) - respond(r, nil); -} - -void -plumburl(char *url, char *base) -{ - int i; - Client *c; - Url *ubase, *uurl; - - ubase = nil; - if(base){ - ubase = parseurl(base, nil); - if(ubase == nil) - return; - } - uurl = parseurl(url, ubase); - if(uurl == nil){ - freeurl(ubase); - return; - } - i = newclient(1); - c = client[i]; - c->ref++; - c->baseurl = ubase; - c->url = uurl; - sendp(c->creq, nil); -} - -void -clientbodyread(Client *c, Req *r) -{ - char e[ERRMAX]; - - if(c->url->read == nil){ - respond(r, "unsupported url type"); - return; - } - if(c->url->read(c, r) < 0){ - rerrstr(e, sizeof e); - c->iobusy = 0; - respond(r, e); - return; - } - c->iobusy = 0; - respond(r, nil); -} - -static void -clientthread(void *a) -{ - Client *c; - Req *r; - - c = a; - if(c->plumbed) { - recvp(c->creq); - if(c->url == nil){ - fprint(2, "bad url got plumbed\n"); - return; - } - clientbodyopen(c, nil); - replumb(c); - } - while((r = recvp(c->creq)) != nil){ - if(fsdebug) - fprint(2, "clientthread %F\n", &r->ifcall); - switch(r->ifcall.type){ - case Topen: - if(c->plumbed) { - c->plumbed = 0; - c->ref--; /* from plumburl() */ - respond(r, nil); - } - else - clientbodyopen(c, r); - break; - case Tread: - clientbodyread(c, r); - break; - case Tflush: - respond(r, nil); - } - if(fsdebug) - fprint(2, "clientthread finished req\n"); - } -} - -enum -{ - Bool, - Int, - String, - XRel, - XUrl, - Fn, -}; - -typedef struct Ctab Ctab; -struct Ctab { - char *name; - int type; - void *offset; -}; - -Ctab ctltab[] = { - "acceptcookies", Bool, (void*)offsetof(Ctl, acceptcookies), - "sendcookies", Bool, (void*)offsetof(Ctl, sendcookies), - "redirectlimit", Int, (void*)offsetof(Ctl, redirectlimit), - "useragent", String, (void*)offsetof(Ctl, useragent), -}; - -Ctab globaltab[] = { - "chatty9p", Int, &chatty9p, - "fsdebug", Int, &fsdebug, - "cookiedebug", Int, &cookiedebug, - "urldebug", Int, &urldebug, - "httpdebug", Int, &httpdebug, -}; - -Ctab clienttab[] = { - "baseurl", XUrl, (void*)offsetof(Client, baseurl), - "url", XRel, (void*)offsetof(Client, url), -}; - -static Ctab* -findcmd(char *cmd, Ctab *tab, int ntab) -{ - int i; - - for(i=0; i<ntab; i++) - if(strcmp(tab[i].name, cmd) == 0) - return &tab[i]; - return nil; -} - -static void -parseas(Req *r, char *arg, int type, void *a) -{ - Url *u, *base; - char e[ERRMAX]; - - base = nil; - switch(type){ - case Bool: - if(strcmp(arg, "on")==0 || strcmp(arg, "1")==0) - *(int*)a = 1; - else - *(int*)a = 0; - break; - case String: - free(*(char**)a); - *(char**)a = estrdup(arg); - break; - case XRel: - base = ((Client*)a)->baseurl; - case XUrl: - u = parseurl(arg, base); - if(u == nil){ - snprint(e, sizeof e, "parseurl: %r"); - respond(r, e); - return; - } - freeurl(*(Url**)a); - *(Url**)a = u; - break; - case Int: - if(strcmp(arg, "on")==0) - *(int*)a = 1; - else - *(int*)a = atoi(arg); - break; - } - respond(r, nil); -} - -int -ctlwrite(Req *r, Ctl *ctl, char *cmd, char *arg) -{ - void *a; - Ctab *t; - - if((t = findcmd(cmd, ctltab, nelem(ctltab))) == nil) - return 0; - a = (void*)((uintptr)ctl+(uintptr)t->offset); - parseas(r, arg, t->type, a); - return 1; -} - -int -clientctlwrite(Req *r, Client *c, char *cmd, char *arg) -{ - void *a; - Ctab *t; - - if((t = findcmd(cmd, clienttab, nelem(clienttab))) == nil) - return 0; - a = (void*)((uintptr)c+(uintptr)t->offset); - parseas(r, arg, t->type, a); - return 1; -} - -int -globalctlwrite(Req *r, char *cmd, char *arg) -{ - void *a; - Ctab *t; - - if((t = findcmd(cmd, globaltab, nelem(globaltab))) == nil) - return 0; - a = t->offset; - parseas(r, arg, t->type, a); - return 1; -} - -static void -ctlfmt(Ctl *c, char *s) -{ - int i; - void *a; - char *t; - - for(i=0; i<nelem(ctltab); i++){ - a = (void*)((uintptr)c+(uintptr)ctltab[i].offset); - switch(ctltab[i].type){ - case Bool: - s += sprint(s, "%s %s\n", ctltab[i].name, *(int*)a ? "on" : "off"); - break; - case Int: - s += sprint(s, "%s %d\n", ctltab[i].name, *(int*)a); - break; - case String: - t = *(char**)a; - if(t != nil) - s += sprint(s, "%s %.*s%s\n", ctltab[i].name, utfnlen(t, 100), t, strlen(t)>100 ? "..." : ""); - break; - } - } -} - -void -ctlread(Req *r, Client *c) -{ - char buf[1024]; - - sprint(buf, "%11d \n", c->num); - ctlfmt(&c->ctl, buf+strlen(buf)); - readstr(r, buf); - respond(r, nil); -} - -void -globalctlread(Req *r) -{ - char buf[1024], *s; - int i; - - s = buf; - for(i=0; i<nelem(globaltab); i++) - s += sprint(s, "%s %d\n", globaltab[i].name, *(int*)globaltab[i].offset); - ctlfmt(&globalctl, s); - readstr(r, buf); - respond(r, nil); -} diff --git a/sys/src/cmd/webfs/cookies.c b/sys/src/cmd/webfs/cookies.c deleted file mode 100644 index 1738e599a..000000000 --- a/sys/src/cmd/webfs/cookies.c +++ /dev/null @@ -1,1179 +0,0 @@ -#include <u.h> -#include <libc.h> -#include <bio.h> -#include <ndb.h> -#include <fcall.h> -#include <thread.h> -#include <9p.h> -#include <ctype.h> -#include "dat.h" -#include "fns.h" - -int cookiedebug; - -typedef struct Cookie Cookie; -typedef struct Jar Jar; - -struct Cookie -{ - /* external info */ - char* name; - char* value; - char* dom; /* starts with . */ - char* path; - char* version; - char* comment; /* optional, may be nil */ - - uint expire; /* time of expiration: ~0 means when webcookies dies */ - int secure; - int explicitdom; /* dom was explicitly set */ - int explicitpath; /* path was explicitly set */ - int netscapestyle; - - /* internal info */ - int deleted; - int mark; - int ondisk; -}; - -struct Jar -{ - Cookie *c; - int nc; - int mc; - - Qid qid; - int dirty; - char *file; - char *lockfile; -}; - -struct { - char *s; - int offset; - int ishttp; -} stab[] = { - "domain", offsetof(Cookie, dom), 1, - "path", offsetof(Cookie, path), 1, - "name", offsetof(Cookie, name), 0, - "value", offsetof(Cookie, value), 0, - "comment", offsetof(Cookie, comment), 1, - "version", offsetof(Cookie, version), 1, -}; - -struct { - char *s; - int offset; -} itab[] = { - "expire", offsetof(Cookie, expire), - "secure", offsetof(Cookie, secure), - "explicitdomain", offsetof(Cookie, explicitdom), - "explicitpath", offsetof(Cookie, explicitpath), - "netscapestyle", offsetof(Cookie, netscapestyle), -}; - -#pragma varargck type "J" Jar* -#pragma varargck type "K" Cookie* - -/* HTTP format */ -static int -jarfmt(Fmt *fp) -{ - int i; - Jar *jar; - - jar = va_arg(fp->args, Jar*); - - if(jar == nil || jar->nc == 0) - return 0; - - fmtstrcpy(fp, "Cookie: "); - if(jar->c[0].version) - fmtprint(fp, "$Version=%s; ", jar->c[0].version); - for(i=0; i<jar->nc; i++) - fmtprint(fp, "%s%s=%s", i ? "; ": "", jar->c[i].name, jar->c[i].value); - fmtstrcpy(fp, "\r\n"); - return 0; -} - -/* individual cookie */ -static int -cookiefmt(Fmt *fp) -{ - int j, k, first; - char *t; - Cookie *c; - - c = va_arg(fp->args, Cookie*); - - first = 1; - for(j=0; j<nelem(stab); j++){ - t = *(char**)((uintptr)c+stab[j].offset); - if(t == nil) - continue; - if(first) - first = 0; - else - fmtstrcpy(fp, " "); - fmtprint(fp, "%s=%q", stab[j].s, t); - } - for(j=0; j<nelem(itab); j++){ - k = *(int*)((uintptr)c+itab[j].offset); - if(k == 0) - continue; - if(first) - first = 0; - else - fmtstrcpy(fp, " "); - fmtprint(fp, "%s=%ud", itab[j].s, k); - } - return 0; -} - -/* - * sort cookies: - * - alpha by name - * - alpha by domain - * - longer paths first, then alpha by path (RFC2109 4.3.4) - */ -static int -cookiecmp(Cookie *a, Cookie *b) -{ - int i; - - if((i = strcmp(a->name, b->name)) != 0) - return i; - if((i = cistrcmp(a->dom, b->dom)) != 0) - return i; - if((i = strlen(b->path) - strlen(a->path)) != 0) - return i; - if((i = strcmp(a->path, b->path)) != 0) - return i; - return 0; -} - -static int -exactcookiecmp(Cookie *a, Cookie *b) -{ - int i; - - if((i = cookiecmp(a, b)) != 0) - return i; - if((i = strcmp(a->value, b->value)) != 0) - return i; - if(a->version || b->version){ - if(!a->version) - return -1; - if(!b->version) - return 1; - if((i = strcmp(a->version, b->version)) != 0) - return i; - } - if(a->comment || b->comment){ - if(!a->comment) - return -1; - if(!b->comment) - return 1; - if((i = strcmp(a->comment, b->comment)) != 0) - return i; - } - if((i = b->expire - a->expire) != 0) - return i; - if((i = b->secure - a->secure) != 0) - return i; - if((i = b->explicitdom - a->explicitdom) != 0) - return i; - if((i = b->explicitpath - a->explicitpath) != 0) - return i; - if((i = b->netscapestyle - a->netscapestyle) != 0) - return i; - - return 0; -} - -static void -freecookie(Cookie *c) -{ - int i; - - for(i=0; i<nelem(stab); i++) - free(*(char**)((uintptr)c+stab[i].offset)); -} - -static void -copycookie(Cookie *c) -{ - int i; - char **ps; - - for(i=0; i<nelem(stab); i++){ - ps = (char**)((uintptr)c+stab[i].offset); - if(*ps) - *ps = estrdup(*ps); - } -} - -static void -delcookie(Jar *j, Cookie *c) -{ - int i; - - j->dirty = 1; - i = c - j->c; - if(i < 0 || i >= j->nc) - abort(); - c->deleted = 1; -} - -static void -addcookie(Jar *j, Cookie *c) -{ - int i; - - if(!c->name || !c->value || !c->path || !c->dom){ - fprint(2, "not adding incomplete cookie\n"); - return; - } - - if(cookiedebug) - fprint(2, "add %K\n", c); - - for(i=0; i<j->nc; i++) - if(cookiecmp(&j->c[i], c) == 0){ - if(cookiedebug) - fprint(2, "cookie %K matches %K\n", &j->c[i], c); - if(exactcookiecmp(&j->c[i], c) == 0){ - if(cookiedebug) - fprint(2, "exactly!\n"); - j->c[i].mark = 0; - return; - } - delcookie(j, &j->c[i]); - } - - j->dirty = 1; - if(j->nc == j->mc){ - j->mc += 16; - j->c = erealloc9p(j->c, j->mc*sizeof(Cookie)); - } - j->c[j->nc] = *c; - copycookie(&j->c[j->nc]); - j->nc++; -} - -static void -purgejar(Jar *j) -{ - int i; - - for(i=j->nc-1; i>=0; i--){ - if(!j->c[i].deleted) - continue; - freecookie(&j->c[i]); - --j->nc; - j->c[i] = j->c[j->nc]; - } -} - -static void -addtojar(Jar *jar, char *line, int ondisk) -{ - Cookie c; - int i, j, nf, *pint; - char *f[20], *attr, *val, **pstr; - - memset(&c, 0, sizeof c); - c.expire = ~0; - c.ondisk = ondisk; - nf = tokenize(line, f, nelem(f)); - for(i=0; i<nf; i++){ - attr = f[i]; - if((val = strchr(attr, '=')) != nil) - *val++ = '\0'; - else - val = ""; - /* string attributes */ - for(j=0; j<nelem(stab); j++){ - if(strcmp(stab[j].s, attr) == 0){ - pstr = (char**)((uintptr)&c+stab[j].offset); - *pstr = val; - } - } - /* integer attributes */ - for(j=0; j<nelem(itab); j++){ - if(strcmp(itab[j].s, attr) == 0){ - pint = (int*)((uintptr)&c+itab[j].offset); - if(val[0]=='\0') - *pint = 1; - else - *pint = strtoul(val, 0, 0); - } - } - } - if(c.name==nil || c.value==nil || c.dom==nil || c.path==nil){ - if(cookiedebug) - fprint(2, "ignoring fractional cookie %K\n", &c); - return; - } - addcookie(jar, &c); -} - -static Jar* -newjar(void) -{ - Jar *jar; - - jar = emalloc(sizeof(Jar)); - return jar; -} - -static int -expirejar(Jar *jar, int exiting) -{ - int i, n; - uint now; - - now = time(0); - n = 0; - for(i=0; i<jar->nc; i++){ - if(jar->c[i].expire < now || (exiting && jar->c[i].expire==~0)){ - if(cookiedebug) - fprint(2, "cookie %K expired\n", &jar->c[i]); - delcookie(jar, &jar->c[i]); - n++; - } - } - return n; -} - -static void -dumpjar(Jar *jar, char *desc) -{ - int i; - Biobuf *b; - char *s; - - print("%s\n", desc); - print("\tin memory:\n"); - - for(i=0; i<jar->nc; i++) - print("\t%K%s%s%s\n", &jar->c[i], - jar->c[i].ondisk ? " ondisk" : "", - jar->c[i].deleted ? " deleted" : "", - jar->c[i].mark ? " mark" : ""); - print("\n\ton disk:\n"); - if((b = Bopen(jar->file, OREAD)) == nil){ - print("\tno file\n"); - }else{ - while((s = Brdstr(b, '\n', 1)) != nil){ - print("\t%s\n", s); - free(s); - } - Bterm(b); - } - print("\n"); -} - -static int -syncjar(Jar *jar) -{ - int i, fd; - char *line; - Dir *d; - Biobuf *b; - Qid q; - - if(jar->file==nil) - return 0; - - memset(&q, 0, sizeof q); - if((d = dirstat(jar->file)) != nil){ - q = d->qid; - if(d->qid.path != jar->qid.path || d->qid.vers != jar->qid.vers) - jar->dirty = 1; - free(d); - } - - if(jar->dirty == 0) - return 0; - - fd = -1; - for(i=0; i<50; i++){ - if((fd = create(jar->lockfile, OWRITE, DMEXCL|0666)) < 0){ - sleep(100); - continue; - } - break; - } - if(fd < 0){ - if(cookiedebug) - fprint(2, "open %s: %r", jar->lockfile); - werrstr("cannot acquire jar lock: %r"); - return -1; - } - - for(i=0; i<jar->nc; i++) /* mark is cleared by addcookie */ - jar->c[i].mark = jar->c[i].ondisk; - - if((b = Bopen(jar->file, OREAD)) == nil){ - if(cookiedebug) - fprint(2, "Bopen %s: %r", jar->file); - werrstr("cannot read cookie file %s: %r", jar->file); - close(fd); - return -1; - } - for(; (line = Brdstr(b, '\n', 1)) != nil; free(line)){ - if(*line == '#') - continue; - addtojar(jar, line, 1); - } - Bterm(b); - - for(i=0; i<jar->nc; i++) - if(jar->c[i].mark && jar->c[i].expire != ~0) - delcookie(jar, &jar->c[i]); - - purgejar(jar); - - b = Bopen(jar->file, OTRUNC|OWRITE); - if(b == nil){ - if(cookiedebug) - fprint(2, "Bopen write %s: %r", jar->file); - close(fd); - return -1; - } - Bprint(b, "# webcookies cookie jar\n"); - Bprint(b, "# comments and non-standard fields will be lost\n"); - for(i=0; i<jar->nc; i++){ - if(jar->c[i].expire == ~0) - continue; - Bprint(b, "%K\n", &jar->c[i]); - jar->c[i].ondisk = 1; - } - Bterm(b); - - jar->dirty = 0; - close(fd); - if((d = dirstat(jar->file)) != nil){ - jar->qid = d->qid; - free(d); - } - return 0; -} - -static Jar* -readjar(char *file) -{ - char *lock, *p; - Jar *jar; - - jar = newjar(); - lock = emalloc(strlen(file)+10); - strcpy(lock, file); - if((p = strrchr(lock, '/')) != nil) - p++; - else - p = lock; - memmove(p+2, p, strlen(p)+1); - p[0] = 'L'; - p[1] = '.'; - jar->lockfile = lock; - jar->file = file; - jar->dirty = 1; - - if(syncjar(jar) < 0){ - free(jar->file); - free(jar->lockfile); - free(jar); - return nil; - } - return jar; -} - -static void -closejar(Jar *jar) -{ - int i; - - if(jar == nil) - return; - expirejar(jar, 0); - - if(syncjar(jar) < 0) - fprint(2, "warning: cannot rewrite cookie jar: %r\n"); - - for(i=0; i<jar->nc; i++) - freecookie(&jar->c[i]); - - free(jar->file); - free(jar->c); - free(jar); -} - -/* - * Domain name matching is per RFC2109, section 2: - * - * Hosts names can be specified either as an IP address or a FQHN - * string. Sometimes we compare one host name with another. Host A's - * name domain-matches host B's if - * - * * both host names are IP addresses and their host name strings match - * exactly; or - * - * * both host names are FQDN strings and their host name strings match - * exactly; or - * - * * A is a FQDN string and has the form NB, where N is a non-empty name - * string, B has the form .B', and B' is a FQDN string. (So, x.y.com - * domain-matches .y.com but not y.com.) - * - * Note that domain-match is not a commutative operation: a.b.c.com - * domain-matches .c.com, but not the reverse. - * - * (This does not verify that IP addresses and FQDN's are well-formed.) - */ -static int -isdomainmatch(char *name, char *pattern) -{ - int lname, lpattern; - - if(cistrcmp(name, pattern)==0) - return 1; - - if(strcmp(ipattr(name), "dom")==0 && pattern[0]=='.'){ - lname = strlen(name); - lpattern = strlen(pattern); - /* e.g., name: www.google.com && pattern: .google.com */ - if(lname >= lpattern && cistrcmp(name+lname-lpattern, pattern)==0) - return 1; - /* e.g., name: google.com && pattern: .google.com */ - if(lpattern > lname && - cistrcmp(pattern+lpattern-lname, name) == 0) - return 1; - } - return 0; -} - -/* - * RFC2109 4.3.4: - * - domain must match - * - path in cookie must be a prefix of request path - * - cookie must not have expired - */ -static int -iscookiematch(Cookie *c, char *dom, char *path, uint now) -{ - return isdomainmatch(dom, c->dom) - && strncmp(c->path, path, strlen(c->path))==0 - && (c->expire == 0 || c->expire >= now); -} - -/* - * Produce a subjar of matching cookies. - * Secure cookies are only included if secure is set. - */ -static Jar* -cookiesearch(Jar *jar, char *dom, char *path, int issecure) -{ - int i; - Jar *j; - uint now; - - if(cookiedebug) - fprint(2, "cookiesearch %s %s %d\n", dom, path, issecure); - now = time(0); - j = newjar(); - for(i=0; i<jar->nc; i++){ - if((issecure || !jar->c[i].secure) && - iscookiematch(&jar->c[i], dom, path, now)){ - if(cookiedebug){ - fprint(2, "\t%s %s %d %s\n", jar->c[i].dom, - jar->c[i].path, jar->c[i].secure, - jar->c[i].name); - fprint(2, "\tmatched\n"); - } - addcookie(j, &jar->c[i]); - } - } - if(j->nc == 0){ - closejar(j); - werrstr("no cookies found"); - return nil; - } - qsort(j->c, j->nc, sizeof(j->c[0]), (int(*)(void*, void*))cookiecmp); - return j; -} - -/* - * RFC2109 4.3.2 security checks - */ -static char* -isbadcookie(Cookie *c, char *dom, char *path) -{ - int lcdom, ldom; - - if(strncmp(c->path, path, strlen(c->path)) != 0) - return "cookie path is not a prefix of the request path"; - - /* - * fgb says omitting this test is necessary to get some sites to work, - * but it seems dubious. - */ - if(c->explicitdom && c->dom[0] != '.') - return "cookie domain doesn't start with dot"; - - lcdom = strlen(c->dom); - if(memchr(c->dom+1, '.', lcdom-1-1) == nil) - return "cookie domain doesn't have embedded dots"; - - if(!isdomainmatch(dom, c->dom)) - return "request host does not match cookie domain"; - - ldom = strlen(dom); - if(strcmp(ipattr(dom), "dom")==0 && lcdom > ldom && - memchr(dom, '.', lcdom - ldom) != nil) - return "request host contains dots before cookie domain"; - - return 0; -} - -/* - * Sunday, 25-Jan-2002 12:24:36 GMT - * Sunday, 25 Jan 2002 12:24:36 GMT - * Sun, 25 Jan 02 12:24:36 GMT - */ -static int -isleap(int year) -{ - return year%4==0 && (year%100!=0 || year%400==0); -} - -static uint -strtotime(char *s) -{ - char *os; - int i; - Tm tm; - - static int mday[2][12] = { - 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, - 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, - }; - static char *wday[] = { - "Sunday", "Monday", "Tuesday", "Wednesday", - "Thursday", "Friday", "Saturday", - }; - static char *mon[] = { - "Jan", "Feb", "Mar", "Apr", "May", "Jun", - "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", - }; - - os = s; - /* Sunday, */ - for(i=0; i<nelem(wday); i++){ - if(cistrncmp(s, wday[i], strlen(wday[i])) == 0){ - s += strlen(wday[i]); - break; - } - if(cistrncmp(s, wday[i], 3) == 0){ - s += 3; - break; - } - } - if(i==nelem(wday)){ - if(cookiedebug) - fprint(2, "bad wday (%s)\n", os); - return -1; - } - if(*s++ != ',' || *s++ != ' '){ - if(cookiedebug) - fprint(2, "bad wday separator (%s)\n", os); - return -1; - } - - /* 25- */ - if(!isdigit(s[0]) || !isdigit(s[1]) || (s[2]!='-' && s[2]!=' ')){ - if(cookiedebug) - fprint(2, "bad day of month (%s)\n", os); - return -1; - } - tm.mday = strtol(s, 0, 10); - s += 3; - - /* Jan- */ - for(i=0; i<nelem(mon); i++) - if(cistrncmp(s, mon[i], 3) == 0){ - tm.mon = i; - s += 3; - break; - } - if(i==nelem(mon)){ - if(cookiedebug) - fprint(2, "bad month (%s)\n", os); - return -1; - } - if(s[0] != '-' && s[0] != ' '){ - if(cookiedebug) - fprint(2, "bad month separator (%s)\n", os); - return -1; - } - s++; - - /* 2002 */ - if(!isdigit(s[0]) || !isdigit(s[1])){ - if(cookiedebug) - fprint(2, "bad year (%s)\n", os); - return -1; - } - tm.year = strtol(s, 0, 10); - s += 2; - if(isdigit(s[0]) && isdigit(s[1])) - s += 2; - else{ - if(tm.year <= 68) - tm.year += 2000; - else - tm.year += 1900; - } - if(tm.mday==0 || tm.mday > mday[isleap(tm.year)][tm.mon]){ - if(cookiedebug) - fprint(2, "invalid day of month (%s)\n", os); - return -1; - } - tm.year -= 1900; - if(*s++ != ' '){ - if(cookiedebug) - fprint(2, "bad year separator (%s)\n", os); - return -1; - } - - if(!isdigit(s[0]) || !isdigit(s[1]) || s[2]!=':' - || !isdigit(s[3]) || !isdigit(s[4]) || s[5]!=':' - || !isdigit(s[6]) || !isdigit(s[7]) || s[8]!=' '){ - if(cookiedebug) - fprint(2, "bad time (%s)\n", os); - return -1; - } - - tm.hour = atoi(s); - tm.min = atoi(s+3); - tm.sec = atoi(s+6); - if(tm.hour >= 24 || tm.min >= 60 || tm.sec >= 60){ - if(cookiedebug) - fprint(2, "invalid time (%s)\n", os); - return -1; - } - s += 9; - - if(cistrcmp(s, "GMT") != 0){ - if(cookiedebug) - fprint(2, "time zone not GMT (%s)\n", os); - return -1; - } - strcpy(tm.zone, "GMT"); - tm.yday = 0; - return tm2sec(&tm); -} - -/* - * skip linear whitespace. we're a bit more lenient than RFC2616 2.2. - */ -static char* -skipspace(char *s) -{ - while(*s=='\r' || *s=='\n' || *s==' ' || *s=='\t') - s++; - return s; -} - -/* - * Try to identify old netscape headers. - * The old headers: - * - didn't allow spaces around the '=' - * - used an 'Expires' attribute - * - had no 'Version' attribute - * - had no quotes - * - allowed whitespace in values - * - apparently separated attr/value pairs with ';' exclusively - */ -static int -isnetscape(char *hdr) -{ - char *s; - - for(s=hdr; (s=strchr(s, '=')) != nil; s++){ - if(isspace(s[1]) || (s > hdr && isspace(s[-1]))) - return 0; - if(s[1]=='"') - return 0; - } - if(cistrstr(hdr, "version=")) - return 0; - return 1; -} - -/* - * Parse HTTP response headers, adding cookies to jar. - * Overwrites the headers. - */ -static char* parsecookie(Cookie*, char*, char**, int, char*, char*); -static int -parsehttp(Jar *jar, char *hdr, char *dom, char *path) -{ - static char setcookie[] = "Set-Cookie:"; - char *e, *p, *nextp; - Cookie c; - int isns, n; - - isns = isnetscape(hdr); - n = 0; - for(p=hdr; p; p=nextp){ - p = skipspace(p); - if(*p == '\0') - break; - nextp = strchr(p, '\n'); - if(nextp != nil) - *nextp++ = '\0'; - if(cistrncmp(p, setcookie, strlen(setcookie)) != 0) - continue; - if(cookiedebug) - fprint(2, "%s\n", p); - p = skipspace(p+strlen(setcookie)); - for(; *p; p=skipspace(p)){ - if((e = parsecookie(&c, p, &p, isns, dom, path)) != nil){ - if(cookiedebug) - fprint(2, "parse cookie: %s\n", e); - break; - } - if((e = isbadcookie(&c, dom, path)) != nil){ - if(cookiedebug) - fprint(2, "reject cookie: %s\n", e); - continue; - } - addcookie(jar, &c); - n++; - } - } - - return n; -} - -static char* -skipquoted(char *s) -{ - /* - * Sec 2.2 of RFC2616 defines a "quoted-string" as: - * - * quoted-string = ( <"> *(qdtext | quoted-pair ) <"> ) - * qdtext = <any TEXT except <">> - * quoted-pair = "\" CHAR - * - * TEXT is any octet except CTLs, but including LWS; - * LWS is [CR LF] 1*(SP | HT); - * CHARs are ASCII octets 0-127; (NOTE: we reject 0's) - * CTLs are octets 0-31 and 127; - */ - if(*s != '"') - return s; - - for(s++; 32 <= *s && *s < 127 && *s != '"'; s++) - if(*s == '\\' && *(s+1) != '\0') - s++; - return s; -} - -static char* -skiptoken(char *s) -{ - /* - * Sec 2.2 of RFC2616 defines a "token" as - * 1*<any CHAR except CTLs or separators>; - * CHARs are ASCII octets 0-127; - * CTLs are octets 0-31 and 127; - * separators are "()<>@,;:\/[]?={}", double-quote, SP (32), and HT (9) - */ - while(32 <= *s && *s < 127 && strchr("()<>@,;:[]?={}\" \t\\", *s)==nil) - s++; - - return s; -} - -static char* -skipvalue(char *s, int isns) -{ - char *t; - - /* - * An RFC2109 value is an HTTP token or an HTTP quoted string. - * Netscape servers ignore the spec and rely on semicolons, apparently. - */ - if(isns){ - if((t = strchr(s, ';')) == nil) - t = s+strlen(s); - return t; - } - if(*s == '"') - return skipquoted(s); - return skiptoken(s); -} - -/* - * RMID=80b186bb64c03c65fab767f8; expires=Monday, 10-Feb-2003 04:44:39 GMT; - * path=/; domain=.nytimes.com - */ -static char* -parsecookie(Cookie *c, char *p, char **e, int isns, char *dom, char *path) -{ - int i, done; - char *t, *u, *attr, *val; - - memset(c, 0, sizeof *c); - c->expire = ~0; - - /* NAME=VALUE */ - t = skiptoken(p); - c->name = p; - p = skipspace(t); - if(*p != '='){ - Badname: - return "malformed cookie: no NAME=VALUE"; - } - *t = '\0'; - p = skipspace(p+1); - t = skipvalue(p, isns); - if(*t) - *t++ = '\0'; - c->value = p; - p = skipspace(t); - if(c->name[0]=='\0' || c->value[0]=='\0') - goto Badname; - - done = 0; - for(; *p && !done; p=skipspace(p)){ - attr = p; - t = skiptoken(p); - u = skipspace(t); - switch(*u){ - case '\0': - *t = '\0'; - val = p = u; - break; - case ';': - *t = '\0'; - val = ""; - p = u+1; - break; - case '=': - *t = '\0'; - val = skipspace(u+1); - p = skipvalue(val, isns); - if(*p==',') - done = 1; - if(*p) - *p++ = '\0'; - break; - case ',': - if(!isns){ - val = ""; - p = u; - *p++ = '\0'; - done = 1; - break; - } - default: - if(cookiedebug) - fprint(2, "syntax: %s\n", p); - return "syntax error"; - } - for(i=0; i<nelem(stab); i++) - if(stab[i].ishttp && cistrcmp(stab[i].s, attr)==0) - *(char**)((uintptr)c+stab[i].offset) = val; - if(cistrcmp(attr, "expires") == 0){ - if(!isns) - return "non-netscape cookie has Expires tag"; - if(!val[0]) - return "bad expires tag"; - c->expire = strtotime(val); - if(c->expire == ~0) - return "cannot parse netscape expires tag"; - } - if(cistrcmp(attr, "max-age") == 0) - c->expire = time(0)+atoi(val); - if(cistrcmp(attr, "secure") == 0) - c->secure = 1; - } - *e = p; - - if(c->dom) - c->explicitdom = 1; - else - c->dom = dom; - if(c->path) - c->explicitpath = 1; - else - c->path = path; - c->netscapestyle = isns; - - return nil; -} - -Jar *jar; - -typedef struct Aux Aux; -struct Aux -{ - char *dom; - char *path; - char *inhttp; - char *outhttp; - char *ctext; - int rdoff; -}; -enum -{ - MaxCtext = 16*1024*1024, -}; - -void -cookieopen(Req *r) -{ - char *s, *es; - int i, sz; - Aux *a; - - syncjar(jar); - a = emalloc(sizeof(Aux)); - r->fid->aux = a; - if(r->ifcall.mode&OTRUNC){ - a->ctext = emalloc(1); - a->ctext[0] = '\0'; - }else{ - sz = 256*jar->nc+1024; /* BUG should do better */ - a->ctext = emalloc(sz); - a->ctext[0] = '\0'; - s = a->ctext; - es = s+sz; - for(i=0; i<jar->nc; i++) - s = seprint(s, es, "%K\n", &jar->c[i]); - } - respond(r, nil); -} - -void -cookieread(Req *r) -{ - Aux *a; - - a = r->fid->aux; - readstr(r, a->ctext); - respond(r, nil); -} - -void -cookiewrite(Req *r) -{ - Aux *a; - int sz; - - a = r->fid->aux; - sz = r->ifcall.count+r->ifcall.offset; - if(sz > strlen(a->ctext)){ - if(sz >= MaxCtext){ - respond(r, "cookie file too large"); - return; - } - a->ctext = erealloc9p(a->ctext, sz+1); - a->ctext[sz] = '\0'; - } - memmove(a->ctext+r->ifcall.offset, r->ifcall.data, r->ifcall.count); - r->ofcall.count = r->ifcall.count; - respond(r, nil); -} - -void -cookieclunk(Fid *fid) -{ - char *p, *nextp; - Aux *a; - int i; - - a = fid->aux; - if(a == nil) - return; - for(i=0; i<jar->nc; i++) - jar->c[i].mark = 1; - for(p=a->ctext; *p; p=nextp){ - if((nextp = strchr(p, '\n')) != nil) - *nextp++ = '\0'; - else - nextp = ""; - addtojar(jar, p, 0); - } - for(i=0; i<jar->nc; i++) - if(jar->c[i].mark) - delcookie(jar, &jar->c[i]); - syncjar(jar); - free(a->dom); - free(a->path); - free(a->inhttp); - free(a->outhttp); - free(a->ctext); - free(a); -} - -void -closecookies(void) -{ - closejar(jar); -} - -void -initcookies(char *file) -{ - char *home; - - fmtinstall('J', jarfmt); - fmtinstall('K', cookiefmt); - - if(file == nil){ - home = getenv("home"); - if(home == nil) - sysfatal("no cookie file specified and no $home"); - file = emalloc(strlen(home)+30); - strcpy(file, home); - strcat(file, "/lib/webcookies"); - free(home); - } - jar = readjar(file); - if(jar == nil) - sysfatal("readjar: %r"); -} - -void -httpsetcookie(char *hdr, char *dom, char *path) -{ - char *t; - - path = estrdup(path && path[0] ? path : "/"); - if((t = strchr(path, '?')) != 0) - *t = '\0'; - t = strrchr(path, '/'); - if(t && t != path) - *t = '\0'; - parsehttp(jar, hdr, dom, path); - syncjar(jar); - free(path); -} - -char* -httpcookies(char *dom, char *path, int issecure) -{ - char *s; - Jar *j; - - syncjar(jar); - j = cookiesearch(jar, dom, path, issecure); - s = smprint("%J", j); - closejar(j); - return s; -} diff --git a/sys/src/cmd/webfs/dat.h b/sys/src/cmd/webfs/dat.h index fde326317..4235e48b3 100644 --- a/sys/src/cmd/webfs/dat.h +++ b/sys/src/cmd/webfs/dat.h @@ -1,104 +1,67 @@ -typedef struct Client Client; -typedef struct Ctl Ctl; -typedef struct Ibuf Ibuf; typedef struct Url Url; +typedef struct Buq Buq; +typedef struct Buf Buf; +typedef struct Key Key; -/* simple buffered i/o for network connections; shared by http, ftp */ -struct Ibuf -{ - int fd; - Ioproc *io; - char buf[4096]; - char *rp, *wp; -}; +typedef struct { + char *s1; + char *s2; +} Str2; -struct Ctl -{ - int acceptcookies; - int sendcookies; - int redirectlimit; - char *useragent; -}; +/* 9p */ +typedef struct Req Req; -struct Client +struct Url { - Url *url; - Url *baseurl; - - Ctl ctl; - Channel *creq; /* chan(Req*) */ - int num; - int plumbed; - char *contenttype; - char *postbody; - char *redirect; - char *authenticate; - char *ext; - int npostbody; - int havepostbody; - int iobusy; - int bodyopened; - Ioproc *io; - int ref; - void *aux; + char *scheme; + char *user; + char *pass; + char *host; + char *port; + char *path; + char *query; + char *fragment; }; -/* - * If ischeme is USunknown, then the given URL is a relative - * URL which references the "current document" in the context of the base. - * If this is the case, only the "fragment" and "url" members will have - * meaning, and the given URL structure may not be used as a base URL itself. - */ -enum +struct Buf { - USunknown, - UShttp, - UShttps, - USftp, - USfile, - UScurrent, + Buf *next; + uchar *rp; + uchar *ep; + Req *wreq; + uchar end[]; }; -struct Url +struct Key { - int ischeme; - char* url; - char* scheme; - int (*open)(Client*, Url*); - int (*read)(Client*, Req*); - void (*close)(Client*); - char* schemedata; - char* authority; - char* user; - char* passwd; - char* host; - char* port; - char* path; - char* query; - char* fragment; - union { - struct { - char *page_spec; - } http; - struct { - char *path_spec; - char *type; - } ftp; - }; + Key *next; + char *val; + char key[]; }; -enum +struct Buq { - STACK = 32*1024, /* was 16*1024; there are big arrays on the stack */ -}; + Ref; + QLock; + + Url *url; + Key *hdr; + char *error; + + int closed; + int limit; + int size; -extern Client** client; -extern int cookiedebug; -extern Srv fs; -extern int fsdebug; -extern Ctl globalctl; -extern int nclient; -extern int urldebug; -extern int httpdebug; -extern char* status[]; + /* write buffers */ + Buf *bh; + Buf **bt; + + /* read requests */ + Req *rh; + Req **rt; + + Rendez rz; +}; +int debug; +Url *proxy; diff --git a/sys/src/cmd/webfs/fns.h b/sys/src/cmd/webfs/fns.h index 3d9aecf15..747ebd729 100644 --- a/sys/src/cmd/webfs/fns.h +++ b/sys/src/cmd/webfs/fns.h @@ -1,62 +1,35 @@ -/* buf.c */ -void initibuf(Ibuf*, Ioproc*, int); -int readibuf(Ibuf*, char*, int); -void unreadline(Ibuf*, char*); -int readline(Ibuf*, char*, int); - -/* client.c */ -int newclient(int); -void closeclient(Client*); -void clonectl(Ctl*); -int ctlwrite(Req*, Ctl*, char*, char*); -int clientctlwrite(Req*, Client*, char*, char*); -int globalctlwrite(Req*, char*, char*); -void ctlread(Req*, Client*); -void globalctlread(Req*); -void plumburl(char*, char*); - -/* cookies.c */ -void cookieread(Req*); -void cookiewrite(Req*); -void cookieopen(Req*); -void cookieclunk(Fid*); -void initcookies(char*); -void closecookies(void); -void httpsetcookie(char*, char*, char*); -char* httpcookies(char*, char*, int); - -/* fs.c */ -void initfs(void); - -/* http.c */ -int httpopen(Client*, Url*); -int httpread(Client*, Req*); -void httpclose(Client*); - -/* io.c */ -int iotlsdial(Ioproc*, char*, char*, char*, int*, int); -int ioprint(Ioproc*, int, char*, ...); -#pragma varargck argpos ioprint 3 - -/* plumb.c */ -void plumbinit(void); -void plumbstart(void); -void replumb(Client*); - -/* url.c */ -Url* parseurl(char*, Url*); -void freeurl(Url*); -void rewriteurl(Url*); -int seturlquery(Url*, char*); -Url* copyurl(Url*); -char* escapeurl(char*, char *); -char* unescapeurl(char*, char *); -void initurl(void); - -/* util.c */ -char* estrdup(char*); -char* estrmanydup(char*, ...); -char* estredup(char*, char*); -void* emalloc(uint); -void* erealloc(void*, uint); -char* strlower(char*); +/* sub */ +void* emalloc(int n); +char* estrdup(char *s); + +Key* addkey(Key *h, char *key, char *val); +Key* delkey(Key *h, char *key); +char* lookkey(Key *k, char *key); +Key* parsehdr(char *s); +char* unquote(char *s, char **ps); + +/* url */ +#pragma varargck type "U" Url* +#pragma varargck type "E" Str2 + +int Efmt(Fmt*); +int Ufmt(Fmt*); +char* Upath(Url *); +Url* url(char *s, Url *b); +Url* saneurl(Url *u); +int matchurl(Url *u, Url *s); +void freeurl(Url *u); + +/* buq */ +int buread(Buq *q, void *v, int l); +int buwrite(Buq *q, void *v, int l); +void buclose(Buq *q, char *error); +Buq* bualloc(int limit); +void bufree(Buq *q); + +void bureq(Buq *q, Req *r); +void buflushreq(Buq *q, Req *r); + +/* http */ +void flushauth(Url *u, char *t); +void http(char *m, Url *u, Key *shdr, Buq *qbody, Buq *qpost); diff --git a/sys/src/cmd/webfs/fs.c b/sys/src/cmd/webfs/fs.c index ceb824598..8e8cabbf9 100644 --- a/sys/src/cmd/webfs/fs.c +++ b/sys/src/cmd/webfs/fs.c @@ -1,617 +1,728 @@ -/* - * Web file system. Conventionally mounted at /mnt/web - * - * ctl send control messages (might go away) - * cookies list of cookies, editable - * clone open and read to obtain new connection - * n connection directory - * ctl control messages (like get url) - * body retrieved data - * content-type mime content-type of body - * postbody data to be posted - * parsed parsed version of url - * url entire url - * scheme http, ftp, etc. - * host hostname - * path path on host - * query query after path - * fragment #foo anchor reference - * user user name (ftp) - * password password (ftp) - * ftptype transfer mode (ftp) - */ - #include <u.h> #include <libc.h> -#include <bio.h> -#include <ip.h> -#include <plumb.h> -#include <thread.h> +#include <ctype.h> #include <fcall.h> +#include <thread.h> #include <9p.h> + #include "dat.h" #include "fns.h" -int fsdebug; +typedef struct Webfid Webfid; +typedef struct Client Client; -enum +struct Client { - Qroot, - Qrootctl, - Qclone, - Qcookies, - Qclient, - Qctl, - Qbody, - Qbodyext, - Qcontenttype, - Qpostbody, - Qparsed, - Qurl, - Qscheme, - Qschemedata, - Quser, - Qpasswd, - Qhost, - Qport, - Qpath, - Qquery, - Qfragment, - Qftptype, - Qend, -}; + Ref; -#define PATH(type, n) ((type)|((n)<<8)) -#define TYPE(path) ((int)(path) & 0xFF) -#define NUM(path) ((uint)(path)>>8) + char request[16]; + Url *baseurl; + Url *url; + Key *hdr; -Channel *creq; -Channel *creqwait; -Channel *cclunk; -Channel *cclunkwait; + int obody; /* body opend */ + int cbody; /* body closed */ + Buq *qbody; +}; -typedef struct Tab Tab; -struct Tab +struct Webfid { - char *name; - ulong mode; - int offset; + int level; + + Client *client; + Key *key; /* copy for Qheader */ + Buq *buq; /* reference for Qbody, Qpost */ }; -Tab tab[] = -{ - "/", DMDIR|0555, 0, - "ctl", 0666, 0, - "clone", 0666, 0, - "cookies", 0666, 0, - "XXX", DMDIR|0555, 0, - "ctl", 0666, 0, - "body", 0444, 0, - "XXX", 0444, 0, - "contenttype", 0444, 0, - "postbody", 0666, 0, - "parsed", DMDIR|0555, 0, - "url", 0444, offsetof(Url, url), - "scheme", 0444, offsetof(Url, scheme), - "schemedata", 0444, offsetof(Url, schemedata), - "user", 0444, offsetof(Url, user), - "passwd", 0444, offsetof(Url, passwd), - "host", 0444, offsetof(Url, host), - "port", 0444, offsetof(Url, port), - "path", 0444, offsetof(Url, path), - "query", 0444, offsetof(Url, query), - "fragment", 0444, offsetof(Url, fragment), - "ftptype", 0444, offsetof(Url, ftp.type), +enum { + Qroot, + Qrctl, + Qclone, + Qclient, + Qctl, + Qbody, + Qpost, + Qparsed, + Qurl, + Qurlschm, + Qurluser, + Qurlpass, + Qurlhost, + Qurlport, + Qurlpath, + Qurlqwry, + Qurlfrag, + Qheader, }; -ulong time0; +static char *nametab[] = { + "/", + "ctl", + "clone", + nil, + "ctl", + "body", + "postbody", + "parsed", + "url", + "scheme", + "user", + "passwd", + "host", + "port", + "path", + "query", + "fragment", + nil, +}; -static void -fillstat(Dir *d, uvlong path, ulong length, char *ext) +static long time0; +static char *user; +static Client client[64]; +static int nclient; + +#define CLIENTID(c) (((Client*)(c)) - client) + +Client* +newclient(void) { - Tab *t; - int type; - char buf[32]; + Client *cl; + int i; - memset(d, 0, sizeof(*d)); - d->uid = estrdup("web"); - d->gid = estrdup("web"); - d->qid.path = path; - d->atime = d->mtime = time0; - d->length = length; - type = TYPE(path); - t = &tab[type]; - if(type == Qbodyext) { - snprint(buf, sizeof buf, "body.%s", ext == nil ? "xxx" : ext); - d->name = estrdup(buf); - } - else if(t->name) - d->name = estrdup(t->name); - else{ /* client directory */ - snprint(buf, sizeof buf, "%ud", NUM(path)); - d->name = estrdup(buf); + for(i = 0; i < nclient; i++) + if(client[i].ref == 0) + break; + if(i >= nelem(client)) + return nil; + if(i == nclient) + nclient++; + cl = &client[i]; + incref(cl); + + cl->request[0] = 0; + cl->baseurl = nil; + cl->url = nil; + cl->hdr = nil; + cl->qbody = nil; + + return cl; +} + +void +freeclient(Client *cl) +{ + Key *k; + + if(cl == nil || decref(cl)) + return; + + buclose(cl->qbody, 0); + bufree(cl->qbody); + + while(k = cl->hdr){ + cl->hdr = k->next; + free(k); } - d->qid.type = t->mode>>24; - d->mode = t->mode; + + freeurl(cl->url); + freeurl(cl->baseurl); + + memset(cl, 0, sizeof(*cl)); } -static void -fsstat(Req *r) +static Url* +clienturl(Client *cl) { - fillstat(&r->d, r->fid->qid.path, 0, nil); - respond(r, nil); + static Url nullurl; + + if(cl->qbody && cl->qbody->url) + return cl->qbody->url; + if(cl->url) + return cl->url; + return &nullurl; } -static int -rootgen(int i, Dir *d, void*) +static void* +wfaux(Webfid *f) { - char buf[32]; + if(f->level < Qclient) + return nil; + else if(f->level < Qurl) + return f->client; + else if(f->level < Qheader) + return clienturl(f->client); + else + return f->key; +} - i += Qroot+1; - if(i < Qclient){ - fillstat(d, i, 0, nil); - return 0; - } - i -= Qclient; - if(i < nclient){ - fillstat(d, PATH(Qclient, i), 0, nil); - snprint(buf, sizeof buf, "%d", i); - free(d->name); - d->name = estrdup(buf); - return 0; +static void +fsmkqid(Qid *q, int level, void *aux) +{ + q->type = 0; + q->vers = 0; + switch(level){ + case Qroot: + case Qparsed: + case Qclient: + q->type = QTDIR; + default: + q->path = (level<<24) | (((ulong)aux ^ time0) & 0x00ffffff); } - return -1; } -static int -clientgen(int i, Dir *d, void *aux) +static char* +fshdrname(char *s) { - Client *c; + char *k, *w; - c = aux; - i += Qclient+1; - if(i <= Qparsed){ - fillstat(d, PATH(i, c->num), 0, c->ext); - return 0; - } - return -1; + for(k=w=s; *k; k++) + if(isalnum(*k)) + *w++ = tolower(*k); + *w = 0; + return s; } static int -parsedgen(int i, Dir *d, void *aux) +urlstr(char *buf, int nbuf, Url *u, int level) { - Client *c; + char *s; - c = aux; - i += Qparsed+1; - if(i < Qend){ - fillstat(d, PATH(i, c->num), 0, nil); + if(level == Qurl) + return snprint(buf, nbuf, "%U", u); + if(level == Qurlpath) + return snprint(buf, nbuf, "%s", Upath(u)); + if((s = (&u->scheme)[level - Qurlschm]) == nil){ + buf[0] = 0; return 0; } - return -1; + return snprint(buf, nbuf, "%s", s); } + static void -fsread(Req *r) +fsmkdir(Dir *d, int level, void *aux) { - char *s; - char e[ERRMAX]; - Client *c; - ulong path; - - path = r->fid->qid.path; - switch(TYPE(path)){ - default: - snprint(e, sizeof e, "bug in webfs path=%lux\n", path); - respond(r, e); - break; + char buf[1024]; - case Qroot: - dirread9p(r, rootgen, nil); - respond(r, nil); - break; - - case Qrootctl: - globalctlread(r); - break; - - case Qcookies: - cookieread(r); + memset(d, 0, sizeof(*d)); + fsmkqid(&d->qid, level, aux); + d->mode = 0444; + d->atime = d->mtime = time0; + d->uid = estrdup(user); + d->gid = estrdup(user); + d->muid = estrdup(user); + if(d->qid.type == QTDIR) + d->mode |= DMDIR | 0111; + switch(level){ + case Qheader: + d->name = fshdrname(estrdup(((Key*)aux)->key)); + d->length = strlen(((Key*)aux)->val); break; - case Qclient: - dirread9p(r, clientgen, client[NUM(path)]); - respond(r, nil); + snprint(buf, sizeof(buf), "%ld", CLIENTID(aux)); + d->name = estrdup(buf); break; - case Qctl: - ctlread(r, client[NUM(path)]); - break; - - case Qcontenttype: - c = client[NUM(path)]; - if(c->contenttype == nil) - r->ofcall.count = 0; - else - readstr(r, c->contenttype); - respond(r, nil); - break; - - case Qpostbody: - c = client[NUM(path)]; - readbuf(r, c->postbody, c->npostbody); - respond(r, nil); - break; - - case Qbody: - case Qbodyext: - c = client[NUM(path)]; - if(c->iobusy){ - respond(r, "already have i/o pending"); - break; + case Qrctl: + case Qclone: + d->mode = 0666; + if(0){ + case Qpost: + d->mode = 0222; } - c->iobusy = 1; - sendp(c->creq, r); - break; + default: + d->name = estrdup(nametab[level]); + if(level >= Qurl && level <= Qurlfrag) + d->length = urlstr(buf, sizeof(buf), (Url*)aux, level); + } +} - case Qparsed: - dirread9p(r, parsedgen, client[NUM(path)]); - respond(r, nil); - break; +static void +fsattach(Req *r) +{ + Webfid *f; - case Qurl: - case Qscheme: - case Qschemedata: - case Quser: - case Qpasswd: - case Qhost: - case Qport: - case Qpath: - case Qquery: - case Qfragment: - case Qftptype: - c = client[NUM(path)]; - r->ofcall.count = 0; - if(c->url != nil - && (s = *(char**)((uintptr)c->url+tab[TYPE(path)].offset)) != nil) - readstr(r, s); - respond(r, nil); - break; + if(r->ifcall.aname && r->ifcall.aname[0]){ + respond(r, "invalid attach specifier"); + return; } + f = emalloc(sizeof(*f)); + f->level = Qroot; + fsmkqid(&r->fid->qid, f->level, wfaux(f)); + r->ofcall.qid = r->fid->qid; + r->fid->aux = f; + respond(r, nil); } static void -fswrite(Req *r) +fsstat(Req *r) { - int m; - ulong path; - char e[ERRMAX], *buf, *cmd, *arg; - Client *c; + Webfid *f; - path = r->fid->qid.path; - switch(TYPE(path)){ - default: - snprint(e, sizeof e, "bug in webfs path=%lux\n", path); - respond(r, e); - break; + f = r->fid->aux; + fsmkdir(&r->d, f->level, wfaux(f)); + respond(r, nil); +} - case Qcookies: - cookiewrite(r); - break; +static char* +fswalk1(Fid *fid, char *name, Qid *qid) +{ + Webfid *f; + int i, j; - case Qrootctl: - case Qctl: - if(r->ifcall.count >= 1024){ - respond(r, "ctl message too long"); - return; - } - buf = estredup(r->ifcall.data, (char*)r->ifcall.data+r->ifcall.count); - cmd = buf; - arg = strpbrk(cmd, "\t "); - if(arg){ - *arg++ = '\0'; - arg += strspn(arg, "\t "); - }else - arg = ""; - r->ofcall.count = r->ifcall.count; - if(TYPE(path)==Qrootctl){ - if(!ctlwrite(r, &globalctl, cmd, arg) - && !globalctlwrite(r, cmd, arg)) - respond(r, "unknown control command"); - }else{ - c = client[NUM(path)]; - if(!ctlwrite(r, &c->ctl, cmd, arg) - && !clientctlwrite(r, c, cmd, arg)) - respond(r, "unknown control command"); - } - free(buf); - break; + if(!(fid->qid.type&QTDIR)) + return "walk in non-directory"; - case Qpostbody: - c = client[NUM(path)]; - if(c->bodyopened){ - respond(r, "cannot write postbody after opening body"); + f = fid->aux; + if(strcmp(name, "..") == 0){ + switch(f->level){ + case Qroot: break; - } - if(r->ifcall.offset >= 128*1024*1024){ /* >128MB is probably a mistake */ - respond(r, "offset too large"); + case Qclient: + freeclient(f->client); + f->client = nil; break; + default: + if(f->level > Qparsed) + f->level = Qparsed; + else + f->level = Qclient; } - m = r->ifcall.offset + r->ifcall.count; - if(c->npostbody < m){ - c->postbody = erealloc(c->postbody, m); - memset(c->postbody+c->npostbody, 0, m-c->npostbody); - c->npostbody = m; + } else { + for(i=f->level+1; i < nelem(nametab); i++){ + if(nametab[i]){ + if(strcmp(name, nametab[i]) == 0) + break; + if(i == Qbody && strncmp(name, "body.", 5) == 0) + break; + } + if(i == Qclient){ + j = atoi(name); + if(j >= 0 && j < nclient){ + f->client = &client[j]; + incref(f->client); + break; + } + } + if(i == Qheader && f->client && f->client->qbody){ + char buf[128]; + Key *k; + + for(k = f->client->qbody->hdr; k; k = k->next){ + strncpy(buf, k->key, sizeof(buf)); + if(!strcmp(name, fshdrname(buf))) + break; + } + if(k != nil){ + /* need to copy as key is owned by qbody wich might go away */ + f->key = addkey(0, k->key, k->val); + break; + } + } } - memmove(c->postbody+r->ifcall.offset, r->ifcall.data, r->ifcall.count); - r->ofcall.count = r->ifcall.count; - respond(r, nil); - break; + if(i >= nelem(nametab)) + return "directory entry not found"; + f->level = i; } + fsmkqid(qid, f->level, wfaux(f)); + fid->qid = *qid; + return nil; +} + +static char* +fsclone(Fid *oldfid, Fid *newfid) +{ + Webfid *f, *o; + + o = oldfid->aux; + if(o == nil || o->key || o->buq) + return "bad fid"; + f = emalloc(sizeof(*f)); + memmove(f, o, sizeof(*f)); + if(f->client) + incref(f->client); + newfid->aux = f; + return nil; } static void fsopen(Req *r) { - static int need[4] = { 4, 2, 6, 1 }; - ulong path; - int n; - Client *c; - Tab *t; - - /* - * lib9p already handles the blatantly obvious. - * we just have to enforce the permissions we have set. - */ - path = r->fid->qid.path; - t = &tab[TYPE(path)]; - n = need[r->ifcall.mode&3]; - if((n&t->mode) != n){ - respond(r, "permission denied"); - return; - } + Webfid *f; + Client *cl; - switch(TYPE(path)){ - case Qcookies: - cookieopen(r); - break; - - case Qpostbody: - c = client[NUM(path)]; - c->havepostbody++; - c->ref++; - respond(r, nil); + f = r->fid->aux; + cl = f->client; + switch(f->level){ + case Qclone: + if((cl = newclient()) == nil){ + respond(r, "no more clients"); + return; + } + f->level = Qctl; + f->client = cl; + fsmkqid(&r->fid->qid, f->level, wfaux(f)); + r->ofcall.qid = r->fid->qid; break; - + case Qpost: + if(cl->qbody && !cl->cbody){ + Inuse: + respond(r, "client in use"); + return; + } case Qbody: - case Qbodyext: - c = client[NUM(path)]; - if(c->url == nil){ - respond(r, "url is not yet set"); - break; + if(cl->obody) + goto Inuse; + if(cl->cbody){ + bufree(cl->qbody); + cl->qbody = nil; + cl->cbody = 0; } - c->bodyopened = 1; - c->ref++; - sendp(c->creq, r); - break; + if(cl->qbody == nil){ + char *m; - case Qclone: - n = newclient(0); - path = PATH(Qctl, n); - r->fid->qid.path = path; - r->ofcall.qid.path = path; - if(fsdebug) - fprint(2, "open clone => path=%lux\n", path); - t = &tab[Qctl]; - /* fall through */ - default: - if(t-tab >= Qclient) - client[NUM(path)]->ref++; - respond(r, nil); - break; + if(cl->url == nil){ + respond(r, "no url set"); + return; + } + cl->qbody = bualloc(16*1024); + if(f->level != Qbody){ + f->buq = bualloc(64*1024); + if(!lookkey(cl->hdr, "Content-Type")) + cl->hdr = addkey(cl->hdr, "Content-Type", + "application/x-www-form-urlencoded"); + m = "POST"; + } else + m = "GET"; + if(cl->request[0]) + m = cl->request; + if(!lookkey(cl->hdr, "Connection")) + cl->hdr = addkey(cl->hdr, "Connection", "keep-alive"); + if(!lookkey(cl->hdr, "User-Agent")) + cl->hdr = addkey(cl->hdr, "User-Agent", "webfs/0.1 (Plan 9 Front)"); + http(m, cl->url, cl->hdr, cl->qbody, f->buq); + cl->request[0] = 0; + cl->url = nil; + cl->hdr = nil; + } + if(f->buq) + break; + cl->obody = 1; + incref(cl->qbody); + bureq(f->buq = cl->qbody, r); + return; } + respond(r, nil); } -static void -fsdestroyfid(Fid *fid) +static int +rootgen(int i, Dir *d, void *) { - sendp(cclunk, fid); - recvp(cclunkwait); + i += Qroot+1; + if(i < Qclient){ + fsmkdir(d, i, 0); + return 0; + } + i -= Qclient; + if(i < nclient){ + fsmkdir(d, Qclient, &client[i]); + return 0; + } + return -1; +} + +static int +clientgen(int i, Dir *d, void *aux) +{ + i += Qclient+1; + if(i > Qparsed){ + Client *cl = aux; + Key *k; + + i -= Qparsed+1; + if(cl == nil || cl->qbody == nil) + return -1; + for(k = cl->qbody->hdr; i > 0 && k; i--, k = k->next) + ; + if(k == nil || i > 0) + return -1; + i = Qheader; + aux = k; + } + fsmkdir(d, i, aux); + return 0; +} + +static int +parsedgen(int i, Dir *d, void *aux) +{ + i += Qparsed+1; + if(i > Qurlfrag) + return -1; + fsmkdir(d, i, aux); + return 0; } static void -fsattach(Req *r) +fsread(Req *r) { - if(r->ifcall.aname && r->ifcall.aname[0]){ - respond(r, "invalid attach specifier"); + char buf[1024]; + Webfid *f; + + f = r->fid->aux; + switch(f->level){ + case Qroot: + dirread9p(r, rootgen, nil); + respond(r, nil); + return; + case Qclient: + dirread9p(r, clientgen, f->client); + respond(r, nil); + return; + case Qparsed: + dirread9p(r, parsedgen, clienturl(f->client)); + respond(r, nil); + return; + case Qrctl: + buf[0] = 0; + String: + readstr(r, buf); + respond(r, nil); + return; + case Qctl: + snprint(buf, sizeof(buf), "%ld\n", CLIENTID(f->client)); + goto String; + case Qheader: + snprint(buf, sizeof(buf), "%s", f->key->val); + goto String; + case Qurl: + case Qurlschm: + case Qurluser: + case Qurlpass: + case Qurlhost: + case Qurlport: + case Qurlpath: + case Qurlqwry: + case Qurlfrag: + urlstr(buf, sizeof(buf), clienturl(f->client), f->level); + goto String; + case Qbody: + bureq(f->buq, r); return; } - r->fid->qid.path = PATH(Qroot, 0); - r->fid->qid.type = QTDIR; - r->fid->qid.vers = 0; - r->ofcall.qid = r->fid->qid; - respond(r, nil); + respond(r, "not implemented"); } static char* -fswalk1(Fid *fid, char *name, Qid *qid) +rootctl(char *ctl, char *arg) { - int i, n; - ulong path; - char buf[32], *ext; - - path = fid->qid.path; - if(!(fid->qid.type&QTDIR)) - return "walk in non-directory"; - - if(strcmp(name, "..") == 0){ - switch(TYPE(path)){ - case Qparsed: - qid->path = PATH(Qclient, NUM(path)); - qid->type = tab[Qclient].mode>>24; - return nil; - case Qclient: - case Qroot: - qid->path = PATH(Qroot, 0); - qid->type = tab[Qroot].mode>>24; - return nil; - default: - return "bug in fswalk1"; - } + Url *u; + + if(debug) + fprint(2, "rootctl: %q %q\n", ctl, arg); + + if(!strcmp(ctl, "flushauth")){ + u = nil; + if(arg && *arg) + u = saneurl(url(arg, 0)); + flushauth(u, 0); + freeurl(u); + return nil; } + return "bad ctl message"; +} - i = TYPE(path)+1; - for(; i<nelem(tab); i++){ - if(i==Qclient){ - n = atoi(name); - snprint(buf, sizeof buf, "%d", n); - if(n < nclient && strcmp(buf, name) == 0){ - qid->path = PATH(i, n); - qid->type = tab[i].mode>>24; - return nil; +static char* +clientctl(Client *cl, char *ctl, char *arg) +{ + char *p; + Url *u; + Key *k; + + if(debug) + fprint(2, "clientctl: %q %q\n", ctl, arg); + + if(!strcmp(ctl, "url")){ + if((u = saneurl(url(arg, cl->baseurl))) == nil) + return "bad url"; + freeurl(cl->url); + cl->url = u; + } + else if(!strcmp(ctl, "baseurl")){ + if((u = url(arg, 0)) == nil) + return "bad baseurl"; + freeurl(cl->baseurl); + cl->baseurl = u; + } + else if(!strcmp(ctl, "request")){ + p = cl->request; + strncpy(p, arg, sizeof(cl->request)); + for(; *p && isalpha(*p); p++) + *p = toupper(*p); + *p = 0; + } + else if(!strcmp(ctl, "headers")){ + while(arg && *arg){ + ctl = arg; + while(*ctl && strchr("\r\n\t ", *ctl)) + ctl++; + if(arg = strchr(ctl, '\n')) + *arg++ = 0; + if(k = parsehdr(ctl)){ + k->next = cl->hdr; + cl->hdr = k; } - break; } - if(i==Qbodyext){ - ext = client[NUM(path)]->ext; - snprint(buf, sizeof buf, "body.%s", ext == nil ? "xxx" : ext); - if(strcmp(buf, name) == 0){ - qid->path = PATH(i, NUM(path)); - qid->type = tab[i].mode>>24; - return nil; + } + else { + char buf[128], **t; + static char *tab[] = { + "User-Agent", + "Content-Type", + nil, + }; + for(t = tab; *t; t++){ + strncpy(buf, *t, sizeof(buf)); + if(!strcmp(ctl, fshdrname(buf))){ + cl->hdr = delkey(cl->hdr, *t); + if(arg && *arg) + cl->hdr = addkey(cl->hdr, *t, arg); + break; } } - else if(strcmp(name, tab[i].name) == 0){ - qid->path = PATH(i, NUM(path)); - qid->type = tab[i].mode>>24; - return nil; - } - if(tab[i].mode&DMDIR) - break; + if(*t == nil) + return "bad ctl message"; } - return "directory entry not found"; + return nil; +} + +static void +fswrite(Req *r) +{ + int n; + Webfid *f; + char *s, *t; + + f = r->fid->aux; + switch(f->level){ + case Qrctl: + case Qctl: + n = r->ofcall.count = r->ifcall.count; + s = emalloc(n+1); + memmove(s, r->ifcall.data, n); + while(n > 0 && strchr("\r\n", s[n-1])) + n--; + s[n] = 0; + t = s; + while(*t && strchr("\r\n\t ", *t)==0) + t++; + while(*t && strchr("\r\n\t ", *t)) + *t++ = 0; + if(f->level == Qctl) + t = clientctl(f->client, s, t); + else + t = rootctl(s, t); + free(s); + respond(r, t); + return; + case Qpost: + bureq(f->buq, r); + return; + } + respond(r, "not implemented"); } static void fsflush(Req *r) { - Req *or; - int t; - Client *c; - ulong path; - - or=r; - while(or->ifcall.type==Tflush) - or = or->oldreq; - - if(or->ifcall.type != Tread && or->ifcall.type != Topen) - abort(); - - path = or->fid->qid.path; - t = TYPE(path); - if(t != Qbody && t != Qbodyext) - abort(); - - c = client[NUM(path)]; - sendp(c->creq, r); - iointerrupt(c->io); - ioflush(c->io); + Webfid *f; + Req *o; + + if(o = r->oldreq) + if(f = o->fid->aux) + buflushreq(f->buq, o); + respond(r, nil); } static void -fsthread(void*) +fsdestroyfid(Fid *fid) { - ulong path; - Alt a[3]; - Fid *fid; - Req *r; - - threadsetname("fsthread"); - plumbstart(); - - a[0].op = CHANRCV; - a[0].c = cclunk; - a[0].v = &fid; - a[1].op = CHANRCV; - a[1].c = creq; - a[1].v = &r; - a[2].op = CHANEND; - - for(;;){ - switch(alt(a)){ - case 0: - path = fid->qid.path; - if(TYPE(path)==Qcookies) - cookieclunk(fid); - if(fid->omode != -1 && TYPE(path) >= Qclient) - closeclient(client[NUM(path)]); - sendp(cclunkwait, nil); - break; - case 1: - switch(r->ifcall.type){ - case Tattach: - fsattach(r); - break; - case Topen: - fsopen(r); - break; - case Tread: - fsread(r); - break; - case Twrite: - fswrite(r); - break; - case Tstat: - fsstat(r); - break; - case Tflush: - fsflush(r); - break; - default: - respond(r, "bug in fsthread"); - break; + Webfid *f; + + if(f = fid->aux){ + fid->aux = nil; + if(f->buq){ + buclose(f->buq, 0); + if(f->client->qbody == f->buq){ + f->client->obody = 0; + f->client->cbody = 1; } - sendp(creqwait, 0); - break; + bufree(f->buq); } + if(f->key) + free(f->key); + freeclient(f->client); + free(f); } } -static void -fssend(Req *r) +Srv fs = { - sendp(creq, r); - recvp(creqwait); /* avoids need to deal with spurious flushes */ -} + .attach=fsattach, + .stat=fsstat, + .walk1=fswalk1, + .clone=fsclone, + .open=fsopen, + .read=fsread, + .write=fswrite, + .flush=fsflush, + .destroyfid=fsdestroyfid, +}; void -initfs(void) +usage(void) { - time0 = time(0); - creq = chancreate(sizeof(void*), 0); - creqwait = chancreate(sizeof(void*), 0); - cclunk = chancreate(sizeof(void*), 0); - cclunkwait = chancreate(sizeof(void*), 0); - procrfork(fsthread, nil, STACK, RFNAMEG); + fprint(2, "usage: %s [-D] [-m mtpt] [-s srv]\n", argv0); + exits("usage"); } void -takedown(Srv*) +main(int argc, char *argv[]) { - closecookies(); - threadexitsall("done"); -} + char *srv, *mtpt, *s; -Srv fs = -{ -.attach= fssend, -.destroyfid= fsdestroyfid, -.walk1= fswalk1, -.open= fssend, -.read= fssend, -.write= fssend, -.stat= fssend, -.flush= fssend, -.end= takedown, -}; + quotefmtinstall(); + fmtinstall('U', Ufmt); + fmtinstall('E', Efmt); + srv = nil; + mtpt = "/mnt/web"; + user = getuser(); + time0 = time(0); + + ARGBEGIN { + case 'D': + chatty9p++; + break; + case 'm': + mtpt = EARGF(usage()); + break; + case 's': + srv = EARGF(usage()); + break; + case 'd': + debug++; + break; + default: + usage(); + } ARGEND; + + rfork(RFNOTEG); + + if(s = getenv("httpproxy")){ + proxy = saneurl(url(s, 0)); + free(s); + } + + postmountsrv(&fs, srv, mtpt, MREPL); +} diff --git a/sys/src/cmd/webfs/http.c b/sys/src/cmd/webfs/http.c index 5acbcb4d2..04724268d 100644 --- a/sys/src/cmd/webfs/http.c +++ b/sys/src/cmd/webfs/http.c @@ -1,540 +1,825 @@ #include <u.h> #include <libc.h> -#include <bio.h> -#include <ip.h> -#include <plumb.h> -#include <thread.h> +#include <ctype.h> #include <fcall.h> +#include <thread.h> #include <9p.h> -#include <libsec.h> -#include <auth.h> + #include "dat.h" #include "fns.h" -char PostContentType[] = "application/x-www-form-urlencoded"; -int httpdebug; +#include <auth.h> +#include <mp.h> +#include <libsec.h> + +typedef struct Hconn Hconn; +typedef struct Hpool Hpool; +typedef struct Hauth Hauth; -typedef struct HttpState HttpState; -struct HttpState +struct Hconn { - int fd; - Client *c; - char *location; - char *setcookie; - char *netaddr; - char *credentials; - char autherror[ERRMAX]; - Ibuf b; + Hconn *next; + + int fd; + int keep; + int cancel; + int len; + char addr[128]; + char buf[8192+2]; }; -static void -location(HttpState *hs, char *value) +struct Hpool { - if(hs->location == nil) - hs->location = estrdup(value); -} + QLock; -static void -contenttype(HttpState *hs, char *value) + Hconn *head; + int active; + int limit; +}; + +struct Hauth { - if(hs->c->contenttype != nil) - free(hs->c->contenttype); - hs->c->contenttype = estrdup(value); -} + Hauth *next; + Url *url; + char *auth; +}; -static void -setcookie(HttpState *hs, char *value) +static Hpool hpool = { + .limit = 16, +}; + +static QLock authlk; +static Hauth *hauth; + +static Hconn* +hdial(Url *u) { - char *s, *t; - Fmt f; - - s = hs->setcookie; - fmtstrinit(&f); - if(s) - fmtprint(&f, "%s", s); - fmtprint(&f, "set-cookie: "); - fmtprint(&f, "%s", value); - fmtprint(&f, "\n"); - t = fmtstrflush(&f); - if(t){ - free(s); - hs->setcookie = t; + char addr[128]; + Hconn *h, *p; + int fd, ofd; + + snprint(addr, sizeof(addr), "tcp!%s!%s", u->host, u->port ? u->port : u->scheme); + + qlock(&hpool); + for(p = nil, h = hpool.head; h; p = h, h = h->next){ + if(strcmp(h->addr, addr) == 0){ + if(p) + p->next = h->next; + else + hpool.head = h->next; + h->next = nil; + qunlock(&hpool); + return h; + } + } + hpool.active++; + qunlock(&hpool); + if(debug) + fprint(2, "hdial [%d] %s\n", hpool.active, addr); + + if((fd = dial(addr, 0, 0, 0)) < 0) + return nil; + if(strcmp(u->scheme, "https") == 0){ + TLSconn *tc; + + tc = emalloc(sizeof(*tc)); + fd = tlsClient(ofd = fd, tc); + close(ofd); + /* BUG: should validate but how? */ + free(tc->cert); + free(tc->sessionID); + free(tc); + if(fd < 0) + return nil; } + + h = emalloc(sizeof(*h)); + h->next = nil; + h->cancel = 0; + h->keep = 1; + h->len = 0; + h->fd = fd; + strncpy(h->addr, addr, sizeof(h->addr)); + + return h; } -static char* -unquote(char *s, char **ps) +static void +hclose(Hconn *h) { - char *p; + Hconn *x, *t; + int i; - if(*s != '"'){ - p = strpbrk(s, " \t\r\n"); - *p++ = 0; - *ps = p; - return s; - } - for(p=s+1; *p; p++){ - if(*p == '\"'){ - *p++ = 0; - break; + if(h == nil) + return; + + qlock(&hpool); + if(h->keep && h->fd >= 0){ + for(i = 0, t = nil, x = hpool.head; x; x = x->next){ + if(strcmp(x->addr, h->addr) == 0) + break; + if(++i < hpool.limit) + t = x; } - if(*p == '\\' && *(p+1)){ - p++; - continue; + if(x == nil){ + /* return connection to pool */ + h->next = hpool.head; + hpool.head = h; + + /* cut off tail */ + if(t){ + x = t->next; + t->next = nil; + } + qunlock(&hpool); + + /* free the tail */ + while(h = x){ + x = h->next; + h->next = nil; + h->keep = 0; + hclose(h); + } + return; } } - memmove(s, s+1, p-(s+1)); - s[p-(s+1)] = 0; - *ps = p; - return s; -} + hpool.active--; + qunlock(&hpool); -static char* -servername(char *addr) -{ - char *p; - - if(strncmp(addr, "tcp!", 4) == 0 - || strncmp(addr, "net!", 4) == 0) - addr += 4; - addr = estrdup(addr); - p = addr+strlen(addr); - if(p>addr && *(p-1) == 's') - p--; - if(p>addr+5 && strcmp(p-5, "!http") == 0) - p[-5] = 0; - return addr; + if(debug) + fprint(2, "hclose [%d] %s\n", hpool.active, h->addr); + + if(h->fd >= 0) + close(h->fd); + free(h); } -void -wwwauthenticate(HttpState *hs, char *line) +static int +hread(Hconn *h, void *data, int len) { - char cred[64], *user, *pass, *realm, *s, *spec, *name; - Fmt fmt; - UserPasswd *up; - - spec = nil; - up = nil; - cred[0] = 0; - hs->autherror[0] = 0; - if(cistrncmp(line, "basic ", 6) != 0){ - werrstr("unknown auth: %s", line); - goto error; - } - line += 6; - if(cistrncmp(line, "realm=", 6) != 0){ - werrstr("missing realm: %s", line); - goto error; + if(h->len > 0){ + if(len > h->len) + len = h->len; + memmove(data, h->buf, len); + h->len -= len; + if(h->len > 0) + memmove(h->buf, h->buf + len, h->len); + return len; } - line += 6; - user = hs->c->url->user; - pass = hs->c->url->passwd; - if(user==nil || pass==nil){ - realm = unquote(line, &line); - fmtstrinit(&fmt); - name = servername(hs->netaddr); - fmtprint(&fmt, "proto=pass service=http server=%q realm=%q", name, realm); - free(name); - if(hs->c->url->user) - fmtprint(&fmt, " user=%q", hs->c->url->user); - spec = fmtstrflush(&fmt); - if(spec == nil) - goto error; - if((up = auth_getuserpasswd(nil, "%s", spec)) == nil) - goto error; - user = up->user; - pass = up->passwd; - } - if((s = smprint("%s:%s", user, pass)) == nil) - goto error; - free(up); - enc64(cred, sizeof(cred), (uchar*)s, strlen(s)); - memset(s, 0, strlen(s)); - free(s); - hs->credentials = smprint("Basic %s", cred); - if(hs->credentials == nil) - goto error; - return; - -error: - free(up); - free(spec); - snprint(hs->autherror, sizeof hs->autherror, "%r"); - fprint(2, "%s: Authentication failed: %r\n", argv0); + if((len = read(h->fd, data, len)) <= 0) + h->keep = 0; + return len; } -struct { - char *name; /* Case-insensitive */ - void (*fn)(HttpState *hs, char *value); -} hdrtab[] = { - { "location:", location }, - { "content-type:", contenttype }, - { "set-cookie:", setcookie }, - { "www-authenticate:", wwwauthenticate }, -}; - static int -httprcode(HttpState *hs) +hwrite(Hconn *h, void *data, int len) { - int n; - char *p; - char buf[256]; - - n = readline(&hs->b, buf, sizeof(buf)-1); - if(n <= 0) - return n; - if(httpdebug) - fprint(2, "-> %s\n", buf); - p = strchr(buf, ' '); - if(memcmp(buf, "HTTP/", 5) != 0 || p == nil){ - werrstr("bad response from server"); + if(write(h->fd, data, len) != len){ + h->keep = 0; return -1; } - buf[n] = 0; - return atoi(p+1); + return len; } - -/* - * read a single mime header, collect continuations. - * - * this routine assumes that there is a blank line twixt - * the header and the message body, otherwise bytes will - * be lost. - */ + static int -getheader(HttpState *hs, char *buf, int n) +hline(Hconn *h, char *data, int len, int cont) { - char *p, *e; - int i; + char *x, *y, *e; + int n; - n--; - p = buf; - for(e = p + n; ; p += i){ - i = readline(&hs->b, p, e-p); - if(i < 0) - return i; - - if(p == buf){ - /* first line */ - if(strchr(buf, ':') == nil) - break; /* end of headers */ - } else { - /* continuation line */ - if(*p != ' ' && *p != '\t'){ - unreadline(&hs->b, p); - *p = 0; - break; /* end of this header */ + data[0] = 0; + for(;;){ + if(h->len > 0){ + while(x = memchr(h->buf, '\n', h->len)){ + n = x - h->buf; + if(n > 0 && x[-1] == '\r') + n--; + if(n > 0 && cont){ + e = h->buf + h->len; + for(y = x+1; y < e; y++) + if(!strchr("\t ", *y)) + break; + if(y >= e || strchr("\t ", *y)) + break; + if(y > x+1){ + if(x > h->buf && x[-1] == '\r') + x--; + memmove(x, y, e - y); + h->len -= y - x; + continue; + } + } + if(n < len) + len = n; + memmove(data, h->buf, len); + data[len] = 0; + h->len -= (++x - h->buf); + if(h->len > 0) + memmove(h->buf, x, h->len); + return len; } } + if(h->len >= sizeof(h->buf)) + return 0; + if((n = read(h->fd, h->buf + h->len, sizeof(h->buf) - h->len)) <= 0){ + h->keep = 0; + return -1; + } + h->len += n; } - - if(httpdebug) - fprint(2, "-> %s\n", buf); - return p-buf; } static int -httpheaders(HttpState *hs) +authenticate(Url *u, Url *ru, char *method, char *s) { - char buf[2048]; - char *p; - int i, n; + char *user, *pass, *realm, *nonce, *opaque, *x; + UserPasswd *up; + Hauth *a; + Fmt fmt; + int n; - for(;;){ - n = getheader(hs, buf, sizeof(buf)); - if(n < 0) + up = nil; + user = u->user; + pass = u->pass; + realm = nonce = opaque = nil; + fmtstrinit(&fmt); + if(!cistrncmp(s, "Basic ", 6)){ + char cred[64]; + + s += 6; + if(x = cistrstr(s, "realm=")) + realm = unquote(x+6, &s); + if(realm == nil) return -1; - if(n == 0) - return 0; - // print("http header: '%.*s'\n", n, buf); - for(i = 0; i < nelem(hdrtab); i++){ - n = strlen(hdrtab[i].name); - if(cistrncmp(buf, hdrtab[i].name, n) == 0){ - /* skip field name and leading white */ - p = buf + n; - while(*p == ' ' || *p == '\t') - p++; - (*hdrtab[i].fn)(hs, p); - break; - } + if(user == nil || pass == nil){ + fmtprint(&fmt, " realm=%q", realm); + if(user) + fmtprint(&fmt, " user=%q", user); + if((s = fmtstrflush(&fmt)) == nil) + return -1; + if((up = auth_getuserpasswd(nil, "proto=pass service=http server=%q%s", + u->host, s)) == nil) + return -1; + user = up->user; + pass = up->passwd; } + fmtstrinit(&fmt); + fmtprint(&fmt, "%s:%s", user ? user : "", pass ? pass : ""); + free(up); + if((s = fmtstrflush(&fmt)) == nil) + return -1; + n = enc64(cred, sizeof(cred), (uchar*)s, strlen(s)); + memset(s, 0, strlen(s)); + free(s); + if(n == -1) + return -1; + fmtstrinit(&fmt); + fmtprint(&fmt, "Basic %s", cred); + u = saneurl(url(".", u)); /* all uris below the requested one */ + }else + if(!cistrncmp(s, "Digest ", 7)){ + char chal[1024], ouser[128], resp[2*MD5LEN+1]; + int nchal; + + s += 7; + if(x = cistrstr(s, "realm=")) + realm = unquote(x+6, &s); + if(x = cistrstr(s, "nonce=")) + nonce = unquote(x+6, &s); + if(x = cistrstr(s, "opaque=")) + opaque = unquote(x+7, &s); + if(realm == nil || nonce == nil) + return -1; + nchal = snprint(chal, sizeof(chal), "%s %s %U", nonce, method, ru); + fmtprint(&fmt, " realm=%q", realm); + if(user) + fmtprint(&fmt, " user=%q", user); + if((s = fmtstrflush(&fmt)) == nil) + return -1; + if(auth_respond(chal, nchal, ouser, sizeof ouser, resp, sizeof resp, nil, + "proto=httpdigest role=client server=%q%s", u->host, s) < 0) + return -1; + fmtstrinit(&fmt); + fmtprint(&fmt, "Digest "); + fmtprint(&fmt, "username=\"%s\", ", ouser); + fmtprint(&fmt, "realm=\"%s\", ", realm); + fmtprint(&fmt, "host=\"%s\", ", u->host); + fmtprint(&fmt, "uri=\"%U\", ", ru); + fmtprint(&fmt, "nonce=\"%s\", ", nonce); + fmtprint(&fmt, "response=\"%s\"", resp); + if(opaque) + fmtprint(&fmt, ", opaque=\"%s\"", opaque); + u = saneurl(url("/", u)); /* BUG: should be the ones in domain= only */ + } else + return -1; + if(u == nil) + return -1; + if((s = fmtstrflush(&fmt)) == nil){ + freeurl(u); + return -1; } + a = emalloc(sizeof(*a)); + a->url = u; + a->auth = s; + qlock(&authlk); + a->next = hauth; + hauth = a; + qunlock(&authlk); + + return 0; +} + +void +flushauth(Url *u, char *t) +{ + Hauth *a, *p; + + qlock(&authlk); +Again: + for(p = nil, a = hauth; a; p = a, a = a->next) + if(matchurl(u, a->url) && (t == nil || !strcmp(t, a->auth))){ + if(p) + p->next = a->next; + else + hauth = a->next; + if(debug) + fprint(2, "flushauth for %U\n", a->url); + freeurl(a->url); + memset(a->auth, 0, strlen(a->auth)); + free(a->auth); + free(a); + goto Again; + } + qunlock(&authlk); } -int -httpopen(Client *c, Url *url) +static void +catch(void *, char *msg) { - int fd, code, redirect, authenticate; - char *cookies; - Ioproc *io; - HttpState *hs; - char *service; - - if(httpdebug) - fprint(2, "httpopen\n"); - io = c->io; - hs = emalloc(sizeof(*hs)); - hs->c = c; - - if(url->port) - service = url->port; + if(strstr("alarm", msg) || strstr("die", msg)) + noted(NCONT); else - service = url->scheme; - hs->netaddr = estrdup(netmkaddr(url->host, 0, service)); - c->aux = hs; - if(httpdebug){ - fprint(2, "dial %s\n", hs->netaddr); - fprint(2, "dial port: %s\n", url->port); - } - fd = iotlsdial(io, hs->netaddr, 0, 0, 0, url->ischeme==UShttps); - if(fd < 0){ - Error: - if(httpdebug) - fprint(2, "iodial: %r\n"); - free(hs->location); - free(hs->setcookie); - free(hs->netaddr); - free(hs->credentials); - if(fd >= 0) - ioclose(io, hs->fd); - hs->fd = -1; - free(hs); - c->aux = nil; - return -1; - } - hs->fd = fd; - if(httpdebug) - fprint(2, "<- %s %s HTTP/1.0\n<- Host: %s\n", - c->havepostbody? "POST": "GET", url->http.page_spec, url->host); - ioprint(io, fd, "%s %s HTTP/1.0\r\nHost: %s\r\n", - c->havepostbody? "POST" : "GET", url->http.page_spec, url->host); - if(httpdebug) - fprint(2, "<- User-Agent: %s\n", c->ctl.useragent); - if(c->ctl.useragent) - ioprint(io, fd, "User-Agent: %s\r\n", c->ctl.useragent); - if(c->ctl.sendcookies){ - /* should we use url->page here? sometimes it is nil. */ - cookies = httpcookies(url->host, url->http.page_spec, - url->ischeme == UShttps); - if(cookies && cookies[0]) - ioprint(io, fd, "%s", cookies); - if(httpdebug) - fprint(2, "<- %s", cookies); - free(cookies); - } - if(c->havepostbody){ - ioprint(io, fd, "Content-type: %s\r\n", PostContentType); - ioprint(io, fd, "Content-length: %ud\r\n", c->npostbody); - if(httpdebug){ - fprint(2, "<- Content-type: %s\n", PostContentType); - fprint(2, "<- Content-length: %ud\n", c->npostbody); + noted(NDFLT); +} + +#define NOLENGTH 0x7fffffffffffffffLL + +void +http(char *m, Url *u, Key *shdr, Buq *qbody, Buq *qpost) +{ + int i, l, n, try, pid, fd, cfd, chunked, retry, nobody; + char *s, *x, buf[8192+2], status[256], method[16]; + vlong length, offset; + Url ru, tu, *nu; + Key *k, *rhdr; + Hconn *h; + Hauth *a; + + incref(qbody); + if(qpost) incref(qpost); + strncpy(method, m, sizeof(method)); + switch(rfork(RFPROC|RFMEM|RFNOWAIT)){ + default: + return; + case -1: + buclose(qbody, "can't fork"); + bufree(qbody); + buclose(qpost, "can't fork"); + bufree(qpost); + while(k = shdr){ + shdr = k->next; + free(k); } - } - if(c->authenticate){ - ioprint(io, fd, "Authorization: %s\r\n", c->authenticate); - if(httpdebug) - fprint(2, "<- Authorization: %s\n", c->authenticate); - } - ioprint(io, fd, "\r\n"); - if(c->havepostbody) - if(iowrite(io, fd, c->postbody, c->npostbody) != c->npostbody) - goto Error; - - redirect = 0; - authenticate = 0; - initibuf(&hs->b, io, fd); - code = httprcode(hs); - - switch(code){ - case -1: /* connection timed out */ - goto Error; - -/* - case Eof: - werrstr("EOF from HTTP server"); - goto Error; -*/ - - case 200: /* OK */ - case 201: /* Created */ - case 202: /* Accepted */ - case 204: /* No Content */ - case 205: /* Reset Content */ -#ifdef NOT_DEFINED - if(ofile == nil && r->start != 0) - sysfatal("page changed underfoot"); -#endif + freeurl(u); + return; + case 0: break; + } - case 206: /* Partial Content */ - werrstr("Partial Content (206)"); - goto Error; + notify(catch); + if(qpost){ + /* file for spooling the postbody if we need to restart the request */ + snprint(buf, sizeof(buf), "/tmp/http.%d.%d.post", getppid(), getpid()); + fd = create(buf, OEXCL|ORDWR|ORCLOSE, 0600); + } else + fd = -1; + + h = nil; + pid = 0; + werrstr("too many errors"); + for(try = 0; try < 6; try++){ + if(u == nil || (strcmp(u->scheme, "http") && strcmp(u->scheme, "https"))){ + werrstr("bad url"); + break; + } - case 303: /* See Other */ - c->havepostbody = 0; - case 301: /* Moved Permanently */ - case 302: /* Moved Temporarily */ - case 307: /* Temporary Redirect */ - redirect = 1; - break; + if(debug) + fprint(2, "http(%d): %s %U\n", try, method, u); + + /* preemptive authentication from hauth cache */ + qlock(&authlk); + if(proxy && !lookkey(shdr, "Proxy-Authorization")) + for(a = hauth; a; a = a->next) + if(matchurl(a->url, proxy)){ + shdr = addkey(shdr, "Proxy-Authorization", a->auth); + break; + } + if(!lookkey(shdr, "Authorization")) + for(a = hauth; a; a = a->next) + if(matchurl(a->url, u)){ + shdr = addkey(shdr, "Authorization", a->auth); + break; + } + qunlock(&authlk); + + if(proxy){ + ru = *u; + ru.fragment = nil; + } else { + memset(&ru, 0, sizeof(tu)); + ru.path = Upath(u); + ru.query = u->query; + } + n = snprint(buf, sizeof(buf), "%s %U HTTP/1.1\r\nHost: %s%s%s\r\n", + method, &ru, u->host, u->port ? ":" : "", u->port ? u->port : ""); - case 304: /* Not Modified */ - break; + for(k = shdr; k; k = k->next) + n += snprint(buf+n, sizeof(buf)-2 - n, "%s: %s\r\n", k->key, k->val); - case 400: /* Bad Request */ - werrstr("Bad Request (400)"); - goto Error; + if(n >= sizeof(buf)-64){ + werrstr("request too large"); + break; + } - case 401: /* Unauthorized */ - if(c->authenticate){ - werrstr("Authentication failed (401)"); - goto Error; + nobody = !cistrcmp(method, "HEAD"); + length = 0; + chunked = 0; + if(qpost){ + qlock(qpost); + /* wait until buffer is full, most posts are small */ + while(!qpost->closed && qpost->size < qpost->limit) + rsleep(&qpost->rz); + + if(lookkey(shdr, "Content-Length")) + chunked = 0; + else if(x = lookkey(shdr, "Transfer-Encoding")) + chunked = cistrstr(x, "chunked") != nil; + else if(chunked = !qpost->closed) + n += snprint(buf+n, sizeof(buf)-n, "Transfer-Encoding: chunked\r\n"); + else if(qpost->closed){ + if(fd >= 0){ + length = seek(fd, 0, 2); + if(length < 0) + length = 0; + } + length += qpost->size; + n += snprint(buf+n, sizeof(buf)-n, "Content-Length: %lld\r\n", length); + } + qunlock(qpost); } - authenticate = 1; - break; - case 402: /* Payment Required */ - werrstr("Payment Required (402)"); - goto Error; - - case 403: /* Forbidden */ - werrstr("Forbidden by server (403)"); - goto Error; - - case 404: /* Not Found */ - werrstr("Not found on server (404)"); - goto Error; - - case 405: /* Method Not Allowed */ - werrstr("Method not allowed (405)"); - goto Error; - - case 406: /* Not Acceptable */ - werrstr("Not Acceptable (406)"); - goto Error; - - case 407: /* Proxy auth */ - werrstr("Proxy authentication required (407)"); - goto Error; - - case 408: /* Request Timeout */ - werrstr("Request Timeout (408)"); - goto Error; - - case 409: /* Conflict */ - werrstr("Conflict (409)"); - goto Error; - - case 410: /* Gone */ - werrstr("Gone (410)"); - goto Error; - - case 411: /* Length Required */ - werrstr("Length Required (411)"); - goto Error; - - case 412: /* Precondition Failed */ - werrstr("Precondition Failed (412)"); - goto Error; - - case 413: /* Request Entity Too Large */ - werrstr("Request Entity Too Large (413)"); - goto Error; - - case 414: /* Request-URI Too Long */ - werrstr("Request-URI Too Long (414)"); - goto Error; - - case 415: /* Unsupported Media Type */ - werrstr("Unsupported Media Type (415)"); - goto Error; - - case 416: /* Requested Range Not Satisfiable */ - werrstr("Requested Range Not Satisfiable (416)"); - goto Error; - - case 417: /* Expectation Failed */ - werrstr("Expectation Failed (417)"); - goto Error; - - case 500: /* Internal server error */ - werrstr("Server choked (500)"); - goto Error; - - case 501: /* Not implemented */ - werrstr("Server can't do it (501)"); - goto Error; - - case 502: /* Bad gateway */ - werrstr("Bad gateway (502)"); - goto Error; - - case 503: /* Service unavailable */ - werrstr("Service unavailable (503)"); - goto Error; - - default: - /* Bogus: we should treat unknown code XYZ as code X00 */ - werrstr("Unknown response code %d", code); - goto Error; - } - if(httpheaders(hs) < 0) - goto Error; - if(c->ctl.acceptcookies && hs->setcookie) - httpsetcookie(hs->setcookie, url->host, url->path); - if(authenticate){ - if(!hs->credentials){ - if(hs->autherror[0]) - werrstr("%s", hs->autherror); - else - werrstr("unauthorized; no www-authenticate: header"); - goto Error; + /* give 5 seconds to dial */ + if(h == nil){ + alarm(5000); + if((h = hdial(proxy ? proxy : u)) == nil) + break; } - c->authenticate = hs->credentials; - hs->credentials = nil; - }else if(c->authenticate) - c->authenticate = 0; - if(redirect){ - if(!hs->location){ - werrstr("redirection without Location: header"); - goto Error; + + if((cfd = open("/mnt/webcookies/http", ORDWR)) >= 0){ + /* only scheme, host and path are relevant for cookies */ + memset(&tu, 0, sizeof(tu)); + tu.scheme = u->scheme; + tu.host = u->host; + tu.path = Upath(u); + fprint(cfd, "%U", &tu); + for(;;){ + if(n >= sizeof(buf)-2){ + if(debug) + fprint(2, "-> %.*s", n, buf); + if(hwrite(h, buf, n) != n) + goto Badflush; + n = 0; + } + if((l = read(cfd, buf+n, sizeof(buf)-2 - n)) == 0) + break; + if(l < 0){ + close(cfd); + cfd = -1; + break; + } + n += l; + } } - c->redirect = hs->location; - hs->location = nil; - } - return 0; -} -int -httpread(Client *c, Req *r) -{ - HttpState *hs; - long n; + n += snprint(buf+n, sizeof(buf)-n, "\r\n"); + if(debug) + fprint(2, "-> %.*s", n, buf); + if(hwrite(h, buf, n) != n){ + Badflush: + alarm(0); + goto Retry; + } - hs = c->aux; - n = readibuf(&hs->b, r->ofcall.data, r->ifcall.count); - if(n < 0) - return -1; + if(qpost){ + h->cancel = 0; + if((pid = rfork(RFMEM|RFPROC)) <= 0){ + int ifd; + + alarm(0); + if((ifd = fd) >= 0) + seek(ifd, 0, 0); + while(!h->cancel){ + if((ifd < 0) || ((n = read(ifd, buf, sizeof(buf)-2)) <= 0)){ + ifd = -1; + if((n = buread(qpost, buf, sizeof(buf)-2)) <= 0) + break; + if(fd >= 0) + if(write(fd, buf, n) != n) + break; + } + if(chunked){ + char tmp[32]; + hwrite(h, tmp, snprint(tmp, sizeof(tmp), "%x\r\n", n)); + buf[n++] = '\r'; + buf[n++] = '\n'; + } + if(hwrite(h, buf, n) != n) + break; + } + if(chunked) + hwrite(h, "0\r\n\r\n", 5); + else + h->keep = 0; + if(pid == 0) + exits(0); + } + /* no timeout when posting */ + alarm(0); + } else { + /* wait 10 seconds for the response */ + alarm(10000); + } - r->ofcall.count = n; - return 0; -} + Cont: + rhdr = 0; + retry = 0; + chunked = 0; + status[0] = 0; + offset = 0; + length = NOLENGTH; + for(l = 0; hline(h, s = buf, sizeof(buf)-1, 1) > 0; l++){ + if(debug) + fprint(2, "<- %s\n", s); + if(l == 0){ + if(x = strchr(s, ' ')) + while(*x == ' ') + *x++ = 0; + if(cistrncmp(s, "HTTP", 4)){ + h->keep = 0; + if(cistrcmp(s, "ICY")) + break; + } + strncpy(status, x, sizeof(status)); + continue; + } + if((k = parsehdr(s)) == nil) + continue; + if(!cistrcmp(k->key, "Connection")){ + if(cistrstr(k->val, "close")) + h->keep = 0; + } + else if(!cistrcmp(k->key, "Content-Length")) + length = atoll(k->val); + else if(!cistrcmp(k->key, "Transfer-Encoding")){ + if(cistrstr(k->val, "chunked")) + chunked = 1; + } + else if(!cistrcmp(k->key, "Set-Cookie") || + !cistrcmp(k->key, "Set-Cookie2")){ + if(cfd >= 0) + fprint(cfd, "Set-Cookie: %s\n", k->val); + free(k); + continue; + } + k->next = rhdr; + rhdr = k; + } + alarm(0); + if(cfd >= 0){ + close(cfd); + cfd = -1; + } -void -httpclose(Client *c) -{ - HttpState *hs; + if((i = atoi(status)) < 0) + i = 0; + Status: + switch(i){ + default: + if(i % 100){ + i -= (i % 100); + goto Status; + } + case 100: /* Continue */ + case 101: /* Switching Protocols */ + while(k = rhdr){ + rhdr = k->next; + free(k); + } + goto Cont; + case 304: /* Not Modified */ + nobody = 1; + case 305: /* Use Proxy */ + case 400: /* Bad Request */ + case 402: /* Payment Required */ + case 403: /* Forbidden */ + case 404: /* Not Found */ + case 405: /* Method Not Allowed */ + case 406: /* Not Acceptable */ + case 408: /* Request Timeout */ + case 409: /* Conflict */ + case 410: /* Gone */ + case 411: /* Length Required */ + case 412: /* Precondition Failed */ + case 413: /* Request Entity Too Large */ + case 414: /* Request URI Too Large */ + case 415: /* Unsupported Media Type */ + case 416: /* Requested Range Not Satisfiable */ + case 417: /* Expectation Failed */ + case 500: /* Internal server error */ + case 501: /* Not implemented */ + case 502: /* Bad gateway */ + case 503: /* Service unavailable */ + case 504: /* Gateway Timeout */ + case 505: /* HTTP Version not Supported */ + Error: + h->cancel = 1; + buclose(qbody, status); + buclose(qpost, status); + break; + case 300: /* Multiple choices */ + case 302: /* Found */ + case 303: /* See Other */ + if(qpost){ + if(pid > 0){ + waitpid(); + pid = 0; + } + buclose(qpost, 0); + bufree(qpost); + qpost = nil; + } + if(cistrcmp(method, "HEAD")) + strncpy(method, "GET", sizeof(method)); + case 301: /* Moved Permanently */ + case 307: /* Temporary Redirect */ + case 308: /* Resume Incomplete */ + if((x = lookkey(rhdr, "Location")) == nil) + goto Error; + if((nu = saneurl(url(x, u))) == nil) + goto Error; + freeurl(u); + u = nu; + if(0){ + case 401: /* Unauthorized */ + if(x = lookkey(shdr, "Authorization")) + flushauth(nil, x); + if((x = lookkey(rhdr, "WWW-Authenticate")) == nil) + goto Error; + if(authenticate(u, &ru, method, x) < 0) + goto Error; + } + if(0){ + case 407: /* Proxy Auth */ + if(proxy == nil) + goto Error; + if(x = lookkey(shdr, "Proxy-Authorization")) + flushauth(proxy, x); + if((x = lookkey(rhdr, "Proxy-Authenticate")) == nil) + goto Error; + if(authenticate(proxy, proxy, method, x) < 0) + goto Error; + } + case 0: /* No status */ + if(qpost && fd < 0){ + if(i > 0) + goto Error; + break; + } + h->cancel = 1; + retry = 1; + break; + case 204: /* No Content */ + case 205: /* Reset Content */ + nobody = 1; + case 200: /* OK */ + case 201: /* Created */ + case 202: /* Accepted */ + case 203: /* Non-Authoritative Information */ + case 206: /* Partial Content */ + qbody->url = u; u = nil; + qbody->hdr = rhdr; rhdr = nil; + if(nobody) + buclose(qbody, 0); + break; + } - hs = c->aux; - if(hs == nil) - return; - if(hs->fd >= 0) - ioclose(c->io, hs->fd); - hs->fd = -1; - free(hs->location); - free(hs->setcookie); - free(hs->netaddr); - free(hs->credentials); - free(hs); - c->aux = nil; + while(k = rhdr){ + rhdr = k->next; + free(k); + } + + /* + * remove authorization headers so on the next round, we use + * the hauth cache (wich checks the scope url). this makes + * sure we wont send credentials to the wrong url after + * a redirect. + */ + shdr = delkey(shdr, "Proxy-Authorization"); + shdr = delkey(shdr, "Authorization"); + + if(!chunked && length == NOLENGTH) + h->keep = 0; + + /* + * read the response body (if any). retry means we'r just + * skipping the error page so we wont touch qbody. + */ + while(!nobody){ + if((qbody->closed || retry) && !h->keep) + break; + if(chunked){ + if(hline(h, buf, sizeof(buf)-1, 0) <= 0) + break; + length = strtoll(buf, nil, 16); + offset = 0; + } + while(offset < length){ + l = sizeof(buf); + if(l > (length - offset)) + l = (length - offset); + if((n = hread(h, buf, l)) <= 0) + break; + offset += n; + if(!retry) + if(buwrite(qbody, buf, n) != n) + break; + } + if(offset != length){ + h->keep = 0; + if(length != NOLENGTH) + break; + } + if(chunked){ + while(hline(h, buf, sizeof(buf)-1, 1) > 0){ + if(debug) + fprint(2, "<= %s\n", buf); + if(!retry) + if(k = parsehdr(buf)){ + k->next = qbody->hdr; + qbody->hdr = k; + } + } + if(length > 0) + continue; + } + if(!retry) + buclose(qbody, 0); + break; + } + + if(!retry) + break; + Retry: + if(cfd >= 0) + close(cfd); + if(pid > 0){ + waitpid(); + pid = 0; + } + hclose(h); + h = nil; + } + alarm(0); + + rerrstr(buf, sizeof(buf)); + buclose(qbody, buf); + bufree(qbody); + + if(qpost){ + if(pid > 0) + waitpid(); + buclose(qpost, buf); + bufree(qpost); + } + if(fd >= 0) + close(fd); + + hclose(h); + freeurl(u); + + while(k = shdr){ + shdr = k->next; + free(k); + } + exits(0); } diff --git a/sys/src/cmd/webfs/io.c b/sys/src/cmd/webfs/io.c deleted file mode 100644 index 4359e3d8e..000000000 --- a/sys/src/cmd/webfs/io.c +++ /dev/null @@ -1,86 +0,0 @@ -#include <u.h> -#include <libc.h> -#include <bio.h> -#include <ip.h> -#include <plumb.h> -#include <thread.h> -#include <fcall.h> -#include <9p.h> -#include <mp.h> -#include <libsec.h> -#include "dat.h" -#include "fns.h" - -static long -_iovfprint(va_list *arg) -{ - int fd; - char *fmt; - va_list arg2; - - fd = va_arg(*arg, int); - fmt = va_arg(*arg, char*); - arg2 = va_arg(*arg, va_list); - return vfprint(fd, fmt, arg2); -} - -int -iovfprint(Ioproc *io, int fd, char *fmt, va_list arg) -{ - return iocall(io, _iovfprint, fd, fmt, arg); -} - -int -ioprint(Ioproc *io, int fd, char *fmt, ...) -{ - int n; - va_list arg; - - va_start(arg, fmt); - n = iovfprint(io, fd, fmt, arg); - va_end(arg); - return n; -} - -static long -_iotlsdial(va_list *arg) -{ - char *addr, *local, *dir; - int *cfdp, fd, tfd, usetls; - TLSconn conn; - - addr = va_arg(*arg, char*); - local = va_arg(*arg, char*); - dir = va_arg(*arg, char*); - cfdp = va_arg(*arg, int*); - usetls = va_arg(*arg, int); - - fd = dial(addr, local, dir, cfdp); - if(fd < 0) - return -1; - if(!usetls) - return fd; - - memset(&conn, 0, sizeof conn); - /* does no good, so far anyway */ - // conn.chain = readcertchain("/sys/lib/ssl/vsignss.pem"); - - tfd = tlsClient(fd, &conn); - close(fd); - if(tfd < 0) - fprint(2, "%s: tlsClient: %r\n", argv0); - else { - /* BUG: check cert here? */ - if(conn.cert) - free(conn.cert); - if(conn.sessionID) - free(conn.sessionID); - } - return tfd; -} - -int -iotlsdial(Ioproc *io, char *addr, char *local, char *dir, int *cfdp, int usetls) -{ - return iocall(io, _iotlsdial, addr, local, dir, cfdp, usetls); -} diff --git a/sys/src/cmd/webfs/main.c b/sys/src/cmd/webfs/main.c deleted file mode 100644 index 0792dbf13..000000000 --- a/sys/src/cmd/webfs/main.c +++ /dev/null @@ -1,67 +0,0 @@ -#include <u.h> -#include <libc.h> -#include <bio.h> -#include <ip.h> -#include <plumb.h> -#include <thread.h> -#include <fcall.h> -#include <9p.h> -#include "dat.h" -#include "fns.h" - -char *cookiefile; -char *mtpt = "/mnt/web"; -char *service; - -Ctl globalctl = -{ - 1, /* accept cookies */ - 1, /* send cookies */ - 10, /* redirect limit */ - "webfs/2.0 (plan 9)" /* user agent */ -}; - -void -usage(void) -{ - fprint(2, "usage: webfs [-c cookies] [-m mtpt] [-s service]\n"); - threadexitsall("usage"); -} - -#include <pool.h> -void -threadmain(int argc, char **argv) -{ - rfork(RFNOTEG); - ARGBEGIN{ - case 'd': - mainmem->flags |= POOL_PARANOIA|POOL_ANTAGONISM; - break; - case 'D': - chatty9p++; - break; - case 'c': - cookiefile = EARGF(usage()); - break; - case 'm': - mtpt = EARGF(usage()); - break; - case 's': - service = EARGF(usage()); - break; - default: - usage(); - }ARGEND - - quotefmtinstall(); - if(argc != 0) - usage(); - - plumbinit(); - globalctl.useragent = estrdup(globalctl.useragent); - initcookies(cookiefile); - initurl(); - initfs(); - threadpostmountsrv(&fs, service, mtpt, MREPL); - threadexits(nil); -} diff --git a/sys/src/cmd/webfs/mkfile b/sys/src/cmd/webfs/mkfile index 879ab16b9..08993cf32 100644 --- a/sys/src/cmd/webfs/mkfile +++ b/sys/src/cmd/webfs/mkfile @@ -1,35 +1,8 @@ </$objtype/mkfile BIN=/$objtype/bin - TARG=webfs -SCHEMEOFILES=\ - file.$O\ - ftp.$O\ - http.$O\ - -OFILES=\ - buf.$O\ - client.$O\ - cookies.$O\ - fs.$O\ - http.$O\ - io.$O\ - main.$O\ - plumb.$O\ - url.$O\ - util.$O\ -# $SCHEMEOFILES - -HFILES=\ - dat.h\ - fns.h\ - -UPDATE=\ - mkfile\ - $HFILES\ - ${OFILES:%.$O=%.c}\ - ${TARG:%=/386/bin/%}\ +HFILES=fns.h dat.h +OFILES=sub.$O url.$O buq.$O http.$O fs.$O </sys/src/cmd/mkone - diff --git a/sys/src/cmd/webfs/plumb.c b/sys/src/cmd/webfs/plumb.c deleted file mode 100644 index ada0f4168..000000000 --- a/sys/src/cmd/webfs/plumb.c +++ /dev/null @@ -1,165 +0,0 @@ -#include <u.h> -#include <libc.h> -#include <auth.h> -#include <fcall.h> -#include <thread.h> -#include <plumb.h> -#include <9p.h> - -#include "dat.h" -#include "fns.h" - -static int plumbsendfd; -static int plumbwebfd; -static Channel *plumbchan; - -static void plumbwebproc(void*); -static void plumbwebthread(void*); -static void plumbsendproc(void*); - -void -plumbinit(void) -{ - plumbsendfd = plumbopen("send", OWRITE|OCEXEC); - plumbwebfd = plumbopen("web", OREAD|OCEXEC); -} - -void -plumbstart(void) -{ - plumbchan = chancreate(sizeof(Plumbmsg*), 0); - proccreate(plumbwebproc, nil, STACK); - threadcreate(plumbwebthread, nil, STACK); -} - -static void -plumbwebthread(void*) -{ - char *base; - Plumbmsg *m; - - for(;;){ - m = recvp(plumbchan); - if(m == nil) - threadexits(nil); - base = plumblookup(m->attr, "baseurl"); - if(base == nil) - base = m->wdir; - plumburl(m->data, base); - plumbfree(m); - } -} - -static void -plumbwebproc(void*) -{ - Plumbmsg *m; - - for(;;){ - m = plumbrecv(plumbwebfd); - sendp(plumbchan, m); - if(m == nil) - threadexits(nil); - } -} - -static void -addattr(Plumbmsg *m, char *name, char *value) -{ - Plumbattr *a; - - a = malloc(sizeof(Plumbattr)); - a->name = name; - a->value = value; - a->next = m->attr; - m->attr = a; -} - -static void -freeattrs(Plumbmsg *m) -{ - Plumbattr *a, *next; - - a = m->attr; - while(a != nil) { - next = a->next; - free(a); - a = next; - } -} - -static struct -{ - char *ctype; - char *ext; -} -ctypes[] = -{ - { "application/msword", "doc" }, - { "application/pdf", "pdf" }, - { "application/postscript", "ps" }, - { "application/rtf", "rtf" }, - { "image/gif", "gif" }, - { "image/jpeg", "jpg" }, - { "image/png", "png" }, - { "image/ppm", "ppm" }, - { "image/tiff", "tiff" }, - { "text/html", "html" }, - { "text/plain", "txt" }, - { "text/xml", "xml" }, -}; - -void -replumb(Client *c) -{ - int i; - Plumbmsg *m; - char name[128], *ctype, *ext, *p; - - if(!c->plumbed) - return; - m = emalloc(sizeof(Plumbmsg)); - m->src = "webfs"; - m->dst = nil; - m->wdir = "/"; - m->type = "text"; - m->attr = nil; - addattr(m, "url", c->url->url); - ctype = c->contenttype; - ext = nil; - if(ctype != nil) { - addattr(m, "content-type", ctype); - for(i = 0; i < nelem(ctypes); i++) { - if(strcmp(ctype, ctypes[i].ctype) == 0) { - ext = ctypes[i].ext; - break; - } - } - } - if(ext == nil) { - p = strrchr(c->url->url, '/'); - if(p != nil) - p = strrchr(p+1, '.'); - if(p != nil && strlen(p) <= 5) - ext = p+1; - else - ext = "txt"; /* punt */ - } - c->ext = ext; -if(0)fprint(2, "content type %s -> extension .%s\n", ctype, ext); - m->ndata = snprint(name, sizeof name, "/mnt/web/%d/body.%s", c->num, ext); - m->data = estrdup(name); - proccreate(plumbsendproc, m, STACK); /* separate proc to avoid a deadlock */ -} - -static void -plumbsendproc(void *x) -{ - Plumbmsg *m; - - m = x; - plumbsend(plumbsendfd, m); - freeattrs(m); - free(m->data); - free(m); -} diff --git a/sys/src/cmd/webfs/sub.c b/sys/src/cmd/webfs/sub.c new file mode 100644 index 000000000..274ef0b08 --- /dev/null +++ b/sys/src/cmd/webfs/sub.c @@ -0,0 +1,119 @@ +#include <u.h> +#include <libc.h> +#include <ctype.h> +#include <fcall.h> +#include <thread.h> +#include <9p.h> + +#include "dat.h" +#include "fns.h" + +void* +emalloc(int n) +{ + void *v; + v = emalloc9p(n); + setmalloctag(v, getcallerpc(&n)); + memset(v, 0, n); + return v; +} + +char* +estrdup(char *s) +{ + s = estrdup9p(s); + setmalloctag(s, getcallerpc(&s)); + return s; +} + +Key* +addkey(Key *h, char *key, char *val) +{ + Key *k; + int n; + + if(val == nil) + val = ""; + n = strlen(key)+1; + k = emalloc(sizeof(*k) + n + strlen(val)+1); + k->next = h; + k->val = k->key + n; + strcpy(k->key, key); + strcpy(k->val, val); + return k; +} + +Key* +delkey(Key *h, char *key) +{ + Key *k, *p; + + for(p = nil, k = h; k; p = k, k = k->next){ + if(!cistrcmp(k->key, key)){ + if(p) + p->next = k->next; + else + h = k->next; + memset(k->val, 0, strlen(k->val)); + free(k); + break; + } + } + return h; +} + +char* +lookkey(Key *k, char *key) +{ + while(k){ + if(!cistrcmp(k->key, key)) + return k->val; + k = k->next; + } + return nil; +} + +Key* +parsehdr(char *s) +{ + char *v; + + v = strchr(s, 0)-1; + while(v >= s && strchr("\n\r\t ", *v)) + *v-- = 0; + if(v = strchr(s, ':')){ + *v++ = 0; + while(strchr("\t ", *v)) + v++; + if(*s && *v) + return addkey(0, s, v); + } + return nil; +} + +char* +unquote(char *s, char **ps) +{ + char *p; + + if(*s != '"'){ + p = strpbrk(s, " \t\r\n"); + *p++ = 0; + *ps = p; + return s; + } + for(p=s+1; *p; p++){ + if(*p == '\"'){ + *p++ = 0; + break; + } + if(*p == '\\' && *(p+1)){ + p++; + continue; + } + } + memmove(s, s+1, p-(s+1)); + s[p-(s+1)] = 0; + *ps = p; + return s; +} diff --git a/sys/src/cmd/webfs/url.c b/sys/src/cmd/webfs/url.c index c6c5695f0..2137010d1 100644 --- a/sys/src/cmd/webfs/url.c +++ b/sys/src/cmd/webfs/url.c @@ -1,871 +1,360 @@ -/* - * This is a URL parser, written to parse "Common Internet Scheme" URL - * syntax as described in RFC1738 and updated by RFC2396. Only absolute URLs - * are supported, using "server-based" naming authorities in the schemes. - * Support for literal IPv6 addresses is included, per RFC2732. - * - * Current "known" schemes: http, ftp, file. - * - * We can do all the parsing operations without Runes since URLs are - * defined to be composed of US-ASCII printable characters. - * See RFC1738, RFC2396. - */ - #include <u.h> #include <libc.h> #include <ctype.h> -#include <regexp.h> -#include <plumb.h> -#include <thread.h> #include <fcall.h> +#include <thread.h> #include <9p.h> + #include "dat.h" #include "fns.h" -int urldebug; - -/* If set, relative paths with leading ".." segments will have them trimmed */ -#define RemoveExtraRelDotDots 0 -#define ExpandCurrentDocUrls 1 - -static char* -schemestrtab[] = -{ - nil, - "http", - "https", - "ftp", - "file", -}; - static int -ischeme(char *s) -{ - int i; - - for(i=0; i<nelem(schemestrtab); i++) - if(schemestrtab[i] && strcmp(s, schemestrtab[i])==0) - return i; - return USunknown; -} - -/* - * URI splitting regexp is from RFC2396, Appendix B: - * ^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))? - * 12 3 4 5 6 7 8 9 - * - * Example: "http://www.ics.uci.edu/pub/ietf/uri/#Related" - * $2 = scheme "http" - * $4 = authority "www.ics.uci.edu" - * $5 = path "/pub/ietf/uri/" - * $7 = query <undefined> - * $9 = fragment "Related" - */ - -/* - * RFC2396, Sec 3.1, contains: - * - * Scheme names consist of a sequence of characters beginning with a - * lower case letter and followed by any combination of lower case - * letters, digits, plus ("+"), period ("."), or hyphen ("-"). For - * resiliency, programs interpreting URI should treat upper case letters - * as equivalent to lower case in scheme names (e.g., allow "HTTP" as - * well as "http"). - */ - -/* - * For server-based naming authorities (RFC2396 Sec 3.2.2): - * server = [ [ userinfo "@" ] hostport ] - * userinfo = *( unreserved | escaped | - * ";" | ":" | "&" | "=" | "+" | "$" | "," ) - * hostport = host [ ":" port ] - * host = hostname | IPv4address - * hostname = *( domainlabel "." ) toplabel [ "." ] - * domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum - * toplabel = alpha | alpha *( alphanum | "-" ) alphanum - * IPv4address = 1*digit "." 1*digit "." 1*digit "." 1*digit - * port = *digit - * - * The host is a domain name of a network host, or its IPv4 address as a - * set of four decimal digit groups separated by ".". Literal IPv6 - * addresses are not supported. - * - * Note that literal IPv6 address support is outlined in RFC2732: - * host = hostname | IPv4address | IPv6reference - * ipv6reference = "[" IPv6address "]" (RFC2373) - * - * Since hostnames and numbers will have to be resolved by the OS anyway, - * we don't have to parse them too pedantically (counting '.'s, checking - * for well-formed literal IP addresses, etc.). - * - * In FTP/file paths, we reject most ";param"s and querys. In HTTP paths, - * we just pass them through. - * - * Instead of letting a "path" be 0-or-more characters as RFC2396 suggests, - * we'll say it's 1-or-more characters, 0-or-1 times. This way, an absent - * path yields a nil substring match, instead of an empty one. - * - * We're more restrictive than RFC2396 indicates with "userinfo" strings, - * insisting they have the form "[user[:password]]". This may need to - * change at some point, however. - */ - -/* RE character-class components -- these go in brackets */ -#define PUNCT "\\-_.!~*'()" -#define ALNUM "a-zA-Z0-9" -#define HEX "0-9a-fA-F" -#define UNRES ALNUM PUNCT - -/* RE components; _N => has N parenthesized subexpressions when expanded */ -#define USERINFO_2 "([" UNRES ";:&=+$,]|(%[" HEX "][" HEX "]))" - -typedef struct Retab Retab; -struct Retab -{ - char *str; - Reprog *prog; - int size; - int ind[5]; -}; - -enum -{ - REsplit = 0, - REscheme, - REauthority, - REhost, - REuserinfo, - REftppath, - - MaxResub= 20, -}; - -Retab retab[] = /* view in constant width Font */ -{ -[REsplit] - "^(([^:/?#]+):)?(//([^/?#]*))?([^?#]+)?(\\?([^#]*))?(#(.*))?$", nil, 0, - /* |-scheme-| |-auth.-| |path--| |query| |--|frag */ - { 2, 4, 5, 7, 9}, - -[REscheme] - "^[a-z][a-z0-9+-.]*$", nil, 0, - { 0, }, - -[REauthority] - "^(((" USERINFO_2 "*)@)?(((\\[[^\\]@]+\\])|([^:\\[@]+))(:([0-9]*))?)?)?$", nil, 0, - /* |----user info-----| |--------host----------------| |-port-| */ - { 3, 7, 11, }, - -[REhost] - "^(([a-zA-Z0-9\\-.]+)|(\\[([a-fA-F0-9.:]+)\\]))$", nil, 0, - /* |--regular host--| |-IPv6 literal-| */ - { 2, 4, }, - -[REuserinfo] - "^(([^:]*)(:([^:]*))?)$", nil, 0, - /* |user-| |pass-| */ - { 2, 4, }, - -[REftppath] - "^(.+)(;[tT][yY][pP][eE]=([aAiIdD]))?$", nil, 0, - /*|--|-path |ftptype-| */ - { 1, 3, }, -}; - -static int -countleftparen(char *s) -{ - int n; - - n = 0; - for(; *s; s++) - if(*s == '(') - n++; - return n; -} - -void -initurl(void) +dhex(char c) { - int i, j; - - for(i=0; i<nelem(retab); i++){ - retab[i].prog = regcomp(retab[i].str); - if(retab[i].prog == nil) - sysfatal("recomp(%s): %r", retab[i].str); - retab[i].size = countleftparen(retab[i].str)+1; - for(j=0; j<nelem(retab[i].ind); j++) - if(retab[i].ind[j] >= retab[i].size) - sysfatal("bad index in regexp table: retab[%d].ind[%d] = %d >= %d", - i, j, retab[i].ind[j], retab[i].size); - if(MaxResub < retab[i].size) - sysfatal("MaxResub too small: %d < %d", MaxResub, retab[i].size); - } + if('0' <= c && c <= '9') + return c-'0'; + if('a' <= c && c <= 'f') + return c-'a'+10; + if('A' <= c && c <= 'F') + return c-'A'+10; + return 0; } -typedef struct SplitUrl SplitUrl; -struct SplitUrl -{ - struct { - char *s; - char *e; - } url, scheme, authority, path, query, fragment; -}; - -/* - * Implements the algorithm in RFC2396 sec 5.2 step 6. - * Returns number of chars written, excluding NUL terminator. - * dest is known to be >= strlen(base)+rel_len. - */ -static void -merge_relative_path(char *base, char *rel_st, int rel_len, char *dest) +static char* +unescape(char *s, char *spec) { - char *s, *p, *e, *pdest; - - pdest = dest; - - /* 6a: start with base, discard last segment */ - if(base && base[0]){ - /* Empty paths don't match in our scheme; 'base' should be nil */ - assert(base[0] == '/'); - e = strrchr(base, '/'); - e++; - memmove(pdest, base, e-base); - pdest += e-base; - }else{ - /* Artistic license on my part */ - *pdest++ = '/'; - } - - /* 6b: append relative component */ - if(rel_st){ - memmove(pdest, rel_st, rel_len); - pdest += rel_len; - } - - /* 6c: remove any occurrences of "./" as a complete segment */ - s = dest; - *pdest = '\0'; - while(e = strstr(s, "./")){ - if((e == dest) || (*(e-1) == '/')){ - memmove(e, e+2, pdest+1-(e+2)); /* +1 for NUL */ - pdest -= 2; - }else - s = e+1; - } - - /* 6d: remove a trailing "." as a complete segment */ - if(pdest>dest && *(pdest-1)=='.' && - (pdest==dest+1 || *(pdest-2)=='/')) - *--pdest = '\0'; - - /* 6e: remove occurences of "seg/../", where seg != "..", left->right */ - s = dest+1; - while(e = strstr(s, "/../")){ - p = e - 1; - while(p >= dest && *p != '/') - p--; - if(memcmp(p, "/../", 4) != 0){ - memmove(p+1, e+4, pdest+1-(e+4)); - pdest -= (e+4) - (p+1); - }else - s = e+1; - } - - /* 6f: remove a trailing "seg/..", where seg isn't ".." */ - if(pdest-3 > dest && memcmp(pdest-3, "/..", 3)==0){ - p = pdest-3 - 1; - while(p >= dest && *p != '/') - p--; - if(memcmp(p, "/../", 4) != 0){ - pdest = p+1; - *pdest = '\0'; - } - } + char *r, *w; + uchar x; - /* 6g: leading ".." segments are errors -- we'll just blat them out. */ - if(RemoveExtraRelDotDots){ - p = dest; - if (p[0] == '/') - p++; - s = p; - while(s[0]=='.' && s[1]=='.' && (s[2]==0 || s[2]=='/')) - s += 3; - if(s > p){ - memmove(p, s, pdest+1-s); - pdest -= s-p; + if(s == nil) + return s; + for(r=w=s; x = *r; r++){ + if(x == '%' && isxdigit(r[1]) && isxdigit(r[2])){ + x = (dhex(r[1])<<4)|dhex(r[2]); + if(x == 0 || (x > 0x1F && x < 0x7F && strchr(spec, x))){ + *w++ = '%'; + *w++ = toupper(r[1]); + *w++ = toupper(r[2]); + } + else + *w++ = x; + r += 2; + continue; } + if(x == '+') + x = ' '; + *w++ = x; } - USED(pdest); - - if(urldebug) - fprint(2, "merge_relative_path: '%s' + '%.*s' -> '%s'\n", base, rel_len, - rel_st, dest); + *w = 0; + return s; } -/* - * See RFC2396 sec 5.2 for info on resolving relative URIs to absolute form. - * - * If successful, this just ends up freeing and replacing "u->url". - */ -static int -resolve_relative(SplitUrl *su, Url *base, Url *u) +int +Efmt(Fmt *f) { - char *url, *path; - char *purl, *ppath; - int currentdoc, ulen, plen; - - if(base == nil){ - werrstr("relative URI given without base"); - return -1; - } - if(base->scheme == nil){ - werrstr("relative URI given with no scheme"); - return -1; - } - if(base->ischeme == USunknown){ - werrstr("relative URI given with unknown scheme"); - return -1; - } - if(base->ischeme == UScurrent){ - werrstr("relative URI given with incomplete base"); - return -1; - } - assert(su->scheme.s == nil); - - /* Sec 5.2 step 2 */ - currentdoc = 0; - if(su->path.s==nil && su->scheme.s==nil && su->authority.s==nil && su->query.s==nil){ - /* Reference is to current document */ - if(urldebug) - fprint(2, "url %s is relative to current document\n", u->url); - u->ischeme = UScurrent; - if(!ExpandCurrentDocUrls) - return 0; - currentdoc = 1; - } - - /* Over-estimate the maximum lengths, for allocation purposes */ - /* (constants are for separators) */ - plen = 1; - if(base->path) - plen += strlen(base->path); - if(su->path.s) - plen += 1 + (su->path.e - su->path.s); - - ulen = 0; - ulen += strlen(base->scheme) + 1; - if(su->authority.s) - ulen += 2 + (su->authority.e - su->authority.s); - else - ulen += 2 + ((base->authority) ? strlen(base->authority) : 0); - ulen += plen; - if(su->query.s) - ulen += 1 + (su->query.e - su->query.s); - else if(currentdoc && base->query) - ulen += 1 + strlen(base->query); - if(su->fragment.s) - ulen += 1 + (su->fragment.e - su->fragment.s); - else if(currentdoc && base->fragment) - ulen += 1 + strlen(base->fragment); - url = emalloc(ulen+1); - path = emalloc(plen+1); + char *s, *spec; + Str2 s2; - url[0] = '\0'; - purl = url; - path[0] = '\0'; - ppath = path; - - if(su->authority.s || (su->path.s && (su->path.s[0] == '/'))){ - /* Is a "network-path" or "absolute-path"; don't merge with base path */ - /* Sec 5.2 steps 4,5 */ - if(su->path.s){ - memmove(ppath, su->path.s, su->path.e - su->path.s); - ppath += su->path.e - su->path.s; - *ppath = '\0'; - } - }else if(currentdoc){ - /* Is a current-doc reference; just copy the path from the base URL */ - if(base->path){ - strcpy(ppath, base->path); - ppath += strlen(ppath); + s2 = va_arg(f->args, Str2); + s = s2.s1; + spec = s2.s2; + for(; *s; s++) + if(*s == '%' && isxdigit(s[1]) && isxdigit(s[2])){ + fmtprint(f, "%%%c%c", toupper(s[1]), toupper(s[2])); + s += 2; } - USED(ppath); - }else{ - /* Is a relative-path reference; we have to merge it */ - /* Sec 5.2 step 6 */ - merge_relative_path(base->path, - su->path.s, su->path.e - su->path.s, ppath); - } - - /* Build new URL from pieces, inheriting from base where needed */ - strcpy(purl, base->scheme); - purl += strlen(purl); - *purl++ = ':'; - if(su->authority.s){ - strcpy(purl, "//"); - purl += strlen(purl); - memmove(purl, su->authority.s, su->authority.e - su->authority.s); - purl += su->authority.e - su->authority.s; - }else if(base->authority){ - strcpy(purl, "//"); - purl += strlen(purl); - strcpy(purl, base->authority); - purl += strlen(purl); - } - assert((path[0] == '\0') || (path[0] == '/')); - strcpy(purl, path); - purl += strlen(purl); - - /* - * The query and fragment are not inherited from the base, - * except in case of "current document" URLs, which inherit any query - * and may inherit the fragment. - */ - if(su->query.s){ - *purl++ = '?'; - memmove(purl, su->query.s, su->query.e - su->query.s); - purl += su->query.e - su->query.s; - }else if(currentdoc && base->query){ - *purl++ = '?'; - strcpy(purl, base->query); - purl += strlen(purl); - } - - if(su->fragment.s){ - *purl++ = '#'; - memmove(purl, su->fragment.s, su->fragment.e - su->fragment.s); - purl += su->fragment.e - su->fragment.s; - }else if(currentdoc && base->fragment){ - *purl++ = '#'; - strcpy(purl, base->fragment); - purl += strlen(purl); - } - USED(purl); - - if(urldebug) - fprint(2, "resolve_relative: '%s' + '%s' -> '%s'\n", base->url, u->url, url); - free(u->url); - u->url = url; - free(path); + else if(isalnum(*s) || strchr(".-_~!$&'()*,;=", *s) || strchr(spec, *s)) + fmtprint(f, "%c", *s); + else + fmtprint(f, "%%%.2X", *s & 0xff); return 0; } int -regx(Reprog *prog, char *s, Resub *m, int nm) -{ - int i; - - if(s == nil) - s = m[0].sp; /* why is this necessary? */ - - i = regexec(prog, s, m, nm); -/* - if(i >= 0) - for(j=0; j<nm; j++) - fprint(2, "match%d: %.*s\n", j, utfnlen(m[j].sp, m[j].ep-m[j].sp), m[j].sp); -*/ - return i; -} - -static int -ismatch(int i, char *s, char *desc) +Ufmt(Fmt *f) { - Resub m[1]; - - m[0].sp = m[0].ep = nil; - if(!regx(retab[i].prog, s, m, 1)){ - werrstr("malformed %s: %q", desc, s); - return 0; - } - return 1; -} - -static int -spliturl(char *url, SplitUrl *su) -{ - Resub m[MaxResub]; - Retab *t; - - /* - * Newlines are not valid in a URI, but regexp(2) treats them specially - * so it's best to make sure there are none before proceeding. - */ - if(strchr(url, '\n')){ - werrstr("newline in URI"); - return -1; - } - - m[0].sp = m[0].ep = nil; - t = &retab[REsplit]; - if(!regx(t->prog, url, m, t->size)){ - werrstr("malformed URI: %q", url); - return -1; - } - - su->url.s = m[0].sp; - su->url.e = m[0].ep; - su->scheme.s = m[t->ind[0]].sp; - su->scheme.e = m[t->ind[0]].ep; - su->authority.s = m[t->ind[1]].sp; - su->authority.e = m[t->ind[1]].ep; - su->path.s = m[t->ind[2]].sp; - su->path.e = m[t->ind[2]].ep; - su->query.s = m[t->ind[3]].sp; - su->query.e = m[t->ind[3]].ep; - su->fragment.s = m[t->ind[4]].sp; - su->fragment.e = m[t->ind[4]].ep; - - if(urldebug) - fprint(2, "split url %s into %.*q %.*q %.*q %.*q %.*q %.*q\n", - url, - su->url.s ? utfnlen(su->url.s, su->url.e-su->url.s) : 10, su->url.s ? su->url.s : "", - su->scheme.s ? utfnlen(su->scheme.s, su->scheme.e-su->scheme.s) : 10, su->scheme.s ? su->scheme.s : "", - su->authority.s ? utfnlen(su->authority.s, su->authority.e-su->authority.s) : 10, su->authority.s ? su->authority.s : "", - su->path.s ? utfnlen(su->path.s, su->path.e-su->path.s) : 10, su->path.s ? su->path.s : "", - su->query.s ? utfnlen(su->query.s, su->query.e-su->query.s) : 10, su->query.s ? su->query.s : "", - su->fragment.s ? utfnlen(su->fragment.s, su->fragment.e-su->fragment.s) : 10, su->fragment.s ? su->fragment.s : ""); + char *s; + Url *u; + if((u = va_arg(f->args, Url*)) == nil) + return fmtprint(f, "nil"); + if(u->scheme) + fmtprint(f, "%s:", u->scheme); + if(u->user || u->host) + fmtprint(f, "//"); + if(u->user){ + fmtprint(f, "%E", (Str2){u->user, ""}); + if(u->pass) + fmtprint(f, ":%E", (Str2){u->pass, ""}); + fmtprint(f, "@"); + } + if(u->host){ + fmtprint(f, strchr(u->host, ':') ? "[%s]" : "%s", u->host); + if(u->port) + fmtprint(f, ":%s", u->port); + } + if(s = Upath(u)) + fmtprint(f, "%E", (Str2){s, "/:@"}); + if(u->query) + fmtprint(f, "?%E", (Str2){u->query, "/:@"}); + if(u->fragment) + fmtprint(f, "#%E", (Str2){u->fragment, "/:@?"}); return 0; } -static int -parse_scheme(SplitUrl *su, Url *u) +char* +Upath(Url *u) { - if(su->scheme.s == nil){ - werrstr("missing scheme"); - return -1; + if(u){ + if(u->path) + return u->path; + if(u->user || u->host) + return "/"; } - u->scheme = estredup(su->scheme.s, su->scheme.e); - strlower(u->scheme); - - if(!ismatch(REscheme, u->scheme, "scheme")) - return -1; - - u->ischeme = ischeme(u->scheme); - if(urldebug) - fprint(2, "parse_scheme %s => %d\n", u->scheme, u->ischeme); - return 0; + return nil; } -static int -parse_unknown_part(SplitUrl *su, Url *u) -{ - char *s, *e; - - assert(u->ischeme == USunknown); - assert(su->scheme.e[0] == ':'); - - s = su->scheme.e+1; - if(su->fragment.s){ - e = su->fragment.s-1; - assert(*e == '#'); - }else - e = s+strlen(s); - - u->schemedata = estredup(s, e); - return 0; +static char* +remdot(char *s) +{ + char *b, *d, *p; + int dir, n; + + dir = 1; + b = d = s; + while(*s == '/') + s++; + for(; s; s = p){ + if(p = strchr(s, '/')) + while(*p == '/') + *p++ = 0; + if(*s == '.' && ((s[1] == 0) || (s[1] == '.' && s[2] == 0))){ + if(s[1] == '.') + while(d > b) + if(*--d == '/') + break; + dir = 1; + continue; + } else + dir = (p != nil); + n = strlen(s); + memmove(d+1, s, n); + *d = '/'; + d += n+1; + } + if(dir) + *d++ = '/'; + *d = 0; + return b; } -static int -parse_userinfo(char *s, char *e, Url *u) +static char* +abspath(char *s, char *b) { - Resub m[MaxResub]; - Retab *t; + char *x, *a; - m[0].sp = s; - m[0].ep = e; - t = &retab[REuserinfo]; - if(!regx(t->prog, nil, m, t->size)){ - werrstr("malformed userinfo: %.*q", utfnlen(s, e-s), s); - return -1; + if(b && *b){ + if(s == nil || *s == 0) + return estrdup(b); + if(*s != '/' && (x = strrchr(b, '/'))){ + a = emalloc((x - b) + strlen(s) + 4); + sprint(a, "/%.*s/%s", (int)(x - b), b, s); + return remdot(a); + } } - if(m[t->ind[0]].sp) - u->user = estredup(m[t->ind[0]].sp, m[t->ind[0]].ep); - if(m[t->ind[1]].sp) - u->user = estredup(m[t->ind[1]].sp, m[t->ind[1]].ep); - return 0; -} - -static int -parse_host(char *s, char *e, Url *u) -{ - Resub m[MaxResub]; - Retab *t; - - m[0].sp = s; - m[0].ep = e; - t = &retab[REhost]; - if(!regx(t->prog, nil, m, t->size)){ - werrstr("malformed host: %.*q", utfnlen(s, e-s), s); - return -1; + if(s && *s){ + if(*s != '/') + return estrdup(s); + a = emalloc(strlen(s) + 4); + sprint(a, "/%s", s); + return remdot(a); } - - assert(m[t->ind[0]].sp || m[t->ind[1]].sp); - - if(m[t->ind[0]].sp) /* regular */ - u->host = estredup(m[t->ind[0]].sp, m[t->ind[0]].ep); - else - u->host = estredup(m[t->ind[1]].sp, m[t->ind[1]].ep); - return 0; + return nil; } -static int -parse_authority(SplitUrl *su, Url *u) +static void +pstrdup(char **p) { - Resub m[MaxResub]; - Retab *t; - char *host; - char *userinfo; - - if(su->authority.s == nil) - return 0; - - u->authority = estredup(su->authority.s, su->authority.e); - m[0].sp = m[0].ep = nil; - t = &retab[REauthority]; - if(!regx(t->prog, u->authority, m, t->size)){ - werrstr("malformed authority: %q", u->authority); - return -1; - } - - if(m[t->ind[0]].sp) - if(parse_userinfo(m[t->ind[0]].sp, m[t->ind[0]].ep, u) < 0) - return -1; - if(m[t->ind[1]].sp) - if(parse_host(m[t->ind[1]].sp, m[t->ind[1]].ep, u) < 0) - return -1; - if(m[t->ind[2]].sp) - u->port = estredup(m[t->ind[2]].sp, m[t->ind[2]].ep); - - - if(urldebug > 0){ - userinfo = estredup(m[t->ind[0]].sp, m[t->ind[0]].ep); - host = estredup(m[t->ind[1]].sp, m[t->ind[1]].ep); - fprint(2, "port: %q, authority %q\n", u->port, u->authority); - fprint(2, "host %q, userinfo %q\n", host, userinfo); - free(host); - free(userinfo); + if(p == nil || *p == nil) + return; + if(**p == 0){ + *p = nil; + return; } - return 0; + *p = estrdup(*p); } -static int -parse_abspath(SplitUrl *su, Url *u) -{ - char *s; - - if(su->path.s == nil) - return 0; - s = estredup(su->path.s, su->path.e); - u->path = unescapeurl(s, "/"); - free(s); - return 0; -} - -static int -parse_query(SplitUrl *su, Url *u) -{ - char *s; - - if(su->query.s == nil) - return 0; - s = estredup(su->query.s, su->query.e); - u->query = unescapeurl(s, "&;=/"); - free(s); - return 0; -} - -static int -parse_fragment(SplitUrl *su, Url *u) -{ - char *s; - - if(su->fragment.s == nil) - return 0; - s = estredup(su->fragment.s, su->fragment.e); - u->fragment = unescapeurl(s, ""); - free(s); - return 0; -} - -static int -postparse_http(Url *u) +static char* +mklowcase(char *s) { - char *p, *q; - - u->open = httpopen; - u->read = httpread; - u->close = httpclose; + char *p; - if(u->authority==nil){ - werrstr("missing authority (hostname, port, etc.)"); - return -1; - } - if(u->host == nil){ - werrstr("missing host specification"); - return -1; - } - - if(u->path == nil){ - u->http.page_spec = estrdup("/"); - return 0; - } - p = escapeurl(u->path, "/"); - if(u->query){ - q = escapeurl(u->query, "&;=/"); - u->http.page_spec = emalloc(strlen(p)+1+strlen(q)+1); - strcpy(u->http.page_spec, p); - strcat(u->http.page_spec, "?"); - strcat(u->http.page_spec, q); - free(q); - free(p); - }else - u->http.page_spec = p; - return 0; + if(s == nil) + return s; + for(p = s; *p; p++) + *p = tolower(*p); + return s; } -static int -postparse_ftp(Url *u) +Url* +url(char *s, Url *b) { - Resub m[MaxResub]; - Retab *t; + char *t, *p, *x, *y; + Url *u; - if(u->authority==nil){ - werrstr("missing authority (hostname, port, etc.)"); - return -1; - } - if(u->query){ - werrstr("unexpected \"?query\" in ftp path"); - return -1; + if(s == nil) + s = ""; + t = nil; + s = p = estrdup(s); + u = emalloc(sizeof(*u)); + for(; *p; p++){ + if(*p == ':'){ + if(p == s) + break; + *p++ = 0; + u->scheme = s; + b = nil; + goto Abs; + } + if(!isalpha(*p)) + if((p == s) || ((!isdigit(*p) && strchr("+-.", *p) == nil))) + break; + } + p = s; + if(b){ + switch(*p){ + case 0: + memmove(u, b, sizeof(*u)); + goto Out; + case '#': + memmove(u, b, sizeof(*u)); + u->fragment = p+1; + goto Out; + case '?': + memmove(u, b, sizeof(*u)); + u->fragment = u->query = nil; + break; + case '/': + if(p[1] == '/'){ + u->scheme = b->scheme; + b = nil; + break; + } + default: + memmove(u, b, sizeof(*u)); + u->fragment = u->query = u->path = nil; + break; + } } - if(u->host == nil){ - werrstr("missing host specification"); - return -1; +Abs: + if(x = strchr(p, '#')){ + *x = 0; + u->fragment = x+1; } - - if(u->path == nil){ - u->ftp.path_spec = estrdup("/"); - return 0; + if(x = strchr(p, '?')){ + *x = 0; + u->query = x+1; } - - m[0].sp = m[0].ep = nil; - t = &retab[REftppath]; - if(!regx(t->prog, u->path, m, t->size)){ - werrstr("malformed ftp path: %q", u->path); - return -1; - } - - if(m[t->ind[0]].sp){ - u->ftp.path_spec = estredup(m[t->ind[0]].sp, m[t->ind[0]].ep); - if(strchr(u->ftp.path_spec, ';')){ - werrstr("unexpected \";param\" in ftp path"); - return -1; + if(p[0] == '/' && p[1] == '/'){ + p += 2; + if(x = strchr(p, '/')){ + u->path = t = abspath(x, Upath(b)); + *x = 0; } - }else - u->ftp.path_spec = estrdup("/"); - - if(m[t->ind[1]].sp){ - u->ftp.type = estredup(m[t->ind[1]].sp, m[t->ind[1]].ep); - strlower(u->ftp.type); - } - return 0; -} + if(x = strchr(p, '@')){ + *x = 0; + if(y = strchr(p, ':')){ + *y = 0; + u->pass = y+1; + } + u->user = p; + p = x+1; + } + if((x = strrchr(p, ']')) == nil) + x = p; + if(x = strrchr(x, ':')){ + *x = 0; + u->port = x+1; + } + if(x = strchr(p, '[')){ + p = x+1; + if(y = strchr(p, ']')) + *y = 0; + } + u->host = p; + } else { + u->path = t = abspath(p, Upath(b)); + } +Out: + pstrdup(&u->scheme); + pstrdup(&u->user); + pstrdup(&u->pass); + pstrdup(&u->host); + pstrdup(&u->port); + pstrdup(&u->path); + pstrdup(&u->query); + pstrdup(&u->fragment); + free(s); + free(t); -static int -postparse_file(Url *u) -{ - if(u->user || u->passwd){ - werrstr("user information not valid with file scheme"); - return -1; - } - if(u->query){ - werrstr("unexpected \"?query\" in file path"); - return -1; - } - if(u->port){ - werrstr("port not valid with file scheme"); - return -1; - } - if(u->path == nil){ - werrstr("missing path in file scheme"); - return -1; - } - if(strchr(u->path, ';')){ - werrstr("unexpected \";param\" in file path"); - return -1; - } + unescape(u->user, ""); + unescape(u->pass, ""); + unescape(u->path, "/"); + unescape(u->query, "&;=/?#"); + unescape(u->fragment, ""); + mklowcase(u->scheme); + mklowcase(u->host); + mklowcase(u->port); - /* "localhost" is equivalent to no host spec, we'll chose the latter */ - if(u->host && cistrcmp(u->host, "localhost") == 0){ - free(u->host); - u->host = nil; - } - return 0; + return u; } -static int (*postparse[])(Url*) = { - nil, - postparse_http, - postparse_http, - postparse_ftp, - postparse_file, -}; - Url* -parseurl(char *url, Url *base) +saneurl(Url *u) { - Url *u; - SplitUrl su; - - if(urldebug) - fprint(2, "parseurl %s with base %s\n", url, base ? base->url : "<none>"); - - u = emalloc(sizeof(Url)); - u->url = estrdup(url); - if(spliturl(u->url, &su) < 0){ - Fail: + if(u == nil || u->scheme == nil || u->host == nil || Upath(u) == nil){ freeurl(u); return nil; } - - /* RFC2396 sec 3.1 says relative URIs are distinguished by absent scheme */ - if(su.scheme.s==nil){ - if(urldebug) - fprint(2, "parseurl has nil scheme\n"); - if(resolve_relative(&su, base, u) < 0 || spliturl(u->url, &su) < 0) - goto Fail; - if(u->ischeme == UScurrent){ - /* 'u.url' refers to current document; set fragment and return */ - if(parse_fragment(&su, u) < 0) - goto Fail; - goto Done; + if(u->port){ + /* remove default ports */ + switch(atoi(u->port)){ + case 21: if(!strcmp(u->scheme, "ftp")) goto Defport; break; + case 70: if(!strcmp(u->scheme, "gopher"))goto Defport; break; + case 80: if(!strcmp(u->scheme, "http")) goto Defport; break; + case 443: if(!strcmp(u->scheme, "https")) goto Defport; break; + default: if(!strcmp(u->scheme, u->port)) goto Defport; break; + Defport: + free(u->port); + u->port = nil; } } + return u; +} - if(parse_scheme(&su, u) < 0 - || parse_fragment(&su, u) < 0) - goto Fail; +int +matchurl(Url *u, Url *s) +{ + if(u){ + char *a, *b; - if(u->ischeme == USunknown){ - if(parse_unknown_part(&su, u) < 0) - goto Fail; - goto Done; + if(s == nil) + return 0; + if(u->scheme && (s->scheme == nil || strcmp(u->scheme, s->scheme))) + return 0; + if(u->user && (s->user == nil || strcmp(u->user, s->user))) + return 0; + if(u->host && (s->host == nil || strcmp(u->host, s->host))) + return 0; + if(u->port && (s->port == nil || strcmp(u->port, s->port))) + return 0; + if(a = Upath(u)){ + b = Upath(s); + if(b == nil || strncmp(a, b, strlen(a))) + return 0; + } } - - if(parse_query(&su, u) < 0 - || parse_authority(&su, u) < 0 - || parse_abspath(&su, u) < 0) - goto Fail; - - if(u->ischeme < nelem(postparse) && postparse[u->ischeme]) - if((*postparse[u->ischeme])(u) < 0) - goto Fail; - -Done: - setmalloctag(u, getcallerpc(&url)); - rewriteurl(u); - return u; + return 1; } void @@ -873,162 +362,13 @@ freeurl(Url *u) { if(u == nil) return; - free(u->url); free(u->scheme); - free(u->schemedata); - free(u->authority); free(u->user); - free(u->passwd); + free(u->pass); free(u->host); free(u->port); free(u->path); free(u->query); free(u->fragment); - switch(u->ischeme){ - case UShttp: - case UShttps: - free(u->http.page_spec); - break; - case USftp: - free(u->ftp.path_spec); - free(u->ftp.type); - break; - } free(u); } - -void -rewriteurl(Url *u) -{ - char *s; - - if(u->scheme == nil) - return; - if(u->schemedata) - s = estrmanydup(u->scheme, ":", u->schemedata, nil); - else - s = estrmanydup(u->scheme, "://", - u->user ? u->user : "", - u->passwd ? ":" : "", u->passwd ? u->passwd : "", - u->user ? "@" : "", u->host ? u->host : "", - u->port ? ":" : "", u->port ? u->port : "", - u->path ? u->path : "", - u->query ? "?" : "", u->query ? u->query : "", - u->fragment ? "#" : "", u->fragment ? u->fragment : "", - nil); - free(u->url); - u->url = s; -} - -int -seturlquery(Url *u, char *query) -{ - if(query == nil){ - free(u->query); - u->query = nil; - return 0; - } - free(u->query); - u->query = unescapeurl(query, "&;=/"); - return 0; -} - -static void -dupp(char **p) -{ - if(*p) - *p = estrdup(*p); -} - -Url* -copyurl(Url *u) -{ - Url *v; - - v = emalloc(sizeof(Url)); - *v = *u; - dupp(&v->url); - dupp(&v->scheme); - dupp(&v->schemedata); - dupp(&v->authority); - dupp(&v->user); - dupp(&v->passwd); - dupp(&v->host); - dupp(&v->port); - dupp(&v->path); - dupp(&v->query); - dupp(&v->fragment); - - switch(v->ischeme){ - case UShttp: - case UShttps: - dupp(&v->http.page_spec); - break; - case USftp: - dupp(&v->ftp.path_spec); - dupp(&v->ftp.type); - break; - } - return v; -} - -static int -dhex(char c) -{ - if('0' <= c && c <= '9') - return c-'0'; - if('a' <= c && c <= 'f') - return c-'a'+10; - if('A' <= c && c <= 'F') - return c-'A'+10; - return 0; -} - -char* -escapeurl(char *s, char *special) -{ - static char *hex = "0123456789abcdef"; - char *t, *u; - - t = u = emalloc(strlen(s)*3+1); - for(; *s; s++){ - if((s[0] == '%' && isxdigit(s[1]) && isxdigit(s[2])) || - (*s >= '0' && *s <= '9') || - (*s >= 'a' && *s <= 'z') || - (*s >= 'A' && *s <= 'Z') || - strchr(".-_~", *s) || strchr(special, *s)) - *u++ = *s; - else if(s[0] == ' ') - *u++ = '+'; - else { - *u++ = '%'; - *u++ = hex[(*s>>4)&0xF]; - *u++ = hex[*s&0xF]; - } - } - *u = '\0'; - return t; -} - -char* -unescapeurl(char *s, char *special) -{ - char *r, *w; - Rune x; - - s = estrdup(s); - for(r=w=s; x = *r; r++){ - if(x=='%' && isxdigit(r[1]) && isxdigit(r[2])){ - x = (dhex(r[1])<<4)|dhex(r[2]); - if(x == 0 || (x > 0x1F && x < 0x7F && strchr(special, x))) - x = *r; - else - r += 2; - } else if(x=='+') - x = ' '; - *w++ = x; - } - *w = '\0'; - return s; -} - diff --git a/sys/src/cmd/webfs/util.c b/sys/src/cmd/webfs/util.c deleted file mode 100644 index b6788194e..000000000 --- a/sys/src/cmd/webfs/util.c +++ /dev/null @@ -1,86 +0,0 @@ -#include <u.h> -#include <libc.h> -#include <bio.h> -#include <ndb.h> -#include <fcall.h> -#include <thread.h> -#include <9p.h> -#include <ctype.h> -#include "dat.h" -#include "fns.h" - -void* -erealloc(void *a, uint n) -{ - a = realloc(a, n); - if(a == nil) - sysfatal("realloc %d: out of memory", n); - setrealloctag(a, getcallerpc(&a)); - return a; -} - -void* -emalloc(uint n) -{ - void *a; - - a = mallocz(n, 1); - if(a == nil) - sysfatal("malloc %d: out of memory", n); - setmalloctag(a, getcallerpc(&n)); - return a; -} - -char* -estrdup(char *s) -{ - s = strdup(s); - if(s == nil) - sysfatal("strdup: out of memory"); - setmalloctag(s, getcallerpc(&s)); - return s; -} - -char* -estredup(char *s, char *e) -{ - char *t; - - t = emalloc(e-s+1); - memmove(t, s, e-s); - t[e-s] = '\0'; - setmalloctag(t, getcallerpc(&s)); - return t; -} - -char* -estrmanydup(char *s, ...) -{ - char *p, *t; - int len; - va_list arg; - - len = strlen(s); - va_start(arg, s); - while((p = va_arg(arg, char*)) != nil) - len += strlen(p); - len++; - - t = emalloc(len); - strcpy(t, s); - va_start(arg, s); - while((p = va_arg(arg, char*)) != nil) - strcat(t, p); - return t; -} - -char* -strlower(char *s) -{ - char *t; - - for(t=s; *t; t++) - if('A' <= *t && *t <= 'Z') - *t += 'a'-'A'; - return s; -} diff --git a/sys/src/cmd/webfs/webget.c b/sys/src/cmd/webfs/webget.c deleted file mode 100644 index d3a13afa1..000000000 --- a/sys/src/cmd/webfs/webget.c +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Sample client. - */ -#include <u.h> -#include <libc.h> - -void -xfer(int from, int to) -{ - char buf[12*1024]; - int n; - - while((n = read(from, buf, sizeof buf)) > 0) - if(write(to, buf, n) < 0) - sysfatal("write failed: %r"); - if(n < 0) - sysfatal("read failed: %r"); -} - -void -usage(void) -{ - fprint(2, "usage: webget [-b baseurl] [-m mtpt] [-p postbody] url\n"); - exits("usage"); -} - -void -main(int argc, char **argv) -{ - int conn, ctlfd, fd, n; - char buf[128], *base, *mtpt, *post, *url; - - mtpt = "/mnt/web"; - post = nil; - base = nil; - ARGBEGIN{ - default: - usage(); - case 'b': - base = EARGF(usage()); - break; - case 'm': - mtpt = EARGF(usage()); - break; - case 'p': - post = EARGF(usage()); - break; - }ARGEND; - - if (argc != 1) - usage(); - - url = argv[0]; - - snprint(buf, sizeof buf, "%s/clone", mtpt); - if((ctlfd = open(buf, ORDWR)) < 0) - sysfatal("couldn't open %s: %r", buf); - if((n = read(ctlfd, buf, sizeof buf-1)) < 0) - sysfatal("reading clone: %r"); - if(n == 0) - sysfatal("short read on clone"); - buf[n] = '\0'; - conn = atoi(buf); - - if(base) - if(fprint(ctlfd, "baseurl %s", base) < 0) - sysfatal("baseurl ctl write: %r"); - - if(fprint(ctlfd, "url %s", url) <= 0) - sysfatal("get ctl write: %r"); - - if(post){ - snprint(buf, sizeof buf, "%s/%d/postbody", mtpt, conn); - if((fd = open(buf, OWRITE)) < 0) - sysfatal("open %s: %r", buf); - if(write(fd, post, strlen(post)) < 0) - sysfatal("post write failed: %r"); - close(fd); - } - - snprint(buf, sizeof buf, "%s/%d/body", mtpt, conn); - if((fd = open(buf, OREAD)) < 0) - sysfatal("open %s: %r", buf); - - xfer(fd, 1); - exits(nil); -} diff --git a/sys/src/cmd/webfsget.c b/sys/src/cmd/webfsget.c deleted file mode 100644 index 906d7d1cb..000000000 --- a/sys/src/cmd/webfsget.c +++ /dev/null @@ -1,85 +0,0 @@ -/* Example of how to use webfs */ -#include <u.h> -#include <libc.h> - -void -xfer(int from, int to) -{ - char buf[12*1024]; - int n; - - while((n = read(from, buf, sizeof buf)) > 0) - if(write(to, buf, n) < 0) - sysfatal("write failed: %r"); - if(n < 0) - sysfatal("read failed: %r"); -} - -void -usage(void) -{ - fprint(2, "usage: webfsget [-b baseurl] [-m mtpt] [-p postbody] url\n"); - exits("usage"); -} - -void -main(int argc, char **argv) -{ - int conn, ctlfd, fd, n; - char buf[128], *base, *mtpt, *post, *url; - - mtpt = "/mnt/web"; - post = nil; - base = nil; - ARGBEGIN{ - default: - usage(); - case 'b': - base = EARGF(usage()); - break; - case 'm': - mtpt = EARGF(usage()); - break; - case 'p': - post = EARGF(usage()); - break; - }ARGEND; - - if (argc != 1) - usage(); - - url = argv[0]; - - snprint(buf, sizeof buf, "%s/clone", mtpt); - if((ctlfd = open(buf, ORDWR)) < 0) - sysfatal("couldn't open %s: %r", buf); - if((n = read(ctlfd, buf, sizeof buf-1)) < 0) - sysfatal("reading clone: %r"); - if(n == 0) - sysfatal("short read on clone"); - buf[n] = '\0'; - conn = atoi(buf); - - if(base) - if(fprint(ctlfd, "baseurl %s", base) < 0) - sysfatal("baseurl ctl write: %r"); - - if(fprint(ctlfd, "url %s", url) <= 0) - sysfatal("get ctl write: %r"); - - if(post){ - snprint(buf, sizeof buf, "%s/%d/postbody", mtpt, conn); - if((fd = open(buf, OWRITE)) < 0) - sysfatal("open %s: %r", buf); - if(write(fd, post, strlen(post)) < 0) - sysfatal("post write failed: %r"); - close(fd); - } - - snprint(buf, sizeof buf, "%s/%d/body", mtpt, conn); - if((fd = open(buf, OREAD)) < 0) - sysfatal("open %s: %r", buf); - - xfer(fd, 1); - exits(nil); -} |