aboutsummaryrefslogtreecommitdiff
path: root/IkiWiki
diff options
context:
space:
mode:
Diffstat (limited to 'IkiWiki')
-rw-r--r--IkiWiki/CGI.pm35
-rw-r--r--IkiWiki/Plugin/attachment.pm31
-rw-r--r--IkiWiki/Plugin/autoindex.pm3
-rw-r--r--IkiWiki/Plugin/blogspam.pm50
-rw-r--r--IkiWiki/Plugin/brokenlinks.pm2
-rw-r--r--IkiWiki/Plugin/bzr.pm4
-rw-r--r--IkiWiki/Plugin/calendar.pm272
-rw-r--r--IkiWiki/Plugin/comments.pm62
-rw-r--r--IkiWiki/Plugin/cvs.pm4
-rw-r--r--IkiWiki/Plugin/darcs.pm4
-rw-r--r--IkiWiki/Plugin/editpage.pm2
-rw-r--r--IkiWiki/Plugin/emailauth.pm203
-rw-r--r--IkiWiki/Plugin/filecheck.pm4
-rw-r--r--IkiWiki/Plugin/git.pm31
-rw-r--r--IkiWiki/Plugin/goto.pm2
-rw-r--r--IkiWiki/Plugin/haiku.pm8
-rw-r--r--IkiWiki/Plugin/img.pm8
-rw-r--r--IkiWiki/Plugin/inline.pm13
-rw-r--r--IkiWiki/Plugin/loginselector.pm132
-rw-r--r--IkiWiki/Plugin/mercurial.pm4
-rw-r--r--IkiWiki/Plugin/meta.pm1
-rw-r--r--IkiWiki/Plugin/mirrorlist.pm2
-rw-r--r--IkiWiki/Plugin/monotone.pm4
-rw-r--r--IkiWiki/Plugin/openid.pm84
-rw-r--r--IkiWiki/Plugin/passwordauth.pm8
-rw-r--r--IkiWiki/Plugin/po.pm7
-rw-r--r--IkiWiki/Plugin/poll.pm2
-rw-r--r--IkiWiki/Plugin/polygen.pm5
-rw-r--r--IkiWiki/Plugin/recentchanges.pm2
-rw-r--r--IkiWiki/Plugin/remove.pm2
-rw-r--r--IkiWiki/Plugin/rename.pm10
-rw-r--r--IkiWiki/Plugin/svn.pm4
-rw-r--r--IkiWiki/Plugin/templatebody.pm81
-rw-r--r--IkiWiki/Plugin/teximg.pm4
-rw-r--r--IkiWiki/Plugin/tla.pm4
-rw-r--r--IkiWiki/Plugin/websetup.pm8
-rw-r--r--IkiWiki/Render.pm38
-rw-r--r--IkiWiki/Setup/Automator.pm52
-rw-r--r--IkiWiki/Wrapper.pm32
39 files changed, 992 insertions, 232 deletions
diff --git a/IkiWiki/CGI.pm b/IkiWiki/CGI.pm
index cb83319e6..1763828a4 100644
--- a/IkiWiki/CGI.pm
+++ b/IkiWiki/CGI.pm
@@ -58,12 +58,15 @@ sub cgitemplate ($$$;@) {
my $template=template("page.tmpl");
- my $topurl = defined $cgi ? $cgi->url : $config{url};
+ my $topurl = $config{url};
+ if (defined $cgi && ! $config{w3mmode} && ! $config{reverse_proxy}) {
+ $topurl = $cgi->url;
+ }
my $page="";
if (exists $params{page}) {
$page=delete $params{page};
- $params{forcebaseurl}=urlabs(urlto($page), $topurl);
+ $params{forcebaseurl}=urlto($page);
}
run_hooks(pagetemplate => sub {
shift->(
@@ -74,12 +77,14 @@ sub cgitemplate ($$$;@) {
});
templateactions($template, "");
+ my $baseurl = baseurl();
+
$template->param(
dynamic => 1,
title => $title,
wikiname => $config{wikiname},
content => $content,
- baseurl => urlabs(baseurl(), $topurl),
+ baseurl => $baseurl,
html5 => $config{html5},
%params,
);
@@ -90,7 +95,13 @@ sub cgitemplate ($$$;@) {
sub redirect ($$) {
my $q=shift;
eval q{use URI};
- my $url=URI->new(urlabs(shift, $q->url));
+
+ my $topurl;
+ if (defined $q && ! $config{w3mmode} && ! $config{reverse_proxy}) {
+ $topurl = $q->url;
+ }
+
+ my $url=URI->new(urlabs(shift, $topurl));
if (! $config{w3mmode}) {
print $q->redirect($url);
}
@@ -105,16 +116,15 @@ sub decode_cgi_utf8 ($) {
if ($] < 5.01) {
my $cgi = shift;
foreach my $f ($cgi->param) {
- $cgi->param($f, map { decode_utf8 $_ } $cgi->param($f));
+ $cgi->param($f, map { decode_utf8 $_ }
+ @{$cgi->param_fetch($f)});
}
}
}
sub safe_decode_utf8 ($) {
my $octets = shift;
- # call decode_utf8 on >= 5.20 only if it's not already decoded,
- # otherwise it balks, on < 5.20, always call it
- if ($] < 5.02 || !Encode::is_utf8($octets)) {
+ if (!Encode::is_utf8($octets)) {
return decode_utf8($octets);
}
else {
@@ -326,16 +336,19 @@ sub check_banned ($$) {
my $banned=0;
my $name=$session->param("name");
+ my $cloak=cloak($name) if defined $name;
if (defined $name &&
- grep { $name eq $_ } @{$config{banned_users}}) {
+ grep { $name eq $_ || $cloak eq $_ } @{$config{banned_users}}) {
$banned=1;
}
foreach my $b (@{$config{banned_users}}) {
if (pagespec_match("", $b,
ip => $session->remote_addr(),
- name => defined $name ? $name : "",
- )) {
+ name => defined $name ? $name : "")
+ || pagespec_match("", $b,
+ ip => cloak($session->remote_addr()),
+ name => defined $cloak ? $cloak : "")) {
$banned=1;
last;
}
diff --git a/IkiWiki/Plugin/attachment.pm b/IkiWiki/Plugin/attachment.pm
index d56dd18ad..e8135a8fd 100644
--- a/IkiWiki/Plugin/attachment.pm
+++ b/IkiWiki/Plugin/attachment.pm
@@ -132,9 +132,11 @@ sub formbuilder (@) {
return if ! defined $form->field("do") || ($form->field("do") ne "edit" && $form->field("do") ne "create") ;
- my $filename=Encode::decode_utf8($q->param('attachment'));
+ my $filename=Encode::decode_utf8(scalar $q->param('attachment'));
+ my $handle=$q->upload('attachment');
+
if (defined $filename && length $filename) {
- attachment_store($filename, $form, $q, $params{session});
+ attachment_store($filename, $handle, $form, $q, $params{session});
}
if ($form->submitted eq "Save Page") {
@@ -142,9 +144,9 @@ sub formbuilder (@) {
}
if ($form->submitted eq "Insert Links") {
- my $page=quotemeta(Encode::decode_utf8($q->param("page")));
+ my $page=quotemeta(Encode::decode_utf8(scalar $q->param("page")));
my $add="";
- foreach my $f ($q->param("attachment_select")) {
+ foreach my $f (@{$q->param_fetch("attachment_select")}) {
$f=Encode::decode_utf8($f);
$f=~s/^$page\///;
if (IkiWiki::isinlinableimage($f) &&
@@ -190,13 +192,20 @@ sub is_held_attachment {
# Stores the attachment in a holding area, not yet in the wiki proper.
sub attachment_store {
my $filename=shift;
+ my $handle=shift;
my $form=shift;
my $q=shift;
my $session=shift;
-
- # This is an (apparently undocumented) way to get the name
- # of the temp file that CGI writes the upload to.
- my $tempfile=$q->tmpFileName($filename);
+
+ my $tempfile;
+ if (defined $handle) {
+ # This is what works in CGI.pm 4.09+: $q->tmpFileName($q->upload('attachment'))
+ $tempfile=$q->tmpFileName($handle);
+ }
+ if (! defined $tempfile || ! length $tempfile) {
+ # This is what is *documented* in CGI.pm 4.09: $q->tmpFileName($q->param('attachment'))
+ $tempfile=$q->tmpFileName($filename);
+ }
if (! defined $tempfile || ! length $tempfile) {
# perl 5.8 needs an alternative, awful method
if ($q =~ /HASH/ && exists $q->{'.tmpfiles'}) {
@@ -205,9 +214,9 @@ sub attachment_store {
last if defined $tempfile && length $tempfile;
}
}
- if (! defined $tempfile || ! length $tempfile) {
- error("CGI::tmpFileName failed to return the uploaded file name");
- }
+ }
+ if (! defined $tempfile || ! length $tempfile) {
+ error("CGI::tmpFileName failed to return the uploaded file name");
}
$filename=IkiWiki::basename($filename);
diff --git a/IkiWiki/Plugin/autoindex.pm b/IkiWiki/Plugin/autoindex.pm
index 78571b276..d5ee4b58f 100644
--- a/IkiWiki/Plugin/autoindex.pm
+++ b/IkiWiki/Plugin/autoindex.pm
@@ -71,7 +71,6 @@ sub refresh () {
my (%pages, %dirs);
foreach my $dir ($config{srcdir}, @{$config{underlaydirs}}, $config{underlaydir}) {
- next if $dir eq $IkiWiki::Plugin::transient::transientdir;
chdir($dir) || next;
find({
@@ -90,7 +89,7 @@ sub refresh () {
if (! -d _) {
$pages{pagename($f)}=1;
}
- elsif ($dir eq $config{srcdir}) {
+ elsif ($dir eq $config{srcdir} || ! $config{autoindex_commit}) {
$dirs{$f}=1;
}
}
diff --git a/IkiWiki/Plugin/blogspam.pm b/IkiWiki/Plugin/blogspam.pm
index e48ed729f..3eb4cf8b3 100644
--- a/IkiWiki/Plugin/blogspam.pm
+++ b/IkiWiki/Plugin/blogspam.pm
@@ -6,7 +6,8 @@ use strict;
use IkiWiki 3.00;
use Encode;
-my $defaulturl='http://test.blogspam.net:8888/';
+my $defaulturl='http://test.blogspam.net:9999/';
+my $client;
sub import {
hook(type => "getsetup", id => "blogspam", call => \&getsetup);
@@ -33,14 +34,14 @@ sub getsetup () {
type => "string",
example => "blacklist=1.2.3.4,blacklist=8.7.6.5,max-links=10",
description => "options to send to blogspam server",
- link => "http://blogspam.net/api/testComment.html#options",
+ link => "http://blogspam.net/api/2.0/testComment.html#options",
safe => 1,
rebuild => 0,
},
blogspam_server => {
type => "string",
default => $defaulturl,
- description => "blogspam server XML-RPC url",
+ description => "blogspam server JSON url",
safe => 1,
rebuild => 0,
},
@@ -51,11 +52,23 @@ sub checkconfig () {
# if the module is missing when a spam is posted would not
# let the admin know about the problem.
eval q{
- use RPC::XML;
- use RPC::XML::Client;
- $RPC::XML::ENCODING = 'utf-8';
+ use JSON;
+ use HTTP::Request;
};
error $@ if $@;
+
+ eval q{use LWPx::ParanoidAgent};
+ if (!$@) {
+ $client=LWPx::ParanoidAgent->new(agent => $config{useragent});
+ }
+ else {
+ eval q{use LWP};
+ if ($@) {
+ error $@;
+ return;
+ }
+ $client=useragent();
+ }
}
sub checkcontent (@) {
@@ -77,8 +90,6 @@ sub checkcontent (@) {
my $url=$defaulturl;
$url = $config{blogspam_server} if exists $config{blogspam_server};
- my $client = RPC::XML::Client->new($url);
-
my @options = split(",", $config{blogspam_options})
if exists $config{blogspam_options};
@@ -107,19 +118,28 @@ sub checkcontent (@) {
site => encode_utf8($config{url}),
version => "ikiwiki ".$IkiWiki::version,
);
- my $res = $client->send_request('testComment', \%req);
+ eval q{use JSON; use HTTP::Request}; # errors handled in checkconfig()
+ my $res = $client->request(
+ HTTP::Request->new(
+ 'POST',
+ $url,
+ [ 'Content-Type' => 'application/json' ],
+ to_json(\%req),
+ ),
+ );
- if (! ref $res || ! defined $res->value) {
+ if (! ref $res || ! $res->is_success()) {
debug("failed to get response from blogspam server ($url)");
return undef;
}
- elsif ($res->value =~ /^SPAM:(.*)/) {
+ my $details = from_json($res->content);
+ if ($details->{result} eq 'SPAM') {
eval q{use Data::Dumper};
- debug("blogspam server reports ".$res->value.": ".Dumper(\%req));
- return gettext("Sorry, but that looks like spam to <a href=\"http://blogspam.net/\">blogspam</a>: ").$1;
+ debug("blogspam server reports $details->{reason}: ".Dumper(\%req));
+ return gettext("Sorry, but that looks like spam to <a href=\"http://blogspam.net/\">blogspam</a>: ").$details->{reason};
}
- elsif ($res->value ne 'OK') {
- debug("blogspam server failure: ".$res->value);
+ elsif ($details->{result} ne 'OK') {
+ debug("blogspam server failure: ".$res->content);
return undef;
}
else {
diff --git a/IkiWiki/Plugin/brokenlinks.pm b/IkiWiki/Plugin/brokenlinks.pm
index 8ee734bf9..2318298fe 100644
--- a/IkiWiki/Plugin/brokenlinks.pm
+++ b/IkiWiki/Plugin/brokenlinks.pm
@@ -39,7 +39,7 @@ sub preprocess (@) {
htmllink($page, $params{destpage}, $link, noimageinline => 1),
join(", ", map {
htmllink($params{page}, $params{destpage}, $_, noimageinline => 1)
- } @pages)
+ } sort @pages)
);
}
diff --git a/IkiWiki/Plugin/bzr.pm b/IkiWiki/Plugin/bzr.pm
index e2b102dee..5ec254f84 100644
--- a/IkiWiki/Plugin/bzr.pm
+++ b/IkiWiki/Plugin/bzr.pm
@@ -133,10 +133,10 @@ sub bzr_author ($) {
my $ipaddr=$session->remote_addr();
if (defined $user) {
- return IkiWiki::possibly_foolish_untaint($user);
+ return IkiWiki::possibly_foolish_untaint(IkiWiki::cloak($user));
}
elsif (defined $ipaddr) {
- return "Anonymous from ".IkiWiki::possibly_foolish_untaint($ipaddr);
+ return "Anonymous from ".IkiWiki::possibly_foolish_untaint(IkiWiki::cloak($ipaddr));
}
else {
return "Anonymous";
diff --git a/IkiWiki/Plugin/calendar.pm b/IkiWiki/Plugin/calendar.pm
index 682bfb6fb..c03b89667 100644
--- a/IkiWiki/Plugin/calendar.pm
+++ b/IkiWiki/Plugin/calendar.pm
@@ -1,4 +1,4 @@
-#! /usr/bin/perl
+#!/usr/bin/perl
# Copyright (c) 2006, 2007 Manoj Srivastava <srivasta@debian.org>
#
# This program is free software; you can redistribute it and/or modify
@@ -25,11 +25,17 @@ use Time::Local;
my $time=time;
my @now=localtime($time);
+my %changed;
sub import {
+ hook(type => "checkconfig", id => "calendar", call => \&checkconfig);
hook(type => "getsetup", id => "calendar", call => \&getsetup);
hook(type => "needsbuild", id => "calendar", call => \&needsbuild);
hook(type => "preprocess", id => "calendar", call => \&preprocess);
+ hook(type => "scan", id => "calendar", call => \&scan);
+ hook(type => "build_affected", id => "calendar", call => \&build_affected);
+
+ IkiWiki::loadplugin("transient");
}
sub getsetup () {
@@ -49,11 +55,41 @@ sub getsetup () {
archive_pagespec => {
type => "pagespec",
example => "page(posts/*) and !*/Discussion",
- description => "PageSpec of pages to include in the archives; used by ikiwiki-calendar command",
+ description => "PageSpec of pages to include in the archives, if option `calendar_autocreate` is true.",
link => 'ikiwiki/PageSpec',
safe => 1,
rebuild => 0,
},
+ calendar_autocreate => {
+ type => "boolean",
+ example => 1,
+ description => "autocreate new calendar pages?",
+ safe => 1,
+ rebuild => undef,
+ },
+ calendar_fill_gaps => {
+ type => "boolean",
+ example => 1,
+ default => 1,
+ description => "if set, when building calendar pages, also build pages of year and month when no pages were published (building empty calendars).",
+ safe => 1,
+ rebuild => 0,
+ },
+}
+
+sub checkconfig () {
+ if (! defined $config{calendar_autocreate}) {
+ $config{calendar_autocreate} = defined $config{archivebase};
+ }
+ if (! defined $config{archive_pagespec}) {
+ $config{archive_pagespec} = '*';
+ }
+ if (! defined $config{archivebase}) {
+ $config{archivebase} = 'archives';
+ }
+ if (! defined $config{calendar_fill_gaps}) {
+ $config{calendar_fill_gaps} = 1;
+ }
}
sub is_leap_year (@) {
@@ -70,6 +106,184 @@ sub month_days {
return $days_in_month;
}
+sub build_affected {
+ my %affected;
+ my ($ayear, $amonth, $valid);
+
+ foreach my $year (keys %changed) {
+ ($ayear, $valid) = nextyear($year, $config{archivebase});
+ $affected{calendarlink($ayear)} = sprintf(gettext("building calendar for %s, its previous or next year has changed"), $ayear) if ($valid);
+ ($ayear, $valid) = previousyear($year, $config{archivebase});
+ $affected{calendarlink($ayear)} = sprintf(gettext("building calendar for %s, its previous or next year has changed"), $ayear) if ($valid);
+ foreach my $month (keys %{$changed{$year}}) {
+ ($ayear, $amonth, $valid) = nextmonth($year, $month, $config{archivebase});
+ $affected{calendarlink($ayear, sprintf("%02d", $amonth))} = sprintf(gettext("building calendar for %s/%s, its previous or next month has changed"), $amonth, $ayear) if ($valid);
+ ($ayear, $amonth, $valid) = previousmonth($year, $month, $config{archivebase});
+ $affected{calendarlink($ayear, sprintf("%02d", $amonth))} = sprintf(gettext("building calendar for %s/%s, its previous or next month has changed"), $amonth, $ayear) if ($valid);
+ }
+ }
+
+ return %affected;
+}
+
+sub autocreate {
+ my ($page, $pagefile, $year, $month) = @_;
+ my $message=sprintf(gettext("creating calendar page %s"), $page);
+ debug($message);
+
+ my $template;
+ if (defined $month) {
+ $template=template("calendarmonth.tmpl");
+ } else {
+ $template=template("calendaryear.tmpl");
+ }
+ $template->param(year => $year);
+ $template->param(month => $month) if defined $month;
+ $template->param(pagespec => $config{archive_pagespec});
+
+ my $dir = $IkiWiki::Plugin::transient::transientdir;
+
+ writefile($pagefile, $dir, $template->output);
+}
+
+sub calendarlink($;$) {
+ my ($year, $month) = @_;
+ if (defined $month) {
+ return $config{archivebase} . "/" . $year . "/" . $month;
+ } else {
+ return $config{archivebase} . "/" . $year;
+ }
+}
+
+sub gencalendarmonth{
+ my $year = shift;
+ my $month = sprintf("%02d", shift);
+
+ my $page = calendarlink($year, $month);
+ my $pagefile = newpagefile($page, $config{default_pageext});
+ add_autofile(
+ $pagefile, "calendar",
+ sub {return autocreate($page, $pagefile, $year, $month);}
+ );
+}
+
+sub gencalendaryear {
+ my $year = shift;
+ my %params = @_;
+
+ # Building year page
+ my $page = calendarlink($year);
+ my $pagefile = newpagefile($page, $config{default_pageext});
+ add_autofile(
+ $pagefile, "calendar",
+ sub {return autocreate($page, $pagefile, $year);}
+ );
+
+ if (not exists $wikistate{calendar}{minyear}) {
+ $wikistate{calendar}{minyear} = $year;
+ }
+ if (not exists $wikistate{calendar}{maxyear}) {
+ $wikistate{calendar}{maxyear} = $year;
+ }
+
+ if ($config{calendar_fill_gaps}) {
+ # Building month pages
+ foreach my $month (1 .. 12) {
+ gencalendarmonth($year, $month);
+ }
+
+ # Filling potential gaps in years (e.g. calendar goes from 2010 to 2014,
+ # and we just added year 2005. We have to add years 2006 to 2009).
+ return if $params{norecurse};
+ if ($wikistate{calendar}{minyear} > $year) {
+ foreach my $other ($year + 1 .. $wikistate{calendar}{minyear} - 1) {
+ gencalendaryear($other, norecurse => 1);
+ }
+ $wikistate{calendar}{minyear} = $year;
+ }
+ if ($wikistate{calendar}{maxyear} < $year) {
+ foreach my $other ($wikistate{calendar}{maxyear} + 1 .. $year - 1) {
+ gencalendaryear($other, norecurse => 1);
+ }
+ $wikistate{calendar}{maxyear} = $year;
+ }
+ }
+ if ($year < $wikistate{calendar}{minyear}) {
+ $wikistate{calendar}{minyear} = $year;
+ }
+ if ($year > $wikistate{calendar}{maxyear}) {
+ $wikistate{calendar}{maxyear} = $year;
+ }
+}
+
+sub previousmonth($$$) {
+ my $year = shift;
+ my $month = shift;
+ my $archivebase = shift;
+
+ if (not exists $wikistate{calendar}{minyear}) {
+ $wikistate{calendar}{minyear} = $year;
+ }
+
+ my $pmonth = $month;
+ my $pyear = $year;
+ while ((not exists $pagesources{"$archivebase/$pyear/" . sprintf("%02d", $pmonth)}) or ($pmonth == $month and $pyear == $year)) {
+ $pmonth -= 1;
+ if ($pmonth == 0) {
+ $pyear -= 1;
+ $pmonth = 12;
+ return ($pyear, $pmonth, 0) unless $pyear >= $wikistate{calendar}{minyear};
+ }
+ }
+ return ($pyear, $pmonth, 1);
+}
+
+sub nextmonth($$$) {
+ my $year = shift;
+ my $month = shift;
+ my $archivebase = shift;
+
+ if (not exists $wikistate{calendar}{maxyear}) {
+ $wikistate{calendar}{maxyear} = $year;
+ }
+
+ my $nmonth = $month;
+ my $nyear = $year;
+ while ((not exists $pagesources{"$archivebase/$nyear/" . sprintf("%02d", $nmonth)}) or ($nmonth == $month and $nyear == $year)) {
+ $nmonth += 1;
+ if ($nmonth == 13) {
+ $nyear += 1;
+ $nmonth = 1;
+ return ($nyear, $nmonth, 0) unless $nyear <= $wikistate{calendar}{maxyear};
+ }
+ }
+ return ($nyear, $nmonth, 1);
+}
+
+sub previousyear($$) {
+ my $year = shift;
+ my $archivebase = shift;
+
+ my $pyear = $year - 1;
+ while (not exists $pagesources{"$archivebase/$pyear"}) {
+ $pyear -= 1;
+ return ($pyear, 0) unless ($pyear >= $wikistate{calendar}{minyear});
+ }
+ return ($pyear, 1);
+}
+
+sub nextyear($$) {
+ my $year = shift;
+ my $archivebase = shift;
+
+ my $nyear = $year + 1;
+ while (not exists $pagesources{"$archivebase/$nyear"}) {
+ $nyear += 1;
+ return ($nyear, 0) unless ($nyear <= $wikistate{calendar}{maxyear});
+ }
+ return ($nyear, 1);
+}
+
sub format_month (@) {
my %params=@_;
@@ -92,20 +306,12 @@ sub format_month (@) {
push(@{$linkcache{"$year/$mtag/$mday"}}, $p);
}
- my $pmonth = $params{month} - 1;
- my $nmonth = $params{month} + 1;
- my $pyear = $params{year};
- my $nyear = $params{year};
-
- # Adjust for January and December
- if ($params{month} == 1) {
- $pmonth = 12;
- $pyear--;
- }
- if ($params{month} == 12) {
- $nmonth = 1;
- $nyear++;
- }
+ my $archivebase = 'archives';
+ $archivebase = $config{archivebase} if defined $config{archivebase};
+ $archivebase = $params{archivebase} if defined $params{archivebase};
+
+ my ($pyear, $pmonth, $pvalid) = previousmonth($params{year}, $params{month}, $archivebase);
+ my ($nyear, $nmonth, $nvalid) = nextmonth($params{year}, $params{month}, $archivebase);
# Add padding.
$pmonth=sprintf("%02d", $pmonth);
@@ -129,10 +335,6 @@ sub format_month (@) {
my $pmonthname=strftime_utf8("%B", localtime(timelocal(0,0,0,1,$pmonth-1,$pyear-1900)));
my $nmonthname=strftime_utf8("%B", localtime(timelocal(0,0,0,1,$nmonth-1,$nyear-1900)));
- my $archivebase = 'archives';
- $archivebase = $config{archivebase} if defined $config{archivebase};
- $archivebase = $params{archivebase} if defined $params{archivebase};
-
# Calculate URL's for monthly archives.
my ($url, $purl, $nurl)=("$monthname $params{year}",'','');
if (exists $pagesources{"$archivebase/$params{year}/$params{month}"}) {
@@ -274,7 +476,7 @@ EOF
sub format_year (@) {
my %params=@_;
-
+
my @post_months;
foreach my $p (pagespec_match_list($params{page},
"creation_year($params{year}) and ($params{pages})",
@@ -290,18 +492,18 @@ sub format_year (@) {
}
my $calendar="\n";
+
+ my $archivebase = 'archives';
+ $archivebase = $config{archivebase} if defined $config{archivebase};
+ $archivebase = $params{archivebase} if defined $params{archivebase};
- my $pyear = $params{year} - 1;
- my $nyear = $params{year} + 1;
+ my ($pyear, $pvalid) = previousyear($params{year}, $archivebase);
+ my ($nyear, $nvalid) = nextyear($params{year}, $archivebase);
my $thisyear = $now[5]+1900;
my $future_month = 0;
$future_month = $now[4]+1 if $params{year} == $thisyear;
- my $archivebase = 'archives';
- $archivebase = $config{archivebase} if defined $config{archivebase};
- $archivebase = $params{archivebase} if defined $params{archivebase};
-
# calculate URL's for previous and next years
my ($url, $purl, $nurl)=("$params{year}",'','');
if (exists $pagesources{"$archivebase/$params{year}"}) {
@@ -431,6 +633,7 @@ sub preprocess (@) {
}
$params{month} = sprintf("%02d", $params{month});
+ $changed{$params{year}}{$params{month}} = 1;
if ($params{type} eq 'month' && $params{year} == $thisyear
&& $params{month} == $thismonth) {
@@ -508,7 +711,22 @@ sub needsbuild (@) {
}
}
}
+
return $needsbuild;
}
+sub scan (@) {
+ my %params=@_;
+ my $page=$params{page};
+
+ return unless $config{calendar_autocreate};
+
+ # Check if year pages have to be generated
+ if (pagespec_match($page, $config{archive_pagespec})) {
+ my @ctime = localtime($IkiWiki::pagectime{$page});
+ gencalendaryear($ctime[5]+1900);
+ gencalendarmonth($ctime[5]+1900, $ctime[4]+1);
+ }
+}
+
1
diff --git a/IkiWiki/Plugin/comments.pm b/IkiWiki/Plugin/comments.pm
index 98ae13810..ad813d19d 100644
--- a/IkiWiki/Plugin/comments.pm
+++ b/IkiWiki/Plugin/comments.pm
@@ -198,7 +198,6 @@ sub preprocess {
$commentuser = $params{username};
my $oiduser = eval { IkiWiki::openiduser($commentuser) };
-
if (defined $oiduser) {
# looks like an OpenID
$commentauthorurl = $commentuser;
@@ -206,10 +205,17 @@ sub preprocess {
$commentopenid = $commentuser;
}
else {
- $commentauthorurl = IkiWiki::cgiurl(
- do => 'goto',
- page => IkiWiki::userpage($commentuser)
- );
+ my $emailuser = IkiWiki::emailuser($commentuser);
+ if (defined $emailuser) {
+ $commentuser=$emailuser;
+ }
+
+ if (length $config{cgiurl}) {
+ $commentauthorurl = IkiWiki::cgiurl(
+ do => 'goto',
+ page => IkiWiki::userpage($commentuser)
+ );
+ }
$commentauthor = $commentuser;
}
@@ -221,22 +227,9 @@ sub preprocess {
$commentauthor = gettext("Anonymous");
}
- $commentstate{$page}{commentuser} = $commentuser;
- $commentstate{$page}{commentopenid} = $commentopenid;
- $commentstate{$page}{commentip} = $commentip;
- $commentstate{$page}{commentauthor} = $commentauthor;
- $commentstate{$page}{commentauthorurl} = $commentauthorurl;
- $commentstate{$page}{commentauthoravatar} = $params{avatar};
- if (! defined $pagestate{$page}{meta}{author}) {
- $pagestate{$page}{meta}{author} = $commentauthor;
- }
- if (! defined $pagestate{$page}{meta}{authorurl}) {
- $pagestate{$page}{meta}{authorurl} = $commentauthorurl;
- }
-
if ($config{comments_allowauthor}) {
if (defined $params{claimedauthor}) {
- $pagestate{$page}{meta}{author} = $params{claimedauthor};
+ $commentauthor = $params{claimedauthor};
}
if (defined $params{url}) {
@@ -248,12 +241,21 @@ sub preprocess {
}
if (safeurl($url)) {
- $pagestate{$page}{meta}{authorurl} = $url;
+ $commentauthorurl = $url;
}
}
}
- else {
+
+ $commentstate{$page}{commentuser} = $commentuser;
+ $commentstate{$page}{commentopenid} = $commentopenid;
+ $commentstate{$page}{commentip} = $commentip;
+ $commentstate{$page}{commentauthor} = $commentauthor;
+ $commentstate{$page}{commentauthorurl} = $commentauthorurl;
+ $commentstate{$page}{commentauthoravatar} = $params{avatar};
+ if (! defined $pagestate{$page}{meta}{author}) {
$pagestate{$page}{meta}{author} = $commentauthor;
+ }
+ if (! defined $pagestate{$page}{meta}{authorurl}) {
$pagestate{$page}{meta}{authorurl} = $commentauthorurl;
}
@@ -464,17 +466,20 @@ sub editcomment ($$) {
my $content = "[[!comment format=$type\n";
if (defined $session->param('name')) {
- my $username = $session->param('name');
+ my $username = IkiWiki::cloak($session->param('name'));
$username =~ s/"/&quot;/g;
$content .= " username=\"$username\"\n";
}
+
if (defined $session->param('nickname')) {
my $nickname = $session->param('nickname');
$nickname =~ s/"/&quot;/g;
$content .= " nickname=\"$nickname\"\n";
}
- elsif (defined $session->remote_addr()) {
- $content .= " ip=\"".$session->remote_addr()."\"\n";
+
+ if (!(defined $session->param('name') || defined $session->param('nickname')) &&
+ defined $session->remote_addr()) {
+ $content .= " ip=\"".IkiWiki::cloak($session->remote_addr())."\"\n";
}
if ($config{comments_allowauthor}) {
@@ -504,8 +509,7 @@ sub editcomment ($$) {
$subject = "comment ".(num_comments($page, $config{srcdir}) + 1);
}
$content .= " subject=\"$subject\"\n";
-
- $content .= " date=\"" . strftime_utf8('%Y-%m-%dT%H:%M:%SZ', gmtime) . "\"\n";
+ $content .= " date=\"" . commentdate() . "\"\n";
my $editcontent = $form->field('editcontent');
$editcontent="" if ! defined $editcontent;
@@ -633,6 +637,10 @@ sub editcomment ($$) {
exit;
}
+sub commentdate () {
+ strftime_utf8('%Y-%m-%dT%H:%M:%SZ', gmtime);
+}
+
sub getavatar ($) {
my $user=shift;
return undef unless defined $user;
@@ -1009,7 +1017,7 @@ sub num_comments ($$) {
return int @comments;
}
-sub unique_comment_location ($$$$) {
+sub unique_comment_location ($$$;$) {
my $page=shift;
eval q{use Digest::MD5 'md5_hex'};
error($@) if $@;
diff --git a/IkiWiki/Plugin/cvs.pm b/IkiWiki/Plugin/cvs.pm
index 841aec914..8989a26e3 100644
--- a/IkiWiki/Plugin/cvs.pm
+++ b/IkiWiki/Plugin/cvs.pm
@@ -456,12 +456,12 @@ sub commitmessage (@) {
if (defined $params{session}) {
if (defined $params{session}->param("name")) {
return "web commit by ".
- $params{session}->param("name").
+ IkiWiki::cloak($params{session}->param("name")).
(length $params{message} ? ": $params{message}" : "");
}
elsif (defined $params{session}->remote_addr()) {
return "web commit from ".
- $params{session}->remote_addr().
+ IkiWiki::cloak($params{session}->remote_addr()).
(length $params{message} ? ": $params{message}" : "");
}
}
diff --git a/IkiWiki/Plugin/darcs.pm b/IkiWiki/Plugin/darcs.pm
index 646f65df1..9dccd95a4 100644
--- a/IkiWiki/Plugin/darcs.pm
+++ b/IkiWiki/Plugin/darcs.pm
@@ -147,10 +147,10 @@ sub commitauthor (@) {
my $author="anon\@web";
if (defined $params{session}) {
if (defined $params{session}->param("name")) {
- return $params{session}->param("name").'@web';
+ return IkiWiki::cloak($params{session}->param("name")).'@web';
}
elsif (defined $params{session}->remote_addr()) {
- return $params{session}->remote_addr().'@web';
+ return IkiWiki::cloak($params{session}->remote_addr()).'@web';
}
}
return 'anon@web';
diff --git a/IkiWiki/Plugin/editpage.pm b/IkiWiki/Plugin/editpage.pm
index 3047869c4..78d0704c7 100644
--- a/IkiWiki/Plugin/editpage.pm
+++ b/IkiWiki/Plugin/editpage.pm
@@ -342,7 +342,7 @@ sub cgi_editpage ($$) {
else {
# save page
check_canedit($page, $q, $session);
- checksessionexpiry($q, $session, $q->param('sid'));
+ checksessionexpiry($q, $session);
my $exists=-e "$config{srcdir}/$file";
diff --git a/IkiWiki/Plugin/emailauth.pm b/IkiWiki/Plugin/emailauth.pm
new file mode 100644
index 000000000..6674fe3d6
--- /dev/null
+++ b/IkiWiki/Plugin/emailauth.pm
@@ -0,0 +1,203 @@
+#!/usr/bin/perl
+# Ikiwiki email address as login
+package IkiWiki::Plugin::emailauth;
+
+use warnings;
+use strict;
+use IkiWiki 3.00;
+
+sub import {
+ hook(type => "getsetup", id => "emailauth", "call" => \&getsetup);
+ hook(type => "cgi", id => "emailauth", "call" => \&cgi);
+ hook(type => "formbuilder_setup", id => "emailauth", "call" => \&formbuilder_setup);
+ IkiWiki::loadplugin("loginselector");
+ IkiWiki::Plugin::loginselector::register_login_plugin(
+ "emailauth",
+ \&email_setup,
+ \&email_check_input,
+ \&email_auth,
+ );
+}
+
+sub getsetup () {
+ return
+ plugin => {
+ safe => 1,
+ rebuild => 0,
+ section => "auth",
+ },
+ emailauth_sender => {
+ type => "string",
+ description => "email address to send emailauth mails as (default: adminemail)",
+ safe => 1,
+ rebuild => 0,
+ },
+}
+
+sub email_setup ($$) {
+ my $q=shift;
+ my $template=shift;
+
+ return 1;
+}
+
+sub email_check_input ($) {
+ my $cgi=shift;
+ defined $cgi->param('do')
+ && $cgi->param("do") eq "signin"
+ && defined $cgi->param('Email_entry')
+ && length $cgi->param('Email_entry');
+}
+
+# Send login link to email.
+sub email_auth ($$$$) {
+ my $cgi=shift;
+ my $session=shift;
+ my $errordisplayer=shift;
+ my $infodisplayer=shift;
+
+ my $email=$cgi->param('Email_entry');
+ unless ($email =~ /.\@./) {
+ $errordisplayer->(gettext("Invalid email address."));
+ return;
+ }
+
+ # Implicit account creation.
+ my $userinfo=IkiWiki::userinfo_retrieve();
+ if (! exists $userinfo->{$email} || ! ref $userinfo->{$email}) {
+ IkiWiki::userinfo_setall($email, {
+ 'email' => $email,
+ 'regdate' => time,
+ });
+ }
+
+ my $token=gentoken($email, $session);
+ my $template=template("emailauth.tmpl");
+ $template->param(
+ wikiname => $config{wikiname},
+ # Intentionally using short field names to keep link short.
+ authurl => IkiWiki::cgiurl_abs(
+ 'e' => $email,
+ 'v' => $token,
+ ),
+ );
+
+ eval q{use Mail::Sendmail};
+ error($@) if $@;
+ my $shorturl=$config{url};
+ $shorturl=~s/^https?:\/\///i;
+ my $emailauth_sender=$config{emailauth_sender};
+ $emailauth_sender=$config{adminemail} unless defined $emailauth_sender;
+ sendmail(
+ To => $email,
+ From => "$config{wikiname} admin <".
+ (defined $emailauth_sender ? $emailauth_sender : "")
+ .">",
+ Subject => "$config{wikiname} login | $shorturl",
+ Message => $template->output,
+ ) or error(gettext("Failed to send mail"));
+
+ $infodisplayer->(gettext("You have been sent an email, with a link you can open to complete the login process."));
+}
+
+# Finish login process.
+sub cgi ($$) {
+ my $cgi=shift;
+
+ my $email=$cgi->param('e');
+ my $v=$cgi->param('v');
+ if (defined $email && defined $v && length $email && length $v) {
+ my $token=gettoken($email);
+ if ($token eq $v) {
+ cleartoken($email);
+ my $session=getsession($email);
+ IkiWiki::cgi_postsignin($cgi, $session);
+ }
+ elsif (length $token ne length $cgi->param('v')) {
+ error(gettext("Wrong login token length. Please check that you pasted in the complete login link from the email!"));
+ }
+ else {
+ loginfailure();
+ }
+ }
+}
+
+sub formbuilder_setup (@) {
+ my %params=@_;
+ my $form=$params{form};
+ my $session=$params{session};
+
+ if ($form->title eq "preferences" &&
+ IkiWiki::emailuser($session->param("name"))) {
+ $form->field(name => "email", disabled => 1);
+ }
+}
+
+# Generates the token that will be used in the authurl to log the user in.
+# This needs to be hard to guess, and relatively short. Generating a cgi
+# session id will make it as hard to guess as any cgi session.
+#
+# Store token in userinfo; this allows the user to log in
+# using a different browser session, if it takes a while for the
+# email to get to them.
+#
+# The postsignin value from the session is also stored in the userinfo
+# to allow resuming in a different browser session.
+sub gentoken ($$) {
+ my $email=shift;
+ my $session=shift;
+ eval q{use CGI::Session};
+ error($@) if $@;
+ my $token = CGI::Session->new->id;
+ IkiWiki::userinfo_set($email, "emailauthexpire", time+(60*60*24));
+ IkiWiki::userinfo_set($email, "emailauth", $token);
+ IkiWiki::userinfo_set($email, "emailauthpostsignin", defined $session->param("postsignin") ? $session->param("postsignin") : "");
+ return $token;
+}
+
+# Gets the token, checking for expiry.
+sub gettoken ($) {
+ my $email=shift;
+ my $val=IkiWiki::userinfo_get($email, "emailauth");
+ my $expire=IkiWiki::userinfo_get($email, "emailauthexpire");
+ if (! length $val || time > $expire) {
+ loginfailure();
+ }
+ return $val;
+}
+
+# Generate a session to use after successful login.
+sub getsession ($) {
+ my $email=shift;
+
+ IkiWiki::lockwiki();
+ IkiWiki::loadindex();
+ my $session=IkiWiki::cgi_getsession();
+
+ my $postsignin=IkiWiki::userinfo_get($email, "emailauthpostsignin");
+ IkiWiki::userinfo_set($email, "emailauthpostsignin", "");
+ if (defined $postsignin && length $postsignin) {
+ $session->param(postsignin => $postsignin);
+ }
+
+ $session->param(name => $email);
+ my $nickname=$email;
+ $nickname=~s/@.*//;
+ $session->param(nickname => Encode::decode_utf8($nickname));
+
+ IkiWiki::cgi_savesession($session);
+
+ return $session;
+}
+
+sub cleartoken ($) {
+ my $email=shift;
+ IkiWiki::userinfo_set($email, "emailauthexpire", 0);
+ IkiWiki::userinfo_set($email, "emailauth", "");
+}
+
+sub loginfailure () {
+ error "Bad email authentication token. Please retry login.";
+}
+
+1
diff --git a/IkiWiki/Plugin/filecheck.pm b/IkiWiki/Plugin/filecheck.pm
index cdea5c706..6e6528398 100644
--- a/IkiWiki/Plugin/filecheck.pm
+++ b/IkiWiki/Plugin/filecheck.pm
@@ -150,7 +150,7 @@ sub match_mimetype ($$;@) {
chomp $mimetype;
close $file_h;
}
- if (! defined $mimetype || $mimetype !~s /;.*//) {
+ if (! defined $mimetype) {
# Fall back to default value.
$mimetype=File::MimeInfo::Magic::default($file)
if $mimeinfo_ok;
@@ -158,6 +158,8 @@ sub match_mimetype ($$;@) {
$mimetype="unknown";
}
}
+ # Ignore any parameters, we only want the type itself
+ $mimetype =~ s/;.*//;
my $regexp=IkiWiki::glob2re($wanted);
if ($mimetype!~$regexp) {
diff --git a/IkiWiki/Plugin/git.pm b/IkiWiki/Plugin/git.pm
index 75b89e476..f963f06ba 100644
--- a/IkiWiki/Plugin/git.pm
+++ b/IkiWiki/Plugin/git.pm
@@ -220,6 +220,21 @@ sub run_or_die ($@) { safe_git(\&error, undef, @_) }
sub run_or_cry ($@) { safe_git(sub { warn @_ }, undef, @_) }
sub run_or_non ($@) { safe_git(undef, undef, @_) }
+sub ensure_committer {
+ if (! length $ENV{GIT_AUTHOR_NAME} || ! length $ENV{GIT_COMMITTER_NAME}) {
+ my $name = join('', run_or_non("git", "config", "user.name"));
+ if (! length $name) {
+ run_or_die("git", "config", "user.name", "IkiWiki");
+ }
+ }
+
+ if (! length $ENV{GIT_AUTHOR_EMAIL} || ! length $ENV{GIT_COMMITTER_EMAIL}) {
+ my $email = join('', run_or_non("git", "config", "user.email"));
+ if (! length $email) {
+ run_or_die("git", "config", "user.email", "ikiwiki.info");
+ }
+ }
+}
sub merge_past ($$$) {
# Unlike with Subversion, Git cannot make a 'svn merge -rN:M file'.
@@ -258,6 +273,8 @@ sub merge_past ($$$) {
my @undo; # undo stack for cleanup in case of an error
my $conflict; # file content with conflict markers
+ ensure_committer();
+
eval {
# Hide local changes from Git by renaming the modified file.
# Relative paths must be converted to absolute for renaming.
@@ -526,6 +543,8 @@ sub rcs_get_current_rev () {
sub rcs_update () {
# Update working directory.
+ ensure_committer();
+
if (length $config{gitorigin_branch}) {
run_or_cry('git', 'pull', '--prune', $config{gitorigin_branch});
}
@@ -579,7 +598,7 @@ sub rcs_commit_helper (@) {
$u=$params{session}->remote_addr();
}
if (defined $u) {
- $u=encode_utf8($u);
+ $u=encode_utf8(IkiWiki::cloak($u));
$ENV{GIT_AUTHOR_NAME}=$u;
}
if (defined $params{session}->param("nickname")) {
@@ -592,6 +611,8 @@ sub rcs_commit_helper (@) {
}
}
+ ensure_committer();
+
$params{message} = IkiWiki::possibly_foolish_untaint($params{message});
my @opts;
if ($params{message} !~ /\S/) {
@@ -631,6 +652,8 @@ sub rcs_add ($) {
my ($file) = @_;
+ ensure_committer();
+
run_or_cry('git', 'add', $file);
}
@@ -639,12 +662,16 @@ sub rcs_remove ($) {
my ($file) = @_;
+ ensure_committer();
+
run_or_cry('git', 'rm', '-f', $file);
}
sub rcs_rename ($$) {
my ($src, $dest) = @_;
+ ensure_committer();
+
run_or_cry('git', 'mv', '-f', $src, $dest);
}
@@ -944,6 +971,8 @@ sub rcs_revert ($) {
my $rev = shift;
my ($sha1) = $rev =~ /^($sha1_pattern)$/; # untaint
+ ensure_committer();
+
if (run_or_non('git', 'revert', '--no-commit', $sha1)) {
return undef;
}
diff --git a/IkiWiki/Plugin/goto.pm b/IkiWiki/Plugin/goto.pm
index 6b596ac8b..3a946b19d 100644
--- a/IkiWiki/Plugin/goto.pm
+++ b/IkiWiki/Plugin/goto.pm
@@ -27,7 +27,7 @@ sub cgi_goto ($;$) {
my $page = shift;
if (!defined $page) {
- $page = IkiWiki::decode_utf8($q->param("page"));
+ $page = IkiWiki::decode_utf8(scalar $q->param("page"));
if (!defined $page) {
error("missing page parameter");
diff --git a/IkiWiki/Plugin/haiku.pm b/IkiWiki/Plugin/haiku.pm
index bf23dce67..7ce74696b 100644
--- a/IkiWiki/Plugin/haiku.pm
+++ b/IkiWiki/Plugin/haiku.pm
@@ -25,7 +25,13 @@ sub preprocess (@) {
my $haiku;
eval q{use Coy};
- if ($@ || ! Coy->can("Coy::with_haiku")) {
+ if ($config{deterministic}) {
+ $haiku = "Coy changes often.
+ For deterministic builds
+ try this substitute.
+ ",
+ }
+ elsif ($@ || ! Coy->can("Coy::with_haiku")) {
my @canned=(
"The lack of a Coy:
No darting, subtle haiku.
diff --git a/IkiWiki/Plugin/img.pm b/IkiWiki/Plugin/img.pm
index 54c13d069..169f5e713 100644
--- a/IkiWiki/Plugin/img.pm
+++ b/IkiWiki/Plugin/img.pm
@@ -76,9 +76,13 @@ sub preprocess (@) {
my $im = Image::Magick->new();
my $imglink;
my $imgdatalink;
- my $r = $im->Read("$srcfile\[$pagenumber]");
+ my $r = $im->Read(":$srcfile\[$pagenumber]");
error sprintf(gettext("failed to read %s: %s"), $file, $r) if $r;
-
+
+ if (! defined $im->Get("width") || ! defined $im->Get("height")) {
+ error sprintf(gettext("failed to get dimensions of %s"), $file);
+ }
+
my ($dwidth, $dheight);
if ($params{size} eq 'full') {
diff --git a/IkiWiki/Plugin/inline.pm b/IkiWiki/Plugin/inline.pm
index b122bd0f2..d68723dff 100644
--- a/IkiWiki/Plugin/inline.pm
+++ b/IkiWiki/Plugin/inline.pm
@@ -119,7 +119,7 @@ sub sessioncgi ($$) {
my $session=shift;
if ($q->param('do') eq 'blog') {
- my $page=titlepage(decode_utf8($q->param('title')));
+ my $page=titlepage(decode_utf8(scalar $q->param('title')));
$page=~s/(\/)/"__".ord($1)."__"/eg; # don't create subdirs
# if the page already exists, munge it to be unique
my $from=$q->param('from');
@@ -160,16 +160,17 @@ sub preprocess_inline (@) {
# Running in scan mode: only do the essentials
if (yesno($params{trail}) && IkiWiki::Plugin::trail->can("preprocess_trailitems")) {
- # default to sorting age, the same as inline itself,
- # but let the params override that
- IkiWiki::Plugin::trail::preprocess_trailitems(sort => 'age', %params);
+ # default to sorting by age with fallback to title,
+ # the same as inline itself, but let the params
+ # override that
+ IkiWiki::Plugin::trail::preprocess_trailitems(sort => 'age title', %params);
}
return;
}
if (yesno($params{trail}) && IkiWiki::Plugin::trail->can("preprocess_trailitems")) {
- scalar IkiWiki::Plugin::trail::preprocess_trailitems(sort => 'age', %params);
+ scalar IkiWiki::Plugin::trail::preprocess_trailitems(sort => 'age title', %params);
}
my $raw=yesno($params{raw});
@@ -256,7 +257,7 @@ sub preprocess_inline (@) {
@list = pagespec_match_list($params{page}, $params{pages},
deptype => deptype($quick ? "presence" : "content"),
filter => sub { $_[0] eq $params{page} },
- sort => exists $params{sort} ? $params{sort} : "age",
+ sort => exists $params{sort} ? $params{sort} : "age title",
reverse => yesno($params{reverse}),
($num ? (num => $num) : ()),
);
diff --git a/IkiWiki/Plugin/loginselector.pm b/IkiWiki/Plugin/loginselector.pm
new file mode 100644
index 000000000..26c80b4ce
--- /dev/null
+++ b/IkiWiki/Plugin/loginselector.pm
@@ -0,0 +1,132 @@
+#!/usr/bin/perl
+package IkiWiki::Plugin::loginselector;
+
+use warnings;
+use strict;
+use IkiWiki 3.00;
+
+# Plugins that provide login methods can register themselves here.
+# Note that the template and js file also have be be modifed to add a new
+# login method.
+our %login_plugins;
+
+sub register_login_plugin ($$$$) {
+ # Same as the name of the plugin that is registering itself as a
+ # login plugin. eg, "openid"
+ my $plugin_name=shift;
+ # This sub is passed a cgi object and a template object which it
+ # can manipulate. It should return true if the plugin can be used
+ # (it might load necessary modules for auth checking, for example).
+ my $plugin_setup=shift;
+ # This sub is passed a cgi object, and should return true
+ # if it looks like the user is logging in using the plugin.
+ my $plugin_check_input=shift;
+ # This sub is passed a cgi object, a session object, an error
+ # display callback, and an info display callback, and should
+ # handle the actual authentication. It can either exit w/o
+ # returning, if it is able to handle auth, or it can pass an
+ # error message to the error display callback to make the
+ # openid selector form be re-disiplayed with an error message
+ # on it.
+ my $plugin_auth=shift;
+ $login_plugins{$plugin_name}={
+ setup => $plugin_setup,
+ check_input => $plugin_check_input,
+ auth => $plugin_auth,
+ };
+}
+
+sub login_selector {
+ my $real_cgi_signin=shift;
+ my $otherform_label=shift;
+ my $q=shift;
+ my $session=shift;
+
+ my $template=IkiWiki::template("login-selector.tmpl");
+
+ foreach my $plugin (keys %login_plugins) {
+ if (! $login_plugins{$plugin}->{setup}->($template)) {
+ delete $login_plugins{$plugin};
+ }
+ else {
+ $template->param("login_selector_$plugin", 1);
+ }
+ }
+
+ foreach my $plugin (keys %login_plugins) {
+ if ($login_plugins{$plugin}->{check_input}->($q)) {
+ $login_plugins{$plugin}->{auth}->($q, $session, sub {
+ $template->param(login_error => shift());
+ }, sub {
+ $template->param(login_info => shift());
+ });
+ last;
+ }
+ }
+
+ $template->param(
+ cgiurl => IkiWiki::cgiurl(),
+ ($real_cgi_signin ? (otherform => $real_cgi_signin->($q, $session, 1)) : ()),
+ otherform_label => $otherform_label,
+ );
+
+ IkiWiki::printheader($session);
+ print IkiWiki::cgitemplate($q, "signin", $template->output);
+ exit;
+}
+
+sub import {
+ add_underlay("login-selector");
+ add_underlay("jquery");
+ hook(type => "getsetup", id => "loginselector", call => \&getsetup);
+ hook(type => "checkconfig", id => "loginselector", call => \&checkconfig);
+ hook(type => "auth", id => "loginselector", call => \&authstub);
+}
+
+sub checkconfig () {
+ if ($config{cgi}) {
+ # Intercept normal signin form, so the login selector
+ # can be displayed.
+ #
+ # When other auth hooks are registered, give the selector
+ # a reference to the normal signin form.
+ require IkiWiki::CGI;
+ my $real_cgi_signin;
+ my $otherform_label=gettext("Other");
+ if (keys %{$IkiWiki::hooks{auth}} > 1) {
+ $real_cgi_signin=\&IkiWiki::cgi_signin;
+ # Special case to avoid labeling password auth as
+ # "Other" when it's the only auth plugin not
+ # integrated with the loginselector.
+ my %h=%{$IkiWiki::hooks{auth}};
+ foreach my $p (keys %login_plugins) {
+ delete $h{$p};
+ }
+ delete $h{loginselector};
+ if (keys %h == 1 && exists $h{passwordauth}) {
+ $otherform_label=gettext("Password");
+ }
+ }
+ inject(name => "IkiWiki::cgi_signin", call => sub ($$) {
+ login_selector($real_cgi_signin, $otherform_label, @_);
+ });
+ }
+}
+
+sub getsetup () {
+ return
+ plugin => {
+ # this plugin is safe but only makes sense as a
+ # dependency
+ safe => 0,
+ rebuild => 0,
+ },
+}
+
+sub authstub ($$) {
+ # While this hook is not currently used, it needs to exist
+ # so ikiwiki knows that the wiki supports logins, and will
+ # enable the Preferences page.
+}
+
+1
diff --git a/IkiWiki/Plugin/mercurial.pm b/IkiWiki/Plugin/mercurial.pm
index 8da4ceb07..9f0c5b721 100644
--- a/IkiWiki/Plugin/mercurial.pm
+++ b/IkiWiki/Plugin/mercurial.pm
@@ -183,10 +183,10 @@ sub rcs_commit_helper (@) {
my $user="Anonymous";
if (defined $params{session}) {
if (defined $params{session}->param("name")) {
- $user = $params{session}->param("name");
+ $user = IkiWiki::cloak($params{session}->param("name"));
}
elsif (defined $params{session}->remote_addr()) {
- $user = $params{session}->remote_addr();
+ $user = IkiWiki::cloak($params{session}->remote_addr());
}
my $nickname=$user;
diff --git a/IkiWiki/Plugin/meta.pm b/IkiWiki/Plugin/meta.pm
index e7b96bdf1..ea099f955 100644
--- a/IkiWiki/Plugin/meta.pm
+++ b/IkiWiki/Plugin/meta.pm
@@ -294,6 +294,7 @@ sub preprocess (@) {
elsif ($key eq 'name') {
push @{$metaheaders{$page}}, scrub('<meta name="'.
encode_entities($value).
+ '" '.
join(' ', map { "$_=\"$params{$_}\"" } keys %params).
' />', $page, $destpage);
}
diff --git a/IkiWiki/Plugin/mirrorlist.pm b/IkiWiki/Plugin/mirrorlist.pm
index b7e532485..5afd9ec1a 100644
--- a/IkiWiki/Plugin/mirrorlist.pm
+++ b/IkiWiki/Plugin/mirrorlist.pm
@@ -53,7 +53,7 @@ sub pagetemplate (@) {
sub mirrorlist ($) {
my $page=shift;
- return ($config{html5} ? '<nav id="mirrorlist">' : '<div>').
+ return ($config{html5} ? '<nav' : '<div').' id="mirrorlist">'.
(keys %{$config{mirrorlist}} > 1 ? gettext("Mirrors") : gettext("Mirror")).
": ".
join(", ",
diff --git a/IkiWiki/Plugin/monotone.pm b/IkiWiki/Plugin/monotone.pm
index 105627814..b0bba5661 100644
--- a/IkiWiki/Plugin/monotone.pm
+++ b/IkiWiki/Plugin/monotone.pm
@@ -310,10 +310,10 @@ sub commitauthor (@) {
if (defined $params{session}) {
if (defined $params{session}->param("name")) {
- return "Web user: " . $params{session}->param("name");
+ return "Web user: " . IkiWiki::cloak($params{session}->param("name"));
}
elsif (defined $params{session}->remote_addr()) {
- return "Web IP: " . $params{session}->remote_addr();
+ return "Web IP: " . IkiWiki::cloak($params{session}->remote_addr());
}
}
return "Web: Anonymous";
diff --git a/IkiWiki/Plugin/openid.pm b/IkiWiki/Plugin/openid.pm
index 3b96e4b8e..35ef52a58 100644
--- a/IkiWiki/Plugin/openid.pm
+++ b/IkiWiki/Plugin/openid.pm
@@ -7,31 +7,18 @@ use strict;
use IkiWiki 3.00;
sub import {
- add_underlay("openid-selector");
- add_underlay("jquery");
- hook(type => "checkconfig", id => "openid", call => \&checkconfig);
hook(type => "getsetup", id => "openid", call => \&getsetup);
hook(type => "auth", id => "openid", call => \&auth);
hook(type => "formbuilder_setup", id => "openid",
call => \&formbuilder_setup, last => 1);
-}
-
-sub checkconfig () {
- if ($config{cgi}) {
- # Intercept normal signin form, so the openid selector
- # can be displayed.
- #
- # When other auth hooks are registered, give the selector
- # a reference to the normal signin form.
- require IkiWiki::CGI;
- my $real_cgi_signin;
- if (keys %{$IkiWiki::hooks{auth}} > 1) {
- $real_cgi_signin=\&IkiWiki::cgi_signin;
- }
- inject(name => "IkiWiki::cgi_signin", call => sub ($$) {
- openid_selector($real_cgi_signin, @_);
- });
- }
+ IkiWiki::loadplugin("emailauth");
+ IkiWiki::loadplugin("loginselector");
+ IkiWiki::Plugin::loginselector::register_login_plugin(
+ "openid",
+ \&openid_setup,
+ \&openid_check_input,
+ \&openid_auth,
+ );
}
sub getsetup () {
@@ -55,38 +42,34 @@ sub getsetup () {
},
}
-sub openid_selector {
- my $real_cgi_signin=shift;
- my $q=shift;
- my $session=shift;
-
- my $openid_url=$q->param('openid_identifier');
- my $openid_error;
+sub openid_setup ($$) {
+ my $q=shift;
+ my $template=shift;
- if (! load_openid_module()) {
- if ($real_cgi_signin) {
- $real_cgi_signin->($q, $session);
- exit;
+ if (load_openid_module()) {
+ my $openid_url=$q->param('openid_identifier');
+ if (defined $openid_url) {
+ $template->param(openid_url => $openid_url);
}
- error(sprintf(gettext("failed to load openid module: "), @_));
+ return 1;
}
- elsif (defined $q->param("action") && $q->param("action") eq "verify") {
- validate($q, $session, $openid_url, sub {
- $openid_error=shift;
- });
+ else {
+ return 0;
}
+}
- my $template=IkiWiki::template("openid-selector.tmpl");
- $template->param(
- 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)) : ()),
- );
+sub openid_check_input ($) {
+ my $q=shift;
+ my $openid_url=$q->param('openid_identifier');
+ defined $q->param("action") && $q->param("action") eq "verify" && defined $openid_url && length $openid_url;
+}
- IkiWiki::printheader($session);
- print IkiWiki::cgitemplate($q, "signin", $template->output);
- exit;
+sub openid_auth ($$$$) {
+ my $q=shift;
+ my $session=shift;
+ my $errordisplayer=shift;
+ my $openid_url=$q->param('openid_identifier');
+ validate($q, $session, $openid_url, $errordisplayer);
}
sub formbuilder_setup (@) {
@@ -104,7 +87,6 @@ sub formbuilder_setup (@) {
size => 1, force => 1,
fieldset => "login",
comment => $session->param("name"));
- $form->field(name => "email", type => "hidden");
}
}
@@ -119,7 +101,9 @@ sub validate ($$$;$) {
my $claimed_identity = $csr->claimed_identity($openid_url);
if (! $claimed_identity) {
if ($errhandler) {
- $errhandler->($csr->err);
+ if (ref($errhandler) eq 'CODE') {
+ $errhandler->($csr->err);
+ }
return 0;
}
else {
@@ -223,7 +207,7 @@ sub auth ($$) {
}
elsif (defined $q->param('openid_identifier')) {
# myopenid.com affiliate support
- validate($q, $session, $q->param('openid_identifier'));
+ validate($q, $session, scalar $q->param('openid_identifier'));
}
}
diff --git a/IkiWiki/Plugin/passwordauth.pm b/IkiWiki/Plugin/passwordauth.pm
index 0cf2a26ea..3bdd9de2e 100644
--- a/IkiWiki/Plugin/passwordauth.pm
+++ b/IkiWiki/Plugin/passwordauth.pm
@@ -251,6 +251,12 @@ sub formbuilder_setup (@) {
my $name=shift;
length $name &&
$name=~/$config{wiki_file_regexp}/ &&
+ # don't allow registering
+ # accounts that look like
+ # openids, or email
+ # addresses, even if the
+ # file regexp allows it
+ $name!~/[\/:\@]/ &&
! IkiWiki::userinfo_get($name, "regdate");
},
);
@@ -277,7 +283,7 @@ sub formbuilder_setup (@) {
}
elsif ($form->title eq "preferences") {
my $user=$session->param("name");
- if (! IkiWiki::openiduser($user)) {
+ if (! IkiWiki::openiduser($user) && ! IkiWiki::emailuser($user)) {
$form->field(name => "name", disabled => 1,
value => $user, force => 1,
fieldset => "login");
diff --git a/IkiWiki/Plugin/po.pm b/IkiWiki/Plugin/po.pm
index 6107a4a22..6b55ee351 100644
--- a/IkiWiki/Plugin/po.pm
+++ b/IkiWiki/Plugin/po.pm
@@ -993,10 +993,9 @@ sub refreshpofiles ($@) {
}
if (-e $pofile) {
- system("msgmerge", "--previous", "-q", "-U", "--backup=none", $pofile, $potfile) == 0
- or error("po(refreshpofiles) ".
- sprintf(gettext("failed to update %s"),
- $pofile));
+ if (! (system("msgmerge", "--previous", "-q", "-U", "--backup=none", $pofile, $potfile) == 0)) {
+ print STDERR ("po(refreshpofiles) ". sprintf(gettext("failed to update %s"), $pofile));
+ }
}
else {
File::Copy::syscopy($potfile,$pofile)
diff --git a/IkiWiki/Plugin/poll.pm b/IkiWiki/Plugin/poll.pm
index 3bd4af206..eb0e6ef04 100644
--- a/IkiWiki/Plugin/poll.pm
+++ b/IkiWiki/Plugin/poll.pm
@@ -99,7 +99,7 @@ sub sessioncgi ($$) {
my $cgi=shift;
my $session=shift;
if (defined $cgi->param('do') && $cgi->param('do') eq "poll") {
- my $choice=decode_utf8($cgi->param('choice'));
+ my $choice=decode_utf8(scalar $cgi->param('choice'));
if (! defined $choice || not length $choice) {
error("no choice specified");
}
diff --git a/IkiWiki/Plugin/polygen.pm b/IkiWiki/Plugin/polygen.pm
index 78e3611e1..8ce62b754 100644
--- a/IkiWiki/Plugin/polygen.pm
+++ b/IkiWiki/Plugin/polygen.pm
@@ -28,6 +28,7 @@ sub preprocess (@) {
my %params=@_;
my $grammar = ($params{grammar} or 'polygen');
my $symbol = ($params{symbol} or undef);
+ my $options = ($config{deterministic} ? '-seed 42' : '');
# Sanitize parameters
$grammar =~ IkiWiki::basename($grammar);
@@ -51,10 +52,10 @@ sub preprocess (@) {
my $res;
if (defined $symbol) {
- $res = `polygen -S $symbol $grmfile 2>/dev/null`;
+ $res = `polygen -S $symbol $options $grmfile 2>/dev/null`;
}
else {
- $res = `polygen $grmfile 2>/dev/null`;
+ $res = `polygen $options $grmfile 2>/dev/null`;
}
if ($?) {
diff --git a/IkiWiki/Plugin/recentchanges.pm b/IkiWiki/Plugin/recentchanges.pm
index eec9803be..2b2f43b7a 100644
--- a/IkiWiki/Plugin/recentchanges.pm
+++ b/IkiWiki/Plugin/recentchanges.pm
@@ -102,7 +102,7 @@ sub sessioncgi ($$) {
IkiWiki::decode_form_utf8($form);
if ($form->submitted eq 'Revert' && $form->validate) {
- IkiWiki::checksessionexpiry($q, $session, $q->param('sid'));
+ IkiWiki::checksessionexpiry($q, $session);
my $message=sprintf(gettext("This reverts commit %s"), $rev);
if (defined $form->field('revertmessage') &&
length $form->field('revertmessage')) {
diff --git a/IkiWiki/Plugin/remove.pm b/IkiWiki/Plugin/remove.pm
index d48b28f95..5c99b387e 100644
--- a/IkiWiki/Plugin/remove.pm
+++ b/IkiWiki/Plugin/remove.pm
@@ -219,7 +219,7 @@ sub sessioncgi ($$) {
postremove($session);
}
elsif ($form->submitted eq 'Remove' && $form->validate) {
- IkiWiki::checksessionexpiry($q, $session, $q->param('sid'));
+ IkiWiki::checksessionexpiry($q, $session);
my @pages=$form->field("page");
diff --git a/IkiWiki/Plugin/rename.pm b/IkiWiki/Plugin/rename.pm
index 8387a1e32..6d56340b8 100644
--- a/IkiWiki/Plugin/rename.pm
+++ b/IkiWiki/Plugin/rename.pm
@@ -237,7 +237,7 @@ sub postrename ($$$;$$) {
# on it.
$oldcgi->param("editcontent",
renamepage_hook($dest, $src, $dest,
- $oldcgi->param("editcontent")));
+ scalar $oldcgi->param("editcontent")));
# Get a new edit token; old was likely invalidated.
$oldcgi->param("rcsinfo",
@@ -297,7 +297,7 @@ sub sessioncgi ($$) {
if ($q->param("do") eq 'rename') {
my $session=shift;
- my ($form, $buttons)=rename_form($q, $session, Encode::decode_utf8($q->param("page")));
+ my ($form, $buttons)=rename_form($q, $session, Encode::decode_utf8(scalar $q->param("page")));
IkiWiki::decode_form_utf8($form);
my $src=$form->field("page");
@@ -305,7 +305,7 @@ sub sessioncgi ($$) {
postrename($q, $session, $src);
}
elsif ($form->submitted eq 'Rename' && $form->validate) {
- IkiWiki::checksessionexpiry($q, $session, $q->param('sid'));
+ IkiWiki::checksessionexpiry($q, $session);
# These untaints are safe because of the checks
# performed in check_canrename later.
@@ -332,7 +332,7 @@ sub sessioncgi ($$) {
IkiWiki::Plugin::attachment::is_held_attachment($src);
if ($held) {
rename($held, IkiWiki::Plugin::attachment::attachment_holding_location($dest));
- postrename($q, $session, $src, $dest, $q->param("attachment"))
+ postrename($q, $session, $src, $dest, scalar $q->param("attachment"))
unless defined $srcfile;
}
@@ -438,7 +438,7 @@ sub sessioncgi ($$) {
$renamesummary.=$template->output;
}
- postrename($q, $session, $src, $dest, $q->param("attachment"));
+ postrename($q, $session, $src, $dest, scalar $q->param("attachment"));
}
else {
IkiWiki::showform($form, $buttons, $session, $q);
diff --git a/IkiWiki/Plugin/svn.pm b/IkiWiki/Plugin/svn.pm
index fd11f2c63..c46a52dcf 100644
--- a/IkiWiki/Plugin/svn.pm
+++ b/IkiWiki/Plugin/svn.pm
@@ -147,12 +147,12 @@ sub commitmessage (@) {
if (defined $params{session}) {
if (defined $params{session}->param("name")) {
return "web commit by ".
- $params{session}->param("name").
+ IkiWiki::cloak($params{session}->param("name")).
(length $params{message} ? ": $params{message}" : "");
}
elsif (defined $params{session}->remote_addr()) {
return "web commit from ".
- $params{session}->remote_addr().
+ IkiWiki::cloak($params{session}->remote_addr()).
(length $params{message} ? ": $params{message}" : "");
}
}
diff --git a/IkiWiki/Plugin/templatebody.pm b/IkiWiki/Plugin/templatebody.pm
new file mode 100644
index 000000000..3848b75c7
--- /dev/null
+++ b/IkiWiki/Plugin/templatebody.pm
@@ -0,0 +1,81 @@
+#!/usr/bin/perl
+# Define self-documenting templates as wiki pages without HTML::Template
+# markup leaking into IkiWiki's output.
+# Copyright © 2013-2014 Simon McVittie. GPL-2+, see debian/copyright
+package IkiWiki::Plugin::templatebody;
+
+use warnings;
+use strict;
+use IkiWiki 3.00;
+use Encode;
+
+sub import {
+ hook(type => "getsetup", id => "templatebody", call => \&getsetup);
+ hook(type => "preprocess", id => "templatebody", call => \&preprocess,
+ scan => 1);
+ hook(type => "readtemplate", id => "templatebody",
+ call => \&readtemplate);
+}
+
+sub getsetup () {
+ return
+ plugin => {
+ safe => 1,
+ rebuild => undef,
+ section => "core",
+ },
+}
+
+# This doesn't persist between runs: we're going to read and scan the
+# template file regardless, so there's no point in saving it to the index.
+# Example contents:
+# ("templates/note" => "<div class=\"notebox\">\n<TMPL_VAR text>\n</div>")
+my %templates;
+
+sub preprocess (@) {
+ my %params=@_;
+
+ # [[!templatebody "<div>hello</div>"]] results in
+ # preprocess("<div>hello</div>" => undef, page => ...)
+ my $content = $_[0];
+ if (length $_[1]) {
+ error(gettext("first parameter must be the content"));
+ }
+
+ $templates{$params{page}} = $content;
+
+ return "";
+}
+
+sub readtemplate {
+ my %params = @_;
+ my $tpage = $params{page};
+ my $content = $params{content};
+ my $filename = $params{filename};
+
+ # pass-through if it's a .tmpl attachment or otherwise unsuitable
+ return $content unless defined $tpage;
+ return $content if $tpage =~ /\.tmpl$/;
+ my $src = $pagesources{$tpage};
+ return $content unless defined $src;
+ return $content unless defined pagetype($src);
+
+ # We might be using the template for [[!template]], which has to run
+ # during the scan stage so that templates can include scannable
+ # directives which are expanded in the resulting page. Calls to
+ # IkiWiki::scan are in arbitrary order, so the template might
+ # not have been scanned yet. Make sure.
+ require IkiWiki::Render;
+ IkiWiki::scan($src);
+
+ # Having scanned it, we know whether it had a [[!templatebody]].
+ if (exists $templates{$tpage}) {
+ return $templates{$tpage};
+ }
+
+ # If not, return the whole thing. (Eventually, after implementing
+ # a transition, this can become an error.)
+ return $content;
+}
+
+1
diff --git a/IkiWiki/Plugin/teximg.pm b/IkiWiki/Plugin/teximg.pm
index 3d6fa9942..e0f97614b 100644
--- a/IkiWiki/Plugin/teximg.pm
+++ b/IkiWiki/Plugin/teximg.pm
@@ -146,8 +146,8 @@ sub gen_image ($$$$) {
my $tex = $config{teximg_prefix};
$tex .= '\['.$code.'\]';
$tex .= $config{teximg_postfix};
- $tex =~ s!\\documentclass{article}!\\documentclass[${height}pt]{article}!g;
- $tex =~ s!\\documentclass{scrartcl}!\\documentclass[${height}pt]{scrartcl}!g;
+ $tex =~ s!\\documentclass\{article}!\\documentclass[${height}pt]{article}!g;
+ $tex =~ s!\\documentclass\{scrartcl}!\\documentclass[${height}pt]{scrartcl}!g;
my $tmp = eval { create_tmp_dir($digest) };
if (! $@ &&
diff --git a/IkiWiki/Plugin/tla.pm b/IkiWiki/Plugin/tla.pm
index 11be248e8..c2fffbced 100644
--- a/IkiWiki/Plugin/tla.pm
+++ b/IkiWiki/Plugin/tla.pm
@@ -108,12 +108,12 @@ sub rcs_commit (@) {
if (defined $params{session}) {
if (defined $params{session}->param("name")) {
$message="web commit by ".
- $params{session}->param("name").
+ IkiWiki::cloak($params{session}->param("name")).
(length $message ? ": $message" : "");
}
elsif (defined $params{session}->remote_addr()) {
$message="web commit from ".
- $params{session}->remote_addr().
+ IkiWiki::cloak($params{session}->remote_addr()).
(length $message ? ": $message" : "");
}
}
diff --git a/IkiWiki/Plugin/websetup.pm b/IkiWiki/Plugin/websetup.pm
index f95017c90..d3cbff0d5 100644
--- a/IkiWiki/Plugin/websetup.pm
+++ b/IkiWiki/Plugin/websetup.pm
@@ -460,12 +460,12 @@ sub showform ($$) {
my @command;
if ($form->submitted eq 'Rebuild Wiki') {
- @command=("ikiwiki", "-setup", $config{setupfile},
- "-rebuild", "-v");
+ @command=("ikiwiki", "--setup", $config{setupfile},
+ "--rebuild", "-v");
}
else {
- @command=("ikiwiki", "-setup", $config{setupfile},
- "-refresh", "-wrappers", "-v");
+ @command=("ikiwiki", "--setup", $config{setupfile},
+ "--refresh", "--wrappers", "-v");
}
close STDERR;
diff --git a/IkiWiki/Render.pm b/IkiWiki/Render.pm
index fa2940b01..4c998b156 100644
--- a/IkiWiki/Render.pm
+++ b/IkiWiki/Render.pm
@@ -6,7 +6,7 @@ use warnings;
use strict;
use IkiWiki;
-my (%backlinks, %rendered);
+my (%backlinks, %rendered, %scanned);
our %brokenlinks;
my $links_calculated=0;
@@ -111,7 +111,7 @@ sub genpage ($$) {
}
templateactions($template, $page);
- my @backlinks=sort { $a->{page} cmp $b->{page} } backlinks($page);
+ my @backlinks=sort { $a->{page} cmp $b->{page} || $a->{url} cmp $b->{url} } backlinks($page);
my ($backlinks, $more_backlinks);
if (@backlinks <= $config{numbacklinks} || ! $config{numbacklinks}) {
$backlinks=\@backlinks;
@@ -134,6 +134,7 @@ sub genpage ($$) {
ctime => displaytime($pagectime{$page}, undef, 1),
baseurl => baseurl($page),
html5 => $config{html5},
+ responsive_layout => $config{responsive_layout},
);
run_hooks(pagetemplate => sub {
@@ -154,6 +155,8 @@ sub genpage ($$) {
sub scan ($) {
my $file=shift;
+ return if ($config{rebuild} && $phase > PHASE_SCAN) || $scanned{$file};
+ $scanned{$file}=1;
debug(sprintf(gettext("scanning %s"), $file));
@@ -471,7 +474,12 @@ sub find_new_files ($) {
}
$pagecase{lc $page}=$page;
if (! exists $pagectime{$page}) {
- $pagectime{$page}=(srcfile_stat($file))[10];
+ my @stat=srcfile_stat($file, 1);
+ # For the creation time of the page, take the
+ # inode change time (not creation time!) or
+ # the modification time, whichever is older.
+ my $ctime=($stat[10] < $stat[9] ? $stat[10] : $stat[9]);
+ $pagectime{$page}=$ctime if defined $ctime;
}
}
}
@@ -531,10 +539,11 @@ sub find_changed ($) {
my @internal_changed;
foreach my $file (@$files) {
my $page=pagename($file);
- my ($srcfile, @stat)=srcfile_stat($file);
- if (! exists $pagemtime{$page} ||
- $stat[9] > $pagemtime{$page} ||
- $forcerebuild{$page}) {
+ my ($srcfile, @stat)=srcfile_stat($file, 1);
+ if (defined $srcfile &&
+ (! exists $pagemtime{$page} ||
+ $stat[9] > $pagemtime{$page} ||
+ $forcerebuild{$page})) {
$pagemtime{$page}=$stat[9];
if (isinternal($page)) {
@@ -825,6 +834,8 @@ sub gen_autofile ($$$) {
}
sub refresh () {
+ $phase = PHASE_SCAN;
+
srcdir_check();
run_hooks(refresh => sub { shift->() });
my ($files, $pages, $new, $internal_new, $del, $internal_del, $changed, $internal_changed);
@@ -876,7 +887,14 @@ sub refresh () {
}
calculate_links();
-
+
+ # At this point it becomes OK to start matching pagespecs.
+ $phase = PHASE_RENDER;
+ # Save some memory in full rebuilds: we no longer need to keep
+ # track of which pages we've scanned, because we can assume the
+ # answer is "all of them".
+ %scanned = () if $config{rebuild};
+
remove_del(@$del, @$internal_del);
foreach my $file (@$changed) {
@@ -940,6 +958,10 @@ sub commandline_render () {
loadindex();
unlockwiki();
+ # This function behaves as though it's in the render phase;
+ # all other files are assumed to have been scanned last time.
+ $phase = PHASE_RENDER;
+
my $srcfile=possibly_foolish_untaint($config{render});
my $file=$srcfile;
$file=~s/\Q$config{srcdir}\E\/?//;
diff --git a/IkiWiki/Setup/Automator.pm b/IkiWiki/Setup/Automator.pm
index 671438710..9239974ad 100644
--- a/IkiWiki/Setup/Automator.pm
+++ b/IkiWiki/Setup/Automator.pm
@@ -154,31 +154,33 @@ sub import (@) {
foreach my $admin (@{$config{adminuser}}) {
next if defined IkiWiki::openiduser($admin);
- # Prompt for password w/o echo.
- my ($password, $password2);
- system('stty -echo 2>/dev/null');
- local $|=1;
- print "\n\nCreating wiki admin $admin ...\n";
- for (;;) {
- print "Choose a password: ";
- chomp($password=<STDIN>);
- print "\n";
- print "Confirm password: ";
- chomp($password2=<STDIN>);
-
- last if $password2 eq $password;
-
- print "Password mismatch.\n\n";
- }
- print "\n\n\n";
- system('stty sane 2>/dev/null');
+ if (! defined IkiWiki::emailuser($admin)) {
+ # Prompt for password w/o echo.
+ my ($password, $password2);
+ system('stty -echo 2>/dev/null');
+ local $|=1;
+ print "\n\nCreating wiki admin $admin ...\n";
+ for (;;) {
+ print "Choose a password: ";
+ chomp($password=<STDIN>);
+ print "\n";
+ print "Confirm password: ";
+ chomp($password2=<STDIN>);
+
+ last if $password2 eq $password;
+
+ print "Password mismatch.\n\n";
+ }
+ print "\n\n\n";
+ system('stty sane 2>/dev/null');
- if (IkiWiki::userinfo_setall($admin, { regdate => time }) &&
- IkiWiki::Plugin::passwordauth::setpassword($admin, $password)) {
- IkiWiki::userinfo_set($admin, "email", $config{adminemail}) if defined $config{adminemail};
- }
- else {
- error("problem setting up $admin user");
+ if (IkiWiki::userinfo_setall($admin, { regdate => time }) &&
+ IkiWiki::Plugin::passwordauth::setpassword($admin, $password)) {
+ IkiWiki::userinfo_set($admin, "email", $config{adminemail}) if defined $config{adminemail};
+ }
+ else {
+ error("problem setting up $admin user");
+ }
}
}
@@ -206,7 +208,7 @@ sub import (@) {
prettydir($config{$key})."\n";
}
print "To modify settings, edit ".prettydir($config{dumpsetup})." and then run:\n";
- print " ikiwiki -setup ".prettydir($config{dumpsetup})."\n";
+ print " ikiwiki --setup ".prettydir($config{dumpsetup})."\n";
exit 0;
}
diff --git a/IkiWiki/Wrapper.pm b/IkiWiki/Wrapper.pm
index b46bc6aa9..69500029c 100644
--- a/IkiWiki/Wrapper.pm
+++ b/IkiWiki/Wrapper.pm
@@ -52,6 +52,7 @@ sub gen_wrapper () {
HTTP_COOKIE REMOTE_USER HTTPS REDIRECT_STATUS
HTTP_HOST SERVER_PORT HTTPS HTTP_ACCEPT
REDIRECT_URL} if $config{cgi};
+ my $envsize=$#envsave;
my $envsave="";
foreach my $var (@envsave) {
$envsave.=<<"EOF";
@@ -59,6 +60,18 @@ sub gen_wrapper () {
addenv("$var", s);
EOF
}
+ if (ref $config{ENV} eq 'HASH') {
+ foreach my $key (keys %{$config{ENV}}) {
+ my $val=$config{ENV}{$key};
+ utf8::encode($val) if utf8::is_utf8($val);
+ $val =~ s/([^A-Za-z0-9])/sprintf '""\\x%02x""', ord($1)/ge;
+ $envsize += 1;
+ $envsave.=<<"EOF";
+ addenv("$key", "$val");
+EOF
+ }
+ delete $config{ENV};
+ }
my @wrapper_hooks;
run_hooks(genwrapper => sub { push @wrapper_hooks, shift->() });
@@ -171,21 +184,28 @@ EOF
#include <sys/file.h>
extern char **environ;
-char *newenviron[$#envsave+7];
+char *newenviron[$envsize+7];
int i=0;
void addenv(char *var, char *val) {
char *s=malloc(strlen(var)+1+strlen(val)+1);
- if (!s)
+ if (!s) {
perror("malloc");
- sprintf(s, "%s=%s", var, val);
- newenviron[i++]=s;
+ exit(1);
+ }
+ else {
+ sprintf(s, "%s=%s", var, val);
+ newenviron[i++]=s;
+ }
}
void set_cgilock_fd (int lockfd) {
- char *fd_s=malloc(8);
+ char fd_s[12];
sprintf(fd_s, "%i", lockfd);
- setenv("IKIWIKI_CGILOCK_FD", fd_s, 1);
+ if (setenv("IKIWIKI_CGILOCK_FD", fd_s, 1) != 0) {
+ perror("setenv");
+ exit(1);
+ }
}
int main (int argc, char **argv) {