]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/mothra/forms.c
mothra: multipart/form-data
[plan9front.git] / sys / src / cmd / mothra / forms.c
1 /*
2  * type=image is treated like submit
3  */
4 #include <u.h>
5 #include <libc.h>
6 #include <draw.h>
7 #include <event.h>
8 #include <panel.h>
9 #include "mothra.h"
10 #include "html.h"
11
12 typedef struct Field Field;
13 typedef struct Option Option;
14 struct Form{
15         int method;
16         char *ctype;
17         char *action;
18         Field *fields, *efields;
19         Form *next;
20 };
21 struct Field{
22         Field *next;
23         Form *form;
24         char *name;
25         char *value;
26         int checked;
27         int size;               /* should be a point, but that feature is deprecated */
28         int maxlength;
29         int type;
30         int rows, cols;
31         Option *options;
32         int multiple;
33         int state;              /* is the button marked? */
34         Panel *p;
35         Panel *pulldown;
36         Panel *textwin;
37 };
38 /*
39  * Field types
40  */
41 enum{
42         TYPEIN=1,
43         CHECK,
44         PASSWD,
45         RADIO,
46         SUBMIT,
47         RESET,
48         BUTTON,
49         SELECT,
50         TEXTWIN,
51         HIDDEN,
52         INDEX,
53 };
54 struct Option{
55         int selected;
56         int def;
57         char label[NLABEL+1];
58         char *value;
59         Option *next;
60 };
61
62 #define BOUNDARY "hjdicksHjDiCkSHJDICKS"
63
64 void h_checkinput(Panel *, int, int);
65 void h_radioinput(Panel *, int, int);
66 void h_submitinput(Panel *, int);
67 void h_buttoninput(Panel *, int);
68 void h_submittype(Panel *, char *);
69 void h_submitindex(Panel *, char *);
70 void h_resetinput(Panel *, int);
71 void h_select(Panel *, int, int);
72 char *selgen(Panel *, int);
73 char *nullgen(Panel *, int);
74 Field *newfield(Form *form){
75         Field *f;
76         f=emallocz(sizeof(Field), 1);
77         if(form->efields==0)
78                 form->fields=f;
79         else
80                 form->efields->next=f;
81         form->efields=f;
82         f->next=0;
83         f->form=form;
84         return f;
85 }
86 /*
87  * Called by rdhtml on seeing a forms-related tag
88  */
89 void rdform(Hglob *g){
90         char *s;
91         Field *f;
92         Option *o, **op;
93         Form *form;
94         switch(g->tag){
95         default:
96                 fprint(2, "Bad tag <%s> in rdform (Can't happen!)\n", g->token);
97                 return;
98         case Tag_form:
99                 if(g->form){
100                         htmlerror(g->name, g->lineno, "nested forms illegal\n");
101                         break;
102                 }
103                 g->form=emallocz(sizeof(Form), 1);
104                 s=pl_getattr(g->attr, "action");
105                 g->form->action=strdup((s && s[0]) ? s : g->dst->url->fullname);
106                 s=pl_getattr(g->attr, "method");
107                 if(s==0)
108                         g->form->method=GET;
109                 else if(cistrcmp(s, "post")==0)
110                         g->form->method=POST;
111                 else{
112                         if(cistrcmp(s, "get")!=0)
113                                 htmlerror(g->name, g->lineno,
114                                         "unknown form method %s\n", s);
115                         g->form->method=GET;
116                 }
117                 s=pl_getattr(g->attr, "enctype");
118                 if(s && cistrcmp(s, "multipart/form-data")==0)
119                         g->form->ctype = "multipart/form-data; boundary=" BOUNDARY;
120                 g->form->fields=0;
121
122                 g->form->next = g->dst->form;
123                 g->dst->form = g->form;
124
125                 break;
126         case Tag_input:
127         case Tag_button:
128                 if(g->form==0){
129                 BadTag:
130                         htmlerror(g->name, g->lineno, "<%s> not in form, ignored\n",
131                                 tag[g->tag].name);
132                         break;
133                 }
134                 f=newfield(g->form);
135                 s=pl_getattr(g->attr, "name");
136                 if(s==0)
137                         f->name=0;
138                 else
139                         f->name=strdup(s);
140                 s=pl_getattr(g->attr, "value");
141                 if(s==0)
142                         f->value=strdup("");
143                 else
144                         f->value=strdup(s);
145                 f->checked=pl_hasattr(g->attr, "checked");
146                 s=pl_getattr(g->attr, "size");
147                 if(s==0)
148                         f->size=20;
149                 else
150                         f->size=atoi(s);
151                 s=pl_getattr(g->attr, "maxlength");
152                 if(s==0)
153                         f->maxlength=0x3fffffff;
154                 else
155                         f->maxlength=atoi(s);
156                 s=pl_getattr(g->attr, "type");
157                 if((g->tag == Tag_button) && 
158                    (s==0 || cistrcmp(s, "reset") || cistrcmp(s, "button")))
159                         s="submit";
160                 else if(s==0)
161                         s="text";
162                 if(cistrcmp(s, "checkbox")==0)
163                         f->type=CHECK;
164                 else if(cistrcmp(s, "radio")==0)
165                         f->type=RADIO;
166                 else if(cistrcmp(s, "submit")==0)
167                         f->type=SUBMIT;
168                 else if(cistrcmp(s, "button")==0)
169                         f->type=BUTTON;
170                 else if(cistrcmp(s, "image")==0){
171                         /* presotto's egregious hack to make image submits do something */
172                         if(f->name){
173                                 free(f->name);
174                                 f->name=0;
175                         }
176                         f->type=SUBMIT;
177                 } else if(cistrcmp(s, "reset")==0)
178                         f->type=RESET;
179                 else if(cistrcmp(s, "hidden")==0)
180                         f->type=HIDDEN;
181                 else{
182                         f->type=TYPEIN;
183                         if(cistrcmp(s, "password")==0)
184                                 f->type=PASSWD;
185
186                         s=f->name;
187                         if(s && cistrcmp(s, "isindex")==0)
188                                 f->type=INDEX;
189
190                         /*
191                          * If there's exactly one attribute, use its value as the name,
192                          * regardless of the attribute name.  This makes
193                          * http://linus.att.com/ias/puborder.html work.
194                          */
195                         if(s==0){
196                                 if(g->attr[0].name && g->attr[1].name==0)
197                                         f->name=strdup(g->attr[0].value);
198                                 else
199                                         f->name=strdup("no-name");
200                         }
201                 }
202                 if((f->type==CHECK || f->type==RADIO) && !pl_hasattr(g->attr, "value")){
203                         free(f->value);
204                         f->value=strdup("on");
205                 }
206                 if(f->type!=HIDDEN)
207                         pl_htmloutput(g, g->nsp, f->value[0]?f->value:"blank field", f);
208                 break;
209         case Tag_select:
210                 if(g->form==0) goto BadTag;
211                 f=newfield(g->form);
212                 s=pl_getattr(g->attr, "name");
213                 if(s==0){
214                         f->name=strdup("select");
215                         htmlerror(g->name, g->lineno, "select has no name=\n");
216                 }
217                 else
218                         f->name=strdup(s);
219                 s=pl_getattr(g->attr, "size");
220                 if(s==0) f->size=4;
221                 else{
222                         f->size=atoi(s);
223                         if(f->size<=0) f->size=1;
224                 }
225                 f->multiple=pl_hasattr(g->attr, "multiple");
226                 f->type=SELECT;
227                 f->options=0;
228                 g->text=g->token;
229                 g->tp=g->text;
230                 g->etext=g->text;
231                 break;
232         case Tag_option:
233                 if(g->form==0) goto BadTag;
234                 if((f=g->form->efields)==0) goto BadTag;
235                 o=emallocz(sizeof(Option), 1);
236                 for(op=&f->options;*op;op=&(*op)->next);
237                 *op=o;
238                 o->next=0;
239                 g->text=o->label;
240                 g->tp=o->label;
241                 g->etext=o->label+NLABEL;
242                 memset(o->label, 0, NLABEL+1);
243                 *g->tp++=' ';
244                 o->def=pl_hasattr(g->attr, "selected");
245                 o->selected=o->def;
246                 s=pl_getattr(g->attr, "value");
247                 if(s==0)
248                         o->value=o->label+1;
249                 else
250                         o->value=strdup(s);
251                 break;
252         case Tag_textarea:
253                 if(g->form==0) goto BadTag;
254                 f=newfield(g->form);
255                 s=pl_getattr(g->attr, "name");
256                 if(s==0){
257                         f->name=strdup("enter text");
258                         htmlerror(g->name, g->lineno, "select has no name=\n");
259                 }
260                 else
261                         f->name=strdup(s);
262                 s=pl_getattr(g->attr, "rows");
263                 f->rows=s?atoi(s):8;
264                 s=pl_getattr(g->attr, "cols");
265                 f->cols=s?atoi(s):30;
266                 f->type=TEXTWIN;
267                 /* suck up initial text */
268                 pl_htmloutput(g, g->nsp, f->name, f);
269                 break;
270         case Tag_isindex:
271                 /*
272                  * Make up a form with one tag, of type INDEX
273                  * I have seen a page with <ISINDEX PROMPT="Enter a title here ">,
274                  * which is nonstandard and not handled here.
275                  */
276                 form=emalloc(sizeof(Form));
277                 form->fields=0;
278                 form->efields=0;
279                 s=pl_getattr(g->attr, "action");
280                 form->action=strdup((s && s[0]) ? s : g->dst->url->fullname);
281                 form->method=GET;
282                 form->fields=0;
283                 f=newfield(form);
284                 f->name=0;
285                 f->value=strdup("");
286                 f->size=20;
287                 f->maxlength=0x3fffffff;
288                 f->type=INDEX;
289                 pl_htmloutput(g, g->nsp, f->value[0]?f->value:"blank field", f);
290                 break;
291         }
292 }
293 /*
294  * Called by rdhtml on seeing a forms-related end tag
295  */
296 void endform(Hglob *g){
297         Field *f;
298
299         switch(g->tag){
300         case Tag_form:
301                 g->form=0;
302                 break;
303         case Tag_select:
304                 if(g->form==0)
305                         htmlerror(g->name, g->lineno, "</select> not in form, ignored\n");
306                 else if((f=g->form->efields)==0)
307                         htmlerror(g->name, g->lineno, "spurious </select>\n");
308                 else
309                         pl_htmloutput(g, g->nsp, f->name, f);
310                 break;
311         case Tag_textarea:
312                 break;
313         }
314 }
315 char *nullgen(Panel *, int ){
316         return 0;
317 }
318 char *selgen(Panel *p, int index){
319         Option *a;
320         Field *f;
321         f=p->userp;
322         if(f==0) return 0;
323         for(a=f->options;index!=0 && a!=0;--index,a=a->next);
324         if(a==0) return 0;
325         a->label[0]=a->selected?'*':' ';
326         return a->label;
327 }
328 char *seloption(Field *f){
329         Option *a;
330         for(a=f->options;a!=0;a=a->next)
331                 if(a->selected)
332                         return a->label+1;
333         return f->name;
334 }
335 void mkfieldpanel(Rtext *t){
336         Action *a;
337         Panel *win, *scrl, *menu, *pop, *button;
338         Field *f;
339
340         if((a = t->user) == nil)
341                 return;
342         if((f = a->field) == nil)
343                 return;
344
345         f->p=0;
346         switch(f->type){
347         case TYPEIN:
348                 f->p=plentry(0, 0, f->size*chrwidth, f->value, h_submittype);
349                 break;
350         case PASSWD:
351                 f->p=plentry(0, USERFL, f->size*chrwidth, f->value, h_submittype);
352                 break;
353         case CHECK:
354                 f->p=plcheckbutton(0, 0, "", h_checkinput);
355                 f->state=f->checked;
356                 plsetbutton(f->p, f->checked);
357                 break;
358         case RADIO:
359                 f->p=plradiobutton(0, 0, "", h_radioinput);
360                 f->state=f->checked;
361                 plsetbutton(f->p, f->checked);
362                 break;
363         case SUBMIT:
364                 f->p=plbutton(0, 0, f->value[0]?f->value:"submit", h_submitinput);
365                 break;
366         case RESET:
367                 f->p=plbutton(0, 0, f->value[0]?f->value:"reset", h_resetinput);
368                 break;
369         case BUTTON:
370                 f->p=plbutton(0, 0, f->value[0]?f->value:"button", h_buttoninput);
371                 break;
372         case SELECT:
373                 f->pulldown=plgroup(0,0);
374                 scrl=plscrollbar(f->pulldown, PACKW|FILLY);
375                 win=pllist(f->pulldown, PACKN, nullgen, f->size, h_select);
376                 win->userp=f;
377                 plinitlist(win, PACKN, selgen, f->size, h_select);
378                 plscroll(win, 0, scrl);
379                 plpack(f->pulldown, Rect(0,0,1024,1024));
380                 f->p=plpulldown(0, FIXEDX, seloption(f), f->pulldown, PACKS);
381                 f->p->fixedsize.x=f->pulldown->r.max.x-f->pulldown->r.min.x;
382                 break;
383         case TEXTWIN:
384                 f->p=plframe(0,0);
385                 pllabel(f->p, PACKN|FILLX, f->name);
386                 scrl=plscrollbar(f->p, PACKW|FILLY);
387                 f->textwin=pledit(f->p, EXPAND, Pt(f->cols*chrwidth, f->rows*font->height),
388                         0, 0, 0);
389                 f->textwin->userp=f;
390                 plscroll(f->textwin, 0, scrl);
391                 break;
392         case INDEX:
393                 f->p=plentry(0, 0, f->size*chrwidth, f->value, h_submitindex);
394                 break;
395         }
396         if(f->p){
397                 f->p->userp=f;
398                 free(t->text);
399                 t->text=0;
400                 t->p=f->p;
401                 t->hot=1;
402         }
403 }
404 void h_checkinput(Panel *p, int, int v){
405         ((Field *)p->userp)->state=v;
406 }
407 void h_radioinput(Panel *p, int, int v){
408         Field *f, *me;
409         me=p->userp;
410         me->state=v;
411         if(v){
412                 for(f=me->form->fields;f;f=f->next)
413                         if(f->type==RADIO && f!=me && strcmp(f->name, me->name)==0){
414                                 plsetbutton(f->p, 0);
415                                 f->state=0;
416                                 pldraw(f->p, screen);
417                         }
418         }
419 }
420 void h_select(Panel *p, int, int index){
421         Option *a;
422         Field *f;
423         f=p->userp;
424         if(f==0) return;
425         if(!f->multiple) for(a=f->options;a;a=a->next) a->selected=0;
426         for(a=f->options;index!=0 && a!=0;--index,a=a->next);
427         if(a==0) return;
428         a->selected=!a->selected;
429         plinitpulldown(f->p, FIXEDX, seloption(f), f->pulldown, PACKS);
430         pldraw(f->p, screen);
431 }
432 void h_resetinput(Panel *p, int){
433         Field *f;
434         Option *o;
435         for(f=((Field *)p->userp)->form->fields;f;f=f->next) switch(f->type){
436         case TYPEIN:
437                 plinitentry(f->p, 0, f->size*chrwidth, f->value, 0);
438                 break;
439         case PASSWD:
440                 plinitentry(f->p, USERFL, f->size*chrwidth, f->value, 0);
441                 break;
442         case CHECK:
443         case RADIO:
444                 f->state=f->checked;
445                 plsetbutton(f->p, f->checked);
446                 break;
447         case SELECT:
448                 for(o=f->options;o;o=o->next)
449                         o->selected=o->def;
450                 break;
451         }
452         pldraw(text, screen);
453 }
454 void h_buttoninput(Panel *p, int){
455 }
456
457 /*
458  * If there's exactly one button with type=text, then
459  * a CR in the button is supposed to submit the form.
460  */
461 void h_submittype(Panel *p, char *){
462         int ntype;
463         Field *f;
464         ntype=0;
465         for(f=((Field *)p->userp)->form->fields;f;f=f->next)
466                 if(f->type==TYPEIN || f->type==PASSWD)
467                         ntype++;
468         if(ntype==1) h_submitinput(p, 0);
469 }
470 void h_submitindex(Panel *p, char *){
471         h_submitinput(p, 0);
472 }
473
474 void mencodeform(Form *form, int fd){
475         char *b, *p, *sep;
476         Option *o;
477         Field *f;
478         Rune *rp;
479         int n;
480
481 #define SEPS    "-----------------------------" BOUNDARY
482 #define NEXT    "\r\n" SEPS
483
484         sep = SEPS;
485         for(f=form->fields;f;f=f->next){
486                 switch(f->type){
487                 case TYPEIN:
488                 case PASSWD:
489                         fprint(fd, "%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%s",
490                                 sep, f->name, plentryval(f->p));
491                         sep = NEXT;
492                         break;
493                 case CHECK:
494                 case RADIO:
495                         if(!f->state) break;
496                 case HIDDEN:
497                         fprint(fd, "%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%s",
498                                 sep, f->name, f->value);
499                         sep = NEXT;
500                         break;
501                 case SELECT:
502                         if(f->name==0)
503                                 continue;
504                         for(o=f->options;o;o=o->next)
505                                 if(o->selected && o->value){
506                                         fprint(fd, "%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%s",
507                                                 sep, f->name, o->value);
508                                         sep = NEXT;
509                                 }
510                         break;
511                 case TEXTWIN:
512                         if(f->name==0)
513                                 continue;
514                         n=plelen(f->textwin);
515                         rp=pleget(f->textwin);
516                         p=b=malloc(UTFmax*n+1);
517                         if(b == nil)
518                                 continue;
519                         while(n > 0){
520                                 p += runetochar(p, rp);
521                                 rp++;
522                                 n--;
523                         }
524                         *p = 0;
525                         fprint(fd, "%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%s",
526                                 sep, f->name, b);
527                         sep = NEXT;
528                         free(b);
529                         break;
530                 }
531         }
532         fprint(fd, "%s--\r\n", sep);
533 }
534
535 void uencodeform(Form *form, int fd){
536         char *b, *p, *sep;
537         Option *o;
538         Field *f;
539         Rune *rp;
540         int n;
541
542         sep = "";
543         for(f=form->fields;f;f=f->next) switch(f->type){
544         case TYPEIN:
545         case PASSWD:
546                 fprint(fd, "%s%U=%U", sep, f->name, plentryval(f->p));
547                 sep = "&";
548                 break;
549         case INDEX:
550                 fprint(fd, "%s%U", sep, plentryval(f->p));
551                 sep = "&";
552                 break;
553         case CHECK:
554         case RADIO:
555                 if(!f->state) break;
556         case HIDDEN:
557                 if(f->name==0 || f->value==0)
558                         continue;
559                 fprint(fd, "%s%U=%U", sep, f->name, f->value);
560                 sep = "&";
561                 break;
562         case SELECT:
563                 if(f->name==0)
564                         continue;
565                 for(o=f->options;o;o=o->next)
566                         if(o->selected && o->value){
567                                 fprint(fd, "%s%U=%U", sep, f->name, o->value);
568                                 sep = "&";
569                         }
570                 break;
571         case TEXTWIN:
572                 if(f->name==0)
573                         continue;
574                 n=plelen(f->textwin);
575                 rp=pleget(f->textwin);
576                 p=b=malloc(UTFmax*n+1);
577                 if(b == nil)
578                         continue;
579                 while(n > 0){
580                         p += runetochar(p, rp);
581                         rp++;
582                         n--;
583                 }
584                 *p = 0;
585                 fprint(fd, "%s%U=%U", sep, f->name, b);
586                 sep = "&";
587                 free(b);
588                 break;
589         }
590 }
591
592 void h_submitinput(Panel *p, int){
593         char buf[NNAME];
594         Form *form;
595         int n, fd;
596
597         form=((Field *)p->userp)->form;
598         if(form->method==GET){
599                 strcpy(buf, "/tmp/mfXXXXXXXXXXX");
600                 fd = create(mktemp(buf), ORDWR|ORCLOSE, 0600);
601         } else
602                 fd = urlpost(selurl(form->action), form->ctype);
603         if(fd < 0){
604                 message("submit: %r");
605                 return;
606         }
607         if(form->method==GET){
608                 fprint(fd, "%s?", form->action);
609                 uencodeform(form, fd);
610                 seek(fd, 0, 0);
611                 n = readn(fd, buf, sizeof(buf));
612                 close(fd);
613                 if(n < 0 || n >= sizeof(buf)){
614                         message("submit: too large");
615                         return;
616                 }
617                 buf[n] = 0;
618                 if(debug)fprint(2, "GET %s\n", buf);
619                 geturl(buf, -1, 0, 0);
620         } else {
621                 /* only set for multipart/form-data */
622                 if(form->ctype)
623                         mencodeform(form, fd);
624                 else
625                         uencodeform(form, fd);
626                 if(debug)fprint(2, "POST %s\n", form->action);
627                 geturl(form->action, fd, 0, 0);
628         }
629 }
630
631 void freeform(void *p)
632 {
633         Form *form;
634         Field *f;
635         Option *o;
636
637         while(form = p){
638                 p = form->next;
639                 free(form->action);
640                 while(f = form->fields){
641                         form->fields = f->next;
642
643                         if(f->p!=0)
644                                 plfree(f->p);
645
646                         free(f->name);
647                         free(f->value);
648
649                         while(o = f->options){
650                                 f->options = o->next;
651                                 if(o->value != o->label+1)
652                                         free(o->value);
653                                 free(o);
654                         }
655
656                         free(f);
657                 }
658                 free(form);
659         }
660 }
661
662 int Ufmt(Fmt *f){
663         char *s = va_arg(f->args, char*);
664         for(; *s; s++){
665                 if(strchr("/$-_@.!*'(),", *s)
666                 || 'a'<=*s && *s<='z'
667                 || 'A'<=*s && *s<='Z'
668                 || '0'<=*s && *s<='9')
669                         fmtprint(f, "%c", *s);
670                 else if(*s==' ')
671                         fmtprint(f, "+");
672                 else
673                         fmtprint(f, "%%%.2X", (unsigned int)*s);
674         }
675         return 0;
676 }