k-php-serialize 1.2.0

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 (4) hide show
  1. checksums.yaml +7 -0
  2. data/lib/php_serialize.rb +316 -0
  3. data/test.rb +137 -0
  4. metadata +47 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 0ca2fe3387bf53ef734923627612458e40617b53
4
+ data.tar.gz: 641a388382641c7ecafa503366f8df96632ef99a
5
+ SHA512:
6
+ metadata.gz: b1d1089a746f1b44b17d9ad1610efdb9baa629c93832fbf591354c3b9d0da2d185c24fd0e2463acf2721545eefcb9b6dbe1e15b43f00d1c993848af1e0fc9d44
7
+ data.tar.gz: b9a5a79f1e2cf93e9eb36977971acebad374e51903a026efdb3b26f4d8322f003bf1add2f59105e87478c274f43b04966cd1dc6350d03b5f074679184cc2e8fc
@@ -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
+ require 'stringio'
23
+
24
+ # PHP serialize() and unserialize() workalikes
25
+ #
26
+ # Release History:
27
+ # 1.0.0 - 2003-06-02 - First release.
28
+ # 1.0.1 - 2003-06-16 - Minor bugfixes.
29
+ # 1.0.2 - 2004-09-17 - Switch all {}'s to explicit Hash.new's.
30
+ # 1.1.0 - 2009-04-01 - Pass assoc to recursive calls (thanks to Edward Speyer).
31
+ # - Serialize Symbol like String.
32
+ # - Add testsuite.
33
+ # - Instantiate auto-generated Structs properly (thanks
34
+ # to Philip Hallstrom).
35
+ # - Unserialize arrays properly in assoc mode.
36
+ # - Add PHP session support (thanks to TJ Vanderpoel).
37
+ # - Release as tarball and gem.
38
+ #
39
+ # See http://www.php.net/serialize and http://www.php.net/unserialize for
40
+ # details on the PHP side of all this.
41
+ module PHP
42
+ class StringIOReader < StringIO
43
+ # Reads data from the buffer until +char+ is found. The
44
+ # returned string will include +char+.
45
+ def read_until(char)
46
+ val, cpos = '', pos
47
+ if idx = string.index(char, cpos)
48
+ val = read(idx - cpos + 1)
49
+ end
50
+ val
51
+ end
52
+ end
53
+
54
+ # string = PHP.serialize(mixed var[, bool assoc])
55
+ #
56
+ # Returns a string representing the argument in a form PHP.unserialize
57
+ # and PHP's unserialize() should both be able to load.
58
+ #
59
+ # Array, Hash, Fixnum, Float, True/FalseClass, NilClass, String and Struct
60
+ # are supported; as are objects which support the to_assoc method, which
61
+ # returns an array of the form [['attr_name', 'value']..]. Anything else
62
+ # will raise a TypeError.
63
+ #
64
+ # If 'assoc' is specified, Array's who's first element is a two value
65
+ # array will be assumed to be an associative array, and will be serialized
66
+ # as a PHP associative array rather than a multidimensional array.
67
+ def PHP.serialize(var, assoc = false) # {{{
68
+ s = ''
69
+ case var
70
+ when Array
71
+ s << "a:#{var.size}:{"
72
+ if assoc and var.first.is_a?(Array) and var.first.size == 2
73
+ var.each { |k,v|
74
+ s << PHP.serialize(k, assoc) << PHP.serialize(v, assoc)
75
+ }
76
+ else
77
+ var.each_with_index { |v,i|
78
+ s << "i:#{i};#{PHP.serialize(v, assoc)}"
79
+ }
80
+ end
81
+
82
+ s << '}'
83
+
84
+ when Hash
85
+ s << "a:#{var.size}:{"
86
+ var.each do |k,v|
87
+ s << "#{PHP.serialize(k, assoc)}#{PHP.serialize(v, assoc)}"
88
+ end
89
+ s << '}'
90
+
91
+ when Struct
92
+ # encode as Object with same name
93
+ s << "O:#{var.class.to_s.length}:\"#{var.class.to_s.downcase}\":#{var.members.length}:{"
94
+ var.members.each do |member|
95
+ s << "#{PHP.serialize(member, assoc)}#{PHP.serialize(var[member], assoc)}"
96
+ end
97
+ s << '}'
98
+
99
+ when String, Symbol
100
+ s << "s:#{var.to_s.bytesize}:\"#{var.to_s}\";"
101
+
102
+ when Fixnum # PHP doesn't have bignums
103
+ s << "i:#{var};"
104
+
105
+ when Float
106
+ s << "d:#{var};"
107
+
108
+ when NilClass
109
+ s << 'N;'
110
+
111
+ when FalseClass, TrueClass
112
+ s << "b:#{var ? 1 :0};"
113
+
114
+ else
115
+ if var.respond_to?(:to_assoc)
116
+ v = var.to_assoc
117
+ # encode as Object with same name
118
+ s << "O:#{var.class.to_s.length}:\"#{var.class.to_s.downcase}\":#{v.length}:{"
119
+ v.each do |k,v|
120
+ s << "#{PHP.serialize(k.to_s, assoc)}#{PHP.serialize(v, assoc)}"
121
+ end
122
+ s << '}'
123
+ else
124
+ raise TypeError, "Unable to serialize type #{var.class}"
125
+ end
126
+ end
127
+
128
+ s
129
+ end # }}}
130
+
131
+ # string = PHP.serialize_session(mixed var[, bool assoc])
132
+ #
133
+ # Like PHP.serialize, but only accepts a Hash or associative Array as the root
134
+ # type. The results are returned in PHP session format.
135
+ def PHP.serialize_session(var, assoc = false) # {{{
136
+ s = ''
137
+ case var
138
+ when Hash
139
+ var.each do |key,value|
140
+ if key.to_s =~ /\|/
141
+ raise IndexError, "Top level names may not contain pipes"
142
+ end
143
+ s << "#{key}|#{PHP.serialize(value, assoc)}"
144
+ end
145
+ when Array
146
+ var.each do |x|
147
+ case x
148
+ when Array
149
+ if x.size == 2
150
+ s << "#{x[0]}|#{PHP.serialize(x[1])}"
151
+ else
152
+ raise TypeError, "Array is not associative"
153
+ end
154
+ end
155
+ end
156
+ else
157
+ raise TypeError, "Unable to serialize sessions with top level types other than Hash and associative Array"
158
+ end
159
+ s
160
+ end # }}}
161
+
162
+ # mixed = PHP.unserialize(string serialized, [hash classmap, [bool assoc]])
163
+ #
164
+ # Returns an object containing the reconstituted data from serialized.
165
+ #
166
+ # If a PHP array (associative; like an ordered hash) is encountered, it
167
+ # scans the keys; if they're all incrementing integers counting from 0,
168
+ # it's unserialized as an Array, otherwise it's unserialized as a Hash.
169
+ # Note: this will lose ordering. To avoid this, specify assoc=true,
170
+ # and it will be unserialized as an associative array: [[key,value],...]
171
+ #
172
+ # If a serialized object is encountered, the hash 'classmap' is searched for
173
+ # the class name (as a symbol). Since PHP classnames are not case-preserving,
174
+ # this *must* be a .capitalize()d representation. The value is expected
175
+ # to be the class itself; i.e. something you could call .new on.
176
+ #
177
+ # If it's not found in 'classmap', the current constant namespace is searched,
178
+ # and failing that, a new Struct(classname) is generated, with the arguments
179
+ # for .new specified in the same order PHP provided; since PHP uses hashes
180
+ # to represent attributes, this should be the same order they're specified
181
+ # in PHP, but this is untested.
182
+ #
183
+ # each serialized attribute is sent to the new object using the respective
184
+ # {attribute}=() method; you'll get a NameError if the method doesn't exist.
185
+ #
186
+ # Array, Hash, Fixnum, Float, True/FalseClass, NilClass and String should
187
+ # be returned identically (i.e. foo == PHP.unserialize(PHP.serialize(foo))
188
+ # for these types); Struct should be too, provided it's in the namespace
189
+ # Module.const_get within unserialize() can see, or you gave it the same
190
+ # name in the Struct.new(<structname>), otherwise you should provide it in
191
+ # classmap.
192
+ #
193
+ # Note: StringIO is required for unserialize(); it's loaded as needed
194
+ def PHP.unserialize(string, classmap = nil, assoc = false) # {{{
195
+ if classmap == true or classmap == false
196
+ assoc = classmap
197
+ classmap = {}
198
+ end
199
+ classmap ||= {}
200
+
201
+ ret = nil
202
+ original_encoding = string.encoding
203
+ string = StringIOReader.new(string.force_encoding('BINARY'))
204
+ while string.string[string.pos, 32] =~ /^(\w+)\|/ # session_name|serialized_data
205
+ ret ||= {}
206
+ string.pos += $&.size
207
+ ret[$1] = PHP.do_unserialize(string, classmap, assoc, original_encoding)
208
+ end
209
+
210
+ ret ? ret : PHP.do_unserialize(string, classmap, assoc, original_encoding)
211
+ end
212
+
213
+ private
214
+ def PHP.do_unserialize(string, classmap, assoc, original_encoding)
215
+ val = nil
216
+ # determine a type
217
+ type = string.read(2)[0,1]
218
+ case type
219
+ when 'a' # associative array, a:length:{[index][value]...}
220
+ count = string.read_until('{').to_i
221
+ val = vals = Array.new
222
+ count.times do |i|
223
+ vals << [do_unserialize(string, classmap, assoc, original_encoding), do_unserialize(string, classmap, assoc, original_encoding)]
224
+ end
225
+ string.read(1) # skip the ending }
226
+
227
+ # now, we have an associative array, let's clean it up a bit...
228
+ # arrays have all numeric indexes, in order; otherwise we assume a hash
229
+ array = true
230
+ i = 0
231
+ vals.each do |key,value|
232
+ if key != i # wrong index -> assume hash
233
+ array = false
234
+ break
235
+ end
236
+ i += 1
237
+ end
238
+
239
+ if array
240
+ vals.collect! do |key,value|
241
+ value.kind_of?(String) ? value.force_encoding(original_encoding) : value
242
+ end
243
+ else
244
+ if assoc
245
+ val = vals.map {|v| v }
246
+ else
247
+ val = Hash.new
248
+ vals.each do |key,value|
249
+ key = key.force_encoding(original_encoding) if key.kind_of?(String)
250
+ value = value.force_encoding(original_encoding) if value.kind_of?(String)
251
+ val[key] = value
252
+ end
253
+ end
254
+ end
255
+
256
+ when 'O' # object, O:length:"class":length:{[attribute][value]...}
257
+ # class name (lowercase in PHP, grr)
258
+ len = string.read_until(':').to_i + 3 # quotes, seperator
259
+ klass = string.read(len)[1...-2].capitalize.intern # read it, kill useless quotes
260
+
261
+ # read the attributes
262
+ attrs = []
263
+ len = string.read_until('{').to_i
264
+
265
+ len.times do
266
+ attr = (do_unserialize(string, classmap, assoc, original_encoding))
267
+ attrs << [attr.intern, (attr << '=').intern, do_unserialize(string, classmap, assoc, original_encoding)]
268
+ end
269
+ string.read(1)
270
+
271
+ val = nil
272
+ # See if we need to map to a particular object
273
+ if classmap.has_key?(klass)
274
+ val = classmap[klass].new
275
+ elsif Struct.const_defined?(klass) # Nope; see if there's a Struct
276
+ classmap[klass] = val = Struct.const_get(klass)
277
+ val = val.new
278
+ else # Nope; see if there's a Constant
279
+ begin
280
+ classmap[klass] = val = Module.const_get(klass)
281
+
282
+ val = val.new
283
+ rescue NameError # Nope; make a new Struct
284
+ classmap[klass] = val = Struct.new(klass.to_s, *attrs.collect { |v| v[0].to_s })
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].force_encoding(original_encoding) # 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,137 @@
1
+ #!/usr/local/bin/ruby
2
+ # encoding: utf-8
3
+
4
+ require 'test/unit'
5
+
6
+ $:.unshift File.join(File.dirname(__FILE__), 'lib')
7
+ require 'php_serialize'
8
+
9
+ TestStruct = Struct.new(:name, :value)
10
+ class TestClass
11
+ attr_accessor :name
12
+ attr_accessor :value
13
+
14
+ def initialize(name = nil, value = nil)
15
+ @name = name
16
+ @value = value
17
+ end
18
+
19
+ def to_assoc
20
+ [['name', @name], ['value', @value]]
21
+ end
22
+
23
+ def ==(other)
24
+ other.class == self.class and other.name == @name and other.value == @value
25
+ end
26
+ end
27
+
28
+ ClassMap = {
29
+ TestStruct.name.capitalize.intern => TestStruct,
30
+ TestClass.name.capitalize.intern => TestClass
31
+ }
32
+
33
+ class TestPhpSerialize < Test::Unit::TestCase
34
+ def self.test(ruby, php, opts = {})
35
+ if opts[:name]
36
+ name = opts[:name]
37
+ else
38
+ name = ruby.to_s
39
+ end
40
+
41
+ define_method("test_#{name}".intern) do
42
+ assert_nothing_thrown do
43
+ serialized = PHP.serialize(ruby)
44
+ assert_equal php, serialized
45
+
46
+ unserialized = PHP.unserialize(serialized, ClassMap)
47
+ case ruby
48
+ when Symbol
49
+ assert_equal ruby.to_s, unserialized
50
+ else
51
+ assert_equal ruby, unserialized
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ test nil, 'N;'
58
+ test false, 'b:0;'
59
+ test true, 'b:1;'
60
+ test 42, 'i:42;'
61
+ test -42, 'i:-42;'
62
+ test 2147483647, "i:2147483647;", :name => 'Max Fixnum'
63
+ test -2147483648, "i:-2147483648;", :name => 'Min Fixnum'
64
+ test 4.2, 'd:4.2;'
65
+ test 'test', 's:4:"test";'
66
+ test :test, 's:4:"test";', :name => 'Symbol'
67
+ test "\"\n\t\"", "s:4:\"\"\n\t\"\";", :name => 'Complex string'
68
+ 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";}',
69
+ :name => 'Array'
70
+ 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')
71
+ test TestStruct.new("Foo", 65), 'O:10:"teststruct":2:{s:4:"name";s:3:"Foo";s:5:"value";i:65;}',
72
+ :name => 'Struct'
73
+ test TestClass.new("Foo", 65), 'O:9:"testclass":2:{s:4:"name";s:3:"Foo";s:5:"value";i:65;}',
74
+ :name => 'Class'
75
+
76
+ # PHP counts multibyte string, not string length
77
+ def test_multibyte_string
78
+ assert_equal "s:6:\"öäü\";", PHP.serialize("öäü")
79
+ assert_equal PHP.unserialize("s:6:\"öäü\";"), "öäü"
80
+ end
81
+
82
+ # Verify assoc is passed down calls.
83
+ # Slightly awkward because hashes don't guarantee order.
84
+ def test_assoc
85
+ assert_nothing_raised do
86
+ ruby = {'foo' => ['bar','baz'], 'hash' => {'hash' => 'smoke'}}
87
+ ruby_assoc = [['foo', ['bar','baz']], ['hash', [['hash','smoke']]]]
88
+ phps = [
89
+ '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";}}',
90
+ '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";}}'
91
+ ]
92
+ serialized = PHP.serialize(ruby, true)
93
+ assert phps.include?(serialized)
94
+ unserialized = PHP.unserialize(serialized, true)
95
+ assert_equal ruby_assoc.sort, unserialized.sort
96
+ end
97
+ end
98
+
99
+ # Multibyte version.
100
+ # Verify assoc is passed down calls.
101
+ # Slightly awkward because hashes don't guarantee order.
102
+ def test_assoc_multibyte
103
+ assert_nothing_raised do
104
+ ruby = {'ああ' => ['öäü','漢字'], 'hash' => {'おはよう' => 'smoke'}}
105
+ ruby_assoc = [['ああ', ['öäü','漢字']], ['hash', [['おはよう','smoke']]]]
106
+ phps = [
107
+ 'a:2:{s:6:"ああ";a:2:{i:0;s:6:"öäü";i:1;s:6:"漢字";}s:4:"hash";a:1:{s:12:"おはよう";s:5:"smoke";}}',
108
+ 'a:2:{s:4:"hash";a:1:{s:12:"おはよう";s:5:"smoke";}s:6:"ああ";a:2:{i:0;s:6:"öäü";i:1;s:6:"漢字";}}'
109
+ ]
110
+ serialized = PHP.serialize(ruby, true)
111
+ assert phps.include?(serialized)
112
+ unserialized = PHP.unserialize(serialized, true)
113
+ assert_equal ruby_assoc.sort, unserialized.sort
114
+ end
115
+ end
116
+
117
+ def test_sessions
118
+ assert_nothing_raised do
119
+ ruby = {'session_id' => 42, 'user_data' => {'uid' => 666}}
120
+ phps = [
121
+ 'session_id|i:42;user_data|a:1:{s:3:"uid";i:666;}',
122
+ 'user_data|a:1:{s:3:"uid";i:666;}session_id|i:42;'
123
+ ]
124
+ unserialized = PHP.unserialize(phps.first)
125
+ assert_equal ruby, unserialized
126
+ serialized = PHP.serialize_session(ruby)
127
+ assert phps.include?(serialized)
128
+ end
129
+ end
130
+
131
+ def test_new_struct_creation
132
+ assert_nothing_raised do
133
+ phps = 'O:8:"stdClass":2:{s:3:"url";s:17:"/legacy/index.php";s:8:"dateTime";s:19:"2012-10-24 22:29:13";}'
134
+ unserialized = PHP.unserialize(phps)
135
+ end
136
+ end
137
+ end
metadata ADDED
@@ -0,0 +1,47 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: k-php-serialize
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Keitaroh Kobayashi
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-05-27 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: "\tThis module provides two methods: PHP.serialize() and PHP.unserialize(),
14
+ both\n\tof which should be compatible with the similarly named functions in PHP.\n\n\tIt
15
+ can also serialize and unserialize PHP sessions.\n"
16
+ email: keita@kkob.us
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - lib/php_serialize.rb
22
+ - test.rb
23
+ homepage: https://github.com/keichan34/php-serialize
24
+ licenses: []
25
+ metadata: {}
26
+ post_install_message:
27
+ rdoc_options: []
28
+ require_paths:
29
+ - lib/
30
+ required_ruby_version: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - '>='
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ required_rubygems_version: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - '>='
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ requirements: []
41
+ rubyforge_project:
42
+ rubygems_version: 2.0.3
43
+ signing_key:
44
+ specification_version: 4
45
+ summary: Ruby analogs to PHP's serialize() and unserialize() functions
46
+ test_files:
47
+ - test.rb