X-Git-Url: http://git.vanrenterghem.biz/git.ikiwiki.info.git/blobdiff_plain/497513e737a92d2115f5d2a16610561b8a1dffdc..15250ee6bfdb66cd823fb856bda84e372647f61f:/IkiWiki.pm diff --git a/IkiWiki.pm b/IkiWiki.pm index 53eb8235b..efb48293a 100644 --- a/IkiWiki.pm +++ b/IkiWiki.pm @@ -165,7 +165,7 @@ sub getsetup () { default_plugins => { type => "internal", default => [qw{mdwn link inline meta htmlscrubber passwordauth - openid emailauth signinedit lockedit conditional + openid signinedit lockedit conditional recentchanges parentlinks editpage templatebody}], description => "plugins to enable by default", @@ -566,6 +566,14 @@ sub getsetup () { safe => 1, rebuild => 1, }, + deterministic => { + type => "boolean", + default => 0, + description => "try harder to produce deterministic output", + safe => 1, + rebuild => 1, + advanced => 1, + }, } sub getlibdirs () { @@ -835,10 +843,9 @@ sub log_message ($$) { $log_open=1; } eval { - # keep a copy to avoid editing the original config repeatedly - my $wikiname = $config{wikiname}; - utf8::encode($wikiname); - Sys::Syslog::syslog($type, "[$wikiname] %s", join(" ", @_)); + my $message = "[$config{wikiname}] ".join(" ", @_); + utf8::encode($message); + Sys::Syslog::syslog($type, "%s", $message); }; if ($@) { print STDERR "failed to syslog: $@" unless $log_failed; @@ -1217,7 +1224,7 @@ sub cgiurl (@) { } return $cgiurl."?". - join("&", map $_."=".uri_escape_utf8($params{$_}), keys %params); + join("&", map $_."=".uri_escape_utf8($params{$_}), sort(keys %params)); } sub cgiurl_abs (@) { @@ -1225,6 +1232,19 @@ sub cgiurl_abs (@) { URI->new_abs(cgiurl(@_), $config{cgiurl}); } +# Same as cgiurl_abs, but when the user connected using https, +# will be a https url even if the cgiurl is normally a http url. +# +# This should be used for anything involving emailing a login link, +# because a https session cookie will not be sent over http. +sub cgiurl_abs_samescheme (@) { + my $u=cgiurl_abs(@_); + if (($ENV{HTTPS} && lc $ENV{HTTPS} ne "off")) { + $u=~s/^http:/https:/i; + } + return $u +} + sub baseurl (;$) { my $page=shift; @@ -1286,14 +1306,20 @@ sub formattime ($;$) { my $strftime_encoding; sub strftime_utf8 { - # strftime doesn't know about encodings, so make sure + # strftime didn't know about encodings in older Perl, so make sure # its output is properly treated as utf8. # Note that this does not handle utf-8 in the format string. + my $result = POSIX::strftime(@_); + + if (Encode::is_utf8($result)) { + return $result; + } + ($strftime_encoding) = POSIX::setlocale(&POSIX::LC_TIME) =~ m#\.([^@]+)# unless defined $strftime_encoding; $strftime_encoding - ? Encode::decode($strftime_encoding, POSIX::strftime(@_)) - : POSIX::strftime(@_); + ? Encode::decode($strftime_encoding, $result) + : $result; } sub date_3339 ($) { @@ -1430,6 +1456,7 @@ sub userpage ($) { return length $config{userdir} ? "$config{userdir}/$user" : $user; } +# Username to display for openid accounts. sub openiduser ($) { my $user=shift; @@ -1464,14 +1491,36 @@ sub openiduser ($) { return; } +# Username to display for emailauth accounts. sub emailuser ($) { my $user=shift; if (defined $user && $user =~ m/(.+)@/) { - return $1; + my $nick=$1; + # remove any characters from not allowed in wiki files + # support use w/o %config set + my $chars = defined $config{wiki_file_chars} ? $config{wiki_file_chars} : "-[:alnum:]+/.:_"; + $nick=~s/[^$chars]/_/g; + return $nick; } return; } +# Some user information should not be exposed in commit metadata, etc. +# This generates a cloaked form of such information. +sub cloak ($) { + my $user=shift; + # cloak email address using http://xmlns.com/foaf/spec/#term_mbox_sha1sum + if ($user=~m/(.+)@/) { + my $nick=$1; + eval q{use Digest::SHA}; + return $user if $@; + return $nick.'@'.Digest::SHA::sha1_hex("mailto:$user"); + } + else { + return $user; + } +} + sub htmlize ($$$$) { my $page=shift; my $destpage=shift; @@ -1617,6 +1666,11 @@ sub preprocess ($$$;$$) { if ($@) { my $error=$@; chomp $error; + eval q{use HTML::Entities}; + # Also encode most ASCII punctuation + # as entities so that error messages + # are not interpreted as Markdown etc. + $error = encode_entities($error, '^-A-Za-z0-9+_,./:;= '."'"); $ret="[[!$command ". gettext("Error").": $error"."]]"; } @@ -1794,7 +1848,7 @@ sub check_canchange (@) { $file=possibly_foolish_untaint($file); if (! defined $file || ! length $file || file_pruned($file)) { - error(gettext("bad file name %s"), $file); + error(sprintf(gettext("bad file name %s"), $file)); } my $type=pagetype($file); @@ -2415,12 +2469,131 @@ sub add_autofile ($$$) { $autofiles{$file}{generator}=$generator; } -sub useragent () { - return LWP::UserAgent->new( - cookie_jar => $config{cookiejar}, - env_proxy => 1, # respect proxy env vars +sub useragent (@) { + my %params = @_; + my $for_url = delete $params{for_url}; + # Fail safe, in case a plugin calling this function is relying on + # a future parameter to make the UA more strict + foreach my $key (keys %params) { + error "Internal error: useragent(\"$key\" => ...) not understood"; + } + + eval q{use LWP}; + error($@) if $@; + + my %args = ( agent => $config{useragent}, + cookie_jar => $config{cookiejar}, + env_proxy => 0, + protocols_allowed => [qw(http https)], ); + my %proxies; + + if (defined $for_url) { + # We know which URL we're going to fetch, so we can choose + # whether it's going to go through a proxy or not. + # + # We reimplement http_proxy, https_proxy and no_proxy here, so + # that we are not relying on LWP implementing them exactly the + # same way we do. + + eval q{use URI}; + error($@) if $@; + + my $proxy; + my $uri = URI->new($for_url); + + if ($uri->scheme eq 'http') { + $proxy = $ENV{http_proxy}; + # HTTP_PROXY is deliberately not implemented + # because the HTTP_* namespace is also used by CGI + } + elsif ($uri->scheme eq 'https') { + $proxy = $ENV{https_proxy}; + $proxy = $ENV{HTTPS_PROXY} unless defined $proxy; + } + else { + $proxy = undef; + } + + foreach my $var (qw(no_proxy NO_PROXY)) { + my $no_proxy = $ENV{$var}; + if (defined $no_proxy) { + foreach my $domain (split /\s*,\s*/, $no_proxy) { + if ($domain =~ s/^\*?\.//) { + # no_proxy="*.example.com" or + # ".example.com": match suffix + # against .example.com + if ($uri->host =~ m/(^|\.)\Q$domain\E$/i) { + $proxy = undef; + } + } + else { + # no_proxy="example.com": + # match exactly example.com + if (lc $uri->host eq lc $domain) { + $proxy = undef; + } + } + } + } + } + + if (defined $proxy) { + $proxies{$uri->scheme} = $proxy; + # Paranoia: make sure we can't bypass the proxy + $args{protocols_allowed} = [$uri->scheme]; + } + } + else { + # The plugin doesn't know yet which URL(s) it's going to + # fetch, so we have to make some conservative assumptions. + my $http_proxy = $ENV{http_proxy}; + my $https_proxy = $ENV{https_proxy}; + $https_proxy = $ENV{HTTPS_PROXY} unless defined $https_proxy; + + # We don't respect no_proxy here: if we are not using the + # paranoid user-agent, then we need to give the proxy the + # opportunity to reject undesirable requests. + + # If we have one, we need the other: otherwise, neither + # LWPx::ParanoidAgent nor the proxy would have the + # opportunity to filter requests for the other protocol. + if (defined $https_proxy && defined $http_proxy) { + %proxies = (http => $http_proxy, https => $https_proxy); + } + elsif (defined $https_proxy) { + %proxies = (http => $https_proxy, https => $https_proxy); + } + elsif (defined $http_proxy) { + %proxies = (http => $http_proxy, https => $http_proxy); + } + + } + + if (scalar keys %proxies) { + # The configured proxy is responsible for deciding which + # URLs are acceptable to fetch and which URLs are not. + my $ua = LWP::UserAgent->new(%args); + foreach my $scheme (@{$ua->protocols_allowed}) { + unless ($proxies{$scheme}) { + error "internal error: $scheme is allowed but has no proxy"; + } + } + # We can't pass the proxies in %args because that only + # works since LWP 6.24. + foreach my $scheme (keys %proxies) { + $ua->proxy($scheme, $proxies{$scheme}); + } + return $ua; + } + + eval q{use LWPx::ParanoidAgent}; + if ($@) { + print STDERR "warning: installing LWPx::ParanoidAgent is recommended\n"; + return LWP::UserAgent->new(%args); + } + return LWPx::ParanoidAgent->new(%args); } sub sortspec_translate ($$) {