public
Description: Ruby on Rails
Homepage: http://rubyonrails.org
Clone URL: git://github.com/rails/rails.git
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]
Adam Majer (author)
Fri Sep 19 19:38:39 -0700 2008
NZKoz (committer)
Sat Oct 25 03:54:48 -0700 2008
commit  932dffc559ef188eb31d0223116e9da361833488
tree    3b0afabc3f70537b143b8c468182bcc329bbb4c9
parent  5c97d4ff29cfd944da751f01177a3024626d57bb
...
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
...
347
348
349
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
350
351
352
353
 
354
355
356
...
463
464
465
 
 
 
 
 
 
 
466
467
468
469
470
 
 
 
471
472
473
...
68
69
70
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
72
73
...
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
 
355
356
357
358
...
465
466
467
468
469
470
471
472
473
474
475
476
477
478
 
479
480
481
482
483
484
0
@@ -68,72 +68,6 @@ module ActiveRecord
0
           super
0
         end
0
   
0
-        # Escapes binary strings for bytea input to the database.
0
-        def self.string_to_binary(value)
0
-          if PGconn.respond_to?(:escape_bytea)
0
-            self.class.module_eval do
0
-              define_method(:string_to_binary) do |value|
0
-                PGconn.escape_bytea(value) if value
0
-              end
0
-            end
0
-          else
0
-            self.class.module_eval do
0
-              define_method(:string_to_binary) do |value|
0
-                if value
0
-                  result = ''
0
-                  value.each_byte { |c| result << sprintf('\\\\%03o', c) }
0
-                  result
0
-                end
0
-              end
0
-            end
0
-          end
0
-          self.class.string_to_binary(value)
0
-        end
0
-  
0
-        # Unescapes bytea output from a database to the binary string it represents.
0
-        def self.binary_to_string(value)
0
-          # In each case, check if the value actually is escaped PostgreSQL bytea output
0
-          # or an unescaped Active Record attribute that was just written.
0
-          if PGconn.respond_to?(:unescape_bytea)
0
-            self.class.module_eval do
0
-              define_method(:binary_to_string) do |value|
0
-                if value =~ /\\\d{3}/
0
-                  PGconn.unescape_bytea(value)
0
-                else
0
-                  value
0
-                end
0
-              end
0
-            end
0
-          else
0
-            self.class.module_eval do
0
-              define_method(:binary_to_string) do |value|
0
-                if value =~ /\\\d{3}/
0
-                  result = ''
0
-                  i, max = 0, value.size
0
-                  while i < max
0
-                    char = value[i]
0
-                    if char == ?\\
0
-                      if value[i+1] == ?\\
0
-                        char = ?\\
0
-                        i += 1
0
-                      else
0
-                        char = value[i+1..i+3].oct
0
-                        i += 3
0
-                      end
0
-                    end
0
-                    result << char
0
-                    i += 1
0
-                  end
0
-                  result
0
-                else
0
-                  value
0
-                end
0
-              end
0
-            end
0
-          end
0
-          self.class.binary_to_string(value)
0
-        end  
0
-  
0
         # Maps PostgreSQL-specific data types to logical Rails types.
0
         def simplified_type(field_type)
0
           case field_type
0
@@ -347,10 +281,78 @@ module ActiveRecord
0
 
0
       # QUOTING ==================================================
0
 
0
+      # Escapes binary strings for bytea input to the database.
0
+      def escape_bytea(value)
0
+        if PGconn.respond_to?(:escape_bytea)
0
+          self.class.instance_eval do
0
+            define_method(:escape_bytea) do |value|
0
+              PGconn.escape_bytea(value) if value
0
+            end
0
+          end
0
+        else
0
+          self.class.instance_eval do
0
+            define_method(:escape_bytea) do |value|
0
+              if value
0
+                result = ''
0
+                value.each_byte { |c| result << sprintf('\\\\%03o', c) }
0
+                result
0
+              end
0
+            end
0
+          end
0
+        end
0
+        escape_bytea(value)
0
+      end
0
+
0
+      # Unescapes bytea output from a database to the binary string it represents.
0
+      # NOTE: This is NOT an inverse of escape_bytea! This is only to be used
0
+      #       on escaped binary output from database drive.
0
+      def unescape_bytea(value)
0
+        # In each case, check if the value actually is escaped PostgreSQL bytea output
0
+        # or an unescaped Active Record attribute that was just written.
0
+        if PGconn.respond_to?(:unescape_bytea)
0
+          self.class.instance_eval do
0
+            define_method(:unescape_bytea) do |value|
0
+              if value =~ /\\\d{3}/
0
+                PGconn.unescape_bytea(value)
0
+              else
0
+                value
0
+              end
0
+            end
0
+          end
0
+        else
0
+          self.class.instance_eval do
0
+            define_method(:unescape_bytea) do |value|
0
+              if value =~ /\\\d{3}/
0
+                result = ''
0
+                i, max = 0, value.size
0
+                while i < max
0
+                  char = value[i]
0
+                  if char == ?\\
0
+                    if value[i+1] == ?\\
0
+                      char = ?\\
0
+                      i += 1
0
+                    else
0
+                      char = value[i+1..i+3].oct
0
+                      i += 3
0
+                    end
0
+                  end
0
+                  result << char
0
+                  i += 1
0
+                end
0
+                result
0
+              else
0
+                value
0
+              end
0
+            end
0
+          end
0
+        end
0
+        unescape_bytea(value)
0
+      end
0
+
0
       # Quotes PostgreSQL-specific data types for SQL input.
0
       def quote(value, column = nil) #:nodoc:
0
         if value.kind_of?(String) && column && column.type == :binary
0
-          "#{quoted_string_prefix}'#{column.class.string_to_binary(value)}'"
0
+          "#{quoted_string_prefix}'#{escape_bytea(value)}'"
0
         elsif value.kind_of?(String) && column && column.sql_type =~ /^xml$/
0
           "xml '#{quote_string(value)}'"
0
         elsif value.kind_of?(Numeric) && column && column.sql_type =~ /^money$/
0
@@ -463,11 +465,20 @@ module ActiveRecord
0
 
0
       # create a 2D array representing the result set
0
       def result_as_array(res) #:nodoc:
0
+        # check if we have any binary column and if they need escaping
0
+        unescape_col = []
0
+        for j in 0...res.nfields do
0
+          # unescape string passed BYTEA field (OID == 17)
0
+          unescape_col << ( res.fformat(j)==0 and res.ftype(j)==17 )
0
+        end
0
+
0
         ary = []
0
         for i in 0...res.ntuples do
0
           ary << []
0
           for j in 0...res.nfields do
0
-            ary[i] << res.getvalue(i,j)
0
+            data = res.getvalue(i,j)
0
+            data = unescape_bytea(data) if unescape_col[j] and data.is_a?(String)
0
+            ary[i] << data
0
           end
0
         end
0
         return ary

Comments