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 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: []