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