Files
sharedinbox/lib/core/utils/cid_utils.dart
T
Thomas SharedInboxandClaude Sonnet 4.6 653ef92430 fix(email): resolve cid: inline images in multipart/related messages (#89)
Emails with multipart/related structure reference embedded images via
cid: URIs.  The WebView's CSP only allows data:/blob: sources, so those
images were never shown.  injectInlineImages() now replaces each cid:
reference with a data: URI using the decoded bytes from the MIME tree,
both for double-quoted and single-quoted src attributes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-15 11:02:22 +02:00

45 lines
1.5 KiB
Dart

import 'dart:convert';
import 'package:enough_mail/enough_mail.dart' as imap;
/// Replaces `src="cid:..."` references in [html] with inline `data:` URIs
/// by looking up each Content-ID in the MIME tree of [msg].
///
/// Emails with `multipart/related` often embed images this way. Without
/// substitution the WebView shows broken image icons even after the full
/// message has been downloaded.
String injectInlineImages(String html, imap.MimeMessage msg) {
final inlineParts = msg.findContentInfo(
disposition: imap.ContentDisposition.inline,
);
if (inlineParts.isEmpty) return html;
var result = html;
for (final info in inlineParts) {
final cid = info.cid;
if (cid == null || cid.isEmpty) continue;
final bareCid = cid.startsWith('<') && cid.endsWith('>')
? cid.substring(1, cid.length - 1)
: cid;
final part = msg.getPart(info.fetchId);
if (part == null) continue;
final bytes = part.decodeContentBinary();
if (bytes == null || bytes.isEmpty) continue;
final contentType =
info.contentType?.mediaType.text ?? 'application/octet-stream';
final dataUri = 'data:$contentType;base64,${base64.encode(bytes)}';
result = result
.replaceAll('src="cid:$bareCid"', 'src="$dataUri"')
.replaceAll("src='cid:$bareCid'", "src='$dataUri'")
.replaceAll('src="cid:${bareCid.toLowerCase()}"', 'src="$dataUri"')
.replaceAll(
"src='cid:${bareCid.toLowerCase()}'",
"src='$dataUri'",
);
}
return result;
}