MediaWiki  master
Validator.php
Go to the documentation of this file.
1 <?php
2 
4 
12 use Wikimedia\ObjectFactory\ObjectFactory;
24 
32 class Validator {
33 
35  private const TYPE_DEFS = [
36  'boolean' => [ 'class' => BooleanDef::class ],
37  'enum' => [ 'class' => EnumDef::class ],
38  'integer' => [ 'class' => IntegerDef::class ],
39  'float' => [ 'class' => FloatDef::class ],
40  'double' => [ 'class' => FloatDef::class ],
41  'NULL' => [
42  'class' => StringDef::class,
43  'args' => [ [
44  'allowEmptyWhenRequired' => true,
45  ] ],
46  ],
47  'password' => [ 'class' => PasswordDef::class ],
48  'string' => [ 'class' => StringDef::class ],
49  'timestamp' => [ 'class' => TimestampDef::class ],
50  'upload' => [ 'class' => UploadDef::class ],
51  'expiry' => [ 'class' => ExpiryDef::class ],
52  'title' => [
53  'class' => TitleDef::class,
54  'services' => [ 'TitleFactory' ],
55  ],
56  'user' => [
57  'class' => UserDef::class,
58  'services' => [ 'UserIdentityLookup', 'TitleParser', 'UserNameUtils' ]
59  ],
60  ];
61 
63  private const NO_BODY_METHODS = [ 'GET', 'HEAD' ];
64 
66  private const BODY_METHODS = [ 'POST', 'PUT' ];
67 
68  // NOTE: per RFC 7231 (https://www.rfc-editor.org/rfc/rfc7231#section-4.3.5), sending a body
69  // with the DELETE method "has no defined semantics". We allow it, as it is useful for
70  // passing the csrf token required by some authentication methods.
71 
73  private const FORM_DATA_CONTENT_TYPES = [
74  'application/x-www-form-urlencoded',
75  'multipart/form-data',
76  ];
77 
79  private $paramValidator;
80 
87  public function __construct(
88  ObjectFactory $objectFactory,
89  RequestInterface $request,
90  Authority $authority
91  ) {
92  $this->paramValidator = new ParamValidator(
93  new ParamValidatorCallbacks( $request, $authority ),
94  $objectFactory,
95  [
96  'typeDefs' => self::TYPE_DEFS,
97  ]
98  );
99  }
100 
107  public function validateParams( array $paramSettings ) {
108  $validatedParams = [];
109  foreach ( $paramSettings as $name => $settings ) {
110  try {
111  $validatedParams[$name] = $this->paramValidator->getValue( $name, $settings, [
112  'source' => $settings[Handler::PARAM_SOURCE] ?? 'unspecified',
113  ] );
114  } catch ( ValidationException $e ) {
115  throw new LocalizedHttpException( $e->getFailureMessage(), 400, [
116  'error' => 'parameter-validation-failed',
117  'name' => $e->getParamName(),
118  'value' => $e->getParamValue(),
119  'failureCode' => $e->getFailureMessage()->getCode(),
120  'failureData' => $e->getFailureMessage()->getData(),
121  ] );
122  }
123  }
124  return $validatedParams;
125  }
126 
139  public function validateBody( RequestInterface $request, Handler $handler ) {
140  $method = strtoupper( trim( $request->getMethod() ) );
141 
142  // If the method should never have a body, don't bother validating.
143  if ( in_array( $method, self::NO_BODY_METHODS, true ) ) {
144  return null;
145  }
146 
147  // Get the content type
148  [ $ct ] = explode( ';', $request->getHeaderLine( 'Content-Type' ), 2 );
149  $ct = strtolower( trim( $ct ) );
150  if ( $ct === '' ) {
151  // No Content-Type was supplied. RFC 7231 ยง 3.1.1.5 allows this, but since it's probably a
152  // client error let's return a 415. But don't 415 for unknown methods and an empty body.
153  if ( !in_array( $method, self::BODY_METHODS, true ) ) {
154  $body = $request->getBody();
155  $size = $body->getSize();
156  if ( $size === null ) {
157  // No size available. Try reading 1 byte.
158  if ( $body->isSeekable() ) {
159  $body->rewind();
160  }
161  $size = $body->read( 1 ) === '' ? 0 : 1;
162  }
163  if ( $size === 0 ) {
164  return null;
165  }
166  }
167  throw new HttpException( "A Content-Type header must be supplied with a request payload.", 415, [
168  'error' => 'no-content-type',
169  ] );
170  }
171 
172  // Form data is parsed into $_POST and $_FILES by PHP and from there is accessed as parameters,
173  // don't bother trying to handle these via BodyValidator too.
174  if ( in_array( $ct, self::FORM_DATA_CONTENT_TYPES, true ) ) {
175  return null;
176  }
177 
178  // Validate the body. BodyValidator throws an HttpException on failure.
179  return $handler->getBodyValidator( $ct )->validateBody( $request );
180  }
181 
182 }
Base class for REST route handlers.
Definition: Handler.php:20
getBodyValidator( $contentType)
Fetch the BodyValidator.
Definition: Handler.php:360
const PARAM_SOURCE
(string) ParamValidator constant to specify the source of the parameter.
Definition: Handler.php:29
This is the base exception class for non-fatal exceptions thrown from REST handlers.
Wrapper for ParamValidator.
Definition: Validator.php:32
validateParams(array $paramSettings)
Validate parameters.
Definition: Validator.php:107
validateBody(RequestInterface $request, Handler $handler)
Validate the body of a request.
Definition: Validator.php:139
__construct(ObjectFactory $objectFactory, RequestInterface $request, Authority $authority)
Definition: Validator.php:87
Service for formatting and validating API parameters.
Type definition for boolean types.
Definition: BooleanDef.php:23
Type definition for enumeration types.
Definition: EnumDef.php:32
Type definition for expiry timestamps.
Definition: ExpiryDef.php:17
Type definition for a floating-point type.
Definition: FloatDef.php:29
Type definition for integer types.
Definition: IntegerDef.php:23
Type definition for "password" types.
Definition: PasswordDef.php:16
Type definition for string types.
Definition: StringDef.php:24
Type definition for timestamp types.
Type definition for upload types.
Definition: UploadDef.php:34
Error reporting for ParamValidator.
getParamValue()
Fetch the parameter value that failed validation.
getFailureMessage()
Fetch the validation failure message.
getParamName()
Fetch the parameter name that failed validation.
This interface represents the authority associated the current execution context, such as a web reque...
Definition: Authority.php:37
A request interface similar to PSR-7's ServerRequestInterface.
getMethod()
Retrieves the HTTP method of the request.
getBody()
Gets the body of the message.
getHeaderLine( $name)
Retrieves a comma-separated string of the values for a single header.
Copyright (C) 2011-2020 Wikimedia Foundation and others.