Skip to content

Commit 3495991

Browse files
Move reading of multipart response into try body (#19062)
1 parent 2c4057b commit 3495991

File tree

3 files changed

+61
-1
lines changed

3 files changed

+61
-1
lines changed

changelog.d/19062.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix a bug introduced in 1.111.0 where failed attempts to download authenticated remote media would not be handled correctly.

synapse/http/matrixfederationclient.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1755,6 +1755,7 @@ async def federation_get_file(
17551755
response, output_stream, boundary, expected_size + 1
17561756
)
17571757
deferred.addTimeout(self.default_timeout_seconds, self.reactor)
1758+
multipart_response = await make_deferred_yieldable(deferred)
17581759
except BodyExceededMaxSize:
17591760
msg = "Requested file is too large > %r bytes" % (expected_size,)
17601761
logger.warning(
@@ -1791,7 +1792,6 @@ async def federation_get_file(
17911792
)
17921793
raise
17931794

1794-
multipart_response = await make_deferred_yieldable(deferred)
17951795
if not multipart_response.url:
17961796
assert multipart_response.length is not None
17971797
length = multipart_response.length

tests/http/test_matrixfederationclient.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,65 @@ def test_authed_media_redirect_response(self) -> None:
414414
self.assertEqual(length, len(data))
415415
self.assertEqual(output_stream.getvalue(), data)
416416

417+
@override_config(
418+
{
419+
"federation": {
420+
# Set the timeout to a deterministic value, in case the defaults
421+
# change.
422+
"client_timeout": "10s",
423+
}
424+
}
425+
)
426+
def test_authed_media_timeout_reading_body(self) -> None:
427+
"""
428+
If the HTTP request is connected, but gets no response before being
429+
timed out, it'll give a RequestSendFailed with can_retry.
430+
431+
Regression test for https://github.com/element-hq/synapse/issues/19061
432+
"""
433+
limiter = Ratelimiter(
434+
store=self.hs.get_datastores().main,
435+
clock=self.clock,
436+
cfg=RatelimitSettings(key="", per_second=0.17, burst_count=1048576),
437+
)
438+
439+
output_stream = io.BytesIO()
440+
441+
d = defer.ensureDeferred(
442+
# timeout is set by `client_timeout`, which we override above.
443+
self.cl.federation_get_file(
444+
"testserv:8008", "path", output_stream, limiter, "127.0.0.1", 10000
445+
)
446+
)
447+
448+
self.pump()
449+
450+
conn = Mock()
451+
clients = self.reactor.tcpClients
452+
client = clients[0][2].buildProtocol(None)
453+
client.makeConnection(conn)
454+
455+
# Deferred does not have a result
456+
self.assertNoResult(d)
457+
458+
# Send it the HTTP response
459+
client.dataReceived(
460+
b"HTTP/1.1 200 OK\r\n"
461+
b"Server: Fake\r\n"
462+
# Set a large content length, prompting the federation client to
463+
# wait to receive the rest of the body.
464+
b"Content-Length: 1000\r\n"
465+
b"Content-Type: multipart/mixed; boundary=6067d4698f8d40a0a794ea7d7379d53a\r\n\r\n"
466+
)
467+
468+
# Push by enough to time it out
469+
self.reactor.advance(10.5)
470+
f = self.failureResultOf(d)
471+
472+
self.assertIsInstance(f.value, RequestSendFailed)
473+
self.assertTrue(f.value.can_retry)
474+
self.assertIsInstance(f.value.inner_exception, defer.TimeoutError)
475+
417476
@parameterized.expand(["get_json", "post_json", "delete_json", "put_json"])
418477
def test_timeout_reading_body(self, method_name: str) -> None:
419478
"""

0 commit comments

Comments
 (0)