Add config for serving main Page from the domain root
[lhc/web/wiklou.git] / includes / Rest / Validator / Validator.php
1 <?php
2
3 namespace MediaWiki\Rest\Validator;
4
5 use MediaWiki\Rest\Handler;
6 use MediaWiki\Rest\HttpException;
7 use MediaWiki\Rest\RequestInterface;
8 use User;
9 use Wikimedia\ObjectFactory;
10 use Wikimedia\ParamValidator\ParamValidator;
11 use Wikimedia\ParamValidator\TypeDef\BooleanDef;
12 use Wikimedia\ParamValidator\TypeDef\EnumDef;
13 use Wikimedia\ParamValidator\TypeDef\FloatDef;
14 use Wikimedia\ParamValidator\TypeDef\IntegerDef;
15 use Wikimedia\ParamValidator\TypeDef\PasswordDef;
16 use Wikimedia\ParamValidator\TypeDef\StringDef;
17 use Wikimedia\ParamValidator\TypeDef\TimestampDef;
18 use Wikimedia\ParamValidator\TypeDef\UploadDef;
19 use Wikimedia\ParamValidator\ValidationException;
20
21 /**
22 * Wrapper for ParamValidator
23 *
24 * It's intended to be used in the REST API classes by composition.
25 *
26 * @since 1.34
27 */
28 class Validator {
29
30 /** @var array Type defs for ParamValidator */
31 private static $typeDefs = [
32 'boolean' => [ 'class' => BooleanDef::class ],
33 'enum' => [ 'class' => EnumDef::class ],
34 'integer' => [ 'class' => IntegerDef::class ],
35 'float' => [ 'class' => FloatDef::class ],
36 'double' => [ 'class' => FloatDef::class ],
37 'NULL' => [
38 'class' => StringDef::class,
39 'args' => [ [
40 'allowEmptyWhenRequired' => true,
41 ] ],
42 ],
43 'password' => [ 'class' => PasswordDef::class ],
44 'string' => [ 'class' => StringDef::class ],
45 'timestamp' => [ 'class' => TimestampDef::class ],
46 'upload' => [ 'class' => UploadDef::class ],
47 ];
48
49 /** @var string[] HTTP request methods that we expect never to have a payload */
50 private static $noBodyMethods = [ 'GET', 'HEAD', 'DELETE' ];
51
52 /** @var string[] HTTP request methods that we expect always to have a payload */
53 private static $bodyMethods = [ 'POST', 'PUT' ];
54
55 /** @var string[] Content types handled via $_POST */
56 private static $formDataContentTypes = [
57 'application/x-www-form-urlencoded',
58 'multipart/form-data',
59 ];
60
61 /** @var ParamValidator */
62 private $paramValidator;
63
64 /**
65 * @internal
66 * @param ObjectFactory $objectFactory
67 * @param RequestInterface $request
68 * @param User $user
69 */
70 public function __construct(
71 ObjectFactory $objectFactory, RequestInterface $request, User $user
72 ) {
73 $this->paramValidator = new ParamValidator(
74 new ParamValidatorCallbacks( $request, $user ),
75 $objectFactory,
76 [
77 'typeDefs' => self::$typeDefs,
78 ]
79 );
80 }
81
82 /**
83 * Validate parameters
84 * @param array[] $paramSettings Parameter settings
85 * @return array Validated parameters
86 * @throws HttpException on validaton failure
87 */
88 public function validateParams( array $paramSettings ) {
89 $validatedParams = [];
90 foreach ( $paramSettings as $name => $settings ) {
91 try {
92 $validatedParams[$name] = $this->paramValidator->getValue( $name, $settings, [
93 'source' => $settings[Handler::PARAM_SOURCE] ?? 'unspecified',
94 ] );
95 } catch ( ValidationException $e ) {
96 throw new HttpException( 'Parameter validation failed', 400, [
97 'error' => 'parameter-validation-failed',
98 'name' => $e->getParamName(),
99 'value' => $e->getParamValue(),
100 'failureCode' => $e->getFailureCode(),
101 'failureData' => $e->getFailureData(),
102 ] );
103 }
104 }
105 return $validatedParams;
106 }
107
108 /**
109 * Validate the body of a request.
110 *
111 * This may return a data structure representing the parsed body. When used
112 * in the context of Handler::validateParams(), the returned value will be
113 * available to the handler via Handler::getValidatedBody().
114 *
115 * @param RequestInterface $request
116 * @param Handler $handler Used to call getBodyValidator()
117 * @return mixed May be null
118 * @throws HttpException on validation failure
119 */
120 public function validateBody( RequestInterface $request, Handler $handler ) {
121 $method = strtoupper( trim( $request->getMethod() ) );
122
123 // If the method should never have a body, don't bother validating.
124 if ( in_array( $method, self::$noBodyMethods, true ) ) {
125 return null;
126 }
127
128 // Get the content type
129 list( $ct ) = explode( ';', $request->getHeaderLine( 'Content-Type' ), 2 );
130 $ct = strtolower( trim( $ct ) );
131 if ( $ct === '' ) {
132 // No Content-Type was supplied. RFC 7231 ยง 3.1.1.5 allows this, but since it's probably a
133 // client error let's return a 415. But don't 415 for unknown methods and an empty body.
134 if ( !in_array( $method, self::$bodyMethods, true ) ) {
135 $body = $request->getBody();
136 $size = $body->getSize();
137 if ( $size === null ) {
138 // No size available. Try reading 1 byte.
139 if ( $body->isSeekable() ) {
140 $body->rewind();
141 }
142 $size = $body->read( 1 ) === '' ? 0 : 1;
143 }
144 if ( $size === 0 ) {
145 return null;
146 }
147 }
148 throw new HttpException( "A Content-Type header must be supplied with a request payload.", 415, [
149 'error' => 'no-content-type',
150 ] );
151 }
152
153 // Form data is parsed into $_POST and $_FILES by PHP and from there is accessed as parameters,
154 // don't bother trying to handle these via BodyValidator too.
155 if ( in_array( $ct, self::$formDataContentTypes, true ) ) {
156 return null;
157 }
158
159 // Validate the body. BodyValidator throws an HttpException on failure.
160 return $handler->getBodyValidator( $ct )->validateBody( $request );
161 }
162
163 }