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 } 306