]> git.vanrenterghem.biz Git - git.ikiwiki.info.git/blobdiff - IkiWiki.pm
cherry-pick uri security fix
[git.ikiwiki.info.git] / IkiWiki.pm
index 6d89adddf78a24792f8b986eaee0446b6caa98d9..0c05bb0d377c8622b2e0ea3ff6a21abea14a6322 100644 (file)
@@ -10,15 +10,16 @@ use POSIX;
 use open qw{:utf8 :std};
 
 use vars qw{%config %links %oldlinks %pagemtime %pagectime %pagecase
 use open qw{:utf8 :std};
 
 use vars qw{%config %links %oldlinks %pagemtime %pagectime %pagecase
-            %renderedfiles %oldrenderedfiles %pagesources %destsources
-            %depends %hooks %forcerebuild $gettext_obj};
+           %pagestate %renderedfiles %oldrenderedfiles %pagesources
+           %destsources %depends %hooks %forcerebuild $gettext_obj};
 
 use Exporter q{import};
 our @EXPORT = qw(hook debug error template htmlpage add_depends pagespec_match
                  bestlink htmllink readfile writefile pagetype srcfile pagename
                  displaytime will_render gettext urlto targetpage
                 add_underlay
 
 use Exporter q{import};
 our @EXPORT = qw(hook debug error template htmlpage add_depends pagespec_match
                  bestlink htmllink readfile writefile pagetype srcfile pagename
                  displaytime will_render gettext urlto targetpage
                 add_underlay
-                 %config %links %renderedfiles %pagesources %destsources);
+                 %config %links %pagestate %renderedfiles
+                 %pagesources %destsources);
 our $VERSION = 2.00; # plugin interface version, next is ikiwiki version
 our $version='unknown'; # VERSION_AUTOREPLACE done by Makefile, DNE
 my $installdir=''; # INSTALLDIR_AUTOREPLACE done by Makefile, DNE
 our $VERSION = 2.00; # plugin interface version, next is ikiwiki version
 our $version='unknown'; # VERSION_AUTOREPLACE done by Makefile, DNE
 my $installdir=''; # INSTALLDIR_AUTOREPLACE done by Makefile, DNE
