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