← Index
NYTProf Performance Profile   « line view »
For ./view
  Run on Fri Jul 31 18:42:36 2015
Reported on Fri Jul 31 18:48:15 2015

Filename/var/www/foswikidev/core/lib/Foswiki/UI/Rest.pm
StatementsExecuted 53 statements in 8.07ms
Subroutines
Calls P F Exclusive
Time
Inclusive
Time
Subroutine
181197µs97µsFoswiki::UI::Rest::::registerRESTHandlerFoswiki::UI::Rest::registerRESTHandler
11113µs26µsFoswiki::UI::Rest::::BEGIN@13Foswiki::UI::Rest::BEGIN@13
11110µs20µsFoswiki::UI::Rest::::BEGIN@276Foswiki::UI::Rest::BEGIN@276
11110µs23µsFoswiki::UI::Rest::::BEGIN@274Foswiki::UI::Rest::BEGIN@274
1119µs13µsFoswiki::UI::Rest::::BEGIN@14Foswiki::UI::Rest::BEGIN@14
1119µs136µsFoswiki::UI::Rest::::BEGIN@16Foswiki::UI::Rest::BEGIN@16
1114µs4µsFoswiki::UI::Rest::::BEGIN@17Foswiki::UI::Rest::BEGIN@17
1114µs4µsFoswiki::UI::Rest::::BEGIN@19Foswiki::UI::Rest::BEGIN@19
1113µs3µsFoswiki::UI::Rest::::BEGIN@15Foswiki::UI::Rest::BEGIN@15
0000s0sFoswiki::UI::Rest::::__ANON__[:277]Foswiki::UI::Rest::__ANON__[:277]
0000s0sFoswiki::UI::Rest::::__ANON__[:290]Foswiki::UI::Rest::__ANON__[:290]
0000s0sFoswiki::UI::Rest::::_listHandlersFoswiki::UI::Rest::_listHandlers
0000s0sFoswiki::UI::Rest::::getRegisteredHandlersFoswiki::UI::Rest::getRegisteredHandlers
0000s0sFoswiki::UI::Rest::::restFoswiki::UI::Rest::rest
Call graph for these subroutines as a Graphviz dot language file.
Line State
ments
Time
on line
Calls Time
in subs
Code
1# See bottom of file for license and copyright information
2
3=begin TML
4
5---+ package Foswiki::UI::Rest
6
7UI delegate for REST interface
8
9=cut
10
11package Foswiki::UI::Rest;
12
13228µs240µs
# spent 26µs (13+13) within Foswiki::UI::Rest::BEGIN@13 which was called: # once (13µs+13µs) by Foswiki::Func::registerRESTHandler at line 13
use strict;
# spent 26µs making 1 call to Foswiki::UI::Rest::BEGIN@13 # spent 13µs making 1 call to strict::import
14223µs218µs
# spent 13µs (9+4) within Foswiki::UI::Rest::BEGIN@14 which was called: # once (9µs+4µs) by Foswiki::Func::registerRESTHandler at line 14
use warnings;
# spent 13µs making 1 call to Foswiki::UI::Rest::BEGIN@14 # spent 4µs making 1 call to warnings::import
15223µs13µs
# spent 3µs within Foswiki::UI::Rest::BEGIN@15 which was called: # once (3µs+0s) by Foswiki::Func::registerRESTHandler at line 15
use Foswiki ();
# spent 3µs making 1 call to Foswiki::UI::Rest::BEGIN@15
16230µs2263µs
# spent 136µs (9+127) within Foswiki::UI::Rest::BEGIN@16 which was called: # once (9µs+127µs) by Foswiki::Func::registerRESTHandler at line 16
use Error qw( :try );
# spent 136µs making 1 call to Foswiki::UI::Rest::BEGIN@16 # spent 127µs making 1 call to Error::import
17243µs14µs
# spent 4µs within Foswiki::UI::Rest::BEGIN@17 which was called: # once (4µs+0s) by Foswiki::Func::registerRESTHandler at line 17
use Foswiki::PageCache ();
# spent 4µs making 1 call to Foswiki::UI::Rest::BEGIN@17
18
19
# spent 4µs within Foswiki::UI::Rest::BEGIN@19 which was called: # once (4µs+0s) by Foswiki::Func::registerRESTHandler at line 24
BEGIN {
2015µs if ( $Foswiki::cfg{UseLocale} ) {
21 require locale;
22 import locale();
23 }
241816µs14µs}
# spent 4µs making 1 call to Foswiki::UI::Rest::BEGIN@19
25
261300nsour %restDispatch;
27
28# Used by Plugin diagnostics to access all the registered handlers
29sub getRegisteredHandlers {
30 return \%restDispatch;
31}
32
33=begin TML
34
35---++ StaticMethod registerRESTHandler( $subject, $verb, \&fn, %options )
36
37Adds a function to the dispatch table of the REST interface
38for a given subject. See System.CommandAndCGIScripts#rest for more info.
39
40 * =$subject= - The subject under which the function will be registered.
41 * =$verb= - The verb under which the function will be registered.
42 * =\&fn= - Reference to the function.
43
44The handler function must be of the form:
45<verbatim>
46sub handler(\%session, $subject, $verb, $response) -> $text
47</verbatim>
48where:
49 * =\%session= - a reference to the Foswiki session object (may be ignored)
50 * =$subject= - The invoked subject (may be ignored)
51 * =$verb= - The invoked verb (may be ignored)
52 * =$response= reference to the Foswiki::Response object that is used to compose a reply to the request
53
54If the =redirectto= parameter is not present on the REST request, then the return
55value from the handler is used to determine the endpoint for the
56request. It can be:
57 * =undef= - causes the core to assume the handler handled the complete
58 request i.e. the core will not generate any response to the request
59 * =text= - any other non-undef value will be written out as the content
60 of an HTTP 200 response. Only the standard headers in the response are
61 written.
62
63Additional options are set in the =%options= hash. These options are important
64to ensuring that requests to your handler can't be used in cross-scripting
65attacks, or used for phishing.
66 * =authenticate= - use this boolean option to require authentication for the
67 handler. If this is set, then an authenticated session must be in place
68 or the REST call will be rejected with a 401 (Unauthorized) status code.
69 By default, rest handlers do *not* require authentication.
70 * =validate= - use this boolean option to require validation of any requests
71 made to this handler.
72 By default, requests made to REST handlers are not validated.
73 * =http_allow= use this option to specify the HTTP methods that can
74 be used to invoke the handler.
75
76=cut
77
78
# spent 97µs within Foswiki::UI::Rest::registerRESTHandler which was called 18 times, avg 5µs/call: # 18 times (97µs+0s) by Foswiki::Func::registerRESTHandler at line 772 of /var/www/foswikidev/core/lib/Foswiki/Func.pm, avg 5µs/call
sub registerRESTHandler {
791825µs my ( $subject, $verb, $fnref, %options ) = @_;
80
81186.71ms $restDispatch{$subject}{$verb} = {
82 function => $fnref,
83 %options
84 };
85}
86
87sub rest {
88 my ( $session, %initialContext ) = @_;
89
90 my $req = $session->{request};
91 my $res = $session->{response};
92 my $err;
93
94 # Referer is useful for logging REST request errors
95 my $referer = ( defined $ENV{HTTP_REFERER} ) ? $ENV{HTTP_REFERER} : '';
96
97 # Must define topic param in the query to avoid plugins being
98 # passed the path_info when the are initialised. We can't affect
99 # the path_info, but we *can* persuade Foswiki to ignore it.
100 my $topic = $req->param('topic');
101 if ($topic) {
102 unless ( $topic =~ m/\.|\// ) {
103 $res->header( -type => 'text/html', -status => '400' );
104 $err = 'ERROR: (400) Invalid REST invocation'
105 . " - Invalid topic parameter $topic\n";
106 $res->print($err);
107 $session->logger->log( 'warning', "REST rejected: " . $err,
108 " - $referer", );
109 throw Foswiki::EngineException( 400, $err, $res );
110 }
111 }
112 else {
113
114 # No topic specified, but we still have to set a topic to stop
115 # plugins being passed the subject and verb in place of a topic.
116 Foswiki::Func::popTopicContext();
117 Foswiki::Func::pushTopicContext( $Foswiki::cfg{UsersWebName},
118 $Foswiki::cfg{HomeTopicName} );
119 }
120
121 return
122 if $session->satisfiedByCache( 'rest', $session->{webName},
123 $session->{topicName} );
124
125 Foswiki::Func::writeDebug(
126 "computing REST for $session->{webName}.$session->{topicName}")
127 if Foswiki::PageCache::TRACE();
128
129 my $pathInfo = Foswiki::urlDecode( $req->path_info() );
130
131 # Foswiki rest invocations are defined as having a subject (pluginName)
132 # and verb (restHandler in that plugin). Make sure the path_info is
133 # well-structured.
134 unless ( $pathInfo =~ m#/(.*?)[./]([^/]*)# ) {
135
136 $res->header( -type => 'text/html', -status => '400' );
137 $err =
138 "ERROR: (400) Invalid REST invocation - $pathInfo is malformed\n";
139 $res->print($err);
140 $session->logger->log( 'warning', "REST rejected: " . $err,
141 " - $referer", );
142 _listHandlers($res) if $session->inContext('command_line');
143 throw Foswiki::EngineException( 400, $err, $res );
144 }
145
146 # Implicit untaint OK - validated later
147 my ( $subject, $verb ) = ( $1, $2 );
148
149 my $record = $restDispatch{$subject}{$verb};
150
151 # Check we have this handler
152 unless ($record) {
153 $res->header( -type => 'text/html', -status => '404' );
154 $err =
155 'ERROR: (404) Invalid REST invocation - '
156 . $pathInfo
157 . ' does not refer to a known handler';
158 _listHandlers($res) if $session->inContext('command_line');
159 $session->logger->log( 'warning', "REST rejected: " . $err,
160 " - $referer", );
161 $res->print($err);
162 throw Foswiki::EngineException( 404, $err, $res );
163 }
164
165 # Log warnings if defaults are needed.
166 if ( !defined $record->{http_allow}
167 || !defined $record->{authenticate}
168 || !defined $record->{validate} )
169 {
170 my $msg;
171 if ( $Foswiki::cfg{LegacyRESTSecurity} ) {
172 $msg =
173'WARNING: This REST handler does not specify http_allow, validate and/or authenticate. LegacyRESTSecurity is enabled. This handler may be insecure and should be examined:';
174 }
175 else {
176 $msg =
177'WARNING: This REST handler does not specify http_allow, validate and/or authenticate. Foswiki has chosen secure defaults:';
178 $record->{http_allow} = 'POST' unless defined $record->{http_allow};
179 $record->{authenticate} = 1 unless defined $record->{authenticate};
180 $record->{validate} = 1 unless defined $record->{validate};
181 }
182 $session->logger->log( 'warning', $msg, " $subject/$verb - $referer", );
183 }
184
185 # Check the method is allowed
186 if ( $record->{http_allow} && defined $req->method() ) {
187 unless ( $session->inContext('command_line') ) {
188 my %allowed =
189 map { $_ => 1 } split( /[,\s]+/, $record->{http_allow} );
190 unless ( $allowed{ uc( $req->method() ) } ) {
191 $res->header( -type => 'text/html', -status => '405' );
192 $err =
193 'ERROR: (405) Bad Request: '
194 . uc( $req->method() )
195 . ' denied';
196 $session->logger->log(
197 'warning',
198 "REST rejected: " . $err,
199 " $subject/$verb - $referer",
200 );
201 $res->print($err);
202 throw Foswiki::EngineException( 405, $err, $res );
203 }
204 }
205 }
206
207 # Check someone is logged in
208 if ( $record->{authenticate} ) {
209
210 # no need to exempt cli. LoginManager sets authenticated correctly.
211 unless ( $session->inContext('authenticated')
212 || $Foswiki::cfg{LoginManager} eq 'none' )
213 {
214 $res->header( -type => 'text/html', -status => '401' );
215 $err = "ERROR: (401) $pathInfo requires you to be logged in";
216 $session->logger->log(
217 'warning',
218 "REST rejected: " . $err,
219 " $subject/$verb - $referer"
220 );
221 $res->print($err);
222 throw Foswiki::EngineException( 401, $err, $res );
223 }
224 }
225
226 # Validate the request
227 # SMELL: We can't use Foswiki::UI::checkValidationKey.
228 # The common reoutine expires the key, but if we expired it,
229 # then subsequent requests using the same code would have to be
230 # interactively confirmed, which isn't really an option with
231 # an XHR. Also, the common routine throws a ValidationException
232 # and we want a simple engine exception here.
233 if ( $record->{validate}
234 && $Foswiki::cfg{Validation}{Method} ne 'none'
235 && !$session->inContext('command_line')
236 && uc( $req->method() eq 'POST' ) )
237 {
238
239 my $nonce = $req->param('validation_key');
240 if (
241 !defined($nonce)
242 || !Foswiki::Validation::isValidNonce(
243 $session->getCGISession(), $nonce
244 )
245 )
246 {
247 $res->header( -type => 'text/html', -status => '403' );
248 $err = "ERROR: (403) Invalid validation code";
249 $session->logger->log(
250 'warning',
251 "REST rejected: " . $err,
252 " $subject/$verb - $referer"
253 );
254 $res->print($err);
255 throw Foswiki::EngineException( 403, $err, $res );
256 }
257 }
258
259 my $function = $record->{function};
260
261 $session->logger->log(
262 {
263 level => 'info',
264 action => 'rest',
265 webTopic => $session->{webName} . '.' . $session->{topicName},
266 extra => "$subject $verb",
267 }
268 );
269
270 my $result;
271 my $error = 0;
272
273 try {
274242µs237µs
# spent 23µs (10+14) within Foswiki::UI::Rest::BEGIN@274 which was called: # once (10µs+14µs) by Foswiki::Func::registerRESTHandler at line 274
no strict 'refs';
# spent 23µs making 1 call to Foswiki::UI::Rest::BEGIN@274 # spent 14µs making 1 call to strict::unimport
275 $result = &$function( $session, $subject, $verb, $session->{response} );
2762314µs229µs
# spent 20µs (10+9) within Foswiki::UI::Rest::BEGIN@276 which was called: # once (10µs+9µs) by Foswiki::Func::registerRESTHandler at line 276
use strict 'refs';
# spent 20µs making 1 call to Foswiki::UI::Rest::BEGIN@276 # spent 9µs making 1 call to strict::import
277 }
278 catch Error::Simple with {
279
280 # Note: we're *not* catching Error here, just Error::Simple
281 # so we catch things like OopsException
282 $session->{response}->header(
283 -status => 500,
284 -type => 'text/plain',
285 -charset => 'UTF-8'
286 );
287 $session->{response}->print(
288 'ERROR: (500) Internal server error - ' . shift->stringify() );
289 $error = 1;
290 };
291
292 if ( !$error ) {
293
294 # endpoint is now deprecated, but may still be
295 # used by old rest handlers to redirect to an alternate topic.
296 # Note that this might be better validated before dispatching
297 # the rest handler however come handlers modify
298 # the endPoint and validating it early fails.
299
300 # endPoint still supported for compatibility
301 my $target = $session->redirectto( scalar( $req->param('endPoint') ) );
302
303 if ( defined($target) ) {
304 $session->redirect($target);
305 }
306 else {
307 if ( defined $req->param('redirectto')
308 || defined $req->param('endPoint') )
309 {
310 $session->{response}->header(
311 -status => 403,
312 -type => 'text/plain',
313 -charset => 'UTF-8'
314 );
315 $session->{response}
316 ->print( 'ERROR: (403) Invalid REST invocation - '
317 . ' redirectto does not refer to a valid redirect target'
318 );
319 return;
320 }
321 }
322 }
323
324 if ($result) {
325
326 # If the handler doesn't want to handle all the details of the
327 # response, they can return a page here and get it 200'd
328 $session->writeCompletePage($result);
329 }
330
331 # Otherwise it's assumed that the handler dealt with the response.
332}
333
334sub _listHandlers {
335 $_[0]->print(
336 "\nusage: ./rest /PluginName/restHandler param=value\n\n"
337 . join( "\n",
338 map { $_ . ' : ' . join( ' , ', keys( %{ $restDispatch{$_} } ) ) }
339 keys(%restDispatch) )
340 . "\n\n"
341 );
342}
343
34412µs1;
345__END__