Respond with useful error codes when Content-Length header/s are invalid#19212
Respond with useful error codes when Content-Length header/s are invalid#19212
Content-Length header/s are invalid#19212Conversation
| # we should get a 415 | ||
| self.assertRegex(transport.value().decode(), r"^HTTP/1\.1 415 ") | ||
|
|
||
| def test_content_length_too_large(self) -> None: |
There was a problem hiding this comment.
(test hasn't been added yet)
synapse/http/site.py
Outdated
| command.decode("ascii", errors="replace"), | ||
| self.get_redacted_uri(), | ||
| ) | ||
| self.loseConnection() |
There was a problem hiding this comment.
I think it would be nice to respond with an actual HTTP status code and a good error explaining why.
In the case of multiple Content-Length headers -> 400 Bad Request
In the case of no Content-Length header -> 411 Length Required
There was a problem hiding this comment.
Yeah, in the case of multiple headers we can do that.
In the case of no header, we can't jump to that conclusion so easily. It depends on the HTTP version. Required in HTTP 1, not required if there is a Transfer-Encoding: chunked header in the HTTP 1.1, and not required in HTTP 2.
https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Length
We could bake in all these additional checks if you think it doesn't blow this PR up. I'm not sure if twisted already handles these cases or not.
There was a problem hiding this comment.
I have added functionality for ensuring the Content-Length header is valid when present.
I don't think it is worth adding in further validation to enforce HTTP spec compliance. Doing so adds complexity without addressing any specific issues, and becomes a maintenance burden trying to account for all the HTTP variations and future spec changes.
Also, not all HTTP clients strictly follow the HTTP spec/s so adding in strict spec compliance checks could break existing deployments for no real reason.
| # we should get a 415 | ||
| self.assertRegex(transport.value().decode(), r"^HTTP/1\.1 415 ") | ||
|
|
||
| def test_content_length_too_large(self) -> None: |
There was a problem hiding this comment.
I did a bunch of testing and Synapse (Twisted) handles this by truncating the HTTP request at the specified size of the
Content-Length. So the request will go through to the server correctly but the body will be silently truncated. The rest of the bytes are dropped.
Perhaps some request with a basic JSON body that will be cut-off and we expect M_NOT_JSON 🤷
Co-authored-by: Eric Eastwood <erice@element.io>
Content-Length header/s are invalid
synapse/http/site.py
Outdated
| self.get_redacted_uri(), | ||
| ) | ||
| error_response_json = { | ||
| "errcode": Codes.UNKNOWN, |
There was a problem hiding this comment.
Optional: We could have our own errcode for the known scenarios.
IO.ELEMENT.INVALID_CONTENT_LENGTH, etc
There was a problem hiding this comment.
I think using M_UNKNOWN here is more in the spirit of the spec https://spec.matrix.org/v1.16/client-server-api/#standard-error-response
We are trying to provide an HTTP error here since this is an HTTP issue, not a matrix endpoint issue. Assigning it a custom matrix errcode carries with it the implication that this is a matrix error.
From the spec:
When encountering the error code M_UNKNOWN, clients should prefer the HTTP status code as a more reliable reference for what the issue was.
if the client were to receive an error code of M_UNKNOWN with a 400 Bad Request, the client should assume that the request being made was invalid
There was a problem hiding this comment.
I think that works. But I also think we could do better ⏩
By the same logic, M_TOO_LARGE would be inappropriate here then. Bottom-line is that we just need better error codes in the spec and that could start with our own namespaced versions which can be parsed by downstream code.
There was a problem hiding this comment.
M_TOO_LARGE happens due to an internal Synapse limit based on the size of transactions from the matrix spec (or config setting). So it's easy to argue that it's a matrix based error here, not a generic HTTP error.
synapse/http/site.py
Outdated
| return | ||
|
|
||
| if content_length is not None and self.content is not None: | ||
| if content_length < self.content.tell(): |
There was a problem hiding this comment.
What about the opposite case: content_length > self.content.tell()?
There was a problem hiding this comment.
It wasn't clear to me from the spec that this is always an error.
But we can treat it that way and be spec compliant. So I have added it for completeness.
There was a problem hiding this comment.
I also refactored things now that we have so many comparisons to reduce duplication.
tests/test_server.py
Outdated
| self.assertEqual(channel.code, 200) | ||
| self.assertNotIn("body", channel.result) | ||
|
|
||
| def test_content_too_large(self) -> None: |
There was a problem hiding this comment.
Test for the opposite case?
Co-authored-by: Eric Eastwood <erice@element.io>
Co-authored-by: Eric Eastwood <erice@element.io>
Co-authored-by: Eric Eastwood <erice@element.io>
Related to #17035, when Synapse receives a request that is larger than the maximum size allowed, it aborts the connection without ever sending back a HTTP response.
I dug into our usage of twisted and how best to try and report such an error and this is what I came up with.
It would be ideal to be able to report the status from within
handleContentChunkbut that is called too early on in the twisted http handling code, before things have been setup enough to be able to properly write a response.I tested this change out locally (both with C-S and S-S apis) and they do receive a 413 response now in addition to the connection being closed.
Hopefully this will aid in being able to quickly detect when #17035 is occurring as the current situation makes it very hard to narrow things down to that specific issue without making a lot of assumptions.
This PR also responds with more meaningful error codes now in the case of:
Content-LengthheadersContent-Lengthheader valueContent-LengthvaluePull Request Checklist
EventStoretoEventWorkerStore.".code blocks.