xref: /plan9/sys/src/cmd/ip/httpd/wikipost.c (revision b39189fd423aed869c5cf5189bc504918cff969b)
1 /*
2  * Accept new wiki pages or modifications to existing ones via POST method.
3  *
4  * Talks to the server at /srv/wiki.service.
5  */
6 #include <u.h>
7 #include <libc.h>
8 #include <bio.h>
9 #include "httpd.h"
10 #include "httpsrv.h"
11 
12 #define LOG "wiki"
13 
14 HConnect *hc;
15 HSPriv *hp;
16 
17 
18 /* go from possibly-latin1 url with escapes to utf */
19 char *
_urlunesc(char * s)20 _urlunesc(char *s)
21 {
22 	char *t, *v, *u;
23 	Rune r;
24 	int c, n;
25 
26 	/* unescape */
27 	u = halloc(hc, strlen(s)+1);
28 	for(t = u; c = *s; s++){
29 		if(c == '%'){
30 			n = s[1];
31 			if(n >= '0' && n <= '9')
32 				n = n - '0';
33 			else if(n >= 'A' && n <= 'F')
34 				n = n - 'A' + 10;
35 			else if(n >= 'a' && n <= 'f')
36 				n = n - 'a' + 10;
37 			else
38 				break;
39 			r = n;
40 			n = s[2];
41 			if(n >= '0' && n <= '9')
42 				n = n - '0';
43 			else if(n >= 'A' && n <= 'F')
44 				n = n - 'A' + 10;
45 			else if(n >= 'a' && n <= 'f')
46 				n = n - 'a' + 10;
47 			else
48 				break;
49 			s += 2;
50 			c = r*16+n;
51 		}
52 		*t++ = c;
53 	}
54 	*t = 0;
55 
56 	/* latin1 heuristic */
57 	v = halloc(hc, UTFmax*strlen(u) + 1);
58 	s = u;
59 	t = v;
60 	while(*s){
61 		/* in decoding error, assume latin1 */
62 		if((n=chartorune(&r, s)) == 1 && r == 0x80)
63 			r = *s;
64 		s += n;
65 		t += runetochar(t, &r);
66 	}
67 	*t = 0;
68 
69 	return v;
70 }
71 
72 enum
73 {
74 	MaxLog		= 100*1024,		/* limit on length of any one log request */
75 };
76 
77 static int
dangerous(char * s)78 dangerous(char *s)
79 {
80 	if(s == nil)
81 		return 1;
82 
83 	/*
84 	 * This check shouldn't be needed;
85 	 * filename folding is already supposed to have happened.
86 	 * But I'm paranoid.
87 	 */
88 	while(s = strchr(s,'/')){
89 		if(s[1]=='.' && s[2]=='.')
90 			return 1;
91 		s++;
92 	}
93 	return 0;
94 }
95 
96 char*
unhttp(char * s)97 unhttp(char *s)
98 {
99 	char *p, *r, *w;
100 
101 	if(s == nil)
102 		return nil;
103 
104 	for(p=s; *p; p++)
105 		if(*p=='+')
106 			*p = ' ';
107 	s = _urlunesc(s);
108 
109 	for(r=w=s; *r; r++){
110 		if(*r != '\r')
111 			*w++ = *r;
112 	}
113 	*w = '\0';
114 	return s;
115 }
116 
117 void
mountwiki(HConnect * c,char * service)118 mountwiki(HConnect *c, char *service)
119 {
120 	char buf[128];
121 	int fd;
122 
123 	/* already in (possibly private) namespace? */
124 	snprint(buf, sizeof buf, "/mnt/wiki.%s/new", service);
125 	if (access(buf, AREAD) == 0){
126 		if (bind(buf, "/mnt/wiki", MREPL) < 0){
127 			syslog(0, LOG, "%s bind /mnt/wiki failed: %r",
128 				hp->remotesys);
129 			hfail(c, HNotFound);
130 			exits("bind /mnt/wiki failed");
131 		}
132 		return;
133 	}
134 
135 	/* old way: public wikifs from /srv */
136 	snprint(buf, sizeof buf, "/srv/wiki.%s", service);
137 	if((fd = open(buf, ORDWR)) < 0){
138 		syslog(0, LOG, "%s open %s failed: %r", buf, hp->remotesys);
139 		hfail(c, HNotFound);
140 		exits("failed");
141 	}
142 	if(mount(fd, -1, "/mnt/wiki", MREPL, "") < 0){
143 		syslog(0, LOG, "%s mount /mnt/wiki failed: %r", hp->remotesys);
144 		hfail(c, HNotFound);
145 		exits("failed");
146 	}
147 	close(fd);
148 }
149 
150 char*
dowiki(HConnect * c,char * title,char * author,char * comment,char * base,ulong version,char * text)151 dowiki(HConnect *c, char *title, char *author, char *comment, char *base, ulong version, char *text)
152 {
153 	int fd, l, n, err;
154 	char *p, tmp[256];
155 int i;
156 
157 	if((fd = open("/mnt/wiki/new", ORDWR)) < 0){
158 		syslog(0, LOG, "%s open /mnt/wiki/new failed: %r", hp->remotesys);
159 		hfail(c, HNotFound);
160 		exits("failed");
161 	}
162 
163 i=0;
164 	if((i++,fprint(fd, "%s\nD%lud\nA%s (%s)\n", title, version, author, hp->remotesys) < 0)
165 	|| (i++,(comment && comment[0] && fprint(fd, "C%s\n", comment) < 0))
166 	|| (i++,fprint(fd, "\n") < 0)
167 	|| (i++,(text[0] && write(fd, text, strlen(text)) != strlen(text)))){
168 		syslog(0, LOG, "%s write failed %d %ld fd %d: %r", hp->remotesys, i, strlen(text), fd);
169 		hfail(c, HInternal);
170 		exits("failed");
171 	}
172 
173 	err = write(fd, "", 0);
174 	if(err)
175 		syslog(0, LOG, "%s commit failed %d: %r", hp->remotesys, err);
176 
177 	seek(fd, 0, 0);
178 	if((n = read(fd, tmp, sizeof(tmp)-1)) <= 0){
179 		if(n == 0)
180 			werrstr("short read");
181 		syslog(0, LOG, "%s read failed: %r", hp->remotesys);
182 		hfail(c, HInternal);
183 		exits("failed");
184 	}
185 
186 	tmp[n] = '\0';
187 
188 	p = halloc(c, l=strlen(base)+strlen(tmp)+40);
189 	snprint(p, l, "%s/%s/%s.html", base, tmp, err ? "werror" : "index");
190 	return p;
191 }
192 
193 
194 void
main(int argc,char ** argv)195 main(int argc, char **argv)
196 {
197 	Hio *hin, *hout;
198 	char *s, *t, *p, *f[10];
199 	char *text, *title, *service, *base, *author, *comment, *url;
200 	int i, nf;
201 	ulong version;
202 
203 	hc = init(argc, argv);
204 	hp = hc->private;
205 
206 	if(dangerous(hc->req.uri)){
207 		hfail(hc, HSyntax);
208 		exits("failed");
209 	}
210 
211 	if(hparseheaders(hc, HSTIMEOUT) < 0)
212 		exits("failed");
213 	hout = &hc->hout;
214 	if(hc->head.expectother){
215 		hfail(hc, HExpectFail, nil);
216 		exits("failed");
217 	}
218 	if(hc->head.expectcont){
219 		hprint(hout, "100 Continue\r\n");
220 		hprint(hout, "\r\n");
221 		hflush(hout);
222 	}
223 
224 	s = nil;
225 	if(strcmp(hc->req.meth, "POST") == 0){
226 		hin = hbodypush(&hc->hin, hc->head.contlen, hc->head.transenc);
227 		if(hin != nil){
228 			alarm(15*60*1000);
229 			s = hreadbuf(hin, hin->pos);
230 			alarm(0);
231 		}
232 		if(s == nil){
233 			hfail(hc, HBadReq, nil);
234 			exits("failed");
235 		}
236 		t = strchr(s, '\n');
237 		if(t != nil)
238 			*t = '\0';
239 	}else{
240 		hunallowed(hc, "GET, HEAD, PUT");
241 		exits("unallowed");
242 	}
243 
244 	if(s == nil){
245 		hfail(hc, HNoData, "wiki");
246 		exits("failed");
247 	}
248 
249 	text = nil;
250 	title = nil;
251 	service = nil;
252 	author = "???";
253 	comment = "";
254 	base = nil;
255 	version = ~0;
256 	nf = getfields(s, f, nelem(f), 1, "&");
257 	for(i=0; i<nf; i++){
258 		if((p = strchr(f[i], '=')) == nil)
259 			continue;
260 		*p++ = '\0';
261 		if(strcmp(f[i], "title")==0)
262 			title = p;
263 		else if(strcmp(f[i], "version")==0)
264 			version = strtoul(unhttp(p), 0, 10);
265 		else if(strcmp(f[i], "text")==0)
266 			text = p;
267 		else if(strcmp(f[i], "service")==0)
268 			service = p;
269 		else if(strcmp(f[i], "comment")==0)
270 			comment = p;
271 		else if(strcmp(f[i], "author")==0)
272 			author = p;
273 		else if(strcmp(f[i], "base")==0)
274 			base = p;
275 	}
276 
277 	syslog(0, LOG, "%s post s %s t '%s' v %ld a %s c %s b %s t 0x%p",
278 		hp->remotesys, service, title, (long)version, author, comment, base, text);
279 
280 	title = unhttp(title);
281 	comment = unhttp(comment);
282 	service = unhttp(service);
283 	text = unhttp(text);
284 	author = unhttp(author);
285 	base = unhttp(base);
286 
287 	if(title==nil || version==~0 || text==nil || text[0]=='\0' || base == nil
288 	|| service == nil || strchr(title, '\n') || strchr(comment, '\n')
289 	|| dangerous(service) || strchr(service, '/') || strlen(service)>20){
290 		syslog(0, LOG, "%s failed dangerous", hp->remotesys);
291 		hfail(hc, HSyntax);
292 		exits("failed");
293 	}
294 
295 	syslog(0, LOG, "%s post s %s t '%s' v %ld a %s c %s",
296 		hp->remotesys, service, title, (long)version, author, comment);
297 
298 	if(strlen(text) > MaxLog)
299 		text[MaxLog] = '\0';
300 
301 	mountwiki(hc, service);
302 	url = dowiki(hc, title, author, comment, base, version, text);
303 	hredirected(hc, "303 See Other", url);
304 	exits(nil);
305 }
306