Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RequestBodyBufferMiddleware should not reject requests that exceed post_max_size #254

Closed
clue opened this issue Nov 21, 2017 · 10 comments · Fixed by #263
Closed

RequestBodyBufferMiddleware should not reject requests that exceed post_max_size #254

clue opened this issue Nov 21, 2017 · 10 comments · Fixed by #263
Milestone

Comments

@clue
Copy link
Member

clue commented Nov 21, 2017

If you try out example 12 (introduced via #252), you can see that the server currently rejects the request with a rather ugly error page if you exceed the maximum size of 100 KiB. PHP's default behavior is actually to just ignore the request body and still process things normally otherwise. This also allows the consumer to create a custom error message.

See http://php.net/manual/en/ini.core.php#ini.post-max-size

@WyriHaximus
Copy link
Member

Actually this happens in RequestBodyBufferMiddleware, I'll craft a PR for updating that middleware instead 👍

@WyriHaximus
Copy link
Member

PR up at #263

@clue clue changed the title RequestBodyParserMiddleware should not reject requests that exceed post_max_size RequestBodyBufferMiddleware should not reject requests that exceed post_max_size Nov 23, 2017
@jsor jsor closed this as completed in #263 Dec 8, 2017
@acasademont
Copy link

Hi! I stumbled upon this today and I was expecting a 413 on the server. What would be the best way to detect that the request has exceeded the buffer size compared to a legit empty body? By comparing the request size header with the actual body length? Thx!

@clue
Copy link
Member Author

clue commented Sep 23, 2020

@acasademont You're right, the current middleware implementation doesn't reject excessive requests by design (to be in line with PHP's implementation). Instead, excessive requests will be forwarded like requests with an empty request body.

I don't think we currently expose a difference between empty and excessive requests, but I can totally see some value in this. Have you tried implementing this as a custom middleware or do you think the existing middleware implementation needs some addition?

@acasademont
Copy link

@clue Nopes, I just increased the buffer size :p

On PHP you get a warning if the request body size exceeds the post_max_size setting (https://github.com/php/php-src/blob/1b2ec73c1d9175769c3ad4dd40825546851287bc/main/SAPI.c#L253), here it's a bit silent, I spent quite a lot of time debugging one of our big requests and to fully understand what was going on and why the body was blank.

So if we copy PHP behaviour we should copy it with the warning too (I'm not sure how a warning could be implemented here 😅), if not, I'd rather go back to the 413 error to avoid confusion.

Also, I was a bit confused too by the fact that post_max_size is not honored, it's capped at 64KB.

@clue
Copy link
Member Author

clue commented Sep 23, 2020

So if we copy PHP behaviour we should copy it with the warning too (I'm not sure how a warning could be implemented here sweat_smile), if not, I'd rather go back to the 413 error to avoid confusion.

It's an interesting thought, but this contradicts the original suggestion in this ticket and the implementation in #263, so I'm not sure about the BC implications here. If you feel this is worth investigating, perhaps let's create a follow-up ticket instead of resurrecting this old one?

Also, I was a bit confused too by the fact that post_max_size is not honored, it's capped at 64KB.

Yeah, this is a recent change introduced via #371 which also includes the rationale for this. I agree this can be confusing and this is definitely something we can improve. Let me know if you have any thoughts on this.

@mmoreram
Copy link

mmoreram commented Feb 24, 2021

I found this issue, looking for exactly the same as @acasademont

I understand the purpose for bypassing an empty body when this is overflown, but by doing that, it's clearly impossible to detect when that happens. In my case, I'd like to limit the body size, and report a 413 when this body is overflown. So I still think that, and after reading all your comments, that allowing custom behavior would be nice.

About the implementation, an optional callback in the constructor would be enough. This callback would be executed when the limit is reached, instead of passing to the next middleware as exposed

new RequestBodyBufferMiddleware($this->maxBufferSize, function() {
    throw new Exception('buffer limit reached - 413');
})

By default, this value would be null, exposing the same behavior as it's at the moment.

if ($error instanceof OverflowException) {
    if ($this->limitReachedCallback) {
        $this->limitReachedCallback();
    }
    
    return Stream\first($body, 'close')->then(function () use ($stack, $request) {
        return $stack($request);
    });
}

This would be an elegant way of exposing customization.

@gitneko
Copy link

gitneko commented Feb 24, 2021

@mmoreram I'm not sure where you're coming from that it's impossible to detect. There are two possible "return types" that the request body buffer middleware has - either the request body is a PSR stream or a ReactPHP readable stream. If it's a ReactPHP readable stream, then the request body was clearly too large and you can throw HTTP 413.

All you need to do is to provide a middleware after the request body buffer middleware which checks whether the request body is a ReactPHP readable stream - and if it is, the buffer middleware didn't buffer because the body is too large.

return Stream\buffer($body, $sizeLimit)->then(function ($buffer) use ($request, $stack) {
$request = $request->withBody(new BufferedBody($buffer));
return $stack($request);
}, function ($error) use ($stack, $request, $body) {
// On buffer overflow keep the request body stream in,
// but ignore the contents and wait for the close event
// before passing the request on to the next middleware.
if ($error instanceof OverflowException) {
return Stream\first($body, 'close')->then(function () use ($stack, $request) {
return $stack($request);
});
}

My ReactPHP HTTP server is a bit complex and I've been doing some things differently (including invoking middlewares), but I've been using this "middleware" after the request body buffer middleware for months without issues.

function (ServerRequestInterface $request) {
    // The buffer middleware leaves the stream intact on overflow
    if($request->getBody() instanceof ReadableStreamInterface) {
        throw new RequestEntityTooLarge();
    }
    
    return $request;
}

@clue
Copy link
Member Author

clue commented Feb 25, 2021

@gitneko That's an interesting find! Note that you're relying on undocumented behavior here, the fact that this currently seems to return a specific body instance is undocumented and may change in the future.

@mmoreram I agree that this can be useful for some use cases. Afaict this should also be possible from within a dedicated middleware handler similar to what @gitneko suggested, so I don't think this requires an addition to the existing middleware implementation.

Incoming requests may or may not have a body (an empty body equals "no body"). If the request contains a body, it will have a Content-Length header (most likely) or a Transfer-Encoding: chunked header (much less likely).

In the former case, you know the exact size of bytes, so it's trivial to reject a request that exceeds your threshold with a 413 (Payload Too Large) error response. In the latter case, there's no way to know the size in advance, so the only way to detect you're over your threshold is by buffering or streaming and counting. In this case (and because it's a feature that's very rarely used), it's also quite common to use a 411 (Length Required) error response.

@mmoreram
Copy link

Thanks @clue and @gitneko for your explanation :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants