New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Multipart part's Content-Disposition with non-ascii filename fails to parse #5809
Comments
According to the spec, the So this is a question of how strictly we should follow the spec. |
isn't |
@aeons I'm getting the same when sending through postman, though then the content-disposition header is a bit different. Still there's a parsing error, though:
maybe if |
@aeons In spec
When sending request from Postman, it generate import org.http4s.headers.`Content-Disposition`.parse
val str = """form-data; name="b"; filename="中文文件名.json"; filename*=UTF-8''%E4%B8%AD%E6%96%87%E6%96%87%E4%BB%B6%E5%90%8D.json"""
parse(str)
val res0: org.http4s.ParseResult[org.http4s.headers.Content-Disposition] = Left(org.http4s.ParseFailure: Invalid Content-Disposition header: Error(31,NonEmptyList(InRange(31,","))))
Accroding the spec, should we use Above code works in http4s 0.21.31 parse(str)
val res0: org.http4s.ParseResult[org.http4s.headers.Content-Disposition] = Right(Content-Disposition: form-data; name="b"; filename="中文文件名.json"; filename*="UTF-8''%E4%B8%AD%E6%96%87%E6%96%87%E4%BB%B6%E5%90%8D.json") I'm not familiar with the parsing process , it seems that the parser implement is not the same as 0.23 def CONTENT_DISPOSITION(value: String): ParseResult[`Content-Disposition`] =
new Http4sHeaderParser[`Content-Disposition`](value) {
def entry =
rule {
Token ~ zeroOrMore(";" ~ OptWS ~ Parameter) ~ EOL ~> {
(token: String, params: collection.Seq[(String, String)]) =>
`Content-Disposition`(token, params.toMap)
}
}
}.parse see also: akka/akka-http#1240 |
I cant find it, could you provide any link? @ritschwumm |
It seems the http4s/core/shared/src/main/scala/org/http4s/headers/Content-Disposition.scala Lines 89 to 127 in d6e5f6a
The original request seem like
It wiil be convert to
Well, it seem this idea is inspired by akka-http, so I try to reproduce this issue on akka-http(10.2.10) import akka.actor.typed.ActorSystem
import akka.actor.typed.scaladsl.Behaviors
import akka.http.scaladsl.Http
import akka.http.scaladsl.server.Directives._
import scala.io.StdIn
object Test extends App {
implicit val system = ActorSystem(Behaviors.empty, "my-system")
// needed for the future flatMap/onComplete in the end
implicit val executionContext = system.executionContext
val route =
path("file") {
post {
fileUpload("file") {
case (metadata, byteSource) =>
complete(s"Filename: ${metadata.fileName}")
}
}
}
val bindingFuture = Http().newServerAt("localhost", 8082).bind(route)
println(s"Server now online. Please navigate to http://localhost:8082/hello\nPress RETURN to stop...")
StdIn.readLine() // let it run until user presses return
bindingFuture
.flatMap(_.unbind()) // trigger unbinding from the port
.onComplete(_ => system.terminate()) // and shutdown when done
}
it seems works well $ curl --location --request POST 'http://localhost:8082/file' \
--form 'file=@"/Users/counter/Desktop/中文文件名.json"' -vvvv
Note: Unnecessary use of -X or --request, POST is already inferred.
* Trying 127.0.0.1:8082...
* Connected to localhost (127.0.0.1) port 8082 (#0)
> POST /file HTTP/1.1
> Host: localhost:8082
> User-Agent: curl/7.79.1
> Accept: */*
> Content-Length: 211
> Content-Type: multipart/form-data; boundary=------------------------c0013cc1164d7df3
>
* We are completely uploaded and fine
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Server: akka-http/10.2.10
< Date: Thu, 22 Sep 2022 08:46:08 GMT
< Content-Type: text/plain; charset=UTF-8
< Content-Length: 30
<
* Connection #0 to host localhost left intact
Filename: 中文文件名.json The header parser result seem like this import akka.http.scaladsl.model.headers.{`Content-Disposition` => cd}
val res = cd.parseFromValueString("""form-data; name="file"; filename="中文文件名.json"""")
// Right(Content-Disposition: form-data; filename="?????.json"; filename*=UTF-8''%E4%B8%AD%E6%96%87%E6%96%87%E4%BB%B6%E5%90%8D.json; name="file")
println(res) And the code // http://tools.ietf.org/html/rfc6266#section-4.1
def `content-disposition` = rule {
`disposition-type` ~ zeroOrMore(ws(';') ~ `disposition-parm`) ~ EOI ~> { p =>
val all = TreeMap(p: _*)
// https://tools.ietf.org/html/rfc6266#section-4.3
// when both "filename" and "filename*" are present in a single header field value,
// recipients SHOULD pick "filename*" and ignore "filename"
all.get("filename*").map(fExt => all - "filename*" + ("filename" -> fExt)) getOrElse all
} ~> (`Content-Disposition`(_, _))
} @aeons do you have time to take a look at it? |
@counter2015 some time ago, I proposed these changes you described – #6475. But we have ended up with this #6485. |
@danicheg That sounds good! Was this commit merged into series/0.23 ? |
@counter2015 yes, it was first released in 0.23.13. |
I just test code above on this version, it cant pass on latest version. val http4s = "0.23.16"
val http4sBlaze = "0.23.12"
libraryDependencies ++= Seq(
"org.http4s" %% "http4s-dsl" % http4s,
"org.http4s" %% "http4s-blaze-server" % http4sBlaze,
"org.http4s" %% "http4s-blaze-client" % http4sBlaze
) it seems cant parse correctly, it's a regression here? the request is generate from postman(without any customisations )
And the server print this message to console
And on http4s 0.23.13, it complains that
|
Issue persists in 0.23.17 |
I'm trying to invest the exception cause. The parser implement is adpated from akka-http The solution of akka to handle no-ascii character in In http4s, it has the same logic too, but seems not work fine. http4s/core/shared/src/main/scala/org/http4s/headers/Content-Disposition.scala Lines 89 to 127 in e896073
in line IMO, the ideal result of parser could be following: test("Content-Disposition header should work when `filename` contains no-ascii character but has `filename*` field") {
val headerValue = """attachment; filename="中文文件名.json"; filename*=UTF-8''%E4%B8%AD%E6%96%87%E6%96%87%E4%BB%B6%E5%90%8D.json"""
val parsedHeader = `Content-Disposition`.parse(headerValue)
assertEquals(parsedHeader.map(_.filename), Right(Some("中文文件名.json")))
} but this would be very annoying to implement: we should first check the as a fallback, playframework choose parser result like this https://github.com/playframework/playframework/pull/8314/files "support UTF-8 encoded filenames in Content-Disposition headers" in {
val tempFile: Path = JFiles.createTempFile("ScalaResultsHandlingSpec", "txt")
try {
withServer {
import scala.concurrent.ExecutionContext.Implicits.global
implicit val mimeTypes: FileMimeTypes = new DefaultFileMimeTypes(FileMimeTypesConfiguration())
Results.Ok.sendFile(
tempFile.toFile,
fileName = _ => "测 试.tmp"
)
} { port =>
val response = BasicHttpClient.makeRequests(port)(
BasicRequest("GET", "/", "HTTP/1.1", Map(), "")
).head
response.status must_== 200
response.body must beLeft("")
response.headers.get(CONTENT_DISPOSITION) must beSome(s"""inline; filename="? ?.tmp"; filename*=utf-8''%e6%b5%8b%20%e8%af%95.tmp""")
}
} finally {
tempFile.toFile.delete()
}
} |
I hadn't seen this issue before #7419, but there's an extra complication here. |
Using http4s 0.23.7.
Given the a simple service which parses the body as a multipart, which:
And invocation:
The result on stdout is:
The text was updated successfully, but these errors were encountered: