diff --git a/src/Http/HttpRequestFactory.php b/src/Http/HttpRequestFactory.php index 51037ab..5eed065 100644 --- a/src/Http/HttpRequestFactory.php +++ b/src/Http/HttpRequestFactory.php @@ -61,7 +61,9 @@ public function createRequestFromGlobals() { */ private function isJsonContent(array $serverParameters): bool { $contentType = $serverParameters[self::SERVER_PARAMETER_CONTENT_TYPE] ?? null; - return $contentType === self::CONTENT_TYPE_JSON; + $contentType = strtolower(trim(explode(';', $contentType)[0] ?? '')); + + return $contentType === self::CONTENT_TYPE_JSON || str_ends_with($contentType, '+json'); } /** diff --git a/tests/Unit/Http/HttpRequestFactoryTest.php b/tests/Unit/Http/HttpRequestFactoryTest.php new file mode 100644 index 0000000..d7d7cc2 --- /dev/null +++ b/tests/Unit/Http/HttpRequestFactoryTest.php @@ -0,0 +1,120 @@ + 'POST', + 'CONTENT_TYPE' => $contentType, + 'REQUEST_URI' => '/api/v1/articles', + 'HTTP_HOST' => 'localhost', + 'SERVER_PROTOCOL' => 'HTTP/1.1' + ]); + $_REQUEST = []; + $_FILES = []; + + $request = $masterFactory->createRequestFromGlobals(); + + $this->assertInstanceOf(HttpPostRequest::class, $request); + + $parsedBody = $request->getParsedBody(); + $this->assertEquals('articles', $parsedBody->data->type); + $this->assertEquals('1', $parsedBody->data->id); + } + + /** + * @dataProvider badContentTypeDataProvider + */ + public function testItIgnoresWeirdContentTypes(string $contentType): void + { + $masterFactory = new MasterFactory(new Configuration([], __DIR__)); + + PhpInputStreamMock::$content = '{"data": {"type": "articles", "id": "1"}}'; + + $_SERVER = array_merge($_SERVER, [ + 'REQUEST_METHOD' => 'POST', + 'CONTENT_TYPE' => $contentType, + 'REQUEST_URI' => '/api/v1/articles', + 'HTTP_HOST' => 'localhost', + 'SERVER_PROTOCOL' => 'HTTP/1.1' + ]); + $_REQUEST = []; + $_FILES = []; + + $request = $masterFactory->createRequestFromGlobals(); + + $this->assertInstanceOf(HttpPostRequest::class, $request); + + $parsedBody = $request->getParsedBody(); + $this->assertInstanceOf(PhpInputStream::class, $parsedBody); + $this->assertSame('{"data": {"type": "articles", "id": "1"}}', $parsedBody->getContents()); + } + + protected function setUp(): void + { + parent::setUp(); + + $this->originalServer = $_SERVER; + $this->originalRequest = $_REQUEST; + $this->originalFiles = $_FILES; + + // autoload-dev missing + require_once __DIR__ . '/../Stream/PhpInputStreamMock.php'; + + stream_wrapper_unregister('php'); + stream_wrapper_register('php', PhpInputStreamMock::class); + } + + protected function tearDown(): void + { + $_SERVER = $this->originalServer; + $_REQUEST = $this->originalRequest; + $_FILES = $this->originalFiles; + + stream_wrapper_restore('php'); + PhpInputStreamMock::$content = ''; + + parent::tearDown(); + } + + public static function contentTypeDataProvider(): array + { + return [ + 'standard json' => ['application/json'], + 'json api' => ['application/vnd.api+json'], + 'json + charset' => ['application/json; charset=utf-8'], + 'json api + charset' => ['application/vnd.api+json; charset=utf-8'], + ]; + } + + public static function badContentTypeDataProvider(): array + { + return [ + [''], + ['adf'], + ]; + } +} \ No newline at end of file diff --git a/tests/Unit/Stream/PhpInputStreamMock.php b/tests/Unit/Stream/PhpInputStreamMock.php new file mode 100644 index 0000000..cc79b74 --- /dev/null +++ b/tests/Unit/Stream/PhpInputStreamMock.php @@ -0,0 +1,37 @@ +position, $count); + $this->position += strlen($ret); + return $ret; + } + + public function stream_eof(): bool + { + return $this->position >= strlen(self::$content); + } + + public function stream_stat(): array + { + return []; + } + + public function stream_set_option(): bool + { + return true; + } +} \ No newline at end of file