xref: /plan9/sys/src/cmd/ip/httpd/wikipost.c (revision 453ee12c5ca2e4fdd97e94dd56f9cc72c0d42ba4)
180ee5cbfSDavid du Colombier /*
280ee5cbfSDavid du Colombier  * Accept new wiki pages or modifications to existing ones via POST method.
380ee5cbfSDavid du Colombier  *
480ee5cbfSDavid du Colombier  * Talks to the server at /srv/wiki.service.
580ee5cbfSDavid du Colombier  */
680ee5cbfSDavid du Colombier #include <u.h>
780ee5cbfSDavid du Colombier #include <libc.h>
880ee5cbfSDavid du Colombier #include <bio.h>
980ee5cbfSDavid du Colombier #include "httpd.h"
109a747e4fSDavid du Colombier #include "httpsrv.h"
1180ee5cbfSDavid du Colombier 
1280ee5cbfSDavid du Colombier #define LOG "wiki"
1380ee5cbfSDavid du Colombier 
149a747e4fSDavid du Colombier HConnect *hc;
159a747e4fSDavid du Colombier HSPriv *hp;
169a747e4fSDavid du Colombier 
179a747e4fSDavid du Colombier 
1880ee5cbfSDavid du Colombier /* go from possibly-latin1 url with escapes to utf */
1980ee5cbfSDavid du Colombier char *
2080ee5cbfSDavid du Colombier _urlunesc(char *s)
2180ee5cbfSDavid du Colombier {
2280ee5cbfSDavid du Colombier 	char *t, *v, *u;
2380ee5cbfSDavid du Colombier 	Rune r;
2480ee5cbfSDavid du Colombier 	int c, n;
2580ee5cbfSDavid du Colombier 
2680ee5cbfSDavid du Colombier 	/* unescape */
279a747e4fSDavid du Colombier 	u = halloc(hc, strlen(s)+1);
2880ee5cbfSDavid du Colombier 	for(t = u; c = *s; s++){
2980ee5cbfSDavid du Colombier 		if(c == '%'){
3080ee5cbfSDavid du Colombier 			n = s[1];
3180ee5cbfSDavid du Colombier 			if(n >= '0' && n <= '9')
3280ee5cbfSDavid du Colombier 				n = n - '0';
3380ee5cbfSDavid du Colombier 			else if(n >= 'A' && n <= 'F')
3480ee5cbfSDavid du Colombier 				n = n - 'A' + 10;
3580ee5cbfSDavid du Colombier 			else if(n >= 'a' && n <= 'f')
3680ee5cbfSDavid du Colombier 				n = n - 'a' + 10;
3780ee5cbfSDavid du Colombier 			else
3880ee5cbfSDavid du Colombier 				break;
3980ee5cbfSDavid du Colombier 			r = n;
4080ee5cbfSDavid du Colombier 			n = s[2];
4180ee5cbfSDavid du Colombier 			if(n >= '0' && n <= '9')
4280ee5cbfSDavid du Colombier 				n = n - '0';
4380ee5cbfSDavid du Colombier 			else if(n >= 'A' && n <= 'F')
4480ee5cbfSDavid du Colombier 				n = n - 'A' + 10;
4580ee5cbfSDavid du Colombier 			else if(n >= 'a' && n <= 'f')
4680ee5cbfSDavid du Colombier 				n = n - 'a' + 10;
4780ee5cbfSDavid du Colombier 			else
4880ee5cbfSDavid du Colombier 				break;
4980ee5cbfSDavid du Colombier 			s += 2;
5080ee5cbfSDavid du Colombier 			c = r*16+n;
5180ee5cbfSDavid du Colombier 		}
5280ee5cbfSDavid du Colombier 		*t++ = c;
5380ee5cbfSDavid du Colombier 	}
5480ee5cbfSDavid du Colombier 	*t = 0;
5580ee5cbfSDavid du Colombier 
5680ee5cbfSDavid du Colombier 	/* latin1 heuristic */
579a747e4fSDavid du Colombier 	v = halloc(hc, UTFmax*strlen(u) + 1);
5880ee5cbfSDavid du Colombier 	s = u;
5980ee5cbfSDavid du Colombier 	t = v;
6080ee5cbfSDavid du Colombier 	while(*s){
6180ee5cbfSDavid du Colombier 		/* in decoding error, assume latin1 */
6280ee5cbfSDavid du Colombier 		if((n=chartorune(&r, s)) == 1 && r == 0x80)
6380ee5cbfSDavid du Colombier 			r = *s;
6480ee5cbfSDavid du Colombier 		s += n;
6580ee5cbfSDavid du Colombier 		t += runetochar(t, &r);
6680ee5cbfSDavid du Colombier 	}
6780ee5cbfSDavid du Colombier 	*t = 0;
6880ee5cbfSDavid du Colombier 
6980ee5cbfSDavid du Colombier 	return v;
7080ee5cbfSDavid du Colombier }
7180ee5cbfSDavid du Colombier 
7280ee5cbfSDavid du Colombier enum
7380ee5cbfSDavid du Colombier {
7480ee5cbfSDavid du Colombier 	MaxLog		= 100*1024,		/* limit on length of any one log request */
7580ee5cbfSDavid du Colombier };
7680ee5cbfSDavid du Colombier 
7780ee5cbfSDavid du Colombier static int
7880ee5cbfSDavid du Colombier dangerous(char *s)
7980ee5cbfSDavid du Colombier {
8080ee5cbfSDavid du Colombier 	if(s == nil)
8180ee5cbfSDavid du Colombier 		return 1;
8280ee5cbfSDavid du Colombier 
8380ee5cbfSDavid du Colombier 	/*
8480ee5cbfSDavid du Colombier 	 * This check shouldn't be needed;
8580ee5cbfSDavid du Colombier 	 * filename folding is already supposed to have happened.
8680ee5cbfSDavid du Colombier 	 * But I'm paranoid.
8780ee5cbfSDavid du Colombier 	 */
8880ee5cbfSDavid du Colombier 	while(s = strchr(s,'/')){
8980ee5cbfSDavid du Colombier 		if(s[1]=='.' && s[2]=='.')
9080ee5cbfSDavid du Colombier 			return 1;
9180ee5cbfSDavid du Colombier 		s++;
9280ee5cbfSDavid du Colombier 	}
9380ee5cbfSDavid du Colombier 	return 0;
9480ee5cbfSDavid du Colombier }
9580ee5cbfSDavid du Colombier 
9680ee5cbfSDavid du Colombier char*
9780ee5cbfSDavid du Colombier unhttp(char *s)
9880ee5cbfSDavid du Colombier {
9980ee5cbfSDavid du Colombier 	char *p, *r, *w;
10080ee5cbfSDavid du Colombier 
10180ee5cbfSDavid du Colombier 	if(s == nil)
10280ee5cbfSDavid du Colombier 		return nil;
10380ee5cbfSDavid du Colombier 
10480ee5cbfSDavid du Colombier 	for(p=s; *p; p++)
10580ee5cbfSDavid du Colombier 		if(*p=='+')
10680ee5cbfSDavid du Colombier 			*p = ' ';
10780ee5cbfSDavid du Colombier 	s = _urlunesc(s);
10880ee5cbfSDavid du Colombier 
10980ee5cbfSDavid du Colombier 	for(r=w=s; *r; r++){
11080ee5cbfSDavid du Colombier 		if(*r != '\r')
11180ee5cbfSDavid du Colombier 			*w++ = *r;
11280ee5cbfSDavid du Colombier 	}
11380ee5cbfSDavid du Colombier 	*w = '\0';
11480ee5cbfSDavid du Colombier 	return s;
11580ee5cbfSDavid du Colombier }
11680ee5cbfSDavid du Colombier 
11780ee5cbfSDavid du Colombier void
1189a747e4fSDavid du Colombier mountwiki(HConnect *c, char *service)
11980ee5cbfSDavid du Colombier {
1209a747e4fSDavid du Colombier 	char buf[64];
12180ee5cbfSDavid du Colombier 	int fd;
12280ee5cbfSDavid du Colombier 
12380ee5cbfSDavid du Colombier 	snprint(buf, sizeof buf, "/srv/wiki.%s", service);
12480ee5cbfSDavid du Colombier 	if((fd = open(buf, ORDWR)) < 0){
1259a747e4fSDavid du Colombier 		syslog(0, LOG, "%s open %s failed: %r", buf, hp->remotesys);
1269a747e4fSDavid du Colombier 		hfail(c, HNotFound);
12780ee5cbfSDavid du Colombier 		exits("failed");
12880ee5cbfSDavid du Colombier 	}
1299a747e4fSDavid du Colombier 	if(mount(fd, -1, "/mnt/wiki", MREPL, "") < 0){
1309a747e4fSDavid du Colombier 		syslog(0, LOG, "%s mount /mnt/wiki failed: %r", hp->remotesys);
1319a747e4fSDavid du Colombier 		hfail(c, HNotFound);
13280ee5cbfSDavid du Colombier 		exits("failed");
13380ee5cbfSDavid du Colombier 	}
13480ee5cbfSDavid du Colombier 	close(fd);
13580ee5cbfSDavid du Colombier }
13680ee5cbfSDavid du Colombier 
13780ee5cbfSDavid du Colombier char*
1389a747e4fSDavid du Colombier dowiki(HConnect *c, char *title, char *author, char *comment, char *base, ulong version, char *text)
13980ee5cbfSDavid du Colombier {
14080ee5cbfSDavid du Colombier 	int fd, l, n, err;
1419a747e4fSDavid du Colombier 	char *p, tmp[256];
1429a747e4fSDavid du Colombier int i;
14380ee5cbfSDavid du Colombier 
14480ee5cbfSDavid du Colombier 	if((fd = open("/mnt/wiki/new", ORDWR)) < 0){
1459a747e4fSDavid du Colombier 		syslog(0, LOG, "%s open /mnt/wiki/new failed: %r", hp->remotesys);
1469a747e4fSDavid du Colombier 		hfail(c, HNotFound);
14780ee5cbfSDavid du Colombier 		exits("failed");
14880ee5cbfSDavid du Colombier 	}
14980ee5cbfSDavid du Colombier 
1509a747e4fSDavid du Colombier i=0;
1519a747e4fSDavid du Colombier 	if((i++,fprint(fd, "%s\nD%lud\nA%s (%s)\n", title, version, author, hp->remotesys) < 0)
1529a747e4fSDavid du Colombier 	|| (i++,(comment && comment[0] && fprint(fd, "C%s\n", comment) < 0))
1539a747e4fSDavid du Colombier 	|| (i++,fprint(fd, "\n") < 0)
154*453ee12cSDavid du Colombier 	|| (i++,(text[0] && write(fd, text, strlen(text)) != strlen(text)))){
1559a747e4fSDavid du Colombier 		syslog(0, LOG, "%s write failed %d %ld fd %d: %r", hp->remotesys, i, strlen(text), fd);
1569a747e4fSDavid du Colombier 		hfail(c, HInternal);
15780ee5cbfSDavid du Colombier 		exits("failed");
15880ee5cbfSDavid du Colombier 	}
15980ee5cbfSDavid du Colombier 
16080ee5cbfSDavid du Colombier 	err = write(fd, "", 0);
16180ee5cbfSDavid du Colombier 	if(err)
1629a747e4fSDavid du Colombier 		syslog(0, LOG, "%s commit failed %d: %r", hp->remotesys, err);
16380ee5cbfSDavid du Colombier 
16480ee5cbfSDavid du Colombier 	seek(fd, 0, 0);
16580ee5cbfSDavid du Colombier 	if((n = read(fd, tmp, sizeof(tmp)-1)) <= 0){
1669a747e4fSDavid du Colombier 		if(n == 0)
1679a747e4fSDavid du Colombier 			werrstr("short read");
1689a747e4fSDavid du Colombier 		syslog(0, LOG, "%s read failed: %r", hp->remotesys);
1699a747e4fSDavid du Colombier 		hfail(c, HInternal);
17080ee5cbfSDavid du Colombier 		exits("failed");
17180ee5cbfSDavid du Colombier 	}
17280ee5cbfSDavid du Colombier 
17380ee5cbfSDavid du Colombier 	tmp[n] = '\0';
17480ee5cbfSDavid du Colombier 
1759a747e4fSDavid du Colombier 	p = halloc(c, l=strlen(base)+strlen(tmp)+40);
1769a747e4fSDavid du Colombier 	snprint(p, l, "%s/%s/%s.html", base, tmp, err ? "werror" : "index");
17780ee5cbfSDavid du Colombier 	return p;
17880ee5cbfSDavid du Colombier }
17980ee5cbfSDavid du Colombier 
18080ee5cbfSDavid du Colombier 
18180ee5cbfSDavid du Colombier void
18280ee5cbfSDavid du Colombier main(int argc, char **argv)
18380ee5cbfSDavid du Colombier {
18480ee5cbfSDavid du Colombier 	Hio *hin, *hout;
18580ee5cbfSDavid du Colombier 	char *s, *t, *p, *f[10];
18680ee5cbfSDavid du Colombier 	char *text, *title, *service, *base, *author, *comment, *url;
18780ee5cbfSDavid du Colombier 	int i, nf;
18880ee5cbfSDavid du Colombier 	ulong version;
18980ee5cbfSDavid du Colombier 
1909a747e4fSDavid du Colombier 	hc = init(argc, argv);
1919a747e4fSDavid du Colombier 	hp = hc->private;
19280ee5cbfSDavid du Colombier 
1939a747e4fSDavid du Colombier 	if(dangerous(hc->req.uri)){
1949a747e4fSDavid du Colombier 		hfail(hc, HSyntax);
19580ee5cbfSDavid du Colombier 		exits("failed");
19680ee5cbfSDavid du Colombier 	}
19780ee5cbfSDavid du Colombier 
1989a747e4fSDavid du Colombier 	if(hparseheaders(hc, HSTIMEOUT) < 0)
19980ee5cbfSDavid du Colombier 		exits("failed");
2009a747e4fSDavid du Colombier 	hout = &hc->hout;
2019a747e4fSDavid du Colombier 	if(hc->head.expectother){
2029a747e4fSDavid du Colombier 		hfail(hc, HExpectFail, nil);
20380ee5cbfSDavid du Colombier 		exits("failed");
20480ee5cbfSDavid du Colombier 	}
2059a747e4fSDavid du Colombier 	if(hc->head.expectcont){
20680ee5cbfSDavid du Colombier 		hprint(hout, "100 Continue\r\n");
20780ee5cbfSDavid du Colombier 		hprint(hout, "\r\n");
20880ee5cbfSDavid du Colombier 		hflush(hout);
20980ee5cbfSDavid du Colombier 	}
21080ee5cbfSDavid du Colombier 
21180ee5cbfSDavid du Colombier 	s = nil;
2129a747e4fSDavid du Colombier 	if(strcmp(hc->req.meth, "POST") == 0){
2139a747e4fSDavid du Colombier 		hin = hbodypush(&hc->hin, hc->head.contlen, hc->head.transenc);
21480ee5cbfSDavid du Colombier 		if(hin != nil){
21580ee5cbfSDavid du Colombier 			alarm(15*60*1000);
21680ee5cbfSDavid du Colombier 			s = hreadbuf(hin, hin->pos);
21780ee5cbfSDavid du Colombier 			alarm(0);
21880ee5cbfSDavid du Colombier 		}
21980ee5cbfSDavid du Colombier 		if(s == nil){
2209a747e4fSDavid du Colombier 			hfail(hc, HBadReq, nil);
22180ee5cbfSDavid du Colombier 			exits("failed");
22280ee5cbfSDavid du Colombier 		}
22380ee5cbfSDavid du Colombier 		t = strchr(s, '\n');
22480ee5cbfSDavid du Colombier 		if(t != nil)
22580ee5cbfSDavid du Colombier 			*t = '\0';
22680ee5cbfSDavid du Colombier 	}else{
2279a747e4fSDavid du Colombier 		hunallowed(hc, "GET, HEAD, PUT");
22880ee5cbfSDavid du Colombier 		exits("unallowed");
2299a747e4fSDavid du Colombier 	}
23080ee5cbfSDavid du Colombier 
23180ee5cbfSDavid du Colombier 	if(s == nil){
2329a747e4fSDavid du Colombier 		hfail(hc, HNoData, "wiki");
23380ee5cbfSDavid du Colombier 		exits("failed");
23480ee5cbfSDavid du Colombier 	}
23580ee5cbfSDavid du Colombier 
23680ee5cbfSDavid du Colombier 	text = nil;
23780ee5cbfSDavid du Colombier 	title = nil;
23880ee5cbfSDavid du Colombier 	service = nil;
23980ee5cbfSDavid du Colombier 	author = "???";
24080ee5cbfSDavid du Colombier 	comment = "";
24180ee5cbfSDavid du Colombier 	base = nil;
24280ee5cbfSDavid du Colombier 	version = ~0;
24380ee5cbfSDavid du Colombier 	nf = getfields(s, f, nelem(f), 1, "&");
24480ee5cbfSDavid du Colombier 	for(i=0; i<nf; i++){
24580ee5cbfSDavid du Colombier 		if((p = strchr(f[i], '=')) == nil)
24680ee5cbfSDavid du Colombier 			continue;
24780ee5cbfSDavid du Colombier 		*p++ = '\0';
24880ee5cbfSDavid du Colombier 		if(strcmp(f[i], "title")==0)
24980ee5cbfSDavid du Colombier 			title = p;
25080ee5cbfSDavid du Colombier 		else if(strcmp(f[i], "version")==0)
25180ee5cbfSDavid du Colombier 			version = strtoul(unhttp(p), 0, 10);
25280ee5cbfSDavid du Colombier 		else if(strcmp(f[i], "text")==0)
25380ee5cbfSDavid du Colombier 			text = p;
25480ee5cbfSDavid du Colombier 		else if(strcmp(f[i], "service")==0)
25580ee5cbfSDavid du Colombier 			service = p;
25680ee5cbfSDavid du Colombier 		else if(strcmp(f[i], "comment")==0)
25780ee5cbfSDavid du Colombier 			comment = p;
25880ee5cbfSDavid du Colombier 		else if(strcmp(f[i], "author")==0)
25980ee5cbfSDavid du Colombier 			author = p;
26080ee5cbfSDavid du Colombier 		else if(strcmp(f[i], "base")==0)
26180ee5cbfSDavid du Colombier 			base = p;
26280ee5cbfSDavid du Colombier 	}
26380ee5cbfSDavid du Colombier 
26480ee5cbfSDavid du Colombier 	syslog(0, LOG, "%s post s %s t '%s' v %ld a %s c %s b %s t 0x%p",
2659a747e4fSDavid du Colombier 		hp->remotesys, service, title, (long)version, author, comment, base, text);
26680ee5cbfSDavid du Colombier 
26780ee5cbfSDavid du Colombier 	title = unhttp(title);
26880ee5cbfSDavid du Colombier 	comment = unhttp(comment);
26980ee5cbfSDavid du Colombier 	service = unhttp(service);
27080ee5cbfSDavid du Colombier 	text = unhttp(text);
27180ee5cbfSDavid du Colombier 	author = unhttp(author);
27280ee5cbfSDavid du Colombier 	base = unhttp(base);
27380ee5cbfSDavid du Colombier 
27480ee5cbfSDavid du Colombier 	if(title==nil || version==~0 || text==nil || text[0]=='\0' || base == nil
27580ee5cbfSDavid du Colombier 	|| service == nil || strchr(title, '\n') || strchr(comment, '\n')
2769a747e4fSDavid du Colombier 	|| dangerous(service) || strchr(service, '/') || strlen(service)>20){
2779a747e4fSDavid du Colombier 		syslog(0, LOG, "%s failed dangerous", hp->remotesys);
2789a747e4fSDavid du Colombier 		hfail(hc, HSyntax);
27980ee5cbfSDavid du Colombier 		exits("failed");
28080ee5cbfSDavid du Colombier 	}
28180ee5cbfSDavid du Colombier 
28280ee5cbfSDavid du Colombier 	syslog(0, LOG, "%s post s %s t '%s' v %ld a %s c %s",
2839a747e4fSDavid du Colombier 		hp->remotesys, service, title, (long)version, author, comment);
28480ee5cbfSDavid du Colombier 
28580ee5cbfSDavid du Colombier 	if(strlen(text) > MaxLog)
28680ee5cbfSDavid du Colombier 		text[MaxLog] = '\0';
28780ee5cbfSDavid du Colombier 
2889a747e4fSDavid du Colombier 	mountwiki(hc, service);
2899a747e4fSDavid du Colombier 	url = dowiki(hc, title, author, comment, base, version, text);
2909a747e4fSDavid du Colombier 	hredirected(hc, "303 See Other", url);
29180ee5cbfSDavid du Colombier 	exits(nil);
29280ee5cbfSDavid du Colombier }
293