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