Skip to content

Commit 52721cf

Browse files
committed
Fix intermittent CID embedded image loading in HTML viewer
- Use proper file:/// URL format (three slashes) for local file references - Add explicit disk flush when saving embedded images and HTML preview files - Reorder DisplayMimeMessage to generate HTML preview BEFORE accessing BodyParts (fixes issue where cached MimeMessage internal state affected MimeVisitor traversal)
1 parent 893c205 commit 52721cf

File tree

4 files changed

+20
-6
lines changed

4 files changed

+20
-6
lines changed

src/Papercut.UI/AppLayer/HtmlPreviews/HtmlPreviewGeneratorImpl.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,15 @@ public string GetHtmlPreview(MimeMessage? mailMessageEx, string? tempDir = null)
4444

4545
logger.Verbose("Writing HTML Preview file {HtmlFile}", htmlFile);
4646

47-
File.WriteAllText(htmlFile, htmlPreview, Encoding.Unicode);
47+
// Use explicit FileStream with flush to ensure file is fully written to disk
48+
// before WebView2 navigates to it (prevents timing issues with CID images)
49+
using (var fs = new FileStream(htmlFile, FileMode.Create, FileAccess.Write, FileShare.None))
50+
using (var writer = new StreamWriter(fs, Encoding.Unicode))
51+
{
52+
writer.Write(htmlPreview);
53+
writer.Flush();
54+
fs.Flush(flushToDisk: true);
55+
}
4856

4957
return htmlFile;
5058
}

src/Papercut.UI/Helpers/HtmlPreviewVisitor.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,9 +110,11 @@ private string SaveImage(MimePart image, string url)
110110
{
111111
using var output = File.Create(path);
112112
image.Content.DecodeTo(output);
113+
output.Flush(flushToDisk: true); // Ensure file is fully written before WebView2 loads it
113114
}
114115

115-
return $"file://{path.Replace('\\', '/')}";
116+
// Use proper file:/// URL format (three slashes for local files)
117+
return $"file:///{path.Replace('\\', '/')}";
116118
}
117119

118120
private static string? GetExtensionFromMimeType(string mimeType)
@@ -145,10 +147,9 @@ void WriteTagAsIs()
145147
AddMetaCompatibleIeEdge(htmlWriter);
146148

147149
break;
148-
150+
149151
case HtmlTagId.Image when !ctx.IsEndTag && _stack.Count > 0:
150152
LinkImageTag(ctx, htmlWriter);
151-
152153
break;
153154
case HtmlTagId.Body when !ctx.IsEndTag:
154155
RemoveContextMenuFromBodyTag(ctx, htmlWriter);

src/Papercut.UI/ViewModels/MessageDetailHtmlViewModel.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -542,7 +542,8 @@ await coreWebView.AddScriptToExecuteOnDocumentCreatedAsync(@"
542542
}
543543
else
544544
{
545-
coreWebView.Navigate($"file://{file.Replace("/", @"\")}");
545+
// Use proper file:/// URL format (three slashes for local files)
546+
coreWebView.Navigate($"file:///{file.Replace('\\', '/')}");
546547
}
547548
}
548549
);

src/Papercut.UI/ViewModels/MessageDetailViewModel.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,11 @@ void DisplayMimeMessage(MimeMessage? mailMessageEx)
306306
{
307307
this.HeaderViewModel.Headers = string.Join("\r\n", mailMessageEx.Headers.Select(h => h.ToString()));
308308

309+
// IMPORTANT: Generate HTML preview FIRST before any other access to BodyParts
310+
// This ensures the MimeVisitor sees the full multipart/related structure
311+
// Accessing BodyParts can affect the internal state of cached MimeMessage objects
312+
this.HtmlViewModel.ShowMessage(mailMessageEx);
313+
309314
var parts = mailMessageEx.BodyParts.OfType<MimePart>().ToList();
310315
var mainBody = parts.GetMainBodyTextPart();
311316

@@ -330,7 +335,6 @@ void DisplayMimeMessage(MimeMessage? mailMessageEx)
330335

331336
if (mainBody != null) {
332337
this.IsHtml = mainBody.IsContentHtml();
333-
this.HtmlViewModel.ShowMessage(mailMessageEx);
334338

335339
if (this.IsHtml)
336340
{

0 commit comments

Comments
 (0)