1 #include <u.h>
2 #include <libc.h>
3 #include <bio.h>
4 #include <String.h>
5 #include <thread.h>
6 #include "wiki.h"
7
8 /*
9 * Get HTML and text templates from underlying file system.
10 * Caches them, which means changes don't take effect for
11 * up to Tcache seconds after they are made.
12 *
13 * If the files are deleted, we keep returning the last
14 * known copy.
15 */
16 enum {
17 WAIT = 60
18 };
19
20 static char *name[2*Ntemplate] = {
21 [Tpage] "page.html",
22 [Tedit] "edit.html",
23 [Tdiff] "diff.html",
24 [Thistory] "history.html",
25 [Tnew] "new.html",
26 [Toldpage] "oldpage.html",
27 [Twerror] "werror.html",
28 [Ntemplate+Tpage] "page.txt",
29 [Ntemplate+Tdiff] "diff.txt",
30 [Ntemplate+Thistory] "history.txt",
31 [Ntemplate+Toldpage] "oldpage.txt",
32 [Ntemplate+Twerror] "werror.txt",
33 };
34
35 static struct {
36 RWLock;
37 String *s;
38 ulong t;
39 Qid qid;
40 } cache[2*Ntemplate];
41
42 static void
cacheinit(void)43 cacheinit(void)
44 {
45 int i;
46 static int x;
47 static Lock l;
48
49 if(x)
50 return;
51 lock(&l);
52 if(x){
53 unlock(&l);
54 return;
55 }
56
57 for(i=0; i<2*Ntemplate; i++)
58 if(name[i])
59 cache[i].s = s_copy("");
60 x = 1;
61 unlock(&l);
62 }
63
64 static String*
gettemplate(int type)65 gettemplate(int type)
66 {
67 int n;
68 Biobuf *b;
69 Dir *d;
70 String *s, *ns;
71
72 if(name[type]==nil)
73 return nil;
74
75 cacheinit();
76
77 rlock(&cache[type]);
78 if(0 && cache[type].t+Tcache >= time(0)){
79 s = s_incref(cache[type].s);
80 runlock(&cache[type]);
81 return s;
82 }
83 runlock(&cache[type]);
84
85 // d = nil;
86 wlock(&cache[type]);
87 if(0 && cache[type].t+Tcache >= time(0) || (d = wdirstat(name[type])) == nil)
88 goto Return;
89
90 if(0 && d->qid.vers == cache[type].qid.vers && d->qid.path == cache[type].qid.path){
91 cache[type].t = time(0);
92 goto Return;
93 }
94
95 if((b = wBopen(name[type], OREAD)) == nil)
96 goto Return;
97
98 ns = s_reset(nil);
99 do
100 n = s_read(b, ns, Bsize);
101 while(n > 0);
102 Bterm(b);
103 if(n < 0) {
104 s_free(ns);
105 goto Return;
106 }
107
108 s_free(cache[type].s);
109 cache[type].s = ns;
110 cache[type].qid = d->qid;
111 cache[type].t = time(0);
112
113 Return:
114 free(d);
115 s = s_incref(cache[type].s);
116 wunlock(&cache[type]);
117 return s;
118 }
119
120
121 /*
122 * Write wiki document in HTML.
123 */
124 static String*
s_escappend(String * s,char * p,int pre)125 s_escappend(String *s, char *p, int pre)
126 {
127 char *q;
128
129 while(q = strpbrk(p, pre ? "<>&" : " <>&")){
130 s = s_nappend(s, p, q-p);
131 switch(*q){
132 case '<':
133 s = s_append(s, "<");
134 break;
135 case '>':
136 s = s_append(s, ">");
137 break;
138 case '&':
139 s = s_append(s, "&");
140 break;
141 case ' ':
142 s = s_append(s, "\n");
143 }
144 p = q+1;
145 }
146 s = s_append(s, p);
147 return s;
148 }
149
150 static char*
mkurl(char * s,int ty)151 mkurl(char *s, int ty)
152 {
153 char *p, *q;
154
155 if(strncmp(s, "http:", 5)==0
156 || strncmp(s, "https:", 6)==0
157 || strncmp(s, "#", 1)==0
158 || strncmp(s, "ftp:", 4)==0
159 || strncmp(s, "mailto:", 7)==0
160 || strncmp(s, "telnet:", 7)==0
161 || strncmp(s, "file:", 5)==0)
162 return estrdup(s);
163
164 if(strchr(s, ' ')==nil && strchr(s, '@')!=nil){
165 p = emalloc(strlen(s)+8);
166 strcpy(p, "mailto:");
167 strcat(p, s);
168 return p;
169 }
170
171 if(ty == Toldpage)
172 p = smprint("../../%s", s);
173 else
174 p = smprint("../%s", s);
175
176 for(q=p; *q; q++)
177 if(*q==' ')
178 *q = '_';
179 return p;
180 }
181
182 int okayinlist[Nwtxt] =
183 {
184 [Wbullet] 1,
185 [Wlink] 1,
186 [Wman] 1,
187 [Wplain] 1,
188 };
189
190 int okayinpre[Nwtxt] =
191 {
192 [Wlink] 1,
193 [Wman] 1,
194 [Wpre] 1,
195 };
196
197 int okayinpara[Nwtxt] =
198 {
199 [Wpara] 1,
200 [Wlink] 1,
201 [Wman] 1,
202 [Wplain] 1,
203 };
204
205 char*
nospaces(char * s)206 nospaces(char *s)
207 {
208 char *q;
209 s = strdup(s);
210 if(s == nil)
211 return nil;
212 for(q=s; *q; q++)
213 if(*q == ' ')
214 *q = '_';
215 return s;
216 }
217
218 String*
pagehtml(String * s,Wpage * wtxt,int ty)219 pagehtml(String *s, Wpage *wtxt, int ty)
220 {
221 char *p, tmp[40];
222 int inlist, inpara, inpre, t, tnext;
223 Wpage *w;
224
225 inlist = 0;
226 inpre = 0;
227 inpara = 0;
228
229 for(w=wtxt; w; w=w->next){
230 t = w->type;
231 tnext = Whr;
232 if(w->next)
233 tnext = w->next->type;
234
235 if(inlist && !okayinlist[t]){
236 inlist = 0;
237 s = s_append(s, "\n</li>\n</ul>\n");
238 }
239 if(inpre && !okayinpre[t]){
240 inpre = 0;
241 s = s_append(s, "</pre>\n");
242 }
243
244 switch(t){
245 case Wheading:
246 p = nospaces(w->text);
247 s = s_appendlist(s,
248 "\n<a name=\"", p, "\" /><h3>",
249 w->text, "</h3>\n", nil);
250 free(p);
251 break;
252
253 case Wpara:
254 if(inpara){
255 s = s_append(s, "\n</p>\n");
256 inpara = 0;
257 }
258 if(okayinpara[tnext]){
259 s = s_append(s, "\n<p class='para'>\n");
260 inpara = 1;
261 }
262 break;
263
264 case Wbullet:
265 if(!inlist){
266 inlist = 1;
267 s = s_append(s, "\n<ul>\n");
268 }else
269 s = s_append(s, "\n</li>\n");
270 s = s_append(s, "\n<li>\n");
271 break;
272
273 case Wlink:
274 if(w->url == nil)
275 p = mkurl(w->text, ty);
276 else
277 p = w->url;
278 s = s_appendlist(s, "<a href=\"", p, "\">", nil);
279 s = s_escappend(s, w->text, 0);
280 s = s_append(s, "</a>");
281 if(w->url == nil)
282 free(p);
283 break;
284
285 case Wman:
286 sprint(tmp, "%d", w->section);
287 s = s_appendlist(s,
288 "<a href=\"http://plan9.bell-labs.com/magic/man2html/",
289 tmp, "/", w->text, "\"><i>", w->text, "</i>(",
290 tmp, ")</a>", nil);
291 break;
292
293 case Wpre:
294 if(!inpre){
295 inpre = 1;
296 s = s_append(s, "\n<pre>\n");
297 }
298 s = s_escappend(s, w->text, 1);
299 s = s_append(s, "\n");
300 break;
301
302 case Whr:
303 s = s_append(s, "<hr />");
304 break;
305
306 case Wplain:
307 s = s_escappend(s, w->text, 0);
308 break;
309 }
310 }
311 if(inlist)
312 s = s_append(s, "\n</li>\n</ul>\n");
313 if(inpre)
314 s = s_append(s, "</pre>\n");
315 if(inpara)
316 s = s_append(s, "\n</p>\n");
317 return s;
318 }
319
320 static String*
copythru(String * s,char ** newp,int * nlinep,int l)321 copythru(String *s, char **newp, int *nlinep, int l)
322 {
323 char *oq, *q, *r;
324 int ol;
325
326 q = *newp;
327 oq = q;
328 ol = *nlinep;
329 while(ol < l){
330 if(r = strchr(q, '\n'))
331 q = r+1;
332 else{
333 q += strlen(q);
334 break;
335 }
336 ol++;
337 }
338 if(*nlinep < l)
339 *nlinep = l;
340 *newp = q;
341 return s_nappend(s, oq, q-oq);
342 }
343
344 static int
dodiff(char * f1,char * f2)345 dodiff(char *f1, char *f2)
346 {
347 int p[2];
348
349 if(pipe(p) < 0){
350 return -1;
351 }
352
353 switch(fork()){
354 case -1:
355 return -1;
356
357 case 0:
358 close(p[0]);
359 dup(p[1], 1);
360 execl("/bin/diff", "diff", f1, f2, nil);
361 _exits(nil);
362 }
363 close(p[1]);
364 return p[0];
365 }
366
367
368 /* print document i grayed out, with only diffs relative to j in black */
369 static String*
s_diff(String * s,Whist * h,int i,int j)370 s_diff(String *s, Whist *h, int i, int j)
371 {
372 char *p, *q, *pnew;
373 int fdiff, fd1, fd2, n1, n2;
374 Biobuf b;
375 char fn1[40], fn2[40];
376 String *new, *old;
377 int nline;
378
379 if(j < 0)
380 return pagehtml(s, h->doc[i].wtxt, Tpage);
381
382 strcpy(fn1, "/tmp/wiki.XXXXXX");
383 strcpy(fn2, "/tmp/wiki.XXXXXX");
384 if((fd1 = opentemp(fn1)) < 0 || (fd2 = opentemp(fn2)) < 0){
385 close(fd1);
386 s = s_append(s, "\nopentemp failed; sorry\n");
387 return s;
388 }
389
390 new = pagehtml(s_reset(nil), h->doc[i].wtxt, Tpage);
391 old = pagehtml(s_reset(nil), h->doc[j].wtxt, Tpage);
392 write(fd1, s_to_c(new), s_len(new));
393 write(fd2, s_to_c(old), s_len(old));
394
395 fdiff = dodiff(fn2, fn1);
396 if(fdiff < 0)
397 s = s_append(s, "\ndiff failed; sorry\n");
398 else{
399 nline = 0;
400 pnew = s_to_c(new);
401 Binit(&b, fdiff, OREAD);
402 while(p = Brdline(&b, '\n')){
403 if(p[0]=='<' || p[0]=='>' || p[0]=='-')
404 continue;
405 p[Blinelen(&b)-1] = '\0';
406 if((p = strpbrk(p, "acd")) == nil)
407 continue;
408 n1 = atoi(p+1);
409 if(q = strchr(p, ','))
410 n2 = atoi(q+1);
411 else
412 n2 = n1;
413 switch(*p){
414 case 'a':
415 case 'c':
416 s = s_append(s, "<span class='old_text'>");
417 s = copythru(s, &pnew, &nline, n1-1);
418 s = s_append(s, "</span><span class='new_text'>");
419 s = copythru(s, &pnew, &nline, n2);
420 s = s_append(s, "</span>");
421 break;
422 }
423 }
424 close(fdiff);
425 s = s_append(s, "<span class='old_text'>");
426 s = s_append(s, pnew);
427 s = s_append(s, "</span>");
428
429 }
430 s_free(new);
431 s_free(old);
432 close(fd1);
433 close(fd2);
434 return s;
435 }
436
437 static String*
diffhtml(String * s,Whist * h)438 diffhtml(String *s, Whist *h)
439 {
440 int i;
441 char tmp[50];
442 char *atime;
443
444 for(i=h->ndoc-1; i>=0; i--){
445 s = s_append(s, "<hr /><div class='diff_head'>\n");
446 if(i==h->current)
447 sprint(tmp, "index.html");
448 else
449 sprint(tmp, "%lud", h->doc[i].time);
450 atime = ctime(h->doc[i].time);
451 atime[strlen(atime)-1] = '\0';
452 s = s_appendlist(s,
453 "<a href=\"", tmp, "\">",
454 atime, "</a>", nil);
455 if(h->doc[i].author)
456 s = s_appendlist(s, ", ", h->doc[i].author, nil);
457 if(h->doc[i].conflict)
458 s = s_append(s, ", conflicting write");
459 s = s_append(s, "\n");
460 if(h->doc[i].comment)
461 s = s_appendlist(s, "<br /><i>", h->doc[i].comment, "</i>\n", nil);
462 s = s_append(s, "</div><hr />");
463 s = s_diff(s, h, i, i-1);
464 }
465 s = s_append(s, "<hr>");
466 return s;
467 }
468
469 static String*
historyhtml(String * s,Whist * h)470 historyhtml(String *s, Whist *h)
471 {
472 int i;
473 char tmp[40];
474 char *atime;
475
476 s = s_append(s, "<ul>\n");
477 for(i=h->ndoc-1; i>=0; i--){
478 if(i==h->current)
479 sprint(tmp, "index.html");
480 else
481 sprint(tmp, "%lud", h->doc[i].time);
482 atime = ctime(h->doc[i].time);
483 atime[strlen(atime)-1] = '\0';
484 s = s_appendlist(s,
485 "<li><a href=\"", tmp, "\">",
486 atime, "</a>", nil);
487 if(h->doc[i].author)
488 s = s_appendlist(s, ", ", h->doc[i].author, nil);
489 if(h->doc[i].conflict)
490 s = s_append(s, ", conflicting write");
491 s = s_append(s, "\n");
492 if(h->doc[i].comment)
493 s = s_appendlist(s, "<br><i>", h->doc[i].comment, "</i>\n", nil);
494 }
495 s = s_append(s, "</ul>");
496 return s;
497 }
498
499 String*
tohtml(Whist * h,Wdoc * d,int ty)500 tohtml(Whist *h, Wdoc *d, int ty)
501 {
502 char *atime;
503 char *p, *q, ver[40];
504 int nsub;
505 Sub sub[3];
506 String *s, *t;
507
508 t = gettemplate(ty);
509 if(p = strstr(s_to_c(t), "PAGE"))
510 q = p+4;
511 else{
512 p = s_to_c(t)+s_len(t);
513 q = nil;
514 }
515
516 nsub = 0;
517 if(h){
518 sub[nsub] = (Sub){ "TITLE", h->title };
519 nsub++;
520 }
521 if(d){
522 sprint(ver, "%lud", d->time);
523 sub[nsub] = (Sub){ "VERSION", ver };
524 nsub++;
525 atime = ctime(d->time);
526 atime[strlen(atime)-1] = '\0';
527 sub[nsub] = (Sub){ "DATE", atime };
528 nsub++;
529 }
530
531 s = s_reset(nil);
532 s = s_appendsub(s, s_to_c(t), p-s_to_c(t), sub, nsub);
533 switch(ty){
534 case Tpage:
535 case Toldpage:
536 s = pagehtml(s, d->wtxt, ty);
537 break;
538 case Tedit:
539 s = pagetext(s, d->wtxt, 0);
540 break;
541 case Tdiff:
542 s = diffhtml(s, h);
543 break;
544 case Thistory:
545 s = historyhtml(s, h);
546 break;
547 case Tnew:
548 case Twerror:
549 break;
550 }
551 if(q)
552 s = s_appendsub(s, q, strlen(q), sub, nsub);
553 s_free(t);
554 return s;
555 }
556
557 enum {
558 LINELEN = 70,
559 };
560
561 static String*
s_appendbrk(String * s,char * p,char * prefix,int dosharp)562 s_appendbrk(String *s, char *p, char *prefix, int dosharp)
563 {
564 char *e, *w, *x;
565 int first, l;
566 Rune r;
567
568 first = 1;
569 while(*p){
570 s = s_append(s, p);
571 e = strrchr(s_to_c(s), '\n');
572 if(e == nil)
573 e = s_to_c(s);
574 else
575 e++;
576 if(utflen(e) <= LINELEN)
577 break;
578 x = e; l=LINELEN;
579 while(l--)
580 x+=chartorune(&r, x);
581 x = strchr(x, ' ');
582 if(x){
583 *x = '\0';
584 w = strrchr(e, ' ');
585 *x = ' ';
586 }else
587 w = strrchr(e, ' ');
588
589 if(w-s_to_c(s) < strlen(prefix))
590 break;
591
592 x = estrdup(w+1);
593 *w = '\0';
594 s->ptr = w;
595 s_append(s, "\n");
596 if(dosharp)
597 s_append(s, "#");
598 s_append(s, prefix);
599 if(!first)
600 free(p);
601 first = 0;
602 p = x;
603 }
604 if(!first)
605 free(p);
606 return s;
607 }
608
609 static void
s_endline(String * s,int dosharp)610 s_endline(String *s, int dosharp)
611 {
612 if(dosharp){
613 if(s->ptr == s->base+1 && s->ptr[-1] == '#')
614 return;
615
616 if(s->ptr > s->base+1 && s->ptr[-1] == '#' && s->ptr[-2] == '\n')
617 return;
618 s_append(s, "\n#");
619 }else{
620 if(s->ptr > s->base+1 && s->ptr[-1] == '\n')
621 return;
622 s_append(s, "\n");
623 }
624 }
625
626 String*
pagetext(String * s,Wpage * page,int dosharp)627 pagetext(String *s, Wpage *page, int dosharp)
628 {
629 int inlist, inpara;
630 char *prefix, *sharp, tmp[40];
631 String *t;
632 Wpage *w;
633
634 inlist = 0;
635 inpara = 0;
636 prefix = "";
637 sharp = dosharp ? "#" : "";
638 s = s_append(s, sharp);
639 for(w=page; w; w=w->next){
640 switch(w->type){
641 case Wheading:
642 if(inlist){
643 prefix = "";
644 inlist = 0;
645 }
646 s_endline(s, dosharp);
647 if(!inpara){
648 inpara = 1;
649 s = s_appendlist(s, "\n", sharp, nil);
650 }
651 s = s_appendlist(s, w->text, "\n", sharp, "\n", sharp, nil);
652 break;
653
654 case Wpara:
655 s_endline(s, dosharp);
656 if(inlist){
657 prefix = "";
658 inlist = 0;
659 }
660 if(!inpara){
661 inpara = 1;
662 s = s_appendlist(s, "\n", sharp, nil);
663 }
664 break;
665
666 case Wbullet:
667 s_endline(s, dosharp);
668 if(!inlist)
669 inlist = 1;
670 if(inpara)
671 inpara = 0;
672 s = s_append(s, " *\t");
673 prefix = "\t";
674 break;
675
676 case Wlink:
677 if(inpara)
678 inpara = 0;
679 t = s_append(s_copy("["), w->text);
680 if(w->url == nil)
681 t = s_append(t, "]");
682 else{
683 t = s_append(t, " | ");
684 t = s_append(t, w->url);
685 t = s_append(t, "]");
686 }
687 s = s_appendbrk(s, s_to_c(t), prefix, dosharp);
688 s_free(t);
689 break;
690
691 case Wman:
692 if(inpara)
693 inpara = 0;
694 s = s_appendbrk(s, w->text, prefix, dosharp);
695 sprint(tmp, "(%d)", w->section);
696 s = s_appendbrk(s, tmp, prefix, dosharp);
697 break;
698
699 case Wpre:
700 if(inlist){
701 prefix = "";
702 inlist = 0;
703 }
704 if(inpara)
705 inpara = 0;
706 s_endline(s, dosharp);
707 s = s_appendlist(s, "! ", w->text, "\n", sharp, nil);
708 break;
709 case Whr:
710 s_endline(s, dosharp);
711 s = s_appendlist(s, "------------------------------------------------------ \n", sharp, nil);
712 break;
713
714 case Wplain:
715 if(inpara)
716 inpara = 0;
717 s = s_appendbrk(s, w->text, prefix, dosharp);
718 break;
719 }
720 }
721 s_endline(s, dosharp);
722 s->ptr--;
723 *s->ptr = '\0';
724 return s;
725 }
726
727 static String*
historytext(String * s,Whist * h)728 historytext(String *s, Whist *h)
729 {
730 int i;
731 char tmp[40];
732 char *atime;
733
734 for(i=h->ndoc-1; i>=0; i--){
735 if(i==h->current)
736 sprint(tmp, "[current]");
737 else
738 sprint(tmp, "[%lud/]", h->doc[i].time);
739 atime = ctime(h->doc[i].time);
740 atime[strlen(atime)-1] = '\0';
741 s = s_appendlist(s, " * ", tmp, " ", atime, nil);
742 if(h->doc[i].author)
743 s = s_appendlist(s, ", ", h->doc[i].author, nil);
744 if(h->doc[i].conflict)
745 s = s_append(s, ", conflicting write");
746 s = s_append(s, "\n");
747 if(h->doc[i].comment)
748 s = s_appendlist(s, "<i>", h->doc[i].comment, "</i>\n", nil);
749 }
750 return s;
751 }
752
753 String*
totext(Whist * h,Wdoc * d,int ty)754 totext(Whist *h, Wdoc *d, int ty)
755 {
756 char *atime;
757 char *p, *q, ver[40];
758 int nsub;
759 Sub sub[3];
760 String *s, *t;
761
762 t = gettemplate(Ntemplate+ty);
763 if(p = strstr(s_to_c(t), "PAGE"))
764 q = p+4;
765 else{
766 p = s_to_c(t)+s_len(t);
767 q = nil;
768 }
769
770 nsub = 0;
771 if(h){
772 sub[nsub] = (Sub){ "TITLE", h->title };
773 nsub++;
774 }
775 if(d){
776 sprint(ver, "%lud", d->time);
777 sub[nsub] = (Sub){ "VERSION", ver };
778 nsub++;
779 atime = ctime(d->time);
780 atime[strlen(atime)-1] = '\0';
781 sub[nsub] = (Sub){ "DATE", atime };
782 nsub++;
783 }
784
785 s = s_reset(nil);
786 s = s_appendsub(s, s_to_c(t), p-s_to_c(t), sub, nsub);
787 switch(ty){
788 case Tpage:
789 case Toldpage:
790 s = pagetext(s, d->wtxt, 0);
791 break;
792 case Thistory:
793 s = historytext(s, h);
794 break;
795 case Tnew:
796 case Twerror:
797 break;
798 }
799 if(q)
800 s = s_appendsub(s, q, strlen(q), sub, nsub);
801 s_free(t);
802 return s;
803 }
804
805 String*
doctext(String * s,Wdoc * d)806 doctext(String *s, Wdoc *d)
807 {
808 char tmp[40];
809
810 sprint(tmp, "D%lud", d->time);
811 s = s_append(s, tmp);
812 if(d->comment){
813 s = s_append(s, "\nC");
814 s = s_append(s, d->comment);
815 }
816 if(d->author){
817 s = s_append(s, "\nA");
818 s = s_append(s, d->author);
819 }
820 if(d->conflict)
821 s = s_append(s, "\nX");
822 s = s_append(s, "\n");
823 s = pagetext(s, d->wtxt, 1);
824 return s;
825 }
826