Skip to content

Commit f5702b7

Browse files
rnwoodweb-flow
andauthored
fix: Handle duplicate content-IDs gracefully with warnings - Resolves #1874 (#1876)
Co-authored-by: smtp4dev-automation <[email protected]>
1 parent 1be5723 commit f5702b7

6 files changed

Lines changed: 41 additions & 9 deletions

File tree

Rnwood.Smtp4dev/ApiModel/Message.cs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
using MimeKit;
1+
using Microsoft.AspNetCore.Mvc;
2+
using MimeKit;
3+
using Rnwood.Smtp4dev.Migrations;
24
using System;
35
using System.Collections.Generic;
46
using System.IO;
57
using System.Linq;
8+
using System.Security.Cryptography;
69
using System.Text;
7-
using Microsoft.AspNetCore.Mvc;
810
using System.Text.Json.Serialization;
9-
using Rnwood.Smtp4dev.Migrations;
1011

1112
namespace Rnwood.Smtp4dev.ApiModel
1213
{
@@ -88,6 +89,18 @@ public Message(DbModel.Message dbMessage)
8889
Parts.Add(HandleMimeEntity(MimeMessage.Body));
8990
HasHtmlBody = MimeMessage.HtmlBody != null;
9091
HasPlainTextBody = MimeMessage.TextBody != null;
92+
93+
var duplicateCids = Parts.Flatten(p => p.ChildParts)
94+
.Where(p => !string.IsNullOrEmpty(p.ContentId))
95+
.GroupBy(p => p.ContentId)
96+
.Where(g => g.Count() > 1)
97+
.Select(g => g.Key)
98+
.ToList();
99+
100+
if (duplicateCids.Any())
101+
{
102+
Warnings.Add(new MessageWarning { Details = $"Non unique MIME Content-Id headers found: {string.Join(", ", duplicateCids.Select(cid => string.Concat("'", cid, "'")))}" });
103+
}
91104
}
92105
}
93106

Rnwood.Smtp4dev/ApiModel/MessageSummary.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using Rnwood.Smtp4dev.Migrations;
2+
using Rnwood.Smtp4dev.Server;
23
using System;
4+
using System.Text.Json;
35
using System.Text.Json.Serialization;
46

57
namespace Rnwood.Smtp4dev.ApiModel
@@ -31,7 +33,13 @@ public MessageSummary(DbModel.Projections.MessageSummaryProjection messagesSumma
3133
IsUnread = messagesSummaryProjection.IsUnread;
3234
IsRelayed = messagesSummaryProjection.IsRelayed;
3335
DeliveredTo = messagesSummaryProjection.DeliveredTo;
34-
HasWarnings = messagesSummaryProjection.HasBareLineFeed;
36+
37+
MimeMetadata mimeMetadata = !string.IsNullOrEmpty(messagesSummaryProjection.MimeMetadata) ?
38+
JsonSerializer.Deserialize<MimeMetadata>(messagesSummaryProjection.MimeMetadata)
39+
: null;
40+
41+
HasWarnings = messagesSummaryProjection.HasBareLineFeed
42+
|| (mimeMetadata?.HasDuplicatedContentIds.GetValueOrDefault() ?? false);
3543
}
3644

3745
public bool IsRelayed { get; set; }

Rnwood.Smtp4dev/ClientApp/src/components/messageviewhtml.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<template>
22

3-
<div class="hfillpanel" v-loading="!html">
3+
<div class="hfillpanel" v-loading="loading">
44

55
<div class="toolbar">
66
<el-select style="flex: 1 0 200px;" v-model="selectedViewportSizeName" filterable @change="viewportRotate=false">

Rnwood.Smtp4dev/Controllers/MessagesController.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,7 @@ public async Task<ActionResult<string>> GetMessageHtml(Guid id)
418418
{
419419
string cid = imageElement.Attributes["src"].Value.Replace("cid:", "", StringComparison.OrdinalIgnoreCase);
420420

421-
var part = message.Parts.Flatten(p => p.ChildParts).SingleOrDefault(p => p.ContentId == cid);
421+
var part = message.Parts.Flatten(p => p.ChildParts).FirstOrDefault(p => p.ContentId == cid);
422422

423423
imageElement.Attributes["src"].Value = $"api/Messages/{id.ToString()}/part/{part?.Id ?? "notfound"}/content";
424424
}

Rnwood.Smtp4dev/Server/MimeMetadata.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,5 +43,8 @@ public class MimeMetadata
4343
/// </summary>
4444
[JsonPropertyName("partCount")]
4545
public int PartCount { get; set; }
46+
47+
[JsonPropertyName("hasDuplicatedContentIds")]
48+
public bool? HasDuplicatedContentIds { get; set; }
4649
}
4750
}

Rnwood.Smtp4dev/Server/MimeProcessingService.cs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public MimeMetadata ExtractMimeMetadata(MimeMessage mime)
5656
}
5757

5858
// Extract attachment filenames and body content info
59-
ExtractPartMetadata(mime.Body, metadata);
59+
ExtractPartMetadata(mime.Body, metadata, new HashSet<string>());
6060

6161
// Set content type
6262
metadata.ContentType = mime.Body?.ContentType?.MimeType ?? "";
@@ -69,12 +69,20 @@ public MimeMetadata ExtractMimeMetadata(MimeMessage mime)
6969
return metadata;
7070
}
7171

72-
public void ExtractPartMetadata(MimeEntity entity, MimeMetadata metadata)
72+
public void ExtractPartMetadata(MimeEntity entity, MimeMetadata metadata, HashSet<string> seenContentIds)
7373
{
7474
if (entity == null) return;
7575

7676
metadata.PartCount++;
7777

78+
if (!string.IsNullOrEmpty(entity.ContentId) && seenContentIds.Contains(entity.ContentId))
79+
{
80+
metadata.HasDuplicatedContentIds = true;
81+
} else
82+
{
83+
seenContentIds.Add(entity.ContentId);
84+
}
85+
7886
// Check for attachments
7987
if (entity.IsAttachment)
8088
{
@@ -101,7 +109,7 @@ public void ExtractPartMetadata(MimeEntity entity, MimeMetadata metadata)
101109
{
102110
foreach (var part in multipart)
103111
{
104-
ExtractPartMetadata(part, metadata);
112+
ExtractPartMetadata(part, metadata, seenContentIds);
105113
}
106114
}
107115
}

0 commit comments

Comments
 (0)