]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/webfs/fs.c
webfs: add global timeout parameter settable in rootctl and command line
[plan9front.git] / sys / src / cmd / webfs / fs.c
1 #include <u.h>
2 #include <libc.h>
3 #include <ctype.h>
4 #include <fcall.h>
5 #include <thread.h>
6 #include <9p.h>
7
8 #include "dat.h"
9 #include "fns.h"
10
11 typedef struct Webfid Webfid;
12 typedef struct Client Client;
13
14 struct Client
15 {
16         Ref;
17
18         char    request[16];
19         Url     *baseurl;
20         Url     *url;
21         Key     *hdr;
22
23         int     obody;  /* body opend */
24         int     cbody;  /* body closed */
25         Buq     *qbody;
26 };
27
28 struct Webfid
29 {
30         int     level;
31
32         Client  *client;
33         Key     *key;   /* copy for Qheader */
34         Buq     *buq;   /* reference for Qbody, Qpost */
35 };
36
37 enum {
38         Qroot,
39                 Qrctl,
40                 Qclone,
41                 Qclient,
42                         Qctl,
43                         Qbody,
44                         Qpost,
45                         Qparsed,
46                                 Qurl,
47                                 Qurlschm,
48                                 Qurluser,
49                                 Qurlpass,
50                                 Qurlhost,
51                                 Qurlport,
52                                 Qurlpath,
53                                 Qurlqwry,
54                                 Qurlfrag,
55                         Qheader,
56 };
57
58 static char *nametab[] = {
59         "/",
60                 "ctl",
61                 "clone",
62                 nil,
63                         "ctl",
64                         "body",
65                         "postbody",
66                         "parsed",
67                                 "url",
68                                 "scheme",
69                                 "user",
70                                 "passwd",
71                                 "host",
72                                 "port",
73                                 "path",
74                                 "query",
75                                 "fragment",
76                         nil,
77 };
78
79 static long time0;
80 static char *user;
81 static char *agent;
82 static Client client[64];
83 static int nclient;
84
85 #define CLIENTID(c)     (((Client*)(c)) - client)
86
87 Client*
88 newclient(void)
89 {
90         Client *cl;
91         int i;
92
93         for(i = 0; i < nclient; i++)
94                 if(client[i].ref == 0)
95                         break;
96         if(i >= nelem(client))
97                 return nil;
98         if(i == nclient)
99                 nclient++;
100         cl = &client[i];
101         incref(cl);
102
103         cl->request[0] = 0;
104         cl->baseurl = nil;
105         cl->url = nil;
106         cl->hdr = nil;
107         cl->qbody = nil;
108         
109         return cl;
110 }
111
112 void
113 freeclient(Client *cl)
114 {
115         Key *k;
116
117         if(cl == nil || decref(cl))
118                 return;
119
120         buclose(cl->qbody, 0);
121         bufree(cl->qbody);
122
123         while(k = cl->hdr){
124                 cl->hdr = k->next;
125                 free(k);
126         }
127
128         freeurl(cl->url);
129         freeurl(cl->baseurl);
130
131         memset(cl, 0, sizeof(*cl));
132 }
133
134 static Url*
135 clienturl(Client *cl)
136 {
137         static Url nullurl;
138
139         if(cl->qbody && cl->qbody->url)
140                 return cl->qbody->url;
141         if(cl->url)
142                 return cl->url;
143         return &nullurl;
144 }
145
146 static void*
147 wfaux(Webfid *f)
148 {
149         if(f->level < Qclient)
150                 return nil;
151         else if(f->level < Qurl)
152                 return f->client;
153         else if(f->level < Qheader)
154                 return clienturl(f->client);
155         else
156                 return f->key;
157 }
158
159 static void
160 fsmkqid(Qid *q, int level, void *aux)
161 {
162         q->type = 0;
163         q->vers = 0;
164         switch(level){
165         case Qroot:
166         case Qparsed:
167         case Qclient:
168                 q->type = QTDIR;
169         default:
170                 q->path = (level<<24) | (((ulong)aux ^ time0) & 0x00ffffff);
171         }
172 }
173
174 static char*
175 fshdrname(char *s)
176 {
177         char *k, *w;
178
179         for(k=w=s; *k; k++)
180                 if(isalnum(*k))
181                         *w++ = tolower(*k);
182         *w = 0;
183         return s;
184 }
185
186 static int
187 urlstr(char *buf, int nbuf, Url *u, int level)
188 {
189         char *s;
190
191         if(level == Qurl)
192                 return snprint(buf, nbuf, "%U", u);
193         if(level == Qurlpath)
194                 return snprint(buf, nbuf, "%s", Upath(u));
195         if((s = (&u->scheme)[level - Qurlschm]) == nil){
196                 buf[0] = 0;
197                 return 0;
198         }
199         return snprint(buf, nbuf, "%s", s);
200 }
201
202
203 static void
204 fsmkdir(Dir *d, int level, void *aux)
205 {
206         char buf[1024];
207
208         memset(d, 0, sizeof(*d));
209         fsmkqid(&d->qid, level, aux);
210         d->mode = 0444;
211         d->atime = d->mtime = time0;
212         d->uid = estrdup(user);
213         d->gid = estrdup(user);
214         d->muid = estrdup(user);
215         if(d->qid.type & QTDIR)
216                 d->mode |= DMDIR | 0111;
217         switch(level){
218         case Qheader:
219                 d->name = fshdrname(estrdup(((Key*)aux)->key));
220                 d->length = strlen(((Key*)aux)->val);
221                 break;
222         case Qclient:
223                 snprint(buf, sizeof(buf), "%ld", CLIENTID(aux));
224                 d->name = estrdup(buf);
225                 break;
226         case Qctl:
227         case Qrctl:
228         case Qclone:
229                 d->mode = 0666;
230                 if(0){
231         case Qpost:
232                 d->mode = 0222;
233                 }
234         default:
235                 d->name = estrdup(nametab[level]);
236                 if(level >= Qurl && level <= Qurlfrag)
237                         d->length = urlstr(buf, sizeof(buf), (Url*)aux, level);
238         }
239 }
240
241 static void
242 fsattach(Req *r)
243 {
244         Webfid *f;
245
246         if(r->ifcall.aname && r->ifcall.aname[0]){
247                 respond(r, "invalid attach specifier");
248                 return;
249         }
250         f = emalloc(sizeof(*f));
251         f->level = Qroot;
252         fsmkqid(&r->fid->qid, f->level, wfaux(f));
253         r->ofcall.qid = r->fid->qid;
254         r->fid->aux = f;
255         respond(r, nil);
256 }
257
258 static void
259 fsstat(Req *r)
260 {
261         Webfid *f;
262
263         f = r->fid->aux;
264         fsmkdir(&r->d, f->level, wfaux(f));
265         respond(r, nil);
266 }
267
268 static char*
269 fswalk1(Fid *fid, char *name, Qid *qid)
270 {
271         Webfid *f;
272         int i, j;
273
274         if(!(fid->qid.type&QTDIR))
275                 return "walk in non-directory";
276
277         f = fid->aux;
278         if(strcmp(name, "..") == 0){
279                 switch(f->level){
280                 case Qroot:
281                         break;
282                 case Qclient:
283                         freeclient(f->client);
284                         f->client = nil;
285                         break;
286                 default:
287                         if(f->level > Qparsed)
288                                 f->level = Qparsed;
289                         else
290                                 f->level = Qclient;
291                 }
292         } else {
293                 for(i=f->level+1; i < nelem(nametab); i++){
294                         if(nametab[i]){
295                                 if(strcmp(name, nametab[i]) == 0)
296                                         break;
297                                 if(i == Qbody && strncmp(name, "body.", 5) == 0)
298                                         break;
299                         }
300                         if(i == Qclient){
301                                 j = atoi(name);
302                                 if(j >= 0 && j < nclient){
303                                         f->client = &client[j];
304                                         incref(f->client);
305                                         break;
306                                 }
307                         }
308                         if(i == Qheader && f->client && f->client->qbody){
309                                 char buf[128];
310                                 Key *k;
311
312                                 for(k = f->client->qbody->hdr; k; k = k->next){
313                                         strncpy(buf, k->key, sizeof(buf));
314                                         if(!strcmp(name, fshdrname(buf)))
315                                                 break;
316                                 }
317                                 if(k != nil){
318                                         /* need to copy as key is owned by qbody wich might go away */
319                                         f->key = addkey(0, k->key, k->val);
320                                         break;
321                                 }
322                         }
323                 }
324                 if(i >= nelem(nametab))
325                         return "directory entry not found";
326                 f->level = i;
327         }
328         fsmkqid(qid, f->level, wfaux(f));
329         fid->qid = *qid;
330         return nil;
331 }
332
333 static char*
334 fsclone(Fid *oldfid, Fid *newfid)
335 {
336         Webfid *f, *o;
337
338         o = oldfid->aux;
339         if(o == nil || o->key || o->buq)
340                 return "bad fid";
341         f = emalloc(sizeof(*f));
342         memmove(f, o, sizeof(*f));
343         if(f->client)
344                 incref(f->client);
345         newfid->aux = f;
346         return nil;
347 }
348
349 static void
350 fsopen(Req *r)
351 {
352         Webfid *f;
353         Client *cl;
354
355         f = r->fid->aux;
356         cl = f->client;
357         switch(f->level){
358         case Qclone:
359                 if((cl = newclient()) == nil){
360                         respond(r, "no more clients");
361                         return;
362                 }
363                 f->level = Qctl;
364                 f->client = cl;
365                 fsmkqid(&r->fid->qid, f->level, wfaux(f));
366                 r->ofcall.qid = r->fid->qid;
367                 break;
368         case Qpost:
369                 if(cl->qbody && !cl->cbody){
370                 Inuse:
371                         respond(r, "client in use");
372                         return;
373                 }
374         case Qbody:
375                 if(cl->obody)
376                         goto Inuse;
377                 if(cl->cbody){
378                         bufree(cl->qbody);
379                         cl->qbody = nil;
380                         cl->cbody = 0;
381                 }
382                 if(cl->qbody == nil){
383                         char *m;
384
385                         if(cl->url == nil){
386                                 respond(r, "no url set");
387                                 return;
388                         }
389                         cl->qbody = bualloc(16*1024);
390                         if(f->level != Qbody){
391                                 f->buq = bualloc(64*1024);
392                                 if(!lookkey(cl->hdr, "Content-Type"))
393                                         cl->hdr = addkey(cl->hdr, "Content-Type", 
394                                                 "application/x-www-form-urlencoded");
395                                 m = "POST";
396                         } else
397                                 m = "GET";
398                         if(cl->request[0])
399                                 m = cl->request;
400
401                         if(!lookkey(cl->hdr, "Referer")){
402                                 char *r;
403                                 Url *u;
404
405                                 /*
406                                  * Referer header is often required on broken
407                                  * websites even if the spec makes them optional,
408                                  * so we make one up.
409                                  */
410                                 if(u = url("/", cl->url)){
411                                         if(r = smprint("%U", u)){
412                                                 cl->hdr = addkey(cl->hdr, "Referer", r);
413                                                 free(r);
414                                         }
415                                         freeurl(u);
416                                 }
417                         }
418
419                         if(!lookkey(cl->hdr, "Connection"))
420                                 cl->hdr = addkey(cl->hdr, "Connection", "keep-alive");
421
422                         if(agent && !lookkey(cl->hdr, "User-Agent"))
423                                 cl->hdr = addkey(cl->hdr, "User-Agent", agent);
424
425                         http(m, cl->url, cl->hdr, cl->qbody, f->buq);
426                         cl->request[0] = 0;
427                         cl->url = nil;
428                         cl->hdr = nil;
429                 }
430                 if(f->buq)
431                         break;
432                 cl->obody = 1;
433                 incref(cl->qbody);
434                 bureq(f->buq = cl->qbody, r);
435                 return;
436         }
437         respond(r, nil);
438 }
439
440 static int
441 rootgen(int i, Dir *d, void *)
442 {
443         i += Qroot+1;
444         if(i < Qclient){
445                 fsmkdir(d, i, 0);
446                 return 0;
447         }
448         i -= Qclient;
449         if(i < nclient){
450                 fsmkdir(d, Qclient, &client[i]);
451                 return 0;
452         }
453         return -1;
454 }
455
456 static int
457 clientgen(int i, Dir *d, void *aux)
458 {
459         i += Qclient+1;
460         if(i > Qparsed){
461                 Client *cl = aux;
462                 Key *k;
463
464                 i -= Qparsed+1;
465                 if(cl == nil || cl->qbody == nil)
466                         return -1;
467                 for(k = cl->qbody->hdr; i > 0 && k; i--, k = k->next)
468                         ;
469                 if(k == nil || i > 0)
470                         return -1;
471                 i = Qheader;
472                 aux = k;
473         }
474         fsmkdir(d, i, aux);
475         return 0;
476 }
477
478 static int
479 parsedgen(int i, Dir *d, void *aux)
480 {
481         i += Qparsed+1;
482         if(i > Qurlfrag)
483                 return -1;
484         fsmkdir(d, i, aux);
485         return 0;
486 }
487
488 static void
489 fsread(Req *r)
490 {
491         char buf[1024];
492         Webfid *f;
493
494         f = r->fid->aux;
495         switch(f->level){
496         case Qroot:
497                 dirread9p(r, rootgen, nil);
498                 respond(r, nil);
499                 return;
500         case Qclient:
501                 dirread9p(r, clientgen, f->client);
502                 respond(r, nil);
503                 return;
504         case Qparsed:
505                 dirread9p(r, parsedgen, clienturl(f->client));
506                 respond(r, nil);
507                 return;
508         case Qrctl:
509                 snprint(buf, sizeof(buf), "useragent %s\ntimeout %d\n", agent, timeout);
510         String:
511                 readstr(r, buf);
512                 respond(r, nil);
513                 return;
514         case Qctl:
515                 snprint(buf, sizeof(buf), "%ld\n", CLIENTID(f->client));
516                 goto String;
517         case Qheader:
518                 snprint(buf, sizeof(buf), "%s", f->key->val);
519                 goto String;
520         case Qurl:
521         case Qurlschm:
522         case Qurluser:
523         case Qurlpass:
524         case Qurlhost:
525         case Qurlport:
526         case Qurlpath:
527         case Qurlqwry:
528         case Qurlfrag:
529                 urlstr(buf, sizeof(buf), clienturl(f->client), f->level);
530                 goto String;
531         case Qbody:
532                 bureq(f->buq, r);
533                 return;
534         }
535         respond(r, "not implemented");
536 }
537
538 static char*
539 rootctl(char *ctl, char *arg)
540 {
541         Url *u;
542
543         if(debug)
544                 fprint(2, "rootctl: %q %q\n", ctl, arg);
545
546         if(!strcmp(ctl, "useragent")){
547                 free(agent);
548                 if(arg && *arg)
549                         agent = estrdup(arg);
550                 else
551                         agent = nil;
552                 return nil;
553         }
554
555         if(!strcmp(ctl, "flushauth")){
556                 u = nil;
557                 if(arg && *arg)
558                         u = saneurl(url(arg, 0));
559                 flushauth(u, 0);
560                 freeurl(u);
561                 return nil;
562         }
563
564         if(!strcmp(ctl, "timeout")){
565                 if(arg && *arg)
566                         timeout = atoi(arg);
567                 else
568                         timeout = 0;
569                 if(timeout < 0)
570                         timeout = 0;
571                 return nil;
572         }
573
574         return "bad ctl message";
575 }
576
577 static char*
578 clientctl(Client *cl, char *ctl, char *arg)
579 {
580         char *p;
581         Url *u;
582         Key *k;
583
584         if(debug)
585                 fprint(2, "clientctl: %q %q\n", ctl, arg);
586
587         if(!strcmp(ctl, "url")){
588                 if((u = saneurl(url(arg, cl->baseurl))) == nil)
589                         return "bad url";
590                 freeurl(cl->url);
591                 cl->url = u;
592         }
593         else if(!strcmp(ctl, "baseurl")){
594                 if((u = url(arg, 0)) == nil)
595                         return "bad baseurl";
596                 freeurl(cl->baseurl);
597                 cl->baseurl = u;
598         }
599         else if(!strcmp(ctl, "request")){
600                 p = cl->request;
601                 strncpy(p, arg, sizeof(cl->request));
602                 for(; *p && isalpha(*p); p++)
603                         *p = toupper(*p);
604                 *p = 0;
605         }
606         else if(!strcmp(ctl, "headers")){
607                 while(arg && *arg){
608                         ctl = arg;
609                         while(*ctl && strchr("\r\n\t ", *ctl))
610                                 ctl++;
611                         if(arg = strchr(ctl, '\n'))
612                                 *arg++ = 0;
613                         if(k = parsehdr(ctl)){
614                                 k->next = cl->hdr;
615                                 cl->hdr = k;
616                         }
617                 }
618         }
619         else {
620                 char buf[128], **t;
621                 static char *tab[] = {
622                         "User-Agent",
623                         "Content-Type",
624                         nil,
625                 };
626                 for(t = tab; *t; t++){
627                         strncpy(buf, *t, sizeof(buf));
628                         if(!strcmp(ctl, fshdrname(buf))){
629                                 cl->hdr = delkey(cl->hdr, *t);
630                                 if(arg && *arg)
631                                         cl->hdr = addkey(cl->hdr, *t, arg);
632                                 break;
633                         }
634                 }
635                 if(*t == nil)
636                         return "bad ctl message";
637         }
638         return nil;
639 }
640
641 static void
642 fswrite(Req *r)
643 {
644         int n;
645         Webfid *f;
646         char *s, *t;
647
648         f = r->fid->aux;
649         switch(f->level){
650         case Qrctl:
651         case Qctl:
652                 n = r->ofcall.count = r->ifcall.count;
653                 s = emalloc(n+1);
654                 memmove(s, r->ifcall.data, n);
655                 while(n > 0 && strchr("\r\n", s[n-1]))
656                         n--;
657                 s[n] = 0;
658                 t = s;
659                 while(*t && strchr("\r\n\t ", *t)==0)
660                         t++;
661                 while(*t && strchr("\r\n\t ", *t))
662                         *t++ = 0;
663                 if(f->level == Qctl)
664                         t = clientctl(f->client, s, t);
665                 else
666                         t = rootctl(s, t);
667                 free(s);
668                 respond(r, t);
669                 return;
670         case Qpost:
671                 bureq(f->buq, r);
672                 return;
673         }
674         respond(r, "not implemented");
675 }
676
677 static void
678 fsflush(Req *r)
679 {
680         Webfid *f;
681         Req *o;
682
683         if(o = r->oldreq)
684         if(f = o->fid->aux)
685                 buflushreq(f->buq, o);
686         respond(r, nil);
687 }
688
689 static void
690 fsdestroyfid(Fid *fid)
691 {
692         Webfid *f;
693
694         if(f = fid->aux){
695                 fid->aux = nil;
696                 if(f->buq){
697                         buclose(f->buq, 0);
698                         if(f->client->qbody == f->buq){
699                                 f->client->obody = 0;
700                                 f->client->cbody = 1;
701                         }
702                         bufree(f->buq);
703                 }
704                 if(f->key)
705                         free(f->key);
706                 freeclient(f->client);
707                 free(f);
708         }
709 }
710
711 Srv fs = 
712 {
713         .attach=fsattach,
714         .stat=fsstat,
715         .walk1=fswalk1,
716         .clone=fsclone,
717         .open=fsopen,
718         .read=fsread,
719         .write=fswrite,
720         .flush=fsflush,
721         .destroyfid=fsdestroyfid,
722 };
723
724 void
725 usage(void)
726 {
727         fprint(2, "usage: %s [-D] [-A useragent] [-T timeout] [-m mtpt] [-s srv]\n", argv0);
728         exits("usage");
729 }
730
731 void
732 main(int argc, char *argv[])
733 {
734         char *srv, *mtpt, *s;
735
736         quotefmtinstall();
737         fmtinstall('U', Ufmt);
738         fmtinstall('E', Efmt);
739
740         srv = nil;
741         mtpt = "/mnt/web";
742         user = getuser();
743         time0 = time(0);
744         timeout = 10000;
745         agent = nil;
746
747         ARGBEGIN {
748         case 'D':
749                 chatty9p++;
750                 break;
751         case 'A':
752                 agent = EARGF(usage());
753                 break;
754         case 'T':
755                 timeout = atoi(EARGF(usage()));
756                 if(timeout < 0)
757                         timeout = 0;
758                 break;
759         case 'm':
760                 mtpt = EARGF(usage());
761                 break;
762         case 's':
763                 srv = EARGF(usage());
764                 break;
765         case 'd':
766                 debug++;
767                 break;
768         default:
769                 usage();
770         } ARGEND;
771
772         rfork(RFNOTEG);
773
774         if(agent == nil)
775                 agent = "hjdicks";
776         agent = estrdup(agent);
777
778         if(s = getenv("httpproxy")){
779                 proxy = saneurl(url(s, 0));
780                 free(s);
781         }
782
783         postmountsrv(&fs, srv, mtpt, MREPL);
784 }