]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/mothra/forms.c
add mothra
[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         Url *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                 g->form->action=emalloc(sizeof(Url));
102                 s=pl_getattr(g->attr, "action");
103                 if(s==0)
104                         *g->form->action=*g->dst->url;
105                 else
106                         crackurl(g->form->action, s, g->dst->base);
107                 s=pl_getattr(g->attr, "method");
108                 if(s==0)
109                         g->form->method=GET;
110                 else if(cistrcmp(s, "post")==0)
111                         g->form->method=POST;
112                 else{
113                         if(cistrcmp(s, "get")!=0)
114                                 htmlerror(g->name, g->lineno,
115                                         "unknown form method %s\n", s);
116                         g->form->method=GET;
117                 }
118                 g->form->fields=0;
119
120                 g->form->next = g->dst->form;
121                 g->dst->form = g->form;
122
123                 break;
124         case Tag_input:
125                 if(g->form==0){
126                 BadTag:
127                         htmlerror(g->name, g->lineno, "<%s> not in form, ignored\n",
128                                 tag[g->tag].name);
129                         break;
130                 }
131                 f=newfield(g->form);
132                 s=pl_getattr(g->attr, "name");
133                 if(s==0)
134                         f->name=0;
135                 else
136                         f->name=strdup(s);
137                 s=pl_getattr(g->attr, "value");
138                 if(s==0)
139                         f->value=strdup("");
140                 else
141                         f->value=strdup(s);
142                 f->checked=pl_hasattr(g->attr, "checked");
143                 s=pl_getattr(g->attr, "size");
144                 if(s==0)
145                         f->size=20;
146                 else
147                         f->size=atoi(s);
148                 s=pl_getattr(g->attr, "maxlength");
149                 if(s==0)
150                         f->maxlength=0x3fffffff;
151                 else
152                         f->maxlength=atoi(s);
153                 s=pl_getattr(g->attr, "type");
154                 /* bug -- password treated as text */
155                 if(s==0 || cistrcmp(s, "text")==0 || cistrcmp(s, "password")==0 || cistrcmp(s, "int")==0){
156                         s=pl_getattr(g->attr, "name");
157                         if(s!=0 && strcmp(s, "isindex")==0)
158                                 f->type=INDEX;
159                         else
160                                 f->type=TYPEIN;
161                         /*
162                          * If there's exactly one attribute, use its value as the name,
163                          * regardless of the attribute name.  This makes
164                          * http://linus.att.com/ias/puborder.html work.
165                          */
166                         if(s==0){
167                                 if(g->attr[0].name && g->attr[1].name==0)
168                                         f->name=strdup(g->attr[0].value);
169                                 else
170                                         f->name=strdup("no-name");
171                         }
172                 }
173                 else if(cistrcmp(s, "checkbox")==0)
174                         f->type=CHECK;
175                 else if(cistrcmp(s, "radio")==0)
176                         f->type=RADIO;
177                 else if(cistrcmp(s, "submit")==0)
178                         f->type=SUBMIT;
179                 else if(cistrcmp(s, "image")==0){
180                         /* presotto's egregious hack to make image submits do something */
181                         if(f->name){
182                                 free(f->name);
183                                 f->name=0;
184                         }
185                         f->type=SUBMIT;
186                 } else if(cistrcmp(s, "reset")==0)
187                         f->type=RESET;
188                 else if(cistrcmp(s, "hidden")==0)
189                         f->type=HIDDEN;
190                 else{
191                         htmlerror(g->name, g->lineno, "bad field type %s, ignored", s);
192                         break;
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                 f=g->form->efields;
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                 form->action=emalloc(sizeof(Url));
272                 s=pl_getattr(g->attr, "action");
273                 if(s==0)
274                         *form->action=*g->dst->url;
275                 else
276                         crackurl(form->action, s, g->dst->base);
277                 form->method=GET;
278                 form->fields=0;
279                 f=newfield(form);
280                 f->name=0;
281                 f->value=strdup("");
282                 f->size=20;
283                 f->maxlength=0x3fffffff;
284                 f->type=INDEX;
285                 pl_htmloutput(g, g->nsp, f->value[0]?f->value:"blank field", f);
286                 break;
287         }
288 }
289 /*
290  * Called by rdhtml on seeing a forms-related end tag
291  */
292 void endform(Hglob *g){
293         switch(g->tag){
294         case Tag_form:
295                 g->form=0;
296                 break;
297         case Tag_select:
298                 if(g->form==0)
299                         htmlerror(g->name, g->lineno, "</select> not in form, ignored\n");
300                 else
301                         pl_htmloutput(g, g->nsp, g->form->efields->name,g->form->efields);
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 CHECK:
343                 f->p=plcheckbutton(0, 0, "", h_checkinput);
344                 f->state=f->checked;
345                 plsetbutton(f->p, f->checked);
346                 break;
347         case RADIO:
348                 f->p=plradiobutton(0, 0, "", h_radioinput);
349                 f->state=f->checked;
350                 plsetbutton(f->p, f->checked);
351                 break;
352         case SUBMIT:
353                 f->p=plbutton(0, 0, f->value[0]?f->value:"submit", h_submitinput);
354                 break;
355         case RESET:
356                 f->p=plbutton(0, 0, f->value[0]?f->value:"reset", h_resetinput);
357                 break;
358         case SELECT:
359                 f->pulldown=plgroup(0,0);
360                 scrl=plscrollbar(f->pulldown, PACKW|FILLY);
361                 win=pllist(f->pulldown, PACKN, nullgen, f->size, h_select);
362                 win->userp=f;
363                 plinitlist(win, PACKN, selgen, f->size, h_select);
364                 plscroll(win, 0, scrl);
365                 plpack(f->pulldown, Rect(0,0,1024,1024));
366                 f->p=plpulldown(0, FIXEDX, seloption(f), f->pulldown, PACKS);
367                 f->p->fixedsize.x=f->pulldown->r.max.x-f->pulldown->r.min.x;
368                 break;
369         case TEXTWIN:
370                 menu=plgroup(0,0);
371                 f->p=plframe(0,0);
372                 pllabel(f->p, PACKN|FILLX, f->name);
373                 scrl=plscrollbar(f->p, PACKW|FILLY);
374                 pop=plpopup(f->p, PACKN|FILLX, 0, menu, 0);
375                 f->textwin=pledit(pop, EXPAND, Pt(f->cols*chrwidth, f->rows*font->height),
376                         0, 0, h_edit);
377                 f->textwin->userp=f;
378                 button=plbutton(menu, PACKN|FILLX, "cut", h_cut);
379                 button->userp=f->textwin;
380                 button=plbutton(menu, PACKN|FILLX, "paste", h_paste);
381                 button->userp=f->textwin;
382                 button=plbutton(menu, PACKN|FILLX, "snarf", h_snarf);
383                 button->userp=f->textwin;
384                 plscroll(f->textwin, 0, scrl);
385                 break;
386         case INDEX:
387                 f->p=plentry(0, 0, f->size*chrwidth, f->value, h_submitindex);
388                 break;
389         }
390         if(f->p){
391                 f->p->userp=f;
392                 free(t->text);
393                 t->text=0;
394                 t->p=f->p;
395                 t->hot=1;
396         }
397 }
398 void h_checkinput(Panel *p, int, int v){
399         ((Field *)p->userp)->state=v;
400 }
401 void h_radioinput(Panel *p, int, int v){
402         Field *f, *me;
403         me=p->userp;
404         me->state=v;
405         if(v){
406                 for(f=me->form->fields;f;f=f->next)
407                         if(f->type==RADIO && f!=me && strcmp(f->name, me->name)==0){
408                                 plsetbutton(f->p, 0);
409                                 f->state=0;
410                                 pldraw(f->p, screen);
411                         }
412         }
413 }
414 void h_select(Panel *p, int, int index){
415         Option *a;
416         Field *f;
417         f=p->userp;
418         if(f==0) return;
419         if(!f->multiple) for(a=f->options;a;a=a->next) a->selected=0;
420         for(a=f->options;index!=0 && a!=0;--index,a=a->next);
421         if(a==0) return;
422         a->selected=!a->selected;
423         plinitpulldown(f->p, FIXEDX, seloption(f), f->pulldown, PACKS);
424         pldraw(f->p, screen);
425 }
426 void h_resetinput(Panel *p, int){
427         Field *f;
428         Option *o;
429         for(f=((Field *)p->userp)->form->fields;f;f=f->next) switch(f->type){
430         case TYPEIN:
431         case PASSWD:
432                 plinitentry(f->p, 0, 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_edit(Panel *p){
447         plgrabkb(p);
448 }
449 Rune *snarfbuf=0;
450 int nsnarfbuf=0;
451 void h_snarf(Panel *p, int){
452         int s0, s1;
453         Rune *text;
454         p=p->userp;
455         plegetsel(p, &s0, &s1);
456         if(s0==s1) return;
457         text=pleget(p);
458         if(snarfbuf) free(snarfbuf);
459         nsnarfbuf=s1-s0;
460         snarfbuf=malloc(nsnarfbuf*sizeof(Rune));
461         if(snarfbuf==0){
462                 fprint(2, "No mem\n");
463                 exits("no mem");
464         }
465         memmove(snarfbuf, text+s0, nsnarfbuf*sizeof(Rune));
466 }
467 void h_cut(Panel *p, int b){
468         h_snarf(p, b);
469         plepaste(p->userp, 0, 0);
470 }
471 void h_paste(Panel *p, int){
472         plepaste(p->userp, snarfbuf, nsnarfbuf);
473 }
474 int ulen(char *s){
475         int len;
476         len=0;
477         for(;*s;s++){
478                 if(strchr("/$-_@.!*'(), ", *s)
479                 || 'a'<=*s && *s<='z'
480                 || 'A'<=*s && *s<='Z'
481                 || '0'<=*s && *s<='9')
482                         len++;
483                 else
484                         len+=3;
485         }
486         return len;
487 }
488 int hexdigit(int v){
489         return 0<=v && v<=9?'0'+v:'A'+v-10;
490 }
491 char *ucpy(char *buf, char *s){
492         for(;*s;s++){
493                 if(strchr("/$-_@.!*'(),", *s)
494                 || 'a'<=*s && *s<='z'
495                 || 'A'<=*s && *s<='Z'
496                 || '0'<=*s && *s<='9')
497                         *buf++=*s;
498                 else if(*s==' ')
499                         *buf++='+';
500                 else{
501                         *buf++='%';
502                         *buf++=hexdigit((*s>>4)&15);
503                         *buf++=hexdigit(*s&15);
504                 }
505         }
506         *buf='\0';
507         return buf;
508 }
509 char *runetou(char *buf, Rune r){
510         char rbuf[2];
511         if(r<=255){
512                 rbuf[0]=r;
513                 rbuf[1]='\0';
514                 buf=ucpy(buf, rbuf);
515         }
516         return buf;
517 }
518 /*
519  * If there's exactly one button with type=text, then
520  * a CR in the button is supposed to submit the form.
521  */
522 void h_submittype(Panel *p, char *){
523         int ntype;
524         Field *f;
525         ntype=0;
526         for(f=((Field *)p->userp)->form->fields;f;f=f->next) if(f->type==TYPEIN) ntype++;
527         if(ntype==1) h_submitinput(p, 0);
528 }
529 void h_submitindex(Panel *p, char *){
530         h_submitinput(p, 0);
531 }
532 void h_submitinput(Panel *p, int){
533         Form *form;
534         int size, nrune;
535         char *buf, *bufp, sep;
536         Rune *rp;
537         Field *f;
538         Option *o;
539         form=((Field *)p->userp)->form;
540         if(form->method==GET) size=ulen(form->action->fullname)+1;
541         else size=1;
542         for(f=form->fields;f;f=f->next) switch(f->type){
543         case TYPEIN:
544         case PASSWD:
545                 size+=ulen(f->name)+1+ulen(plentryval(f->p))+1;
546                 break;
547         case INDEX:
548                 size+=ulen(plentryval(f->p))+1;
549                 break;
550         case CHECK:
551         case RADIO:
552                 if(!f->state) break;
553         case HIDDEN:
554                 size+=ulen(f->name)+1+ulen(f->value)+1;
555                 break;
556         case SELECT:
557                 for(o=f->options;o;o=o->next)
558                         if(o->selected)
559                                 size+=ulen(f->name)+1+ulen(o->value)+1;
560                 break;
561         case TEXTWIN:
562                 size+=ulen(f->name)+1+plelen(f->textwin)*3+1;
563                 break;
564         }
565         buf=emalloc(size);
566         if(form->method==GET){
567                 strcpy(buf, form->action->fullname);
568                 sep='?';
569         }
570         else{
571                 buf[0]='\0';
572                 sep=0;
573         }
574         bufp=buf+strlen(buf);
575         if(form->method==GET && bufp!=buf && bufp[-1]=='?') *--bufp='\0'; /* spurious ? */
576         for(f=form->fields;f;f=f->next) switch(f->type){
577         case TYPEIN:
578         case PASSWD:
579                 if(sep) *bufp++=sep;
580                 sep='&';
581                 bufp=ucpy(bufp, f->name);
582                 *bufp++='=';
583                 bufp=ucpy(bufp, plentryval(f->p));
584                 break;
585         case INDEX:
586                 if(sep) *bufp++=sep;
587                 sep='&';
588                 bufp=ucpy(bufp, plentryval(f->p));
589                 break;
590         case CHECK:
591         case RADIO:
592                 if(!f->state) break;
593         case HIDDEN:
594                 if(sep) *bufp++=sep;
595                 sep='&';
596                 bufp=ucpy(bufp, f->name);
597                 *bufp++='=';
598                 bufp=ucpy(bufp, f->value);
599                 break;
600         case SELECT:
601                 for(o=f->options;o;o=o->next)
602                         if(o->selected){
603                                 if(sep) *bufp++=sep;
604                                 sep='&';
605                                 bufp=ucpy(bufp, f->name);
606                                 *bufp++='=';
607                                 bufp=ucpy(bufp, o->value);
608                         }
609                 break;
610         case TEXTWIN:
611                 if(sep) *bufp++=sep;
612                 sep='&';
613                 bufp=ucpy(bufp, f->name);
614                 *bufp++='=';
615                 rp=pleget(f->textwin);
616                 for(nrune=plelen(f->textwin);nrune!=0;--nrune)
617                         bufp=runetou(bufp, *rp++);
618                 *bufp='\0';
619                 break;
620         }
621         if(form->method==GET){
622 fprint(2, "GET %s\n", buf);
623                 geturl(buf, GET, 0, 0, 0);
624         }
625         else{
626 fprint(2, "POST %s: %s\n", form->action->fullname, buf);
627                 geturl(form->action->fullname, POST, buf, 0, 0);
628         }
629         free(buf);
630 }
631
632 void freeform(void *p)
633 {
634         Form *form;
635         Field *f;
636         Option *o;
637
638         while(form = p){
639                 p = form->next;
640                 free(form->action);
641                 while(f = form->fields){
642                         form->fields = f->next;
643
644                         if(f->p!=0)
645                                 plfree(f->p);
646
647                         free(f->name);
648                         free(f->value);
649
650                         while(o = f->options){
651                                 f->options = o->next;
652                                 if(o->value != o->label+1)
653                                         free(o->value);
654                                 free(o);
655                         }
656
657                         free(f);
658                 }
659                 free(form);
660         }
661 }