@@ -31,7 +32,7 @@ memoize("file_pruned");
 
 sub defaultconfig () { #{{{
        return
 
 sub defaultconfig () { #{{{
        return
-       wiki_file_prune_regexps => [qr/\.\./, qr/^\./, qr/\/\./,
+       wiki_file_prune_regexps => [qr/(^|\/)\.\.(\/|$)/, qr/^\./, qr/\/\./,
                qr/\.x?html?$/, qr/\.ikiwiki-new$/,
                qr/(^|\/).svn\//, qr/.arch-ids\//, qr/{arch}\//,
                qr/(^|\/)_MTN\//,
                qr/\.x?html?$/, qr/\.ikiwiki-new$/,
                qr/(^|\/).svn\//, qr/.arch-ids\//, qr/{arch}\//,
                qr/(^|\/)_MTN\//,
@@ -39,7 +40,7 @@ sub defaultconfig () { #{{{
        wiki_link_regexp => qr{
                \[\[                    # beginning of link
                (?:
        wiki_link_regexp => qr{
                \[\[                    # beginning of link
                (?:
-                       ([^\]\|\n]+)      # 1: link text
+                       ([^\]\|\n\s]+)  # 1: link text
                        \|              # followed by '|'
                )?                      # optional
                
                        \|              # followed by '|'
                )?                      # optional
                
@@ -61,13 +62,14 @@ sub defaultconfig () { #{{{
        cgi => 0,
        post_commit => 0,
        rcs => '',
        cgi => 0,
        post_commit => 0,
        rcs => '',
-       notify => 0,
        url => '',
        cgiurl => '',
        historyurl => '',
        diffurl => '',
        rss => 0,
        atom => 0,
        url => '',
        cgiurl => '',
        historyurl => '',
        diffurl => '',
        rss => 0,
        atom => 0,
+       allowrss => 0,
+       allowatom => 0,
        discussion => 1,
        rebuild => 0,
        refresh => 0,
        discussion => 1,
        rebuild => 0,
        refresh => 0,
@@ -75,7 +77,6 @@ sub defaultconfig () { #{{{
        w3mmode => 0,
        wrapper => undef,
        wrappermode => undef,
        w3mmode => 0,
        wrapper => undef,
        wrappermode => undef,
-       svnrepo => undef,
        svnpath => "trunk",
        gitorigin_branch => "origin",
        gitmaster_branch => "master",
        svnpath => "trunk",
        gitorigin_branch => "origin",
        gitmaster_branch => "master",
@@ -89,7 +90,7 @@ sub defaultconfig () { #{{{
        adminuser => undef,
        adminemail => undef,
        plugin => [qw{mdwn inline htmlscrubber passwordauth openid signinedit
        adminuser => undef,
        adminemail => undef,
        plugin => [qw{mdwn inline htmlscrubber passwordauth openid signinedit
-                     lockedit conditional}],
+                     lockedit conditional recentchanges}],
        libdir => undef,
        timeformat => '%c',
        locale => undef,
        libdir => undef,
        timeformat => '%c',
        locale => undef,
@@ -141,6 +142,10 @@ sub checkconfig () { #{{{
                require IkiWiki::Rcs::Stub;
        }
 
                require IkiWiki::Rcs::Stub;
        }
 
+       if (exists $config{umask}) {
+               umask(possibly_foolish_untaint($config{umask}));
+       }
+
        run_hooks(checkconfig => sub { shift->() });
 
        return 1;
        run_hooks(checkconfig => sub { shift->() });
 
        return 1;
@@ -257,6 +262,12 @@ sub pagetype ($) { #{{{
        return;
 } #}}}
 
        return;
 } #}}}
 
+sub isinternal ($) { #{{{
+       my $page=shift;
+       return exists $pagesources{$page} &&
+               $pagesources{$page} =~ /\._([^.]+)$/;
+} #}}}
+
 sub pagename ($) { #{{{
        my $file=shift;
 
 sub pagename ($) { #{{{
        my $file=shift;
 
@@ -407,6 +418,7 @@ sub bestlink ($$) { #{{{
                # absolute links
                $cwd="";
        }
                # absolute links
                $cwd="";
        }
+       $link=~s/\/$//;
 
        do {
                my $l=$cwd;
 
        do {
                my $l=$cwd;
@@ -498,19 +510,24 @@ sub abs2rel ($$) { #{{{
        return $ret;
 } #}}}
 
        return $ret;
 } #}}}
 
-sub displaytime ($) { #{{{
+sub displaytime ($;$) { #{{{
        my $time=shift;
        my $time=shift;
+       my $format=shift;
+       if (! defined $format) {
+               $format=$config{timeformat};
+       }
 
        # strftime doesn't know about encodings, so make sure
        # its output is properly treated as utf8
 
        # strftime doesn't know about encodings, so make sure
        # its output is properly treated as utf8
-       return decode_utf8(POSIX::strftime(
-                       $config{timeformat}, localtime($time)));
+       return decode_utf8(POSIX::strftime($format, localtime($time)));
 } #}}}
 
 sub beautify_url ($) { #{{{
        my $url=shift;
 
 } #}}}
 
 sub beautify_url ($) { #{{{
        my $url=shift;
 
-       $url =~ s!/index.$config{htmlext}$!/!;
+       if ($config{usedirs}) {
+               $url =~ s!/index.$config{htmlext}$!/!;
+       }
        $url =~ s!^$!./!; # Browsers don't like empty links...
 
        return $url;
        $url =~ s!^$!./!; # Browsers don't like empty links...
 
        return $url;
@@ -539,6 +556,8 @@ sub htmllink ($$$;@) { #{{{
        my $link=shift;
        my %opts=@_;
 
        my $link=shift;
        my %opts=@_;
 
+       $link=~s/\/$//;
+
        my $bestlink;
        if (! $opts{forcesubpage}) {
                $bestlink=bestlink($lpage, $link);
        my $bestlink;
        if (! $opts{forcesubpage}) {
                $bestlink=bestlink($lpage, $link);
@@ -556,14 +575,15 @@ sub htmllink ($$$;@) { #{{{
        }
        
        return "<span class=\"selflink\">$linktext</span>"
        }
        
        return "<span class=\"selflink\">$linktext</span>"
-               if length $bestlink && $page eq $bestlink;
+               if length $bestlink && $page eq $bestlink &&
+                  ! defined $opts{anchor};
        
        if (! $destsources{$bestlink}) {
                $bestlink=htmlpage($bestlink);
 
                if (! $destsources{$bestlink}) {
                        return $linktext unless length $config{cgiurl};
        
        if (! $destsources{$bestlink}) {
                $bestlink=htmlpage($bestlink);
 
                if (! $destsources{$bestlink}) {
                        return $linktext unless length $config{cgiurl};
-                       return "<span><a href=\"".
+                       return "<span class=\"createlink\"><a href=\"".
                                cgiurl(
                                        do => "create",
                                        page => pagetitle(lc($link), 1),
                                cgiurl(
                                        do => "create",
                                        page => pagetitle(lc($link), 1),
@@ -588,14 +608,33 @@ sub htmllink ($$$;@) { #{{{
        if (defined $opts{rel}) {
                push @attrs, ' rel="'.$opts{rel}.'"';
        }
        if (defined $opts{rel}) {
                push @attrs, ' rel="'.$opts{rel}.'"';
        }
+       if (defined $opts{class}) {
+               push @attrs, ' class="'.$opts{class}.'"';
+       }
 
        return "<a href=\"$bestlink\"@attrs>$linktext</a>";
 } #}}}
 
 
        return "<a href=\"$bestlink\"@attrs>$linktext</a>";
 } #}}}
 
+sub userlink ($) { #{{{
+       my $user=shift;
+
+       my $oiduser=eval { openiduser($user) };
+       if (defined $oiduser) {
+               return "<a href=\"$user\">$oiduser</a>";
+       }
+       else {
+               return htmllink("", "", escapeHTML(
+                       length $config{userdir} ? $config{userdir}."/".$user : $user
+               ), noimageinline => 1);
+       }
+} #}}}
+
 sub htmlize ($$$) { #{{{
        my $page=shift;
        my $type=shift;
        my $content=shift;
 sub htmlize ($$$) { #{{{
        my $page=shift;
        my $type=shift;
        my $content=shift;
+       
+       my $oneline = $content !~ /\n/;
 
        if (exists $hooks{htmlize}{$type}) {
                $content=$hooks{htmlize}{$type}{call}->(
 
        if (exists $hooks{htmlize}{$type}) {
                $content=$hooks{htmlize}{$type}{call}->(
@@ -613,6 +652,14 @@ sub htmlize ($$$) { #{{{
                        content => $content,
                );
        });
                        content => $content,
                );
        });
+       
+       if ($oneline) {
+               # hack to get rid of enclosing junk added by markdown
+               # and other htmlizers
+               $content=~s/^<p>//i;
+               $content=~s/<\/p>$//i;
+               chomp $content;
+       }
 
        return $content;
 } #}}}
 
        return $content;
 } #}}}
@@ -663,7 +710,7 @@ sub preprocess ($$$;$$) { #{{{
                        # consider it significant.
                        my @params;
                        while ($params =~ m{
                        # consider it significant.
                        my @params;
                        while ($params =~ m{
-                               (?:(\w+)=)?             # 1: named parameter key?
+                               (?:([-\w]+)=)?          # 1: named parameter key?
                                (?:
                                        """(.*?)"""     # 2: triple-quoted value
                                |
                                (?:
                                        """(.*?)"""     # 2: triple-quoted value
                                |
@@ -706,12 +753,25 @@ sub preprocess ($$$;$$) { #{{{
                                        $command, $page, $preprocessing{$page}).
                                "]]";
                        }
                                        $command, $page, $preprocessing{$page}).
                                "]]";
                        }
-                       my $ret=$hooks{preprocess}{$command}{call}->(
-                               @params,
-                               page => $page,
-                               destpage => $destpage,
-                               preview => $preprocess_preview,
-                       );
+                       my $ret;
+                       if (! $scan) {
+                               $ret=$hooks{preprocess}{$command}{call}->(
+                                       @params,
+                                       page => $page,
+                                       destpage => $destpage,
+                                       preview => $preprocess_preview,
+                               );
+                       }
+                       else {
+                               # use void context during scan pass
+                               $hooks{preprocess}{$command}{call}->(
+                                       @params,
+                                       page => $page,
+                                       destpage => $destpage,
+                                       preview => $preprocess_preview,
+                               );
+                               $ret="";
+                       }
                        $preprocessing{$page}--;
                        return $ret;
                }
                        $preprocessing{$page}--;
                        return $ret;
                }
@@ -723,11 +783,11 @@ sub preprocess ($$$;$$) { #{{{
        $content =~ s{
                (\\?)           # 1: escape?
                \[\[            # directive open
        $content =~ s{
                (\\?)           # 1: escape?
                \[\[            # directive open
-               (\w+)           # 2: command
+               ([-\w]+)        # 2: command
                \s+
                (               # 3: the parameters..
                        (?:
                \s+
                (               # 3: the parameters..
                        (?:
-                               (?:\w+=)?               # named parameter key?
+                               (?:[-\w]+=)?            # named parameter key?
                                (?:
                                        """.*?"""       # triple-quoted value
                                        |
                                (?:
                                        """.*?"""       # triple-quoted value
                                        |
@@ -823,6 +883,11 @@ sub enable_commit_hook () { #{{{
 } #}}}
 
 sub loadindex () { #{{{
 } #}}}
 
 sub loadindex () { #{{{
+       %oldrenderedfiles=%pagectime=();
+       if (! $config{rebuild}) {
+               %pagesources=%pagemtime=%oldlinks=%links=%depends=
+                       %destsources=%renderedfiles=%pagecase=%pagestate=();
+       }
        open (my $in, "<", "$config{wikistatedir}/index") || return;
        while (<$in>) {
                $_=possibly_foolish_untaint($_);
        open (my $in, "<", "$config{wikistatedir}/index") || return;
        while (<$in>) {
                $_=possibly_foolish_untaint($_);
@@ -847,6 +912,10 @@ sub loadindex () { #{{{
                        $destsources{$_}=$page foreach @{$items{dest}};
                        $renderedfiles{$page}=[@{$items{dest}}];
                        $pagecase{lc $page}=$page;
                        $destsources{$_}=$page foreach @{$items{dest}};
                        $renderedfiles{$page}=[@{$items{dest}}];
                        $pagecase{lc $page}=$page;
+                       foreach my $k (grep /_/, keys %items) {
+                               my ($id, $key)=split(/_/, $k, 2);
+                               $pagestate{$page}{decode_entities($id)}{decode_entities($key)}=$items{$k}[0];
+                       }
                }
                $oldrenderedfiles{$page}=[@{$items{dest}}];
                $pagectime{$page}=$items{ctime}[0];
                }
                $oldrenderedfiles{$page}=[@{$items{dest}}];
                $pagectime{$page}=$items{ctime}[0];
@@ -857,6 +926,12 @@ sub loadindex () { #{{{
 sub saveindex () { #{{{
        run_hooks(savestate => sub { shift->() });
 
 sub saveindex () { #{{{
        run_hooks(savestate => sub { shift->() });
 
+       my %hookids;
+       foreach my $type (keys %hooks) {
+               $hookids{encode_entities($_)}=1 foreach keys %{$hooks{$type}};
+       }
+       my @hookids=sort keys %hookids;
+
        if (! -d $config{wikistatedir}) {
                mkdir($config{wikistatedir});
        }
        if (! -d $config{wikistatedir}) {
                mkdir($config{wikistatedir});
        }
@@ -874,6 +949,13 @@ sub saveindex () { #{{{
                if (exists $depends{$page}) {
                        $line.=" depends=".encode_entities($depends{$page}, " \t\n");
                }
                if (exists $depends{$page}) {
                        $line.=" depends=".encode_entities($depends{$page}, " \t\n");
                }
+               if (exists $pagestate{$page}) {
+                       foreach my $id (@hookids) {
+                               foreach my $key (keys %{$pagestate{$page}{$id}}) {
+                                       $line.=' '.$id.'_'.encode_entities($key)."=".encode_entities($pagestate{$page}{$id}{$key}, " \t\n");
+                               }
+                       }
+               }
                print $out $line."\n" || error("failed writing to $newfile: $!", $cleanup);
        }
        close $out || error("failed saving to $newfile: $!", $cleanup);
                print $out $line."\n" || error("failed writing to $newfile: $!", $cleanup);
        }
        close $out || error("failed saving to $newfile: $!", $cleanup);
@@ -903,7 +985,7 @@ sub template_params (@) { #{{{
        my @ret=(
                filter => sub {
                        my $text_ref = shift;
        my @ret=(
                filter => sub {
                        my $text_ref = shift;
-                       ${$text_ref} = Encode::decode_utf8(${$text_ref});
+                       ${$text_ref} = decode_utf8(${$text_ref});
                },
                filename => $filename,
                loop_context_vars => 1,
                },
                filename => $filename,
                loop_context_vars => 1,
@@ -1028,10 +1110,10 @@ sub file_pruned ($$) { #{{{
        require File::Spec;
        my $file=File::Spec->canonpath(shift);
        my $base=File::Spec->canonpath(shift);
        require File::Spec;
        my $file=File::Spec->canonpath(shift);
        my $base=File::Spec->canonpath(shift);
-       $file =~ s#^\Q$base\E/*##;
+       $file =~ s#^\Q$base\E/+##;
 
        my $regexp='('.join('|', @{$config{wiki_file_prune_regexps}}).')';
 
        my $regexp='('.join('|', @{$config{wiki_file_prune_regexps}}).')';
-       return $file =~ m/$regexp/;
+       return $file =~ m/$regexp/ && $file ne $base;
 } #}}}
 
 sub gettext { #{{{
 } #}}}
 
 sub gettext { #{{{
@@ -1190,13 +1272,22 @@ sub match_glob ($$;@) { #{{{
        $glob=~s/\\\?/./g;
 
        if ($page=~/^$glob$/i) {
        $glob=~s/\\\?/./g;
 
        if ($page=~/^$glob$/i) {
-               return IkiWiki::SuccessReason->new("$glob matches $page");
+               if (! IkiWiki::isinternal($page) || $params{internal}) {
+                       return IkiWiki::SuccessReason->new("$glob matches $page");
+               }
+               else {
+                       return IkiWiki::FailReason->new("$glob matches $page, but the page is an internal page");
+               }
        }
        else {
                return IkiWiki::FailReason->new("$glob does not match $page");
        }
 } #}}}
 
        }
        else {
                return IkiWiki::FailReason->new("$glob does not match $page");
        }
 } #}}}
 
+sub match_internal ($$;@) { #{{{
+       return match_glob($_[0], $_[1], @_, internal => 1)
+} #}}}
+
 sub match_link ($$;@) { #{{{
        my $page=shift;
        my $link=lc(shift);
 sub match_link ($$;@) { #{{{
        my $page=shift;
        my $link=lc(shift);
@@ -1220,7 +1311,7 @@ sub match_link ($$;@) { #{{{
                                if $bestlink eq IkiWiki::bestlink($page, $p);
                }
                else {
                                if $bestlink eq IkiWiki::bestlink($page, $p);
                }
                else {
-                       return IkiWiki::SuccessReason->new("$page links to page matching $link")
+                       return IkiWiki::SuccessReason->new("$page links to page $p matching $link")
                                if match_glob($p, $link, %params);
                }
        }
                                if match_glob($p, $link, %params);
                }
        }
@@ -1292,19 +1383,4 @@ sub match_creation_year ($$;@) { #{{{
        }
 } #}}}
 
        }
 } #}}}
 
-sub match_user ($$;@) { #{{{
-       shift;
-       my $user=shift;
-       my %params=@_;
-
-       return IkiWiki::FailReason->new('cannot match user')
-               unless exists $params{user};
-       if ($user eq $params{user}) {
-               return IkiWiki::SuccessReason->new("user is $user")
-       }
-       else {
-               return IkiWiki::FailReason->new("user is not $user");
-       }
-} #}}}
-
 1
 1