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 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* 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* 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* 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* 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* 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* 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 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* 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* 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* 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* 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* 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 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* 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* 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* 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* 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