summaryrefslogtreecommitdiff
path: root/ikiwiki
blob: 80ad72526ef2434f4fc16cec9e1ab6852429a1ce (plain)
  1. #!/usr/bin/perl -T
  2. eval 'exec /usr/bin/perl -T -S $0 ${1+"$@"}'
  3. if 0; # not running under some shell
  4. $ENV{PATH}="/usr/local/bin:/usr/bin:/bin";
  5. use warnings;
  6. use strict;
  7. use Memoize;
  8. use File::Spec;
  9. use HTML::Template;
  10. use Getopt::Long;
  11. my (%links, %oldlinks, %oldpagemtime, %renderedfiles, %pagesources);
  12. # Holds global config settings, also used by some modules.
  13. our %config=( #{{{
  14. wiki_file_prune_regexp => qr{((^|/).svn/|\.\.|^\.|\/\.|\.html?$)},
  15. wiki_link_regexp => qr/\[\[([^\s\]]+)\]\]/,
  16. wiki_file_regexp => qr/(^[-A-Za-z0-9_.:\/+]+$)/,
  17. verbose => 0,
  18. wikiname => "wiki",
  19. default_pageext => ".mdwn",
  20. cgi => 0,
  21. svn => 1,
  22. url => '',
  23. cgiurl => '',
  24. historyurl => '',
  25. diffurl => '',
  26. anonok => 0,
  27. rebuild => 0,
  28. wrapper => undef,
  29. wrappermode => undef,
  30. srcdir => undef,
  31. destdir => undef,
  32. templatedir => undef,
  33. setup => undef,
  34. adminuser => undef,
  35. ); #}}}
  36. GetOptions( #{{{
  37. "setup=s" => \$config{setup},
  38. "wikiname=s" => \$config{wikiname},
  39. "verbose|v!" => \$config{verbose},
  40. "rebuild!" => \$config{rebuild},
  41. "wrapper=s" => sub { $config{wrapper}=$_[1] ? $_[1] : "ikiwiki-wrap" },
  42. "wrappermode=i" => \$config{wrappermode},
  43. "svn!" => \$config{svn},
  44. "anonok!" => \$config{anonok},
  45. "cgi!" => \$config{cgi},
  46. "url=s" => \$config{url},
  47. "cgiurl=s" => \$config{cgiurl},
  48. "historyurl=s" => \$config{historyurl},
  49. "diffurl=s" => \$config{diffurl},
  50. "exclude=s@" => sub {
  51. $config{wiki_file_prune_regexp}=qr/$config{wiki_file_prune_regexp}|$_[1]/;
  52. },
  53. "adminuser=s@" => sub { push @{$config{adminuser}}, $_[1] },
  54. ) || usage();
  55. if (! $config{setup}) {
  56. usage() unless @ARGV == 3;
  57. $config{srcdir} = possibly_foolish_untaint(shift);
  58. $config{templatedir} = possibly_foolish_untaint(shift);
  59. $config{destdir} = possibly_foolish_untaint(shift);
  60. if ($config{cgi} && ! length $config{url}) {
  61. error("Must specify url to wiki with --url when using --cgi");
  62. }
  63. }
  64. #}}}
  65. sub usage { #{{{
  66. die "usage: ikiwiki [options] source templates dest\n";
  67. } #}}}
  68. sub error { #{{{
  69. if ($config{cgi}) {
  70. print "Content-type: text/html\n\n";
  71. print misctemplate("Error", "<p>Error: @_</p>");
  72. }
  73. die @_;
  74. } #}}}
  75. sub debug ($) { #{{{
  76. return unless $config{verbose};
  77. if (! $config{cgi}) {
  78. print "@_\n";
  79. }
  80. else {
  81. print STDERR "@_\n";
  82. }
  83. } #}}}
  84. sub mtime ($) { #{{{
  85. my $page=shift;
  86. return (stat($page))[9];
  87. } #}}}
  88. sub possibly_foolish_untaint { #{{{
  89. my $tainted=shift;
  90. my ($untainted)=$tainted=~/(.*)/;
  91. return $untainted;
  92. } #}}}
  93. sub basename ($) { #{{{
  94. my $file=shift;
  95. $file=~s!.*/!!;
  96. return $file;
  97. } #}}}
  98. sub dirname ($) { #{{{
  99. my $file=shift;
  100. $file=~s!/?[^/]+$!!;
  101. return $file;
  102. } #}}}
  103. sub pagetype ($) { #{{{
  104. my $page=shift;
  105. if ($page =~ /\.mdwn$/) {
  106. return ".mdwn";
  107. }
  108. else {
  109. return "unknown";
  110. }
  111. } #}}}
  112. sub pagename ($) { #{{{
  113. my $file=shift;
  114. my $type=pagetype($file);
  115. my $page=$file;
  116. $page=~s/\Q$type\E*$// unless $type eq 'unknown';
  117. return $page;
  118. } #}}}
  119. sub htmlpage ($) { #{{{
  120. my $page=shift;
  121. return $page.".html";
  122. } #}}}
  123. sub readfile ($) { #{{{
  124. my $file=shift;
  125. local $/=undef;
  126. open (IN, "$file") || error("failed to read $file: $!");
  127. my $ret=<IN>;
  128. close IN;
  129. return $ret;
  130. } #}}}
  131. sub writefile ($$) { #{{{
  132. my $file=shift;
  133. my $content=shift;
  134. my $dir=dirname($file);
  135. if (! -d $dir) {
  136. my $d="";
  137. foreach my $s (split(m!/+!, $dir)) {
  138. $d.="$s/";
  139. if (! -d $d) {
  140. mkdir($d) || error("failed to create directory $d: $!");
  141. }
  142. }
  143. }
  144. open (OUT, ">$file") || error("failed to write $file: $!");
  145. print OUT $content;
  146. close OUT;
  147. } #}}}
  148. sub findlinks ($$) { #{{{
  149. my $content=shift;
  150. my $page=shift;
  151. my @links;
  152. while ($content =~ /(?<!\\)$config{wiki_link_regexp}/g) {
  153. push @links, lc($1);
  154. }
  155. # Discussion links are a special case since they're not in the text
  156. # of the page, but on its template.
  157. return @links, "$page/discussion";
  158. } #}}}
  159. sub bestlink ($$) { #{{{
  160. # Given a page and the text of a link on the page, determine which
  161. # existing page that link best points to. Prefers pages under a
  162. # subdirectory with the same name as the source page, failing that
  163. # goes down the directory tree to the base looking for matching
  164. # pages.
  165. my $page=shift;
  166. my $link=lc(shift);
  167. my $cwd=$page;
  168. do {
  169. my $l=$cwd;
  170. $l.="/" if length $l;
  171. $l.=$link;
  172. if (exists $links{$l}) {
  173. #debug("for $page, \"$link\", use $l");
  174. return $l;
  175. }
  176. } while $cwd=~s!/?[^/]+$!!;
  177. #print STDERR "warning: page $page, broken link: $link\n";
  178. return "";
  179. } #}}}
  180. sub isinlinableimage ($) { #{{{
  181. my $file=shift;
  182. $file=~/\.(png|gif|jpg|jpeg)$/;
  183. } #}}}
  184. sub htmllink { #{{{
  185. my $page=shift;
  186. my $link=shift;
  187. my $noimageinline=shift; # don't turn links into inline html images
  188. my $forcesubpage=shift; # force a link to a subpage
  189. my $bestlink;
  190. if (! $forcesubpage) {
  191. $bestlink=bestlink($page, $link);
  192. }
  193. else {
  194. $bestlink="$page/".lc($link);
  195. }
  196. return $link if length $bestlink && $page eq $bestlink;
  197. # TODO BUG: %renderedfiles may not have it, if the linked to page
  198. # was also added and isn't yet rendered! Note that this bug is
  199. # masked by the bug mentioned below that makes all new files
  200. # be rendered twice.
  201. if (! grep { $_ eq $bestlink } values %renderedfiles) {
  202. $bestlink=htmlpage($bestlink);
  203. }
  204. if (! grep { $_ eq $bestlink } values %renderedfiles) {
  205. return "<a href=\"$config{cgiurl}?do=create&page=$link&from=$page\">?</a>$link"
  206. }
  207. $bestlink=File::Spec->abs2rel($bestlink, dirname($page));
  208. if (! $noimageinline && isinlinableimage($bestlink)) {
  209. return "<img src=\"$bestlink\">";
  210. }
  211. return "<a href=\"$bestlink\">$link</a>";
  212. } #}}}
  213. sub linkify ($$) { #{{{
  214. my $content=shift;
  215. my $page=shift;
  216. $content =~ s{(\\?)$config{wiki_link_regexp}}{
  217. $1 ? "[[$2]]" : htmllink($page, $2)
  218. }eg;
  219. return $content;
  220. } #}}}
  221. sub htmlize ($$) { #{{{
  222. my $type=shift;
  223. my $content=shift;
  224. if (! $INC{"/usr/bin/markdown"}) {
  225. no warnings 'once';
  226. $blosxom::version="is a proper perl module too much to ask?";
  227. use warnings 'all';
  228. do "/usr/bin/markdown";
  229. }
  230. if ($type eq '.mdwn') {
  231. return Markdown::Markdown($content);
  232. }
  233. else {
  234. error("htmlization of $type not supported");
  235. }
  236. } #}}}
  237. sub backlinks ($) { #{{{
  238. my $page=shift;
  239. my @links;
  240. foreach my $p (keys %links) {
  241. next if bestlink($page, $p) eq $page;
  242. if (grep { length $_ && bestlink($p, $_) eq $page } @{$links{$p}}) {
  243. my $href=File::Spec->abs2rel(htmlpage($p), dirname($page));
  244. # Trim common dir prefixes from both pages.
  245. my $p_trimmed=$p;
  246. my $page_trimmed=$page;
  247. my $dir;
  248. 1 while (($dir)=$page_trimmed=~m!^([^/]+/)!) &&
  249. defined $dir &&
  250. $p_trimmed=~s/^\Q$dir\E// &&
  251. $page_trimmed=~s/^\Q$dir\E//;
  252. push @links, { url => $href, page => $p_trimmed };
  253. }
  254. }
  255. return sort { $a->{page} cmp $b->{page} } @links;
  256. } #}}}
  257. sub parentlinks ($) { #{{{
  258. my $page=shift;
  259. my @ret;
  260. my $pagelink="";
  261. my $path="";
  262. my $skip=1;
  263. foreach my $dir (reverse split("/", $page)) {
  264. if (! $skip) {
  265. $path.="../";
  266. unshift @ret, { url => "$path$dir.html", page => $dir };
  267. }
  268. else {
  269. $skip=0;
  270. }
  271. }
  272. unshift @ret, { url => length $path ? $path : ".", page => $config{wikiname} };
  273. return @ret;
  274. } #}}}
  275. sub indexlink () { #{{{
  276. return "<a href=\"$config{url}\">$config{wikiname}</a>";
  277. } #}}}
  278. sub finalize ($$$) { #{{{
  279. my $content=shift;
  280. my $page=shift;
  281. my $mtime=shift;
  282. my $title=basename($page);
  283. $title=~s/_/ /g;
  284. my $template=HTML::Template->new(blind_cache => 1,
  285. filename => "$config{templatedir}/page.tmpl");
  286. if (length $config{cgiurl}) {
  287. $template->param(editurl => "$config{cgiurl}?do=edit&page=$page");
  288. $template->param(prefsurl => "$config{cgiurl}?do=prefs");
  289. if ($config{svn}) {
  290. $template->param(recentchangesurl => "$config{cgiurl}?do=recentchanges");
  291. }
  292. }
  293. if (length $config{historyurl}) {
  294. my $u=$config{historyurl};
  295. $u=~s/\[\[file\]\]/$pagesources{$page}/g;
  296. $template->param(historyurl => $u);
  297. }
  298. $template->param(
  299. title => $title,
  300. wikiname => $config{wikiname},
  301. parentlinks => [parentlinks($page)],
  302. content => $content,
  303. backlinks => [backlinks($page)],
  304. discussionlink => htmllink($page, "Discussion", 1, 1),
  305. mtime => scalar(gmtime($mtime)),
  306. );
  307. return $template->output;
  308. } #}}}
  309. sub check_overwrite ($$) { #{{{
  310. # Important security check. Make sure to call this before saving
  311. # any files to the source directory.
  312. my $dest=shift;
  313. my $src=shift;
  314. if (! exists $renderedfiles{$src} && -e $dest && ! $config{rebuild}) {
  315. error("$dest already exists and was rendered from ".
  316. join(" ",(grep { $renderedfiles{$_} eq $dest } keys
  317. %renderedfiles)).
  318. ", before, so not rendering from $src");
  319. }
  320. } #}}}
  321. sub render ($) { #{{{
  322. my $file=shift;
  323. my $type=pagetype($file);
  324. my $content=readfile("$config{srcdir}/$file");
  325. if ($type ne 'unknown') {
  326. my $page=pagename($file);
  327. $links{$page}=[findlinks($content, $page)];
  328. $content=linkify($content, $page);
  329. $content=htmlize($type, $content);
  330. $content=finalize($content, $page,
  331. mtime("$config{srcdir}/$file"));
  332. check_overwrite("$config{destdir}/".htmlpage($page), $page);
  333. writefile("$config{destdir}/".htmlpage($page), $content);
  334. $oldpagemtime{$page}=time;
  335. $renderedfiles{$page}=htmlpage($page);
  336. }
  337. else {
  338. $links{$file}=[];
  339. check_overwrite("$config{destdir}/$file", $file);
  340. writefile("$config{destdir}/$file", $content);
  341. $oldpagemtime{$file}=time;
  342. $renderedfiles{$file}=$file;
  343. }
  344. } #}}}
  345. sub lockwiki () { #{{{
  346. # Take an exclusive lock on the wiki to prevent multiple concurrent
  347. # run issues. The lock will be dropped on program exit.
  348. if (! -d "$config{srcdir}/.ikiwiki") {
  349. mkdir("$config{srcdir}/.ikiwiki");
  350. }
  351. open(WIKILOCK, ">$config{srcdir}/.ikiwiki/lockfile") || error ("cannot write to lockfile: $!");
  352. if (! flock(WIKILOCK, 2 | 4)) {
  353. debug("wiki seems to be locked, waiting for lock");
  354. my $wait=600; # arbitrary, but don't hang forever to
  355. # prevent process pileup
  356. for (1..600) {
  357. return if flock(WIKILOCK, 2 | 4);
  358. sleep 1;
  359. }
  360. error("wiki is locked; waited $wait seconds without lock being freed (possible stuck process or stale lock?)");
  361. }
  362. } #}}}
  363. sub unlockwiki () { #{{{
  364. close WIKILOCK;
  365. } #}}}
  366. sub loadindex () { #{{{
  367. open (IN, "$config{srcdir}/.ikiwiki/index") || return;
  368. while (<IN>) {
  369. $_=possibly_foolish_untaint($_);
  370. chomp;
  371. my ($mtime, $file, $rendered, @links)=split(' ', $_);
  372. my $page=pagename($file);
  373. $pagesources{$page}=$file;
  374. $oldpagemtime{$page}=$mtime;
  375. $oldlinks{$page}=[@links];
  376. $links{$page}=[@links];
  377. $renderedfiles{$page}=$rendered;
  378. }
  379. close IN;
  380. } #}}}
  381. sub saveindex () { #{{{
  382. if (! -d "$config{srcdir}/.ikiwiki") {
  383. mkdir("$config{srcdir}/.ikiwiki");
  384. }
  385. open (OUT, ">$config{srcdir}/.ikiwiki/index") || error("cannot write to index: $!");
  386. foreach my $page (keys %oldpagemtime) {
  387. print OUT "$oldpagemtime{$page} $pagesources{$page} $renderedfiles{$page} ".
  388. join(" ", @{$links{$page}})."\n"
  389. if $oldpagemtime{$page};
  390. }
  391. close OUT;
  392. } #}}}
  393. sub rcs_update () { #{{{
  394. if (-d "$config{srcdir}/.svn") {
  395. if (system("svn", "update", "--quiet", $config{srcdir}) != 0) {
  396. warn("svn update failed\n");
  397. }
  398. }
  399. } #}}}
  400. sub rcs_prepedit ($) { #{{{
  401. # Prepares to edit a file under revision control. Returns a token
  402. # that must be passed into rcs_commit when the file is ready
  403. # for committing.
  404. # The file is relative to the srcdir.
  405. my $file=shift;
  406. if (-d "$config{srcdir}/.svn") {
  407. # For subversion, return the revision of the file when
  408. # editing begins.
  409. my $rev=svn_info("Revision", "$config{srcdir}/$file");
  410. return defined $rev ? $rev : "";
  411. }
  412. } #}}}
  413. sub rcs_commit ($$$) { #{{{
  414. # Tries to commit the page; returns undef on _success_ and
  415. # a version of the page with the rcs's conflict markers on failure.
  416. # The file is relative to the srcdir.
  417. my $file=shift;
  418. my $message=shift;
  419. my $rcstoken=shift;
  420. if (-d "$config{srcdir}/.svn") {
  421. # Check to see if the page has been changed by someone
  422. # else since rcs_prepedit was called.
  423. my ($oldrev)=$rcstoken=~/^([0-9]+)$/; # untaint
  424. my $rev=svn_info("Revision", "$config{srcdir}/$file");
  425. if (defined $rev && defined $oldrev && $rev != $oldrev) {
  426. # Merge their changes into the file that we've
  427. # changed.
  428. chdir($config{srcdir}); # svn merge wants to be here
  429. if (system("svn", "merge", "--quiet", "-r$oldrev:$rev",
  430. "$config{srcdir}/$file") != 0) {
  431. warn("svn merge -r$oldrev:$rev failed\n");
  432. }
  433. }
  434. if (system("svn", "commit", "--quiet", "-m",
  435. possibly_foolish_untaint($message),
  436. "$config{srcdir}") != 0) {
  437. my $conflict=readfile("$config{srcdir}/$file");
  438. if (system("svn", "revert", "--quiet", "$config{srcdir}/$file") != 0) {
  439. warn("svn revert failed\n");
  440. }
  441. return $conflict;
  442. }
  443. }
  444. return undef # success
  445. } #}}}
  446. sub rcs_add ($) { #{{{
  447. # filename is relative to the root of the srcdir
  448. my $file=shift;
  449. if (-d "$config{srcdir}/.svn") {
  450. my $parent=dirname($file);
  451. while (! -d "$config{srcdir}/$parent/.svn") {
  452. $file=$parent;
  453. $parent=dirname($file);
  454. }
  455. if (system("svn", "add", "--quiet", "$config{srcdir}/$file") != 0) {
  456. warn("svn add failed\n");
  457. }
  458. }
  459. } #}}}
  460. sub svn_info ($$) { #{{{
  461. my $field=shift;
  462. my $file=shift;
  463. my $info=`LANG=C svn info $file`;
  464. my ($ret)=$info=~/^$field: (.*)$/m;
  465. return $ret;
  466. } #}}}
  467. sub rcs_recentchanges ($) { #{{{
  468. my $num=shift;
  469. my @ret;
  470. eval q{use CGI 'escapeHTML'};
  471. eval q{use Date::Parse};
  472. eval q{use Time::Duration};
  473. if (-d "$config{srcdir}/.svn") {
  474. my $svn_url=svn_info("URL", $config{srcdir});
  475. # FIXME: currently assumes that the wiki is somewhere
  476. # under trunk in svn, doesn't support other layouts.
  477. my ($svn_base)=$svn_url=~m!(/trunk(?:/.*)?)$!;
  478. my $div=qr/^--------------------+$/;
  479. my $infoline=qr/^r(\d+)\s+\|\s+([^\s]+)\s+\|\s+(\d+-\d+-\d+\s+\d+:\d+:\d+\s+[-+]?\d+).*/;
  480. my $state='start';
  481. my ($rev, $user, $when, @pages, @message);
  482. foreach (`LANG=C svn log --limit $num -v '$svn_url'`) {
  483. chomp;
  484. if ($state eq 'start' && /$div/) {
  485. $state='header';
  486. }
  487. elsif ($state eq 'header' && /$infoline/) {
  488. $rev=$1;
  489. $user=$2;
  490. $when=concise(ago(time - str2time($3)));
  491. }
  492. elsif ($state eq 'header' && /^\s+[A-Z]\s+\Q$svn_base\E\/([^ ]+)(?:$|\s)/) {
  493. my $file=$1;
  494. my $diffurl=$config{diffurl};
  495. $diffurl=~s/\[\[file\]\]/$file/g;
  496. $diffurl=~s/\[\[r1\]\]/$rev - 1/eg;
  497. $diffurl=~s/\[\[r2\]\]/$rev/g;
  498. push @pages, {
  499. link => htmllink("", pagename($file), 1),
  500. diffurl => $diffurl,
  501. } if length $file;
  502. }
  503. elsif ($state eq 'header' && /^$/) {
  504. $state='body';
  505. }
  506. elsif ($state eq 'body' && /$div/) {
  507. my $committype="web";
  508. if (defined $message[0] &&
  509. $message[0]->{line}=~/^web commit by (\w+):?(.*)/) {
  510. $user="$1";
  511. $message[0]->{line}=$2;
  512. }
  513. else {
  514. $committype="svn";
  515. }
  516. push @ret, { rev => $rev,
  517. user => htmllink("", $user, 1),
  518. committype => $committype,
  519. when => $when, message => [@message],
  520. pages => [@pages],
  521. } if @pages;
  522. return @ret if @ret >= $num;
  523. $state='header';
  524. $rev=$user=$when=undef;
  525. @pages=@message=();
  526. }
  527. elsif ($state eq 'body') {
  528. push @message, {line => escapeHTML($_)},
  529. }
  530. }
  531. }
  532. return @ret;
  533. } #}}}
  534. sub prune ($) { #{{{
  535. my $file=shift;
  536. unlink($file);
  537. my $dir=dirname($file);
  538. while (rmdir($dir)) {
  539. $dir=dirname($dir);
  540. }
  541. } #}}}
  542. sub refresh () { #{{{
  543. # find existing pages
  544. my %exists;
  545. my @files;
  546. eval q{use File::Find};
  547. find({
  548. no_chdir => 1,
  549. wanted => sub {
  550. if (/$config{wiki_file_prune_regexp}/) {
  551. no warnings 'once';
  552. $File::Find::prune=1;
  553. use warnings "all";
  554. }
  555. elsif (! -d $_ && ! -l $_) {
  556. my ($f)=/$config{wiki_file_regexp}/; # untaint
  557. if (! defined $f) {
  558. warn("skipping bad filename $_\n");
  559. }
  560. else {
  561. $f=~s/^\Q$config{srcdir}\E\/?//;
  562. push @files, $f;
  563. $exists{pagename($f)}=1;
  564. }
  565. }
  566. },
  567. }, $config{srcdir});
  568. my %rendered;
  569. # check for added or removed pages
  570. my @add;
  571. foreach my $file (@files) {
  572. my $page=pagename($file);
  573. if (! $oldpagemtime{$page}) {
  574. debug("new page $page");
  575. push @add, $file;
  576. $links{$page}=[];
  577. $pagesources{$page}=$file;
  578. }
  579. }
  580. my @del;
  581. foreach my $page (keys %oldpagemtime) {
  582. if (! $exists{$page}) {
  583. debug("removing old page $page");
  584. push @del, $pagesources{$page};
  585. prune($config{destdir}."/".$renderedfiles{$page});
  586. delete $renderedfiles{$page};
  587. $oldpagemtime{$page}=0;
  588. delete $pagesources{$page};
  589. }
  590. }
  591. # render any updated files
  592. foreach my $file (@files) {
  593. my $page=pagename($file);
  594. if (! exists $oldpagemtime{$page} ||
  595. mtime("$config{srcdir}/$file") > $oldpagemtime{$page}) {
  596. debug("rendering changed file $file");
  597. render($file);
  598. $rendered{$file}=1;
  599. }
  600. }
  601. # if any files were added or removed, check to see if each page
  602. # needs an update due to linking to them
  603. # TODO: inefficient; pages may get rendered above and again here;
  604. # problem is the bestlink may have changed and we won't know until
  605. # now
  606. if (@add || @del) {
  607. FILE: foreach my $file (@files) {
  608. my $page=pagename($file);
  609. foreach my $f (@add, @del) {
  610. my $p=pagename($f);
  611. foreach my $link (@{$links{$page}}) {
  612. if (bestlink($page, $link) eq $p) {
  613. debug("rendering $file, which links to $p");
  614. render($file);
  615. $rendered{$file}=1;
  616. next FILE;
  617. }
  618. }
  619. }
  620. }
  621. }
  622. # handle backlinks; if a page has added/removed links, update the
  623. # pages it links to
  624. # TODO: inefficient; pages may get rendered above and again here;
  625. # problem is the backlinks could be wrong in the first pass render
  626. # above
  627. if (%rendered) {
  628. my %linkchanged;
  629. foreach my $file (keys %rendered, @del) {
  630. my $page=pagename($file);
  631. if (exists $links{$page}) {
  632. foreach my $link (map { bestlink($page, $_) } @{$links{$page}}) {
  633. if (length $link &&
  634. ! exists $oldlinks{$page} ||
  635. ! grep { $_ eq $link } @{$oldlinks{$page}}) {
  636. $linkchanged{$link}=1;
  637. }
  638. }
  639. }
  640. if (exists $oldlinks{$page}) {
  641. foreach my $link (map { bestlink($page, $_) } @{$oldlinks{$page}}) {
  642. if (length $link &&
  643. ! exists $links{$page} ||
  644. ! grep { $_ eq $link } @{$links{$page}}) {
  645. $linkchanged{$link}=1;
  646. }
  647. }
  648. }
  649. }
  650. foreach my $link (keys %linkchanged) {
  651. my $linkfile=$pagesources{$link};
  652. if (defined $linkfile) {
  653. debug("rendering $linkfile, to update its backlinks");
  654. render($linkfile);
  655. }
  656. }
  657. }
  658. } #}}}
  659. sub gen_wrapper (@) { #{{{
  660. my %config=(@_);
  661. eval q{use Cwd 'abs_path'};
  662. $config{srcdir}=abs_path($config{srcdir});
  663. $config{destdir}=abs_path($config{destdir});
  664. my $this=abs_path($0);
  665. if (! -x $this) {
  666. error("$this doesn't seem to be executable");
  667. }
  668. if ($config{setup}) {
  669. error("cannot create a wrapper that uses a setup file");
  670. }
  671. my @params=($config{srcdir}, $config{templatedir}, $config{destdir},
  672. "--wikiname=$config{wikiname}");
  673. push @params, "--verbose" if $config{verbose};
  674. push @params, "--rebuild" if $config{rebuild};
  675. push @params, "--nosvn" if !$config{svn};
  676. push @params, "--cgi" if $config{cgi};
  677. push @params, "--url=$config{url}" if length $config{url};
  678. push @params, "--cgiurl=$config{cgiurl}" if length $config{cgiurl};
  679. push @params, "--historyurl=$config{historyurl}" if length $config{historyurl};
  680. push @params, "--diffurl=$config{diffurl}" if length $config{diffurl};
  681. push @params, "--anonok" if $config{anonok};
  682. push @params, "--adminuser=$_" foreach @{$config{adminuser}};
  683. my $params=join(" ", @params);
  684. my $call='';
  685. foreach my $p ($this, $this, @params) {
  686. $call.=qq{"$p", };
  687. }
  688. $call.="NULL";
  689. my @envsave;
  690. push @envsave, qw{REMOTE_ADDR QUERY_STRING REQUEST_METHOD REQUEST_URI
  691. CONTENT_TYPE CONTENT_LENGTH GATEWAY_INTERFACE
  692. HTTP_COOKIE} if $config{cgi};
  693. my $envsave="";
  694. foreach my $var (@envsave) {
  695. $envsave.=<<"EOF"
  696. if ((s=getenv("$var")))
  697. asprintf(&newenviron[i++], "%s=%s", "$var", s);
  698. EOF
  699. }
  700. open(OUT, ">ikiwiki-wrap.c") || error("failed to write ikiwiki-wrap.c: $!");;
  701. print OUT <<"EOF";
  702. /* A wrapper for ikiwiki, can be safely made suid. */
  703. #define _GNU_SOURCE
  704. #include <stdio.h>
  705. #include <unistd.h>
  706. #include <stdlib.h>
  707. #include <string.h>
  708. extern char **environ;
  709. int main (int argc, char **argv) {
  710. /* Sanitize environment. */
  711. char *s;
  712. char *newenviron[$#envsave+3];
  713. int i=0;
  714. $envsave
  715. newenviron[i++]="HOME=$ENV{HOME}";
  716. newenviron[i]=NULL;
  717. environ=newenviron;
  718. if (argc == 2 && strcmp(argv[1], "--params") == 0) {
  719. printf("$params\\n");
  720. exit(0);
  721. }
  722. execl($call);
  723. perror("failed to run $this");
  724. exit(1);
  725. }
  726. EOF
  727. close OUT;
  728. if (system("gcc", "ikiwiki-wrap.c", "-o", possibly_foolish_untaint($config{wrapper})) != 0) {
  729. error("failed to compile ikiwiki-wrap.c");
  730. }
  731. unlink("ikiwiki-wrap.c");
  732. if (defined $config{wrappermode} &&
  733. ! chmod(oct($config{wrappermode}), possibly_foolish_untaint($config{wrapper}))) {
  734. error("chmod $config{wrapper}: $!");
  735. }
  736. print "successfully generated $config{wrapper}\n";
  737. } #}}}
  738. sub misctemplate ($$) { #{{{
  739. my $title=shift;
  740. my $pagebody=shift;
  741. my $template=HTML::Template->new(
  742. filename => "$config{templatedir}/misc.tmpl"
  743. );
  744. $template->param(
  745. title => $title,
  746. indexlink => indexlink(),
  747. wikiname => $config{wikiname},
  748. pagebody => $pagebody,
  749. );
  750. return $template->output;
  751. }#}}}
  752. sub cgi_recentchanges ($) { #{{{
  753. my $q=shift;
  754. my $template=HTML::Template->new(
  755. filename => "$config{templatedir}/recentchanges.tmpl"
  756. );
  757. $template->param(
  758. title => "RecentChanges",
  759. indexlink => indexlink(),
  760. wikiname => $config{wikiname},
  761. changelog => [rcs_recentchanges(100)],
  762. );
  763. print $q->header, $template->output;
  764. } #}}}
  765. sub userinfo_get ($$) { #{{{
  766. my $user=shift;
  767. my $field=shift;
  768. eval q{use Storable};
  769. my $userdata=eval{ Storable::lock_retrieve("$config{srcdir}/.ikiwiki/userdb") };
  770. if (! defined $userdata || ! ref $userdata ||
  771. ! exists $userdata->{$user} || ! ref $userdata->{$user} ||
  772. ! exists $userdata->{$user}->{$field}) {
  773. return "";
  774. }
  775. return $userdata->{$user}->{$field};
  776. } #}}}
  777. sub userinfo_set ($$$) { #{{{
  778. my $user=shift;
  779. my $field=shift;
  780. my $value=shift;
  781. eval q{use Storable};
  782. my $userdata=eval{ Storable::lock_retrieve("$config{srcdir}/.ikiwiki/userdb") };
  783. if (! defined $userdata || ! ref $userdata ||
  784. ! exists $userdata->{$user} || ! ref $userdata->{$user}) {
  785. return "";
  786. }
  787. $userdata->{$user}->{$field}=$value;
  788. my $oldmask=umask(077);
  789. my $ret=Storable::lock_store($userdata, "$config{srcdir}/.ikiwiki/userdb");
  790. umask($oldmask);
  791. return $ret;
  792. } #}}}
  793. sub userinfo_setall ($$) { #{{{
  794. my $user=shift;
  795. my $info=shift;
  796. eval q{use Storable};
  797. my $userdata=eval{ Storable::lock_retrieve("$config{srcdir}/.ikiwiki/userdb") };
  798. if (! defined $userdata || ! ref $userdata) {
  799. $userdata={};
  800. }
  801. $userdata->{$user}=$info;
  802. my $oldmask=umask(077);
  803. my $ret=Storable::lock_store($userdata, "$config{srcdir}/.ikiwiki/userdb");
  804. umask($oldmask);
  805. return $ret;
  806. } #}}}
  807. sub cgi_signin ($$) { #{{{
  808. my $q=shift;
  809. my $session=shift;
  810. eval q{use CGI::FormBuilder};
  811. my $form = CGI::FormBuilder->new(
  812. title => "signin",
  813. fields => [qw(do page from name password confirm_password email)],
  814. header => 1,
  815. method => 'POST',
  816. validate => {
  817. confirm_password => {
  818. perl => q{eq $form->field("password")},
  819. },
  820. email => 'EMAIL',
  821. },
  822. required => 'NONE',
  823. javascript => 0,
  824. params => $q,
  825. action => $q->request_uri,
  826. header => 0,
  827. template => (-e "$config{templatedir}/signin.tmpl" ?
  828. "$config{templatedir}/signin.tmpl" : "")
  829. );
  830. $form->field(name => "name", required => 0);
  831. $form->field(name => "do", type => "hidden");
  832. $form->field(name => "page", type => "hidden");
  833. $form->field(name => "from", type => "hidden");
  834. $form->field(name => "password", type => "password", required => 0);
  835. $form->field(name => "confirm_password", type => "password", required => 0);
  836. $form->field(name => "email", required => 0);
  837. if ($q->param("do") ne "signin") {
  838. $form->text("You need to log in first.");
  839. }
  840. if ($form->submitted) {
  841. # Set required fields based on how form was submitted.
  842. my %required=(
  843. "Login" => [qw(name password)],
  844. "Register" => [qw(name password confirm_password email)],
  845. "Mail Password" => [qw(name)],
  846. );
  847. foreach my $opt (@{$required{$form->submitted}}) {
  848. $form->field(name => $opt, required => 1);
  849. }
  850. # Validate password differently depending on how
  851. # form was submitted.
  852. if ($form->submitted eq 'Login') {
  853. $form->field(
  854. name => "password",
  855. validate => sub {
  856. length $form->field("name") &&
  857. shift eq userinfo_get($form->field("name"), 'password');
  858. },
  859. );
  860. $form->field(name => "name", validate => '/^\w+$/');
  861. }
  862. else {
  863. $form->field(name => "password", validate => 'VALUE');
  864. }
  865. # And make sure the entered name exists when logging
  866. # in or sending email, and does not when registering.
  867. if ($form->submitted eq 'Register') {
  868. $form->field(
  869. name => "name",
  870. validate => sub {
  871. my $name=shift;
  872. length $name &&
  873. ! userinfo_get($name, "regdate");
  874. },
  875. );
  876. }
  877. else {
  878. $form->field(
  879. name => "name",
  880. validate => sub {
  881. my $name=shift;
  882. length $name &&
  883. userinfo_get($name, "regdate");
  884. },
  885. );
  886. }
  887. }
  888. else {
  889. # First time settings.
  890. $form->field(name => "name", comment => "use FirstnameLastName");
  891. $form->field(name => "confirm_password", comment => "(only needed");
  892. $form->field(name => "email", comment => "for registration)");
  893. if ($session->param("name")) {
  894. $form->field(name => "name", value => $session->param("name"));
  895. }
  896. }
  897. if ($form->submitted && $form->validate) {
  898. if ($form->submitted eq 'Login') {
  899. $session->param("name", $form->field("name"));
  900. if (defined $form->field("do") &&
  901. $form->field("do") ne 'signin') {
  902. print $q->redirect(
  903. "$config{cgiurl}?do=".$form->field("do").
  904. "&page=".$form->field("page").
  905. "&from=".$form->field("from"));;
  906. }
  907. else {
  908. print $q->redirect($config{url});
  909. }
  910. }
  911. elsif ($form->submitted eq 'Register') {
  912. my $user_name=$form->field('name');
  913. if (userinfo_setall($user_name, {
  914. 'email' => $form->field('email'),
  915. 'password' => $form->field('password'),
  916. 'regdate' => time
  917. })) {
  918. $form->field(name => "confirm_password", type => "hidden");
  919. $form->field(name => "email", type => "hidden");
  920. $form->text("Registration successful. Now you can Login.");
  921. print $session->header();
  922. print misctemplate($form->title, $form->render(submit => ["Login"]));
  923. }
  924. else {
  925. error("Error saving registration.");
  926. }
  927. }
  928. elsif ($form->submitted eq 'Mail Password') {
  929. my $user_name=$form->field("name");
  930. my $template=HTML::Template->new(
  931. filename => "$config{templatedir}/passwordmail.tmpl"
  932. );
  933. $template->param(
  934. user_name => $user_name,
  935. user_password => userinfo_get($user_name, "password"),
  936. wikiurl => $config{url},
  937. wikiname => $config{wikiname},
  938. REMOTE_ADDR => $ENV{REMOTE_ADDR},
  939. );
  940. eval q{use Mail::Sendmail};
  941. my ($fromhost) = $config{cgiurl} =~ m!/([^/]+)!;
  942. sendmail(
  943. To => userinfo_get($user_name, "email"),
  944. From => "$config{wikiname} admin <".(getpwuid($>))[0]."@".$fromhost.">",
  945. Subject => "$config{wikiname} information",
  946. Message => $template->output,
  947. ) or error("Failed to send mail");
  948. $form->text("Your password has been emailed to you.");
  949. $form->field(name => "name", required => 0);
  950. print $session->header();
  951. print misctemplate($form->title, $form->render(submit => ["Login", "Register", "Mail Password"]));
  952. }
  953. }
  954. else {
  955. print $session->header();
  956. print misctemplate($form->title, $form->render(submit => ["Login", "Register", "Mail Password"]));
  957. }
  958. } #}}}
  959. sub is_admin ($) { #{{{
  960. my $user_name=shift;
  961. return grep { $_ eq $user_name } @{$config{adminuser}};
  962. } #}}}
  963. sub glob_match ($$) { #{{{
  964. my $page=shift;
  965. my $glob=shift;
  966. # turn glob into safe regexp
  967. $glob=quotemeta($glob);
  968. $glob=~s/\\\*/.*/g;
  969. $glob=~s/\\\?/./g;
  970. $glob=~s!\\/!/!g;
  971. $page=~/^$glob$/i;
  972. } #}}}
  973. sub globlist_match ($$) { #{{{
  974. my $page=shift;
  975. my @globlist=split(" ", shift);
  976. # check any negated globs first
  977. foreach my $glob (@globlist) {
  978. return 0 if $glob=~/^!(.*)/ && glob_match($page, $1);
  979. }
  980. foreach my $glob (@globlist) {
  981. return 1 if glob_match($page, $glob);
  982. }
  983. return 0;
  984. } #}}}
  985. sub page_locked ($$;$) { #{{{
  986. my $page=shift;
  987. my $session=shift;
  988. my $nonfatal=shift;
  989. my $user=$session->param("name");
  990. return if length $user && is_admin($user);
  991. foreach my $admin (@{$config{adminuser}}) {
  992. my $locked_pages=userinfo_get($admin, "locked_pages");
  993. if (globlist_match($page, userinfo_get($admin, "locked_pages"))) {
  994. return 1 if $nonfatal;
  995. error(htmllink("", $page, 1)." is locked by ".
  996. htmllink("", $admin, 1)." and cannot be edited.");
  997. }
  998. }
  999. return 0;
  1000. } #}}}
  1001. sub cgi_prefs ($$) { #{{{
  1002. my $q=shift;
  1003. my $session=shift;
  1004. eval q{use CGI::FormBuilder};
  1005. my $form = CGI::FormBuilder->new(
  1006. title => "preferences",
  1007. fields => [qw(do name password confirm_password email locked_pages)],
  1008. header => 0,
  1009. method => 'POST',
  1010. validate => {
  1011. confirm_password => {
  1012. perl => q{eq $form->field("password")},
  1013. },
  1014. email => 'EMAIL',
  1015. },
  1016. required => 'NONE',
  1017. javascript => 0,
  1018. params => $q,
  1019. action => $q->request_uri,
  1020. template => (-e "$config{templatedir}/prefs.tmpl" ?
  1021. "$config{templatedir}/prefs.tmpl" : "")
  1022. );
  1023. my @buttons=("Save Preferences", "Logout", "Cancel");
  1024. my $user_name=$session->param("name");
  1025. $form->field(name => "do", type => "hidden");
  1026. $form->field(name => "name", disabled => 1,
  1027. value => $user_name, force => 1);
  1028. $form->field(name => "password", type => "password");
  1029. $form->field(name => "confirm_password", type => "password");
  1030. $form->field(name => "locked_pages", size => 50,
  1031. comment => "(".htmllink("", "GlobList", 1).")");
  1032. if (! is_admin($user_name)) {
  1033. $form->field(name => "locked_pages", type => "hidden");
  1034. }
  1035. if (! $form->submitted) {
  1036. $form->field(name => "email", force => 1,
  1037. value => userinfo_get($user_name, "email"));
  1038. $form->field(name => "locked_pages", force => 1,
  1039. value => userinfo_get($user_name, "locked_pages"));
  1040. }
  1041. if ($form->submitted eq 'Logout') {
  1042. $session->delete();
  1043. print $q->redirect($config{url});
  1044. return;
  1045. }
  1046. elsif ($form->submitted eq 'Cancel') {
  1047. print $q->redirect($config{url});
  1048. return;
  1049. }
  1050. elsif ($form->submitted eq "Save Preferences" && $form->validate) {
  1051. foreach my $field (qw(password email locked_pages)) {
  1052. if (length $form->field($field)) {
  1053. userinfo_set($user_name, $field, $form->field($field)) || error("failed to set $field");
  1054. }
  1055. }
  1056. $form->text("Preferences saved.");
  1057. }
  1058. print $session->header();
  1059. print misctemplate($form->title, $form->render(submit => \@buttons));
  1060. } #}}}
  1061. sub cgi_editpage ($$) { #{{{
  1062. my $q=shift;
  1063. my $session=shift;
  1064. eval q{use CGI::FormBuilder};
  1065. my $form = CGI::FormBuilder->new(
  1066. fields => [qw(do rcsinfo from page content comments)],
  1067. header => 1,
  1068. method => 'POST',
  1069. validate => {
  1070. content => '/.+/',
  1071. },
  1072. required => [qw{content}],
  1073. javascript => 0,
  1074. params => $q,
  1075. action => $q->request_uri,
  1076. table => 0,
  1077. template => "$config{templatedir}/editpage.tmpl"
  1078. );
  1079. my @buttons=("Save Page", "Preview", "Cancel");
  1080. my ($page)=$form->param('page')=~/$config{wiki_file_regexp}/;
  1081. if (! defined $page || ! length $page || $page ne $q->param('page') ||
  1082. $page=~/$config{wiki_file_prune_regexp}/ || $page=~/^\//) {
  1083. error("bad page name");
  1084. }
  1085. $page=lc($page);
  1086. my $file=$page.$config{default_pageext};
  1087. my $newfile=1;
  1088. if (exists $pagesources{lc($page)}) {
  1089. $file=$pagesources{lc($page)};
  1090. $newfile=0;
  1091. }
  1092. $form->field(name => "do", type => 'hidden');
  1093. $form->field(name => "from", type => 'hidden');
  1094. $form->field(name => "rcsinfo", type => 'hidden');
  1095. $form->field(name => "page", value => "$page", force => 1);
  1096. $form->field(name => "comments", type => "text", size => 80);
  1097. $form->field(name => "content", type => "textarea", rows => 20,
  1098. cols => 80);
  1099. $form->tmpl_param("can_commit", $config{svn});
  1100. $form->tmpl_param("indexlink", indexlink());
  1101. $form->tmpl_param("helponformattinglink",
  1102. htmllink("", "HelpOnFormatting", 1));
  1103. if (! $form->submitted) {
  1104. $form->field(name => "rcsinfo", value => rcs_prepedit($file),
  1105. force => 1);
  1106. }
  1107. if ($form->submitted eq "Cancel") {
  1108. print $q->redirect("$config{url}/".htmlpage($page));
  1109. return;
  1110. }
  1111. elsif ($form->submitted eq "Preview") {
  1112. $form->tmpl_param("page_preview",
  1113. htmlize($config{default_pageext},
  1114. linkify($form->field('content'), $page)));
  1115. }
  1116. else {
  1117. $form->tmpl_param("page_preview", "");
  1118. }
  1119. $form->tmpl_param("page_conflict", "");
  1120. if (! $form->submitted || $form->submitted eq "Preview" ||
  1121. ! $form->validate) {
  1122. if ($form->field("do") eq "create") {
  1123. if (exists $pagesources{lc($page)}) {
  1124. # hmm, someone else made the page in the
  1125. # meantime?
  1126. print $q->redirect("$config{url}/".htmlpage($page));
  1127. return;
  1128. }
  1129. my @page_locs;
  1130. my $best_loc;
  1131. my ($from)=$form->param('from')=~/$config{wiki_file_regexp}/;
  1132. if (! defined $from || ! length $from ||
  1133. $from ne $form->param('from') ||
  1134. $from=~/$config{wiki_file_prune_regexp}/ || $from=~/^\//) {
  1135. @page_locs=$best_loc=$page;
  1136. }
  1137. else {
  1138. my $dir=$from."/";
  1139. $dir=~s![^/]+/$!!;
  1140. if ($page eq 'discussion') {
  1141. $best_loc="$from/$page";
  1142. }
  1143. else {
  1144. $best_loc=$dir.$page;
  1145. }
  1146. push @page_locs, $dir.$page;
  1147. push @page_locs, "$from/$page";
  1148. while (length $dir) {
  1149. $dir=~s![^/]+/$!!;
  1150. push @page_locs, $dir.$page;
  1151. }
  1152. @page_locs = grep {
  1153. ! exists $pagesources{lc($_)} &&
  1154. ! page_locked($_, $session, 1)
  1155. } @page_locs;
  1156. }
  1157. $form->tmpl_param("page_select", 1);
  1158. $form->field(name => "page", type => 'select',
  1159. options => \@page_locs, value => $best_loc);
  1160. $form->title("creating $page");
  1161. }
  1162. elsif ($form->field("do") eq "edit") {
  1163. page_locked($page, $session);
  1164. if (! defined $form->field('content') ||
  1165. ! length $form->field('content')) {
  1166. my $content="";
  1167. if (exists $pagesources{lc($page)}) {
  1168. $content=readfile("$config{srcdir}/$pagesources{lc($page)}");
  1169. $content=~s/\n/\r\n/g;
  1170. }
  1171. $form->field(name => "content", value => $content,
  1172. force => 1);
  1173. }
  1174. $form->tmpl_param("page_select", 0);
  1175. $form->field(name => "page", type => 'hidden');
  1176. $form->title("editing $page");
  1177. }
  1178. print $form->render(submit => \@buttons);
  1179. }
  1180. else {
  1181. # save page
  1182. page_locked($page, $session);
  1183. my $content=$form->field('content');
  1184. $content=~s/\r\n/\n/g;
  1185. $content=~s/\r/\n/g;
  1186. writefile("$config{srcdir}/$file", $content);
  1187. my $message="web commit ";
  1188. if (length $session->param("name")) {
  1189. $message.="by ".$session->param("name");
  1190. }
  1191. else {
  1192. $message.="from $ENV{REMOTE_ADDR}";
  1193. }
  1194. if (defined $form->field('comments') &&
  1195. length $form->field('comments')) {
  1196. $message.=": ".$form->field('comments');
  1197. }
  1198. if ($config{svn}) {
  1199. if ($newfile) {
  1200. rcs_add($file);
  1201. }
  1202. # prevent deadlock with post-commit hook
  1203. unlockwiki();
  1204. # presumably the commit will trigger an update
  1205. # of the wiki
  1206. my $conflict=rcs_commit($file, $message,
  1207. $form->field("rcsinfo"));
  1208. if (defined $conflict) {
  1209. $form->field(name => "rcsinfo", value => rcs_prepedit($file),
  1210. force => 1);
  1211. $form->tmpl_param("page_conflict", 1);
  1212. $form->field("content", value => $conflict, force => 1);
  1213. $form->field("do", "edit)");
  1214. $form->tmpl_param("page_select", 0);
  1215. $form->field(name => "page", type => 'hidden');
  1216. $form->title("editing $page");
  1217. print $form->render(submit => \@buttons);
  1218. return;
  1219. }
  1220. }
  1221. else {
  1222. loadindex();
  1223. refresh();
  1224. saveindex();
  1225. }
  1226. # The trailing question mark tries to avoid broken
  1227. # caches and get the most recent version of the page.
  1228. print $q->redirect("$config{url}/".htmlpage($page)."?updated");
  1229. }
  1230. } #}}}
  1231. sub cgi () { #{{{
  1232. eval q{use CGI};
  1233. eval q{use CGI::Session};
  1234. my $q=CGI->new;
  1235. my $do=$q->param('do');
  1236. if (! defined $do || ! length $do) {
  1237. error("\"do\" parameter missing");
  1238. }
  1239. # This does not need a session.
  1240. if ($do eq 'recentchanges') {
  1241. cgi_recentchanges($q);
  1242. return;
  1243. }
  1244. CGI::Session->name("ikiwiki_session");
  1245. my $oldmask=umask(077);
  1246. my $session = CGI::Session->new("driver:db_file", $q,
  1247. { FileName => "$config{srcdir}/.ikiwiki/sessions.db" });
  1248. umask($oldmask);
  1249. # Everything below this point needs the user to be signed in.
  1250. if ((! $config{anonok} && ! defined $session->param("name") ||
  1251. ! defined $session->param("name") ||
  1252. ! userinfo_get($session->param("name"), "regdate")) || $do eq 'signin') {
  1253. cgi_signin($q, $session);
  1254. # Force session flush with safe umask.
  1255. my $oldmask=umask(077);
  1256. $session->flush;
  1257. umask($oldmask);
  1258. return;
  1259. }
  1260. if ($do eq 'create' || $do eq 'edit') {
  1261. cgi_editpage($q, $session);
  1262. }
  1263. elsif ($do eq 'prefs') {
  1264. cgi_prefs($q, $session);
  1265. }
  1266. else {
  1267. error("unknown do parameter");
  1268. }
  1269. } #}}}
  1270. sub setup () { # {{{
  1271. my $setup=possibly_foolish_untaint($config{setup});
  1272. delete $config{setup};
  1273. open (IN, $setup) || error("read $setup: $!\n");
  1274. local $/=undef;
  1275. my $code=<IN>;
  1276. ($code)=$code=~/(.*)/s;
  1277. close IN;
  1278. eval $code;
  1279. error($@) if $@;
  1280. exit;
  1281. } #}}}
  1282. # main {{{
  1283. setup() if $config{setup};
  1284. lockwiki();
  1285. if ($config{wrapper}) {
  1286. gen_wrapper(%config);
  1287. exit;
  1288. }
  1289. memoize('pagename');
  1290. memoize('bestlink');
  1291. loadindex() unless $config{rebuild};
  1292. if ($config{cgi}) {
  1293. cgi();
  1294. }
  1295. else {
  1296. rcs_update() if $config{svn};
  1297. refresh();
  1298. saveindex();
  1299. }
  1300. #}}}