Fix double-encoding of escaped string variables in multipart uploads#9810
Open
BickelLukas wants to merge 1 commit into
Open
Fix double-encoding of escaped string variables in multipart uploads#9810BickelLukas wants to merge 1 commit into
BickelLukas wants to merge 1 commit into
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR fixes a bug in HttpMultipartMiddleware where rewriting the operations JSON for GraphQL multipart uploads could double-encode escaped string variables (e.g., turning \" into \\\"), causing resolvers to receive incorrect string values.
Changes:
- Update JSON rewrite logic to decode escaped string tokens (
ValueIsEscaped) viaUtf8JsonReader.GetString()before writing, while retaining a zero-allocation path for unescaped strings. - Add an integration test covering multipart upload with a non-file string variable containing an escaped quote.
- Extend the upload test query utilities with a resolver that echoes back the provided text alongside consuming the uploaded file.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| src/HotChocolate/AspNetCore/test/AspNetCore.Tests/HttpMultipartMiddlewareTests.cs | Adds a regression test ensuring escaped string variables are not double-encoded during multipart operations rewrite. |
| src/HotChocolate/AspNetCore/test/AspNetCore.Tests.Utilities/UploadQuery.cs | Adds UploadWithText resolver used by the new test to validate the received string value. |
| src/HotChocolate/AspNetCore/src/AspNetCore.Pipeline/HttpMultipartMiddleware.cs | Fixes string token copying to avoid re-escaping already-escaped JSON string content. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
When a GraphQL multipart request (
graphql-multipart-request-spec) is processed,HttpMultipartMiddlewarerewrites theoperationsJSON to inject file references in place of thenullplaceholders. During this rewrite, any string variable that was not a file was copied usingwriter.WriteStringValue(reader.ValueSpan).ValueSpanreturns the raw, still-escaped JSON bytes, whileWriteStringValue(ReadOnlySpan<byte>)treats its input as unescaped text and escapes it again. The result is that every escape sequence in a string variable was doubled.For example, a variable with the value
test " test(JSON:"test \" test") was rewritten to"test \\\" test", so the resolver receivedtest \" testinstead oftest " test. The same applied to any backslash, newline, unicode escape, etc.Fix
Decode the value before writing it back, but keep a zero-allocation fast path when no escaping is involved:
Tests
Added
Upload_File_With_Escaped_String_Variable_Is_Not_Double_Encoded, which sends a file upload alongside a string variable containing an escaped quote and asserts the resolver receives the original, correctly-decoded value. Verified on net8.0, net9.0, and net10.0.