summaryrefslogtreecommitdiff
path: root/ikiwiki
blob: 7e47110593c30b9a2e1b5bd06f4f50b3b73ab8ca (plain)
  1. #!/usr/bin/perl -T
  2. use warnings;
  3. use strict;
  4. use File::Find;
  5. use Memoize;
  6. use File::Spec;
  7. BEGIN {
  8. $blosxom::version="is a proper perl module too much to ask?";
  9. do "/usr/bin/markdown";
  10. }
  11. $ENV{PATH}="/usr/local/bin:/usr/bin:/bin";
  12. my ($srcdir, $destdir, %links, %oldlinks, %oldpagemtime, %renderedfiles,
  13. %pagesources);
  14. my $wiki_link_regexp=qr/\[\[([^\s]+)\]\]/;
  15. my $wiki_file_regexp=qr/(^[-A-Za-z0-9_.:\/+]+$)/;
  16. my $wiki_file_prune_regexp=qr!((^|/).svn/|\.\.|^\.|\/\.|\.html?$)!;
  17. my $verbose=0;
  18. my $wikiname="wiki";
  19. my $default_pagetype=".mdwn";
  20. my $cgi=0;
  21. my $url="";
  22. my $cgiurl="";
  23. my $historyurl="";
  24. my $svn=1;
  25. sub usage { #{{{
  26. die "usage: ikiwiki [options] source dest\n";
  27. } #}}}
  28. sub error ($) { #{{{
  29. if ($cgi) {
  30. print "Content-type: text/html\n\n";
  31. print "Error: @_\n";
  32. exit 1;
  33. }
  34. else {
  35. die @_;
  36. }
  37. } #}}}
  38. sub debug ($) { #{{{
  39. print "@_\n" if $verbose;
  40. } #}}}
  41. sub mtime ($) { #{{{
  42. my $page=shift;
  43. return (stat($page))[9];
  44. } #}}}
  45. sub possibly_foolish_untaint ($) { #{{{
  46. my $tainted=shift;
  47. my ($untainted)=$tainted=~/(.*)/;
  48. return $untainted;
  49. } #}}}
  50. sub basename ($) { #{{{
  51. my $file=shift;
  52. $file=~s!.*/!!;
  53. return $file;
  54. } #}}}
  55. sub dirname ($) { #{{{
  56. my $file=shift;
  57. $file=~s!/?[^/]+$!!;
  58. return $file;
  59. } #}}}
  60. sub pagetype ($) { #{{{
  61. my $page=shift;
  62. if ($page =~ /\.mdwn$/) {
  63. return ".mdwn";
  64. }
  65. else {
  66. return "unknown";
  67. }
  68. } #}}}
  69. sub pagename ($) { #{{{
  70. my $file=shift;
  71. my $type=pagetype($file);
  72. my $page=$file;
  73. $page=~s/\Q$type\E*$// unless $type eq 'unknown';
  74. return $page;
  75. } #}}}
  76. sub htmlpage ($) { #{{{
  77. my $page=shift;
  78. return $page.".html";
  79. } #}}}
  80. sub readfile ($) { #{{{
  81. my $file=shift;
  82. local $/=undef;
  83. open (IN, "$file") || error("failed to read $file: $!");
  84. my $ret=<IN>;
  85. close IN;
  86. return $ret;
  87. } #}}}
  88. sub writefile ($$) { #{{{
  89. my $file=shift;
  90. my $content=shift;
  91. my $dir=dirname($file);
  92. if (! -d $dir) {
  93. my $d="";
  94. foreach my $s (split(m!/+!, $dir)) {
  95. $d.="$s/";
  96. if (! -d $d) {
  97. mkdir($d) || error("failed to create directory $d: $!");
  98. }
  99. }
  100. }
  101. open (OUT, ">$file") || error("failed to write $file: $!");
  102. print OUT $content;
  103. close OUT;
  104. } #}}}
  105. sub findlinks ($) { #{{{
  106. my $content=shift;
  107. my @links;
  108. while ($content =~ /$wiki_link_regexp/g) {
  109. push @links, lc($1);
  110. }
  111. return @links;
  112. } #}}}
  113. # Given a page and the text of a link on the page, determine which existing
  114. # page that link best points to. Prefers pages under a subdirectory with
  115. # the same name as the source page, failing that goes down the directory tree
  116. # to the base looking for matching pages.
  117. sub bestlink ($$) { #{{{
  118. my $page=shift;
  119. my $link=lc(shift);
  120. my $cwd=$page;
  121. do {
  122. my $l=$cwd;
  123. $l.="/" if length $l;
  124. $l.=$link;
  125. if (exists $links{$l}) {
  126. #debug("for $page, \"$link\", use $l");
  127. return $l;
  128. }
  129. } while $cwd=~s!/?[^/]+$!!;
  130. #print STDERR "warning: page $page, broken link: $link\n";
  131. return "";
  132. } #}}}
  133. sub isinlinableimage ($) { #{{{
  134. my $file=shift;
  135. $file=~/\.(png|gif|jpg|jpeg)$/;
  136. } #}}}
  137. sub htmllink { #{{{
  138. my $page=shift;
  139. my $link=shift;
  140. my $noimagelink=shift;
  141. my $bestlink=bestlink($page, $link);
  142. return $link if $page eq $bestlink;
  143. # TODO BUG: %renderedfiles may not have it, if the linked to page
  144. # was also added and isn't yet rendered! Note that this bug is
  145. # masked by the bug mentioned below that makes all new files
  146. # be rendered twice.
  147. if (! grep { $_ eq $bestlink } values %renderedfiles) {
  148. $bestlink=htmlpage($bestlink);
  149. }
  150. if (! grep { $_ eq $bestlink } values %renderedfiles) {
  151. return "<a href=\"$cgiurl?do=create&page=$link&from=$page\">?</a>$link"
  152. }
  153. $bestlink=File::Spec->abs2rel($bestlink, dirname($page));
  154. if (! $noimagelink && isinlinableimage($bestlink)) {
  155. return "<img src=\"$bestlink\">";
  156. }
  157. return "<a href=\"$bestlink\">$link</a>";
  158. } #}}}
  159. sub linkify ($$) { #{{{
  160. my $content=shift;
  161. my $file=shift;
  162. $content =~ s/$wiki_link_regexp/htmllink(pagename($file), $1)/eg;
  163. return $content;
  164. } #}}}
  165. sub htmlize ($$) { #{{{
  166. my $type=shift;
  167. my $content=shift;
  168. if ($type eq '.mdwn') {
  169. return Markdown::Markdown($content);
  170. }
  171. else {
  172. error("htmlization of $type not supported");
  173. }
  174. } #}}}
  175. sub linkbacks ($$) { #{{{
  176. my $content=shift;
  177. my $page=shift;
  178. my @links;
  179. foreach my $p (keys %links) {
  180. next if bestlink($page, $p) eq $page;
  181. if (grep { length $_ && bestlink($p, $_) eq $page } @{$links{$p}}) {
  182. my $href=File::Spec->abs2rel(htmlpage($p), dirname($page));
  183. # Trim common dir prefixes from both pages.
  184. my $p_trimmed=$p;
  185. my $page_trimmed=$page;
  186. my $dir;
  187. 1 while (($dir)=$page_trimmed=~m!^([^/]+/)!) &&
  188. defined $dir &&
  189. $p_trimmed=~s/^\Q$dir\E// &&
  190. $page_trimmed=~s/^\Q$dir\E//;
  191. push @links, "<a href=\"$href\">$p_trimmed</a>";
  192. }
  193. }
  194. $content.="<hr><p>Links: ".join(" ", sort @links)."</p>\n" if @links;
  195. return $content;
  196. } #}}}
  197. sub indexlink () { #{{{
  198. return "<a href=\"$url\">$wikiname</a>/ ";
  199. } #}}}
  200. sub finalize ($$) { #{{{
  201. my $content=shift;
  202. my $page=shift;
  203. my $title=basename($page);
  204. $title=~s/_/ /g;
  205. my $pagelink="";
  206. my $path="";
  207. foreach my $dir (reverse split("/", $page)) {
  208. if (length($pagelink)) {
  209. $pagelink="<a href=\"$path$dir.html\">$dir</a>/ $pagelink";
  210. }
  211. else {
  212. $pagelink=$dir;
  213. }
  214. $path.="../";
  215. }
  216. $path=~s/\.\.\/$/index.html/;
  217. $pagelink=indexlink()." $pagelink";
  218. my @actions;
  219. if (length $cgiurl) {
  220. push @actions, "<a href=\"$cgiurl?do=edit&page=$page\">Edit</a>";
  221. push @actions, "<a href=\"$cgiurl?do=recentchanges\">RecentChanges</a>";
  222. }
  223. if (length $historyurl) {
  224. my $url=$historyurl;
  225. $url=~s/\[\[\]\]/$pagesources{$page}/g;
  226. push @actions, "<a href=\"$url\">History</a>";
  227. }
  228. $content="<html>\n<head><title>$title</title></head>\n<body>\n".
  229. "<h1>$pagelink</h1>\n".
  230. "@actions\n<hr>\n".
  231. $content.
  232. "</body>\n</html>\n";
  233. return $content;
  234. } #}}}
  235. sub render ($) { #{{{
  236. my $file=shift;
  237. my $type=pagetype($file);
  238. my $content=readfile("$srcdir/$file");
  239. if ($type ne 'unknown') {
  240. my $page=pagename($file);
  241. $links{$page}=[findlinks($content)];
  242. $content=linkify($content, $file);
  243. $content=htmlize($type, $content);
  244. $content=linkbacks($content, $page);
  245. $content=finalize($content, $page);
  246. writefile("$destdir/".htmlpage($page), $content);
  247. $oldpagemtime{$page}=time;
  248. $renderedfiles{$page}=htmlpage($page);
  249. }
  250. else {
  251. $links{$file}=[];
  252. writefile("$destdir/$file", $content);
  253. $oldpagemtime{$file}=time;
  254. $renderedfiles{$file}=$file;
  255. }
  256. } #}}}
  257. sub loadindex () { #{{{
  258. open (IN, "$srcdir/.ikiwiki/index") || return;
  259. while (<IN>) {
  260. $_=possibly_foolish_untaint($_);
  261. chomp;
  262. my ($mtime, $file, $rendered, @links)=split(' ', $_);
  263. my $page=pagename($file);
  264. $pagesources{$page}=$file;
  265. $oldpagemtime{$page}=$mtime;
  266. $oldlinks{$page}=[@links];
  267. $links{$page}=[@links];
  268. $renderedfiles{$page}=$rendered;
  269. }
  270. close IN;
  271. } #}}}
  272. sub saveindex () { #{{{
  273. if (! -d "$srcdir/.ikiwiki") {
  274. mkdir("$srcdir/.ikiwiki");
  275. }
  276. open (OUT, ">$srcdir/.ikiwiki/index") || error("cannot write to index: $!");
  277. foreach my $page (keys %oldpagemtime) {
  278. print OUT "$oldpagemtime{$page} $pagesources{$page} $renderedfiles{$page} ".
  279. join(" ", @{$links{$page}})."\n"
  280. if $oldpagemtime{$page};
  281. }
  282. close OUT;
  283. } #}}}
  284. sub rcs_update () { #{{{
  285. if (-d "$srcdir/.svn") {
  286. if (system("svn", "update", "--quiet", $srcdir) != 0) {
  287. warn("svn update failed\n");
  288. }
  289. }
  290. } #}}}
  291. sub rcs_commit ($) { #{{{
  292. my $message=shift;
  293. if (-d "$srcdir/.svn") {
  294. if (system("svn", "commit", "--quiet", "-m",
  295. possibly_foolish_untaint($message), $srcdir) != 0) {
  296. warn("svn commit failed\n");
  297. }
  298. }
  299. } #}}}
  300. sub rcs_add ($) { #{{{
  301. my $file=shift;
  302. if (-d "$srcdir/.svn") {
  303. my $parent=dirname($file);
  304. while (! -d "$srcdir/$parent/.svn") {
  305. $file=$parent;
  306. $parent=dirname($file);
  307. }
  308. if (system("svn", "add", "--quiet", "$srcdir/$file") != 0) {
  309. warn("svn add failed\n");
  310. }
  311. }
  312. } #}}}
  313. sub rcs_recentchanges ($) { #{{{
  314. my $num=shift;
  315. my @ret;
  316. eval q{use Date::Parse};
  317. eval q{use Time::Duration};
  318. if (-d "$srcdir/.svn") {
  319. my $info=`LANG=C svn info $srcdir`;
  320. my ($svn_url)=$info=~/^URL: (.*)$/m;
  321. # FIXME: currently assumes that the wiki is somewhere
  322. # under trunk in svn, doesn't support other layouts.
  323. my ($svn_base)=$svn_url=~m!(/trunk(?:/.*)?)$!;
  324. my $div=qr/^--------------------+$/;
  325. my $infoline=qr/^r(\d+)\s+\|\s+([^\s]+)\s+\|\s+(\d+-\d+-\d+\s+\d+:\d+:\d+\s+[-+]?\d+).*/;
  326. my $state='start';
  327. my ($rev, $user, $when, @pages, $message);
  328. foreach (`LANG=C svn log -v '$svn_url'`) {
  329. chomp;
  330. if ($state eq 'start' && /$div/) {
  331. $state='header';
  332. }
  333. elsif ($state eq 'header' && /$infoline/) {
  334. $rev=$1;
  335. $user=$2;
  336. $when=concise(ago(time - str2time($3)));
  337. }
  338. elsif ($state eq 'header' && /^\s+[A-Z]\s+\Q$svn_base\E\/(.+)$/) {
  339. push @pages, pagename($1) if length $1;
  340. }
  341. elsif ($state eq 'header' && /^$/) {
  342. $state='body';
  343. }
  344. elsif ($state eq 'body' && /$div/) {
  345. push @ret, { rev => $rev, user => $user,
  346. when => $when, message => $message,
  347. pages => [@pages] } if @pages;
  348. return @ret if @ret >= $num;
  349. $state='header';
  350. $message=$rev=$user=$when=undef;
  351. @pages=();
  352. }
  353. elsif ($state eq 'body') {
  354. $message.="$_<br>\n";
  355. }
  356. }
  357. }
  358. return @ret;
  359. } #}}}
  360. sub prune ($) { #{{{
  361. my $file=shift;
  362. unlink($file);
  363. my $dir=dirname($file);
  364. while (rmdir($dir)) {
  365. $dir=dirname($dir);
  366. }
  367. } #}}}
  368. sub refresh () { #{{{
  369. # Find existing pages.
  370. my %exists;
  371. my @files;
  372. find({
  373. no_chdir => 1,
  374. wanted => sub {
  375. if (/$wiki_file_prune_regexp/) {
  376. $File::Find::prune=1;
  377. }
  378. elsif (! -d $_) {
  379. my ($f)=/$wiki_file_regexp/; # untaint
  380. if (! defined $f) {
  381. warn("skipping bad filename $_\n");
  382. }
  383. else {
  384. $f=~s/^\Q$srcdir\E\/?//;
  385. push @files, $f;
  386. $exists{pagename($f)}=1;
  387. }
  388. }
  389. },
  390. }, $srcdir);
  391. my %rendered;
  392. # check for added or removed pages
  393. my @add;
  394. foreach my $file (@files) {
  395. my $page=pagename($file);
  396. if (! $oldpagemtime{$page}) {
  397. debug("new page $page");
  398. push @add, $file;
  399. $links{$page}=[];
  400. $pagesources{$page}=$file;
  401. }
  402. }
  403. my @del;
  404. foreach my $page (keys %oldpagemtime) {
  405. if (! $exists{$page}) {
  406. debug("removing old page $page");
  407. push @del, $renderedfiles{$page};
  408. prune($destdir."/".$renderedfiles{$page});
  409. delete $renderedfiles{$page};
  410. $oldpagemtime{$page}=0;
  411. delete $pagesources{$page};
  412. }
  413. }
  414. # render any updated files
  415. foreach my $file (@files) {
  416. my $page=pagename($file);
  417. if (! exists $oldpagemtime{$page} ||
  418. mtime("$srcdir/$file") > $oldpagemtime{$page}) {
  419. debug("rendering changed file $file");
  420. render($file);
  421. $rendered{$file}=1;
  422. }
  423. }
  424. # if any files were added or removed, check to see if each page
  425. # needs an update due to linking to them
  426. # TODO: inefficient; pages may get rendered above and again here;
  427. # problem is the bestlink may have changed and we won't know until
  428. # now
  429. if (@add || @del) {
  430. FILE: foreach my $file (@files) {
  431. my $page=pagename($file);
  432. foreach my $f (@add, @del) {
  433. my $p=pagename($f);
  434. foreach my $link (@{$links{$page}}) {
  435. if (bestlink($page, $link) eq $p) {
  436. debug("rendering $file, which links to $p");
  437. render($file);
  438. $rendered{$file}=1;
  439. next FILE;
  440. }
  441. }
  442. }
  443. }
  444. }
  445. # handle linkbacks; if a page has added/removed links, update the
  446. # pages it links to
  447. # TODO: inefficient; pages may get rendered above and again here;
  448. # problem is the linkbacks could be wrong in the first pass render
  449. # above
  450. if (%rendered) {
  451. my %linkchanged;
  452. foreach my $file (keys %rendered, @del) {
  453. my $page=pagename($file);
  454. if (exists $links{$page}) {
  455. foreach my $link (@{$links{$page}}) {
  456. $link=bestlink($page, $link);
  457. if (length $link &&
  458. ! exists $oldlinks{$page} ||
  459. ! grep { $_ eq $link } @{$oldlinks{$page}}) {
  460. $linkchanged{$link}=1;
  461. }
  462. }
  463. }
  464. if (exists $oldlinks{$page}) {
  465. foreach my $link (@{$oldlinks{$page}}) {
  466. $link=bestlink($page, $link);
  467. if (length $link &&
  468. ! exists $links{$page} ||
  469. ! grep { $_ eq $link } @{$links{$page}}) {
  470. $linkchanged{$link}=1;
  471. }
  472. }
  473. }
  474. }
  475. foreach my $link (keys %linkchanged) {
  476. my $linkfile=$pagesources{$link};
  477. if (defined $linkfile) {
  478. debug("rendering $linkfile, to update its linkbacks");
  479. render($linkfile);
  480. }
  481. }
  482. }
  483. } #}}}
  484. # Generates a C wrapper program for running ikiwiki in a specific way.
  485. # The wrapper may be safely made suid.
  486. sub gen_wrapper ($$) { #{{{
  487. my ($svn, $rebuild)=@_;
  488. eval q{use Cwd 'abs_path'};
  489. $srcdir=abs_path($srcdir);
  490. $destdir=abs_path($destdir);
  491. my $this=abs_path($0);
  492. if (! -x $this) {
  493. error("$this doesn't seem to be executable");
  494. }
  495. my @params=($srcdir, $destdir, "--wikiname=$wikiname");
  496. push @params, "--verbose" if $verbose;
  497. push @params, "--rebuild" if $rebuild;
  498. push @params, "--nosvn" if !$svn;
  499. push @params, "--cgi" if $cgi;
  500. push @params, "--url=$url" if $url;
  501. push @params, "--cgiurl=$cgiurl" if $cgiurl;
  502. push @params, "--historyurl=$historyurl" if $historyurl;
  503. my $params=join(" ", @params);
  504. my $call='';
  505. foreach my $p ($this, $this, @params) {
  506. $call.=qq{"$p", };
  507. }
  508. $call.="NULL";
  509. my @envsave;
  510. push @envsave, qw{REMOTE_ADDR QUERY_STRING REQUEST_METHOD REQUEST_URI
  511. CONTENT_TYPE CONTENT_LENGTH GATEWAY_INTERFACE
  512. HTTP_COOKIE} if $cgi;
  513. my $envsave="";
  514. foreach my $var (@envsave) {
  515. $envsave.=<<"EOF"
  516. if ((s=getenv("$var")))
  517. asprintf(&newenviron[i++], "%s=%s", "$var", s);
  518. EOF
  519. }
  520. open(OUT, ">ikiwiki-wrap.c") || error("failed to write ikiwiki-wrap.c: $!");;
  521. print OUT <<"EOF";
  522. /* A wrapper for ikiwiki, can be safely made suid. */
  523. #define _GNU_SOURCE
  524. #include <stdio.h>
  525. #include <unistd.h>
  526. #include <stdlib.h>
  527. #include <string.h>
  528. extern char **environ;
  529. int main (int argc, char **argv) {
  530. /* Sanitize environment. */
  531. char *s;
  532. char *newenviron[$#envsave+3];
  533. int i=0;
  534. $envsave
  535. newenviron[i++]="HOME=$ENV{HOME}";
  536. newenviron[i]=NULL;
  537. environ=newenviron;
  538. if (argc == 2 && strcmp(argv[1], "--params") == 0) {
  539. printf("$params\\n");
  540. exit(0);
  541. }
  542. execl($call);
  543. perror("failed to run $this");
  544. exit(1);
  545. }
  546. EOF
  547. close OUT;
  548. if (system("gcc", "ikiwiki-wrap.c", "-o", "ikiwiki-wrap") != 0) {
  549. error("failed to compile ikiwiki-wrap.c");
  550. }
  551. unlink("ikiwiki-wrap.c");
  552. print "successfully generated ikiwiki-wrap\n";
  553. exit 0;
  554. } #}}}
  555. sub cgi_recentchanges ($) { #{{{
  556. my $q=shift;
  557. my $list="<ul>\n";
  558. foreach my $change (rcs_recentchanges(100)) {
  559. $list.="<li>";
  560. $list.=join(", ", map { htmllink("", $_, 1) } @{$change->{pages}});
  561. $list.="<br>\n";
  562. $list.="changed ".$change->{when}." by ".
  563. htmllink("", $change->{user}, 1).
  564. ": <i>".$change->{message}."</i>\n";
  565. $list.="</li>\n";
  566. }
  567. $list.="</ul>\n";
  568. print $q->header,
  569. $q->start_html("RecentChanges"),
  570. $q->h1(indexlink()." RecentChanges"),
  571. $list,
  572. $q->end_form,
  573. $q->end_html;
  574. } #}}}
  575. sub cgi_signin ($$) { #{{{
  576. my $q=shift;
  577. my $session=shift;
  578. eval q{use CGI::FormBuilder};
  579. my $form = CGI::FormBuilder->new(
  580. title => "$wikiname signin",
  581. fields => [qw(do page name password confirm_password email)],
  582. header => 1,
  583. method => 'POST',
  584. validate => {
  585. name => '/^\w+$/',
  586. confirm_password => {
  587. perl => q{eq $form->field("password")},
  588. },
  589. email => 'EMAIL',
  590. },
  591. required => 'NONE',
  592. javascript => 0,
  593. params => $q,
  594. action => $q->request_uri,
  595. );
  596. $form->sessionid($session->id);
  597. $form->field(name => "name", required => 0);
  598. $form->field(name => "do", type => "hidden");
  599. $form->field(name => "page", type => "hidden");
  600. $form->field(name => "password", type => "password", required => 0);
  601. $form->field(name => "confirm_password", type => "password", required => 0);
  602. $form->field(name => "email", required => 0);
  603. if ($session->param("name")) {
  604. $form->field(name => "name", value => $session->param("name"));
  605. }
  606. if ($q->param("do") ne "signin") {
  607. $form->text("You need to log in before you can edit pages.");
  608. }
  609. if ($form->submitted) {
  610. # Set required fields based on how form was submitted.
  611. my %required=(
  612. "Login" => [qw(name password)],
  613. "Register" => [qw(name password confirm_password email)],
  614. "Mail Password" => [qw(name)],
  615. );
  616. foreach my $opt (@{$required{$form->submitted}}) {
  617. $form->field(name => $opt, required => 1);
  618. }
  619. # Validate password differently depending on how form was
  620. # submitted.
  621. if ($form->submitted eq 'Login') {
  622. $form->field(
  623. name => "password",
  624. validate => sub {
  625. # TODO get real user password
  626. shift eq "foo";
  627. },
  628. );
  629. }
  630. else {
  631. $form->field(name => "password", validate => 'VALUE');
  632. }
  633. }
  634. else {
  635. # Comments only shown first time.
  636. $form->field(name => "name", comment => "use FirstnameLastName");
  637. $form->field(name => "confirm_password", comment => "(only needed");
  638. $form->field(name => "email", comment => "for registration)");
  639. }
  640. if ($form->submitted && $form->validate) {
  641. if ($form->submitted eq 'Login') {
  642. $session->param("name", $form->field("name"));
  643. if (defined $form->field("do")) {
  644. $q->redirect(
  645. "$cgiurl?do=".$form->field("do").
  646. "&page=".$form->field("page"));
  647. }
  648. else {
  649. $q->redirect($url);
  650. }
  651. }
  652. elsif ($form->submitted eq 'Register') {
  653. # TODO: save registration info
  654. $form->field(name => "confirm_password", type => "hidden");
  655. $form->field(name => "email", type => "hidden");
  656. $form->text("Registration successful. Now you can Login.");
  657. print $form->render(submit => ["Login"]);;
  658. }
  659. elsif ($form->submitted eq 'Mail Password') {
  660. # TODO mail password
  661. $form->text("Your password has been emailed to you.");
  662. print $form->render(submit => ["Login", "Register", "Mail Password"]);;
  663. }
  664. }
  665. else {
  666. print $form->render(submit => ["Login", "Register", "Mail Password"]);;
  667. }
  668. } #}}}
  669. sub cgi () { #{{{
  670. eval q{use CGI};
  671. eval q{use CGI::Session};
  672. my $q=CGI->new;
  673. # session id has to be _sessionid for CGI::FormBuilder to work.
  674. # TODO: stop having the formbuilder emit cookies and change session
  675. # id to something else.
  676. CGI::Session->name("_sessionid");
  677. my $session = CGI::Session->new(undef, $q,
  678. { Directory=> "$srcdir/.ikiwiki/sessions" });
  679. my $do=$q->param('do');
  680. if (! defined $do || ! length $do) {
  681. error("\"do\" parameter missing");
  682. }
  683. if ($do eq 'recentchanges') {
  684. cgi_recentchanges($q);
  685. return;
  686. }
  687. if (! defined $session->param("name") || $do eq 'signin') {
  688. cgi_signin($q, $session);
  689. return;
  690. }
  691. my ($page)=$q->param('page')=~/$wiki_file_regexp/;
  692. if (! defined $page || ! length $page || $page ne $q->param('page') ||
  693. $page=~/$wiki_file_prune_regexp/ || $page=~/^\//) {
  694. error("bad page name");
  695. }
  696. $page=lc($page);
  697. my $action=$q->request_uri;
  698. $action=~s/\?.*//;
  699. if ($do eq 'create') {
  700. if (exists $pagesources{lc($page)}) {
  701. # hmm, someone else made the page in the meantime?
  702. print $q->redirect("$url/".htmlpage($page));
  703. }
  704. my @page_locs;
  705. my ($from)=$q->param('from')=~/$wiki_file_regexp/;
  706. if (! defined $from || ! length $from ||
  707. $from ne $q->param('from') ||
  708. $from=~/$wiki_file_prune_regexp/ || $from=~/^\//) {
  709. @page_locs=$page;
  710. }
  711. else {
  712. my $dir=$from."/";
  713. $dir=~s![^/]+/$!!;
  714. push @page_locs, $dir.$page;
  715. push @page_locs, "$from/$page";
  716. while (length $dir) {
  717. $dir=~s![^/]+/$!!;
  718. push @page_locs, $dir.$page;
  719. }
  720. }
  721. $q->param("do", "save");
  722. print $q->header,
  723. $q->start_html("Creating $page"),
  724. $q->h1(indexlink()." Creating $page"),
  725. $q->start_form(-action => $action),
  726. $q->hidden('do'),
  727. "Select page location:",
  728. $q->popup_menu('page', \@page_locs),
  729. $q->textarea(-name => 'content',
  730. -default => "",
  731. -rows => 20,
  732. -columns => 80),
  733. $q->br,
  734. "Optional comment about this change:",
  735. $q->br,
  736. $q->textfield(-name => "comments", -size => 80),
  737. $q->br,
  738. $q->submit("Save Page"),
  739. $q->end_form,
  740. $q->end_html;
  741. }
  742. elsif ($do eq 'edit') {
  743. my $content="";
  744. if (exists $pagesources{lc($page)}) {
  745. $content=readfile("$srcdir/$pagesources{lc($page)}");
  746. $content=~s/\n/\r\n/g;
  747. }
  748. $q->param("do", "save");
  749. print $q->header,
  750. $q->start_html("Editing $page"),
  751. $q->h1(indexlink()." Editing $page"),
  752. $q->start_form(-action => $action),
  753. $q->hidden('do'),
  754. $q->hidden('page'),
  755. $q->textarea(-name => 'content',
  756. -default => $content,
  757. -rows => 20,
  758. -columns => 80),
  759. $q->br,
  760. "Optional comment about this change:",
  761. $q->br,
  762. $q->textfield(-name => "comments", -size => 80),
  763. $q->br,
  764. $q->submit("Save Page"),
  765. $q->end_form,
  766. $q->end_html;
  767. }
  768. elsif ($do eq 'save') {
  769. my $file=$page.$default_pagetype;
  770. my $newfile=1;
  771. if (exists $pagesources{lc($page)}) {
  772. $file=$pagesources{lc($page)};
  773. $newfile=0;
  774. }
  775. my $content=$q->param('content');
  776. $content=~s/\r\n/\n/g;
  777. $content=~s/\r/\n/g;
  778. writefile("$srcdir/$file", $content);
  779. my $message="web commit from $ENV{REMOTE_ADDR}";
  780. if (defined $q->param('comments')) {
  781. $message.=": ".$q->param('comments');
  782. }
  783. if ($svn) {
  784. if ($newfile) {
  785. rcs_add($file);
  786. }
  787. # presumably the commit will trigger an update
  788. # of the wiki
  789. rcs_commit($message);
  790. }
  791. else {
  792. refresh();
  793. }
  794. print $q->redirect("$url/".htmlpage($page));
  795. }
  796. else {
  797. error("unknown do parameter");
  798. }
  799. } #}}}
  800. # main {{{
  801. my $rebuild=0;
  802. my $wrapper=0;
  803. if (grep /^-/, @ARGV) {
  804. eval {use Getopt::Long};
  805. GetOptions(
  806. "wikiname=s" => \$wikiname,
  807. "verbose|v" => \$verbose,
  808. "rebuild" => \$rebuild,
  809. "wrapper" => \$wrapper,
  810. "svn!" => \$svn,
  811. "cgi" => \$cgi,
  812. "url=s" => \$url,
  813. "cgiurl=s" => \$cgiurl,
  814. "historyurl=s" => \$historyurl,
  815. ) || usage();
  816. }
  817. usage() unless @ARGV == 2;
  818. ($srcdir) = possibly_foolish_untaint(shift);
  819. ($destdir) = possibly_foolish_untaint(shift);
  820. if ($cgi && ! length $url) {
  821. error("Must specify url to wiki with --url when using --cgi");
  822. }
  823. gen_wrapper($svn, $rebuild) if $wrapper;
  824. memoize('pagename');
  825. memoize('bestlink');
  826. loadindex() unless $rebuild;
  827. if ($cgi) {
  828. cgi();
  829. }
  830. else {
  831. rcs_update() if $svn;
  832. refresh();
  833. saveindex();
  834. }
  835. #}}}