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