Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
93.95% |
264 / 281 |
|
76.60% |
36 / 47 |
CRAP | |
0.00% |
0 / 1 |
Handler | |
93.95% |
264 / 281 |
|
76.60% |
36 / 47 |
107.44 | |
0.00% |
0 / 1 |
initContext | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
1 | |||
initServices | |
100.00% |
18 / 18 |
|
100.00% |
1 / 1 |
1 | |||
initSession | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
1 | |||
initForExecute | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
2 | |||
processRequestBody | |
100.00% |
16 / 16 |
|
100.00% |
1 / 1 |
6 | |||
getPath | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getSupportedPathParams | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
getRouter | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getModule | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getRouteUrl | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
urlEncodeTitle | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
2 | |||
getRequest | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getAuthority | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getConfig | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getResponseFactory | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getSession | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
validate | |
100.00% |
19 / 19 |
|
100.00% |
1 / 1 |
5 | |||
detectExtraneousBodyFields | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
2 | |||
checkSession | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
4 | |||
getJsonLocalizer | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
2 | |||
getConditionalHeaderUtil | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
2 | |||
checkPreconditions | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
2 | |||
applyConditionalResponseHeaders | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
3 | |||
applyCacheControl | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
6 | |||
getParamSettings | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getBodyParamSettings | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getOpenApiSpec | |
95.65% |
22 / 23 |
|
0.00% |
0 / 1 |
8 | |||
getRequestSpec | |
90.91% |
10 / 11 |
|
0.00% |
0 / 1 |
4.01 | |||
getRequestBodySchema | |
92.86% |
26 / 28 |
|
0.00% |
0 / 1 |
8.02 | |||
getResponseBodySchema | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
getResponseBodySchemaFileName | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
generateResponseSpec | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
2 | |||
getBodyValidator | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getValidatedParams | |
66.67% |
2 / 3 |
|
0.00% |
0 / 1 |
2.15 | |||
getValidatedBody | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
parseBodyData | |
100.00% |
36 / 36 |
|
100.00% |
1 / 1 |
13 | |||
getSupportedRequestTypes | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
3 | |||
getHookContainer | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getHookRunner | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getLastModified | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getETag | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
hasRepresentation | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
needsReadAccess | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
needsWriteAccess | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
requireSafeAgainstCsrf | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
postInitSetup | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
postValidationSetup | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
execute | n/a |
0 / 0 |
n/a |
0 / 0 |
0 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Rest; |
4 | |
5 | use DateTime; |
6 | use MediaWiki\Debug\MWDebug; |
7 | use MediaWiki\HookContainer\HookContainer; |
8 | use MediaWiki\HookContainer\HookRunner; |
9 | use MediaWiki\Permissions\Authority; |
10 | use MediaWiki\Rest\Module\Module; |
11 | use MediaWiki\Rest\Validator\BodyValidator; |
12 | use MediaWiki\Rest\Validator\NullBodyValidator; |
13 | use MediaWiki\Rest\Validator\Validator; |
14 | use MediaWiki\Session\Session; |
15 | use UtfNormal\Validator as UtfNormalValidator; |
16 | use Wikimedia\Assert\Assert; |
17 | use Wikimedia\Message\MessageValue; |
18 | use Wikimedia\ParamValidator\ParamValidator; |
19 | |
20 | /** |
21 | * Base class for REST route handlers. |
22 | * |
23 | * @stable to extend. |
24 | */ |
25 | abstract class Handler { |
26 | |
27 | /** |
28 | * @see Validator::KNOWN_PARAM_SOURCES |
29 | */ |
30 | public const KNOWN_PARAM_SOURCES = Validator::KNOWN_PARAM_SOURCES; |
31 | |
32 | /** |
33 | * @see Validator::PARAM_SOURCE |
34 | */ |
35 | public const PARAM_SOURCE = Validator::PARAM_SOURCE; |
36 | |
37 | /** |
38 | * @see Validator::PARAM_DESCRIPTION |
39 | */ |
40 | public const PARAM_DESCRIPTION = Validator::PARAM_DESCRIPTION; |
41 | |
42 | public const OPENAPI_DESCRIPTION_KEY = 'description'; |
43 | |
44 | /** @var Module */ |
45 | private $module; |
46 | |
47 | /** @var RequestInterface */ |
48 | private $request; |
49 | |
50 | /** @var Authority */ |
51 | private $authority; |
52 | |
53 | /** @var string */ |
54 | private $path; |
55 | |
56 | /** @var array */ |
57 | private $config; |
58 | |
59 | /** @var array */ |
60 | private $openApiSpec; |
61 | |
62 | /** @var ResponseFactory */ |
63 | private $responseFactory; |
64 | |
65 | /** @var array|null */ |
66 | private $validatedParams; |
67 | |
68 | /** @var mixed|null */ |
69 | private $validatedBody; |
70 | |
71 | /** @var ConditionalHeaderUtil */ |
72 | private $conditionalHeaderUtil; |
73 | |
74 | /** @var JsonLocalizer */ |
75 | private $jsonLocalizer; |
76 | |
77 | /** @var HookContainer */ |
78 | private $hookContainer; |
79 | |
80 | /** @var Session */ |
81 | private $session; |
82 | |
83 | /** @var HookRunner */ |
84 | private $hookRunner; |
85 | |
86 | /** |
87 | * Injects information about the handler's context in the Module. |
88 | * The framework should call this right after the object was constructed. |
89 | * |
90 | * First function of the initialization function, must be called before |
91 | * initServices(). |
92 | * |
93 | * @param Module $module |
94 | * @param string $path |
95 | * @param array $routeConfig information about the route declaration. |
96 | * @param array $openApiSpec OpenAPI meta-data, such as the description. |
97 | * |
98 | * @internal |
99 | */ |
100 | final public function initContext( |
101 | Module $module, |
102 | string $path, |
103 | array $routeConfig, |
104 | array $openApiSpec = [] |
105 | ) { |
106 | Assert::precondition( |
107 | $this->authority === null, |
108 | 'initContext() must be called before initServices()' |
109 | ); |
110 | |
111 | $this->module = $module; |
112 | $this->path = $path; |
113 | $this->config = $routeConfig; |
114 | $this->openApiSpec = $openApiSpec; |
115 | } |
116 | |
117 | /** |
118 | * Inject service objects. |
119 | * |
120 | * Second function of the initialization function, must be called after |
121 | * initContext() and before initSession(). |
122 | * |
123 | * @param Authority $authority |
124 | * @param ResponseFactory $responseFactory |
125 | * @param HookContainer $hookContainer |
126 | * |
127 | * @internal |
128 | */ |
129 | final public function initServices( |
130 | Authority $authority, ResponseFactory $responseFactory, HookContainer $hookContainer |
131 | ) { |
132 | // Warn if a subclass overrides getBodyValidator() |
133 | MWDebug::detectDeprecatedOverride( |
134 | $this, |
135 | __CLASS__, |
136 | 'getBodyValidator', |
137 | '1.43' |
138 | ); |
139 | |
140 | Assert::precondition( |
141 | $this->module !== null, |
142 | 'initServices() must not be called before initContext()' |
143 | ); |
144 | Assert::precondition( |
145 | $this->session === null, |
146 | 'initServices() must be called before initSession()' |
147 | ); |
148 | |
149 | $this->authority = $authority; |
150 | $this->responseFactory = $responseFactory; |
151 | $this->hookContainer = $hookContainer; |
152 | $this->hookRunner = new HookRunner( $hookContainer ); |
153 | } |
154 | |
155 | /** |
156 | * Inject session information. |
157 | * |
158 | * Third function of the initialization function, must be called after |
159 | * initServices() and before initForExecute(). |
160 | * |
161 | * @param Session $session |
162 | * |
163 | * @internal |
164 | */ |
165 | final public function initSession( Session $session ) { |
166 | Assert::precondition( |
167 | $this->authority !== null, |
168 | 'initSession() must not be called before initContext()' |
169 | ); |
170 | Assert::precondition( |
171 | $this->request === null, |
172 | 'initSession() must be called before initForExecute()' |
173 | ); |
174 | |
175 | $this->session = $session; |
176 | } |
177 | |
178 | /** |
179 | * Initialise for execution based on the given request. |
180 | * |
181 | * Last function of the initialization function, must be called after |
182 | * initSession() and before validate() and checkPreconditions(). |
183 | * |
184 | * This function will call postInitSetup() to allow subclasses to |
185 | * perform their own initialization. |
186 | * |
187 | * The request object is updated with parsed body data if needed. |
188 | * |
189 | * @internal |
190 | * |
191 | * @param RequestInterface $request |
192 | * |
193 | * @throws HttpException if the handler does not accept the request for |
194 | * some reason. |
195 | */ |
196 | final public function initForExecute( RequestInterface $request ) { |
197 | Assert::precondition( |
198 | $this->session !== null, |
199 | 'initForExecute() must not be called before initSession()' |
200 | ); |
201 | |
202 | if ( $request->getParsedBody() === null ) { |
203 | $this->processRequestBody( $request ); |
204 | } |
205 | |
206 | $this->request = $request; |
207 | |
208 | $this->postInitSetup(); |
209 | } |
210 | |
211 | /** |
212 | * Process the request's request body and set the parsed body data |
213 | * if appropriate. |
214 | * |
215 | * @see parseBodyData() |
216 | * |
217 | * @throws HttpException if the request body is not acceptable. |
218 | */ |
219 | private function processRequestBody( RequestInterface $request ) { |
220 | // fail if the request method is in NO_BODY_METHODS but has body |
221 | $requestMethod = $request->getMethod(); |
222 | if ( in_array( $requestMethod, RequestInterface::NO_BODY_METHODS ) ) { |
223 | // check if the request has a body |
224 | if ( $request->hasBody() ) { |
225 | // NOTE: Don't throw, see T359509. |
226 | // TODO: Ignore only empty bodies, log a warning or fail if |
227 | // there is actual content. |
228 | return; |
229 | } |
230 | } |
231 | |
232 | // fail if the request method expects a body but has no body |
233 | if ( in_array( $requestMethod, RequestInterface::BODY_METHODS ) ) { |
234 | // check if it has no body |
235 | if ( !$request->hasBody() ) { |
236 | throw new LocalizedHttpException( |
237 | new MessageValue( |
238 | "rest-request-body-expected", |
239 | [ $requestMethod ] |
240 | ), |
241 | 411 |
242 | ); |
243 | } |
244 | } |
245 | |
246 | // call parsedbody |
247 | if ( $request->hasBody() ) { |
248 | $parsedBody = $this->parseBodyData( $request ); |
249 | // Set the parsed body data on the request object |
250 | $request->setParsedBody( $parsedBody ); |
251 | } |
252 | } |
253 | |
254 | /** |
255 | * Returns the path this handler is bound to relative to the module prefix. |
256 | * Includes path variables. |
257 | */ |
258 | public function getPath(): string { |
259 | return $this->path; |
260 | } |
261 | |
262 | /** |
263 | * Get a list of parameter placeholders present in the route's path |
264 | * as returned by getPath(). Note that this is independent of the parameters |
265 | * defined by getParamSettings(): required path parameters defined in |
266 | * getParamSettings() should be present in the path, but there is no |
267 | * mechanism to ensure that they are. |
268 | * |
269 | * @return string[] |
270 | */ |
271 | public function getSupportedPathParams(): array { |
272 | $path = $this->getPath(); |
273 | |
274 | preg_match_all( '/\{(.*?)\}/', $path, $matches, PREG_PATTERN_ORDER ); |
275 | |
276 | return $matches[1] ?? []; |
277 | } |
278 | |
279 | protected function getRouter(): Router { |
280 | return $this->module->getRouter(); |
281 | } |
282 | |
283 | /** |
284 | * Get the Module this handler belongs to. |
285 | * Will fail hard if called before initContext(). |
286 | */ |
287 | protected function getModule(): Module { |
288 | return $this->module; |
289 | } |
290 | |
291 | /** |
292 | * Get the URL of this handler's endpoint. |
293 | * Supports the substitution of path parameters, and additions of query parameters. |
294 | * |
295 | * @see Router::getRouteUrl() |
296 | * |
297 | * @param string[] $pathParams Path parameters to be injected into the path |
298 | * @param string[] $queryParams Query parameters to be attached to the URL |
299 | * |
300 | * @return string |
301 | */ |
302 | protected function getRouteUrl( $pathParams = [], $queryParams = [] ): string { |
303 | $path = $this->getPath(); |
304 | return $this->getRouter()->getRouteUrl( $path, $pathParams, $queryParams ); |
305 | } |
306 | |
307 | /** |
308 | * URL-encode titles in a "pretty" way. |
309 | * |
310 | * Keeps intact ;@$!*(),~: (urlencode does not, but wfUrlencode does). |
311 | * Encodes spaces as underscores (wfUrlencode does not). |
312 | * Encodes slashes (wfUrlencode does not, but keeping them messes with REST paths). |
313 | * Encodes pluses (this is not necessary, and may change). |
314 | * |
315 | * @see wfUrlencode |
316 | * |
317 | * @param string $title |
318 | * |
319 | * @return string |
320 | */ |
321 | protected function urlEncodeTitle( $title ) { |
322 | $title = str_replace( ' ', '_', $title ); |
323 | $title = urlencode( $title ); |
324 | |
325 | // %3B_a_%40_b_%24_c_%21_d_%2A_e_%28_f_%29_g_%2C_h_~_i_%3A |
326 | $replace = [ '%3B', '%40', '%24', '%21', '%2A', '%28', '%29', '%2C', '%7E', '%3A' ]; |
327 | $with = [ ';', '@', '$', '!', '*', '(', ')', ',', '~', ':' ]; |
328 | |
329 | return str_replace( $replace, $with, $title ); |
330 | } |
331 | |
332 | /** |
333 | * Get the current request. The return type declaration causes it to raise |
334 | * a fatal error if initForExecute() has not yet been called. |
335 | */ |
336 | public function getRequest(): RequestInterface { |
337 | return $this->request; |
338 | } |
339 | |
340 | /** |
341 | * Get the current acting authority. The return type declaration causes it to raise |
342 | * a fatal error if initServices() has not yet been called. |
343 | * |
344 | * @since 1.36 |
345 | * @return Authority |
346 | */ |
347 | public function getAuthority(): Authority { |
348 | return $this->authority; |
349 | } |
350 | |
351 | /** |
352 | * Get the configuration array for the current route. The return type |
353 | * declaration causes it to raise a fatal error if initContext() has not |
354 | * been called. |
355 | */ |
356 | public function getConfig(): array { |
357 | return $this->config; |
358 | } |
359 | |
360 | /** |
361 | * Get the ResponseFactory which can be used to generate Response objects. |
362 | * This will raise a fatal error if initServices() has not been |
363 | * called. |
364 | */ |
365 | public function getResponseFactory(): ResponseFactory { |
366 | return $this->responseFactory; |
367 | } |
368 | |
369 | /** |
370 | * Get the Session. |
371 | * This will raise a fatal error if initSession() has not been |
372 | * called. |
373 | */ |
374 | public function getSession(): Session { |
375 | return $this->session; |
376 | } |
377 | |
378 | /** |
379 | * Validate the request parameters/attributes and body. If there is a validation |
380 | * failure, a response with an error message should be returned or an |
381 | * HttpException should be thrown. |
382 | * |
383 | * @stable to override |
384 | * @param Validator $restValidator |
385 | * @throws HttpException On validation failure. |
386 | */ |
387 | public function validate( Validator $restValidator ) { |
388 | $this->validatedParams = $restValidator->validateParams( |
389 | $this->getParamSettings() |
390 | ); |
391 | |
392 | $bodyType = $this->request->getBodyType(); |
393 | $legacyBodyValidator = $bodyType === null ? null |
394 | : $this->getBodyValidator( $bodyType ); |
395 | |
396 | if ( $legacyBodyValidator && !$legacyBodyValidator instanceof NullBodyValidator ) { |
397 | $this->validatedBody = $restValidator->validateBody( $this->request, $this ); |
398 | } else { |
399 | // Allow type coercion if the request body is form data. |
400 | // For JSON requests, insist on proper types. |
401 | $enforceTypes = !in_array( |
402 | $this->request->getBodyType(), |
403 | RequestInterface::FORM_DATA_CONTENT_TYPES |
404 | ); |
405 | |
406 | $this->validatedBody = $restValidator->validateBodyParams( |
407 | $this->getBodyParamSettings(), |
408 | $enforceTypes |
409 | ); |
410 | |
411 | // If there is a body, check if it contains extra fields. |
412 | if ( $this->getRequest()->hasBody() ) { |
413 | $this->detectExtraneousBodyFields( $restValidator ); |
414 | } |
415 | } |
416 | |
417 | $this->postValidationSetup(); |
418 | } |
419 | |
420 | /** |
421 | * Subclasses may override this to disable or modify checks for extraneous |
422 | * body fields. |
423 | * |
424 | * @since 1.42 |
425 | * @stable to override |
426 | * @param Validator $restValidator |
427 | * @throws HttpException On validation failure. |
428 | */ |
429 | protected function detectExtraneousBodyFields( Validator $restValidator ) { |
430 | $parsedBody = $this->getRequest()->getParsedBody(); |
431 | |
432 | if ( !$parsedBody ) { |
433 | // nothing to do |
434 | return; |
435 | } |
436 | |
437 | $restValidator->detectExtraneousBodyFields( |
438 | $this->getBodyParamSettings(), |
439 | $parsedBody |
440 | ); |
441 | } |
442 | |
443 | /** |
444 | * Check the session (and session provider) |
445 | * @throws HttpException on failed check |
446 | * @internal |
447 | */ |
448 | public function checkSession() { |
449 | if ( !$this->session->getProvider()->safeAgainstCsrf() ) { |
450 | if ( $this->requireSafeAgainstCsrf() ) { |
451 | throw new LocalizedHttpException( |
452 | new MessageValue( 'rest-requires-safe-against-csrf' ), |
453 | 400 |
454 | ); |
455 | } |
456 | } elseif ( !empty( $this->validatedBody['token'] ) ) { |
457 | throw new LocalizedHttpException( |
458 | new MessageValue( 'rest-extraneous-csrf-token' ), |
459 | 400 |
460 | ); |
461 | } |
462 | } |
463 | |
464 | /** |
465 | * Get a JsonLocalizer object. |
466 | * |
467 | * @return JsonLocalizer |
468 | */ |
469 | protected function getJsonLocalizer(): JsonLocalizer { |
470 | Assert::precondition( |
471 | $this->responseFactory !== null, |
472 | 'getJsonLocalizer() must not be called before initServices()' |
473 | ); |
474 | |
475 | if ( $this->jsonLocalizer === null ) { |
476 | $this->jsonLocalizer = new JsonLocalizer( $this->responseFactory ); |
477 | } |
478 | |
479 | return $this->jsonLocalizer; |
480 | } |
481 | |
482 | /** |
483 | * Get a ConditionalHeaderUtil object. |
484 | * |
485 | * On the first call to this method, the object will be initialized with |
486 | * validator values by calling getETag(), getLastModified() and |
487 | * hasRepresentation(). |
488 | * |
489 | * @return ConditionalHeaderUtil |
490 | */ |
491 | protected function getConditionalHeaderUtil() { |
492 | if ( $this->conditionalHeaderUtil === null ) { |
493 | $this->conditionalHeaderUtil = new ConditionalHeaderUtil; |
494 | |
495 | // NOTE: It would be nicer to have Handler implement a |
496 | // ConditionalHeaderValues interface that defines methods that |
497 | // ConditionalHeaderUtil can call. But the relevant methods already |
498 | // exist in Handler as protected and stable to override. |
499 | // We can't make them public without breaking all subclasses that |
500 | // override them. So we pass closures for now. |
501 | $this->conditionalHeaderUtil->setValidators( |
502 | fn () => $this->getETag(), |
503 | fn () => $this->getLastModified(), |
504 | fn () => $this->hasRepresentation() |
505 | ); |
506 | } |
507 | return $this->conditionalHeaderUtil; |
508 | } |
509 | |
510 | /** |
511 | * Check the conditional request headers and generate a response if appropriate. |
512 | * This is called by the Router before execute() and may be overridden. |
513 | * |
514 | * @stable to override |
515 | * |
516 | * @return ResponseInterface|null |
517 | */ |
518 | public function checkPreconditions() { |
519 | $status = $this->getConditionalHeaderUtil()->checkPreconditions( $this->getRequest() ); |
520 | if ( $status ) { |
521 | $response = $this->getResponseFactory()->create(); |
522 | $response->setStatus( $status ); |
523 | $this->applyConditionalResponseHeaders( $response ); |
524 | return $response; |
525 | } |
526 | |
527 | return null; |
528 | } |
529 | |
530 | /** |
531 | * Apply verifier headers to the response, per RFC 7231 §7.2. |
532 | * This is called after execute() returns. |
533 | * |
534 | * For GET and HEAD requests, the default behavior is to set the ETag and |
535 | * Last-Modified headers based on the values returned by getETag() and |
536 | * getLastModified() when they were called before execute() was run. |
537 | * |
538 | * Other request methods are assumed to be state-changing, so no headers |
539 | * will be set by default. |
540 | * |
541 | * This may be overridden to modify the verifier headers sent in the response. |
542 | * However, handlers that modify the resource's state would typically just |
543 | * set the ETag and Last-Modified headers in the execute() method. |
544 | * |
545 | * @stable to override |
546 | * |
547 | * @param ResponseInterface $response |
548 | */ |
549 | public function applyConditionalResponseHeaders( ResponseInterface $response ) { |
550 | $method = $this->getRequest()->getMethod(); |
551 | if ( $method === 'GET' || $method === 'HEAD' ) { |
552 | $this->getConditionalHeaderUtil()->applyResponseHeaders( $response ); |
553 | } |
554 | } |
555 | |
556 | /** |
557 | * Apply cache control to enforce privacy. |
558 | */ |
559 | public function applyCacheControl( ResponseInterface $response ) { |
560 | // NOTE: keep this consistent with the logic in OutputPage::sendCacheControl |
561 | |
562 | // If the response sets cookies, it must not be cached in proxies. |
563 | // If there's an active cookie-based session (logged-in user or anonymous user with |
564 | // session-scoped cookies), it is not safe to cache either, as the session manager may set |
565 | // cookies in the response, or the response itself may vary on user-specific variables, |
566 | // for example on private wikis where the 'read' permission is restricted. (T264631) |
567 | if ( $response->getHeaderLine( 'Set-Cookie' ) || $this->getSession()->isPersistent() ) { |
568 | $response->setHeader( 'Cache-Control', 'private,must-revalidate,s-maxage=0' ); |
569 | } |
570 | |
571 | if ( !$response->getHeaderLine( 'Cache-Control' ) ) { |
572 | $rqMethod = $this->getRequest()->getMethod(); |
573 | if ( $rqMethod !== 'GET' && $rqMethod !== 'HEAD' ) { |
574 | // Responses to requests other than GET or HEAD should not be cacheable by default. |
575 | $response->setHeader( 'Cache-Control', 'private,no-cache,s-maxage=0' ); |
576 | } |
577 | } |
578 | } |
579 | |
580 | /** |
581 | * Fetch ParamValidator settings for parameters |
582 | * |
583 | * Every setting must include self::PARAM_SOURCE to specify which part of |
584 | * the request is to contain the parameter. |
585 | * |
586 | * Can be used for the request body as well, by setting self::PARAM_SOURCE |
587 | * to "post". Note that the values of "post" parameters will be accessible |
588 | * through getValidatedParams(). "post" parameters are used with |
589 | * form data (application/x-www-form-urlencoded or multipart/form-data). |
590 | * |
591 | * For "query" parameters, a PARAM_REQUIRED setting of "false" means the caller |
592 | * does not have to supply the parameter. For "path" parameters, the path matcher will always |
593 | * require the caller to supply all path parameters for a route, regardless of the |
594 | * PARAM_REQUIRED setting. However, "path" parameters may be specified in getParamSettings() |
595 | * as non-required to indicate that the handler services multiple routes, some of which may |
596 | * not supply the parameter. |
597 | * |
598 | * @stable to override |
599 | * |
600 | * @return array[] Associative array mapping parameter names to |
601 | * ParamValidator settings arrays |
602 | */ |
603 | public function getParamSettings() { |
604 | return []; |
605 | } |
606 | |
607 | /** |
608 | * Fetch ParamValidator settings for body fields. Parameters defined |
609 | * by this method are used to validate the request body. The parameter |
610 | * values will become available through getValidatedBody(). |
611 | * |
612 | * Subclasses may override this method to specify what fields they support |
613 | * in the request body. All parameter settings returned by this method must |
614 | * have self::PARAM_SOURCE set to 'body'. |
615 | * |
616 | * @return array[] |
617 | */ |
618 | public function getBodyParamSettings(): array { |
619 | return []; |
620 | } |
621 | |
622 | /** |
623 | * Returns an OpenAPI Operation Object specification structure as an associative array. |
624 | * |
625 | * @see https://swagger.io/specification/#operation-object |
626 | * |
627 | * By default, this will contain information about the supported parameters, as well as |
628 | * the response for status 200. |
629 | * |
630 | * Subclasses may override this to provide additional information. |
631 | * |
632 | * @since 1.42 |
633 | * @stable to override |
634 | * |
635 | * @param string $method The HTTP method to produce a spec for ("get", "post", etc). |
636 | * Useful for handlers that behave differently depending on the |
637 | * request method. |
638 | * |
639 | * @return array |
640 | */ |
641 | public function getOpenApiSpec( string $method ): array { |
642 | $parameters = []; |
643 | |
644 | $supportedPathParams = array_flip( $this->getSupportedPathParams() ); |
645 | |
646 | foreach ( $this->getParamSettings() as $name => $setting ) { |
647 | $source = $setting[ Validator::PARAM_SOURCE ] ?? ''; |
648 | |
649 | if ( $source !== 'query' && $source !== 'path' ) { |
650 | continue; |
651 | } |
652 | |
653 | if ( $source === 'path' && !isset( $supportedPathParams[$name] ) ) { |
654 | // Skip optional path param not used in the current path |
655 | continue; |
656 | } |
657 | |
658 | $setting[ Validator::PARAM_DESCRIPTION ] = $this->getJsonLocalizer()->localizeValue( |
659 | $setting, Validator::PARAM_DESCRIPTION, |
660 | ); |
661 | |
662 | $param = Validator::getParameterSpec( $name, $setting ); |
663 | |
664 | $parameters[] = $param; |
665 | } |
666 | |
667 | $spec = [ |
668 | 'parameters' => $parameters, |
669 | 'responses' => $this->generateResponseSpec( $method ), |
670 | ]; |
671 | |
672 | if ( !in_array( $method, RequestInterface::NO_BODY_METHODS ) ) { |
673 | $requestBody = $this->getRequestSpec( $method ); |
674 | if ( $requestBody ) { |
675 | $spec['requestBody'] = $requestBody; |
676 | } |
677 | } |
678 | |
679 | // TODO: Allow additional information about parameters and responses to |
680 | // be provided in the route definition. |
681 | $spec += $this->openApiSpec; |
682 | |
683 | return $spec; |
684 | } |
685 | |
686 | /** |
687 | * Returns an OpenAPI Request Body Object specification structure as an associative array. |
688 | * |
689 | * @see https://swagger.io/specification/#request-body-object |
690 | * |
691 | * This is based on the getBodyParamSettings() and getSupportedRequestTypes(). |
692 | * |
693 | * Subclasses may override this to provide additional information about the |
694 | * structure of responses, or to add support for additional mediaTypes. |
695 | * |
696 | * @stable to override getBodySchema() to generate a schema for each |
697 | * supported media type as returned by getSupportedBodyTypes(). |
698 | * |
699 | * @param string $method |
700 | * |
701 | * @return ?array |
702 | */ |
703 | protected function getRequestSpec( string $method ): ?array { |
704 | $mediaTypes = []; |
705 | |
706 | foreach ( $this->getSupportedRequestTypes() as $type ) { |
707 | $schema = $this->getRequestBodySchema( $type ); |
708 | |
709 | if ( $schema ) { |
710 | $mediaTypes[$type] = [ 'schema' => $schema ]; |
711 | } |
712 | } |
713 | |
714 | if ( !$mediaTypes ) { |
715 | return null; |
716 | } |
717 | |
718 | return [ |
719 | // TODO: some DELETE handlers may require a body that contains a token |
720 | // FIXME: check if there are required body params! |
721 | 'required' => in_array( $method, RequestInterface::BODY_METHODS ), |
722 | 'content' => $mediaTypes |
723 | ]; |
724 | } |
725 | |
726 | /** |
727 | * Returns a content schema per the OpenAPI spec. |
728 | * @see https://swagger.io/specification/#schema-object |
729 | * |
730 | * Per default, this provides schemas for JSON requests and form data, based |
731 | * on the parameter declarations returned by getParamSettings(). |
732 | * |
733 | * Subclasses may override this to provide additional information about the |
734 | * structure of responses, or to add support for additional mediaTypes. |
735 | * |
736 | * @stable to override |
737 | * @return array |
738 | */ |
739 | protected function getRequestBodySchema( string $mediaType ): array { |
740 | if ( $mediaType === RequestInterface::FORM_URLENCODED_CONTENT_TYPE ) { |
741 | $allowedSources = [ 'body', 'post' ]; |
742 | } elseif ( $mediaType === RequestInterface::MULTIPART_FORM_DATA_CONTENT_TYPE ) { |
743 | $allowedSources = [ 'body', 'post' ]; |
744 | } else { |
745 | $allowedSources = [ 'body' ]; |
746 | } |
747 | |
748 | $paramSettings = $this->getBodyParamSettings(); |
749 | |
750 | $properties = []; |
751 | $required = []; |
752 | |
753 | foreach ( $paramSettings as $name => $settings ) { |
754 | $source = $settings[ Validator::PARAM_SOURCE ] ?? ''; |
755 | $isRequired = $settings[ ParamValidator::PARAM_REQUIRED ] ?? false; |
756 | |
757 | if ( !in_array( $source, $allowedSources ) ) { |
758 | // TODO: post parameters also work as body parameters... |
759 | continue; |
760 | } |
761 | |
762 | $properties[$name] = Validator::getParameterSchema( $settings ); |
763 | $properties[$name][self::OPENAPI_DESCRIPTION_KEY] = |
764 | $this->getJsonLocalizer()->localizeValue( $settings, Validator::PARAM_DESCRIPTION ) |
765 | ?? "$name parameter"; |
766 | |
767 | if ( $isRequired ) { |
768 | $required[] = $name; |
769 | } |
770 | } |
771 | |
772 | if ( !$properties ) { |
773 | return []; |
774 | } |
775 | |
776 | $schema = [ |
777 | 'type' => 'object', |
778 | 'properties' => $properties, |
779 | ]; |
780 | |
781 | if ( $required ) { |
782 | $schema['required'] = $required; |
783 | } |
784 | |
785 | return $schema; |
786 | } |
787 | |
788 | /** |
789 | * Returns an OpenAPI Schema Object specification structure as an associative array. |
790 | * |
791 | * @see https://swagger.io/specification/#schema-object |
792 | * |
793 | * Returns null by default. Subclasses that return a JSON response should |
794 | * implement this method to return a schema of the response body. |
795 | * |
796 | * @param string $method The HTTP method to produce a spec for ("get", "post", etc). |
797 | * |
798 | * @stable to override |
799 | * @return ?array |
800 | */ |
801 | protected function getResponseBodySchema( string $method ): ?array { |
802 | $file = $this->getResponseBodySchemaFileName( $method ); |
803 | return $file ? Module::loadJsonFile( $file ) : null; |
804 | } |
805 | |
806 | /** |
807 | * Returns the path and name of a JSON file containing an OpenAPI Schema Object |
808 | * specification structure. |
809 | * |
810 | * @see https://swagger.io/specification/#schema-object |
811 | * |
812 | * Returns null by default. Subclasses with a suitable JSON file should implement this method. |
813 | * |
814 | * @param string $method The HTTP method to produce a spec for ("get", "post", etc). |
815 | * |
816 | * @stable to override |
817 | * @since 1.43 |
818 | * @return ?string |
819 | */ |
820 | protected function getResponseBodySchemaFileName( string $method ): ?string { |
821 | return null; |
822 | } |
823 | |
824 | /** |
825 | * Returns an OpenAPI Responses Object specification structure as an associative array. |
826 | * |
827 | * @see https://swagger.io/specification/#responses-object |
828 | * |
829 | * By default, this will contain basic information response for status 200, 400, and 500. |
830 | * The getResponseBodySchema() method is used to determine the structure of the response for status 200. |
831 | * |
832 | * Subclasses may override this to provide additional information about the structure of responses. |
833 | * |
834 | * @param string $method The HTTP method to produce a spec for ("get", "post", etc). |
835 | * |
836 | * @stable to override |
837 | * @return array |
838 | */ |
839 | protected function generateResponseSpec( string $method ): array { |
840 | $ok = [ self::OPENAPI_DESCRIPTION_KEY => 'OK' ]; |
841 | |
842 | $bodySchema = $this->getResponseBodySchema( $method ); |
843 | |
844 | if ( $bodySchema ) { |
845 | $bodySchema = $this->getJsonLocalizer()->localizeJson( $bodySchema ); |
846 | $ok['content']['application/json']['schema'] = $bodySchema; |
847 | } |
848 | |
849 | // XXX: we should add info about redirects |
850 | return [ |
851 | '200' => $ok, |
852 | 'default' => [ '$ref' => '#/components/responses/GenericErrorResponse' ], |
853 | ]; |
854 | } |
855 | |
856 | /** |
857 | * Fetch the BodyValidator |
858 | * |
859 | * @deprecated since 1.43, return body properties from getBodyParamSettings(). |
860 | * Subclasses that need full control over body data parsing should override |
861 | * parseBodyData() or implement validation in the execute() method based on |
862 | * the unparsed body data returned by getRequest()->getBody(). |
863 | * |
864 | * @param string $contentType Content type of the request. |
865 | * @return BodyValidator A {@see NullBodyValidator} in this default implementation |
866 | * @throws HttpException It's possible to fail early here when e.g. $contentType is unsupported, |
867 | * or later when {@see BodyValidator::validateBody} is called |
868 | */ |
869 | public function getBodyValidator( $contentType ) { |
870 | // NOTE: When removing this method, also remove the BodyValidator interface and |
871 | // all classes implementing it! |
872 | return new NullBodyValidator(); |
873 | } |
874 | |
875 | /** |
876 | * Fetch the validated parameters. This must be called after validate() is |
877 | * called. During execute() is fine. |
878 | * |
879 | * @return array Array mapping parameter names to validated values |
880 | * @throws \RuntimeException If validate() has not been called |
881 | */ |
882 | public function getValidatedParams() { |
883 | if ( $this->validatedParams === null ) { |
884 | throw new \RuntimeException( 'getValidatedParams() called before validate()' ); |
885 | } |
886 | return $this->validatedParams; |
887 | } |
888 | |
889 | /** |
890 | * Fetch the validated body |
891 | * @return mixed|null Value returned by the body validator, or null if validate() was |
892 | * not called yet, validation failed, there was no body, or the body was form data. |
893 | */ |
894 | public function getValidatedBody() { |
895 | return $this->validatedBody; |
896 | } |
897 | |
898 | /** |
899 | * Returns the parsed body of the request. |
900 | * Should only be called if $request->hasBody() returns true. |
901 | * |
902 | * The default implementation handles application/x-www-form-urlencoded |
903 | * and multipart/form-data by calling $request->getPostParams(), |
904 | * if the list returned by getSupportedRequestTypes() includes these types. |
905 | * |
906 | * The default implementation handles application/json by parsing |
907 | * the body content as JSON. Only object structures (maps) are supported, |
908 | * other types will trigger an HttpException with status 400. |
909 | * |
910 | * Other content types will trigger a HttpException with status 415 per |
911 | * default. |
912 | * |
913 | * Subclasses may override this method to support parsing additional |
914 | * content types or to disallow content types by throwing an HttpException |
915 | * with status 415. Subclasses may also return null to indicate that they |
916 | * support reading the content, but intend to handle it as an unparsed |
917 | * stream in their implementation of the execute() method. |
918 | * |
919 | * Subclasses that override this method to support additional request types |
920 | * should also override getSupportedRequestTypes() to allow that support |
921 | * to be documented in the OpenAPI spec. |
922 | * |
923 | * @since 1.42 |
924 | * |
925 | * @throws HttpException If the content type is not supported or the content |
926 | * is malformed. |
927 | * |
928 | * @return array|null The body content represented as an associative array, |
929 | * or null if the request body is accepted unparsed. |
930 | */ |
931 | public function parseBodyData( RequestInterface $request ): ?array { |
932 | // Parse the body based on its content type |
933 | $contentType = $request->getBodyType(); |
934 | |
935 | // HACK: If the Handler uses a custom BodyValidator, the |
936 | // getBodyValidator() is also responsible for checking whether |
937 | // the content type is valid, and for parsing the body. |
938 | // See T359149. |
939 | // TODO: remove once no subclasses override getBodyValidator() anymore |
940 | $bodyValidator = $this->getBodyValidator( $contentType ?? 'unknown/unknown' ); |
941 | if ( !$bodyValidator instanceof NullBodyValidator ) { |
942 | // TODO: Trigger a deprecation warning. |
943 | return null; |
944 | } |
945 | |
946 | $supportedTypes = $this->getSupportedRequestTypes(); |
947 | if ( $contentType !== null && !in_array( $contentType, $supportedTypes ) ) { |
948 | throw new LocalizedHttpException( |
949 | new MessageValue( 'rest-unsupported-content-type', [ $contentType ] ), |
950 | 415 |
951 | ); |
952 | } |
953 | |
954 | // if it's supported and ends with "+json", we can probably parse it like a normal application/json request |
955 | $contentType = str_ends_with( $contentType ?? '', '+json' ) |
956 | ? RequestInterface::JSON_CONTENT_TYPE |
957 | : $contentType; |
958 | |
959 | switch ( $contentType ) { |
960 | case RequestInterface::FORM_URLENCODED_CONTENT_TYPE: |
961 | case RequestInterface::MULTIPART_FORM_DATA_CONTENT_TYPE: |
962 | $params = $request->getPostParams(); |
963 | foreach ( $params as $key => $value ) { |
964 | $params[ $key ] = UtfNormalValidator::cleanUp( $value ); |
965 | // TODO: Warn if normalization was applied |
966 | } |
967 | return $params; |
968 | case RequestInterface::JSON_CONTENT_TYPE: |
969 | $jsonStream = $request->getBody(); |
970 | $jsonString = (string)$jsonStream; |
971 | $normalizedJsonString = UtfNormalValidator::cleanUp( $jsonString ); |
972 | $parsedBody = json_decode( $normalizedJsonString, true ); |
973 | if ( !is_array( $parsedBody ) ) { |
974 | throw new LocalizedHttpException( |
975 | new MessageValue( |
976 | 'rest-json-body-parse-error', |
977 | [ 'not a valid JSON object' ] |
978 | ), |
979 | 400 |
980 | ); |
981 | } |
982 | // TODO: Warn if normalization was applied |
983 | return $parsedBody; |
984 | case null: |
985 | // Specifying no Content-Type is fine if the body is empty |
986 | if ( $request->getBody()->getSize() === 0 ) { |
987 | return null; |
988 | } |
989 | // no break, else fall through to the error below. |
990 | default: |
991 | throw new LocalizedHttpException( |
992 | new MessageValue( 'rest-unsupported-content-type', [ $contentType ?? '(null)' ] ), |
993 | 415 |
994 | ); |
995 | } |
996 | } |
997 | |
998 | /** |
999 | * Returns the content types that should be accepted by parseBodyData(). |
1000 | * |
1001 | * Subclasses that support request types other than application/json |
1002 | * should override this method. |
1003 | * |
1004 | * If "application/x-www-form-urlencoded" or "multipart/form-data" are |
1005 | * returned, parseBodyData() will use $request->getPostParams() to determine |
1006 | * the body data. |
1007 | * |
1008 | * @note The return value of this method is ignored for requests |
1009 | * using a method listed in Validator::NO_BODY_METHODS, |
1010 | * in particular for the GET method. |
1011 | * |
1012 | * @note for backwards compatibility, the default implementation of this |
1013 | * method will examine the parameter definitions returned by getParamSettings() |
1014 | * to see if any of the parameters are declared as "post" parameters. If this |
1015 | * is the case, support for "application/x-www-form-urlencoded" and |
1016 | * "multipart/form-data" is added. This may change in future releases. |
1017 | * It is preferred to use "body" parameters and override this method explicitly |
1018 | * when support for form data is desired. |
1019 | * |
1020 | * @stable to override |
1021 | * |
1022 | * @return string[] A list of content-types |
1023 | */ |
1024 | public function getSupportedRequestTypes(): array { |
1025 | $types = [ |
1026 | RequestInterface::JSON_CONTENT_TYPE |
1027 | ]; |
1028 | |
1029 | // TODO: remove this once "post" parameters are no longer supported! T362850 |
1030 | foreach ( $this->getParamSettings() as $settings ) { |
1031 | if ( ( $settings[self::PARAM_SOURCE] ?? null ) === 'post' ) { |
1032 | $types[] = RequestInterface::FORM_URLENCODED_CONTENT_TYPE; |
1033 | $types[] = RequestInterface::MULTIPART_FORM_DATA_CONTENT_TYPE; |
1034 | break; |
1035 | } |
1036 | } |
1037 | |
1038 | return $types; |
1039 | } |
1040 | |
1041 | /** |
1042 | * Get a HookContainer, for running extension hooks or for hook metadata. |
1043 | * |
1044 | * @since 1.35 |
1045 | * @return HookContainer |
1046 | */ |
1047 | protected function getHookContainer() { |
1048 | return $this->hookContainer; |
1049 | } |
1050 | |
1051 | /** |
1052 | * Get a HookRunner for running core hooks. |
1053 | * |
1054 | * @internal This is for use by core only. Hook interfaces may be removed |
1055 | * without notice. |
1056 | * @since 1.35 |
1057 | * @return HookRunner |
1058 | */ |
1059 | protected function getHookRunner() { |
1060 | return $this->hookRunner; |
1061 | } |
1062 | |
1063 | /** |
1064 | * The subclass should override this to provide the maximum last modified |
1065 | * timestamp of the requested resource. This is called before execute() in |
1066 | * order to decide whether to send a 304. If the request is going to |
1067 | * change the state of the resource, the time returned must represent |
1068 | * the last modification date before the change. In other words, it must |
1069 | * provide the timestamp of the entity that the change is going to be |
1070 | * applied to. |
1071 | * |
1072 | * For GET and HEAD requests, this value will automatically be included |
1073 | * in the response in the Last-Modified header. |
1074 | * |
1075 | * Handlers that modify the resource and want to return a Last-Modified |
1076 | * header representing the new state in the response should set the header |
1077 | * in the execute() method. |
1078 | * |
1079 | * See RFC 7231 §7.2 and RFC 7232 §2.3 for semantics. |
1080 | * |
1081 | * @stable to override |
1082 | * |
1083 | * @return string|int|float|DateTime|null |
1084 | */ |
1085 | protected function getLastModified() { |
1086 | return null; |
1087 | } |
1088 | |
1089 | /** |
1090 | * The subclass should override this to provide an ETag for the current |
1091 | * state of the requested resource. This is called before execute() in |
1092 | * order to decide whether to send a 304. If the request is going to |
1093 | * change the state of the resource, the ETag returned must represent |
1094 | * the state before the change. In other words, it must identify |
1095 | * the entity that the change is going to be applied to. |
1096 | * |
1097 | * For GET and HEAD requests, this ETag will also be included in the |
1098 | * response. |
1099 | * |
1100 | * Handlers that modify the resource and want to return an ETag |
1101 | * header representing the new state in the response should set the header |
1102 | * in the execute() method. However, note that responses to PUT requests |
1103 | * must not return an ETag unless the new content of the resource is exactly |
1104 | * the data that was sent by the client in the request body. |
1105 | * |
1106 | * This must be a complete ETag, including double quotes. |
1107 | * See RFC 7231 §7.2 and RFC 7232 §2.3 for semantics. |
1108 | * |
1109 | * This method should return null if the resource doesn't exist. It may also |
1110 | * return null if ETag semantics is not supported by the Handler. |
1111 | * |
1112 | * @stable to override |
1113 | * |
1114 | * @return string|null |
1115 | */ |
1116 | protected function getETag() { |
1117 | return null; |
1118 | } |
1119 | |
1120 | /** |
1121 | * The subclass should override this to indicate whether the resource |
1122 | * exists. This is used for wildcard validators, for example "If-Match: *" |
1123 | * fails if the resource does not exist. |
1124 | * |
1125 | * If this method returns null, the value returned by getETag() will be used |
1126 | * to determine whether the resource exists. |
1127 | * |
1128 | * In a state-changing request, the return value of this method should |
1129 | * reflect the state before the requested change is applied. |
1130 | * |
1131 | * @stable to override |
1132 | * |
1133 | * @return bool|null |
1134 | */ |
1135 | protected function hasRepresentation() { |
1136 | return null; |
1137 | } |
1138 | |
1139 | /** |
1140 | * Indicates whether this route requires read rights. |
1141 | * |
1142 | * The handler should override this if it does not need to read from the |
1143 | * wiki. This is uncommon, but may be useful for login and other account |
1144 | * management APIs. |
1145 | * |
1146 | * @stable to override |
1147 | * |
1148 | * @return bool |
1149 | */ |
1150 | public function needsReadAccess() { |
1151 | return true; |
1152 | } |
1153 | |
1154 | /** |
1155 | * Indicates whether this route requires write access to the wiki. |
1156 | * |
1157 | * Handlers may override this method to return false if and only if the operation they |
1158 | * implement is "safe" per RFC 7231 section 4.2.1. A handler's operation is "safe" if |
1159 | * it is essentially read-only, i.e. the client does not request nor expect any state |
1160 | * change that would be observable in the responses to future requests. |
1161 | * |
1162 | * Implementations of this method must always return the same value, regardless of the |
1163 | * parameters passed to the constructor or system state. |
1164 | * |
1165 | * Handlers for GET, HEAD, OPTIONS, and TRACE requests should each implement a "safe" |
1166 | * operation. Handlers of PUT and DELETE requests should each implement a non-"safe" |
1167 | * operation. Note that handlers of POST requests can implement a "safe" operation, |
1168 | * particularly in the case where large input parameters are required. |
1169 | * |
1170 | * The information provided by this method is used to perform basic authorization checks |
1171 | * and to determine whether cross-origin requests are safe. |
1172 | * |
1173 | * @stable to override |
1174 | * |
1175 | * @return bool |
1176 | */ |
1177 | public function needsWriteAccess() { |
1178 | return true; |
1179 | } |
1180 | |
1181 | /** |
1182 | * Indicates whether this route can be accessed only by session providers safe vs csrf |
1183 | * |
1184 | * The handler should override this if the route must only be accessed by session |
1185 | * providers that are safe against csrf. |
1186 | * |
1187 | * A return value of false does not necessarily mean the route is vulnerable to csrf attacks. |
1188 | * It means the route can be accessed by session providers that are not automatically safe |
1189 | * against csrf attacks, so the possibility of csrf attacks must be considered. |
1190 | * |
1191 | * @stable to override |
1192 | * |
1193 | * @return bool |
1194 | */ |
1195 | public function requireSafeAgainstCsrf() { |
1196 | return false; |
1197 | } |
1198 | |
1199 | /** |
1200 | * The handler can override this to do any necessary setup after the init functions |
1201 | * are called to inject dependencies. |
1202 | * |
1203 | * @stable to override |
1204 | * @throws HttpException if the handler does not accept the request for |
1205 | * some reason. |
1206 | */ |
1207 | protected function postInitSetup() { |
1208 | } |
1209 | |
1210 | /** |
1211 | * The handler can override this to do any necessary setup after validate() |
1212 | * has been called. This gives the handler an opportunity to do initialization |
1213 | * based on parameters before pre-execution calls like getLastModified() or getETag(). |
1214 | * |
1215 | * @stable to override |
1216 | * @since 1.36 |
1217 | */ |
1218 | protected function postValidationSetup() { |
1219 | } |
1220 | |
1221 | /** |
1222 | * Execute the handler. This is called after parameter validation. The |
1223 | * return value can either be a Response or any type accepted by |
1224 | * ResponseFactory::createFromReturnValue(). |
1225 | * |
1226 | * To automatically construct an error response, execute() should throw a |
1227 | * \MediaWiki\Rest\HttpException. Such exceptions will not be logged like |
1228 | * a normal exception. |
1229 | * |
1230 | * If execute() throws any other kind of exception, the exception will be |
1231 | * logged and a generic 500 error page will be shown. |
1232 | * |
1233 | * @stable to override |
1234 | * |
1235 | * @return mixed |
1236 | */ |
1237 | abstract public function execute(); |
1238 | } |