Skip to content

fix(client): replace lone UTF-16 surrogates instead of dropping the message - #648

Merged
route merged 1 commit into
mainfrom
fix-binary-post-data
Oct 16, 2026
Merged

route merged 1 commit into
mainfrom
fix-binary-post-data

Conversation

@route

@route route commented Oct 16, 2026

Copy link
Copy Markdown
Member

Fixes rubycdp/cuprite#308.

With blacklist=/whitelist= (Cuprite's url_blacklist), every request is paused with Fetch.requestPaused until Ferrum continues or aborts it. For an in-memory Blob body, Chrome includes the raw bytes as postData, and some byte sequences come out as lone UTF-16 surrogates, e.g. ED BD B5 → \udf75. The json gem rejects lone leading surrogates on every version and lone trailing ones since 3.x. The fallback from #452 unescaped every \uXXXX, which turned escaped control characters into raw ones and failed as well. So the message was dropped, Fetch.continueRequest was never sent, and the upload hung. Rails 8's ActiveStorage direct upload sends file.slice(), a Blob, so it hit this after upgrading from 7.2.

When the first parse fails, only lone surrogate escapes are replaced with �. Escapes are matched left to right, so valid pairs, \", \\ and control-character escapes stay as they are.

Behavior change: broken text set from JS (#452's case) now reads as � instead of ???. The existing spec is updated.

Upload body (300 KB) with url_blacklist Before After
xhr.send(file) done done
xhr.send(file.slice()) hangs done
fetch with new Blob([...]) hangs done
string body done done

The new spec uploads a Blob with a lone leading surrogate, a quote, a backslash, NUL and a newline while a blacklist is set. Without the fix it raises Ferrum::TimeoutError. Ferrum's bundle has json 2.18; I also checked that the real 300 KB message from the report parses with json 3.1.0.

…essage

Chrome sends binary request bodies, e.g. a Blob upload paused by blacklist=/whitelist=, and broken page text with lone
surrogates that the json gem rejects. The fallback unescaped every \uXXXX, which broke messages with escaped control
characters, so Fetch.requestPaused was dropped and the request hung. Only lone surrogates are replaced with U+FFFD now.
Refs rubycdp/cuprite#308
@route
route merged commit bc63f77 into main Oct 16, 2026
7 checks passed
@route
route deleted the fix-binary-post-data branch October 16, 2026 09:59
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.

Cuprite url_blacklist (Fetch interception) can hang XHR PUT with Blob body (Rails ActiveStorage direct uploads)

1 participant