mirror of
https://github.com/github/rails.git
synced 2026-04-04 03:00:58 -04:00
Fix binary data corruption bug in PostgreSQL adaptor
1. Move the binary escape/unescape from column to the driver - we should store binary data AR just like most other adaptors
2. check to make sure we only unescape bytea data
PGresult.ftype( column ) == 17
that is passed to us in escaped format
PGresult.fformat( column ) == 0
Signed-off-by: Michael Koziarski <michael@koziarski.com>
[#1063 state:committed]
This commit is contained in:
committed by
Michael Koziarski
parent
5c97d4ff29
commit
932dffc559
@@ -68,72 +68,6 @@ module ActiveRecord
|
||||
super
|
||||
end
|
||||
|
||||
# Escapes binary strings for bytea input to the database.
|
||||
def self.string_to_binary(value)
|
||||
if PGconn.respond_to?(:escape_bytea)
|
||||
self.class.module_eval do
|
||||
define_method(:string_to_binary) do |value|
|
||||
PGconn.escape_bytea(value) if value
|
||||
end
|
||||
end
|
||||
else
|
||||
self.class.module_eval do
|
||||
define_method(:string_to_binary) do |value|
|
||||
if value
|
||||
result = ''
|
||||
value.each_byte { |c| result << sprintf('\\\\%03o', c) }
|
||||
result
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
self.class.string_to_binary(value)
|
||||
end
|
||||
|
||||
# Unescapes bytea output from a database to the binary string it represents.
|
||||
def self.binary_to_string(value)
|
||||
# In each case, check if the value actually is escaped PostgreSQL bytea output
|
||||
# or an unescaped Active Record attribute that was just written.
|
||||
if PGconn.respond_to?(:unescape_bytea)
|
||||
self.class.module_eval do
|
||||
define_method(:binary_to_string) do |value|
|
||||
if value =~ /\\\d{3}/
|
||||
PGconn.unescape_bytea(value)
|
||||
else
|
||||
value
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
self.class.module_eval do
|
||||
define_method(:binary_to_string) do |value|
|
||||
if value =~ /\\\d{3}/
|
||||
result = ''
|
||||
i, max = 0, value.size
|
||||
while i < max
|
||||
char = value[i]
|
||||
if char == ?\\
|
||||
if value[i+1] == ?\\
|
||||
char = ?\\
|
||||
i += 1
|
||||
else
|
||||
char = value[i+1..i+3].oct
|
||||
i += 3
|
||||
end
|
||||
end
|
||||
result << char
|
||||
i += 1
|
||||
end
|
||||
result
|
||||
else
|
||||
value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
self.class.binary_to_string(value)
|
||||
end
|
||||
|
||||
# Maps PostgreSQL-specific data types to logical Rails types.
|
||||
def simplified_type(field_type)
|
||||
case field_type
|
||||
@@ -347,10 +281,78 @@ module ActiveRecord
|
||||
|
||||
# QUOTING ==================================================
|
||||
|
||||
# Escapes binary strings for bytea input to the database.
|
||||
def escape_bytea(value)
|
||||
if PGconn.respond_to?(:escape_bytea)
|
||||
self.class.instance_eval do
|
||||
define_method(:escape_bytea) do |value|
|
||||
PGconn.escape_bytea(value) if value
|
||||
end
|
||||
end
|
||||
else
|
||||
self.class.instance_eval do
|
||||
define_method(:escape_bytea) do |value|
|
||||
if value
|
||||
result = ''
|
||||
value.each_byte { |c| result << sprintf('\\\\%03o', c) }
|
||||
result
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
escape_bytea(value)
|
||||
end
|
||||
|
||||
# Unescapes bytea output from a database to the binary string it represents.
|
||||
# NOTE: This is NOT an inverse of escape_bytea! This is only to be used
|
||||
# on escaped binary output from database drive.
|
||||
def unescape_bytea(value)
|
||||
# In each case, check if the value actually is escaped PostgreSQL bytea output
|
||||
# or an unescaped Active Record attribute that was just written.
|
||||
if PGconn.respond_to?(:unescape_bytea)
|
||||
self.class.instance_eval do
|
||||
define_method(:unescape_bytea) do |value|
|
||||
if value =~ /\\\d{3}/
|
||||
PGconn.unescape_bytea(value)
|
||||
else
|
||||
value
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
self.class.instance_eval do
|
||||
define_method(:unescape_bytea) do |value|
|
||||
if value =~ /\\\d{3}/
|
||||
result = ''
|
||||
i, max = 0, value.size
|
||||
while i < max
|
||||
char = value[i]
|
||||
if char == ?\\
|
||||
if value[i+1] == ?\\
|
||||
char = ?\\
|
||||
i += 1
|
||||
else
|
||||
char = value[i+1..i+3].oct
|
||||
i += 3
|
||||
end
|
||||
end
|
||||
result << char
|
||||
i += 1
|
||||
end
|
||||
result
|
||||
else
|
||||
value
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
unescape_bytea(value)
|
||||
end
|
||||
|
||||
# Quotes PostgreSQL-specific data types for SQL input.
|
||||
def quote(value, column = nil) #:nodoc:
|
||||
if value.kind_of?(String) && column && column.type == :binary
|
||||
"#{quoted_string_prefix}'#{column.class.string_to_binary(value)}'"
|
||||
"#{quoted_string_prefix}'#{escape_bytea(value)}'"
|
||||
elsif value.kind_of?(String) && column && column.sql_type =~ /^xml$/
|
||||
"xml '#{quote_string(value)}'"
|
||||
elsif value.kind_of?(Numeric) && column && column.sql_type =~ /^money$/
|
||||
@@ -463,11 +465,20 @@ module ActiveRecord
|
||||
|
||||
# create a 2D array representing the result set
|
||||
def result_as_array(res) #:nodoc:
|
||||
# check if we have any binary column and if they need escaping
|
||||
unescape_col = []
|
||||
for j in 0...res.nfields do
|
||||
# unescape string passed BYTEA field (OID == 17)
|
||||
unescape_col << ( res.fformat(j)==0 and res.ftype(j)==17 )
|
||||
end
|
||||
|
||||
ary = []
|
||||
for i in 0...res.ntuples do
|
||||
ary << []
|
||||
for j in 0...res.nfields do
|
||||
ary[i] << res.getvalue(i,j)
|
||||
data = res.getvalue(i,j)
|
||||
data = unescape_bytea(data) if unescape_col[j] and data.is_a?(String)
|
||||
ary[i] << data
|
||||
end
|
||||
end
|
||||
return ary
|
||||
|
||||
Reference in New Issue
Block a user