diff options
-rw-r--r-- | IkiWiki/CGI.pm | 5 | ||||
-rw-r--r-- | IkiWiki/Plugin/apache404.pm | 77 | ||||
-rw-r--r-- | IkiWiki/Plugin/comments.pm | 34 | ||||
-rw-r--r-- | IkiWiki/Plugin/goto.pm | 76 | ||||
-rw-r--r-- | IkiWiki/Plugin/recentchanges.pm | 47 | ||||
-rw-r--r-- | IkiWiki/Wrapper.pm | 3 | ||||
-rw-r--r-- | doc/plugins/apache404.mdwn | 11 | ||||
-rw-r--r-- | doc/plugins/goto.mdwn | 10 | ||||
-rwxr-xr-x | t/apache404.t | 45 |
9 files changed, 231 insertions, 77 deletions
diff --git a/IkiWiki/CGI.pm b/IkiWiki/CGI.pm index 3fadc462e..c91914564 100644 --- a/IkiWiki/CGI.pm +++ b/IkiWiki/CGI.pm @@ -239,6 +239,9 @@ sub check_banned ($$) { print $q->header(-status => "403 Forbidden"); $session->delete(); print gettext("You are banned."); + # Internet Explorer won't show custom 404 responses + # unless they're >= 512 bytes + print " " x 512; cgi_savesession($session); exit; } @@ -317,7 +320,7 @@ sub cgi (;$$) { error("\"do\" parameter missing"); } } - + # Need to lock the wiki before getting a session. lockwiki(); loadindex(); diff --git a/IkiWiki/Plugin/apache404.pm b/IkiWiki/Plugin/apache404.pm new file mode 100644 index 000000000..e7ce70435 --- /dev/null +++ b/IkiWiki/Plugin/apache404.pm @@ -0,0 +1,77 @@ +#!/usr/bin/perl +# Copyright © 2009 Simon McVittie <http://smcv.pseudorandom.co.uk/> +# Licensed under the GNU GPL, version 2, or any later version published by the +# Free Software Foundation +package IkiWiki::Plugin::apache404; + +use warnings; +use strict; +use IkiWiki 3.00; + +sub import { + hook(type => "cgi", id => 'apache404', call => \&cgi); + IkiWiki::loadplugin("goto"); +} + +sub getsetup () { + return + plugin => { + # not really a matter of safety, but enabling/disabling + # through a web interface is useless - it needs web + # server admin action too + safe => 0, + rebuild => 0, + } +} + +sub cgi_page_from_404 ($$$) { + my $path = shift; + my $baseurl = shift; + my $usedirs = shift; + + # fail if missing from environment or whatever + return undef unless defined $path; + return undef unless defined $baseurl; + + # with usedirs on, path is like /~fred/foo/bar/ or /~fred/foo/bar or + # /~fred/foo/bar/index.html + # with usedirs off, path is like /~fred/foo/bar.html + # baseurl is like 'http://people.example.com/~fred' + + # convert baseurl to ~fred + unless ($baseurl =~ s{^https?://[^/]+/?}{}) { + return undef; + } + + # convert path to /~fred/foo/bar + if ($usedirs) { + $path =~ s/\/*(?:index\.$config{htmlext})?$//; + } + else { + $path =~ s/\.$config{htmlext}$//; + } + + # remove /~fred/ + unless ($path =~ s{^/*\Q$baseurl\E/*}{}) { + return undef; + } + + # special case for the index + unless ($path) { + return 'index'; + } + + return $path; +} + +sub cgi ($) { + my $cgi=shift; + + if ($ENV{REDIRECT_STATUS} eq '404') { + my $page = cgi_page_from_404($ENV{REDIRECT_URL}, + $config{url}, $config{usedirs}); + IkiWiki::Plugin::goto::cgi_goto($cgi, $page); + } +} + +1; diff --git a/IkiWiki/Plugin/comments.pm b/IkiWiki/Plugin/comments.pm index cbb3374c2..3cdffe856 100644 --- a/IkiWiki/Plugin/comments.pm +++ b/IkiWiki/Plugin/comments.pm @@ -25,8 +25,9 @@ sub import { hook(type => "sessioncgi", id => 'comment', call => \&sessioncgi); hook(type => "htmlize", id => "_comment", call => \&htmlize); hook(type => "pagetemplate", id => "comments", call => \&pagetemplate); - hook(type => "cgi", id => "comments", call => \&linkcgi); hook(type => "formbuilder_setup", id => "comments", call => \&formbuilder_setup); + # Load goto to fix up user page links for logged-in commenters + IkiWiki::loadplugin("goto"); IkiWiki::loadplugin("inline"); } @@ -167,7 +168,7 @@ sub preprocess { } else { $commentauthorurl = IkiWiki::cgiurl( - do => 'commenter', + do => 'goto', page => (length $config{userdir} ? "$config{userdir}/$commentuser" : "$commentuser")); @@ -235,35 +236,6 @@ sub preprocess { return $content; } -# This is exactly the same as recentchanges_link :-( -sub linkcgi ($) { - my $cgi=shift; - if (defined $cgi->param('do') && $cgi->param('do') eq "commenter") { - - my $page=decode_utf8($cgi->param("page")); - if (! defined $page) { - error("missing page parameter"); - } - - IkiWiki::loadindex(); - - my $link=bestlink("", $page); - if (! length $link) { - print "Content-type: text/html\n\n"; - print IkiWiki::misctemplate(gettext(gettext("missing page")), - "<p>". - sprintf(gettext("The page %s does not exist."), - htmllink("", "", $page)). - "</p>"); - } - else { - IkiWiki::redirect($cgi, urlto($link, undef, 1)); - } - - exit; - } -} - sub sessioncgi ($$) { my $cgi=shift; my $session=shift; diff --git a/IkiWiki/Plugin/goto.pm b/IkiWiki/Plugin/goto.pm new file mode 100644 index 000000000..9e7a2621f --- /dev/null +++ b/IkiWiki/Plugin/goto.pm @@ -0,0 +1,76 @@ +#!/usr/bin/perl +package IkiWiki::Plugin::goto; + +use warnings; +use strict; +use IkiWiki 3.00; + +sub import { + hook(type => "cgi", id => 'goto', call => \&cgi); +} + +sub getsetup () { + return + plugin => { + safe => 1, + rebuild => 0, + } +} + +# cgi_goto(CGI, [page]) +# Redirect to a specified page, or display "not found". If not specified, +# the page param from the CGI object is used. +sub cgi_goto ($;$) { + my $q = shift; + my $page = shift; + + if (!defined $page) { + $page = IkiWiki::decode_utf8($q->param("page")); + + if (!defined $page) { + error("missing page parameter"); + } + } + + IkiWiki::loadindex(); + + # If the page is internal (like a comment), see if it has a + # permalink. Comments do. + if (IkiWiki::isinternal($page) && + defined $pagestate{$page}{meta}{permalink}) { + redirect($q, $pagestate{$page}{meta}{permalink}); + } + + my $link = bestlink("", $page); + + if (! length $link) { + print $q->header(-status => "404 Not Found"); + print IkiWiki::misctemplate(gettext("missing page"), + "<p>". + sprintf(gettext("The page %s does not exist."), + htmllink("", "", $page)). + "</p>". + # Internet Explorer won't show custom 404 responses + # unless they're >= 512 bytes + (" " x 512)); + } + else { + IkiWiki::redirect($q, urlto($link, undef, 1)); + } + + exit; +} + +sub cgi ($) { + my $cgi=shift; + my $do = $cgi->param('do'); + + if (defined $do && ($do eq 'goto' || $do eq 'commenter' || + $do eq 'recentchanged_link')) { + # goto is the preferred name for this; recentchanges_link and + # commenter are for compatibility with any saved URLs + cgi_goto($cgi); + } +} + +1; diff --git a/IkiWiki/Plugin/recentchanges.pm b/IkiWiki/Plugin/recentchanges.pm index ef108b3f0..329dd6f32 100644 --- a/IkiWiki/Plugin/recentchanges.pm +++ b/IkiWiki/Plugin/recentchanges.pm @@ -13,7 +13,8 @@ sub import { hook(type => "refresh", id => "recentchanges", call => \&refresh); hook(type => "pagetemplate", id => "recentchanges", call => \&pagetemplate); hook(type => "htmlize", id => "_change", call => \&htmlize); - hook(type => "cgi", id => "recentchanges", call => \&cgi); + # Load goto to fix up links from recentchanges + IkiWiki::loadplugin("goto"); } sub getsetup () { @@ -79,48 +80,6 @@ sub htmlize (@) { return $params{content}; } -sub cgi ($) { - my $cgi=shift; - if (defined $cgi->param('do') && $cgi->param('do') eq "recentchanges_link") { - # This is a link from a change page to some - # other page. Since the change pages are only generated - # once, statically, links on them won't be updated if the - # page they link to is deleted, or newly created, or - # changes for whatever reason. So this CGI handles that - # dynamic linking stuff. - my $page=decode_utf8($cgi->param("page")); - if (!defined $page) { - error("missing page parameter"); - } - - IkiWiki::loadindex(); - - # If the page is internal (like a comment), see if it has a - # permalink. Comments do. - if (IkiWiki::isinternal($page) && - defined $pagestate{$page}{meta}{permalink}) { - IkiWiki::redirect($cgi, - $pagestate{$page}{meta}{permalink}); - exit; - } - - my $link=bestlink("", $page); - if (! length $link) { - print "Content-type: text/html\n\n"; - print IkiWiki::misctemplate(gettext(gettext("missing page")), - "<p>". - sprintf(gettext("The page %s does not exist."), - htmllink("", "", $page)). - "</p>"); - } - else { - IkiWiki::redirect($cgi, urlto($link, undef, 1)); - } - - exit; - } -} - sub store ($$$) { my $change=shift; @@ -138,7 +97,7 @@ sub store ($$$) { if (length $config{cgiurl}) { $_->{link} = "<a href=\"". IkiWiki::cgiurl( - do => "recentchanges_link", + do => "goto", page => $_->{page} ). "\" rel=\"nofollow\">". diff --git a/IkiWiki/Wrapper.pm b/IkiWiki/Wrapper.pm index dd9971a34..8a6340f58 100644 --- a/IkiWiki/Wrapper.pm +++ b/IkiWiki/Wrapper.pm @@ -28,7 +28,8 @@ sub gen_wrapper () { my @envsave; push @envsave, qw{REMOTE_ADDR QUERY_STRING REQUEST_METHOD REQUEST_URI CONTENT_TYPE CONTENT_LENGTH GATEWAY_INTERFACE - HTTP_COOKIE REMOTE_USER HTTPS} if $config{cgi}; + HTTP_COOKIE REMOTE_USER HTTPS REDIRECT_STATUS + REDIRECT_URL} if $config{cgi}; my $envsave=""; foreach my $var (@envsave) { $envsave.=<<"EOF"; diff --git a/doc/plugins/apache404.mdwn b/doc/plugins/apache404.mdwn new file mode 100644 index 000000000..bab8fb59d --- /dev/null +++ b/doc/plugins/apache404.mdwn @@ -0,0 +1,11 @@ +[[!template id=plugin name=apache404 author="[[Simon_McVittie|smcv]]"]] +[[!tag type/useful]] + +This plugin lets you use the IkiWiki CGI script as an Apache 404 handler, +to give the behaviour of various other wiki engines where visiting a +nonexistent page provides you with a link to create it. + +To achieve this, put something like this in the wiki's Apache configuration +file: + + ErrorDocument 404 /cgi-bin/ikiwiki.cgi diff --git a/doc/plugins/goto.mdwn b/doc/plugins/goto.mdwn new file mode 100644 index 000000000..21dda16b2 --- /dev/null +++ b/doc/plugins/goto.mdwn @@ -0,0 +1,10 @@ +[[!template id=plugin name=goto author="[[Simon_McVittie|smcv]]"]] +[[!tag type/useful]] + +This plugin adds a `do=goto` mode for the IkiWiki CGI script. It's mainly +for internal use by the [[apache404]], [[comments]] and [[recentchanges]] +plugins, which enable it automatically. + +With this plugin enabled you can link to `ikiwiki.cgi?do=goto&page=some/where` +to make a link that will redirect to the page `/some/where` if it exists, or +offer a link to create it if it doesn't. diff --git a/t/apache404.t b/t/apache404.t new file mode 100755 index 000000000..00fc35250 --- /dev/null +++ b/t/apache404.t @@ -0,0 +1,45 @@ +#!/usr/bin/perl +use warnings; +use strict; +use Test::More tests => 17; + +BEGIN { use_ok("IkiWiki::Plugin::apache404"); } + +sub cgi_page_from_404 { + return IkiWiki::Plugin::apache404::cgi_page_from_404(shift, shift, + shift); +} + +$IkiWiki::config{htmlext} = 'html'; + +is(cgi_page_from_404('/', 'http://example.com', 1), 'index'); +is(cgi_page_from_404('/index.html', 'http://example.com', 0), 'index'); +is(cgi_page_from_404('/', 'http://example.com/', 1), 'index'); +is(cgi_page_from_404('/index.html', 'http://example.com/', 0), 'index'); + +is(cgi_page_from_404('/~user/foo/bar', 'http://example.com/~user', 1), + 'foo/bar'); +is(cgi_page_from_404('/~user/foo/bar/index.html', 'http://example.com/~user', 1), + 'foo/bar'); +is(cgi_page_from_404('/~user/foo/bar/', 'http://example.com/~user', 1), + 'foo/bar'); +is(cgi_page_from_404('/~user/foo/bar.html', 'http://example.com/~user', 0), + 'foo/bar'); + +is(cgi_page_from_404('/~user/foo/bar', 'http://example.com/~user/', 1), + 'foo/bar'); +is(cgi_page_from_404('/~user/foo/bar/index.html', 'http://example.com/~user/', 1), + 'foo/bar'); +is(cgi_page_from_404('/~user/foo/bar/', 'http://example.com/~user/', 1), + 'foo/bar'); +is(cgi_page_from_404('/~user/foo/bar.html', 'http://example.com/~user/', 0), + 'foo/bar'); + +is(cgi_page_from_404('/~user/foo', 'https://example.com/~user', 1), + 'foo'); +is(cgi_page_from_404('/~user/foo/index.html', 'https://example.com/~user', 1), + 'foo'); +is(cgi_page_from_404('/~user/foo/', 'https://example.com/~user', 1), + 'foo'); +is(cgi_page_from_404('/~user/foo.html', 'https://example.com/~user', 0), + 'foo'); |