php-serialize_ryan 1.1.1

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 +348 -0
  2. data/test/test.rb +155 -0
  3. metadata +90 -0
@@ -0,0 +1,348 @@
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
+ module PHP
40
+ # string = PHP.serialize(mixed var[, bool assoc])
41
+ #
42
+ # Returns a string representing the argument in a form PHP.unserialize
43
+ # and PHP's unserialize() should both be able to load.
44
+ #
45
+ # Array, Hash, Fixnum, Float, True/FalseClass, NilClass, String and Struct
46
+ # are supported; as are objects which support the to_assoc method, which
47
+ # returns an array of the form [['attr_name', 'value']..]. Anything else
48
+ # will raise a TypeError.
49
+ #
50
+ # If 'assoc' is specified, Array's who's first element is a two value
51
+ # array will be assumed to be an associative array, and will be serialized
52
+ # as a PHP associative array rather than a multidimensional array.
53
+ def PHP.serialize(var, assoc = false) # {{{
54
+ s = ''
55
+ case var
56
+ when Array
57
+ s << "a:#{var.size}:{"
58
+ if assoc and var.first.is_a?(Array) and var.first.size == 2
59
+ var.each { |k,v|
60
+ s << PHP.serialize(k, assoc) << PHP.serialize(v, assoc)
61
+ }
62
+ else
63
+ var.each_with_index { |v,i|
64
+ s << "i:#{i};#{PHP.serialize(v, assoc)}"
65
+ }
66
+ end
67
+
68
+ s << '}'
69
+
70
+ when Hash
71
+ s << "a:#{var.size}:{"
72
+ var.each do |k,v|
73
+ s << "#{PHP.serialize(k, assoc)}#{PHP.serialize(v, assoc)}"
74
+ end
75
+ s << '}'
76
+
77
+ when Struct
78
+ # encode as Object with same name
79
+ s << "O:#{var.class.to_s.length}:\"#{var.class.to_s.downcase}\":#{var.members.length}:{"
80
+ var.members.each do |member|
81
+ s << "#{PHP.serialize(member, assoc)}#{PHP.serialize(var[member], assoc)}"
82
+ end
83
+ s << '}'
84
+
85
+ when String, Symbol
86
+ s << "s:#{var.to_s.bytesize}:\"#{var.to_s}\";"
87
+
88
+ when Fixnum # PHP doesn't have bignums
89
+ s << "i:#{var};"
90
+
91
+ when Float
92
+ s << "d:#{var};"
93
+
94
+ when NilClass
95
+ s << 'N;'
96
+
97
+ when FalseClass, TrueClass
98
+ s << "b:#{var ? 1 :0};"
99
+
100
+ else
101
+ if var.respond_to?(:to_assoc)
102
+ v = var.to_assoc
103
+ # encode as Object with same name
104
+ s << "O:#{var.class.to_s.length}:\"#{var.class.to_s.downcase}\":#{v.length}:{"
105
+ v.each do |k,v|
106
+ s << "#{PHP.serialize(k.to_s, assoc)}#{PHP.serialize(v, assoc)}"
107
+ end
108
+ s << '}'
109
+ else
110
+ raise TypeError, "Unable to serialize type #{var.class}"
111
+ end
112
+ end
113
+
114
+ s
115
+ end # }}}
116
+
117
+ # string = PHP.serialize_session(mixed var[, bool assoc])
118
+ #
119
+ # Like PHP.serialize, but only accepts a Hash or associative Array as the root
120
+ # type. The results are returned in PHP session format.
121
+ def PHP.serialize_session(var, assoc = false) # {{{
122
+ s = ''
123
+ case var
124
+ when Hash
125
+ var.each do |key,value|
126
+ if key.to_s =~ /\|/
127
+ raise IndexError, "Top level names may not contain pipes"
128
+ end
129
+ s << "#{key}|#{PHP.serialize(value, assoc)}"
130
+ end
131
+ when Array
132
+ var.each do |x|
133
+ case x
134
+ when Array
135
+ if x.size == 2
136
+ s << "#{x[0]}|#{PHP.serialize(x[1])}"
137
+ else
138
+ raise TypeError, "Array is not associative"
139
+ end
140
+ end
141
+ end
142
+ else
143
+ raise TypeError, "Unable to serialize sessions with top level types other than Hash and associative Array"
144
+ end
145
+ s
146
+ end # }}}
147
+
148
+ # mixed = PHP.unserialize(string serialized, [hash classmap, [bool assoc]])
149
+ #
150
+ # Returns an object containing the reconstituted data from serialized.
151
+ #
152
+ # If a PHP array (associative; like an ordered hash) is encountered, it
153
+ # scans the keys; if they're all incrementing integers counting from 0,
154
+ # it's unserialized as an Array, otherwise it's unserialized as a Hash.
155
+ # Note: this will lose ordering. To avoid this, specify assoc=true,
156
+ # and it will be unserialized as an associative array: [[key,value],...]
157
+ #
158
+ # If a serialized object is encountered, the hash 'classmap' is searched for
159
+ # the class name (as a symbol). Since PHP classnames are not case-preserving,
160
+ # this *must* be a .capitalize()d representation. The value is expected
161
+ # to be the class itself; i.e. something you could call .new on.
162
+ #
163
+ # If it's not found in 'classmap', the current constant namespace is searched,
164
+ # and failing that, a new Struct(classname) is generated, with the arguments
165
+ # for .new specified in the same order PHP provided; since PHP uses hashes
166
+ # to represent attributes, this should be the same order they're specified
167
+ # in PHP, but this is untested.
168
+ #
169
+ # each serialized attribute is sent to the new object using the respective
170
+ # {attribute}=() method; you'll get a NameError if the method doesn't exist.
171
+ #
172
+ # Array, Hash, Fixnum, Float, True/FalseClass, NilClass and String should
173
+ # be returned identically (i.e. foo == PHP.unserialize(PHP.serialize(foo))
174
+ # for these types); Struct should be too, provided it's in the namespace
175
+ # Module.const_get within unserialize() can see, or you gave it the same
176
+ # name in the Struct.new(<structname>), otherwise you should provide it in
177
+ # classmap.
178
+ #
179
+ # Note: StringIO is required for unserialize(); it's loaded as needed
180
+ def PHP.unserialize(string, classmap = nil, assoc = false) # {{{
181
+ if classmap == true or classmap == false
182
+ assoc = classmap
183
+ classmap = {}
184
+ end
185
+ classmap ||= {}
186
+
187
+ require 'stringio'
188
+ string = StringIO.new(string)
189
+ def string.read_until(char)
190
+ val = ''
191
+ while (c = self.read(1)) != char
192
+ val << c
193
+ end
194
+ val
195
+ end
196
+
197
+ if string.string =~ /^(\w+)\|/ # session_name|serialized_data
198
+ ret = Hash.new
199
+ loop do
200
+ if string.string[string.pos, 32] =~ /^(\w+)\|/
201
+ string.pos += $&.size
202
+ ret[$1] = PHP.do_unserialize(string, classmap, assoc)
203
+ else
204
+ break
205
+ end
206
+ end
207
+ ret
208
+ else
209
+ PHP.do_unserialize(string, classmap, assoc)
210
+ end
211
+ end
212
+
213
+ ##
214
+ # Tests if an input is valid PHP serialized string.
215
+ #
216
+ # Checks if a string is serialized using quick string manipulation
217
+ # to throw out obviously incorrect strings.
218
+ #
219
+ # http://stackoverflow.com/questions/1369936/check-to-see-if-a-string-is-serialized
220
+ def PHP.serialized?(string)
221
+ # If it isn't a string, it isn't serialized
222
+ return false unless string.instance_of? String
223
+
224
+ # Remove any trailing whitespace
225
+ string.chomp!
226
+
227
+ # Serialized FALSE, return TRUE. unserialize() returns FALSE on an
228
+ # invalid string or it could return FALSE if the string is serialized
229
+ # FALSE, eliminate that possibility.
230
+ return true if 'b:0;' === string
231
+
232
+ return true if 'N;' === string
233
+
234
+ badions = /^([adObis]):/.match(string)
235
+ return false if badions.nil?
236
+
237
+ case badions[1]
238
+ when 'a', 'O', 's'
239
+ return true unless (/^#{badions[1]}:[0-9]+:.*[;}]$/m =~ string).nil?
240
+ when 'b', 'i', 'd'
241
+ return true unless (/^#{badions[1]}:[0-9.E-]+;$/ =~ string).nil?
242
+ end
243
+
244
+ false
245
+ end
246
+
247
+ private
248
+ def PHP.do_unserialize(string, classmap, assoc)
249
+ val = nil
250
+ # determine a type
251
+ type = string.read(2)[0,1]
252
+ case type
253
+ when 'a' # associative array, a:length:{[index][value]...}
254
+ count = string.read_until('{').to_i
255
+ val = vals = Array.new
256
+ count.times do |i|
257
+ vals << [do_unserialize(string, classmap, assoc), do_unserialize(string, classmap, assoc)]
258
+ end
259
+ string.read(1) # skip the ending }
260
+
261
+ # now, we have an associative array, let's clean it up a bit...
262
+ # arrays have all numeric indexes, in order; otherwise we assume a hash
263
+ array = true
264
+ i = 0
265
+ vals.each do |key,value|
266
+ if key != i # wrong index -> assume hash
267
+ array = false
268
+ break
269
+ end
270
+ i += 1
271
+ end
272
+
273
+ if array
274
+ vals.collect! do |key,value|
275
+ value
276
+ end
277
+ else
278
+ if assoc
279
+ val = vals.map {|v| v }
280
+ else
281
+ val = Hash.new
282
+ vals.each do |key,value|
283
+ val[key] = value
284
+ end
285
+ end
286
+ end
287
+
288
+ when 'O' # object, O:length:"class":length:{[attribute][value]...}
289
+ # class name (lowercase in PHP, grr)
290
+ len = string.read_until(':').to_i + 3 # quotes, seperator
291
+ klass = string.read(len)[1...-2].capitalize.intern # read it, kill useless quotes
292
+
293
+ # read the attributes
294
+ attrs = []
295
+ len = string.read_until('{').to_i
296
+
297
+ len.times do
298
+ attr = (do_unserialize(string, classmap, assoc))
299
+ attrs << [attr.intern, (attr << '=').intern, do_unserialize(string, classmap, assoc)]
300
+ end
301
+ string.read(1)
302
+
303
+ val = nil
304
+ # See if we need to map to a particular object
305
+ if classmap.has_key?(klass)
306
+ val = classmap[klass].new
307
+ elsif Struct.const_defined?(klass) # Nope; see if there's a Struct
308
+ classmap[klass] = val = Struct.const_get(klass)
309
+ val = val.new
310
+ else # Nope; see if there's a Constant
311
+ begin
312
+ classmap[klass] = val = Module.const_get(klass)
313
+
314
+ val = val.new
315
+ rescue NameError # Nope; make a new Struct
316
+ classmap[klass] = Struct.new(klass.to_s, *attrs.collect { |v| v[0].to_s })
317
+ val = val.new
318
+ end
319
+ end
320
+
321
+ attrs.each do |attr,attrassign,v|
322
+ val.__send__(attrassign, v)
323
+ end
324
+
325
+ when 's' # string, s:length:"data";
326
+ len = string.read_until(':').to_i + 3 # quotes, separator
327
+ val = string.read(len)[1...-2] # read it, kill useless quotes
328
+
329
+ when 'i' # integer, i:123
330
+ val = string.read_until(';').to_i
331
+
332
+ when 'd' # double (float), d:1.23
333
+ val = string.read_until(';').to_f
334
+
335
+ when 'N' # NULL, N;
336
+ val = nil
337
+
338
+ when 'b' # bool, b:0 or 1
339
+ val = (string.read(2)[0] == ?1 ? true : false)
340
+
341
+ else
342
+ raise TypeError, "Unable to unserialize type '#{type}'"
343
+ end
344
+
345
+ val
346
+ end # }}}
347
+ end
348
+
@@ -0,0 +1,155 @@
1
+ #!/usr/local/bin/ruby
2
+ # encoding: utf-8
3
+
4
+ if RUBY_VERSION == "1.9.2"
5
+ # needed, when running in Ruby 1.9.2 -> no stdlib test/unit
6
+ gem 'test-unit'
7
+ end
8
+ require 'test/unit'
9
+ require 'test/unit/ui/console/testrunner'
10
+
11
+ $:.unshift "lib"
12
+ require 'php_serialize'
13
+
14
+ TestStruct = Struct.new(:name, :value)
15
+ class TestClass
16
+ attr_accessor :name
17
+ attr_accessor :value
18
+
19
+ def initialize(name = nil, value = nil)
20
+ @name = name
21
+ @value = value
22
+ end
23
+
24
+ def to_assoc
25
+ [['name', @name], ['value', @value]]
26
+ end
27
+
28
+ def ==(other)
29
+ other.class == self.class and other.name == @name and other.value == @value
30
+ end
31
+ end
32
+
33
+ ClassMap = {
34
+ TestStruct.name.capitalize.intern => TestStruct,
35
+ TestClass.name.capitalize.intern => TestClass
36
+ }
37
+
38
+ class TestPhpSerialize < Test::Unit::TestCase
39
+ def self.test(ruby, php, opts = {})
40
+ if opts[:name]
41
+ name = opts[:name]
42
+ else
43
+ name = ruby.to_s
44
+ end
45
+
46
+ define_method("test_#{name}".intern) do
47
+ assert_nothing_thrown do
48
+ serialized = PHP.serialize(ruby)
49
+ assert_equal php, serialized
50
+
51
+ unserialized = PHP.unserialize(serialized, ClassMap)
52
+ case ruby
53
+ when Symbol
54
+ assert_equal ruby.to_s, unserialized
55
+ else
56
+ assert_equal ruby, unserialized
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ test nil, 'N;'
63
+ test false, 'b:0;'
64
+ test true, 'b:1;'
65
+ test 42, 'i:42;'
66
+ test -42, 'i:-42;'
67
+ test 2147483647, "i:2147483647;", :name => 'Max Fixnum'
68
+ test -2147483648, "i:-2147483648;", :name => 'Min Fixnum'
69
+ test 4.2, 'd:4.2;'
70
+ test 'test', 's:4:"test";'
71
+ test :test, 's:4:"test";', :name => 'Symbol'
72
+ test "\"\n\t\"", "s:4:\"\"\n\t\"\";", :name => 'Complex string'
73
+ 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";}',
74
+ :name => 'Array'
75
+ 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')
76
+ test TestStruct.new("Foo", 65), 'O:10:"teststruct":2:{s:4:"name";s:3:"Foo";s:5:"value";i:65;}',
77
+ :name => 'Struct'
78
+ test TestClass.new("Foo", 65), 'O:9:"testclass":2:{s:4:"name";s:3:"Foo";s:5:"value";i:65;}',
79
+ :name => 'Class'
80
+
81
+ # PHP counts multibyte string, not string length
82
+ def test_multibyte_string
83
+ assert_equal "s:6:\"öäü\";", PHP.serialize("öäü")
84
+ end
85
+ # Verify assoc is passed down calls.
86
+ # Slightly awkward because hashes don't guarantee order.
87
+ def test_assoc
88
+ assert_nothing_raised do
89
+ ruby = {'foo' => ['bar','baz'], 'hash' => {'hash' => 'smoke'}}
90
+ ruby_assoc = [['foo', ['bar','baz']], ['hash', [['hash','smoke']]]]
91
+ phps = [
92
+ '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";}}',
93
+ '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";}}'
94
+ ]
95
+ serialized = PHP.serialize(ruby, true)
96
+ assert phps.include?(serialized)
97
+ unserialized = PHP.unserialize(serialized, true)
98
+ assert_equal ruby_assoc.sort, unserialized.sort
99
+ end
100
+ end
101
+
102
+ def test_sessions
103
+ assert_nothing_raised do
104
+ ruby = {'session_id' => 42, 'user_data' => {'uid' => 666}}
105
+ phps = [
106
+ 'session_id|i:42;user_data|a:1:{s:3:"uid";i:666;}',
107
+ 'user_data|a:1:{s:3:"uid";i:666;}session_id|i:42;'
108
+ ]
109
+ unserialized = PHP.unserialize(phps.first)
110
+ assert_equal ruby, unserialized
111
+ serialized = PHP.serialize_session(ruby)
112
+ assert phps.include?(serialized)
113
+ end
114
+ end
115
+
116
+ def test_is_serialized
117
+ serial_strings = [
118
+ 'a:0:{}',
119
+ 'a:1:{s:12:"_multiwidget";i:1;}',
120
+ '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";}}',
121
+ '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";}}',
122
+ # Boolean
123
+ 'b:1;',
124
+ # Integer
125
+ 'i:1;',
126
+ # Double
127
+ 'd:0.2;',
128
+ # String
129
+ 's:4:"test";',
130
+ # Array
131
+ 'a:3:{i:0;i:1;i:1;i:2;i:2;i:3;}',
132
+ # Object
133
+ 'O:8:"stdClass":0:{}',
134
+ # Null
135
+ 'N;'
136
+ ]
137
+ not_serial_strings = [
138
+ "a:River runs through it.",
139
+ "",
140
+ nil,
141
+ 1,
142
+ {},
143
+ []
144
+ ]
145
+ serial_strings.each do |str|
146
+ assert PHP.serialized?(str), "\"#{str}\" is supposed to be a serialized string."
147
+ end
148
+
149
+ not_serial_strings.each do |str|
150
+ assert !PHP.serialized?(str), "\"#{str}\" is NOT supposed to be a serialized string."
151
+ end
152
+ end
153
+ end
154
+
155
+
metadata ADDED
@@ -0,0 +1,90 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: php-serialize_ryan
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.1.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Thomas Hurst
9
+ - Ryan Lovelett
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2012-08-14 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rake
17
+ requirement: !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ~>
21
+ - !ruby/object:Gem::Version
22
+ version: 0.9.2.2
23
+ type: :development
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ~>
29
+ - !ruby/object:Gem::Version
30
+ version: 0.9.2.2
31
+ - !ruby/object:Gem::Dependency
32
+ name: test-unit
33
+ requirement: !ruby/object:Gem::Requirement
34
+ none: false
35
+ requirements:
36
+ - - ~>
37
+ - !ruby/object:Gem::Version
38
+ version: 2.5.1
39
+ type: :development
40
+ prerelease: false
41
+ version_requirements: !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ~>
45
+ - !ruby/object:Gem::Version
46
+ version: 2.5.1
47
+ description: ! "\tThis module provides two methods: PHP.serialize() and PHP.unserialize(),
48
+ both\n\tof which should be compatible with the similarly named functions in PHP.\n\n\tIt
49
+ can also serialize and unserialize PHP sessions.\n"
50
+ email:
51
+ - tom@hur.st
52
+ - ryan@lovelett.me
53
+ executables: []
54
+ extensions: []
55
+ extra_rdoc_files: []
56
+ files:
57
+ - lib/php_serialize.rb
58
+ - test/test.rb
59
+ homepage: http://www.aagh.net/projects/ruby-php-serialize
60
+ licenses: []
61
+ post_install_message:
62
+ rdoc_options: []
63
+ require_paths:
64
+ - lib/
65
+ required_ruby_version: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ! '>='
69
+ - !ruby/object:Gem::Version
70
+ version: '0'
71
+ segments:
72
+ - 0
73
+ hash: -4401523021577071741
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - ! '>='
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ segments:
81
+ - 0
82
+ hash: -4401523021577071741
83
+ requirements: []
84
+ rubyforge_project:
85
+ rubygems_version: 1.8.24
86
+ signing_key:
87
+ specification_version: 3
88
+ summary: Ruby analogs to PHP's serialize() and unserialize() functions
89
+ test_files:
90
+ - test/test.rb