]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/mothra/forms.c
cwfs: fix listen filedescriptor leaks
[plan9front.git] / sys / src / cmd / mothra / forms.c
1 #include <u.h>
2 #include <libc.h>
3 #include <draw.h>
4 #include <event.h>
5 #include <panel.h>
6 #include "rtext.h"
7 #include "mothra.h"
8 #include "html.h"
9
10 typedef struct Field Field;
11 typedef struct Option Option;
12 struct Form{
13         int method;
14         char *ctype;
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         FILE,
52 };
53 struct Option{
54         int selected;
55         int def;
56         char label[NLABEL+1];
57         char *value;
58         Option *next;
59 };
60
61 #define BOUNDARY "nAboJ9uN6ZXsqoVGzLAdjKq97TWDTGjo"
62
63 void h_checkinput(Panel *, int, int);
64 void h_radioinput(Panel *, int, int);
65 void h_submitinput(Panel *, int);
66 void h_buttoninput(Panel *, int);
67 void h_fileinput(Panel *, int);
68 void h_submittype(Panel *, char *);
69 void h_submitindex(Panel *, char *);
70 void h_resetinput(Panel *, int);
71 void h_select(Panel *, int, int);
72 char *selgen(Panel *, int);
73 char *nullgen(Panel *, int);
74 Field *newfield(Form *form){
75         Field *f;
76         f=emalloc(sizeof(Field));
77         if(form->efields==0)
78                 form->fields=f;
79         else
80                 form->efields->next=f;
81         form->efields=f;
82         f->next=0;
83         f->form=form;
84         return f;
85 }
86 /*
87  * Called by rdhtml on seeing a forms-related tag
88  */
89 void rdform(Hglob *g){
90         char *s;
91         Field *f;
92         Option *o, **op;
93         Form *form;
94         switch(g->tag){
95         default:
96                 fprint(2, "Bad tag <%s> in rdform (Can't happen!)\n", g->token);
97                 return;
98         case Tag_form:
99                 if(g->form){
100                         htmlerror(g->name, g->lineno, "nested forms illegal\n");
101                         break;
102                 }
103                 g->form=emalloc(sizeof(Form));
104                 s=pl_getattr(g->attr, "action");
105                 g->form->action=strdup((s && *s) ? s : g->dst->url->fullname);
106                 s=pl_getattr(g->attr, "method");
107                 if(s==0 || *s==0)
108                         g->form->method=GET;
109                 else if(cistrcmp(s, "post")==0)
110                         g->form->method=POST;
111                 else{
112                         if(cistrcmp(s, "get")!=0)
113                                 htmlerror(g->name, g->lineno,
114                                         "unknown form method %s\n", s);
115                         g->form->method=GET;
116                 }
117                 s=pl_getattr(g->attr, "enctype");
118                 if(s && cistrcmp(s, "multipart/form-data")==0)
119                         g->form->ctype = "multipart/form-data; boundary=" BOUNDARY;
120                 g->form->fields=0;
121
122                 g->form->next = g->dst->form;
123                 g->dst->form = g->form;
124                 break;
125         case Tag_input:
126         case Tag_button:
127                 if(g->form==0){
128                         /* no form, assume link button */
129                         form = emalloc(sizeof(Form));   
130                         form->method = 0;
131                         form->fields = 0;
132                         form->efields = 0;
133                         if(g->state->link)
134                                 form->action = strdup(g->state->link);
135                         form->next = g->dst->form;
136                         g->dst->form = form;
137                         f=newfield(form);
138                 } else
139                         f=newfield(g->form);
140                 s=pl_getattr(g->attr, "name");
141                 if(s==0 || *s == 0)
142                         f->name=0;
143                 else
144                         f->name=strdup(s);
145                 s=pl_getattr(g->attr, "value");
146                 if(s==0)
147                         f->value=strdup("");
148                 else
149                         f->value=strdup(s);
150                 f->checked=pl_hasattr(g->attr, "checked");
151                 s=pl_getattr(g->attr, "size");
152                 if(s==0 || *s==0)
153                         f->size=20;
154                 else
155                         f->size=atoi(s);
156                 s=pl_getattr(g->attr, "maxlength");
157                 if(s==0 || *s==0)
158                         f->maxlength=0x3fffffff;
159                 else
160                         f->maxlength=atoi(s);
161                 s=pl_getattr(g->attr, "type");
162                 if((g->tag == Tag_button) && 
163                    (s==0 || cistrcmp(s, "reset") || cistrcmp(s, "button")))
164                         s="submit";
165                 else if(s==0)
166                         s="text";
167                 if(cistrcmp(s, "checkbox")==0)
168                         f->type=CHECK;
169                 else if(cistrcmp(s, "radio")==0)
170                         f->type=RADIO;
171                 else if(cistrcmp(s, "submit")==0)
172                         f->type=SUBMIT;
173                 else if(cistrcmp(s, "image")==0){
174                         f->type=SUBMIT;
175                         s=pl_getattr(g->attr, "src");
176                         if(s && *s){
177                                 free(g->state->image);
178                                 g->state->image = strdup(s);
179                         }
180                         s=pl_getattr(g->attr, "width");
181                         if(s && *s)
182                                 g->state->width=strtolength(g, HORIZ, s);
183                         s=pl_getattr(g->attr, "height");
184                         if(s && *s)
185                                 g->state->height=strtolength(g, VERT, s);
186                         s=pl_getattr(g->attr, "alt");
187                         if(s==0 || *s == 0) s = f->value;
188                         pl_htmloutput(g, g->nsp, s, f);
189                         free(g->state->image);
190                         g->state->image = 0;
191                         g->state->width=0;
192                         g->state->height=0;
193                         break;
194                 }
195                 else if(cistrcmp(s, "button")==0)
196                         f->type=BUTTON;
197                 else if(cistrcmp(s, "file")==0)
198                         f->type=FILE;
199                 else if(cistrcmp(s, "reset")==0)
200                         f->type=RESET;
201                 else if(cistrcmp(s, "hidden")==0)
202                         f->type=HIDDEN;
203                 else{
204                         f->type=TYPEIN;
205                         if(cistrcmp(s, "password")==0)
206                                 f->type=PASSWD;
207                         s=f->name;
208                         if(s && cistrcmp(s, "isindex")==0)
209                                 f->type=INDEX;
210
211                         /*
212                          * If there's exactly one attribute, use its value as the name,
213                          * regardless of the attribute name.  This makes
214                          * http://linus.att.com/ias/puborder.html work.
215                          */
216                         if(s==0){
217                                 if(g->attr[0].name && g->attr[1].name==0)
218                                         f->name=strdup(g->attr[0].value);
219                                 else
220                                         f->name=strdup("no-name");
221                         }
222                 }
223                 if((f->type==CHECK || f->type==RADIO) && !pl_hasattr(g->attr, "value")){
224                         free(f->value);
225                         f->value=strdup("on");
226                 }
227                 if(f->type!=HIDDEN)
228                         pl_htmloutput(g, g->nsp, f->value[0]?f->value:"blank field", f);
229                 break;
230         case Tag_select:
231                 if(g->form==0){
232                 BadTag:
233                         htmlerror(g->name, g->lineno, "<%s> not in form, ignored\n",
234                                 tag[g->tag].name);
235                         break;
236                 }
237                 f=newfield(g->form);
238                 s=pl_getattr(g->attr, "name");
239                 if(s==0 || *s==0){
240                         f->name=strdup("select");
241                         htmlerror(g->name, g->lineno, "select has no name=\n");
242                 }
243                 else
244                         f->name=strdup(s);
245                 f->multiple=pl_hasattr(g->attr, "multiple");
246                 f->type=SELECT;
247                 f->size=0;
248                 f->options=0;
249                 g->text=g->token;
250                 g->tp=g->text;
251                 g->etext=g->text;
252                 break;
253         case Tag_option:
254                 if(g->form==0) goto BadTag;
255                 if((f=g->form->efields)==0) goto BadTag;
256                 if(f->size<8)
257                         f->size++;
258                 o=emalloc(sizeof(Option));
259                 for(op=&f->options;*op;op=&(*op)->next);
260                 *op=o;
261                 o->next=0;
262                 g->text=o->label;
263                 g->tp=o->label;
264                 g->etext=o->label+NLABEL;
265                 memset(o->label, 0, NLABEL+1);
266                 *g->tp++=' ';
267                 o->def=pl_hasattr(g->attr, "selected");
268                 o->selected=o->def;
269                 if(pl_hasattr(g->attr, "disabled"))
270                         o->selected=0;
271                 s=pl_getattr(g->attr, "value");
272                 if(s==0)
273                         o->value=o->label+1;
274                 else
275                         o->value=strdup(s);
276                 break;
277         case Tag_textarea:
278                 if(g->form==0) goto BadTag;
279                 f=newfield(g->form);
280                 s=pl_getattr(g->attr, "name");
281                 if(s==0 || *s==0){
282                         f->name=strdup("enter text");
283                         htmlerror(g->name, g->lineno, "select has no name=\n");
284                 }
285                 else
286                         f->name=strdup(s);
287                 s=pl_getattr(g->attr, "rows");
288                 f->rows=(s && *s)?atoi(s):8;
289                 s=pl_getattr(g->attr, "cols");
290                 f->cols=(s && *s)?atoi(s):30;
291                 f->type=TEXTWIN;
292                 /* suck up initial text */
293                 pl_htmloutput(g, g->nsp, f->name, f);
294                 break;
295         case Tag_isindex:
296                 /*
297                  * Make up a form with one tag, of type INDEX
298                  * I have seen a page with <ISINDEX PROMPT="Enter a title here ">,
299                  * which is nonstandard and not handled here.
300                  */
301                 form=emalloc(sizeof(Form));
302                 form->fields=0;
303                 form->efields=0;
304                 s=pl_getattr(g->attr, "action");
305                 form->action=strdup((s && *s) ? s : g->dst->url->fullname);
306                 form->method=GET;
307                 form->fields=0;
308                 f=newfield(form);
309                 f->name=0;
310                 f->value=strdup("");
311                 f->size=20;
312                 f->maxlength=0x3fffffff;
313                 f->type=INDEX;
314                 pl_htmloutput(g, g->nsp, f->value[0]?f->value:"blank field", f);
315                 break;
316         }
317 }
318 /*
319  * Called by rdhtml on seeing a forms-related end tag
320  */
321 void endform(Hglob *g){
322         Field *f;
323
324         switch(g->tag){
325         case Tag_form:
326                 g->form=0;
327                 break;
328         case Tag_select:
329                 if(g->form==0)
330                         htmlerror(g->name, g->lineno, "</select> not in form, ignored\n");
331                 else if((f=g->form->efields)==0)
332                         htmlerror(g->name, g->lineno, "spurious </select>\n");
333                 else
334                         pl_htmloutput(g, g->nsp, f->name, f);
335                 break;
336         case Tag_textarea:
337                 break;
338         }
339 }
340 char *nullgen(Panel *, int ){
341         return 0;
342 }
343 char *selgen(Panel *p, int index){
344         Option *a;
345         Field *f;
346         f=p->userp;
347         if(f==0) return 0;
348         for(a=f->options;index!=0 && a!=0;--index,a=a->next);
349         if(a==0) return 0;
350         a->label[0]=a->selected?'*':' ';
351         return a->label;
352 }
353 char *seloption(Field *f){
354         Option *a;
355         for(a=f->options;a!=0;a=a->next)
356                 if(a->selected)
357                         return a->label+1;
358         return f->name;
359 }
360 void mkfieldpanel(Rtext *t){
361         Action *a;
362         Panel *win, *scrl;
363         Field *f;
364
365         if((a = t->user) == nil)
366                 return;
367         if((f = a->field) == nil)
368                 return;
369
370         f->p=0;
371         switch(f->type){
372         case TYPEIN:
373                 f->p=plentry(0, 0, f->size*chrwidth, f->value, h_submittype);
374                 break;
375         case PASSWD:
376                 f->p=plentry(0, USERFL, f->size*chrwidth, f->value, h_submittype);
377                 break;
378         case CHECK:
379                 f->p=plcheckbutton(0, 0, "", h_checkinput);
380                 f->state=f->checked;
381                 plsetbutton(f->p, f->checked);
382                 break;
383         case RADIO:
384                 f->p=plradiobutton(0, 0, "", h_radioinput);
385                 f->state=f->checked;
386                 plsetbutton(f->p, f->checked);
387                 break;
388         case SUBMIT:
389                 f->p=plbutton(0, 0, f->value[0]?f->value:"submit", h_submitinput);
390                 break;
391         case RESET:
392                 f->p=plbutton(0, 0, f->value[0]?f->value:"reset", h_resetinput);
393                 break;
394         case BUTTON:
395                 f->p=plbutton(0, 0, f->value[0]?f->value:"button", h_buttoninput);
396                 break;
397         case FILE:
398                 f->p=plbutton(0, 0, f->value[0]?f->value:"file", h_fileinput);
399                 break;
400         case SELECT:
401                 if(f->size <= 0)
402                         f->size=1;
403                 f->pulldown=plgroup(0,0);
404                 scrl=plscrollbar(f->pulldown, PACKW|FILLY);
405                 win=pllist(f->pulldown, PACKN, nullgen, f->size, h_select);
406                 win->userp=f;
407                 plinitlist(win, PACKN, selgen, f->size, h_select);
408                 plscroll(win, 0, scrl);
409                 plpack(f->pulldown, Rect(0,0,1024,1024));
410                 f->p=plpulldown(0, FIXEDX, seloption(f), f->pulldown, PACKS);
411                 f->p->fixedsize.x=f->pulldown->r.max.x-f->pulldown->r.min.x;
412                 break;
413         case TEXTWIN:
414                 f->p=plframe(0,0);
415                 pllabel(f->p, PACKN|FILLX, f->name);
416                 scrl=plscrollbar(f->p, PACKW|FILLY);
417                 f->textwin=pledit(f->p, EXPAND, Pt(f->cols*chrwidth, f->rows*font->height), 0, 0, 0);
418                 f->textwin->userp=f;
419                 plscroll(f->textwin, 0, scrl);
420                 break;
421         case INDEX:
422                 f->p=plentry(0, 0, f->size*chrwidth, f->value, h_submitindex);
423                 break;
424         }
425         if(f->p){
426                 f->p->userp=f;
427                 free(t->text);
428                 t->text=0;
429                 t->p=f->p;
430                 t->flags|=PL_HOT;
431         }
432 }
433 void h_checkinput(Panel *p, int, int v){
434         ((Field *)p->userp)->state=v;
435 }
436 void h_radioinput(Panel *p, int, int v){
437         Field *f, *me;
438         me=p->userp;
439         me->state=v;
440         if(v){
441                 for(f=me->form->fields;f;f=f->next)
442                         if(f->type==RADIO && f!=me && strcmp(f->name, me->name)==0){
443                                 plsetbutton(f->p, 0);
444                                 f->state=0;
445                                 pldraw(f->p, screen);
446                         }
447         }
448 }
449 void h_select(Panel *p, int, int index){
450         Option *a;
451         Field *f;
452         f=p->userp;
453         if(f==0) return;
454         if(!f->multiple) for(a=f->options;a;a=a->next) a->selected=0;
455         for(a=f->options;index!=0 && a!=0;--index,a=a->next);
456         if(a==0) return;
457         a->selected=!a->selected;
458         plinitpulldown(f->p, FIXEDX, seloption(f), f->pulldown, PACKS);
459         pldraw(f->p, screen);
460 }
461 void h_resetinput(Panel *p, int){
462         Field *f;
463         Option *o;
464         for(f=((Field *)p->userp)->form->fields;f;f=f->next) switch(f->type){
465         case TYPEIN:
466                 plinitentry(f->p, 0, f->size*chrwidth, f->value, 0);
467                 break;
468         case PASSWD:
469                 plinitentry(f->p, USERFL, f->size*chrwidth, f->value, 0);
470                 break;
471         case FILE:
472                 free(f->value);
473                 f->value=strdup("");
474                 if(f->p==nil) break;
475                 f->p->state=0;
476                 pldraw(f->p, screen);
477                 break;
478         case CHECK:
479         case RADIO:
480                 f->state=f->checked;
481                 plsetbutton(f->p, f->checked);
482                 break;
483         case SELECT:
484                 for(o=f->options;o;o=o->next)
485                         o->selected=o->def;
486                 break;
487         }
488         pldraw(text, screen);
489 }
490
491 void h_buttoninput(Panel *p, int){
492         Field *f;
493
494         f = p->userp;
495         if(f && f->form && f->form->method != POST && f->form->action)
496                 geturl(f->form->action, -1, 0, 0);
497 }
498
499 void h_fileinput(Panel *p, int){
500         char name[NNAME];
501         Field *f;
502
503         f = p->userp;
504         nstrcpy(name, f->value, sizeof(name));
505         for(;;){
506                 if(eenter("Upload file", name, sizeof(name), &mouse) <= 0)
507                         break;
508                 if(access(name, AREAD) == 0)
509                         break;
510         }
511         free(f->value);
512         f->value = strdup(name);
513         p->state = name[0] != 0;
514         pldraw(f->p, screen);
515 }
516
517 /*
518  * If there's exactly one button with type=text, then
519  * a CR in the button is supposed to submit the form.
520  */
521 void h_submittype(Panel *p, char *){
522         int ntype;
523         Field *f;
524         ntype=0;
525         for(f=((Field *)p->userp)->form->fields;f;f=f->next)
526                 if(f->type==TYPEIN || f->type==PASSWD)
527                         ntype++;
528         if(ntype==1) h_submitinput(p, 0);
529 }
530 void h_submitindex(Panel *p, char *){
531         h_submitinput(p, 0);
532 }
533
534 void mencodeform(Form *form, int fd){
535         char *b, *p, *sep;
536         int ifd, n, nb;
537         Option *o;
538         Field *f;
539         Rune *rp;
540
541         sep = "--" BOUNDARY;
542         for(f=form->fields;f;f=f->next)switch(f->type){
543         case TYPEIN:
544         case PASSWD:
545                 fprint(fd, "%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%s",
546                         sep, f->name, plentryval(f->p));
547                 sep = "\r\n--" BOUNDARY;
548                 break;
549         case CHECK:
550         case RADIO:
551         case SUBMIT:
552                 if(!f->state) break;
553         case HIDDEN:
554                 if(f->name==0 || f->value==0)
555                         break;
556                 fprint(fd, "%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%s",
557                         sep, f->name, f->value);
558                 sep = "\r\n--" BOUNDARY;
559                 break;
560         case SELECT:
561                 if(f->name==0) break;
562                 for(o=f->options;o;o=o->next)
563                         if(o->selected && o->value){
564                                 fprint(fd, "%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%s",
565                                         sep, f->name, o->value);
566                                 sep = "\r\n--" BOUNDARY;
567                         }
568                 break;
569         case TEXTWIN:
570                 if(f->name==0) break;
571                 n=plelen(f->textwin);
572                 rp=pleget(f->textwin);
573                 p=b=malloc(UTFmax*n+1);
574                 if(b == nil)
575                         break;
576                 while(n > 0){
577                         p += runetochar(p, rp);
578                         rp++;
579                         n--;
580                 }
581                 *p = 0;
582                 fprint(fd, "%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%s",
583                         sep, f->name, b);
584                 sep = "\r\n--" BOUNDARY;
585                 free(b);
586                 break;
587         case FILE:
588                 if(f->name==0 || f->value[0]==0)
589                         break;
590                 if(p = strrchr(f->value, '/'))
591                         p++;
592                 if(p == 0 || *p == 0)
593                         p = f->value;
594                 if((b = malloc(nb = 8192)) == nil)
595                         break;
596                 if((ifd = open(f->value, OREAD)) >= 0){
597                         if(filetype(ifd, b, nb) < 0)
598                                 strcpy(b, "application/octet-stream");
599                         fprint(fd, "%s\r\nContent-Disposition: form-data; name=\"%s\"; filename=\"%s\""
600                                 "\r\nContent-Type: %s\r\n\r\n", sep, f->name, p, b);
601                         sep = "\r\n--" BOUNDARY;
602                         while((n = read(ifd, b, nb)) > 0)
603                                 if(write(fd, b, n) != n)
604                                         break;
605                         close(ifd);
606                 }
607                 free(b);
608                 break;
609         }
610         fprint(fd, "%s--\r\n", sep);
611 }
612
613 void uencodeform(Form *form, int fd){
614         char *b, *p, *sep;
615         Option *o;
616         Field *f;
617         Rune *rp;
618         int n;
619
620         sep = "";
621         for(f=form->fields;f;f=f->next) switch(f->type){
622         case TYPEIN:
623         case PASSWD:
624                 fprint(fd, "%s%U=%U", sep, f->name, plentryval(f->p));
625                 sep = "&";
626                 break;
627         case INDEX:
628                 fprint(fd, "%s%U", sep, plentryval(f->p));
629                 sep = "&";
630                 break;
631         case CHECK:
632         case RADIO:
633         case SUBMIT:
634                 if(!f->state) break;
635         case HIDDEN:
636                 if(f->name==0 || f->value==0)
637                         break;
638                 fprint(fd, "%s%U=%U", sep, f->name, f->value);
639                 sep = "&";
640                 break;
641         case SELECT:
642                 if(f->name==0) break;
643                 for(o=f->options;o;o=o->next)
644                         if(o->selected && o->value){
645                                 fprint(fd, "%s%U=%U", sep, f->name, o->value);
646                                 sep = "&";
647                         }
648                 break;
649         case TEXTWIN:
650                 if(f->name==0) break;
651                 n=plelen(f->textwin);
652                 rp=pleget(f->textwin);
653                 p=b=malloc(UTFmax*n+1);
654                 if(b == nil)
655                         break;
656                 while(n > 0){
657                         p += runetochar(p, rp);
658                         rp++;
659                         n--;
660                 }
661                 *p = 0;
662                 fprint(fd, "%s%U=%U", sep, f->name, b);
663                 sep = "&";
664                 free(b);
665                 break;
666         }
667 }
668
669 void h_submitinput(Panel *p, int){
670         char buf[NNAME];
671         Form *form;
672         Field *f;
673         int n, fd;
674
675         f = p->userp;
676         form=f->form;
677         for(f=form->fields;f;f=f->next)
678                 if(f->type==SUBMIT)
679                         f->state = (f->p == p);
680
681         switch(form->method){
682         case GET:
683                 strcpy(buf, "/tmp/mfXXXXXXXXXXX");
684                 fd = create(mktemp(buf), ORDWR|ORCLOSE, 0600);
685                 break;
686         case POST:
687                 fd = urlpost(selurl(form->action), form->ctype);
688                 break;
689         default:
690                 return;
691         }
692
693         if(fd < 0){
694                 message("submit: %r");
695                 return;
696         }
697         if(form->method==GET){
698                 fprint(fd, "%s?", form->action);
699                 uencodeform(form, fd);
700                 seek(fd, 0, 0);
701                 n = readn(fd, buf, sizeof(buf));
702                 close(fd);
703                 if(n < 0 || n >= sizeof(buf)){
704                         message("submit: too large");
705                         return;
706                 }
707                 buf[n] = 0;
708                 geturl(buf, -1, 0, 0);
709         } else {
710                 /* only set for multipart/form-data */
711                 if(form->ctype)
712                         mencodeform(form, fd);
713                 else
714                         uencodeform(form, fd);
715                 geturl(form->action, fd, 0, 0);
716         }
717 }
718
719 void freeform(void *p)
720 {
721         Form *form;
722         Field *f;
723         Option *o;
724
725         while(form = p){
726                 p = form->next;
727                 free(form->action);
728                 while(f = form->fields){
729                         form->fields = f->next;
730
731                         if(f->p!=0)
732                                 plfree(f->p);
733
734                         free(f->name);
735                         free(f->value);
736
737                         while(o = f->options){
738                                 f->options = o->next;
739                                 if(o->value != o->label+1)
740                                         free(o->value);
741                                 free(o);
742                         }
743
744                         free(f);
745                 }
746                 free(form);
747         }
748 }
749
750 int Ufmt(Fmt *f){
751         char *s = va_arg(f->args, char*);
752         for(; *s; s++){
753                 if(strchr("/$-_@.!*'(),", *s)
754                 || 'a'<=*s && *s<='z'
755                 || 'A'<=*s && *s<='Z'
756                 || '0'<=*s && *s<='9')
757                         fmtprint(f, "%c", *s);
758                 else if(*s==' ')
759                         fmtprint(f, "+");
760                 else
761                         fmtprint(f, "%%%.2X", *s & 0xFF);
762         }
763         return 0;
764 }