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 <btell...@linagora.com> 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 <hqt...@linagora.com> --- .../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" + + " <mime4j.70b.9be3d6cb7586c526.198e07cf...@linagora.com>\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" + + " <mime4j.70b.9be3d6cb7586c526.198e07cf...@linagora.com>\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("<mime4j.70b.9be3d6cb7586c526.198e07cf...@linagora.com>")); + } + + @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: notifications-unsubscr...@james.apache.org For additional commands, e-mail: notifications-h...@james.apache.org