parshal 0.0.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.
- checksums.yaml +7 -0
- data/lib/parshal.rb +7 -0
- data/lib/parshal/marshal.rb +92 -0
- data/lib/parshal/unmarshal.rb +283 -0
- metadata +45 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 7affbe32c6d373f81aaefbfaf744b43c5bb17f4269130c553aefb132ef374ec2
|
4
|
+
data.tar.gz: 958ac000e7eed19af712bd1d93cb92a3b970ce784a3eba471f873b9c6ff44912
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9d68df5fbca0219f1414b8be9b72cd45853e940bfa52c7ad3ae1ce612b635bfffacaa8f2b287d8f9822d59ae36a630d1858c021c30a57c0a8c531e954dae779b
|
7
|
+
data.tar.gz: 3cc129c365490ce6b43e6443e918a0f981b7e83d0c58ea429008845a556f9348e1c5037f7cfefffaddf5a38c4b2fb5186b77868df372e5a8df399b06e44c6fbc
|
data/lib/parshal.rb
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Parshal
|
4
|
+
# A high-level marshal method that takes an object and tries to marshal the
|
5
|
+
# object into a byte array.
|
6
|
+
#
|
7
|
+
# == Parameters:
|
8
|
+
# [obj] The object to serialize
|
9
|
+
#
|
10
|
+
# == Returns:
|
11
|
+
# A byte array of the marshalled object.
|
12
|
+
def self.marshal(obj, prepend_version: true)
|
13
|
+
ret = case obj
|
14
|
+
when NilClass, TrueClass, FalseClass, Symbol
|
15
|
+
marshal_raw(obj)
|
16
|
+
when Integer
|
17
|
+
['i'.ord] +
|
18
|
+
marshal_raw(obj)
|
19
|
+
when String
|
20
|
+
['I'.ord] +
|
21
|
+
marshal_raw(obj) +
|
22
|
+
marshal_raw(1) +
|
23
|
+
marshal_raw(:E) +
|
24
|
+
marshal_raw(true)
|
25
|
+
else
|
26
|
+
raise ArgumentError, "marshal doesn't support objects of class #{obj.class}"
|
27
|
+
end
|
28
|
+
ret = [4, 8] + ret if prepend_version
|
29
|
+
ret.pack('c*')
|
30
|
+
end
|
31
|
+
|
32
|
+
# A low-level marshal dump that dumps "raw" types. Intended primarily for
|
33
|
+
# #marshal to use but may be useful in other contexts. Main differences are
|
34
|
+
# that Integer doesn't include its prefix (as it's used raw in many contexts)
|
35
|
+
# and String doesn't turn into an IVAR, it's just the "raw String"
|
36
|
+
# representation that's inside the normal IVAR.
|
37
|
+
#
|
38
|
+
# == Parameters:
|
39
|
+
# [obj] The object to be serialized.
|
40
|
+
#
|
41
|
+
# == Returns:
|
42
|
+
# The raw form of the serialized object.
|
43
|
+
def self.marshal_raw(obj)
|
44
|
+
case obj
|
45
|
+
when NilClass
|
46
|
+
['0'.ord]
|
47
|
+
when TrueClass
|
48
|
+
['T'.ord]
|
49
|
+
when FalseClass
|
50
|
+
['F'.ord]
|
51
|
+
when Integer
|
52
|
+
marshal_raw_integer obj
|
53
|
+
when Symbol
|
54
|
+
[':'.ord] + marshal_raw(obj.size) + obj.to_s.unpack('c*')
|
55
|
+
when String
|
56
|
+
['"'.ord] + marshal_raw(obj.size) + obj.unpack('c*')
|
57
|
+
else
|
58
|
+
raise ArgumentError, "marshal_raw doesn't support objects object of class #{obj.class}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.marshal_raw_integer(obj)
|
63
|
+
raise ArgumentError, 'marshal_raw_integer only accepts Integers' unless obj.is_a? Integer
|
64
|
+
|
65
|
+
case obj
|
66
|
+
when (-2**30...-2**24)
|
67
|
+
[-4] + [obj].pack('V').unpack('c4')
|
68
|
+
when (-2**24...-2**16)
|
69
|
+
[-3] + [obj].pack('V').unpack('c3')
|
70
|
+
when (-2**16...-2**8)
|
71
|
+
[-2] + [obj].pack('V').unpack('c2')
|
72
|
+
when (-2**8...-123)
|
73
|
+
[-1] + [obj].pack('V').unpack('c1')
|
74
|
+
when (-123..-1)
|
75
|
+
[obj - 5]
|
76
|
+
when 0
|
77
|
+
[0]
|
78
|
+
when (1...123)
|
79
|
+
[obj + 5]
|
80
|
+
when (123...2**8)
|
81
|
+
[1] + [obj].pack('V').unpack('c1')
|
82
|
+
when (2**8...2**16)
|
83
|
+
[2] + [obj].pack('V').unpack('c2')
|
84
|
+
when (2**16...2**24)
|
85
|
+
[3] + [obj].pack('V').unpack('c3')
|
86
|
+
when (2**24...2**30)
|
87
|
+
[4] + [obj].pack('V').unpack('c4')
|
88
|
+
else
|
89
|
+
raise ArgumentError, 'marshal_raw_integer can only marshal integers between -2**30...2**30'
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,283 @@
|
|
1
|
+
module Parshal
|
2
|
+
def self.remove_version_prefix(obj)
|
3
|
+
major, minor = obj[0..1].unpack('cc')
|
4
|
+
|
5
|
+
unless major == 4 && minor == 8
|
6
|
+
warn "WARNING: Reply specifies unsupported version of marshal format: #{major}.#{minor}"
|
7
|
+
end
|
8
|
+
|
9
|
+
obj[2..]
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.unmarshal(raw_obj, remove_version: true, return_rem: false)
|
13
|
+
raise ArgumentError, "raw_obj must be a String type, got #{raw_obj.inspect}" unless raw_obj.is_a? String
|
14
|
+
|
15
|
+
obj = if remove_version
|
16
|
+
remove_version_prefix raw_obj
|
17
|
+
else
|
18
|
+
raw_obj
|
19
|
+
end
|
20
|
+
|
21
|
+
raise ArgumentError, 'Empty object passed to unmarshal' if obj.empty?
|
22
|
+
|
23
|
+
case obj[0]
|
24
|
+
when '0'
|
25
|
+
if return_rem
|
26
|
+
[nil, obj[1..]]
|
27
|
+
else
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
when 'T'
|
31
|
+
if return_rem
|
32
|
+
[true, obj[1..]]
|
33
|
+
else
|
34
|
+
true
|
35
|
+
end
|
36
|
+
when 'F'
|
37
|
+
if return_rem
|
38
|
+
[false, obj[1..]]
|
39
|
+
else
|
40
|
+
false
|
41
|
+
end
|
42
|
+
when 'i'
|
43
|
+
# Integer
|
44
|
+
i, _, rem = unmarshal_raw_integer(obj[1..])
|
45
|
+
|
46
|
+
raise "Remaining data following Integer: #{rem.inspect}" if !return_rem && !rem.empty?
|
47
|
+
|
48
|
+
if return_rem
|
49
|
+
[i, rem]
|
50
|
+
else
|
51
|
+
i
|
52
|
+
end
|
53
|
+
when ':'
|
54
|
+
# Symbol
|
55
|
+
sym, _, rem = unmarshal_raw_string_symbol(obj)
|
56
|
+
|
57
|
+
warn "Remaining data following Symbol: #{rem.inspect}" if !return_rem && !rem.empty?
|
58
|
+
|
59
|
+
if return_rem
|
60
|
+
[sym, rem]
|
61
|
+
else
|
62
|
+
sym
|
63
|
+
end
|
64
|
+
when 'I'
|
65
|
+
# String wrapped in IVAR
|
66
|
+
# we only parse for strings and verify the rest of the basic IVAR parts at
|
67
|
+
# the end
|
68
|
+
# {I, {", marshal(len), raw str}, marshal(1), marshal(:E), marshal(true)}
|
69
|
+
if obj.length < (Marshal.dump('').length - 2) # -2 to strip the version number
|
70
|
+
raise "unexpected IVAR: #{raw_obj.inspect}, smaller than minimum length"
|
71
|
+
end
|
72
|
+
|
73
|
+
str, _, rem = unmarshal_raw_string_symbol(obj[1..])
|
74
|
+
|
75
|
+
raise 'Unexpected end of IVAR' if rem.empty?
|
76
|
+
|
77
|
+
ivar_count, _, rem = unmarshal_raw_integer(rem)
|
78
|
+
|
79
|
+
|
80
|
+
raise "IVAR with more than one instance variable unsupported: #{ivar_count.inspect}" if ivar_count > 1
|
81
|
+
|
82
|
+
raise 'Unexpected end of IVAR' if rem.empty?
|
83
|
+
|
84
|
+
if ivar_count == 0
|
85
|
+
if return_rem
|
86
|
+
return [str, rem]
|
87
|
+
else
|
88
|
+
return str
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
e, _, rem = unmarshal_raw_string_symbol(rem)
|
93
|
+
|
94
|
+
# :E=true for UTF-8 and :E=false for ASCII, :encoding=whatever for everything else
|
95
|
+
# we are not going to support custom encodings
|
96
|
+
raise "IVAR with unexpected instance variable: #{e.inspect}" unless e == :E
|
97
|
+
|
98
|
+
raise 'Unexpected end of IVAR' if rem.empty?
|
99
|
+
b, _, rem = unmarshal_raw_bool(rem)
|
100
|
+
|
101
|
+
if not b
|
102
|
+
str.force_encoding('ascii')
|
103
|
+
end
|
104
|
+
|
105
|
+
raise "Unexpected data at the end of IVAR: #{rem.inspect}" if !return_rem && !rem.empty?
|
106
|
+
|
107
|
+
if return_rem
|
108
|
+
[str, rem]
|
109
|
+
else
|
110
|
+
str
|
111
|
+
end
|
112
|
+
when '['
|
113
|
+
len, _, rem = unmarshal_raw_integer(obj[1..])
|
114
|
+
arr = []
|
115
|
+
for _ in 0...len
|
116
|
+
obj, rem = unmarshal(rem, remove_version: false, return_rem: true)
|
117
|
+
arr.push(obj)
|
118
|
+
end
|
119
|
+
|
120
|
+
raise "Unexpected data after the end of Array: #{rem.inspect}" if !return_rem && !rem.empty?
|
121
|
+
|
122
|
+
if return_rem
|
123
|
+
[arr, rem]
|
124
|
+
else
|
125
|
+
arr
|
126
|
+
end
|
127
|
+
when 'u'
|
128
|
+
# we pull out the symbol and attempt to decode the payload as a basic value
|
129
|
+
sym, _, rem = unmarshal_raw_string_symbol(obj[1..])
|
130
|
+
|
131
|
+
len, _, rem = unmarshal_raw_integer(rem)
|
132
|
+
|
133
|
+
raise "User-defined payload length mismatch, got #{len}, actual: #{rem.length}" if len != rem.length
|
134
|
+
|
135
|
+
payload = rem
|
136
|
+
|
137
|
+
payload_decoded = begin
|
138
|
+
unmarshal(payload)
|
139
|
+
rescue => e
|
140
|
+
puts e.inspect
|
141
|
+
nil
|
142
|
+
end
|
143
|
+
|
144
|
+
if return_rem
|
145
|
+
[[sym, [payload_decoded, payload]], rem]
|
146
|
+
else
|
147
|
+
[sym, [payload_decoded, payload]]
|
148
|
+
end
|
149
|
+
else
|
150
|
+
unmarshal_type_error obj
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def self.unmarshal_type_error(obj)
|
155
|
+
case obj[0]
|
156
|
+
when ';'
|
157
|
+
raise 'Symbol links/references not supported'
|
158
|
+
when '@'
|
159
|
+
raise 'Object links/references not supported'
|
160
|
+
when 'e'
|
161
|
+
raise "'extended' not supported"
|
162
|
+
when 'l'
|
163
|
+
raise 'Bignum not supported'
|
164
|
+
when 'c'
|
165
|
+
raise 'Class not supported'
|
166
|
+
when 'm'
|
167
|
+
raise 'Module not supported'
|
168
|
+
when 'M'
|
169
|
+
raise 'Class/Module not supported'
|
170
|
+
when 'd'
|
171
|
+
raise 'Data objects not supported'
|
172
|
+
when 'f'
|
173
|
+
raise 'Float not supported'
|
174
|
+
when '{', '}'
|
175
|
+
raise 'Hash not supported'
|
176
|
+
when 'o'
|
177
|
+
raise "Object not supported: #{obj.inspect}"
|
178
|
+
when '/'
|
179
|
+
raise 'regular expressions not supported'
|
180
|
+
when 'S'
|
181
|
+
raise 'Struct not supported'
|
182
|
+
when 'C'
|
183
|
+
raise 'user-defined sublass not supported'
|
184
|
+
when 'U'
|
185
|
+
raise "User Marshal serialization not supported: #{obj.inspect}"
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def self.unmarshal_raw(obj)
|
190
|
+
raise ArgumentError, 'unmarshal_raw passed Nil' if obj.nil?
|
191
|
+
|
192
|
+
raise ArgumentError, 'unmarshal_raw passed empty String' if obj.empty?
|
193
|
+
|
194
|
+
case obj[0]
|
195
|
+
when '0'
|
196
|
+
[nil, 1, obj[1..]]
|
197
|
+
when 'T', 'F'
|
198
|
+
unmarshal_raw_bool obj
|
199
|
+
when '"', ':'
|
200
|
+
unmarshal_raw_string_symbol obj
|
201
|
+
else
|
202
|
+
raise 'unmarshal_raw passed unknown argument'
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def self.unmarshal_raw_bool(obj)
|
207
|
+
raise ArgumentError, 'unmarshal_raw_bool passed nil or empty string' if obj.nil? || obj.empty?
|
208
|
+
|
209
|
+
case obj[0]
|
210
|
+
when 'T'
|
211
|
+
[true, 1, obj[1..]]
|
212
|
+
when 'F'
|
213
|
+
[false, 1, obj[1..]]
|
214
|
+
else
|
215
|
+
raise "unmarshal_raw_bool not passed a bool to unmarshal: #{obj.inspect}"
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
def self.unmarshal_raw_string_symbol(obj)
|
220
|
+
raise 'unmarshal_raw_string_symbol passed nil or empty String' if obj.nil? || obj.empty?
|
221
|
+
|
222
|
+
raise 'unmarshal_raw_string_symbol not passed marshaled String or Symbol' unless obj[0] == '"' || obj[0] == ':'
|
223
|
+
|
224
|
+
sz, sz_sz, rem = unmarshal_raw_integer(obj[1..])
|
225
|
+
|
226
|
+
raise 'String or Symbol has negative size' if sz.negative?
|
227
|
+
|
228
|
+
str = rem[...sz]
|
229
|
+
|
230
|
+
raise 'Not enough data supplied to satisfy String or Symbol size' unless sz == str.size
|
231
|
+
|
232
|
+
consumed = 1 + sz_sz + sz
|
233
|
+
|
234
|
+
if obj[0] == '"'
|
235
|
+
[str, consumed, obj[consumed..]]
|
236
|
+
else
|
237
|
+
[str.to_sym, consumed, obj[consumed..]]
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
def self.unmarshal_raw_integer(obj)
|
242
|
+
raise ArgumentError, 'unmarshal_raw_integer passed invalid input' if obj.nil? || obj.empty?
|
243
|
+
|
244
|
+
case obj[0].unpack1('c')
|
245
|
+
when 0
|
246
|
+
# "0 -> Integer: 0"
|
247
|
+
[0, 1, obj[1..]]
|
248
|
+
when 1
|
249
|
+
# "1 -> Integer: (123...2**8)"
|
250
|
+
[obj[1].unpack1('C'), 2, obj[2..]]
|
251
|
+
when 2
|
252
|
+
# "2 -> Integer: (2**8...2**16)"
|
253
|
+
[obj[1..2].unpack1('S<'), 3, obj[3..]]
|
254
|
+
when 3
|
255
|
+
# "3 -> Integer: (2**16...2**24)"
|
256
|
+
["#{obj[1..3]}\x00".unpack1('L<'), 4, obj[4..]]
|
257
|
+
when 4
|
258
|
+
# "4 -> Integer: (2**24...2**30)"
|
259
|
+
[obj[1..4].unpack1('L<'), 5, obj[5..]]
|
260
|
+
when -1
|
261
|
+
# "-1 -> Integer: (-2**8...-123)"
|
262
|
+
[obj[1].unpack1('C') + (-2**8), 2, obj[2..]]
|
263
|
+
when -2
|
264
|
+
# "-2 -> Integer: (-2**16...-2**8)"
|
265
|
+
[obj[1..2].unpack1('S<') + (-2**16), 3, obj[3..]]
|
266
|
+
when -3
|
267
|
+
# "-3 -> Integer: (-2**24...-2**16)"
|
268
|
+
["#{obj[1..3]}\x00".unpack1('L<') + (-2**24), 4, obj[4..]]
|
269
|
+
when -4
|
270
|
+
# "-4 -> Integer: (-2**30...-2**24)"
|
271
|
+
[obj[1..4].unpack1('L<') + (-2**32), 5, obj[5..]]
|
272
|
+
when 6..127
|
273
|
+
# "1...123 -> Integer: i+5"
|
274
|
+
[obj[0].unpack1('c') - 5, 1, obj[1..]]
|
275
|
+
when -128..-6
|
276
|
+
# "-123...-1 -> Integer: i-5"
|
277
|
+
[obj[0].unpack1('c') + 5, 1, obj[1..]]
|
278
|
+
else # 5 and -5 are undefined
|
279
|
+
# "unknown: " + obj[0].ord
|
280
|
+
raise ArgumentError, '5 and -5 are undefined when marshaling integers'
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
metadata
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: parshal
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Addison Amiri
|
8
|
+
- Jeff Dileo
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2021-05-20 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description:
|
15
|
+
email: jeff.dileo@nccgroup.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- lib/parshal.rb
|
21
|
+
- lib/parshal/marshal.rb
|
22
|
+
- lib/parshal/unmarshal.rb
|
23
|
+
homepage: https://github.com/nccgroup/drb-rb
|
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
|
+
rubygems_version: 3.1.4
|
42
|
+
signing_key:
|
43
|
+
specification_version: 4
|
44
|
+
summary: A safer, minimal, partial implementation of Marshal
|
45
|
+
test_files: []
|