Skip to content

Commit

Permalink
#44 add trivial ServerRequest "parsed body" validation
Browse files Browse the repository at this point in the history
  • Loading branch information
lezhnev74 committed Jul 18, 2019
1 parent 1abe2ba commit 9df7b45
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 4 deletions.
75 changes: 71 additions & 4 deletions src/PSR7/Validators/BodyValidator/MultipartValidation.php
Expand Up @@ -16,6 +16,8 @@
use OpenAPIValidation\Schema\Exception\TypeMismatch;
use OpenAPIValidation\Schema\SchemaValidator;
use Psr\Http\Message\MessageInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Riverline\MultiPartParser\Converters\PSR7;
use Riverline\MultiPartParser\StreamedPart;
use RuntimeException;
Expand Down Expand Up @@ -50,10 +52,26 @@ private function validateMultipart(OperationAddress $addr, MessageInterface $mes
throw TypeMismatch::becauseTypeDoesNotMatch('object', $schema->type);
}

if (($message instanceof ResponseInterface) || $message->getBody()->getSize()) {
$this->validatePlainBodyMultipart($addr, $message, $mediaTypeSpecs, $contentType, $schema);
} elseif ($message instanceof ServerRequestInterface) {
$this->validateServerRequestMultipart($addr, $message, $mediaTypeSpecs, $contentType, $schema);
}
}

/**
* @param MediaType[] $mediaTypeSpecs
*/
private function validatePlainBodyMultipart(
OperationAddress $addr,
MessageInterface $message,
array $mediaTypeSpecs,
string $contentType,
Schema $schema
) : void {
// 1. Parse message body
$document = PSR7::convert($message);

// 2. Validate bodies of each part
$body = $this->parseMultipartData($addr, $document);

$validator = new SchemaValidator($this->detectValidationStrategy($message));
Expand All @@ -63,7 +81,7 @@ private function validateMultipart(OperationAddress $addr, MessageInterface $mes
throw InvalidBody::becauseBodyDoesNotMatchSchema($contentType, $addr, $e);
}

// 3. Validate specified part encodings and headers
// 2. Validate specified part encodings and headers
// @see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#encoding-object
// The encoding object SHALL only apply to requestBody objects when the media type is multipart or application/x-www-form-urlencoded.
// An encoding attribute is introduced to give you control over the serialization of parts of multipart request bodies.
Expand All @@ -80,7 +98,7 @@ private function validateMultipart(OperationAddress $addr, MessageInterface $mes
}

foreach ($parts as $part) {
// 3.1 parts encoding
// 2.1 parts encoding
$partContentType = $part->getHeader(self::HEADER_CONTENT_TYPE);
$encodingContentType = $this->detectEncondingContentType($encoding, $part, $schema->properties[$partName]);
if (strpos($encodingContentType, '*') === false) {
Expand All @@ -104,7 +122,7 @@ private function validateMultipart(OperationAddress $addr, MessageInterface $mes
}
}

// 3.2. parts headers
// 2.2. parts headers
foreach ($encoding->headers as $headerName => $headerSpec) {
/** @var Header $headerSpec */
$headerSchema = $headerSpec->schema;
Expand Down Expand Up @@ -184,4 +202,53 @@ private function detectEncondingContentType(Encoding $encoding, StreamedPart $pa

return $contentType;
}

/**
* ServerRequest does not have a plain HTTP body which we can parse. Instead, it has a parsed values in
* getParsedBody() (POST data) and getUploadedFiles (FILES data)
*
* @param MediaType[] $mediaTypeSpecs
*/
private function validateServerRequestMultipart(
OperationAddress $addr,
ServerRequestInterface $message,
array $mediaTypeSpecs,
string $contentType,
Schema $schema
) : void {
// add parsed simple values
$body = $message->getParsedBody();

// add files as binary strings
foreach ($message->getUploadedFiles() as $name => $file) {
$body[$name] = '~~~binary~~~';
}

$validator = new SchemaValidator($this->detectValidationStrategy($message));
try {
$validator->validate($body, $schema);
} catch (SchemaMismatch $e) {
throw InvalidBody::becauseBodyDoesNotMatchSchema($contentType, $addr, $e);
}

// 2. Validate specified part encodings and headers
// @see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#encoding-object
// The encoding object SHALL only apply to requestBody objects when the media type is multipart or application/x-www-form-urlencoded.
// An encoding attribute is introduced to give you control over the serialization of parts of multipart request bodies.
// This attribute is only applicable to "multipart" and "application/x-www-form-urlencoded" request bodies.
$encodings = $mediaTypeSpecs[$contentType]->encoding;

foreach ($encodings as $partName => $encoding) {
if (! isset($body[$partName])) {
throw new RuntimeException(sprintf('Specified body part %s is not found', $partName));
}
$part = $body[$partName];

// 2.1 parts encoding
// todo values are parsed already by php core...

// 2.2. parts headers
// todo headers are parsed already by webserver...
}
}
}
28 changes: 28 additions & 0 deletions tests/PSR7/Validators/BodyValidatorTest.php
Expand Up @@ -5,10 +5,13 @@
namespace OpenAPIValidationTests\PSR7\Validators;

use GuzzleHttp\Psr7\ServerRequest;
use GuzzleHttp\Psr7\UploadedFile;
use GuzzleHttp\Psr7\Uri;
use OpenAPIValidation\PSR7\Exception\Validation\InvalidBody;
use OpenAPIValidation\PSR7\Exception\Validation\InvalidHeaders;
use OpenAPIValidation\PSR7\ValidatorBuilder;
use PHPUnit\Framework\TestCase;
use function filesize;
use function GuzzleHttp\Psr7\parse_request;

class BodyValidatorTest extends TestCase
Expand Down Expand Up @@ -280,4 +283,29 @@ public function testValidateFormUrlencodedGreen(string $message) : void
$validator->validate($serverRequest);
$this->addToAssertionCount(1);
}

public function testValidateMultipartServerRequestGreen() : void
{
$specFile = __DIR__ . '/../../stubs/multipart.yaml';

$imagePath = __DIR__ . '/../../stubs/image.jpg';
$imageSize = filesize($imagePath);

$serverRequest = (new ServerRequest('post', new Uri('/multipart')))
->withHeader('Content-Type', 'multipart/form-data')
->withParsedBody([
'id' => 'bc8e1430-a963-11e9-a2a3-2a2ae2dbcce4',
'address' => [
'street' => 'Some street',
'city' => 'some city',
],
])
->withUploadedFiles([
'profileImage' => new UploadedFile($imagePath, $imageSize, 0),
]);

$validator = (new ValidatorBuilder())->fromYamlFile($specFile)->getServerRequestValidator();
$validator->validate($serverRequest);
$this->addToAssertionCount(1);
}
}
4 changes: 4 additions & 0 deletions tests/stubs/multipart.yaml
Expand Up @@ -16,6 +16,10 @@ paths:
multipart/form-data:
schema:
type: object
required:
- id
- address
- profileImage
properties:
id: # Part 1 (string value)
type: string
Expand Down

0 comments on commit 9df7b45

Please sign in to comment.