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