Skip to content

Commit 64e0b90

Browse files
authored
Merge pull request #263 from WyriHaximus/254
RequestBodyParserMiddleware does not reject requests that exceed post_max_size
2 parents 6995dbe + 802ee34 commit 64e0b90

File tree

5 files changed

+99
-33
lines changed

5 files changed

+99
-33
lines changed

README.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -694,8 +694,14 @@ configuration.
694694
configuration in most cases.)
695695

696696
Any incoming request that has a request body that exceeds this limit will be
697-
rejected with a `413` (Request Entity Too Large) error message without calling
698-
the next middleware handlers.
697+
accepted, but its request body will be discarded (empty request body).
698+
This is done in order to avoid having to keep an incoming request with an
699+
excessive size (for example, think of a 2 GB file upload) in memory.
700+
This allows the next middleware handler to still handle this request, but it
701+
will see an empty request body.
702+
This is similar to PHP's default behavior, where the body will not be parsed
703+
if this limit is exceeded. However, unlike PHP's default behavior, the raw
704+
request body is not available via `php://input`.
699705

700706
The `RequestBodyBufferMiddleware` will buffer requests with bodies of known size
701707
(i.e. with `Content-Length` header specified) as well as requests with bodies of
@@ -782,6 +788,8 @@ See also [example #12](examples) for more details.
782788
handler as given in the example above.
783789
This previous middleware handler is also responsible for rejecting incoming
784790
requests that exceed allowed message sizes (such as big file uploads).
791+
The [`RequestBodyBufferMiddleware`](#requestbodybuffermiddleware) used above
792+
simply discards excessive request bodies, resulting in an empty body.
785793
If you use this middleware without buffering first, it will try to parse an
786794
empty (streaming) body and may thus assume an empty data structure.
787795
See also [`RequestBodyBufferMiddleware`](#requestbodybuffermiddleware) for

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"react/stream": "^1.0 || ^0.7.1",
1111
"react/promise": "^2.3 || ^1.2.1",
1212
"evenement/evenement": "^3.0 || ^2.0 || ^1.0",
13-
"react/promise-stream": "^1.0 || ^0.1.2"
13+
"react/promise-stream": "^1.1"
1414
},
1515
"autoload": {
1616
"psr-4": {

examples/12-upload.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@
119119

120120
// buffer and parse HTTP request body before running our request handler
121121
$server = new StreamingServer(new MiddlewareRunner(array(
122-
new RequestBodyBufferMiddleware(100000), // 100 KB max
122+
new RequestBodyBufferMiddleware(100000), // 100 KB max, ignore body otherwise
123123
new RequestBodyParserMiddleware(),
124124
$handler
125125
)));

src/Middleware/RequestBodyBufferMiddleware.php

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,11 @@
22

33
namespace React\Http\Middleware;
44

5+
use OverflowException;
56
use Psr\Http\Message\ServerRequestInterface;
6-
use React\Http\Response;
77
use React\Promise\Stream;
88
use React\Stream\ReadableStreamInterface;
99
use RingCentral\Psr7\BufferStream;
10-
use OverflowException;
1110

1211
final class RequestBodyBufferMiddleware
1312
{
@@ -32,25 +31,36 @@ public function __invoke(ServerRequestInterface $request, $stack)
3231
{
3332
$body = $request->getBody();
3433

35-
// request body of known size exceeding limit
36-
if ($body->getSize() > $this->sizeLimit) {
37-
return new Response(413, array('Content-Type' => 'text/plain'), 'Request body exceeds allowed limit');
38-
}
39-
34+
// skip if body is already buffered
4035
if (!$body instanceof ReadableStreamInterface) {
36+
// replace with empty buffer if size limit is exceeded
37+
if ($body->getSize() > $this->sizeLimit) {
38+
$request = $request->withBody(new BufferStream(0));
39+
}
40+
4141
return $stack($request);
4242
}
4343

44-
return Stream\buffer($body, $this->sizeLimit)->then(function ($buffer) use ($request, $stack) {
44+
// request body of known size exceeding limit
45+
$sizeLimit = $this->sizeLimit;
46+
if ($body->getSize() > $this->sizeLimit) {
47+
$sizeLimit = 0;
48+
}
49+
50+
return Stream\buffer($body, $sizeLimit)->then(function ($buffer) use ($request, $stack) {
4551
$stream = new BufferStream(strlen($buffer));
4652
$stream->write($buffer);
4753
$request = $request->withBody($stream);
4854

4955
return $stack($request);
50-
}, function($error) {
51-
// request body of unknown size exceeding limit during buffering
56+
}, function ($error) use ($stack, $request, $body) {
57+
// On buffer overflow keep the request body stream in,
58+
// but ignore the contents and wait for the close event
59+
// before passing the request on to the next middleware.
5260
if ($error instanceof OverflowException) {
53-
return new Response(413, array('Content-Type' => 'text/plain'), 'Request body exceeds allowed limit');
61+
return Stream\first($body, 'close')->then(function () use ($stack, $request) {
62+
return $stack($request);
63+
});
5464
}
5565

5666
throw $error;

tests/Middleware/RequestBodyBufferMiddlewareTest.php

Lines changed: 66 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,16 @@
22

33
namespace React\Tests\Http\Middleware;
44

5+
use Clue\React\Block;
56
use Psr\Http\Message\ServerRequestInterface;
67
use React\EventLoop\Factory;
78
use React\Http\Io\HttpBodyStream;
89
use React\Http\Io\ServerRequest;
910
use React\Http\Middleware\RequestBodyBufferMiddleware;
11+
use React\Http\Response;
1012
use React\Stream\ThroughStream;
1113
use React\Tests\Http\TestCase;
1214
use RingCentral\Psr7\BufferStream;
13-
use Clue\React\Block;
1415

1516
final class RequestBodyBufferMiddlewareTest extends TestCase
1617
{
@@ -37,6 +38,7 @@ function (ServerRequestInterface $request) use (&$exposedRequest) {
3738
$stream->write('world');
3839
$stream->end('!');
3940

41+
$this->assertSame(11, $exposedRequest->getBody()->getSize());
4042
$this->assertSame('helloworld!', $exposedRequest->getBody()->getContents());
4143
}
4244

@@ -62,13 +64,14 @@ function (ServerRequestInterface $request) use (&$exposedRequest) {
6264
}
6365
);
6466

67+
$this->assertSame($size, $exposedRequest->getBody()->getSize());
6568
$this->assertSame($body, $exposedRequest->getBody()->getContents());
6669
}
6770

68-
public function testExcessiveSizeImmediatelyReturnsError413ForKnownSize()
71+
public function testKnownExcessiveSizedBodyIsDisgardedTheRequestIsPassedDownToTheNextMiddleware()
6972
{
7073
$loop = Factory::create();
71-
74+
7275
$stream = new ThroughStream();
7376
$stream->end('aa');
7477
$serverRequest = new ServerRequest(
@@ -79,17 +82,40 @@ public function testExcessiveSizeImmediatelyReturnsError413ForKnownSize()
7982
);
8083

8184
$buffer = new RequestBodyBufferMiddleware(1);
82-
$response = $buffer(
85+
$response = Block\await($buffer(
8386
$serverRequest,
8487
function (ServerRequestInterface $request) {
85-
return $request;
88+
return new Response(200, array(), $request->getBody()->getContents());
8689
}
90+
), $loop);
91+
92+
$this->assertSame(200, $response->getStatusCode());
93+
$this->assertSame('', $response->getBody()->getContents());
94+
}
95+
96+
public function testAlreadyBufferedExceedingSizeResolvesImmediatelyWithEmptyBody()
97+
{
98+
$serverRequest = new ServerRequest(
99+
'GET',
100+
'https://example.com/',
101+
array(),
102+
'hello'
87103
);
88104

89-
$this->assertSame(413, $response->getStatusCode());
105+
$exposedRequest = null;
106+
$buffer = new RequestBodyBufferMiddleware(1);
107+
$buffer(
108+
$serverRequest,
109+
function (ServerRequestInterface $request) use (&$exposedRequest) {
110+
$exposedRequest = $request;
111+
}
112+
);
113+
114+
$this->assertSame(0, $exposedRequest->getBody()->getSize());
115+
$this->assertSame('', $exposedRequest->getBody()->getContents());
90116
}
91117

92-
public function testExcessiveSizeReturnsError413()
118+
public function testExcessiveSizeBodyIsDiscardedAndTheRequestIsPassedDownToTheNextMiddleware()
93119
{
94120
$loop = Factory::create();
95121

@@ -105,23 +131,19 @@ public function testExcessiveSizeReturnsError413()
105131
$promise = $buffer(
106132
$serverRequest,
107133
function (ServerRequestInterface $request) {
108-
return $request;
134+
return new Response(200, array(), $request->getBody()->getContents());
109135
}
110136
);
111137

112138
$stream->end('aa');
113139

114-
$exposedResponse = null;
115-
$promise->then(
116-
function($response) use (&$exposedResponse) {
117-
$exposedResponse = $response;
118-
},
140+
$exposedResponse = Block\await($promise->then(
141+
null,
119142
$this->expectCallableNever()
120-
);
121-
122-
$this->assertSame(413, $exposedResponse->getStatusCode());
143+
), $loop);
123144

124-
Block\await($promise, $loop);
145+
$this->assertSame(200, $exposedResponse->getStatusCode());
146+
$this->assertSame('', $exposedResponse->getBody()->getContents());
125147
}
126148

127149
/**
@@ -130,7 +152,7 @@ function($response) use (&$exposedResponse) {
130152
public function testBufferingErrorThrows()
131153
{
132154
$loop = Factory::create();
133-
155+
134156
$stream = new ThroughStream();
135157
$serverRequest = new ServerRequest(
136158
'GET',
@@ -151,4 +173,30 @@ function (ServerRequestInterface $request) {
151173

152174
Block\await($promise, $loop);
153175
}
176+
177+
public function testFullBodyStreamedBeforeCallingNextMiddleware()
178+
{
179+
$promiseResolved = false;
180+
$middleware = new RequestBodyBufferMiddleware(3);
181+
$stream = new ThroughStream();
182+
$serverRequest = new ServerRequest(
183+
'GET',
184+
'https://example.com/',
185+
array(),
186+
new HttpBodyStream($stream, null)
187+
);
188+
189+
$middleware($serverRequest, function () {
190+
return new Response();
191+
})->then(function () use (&$promiseResolved) {
192+
$promiseResolved = true;
193+
});
194+
195+
$stream->write('aaa');
196+
$this->assertFalse($promiseResolved);
197+
$stream->write('aaa');
198+
$this->assertFalse($promiseResolved);
199+
$stream->end('aaa');
200+
$this->assertTrue($promiseResolved);
201+
}
154202
}

0 commit comments

Comments
 (0)