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