parshal 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './parshal/marshal'
4
+ require_relative './parshal/unmarshal'
5
+
6
+ module Parshal
7
+ end
@@ -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: []