Skip to content

Quote the multipart boundary when it isn't a token#9527

Open
eyupcanakman wants to merge 1 commit into
square:masterfrom
eyupcanakman:fix/quote-multipart-boundary
Open

Quote the multipart boundary when it isn't a token#9527
eyupcanakman wants to merge 1 commit into
square:masterfrom
eyupcanakman:fix/quote-multipart-boundary

Conversation

@eyupcanakman

Copy link
Copy Markdown

Fixes #8068.

MultipartBody builds its Content-Type without quoting the boundary. A boundary with a non-token character, like the colon in abc:def, isn't a valid token, so toMediaType() rejects it and build() throws.

This quotes the boundary when it isn't a token, per RFC 2045 section 5.1, so the Content-Type parses and round-trips through parameter("boundary"). A boundary containing a quote or backslash can't round-trip through the quoted-string, so it's rejected instead. Added a test for the colon boundary and one for the rejected case.

Comment on lines +366 to +367
if (TOKEN.matches(this)) return this
require('"' !in this && '\\' !in this) { "boundary contains a character that can't be quoted: $this" }

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really like that this does three separate passes over the string.

And then if it has to quote it creates a string builder to produce a string only to append that to a string builder at the sole call site.

This code isn't in the hot path or anything, but it irks me a little how inefficient it is.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I figured because the current happy path is 1 pass, then it wasn't too bad.

But 1 pass and terminating early should be possible.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed it to a single pass that writes straight into the buildString, so there's no intermediate string. It stops the token check once it knows the boundary needs quoting, and only keeps scanning to reject a " or \.

MultipartBody
.Builder("a\"; charset=\"evil")
.addPart("Hello, World!".toRequestBody(null))
.build()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should only wrap the function which can fail and we need to assert on the message to ensure the code path we want to test is the one being hit (as opposed to some other validation now or in the future).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now only build() is wrapped, and it checks the message so the test can't pass on a different IllegalArgumentException.

MultipartBody built its Content-Type without quoting the boundary, so a boundary with a non-token character like ':' failed toMediaType() and threw.
Quote it per RFC 2045 section 5.1. A boundary with '"' or '\' can't round-trip through MediaType's quoted-string, so reject those.

Fixes square#8068.
@eyupcanakman eyupcanakman force-pushed the fix/quote-multipart-boundary branch from f88e43f to 6f25d58 Compare July 7, 2026 12:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Failed to build MultipartReader

3 participants