Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
### Changed

### Fixed
- A paused request with a binary body, e.g. a `Blob` upload with `blacklist=`/`whitelist=` set, hung forever: Chrome
sends the body with lone UTF-16 surrogates that the `json` gem rejects, and the fallback that unescaped them broke
the message further, so `Fetch.requestPaused` was dropped and the request was never continued. Lone surrogates are
now replaced with U+FFFD, so broken text from the page reads as `�` instead of `???` [rubycdp/cuprite#308]
- Passing a node resolved on the previous page to `evaluate`, e.g. when the page navigates while Cuprite searches
within an element, raised `Ferrum::BrowserError: Argument should belong to the same JavaScript world as target
object`. It's `Ferrum::NodeNotFoundError` now, so callers can treat the node as stale and find it again
Expand Down
11 changes: 5 additions & 6 deletions lib/ferrum/client/web_socket.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class WebSocket
WEBSOCKET_BUG_SLEEP = 0.05
DEFAULT_PORTS = { "ws" => 80, "wss" => 443 }.freeze
SKIP_LOGGING_SCREENSHOTS = !ENV["FERRUM_LOGGING_SCREENSHOTS"]
ESCAPE_OR_LONE_SURROGATE = /\\u[dD][89abAB]\h{2}\\u[dD][c-fC-F]\h{2}|\\u([dD][89a-fA-F]\h{2})|\\./

attr_reader :url, :messages

Expand Down Expand Up @@ -223,14 +224,12 @@ def safely_parse_json(data)
rescue JSON::NestingError
# nop
rescue JSON::ParserError
safely_parse_escaped_json(data)
safely_parse_lone_surrogates(data)
end

def safely_parse_escaped_json(data)
unescaped_unicode =
data.gsub(/\\u([\da-fA-F]{4})/) { |_| [::Regexp.last_match(1)].pack("H*").unpack("n*").pack("U*") }
escaped_data = unescaped_unicode.encode("UTF-8", "UTF-8", undef: :replace, invalid: :replace, replace: "?")
JSON.parse(escaped_data, max_nesting: false)
def safely_parse_lone_surrogates(data)
replaced = data.gsub(ESCAPE_OR_LONE_SURROGATE) { |escape| ::Regexp.last_match(1) ? "\\ufffd" : escape }
JSON.parse(replaced, max_nesting: false)
rescue JSON::ParserError
# nop
end
Expand Down
12 changes: 12 additions & 0 deletions spec/network_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,18 @@
expect(page.body).not_to include("Disappearing header")
end

it "continues requests with a binary body" do
network.blacklist = /jquery/
page.go_to("/")

size = page.evaluate_async(<<~JS, page.timeout)
const bytes = new Uint8Array([0x22, 0x5c, 0x00, 0x0a, 0xed, 0xa0, 0x80, 0x41]);
fetch("/upload", { method: "PUT", body: new Blob([bytes]) }).then((r) => r.text()).then(arguments[0]);
JS

expect(size).to eq("8")
end

it "blocks with single pattern" do
network.blacklist = /jquery/

Expand Down
2 changes: 1 addition & 1 deletion spec/node_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -482,7 +482,7 @@
browser.go_to("/unicode")
node = browser.at_css("#invalid")

expect(node.text).to eq("Havregrynskake med marengs og nøtter ???")
expect(node.text).to eq("Havregrynskake med marengs og nøtter \uFFFD")
end

it "works with curly utf8" do
Expand Down
4 changes: 4 additions & 0 deletions spec/support/application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ def authorized?(login, password)
"Current host is #{request.scheme}://#{request.host}:#{request.port}"
end

put "/upload" do
request.body.read.bytesize.to_s
end

get "/unicode" do
File.read("#{FERRUM_VIEWS}/unicode.html")
end
Expand Down
Loading