wikipost.c 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  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. #define LOG "wiki"
  12. HConnect *hc;
  13. HSPriv *hp;
  14. /* go from possibly-latin1 url with escapes to utf */
  15. char *
  16. _urlunesc(char *s)
  17. {
  18. char *t, *v, *u;
  19. Rune r;
  20. int c, n;
  21. /* unescape */
  22. u = halloc(hc, strlen(s)+1);
  23. for(t = u; c = *s; s++){
  24. if(c == '%'){
  25. n = s[1];
  26. if(n >= '0' && n <= '9')
  27. n = n - '0';
  28. else if(n >= 'A' && n <= 'F')
  29. n = n - 'A' + 10;
  30. else if(n >= 'a' && n <= 'f')
  31. n = n - 'a' + 10;
  32. else
  33. break;
  34. r = n;
  35. n = s[2];
  36. if(n >= '0' && n <= '9')
  37. n = n - '0';
  38. else if(n >= 'A' && n <= 'F')
  39. n = n - 'A' + 10;
  40. else if(n >= 'a' && n <= 'f')
  41. n = n - 'a' + 10;
  42. else
  43. break;
  44. s += 2;
  45. c = r*16+n;
  46. }
  47. *t++ = c;
  48. }
  49. *t = 0;
  50. /* latin1 heuristic */
  51. v = halloc(hc, UTFmax*strlen(u) + 1);
  52. s = u;
  53. t = v;
  54. while(*s){
  55. /* in decoding error, assume latin1 */
  56. if((n=chartorune(&r, s)) == 1 && r == 0x80)
  57. r = *s;
  58. s += n;
  59. t += runetochar(t, &r);
  60. }
  61. *t = 0;
  62. return v;
  63. }
  64. enum
  65. {
  66. MaxLog = 100*1024, /* limit on length of any one log request */
  67. };
  68. static int
  69. dangerous(char *s)
  70. {
  71. if(s == nil)
  72. return 1;
  73. /*
  74. * This check shouldn't be needed;
  75. * filename folding is already supposed to have happened.
  76. * But I'm paranoid.
  77. */
  78. while(s = strchr(s,'/')){
  79. if(s[1]=='.' && s[2]=='.')
  80. return 1;
  81. s++;
  82. }
  83. return 0;
  84. }
  85. char*
  86. unhttp(char *s)
  87. {
  88. char *p, *r, *w;
  89. if(s == nil)
  90. return nil;
  91. for(p=s; *p; p++)
  92. if(*p=='+')
  93. *p = ' ';
  94. s = _urlunesc(s);
  95. for(r=w=s; *r; r++){
  96. if(*r != '\r')
  97. *w++ = *r;
  98. }
  99. *w = '\0';
  100. return s;
  101. }
  102. void
  103. mountwiki(HConnect *c, char *service)
  104. {
  105. char buf[64];
  106. int fd;
  107. snprint(buf, sizeof buf, "/srv/wiki.%s", service);
  108. if((fd = open(buf, ORDWR)) < 0){
  109. syslog(0, LOG, "%s open %s failed: %r", buf, hp->remotesys);
  110. hfail(c, HNotFound);
  111. exits("failed");
  112. }
  113. if(mount(fd, -1, "/mnt/wiki", MREPL, "") < 0){
  114. syslog(0, LOG, "%s mount /mnt/wiki failed: %r", hp->remotesys);
  115. hfail(c, HNotFound);
  116. exits("failed");
  117. }
  118. close(fd);
  119. }
  120. char*
  121. dowiki(HConnect *c, char *title, char *author, char *comment, char *base, ulong version, char *text)
  122. {
  123. int fd, l, n, err;
  124. char *p, tmp[256];
  125. int i;
  126. if((fd = open("/mnt/wiki/new", ORDWR)) < 0){
  127. syslog(0, LOG, "%s open /mnt/wiki/new failed: %r", hp->remotesys);
  128. hfail(c, HNotFound);
  129. exits("failed");
  130. }
  131. i=0;
  132. if((i++,fprint(fd, "%s\nD%lud\nA%s (%s)\n", title, version, author, hp->remotesys) < 0)
  133. || (i++,(comment && comment[0] && fprint(fd, "C%s\n", comment) < 0))
  134. || (i++,fprint(fd, "\n") < 0)
  135. || (i++,(text[0] && write(fd, text, strlen(text)) != strlen(text)))){
  136. syslog(0, LOG, "%s write failed %d %ld fd %d: %r", hp->remotesys, i, strlen(text), fd);
  137. hfail(c, HInternal);
  138. exits("failed");
  139. }
  140. err = write(fd, "", 0);
  141. if(err)
  142. syslog(0, LOG, "%s commit failed %d: %r", hp->remotesys, err);
  143. seek(fd, 0, 0);
  144. if((n = read(fd, tmp, sizeof(tmp)-1)) <= 0){
  145. if(n == 0)
  146. werrstr("short read");
  147. syslog(0, LOG, "%s read failed: %r", hp->remotesys);
  148. hfail(c, HInternal);
  149. exits("failed");
  150. }
  151. tmp[n] = '\0';
  152. p = halloc(c, l=strlen(base)+strlen(tmp)+40);
  153. snprint(p, l, "%s/%s/%s.html", base, tmp, err ? "werror" : "index");
  154. return p;
  155. }
  156. void
  157. main(int argc, char **argv)
  158. {
  159. Hio *hin, *hout;
  160. char *s, *t, *p, *f[10];
  161. char *text, *title, *service, *base, *author, *comment, *url;
  162. int i, nf;
  163. ulong version;
  164. hc = init(argc, argv);
  165. hp = hc->private;
  166. if(dangerous(hc->req.uri)){
  167. hfail(hc, HSyntax);
  168. exits("failed");
  169. }
  170. if(hparseheaders(hc, HSTIMEOUT) < 0)
  171. exits("failed");
  172. hout = &hc->hout;
  173. if(hc->head.expectother){
  174. hfail(hc, HExpectFail, nil);
  175. exits("failed");
  176. }
  177. if(hc->head.expectcont){
  178. hprint(hout, "100 Continue\r\n");
  179. hprint(hout, "\r\n");
  180. hflush(hout);
  181. }
  182. s = nil;
  183. if(strcmp(hc->req.meth, "POST") == 0){
  184. hin = hbodypush(&hc->hin, hc->head.contlen, hc->head.transenc);
  185. if(hin != nil){
  186. alarm(15*60*1000);
  187. s = hreadbuf(hin, hin->pos);
  188. alarm(0);
  189. }
  190. if(s == nil){
  191. hfail(hc, HBadReq, nil);
  192. exits("failed");
  193. }
  194. t = strchr(s, '\n');
  195. if(t != nil)
  196. *t = '\0';
  197. }else{
  198. hunallowed(hc, "GET, HEAD, PUT");
  199. exits("unallowed");
  200. }
  201. if(s == nil){
  202. hfail(hc, HNoData, "wiki");
  203. exits("failed");
  204. }
  205. text = nil;
  206. title = nil;
  207. service = nil;
  208. author = "???";
  209. comment = "";
  210. base = nil;
  211. version = ~0;
  212. nf = getfields(s, f, nelem(f), 1, "&");
  213. for(i=0; i<nf; i++){
  214. if((p = strchr(f[i], '=')) == nil)
  215. continue;
  216. *p++ = '\0';
  217. if(strcmp(f[i], "title")==0)
  218. title = p;
  219. else if(strcmp(f[i], "version")==0)
  220. version = strtoul(unhttp(p), 0, 10);
  221. else if(strcmp(f[i], "text")==0)
  222. text = p;
  223. else if(strcmp(f[i], "service")==0)
  224. service = p;
  225. else if(strcmp(f[i], "comment")==0)
  226. comment = p;
  227. else if(strcmp(f[i], "author")==0)
  228. author = p;
  229. else if(strcmp(f[i], "base")==0)
  230. base = p;
  231. }
  232. syslog(0, LOG, "%s post s %s t '%s' v %ld a %s c %s b %s t 0x%p",
  233. hp->remotesys, service, title, (long)version, author, comment, base, text);
  234. title = unhttp(title);
  235. comment = unhttp(comment);
  236. service = unhttp(service);
  237. text = unhttp(text);
  238. author = unhttp(author);
  239. base = unhttp(base);
  240. if(title==nil || version==~0 || text==nil || text[0]=='\0' || base == nil
  241. || service == nil || strchr(title, '\n') || strchr(comment, '\n')
  242. || dangerous(service) || strchr(service, '/') || strlen(service)>20){
  243. syslog(0, LOG, "%s failed dangerous", hp->remotesys);
  244. hfail(hc, HSyntax);
  245. exits("failed");
  246. }
  247. syslog(0, LOG, "%s post s %s t '%s' v %ld a %s c %s",
  248. hp->remotesys, service, title, (long)version, author, comment);
  249. if(strlen(text) > MaxLog)
  250. text[MaxLog] = '\0';
  251. mountwiki(hc, service);
  252. url = dowiki(hc, title, author, comment, base, version, text);
  253. hredirected(hc, "303 See Other", url);
  254. exits(nil);
  255. }