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