This is an automated email from the ASF dual-hosted git repository.
quantranhong1999 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 04ba203834 [ENHANCEMENT] Remove bidi control chars from attachment
names
04ba203834 is described below
commit 04ba2038348314c33c620e84815aedf86dc8fa81
Author: Benoit TELLIER <[email protected]>
AuthorDate: Sat Sep 26 20:27:09 2026 +0200
[ENHANCEMENT] Remove bidi control chars from attachment names
---
.../scala/org/apache/james/jmap/mail/EmailBodyPart.scala | 12 +++++++++---
.../scala/org/apache/james/jmap/routes/DownloadRoutes.scala | 6 ++++--
2 files changed, 13 insertions(+), 5 deletions(-)
diff --git
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailBodyPart.scala
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailBodyPart.scala
index 94238ce54c..00a114d3ca 100644
---
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailBodyPart.scala
+++
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/mail/EmailBodyPart.scala
@@ -87,7 +87,7 @@ object EmailBodyPart {
blobId = BlobId.of(attachment.getAttachmentId.getId).toOption,
headers = List(),
size = Size.sanitizeSize(attachment.getAttachment.getSize),
- name = attachment.getName.map(Name(_)).toScala,
+ name = attachment.getName.map(Name.of(_)).toScala,
`type` = Type(attachment.getAttachment.getType.mimeType().asString()),
charset = attachment.getAttachment.getType.charset().map(charset =>
Charset(charset.name().toUpperCase(Locale.US))).toScala,
disposition = parseDisposition(attachment),
@@ -183,13 +183,19 @@ object Name {
case contentTypeField: ContentTypeField =>
Option(contentTypeField.getParameter(FILENAME_PREFIX))
.map(DecoderUtil.decodeEncodedWords(_, DecodeMonitor.SILENT))
case _ => None
- }.map(Name(_))
+ }.map(Name.of(_))
.orElse(Option(entity.getHeader.getField(FieldName.CONTENT_DISPOSITION))
.flatMap {
case contentDispositionField: ContentDispositionField =>
Option(contentDispositionField.getFilename)
.map(DecoderUtil.decodeEncodedWords(_, DecodeMonitor.SILENT))
case _ => None
- }.map(Name(_)))
+ }.map(Name.of(_)))
+
+ private val BIDI_CONTROL_CHARACTERS =
"[\u200E\u200F\u202A-\u202E\u2066-\u2069]"
+
+ def normalize(value: String): String =
value.replaceAll(BIDI_CONTROL_CHARACTERS, "")
+
+ def of(value: String): Name = Name(normalize(value))
}
case class Name(value: String) extends AnyVal
diff --git
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/DownloadRoutes.scala
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/DownloadRoutes.scala
index 8481609322..b7129d0295 100644
---
a/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/DownloadRoutes.scala
+++
b/server/protocols/jmap-rfc-8621/src/main/scala/org/apache/james/jmap/routes/DownloadRoutes.scala
@@ -45,7 +45,7 @@ import org.apache.james.jmap.exceptions.UnauthorizedException
import org.apache.james.jmap.http.Authenticator
import org.apache.james.jmap.http.rfc8621.InjectionKeys
import org.apache.james.jmap.json.ResponseSerializer
-import org.apache.james.jmap.mail.{BlobId, MinimalEmailBodyPart}
+import org.apache.james.jmap.mail.{BlobId, MinimalEmailBodyPart, Name}
import org.apache.james.jmap.method.{AccountNotFoundException, ZoneIdProvider}
import org.apache.james.jmap.routes.DownloadRoutes.{BUFFER_SIZE, LOGGER}
import org.apache.james.jmap.{Endpoint, JMAPRoute, JMAPRoutes}
@@ -419,7 +419,8 @@ class DownloadRoutes
@Inject()(@Named(InjectionKeys.RFC_8621) val authenticator:
private def addCacheControlHeader(): HttpServerResponse =>
HttpServerResponse =
resp => resp.header(HttpHeaderNames.CACHE_CONTROL, "private, immutable,
max-age=31536000")
- private def addContentDispositionHeaderRegardingEncoding(name: String, resp:
HttpServerResponse): HttpServerResponse =
+ private def addContentDispositionHeaderRegardingEncoding(rawName: String,
resp: HttpServerResponse): HttpServerResponse = {
+ val name = Name.normalize(rawName)
if (CharMatcher.ascii.matchesAllOf(name)) {
Try(resp.header("Content-Disposition", "attachment; filename=\"" + name
+ "\""))
// Can fail if the file name contains valid ascii character that are
invalid in a contentDisposition header
@@ -427,6 +428,7 @@ class DownloadRoutes
@Inject()(@Named(InjectionKeys.RFC_8621) val authenticator:
} else {
resp.header("Content-Disposition", encodedFileName(name))
}
+ }
private def encodedFileName(name: String) = "attachment; filename*=\"" +
EncoderUtil.encodeEncodedWord(name, Usage.TEXT_TOKEN) + "\""
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]