diff options
Diffstat (limited to 'includes/Rest/Validator/Validator.php')
-rw-r--r-- | includes/Rest/Validator/Validator.php | 163 |
1 files changed, 163 insertions, 0 deletions
diff --git a/includes/Rest/Validator/Validator.php b/includes/Rest/Validator/Validator.php new file mode 100644 index 000000000000..cee1cdb35973 --- /dev/null +++ b/includes/Rest/Validator/Validator.php @@ -0,0 +1,163 @@ +<?php + +namespace MediaWiki\Rest\Validator; + +use MediaWiki\Rest\Handler; +use MediaWiki\Rest\HttpException; +use MediaWiki\Rest\RequestInterface; +use User; +use Wikimedia\ObjectFactory; +use Wikimedia\ParamValidator\ParamValidator; +use Wikimedia\ParamValidator\TypeDef\BooleanDef; +use Wikimedia\ParamValidator\TypeDef\EnumDef; +use Wikimedia\ParamValidator\TypeDef\FloatDef; +use Wikimedia\ParamValidator\TypeDef\IntegerDef; +use Wikimedia\ParamValidator\TypeDef\PasswordDef; +use Wikimedia\ParamValidator\TypeDef\StringDef; +use Wikimedia\ParamValidator\TypeDef\TimestampDef; +use Wikimedia\ParamValidator\TypeDef\UploadDef; +use Wikimedia\ParamValidator\ValidationException; + +/** + * Wrapper for ParamValidator + * + * It's intended to be used in the REST API classes by composition. + * + * @since 1.34 + */ +class Validator { + + /** @var array Type defs for ParamValidator */ + private static $typeDefs = [ + 'boolean' => [ 'class' => BooleanDef::class ], + 'enum' => [ 'class' => EnumDef::class ], + 'integer' => [ 'class' => IntegerDef::class ], + 'float' => [ 'class' => FloatDef::class ], + 'double' => [ 'class' => FloatDef::class ], + 'NULL' => [ + 'class' => StringDef::class, + 'args' => [ [ + 'allowEmptyWhenRequired' => true, + ] ], + ], + 'password' => [ 'class' => PasswordDef::class ], + 'string' => [ 'class' => StringDef::class ], + 'timestamp' => [ 'class' => TimestampDef::class ], + 'upload' => [ 'class' => UploadDef::class ], + ]; + + /** @var string[] HTTP request methods that we expect never to have a payload */ + private static $noBodyMethods = [ 'GET', 'HEAD', 'DELETE' ]; + + /** @var string[] HTTP request methods that we expect always to have a payload */ + private static $bodyMethods = [ 'POST', 'PUT' ]; + + /** @var string[] Content types handled via $_POST */ + private static $formDataContentTypes = [ + 'application/x-www-form-urlencoded', + 'multipart/form-data', + ]; + + /** @var ParamValidator */ + private $paramValidator; + + /** + * @internal + * @param ObjectFactory $objectFactory + * @param RequestInterface $request + * @param User $user + */ + public function __construct( + ObjectFactory $objectFactory, RequestInterface $request, User $user + ) { + $this->paramValidator = new ParamValidator( + new ParamValidatorCallbacks( $request, $user ), + $objectFactory, + [ + 'typeDefs' => self::$typeDefs, + ] + ); + } + + /** + * Validate parameters + * @param array[] $paramSettings Parameter settings + * @return array Validated parameters + * @throws HttpException on validaton failure + */ + public function validateParams( array $paramSettings ) { + $validatedParams = []; + foreach ( $paramSettings as $name => $settings ) { + try { + $validatedParams[$name] = $this->paramValidator->getValue( $name, $settings, [ + 'source' => $settings[Handler::PARAM_SOURCE] ?? 'unspecified', + ] ); + } catch ( ValidationException $e ) { + throw new HttpException( 'Parameter validation failed', 400, [ + 'error' => 'parameter-validation-failed', + 'name' => $e->getParamName(), + 'value' => $e->getParamValue(), + 'failureCode' => $e->getFailureCode(), + 'failureData' => $e->getFailureData(), + ] ); + } + } + return $validatedParams; + } + + /** + * Validate the body of a request. + * + * This may return a data structure representing the parsed body. When used + * in the context of Handler::validateParams(), the returned value will be + * available to the handler via Handler::getValidatedBody(). + * + * @param RequestInterface $request + * @param Handler $handler Used to call getBodyValidator() + * @return mixed May be null + * @throws HttpException on validation failure + */ + public function validateBody( RequestInterface $request, Handler $handler ) { + $method = strtoupper( trim( $request->getMethod() ) ); + + // If the method should never have a body, don't bother validating. + if ( in_array( $method, self::$noBodyMethods, true ) ) { + return null; + } + + // Get the content type + list( $ct ) = explode( ';', $request->getHeaderLine( 'Content-Type' ), 2 ); + $ct = strtolower( trim( $ct ) ); + if ( $ct === '' ) { + // No Content-Type was supplied. RFC 7231 ยง 3.1.1.5 allows this, but since it's probably a + // client error let's return a 415. But don't 415 for unknown methods and an empty body. + if ( !in_array( $method, self::$bodyMethods, true ) ) { + $body = $request->getBody(); + $size = $body->getSize(); + if ( $size === null ) { + // No size available. Try reading 1 byte. + if ( $body->isSeekable() ) { + $body->rewind(); + } + $size = $body->read( 1 ) === '' ? 0 : 1; + } + if ( $size === 0 ) { + return null; + } + } + throw new HttpException( "A Content-Type header must be supplied with a request payload.", 415, [ + 'error' => 'no-content-type', + ] ); + } + + // Form data is parsed into $_POST and $_FILES by PHP and from there is accessed as parameters, + // don't bother trying to handle these via BodyValidator too. + if ( in_array( $ct, self::$formDataContentTypes, true ) ) { + return null; + } + + // Validate the body. BodyValidator throws an HttpException on failure. + return $handler->getBodyValidator( $ct )->validateBody( $request ); + } + +} |