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