diff --git a/core/lib/Foswiki/Access.pm b/core/lib/Foswiki/Access.pm
index 7a31eb0af3..5058e08bcd 100644
--- a/core/lib/Foswiki/Access.pm
+++ b/core/lib/Foswiki/Access.pm
@@ -52,6 +52,7 @@ sub create {
print STDERR "using $imp Access Control\n" if MONITOR;
+ # SMELL Foswiki::local_class() would be preferable instead of eval.
my $ok = eval("require $imp; 1;");
ASSERT( $ok, $@ ) if DEBUG;
my $this = $imp->new( session => $session, _indirect => 1, );
diff --git a/core/lib/Foswiki/App.pm b/core/lib/Foswiki/App.pm
index 4789bd28a3..3ce904f9a6 100644
--- a/core/lib/Foswiki/App.pm
+++ b/core/lib/Foswiki/App.pm
@@ -3,9 +3,11 @@
package Foswiki::App;
use v5.14;
+use constant TRACE_REQUEST => 0;
+
use Cwd;
use Try::Tiny;
-use Foswiki::Config;
+use Foswiki::Config ();
use Moo;
use namespace::clean;
@@ -44,6 +46,14 @@ has request => (
isa =>
Foswiki::Object::isaCLASS( 'request', 'Foswiki::Request', noUndef => 1, ),
);
+has response => (
+ is => 'rw',
+ lazy => 1,
+ default => sub { new Foswiki::Response },
+ isa => Foswiki::Object::isaCLASS(
+ 'response', 'Foswiki::Response', noUndef => 1,
+ ),
+);
has macros => (
is => 'rw',
lazy => 1,
@@ -64,6 +74,14 @@ has ui => (
return $_[0]->create('Foswiki::UI');
},
);
+has _dispatcherObject => (
+ is => 'rw',
+ isa => Foswiki::Object::isaCLASS(
+ '_dispatcherObject', 'Foswiki::Object', noUndef => 1
+ ),
+);
+has _dispatcherMethod => ( is => 'rw', );
+has _dispatcherContext => ( is => 'rw', );
# App-local $Foswiki::system_message.
has system_message => ( is => 'rw', );
@@ -93,6 +111,31 @@ sub BUILD {
$Foswiki::app = $this;
+ my $cfg = $this->cfg;
+ if ( $cfg->data->{Store}{overrideUmask} && $cfg->data->{OS} ne 'WINDOWS' ) {
+
+# Note: The addition of zero is required to force dirPermission and filePermission
+# to be numeric. Without the additition, certain values of the permissions cause
+# runtime errors about illegal characters in subtraction. "and" with 777 to prevent
+# sticky-bits from breaking the umask.
+ my $oldUmask = umask(
+ (
+ oct(777) - (
+ (
+ $cfg->data->{Store}{dirPermission} + 0 |
+ $cfg->data->{Store}{filePermission} + 0
+ )
+ ) & oct(777)
+ )
+ );
+
+#my $umask = sprintf('%04o', umask() );
+#$oldUmask = sprintf('%04o', $oldUmask );
+#my $dirPerm = sprintf('%04o', $Foswiki::cfg{Store}{dirPermission}+0 );
+#my $filePerm = sprintf('%04o', $Foswiki::cfg{Store}{filePermission}+0 );
+#print STDERR " ENGINE changes $oldUmask to $umask from $dirPerm and $filePerm \n";
+ }
+
unless ( defined $this->engine ) {
Foswiki::Exception::Fatal->throw( text => "Cannot initialize engine" );
}
@@ -159,8 +202,100 @@ sub run {
sub handleRequest {
my $this = shift;
- my $res = Foswiki::UI::handleRequest( $this->request );
- $this->engine->finalize( $res, $this->request );
+ my $req = $this->request;
+
+ try {
+ $this->_prepareDispatcher;
+ $this->_checkTickle;
+
+ # Get the params cache from the path
+ my $cache = $req->param('foswiki_redirect_cache');
+ if ( defined $cache ) {
+ $req->delete('foswiki_redirect_cache');
+ }
+
+ # If the path specifies a cache path, use that. It's arbitrary
+ # as to which takes precedence (param or path) because we should
+ # never have both at once.
+ my $path_info = $req->pathInfo;
+ if ( $path_info =~ s#/foswiki_redirect_cache/([a-f0-9]{32})## ) {
+ $cache = $1;
+ $req->pathInfo($path_info);
+ }
+
+ if ( defined $cache && $cache =~ m/^([a-f0-9]{32})$/ ) {
+
+ # implicit untaint required, because $cache may be used in a filename.
+ # Note that the cache serialises the method and path_info, which
+ # will be restored.
+ Foswiki::Request::Cache->new->load( $1, $req );
+ }
+
+ if (TRACE_REQUEST) {
+ print STDERR "INCOMING "
+ . $req->method() . " "
+ . $req->url . " -> "
+ . $sub . "\n";
+ print STDERR "validation_key: "
+ . ( $req->param('validation_key') || 'no key' ) . "\n";
+
+ #require Data::Dumper;
+ #print STDERR Data::Dumper->Dump([$req]);
+ }
+
+ # XXX TODO vrurg – Continue from here...
+ if ( UNIVERSAL::isa( $Foswiki::engine, 'Foswiki::Engine::CLI' ) ) {
+ $this->_dispatcherContext->{command_line} = 1;
+ }
+ elsif (
+ defined $req->method
+ && (
+ (
+ defined $dispatcher->{allow}
+ && !$dispatcher->{allow}->{ uc( $req->method() ) }
+ )
+ || ( defined $dispatcher->{deny}
+ && $dispatcher->{deny}->{ uc( $req->method() ) } )
+ )
+ )
+ {
+ $res = new Foswiki::Response();
+ $res->header( -type => 'text/html', -status => '405' );
+ $res->print( '
Bad Request:
The request method: '
+ . uc( $req->method() )
+ . ' is denied for the '
+ . $req->action()
+ . ' action.' );
+ if ( uc( $req->method() ) eq 'GET' ) {
+ $res->print( '
'
+ . 'The '
+ . $req->action()
+ . ' script can only be called with the POST type method'
+ . '
'
+ . 'For example:
'
+ . ' <form method="post" action="%SCRIPTURL{'
+ . $req->action()
+ . '}%/%WEB%/%TOPIC%">
'
+ . '
See System.CommandAndCGIScripts for more information.'
+ );
+ }
+ return $res;
+ }
+ $res = $this->_execute( \&$sub, %{ $dispatcher->{context} } );
+ return $res;
+
+ #my $res = Foswiki::UI::handleRequest( $this->request );
+ }
+ catch {
+ my $e = $_;
+ }
+ finally {
+ # Whatever happens at this stage we shall be able to reply with a valid
+ # HTTP response using valid HTML.
+ $this->engine->finalize( $res, $this->request );
+ };
}
=begin TML
@@ -176,6 +311,8 @@ sub create {
my $this = shift;
my $class = shift;
+ #Foswiki::load_class($class);
+
unless ( $class->isa('Foswiki::AppObject') ) {
Foswiki::Exception::Fatal->throw(
text => "Class $class is not a Foswiki::AppObject descendant." );
@@ -255,9 +392,16 @@ sub _prepareEngine {
return $engine;
}
+# The request attribute default method.
sub _prepareRequest {
my $this = shift;
my $request = $this->engine->prepare;
+
+ # The following is preferable form of Request creation. The request
+ # constructor will then initialize itself using $app->engine as the source
+ # of information about the environment we're running under.
+
+ # my $request = Foswiki::Request->prepare(app => $this);
return $request;
}
@@ -267,6 +411,68 @@ sub _readConfig {
return $cfg;
}
+# Determines what dispatcher to use for the action requested.
+sub _prepareDispatcher {
+ my $this = shift;
+ my $req = $this->request;
+
+ my $dispatcher = $app->cfg->data->{SwitchBoard}{ $req->action };
+ unless ( defined $dispatcher ) {
+ $res = $this->response;
+ $res->header( -type => 'text/html', -status => '404' );
+ my $html = CGI::start_html('404 Not Found');
+ $html .= CGI::h1( {}, 'Not Found' );
+ $html .= CGI::p( {},
+ "The requested URL "
+ . $req->uri
+ . " was not found on this server." );
+ $html .= CGI::end_html();
+ $res->print($html);
+ Foswiki::Exception::HTTPResponse->throw( status => 404, );
+ }
+
+ # SMELL Shouldn't it be deprecated?
+ if ( ref($dispatcher) eq 'ARRAY' ) {
+
+ # Old-style array entry in switchboard from a plugin
+ my @array = @$dispatcher;
+ $dispatcher = {
+ package => $array[0],
+ function => $array[1],
+ context => $array[2],
+ };
+ }
+
+ $dispatcher->{package} //= 'Foswiki::UI';
+ $this->_dispatcherObject( $this->create( $dispatcher->{package} ) );
+ $this->_dispatcherMethod( $dispatcher->{method}
+ || $dispatcher->{function} );
+ $this->_dispatcherContext( $dispatcher->{context} );
+}
+
+# If the X-Foswiki-Tickle header is present, this request is an attempt to
+# verify that the requested function is available on this Foswiki. Respond with
+# the serialised dispatcher, and finish the request. Need to stringify since
+# VERSION is a version object.
+sub _checkTickle {
+ my $this = shift;
+ my $req = $this->request;
+
+ if ( $req->header('X-Foswiki-Tickle') ) {
+ my $res = $this->response;
+ my $data = {
+ SCRIPT_NAME => $ENV{SCRIPT_NAME},
+ VERSION => $Foswiki::VERSION->stringify(),
+ RELEASE => $Foswiki::RELEASE,
+ };
+ $res->header( -type => 'application/json', -status => '200' );
+
+ my $d = JSON->new->allow_nonref->encode($data);
+ $res->print($d);
+ Foswiki::Exception::HTTPResponse->throw;
+ }
+}
+
1;
__END__
Foswiki - The Free and Open Source Wiki, http://foswiki.org/
diff --git a/core/lib/Foswiki/Engine.pm b/core/lib/Foswiki/Engine.pm
index 6b5f6c22f4..219043e4ff 100644
--- a/core/lib/Foswiki/Engine.pm
+++ b/core/lib/Foswiki/Engine.pm
@@ -24,6 +24,16 @@ use Moo;
use namespace::clean;
extends qw(Foswiki::AppObject);
+has env => (
+ is => 'rw',
+ isa => Foswiki::Object::isaHASH( 'env', noUndef => 1, ),
+ default => sub { $_[0]->app->env },
+);
+
+# pathData attribute is a hash with the following keys: action, path_info, uri
+# uri key can be undef under certain circumstances.
+has pathData => ( is => 'rw', lazy => 1, default => \&_preparePath, );
+
BEGIN {
if ( $Foswiki::cfg{UseLocale} ) {
require locale;
@@ -52,7 +62,7 @@ sub start {
}
elsif ( $params{env}{'psgi.version'} ) {
- # We don't have PSGI support yet.
+ # SMELL TODO We don't have PSGI support yet.
$engine = 'Foswiki::Engine::PSGI';
}
else {
@@ -77,24 +87,24 @@ Constructs an engine object.
=begin TML
----++ ObjectMethod run()
+---++ Obsolete ObjectMethod run()
Start point to Runtime Engines.
=cut
-sub run {
- my $this = shift;
- my $req = $this->prepare();
- if ( ref($req) ) {
- my $res = Foswiki::UI::handleRequest($req);
- $this->finalize( $res, $req );
- }
-}
+#sub run {
+# my $this = shift;
+# my $req = $this->prepare();
+# if ( ref($req) ) {
+# my $res = Foswiki::UI::handleRequest($req);
+# $this->finalize( $res, $req );
+# }
+#}
=begin TML
----++ ObjectMethod prepare() -> $req
+---++ Obsolete ObjectMethod prepare() -> $req
Initialize a Foswiki::Request object by calling many preparation methods
and returns it, or a status code in case of error.
@@ -142,12 +152,16 @@ sub prepare {
$this->prepareUploads($req);
}
catch {
+ # SMELL returns within Try::Tiny try/catch block doesn't return from the
+ # calling sub but from the try/catch block itself.
my $e = $_;
unless ( ref($e) ) {
Foswiki::Exception::Fatal->rethrow($e);
}
- if ( $e->isa('Foswiki::EngineException') ) {
+ if ( $e->isa('Foswiki::EngineException')
+ || $e->isa('Foswiki::Exception::Engine') )
+ {
my $res = $e->response;
unless ( defined $res ) {
$res = Foswiki::Response->new;
@@ -260,16 +274,16 @@ sub prepareHeaders { }
=begin TML
----++ ObjectMethod preparePath( $req )
+---++ Private ObjectMethod _preparePath( )
Abstract method, must be defined by inherited classes.
- * =$req= - Foswiki::Request object to populate
-Should fill $req's uri and pathInfo fields.
+Should return a hashref used to initialize the pathData attribute. In other
+words, the hashref must containt keys valid for the attribute (see its comment).
=cut
-sub preparePath { }
+sub _preparePath { }
=begin TML
@@ -376,7 +390,7 @@ sub finalizeUploads { }
---++ ObjectMethod finalizeError( $res, $req )
-Called if some engine especific error happens.
+Called if some engine specific error happens.
* =$res= - Foswiki::Response object to get data from
* =$req= - Foswiki::Request object to get data from
diff --git a/core/lib/Foswiki/Engine/CGI.pm b/core/lib/Foswiki/Engine/CGI.pm
index 95f3474094..da41d92fbe 100644
--- a/core/lib/Foswiki/Engine/CGI.pm
+++ b/core/lib/Foswiki/Engine/CGI.pm
@@ -143,40 +143,40 @@ around prepareConnection => sub {
my $orig = shift;
my ( $this, $req ) = @_;
- $req->remoteAddress( $ENV{REMOTE_ADDR} );
- $req->method( $ENV{REQUEST_METHOD} );
+ $req->remoteAddress( $this->env->{REMOTE_ADDR} );
+ $req->method( $this->env->{REQUEST_METHOD} );
- if ( $ENV{HTTPS} && uc( $ENV{HTTPS} ) eq 'ON' ) {
+ if ( $this->env->{HTTPS} && uc( $this->env->{HTTPS} ) eq 'ON' ) {
$req->secure(1);
}
- if ( $ENV{SERVER_PORT} && $ENV{SERVER_PORT} == 443 ) {
+ if ( $this->env->{SERVER_PORT} && $this->env->{SERVER_PORT} == 443 ) {
$req->secure(1);
}
- $req->serverPort( $ENV{SERVER_PORT} );
+ $req->serverPort( $this->env->{SERVER_PORT} );
};
around prepareQueryParameters => sub {
my $orig = shift;
my ( $this, $req ) = @_;
- $orig->( $this, $req, $ENV{QUERY_STRING} )
- if $ENV{QUERY_STRING};
+ $orig->( $this, $req, $this->env->{QUERY_STRING} )
+ if $this->env->{QUERY_STRING};
};
around prepareHeaders => sub {
my $orig = shift;
my ( $this, $req ) = @_;
- foreach my $header ( keys %ENV ) {
+ foreach my $header ( keys %{ $this->env } ) {
next unless $header =~ m/^(?:HTTP|CONTENT|COOKIE)/i;
( my $field = $header ) =~ s/^HTTPS?_//;
- $req->header( $field => $ENV{$header} );
+ $req->header( $field => $this->env->{$header} );
}
- $req->remoteUser( $ENV{REMOTE_USER} );
+ $req->remoteUser( $this->env->{REMOTE_USER} );
};
-around preparePath => sub {
+around _preparePath => sub {
my $orig = shift;
- my ( $this, $req ) = @_;
+ my ($this) = @_;
# SMELL: "The Microsoft Internet Information Server is broken with
# respect to additional path information. If you use the Perl DLL
@@ -189,13 +189,13 @@ around preparePath => sub {
# Clean up PATH_INFO problems, e.g. Support.CobaltRaqInstall. A valid
# PATH_INFO is '/Main/WebHome', i.e. the text after the script name;
# invalid PATH_INFO is often a full path starting with '/cgi-bin/...'.
- my $pathInfo = $ENV{PATH_INFO} || '';
+ my $pathInfo = $this->env->{PATH_INFO} || '';
if ( $pathInfo =~ m/['"]/g ) {
$pathInfo = substr( $pathInfo, 0, ( ( pos $pathInfo ) - 1 ) );
}
- unless ( defined $ENV{SCRIPT_NAME} ) {
+ unless ( defined $this->env->{SCRIPT_NAME} ) {
# CGI/1.1 (rfc3875) states that the server MUST set
# SCRIPT_NAME, so if it doens't we have a broken server
@@ -213,16 +213,16 @@ around preparePath => sub {
response => $res
);
}
- my $cgiScriptPath = $ENV{SCRIPT_NAME};
+ my $cgiScriptPath = $this->env->{SCRIPT_NAME};
$pathInfo =~ s{^$cgiScriptPath(?:/+|$)}{/};
my $cgiScriptName = $cgiScriptPath;
$cgiScriptName =~ s/.*?(\w+)(\.\w+)?$/$1/;
my $action;
- if ( exists $ENV{FOSWIKI_ACTION} ) {
+ if ( exists $this->env->{FOSWIKI_ACTION} ) {
# This handles scripts that have set $FOSWIKI_ACTION
- $action = $ENV{FOSWIKI_ACTION};
+ $action = $this->env->{FOSWIKI_ACTION};
}
elsif ( exists $Foswiki::cfg{SwitchBoard}{$cgiScriptName} ) {
@@ -245,17 +245,18 @@ around preparePath => sub {
}
$action ||= 'view';
ASSERT( defined $pathInfo ) if DEBUG;
- $req->action($action);
- $req->pathInfo($pathInfo);
- $req->uri( $ENV{REQUEST_URI}
- || $req->url( -absolute => 1, -path => 1, -query => 1 ) );
+ return {
+ action => $action,
+ path_info => $pathInfo,
+ uri => $this->env->{REQUEST_URI} // undef,
+ };
};
around prepareBody => sub {
my $orig = shift;
my ( $this, $req ) = @_;
- return unless $ENV{CONTENT_LENGTH};
+ return unless $this->env->{CONTENT_LENGTH};
# Record the master process so we don't reap temp files in
# sub-processes (see long comment ****)
@@ -277,7 +278,7 @@ around prepareBodyParameters => sub {
my $orig = shift;
my ( $this, $req ) = @_;
- return unless $ENV{CONTENT_LENGTH};
+ return unless $this->env->{CONTENT_LENGTH};
my @plist = $this->cgi->multi_param();
foreach my $pname (@plist) {
my $upname = NFC( Foswiki::decode_utf8($pname) );
@@ -302,7 +303,7 @@ around prepareUploads => sub {
my $orig = shift;
my ( $this, $req ) = @_;
- return unless $ENV{CONTENT_LENGTH};
+ return unless $this->env->{CONTENT_LENGTH};
my %uploads;
foreach my $key ( keys %{ $this->uploads } ) {
my $fname = $this->cgi->param($key);
diff --git a/core/lib/Foswiki/Exception.pm b/core/lib/Foswiki/Exception.pm
index 6f059acda0..86b57e746d 100644
--- a/core/lib/Foswiki/Exception.pm
+++ b/core/lib/Foswiki/Exception.pm
@@ -265,33 +265,67 @@ sub BUILD {
my $this = shift;
}
-package Foswiki::Exception::Engine;
+=begin TML
+
+---++ Exception Foswiki::Exception::HTTPResponse
+
+Used to send HTTP status responses to the user.
+
+Attributes:
+
+ * =status= - HTTP status code, integer; response status code used if omitted.
+ * =response= - a Foswiki::Response object. If not supplied then the default from $Foswiki::app->response is used.
+ * =text= – read-only, generated using the exception attributes.
+
+=cut
+
+package Foswiki::Exception::HTTPResponse;
use Moo;
use namespace::clean;
extends qw(Foswiki::Exception);
our @_newParameters = qw(status reason response);
-has status => ( is => 'ro', required => 1, );
-has reason => ( is => 'ro', required => 1, );
-has response => ( is => 'ro', required => 1, );
+has status =>
+ ( is => 'ro', lazy => 1, default => sub { $_[0]->response->status, }, );
+has response =>
+ ( is => 'ro', lazy => 1, default => sub { $Foswiki::app->response }, );
+has '+text' => (
+ is => 'ro',
+ lazy => 1,
+ default => sub {
+ return 'EngineException: Status code "' . $this->status;
+ },
+);
=begin TML
+---++ Exception Foswiki::Exception::Engine
----++ ObjectMethod stringify() -> $string
+Descendant of =Foswiki::Exception::HTTPResponse=.
-Generate a summary string. This is mainly for debugging.
+Attributes:
-=cut
+ * =reason= - reason text, required
+ * =text= – read-only, generated using the exception attributes.
-sub BUILD {
- my $this = shift;
+=cut
- $this->text( 'EngineException: Status code "'
+package Foswiki::Exception::Engine;
+use Moo;
+extends qw(Foswiki::Exception::HTTPResponse);
+
+has reason => ( is => 'ro', required => 1, );
+has '+text' => (
+ is => 'ro',
+ lazy => 1,
+ default => sub {
+ return
+ 'EngineException: Status code "'
. $this->status
. ' defined because of "'
- . $this->reason );
-}
+ . $this->reason;
+ },
+);
1;
__END__
diff --git a/core/lib/Foswiki/Macros.pm b/core/lib/Foswiki/Macros.pm
index 6d23a9f437..b28ab505d5 100644
--- a/core/lib/Foswiki/Macros.pm
+++ b/core/lib/Foswiki/Macros.pm
@@ -208,7 +208,7 @@ sub expandMacrosOnTopicCreation {
# SMELL Does it really needed in the App model?
#local $Foswiki::Plugins::SESSION = $this;
- local $Foswiki::app = $app;
+ local $Foswiki::app = $this->app;
my $text = $topicObject->text();
if ($text) {
diff --git a/core/lib/Foswiki/Request.pm b/core/lib/Foswiki/Request.pm
index 3c0b3c5e18..fc7758bdea 100644
--- a/core/lib/Foswiki/Request.pm
+++ b/core/lib/Foswiki/Request.pm
@@ -23,6 +23,10 @@ Fields:
files
* =uri= the request uri
+The following fields are parsed from the path_info
+ * =web= the requested web. Access using web method
+ * =topic= the requested topic. Access using topic
+
=cut
package Foswiki::Request;
@@ -50,13 +54,13 @@ BEGIN {
has action => (
is => 'rw',
lazy => 1,
- default => '',
+ default => sub { $_[0]->app->engine->pathData->{action} },
trigger => sub {
my ( $this, $action ) = @_;
# This will set base_action only the first time action would been set.
$this->base_action;
- $ENV{FOSWIKI_ACTION} = $action;
+ $this->app->env->{FOSWIKI_ACTION} = $action;
},
);
has base_action => (
@@ -68,12 +72,38 @@ has base_action => (
# time. See action attribute trigger.
default => sub { return $_[0]->action; },
);
-has path_info => ( is => 'rw', lazy => 1, default => '', );
+
+=begin TML
+
+---++ ObjectAttribute pathInfo
+
+Request path info.
+
+Note that the attribute contains a *URL encoded byte string*
+i.e. it will only contain characters -A-Za-z0-9_.~!*\'();:@&=+$,/?%#[]
+If you intend to analyse it, you will probably have to
+Foswiki::urlDecode it first.
+
+=cut
+
+has pathInfo => (
+ is => 'rw',
+ lazy => 1,
+ default => sub { $_[0]->app->engine->pathData->{path_info} },
+);
has remote_address => ( is => 'rw', lazy => 1, default => '', );
-has uri => ( is => 'rw', lazy => 1, default => '', );
-has cookies => ( is => 'rw', lazy => 1, default => sub { {} }, );
-has headers => ( is => 'rw', lazy => 1, default => sub { {} }, );
-has _param => ( is => 'rw', lazy => 1, default => sub { {} }, );
+has uri => (
+ is => 'rw',
+ lazy => 1,
+ default => sub {
+ my $this = shift;
+ $this->app->engine->pathData->{uri}
+ // $this->url( -absolute => 1, -path => 1, -query => 1 );
+ },
+);
+has cookies => ( is => 'rw', lazy => 1, default => sub { {} }, );
+has headers => ( is => 'rw', lazy => 1, default => sub { {} }, );
+has _param => ( is => 'rw', lazy => 1, default => sub { {} }, );
has uploads => (
is => 'rw',
lazy => 1,
@@ -90,7 +120,27 @@ has start_time => ( # start_time cannot be lazy, can it?
is => 'rw',
default => sub { return [Time::HiRes::gettimeofday] },
);
+has web =>
+ ( is => 'rw', lazy => 1, default => sub { $_[0]->_pathParsed->{web} }, );
+has topic =>
+ ( is => 'rw', lazy => 1, default => sub { $_[0]->_pathParsed->{topic} }, );
+has invalidWeb => (
+ is => 'rw',
+ lazy => 1,
+ default => sub { $_[0]->_pathParsed->{invalidWeb} },
+);
+has invalidTopic => (
+ is => 'rw',
+ lazy => 1,
+ default => sub { $_[0]->_pathParsed->{invalidTopic} },
+);
has _initializer => ( is => 'ro', init_arg => "initializer", );
+has _pathParsed => (
+ is => 'rw',
+ lazy => 1,
+ isa => Foswiki::Object::isaHASH( '_pathParsed', noUndef => 1 ),
+ default => \&_establishWebTopic,
+);
# Aliases are to be declared after all attribute handling methods are been
# created but before CGI methods gets imported via cgiRequest attribute
@@ -184,25 +234,6 @@ Sets/Gets request method (GET, HEAD, POST).
=begin TML
----++ ObjectMethod pathInfo( [ $path ] ) -> $path
-
-Sets/Gets request path info.
-
-Called without parameters returns current pathInfo.
-
-This is an alias to =path_info()= ObjectAttribute.
-
-Note that the string returned is a *URL encoded byte string*
-i.e. it will only contain characters -A-Za-z0-9_.~!*\'();:@&=+$,/?%#[]
-If you intend to analyse it, you will probably have to
-Foswiki::urlDecode it first.
-
-=cut
-
-*pathInfo = \&path_info;
-
-=begin TML
-
---++ ObjectMethod protocol() -> $protocol
Returns 'https' if secure connection. 'http' otherwise.
@@ -535,7 +566,8 @@ sub delete {
foreach my $p (@_) {
next unless exists $this->_param->{$p};
if ( my $upload = $this->uploads->{ $this->param($p) } ) {
- $upload->finish;
+
+ #$upload->finish;
CORE::delete $this->uploads->{ $this->param($p) };
}
CORE::delete $this->_param->{$p};
@@ -790,12 +822,219 @@ Convenience method to get Referer uri.
sub referer { shift->header('Referer') }
+=begin TML
+
+---++ StaticMethod parse([query path]) -> { web => $web, topic => $topic, invalidWeb => optional, invalidTopic => optional }
+
+Parses the rquests query_path and returns a hash of web and topic names.
+If passed a query string, it will parse it and return the extracted
+web / topic.
+
+*This method cannot set the web and topic parsed from the query path.*
+
+Slash (/) can separate webs, subwebs and topics.
+Dot (.) can *only* separate a web path from a topic.
+Trailing slash disambiguates a topic from a subweb when both exist with same name.
+
+If any illegal characters are present, then web and/or topic are undefined. The original bad
+components are returned in the invalidWeb or invalidTopic entries.
+
+webExists and topicExists may be called to disambiguate between subwebs and topics
+however the returned web and topic names do not necessarily exist.
+
+This routine returns two variables when encountering invalid input:
+ * {invalidWeb} contains original invalid web / pathinfo content when validation fails.
+ * {invalidTopic} Same function but for topic name
+
+Ths following paths are supported:
+ * Main Extracts webname, topic is undef
+ * Main/Somename Extracts webname. Somename might be a subweb if it exixsts, or a topic.
+ * Main.Somename Extracts webname and topic.
+ * Main/Somename/ Forces Somename to be a web, if it also exists as a topic
+
+=cut
+
+sub parse {
+ my $query_path = shift;
+
+ my $web_path;
+
+ print STDERR "Processing path ($query_path)\n" if TRACE;
+
+ Foswiki::Exception::Fatal->throw(
+ text => 'No valid query path string passed over to '
+ . __PACKAGE__
+ . '::parse() method' )
+ unless defined $query_path && length $query_path > 1;
+ $query_path =~ s{/+}{/}g; # Remove duplicate slashes
+ $query_path =~ s{^/}{}g; # Remove leading slash
+
+ # trailingSlash Flag - hint that you want the web even if the topic exists
+ my $trailingSlash = ( $query_path =~ s/\/$// );
+
+ # Try the simple, split on dot, maybe it will work.
+ my ( $tweb, $ttopic ) = split( /\./, $query_path );
+ if ( defined $ttopic ) {
+
+ my $web = Foswiki::Sandbox::untaint( $tweb,
+ \&Foswiki::Sandbox::validateWebName );
+
+ my $topic = Foswiki::Sandbox::untaint( $ttopic,
+ \&Foswiki::Sandbox::validateTopicName );
+
+ my $resp = { web => $web, topic => $topic };
+ $resp->{invalidWeb} = $tweb unless defined $web;
+ $resp->{invalidTopic} = $ttopic unless defined $topic;
+
+ print STDERR Data::Dumper::Dumper( \$resp ) if TRACE;
+ return $resp;
+ }
+
+ my @parts = split( /\//, $query_path ); # split the path
+ #print STDERR Data::Dumper::Dumper( \@parts ) if TRACE;
+
+ my $temptopic;
+ my @webs;
+
+ foreach (@parts) {
+ print STDERR "Checking $_\n" if TRACE;
+
+ # Lax check on name to eliminate evil characters.
+ my $p = Foswiki::Sandbox::untaint( $_,
+ \&Foswiki::Sandbox::validateTopicName );
+ unless ($p) {
+
+ # SMELL: It would be better to throw an exception here, but it's too early
+ # in initialization. throwing an oops exception mostly works but the display
+ # has unexpanded macros, and broken links, and no skinning. So for now keep the
+ # old architecture.
+ my $resp = {
+ web => undef,
+ topic => undef,
+ invalidWeb => $_
+ };
+
+ #print STDERR Data::Dumper::Dumper( \$resp ) if TRACE;
+ return $resp;
+ }
+
+ if ( \$_ == \$parts[-1] ) { # This is the last part of path
+ print STDERR "Testing last part web "
+ . join( '/', @webs )
+ . "topic $p \n"
+ if TRACE;
+
+ if ( $trailingSlash
+ && $Foswiki::Plugins::SESSION->webExists(
+ join( '/', @webs, $p ) ) )
+ {
+ print STDERR "Web Exists, Trailing slash, don't check topic: "
+ . join( '/', @webs, $p ) . "\n"
+ if TRACE;
+
+ # It exists in Store as a web
+ push @webs, $p;
+ }
+ elsif (
+ $Foswiki::Plugins::SESSION->topicExists(
+ join( '/', @webs ), $p
+ )
+ )
+ {
+
+ print STDERR "Topic Exists"
+ . join( '/', @webs )
+ . "topic $p \n"
+ if TRACE;
+
+ $temptopic = $p || '';
+ }
+ elsif (
+ $Foswiki::Plugins::SESSION->webExists( join( '/', @webs, $p ) )
+ )
+ {
+
+ print STDERR "Web Exists " . join( '/', @webs, $p ) . "\n"
+ if TRACE;
+
+ # It exists in Store as a web
+ push @webs, $p;
+ }
+ elsif ($trailingSlash) {
+ print STDERR "$p: Not a topic, trailingSlash - treat as web\n"
+ if TRACE;
+ push @webs, $p;
+ }
+ else {
+ print STDERR " $p: Just a topic. " . scalar @webs . "\n"
+ if TRACE;
+ $temptopic = $p;
+ }
+ }
+ else {
+ $p = Foswiki::Sandbox::untaint( $_,
+ \&Foswiki::Sandbox::validateWebName );
+ unless ($p) {
+ my $resp = {
+ web => undef,
+ topic => undef,
+ invalidWeb => $_
+ };
+ return $resp;
+ }
+ else {
+ push @webs, $p;
+ }
+ }
+ }
+ my $resp = { web => join( '/', @webs ), topic => $temptopic };
+
+ #print STDERR Data::Dumper::Dumper( \$resp ) if TRACE;
+ return $resp;
+}
+
+=begin TML
+
+---++ private ObjectMethod _establishWebTopic() -> \%parsed_path_info
+
+Used as default for =_pathParsed= attribute which is then used by
+=web,topic,invalidWeb,invalidTopic= attribute defaults.
+
+=cut
+
+sub _establishWebTopic {
+ my $this = shift;
+
+ return unless defined $this->_pathParsed;
+
+ # Allow topic= query param to override the path
+ my $topicParam = $this->param('topic');
+ my $pathInfo = Foswiki::urlDecode( $this->pathInfo );
+
+ my $parse = Foswiki::Request::parse( $topicParam || $pathInfo );
+
+ # Item3270 - here's the appropriate place to enforce spec
+ # http://develop.twiki.org/~twiki4/cgi-bin/view/Bugs/Item3270
+ $parse->{topic} = ucfirst( $parse->{topic} )
+ if ( defined $parse->{topic} );
+
+ if ( $topicParam && !$parse->{web} ) {
+
+ # Didn't get a web, so try the path
+ $parse = Foswiki::Request::parse($pathInfo);
+ }
+
+ # Note that Web can still be undefined. Caller then determines if the
+ # defaultweb query param, or the HomeWeb config parameter should be used.
+ return $parse;
+}
+
1;
__END__
Module of Foswiki - The Free and Open Source Wiki, http://foswiki.org/
-Copyright (C) 2008-2009 Foswiki Contributors. All Rights Reserved.
+Copyright (C) 2008-2016 Foswiki Contributors. All Rights Reserved.
Foswiki Contributors are listed in the AUTHORS file in the root of this
distribution. NOTE: Please extend that file, not this notice.
diff --git a/core/lib/Foswiki/UI.pm b/core/lib/Foswiki/UI.pm
index 5d491adef4..c657597068 100644
--- a/core/lib/Foswiki/UI.pm
+++ b/core/lib/Foswiki/UI.pm
@@ -377,9 +377,9 @@ sub logon {
&& $Foswiki::cfg{LoginManager} eq 'none' )
{
throw Foswiki::OopsException(
- 'attention',
- status => 500,
- def => 'login_disabled',
+ template => 'attention',
+ status => 500,
+ def => 'login_disabled',
);
}