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
32 changes: 32 additions & 0 deletions rb/lib/selenium/webdriver/remote/http/common.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class Common
'Accept' => CONTENT_TYPE,
'Content-Type' => "#{CONTENT_TYPE}; charset=UTF-8"
}.freeze
BINARY_ENCODINGS = [Encoding::BINARY, Encoding::ASCII_8BIT].freeze

class << self
attr_accessor :extra_headers
Expand Down Expand Up @@ -55,6 +56,7 @@ def call(verb, url, command_hash)
headers['Cache-Control'] = 'no-cache' if verb == :get

if command_hash
command_hash = ensure_utf8_encoding(command_hash)
payload = JSON.generate(command_hash)
headers['Content-Length'] = payload.bytesize.to_s if %i[post put].include?(verb)

Expand Down Expand Up @@ -91,6 +93,36 @@ def request(*)
raise NotImplementedError, 'subclass responsibility'
end

def ensure_utf8_encoding(obj)
case obj
when String
encode_string_to_utf8(obj)
when Array
obj.map { |item| ensure_utf8_encoding(item) }
when Hash
obj.each_with_object({}) do |(key, value), result|
result[ensure_utf8_encoding(key)] = ensure_utf8_encoding(value)
end
else
obj
end
end

def encode_string_to_utf8(str)
return str if str.encoding == Encoding::UTF_8 && str.valid_encoding?

if BINARY_ENCODINGS.include?(str.encoding)
result = str.dup.force_encoding(Encoding::UTF_8)
return result if result.valid_encoding?
end

str.encode(Encoding::UTF_8)
rescue EncodingError => e
raise Error::WebDriverError,
"Unable to encode string to UTF-8: #{e.message}. " \
"String encoding: #{str.encoding}, content: #{str.inspect}"
end

def create_response(code, body, content_type)
code = code.to_i
body = body.to_s.strip
Expand Down
73 changes: 73 additions & 0 deletions rb/spec/unit/selenium/webdriver/remote/http/common_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,79 @@ module Http
.with(:post, URI.parse('http://server/session'),
hash_including('User-Agent' => 'rspec/1.0 (ruby 3.2)'), '{}')
end

context 'when encoding strings to UTF-8' do
it 'converts binary-encoded strings that are valid UTF-8' do
binary_string = +'return navigator.userAgent;'
binary_string.force_encoding(Encoding::BINARY)
command_hash = {script: binary_string, args: []}

common.call(:post, 'execute', command_hash)

expect(common).to have_received(:request) do |_verb, _url, _headers, payload|
expect { JSON.parse(payload) }.not_to raise_error
parsed = JSON.parse(payload)
expect(parsed['script']).to eq('return navigator.userAgent;')
expect(parsed['script'].encoding).to eq(Encoding::UTF_8)
end
end

it 'converts binary-encoded strings in nested hashes' do
binary_string = +'test value'
binary_string.force_encoding(Encoding::BINARY)
command_hash = {
outer: {
inner: binary_string,
another: 'utf8 string'
}
}

common.call(:post, 'test', command_hash)

expect(common).to have_received(:request) do |_verb, _url, _headers, payload|
expect { JSON.parse(payload) }.not_to raise_error
parsed = JSON.parse(payload)
expect(parsed['outer']['inner']).to eq('test value')
end
end

it 'converts binary-encoded strings in arrays' do
binary_string = +'array item'
binary_string.force_encoding(Encoding::BINARY)
command_hash = {items: [binary_string, 'utf8 item']}

common.call(:post, 'test', command_hash)

expect(common).to have_received(:request) do |_verb, _url, _headers, payload|
expect { JSON.parse(payload) }.not_to raise_error
parsed = JSON.parse(payload)
expect(parsed['items']).to eq(['array item', 'utf8 item'])
end
end

it 'raises error for invalid byte sequences' do
# Create an invalid UTF-8 byte sequence
invalid_string = +"\xFF\xFE"
invalid_string.force_encoding(Encoding::BINARY)
command_hash = {script: invalid_string}

expect { common.call(:post, 'execute', command_hash) }
.to raise_error(WebDriver::Error::WebDriverError, /Unable to encode string to UTF-8/)
end

it 'handles already UTF-8 encoded strings' do
utf8_string = 'already utf-8'
command_hash = {script: utf8_string}

common.call(:post, 'execute', command_hash)

expect(common).to have_received(:request) do |_verb, _url, _headers, payload|
expect { JSON.parse(payload) }.not_to raise_error
parsed = JSON.parse(payload)
expect(parsed['script']).to eq('already utf-8')
end
end
end
end
end # Http
end # Remote
Expand Down