]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/ip/httpd/wikipost.c
Import sources from 2011-03-30 iso image
[plan9front.git] / sys / src / cmd / ip / httpd / wikipost.c
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 }