]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/webfs/fs.c
webfs: send Accept headers
[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                                         nstrcpy(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                         /*
402                          * some sites give a 403 Forbidden if we dont include
403                          * a meaningless Accept: */* header in the request.
404                          */
405                         if(!lookkey(cl->hdr, "Accept"))
406                                 cl->hdr = addkey(cl->hdr, "Accept", "*/*");
407
408                         if(!lookkey(cl->hdr, "Referer")){
409                                 char *r;
410                                 Url *u;
411
412                                 /*
413                                  * Referer header is often required on broken
414                                  * websites even if the spec makes them optional,
415                                  * so we make one up.
416                                  */
417                                 if(u = url("/", cl->url)){
418                                         if(r = smprint("%U", u)){
419                                                 cl->hdr = addkey(cl->hdr, "Referer", r);
420                                                 free(r);
421                                         }
422                                         freeurl(u);
423                                 }
424                         }
425
426                         if(!lookkey(cl->hdr, "Connection"))
427                                 cl->hdr = addkey(cl->hdr, "Connection", "keep-alive");
428
429                         if(agent && !lookkey(cl->hdr, "User-Agent"))
430                                 cl->hdr = addkey(cl->hdr, "User-Agent", agent);
431
432                         http(m, cl->url, cl->hdr, cl->qbody, f->buq);
433                         cl->request[0] = 0;
434                         cl->url = nil;
435                         cl->hdr = nil;
436                 }
437                 if(f->buq)
438                         break;
439                 cl->obody = 1;
440                 incref(cl->qbody);
441                 bureq(f->buq = cl->qbody, r);
442                 return;
443         }
444         respond(r, nil);
445 }
446
447 static int
448 rootgen(int i, Dir *d, void *)
449 {
450         i += Qroot+1;
451         if(i < Qclient){
452                 fsmkdir(d, i, 0);
453                 return 0;
454         }
455         i -= Qclient;
456         if(i < nclient){
457                 fsmkdir(d, Qclient, &client[i]);
458                 return 0;
459         }
460         return -1;
461 }
462
463 static int
464 clientgen(int i, Dir *d, void *aux)
465 {
466         i += Qclient+1;
467         if(i > Qparsed){
468                 Client *cl = aux;
469                 Key *k;
470
471                 i -= Qparsed+1;
472                 if(cl == nil || cl->qbody == nil)
473                         return -1;
474                 for(k = cl->qbody->hdr; i > 0 && k; i--, k = k->next)
475                         ;
476                 if(k == nil || i > 0)
477                         return -1;
478                 i = Qheader;
479                 aux = k;
480         }
481         fsmkdir(d, i, aux);
482         return 0;
483 }
484
485 static int
486 parsedgen(int i, Dir *d, void *aux)
487 {
488         i += Qparsed+1;
489         if(i > Qurlfrag)
490                 return -1;
491         fsmkdir(d, i, aux);
492         return 0;
493 }
494
495 static void
496 fsread(Req *r)
497 {
498         char buf[1024];
499         Webfid *f;
500
501         f = r->fid->aux;
502         switch(f->level){
503         case Qroot:
504                 dirread9p(r, rootgen, nil);
505                 respond(r, nil);
506                 return;
507         case Qclient:
508                 dirread9p(r, clientgen, f->client);
509                 respond(r, nil);
510                 return;
511         case Qparsed:
512                 dirread9p(r, parsedgen, clienturl(f->client));
513                 respond(r, nil);
514                 return;
515         case Qrctl:
516                 snprint(buf, sizeof(buf), "useragent %s\ntimeout %d\n", agent, timeout);
517         String:
518                 readstr(r, buf);
519                 respond(r, nil);
520                 return;
521         case Qctl:
522                 snprint(buf, sizeof(buf), "%ld\n", CLIENTID(f->client));
523                 goto String;
524         case Qheader:
525                 snprint(buf, sizeof(buf), "%s", f->key->val);
526                 goto String;
527         case Qurl:
528         case Qurlschm:
529         case Qurluser:
530         case Qurlpass:
531         case Qurlhost:
532         case Qurlport:
533         case Qurlpath:
534         case Qurlqwry:
535         case Qurlfrag:
536                 urlstr(buf, sizeof(buf), clienturl(f->client), f->level);
537                 goto String;
538         case Qbody:
539                 bureq(f->buq, r);
540                 return;
541         }
542         respond(r, "not implemented");
543 }
544
545 static char*
546 rootctl(char *ctl, char *arg)
547 {
548         Url *u;
549
550         if(debug)
551                 fprint(2, "rootctl: %q %q\n", ctl, arg);
552
553         if(!strcmp(ctl, "useragent")){
554                 free(agent);
555                 if(arg && *arg)
556                         agent = estrdup(arg);
557                 else
558                         agent = nil;
559                 return nil;
560         }
561
562         if(!strcmp(ctl, "flushauth")){
563                 u = nil;
564                 if(arg && *arg)
565                         u = saneurl(url(arg, 0));
566                 flushauth(u, 0);
567                 freeurl(u);
568                 return nil;
569         }
570
571         if(!strcmp(ctl, "timeout")){
572                 if(arg && *arg)
573                         timeout = atoi(arg);
574                 else
575                         timeout = 0;
576                 if(timeout < 0)
577                         timeout = 0;
578                 return nil;
579         }
580
581         return "bad ctl message";
582 }
583
584 static char*
585 clientctl(Client *cl, char *ctl, char *arg)
586 {
587         char *p;
588         Url *u;
589         Key *k;
590
591         if(debug)
592                 fprint(2, "clientctl: %q %q\n", ctl, arg);
593
594         if(!strcmp(ctl, "url")){
595                 if((u = saneurl(url(arg, cl->baseurl))) == nil)
596                         return "bad url";
597                 freeurl(cl->url);
598                 cl->url = u;
599         }
600         else if(!strcmp(ctl, "baseurl")){
601                 if((u = url(arg, 0)) == nil)
602                         return "bad baseurl";
603                 freeurl(cl->baseurl);
604                 cl->baseurl = u;
605         }
606         else if(!strcmp(ctl, "request")){
607                 p = cl->request;
608                 nstrcpy(p, arg, sizeof(cl->request));
609                 for(; *p && isalpha(*p); p++)
610                         *p = toupper(*p);
611                 *p = 0;
612         }
613         else if(!strcmp(ctl, "headers")){
614                 while(arg && *arg){
615                         ctl = arg;
616                         while(*ctl && strchr("\r\n\t ", *ctl))
617                                 ctl++;
618                         if(arg = strchr(ctl, '\n'))
619                                 *arg++ = 0;
620                         if(k = parsehdr(ctl)){
621                                 k->next = cl->hdr;
622                                 cl->hdr = k;
623                         }
624                 }
625         }
626         else {
627                 char buf[128], **t;
628                 static char *tab[] = {
629                         "User-Agent",
630                         "Content-Type",
631                         nil,
632                 };
633                 for(t = tab; *t; t++){
634                         nstrcpy(buf, *t, sizeof(buf));
635                         if(!strcmp(ctl, fshdrname(buf))){
636                                 cl->hdr = delkey(cl->hdr, *t);
637                                 if(arg && *arg)
638                                         cl->hdr = addkey(cl->hdr, *t, arg);
639                                 break;
640                         }
641                 }
642                 if(*t == nil)
643                         return "bad ctl message";
644         }
645         return nil;
646 }
647
648 static void
649 fswrite(Req *r)
650 {
651         int n;
652         Webfid *f;
653         char *s, *t;
654
655         f = r->fid->aux;
656         switch(f->level){
657         case Qrctl:
658         case Qctl:
659                 n = r->ofcall.count = r->ifcall.count;
660                 s = emalloc(n+1);
661                 memmove(s, r->ifcall.data, n);
662                 while(n > 0 && strchr("\r\n", s[n-1]))
663                         n--;
664                 s[n] = 0;
665                 t = s;
666                 while(*t && strchr("\r\n\t ", *t)==0)
667                         t++;
668                 while(*t && strchr("\r\n\t ", *t))
669                         *t++ = 0;
670                 if(f->level == Qctl)
671                         t = clientctl(f->client, s, t);
672                 else
673                         t = rootctl(s, t);
674                 free(s);
675                 respond(r, t);
676                 return;
677         case Qpost:
678                 bureq(f->buq, r);
679                 return;
680         }
681         respond(r, "not implemented");
682 }
683
684 static void
685 fsflush(Req *r)
686 {
687         Webfid *f;
688         Req *o;
689
690         if(o = r->oldreq)
691         if(f = o->fid->aux)
692                 buflushreq(f->buq, o);
693         respond(r, nil);
694 }
695
696 static void
697 fsdestroyfid(Fid *fid)
698 {
699         Webfid *f;
700
701         if(f = fid->aux){
702                 fid->aux = nil;
703                 if(f->buq){
704                         buclose(f->buq, 0);
705                         if(f->client->qbody == f->buq){
706                                 f->client->obody = 0;
707                                 f->client->cbody = 1;
708                         }
709                         bufree(f->buq);
710                 }
711                 if(f->key)
712                         free(f->key);
713                 freeclient(f->client);
714                 free(f);
715         }
716 }
717
718 Srv fs = 
719 {
720         .attach=fsattach,
721         .stat=fsstat,
722         .walk1=fswalk1,
723         .clone=fsclone,
724         .open=fsopen,
725         .read=fsread,
726         .write=fswrite,
727         .flush=fsflush,
728         .destroyfid=fsdestroyfid,
729 };
730
731 void
732 usage(void)
733 {
734         fprint(2, "usage: %s [-D] [-A useragent] [-T timeout] [-m mtpt] [-s srv]\n", argv0);
735         exits("usage");
736 }
737
738 void
739 main(int argc, char *argv[])
740 {
741         char *srv, *mtpt, *s;
742
743         quotefmtinstall();
744         fmtinstall('U', Ufmt);
745         fmtinstall('E', Efmt);
746
747         srv = nil;
748         mtpt = "/mnt/web";
749         user = getuser();
750         time0 = time(0);
751         timeout = 10000;
752         agent = nil;
753
754         ARGBEGIN {
755         case 'D':
756                 chatty9p++;
757                 break;
758         case 'A':
759                 agent = EARGF(usage());
760                 break;
761         case 'T':
762                 timeout = atoi(EARGF(usage()));
763                 if(timeout < 0)
764                         timeout = 0;
765                 break;
766         case 'm':
767                 mtpt = EARGF(usage());
768                 break;
769         case 's':
770                 srv = EARGF(usage());
771                 break;
772         case 'd':
773                 debug++;
774                 break;
775         default:
776                 usage();
777         } ARGEND;
778
779         rfork(RFNOTEG);
780
781         if(agent == nil)
782                 agent = "hjdicks";
783         agent = estrdup(agent);
784
785         if(s = getenv("httpproxy")){
786                 proxy = saneurl(url(s, 0));
787                 free(s);
788         }
789
790         postmountsrv(&fs, srv, mtpt, MREPL);
791 }