summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcinap_lenrek <cinap_lenrek@rei2.9hal>2012-01-11 16:17:54 +0100
committercinap_lenrek <cinap_lenrek@rei2.9hal>2012-01-11 16:17:54 +0100
commit75e1ef0ab60acb6bccc54254b82770aec5786ead (patch)
treed273fc755a20e67801aa0a13df30ab75b2883419
parent62fb4f97177d8e76f1fd49bb9d0073007b7c9bcc (diff)
downloadplan9front-75e1ef0ab60acb6bccc54254b82770aec5786ead.tar.xz
new webfs, rc based hget
-rwxr-xr-xrc/bin/hget69
-rw-r--r--sys/lib/dist/usr/glenda/lib/profile10
-rwxr-xr-xsys/lib/newuser5
-rw-r--r--sys/man/1/hget64
-rw-r--r--sys/man/4/webcookies14
-rw-r--r--sys/man/4/webfs316
-rw-r--r--sys/src/cmd/hget.c1480
-rw-r--r--sys/src/cmd/webfs/buf.c89
-rw-r--r--sys/src/cmd/webfs/buq.c263
-rw-r--r--sys/src/cmd/webfs/client.c411
-rw-r--r--sys/src/cmd/webfs/cookies.c1179
-rw-r--r--sys/src/cmd/webfs/dat.h139
-rw-r--r--sys/src/cmd/webfs/fns.h97
-rw-r--r--sys/src/cmd/webfs/fs.c1103
-rw-r--r--sys/src/cmd/webfs/http.c1223
-rw-r--r--sys/src/cmd/webfs/io.c86
-rw-r--r--sys/src/cmd/webfs/main.c67
-rw-r--r--sys/src/cmd/webfs/mkfile31
-rw-r--r--sys/src/cmd/webfs/plumb.c165
-rw-r--r--sys/src/cmd/webfs/sub.c119
-rw-r--r--sys/src/cmd/webfs/url.c1236
-rw-r--r--sys/src/cmd/webfs/util.c86
-rw-r--r--sys/src/cmd/webfs/webget.c87
-rw-r--r--sys/src/cmd/webfsget.c85
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);
-}