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