This is an automated email from the ASF dual-hosted git repository.
btellier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git
The following commit(s) were added to refs/heads/master by this push:
new ff4011523a JAMES-3516 Fix In-Reply-To, References and Subject parsing
for threads (#2790)
ff4011523a is described below
commit ff4011523a12dc0d93cffbc04ea874e4bee573cb
Author: Benoit TELLIER <[email protected]>
AuthorDate: Mon Sep 1 05:53:33 2025 +0700
JAMES-3516 Fix In-Reply-To, References and Subject parsing for threads
(#2790)
Co-authored-by: Quan Tran <[email protected]>
---
.../store/mail/utils/MimeMessageHeadersUtil.java | 27 +++++---
.../mail/utils/MimeMessageHeadersUtilTest.java | 76 ++++++++++++++++++++++
2 files changed, 95 insertions(+), 8 deletions(-)
diff --git
a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/utils/MimeMessageHeadersUtil.java
b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/utils/MimeMessageHeadersUtil.java
index 6530244626..7a26d37cc4 100644
---
a/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/utils/MimeMessageHeadersUtil.java
+++
b/mailbox/store/src/main/java/org/apache/james/mailbox/store/mail/utils/MimeMessageHeadersUtil.java
@@ -25,34 +25,45 @@ import java.util.Optional;
import org.apache.james.mailbox.store.mail.model.MimeMessageId;
import org.apache.james.mailbox.store.mail.model.Subject;
import org.apache.james.mime4j.codec.DecodeMonitor;
+import org.apache.james.mime4j.dom.Header;
import org.apache.james.mime4j.dom.field.UnstructuredField;
import org.apache.james.mime4j.field.UnstructuredFieldImpl;
-import org.apache.james.mime4j.message.HeaderImpl;
import org.apache.james.mime4j.stream.Field;
+import org.apache.james.mime4j.util.MimeUtil;
+import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
public class MimeMessageHeadersUtil {
- public static Optional<MimeMessageId> parseMimeMessageId(HeaderImpl
headers) {
+ private static final Splitter SPLITTER = Splitter.on('
').omitEmptyStrings().trimResults();
+
+ public static Optional<MimeMessageId> parseMimeMessageId(Header headers) {
return Optional.ofNullable(headers.getField("Message-ID")).map(field
-> new MimeMessageId(field.getBody()));
}
- public static Optional<MimeMessageId> parseInReplyTo(HeaderImpl headers) {
- return Optional.ofNullable(headers.getField("In-Reply-To")).map(field
-> new MimeMessageId(field.getBody()));
+ public static Optional<MimeMessageId> parseInReplyTo(Header headers) {
+ return Optional.ofNullable(headers.getField("In-Reply-To"))
+ .map(Field::getBody)
+ .map(MimeUtil::unfold)
+ .map(String::trim)
+ .map(MimeMessageId::new);
}
- public static Optional<List<MimeMessageId>> parseReferences(HeaderImpl
headers) {
+ public static Optional<List<MimeMessageId>> parseReferences(Header
headers) {
List<Field> mimeMessageIdFields = headers.getFields("References");
if (!mimeMessageIdFields.isEmpty()) {
List<MimeMessageId> mimeMessageIdList =
mimeMessageIdFields.stream()
- .map(mimeMessageIdField -> new
MimeMessageId(mimeMessageIdField.getBody()))
+ .map(Field::getBody)
+ .map(MimeUtil::unfold)
+ .flatMap(SPLITTER::splitToStream)
+ .map(MimeMessageId::new)
.collect(ImmutableList.toImmutableList());
return Optional.of(mimeMessageIdList);
}
return Optional.empty();
}
- public static Optional<Subject> parseSubject(HeaderImpl headers) {
+ public static Optional<Subject> parseSubject(Header headers) {
return Optional.ofNullable(headers.getField("Subject"))
.map(field -> {
if (!(field instanceof UnstructuredField)) {
@@ -60,6 +71,6 @@ public class MimeMessageHeadersUtil {
}
return (UnstructuredField) field;
})
- .map(unstructuredField -> new
Subject(unstructuredField.getValue()));
+ .map(unstructuredField -> new
Subject(unstructuredField.getValue().trim()));
}
}
diff --git
a/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/utils/MimeMessageHeadersUtilTest.java
b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/utils/MimeMessageHeadersUtilTest.java
new file mode 100644
index 0000000000..cca5ec8153
--- /dev/null
+++
b/mailbox/store/src/test/java/org/apache/james/mailbox/store/mail/utils/MimeMessageHeadersUtilTest.java
@@ -0,0 +1,76 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one *
+ * or more contributor license agreements. See the NOTICE file *
+ * distributed with this work for additional information *
+ * regarding copyright ownership. The ASF licenses this file *
+ * to you under the Apache License, Version 2.0 (the *
+ * "License"); you may not use this file except in compliance *
+ * with the License. You may obtain a copy of the License at *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, *
+ * software distributed under the License is distributed on an *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
+ * KIND, either express or implied. See the License for the *
+ * specific language governing permissions and limitations *
+ * under the License. *
+ ****************************************************************/
+package org.apache.james.mailbox.store.mail.utils;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.james.mailbox.store.mail.model.MimeMessageId;
+import org.apache.james.mailbox.store.mail.model.Subject;
+import org.apache.james.mime4j.dom.Header;
+import org.apache.james.mime4j.dom.Message;
+import org.apache.james.mime4j.message.DefaultMessageBuilder;
+import org.apache.james.mime4j.stream.MimeConfig;
+import org.junit.jupiter.api.Test;
+
+class MimeMessageHeadersUtilTest {
+ @Test
+ void parseInReplyToShouldHandleHeaderWithCRLFLineBreaks() throws Exception
{
+ Header header = parse("References:\r\n" +
+ "
<cankoxfunsvbjbuuaqsqifavvdh-ovvig772-dkmrabsisoz...@mail.gmail.com>\r\n" +
+ " <[email protected]>\r\n" +
+ "In-Reply-To:\r\n" +
+ "
<cankoxfunsvbjbuuaqsqifavvdh-ovvig772-dkmrabsisoz...@mail.gmail.com>\r\n").getHeader();
+
+ assertThat(MimeMessageHeadersUtil.parseInReplyTo(header))
+ .contains(new
MimeMessageId("<cankoxfunsvbjbuuaqsqifavvdh-ovvig772-dkmrabsisoz...@mail.gmail.com>"));
+ }
+
+ @Test
+ void parseReferencesShouldHandleHeaderWithCRLFLineBreaks() throws
Exception {
+ Header header = parse("References:\r\n" +
+ "
<cankoxfunsvbjbuuaqsqifavvdh-ovvig772-dkmrabsisoz...@mail.gmail.com>\r\n" +
+ " <[email protected]>\r\n" +
+ "In-Reply-To:\r\n" +
+ "
<cankoxfunsvbjbuuaqsqifavvdh-ovvig772-dkmrabsisoz...@mail.gmail.com>\r\n").getHeader();
+
+ assertThat(MimeMessageHeadersUtil.parseReferences(header).get())
+ .containsOnly(new
MimeMessageId("<cankoxfunsvbjbuuaqsqifavvdh-ovvig772-dkmrabsisoz...@mail.gmail.com>"),
+ new
MimeMessageId("<[email protected]>"));
+ }
+
+ @Test
+ void parseSubjectShouldHandleHeaderWithCRLFLineBreaks() throws Exception {
+ Header header = parse("Subject:\r\n"
+ + " This is a\r\n"
+ + " multi-line subject header\r\n").getHeader();
+
+ assertThat(MimeMessageHeadersUtil.parseSubject(header))
+ .contains(new Subject("This is a multi-line subject header"));
+ }
+
+ private Message parse(String s) throws IOException {
+ DefaultMessageBuilder defaultMessageBuilder = new
DefaultMessageBuilder();
+ defaultMessageBuilder.setMimeEntityConfig(MimeConfig.PERMISSIVE);
+ return defaultMessageBuilder.parseMessage(new
ByteArrayInputStream(s.getBytes(StandardCharsets.US_ASCII)));
+ }
+}
\ No newline at end of file
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]