← Index
NYTProf Performance Profile   « line view »
For ./view
  Run on Fri Jul 31 19:05:14 2015
Reported on Fri Jul 31 19:08:10 2015

Filename/var/www/foswiki11/lib/Foswiki/UI/Rest.pm
StatementsExecuted 40 statements in 2.28ms
Subroutines
Calls P F Exclusive
Time
Inclusive
Time
Subroutine
131183µs83µsFoswiki::UI::Rest::::registerRESTHandlerFoswiki::UI::Rest::registerRESTHandler
11138µs75µsFoswiki::UI::Rest::::BEGIN@13Foswiki::UI::Rest::BEGIN@13
11122µs48µsFoswiki::UI::Rest::::BEGIN@240Foswiki::UI::Rest::BEGIN@240
11122µs43µsFoswiki::UI::Rest::::BEGIN@14Foswiki::UI::Rest::BEGIN@14
11119µs305µsFoswiki::UI::Rest::::BEGIN@16Foswiki::UI::Rest::BEGIN@16
11113µs29µsFoswiki::UI::Rest::::BEGIN@243Foswiki::UI::Rest::BEGIN@243
1116µs6µsFoswiki::UI::Rest::::BEGIN@15Foswiki::UI::Rest::BEGIN@15
0000s0sFoswiki::UI::Rest::::__ANON__[:153]Foswiki::UI::Rest::__ANON__[:153]
0000s0sFoswiki::UI::Rest::::__ANON__[:160]Foswiki::UI::Rest::__ANON__[:160]
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
13260µs2112µs
# spent 75µs (38+37) within Foswiki::UI::Rest::BEGIN@13 which was called: # once (38µs+37µs) by Foswiki::Func::registerRESTHandler at line 13
use strict;
# spent 75µs making 1 call to Foswiki::UI::Rest::BEGIN@13 # spent 37µs making 1 call to strict::import
14245µs264µs
# spent 43µs (22+21) within Foswiki::UI::Rest::BEGIN@14 which was called: # once (22µs+21µs) by Foswiki::Func::registerRESTHandler at line 14
use warnings;
# spent 43µs making 1 call to Foswiki::UI::Rest::BEGIN@14 # spent 21µs making 1 call to warnings::import
15245µs16µs
# spent 6µs within Foswiki::UI::Rest::BEGIN@15 which was called: # once (6µs+0s) by Foswiki::Func::registerRESTHandler at line 15
use Foswiki ();
# spent 6µs making 1 call to Foswiki::UI::Rest::BEGIN@15
1621.47ms2591µs
# spent 305µs (19+286) within Foswiki::UI::Rest::BEGIN@16 which was called: # once (19µs+286µs) by Foswiki::Func::registerRESTHandler at line 16
use Error qw( :try );
# spent 305µs making 1 call to Foswiki::UI::Rest::BEGIN@16 # spent 286µs making 1 call to Error::import
17
181500nsour %restDispatch;
19
20=begin TML=
21
22---++ StaticMethod registerRESTHandler( $subject, $verb, \&fn, %options )
23
24Adds a function to the dispatch table of the REST interface
25for a given subject. See System.CommandAndCGIScripts#rest for more info.
26
27 * =$subject= - The subject under which the function will be registered.
28 * =$verb= - The verb under which the function will be registered.
29 * =\&fn= - Reference to the function.
30
31The handler function must be of the form:
32<verbatim>
33sub handler(\%session, $subject, $verb) -> $text
34</verbatim>
35where:
36 * =\%session= - a reference to the Foswiki session object (may be ignored)
37 * =$subject= - The invoked subject (may be ignored)
38 * =$verb= - The invoked verb (may be ignored)
39
40Additional options are set in the =%options= hash. These options are important
41to ensuring that requests to your handler can't be used in cross-scripting
42attacks, or used for phishing.
43 * =authenticate= - use this boolean option to require authentication for the
44 handler. If this is set, then an authenticated session must be in place
45 or the REST call will be rejected with a 401 (Unauthorized) status code.
46 By default, rest handlers do *not* require authentication.
47 * =validate= - use this boolean option to require validation of any requests
48 made to this handler.
49 By default, requests made to REST handlers are not validated.
50 * =http_allow= use this option to specify the HTTP methods that can
51 be used to invoke the handler.
52
53=cut=
54
55
# spent 83µs within Foswiki::UI::Rest::registerRESTHandler which was called 13 times, avg 6µs/call: # 13 times (83µs+0s) by Foswiki::Func::registerRESTHandler at line 708 of /var/www/foswiki11/lib/Foswiki/Func.pm, avg 6µs/call
sub registerRESTHandler {
561319µs my ( $subject, $verb, $fnref, %options ) = @_;
57
581388µs $restDispatch{$subject}{$verb} = {
59 function => $fnref,
60 %options
61 };
62}
63
64sub rest {
65 my ( $session, %initialContext ) = @_;
66
67 my $req = $session->{request};
68 my $res = $session->{response};
69 my $err;
70
71 # Must define topic param in the query to avoid plugins being
72 # passed the path_info when the are initialised. We can't affect
73 # the path_info, but we *can* persuade Foswiki to ignore it.
74 my $topic = $req->param('topic');
75 if ($topic) {
76 unless ( $topic =~ /\.|\// ) {
77 $res->header( -type => 'text/html', -status => '400' );
78 $err = 'ERROR: (400) Invalid REST invocation'
79 . " - Invalid topic parameter $topic\n";
80 $res->print($err);
81 throw Foswiki::EngineException( 400, $err, $res );
82 }
83 }
84 else {
85
86 # No topic specified, but we still have to set a topic to stop
87 # plugins being passed the subject and verb in place of a topic.
88 $session->{webName} = $Foswiki::cfg{UsersWebName};
89 $session->{topicName} = $Foswiki::cfg{HomeTopicName};
90 }
91
92 my $cache = $session->{cache};
93 my $cachedPage;
94 $cachedPage = $cache->getPage( $session->{webName}, $session->{topicName} )
95 if $cache;
96
97 if ($cachedPage) {
98 print STDERR
99 "found REST for $session->{webName}.$session->{topicName} in cache\n"
100 if $Foswiki::cfg{Cache}{Debug};
101
102 # render uncacheable areas
103 my $text = $cachedPage->{text};
104 $cache->renderDirtyAreas( \$text ) if $cachedPage->{isDirty};
105
106 # set status
107 my $status = $cachedPage->{status};
108 if ( $status == 302 ) {
109 $session->{response}->redirect( $cachedPage->{location} );
110 }
111 else {
112 $session->{response}->status($status);
113 }
114
115 # set headers
116 $session->generateHTTPHeaders( 'rest', $cachedPage->{contentType},
117 $text, $cachedPage );
118
119 # send it out
120 $session->{response}->print($text);
121
122 $session->logEvent( 'rest',
123 $session->{webName} . '.' . $session->{topicName}, '(cached)' );
124
125 return;
126 }
127
128 print STDERR
129 "computing REST for $session->{webName}.$session->{topicName}\n"
130 if $Foswiki::cfg{Cache}{Debug};
131
132 # If there's login info, try and apply it
133 my $login = $req->param('username');
134 if ($login) {
135 my $pass = $req->param('password');
136 my $validation = $session->{users}->checkPassword( $login, $pass );
137 unless ($validation) {
138 $res->header( -type => 'text/html', -status => '401' );
139 $err = "ERROR: (401) Can't login as $login";
140 $res->print($err);
141 throw Foswiki::EngineException( 401, $err, $res );
142 }
143
144 my $cUID = $session->{users}->getCanonicalUserID($login);
145 my $WikiName = $session->{users}->getWikiName($cUID);
146 $session->{users}->getLoginManager()->userLoggedIn( $login, $WikiName );
147 }
148
149 # Check that the REST script is authorised under the standard
150 # {AuthScripts} contract
151 try {
152 $session->getLoginManager()->checkAccess();
153 }
154 catch Error with {
155 my $e = shift;
156 $res->header( -type => 'text/html', -status => '401' );
157 $err = "ERROR: (401) $e";
158 $res->print($err);
159 throw Foswiki::EngineException( 401, $err, $res );
160 };
161
162 my $pathInfo = $req->path_info();
163
164 # Foswiki rest invocations are defined as having a subject (pluginName)
165 # and verb (restHandler in that plugin). Make sure the path_info is
166 # well-structured.
167 unless ( $pathInfo =~ m#/(.*?)[./]([^/]*)# ) {
168
169 $res->header( -type => 'text/html', -status => '400' );
170 $err = "ERROR: (400) Invalid REST invocation - $pathInfo is malformed";
171 $res->print($err);
172 throw Foswiki::EngineException( 400, $err, $res );
173 }
174
175 # Implicit untaint OK - validated later
176 my ( $subject, $verb ) = ( $1, $2 );
177
178 my $record = $restDispatch{$subject}{$verb};
179
180 # Check we have this handler
181 unless ($record) {
182 $res->header( -type => 'text/html', -status => '404' );
183 $err =
184 'ERROR: (404) Invalid REST invocation - '
185 . $pathInfo
186 . ' does not refer to a known handler';
187 $res->print($err);
188 throw Foswiki::EngineException( 404, $err, $res );
189 }
190
191 # Check the method is allowed
192 if ( $record->{http_allow} && defined $req->method() ) {
193 my %allowed = map { $_ => 1 } split( /[,\s]+/, $record->{http_allow} );
194 unless ( $allowed{ uc( $req->method() ) } ) {
195 $res->header( -type => 'text/html', -status => '405' );
196 $err =
197 'ERROR: (405) Bad Request: ' . uc( $req->method() ) . ' denied';
198 $res->print($err);
199 throw Foswiki::EngineException( 404, $err, $res );
200 }
201 }
202
203 # Check someone is logged in
204 if ( $record->{authenticate} ) {
205 unless ( $session->inContext('authenticated')
206 || $Foswiki::cfg{LoginManager} eq 'none' )
207 {
208 $res->header( -type => 'text/html', -status => '401' );
209 $err = "ERROR: (401) $pathInfo requires you to be logged in";
210 $res->print($err);
211 throw Foswiki::EngineException( 401, $err, $res );
212 }
213 }
214
215 # Validate the request
216 if ( $record->{validate} ) {
217 my $nonce = $req->param('validation_key');
218 if (
219 !defined($nonce)
220 || !Foswiki::Validation::isValidNonce(
221 $session->getCGISession(), $nonce
222 )
223 )
224 {
225 $res->header( -type => 'text/html', -status => '403' );
226 $err = "ERROR: (403) Invalid validation code";
227 $res->print($err);
228 throw Foswiki::EngineException( 403, $err, $res );
229 }
230
231 # SMELL: Note we don't expire the validation code. If we expired it,
232 # then subsequent requests using the same code would have to be
233 # interactively confirmed, which isn't really an option with
234 # an XHR.
235 }
236
237 $session->logEvent( 'rest',
238 $session->{webName} . '.' . $session->{topicName} );
239
240279µs273µs
# spent 48µs (22+25) within Foswiki::UI::Rest::BEGIN@240 which was called: # once (22µs+25µs) by Foswiki::Func::registerRESTHandler at line 240
no strict 'refs';
# spent 48µs making 1 call to Foswiki::UI::Rest::BEGIN@240 # spent 25µs making 1 call to strict::unimport
241 my $function = $record->{function};
242 my $result = &$function( $session, $subject, $verb, $session->{response} );
2432470µs244µs
# spent 29µs (13+15) within Foswiki::UI::Rest::BEGIN@243 which was called: # once (13µs+15µs) by Foswiki::Func::registerRESTHandler at line 243
use strict 'refs';
# spent 29µs making 1 call to Foswiki::UI::Rest::BEGIN@243 # spent 15µs making 1 call to strict::import
244
245# Used by CommentPlugin rest handler to redirect to an alternate topic.
246# Note that this might be better validated before dispatching the rest handler
247# however the CommentPlugin handler modifies the endPoint and validating it early
248# fails.
249
250 my $endPoint = $req->param('endPoint');
251 my $nurl;
252
253 if ($endPoint) {
254 my $epParms = '';
255
256 # Strip off any anchors or query string
257 if ( $endPoint =~ s/([#\?].*$)// ) {
258 $epParms = $1;
259 }
260
261 my ( $web, $topic ) =
262 Foswiki::Func::normalizeWebTopicName( undef, $endPoint );
263
264 $web = Foswiki::Sandbox::untaint( $web,
265 \&Foswiki::Sandbox::validateWebName );
266
267 $topic = Foswiki::Sandbox::untaint( $topic,
268 \&Foswiki::Sandbox::validateTopicName );
269
270 unless ( Foswiki::Func::topicExists( $web, $topic ) ) {
271 $res->header( -type => 'text/html', -status => '404' );
272 $err =
273 'ERROR: (404) Invalid REST invocation - '
274 . $req->param('endPoint')
275 . ' does not refer to an existing topic';
276 $res->print($err);
277 throw Foswiki::EngineException( 404, $err, $res );
278 }
279
280 $nurl = $session->getScriptUrl( 1, 'view', $web, $topic );
281 $nurl .= $epParms if ($epParms);
282 }
283
284 if ( defined($nurl) ) {
285 $session->redirect($nurl);
286 }
287 elsif ($result) {
288
289 # If the handler doesn't want to handle all the details of the
290 # response, they can return a page here and get it 200'd
291 $session->writeCompletePage($result);
292 }
293
294 # Otherwise it's assumed that the handler dealt with the response.
295}
296
29714µs1;
298__END__