Filename | /usr/local/src/github.com/foswiki/core/lib/Foswiki/UI/Rest.pm |
Statements | Executed 26 statements in 2.37ms |
Calls | P | F | Exclusive Time |
Inclusive Time |
Subroutine |
---|---|---|---|---|---|
6 | 1 | 1 | 83µs | 83µs | registerRESTHandler | Foswiki::UI::Rest::
1 | 1 | 1 | 37µs | 47µs | BEGIN@13 | Foswiki::UI::Rest::
1 | 1 | 1 | 22µs | 46µs | BEGIN@14 | Foswiki::UI::Rest::
1 | 1 | 1 | 20µs | 439µs | BEGIN@16 | Foswiki::UI::Rest::
1 | 1 | 1 | 19µs | 54µs | BEGIN@253 | Foswiki::UI::Rest::
1 | 1 | 1 | 17µs | 46µs | BEGIN@256 | Foswiki::UI::Rest::
1 | 1 | 1 | 10µs | 10µ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 | 58µs | 2 | 57µs | # spent 47µs (37+10) within Foswiki::UI::Rest::BEGIN@13 which was called:
# once (37µs+10µs) by Foswiki::Func::registerRESTHandler at line 13 # spent 47µs making 1 call to Foswiki::UI::Rest::BEGIN@13
# spent 10µs making 1 call to strict::import |
14 | 2 | 51µs | 2 | 70µs | # spent 46µs (22+24) within Foswiki::UI::Rest::BEGIN@14 which was called:
# once (22µs+24µs) by Foswiki::Func::registerRESTHandler at line 14 # spent 46µs making 1 call to Foswiki::UI::Rest::BEGIN@14
# spent 24µs making 1 call to warnings::import |
15 | 2 | 54µs | 1 | 10µs | # spent 10µs within Foswiki::UI::Rest::BEGIN@15 which was called:
# once (10µs+0s) by Foswiki::Func::registerRESTHandler at line 15 # spent 10µs making 1 call to Foswiki::UI::Rest::BEGIN@15 |
16 | 2 | 1.74ms | 2 | 858µs | # spent 439µs (20+419) within Foswiki::UI::Rest::BEGIN@16 which was called:
# once (20µs+419µs) by Foswiki::Func::registerRESTHandler at line 16 # spent 439µs making 1 call to Foswiki::UI::Rest::BEGIN@16
# spent 419µs making 1 call to Error::import |
17 | |||||
18 | 1 | 2µs | 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 6 times, avg 14µs/call:
# 6 times (83µs+0s) by Foswiki::Func::registerRESTHandler at line 669 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Func.pm, avg 14µs/call | ||||
56 | 12 | 92µs | my ( $subject, $verb, $fnref, %options ) = @_; | ||
57 | |||||
58 | $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 = | ||||
171 | "ERROR: (400) Invalid REST invocation - $pathInfo is malformed\n"; | ||||
172 | $res->print($err); | ||||
173 | |||||
174 | $res->print( | ||||
175 | "\nuseage: ./rest /PluginName/restHandler param=value\n\n" . join( | ||||
176 | "\n", | ||||
177 | map { | ||||
178 | $_ . ' : ' | ||||
179 | . join( ' , ', keys( %{ $restDispatch{$_} } ) ) | ||||
180 | } keys(%restDispatch) | ||||
181 | ) | ||||
182 | . "\n\n" | ||||
183 | ) if $session->inContext('command_line'); | ||||
184 | |||||
185 | throw Foswiki::EngineException( 400, $err, $res ); | ||||
186 | } | ||||
187 | |||||
188 | # Implicit untaint OK - validated later | ||||
189 | my ( $subject, $verb ) = ( $1, $2 ); | ||||
190 | |||||
191 | my $record = $restDispatch{$subject}{$verb}; | ||||
192 | |||||
193 | # Check we have this handler | ||||
194 | unless ($record) { | ||||
195 | $res->header( -type => 'text/html', -status => '404' ); | ||||
196 | $err = | ||||
197 | 'ERROR: (404) Invalid REST invocation - ' | ||||
198 | . $pathInfo | ||||
199 | . ' does not refer to a known handler'; | ||||
200 | $res->print($err); | ||||
201 | throw Foswiki::EngineException( 404, $err, $res ); | ||||
202 | } | ||||
203 | |||||
204 | # Check the method is allowed | ||||
205 | if ( $record->{http_allow} && defined $req->method() ) { | ||||
206 | my %allowed = map { $_ => 1 } split( /[,\s]+/, $record->{http_allow} ); | ||||
207 | unless ( $allowed{ uc( $req->method() ) } ) { | ||||
208 | $res->header( -type => 'text/html', -status => '405' ); | ||||
209 | $err = | ||||
210 | 'ERROR: (405) Bad Request: ' . uc( $req->method() ) . ' denied'; | ||||
211 | $res->print($err); | ||||
212 | throw Foswiki::EngineException( 404, $err, $res ); | ||||
213 | } | ||||
214 | } | ||||
215 | |||||
216 | # Check someone is logged in | ||||
217 | if ( $record->{authenticate} ) { | ||||
218 | unless ( $session->inContext('authenticated') | ||||
219 | || $Foswiki::cfg{LoginManager} eq 'none' ) | ||||
220 | { | ||||
221 | $res->header( -type => 'text/html', -status => '401' ); | ||||
222 | $err = "ERROR: (401) $pathInfo requires you to be logged in"; | ||||
223 | $res->print($err); | ||||
224 | throw Foswiki::EngineException( 401, $err, $res ); | ||||
225 | } | ||||
226 | } | ||||
227 | |||||
228 | # Validate the request | ||||
229 | if ( $record->{validate} ) { | ||||
230 | my $nonce = $req->param('validation_key'); | ||||
231 | if ( | ||||
232 | !defined($nonce) | ||||
233 | || !Foswiki::Validation::isValidNonce( | ||||
234 | $session->getCGISession(), $nonce | ||||
235 | ) | ||||
236 | ) | ||||
237 | { | ||||
238 | $res->header( -type => 'text/html', -status => '403' ); | ||||
239 | $err = "ERROR: (403) Invalid validation code"; | ||||
240 | $res->print($err); | ||||
241 | throw Foswiki::EngineException( 403, $err, $res ); | ||||
242 | } | ||||
243 | |||||
244 | # SMELL: Note we don't expire the validation code. If we expired it, | ||||
245 | # then subsequent requests using the same code would have to be | ||||
246 | # interactively confirmed, which isn't really an option with | ||||
247 | # an XHR. | ||||
248 | } | ||||
249 | |||||
250 | $session->logEvent( 'rest', | ||||
251 | $session->{webName} . '.' . $session->{topicName} ); | ||||
252 | |||||
253 | 2 | 87µs | 2 | 90µs | # spent 54µs (19+36) within Foswiki::UI::Rest::BEGIN@253 which was called:
# once (19µs+36µs) by Foswiki::Func::registerRESTHandler at line 253 # spent 54µs making 1 call to Foswiki::UI::Rest::BEGIN@253
# spent 36µs making 1 call to strict::unimport |
254 | my $function = $record->{function}; | ||||
255 | my $result = &$function( $session, $subject, $verb, $session->{response} ); | ||||
256 | 2 | 273µs | 2 | 76µs | # spent 46µs (17+30) within Foswiki::UI::Rest::BEGIN@256 which was called:
# once (17µs+30µs) by Foswiki::Func::registerRESTHandler at line 256 # spent 46µs making 1 call to Foswiki::UI::Rest::BEGIN@256
# spent 30µs making 1 call to strict::import |
257 | |||||
258 | # SMELL: does anyone use endPoint? I can't find any evidence of it. | ||||
259 | my $endPoint = $req->param('endPoint'); | ||||
260 | if ( defined($endPoint) ) { | ||||
261 | my $nurl = $session->getScriptUrl( 1, 'view', '', $endPoint ); | ||||
262 | $session->redirect($nurl); | ||||
263 | } | ||||
264 | elsif ($result) { | ||||
265 | |||||
266 | # If the handler doesn't want to handle all the details of the | ||||
267 | # response, they can return a page here and get it 200'd | ||||
268 | $session->writeCompletePage($result); | ||||
269 | } | ||||
270 | |||||
271 | # Otherwise it's assumed that the handler dealt with the response. | ||||
272 | } | ||||
273 | |||||
274 | 1 | 7µs | 1; | ||
275 | __END__ |