This is an automated email from the ASF dual-hosted git repository. rcordier 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 c80ea10788 [FIX] Attachment filename should be indexed when missing in the `Content-Disposition` header but exists in the `Content-Type` header c80ea10788 is described below commit c80ea107886e8798f44c7e348e10f66371206b58 Author: Quan Tran <hqt...@linagora.com> AuthorDate: Tue May 20 16:32:20 2025 +0700 [FIX] Attachment filename should be indexed when missing in the `Content-Disposition` header but exists in the `Content-Type` header --- .../opensearch/json/IndexableMessageTest.java | 35 ++++++++++++++++ .../src/test/resources/eml/alternative.json | 4 +- .../eml/attachments-filename-in-content-type.eml | 48 ++++++++++++++++++++++ .../mailbox/store/search/mime/MimePartParser.java | 8 +++- .../eml/attachment-filename-in-content-type.eml | 43 +++++++++++++++++++ .../contract/EmailQueryMethodContract.scala | 44 ++++++++++++++++++++ 6 files changed, 178 insertions(+), 4 deletions(-) diff --git a/mailbox/opensearch/src/test/java/org/apache/james/mailbox/opensearch/json/IndexableMessageTest.java b/mailbox/opensearch/src/test/java/org/apache/james/mailbox/opensearch/json/IndexableMessageTest.java index 04385ae896..3836ae127f 100644 --- a/mailbox/opensearch/src/test/java/org/apache/james/mailbox/opensearch/json/IndexableMessageTest.java +++ b/mailbox/opensearch/src/test/java/org/apache/james/mailbox/opensearch/json/IndexableMessageTest.java @@ -250,6 +250,41 @@ class IndexableMessageTest { assertThat(indexableMessage.getAttachments()).isNotEmpty(); } + @Test + void attachmentsFilenameShouldFallbackToContentTypeWhenFilenameIsMissingInContentDisposition() throws Exception { + //Given + MailboxMessage mailboxMessage = mock(MailboxMessage.class); + TestId mailboxId = TestId.of(1); + when(mailboxMessage.getMailboxId()) + .thenReturn(mailboxId); + when(mailboxMessage.getModSeq()) + .thenReturn(ModSeq.first()); + when(mailboxMessage.getMessageId()) + .thenReturn(InMemoryMessageId.of(42)); + when(mailboxMessage.getFullContent()) + .thenReturn(ClassLoader.getSystemResourceAsStream("eml/attachments-filename-in-content-type.eml")); + when(mailboxMessage.createFlags()) + .thenReturn(new Flags()); + when(mailboxMessage.getUid()) + .thenReturn(MESSAGE_UID); + + // When + IndexableMessage indexableMessage = IndexableMessage.builder() + .message(mailboxMessage) + .extractor(new DefaultTextExtractor()) + .zoneId(ZoneId.of("Europe/Paris")) + .indexAttachments(IndexAttachments.YES) + .indexHeaders(IndexHeaders.YES) + .build() + .block(); + + // Then + assertThat(indexableMessage.getAttachments().getFirst().fileName()).isEqualTo(Optional.of("1.txt")); + assertThat(indexableMessage.getAttachments().getFirst().fileExtension()).isEqualTo(Optional.of("txt")); + assertThat(indexableMessage.getAttachments().get(1).fileName()).isEqualTo(Optional.of("2.txt")); + assertThat(indexableMessage.getAttachments().get(1).fileExtension()).isEqualTo(Optional.of("txt")); + } + @SuppressWarnings("checkstyle:LocalVariableName") @Test void otherAttachmentsShouldBeenIndexedWhenOneOfThemCannotBeParsed() throws Exception { diff --git a/mailbox/opensearch/src/test/resources/eml/alternative.json b/mailbox/opensearch/src/test/resources/eml/alternative.json index 4c08c45d45..86a63fc739 100644 --- a/mailbox/opensearch/src/test/resources/eml/alternative.json +++ b/mailbox/opensearch/src/test/resources/eml/alternative.json @@ -3,8 +3,8 @@ { "mediaType":"application", "subtype":"json", - "fileName":null, - "fileExtension":null, + "fileName":"id_rsa.txt", + "fileExtension":"txt", "contentDisposition":"attachment", "textContent":null } diff --git a/mailbox/opensearch/src/test/resources/eml/attachments-filename-in-content-type.eml b/mailbox/opensearch/src/test/resources/eml/attachments-filename-in-content-type.eml new file mode 100644 index 0000000000..837b4fc121 --- /dev/null +++ b/mailbox/opensearch/src/test/resources/eml/attachments-filename-in-content-type.eml @@ -0,0 +1,48 @@ +MIME-Version: 1.0 +Subject: Hello xyz +From: Bob <b...@linagora.com> +To: al...@linagora.com +Message-ID: <mime4j.14.31ce45ac61ec422b.196e75cc...@linagora.com> +User-Agent: Twake-Mail/0.15.3 Mozilla/5.0 (Macintosh; Intel Mac OS X + 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 + Safari/537.36 +Content-Type: multipart/mixed; + boundary="-=Part.16.2ce5c5497f23d613.196e75cd044.32ca13e0fd5dd663=-" + +---=Part.16.2ce5c5497f23d613.196e75cd044.32ca13e0fd5dd663=- +Content-Type: multipart/alternative; + boundary="-=Part.15.4e5239b38c309d78.196e75cd043.9d6830842151817f=-" + +---=Part.15.4e5239b38c309d78.196e75cd043.9d6830842151817f=- +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable +Accept-Language: fr-FR, en-US, vi-VN, ru-RU, ar-TN, it-IT, de-DE +Content-Language: vi-VN + +body + + +---=Part.15.4e5239b38c309d78.196e75cd043.9d6830842151817f=- +Content-Type: text/html; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable +Accept-Language: fr-FR, en-US, vi-VN, ru-RU, ar-TN, it-IT, de-DE +Content-Language: vi-VN + +<div>body<br><br></div> +---=Part.15.4e5239b38c309d78.196e75cd043.9d6830842151817f=--- + +---=Part.16.2ce5c5497f23d613.196e75cd044.32ca13e0fd5dd663=- +Content-Type: text/plain; name="=?US-ASCII?Q?1.txt?="; charset=windows-1252 +Content-Disposition: attachment +Content-Transfer-Encoding: base64 + +d2hhdGV2ZXIK + +---=Part.16.2ce5c5497f23d613.196e75cd044.32ca13e0fd5dd663=- +Content-Type: text/plain; name="=?US-ASCII?Q?2.txt?="; charset=windows-1252 +Content-Disposition: attachment +Content-Transfer-Encoding: base64 + +d2hhdGV2ZXIyCg== + +---=Part.16.2ce5c5497f23d613.196e75cd044.32ca13e0fd5dd663=--- diff --git a/mailbox/store/src/main/java/org/apache/james/mailbox/store/search/mime/MimePartParser.java b/mailbox/store/src/main/java/org/apache/james/mailbox/store/search/mime/MimePartParser.java index 827a2270c3..3dbd62c917 100644 --- a/mailbox/store/src/main/java/org/apache/james/mailbox/store/search/mime/MimePartParser.java +++ b/mailbox/store/src/main/java/org/apache/james/mailbox/store/search/mime/MimePartParser.java @@ -129,8 +129,12 @@ public class MimePartParser { Optional.ofNullable(descriptor.getSubType()) .map(SubType::of) .ifPresent(currentlyBuildMimePart::addSubType); - currentlyBuildMimePart.addContentDisposition(descriptor.getContentDispositionType()) - .addFileName(descriptor.getContentDispositionFilename()); + currentlyBuildMimePart.addContentDisposition(descriptor.getContentDispositionType()); + + Optional.ofNullable(descriptor.getContentDispositionFilename()) + .or(() -> Optional.ofNullable(descriptor.getContentTypeParameters().get("name"))) + .ifPresent(currentlyBuildMimePart::addFileName); + extractCharset(descriptor); } catch (Exception e) { LOGGER.warn("Failed to extract mime body part description", e); diff --git a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/resources/eml/attachment-filename-in-content-type.eml b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/resources/eml/attachment-filename-in-content-type.eml new file mode 100644 index 0000000000..e48ee18253 --- /dev/null +++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/resources/eml/attachment-filename-in-content-type.eml @@ -0,0 +1,43 @@ +MIME-Version: 1.0 +Subject: test send mail with attachment filename search +From: Bob <b...@linagora.com> +To: "al...@gmail.com" <al...@gmail.com> +Reply-To: b...@linagora.com +Message-ID: <mime4j.7.2182050b7626964a.196e6b48...@linagora.com> +User-Agent: Twake-Mail/0.15.3 Mozilla/5.0 (Macintosh; Intel Mac OS X + 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 + Safari/537.36 +Content-Type: multipart/mixed; + boundary="-=Part.9.facdaad4d7d1e26d.196e6b48470.15f465553d6bace6=-" + +---=Part.9.facdaad4d7d1e26d.196e6b48470.15f465553d6bace6=- +Content-Type: multipart/alternative; + boundary="-=Part.8.8fd856f88f8fdedd.196e6b48470.d48dd74354571387=-" + +---=Part.8.8fd856f88f8fdedd.196e6b48470.d48dd74354571387=- +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable +Accept-Language: fr-FR, en-US, vi-VN, ru-RU, ar-TN, it-IT, de-DE +Content-Language: vi-VN + +body + + +---=Part.8.8fd856f88f8fdedd.196e6b48470.d48dd74354571387=- +Content-Type: text/html; charset=UTF-8 +Content-Transfer-Encoding: quoted-printable +Accept-Language: fr-FR, en-US, vi-VN, ru-RU, ar-TN, it-IT, de-DE +Content-Language: vi-VN + +<div>body<br><br></div> +---=Part.8.8fd856f88f8fdedd.196e6b48470.d48dd74354571387=--- + +---=Part.9.facdaad4d7d1e26d.196e6b48470.15f465553d6bace6=- +Content-Type: text/plain; name="=?US-ASCII?Q?filename.txt?="; + charset=windows-1252 +Content-Disposition: attachment +Content-Transfer-Encoding: base64 + +d2hhdGV2ZXIgY29udGVudCA1OTUzNjI0NzY0Cg== + +---=Part.9.facdaad4d7d1e26d.196e6b48470.15f465553d6bace6=--- diff --git a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailQueryMethodContract.scala b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailQueryMethodContract.scala index ca4c8fec63..eeff40dbdf 100644 --- a/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailQueryMethodContract.scala +++ b/server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailQueryMethodContract.scala @@ -6705,6 +6705,50 @@ trait EmailQueryMethodContract { } } + @Test + def attachmentFilenameShouldBeSearchableWhenMissingInContentDispositionAndExistsInContentType(server: GuiceJamesServer): Unit = { + server.getProbe(classOf[MailboxProbeImpl]).createMailbox(inbox(BOB)) + val messageId = server.getProbe(classOf[MailboxProbeImpl]) + .appendMessage(BOB.asString, MailboxPath.inbox(BOB), AppendCommand.from( + ClassLoaderUtils.getSystemResourceAsSharedStream("eml/attachment-filename-in-content-type.eml"))) + .getMessageId + + val request = + s"""{ + | "using": [ + | "urn:ietf:params:jmap:core", + | "urn:ietf:params:jmap:mail"], + | "methodCalls": [[ + | "Email/query", + | { + | "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6", + | "filter": { + | "text": "filename" + | } + | }, + | "c1"]] + |}""".stripMargin + + awaitAtMostTenSeconds.untilAsserted { () => + val response = `given` + .header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER) + .body(request) + .when + .post + .`then` + .statusCode(SC_OK) + .contentType(JSON) + .extract + .body + .asString + + assertThatJson(response) + .inPath("$.methodResponses[0][1].ids") + .isEqualTo( + s"""["${messageId.serialize()}"]""".stripMargin) + } + } + @Test def emailQueryShouldSupportAndOperator(server: GuiceJamesServer): Unit = { val message: Message = buildTestMessage --------------------------------------------------------------------- To unsubscribe, e-mail: notifications-unsubscr...@james.apache.org For additional commands, e-mail: notifications-h...@james.apache.org