Respond with useful error codes when Content-Length header/s are invalid (#19212)

Related to https://github.com/element-hq/synapse/issues/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
`handleContentChunk` but 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
https://github.com/element-hq/synapse/issues/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:
- multiple `Content-Length` headers
- invalid `Content-Length` header value
- request content size being larger than the `Content-Length` value

### Pull Request Checklist

<!-- Please read
https://element-hq.github.io/synapse/latest/development/contributing_guide.html
before submitting your pull request -->

* [X] Pull request is based on the develop branch
* [X] Pull request includes a [changelog
file](https://element-hq.github.io/synapse/latest/development/contributing_guide.html#changelog).
The entry should:
- Be a short description of your change which makes sense to users.
"Fixed a bug that prevented receiving messages from other servers."
instead of "Moved X method from `EventStore` to `EventWorkerStore`.".
  - Use markdown where necessary, mostly for `code blocks`.
  - End with either a period (.) or an exclamation mark (!).
  - Start with a capital letter.
- Feel free to credit yourself, by adding a sentence "Contributed by
@github_username." or "Contributed by [Your Name]." to the end of the
entry.
* [X] [Code
style](https://element-hq.github.io/synapse/latest/code_style.html) is
correct (run the
[linters](https://element-hq.github.io/synapse/latest/development/contributing_guide.html#run-the-linters))

---------

Co-authored-by: Eric Eastwood <erice@element.io>
This commit is contained in:
Devon Hudson
2025-12-08 21:39:18 +00:00
committed by GitHub
parent 09fd2645c2
commit 8b0083cad9
10 changed files with 336 additions and 50 deletions

View File

@@ -212,6 +212,66 @@ class JsonResourceTests(unittest.TestCase):
self.assertEqual(channel.code, 200)
self.assertNotIn("body", channel.result)
def test_content_larger_than_content_length(self) -> None:
"""
HTTP requests with content size exceeding Content-Length should be rejected with 400.
"""
def _callback(
request: SynapseRequest, **kwargs: object
) -> tuple[int, JsonDict]:
return 200, {}
res = JsonResource(self.homeserver)
res.register_paths(
"POST", [re.compile("^/_matrix/foo$")], _callback, "test_servlet"
)
channel = make_request(
self.reactor,
FakeSite(res, self.reactor),
b"POST",
b"/_matrix/foo",
{},
# Set the `Content-Length` value to be smaller than the actual content size
custom_headers=[("Content-Length", "1")],
# The request should disconnect early so don't await the result
await_result=False,
)
self.reactor.advance(0.1)
self.assertEqual(channel.code, 400)
def test_content_smaller_than_content_length(self) -> None:
"""
HTTP requests with content size smaller than Content-Length should be rejected with 400.
"""
def _callback(
request: SynapseRequest, **kwargs: object
) -> tuple[int, JsonDict]:
return 200, {}
res = JsonResource(self.homeserver)
res.register_paths(
"POST", [re.compile("^/_matrix/foo$")], _callback, "test_servlet"
)
channel = make_request(
self.reactor,
FakeSite(res, self.reactor),
b"POST",
b"/_matrix/foo",
{},
# Set the `Content-Length` value to be larger than the actual content size
custom_headers=[("Content-Length", "10")],
# The request should disconnect early so don't await the result
await_result=False,
)
self.reactor.advance(0.1)
self.assertEqual(channel.code, 400)
class OptionsResourceTests(unittest.TestCase):
def setUp(self) -> None: