/*
* Accept new wiki pages or modifications to existing ones via POST method.
*
* Talks to the server at /srv/wiki.service.
*/
#include <u.h>
#include <libc.h>
#include <bio.h>
#include "httpd.h"
#include "httpsrv.h"
#define LOG "wiki"
HConnect *hc;
HSPriv *hp;
/* go from possibly-latin1 url with escapes to utf */
char *
_urlunesc(char *s)
{
char *t, *v, *u;
Rune r;
int c, n;
/* unescape */
u = halloc(hc, strlen(s)+1);
for(t = u; c = *s; s++){
if(c == '%'){
n = s[1];
if(n >= '0' && n <= '9')
n = n - '0';
else if(n >= 'A' && n <= 'F')
n = n - 'A' + 10;
else if(n >= 'a' && n <= 'f')
n = n - 'a' + 10;
else
break;
r = n;
n = s[2];
if(n >= '0' && n <= '9')
n = n - '0';
else if(n >= 'A' && n <= 'F')
n = n - 'A' + 10;
else if(n >= 'a' && n <= 'f')
n = n - 'a' + 10;
else
break;
s += 2;
c = r*16+n;
}
*t++ = c;
}
*t = 0;
/* latin1 heuristic */
v = halloc(hc, UTFmax*strlen(u) + 1);
s = u;
t = v;
while(*s){
/* in decoding error, assume latin1 */
if((n=chartorune(&r, s)) == 1 && r == 0x80)
r = *s;
s += n;
t += runetochar(t, &r);
}
*t = 0;
return v;
}
enum
{
MaxLog = 100*1024, /* limit on length of any one log request */
};
static int
dangerous(char *s)
{
if(s == nil)
return 1;
/*
* This check shouldn't be needed;
* filename folding is already supposed to have happened.
* But I'm paranoid.
*/
while(s = strchr(s,'/')){
if(s[1]=='.' && s[2]=='.')
return 1;
s++;
}
return 0;
}
char*
unhttp(char *s)
{
char *p, *r, *w;
if(s == nil)
return nil;
for(p=s; *p; p++)
if(*p=='+')
*p = ' ';
s = _urlunesc(s);
for(r=w=s; *r; r++){
if(*r != '\r')
*w++ = *r;
}
*w = '\0';
return s;
}
void
mountwiki(HConnect *c, char *service)
{
char buf[128];
int fd;
/* already in (possibly private) namespace? */
snprint(buf, sizeof buf, "/mnt/wiki.%s/new", service);
if (access(buf, AREAD) == 0){
if (bind(buf, "/mnt/wiki", MREPL) < 0){
syslog(0, LOG, "%s bind /mnt/wiki failed: %r",
hp->remotesys);
hfail(c, HNotFound);
exits("bind /mnt/wiki failed");
}
return;
}
/* old way: public wikifs from /srv */
snprint(buf, sizeof buf, "/srv/wiki.%s", service);
if((fd = open(buf, ORDWR)) < 0){
syslog(0, LOG, "%s open %s failed: %r", buf, hp->remotesys);
hfail(c, HNotFound);
exits("failed");
}
if(mount(fd, -1, "/mnt/wiki", MREPL, "") < 0){
syslog(0, LOG, "%s mount /mnt/wiki failed: %r", hp->remotesys);
hfail(c, HNotFound);
exits("failed");
}
close(fd);
}
char*
dowiki(HConnect *c, char *title, char *author, char *comment, char *base, ulong version, char *text)
{
int fd, l, n, err;
char *p, tmp[256];
int i;
if((fd = open("/mnt/wiki/new", ORDWR)) < 0){
syslog(0, LOG, "%s open /mnt/wiki/new failed: %r", hp->remotesys);
hfail(c, HNotFound);
exits("failed");
}
i=0;
if((i++,fprint(fd, "%s\nD%lud\nA%s (%s)\n", title, version, author, hp->remotesys) < 0)
|| (i++,(comment && comment[0] && fprint(fd, "C%s\n", comment) < 0))
|| (i++,fprint(fd, "\n") < 0)
|| (i++,(text[0] && write(fd, text, strlen(text)) != strlen(text)))){
syslog(0, LOG, "%s write failed %d %ld fd %d: %r", hp->remotesys, i, strlen(text), fd);
hfail(c, HInternal);
exits("failed");
}
err = write(fd, "", 0);
if(err)
syslog(0, LOG, "%s commit failed %d: %r", hp->remotesys, err);
seek(fd, 0, 0);
if((n = read(fd, tmp, sizeof(tmp)-1)) <= 0){
if(n == 0)
werrstr("short read");
syslog(0, LOG, "%s read failed: %r", hp->remotesys);
hfail(c, HInternal);
exits("failed");
}
tmp[n] = '\0';
p = halloc(c, l=strlen(base)+strlen(tmp)+40);
snprint(p, l, "%s/%s/%s.html", base, tmp, err ? "werror" : "index");
return p;
}
void
main(int argc, char **argv)
{
Hio *hin, *hout;
char *s, *t, *p, *f[10];
char *text, *title, *service, *base, *author, *comment, *url;
int i, nf;
ulong version;
hc = init(argc, argv);
hp = hc->private;
if(dangerous(hc->req.uri)){
hfail(hc, HSyntax);
exits("failed");
}
if(hparseheaders(hc, HSTIMEOUT) < 0)
exits("failed");
hout = &hc->hout;
if(hc->head.expectother){
hfail(hc, HExpectFail, nil);
exits("failed");
}
if(hc->head.expectcont){
hprint(hout, "100 Continue\r\n");
hprint(hout, "\r\n");
hflush(hout);
}
s = nil;
if(strcmp(hc->req.meth, "POST") == 0){
hin = hbodypush(&hc->hin, hc->head.contlen, hc->head.transenc);
if(hin != nil){
alarm(15*60*1000);
s = hreadbuf(hin, hin->pos);
alarm(0);
}
if(s == nil){
hfail(hc, HBadReq, nil);
exits("failed");
}
t = strchr(s, '\n');
if(t != nil)
*t = '\0';
}else{
hunallowed(hc, "GET, HEAD, PUT");
exits("unallowed");
}
if(s == nil){
hfail(hc, HNoData, "wiki");
exits("failed");
}
text = nil;
title = nil;
service = nil;
author = "???";
comment = "";
base = nil;
version = ~0;
nf = getfields(s, f, nelem(f), 1, "&");
for(i=0; i<nf; i++){
if((p = strchr(f[i], '=')) == nil)
continue;
*p++ = '\0';
if(strcmp(f[i], "title")==0)
title = p;
else if(strcmp(f[i], "version")==0)
version = strtoul(unhttp(p), 0, 10);
else if(strcmp(f[i], "text")==0)
text = p;
else if(strcmp(f[i], "service")==0)
service = p;
else if(strcmp(f[i], "comment")==0)
comment = p;
else if(strcmp(f[i], "author")==0)
author = p;
else if(strcmp(f[i], "base")==0)
base = p;
}
syslog(0, LOG, "%s post s %s t '%s' v %ld a %s c %s b %s t 0x%p",
hp->remotesys, service, title, (long)version, author, comment, base, text);
title = unhttp(title);
comment = unhttp(comment);
service = unhttp(service);
text = unhttp(text);
author = unhttp(author);
base = unhttp(base);
if(title==nil || version==~0 || text==nil || text[0]=='\0' || base == nil
|| service == nil || strchr(title, '\n') || strchr(comment, '\n')
|| dangerous(service) || strchr(service, '/') || strlen(service)>20){
syslog(0, LOG, "%s failed dangerous", hp->remotesys);
hfail(hc, HSyntax);
exits("failed");
}
syslog(0, LOG, "%s post s %s t '%s' v %ld a %s c %s",
hp->remotesys, service, title, (long)version, author, comment);
if(strlen(text) > MaxLog)
text[MaxLog] = '\0';
mountwiki(hc, service);
url = dowiki(hc, title, author, comment, base, version, text);
hredirected(hc, "303 See Other", url);
exits(nil);
}
|