@@ -17,23 +17,42 @@ def read_until(char)
17
17
18
18
module SerializationState
19
19
class Base
20
+ def initialize
21
+ @last_processed_value_index = -1
22
+ end
23
+
20
24
# @return [Boolean]
21
25
attr_accessor :assoc
26
+
27
+ # @return [Integer]
28
+ attr_accessor :last_processed_value_index
22
29
end
23
30
24
31
class ToSerialize < Base
32
+ def initialize
33
+ super
34
+ @serialized_object_id_to_index_map = { }
35
+ end
36
+
37
+ # @return [Hash{Integer => Integer}]
38
+ attr_accessor :serialized_object_id_to_index_map
25
39
end
26
40
27
41
class ToUnserialize < Base
28
42
def initialize
43
+ super
29
44
@classmap = { }
45
+ @unserialized_values = [ ]
30
46
end
31
47
32
48
# @return [Hash{String => Class}]
33
49
attr_accessor :classmap
34
50
35
51
# @return [String]
36
52
attr_accessor :original_encoding
53
+
54
+ # @return [Array<Object>]
55
+ attr_accessor :unserialized_values
37
56
end
38
57
end
39
58
@@ -58,6 +77,7 @@ def PHP.serialize(var, assoc = false) # {{{
58
77
end
59
78
60
79
def PHP . do_serialize ( var , state )
80
+ this_value_index = ( state . last_processed_value_index += 1 )
61
81
s = String . new
62
82
case var
63
83
when Array
@@ -68,7 +88,7 @@ def PHP.do_serialize(var, state)
68
88
}
69
89
else
70
90
var . each_with_index { |v , i |
71
- s << "i: #{ i } ; #{ PHP . do_serialize ( v , state ) } "
91
+ s << PHP . do_serialize ( i , state ) << PHP . do_serialize ( v , state )
72
92
}
73
93
end
74
94
@@ -82,12 +102,15 @@ def PHP.do_serialize(var, state)
82
102
s << '}'
83
103
84
104
when Struct
85
- # encode as Object with same name
86
- s << "O:#{ var . class . to_s . bytesize } :\" #{ var . class . to_s . downcase } \" :#{ var . members . length } :{"
87
- var . members . each do |member |
88
- s << "#{ PHP . do_serialize ( member , state ) } #{ PHP . do_serialize ( var [ member ] , state ) } "
89
- end
90
- s << '}'
105
+ s =
106
+ handling_reference_for_recurring_object ( var , index : this_value_index , state : state ) {
107
+ # encode as Object with same name
108
+ s << "O:#{ var . class . to_s . bytesize } :\" #{ var . class . to_s . downcase } \" :#{ var . members . length } :{"
109
+ var . members . each do |member |
110
+ s << "#{ PHP . do_serialize ( member , state ) } #{ PHP . do_serialize ( var [ member ] , state ) } "
111
+ end
112
+ s << '}'
113
+ }
91
114
92
115
when String , Symbol
93
116
s << "s:#{ var . to_s . bytesize } :\" #{ var . to_s } \" ;"
@@ -106,13 +129,16 @@ def PHP.do_serialize(var, state)
106
129
107
130
else
108
131
if var . respond_to? ( :to_assoc )
109
- v = var . to_assoc
110
- # encode as Object with same name
132
+ s =
133
+ handling_reference_for_recurring_object ( var , index : this_value_index , state : state ) {
134
+ v = var . to_assoc
135
+ # encode as Object with same name
111
136
s << "O:#{ var . class . to_s . bytesize } :\" #{ var . class . to_s . downcase } \" :#{ v . length } :{"
112
- v . each do |k , v |
113
- s << "#{ PHP . do_serialize ( k . to_s , state ) } #{ PHP . do_serialize ( v , state ) } "
114
- end
115
- s << '}'
137
+ v . each do |k , v |
138
+ s << "#{ PHP . do_serialize ( k . to_s , state ) } #{ PHP . do_serialize ( v , state ) } "
139
+ end
140
+ s << '}'
141
+ }
116
142
else
117
143
raise TypeError , "Unable to serialize type #{ var . class } "
118
144
end
@@ -121,6 +147,27 @@ def PHP.do_serialize(var, state)
121
147
s
122
148
end # }}}
123
149
150
+ module InternalMethodsForSerialize
151
+ private
152
+ # Generate an object reference ('r') for a recurring object instead of serializing it again.
153
+ #
154
+ # @param [Object] object object to be serialized
155
+ # @param [Integer] index index of serialized value
156
+ # @param [SerializationState::ToSerialize] state
157
+ # @param [Proc] block original procedure to serialize value
158
+ # @return [String] serialized value or reference
159
+ def handling_reference_for_recurring_object ( object , index :, state :, &block )
160
+ index_of_object_serialized_before = state . serialized_object_id_to_index_map [ object . __id__ ]
161
+ if index_of_object_serialized_before
162
+ "r:#{ index_of_object_serialized_before } ;"
163
+ else
164
+ state . serialized_object_id_to_index_map [ object . __id__ ] = index
165
+ yield
166
+ end
167
+ end
168
+ end
169
+ extend InternalMethodsForSerialize
170
+
124
171
# Like PHP.serialize, but only accepts a Hash or associative Array as the root
125
172
# type. The results are returned in PHP session format.
126
173
#
@@ -213,6 +260,7 @@ def PHP.unserialize(string, classmap = nil, assoc = false) # {{{
213
260
214
261
def PHP . do_unserialize ( string , state )
215
262
val = nil
263
+ this_value_index = ( state . last_processed_value_index += 1 )
216
264
# determine a type
217
265
type = string . read ( 2 ) [ 0 , 1 ]
218
266
case type
@@ -276,7 +324,7 @@ def PHP.do_unserialize(string, state)
276
324
277
325
val = val . new
278
326
rescue NameError # Nope; make a new Struct
279
- classmap [ klass ] = val = Struct . new ( klass . to_s , *attrs . collect { |v | v [ 0 ] . to_s } )
327
+ state . classmap [ klass ] = val = Struct . new ( klass . to_s , *attrs . collect { |v | v [ 0 ] . to_s } )
280
328
val = val . new
281
329
end
282
330
end
@@ -301,10 +349,20 @@ def PHP.do_unserialize(string, state)
301
349
when 'b' # bool, b:0 or 1
302
350
val = string . read ( 2 ) [ 0 ] == '1'
303
351
352
+ when 'R' , 'r' # reference to value/object, R:123 or r:123
353
+ ref_index = string . read_until ( ';' ) . to_i
354
+
355
+ unless ( 0 ...( state . unserialized_values . size ) ) . cover? ( ref_index )
356
+ raise TypeError , "Data part of R/r(Reference) refers to invalid index: #{ ref_index . inspect } "
357
+ end
358
+
359
+ val = state . unserialized_values [ ref_index ]
304
360
else
305
361
raise TypeError , "Unable to unserialize type '#{ type } '"
306
362
end
307
363
364
+ state . unserialized_values [ this_value_index ] = val
365
+
308
366
val
309
367
end # }}}
310
368
end
0 commit comments