]> git.lizzy.rs Git - plan9front.git/blobdiff - sys/src/cmd/webfs/fs.c
git/branch: somewhere in the syncing, the fix for junk files was lost
[plan9front.git] / sys / src / cmd / webfs / fs.c
old mode 100755 (executable)
new mode 100644 (file)
index 087390d..e4e0867
-/*
- * 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 char *mtpt;
+static char *service;
+static long time0;
+static char *user;
+static char *agent;
+static Client client[64];
+static int nclient;
+
+#define        CLIENTID(c)     ((int)(((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) | (((uintptr)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;
+       char buf[1024];
 
-       path = r->fid->qid.path;
-       switch(TYPE(path)){
-       default:
-               snprint(e, sizeof e, "bug in webfs path=%lux\n", path);
-               respond(r, e);
-               break;
-
-       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), "%d", 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){
+                                       nstrcpy(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;
+       Webfid *f;
+       Client *cl;
 
-       /*
-        * 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;
-       }
-
-       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;
+
+                       /*
+                        * some sites give a 403 Forbidden if we dont include
+                        * a meaningless Accept header in the request.
+                        */
+                       if(!lookkey(cl->hdr, "Accept"))
+                               cl->hdr = addkey(cl->hdr, "Accept", "*/*");
+
+                       if(!lookkey(cl->hdr, "Connection"))
+                               cl->hdr = addkey(cl->hdr, "Connection", "keep-alive");
+
+                       if(agent && !lookkey(cl->hdr, "User-Agent"))
+                               cl->hdr = addkey(cl->hdr, "User-Agent", agent);
+
+                       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:
+               snprint(buf, sizeof(buf), "useragent %s\ntimeout %d\n", agent, timeout);
+       String:
+               readstr(r, buf);
+               respond(r, nil);
+               return;
+       case Qctl:
+               snprint(buf, sizeof(buf), "%d\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(Srv *fs, char *ctl, char *arg)
 {
-       int i, n;
-       ulong path;
-       char buf[32], *ext;
+       Url *u;
 
-       path = fid->qid.path;
-       if(!(fid->qid.type&QTDIR))
-               return "walk in non-directory";
+       if(debug)
+               fprint(2, "rootctl: %q %q\n", ctl, arg);
 
-       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";
-               }
+       if(!strcmp(ctl, "useragent")){
+               free(agent);
+               if(arg && *arg)
+                       agent = estrdup(arg);
+               else
+                       agent = nil;
+               return nil;
+       }
+
+       if(!strcmp(ctl, "flushauth")){
+               u = nil;
+               if(arg && *arg)
+                       u = saneurl(url(arg, 0));
+               flushauth(u, 0);
+               freeurl(u);
+               return nil;
+       }
+
+       if(!strcmp(ctl, "timeout")){
+               if(arg && *arg)
+                       timeout = atoi(arg);
+               else
+                       timeout = 0;
+               if(timeout < 0)
+                       timeout = 0;
+               return nil;
+       }
+
+       /* ppreemptive authentication only basic
+        * auth supported, ctl message of the form:
+        *    preauth url realm
+        */
+       if(!strcmp(ctl, "preauth")){
+               char *a[3], buf[256];
+               int rc;
+
+               if(tokenize(arg, a, nelem(a)) != 2)
+                       return "preauth - bad field count";
+               if((u = saneurl(url(a[0], 0))) == nil)
+                       return "preauth - malformed url";
+               snprint(buf, sizeof(buf), "BASIC realm=\"%s\"", a[1]);
+               srvrelease(fs);
+               rc = authenticate(u, u, "GET", buf);
+               srvacquire(fs);
+               freeurl(u);
+               if(rc == -1)
+                       return "preauth failed";
+               return nil;
        }
 
-       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;
+       return "bad ctl message";
+}
+
+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;
+               nstrcpy(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(whitespace, *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++){
+                       nstrcpy(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 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(whitespace, *t)==0)
+                       t++;
+               while(*t && strchr(whitespace, *t))
+                       *t++ = 0;
+               if(f->level == Qctl)
+                       t = clientctl(f->client, s, t);
+               else
+                       t = rootctl(r->srv, s, t);
+               free(s);
+               respond(r, t);
+               return;
+       case Qpost:
+               bureq(f->buq, r);
+               return;
        }
-       return "directory entry not found";
+       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);
+       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)
+fsstart(Srv*)
 {
-       sendp(creq, r);
-       recvp(creqwait);        /* avoids need to deal with spurious flushes */
+       /* drop reference to old webfs mount */
+       if(mtpt != nil)
+               unmount(nil, mtpt);
 }
 
-void
-initfs(void)
+static void
+fsend(Srv*)
 {
-       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);
+       postnote(PNGROUP, getpid(), "shutdown");
+       exits(nil);
 }
 
+Srv fs = 
+{
+       .start=fsstart,
+       .attach=fsattach,
+       .stat=fsstat,
+       .walk1=fswalk1,
+       .clone=fsclone,
+       .open=fsopen,
+       .read=fsread,
+       .write=fswrite,
+       .flush=fsflush,
+       .destroyfid=fsdestroyfid,
+       .end=fsend,
+};
+
 void
-takedown(Srv*)
+usage(void)
 {
-       closecookies();
-       threadexitsall("done");
+       fprint(2, "usage: %s [-Dd] [-A useragent] [-T timeout] [-m mtpt] [-s service]\n", argv0);
+       exits("usage");
 }
 
-Srv fs = 
+void
+main(int argc, char *argv[])
 {
-.attach=               fssend,
-.destroyfid=   fsdestroyfid,
-.walk1=                fswalk1,
-.open=         fssend,
-.read=         fssend,
-.write=                fssend,
-.stat=         fssend,
-.flush=                fssend,
-.end=          takedown,
-};
+       char *s;
+
+       quotefmtinstall();
+       fmtinstall('U', Ufmt);
+       fmtinstall('N', Nfmt);
+       fmtinstall(']', Mfmt);
+       fmtinstall('E', Efmt);
+       fmtinstall('[', encodefmt);
+       fmtinstall('H', encodefmt);
+
+       mtpt = "/mnt/web";
+       user = getuser();
+       time0 = time(0);
+       timeout = 10000;
+
+       ARGBEGIN {
+       case 'D':
+               chatty9p++;
+               break;
+       case 'A':
+               agent = EARGF(usage());
+               break;
+       case 'T':
+               timeout = atoi(EARGF(usage()));
+               if(timeout < 0)
+                       timeout = 0;
+               break;
+       case 'm':
+               mtpt = EARGF(usage());
+               break;
+       case 's':
+               service = EARGF(usage());
+               break;
+       case 'd':
+               debug++;
+               break;
+       default:
+               usage();
+       } ARGEND;
+
+       rfork(RFNOTEG);
 
+       if(agent == nil)
+               agent = "Mozilla/5.0 (compatible; hjdicks)";
+       agent = estrdup(agent);
+
+       if(s = getenv("httpproxy")){
+               proxy = saneurl(url(s, 0));
+               if(proxy == nil || strcmp(proxy->scheme, "http") && strcmp(proxy->scheme, "https"))
+                       sysfatal("invalid httpproxy url: %s", s);
+               free(s);
+       }
+
+       postmountsrv(&fs, service, mtpt, MREPL);
+       exits(nil);
+}