Filename | /var/www/foswiki11/lib/Foswiki/UI/Rest.pm |
Statements | Executed 40 statements in 2.28ms |
Calls | P | F | Exclusive Time |
Inclusive Time |
Subroutine |
---|---|---|---|---|---|
13 | 1 | 1 | 83µs | 83µs | registerRESTHandler | Foswiki::UI::Rest::
1 | 1 | 1 | 38µs | 75µs | BEGIN@13 | Foswiki::UI::Rest::
1 | 1 | 1 | 22µs | 48µs | BEGIN@240 | Foswiki::UI::Rest::
1 | 1 | 1 | 22µs | 43µs | BEGIN@14 | Foswiki::UI::Rest::
1 | 1 | 1 | 19µs | 305µs | BEGIN@16 | Foswiki::UI::Rest::
1 | 1 | 1 | 13µs | 29µs | BEGIN@243 | Foswiki::UI::Rest::
1 | 1 | 1 | 6µs | 6µs | BEGIN@15 | Foswiki::UI::Rest::
0 | 0 | 0 | 0s | 0s | __ANON__[:153] | Foswiki::UI::Rest::
0 | 0 | 0 | 0s | 0s | __ANON__[:160] | Foswiki::UI::Rest::
0 | 0 | 0 | 0s | 0s | rest | Foswiki::UI::Rest::
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 | |||||
7 | UI delegate for REST interface | ||||
8 | |||||
9 | =cut | ||||
10 | |||||
11 | package Foswiki::UI::Rest; | ||||
12 | |||||
13 | 2 | 60µs | 2 | 112µ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 # spent 75µs making 1 call to Foswiki::UI::Rest::BEGIN@13
# spent 37µs making 1 call to strict::import |
14 | 2 | 45µs | 2 | 64µ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 # spent 43µs making 1 call to Foswiki::UI::Rest::BEGIN@14
# spent 21µs making 1 call to warnings::import |
15 | 2 | 45µs | 1 | 6µs | # spent 6µs within Foswiki::UI::Rest::BEGIN@15 which was called:
# once (6µs+0s) by Foswiki::Func::registerRESTHandler at line 15 # spent 6µs making 1 call to Foswiki::UI::Rest::BEGIN@15 |
16 | 2 | 1.47ms | 2 | 591µ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 # spent 305µs making 1 call to Foswiki::UI::Rest::BEGIN@16
# spent 286µs making 1 call to Error::import |
17 | |||||
18 | 1 | 500ns | our %restDispatch; | ||
19 | |||||
20 | =begin TML= | ||||
21 | |||||
22 | ---++ StaticMethod registerRESTHandler( $subject, $verb, \&fn, %options ) | ||||
23 | |||||
24 | Adds a function to the dispatch table of the REST interface | ||||
25 | for 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 | |||||
31 | The handler function must be of the form: | ||||
32 | <verbatim> | ||||
33 | sub handler(\%session, $subject, $verb) -> $text | ||||
34 | </verbatim> | ||||
35 | where: | ||||
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 | |||||
40 | Additional options are set in the =%options= hash. These options are important | ||||
41 | to ensuring that requests to your handler can't be used in cross-scripting | ||||
42 | attacks, 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 | ||||
56 | 13 | 19µs | my ( $subject, $verb, $fnref, %options ) = @_; | ||
57 | |||||
58 | 13 | 88µs | $restDispatch{$subject}{$verb} = { | ||
59 | function => $fnref, | ||||
60 | %options | ||||
61 | }; | ||||
62 | } | ||||
63 | |||||
64 | sub 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 | |||||
240 | 2 | 79µs | 2 | 73µ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 # 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} ); | ||||
243 | 2 | 470µs | 2 | 44µ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 # 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 | |||||
297 | 1 | 4µs | 1; | ||
298 | __END__ |