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