Client. Always quote name and filename in multipart Content-Disposition#5573
Client. Always quote name and filename in multipart Content-Disposition#5573bjhham merged 2 commits intoktorio:mainfrom
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughThis PR changes multipart form-data serialization so Content-Disposition parameters use quoted-string formatting. ChangesMultipart Content-Disposition Quoting
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
ktor-http/common/src/io/ktor/http/HeaderValueWithParameters.kt (1)
126-149:⚠️ Potential issue | 🟡 Minor | ⚡ Quick win
isQuoted()accepts values with an escaped closing quote.Line 132 treats the last
"as a valid closing delimiter without verifying it is not escaped, so malformed input like"abc\"can be reported as quoted.💡 Suggested fix
public fun String.isQuoted(): Boolean { if (length < 2) { return false } if (first() != '"' || last() != '"') { return false } + var trailingSlashes = 0 + var trailingSlashIndex = lastIndex - 1 + while (trailingSlashIndex >= 0 && this[trailingSlashIndex] == '\\') { + trailingSlashes++ + trailingSlashIndex-- + } + if (trailingSlashes % 2 != 0) { + return false + } var startIndex = 1 do { val index = indexOf('"', startIndex) if (index == lastIndex) { break🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@ktor-http/common/src/io/ktor/http/HeaderValueWithParameters.kt` around lines 126 - 149, The isQuoted() routine incorrectly treats the final double-quote as a valid closing delimiter without verifying it's not escaped; update the logic in isQuoted() to check that the quote found at lastIndex is not escaped by counting preceding backslashes (like you do for other quotes) and only accept it as a closing quote when the number of preceding '\' characters is even (i.e. the quote is not escaped); concretely, in the loop that finds index = indexOf('"', startIndex) handle the case index == lastIndex by computing slashesCount the same way you compute it for other quotes and return false if that quote is escaped, otherwise accept and break/return true.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@ktor-client/ktor-client-core/common/test/MultiPartFormDataContentTest.kt`:
- Around line 251-269: The test title is misleading:
MultiPartFormDataContentTest.`filename parameter is always quoted in
Content-Disposition` asserts an unquoted filename because the header was
injected raw and bypasses quoteForMultipart via formData() ->
appendAll(headers); rename the test to reflect actual behavior (e.g., "filename
parameter is not quoted when provided in raw Content-Disposition header") or
alternatively change the setup to let the library produce the
Content-Disposition (remove the raw header and rely on append(key=...,
filename=...)) so the assertion matches the title; refer to
MultiPartFormDataContent, formData, appendAll(headers), and quoteForMultipart
when making the change.
---
Outside diff comments:
In `@ktor-http/common/src/io/ktor/http/HeaderValueWithParameters.kt`:
- Around line 126-149: The isQuoted() routine incorrectly treats the final
double-quote as a valid closing delimiter without verifying it's not escaped;
update the logic in isQuoted() to check that the quote found at lastIndex is not
escaped by counting preceding backslashes (like you do for other quotes) and
only accept it as a closing quote when the number of preceding '\' characters is
even (i.e. the quote is not escaped); concretely, in the loop that finds index =
indexOf('"', startIndex) handle the case index == lastIndex by computing
slashesCount the same way you compute it for other quotes and return false if
that quote is escaped, otherwise accept and break/return true.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 6ea736cb-f2e1-4e7f-b553-cc3e5006caef
📒 Files selected for processing (11)
ktor-client/ktor-client-core/common/src/io/ktor/client/request/forms/formDsl.ktktor-client/ktor-client-core/common/test/FormDslTest.ktktor-client/ktor-client-core/common/test/MultiPartFormDataContentTest.ktktor-client/ktor-client-core/js/test/io/ktor/client/request/forms/FormDslJsTest.ktktor-client/ktor-client-core/wasmJs/test/io/ktor/client/request/forms/FormDslWasmJsTest.ktktor-client/ktor-client-core/web/src/io/ktor/client/request/forms/FormDsl.web.ktktor-client/ktor-client-tests/common/test/io/ktor/client/tests/ContentTest.ktktor-client/ktor-client-tests/common/test/io/ktor/client/tests/LoggingMockedTests.ktktor-http/api/ktor-http.apiktor-http/api/ktor-http.klib.apiktor-http/common/src/io/ktor/http/HeaderValueWithParameters.kt
bjhham
left a comment
There was a problem hiding this comment.
Please address the rabbit's comment regarding the test name contradiction and fix the assertion in io.ktor.client.plugins.logging.OkHttpFormatTest
- Rewrite filename quoting test to use FormBuilder.append(key, filename, ...) so the library's quoteForMultipart path is actually exercised, matching the test name. - Update OkHttpFormatTest.multipartBinaryBody assertions and Content-Length to reflect the new quoted name parameters.
|
Addressed in c9130b1 — fixed the test name/behavior mismatch and updated OkHttpFormatTest assertions. |
Subsystem
Client Core
Motivation
Fixes #5157
Ktor emits unquoted
name=/filename=in multipartContent-Disposition. Strict HTML-spec parsers (e.g. Elysia on Bun) silently drop unquoted parameters, while browsers, curl, OkHttp, and other major clients always quote.Solution
nameandfilenamevia a smallquoteForMultipart()helper that skips re-quoting if the value is already quoted.String.isQuoted()to@InternalAPI publicso the multipart writer inktor-client-corecan reuse it.