| 1 | # See bottom of file for license and copyright information |
|---|
| 2 | package Foswiki; |
|---|
| 3 | |
|---|
| 4 | use strict; |
|---|
| 5 | use warnings; |
|---|
| 6 | |
|---|
| 7 | # applyPatternToIncludedText( $text, $pattern ) -> $text |
|---|
| 8 | # Apply a pattern on included text to extract a subset |
|---|
| 9 | # Package-private; used by IncludeHandlers. |
|---|
| 10 | sub applyPatternToIncludedText { |
|---|
| 11 | my ( $text, $pattern ) = @_; |
|---|
| 12 | |
|---|
| 13 | $pattern = Foswiki::Sandbox::untaint( $pattern, \&validatePattern ); |
|---|
| 14 | |
|---|
| 15 | my $ok = 0; |
|---|
| 16 | eval { |
|---|
| 17 | |
|---|
| 18 | # The eval acts as a try block in case there is anything evil in |
|---|
| 19 | # the pattern. |
|---|
| 20 | |
|---|
| 21 | # The () ensures that $1 is defined if $pattern matches |
|---|
| 22 | # but does not capture anything |
|---|
| 23 | if ( $text =~ m/$pattern()/is ) { |
|---|
| 24 | $text = $1; |
|---|
| 25 | } |
|---|
| 26 | else { |
|---|
| 27 | |
|---|
| 28 | # The pattern did not match, so return nothing |
|---|
| 29 | $text = ''; |
|---|
| 30 | } |
|---|
| 31 | $ok = 1; |
|---|
| 32 | }; |
|---|
| 33 | $text = '' unless $ok; |
|---|
| 34 | |
|---|
| 35 | return $text; |
|---|
| 36 | } |
|---|
| 37 | |
|---|
| 38 | # Replace web references in a topic. Called from forEachLine, applying to |
|---|
| 39 | # each non-verbatim and non-literal line. |
|---|
| 40 | sub _fixupIncludedTopic { |
|---|
| 41 | my ( $text, $options ) = @_; |
|---|
| 42 | |
|---|
| 43 | my $fromWeb = $options->{web}; |
|---|
| 44 | |
|---|
| 45 | unless ( $options->{in_noautolink} ) { |
|---|
| 46 | |
|---|
| 47 | # 'TopicName' to 'Web.TopicName' |
|---|
| 48 | $text =~ |
|---|
| 49 | s#(?:^|(?<=[\s(]))($Foswiki::regex{wikiWordRegex})(?=\s|\)|$)#$fromWeb.$1#go; |
|---|
| 50 | } |
|---|
| 51 | |
|---|
| 52 | # Handle explicit [[]] everywhere |
|---|
| 53 | # '[[TopicName][...]]' to '[[Web.TopicName][...]]' |
|---|
| 54 | $text =~ s/\[\[([^]]+)\](?:\[([^]]+)\])?\]/ |
|---|
| 55 | _fixIncludeLink( $fromWeb, $1, $2 )/geo; |
|---|
| 56 | |
|---|
| 57 | return $text; |
|---|
| 58 | } |
|---|
| 59 | |
|---|
| 60 | # Add a web reference to a [[...][...]] link in an included topic |
|---|
| 61 | sub _fixIncludeLink { |
|---|
| 62 | my ( $web, $link, $label ) = @_; |
|---|
| 63 | |
|---|
| 64 | # Detect absolute and relative URLs and web-qualified wikinames |
|---|
| 65 | if ( $link =~ |
|---|
| 66 | m#^($Foswiki::regex{webNameRegex}\.|$Foswiki::regex{defaultWebNameRegex}\.|$Foswiki::regex{linkProtocolPattern}:|/)#o |
|---|
| 67 | ) |
|---|
| 68 | { |
|---|
| 69 | if ($label) { |
|---|
| 70 | return "[[$link][$label]]"; |
|---|
| 71 | } |
|---|
| 72 | else { |
|---|
| 73 | return "[[$link]]"; |
|---|
| 74 | } |
|---|
| 75 | } |
|---|
| 76 | elsif ( !$label ) { |
|---|
| 77 | |
|---|
| 78 | # Must be wikiword or spaced-out wikiword (or illegal link :-/) |
|---|
| 79 | $label = $link; |
|---|
| 80 | } |
|---|
| 81 | |
|---|
| 82 | # If link is only an anchor, leave it as is (Foswikitask:Item771) |
|---|
| 83 | return "[[$link][$label]]" if $link =~ /^#/; |
|---|
| 84 | return "[[$web.$link][$label]]"; |
|---|
| 85 | } |
|---|
| 86 | |
|---|
| 87 | # generate an include warning |
|---|
| 88 | # SMELL: varying number of parameters idiotic to handle for customized $warn |
|---|
| 89 | sub _includeWarning { |
|---|
| 90 | my $this = shift; |
|---|
| 91 | my $warn = shift; |
|---|
| 92 | my $message = shift; |
|---|
| 93 | |
|---|
| 94 | if ( $warn eq 'on' ) { |
|---|
| 95 | return $this->inlineAlert( 'alerts', $message, @_ ); |
|---|
| 96 | } |
|---|
| 97 | elsif ( isTrue($warn) ) { |
|---|
| 98 | |
|---|
| 99 | # different inlineAlerts need different argument counts |
|---|
| 100 | my $argument = ''; |
|---|
| 101 | if ( $message eq 'topic_not_found' ) { |
|---|
| 102 | my ( $web, $topic ) = @_; |
|---|
| 103 | $argument = "$web.$topic"; |
|---|
| 104 | } |
|---|
| 105 | else { |
|---|
| 106 | $argument = shift; |
|---|
| 107 | } |
|---|
| 108 | $warn =~ s/\$topic/$argument/go if $argument; |
|---|
| 109 | return $warn; |
|---|
| 110 | } # else fail silently |
|---|
| 111 | return ''; |
|---|
| 112 | } |
|---|
| 113 | |
|---|
| 114 | sub _includeProtocol { |
|---|
| 115 | my ( $this, $handler, $control, $params ) = @_; |
|---|
| 116 | |
|---|
| 117 | eval 'use Foswiki::IncludeHandlers::' . $handler . ' ()'; |
|---|
| 118 | if ($@) { |
|---|
| 119 | return $this->_includeWarning( $control->{warn}, 'bad_include_path', |
|---|
| 120 | $control->{_DEFAULT} ); |
|---|
| 121 | } |
|---|
| 122 | else { |
|---|
| 123 | $handler = 'Foswiki::IncludeHandlers::' . $handler; |
|---|
| 124 | return $handler->INCLUDE( $this, $control, $params ); |
|---|
| 125 | } |
|---|
| 126 | } |
|---|
| 127 | |
|---|
| 128 | sub _includeTopic { |
|---|
| 129 | my ( $this, $includingTopicObject, $control, $params ) = @_; |
|---|
| 130 | |
|---|
| 131 | my $includedWeb; |
|---|
| 132 | my $includedTopic = $control->{_DEFAULT}; |
|---|
| 133 | $includedTopic =~ s/\.txt$//; # strip optional (undocumented) .txt |
|---|
| 134 | |
|---|
| 135 | ( $includedWeb, $includedTopic ) = |
|---|
| 136 | $this->normalizeWebTopicName( $includingTopicObject->web, |
|---|
| 137 | $includedTopic ); |
|---|
| 138 | |
|---|
| 139 | if ( !Foswiki::isValidTopicName( $includedTopic, 1 ) ) { |
|---|
| 140 | return $this->_includeWarning( $control->{warn}, 'bad_include_path', |
|---|
| 141 | $control->{_DEFAULT} ), 'bad_include_path'; |
|---|
| 142 | } |
|---|
| 143 | |
|---|
| 144 | # See Codev.FailedIncludeWarning for the history. |
|---|
| 145 | unless ( $this->{store}->topicExists( $includedWeb, $includedTopic ) ) { |
|---|
| 146 | return _includeWarning( $this, $control->{warn}, 'topic_not_found', |
|---|
| 147 | $includedWeb, $includedTopic ), 'topic_not_found'; |
|---|
| 148 | } |
|---|
| 149 | |
|---|
| 150 | # prevent recursive includes. Note that the inclusion of a topic into |
|---|
| 151 | # itself is not blocked; however subsequent attempts to include the |
|---|
| 152 | # topic will fail. There is a hard block of 99 on any recursive include. |
|---|
| 153 | my $key = $includingTopicObject->web . '.' . $includingTopicObject->topic; |
|---|
| 154 | my $count = grep( $key, keys %{ $this->{_INCLUDES} } ); |
|---|
| 155 | $key .= $control->{_sArgs}; |
|---|
| 156 | if ( $this->{_INCLUDES}->{$key} || $count > 99 ) { |
|---|
| 157 | return _includeWarning( $this, $control->{warn}, 'already_included', |
|---|
| 158 | "$includedWeb.$includedTopic", '' ), 'already_included'; |
|---|
| 159 | } |
|---|
| 160 | |
|---|
| 161 | # Push the topic context to the included topic, so we can create |
|---|
| 162 | # local (SESSION) macro definitions without polluting the including |
|---|
| 163 | # topic namespace. |
|---|
| 164 | $this->{prefs}->pushTopicContext( $this->{webName}, $this->{topicName} ); |
|---|
| 165 | |
|---|
| 166 | $this->{_INCLUDES}->{$key} = 1; |
|---|
| 167 | |
|---|
| 168 | my $includedTopicObject = |
|---|
| 169 | Foswiki::Meta->load( $this, $includedWeb, $includedTopic, |
|---|
| 170 | $control->{rev} ); |
|---|
| 171 | unless ( $includedTopicObject->haveAccess('VIEW') ) { |
|---|
| 172 | if ( isTrue( $control->{warn} ) ) { |
|---|
| 173 | return $this->inlineAlert( 'alerts', 'access_denied', |
|---|
| 174 | "[[$includedWeb.$includedTopic]]" ), 'access_denied'; |
|---|
| 175 | } # else fail silently |
|---|
| 176 | return '', 'access_denied'; |
|---|
| 177 | } |
|---|
| 178 | my $memWeb = $this->{prefs}->getPreference('INCLUDINGWEB'); |
|---|
| 179 | my $memTopic = $this->{prefs}->getPreference('INCLUDINGTOPIC'); |
|---|
| 180 | |
|---|
| 181 | my $text = ''; |
|---|
| 182 | my $error = ''; |
|---|
| 183 | my $verbatim = {}; |
|---|
| 184 | my $dirtyAreas = {}; |
|---|
| 185 | try { |
|---|
| 186 | |
|---|
| 187 | # Copy params into session level preferences. That way finalisation |
|---|
| 188 | # will apply to them. These preferences will be popped when the topic |
|---|
| 189 | # context is restored after the include. |
|---|
| 190 | $this->{prefs}->setSessionPreferences(%$params); |
|---|
| 191 | |
|---|
| 192 | # Set preferences that finalisation does *not* apply to |
|---|
| 193 | $this->{prefs}->setInternalPreferences( |
|---|
| 194 | INCLUDINGWEB => $includingTopicObject->web, |
|---|
| 195 | INCLUDINGTOPIC => $includingTopicObject->topic |
|---|
| 196 | ); |
|---|
| 197 | |
|---|
| 198 | $text = $includedTopicObject->text; |
|---|
| 199 | |
|---|
| 200 | # Simplify leading, and remove trailing, newlines. If we don't remove |
|---|
| 201 | # trailing, it becomes impossible to %INCLUDE a topic into a table. |
|---|
| 202 | $text =~ s/^[\r\n]+/\n/; |
|---|
| 203 | $text =~ s/[\r\n]+$//; |
|---|
| 204 | |
|---|
| 205 | # remove everything before and after the default include block unless |
|---|
| 206 | # a section is explicitly defined |
|---|
| 207 | if ( !$control->{section} ) { |
|---|
| 208 | $text =~ s/.*?%STARTINCLUDE%//s; |
|---|
| 209 | $text =~ s/%STOPINCLUDE%.*//s; |
|---|
| 210 | } |
|---|
| 211 | |
|---|
| 212 | # prevent dirty areas in included topics from being parsed |
|---|
| 213 | $text = takeOutBlocks( $text, 'dirtyarea', $dirtyAreas ) |
|---|
| 214 | if $Foswiki::cfg{Cache}{Enabled}; |
|---|
| 215 | |
|---|
| 216 | # handle sections |
|---|
| 217 | my ( $ntext, $sections ) = parseSections($text); |
|---|
| 218 | |
|---|
| 219 | my $interesting = ( defined $control->{section} ); |
|---|
| 220 | if ( $interesting || scalar(@$sections) ) { |
|---|
| 221 | |
|---|
| 222 | # Rebuild the text from the interesting sections |
|---|
| 223 | $text = ''; |
|---|
| 224 | foreach my $s (@$sections) { |
|---|
| 225 | if ( $control->{section} |
|---|
| 226 | && $s->{type} eq 'section' |
|---|
| 227 | && $s->{name} eq $control->{section} ) |
|---|
| 228 | { |
|---|
| 229 | $text .= |
|---|
| 230 | substr( $ntext, $s->{start}, $s->{end} - $s->{start} ); |
|---|
| 231 | $interesting = 1; |
|---|
| 232 | last; |
|---|
| 233 | } |
|---|
| 234 | elsif ( $s->{type} eq 'include' && !$control->{section} ) { |
|---|
| 235 | $text .= |
|---|
| 236 | substr( $ntext, $s->{start}, $s->{end} - $s->{start} ); |
|---|
| 237 | $interesting = 1; |
|---|
| 238 | } |
|---|
| 239 | } |
|---|
| 240 | } |
|---|
| 241 | |
|---|
| 242 | if ( $interesting and ( length($text) eq 0 ) ) { |
|---|
| 243 | $text = |
|---|
| 244 | _includeWarning( $this, $control->{warn}, |
|---|
| 245 | 'topic_section_not_found', $includedWeb, $includedTopic, |
|---|
| 246 | $control->{section} ); |
|---|
| 247 | $error = 'topic_section_not_found'; |
|---|
| 248 | } |
|---|
| 249 | else { |
|---|
| 250 | |
|---|
| 251 | # If there were no interesting sections, restore the whole text |
|---|
| 252 | $text = $ntext unless $interesting; |
|---|
| 253 | |
|---|
| 254 | $text = applyPatternToIncludedText( $text, $control->{pattern} ) |
|---|
| 255 | if ( $control->{pattern} ); |
|---|
| 256 | |
|---|
| 257 | # Do not show TOC in included topic if TOC_HIDE_IF_INCLUDED |
|---|
| 258 | # preference has been set |
|---|
| 259 | if ( isTrue( $this->{prefs}->getPreference('TOC_HIDE_IF_INCLUDED') ) |
|---|
| 260 | ) |
|---|
| 261 | { |
|---|
| 262 | $text =~ s/%TOC(?:{(.*?)})?%//g; |
|---|
| 263 | } |
|---|
| 264 | |
|---|
| 265 | $this->innerExpandMacros( \$text, $includedTopicObject ); |
|---|
| 266 | |
|---|
| 267 | # Item9569: remove verbatim blocks from text passed to commonTagsHandler |
|---|
| 268 | $text = takeOutBlocks( $text, 'verbatim', $verbatim ); |
|---|
| 269 | |
|---|
| 270 | # 4th parameter tells plugin that its called for an included file |
|---|
| 271 | $this->{plugins} |
|---|
| 272 | ->dispatch( 'commonTagsHandler', $text, $includedTopic, |
|---|
| 273 | $includedWeb, 1, $includedTopicObject ); |
|---|
| 274 | putBackBlocks( \$text, $verbatim, 'verbatim' ); |
|---|
| 275 | |
|---|
| 276 | # We have to expand tags again, because a plugin may have inserted |
|---|
| 277 | # additional tags. |
|---|
| 278 | $this->innerExpandMacros( \$text, $includedTopicObject ); |
|---|
| 279 | |
|---|
| 280 | # If needed, fix all 'TopicNames' to 'Web.TopicNames' to get the |
|---|
| 281 | # right context so that links continue to work properly |
|---|
| 282 | if ( $includedWeb ne $includingTopicObject->web ) { |
|---|
| 283 | my $removed = {}; |
|---|
| 284 | |
|---|
| 285 | $text = $this->renderer->forEachLine( |
|---|
| 286 | $text, |
|---|
| 287 | \&_fixupIncludedTopic, |
|---|
| 288 | { |
|---|
| 289 | web => $includedWeb, |
|---|
| 290 | pre => 1, |
|---|
| 291 | noautolink => 1 |
|---|
| 292 | } |
|---|
| 293 | ); |
|---|
| 294 | |
|---|
| 295 | # handle tags again because of plugin hook |
|---|
| 296 | innerExpandMacros( $this, \$text, $includedTopicObject ); |
|---|
| 297 | } |
|---|
| 298 | } |
|---|
| 299 | } |
|---|
| 300 | finally { |
|---|
| 301 | |
|---|
| 302 | # always restore the context, even in the event of an error |
|---|
| 303 | delete $this->{_INCLUDES}->{$key}; |
|---|
| 304 | |
|---|
| 305 | $this->{prefs}->setInternalPreferences( |
|---|
| 306 | INCLUDINGWEB => $memWeb, |
|---|
| 307 | INCLUDINGTOPIC => $memTopic |
|---|
| 308 | ); |
|---|
| 309 | |
|---|
| 310 | # restoring dirty areas |
|---|
| 311 | putBackBlocks( \$text, $dirtyAreas, 'dirtyarea' ) |
|---|
| 312 | if $Foswiki::cfg{Cache}{Enabled}; |
|---|
| 313 | |
|---|
| 314 | ( $this->{webName}, $this->{topicName} ) = |
|---|
| 315 | $this->{prefs}->popTopicContext(); |
|---|
| 316 | }; |
|---|
| 317 | |
|---|
| 318 | return ($text, $error); |
|---|
| 319 | } |
|---|
| 320 | |
|---|
| 321 | # Processes a specific instance %<nop>INCLUDE{...}% syntax. |
|---|
| 322 | # Returns the text to be inserted in place of the INCLUDE command. |
|---|
| 323 | # $includingTopicObject should be for the immediate parent topic in the |
|---|
| 324 | # include hierarchy. Works for both URLs and absolute server paths. |
|---|
| 325 | sub INCLUDE { |
|---|
| 326 | my ( $this, $params, $includingTopicObject ) = @_; |
|---|
| 327 | |
|---|
| 328 | # remember args for the key before mangling the params |
|---|
| 329 | my $args = $params->stringify(); |
|---|
| 330 | |
|---|
| 331 | # Remove params, so they don't get expanded in the included page |
|---|
| 332 | my %control; |
|---|
| 333 | for my $p (qw(_DEFAULT pattern rev section raw warn)) { |
|---|
| 334 | $control{$p} = $params->remove($p); |
|---|
| 335 | } |
|---|
| 336 | $control{_sArgs} = $args; |
|---|
| 337 | |
|---|
| 338 | $control{warn} ||= $this->{prefs}->getPreference('INCLUDEWARNING'); |
|---|
| 339 | |
|---|
| 340 | my $text; |
|---|
| 341 | |
|---|
| 342 | # make sure we have something to include. If we don't do this, then |
|---|
| 343 | # normalizeWebTopicName will default to WebHome. TWikibug:Item2209. |
|---|
| 344 | unless ( $control{_DEFAULT} ) { |
|---|
| 345 | $text = |
|---|
| 346 | $this->_includeWarning( $control{warn}, 'bad_include_path', '' ); |
|---|
| 347 | } |
|---|
| 348 | |
|---|
| 349 | # Filter out '..' from path to prevent includes of '../../file' |
|---|
| 350 | elsif ( $Foswiki::cfg{DenyDotDotInclude} && $control{_DEFAULT} =~ /\.\./ ) { |
|---|
| 351 | $text = |
|---|
| 352 | $this->_includeWarning( $control{warn}, 'bad_include_path', |
|---|
| 353 | $control{_DEFAULT} ); |
|---|
| 354 | } |
|---|
| 355 | |
|---|
| 356 | else { |
|---|
| 357 | |
|---|
| 358 | # no sense in considering an empty string as an unfindable section |
|---|
| 359 | delete $control{section} |
|---|
| 360 | if ( defined( $control{section} ) && $control{section} eq '' ); |
|---|
| 361 | $control{raw} ||= ''; |
|---|
| 362 | $control{inWeb} = $includingTopicObject->web; |
|---|
| 363 | $control{inTopic} = $includingTopicObject->topic; |
|---|
| 364 | |
|---|
| 365 | # Protocol links e.g. http:, https:, doc: |
|---|
| 366 | if ( $control{_DEFAULT} =~ /^([a-z]+):/ ) { |
|---|
| 367 | $text = $this->_includeProtocol( $1, \%control, $params ); |
|---|
| 368 | } |
|---|
| 369 | else { |
|---|
| 370 | my @topics = split(/,\s*/, $control{_DEFAULT}); |
|---|
| 371 | my $error; |
|---|
| 372 | foreach my $t (@topics) { |
|---|
| 373 | $control{_DEFAULT} = $t; |
|---|
| 374 | ($text, $error) = |
|---|
| 375 | $this->_includeTopic( $includingTopicObject, \%control, $params ); |
|---|
| 376 | last if ($error eq ''); |
|---|
| 377 | } |
|---|
| 378 | } |
|---|
| 379 | } |
|---|
| 380 | |
|---|
| 381 | # Apply any heading offset |
|---|
| 382 | my $hoff = $params->{headingoffset}; |
|---|
| 383 | if ( $hoff && $hoff =~ /^([-+]?\d+)$/ && $1 != 0 ) { |
|---|
| 384 | my ( $off, $noff ) = ( 0 + $1, 0 - $1 ); |
|---|
| 385 | $text = "<ho off=\"$off\"/>\n$text\n<ho off=\"$noff\">"; |
|---|
| 386 | } |
|---|
| 387 | |
|---|
| 388 | return $text; |
|---|
| 389 | } |
|---|
| 390 | |
|---|
| 391 | 1; |
|---|
| 392 | __END__ |
|---|
| 393 | Foswiki - The Free and Open Source Wiki, http://foswiki.org/ |
|---|
| 394 | |
|---|
| 395 | Copyright (C) 2008-2009 Foswiki Contributors. Foswiki Contributors |
|---|
| 396 | are listed in the AUTHORS file in the root of this distribution. |
|---|
| 397 | NOTE: Please extend that file, not this notice. |
|---|
| 398 | |
|---|
| 399 | Additional copyrights apply to some or all of the code in this |
|---|
| 400 | file as follows: |
|---|
| 401 | |
|---|
| 402 | Copyright (C) 1999-2007 Peter Thoeny, peter@thoeny.org |
|---|
| 403 | and TWiki Contributors. All Rights Reserved. TWiki Contributors |
|---|
| 404 | are listed in the AUTHORS file in the root of this distribution. |
|---|
| 405 | Based on parts of Ward Cunninghams original Wiki and JosWiki. |
|---|
| 406 | Copyright (C) 1998 Markus Peter - SPiN GmbH (warpi@spin.de) |
|---|
| 407 | Some changes by Dave Harris (drh@bhresearch.co.uk) incorporated |
|---|
| 408 | |
|---|
| 409 | This program is free software; you can redistribute it and/or |
|---|
| 410 | modify it under the terms of the GNU General Public License |
|---|
| 411 | as published by the Free Software Foundation; either version 2 |
|---|
| 412 | of the License, or (at your option) any later version. For |
|---|
| 413 | more details read LICENSE in the root of this distribution. |
|---|
| 414 | |
|---|
| 415 | This program is distributed in the hope that it will be useful, |
|---|
| 416 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 417 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
|---|
| 418 | |
|---|
| 419 | As per the GPL, removal of this notice is prohibited. |
|---|