@@ -17,23 +17,42 @@ def read_until(char)
1717
1818 module SerializationState
1919 class Base
20+ def initialize
21+ @last_processed_value_index = -1
22+ end
23+
2024 # @return [Boolean]
2125 attr_accessor :assoc
26+
27+ # @return [Integer]
28+ attr_accessor :last_processed_value_index
2229 end
2330
2431 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
2539 end
2640
2741 class ToUnserialize < Base
2842 def initialize
43+ super
2944 @classmap = { }
45+ @unserialized_values = [ ]
3046 end
3147
3248 # @return [Hash{String => Class}]
3349 attr_accessor :classmap
3450
3551 # @return [String]
3652 attr_accessor :original_encoding
53+
54+ # @return [Array<Object>]
55+ attr_accessor :unserialized_values
3756 end
3857 end
3958
@@ -58,6 +77,7 @@ def PHP.serialize(var, assoc = false) # {{{
5877 end
5978
6079 def PHP . do_serialize ( var , state )
80+ this_value_index = ( state . last_processed_value_index += 1 )
6181 s = String . new
6282 case var
6383 when Array
@@ -68,7 +88,7 @@ def PHP.do_serialize(var, state)
6888 }
6989 else
7090 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 )
7292 }
7393 end
7494
@@ -82,12 +102,15 @@ def PHP.do_serialize(var, state)
82102 s << '}'
83103
84104 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+ }
91114
92115 when String , Symbol
93116 s << "s:#{ var . to_s . bytesize } :\" #{ var . to_s } \" ;"
@@ -106,13 +129,16 @@ def PHP.do_serialize(var, state)
106129
107130 else
108131 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
111136 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+ }
116142 else
117143 raise TypeError , "Unable to serialize type #{ var . class } "
118144 end
@@ -121,6 +147,27 @@ def PHP.do_serialize(var, state)
121147 s
122148 end # }}}
123149
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+
124171 # Like PHP.serialize, but only accepts a Hash or associative Array as the root
125172 # type. The results are returned in PHP session format.
126173 #
@@ -213,6 +260,7 @@ def PHP.unserialize(string, classmap = nil, assoc = false) # {{{
213260
214261 def PHP . do_unserialize ( string , state )
215262 val = nil
263+ this_value_index = ( state . last_processed_value_index += 1 )
216264 # determine a type
217265 type = string . read ( 2 ) [ 0 , 1 ]
218266 case type
@@ -276,7 +324,7 @@ def PHP.do_unserialize(string, state)
276324
277325 val = val . new
278326 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 } )
280328 val = val . new
281329 end
282330 end
@@ -301,10 +349,20 @@ def PHP.do_unserialize(string, state)
301349 when 'b' # bool, b:0 or 1
302350 val = string . read ( 2 ) [ 0 ] == '1'
303351
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 ]
304360 else
305361 raise TypeError , "Unable to unserialize type '#{ type } '"
306362 end
307363
364+ state . unserialized_values [ this_value_index ] = val
365+
308366 val
309367 end # }}}
310368end
0 commit comments