From 98bf64bcb9648f88bff4cb59a7ae4db2b6410241 Mon Sep 17 00:00:00 2001 From: Andrew White Date: Tue, 13 Apr 2021 15:26:57 +0200 Subject: [PATCH] Use StringScanner to parse Hstore payloads --- .../postgresql/oid/hstore.rb | 61 +++++++++++++++---- .../cases/adapters/postgresql/hstore_test.rb | 1 + 2 files changed, 51 insertions(+), 11 deletions(-) diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb index 1fbf3ffe9db6a..386af30e409e8 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/hstore.rb @@ -1,10 +1,14 @@ # frozen_string_literal: true +require "strscan" + module ActiveRecord module ConnectionAdapters module PostgreSQL module OID # :nodoc: class Hstore < Type::Value # :nodoc: + ERROR = "Invalid Hstore document: %s" + include ActiveModel::Type::Helpers::Mutable def type @@ -12,21 +16,56 @@ def type end def deserialize(value) - if value.is_a?(::String) - hash = {} - value.scan(HstorePair) do |key, value| - key.gsub!('\"', '"') - key.gsub!('\\\\', '\\') + return value unless value.is_a?(::String) - value&.gsub!('\"', '"') - value&.gsub!('\\\\', '\\') + scanner = StringScanner.new(value) + hash = {} - hash[key] = value + until scanner.eos? + unless scanner.skip(/"/) + raise(ArgumentError, ERROR % scanner.string.inspect) + end + + unless key = scanner.scan_until(/(??/) + raise(ArgumentError, ERROR % scanner.string.inspect) + end + + if scanner.scan(/NULL/) + value = nil + else + unless scanner.skip(/"/) + raise(ArgumentError, ERROR % scanner.string.inspect) + end + + unless value = scanner.scan_until(/(? 'b\\ar', '1"foo' => "2") + assert_cycle('a\\"' => 'b\\ar', '1"foo' => "2") end def test_comma