Subversion Repositories planix.SVN

Rev

Rev 2 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 - 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 *
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
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*
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
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*
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
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
}