diff --git a/README.md b/README.md index d113314c..0106a97c 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ Event-driven, streaming plaintext HTTP and secure HTTPS server for [ReactPHP](ht * [Response](#response) * [Middleware](#middleware) * [RequestBodyBufferMiddleware](#requestbodybuffermiddleware) + * [RequestBodyParserMiddleware](#requestbodyparsermiddleware) * [Install](#install) * [Tests](#tests) * [License](#license) @@ -725,6 +726,39 @@ $middlewares = new MiddlewareRunner([ ]); ``` +#### RequestBodyParserMiddleware + +The `RequestBodyParserMiddleware` takes a fully buffered request body (generally from [`RequestBodyBufferMiddleware`](#requestbodybuffermiddleware)), +and parses the forms and uploaded files from the request body. + +Parsed submitted forms will be available from `$request->getParsedBody()` as array. For example the following submitted body: + +`bar[]=beer&bar[]=wine` + +Results in the following parsed body: + +```php +$parsedBody = [ + 'bar' => [ + 'beer', + 'wine', + ], +]; +``` + +Usage: + +```php +$middlewares = new MiddlewareRunner([ + new RequestBodyBufferMiddleware(16 * 1024 * 1024), // 16 MiB + new RequestBodyParserMiddleware(), + function (ServerRequestInterface $request, callable $next) { + // If any, parsed form fields are now available from $request->getParsedBody() + return new Response(200); + }, +]); +``` + ## Install The recommended way to install this library is [through Composer](http://getcomposer.org). diff --git a/src/Middleware/RequestBodyParserMiddleware.php b/src/Middleware/RequestBodyParserMiddleware.php new file mode 100644 index 00000000..5623e03f --- /dev/null +++ b/src/Middleware/RequestBodyParserMiddleware.php @@ -0,0 +1,27 @@ +getHeaderLine('Content-Type')); + + if ($type === 'application/x-www-form-urlencoded') { + return $next($this->parseFormUrlencoded($request)); + } + + return $next($request); + } + + private function parseFormUrlencoded(ServerRequestInterface $request) + { + $ret = array(); + parse_str((string)$request->getBody(), $ret); + + return $request->withParsedBody($ret); + } +} diff --git a/tests/Middleware/RequestBodyParserMiddlewareTest.php b/tests/Middleware/RequestBodyParserMiddlewareTest.php new file mode 100644 index 00000000..e4d2500c --- /dev/null +++ b/tests/Middleware/RequestBodyParserMiddlewareTest.php @@ -0,0 +1,136 @@ + 'application/x-www-form-urlencoded', + ), + 'hello=world' + ); + + /** @var ServerRequestInterface $parsedRequest */ + $parsedRequest = $middleware( + $request, + function (ServerRequestInterface $request) { + return $request; + } + ); + + $this->assertSame( + array('hello' => 'world'), + $parsedRequest->getParsedBody() + ); + $this->assertSame('hello=world', (string)$parsedRequest->getBody()); + } + + public function testFormUrlencodedParsingIgnoresCaseForHeadersButRespectsContentCase() + { + $middleware = new RequestBodyParserMiddleware(); + $request = new ServerRequest( + 'POST', + 'https://example.com/', + array( + 'CONTENT-TYPE' => 'APPLICATION/X-WWW-Form-URLEncoded', + ), + 'Hello=World' + ); + + /** @var ServerRequestInterface $parsedRequest */ + $parsedRequest = $middleware( + $request, + function (ServerRequestInterface $request) { + return $request; + } + ); + + $this->assertSame( + array('Hello' => 'World'), + $parsedRequest->getParsedBody() + ); + $this->assertSame('Hello=World', (string)$parsedRequest->getBody()); + } + + public function testFormUrlencodedParsingNestedStructure() + { + $middleware = new RequestBodyParserMiddleware(); + $request = new ServerRequest( + 'POST', + 'https://example.com/', + array( + 'Content-Type' => 'application/x-www-form-urlencoded', + ), + 'foo=bar&baz[]=cheese&bar[]=beer&bar[]=wine&market[fish]=salmon&market[meat][]=beef&market[meat][]=chicken&market[]=bazaar' + ); + + /** @var ServerRequestInterface $parsedRequest */ + $parsedRequest = $middleware( + $request, + function (ServerRequestInterface $request) { + return $request; + } + ); + + $this->assertSame( + array( + 'foo' => 'bar', + 'baz' => array( + 'cheese', + ), + 'bar' => array( + 'beer', + 'wine', + ), + 'market' => array( + 'fish' => 'salmon', + 'meat' => array( + 'beef', + 'chicken', + ), + 0 => 'bazaar', + ), + ), + $parsedRequest->getParsedBody() + ); + $this->assertSame('foo=bar&baz[]=cheese&bar[]=beer&bar[]=wine&market[fish]=salmon&market[meat][]=beef&market[meat][]=chicken&market[]=bazaar', (string)$parsedRequest->getBody()); + } + + public function testDoesNotParseJsonByDefault() + { + $middleware = new RequestBodyParserMiddleware(); + $request = new ServerRequest( + 'POST', + 'https://example.com/', + array( + 'Content-Type' => 'application/json', + ), + '{"hello":"world"}' + ); + + /** @var ServerRequestInterface $parsedRequest */ + $parsedRequest = $middleware( + $request, + function (ServerRequestInterface $request) { + return $request; + } + ); + + $this->assertSame( + null, + $parsedRequest->getParsedBody() + ); + $this->assertSame('{"hello":"world"}', (string)$parsedRequest->getBody()); + } +}