-/*
- * type=image is treated like submit
- */
#include <u.h>
#include <libc.h>
#include <draw.h>
#include <panel.h>
#include "mothra.h"
#include "html.h"
+
typedef struct Field Field;
typedef struct Option Option;
struct Form{
int method;
+ char *ctype;
char *action;
Field *fields, *efields;
Form *next;
TEXTWIN,
HIDDEN,
INDEX,
+ FILE,
};
struct Option{
int selected;
char *value;
Option *next;
};
+
+#define BOUNDARY "nAboJ9uN6ZXsqoVGzLAdjKq97TWDTGjo"
+
void h_checkinput(Panel *, int, int);
void h_radioinput(Panel *, int, int);
void h_submitinput(Panel *, int);
void h_buttoninput(Panel *, int);
+void h_fileinput(Panel *, int);
void h_submittype(Panel *, char *);
void h_submitindex(Panel *, char *);
void h_resetinput(Panel *, int);
void h_select(Panel *, int, int);
-void h_cut(Panel *, int);
-void h_paste(Panel *, int);
-void h_snarf(Panel *, int);
-void h_edit(Panel *);
char *selgen(Panel *, int);
char *nullgen(Panel *, int);
Field *newfield(Form *form){
Field *f;
- f=emallocz(sizeof(Field), 1);
+ f=emalloc(sizeof(Field));
if(form->efields==0)
form->fields=f;
else
htmlerror(g->name, g->lineno, "nested forms illegal\n");
break;
}
- g->form=emallocz(sizeof(Form), 1);
+ g->form=emalloc(sizeof(Form));
s=pl_getattr(g->attr, "action");
- g->form->action=strdup((s && s[0]) ? s : g->dst->url->fullname);
+ g->form->action=strdup((s && *s) ? s : g->dst->url->fullname);
s=pl_getattr(g->attr, "method");
- if(s==0)
+ if(s==0 || *s==0)
g->form->method=GET;
else if(cistrcmp(s, "post")==0)
g->form->method=POST;
"unknown form method %s\n", s);
g->form->method=GET;
}
+ s=pl_getattr(g->attr, "enctype");
+ if(s && cistrcmp(s, "multipart/form-data")==0)
+ g->form->ctype = "multipart/form-data; boundary=" BOUNDARY;
g->form->fields=0;
g->form->next = g->dst->form;
g->dst->form = g->form;
-
break;
case Tag_input:
case Tag_button:
if(g->form==0){
- BadTag:
- htmlerror(g->name, g->lineno, "<%s> not in form, ignored\n",
- tag[g->tag].name);
- break;
- }
- f=newfield(g->form);
+ /* no form, assume link button */
+ form = emalloc(sizeof(Form));
+ form->method = 0;
+ form->fields = 0;
+ form->efields = 0;
+ if(g->state->link[0])
+ form->action = strdup(g->state->link);
+ form->next = g->dst->form;
+ g->dst->form = form;
+ f=newfield(form);
+ } else
+ f=newfield(g->form);
s=pl_getattr(g->attr, "name");
- if(s==0)
+ if(s==0 || *s == 0)
f->name=0;
else
f->name=strdup(s);
f->value=strdup(s);
f->checked=pl_hasattr(g->attr, "checked");
s=pl_getattr(g->attr, "size");
- if(s==0)
+ if(s==0 || *s==0)
f->size=20;
else
f->size=atoi(s);
s=pl_getattr(g->attr, "maxlength");
- if(s==0)
+ if(s==0 || *s==0)
f->maxlength=0x3fffffff;
else
f->maxlength=atoi(s);
f->type=RADIO;
else if(cistrcmp(s, "submit")==0)
f->type=SUBMIT;
- else if(cistrcmp(s, "button")==0)
- f->type=BUTTON;
else if(cistrcmp(s, "image")==0){
- /* presotto's egregious hack to make image submits do something */
- if(f->name){
- free(f->name);
- f->name=0;
- }
f->type=SUBMIT;
- } else if(cistrcmp(s, "reset")==0)
+ s=pl_getattr(g->attr, "src");
+ if(s && *s)
+ nstrcpy(g->state->image, s, sizeof(g->state->image));
+ s=pl_getattr(g->attr, "width");
+ if(s && *s)
+ g->state->width=strtolength(g, HORIZ, s);
+ s=pl_getattr(g->attr, "height");
+ if(s && *s)
+ g->state->height=strtolength(g, VERT, s);
+ s=pl_getattr(g->attr, "alt");
+ if(s==0 || *s == 0) s = f->value;
+ pl_htmloutput(g, g->nsp, s, f);
+ g->state->image[0] = 0;
+ g->state->width=0;
+ g->state->height=0;
+ break;
+ }
+ else if(cistrcmp(s, "button")==0)
+ f->type=BUTTON;
+ else if(cistrcmp(s, "file")==0)
+ f->type=FILE;
+ else if(cistrcmp(s, "reset")==0)
f->type=RESET;
else if(cistrcmp(s, "hidden")==0)
f->type=HIDDEN;
f->type=TYPEIN;
if(cistrcmp(s, "password")==0)
f->type=PASSWD;
+ s=f->name;
+ if(s && cistrcmp(s, "isindex")==0)
+ f->type=INDEX;
+
/*
* If there's exactly one attribute, use its value as the name,
* regardless of the attribute name. This makes
* http://linus.att.com/ias/puborder.html work.
*/
- s=f->name;
- if(s && cistrcmp(s, "isindex")==0)
- f->type=INDEX;
if(s==0){
if(g->attr[0].name && g->attr[1].name==0)
f->name=strdup(g->attr[0].value);
pl_htmloutput(g, g->nsp, f->value[0]?f->value:"blank field", f);
break;
case Tag_select:
- if(g->form==0) goto BadTag;
+ if(g->form==0){
+ BadTag:
+ htmlerror(g->name, g->lineno, "<%s> not in form, ignored\n",
+ tag[g->tag].name);
+ break;
+ }
f=newfield(g->form);
s=pl_getattr(g->attr, "name");
- if(s==0){
+ if(s==0 || *s==0){
f->name=strdup("select");
htmlerror(g->name, g->lineno, "select has no name=\n");
}
else
f->name=strdup(s);
- s=pl_getattr(g->attr, "size");
- if(s==0) f->size=4;
- else{
- f->size=atoi(s);
- if(f->size<=0) f->size=1;
- }
f->multiple=pl_hasattr(g->attr, "multiple");
f->type=SELECT;
+ f->size=0;
f->options=0;
g->text=g->token;
g->tp=g->text;
case Tag_option:
if(g->form==0) goto BadTag;
if((f=g->form->efields)==0) goto BadTag;
- o=emallocz(sizeof(Option), 1);
+ if(f->size<8)
+ f->size++;
+ o=emalloc(sizeof(Option));
for(op=&f->options;*op;op=&(*op)->next);
*op=o;
o->next=0;
*g->tp++=' ';
o->def=pl_hasattr(g->attr, "selected");
o->selected=o->def;
+ if(pl_hasattr(g->attr, "disabled"))
+ o->selected=0;
s=pl_getattr(g->attr, "value");
if(s==0)
o->value=o->label+1;
if(g->form==0) goto BadTag;
f=newfield(g->form);
s=pl_getattr(g->attr, "name");
- if(s==0){
+ if(s==0 || *s==0){
f->name=strdup("enter text");
htmlerror(g->name, g->lineno, "select has no name=\n");
}
else
f->name=strdup(s);
s=pl_getattr(g->attr, "rows");
- f->rows=s?atoi(s):8;
+ f->rows=(s && *s)?atoi(s):8;
s=pl_getattr(g->attr, "cols");
- f->cols=s?atoi(s):30;
+ f->cols=(s && *s)?atoi(s):30;
f->type=TEXTWIN;
/* suck up initial text */
pl_htmloutput(g, g->nsp, f->name, f);
form->fields=0;
form->efields=0;
s=pl_getattr(g->attr, "action");
- form->action=strdup((s && s[0]) ? s : g->dst->url->fullname);
+ form->action=strdup((s && *s) ? s : g->dst->url->fullname);
form->method=GET;
form->fields=0;
f=newfield(form);
f->p=plentry(0, 0, f->size*chrwidth, f->value, h_submittype);
break;
case PASSWD:
- f->p=plentry(0, 1, f->size*chrwidth, f->value, h_submittype);
+ f->p=plentry(0, USERFL, f->size*chrwidth, f->value, h_submittype);
break;
case CHECK:
f->p=plcheckbutton(0, 0, "", h_checkinput);
case BUTTON:
f->p=plbutton(0, 0, f->value[0]?f->value:"button", h_buttoninput);
break;
+ case FILE:
+ f->p=plbutton(0, 0, f->value[0]?f->value:"file", h_fileinput);
+ break;
case SELECT:
+ if(f->size <= 0)
+ f->size=1;
f->pulldown=plgroup(0,0);
scrl=plscrollbar(f->pulldown, PACKW|FILLY);
win=pllist(f->pulldown, PACKN, nullgen, f->size, h_select);
f->p->fixedsize.x=f->pulldown->r.max.x-f->pulldown->r.min.x;
break;
case TEXTWIN:
- menu=plgroup(0,0);
f->p=plframe(0,0);
pllabel(f->p, PACKN|FILLX, f->name);
scrl=plscrollbar(f->p, PACKW|FILLY);
- pop=plpopup(f->p, PACKN|FILLX, 0, menu, 0);
- f->textwin=pledit(pop, EXPAND, Pt(f->cols*chrwidth, f->rows*font->height),
- 0, 0, h_edit);
+ f->textwin=pledit(f->p, EXPAND, Pt(f->cols*chrwidth, f->rows*font->height), 0, 0, 0);
f->textwin->userp=f;
- button=plbutton(menu, PACKN|FILLX, "cut", h_cut);
- button->userp=f->textwin;
- button=plbutton(menu, PACKN|FILLX, "paste", h_paste);
- button->userp=f->textwin;
- button=plbutton(menu, PACKN|FILLX, "snarf", h_snarf);
- button->userp=f->textwin;
plscroll(f->textwin, 0, scrl);
break;
case INDEX:
free(t->text);
t->text=0;
t->p=f->p;
- t->hot=1;
+ t->flags|=PL_HOT;
}
}
void h_checkinput(Panel *p, int, int v){
plinitentry(f->p, 0, f->size*chrwidth, f->value, 0);
break;
case PASSWD:
- plinitentry(f->p, 1, f->size*chrwidth, f->value, 0);
+ plinitentry(f->p, USERFL, f->size*chrwidth, f->value, 0);
+ break;
+ case FILE:
+ free(f->value);
+ f->value=strdup("");
+ if(f->p==nil) break;
+ f->p->state=0;
+ pldraw(f->p, screen);
break;
case CHECK:
case RADIO:
}
pldraw(text, screen);
}
+
void h_buttoninput(Panel *p, int){
+ Field *f;
+
+ f = p->userp;
+ if(f && f->form && f->form->method != POST && f->form->action)
+ geturl(f->form->action, -1, 0, 0);
}
-void h_edit(Panel *p){
- plgrabkb(p);
-}
-Rune *snarfbuf=0;
-int nsnarfbuf=0;
-void h_snarf(Panel *p, int){
- int s0, s1;
- Rune *text;
- p=p->userp;
- plegetsel(p, &s0, &s1);
- if(s0==s1) return;
- text=pleget(p);
- if(snarfbuf) free(snarfbuf);
- nsnarfbuf=s1-s0;
- snarfbuf=malloc(nsnarfbuf*sizeof(Rune));
- if(snarfbuf==0){
- fprint(2, "No mem\n");
- exits("no mem");
- }
- memmove(snarfbuf, text+s0, nsnarfbuf*sizeof(Rune));
-}
-void h_cut(Panel *p, int b){
- h_snarf(p, b);
- plepaste(p->userp, 0, 0);
-}
-void h_paste(Panel *p, int){
- plepaste(p->userp, snarfbuf, nsnarfbuf);
-}
-int ulen(char *s){
- int len;
- len=0;
- for(;*s;s++){
- if(strchr("/$-_@.!*'(), ", *s)
- || 'a'<=*s && *s<='z'
- || 'A'<=*s && *s<='Z'
- || '0'<=*s && *s<='9')
- len++;
- else
- len+=3;
- }
- return len;
-}
-int hexdigit(int v){
- return 0<=v && v<=9?'0'+v:'A'+v-10;
-}
-char *ucpy(char *buf, char *s){
- for(;*s;s++){
- if(strchr("/$-_@.!*'(),", *s)
- || 'a'<=*s && *s<='z'
- || 'A'<=*s && *s<='Z'
- || '0'<=*s && *s<='9')
- *buf++=*s;
- else if(*s==' ')
- *buf++='+';
- else{
- *buf++='%';
- *buf++=hexdigit((*s>>4)&15);
- *buf++=hexdigit(*s&15);
- }
- }
- *buf='\0';
- return buf;
-}
-char *runetou(char *buf, Rune r){
- char rbuf[2];
- if(r<=255){
- rbuf[0]=r;
- rbuf[1]='\0';
- buf=ucpy(buf, rbuf);
+
+void h_fileinput(Panel *p, int){
+ char name[NNAME];
+ Field *f;
+
+ f = p->userp;
+ nstrcpy(name, f->value, sizeof(name));
+ for(;;){
+ if(eenter("Upload file", name, sizeof(name), &mouse) <= 0)
+ break;
+ if(access(name, AREAD) == 0)
+ break;
}
- return buf;
+ free(f->value);
+ f->value = strdup(name);
+ p->state = name[0] != 0;
+ pldraw(f->p, screen);
}
+
/*
* If there's exactly one button with type=text, then
* a CR in the button is supposed to submit the form.
void h_submitindex(Panel *p, char *){
h_submitinput(p, 0);
}
-void h_submitinput(Panel *p, int){
- Form *form;
- int size, nrune;
- char *buf, *bufp, sep;
- Rune *rp;
- Field *f;
+
+void mencodeform(Form *form, int fd){
+ char *b, *p, *sep;
+ int ifd, n, nb;
Option *o;
- form=((Field *)p->userp)->form;
- if(form->method==GET) size=ulen(form->action)+1;
- else size=1;
- for(f=form->fields;f;f=f->next) switch(f->type){
+ Field *f;
+ Rune *rp;
+
+ sep = "--" BOUNDARY;
+ for(f=form->fields;f;f=f->next)switch(f->type){
case TYPEIN:
case PASSWD:
- if(f->name==0)
- continue;
- size+=ulen(f->name)+1+ulen(plentryval(f->p))+1;
- break;
- case INDEX:
- size+=ulen(plentryval(f->p))+1;
+ fprint(fd, "%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%s",
+ sep, f->name, plentryval(f->p));
+ sep = "\r\n--" BOUNDARY;
break;
case CHECK:
case RADIO:
+ case SUBMIT:
if(!f->state) break;
case HIDDEN:
if(f->name==0 || f->value==0)
- continue;
- size+=ulen(f->name)+1+ulen(f->value)+1;
+ break;
+ fprint(fd, "%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%s",
+ sep, f->name, f->value);
+ sep = "\r\n--" BOUNDARY;
break;
case SELECT:
- if(f->name==0)
- continue;
+ if(f->name==0) break;
for(o=f->options;o;o=o->next)
- if(o->selected && o->value)
- size+=ulen(f->name)+1+ulen(o->value)+1;
+ if(o->selected && o->value){
+ fprint(fd, "%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%s",
+ sep, f->name, o->value);
+ sep = "\r\n--" BOUNDARY;
+ }
break;
case TEXTWIN:
- if(f->name==0)
- continue;
- size+=ulen(f->name)+1+plelen(f->textwin)*3+1;
+ if(f->name==0) break;
+ n=plelen(f->textwin);
+ rp=pleget(f->textwin);
+ p=b=malloc(UTFmax*n+1);
+ if(b == nil)
+ break;
+ while(n > 0){
+ p += runetochar(p, rp);
+ rp++;
+ n--;
+ }
+ *p = 0;
+ fprint(fd, "%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%s",
+ sep, f->name, b);
+ sep = "\r\n--" BOUNDARY;
+ free(b);
+ break;
+ case FILE:
+ if(f->name==0 || f->value[0]==0)
+ break;
+ if(p = strrchr(f->value, '/'))
+ p++;
+ if(p == 0 || *p == 0)
+ p = f->value;
+ if((b = malloc(nb = 8192)) == nil)
+ break;
+ if((ifd = open(f->value, OREAD)) >= 0){
+ if(filetype(ifd, b, nb) < 0)
+ strcpy(b, "application/octet-stream");
+ fprint(fd, "%s\r\nContent-Disposition: form-data; name=\"%s\"; filename=\"%s\""
+ "\r\nContent-Type: %s\r\n\r\n", sep, f->name, p, b);
+ sep = "\r\n--" BOUNDARY;
+ while((n = read(ifd, b, nb)) > 0)
+ if(write(fd, b, n) != n)
+ break;
+ close(ifd);
+ }
+ free(b);
break;
}
- buf=emalloc(size);
- if(form->method==GET){
- strcpy(buf, form->action);
- sep='?';
- }
- else{
- buf[0]='\0';
- sep=0;
- }
- bufp=buf+strlen(buf);
- if(form->method==GET && bufp!=buf && bufp[-1]=='?') *--bufp='\0'; /* spurious ? */
+ fprint(fd, "%s--\r\n", sep);
+}
+
+void uencodeform(Form *form, int fd){
+ char *b, *p, *sep;
+ Option *o;
+ Field *f;
+ Rune *rp;
+ int n;
+
+ sep = "";
for(f=form->fields;f;f=f->next) switch(f->type){
case TYPEIN:
case PASSWD:
- if(f->name==0)
- continue;
- if(sep) *bufp++=sep;
- sep='&';
- bufp=ucpy(bufp, f->name);
- *bufp++='=';
- bufp=ucpy(bufp, plentryval(f->p));
+ fprint(fd, "%s%U=%U", sep, f->name, plentryval(f->p));
+ sep = "&";
break;
case INDEX:
- if(sep) *bufp++=sep;
- sep='&';
- bufp=ucpy(bufp, plentryval(f->p));
+ fprint(fd, "%s%U", sep, plentryval(f->p));
+ sep = "&";
break;
case CHECK:
case RADIO:
+ case SUBMIT:
if(!f->state) break;
case HIDDEN:
if(f->name==0 || f->value==0)
- continue;
- if(sep) *bufp++=sep;
- sep='&';
- bufp=ucpy(bufp, f->name);
- *bufp++='=';
- bufp=ucpy(bufp, f->value);
+ break;
+ fprint(fd, "%s%U=%U", sep, f->name, f->value);
+ sep = "&";
break;
case SELECT:
- if(f->name==0)
- continue;
+ if(f->name==0) break;
for(o=f->options;o;o=o->next)
if(o->selected && o->value){
- if(sep) *bufp++=sep;
- sep='&';
- bufp=ucpy(bufp, f->name);
- *bufp++='=';
- bufp=ucpy(bufp, o->value);
+ fprint(fd, "%s%U=%U", sep, f->name, o->value);
+ sep = "&";
}
break;
case TEXTWIN:
- if(f->name==0)
- continue;
- if(sep) *bufp++=sep;
- sep='&';
- bufp=ucpy(bufp, f->name);
- *bufp++='=';
+ if(f->name==0) break;
+ n=plelen(f->textwin);
rp=pleget(f->textwin);
- for(nrune=plelen(f->textwin);nrune!=0;--nrune)
- bufp=runetou(bufp, *rp++);
- *bufp='\0';
+ p=b=malloc(UTFmax*n+1);
+ if(b == nil)
+ break;
+ while(n > 0){
+ p += runetochar(p, rp);
+ rp++;
+ n--;
+ }
+ *p = 0;
+ fprint(fd, "%s%U=%U", sep, f->name, b);
+ sep = "&";
+ free(b);
break;
}
- if(form->method==GET){
- if(debug)fprint(2, "GET %s\n", buf);
- geturl(buf, GET, 0, 0, 0);
+}
+
+void h_submitinput(Panel *p, int){
+ char buf[NNAME];
+ Form *form;
+ Field *f;
+ int n, fd;
+
+ f = p->userp;
+ form=f->form;
+ for(f=form->fields;f;f=f->next)
+ if(f->type==SUBMIT)
+ f->state = (f->p == p);
+
+ switch(form->method){
+ case GET:
+ strcpy(buf, "/tmp/mfXXXXXXXXXXX");
+ fd = create(mktemp(buf), ORDWR|ORCLOSE, 0600);
+ break;
+ case POST:
+ fd = urlpost(selurl(form->action), form->ctype);
+ break;
+ default:
+ return;
}
- else{
- if(debug)fprint(2, "POST %s: %s\n", form->action, buf);
- geturl(form->action, POST, buf, 0, 0);
+
+ if(fd < 0){
+ message("submit: %r");
+ return;
+ }
+ if(form->method==GET){
+ fprint(fd, "%s?", form->action);
+ uencodeform(form, fd);
+ seek(fd, 0, 0);
+ n = readn(fd, buf, sizeof(buf));
+ close(fd);
+ if(n < 0 || n >= sizeof(buf)){
+ message("submit: too large");
+ return;
+ }
+ buf[n] = 0;
+ geturl(buf, -1, 0, 0);
+ } else {
+ /* only set for multipart/form-data */
+ if(form->ctype)
+ mencodeform(form, fd);
+ else
+ uencodeform(form, fd);
+ geturl(form->action, fd, 0, 0);
}
- free(buf);
}
void freeform(void *p)
free(form);
}
}
+
+int Ufmt(Fmt *f){
+ char *s = va_arg(f->args, char*);
+ for(; *s; s++){
+ if(strchr("/$-_@.!*'(),", *s)
+ || 'a'<=*s && *s<='z'
+ || 'A'<=*s && *s<='Z'
+ || '0'<=*s && *s<='9')
+ fmtprint(f, "%c", *s);
+ else if(*s==' ')
+ fmtprint(f, "+");
+ else
+ fmtprint(f, "%%%.2X", *s & 0xFF);
+ }
+ return 0;
+}