aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--IkiWiki.pm50
-rw-r--r--IkiWiki/CGI.pm8
-rw-r--r--IkiWiki/Plugin/comments.pm15
-rw-r--r--IkiWiki/Plugin/editpage.pm14
-rw-r--r--IkiWiki/Plugin/goto.pm2
-rw-r--r--IkiWiki/Plugin/img.pm4
-rw-r--r--IkiWiki/Plugin/inline.pm2
-rw-r--r--IkiWiki/Plugin/openid.pm8
-rw-r--r--IkiWiki/Plugin/parentlinks.pm2
-rw-r--r--IkiWiki/Plugin/pinger.pm2
-rw-r--r--IkiWiki/Plugin/poll.pm6
-rw-r--r--IkiWiki/Plugin/recentchanges.pm8
-rw-r--r--IkiWiki/Plugin/relativedate.pm11
-rw-r--r--IkiWiki/Plugin/remove.pm4
-rw-r--r--IkiWiki/Plugin/rename.pm2
-rw-r--r--IkiWiki/Plugin/search.pm4
-rw-r--r--IkiWiki/Plugin/toggle.pm11
-rw-r--r--IkiWiki/Plugin/websetup.pm4
-rw-r--r--IkiWiki/Plugin/wmd.pm9
-rw-r--r--doc/plugins/write.mdwn4
-rwxr-xr-xt/urlto.t44
21 files changed, 153 insertions, 61 deletions
diff --git a/IkiWiki.pm b/IkiWiki.pm
index c06751dd3..a893edb3b 100644
--- a/IkiWiki.pm
+++ b/IkiWiki.pm
@@ -501,6 +501,12 @@ sub defaultconfig () {
return @ret;
}
+# URL to top of wiki as a path starting with /, valid from any wiki page or
+# the CGI; if that's not possible, an absolute URL. Either way, it ends with /
+my $local_url;
+# URL to CGI script, similar to $local_url
+my $local_cgiurl;
+
sub checkconfig () {
# locale stuff; avoid LC_ALL since it overrides everything
if (defined $ENV{LC_ALL}) {
@@ -537,7 +543,33 @@ sub checkconfig () {
if ($config{cgi} && ! length $config{url}) {
error(gettext("Must specify url to wiki with --url when using --cgi"));
}
-
+
+ if (length $config{url}) {
+ eval q{use URI};
+ my $baseurl = URI->new($config{url});
+
+ $local_url = $baseurl->path . "/";
+ $local_cgiurl = undef;
+
+ if (length $config{cgiurl}) {
+ my $cgiurl = URI->new($config{cgiurl});
+
+ $local_cgiurl = $cgiurl->path;
+
+ if ($cgiurl->scheme ne $baseurl->scheme or
+ $cgiurl->authority ne $baseurl->authority) {
+ # too far apart, fall back to absolute URLs
+ $local_url = "$config{url}/";
+ $local_cgiurl = $config{cgiurl};
+ }
+ }
+
+ $local_url =~ s{//$}{/};
+ }
+ else {
+ $local_cgiurl = $config{cgiurl};
+ }
+
$config{wikistatedir}="$config{srcdir}/.ikiwiki"
unless exists $config{wikistatedir} && defined $config{wikistatedir};
@@ -1010,11 +1042,17 @@ sub linkpage ($) {
sub cgiurl (@) {
my %params=@_;
- my $cgiurl=$config{cgiurl};
+ my $cgiurl=$local_cgiurl;
+
if (exists $params{cgiurl}) {
$cgiurl=$params{cgiurl};
delete $params{cgiurl};
}
+
+ unless (%params) {
+ return $cgiurl;
+ }
+
return $cgiurl."?".
join("&", map $_."=".uri_escape_utf8($params{$_}), keys %params);
}
@@ -1022,7 +1060,7 @@ sub cgiurl (@) {
sub baseurl (;$) {
my $page=shift;
- return "$config{url}/" if ! defined $page;
+ return $local_url if ! defined $page;
$page=htmlpage($page);
$page=~s/[^\/]+$//;
@@ -1113,6 +1151,12 @@ sub urlto ($$;$) {
return $config{url}.beautify_urlpath("/".$to);
}
+ if (! defined $from) {
+ my $u = $local_url;
+ $u =~ s{/$}{};
+ return $u.beautify_urlpath("/".$to);
+ }
+
my $link = abs2rel($to, dirname(htmlpage($from)));
return beautify_urlpath($link);
diff --git a/IkiWiki/CGI.pm b/IkiWiki/CGI.pm
index f2a32a958..e0bfda9ae 100644
--- a/IkiWiki/CGI.pm
+++ b/IkiWiki/CGI.pm
@@ -116,7 +116,7 @@ sub cgi_signin ($$;$) {
required => 'NONE',
javascript => 0,
params => $q,
- action => $config{cgiurl},
+ action => cgiurl(),
header => 0,
template => {type => 'div'},
stylesheet => 1,
@@ -198,7 +198,7 @@ sub cgi_prefs ($$) {
required => 'NONE',
javascript => 0,
params => $q,
- action => $config{cgiurl},
+ action => cgiurl(),
template => {type => 'div'},
stylesheet => 1,
fieldsets => [
@@ -231,11 +231,11 @@ sub cgi_prefs ($$) {
if ($form->submitted eq 'Logout') {
$session->delete();
- redirect($q, $config{url});
+ redirect($q, baseurl(undef));
return;
}
elsif ($form->submitted eq 'Cancel') {
- redirect($q, $config{url});
+ redirect($q, baseurl(undef));
return;
}
elsif ($form->submitted eq 'Save Preferences' && $form->validate) {
diff --git a/IkiWiki/Plugin/comments.pm b/IkiWiki/Plugin/comments.pm
index a39dab36c..a3cb7c3d5 100644
--- a/IkiWiki/Plugin/comments.pm
+++ b/IkiWiki/Plugin/comments.pm
@@ -237,7 +237,7 @@ sub preprocess {
}
if ($params{page} =~ m/\/\Q$config{comments_pagename}\E\d+_/) {
- $pagestate{$page}{meta}{permalink} = urlto(IkiWiki::dirname($params{page}), undef, 1).
+ $pagestate{$page}{meta}{permalink} = urlto(IkiWiki::dirname($params{page}), undef).
"#".page_to_id($params{page});
}
@@ -301,7 +301,7 @@ sub editcomment ($$) {
required => [qw{editcontent}],
javascript => 0,
params => $cgi,
- action => $config{cgiurl},
+ action => IkiWiki::cgiurl(),
header => 0,
table => 0,
template => { template('editcomment.tmpl') },
@@ -372,7 +372,7 @@ sub editcomment ($$) {
error(gettext("bad page name"));
}
- my $baseurl = urlto($page, undef, 1);
+ my $baseurl = urlto($page, undef);
$form->title(sprintf(gettext("commenting on %s"),
IkiWiki::pagetitle($page)));
@@ -386,7 +386,7 @@ sub editcomment ($$) {
if ($form->submitted eq CANCEL) {
# bounce back to the page they wanted to comment on, and exit.
# CANCEL need not be considered in future
- IkiWiki::redirect($cgi, urlto($page, undef, 1));
+ IkiWiki::redirect($cgi, urlto($page, undef));
exit;
}
@@ -552,7 +552,7 @@ sub editcomment ($$) {
# Jump to the new comment on the page.
# The trailing question mark tries to avoid broken
# caches and get the most recent version of the page.
- IkiWiki::redirect($cgi, urlto($page, undef, 1).
+ IkiWiki::redirect($cgi, urlto($page, undef).
"?updated#".page_to_id($location));
}
@@ -656,6 +656,7 @@ sub commentmoderation ($$) {
$template->param(
sid => $session->id,
comments => \@comments,
+ cgiurl => IkiWiki::cgiurl(),
);
IkiWiki::printheader($session);
my $out=$template->output;
@@ -810,14 +811,14 @@ sub pagetemplate (@) {
if ($shown) {
if ($template->query(name => 'commentsurl')) {
$template->param(commentsurl =>
- urlto($page, undef, 1).'#comments');
+ urlto($page, undef).'#comments');
}
if ($template->query(name => 'atomcommentsurl') && $config{usedirs}) {
# This will 404 until there are some comments, but I
# think that's probably OK...
$template->param(atomcommentsurl =>
- urlto($page, undef, 1).'comments.atom');
+ urlto($page, undef).'comments.atom');
}
if ($template->query(name => 'commentslink')) {
diff --git a/IkiWiki/Plugin/editpage.pm b/IkiWiki/Plugin/editpage.pm
index 1a04a72b5..b3ae1fded 100644
--- a/IkiWiki/Plugin/editpage.pm
+++ b/IkiWiki/Plugin/editpage.pm
@@ -75,7 +75,7 @@ sub cgi_editpage ($$) {
required => [qw{editcontent}],
javascript => 0,
params => $q,
- action => $config{cgiurl},
+ action => IkiWiki::cgiurl(),
header => 0,
table => 0,
template => { template("editpage.tmpl") },
@@ -98,7 +98,7 @@ sub cgi_editpage ($$) {
error(gettext("bad page name"));
}
- my $baseurl = urlto($page, undef, 1);
+ my $baseurl = urlto($page, undef);
my $from;
if (defined $form->field('from')) {
@@ -156,13 +156,13 @@ sub cgi_editpage ($$) {
my $previewing=0;
if ($form->submitted eq "Cancel") {
if ($form->field("do") eq "create" && defined $from) {
- redirect($q, urlto($from, undef, 1));
+ redirect($q, urlto($from, undef));
}
elsif ($form->field("do") eq "create") {
- redirect($q, $config{url});
+ redirect($q, baseurl(undef));
}
else {
- redirect($q, urlto($page, undef, 1));
+ redirect($q, urlto($page, undef));
}
exit;
}
@@ -262,7 +262,7 @@ sub cgi_editpage ($$) {
@page_locs=$page;
}
else {
- redirect($q, urlto($page, undef, 1));
+ redirect($q, urlto($page, undef));
exit;
}
}
@@ -434,7 +434,7 @@ sub cgi_editpage ($$) {
else {
# The trailing question mark tries to avoid broken
# caches and get the most recent version of the page.
- redirect($q, urlto($page, undef, 1)."?updated");
+ redirect($q, urlto($page, undef)."?updated");
}
}
diff --git a/IkiWiki/Plugin/goto.pm b/IkiWiki/Plugin/goto.pm
index 348a663ef..7f31fa5ca 100644
--- a/IkiWiki/Plugin/goto.pm
+++ b/IkiWiki/Plugin/goto.pm
@@ -64,7 +64,7 @@ sub cgi_goto ($;$) {
)
}
else {
- IkiWiki::redirect($q, urlto($link, undef, 1));
+ IkiWiki::redirect($q, urlto($link, undef));
}
exit;
diff --git a/IkiWiki/Plugin/img.pm b/IkiWiki/Plugin/img.pm
index bd527c8c8..b554a3dfc 100644
--- a/IkiWiki/Plugin/img.pm
+++ b/IkiWiki/Plugin/img.pm
@@ -152,8 +152,8 @@ sub preprocess (@) {
$imgurl=urlto($imglink, $params{destpage});
}
else {
- $fileurl="$config{url}/$file";
- $imgurl="$config{url}/$imglink";
+ $fileurl=urlto($file, undef);
+ $imgurl=urlto($imglink, undef);
}
if (! exists $params{class}) {
diff --git a/IkiWiki/Plugin/inline.pm b/IkiWiki/Plugin/inline.pm
index 1fe40a5ea..765271564 100644
--- a/IkiWiki/Plugin/inline.pm
+++ b/IkiWiki/Plugin/inline.pm
@@ -300,7 +300,7 @@ sub preprocess_inline (@) {
IkiWiki->can("cgi_editpage")) {
# Add a blog post form, with feed buttons.
my $formtemplate=template_depends("blogpost.tmpl", $params{page}, blind_cache => 1);
- $formtemplate->param(cgiurl => $config{cgiurl});
+ $formtemplate->param(cgiurl => IkiWiki::cgiurl());
$formtemplate->param(rootpage => rootpage(%params));
$formtemplate->param(rssurl => $rssurl) if $feeds && $rss;
$formtemplate->param(atomurl => $atomurl) if $feeds && $atom;
diff --git a/IkiWiki/Plugin/openid.pm b/IkiWiki/Plugin/openid.pm
index fae9fb77f..0220a3cf6 100644
--- a/IkiWiki/Plugin/openid.pm
+++ b/IkiWiki/Plugin/openid.pm
@@ -77,7 +77,7 @@ sub openid_selector {
my $template=IkiWiki::template("openid-selector.tmpl");
$template->param(
- cgiurl => $config{cgiurl},
+ cgiurl => IkiWiki::cgiurl(),
(defined $openid_error ? (openid_error => $openid_error) : ()),
(defined $openid_url ? (openid_url => $openid_url) : ()),
($real_cgi_signin ? (nonopenidform => $real_cgi_signin->($q, $session, 1)) : ()),
@@ -148,7 +148,7 @@ sub validate ($$$;$) {
}
my $cgiurl=$config{openid_cgiurl};
- $cgiurl=$config{cgiurl} if ! defined $cgiurl;
+ $cgiurl=IkiWiki::cgiurl() if ! defined $cgiurl;
my $trust_root=$config{openid_realm};
$trust_root=$cgiurl if ! defined $trust_root;
@@ -175,7 +175,7 @@ sub auth ($$) {
IkiWiki::redirect($q, $setup_url);
}
elsif ($csr->user_cancel) {
- IkiWiki::redirect($q, $config{url});
+ IkiWiki::redirect($q, IkiWiki::baseurl(undef));
}
elsif (my $vident = $csr->verified_identity) {
$session->param(name => $vident->url);
@@ -249,7 +249,7 @@ sub getobj ($$) {
}
my $cgiurl=$config{openid_cgiurl};
- $cgiurl=$config{cgiurl} if ! defined $cgiurl;
+ $cgiurl=IkiWiki::cgiurl() if ! defined $cgiurl;
return Net::OpenID::Consumer->new(
ua => $ua,
diff --git a/IkiWiki/Plugin/parentlinks.pm b/IkiWiki/Plugin/parentlinks.pm
index bbd2c5752..203ea13b6 100644
--- a/IkiWiki/Plugin/parentlinks.pm
+++ b/IkiWiki/Plugin/parentlinks.pm
@@ -27,7 +27,7 @@ sub parentlinks ($) {
if (! length $page) {
# dynamic page
return {
- url => $config{url},
+ url => IkiWiki::baseurl(undef),
page => $config{wikiname},
};
}
diff --git a/IkiWiki/Plugin/pinger.pm b/IkiWiki/Plugin/pinger.pm
index cc9f52d2b..932619496 100644
--- a/IkiWiki/Plugin/pinger.pm
+++ b/IkiWiki/Plugin/pinger.pm
@@ -106,6 +106,8 @@ sub ping {
# only ping when a page was changed, so a ping loop
# will still be avoided.
next if $url=~/^\Q$config{cgiurl}\E/;
+ my $local_cgiurl = IkiWiki::cgiurl();
+ next if $url=~/^\Q$local_cgiurl\E/;
$ua->get($url);
}
diff --git a/IkiWiki/Plugin/poll.pm b/IkiWiki/Plugin/poll.pm
index b333e2cdc..27df104ab 100644
--- a/IkiWiki/Plugin/poll.pm
+++ b/IkiWiki/Plugin/poll.pm
@@ -52,7 +52,7 @@ sub preprocess (@) {
foreach my $choice (@choices) {
if ($open && exists $config{cgiurl}) {
# use POST to avoid robots
- $ret.="<form method=\"POST\" action=\"$config{cgiurl}\">\n";
+ $ret.="<form method=\"POST\" action=\"".IkiWiki::cgiurl()."\">\n";
}
my $percent=$total > 0 ? int($choices{$choice} / $total * 100) : 0;
$ret.="<p>\n";
@@ -103,7 +103,7 @@ sub sessioncgi ($$) {
my $oldchoice=$session->param($choice_param);
if (defined $oldchoice && $oldchoice eq $choice) {
# Same vote; no-op.
- IkiWiki::redirect($cgi, urlto($page, undef, 1));
+ IkiWiki::redirect($cgi, urlto($page, undef));
exit;
}
@@ -153,7 +153,7 @@ sub sessioncgi ($$) {
error($@) if $@;
my $cookie = CGI::Cookie->new(-name=> $session->name, -value=> $session->id);
print $cgi->redirect(-cookie => $cookie,
- -url => urlto($page, undef, 1));
+ -url => urlto($page, undef));
exit;
}
}
diff --git a/IkiWiki/Plugin/recentchanges.pm b/IkiWiki/Plugin/recentchanges.pm
index 562f61d40..de12bb2c6 100644
--- a/IkiWiki/Plugin/recentchanges.pm
+++ b/IkiWiki/Plugin/recentchanges.pm
@@ -84,7 +84,7 @@ sub sessioncgi ($$) {
method => 'POST',
javascript => 0,
params => $q,
- action => $config{cgiurl},
+ action => IkiWiki::cgiurl(),
stylesheet => 1,
template => { template('revert.tmpl') },
fields => [qw{revertmessage do sid rev}],
@@ -127,7 +127,7 @@ sub sessioncgi ($$) {
exit 0;
}
- IkiWiki::redirect($q, urlto($config{recentchangespage}, ''));
+ IkiWiki::redirect($q, urlto($config{recentchangespage}, undef));
exit 0;
}
@@ -178,7 +178,7 @@ sub store ($$$) {
else {
$_->{link} = pagetitle($_->{page});
}
- $_->{baseurl}="$config{url}/" if length $config{url};
+ $_->{baseurl}=IkiWiki::baseurl(undef) if length $config{url};
$_;
} @{$change->{pages}}
@@ -226,7 +226,7 @@ sub store ($$$) {
wikiname => $config{wikiname},
);
- $template->param(permalink => "$config{url}/$config{recentchangespage}/#change-".titlepage($change->{rev}))
+ $template->param(permalink => urlto($config{recentchangespage}, undef)."#change-".titlepage($change->{rev}))
if exists $config{url};
IkiWiki::run_hooks(pagetemplate => sub {
diff --git a/IkiWiki/Plugin/relativedate.pm b/IkiWiki/Plugin/relativedate.pm
index 7296889ab..4ae0be861 100644
--- a/IkiWiki/Plugin/relativedate.pm
+++ b/IkiWiki/Plugin/relativedate.pm
@@ -28,18 +28,17 @@ sub format (@) {
if (! ($params{content}=~s!^(<body[^>]*>)!$1.include_javascript($params{page})!em)) {
# no <body> tag, probably in preview mode
- $params{content}=include_javascript($params{page}, 1).$params{content};
+ $params{content}=include_javascript(undef).$params{content};
}
return $params{content};
}
-sub include_javascript ($;$) {
- my $page=shift;
- my $absolute=shift;
+sub include_javascript ($) {
+ my $from=shift;
- return '<script src="'.urlto("ikiwiki/ikiwiki.js", $page, $absolute).
+ return '<script src="'.urlto("ikiwiki/ikiwiki.js", $from).
'" type="text/javascript" charset="utf-8"></script>'."\n".
- '<script src="'.urlto("ikiwiki/relativedate.js", $page, $absolute).
+ '<script src="'.urlto("ikiwiki/relativedate.js", $from).
'" type="text/javascript" charset="utf-8"></script>';
}
diff --git a/IkiWiki/Plugin/remove.pm b/IkiWiki/Plugin/remove.pm
index 1717c8cf3..336a781ed 100644
--- a/IkiWiki/Plugin/remove.pm
+++ b/IkiWiki/Plugin/remove.pm
@@ -100,7 +100,7 @@ sub confirmation_form ($$) {
method => 'POST',
javascript => 0,
params => $q,
- action => $config{cgiurl},
+ action => IkiWiki::cgiurl(),
stylesheet => 1,
fields => [qw{do page}],
);
@@ -240,7 +240,7 @@ sub sessioncgi ($$) {
if (! exists $pagesources{$parent}) {
$parent="index";
}
- IkiWiki::redirect($q, urlto($parent, '/', 1));
+ IkiWiki::redirect($q, urlto($parent, undef));
}
}
else {
diff --git a/IkiWiki/Plugin/rename.pm b/IkiWiki/Plugin/rename.pm
index a05e85be0..57747d3c9 100644
--- a/IkiWiki/Plugin/rename.pm
+++ b/IkiWiki/Plugin/rename.pm
@@ -126,7 +126,7 @@ sub rename_form ($$$) {
method => 'POST',
javascript => 0,
params => $q,
- action => $config{cgiurl},
+ action => IkiWiki::cgiurl(),
stylesheet => 1,
fields => [qw{do page new_name attachment}],
);
diff --git a/IkiWiki/Plugin/search.pm b/IkiWiki/Plugin/search.pm
index 8fb9dff0c..78eb750b5 100644
--- a/IkiWiki/Plugin/search.pm
+++ b/IkiWiki/Plugin/search.pm
@@ -58,7 +58,7 @@ sub pagetemplate (@) {
if ($template->query(name => "searchform")) {
if (! defined $form) {
my $searchform = template("searchform.tmpl", blind_cache => 1);
- $searchform->param(searchaction => $config{cgiurl});
+ $searchform->param(searchaction => IkiWiki::cgiurl());
$searchform->param(html5 => $config{html5});
$form=$searchform->output;
}
@@ -176,7 +176,7 @@ sub cgi ($) {
# only works for GET requests
chdir("$config{wikistatedir}/xapian") || error("chdir: $!");
$ENV{OMEGA_CONFIG_FILE}="./omega.conf";
- $ENV{CGIURL}=$config{cgiurl},
+ $ENV{CGIURL}=IkiWiki::cgiurl();
IkiWiki::loadindex();
$ENV{HELPLINK}=htmllink("", "", "ikiwiki/searching",
noimageinline => 1, linktext => "Help");
diff --git a/IkiWiki/Plugin/toggle.pm b/IkiWiki/Plugin/toggle.pm
index 1f93f87fe..af4d2ba3a 100644
--- a/IkiWiki/Plugin/toggle.pm
+++ b/IkiWiki/Plugin/toggle.pm
@@ -70,19 +70,18 @@ sub format (@) {
$params{content}=~s/<div class="toggleableend">//g;
if (! ($params{content}=~s!^(<body[^>]*>)!$1.include_javascript($params{page})!em)) {
# no <body> tag, probably in preview mode
- $params{content}=include_javascript($params{page}, 1).$params{content};
+ $params{content}=include_javascript(undef).$params{content};
}
}
return $params{content};
}
-sub include_javascript ($;$) {
- my $page=shift;
- my $absolute=shift;
+sub include_javascript ($) {
+ my $from=shift;
- return '<script src="'.urlto("ikiwiki/ikiwiki.js", $page, $absolute).
+ return '<script src="'.urlto("ikiwiki/ikiwiki.js", $from).
'" type="text/javascript" charset="utf-8"></script>'."\n".
- '<script src="'.urlto("ikiwiki/toggle.js", $page, $absolute).
+ '<script src="'.urlto("ikiwiki/toggle.js", $from).
'" type="text/javascript" charset="utf-8"></script>';
}
diff --git a/IkiWiki/Plugin/websetup.pm b/IkiWiki/Plugin/websetup.pm
index 9c032fdc6..6a5190301 100644
--- a/IkiWiki/Plugin/websetup.pm
+++ b/IkiWiki/Plugin/websetup.pm
@@ -288,7 +288,7 @@ sub showform ($$) {
fieldsets => [
[main => gettext("main")],
],
- action => $config{cgiurl},
+ action => IkiWiki::cgiurl(),
template => {type => 'div'},
stylesheet => 1,
);
@@ -344,7 +344,7 @@ sub showform ($$) {
IkiWiki::decode_form_utf8($form);
if ($form->submitted eq "Cancel") {
- IkiWiki::redirect($cgi, $config{url});
+ IkiWiki::redirect($cgi, IkiWiki::baseurl(undef));
return;
}
elsif (($form->submitted eq 'Save Setup' || $form->submitted eq 'Rebuild Wiki') && $form->validate) {
diff --git a/IkiWiki/Plugin/wmd.pm b/IkiWiki/Plugin/wmd.pm
index 71d7c9d17..134cfb910 100644
--- a/IkiWiki/Plugin/wmd.pm
+++ b/IkiWiki/Plugin/wmd.pm
@@ -31,14 +31,13 @@ sub formbuilder_setup (@) {
$form->field("do") eq "comment";
$form->tmpl_param("wmd_preview", "<div class=\"wmd-preview\"></div>\n".
- include_javascript(undef, 1));
+ include_javascript(undef));
}
-sub include_javascript ($;$) {
- my $page=shift;
- my $absolute=shift;
+sub include_javascript ($) {
+ my $from=shift;
- my $wmdjs=urlto("wmd/wmd.js", $page, $absolute);
+ my $wmdjs=urlto("wmd/wmd.js", $from);
return <<"EOF"
<script type="text/javascript">
wmd_options = {
diff --git a/doc/plugins/write.mdwn b/doc/plugins/write.mdwn
index 6b751f0cd..33db3e707 100644
--- a/doc/plugins/write.mdwn
+++ b/doc/plugins/write.mdwn
@@ -988,6 +988,10 @@ Construct a relative url to the first parameter from the page named by the
second. The first parameter can be either a page name, or some other
destination file, as registered by `will_render`.
+If the second parameter is `undef`, the URL will be valid from any page on the
+wiki, or from the CGI; if possible it'll be a path starting with `/`, but an
+absolute URL will be used if the wiki and the CGI are on different servers.
+
If the third parameter is passed and is true, an absolute url will be
constructed instead of the default relative url.
diff --git a/t/urlto.t b/t/urlto.t
new file mode 100755
index 000000000..a0b3baa45
--- /dev/null
+++ b/t/urlto.t
@@ -0,0 +1,44 @@
+#!/usr/bin/perl
+use warnings;
+use strict;
+use Test::More tests => 19;
+
+BEGIN { use_ok("IkiWiki"); }
+
+$IkiWiki::config{srcdir} = '/does/not/exist/';
+$IkiWiki::config{usedirs} = 1;
+$IkiWiki::config{htmlext} = "HTML";
+$IkiWiki::config{wiki_file_chars} = "A-Za-z0-9._";
+
+$IkiWiki::config{url} = "http://smcv.example.co.uk";
+$IkiWiki::config{cgiurl} = "http://smcv.example.co.uk/cgi-bin/ikiwiki.cgi";
+is(IkiWiki::checkconfig(), 1);
+
+# absolute version
+is(IkiWiki::cgiurl(cgiurl => $config{cgiurl}), "http://smcv.example.co.uk/cgi-bin/ikiwiki.cgi");
+is(IkiWiki::cgiurl(cgiurl => $config{cgiurl}, do => 'badger'), "http://smcv.example.co.uk/cgi-bin/ikiwiki.cgi?do=badger");
+is(IkiWiki::urlto('index', undef, 1), "http://smcv.example.co.uk/");
+is(IkiWiki::urlto('stoats', undef, 1), "http://smcv.example.co.uk/stoats/");
+
+# "local" (absolute path within site) version (default for cgiurl)
+is(IkiWiki::cgiurl(), "/cgi-bin/ikiwiki.cgi");
+is(IkiWiki::cgiurl(do => 'badger'), "/cgi-bin/ikiwiki.cgi?do=badger");
+is(IkiWiki::baseurl(undef), "/");
+is(IkiWiki::urlto('index', undef), "/");
+is(IkiWiki::urlto('stoats', undef), "/stoats/");
+
+# fully-relative version (default for urlto and baseurl)
+is(IkiWiki::baseurl('badger/mushroom'), "../../");
+is(IkiWiki::urlto('badger/mushroom', 'snake'), "../badger/mushroom/");
+
+# explicit cgiurl override
+is(IkiWiki::cgiurl(cgiurl => 'https://foo/ikiwiki'), "https://foo/ikiwiki");
+is(IkiWiki::cgiurl(do => 'badger', cgiurl => 'https://foo/ikiwiki'), "https://foo/ikiwiki?do=badger");
+
+# with url and cgiurl on different sites, "local" degrades to absolute
+$IkiWiki::config{url} = "http://example.co.uk/~smcv";
+$IkiWiki::config{cgiurl} = "http://dynamic.example.co.uk/~smcv/ikiwiki.cgi";
+is(IkiWiki::checkconfig(), 1);
+is(IkiWiki::cgiurl(), "http://dynamic.example.co.uk/~smcv/ikiwiki.cgi");
+is(IkiWiki::baseurl(undef), "http://example.co.uk/~smcv/");
+is(IkiWiki::urlto('stoats', undef), "http://example.co.uk/~smcv/stoats/");