php_serialize 1.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. data/lib/php_serialize.rb +316 -0
  2. data/test.rb +109 -0
  3. metadata +57 -0
@@ -0,0 +1,316 @@
1
+ #!/usr/bin/env ruby
2
+ # Copyright (c) 2003-2009 Thomas Hurst <tom@hur.st>
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ # of this software and associated documentation files (the "Software"), to deal
6
+ # in the Software without restriction, including without limitation the rights
7
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ # copies of the Software, and to permit persons to whom the Software is
9
+ # furnished to do so, subject to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be included in
12
+ # all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ # SOFTWARE.
21
+
22
+ # PHP serialize() and unserialize() workalikes
23
+ #
24
+ # Release History:
25
+ # 1.0.0 - 2003-06-02 - First release.
26
+ # 1.0.1 - 2003-06-16 - Minor bugfixes.
27
+ # 1.0.2 - 2004-09-17 - Switch all {}'s to explicit Hash.new's.
28
+ # 1.1.0 - 2009-04-01 - Pass assoc to recursive calls (thanks to Edward Speyer).
29
+ # - Serialize Symbol like String.
30
+ # - Add testsuite.
31
+ # - Instantiate auto-generated Structs properly (thanks
32
+ # to Philip Hallstrom).
33
+ # - Unserialize arrays properly in assoc mode.
34
+ # - Add PHP session support (thanks to TJ Vanderpoel).
35
+ # - Release as tarball and gem.
36
+ #
37
+ # See http://www.php.net/serialize and http://www.php.net/unserialize for
38
+ # details on the PHP side of all this.
39
+
40
+ require 'ostruct'
41
+
42
+ module PHP
43
+ # string = PHP.serialize(mixed var[, bool assoc])
44
+ #
45
+ # Returns a string representing the argument in a form PHP.unserialize
46
+ # and PHP's unserialize() should both be able to load.
47
+ #
48
+ # Array, Hash, Fixnum, Float, True/FalseClass, NilClass, String and Struct
49
+ # are supported; as are objects which support the to_assoc method, which
50
+ # returns an array of the form [['attr_name', 'value']..]. Anything else
51
+ # will raise a TypeError.
52
+ #
53
+ # If 'assoc' is specified, Array's who's first element is a two value
54
+ # array will be assumed to be an associative array, and will be serialized
55
+ # as a PHP associative array rather than a multidimensional array.
56
+ def PHP.serialize(var, assoc = false) # {{{
57
+ s = ''
58
+ case var
59
+ when Array
60
+ s << "a:#{var.size}:{"
61
+ if assoc and var.first.is_a?(Array) and var.first.size == 2
62
+ var.each { |k,v|
63
+ s << PHP.serialize(k, assoc) << PHP.serialize(v, assoc)
64
+ }
65
+ else
66
+ var.each_with_index { |v,i|
67
+ s << "i:#{i};#{PHP.serialize(v, assoc)}"
68
+ }
69
+ end
70
+
71
+ s << '}'
72
+
73
+ when Hash
74
+ s << "a:#{var.size}:{"
75
+ var.each do |k,v|
76
+ s << "#{PHP.serialize(k, assoc)}#{PHP.serialize(v, assoc)}"
77
+ end
78
+ s << '}'
79
+
80
+ when Struct
81
+ # encode as Object with same name
82
+ s << "O:#{var.class.to_s.length}:\"#{var.class.to_s.downcase}\":#{var.members.length}:{"
83
+ var.members.each do |member|
84
+ s << "#{PHP.serialize(member, assoc)}#{PHP.serialize(var[member], assoc)}"
85
+ end
86
+ s << '}'
87
+
88
+ when String, Symbol
89
+ s << "s:#{var.to_s.length}:\"#{var.to_s}\";"
90
+
91
+ when Fixnum # PHP doesn't have bignums
92
+ s << "i:#{var};"
93
+
94
+ when Float
95
+ s << "d:#{var};"
96
+
97
+ when NilClass
98
+ s << 'N;'
99
+
100
+ when FalseClass, TrueClass
101
+ s << "b:#{var ? 1 :0};"
102
+
103
+ else
104
+ if var.respond_to?(:to_assoc)
105
+ v = var.to_assoc
106
+ # encode as Object with same name
107
+ s << "O:#{var.class.to_s.length}:\"#{var.class.to_s.downcase}\":#{v.length}:{"
108
+ v.each do |k,v|
109
+ s << "#{PHP.serialize(k.to_s, assoc)}#{PHP.serialize(v, assoc)}"
110
+ end
111
+ s << '}'
112
+ else
113
+ raise TypeError, "Unable to serialize type #{var.class}"
114
+ end
115
+ end
116
+
117
+ s
118
+ end # }}}
119
+
120
+ # string = PHP.serialize_session(mixed var[, bool assoc])
121
+ #
122
+ # Like PHP.serialize, but only accepts a Hash or associative Array as the root
123
+ # type. The results are returned in PHP session format.
124
+ def PHP.serialize_session(var, assoc = false) # {{{
125
+ s = ''
126
+ case var
127
+ when Hash
128
+ var.each do |key,value|
129
+ if key.to_s =~ /\|/
130
+ raise IndexError, "Top level names may not contain pipes"
131
+ end
132
+ s << "#{key}|#{PHP.serialize(value, assoc)}"
133
+ end
134
+ when Array
135
+ var.each do |x|
136
+ case x
137
+ when Array
138
+ if x.size == 2
139
+ s << "#{x[0]}|#{PHP.serialize(x[1])}"
140
+ else
141
+ raise TypeError, "Array is not associative"
142
+ end
143
+ end
144
+ end
145
+ else
146
+ raise TypeError, "Unable to serialize sessions with top level types other than Hash and associative Array"
147
+ end
148
+ s
149
+ end # }}}
150
+
151
+ # mixed = PHP.unserialize(string serialized, [hash classmap, [bool assoc]])
152
+ #
153
+ # Returns an object containing the reconstituted data from serialized.
154
+ #
155
+ # If a PHP array (associative; like an ordered hash) is encountered, it
156
+ # scans the keys; if they're all incrementing integers counting from 0,
157
+ # it's unserialized as an Array, otherwise it's unserialized as a Hash.
158
+ # Note: this will lose ordering. To avoid this, specify assoc=true,
159
+ # and it will be unserialized as an associative array: [[key,value],...]
160
+ #
161
+ # If a serialized object is encountered, the hash 'classmap' is searched for
162
+ # the class name (as a symbol). Since PHP classnames are not case-preserving,
163
+ # this *must* be a .capitalize()d representation. The value is expected
164
+ # to be the class itself; i.e. something you could call .new on.
165
+ #
166
+ # If it's not found in 'classmap', the current constant namespace is searched,
167
+ # and failing that, a new Struct(classname) is generated, with the arguments
168
+ # for .new specified in the same order PHP provided; since PHP uses hashes
169
+ # to represent attributes, this should be the same order they're specified
170
+ # in PHP, but this is untested.
171
+ #
172
+ # each serialized attribute is sent to the new object using the respective
173
+ # {attribute}=() method; you'll get a NameError if the method doesn't exist.
174
+ #
175
+ # Array, Hash, Fixnum, Float, True/FalseClass, NilClass and String should
176
+ # be returned identically (i.e. foo == PHP.unserialize(PHP.serialize(foo))
177
+ # for these types); Struct should be too, provided it's in the namespace
178
+ # Module.const_get within unserialize() can see, or you gave it the same
179
+ # name in the Struct.new(<structname>), otherwise you should provide it in
180
+ # classmap.
181
+ #
182
+ # Note: StringIO is required for unserialize(); it's loaded as needed
183
+ def PHP.unserialize(string, classmap = nil, assoc = false) # {{{
184
+ if classmap == true or classmap == false
185
+ assoc = classmap
186
+ classmap = {}
187
+ end
188
+ classmap ||= {}
189
+
190
+ require 'stringio'
191
+ string = StringIO.new(string)
192
+ def string.read_until(char)
193
+ val = ''
194
+ while (c = self.read(1)) != char
195
+ val << c
196
+ end
197
+ val
198
+ end
199
+
200
+ if string.string =~ /^(\w+)\|/ # session_name|serialized_data
201
+ ret = Hash.new
202
+ loop do
203
+ if string.string[string.pos, 32] =~ /^(\w+)\|/
204
+ string.pos += $&.size
205
+ ret[$1] = PHP.do_unserialize(string, classmap, assoc)
206
+ else
207
+ break
208
+ end
209
+ end
210
+ ret
211
+ else
212
+ PHP.do_unserialize(string, classmap, assoc)
213
+ end
214
+ end
215
+
216
+ private
217
+ def PHP.do_unserialize(string, classmap, assoc)
218
+ val = nil
219
+ # determine a type
220
+ type = string.read(2)[0,1]
221
+ case type
222
+ when 'a' # associative array, a:length:{[index][value]...}
223
+ count = string.read_until('{').to_i
224
+ val = vals = Array.new
225
+ count.times do |i|
226
+ vals << [do_unserialize(string, classmap, assoc), do_unserialize(string, classmap, assoc)]
227
+ end
228
+ string.read(1) # skip the ending }
229
+
230
+ # now, we have an associative array, let's clean it up a bit...
231
+ # arrays have all numeric indexes, in order; otherwise we assume a hash
232
+ array = true
233
+ i = 0
234
+ vals.each do |key,value|
235
+ if key != i # wrong index -> assume hash
236
+ array = false
237
+ break
238
+ end
239
+ i += 1
240
+ end
241
+
242
+ if array
243
+ vals.collect! do |key,value|
244
+ value
245
+ end
246
+ else
247
+ if assoc
248
+ val = vals.map {|v| v }
249
+ else
250
+ val = Hash.new
251
+ vals.each do |key,value|
252
+ val[key] = value
253
+ end
254
+ end
255
+ end
256
+
257
+ when 'O' # object, O:length:"class":length:{[attribute][value]...}
258
+ # class name (lowercase in PHP, grr)
259
+ len = string.read_until(':').to_i + 3 # quotes, seperator
260
+ klass = string.read(len)[1...-2].capitalize.intern # read it, kill useless quotes
261
+
262
+ # read the attributes
263
+ attrs = []
264
+ len = string.read_until('{').to_i
265
+
266
+ len.times do
267
+ attr = (do_unserialize(string, classmap, assoc))
268
+ attrs << [attr.intern, (attr << '=').intern, do_unserialize(string, classmap, assoc)]
269
+ end
270
+ string.read(1)
271
+
272
+ val = nil
273
+ # See if we need to map to a particular object
274
+ if classmap.has_key?(klass)
275
+ val = classmap[klass].new
276
+ elsif Struct.const_defined?(klass) # Nope; see if there's a Struct
277
+ classmap[klass] = val = Struct.const_get(klass)
278
+ val = val.new
279
+ else # Nope; see if there's a Constant
280
+ begin
281
+ classmap[klass] = val = Module.const_get(klass)
282
+ val = val.new
283
+ rescue NameError # Last resort: make a new OpenStruct
284
+ classmap[klass] = val = OpenStruct
285
+ val = val.new
286
+ end
287
+ end
288
+
289
+ attrs.each do |attr,attrassign,v|
290
+ val.__send__(attrassign, v)
291
+ end
292
+
293
+ when 's' # string, s:length:"data";
294
+ len = string.read_until(':').to_i + 3 # quotes, separator
295
+ val = string.read(len)[1...-2] # read it, kill useless quotes
296
+
297
+ when 'i' # integer, i:123
298
+ val = string.read_until(';').to_i
299
+
300
+ when 'd' # double (float), d:1.23
301
+ val = string.read_until(';').to_f
302
+
303
+ when 'N' # NULL, N;
304
+ val = nil
305
+
306
+ when 'b' # bool, b:0 or 1
307
+ val = (string.read(2)[0] == ?1 ? true : false)
308
+
309
+ else
310
+ raise TypeError, "Unable to unserialize type '#{type}'"
311
+ end
312
+
313
+ val
314
+ end # }}}
315
+ end
316
+
data/test.rb ADDED
@@ -0,0 +1,109 @@
1
+ #!/usr/local/bin/ruby
2
+
3
+ require 'test/unit'
4
+
5
+ $:.unshift "lib"
6
+ require 'php_serialize'
7
+
8
+ TestStruct = Struct.new(:name, :value)
9
+ class TestClass
10
+ attr_accessor :name
11
+ attr_accessor :value
12
+
13
+ def initialize(name = nil, value = nil)
14
+ @name = name
15
+ @value = value
16
+ end
17
+
18
+ def to_assoc
19
+ [['name', @name], ['value', @value]]
20
+ end
21
+
22
+ def ==(other)
23
+ other.class == self.class and other.name == @name and other.value == @value
24
+ end
25
+ end
26
+
27
+ ClassMap = {
28
+ TestStruct.name.capitalize.intern => TestStruct,
29
+ TestClass.name.capitalize.intern => TestClass
30
+ }
31
+
32
+ class TestPhpSerialize < Test::Unit::TestCase
33
+ def self.test(ruby, php, opts = {})
34
+ if opts[:name]
35
+ name = opts[:name]
36
+ else
37
+ name = ruby.to_s
38
+ end
39
+
40
+ define_method("test_#{name}".intern) do
41
+ assert_nothing_thrown do
42
+ serialized = PHP.serialize(ruby)
43
+ assert_equal php, serialized
44
+
45
+ unserialized = PHP.unserialize(serialized, ClassMap)
46
+ case ruby
47
+ when Symbol
48
+ assert_equal ruby.to_s, unserialized
49
+ else
50
+ assert_equal ruby, unserialized
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ test nil, 'N;'
57
+ test false, 'b:0;'
58
+ test true, 'b:1;'
59
+ test 42, 'i:42;'
60
+ test -42, 'i:-42;'
61
+ test 2147483647, "i:2147483647;", :name => 'Max Fixnum'
62
+ test -2147483648, "i:-2147483648;", :name => 'Min Fixnum'
63
+ test 4.2, 'd:4.2;'
64
+ test 'test', 's:4:"test";'
65
+ test :test, 's:4:"test";', :name => 'Symbol'
66
+ test "\"\n\t\"", "s:4:\"\"\n\t\"\";", :name => 'Complex string'
67
+ test [nil, true, false, 42, 4.2, 'test'], 'a:6:{i:0;N;i:1;b:1;i:2;b:0;i:3;i:42;i:4;d:4.2;i:5;s:4:"test";}',
68
+ :name => 'Array'
69
+ test({'foo' => 'bar', 4 => [5,4,3,2]}, 'a:2:{s:3:"foo";s:3:"bar";i:4;a:4:{i:0;i:5;i:1;i:4;i:2;i:3;i:3;i:2;}}', :name => 'Hash')
70
+ test TestStruct.new("Foo", 65), 'O:10:"teststruct":2:{s:4:"name";s:3:"Foo";s:5:"value";i:65;}',
71
+ :name => 'Struct'
72
+ test TestClass.new("Foo", 65), 'O:9:"testclass":2:{s:4:"name";s:3:"Foo";s:5:"value";i:65;}',
73
+ :name => 'Class'
74
+
75
+ # Verify assoc is passed down calls.
76
+ # Slightly awkward because hashes don't guarantee order.
77
+ def test_assoc
78
+ assert_nothing_raised do
79
+ ruby = {'foo' => ['bar','baz'], 'hash' => {'hash' => 'smoke'}}
80
+ ruby_assoc = [['foo', ['bar','baz']], ['hash', [['hash','smoke']]]]
81
+ phps = [
82
+ 'a:2:{s:4:"hash";a:1:{s:4:"hash";s:5:"smoke";}s:3:"foo";a:2:{i:0;s:3:"bar";i:1;s:3:"baz";}}',
83
+ 'a:2:{s:3:"foo";a:2:{i:0;s:3:"bar";i:1;s:3:"baz";}s:4:"hash";a:1:{s:4:"hash";s:5:"smoke";}}'
84
+ ]
85
+ serialized = PHP.serialize(ruby, true)
86
+ assert phps.include?(serialized)
87
+ unserialized = PHP.unserialize(serialized, true)
88
+ assert_equal ruby_assoc.sort, unserialized.sort
89
+ end
90
+ end
91
+
92
+ def test_sessions
93
+ assert_nothing_raised do
94
+ ruby = {'session_id' => 42, 'user_data' => {'uid' => 666}}
95
+ phps = [
96
+ 'session_id|i:42;user_data|a:1:{s:3:"uid";i:666;}',
97
+ 'user_data|a:1:{s:3:"uid";i:666;}session_id|i:42;'
98
+ ]
99
+ unserialized = PHP.unserialize(phps.first)
100
+ assert_equal ruby, unserialized
101
+ serialized = PHP.serialize_session(ruby)
102
+ assert phps.include?(serialized)
103
+ end
104
+ end
105
+ end
106
+
107
+ require 'test/unit/ui/console/testrunner'
108
+ Test::Unit::UI::Console::TestRunner.run(TestPhpSerialize)
109
+
metadata ADDED
@@ -0,0 +1,57 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: php_serialize
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.1.3
5
+ platform: ruby
6
+ authors:
7
+ - Thomas Hurst
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-11-29 00:00:00 +01:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: "\tThis module provides two methods: PHP.serialize() and PHP.unserialize(), both\n\
17
+ \tof which should be compatible with the similarly named functions in PHP.\n\n\
18
+ \tIt can also serialize and unserialize PHP sessions.\n"
19
+ email: tom@hur.st
20
+ executables: []
21
+
22
+ extensions: []
23
+
24
+ extra_rdoc_files: []
25
+
26
+ files:
27
+ - lib/php_serialize.rb
28
+ has_rdoc: true
29
+ homepage: http://www.aagh.net/projects/ruby-php-serialize
30
+ licenses: []
31
+
32
+ post_install_message:
33
+ rdoc_options: []
34
+
35
+ require_paths:
36
+ - lib/
37
+ required_ruby_version: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: "0"
42
+ version:
43
+ required_rubygems_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: "0"
48
+ version:
49
+ requirements: []
50
+
51
+ rubyforge_project:
52
+ rubygems_version: 1.3.5
53
+ signing_key:
54
+ specification_version: 3
55
+ summary: Ruby analogs to PHP's serialize() and unserialize() functions
56
+ test_files:
57
+ - test.rb