Index: lib/TWiki.pm =================================================================== --- lib/TWiki.pm (revision 3312) +++ lib/TWiki.pm (working copy) @@ -60,7 +60,7 @@ $doLogTopicView $doLogTopicEdit $doLogTopicSave $doLogRename $doLogTopicAttach $doLogTopicUpload $doLogTopicRdiff $doLogTopicChanges $doLogTopicSearch $doLogRegistration - $superAdminGroup $doSuperAdminGroup $OS + $superAdminGroup $doSuperAdminGroup $sanitizationMode $OS $disableAllPlugins $attachAsciiPath $displayTimeValues $dispScriptUrlPath $dispViewPath ); @@ -2439,6 +2436,55 @@ return $text; } +=pod + +---++ handleCommonTagsSafe( $text, $topic, $web ) => processed $text +Processes %VARIABLE% syntax, but only from "safe" sources +(i.e. nothing that a mere user of the wiki, regardless of their access +level, could modify). + +Returns $text, after variable substitution. + +=cut + +sub handleCommonTagsSafe { + my( $text, $theTopic, $theWeb ) = @_; + + if( !$theWeb ) { + $theWeb = $webName; + } + + my %invocationInternalTags = ( + TOPIC => $theTopic, + WEB => $theWeb, + INCLUDINGTOPIC => $theTopic, + INCLUDINGWEB => $theWeb, + EDITURL => "$dispScriptUrlPath/edit$scriptSuffix/$theWeb/$theTopic\?t=", + ); + + while($text =~ + s< + \%(\w+)\% + >< + my $safevar; + if( defined( $invocationInternalTags{$1} )) { + $safevar = $invocationInternalTags{$1}; + } elsif( defined( $sessionInternalTags{$1} )) { + $safevar = $sessionInternalTags{$1}; + } elsif( defined( $staticInternalTags{$1} )) { + $safevar = $staticInternalTags{$1}; + } else { + $safevar = "\%$1\%"; + } + $safevar; + >egx) + {}; + + $text =~ s/\%\(\w+)\%/\%$1\%/g; + + return $text; +} + =end twiki =cut Index: lib/TWiki.cfg =================================================================== --- lib/TWiki.cfg (revision 3312) +++ lib/TWiki.cfg (working copy) @@ -395,6 +395,8 @@ # Group of users that can use cmd=repRev # or that ALWAYS have edit powers (set $doSuperAdminGroup=1) $superAdminGroup = "TWikiAdminGroup"; +# Sanitization mode. Possible values are 'stripscripts', 'scrubber', and 'none'. +$sanitizationMode = 'scrubber'; # flag variables that could change: # ================================================================== Index: lib/TWiki/Search.pm =================================================================== --- lib/TWiki/Search.pm (revision 3312) +++ lib/TWiki/Search.pm (working copy) @@ -441,8 +441,22 @@ } elsif ($doRenameView ) { # Rename view, showing where topics refer to topic being renamed. $tmpl = &TWiki::Templates::readTemplate( "searchrenameview" ); # JohnTalintyre + } else { + $tmpl = &TWiki::Templates::readTemplate( "search" ); + } - # Create full search string from topic name that is passed in + # won't work if %SPLIT% comes before , but will fail safe + my $tmplhtmlhead; + ( $tmplhtmlhead, $tmpl ) = TWiki::Templates::splitTemplate( $tmpl ); + if( $callback ) { + &$callback($tmplhtmlhead); + } else { + $searchResult .= $tmplhtmlhead; + } + + if( $doRenameView ) { + + # Create full search string from topic name that is passed in $renameTopic = $theSearchVal; if( $renameTopic =~ /(.*)\\\.(.*)/o ) { $renameWeb = $1; @@ -450,7 +464,7 @@ } $spacedTopic = TWiki::searchableTopic( $renameTopic ); $spacedTopic = $renameWeb . '\.' . $spacedTopic if( $renameWeb ); - + # I18N: match non-alpha before and after topic name in renameview searches # This regex must work under grep, i.e. if using Perl 5.6 or higher # the POSIX character classes will be used in grep as well. @@ -458,8 +472,6 @@ $theSearchVal = "(^|[^${alphaNum}_])$theSearchVal" . "([^${alphaNum}_]" . '|$)|' . '(\[\[' . $spacedTopic . '\]\])'; - } else { - $tmpl = &TWiki::Templates::readTemplate( "search" ); } $tmpl =~ s/\%META{.*?}\%//go; # remove %META{"parent"}% Index: lib/TWiki/Render.pm =================================================================== --- lib/TWiki/Render.pm (revision 3312) +++ lib/TWiki/Render.pm (working copy) @@ -760,7 +760,7 @@ sub getRenderedVersion { my( $text, $theWeb, $meta ) = @_; - my( $head, $result, $extraLines, $insidePRE, $insideTABLE, $insideNoAutoLink ); + my( $result, $extraLines, $insidePRE, $insideTABLE, $insideNoAutoLink ); return "" unless $text; # nothing to do @@ -772,7 +772,6 @@ $theWeb = $TWiki::webName; } - $head = ""; $result = ""; $insidePRE = 0; $insideTABLE = 0; @@ -794,15 +793,6 @@ $text =~ s/\\\n//gs; # Join lines ending in "\" - # do not render HTML head, style sheets and scripts - # SMELL: this is easily defeated by ]/i ) { - my $bodyTag = ""; - my $bodyText = ""; - ( $head, $bodyTag, $bodyText ) = split( /(\n$||o; # clean up clutch - return "$head$result"; + return $result; } # Handle the various link constructions @@ -1339,6 +1329,51 @@ =end twiki +=pod + +---++ sanitizeFinalHTML( $text ) +Return value: $text with all script and other potential evilness removed +| =$text= | A chunk of HTML twiki wishes to send to the client + +This is the whole-hog HTML sanitization function described in +http://twiki.org/cgi-bin/view/Codev/UsersCanPutJavascriptInTopics . + =cut +sub sanitizeFinalHTML +{ + my( $text ) = @_; + + my $mode = lc($TWiki::sanitizationMode || 'none'); + if ($mode eq 'stripscripts') { + eval "use HTML::StripScripts::Parser;"; + die "No HTML::StripScripts::Parser module" if $@; + my $hss = HTML::StripScripts::Parser->new({ + Context => 'Document', + AllowSrc => 1, + AllowForms => 1, + AllowHref => 1, + AllowRelURL => 1, + }); + $hss->parse( $text ); + + return $hss->filtered_document; + } elsif ($mode eq 'scrubber') { + eval "use HTML::Scrubber::StripScripts;"; + die "No HTML::Scrubber::StripScripts module" if $@; + my $hss = HTML::Scrubber::StripScripts->new( + Allow_src => 1, + Allow_href => 1, + Allow_forms => 1, + Allow_styles => 1, + Whole_document => 1, + ); + return $hss->scrub($text); + } elsif ($mode eq 'none') { + return $text; + } else { + die "Invalid sanitization mode: $mode"; + } +} + 1; Index: lib/TWiki/Templates.pm =================================================================== --- lib/TWiki/Templates.pm (revision 3312) +++ lib/TWiki/Templates.pm (working copy) @@ -282,4 +282,33 @@ return ""; } +=pod + +---++ sub splitTemplate ( $tmpl ) +Return value: A list of the head and the body of the given template text +| $tmpl | The template text to split + +Splits a template into the head and body sections. + +=cut + +sub splitTemplate +{ + my( $tmpl, $topicName, $webName ) = @_; + + my( $head, $body ); + + if( $tmpl =~ m/\n?/$1/gois; # remove and tags + $tmpl = $tmplhead . TWiki::Render::sanitizeFinalHTML($tmpl); TWiki::writeHeader( TWiki::getCgiQuery(), length( $tmpl )); print $tmpl; } Index: lib/TWiki/UI/Edit.pm =================================================================== --- lib/TWiki/UI/Edit.pm (revision 3312) +++ lib/TWiki/UI/Edit.pm (working copy) @@ -72,6 +72,7 @@ return if TWiki::UI::isMirror( $webName, $topic ); + my $tmplhead = ""; my $tmpl = ""; my $text = ""; my $meta = ""; @@ -124,6 +125,7 @@ # Get edit template, standard or a different skin $tmpl = &TWiki::Templates::readTemplate( "edit", $skin ); + ( $tmplhead, $tmpl ) = TWiki::Templates::splitTemplate( $tmpl, $topic, $webName ); unless( $topicExists ) { if( $templateTopic ) { if( $templateTopic =~ /^(.+)\.(.+)$/ ) { @@ -234,8 +236,8 @@ $tmpl =~ s/%TEXT%/$text/go; $tmpl =~ s/( ?) *<\/?(nop|noautolink)\/?>\n?/$1/gois; # remove and tags + $tmpl = $tmplhead . TWiki::Render::sanitizeFinalHTML($tmpl); TWiki::writeHeaderFull ( $query, 'edit', $cgiAppType, length($tmpl) ); - print $tmpl; } Index: lib/TWiki/UI/Manage.pm =================================================================== --- lib/TWiki/UI/Manage.pm (revision 3312) +++ lib/TWiki/UI/Manage.pm (working copy) @@ -604,7 +604,10 @@ } if( $oldLockUser || $newLockUser ) { + my $tmplhead; my $tmpl = TWiki::Templates::readTemplate( "oopslockedrename", $skin ); + ( $tmplhead, $tmpl ) = TWiki::Templates::splitTemplate( $tmpl, $oldTopic, $oldWeb ); + my $editLock = $TWiki::editLockTime / 60; if( $oldLockUser ) { $tmpl =~ s/%OLD_LOCK%/Source topic $oldWeb.$oldTopic is locked by $oldLockUser, lock expires in $oldLockTime minutes.
/go; @@ -623,6 +626,7 @@ $tmpl = &TWiki::Render::getRenderedVersion( $tmpl, $oldWeb ); $tmpl =~ s/( ?) *<\/?(nop|noautolink)\/?>\n?/$1/gois; # remove and tags # SMELL: this is a redirect! + $tmpl = $tmplhead . TWiki::Render::sanitizeFinalHTML($tmpl); TWiki::writeHeader( $query, length( $tmpl )); print $tmpl; return 0; @@ -643,6 +647,7 @@ $confirm, $currentWebOnly, $doAllowNonWikiWord, $skin ) = @_; my $query = TWiki::getCgiQuery(); + my $tmplhead; my $tmpl = ""; $newTopic = $oldTopic unless ( $newTopic ); @@ -652,7 +657,6 @@ if( $theAttachment ) { $tmpl = TWiki::Templates::readTemplate( "moveattachment", $skin ); - $tmpl =~ s/%FILENAME%/$theAttachment/go; } elsif( $confirm ) { $tmpl = TWiki::Templates::readTemplate( "renameconfirm", $skin ); } elsif( $newWeb eq "Trash" ) { @@ -660,7 +664,12 @@ } else { $tmpl = TWiki::Templates::readTemplate( "rename", $skin ); } + ( $tmplhead, $tmpl ) = TWiki::Templates::splitTemplate( $tmpl, $oldTopic, $oldWeb ); + if( $theAttachment ) { + $tmpl =~ s/%FILENAME%/$theAttachment/go; + } + $tmpl = _setVars( $tmpl, $oldTopic, $newWeb, $newTopic, $nonWikiWordFlag ); $tmpl = &TWiki::handleCommonTags( $tmpl, $oldTopic, $oldWeb ); $tmpl = &TWiki::Render::getRenderedVersion( $tmpl ); @@ -671,6 +680,7 @@ $tmpl = &TWiki::handleCommonTags( $tmpl, $oldTopic, $oldWeb ); $tmpl =~ s/( ?) *<\/?(nop|noautolink)\/?>\n?/$1/gois; # remove and tags + $tmpl = $tmplhead . TWiki::Render::sanitizeFinalHTML($tmpl); TWiki::writeHeader( $query, length( $tmpl )); print $tmpl; } @@ -689,14 +699,17 @@ my( $oldWeb, $oldTopic, $newWeb, $newTopic, $skin ) = @_; my $query = TWiki::getCgiQuery(); + my $tmplhead; my $tmpl = TWiki::Templates::readTemplate( "renamerefs", $skin ); + ( $tmplhead, $tmpl ) = TWiki::Templates::splitTemplate( $tmpl, $topic, $webName ); $tmpl = _setVars( $tmpl, $oldTopic, $newWeb, $newTopic ); $tmpl = TWiki::Render::getRenderedVersion( $tmpl ); $tmpl =~ s/%RESEARCH/%SEARCH/go; # Pre search result from being rendered $tmpl = TWiki::handleCommonTags( $tmpl, $oldTopic, $oldWeb ); $tmpl =~ s/( ?) *<\/?(nop|noautolink)\/?>\n?/$1/gois; # remove and tags - TWiki::writeHeader( $query, length( $tmpl )); + $tmpl = $tmplhead. TWiki::Render::sanitizeFinalHTML($tmpl); + TWiki::writeHeader( $query, length( $tmpl )); print $tmpl; } Index: lib/TWiki/UI/Changes.pm =================================================================== --- lib/TWiki/UI/Changes.pm (revision 3312) +++ lib/TWiki/UI/Changes.pm (working copy) @@ -97,6 +97,7 @@ $after =~ s/( ?) *<\/?(nop|noautolink)\/?>\n?/$1/gois; # remove and tags $page .= $after; + $page = TWiki::Render::sanitizeFinalHTML($page); TWiki::writeHeader( $query, length( $page )); print $page; } Index: lib/TWiki/UI/View.pm =================================================================== --- lib/TWiki/UI/View.pm (revision 3312) +++ lib/TWiki/UI/View.pm (working copy) @@ -167,6 +167,9 @@ return; } + my $tmplhead; + ( $tmplhead, $tmpl ) = TWiki::Templates::splitTemplate( $tmpl, $topic, $webName ); + my( $mirrorSiteName, $mirrorViewURL, $mirrorLink, $mirrorNote ) = TWiki::readOnlyMirrorWeb( $webName ); @@ -316,6 +319,7 @@ $contentType = 'text/html' } + $tmpl = $tmplhead . TWiki::Render::sanitizeFinalHTML($tmpl); TWiki::writeHeaderFull( $query, 'basic', $contentType, length( $tmpl )); print $tmpl; } Index: lib/TWiki/UI/RDiff.pm =================================================================== --- lib/TWiki/UI/RDiff.pm (revision 3312) +++ lib/TWiki/UI/RDiff.pm (working copy) @@ -383,6 +383,7 @@ return unless TWiki::UI::webExists( $webName, $topic ); + my $tmplhead; my $tmpl = ""; my $diff = ""; my $maxrev= 1; @@ -396,6 +397,7 @@ my $scriptUrlPath = $TWiki::scriptUrlPath; $tmpl = TWiki::Templates::readTemplate( "rdiff", $skin ); + ( $tmplhead, $tmpl ) = TWiki::Templates::splitTemplate( $tmpl, $topic, $webName ); $tmpl =~ s/\%META{.*?}\%//go; # remove %META{"parent"}% my( $before, $difftmpl, $after) = split( /%REPEAT%/, $tmpl); @@ -550,6 +552,7 @@ $page .= $after; + $page = $tmplhead . TWiki::Render::sanitizeFinalHTML($page); TWiki::writeHeader( $query, length( $page )); print $page; } Index: lib/TWiki/UI/Oops.pm =================================================================== --- lib/TWiki/UI/Oops.pm (revision 3312) +++ lib/TWiki/UI/Oops.pm (working copy) @@ -44,6 +44,7 @@ my $tmplName = $query->param( 'template' ) || "oops"; my $skin = TWiki::getSkin(); + my $tmplHead; my $tmplData = TWiki::Templates::readTemplate( $tmplName, $skin ); if( ! $tmplData ) { $tmplData = "\n" @@ -53,6 +54,7 @@ . "Check the \$templateDir variable in TWiki.cfg.\n" . "\n"; } else { + ( $tmplHead, $tmplData ) = TWiki::Templates::splitTemplate( $tmplData, $topic ); my $param = $query->param( 'param1' ) || ""; $tmplData =~ s/%PARAM1%/$param/go; $param = $query->param( 'param2' ) || ""; @@ -67,6 +69,7 @@ $tmplData =~ s/( ?) *<\/?(nop|noautolink)\/?>\n?/$1/gois; # remove and tags } + $tmplData = $tmplHead . TWiki::Render::sanitizeFinalHTML($tmplData); TWiki::writeHeader( $query, length( $tmplData )); print $tmplData; } Index: lib/TWiki/UI/Preview.pm =================================================================== --- lib/TWiki/UI/Preview.pm (revision 3312) +++ lib/TWiki/UI/Preview.pm (working copy) @@ -34,6 +34,7 @@ return unless TWiki::UI::webExists( $webName, $topic ); + my $tmplhead; my $tmpl = ""; my $text = ""; my $ptext = ""; @@ -55,6 +56,7 @@ # get view template, standard view or a view with a different skin $tmpl = &TWiki::Templates::readTemplate( "preview", $skin ); + ( $tmplhead, $tmpl ) = TWiki::Templates::splitTemplate( $tmpl, $topic, $webName ); $tmpl =~ s/%DONTNOTIFY%/$dontNotify/go; if( $saveCmd ) { return unless TWiki::UI::userIsAdmin( $webName, $topic, $wikiUserName ); @@ -135,6 +137,7 @@ $tmpl =~ s/%FORMFIELDS%/$formFields/go; $tmpl =~ s/( ?) *<\/?(nop|noautolink)\/?>\n?/$1/gois; # remove and tags + $tmpl = $tmplhead . TWiki::Render::sanitizeFinalHTML($tmpl); TWiki::writeHeader( $query, length( $tmpl )); print $tmpl; } Index: lib/TWiki/Form.pm =================================================================== --- lib/TWiki/Form.pm (revision 3312) +++ lib/TWiki/Form.pm (working copy) @@ -552,7 +552,9 @@ { my( $theWeb, $theTopic, $theQuery ) = @_; + my $tmplhead; my $tmpl = TWiki::Templates::readTemplate( "changeform" ); + ( $tmplhead, $tmpl ) = TWiki::Templates::splitTemplate( $tmpl, $theTopic, $theWeb ); $tmpl = TWiki::handleCommonTags( $tmpl, $theTopic ); $tmpl = TWiki::Render::getRenderedVersion( $tmpl ); my $text = $theQuery->param( 'text' ); @@ -587,7 +589,8 @@ $tmpl =~ s/%TOPICPARENT%/$parent/go; $tmpl =~ s|||goi; - + + $tmpl = $tmplhead . TWiki::Render::sanitizeFinalHTML($tmpl); TWiki::writeHeader( $theQuery, length( $tmpl )); print $tmpl; } Index: bin/setlib.cfg =================================================================== --- bin/setlib.cfg (revision 3312) +++ bin/setlib.cfg (working copy) @@ -37,7 +37,7 @@ # Path to lib directory containing TWiki.pm. # ATTENTION: Set to absolute file path: -$twikiLibPath = '../lib'; +$twikiLibPath = '/home/moise/src/twiki-develop/lib'; # Path to local Perl modules (e.g. under home directory for users # without 'root' on Unix/Linux). Uncomment and set if needed: Index: bin/rdiff =================================================================== --- bin/rdiff (revision 3312) +++ bin/rdiff (working copy) @@ -1,4 +1,4 @@ -#!/usr/bin/perl -wT +#!/usr/bin/perl -w # # TWiki Collaboration Platform, http://TWiki.org/ # Index: templates/twiki.tmpl =================================================================== --- templates/twiki.tmpl (revision 3312) +++ templates/twiki.tmpl (working copy) @@ -6,8 +6,7 @@ %TMPL:DEF{"head"}% %TMPL:P{"titleaction"}%%TOPIC% < %WEB% < %WIKITOOLNAME% - %HTTP_EQUIV_ON_VIEW% - + %TMPL:P{"script"}% %TMPL:END% Index: templates/twiki.pattern.tmpl =================================================================== --- templates/twiki.pattern.tmpl (revision 3312) +++ templates/twiki.pattern.tmpl (working copy) @@ -25,9 +25,9 @@ %TMPL:DEF{"twikistyle"}%