Skip to content

Commit 791833b

Browse files
authored
[FIX] Email/get full view: hasAttachment should filter out inlined at… (#2709)
1 parent 86335a8 commit 791833b

File tree

3 files changed

+238
-1
lines changed

3 files changed

+238
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
Date: Wed, 26 Jan 2022 12:21:37 +0100
2+
From: Bob <[email protected]>
3+
To: Alice <[email protected]>
4+
MIME-Version: 1.0
5+
Subject: inlined attachment
6+
Message-ID: <[email protected]>
7+
Content-Type: multipart/related;
8+
boundary="-=Part.16d.b16547c587c987fd.196a380f8d4.b92d6af2f0294389=-"
9+
10+
---=Part.16d.b16547c587c987fd.196a380f8d4.b92d6af2f0294389=-
11+
Content-Type: multipart/alternative;
12+
boundary="-=Part.16c.d54cec4b999aebb5.196a380f8d2.7189347f48aceda7=-"
13+
14+
---=Part.16c.d54cec4b999aebb5.196a380f8d2.7189347f48aceda7=-
15+
Content-Type: text/plain; charset=UTF-8
16+
Content-Transfer-Encoding: quoted-printable
17+
Accept-Language: fr-FR, en-US, vi-VN, ru-RU, ar-TN, it-IT, de-DE
18+
Content-Language: vi-VN
19+
20+
21+
start[Screenshot 2025-05-06 at 09]end
22+
23+
24+
---=Part.16c.d54cec4b999aebb5.196a380f8d2.7189347f48aceda7=-
25+
Content-Type: text/html; charset=UTF-8
26+
Content-Transfer-Encoding: quoted-printable
27+
Accept-Language: fr-FR, en-US, vi-VN, ru-RU, ar-TN, it-IT, de-DE
28+
Content-Language: vi-VN
29+
30+
<div><br></div><div>start</div><div><img src=3D"cid:e612b3e0-2a24-11f0-aacf=
31+
-77a69744ec20" alt=3D"Screenshot 2025-05-06 at 09" style=3D"max-width:770px=
32+
;" data-mimetype=3D"image/png"></div><div>end</div><div><br></div><div><br>=
33+
</div>
34+
---=Part.16c.d54cec4b999aebb5.196a380f8d2.7189347f48aceda7=---
35+
36+
---=Part.16d.b16547c587c987fd.196a380f8d4.b92d6af2f0294389=-
37+
Content-Type: image/png;
38+
name="=?US-ASCII?Q?Screenshot_2025-05-06_at_09.50.27.png?="; charset=base64
39+
Content-Disposition: inline
40+
Content-Transfer-Encoding: base64
41+
Content-ID: e612b3e0-2a24-11f0-aacf-77a69744ec20
42+
43+
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
88+
RLDk4s+jfi77uXdp+tLWZW7LNi4nLpcsv7nCd0XVSq2VBSsfrxq7qm41a3Xx6rdrJq25UOpSumkt
89+
da1ibXtZeFnDOot1y9d9Kc8sv1HhX7FnveH6xevfbxBuuLrRb2PtJqNNJZs+bRZvvr0leEtdpVVl
90+
6Vbi1vytT7clbDv3C/uX6u0G20u2f90h2dFeFVN1utq9unqn4c5lNWiNoqZzV8quK7sDdjfUOtRu
91+
2cPcU7IX7FXs/WNf6r6b+8P2Nx9gH6g9aHlw/SHGoeI6pG56XXd9Zn17Q1JD2+HQw82NXo2Hjjge
92+
2dFk2lRxVPfosmPUYwuO9R4vON5zQnqi62TGycfNk5rvnhp36vrp6NOtZ8LOnD8bdPbUOc654+e9
93+
zzdd8Lxw+CL7Yv0lt0t1La4th351/fVQq1tr3WX3yw1XPK40to1pO3bV9+rJawHXzl7nXb90I+JG
94+
2834m7dvpdxqvy28/fxOzp1Xv+X/9vnu3HuEe8X3Ne+XPjB8UPm77e972t3ajz4MeNjyKPbR3ceC
95+
xy+eyJ986VjwlPa09JnJs+rnzs+bOoM6r/wx/o+OF9IXn7uK/tT6c/1Lm5cH//L7q6V7XHfHK9mr
96+
3tdL3ui/2fHW5W1zT1TPg3e57z6/L/6g/6HqI/vjuU+Jn559nvqF9KXsq+3Xxm9h3+715vb2Svky
97+
ft+nAAaUR5t0AF7vAICWBAADnhup41Xnw76CqM60fQj8J6w6Q/YVNwBq4Td9dBf8urkFwN5tAFhB
98+
fXoKAFE0AOI8ADp69GAdOMv1nTuVhQjPBpt5X9Ny08C/Kaoz6Q9+D22BUtUFDG3/BXkhgywpQj4b
99+
AAAAimVYSWZNTQAqAAAACAAEARoABQAAAAEAAAA+ARsABQAAAAEAAABGASgAAwAAAAEAAgAAh2kA
100+
BAAAAAEAAABOAAAAAAAAAJAAAAABAAAAkAAAAAEAA5KGAAcAAAASAAAAeKACAAQAAAABAAAAsqAD
101+
AAQAAAABAAAATAAAAABBU0NJSQAAAFNjcmVlbnNob3ReQ+6gAAAACXBIWXMAABYlAAAWJQFJUiTw
102+
AAAB1WlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpu
103+
czptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNi4wLjAiPgogICA8cmRmOlJERiB4bWxuczpyZGY9
104+
Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPgogICAgICA8cmRm
105+
OkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgICAgICAgICB4bWxuczpleGlmPSJodHRwOi8v
106+
bnMuYWRvYmUuY29tL2V4aWYvMS4wLyI+CiAgICAgICAgIDxleGlmOlBpeGVsWURpbWVuc2lvbj43
107+
NjwvZXhpZjpQaXhlbFlEaW1lbnNpb24+CiAgICAgICAgIDxleGlmOlBpeGVsWERpbWVuc2lvbj4x
108+
Nzg8L2V4aWY6UGl4ZWxYRGltZW5zaW9uPgogICAgICAgICA8ZXhpZjpVc2VyQ29tbWVudD5TY3Jl
109+
ZW5zaG90PC9leGlmOlVzZXJDb21tZW50PgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9y
110+
ZGY6UkRGPgo8L3g6eG1wbWV0YT4KYNB7JQAAABxpRE9UAAAAAgAAAAAAAAAmAAAAKAAAACYAAAAm
111+
AAABBBOG5jIAAADQSURBVHgB7NIBEQAgDAMx5l80cLjgL1Owtpl9bzkNfN7AgPz5gt5/DYAMQqIB
112+
kBMzCgEyA4kGQE7MKATIDCQaADkxoxAgM5BoAOTEjEKAzECiAZATMwoBMgOJBkBOzCgEyAwkGgA5
113+
MaMQIDOQaADkxIxCgMxAogGQEzMKATIDiQZATswoBMgMJBoAOTGjECAzkGgA5MSMQoDMQKIBkBMz
114+
CgEyA4kGQE7MKATIDCQaADkxoxAgM5BoAOTEjEKAzECiAZATMwoBMgOJBkBOzCjEAQAA///X4LVo
115+
AAAAzklEQVTt0gERACAMAzHmXzRwuOAvU7C2mX1vOQ183sCA/PmC3n8NgAxCogGQEzMKATIDiQZA
116+
TswoBMgMJBoAOTGjECAzkGgA5MSMQoDMQKIBkBMzCgEyA4kGQE7MKATIDCQaADkxoxAgM5BoAOTE
117+
jEKAzECiAZATMwoBMgOJBkBOzCgEyAwkGgA5MaMQIDOQaADkxIxCgMxAogGQEzMKATIDiQZATswo
118+
BMgMJBoAOTGjECAzkGgA5MSMQoDMQKIBkBMzCgEyA4kGQE7MKMQBRz8vLLnQPmEAAAAASUVORK5C
119+
YII=
120+
121+
---=Part.16d.b16547c587c987fd.196a380f8d4.b92d6af2f0294389=---

server/protocols/jmap-rfc-8621-integration-tests/jmap-rfc-8621-integration-tests-common/src/main/scala/org/apache/james/jmap/rfc8621/contract/EmailGetMethodContract.scala

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4828,6 +4828,120 @@ trait EmailGetMethodContract {
48284828
|}""".stripMargin)
48294829
}
48304830

4831+
@Test
4832+
def inlinedAttachmentMailShouldNotBeCountedAsHasAttachmentWhenEmailFullView(server: GuiceJamesServer): Unit = {
4833+
val path = MailboxPath.inbox(BOB)
4834+
val mailboxId = server.getProbe(classOf[MailboxProbeImpl]).createMailbox(path)
4835+
val messageId: MessageId = server.getProbe(classOf[MailboxProbeImpl])
4836+
.appendMessage(BOB.asString, path, AppendCommand.from(
4837+
ClassLoaderUtils.getSystemResourceAsSharedStream("eml/simple-inlined-attachment.eml")))
4838+
.getMessageId
4839+
4840+
val request =
4841+
s"""{
4842+
| "using": [
4843+
| "urn:ietf:params:jmap:core",
4844+
| "urn:ietf:params:jmap:mail"
4845+
| ],
4846+
| "methodCalls": [
4847+
| [
4848+
| "Email/get",
4849+
| {
4850+
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
4851+
| "ids": ["${messageId.serialize}"],
4852+
| "properties": [
4853+
| "id",
4854+
| "subject",
4855+
| "from",
4856+
| "to",
4857+
| "cc",
4858+
| "bcc",
4859+
| "keywords",
4860+
| "size",
4861+
| "receivedAt",
4862+
| "sentAt",
4863+
| "preview",
4864+
| "hasAttachment",
4865+
| "attachments",
4866+
| "replyTo",
4867+
| "mailboxIds"
4868+
| ],
4869+
| "fetchTextBodyValues": true
4870+
| },
4871+
| "c1"
4872+
| ]
4873+
| ]
4874+
|}""".stripMargin
4875+
val response = `given`
4876+
.header(ACCEPT.toString, ACCEPT_RFC8621_VERSION_HEADER)
4877+
.body(request)
4878+
.when
4879+
.post
4880+
.`then`
4881+
.statusCode(SC_OK)
4882+
.contentType(JSON)
4883+
.extract
4884+
.body
4885+
.asString
4886+
4887+
assertThatJson(response)
4888+
.whenIgnoringPaths("methodResponses[0][1].state")
4889+
.isEqualTo(
4890+
s"""{
4891+
| "sessionState": "${SESSION_STATE.value}",
4892+
| "methodResponses": [
4893+
| [
4894+
| "Email/get",
4895+
| {
4896+
| "accountId": "29883977c13473ae7cb7678ef767cbfbaffc8a44a6e463d971d23a65c1dc4af6",
4897+
| "state": "33ac468f-7903-4f68-ac3e-8120505b5c3d",
4898+
| "list": [
4899+
| {
4900+
| "id": "${messageId.serialize}",
4901+
| "keywords": {},
4902+
| "mailboxIds": {
4903+
| "${mailboxId.serialize}": true
4904+
| },
4905+
| "size": 7609,
4906+
| "receivedAt": "$${json-unit.ignore}",
4907+
| "to": [
4908+
| {
4909+
| "name": "Alice",
4910+
| "email": "[email protected]"
4911+
| }
4912+
| ],
4913+
| "from": [
4914+
| {
4915+
| "name": "Bob",
4916+
| "email": "[email protected]"
4917+
| }
4918+
| ],
4919+
| "subject": "inlined attachment",
4920+
| "sentAt": "$${json-unit.ignore}",
4921+
| "attachments": [
4922+
| {
4923+
| "partId": "5",
4924+
| "blobId": "${messageId.serialize}_5",
4925+
| "size": 4334,
4926+
| "name": "Screenshot 2025-05-06 at 09.50.27.png",
4927+
| "type": "image/png",
4928+
| "charset": "base64",
4929+
| "disposition": "inline",
4930+
| "cid": "e612b3e0-2a24-11f0-aacf-77a69744ec20"
4931+
| }
4932+
| ],
4933+
| "hasAttachment": false,
4934+
| "preview": "start[Screenshot 2025-05-06 at 09]end"
4935+
| }
4936+
| ],
4937+
| "notFound": []
4938+
| },
4939+
| "c1"
4940+
| ]
4941+
| ]
4942+
|}""".stripMargin)
4943+
}
4944+
48314945
@Test
48324946
def shouldUseFastViewWithAttachmentMetadataWhenSupportedBodyProperties(server: GuiceJamesServer): Unit = {
48334947
val path = MailboxPath.inbox(BOB)

server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/Email.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import org.apache.james.jmap.api.projections.{MessageFastViewPrecomputedProperti
3838
import org.apache.james.jmap.core.Id.{Id, IdConstraint}
3939
import org.apache.james.jmap.core.{Properties, UTCDate}
4040
import org.apache.james.jmap.mail.BracketHeader.sanitize
41+
import org.apache.james.jmap.mail.Disposition.ATTACHMENT
4142
import org.apache.james.jmap.mail.EmailFullViewFactory.extractBodyValues
4243
import org.apache.james.jmap.mail.EmailGetRequest.MaxBodyValueBytes
4344
import org.apache.james.jmap.mail.EmailHeaderName.{ADDRESSES_NAMES, DATE, MESSAGE_ID_NAMES}
@@ -625,7 +626,8 @@ private class EmailFullViewFactory @Inject()(zoneIdProvider: ZoneIdProvider, pre
625626
size = sanitizeSize(firstMessage.getSize)),
626627
header = EmailHeaders.from(zoneIdProvider.get())(mime4JMessage),
627628
bodyMetadata = EmailBodyMetadata(
628-
hasAttachment = HasAttachment(!firstMessage.getLoadedAttachments.isEmpty),
629+
hasAttachment = HasAttachment(bodyStructure.attachments.exists(attachment =>
630+
attachment.disposition.contains(ATTACHMENT) && attachment.cid.isEmpty)),
629631
preview = preview),
630632
body = EmailBody(
631633
bodyStructure = bodyStructure,

0 commit comments

Comments
 (0)