X-Git-Url: http://git.vanrenterghem.biz/git.ikiwiki.info.git/blobdiff_plain/84efd3e00ff91cd486c15b4291abe400e61afd7b..15250ee6bfdb66cd823fb856bda84e372647f61f:/IkiWiki.pm?ds=sidebyside

diff --git a/IkiWiki.pm b/IkiWiki.pm
index 8244fa996..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;
@@ -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 ($) {
@@ -1640,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 <span class=\"error\">".
 						gettext("Error").": $error"."</span>]]";
 				}
@@ -1817,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);
@@ -2438,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 ($$) {