Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 7 additions & 12 deletions lib/model/content.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1413,22 +1413,17 @@ class _ZulipContentParser {
final String srcUrl;
final String? thumbnailUrl;
if (src.startsWith('/user_uploads/thumbnail/')) {
// For why we recognize this as the thumbnail form, see discussion:
// https://chat.zulip.org/#narrow/channel/412-api-documentation/topic/documenting.20inline.20images/near/2279872
srcUrl = href;
thumbnailUrl = src;
} else if (src.startsWith('/external_content/')
|| src.startsWith('https://uploads.zulipusercontent.net/')) {
// This image preview uses camo, which still happens on current servers
// (2025-10); discussion:
// https://chat.zulip.org/#narrow/channel/412-api-documentation/topic/documenting.20inline.20images/near/2279235
srcUrl = src;
thumbnailUrl = null;
} else if (href == src) {
// Probably generated by a server before the thumbnailing feature landed:
// https://chat.zulip.org/#narrow/channel/412-api-documentation/topic/documenting.20inline.20images/near/2279234
} else {
// Known cases this handles:
// - `src` starts with CAMO_URI, a server variable (e.g. on Zulip Cloud
// it's "https://uploads.zulipusercontent.net/" in 2025-10).
// - `src` matches `href`, e.g. from pre-thumbnailing servers.
srcUrl = src;
thumbnailUrl = null;
} else {
return UnimplementedBlockContentNode(htmlNode: divElement);
}

double? originalWidth, originalHeight;
Expand Down
38 changes: 35 additions & 3 deletions test/model/content_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -785,8 +785,8 @@ class ContentExample {
]),
]);

static const imagePreviewSingleExternal = ContentExample(
'single image preview external',
static const imagePreviewSingleExternal1 = ContentExample(
'single image preview external, src starts with /external_content',
// https://chat.zulip.org/#narrow/stream/7-test-here/topic/Greg/near/1892172
"https://upload.wikimedia.org/wikipedia/commons/7/78/Verregende_bloem_van_een_Helenium_%27El_Dorado%27._22-07-2023._%28d.j.b%29.jpg",
'<div class="message_inline_image">'
Expand All @@ -799,6 +799,36 @@ class ContentExample {
]),
]);

static const imagePreviewSingleExternal2 = ContentExample(
'single image preview external, src starts with https://uploads.zulipusercontent.net/',
// Zulip Cloud has CAMO_URI = "https://uploads.zulipusercontent.net/";
// this example is from a DM on a closed Zulip Cloud org.
"https://upload.wikimedia.org/wikipedia/commons/7/78/Verregende_bloem_van_een_Helenium_%27El_Dorado%27._22-07-2023._%28d.j.b%29.jpg",
'<div class="message_inline_image">'
'<a href="https://upload.wikimedia.org/wikipedia/commons/7/78/Verregende_bloem_van_een_Helenium_%27El_Dorado%27._22-07-2023._%28d.j.b%29.jpg">'
'<img src="https://uploads.zulipusercontent.net/99742b0f992be15283c428dd42f3b9f5db138d69/68747470733a2f2f75706c6f61642e77696b696d656469612e6f72672f77696b6970656469612f636f6d6d6f6e732f372f37382f566572726567656e64655f626c6f656d5f76616e5f65656e5f48656c656e69756d5f253237456c5f446f7261646f2532372e5f32322d30372d323032332e5f253238642e6a2e622532392e6a7067"></a></div>', [
ImagePreviewNodeList([
ImagePreviewNode(srcUrl: 'https://uploads.zulipusercontent.net/99742b0f992be15283c428dd42f3b9f5db138d69/68747470733a2f2f75706c6f61642e77696b696d656469612e6f72672f77696b6970656469612f636f6d6d6f6e732f372f37382f566572726567656e64655f626c6f656d5f76616e5f65656e5f48656c656e69756d5f253237456c5f446f7261646f2532372e5f32322d30372d323032332e5f253238642e6a2e622532392e6a7067',
thumbnailUrl: null, loading: false,
originalWidth: null, originalHeight: null),
]),
]);

static const imagePreviewSingleExternal3 = ContentExample(
'single image preview external, src starts with https://custom.camo-uri.example/',
// CAMO_URI (server variable) can be set arbitrarily;
// for another possible value, see imagePreviewSingleExternal2.
"https://upload.wikimedia.org/wikipedia/commons/7/78/Verregende_bloem_van_een_Helenium_%27El_Dorado%27._22-07-2023._%28d.j.b%29.jpg",
'<div class="message_inline_image">'
'<a href="https://upload.wikimedia.org/wikipedia/commons/7/78/Verregende_bloem_van_een_Helenium_%27El_Dorado%27._22-07-2023._%28d.j.b%29.jpg">'
'<img src="https://custom.camo-uri.example/99742b0f992be15283c428dd42f3b9f5db138d69/68747470733a2f2f75706c6f61642e77696b696d656469612e6f72672f77696b6970656469612f636f6d6d6f6e732f372f37382f566572726567656e64655f626c6f656d5f76616e5f65656e5f48656c656e69756d5f253237456c5f446f7261646f2532372e5f32322d30372d323032332e5f253238642e6a2e622532392e6a7067"></a></div>', [
ImagePreviewNodeList([
ImagePreviewNode(srcUrl: 'https://custom.camo-uri.example/99742b0f992be15283c428dd42f3b9f5db138d69/68747470733a2f2f75706c6f61642e77696b696d656469612e6f72672f77696b6970656469612f636f6d6d6f6e732f372f37382f566572726567656e64655f626c6f656d5f76616e5f65656e5f48656c656e69756d5f253237456c5f446f7261646f2532372e5f32322d30372d323032332e5f253238642e6a2e622532392e6a7067',
thumbnailUrl: null, loading: false,
originalWidth: null, originalHeight: null),
]),
]);

static const imagePreviewInvalidUrl = ContentExample(
'single image preview with invalid URL',
null, // hypothetical, to test for a risk of crashing
Expand Down Expand Up @@ -1816,7 +1846,9 @@ void main() async {
testParseExample(ContentExample.imagePreviewSingleNoDimensions);
testParseExample(ContentExample.imagePreviewSingleNoThumbnail);
testParseExample(ContentExample.imagePreviewSingleLoadingPlaceholder);
testParseExample(ContentExample.imagePreviewSingleExternal);
testParseExample(ContentExample.imagePreviewSingleExternal1);
testParseExample(ContentExample.imagePreviewSingleExternal2);
testParseExample(ContentExample.imagePreviewSingleExternal3);
testParseExample(ContentExample.imagePreviewInvalidUrl);
testParseExample(ContentExample.imagePreviewCluster);
testParseExample(ContentExample.imagePreviewClusterNoThumbnails);
Expand Down