Skip to content
This repository was archived by the owner on Apr 26, 2024. It is now read-only.

Commit 148fe58

Browse files
authored
Do not break URL previews if an image is unreachable. (#12950)
Avoid breaking a URL preview completely if the chosen image 404s or is unreachable for some other reason (e.g. DNS).
1 parent 1acc897 commit 148fe58

File tree

3 files changed

+53
-6
lines changed

3 files changed

+53
-6
lines changed

changelog.d/12950.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix a long-standing bug where a URL preview would break if the image failed to download.

synapse/rest/media/v1/preview_url_resource.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -586,20 +586,33 @@ async def _precache_image_url(
586586
og: The Open Graph dictionary. This is modified with image information.
587587
"""
588588
# If there's no image or it is blank, there's nothing to do.
589-
if "og:image" not in og or not og["og:image"]:
589+
if "og:image" not in og:
590+
return
591+
592+
# Remove the raw image URL, this will be replaced with an MXC URL, if successful.
593+
image_url = og.pop("og:image")
594+
if not image_url:
590595
return
591596

592597
# The image URL from the HTML might be relative to the previewed page,
593598
# convert it to an URL which can be requested directly.
594-
image_url = og["og:image"]
595599
url_parts = urlparse(image_url)
596600
if url_parts.scheme != "data":
597601
image_url = urljoin(media_info.uri, image_url)
598602

599603
# FIXME: it might be cleaner to use the same flow as the main /preview_url
600604
# request itself and benefit from the same caching etc. But for now we
601605
# just rely on the caching on the master request to speed things up.
602-
image_info = await self._handle_url(image_url, user, allow_data_urls=True)
606+
try:
607+
image_info = await self._handle_url(image_url, user, allow_data_urls=True)
608+
except Exception as e:
609+
# Pre-caching the image failed, don't block the entire URL preview.
610+
logger.warning(
611+
"Pre-caching image failed during URL preview: %s errored with %s",
612+
image_url,
613+
e,
614+
)
615+
return
603616

604617
if _is_media(image_info.media_type):
605618
# TODO: make sure we don't choke on white-on-transparent images
@@ -611,13 +624,11 @@ async def _precache_image_url(
611624
og["og:image:width"] = dims["width"]
612625
og["og:image:height"] = dims["height"]
613626
else:
614-
logger.warning("Couldn't get dims for %s", og["og:image"])
627+
logger.warning("Couldn't get dims for %s", image_url)
615628

616629
og["og:image"] = f"mxc://{self.server_name}/{image_info.filesystem_id}"
617630
og["og:image:type"] = image_info.media_type
618631
og["matrix:image:size"] = image_info.media_length
619-
else:
620-
del og["og:image"]
621632

622633
async def _handle_oembed_response(
623634
self, url: str, media_info: MediaInfo, expiration_ms: int

tests/rest/media/v1/test_url_preview.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,41 @@ def test_accept_language_config_option(self) -> None:
656656
server.data,
657657
)
658658

659+
def test_nonexistent_image(self) -> None:
660+
"""If the preview image doesn't exist, ensure some data is returned."""
661+
self.lookups["matrix.org"] = [(IPv4Address, "10.1.2.3")]
662+
663+
end_content = (
664+
b"""<html><body><img src="http://cdn.matrix.org/foo.jpg"></body></html>"""
665+
)
666+
667+
channel = self.make_request(
668+
"GET",
669+
"preview_url?url=http://matrix.org",
670+
shorthand=False,
671+
await_result=False,
672+
)
673+
self.pump()
674+
675+
client = self.reactor.tcpClients[0][2].buildProtocol(None)
676+
server = AccumulatingProtocol()
677+
server.makeConnection(FakeTransport(client, self.reactor))
678+
client.makeConnection(FakeTransport(server, self.reactor))
679+
client.dataReceived(
680+
(
681+
b"HTTP/1.0 200 OK\r\nContent-Length: %d\r\n"
682+
b'Content-Type: text/html; charset="utf8"\r\n\r\n'
683+
)
684+
% (len(end_content),)
685+
+ end_content
686+
)
687+
688+
self.pump()
689+
self.assertEqual(channel.code, 200)
690+
691+
# The image should not be in the result.
692+
self.assertNotIn("og:image", channel.json_body)
693+
659694
def test_data_url(self) -> None:
660695
"""
661696
Requesting to preview a data URL is not supported.

0 commit comments

Comments
 (0)