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