python-pickle 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.document +3 -0
- data/.github/workflows/ruby.yml +27 -0
- data/.gitignore +5 -0
- data/.rspec +1 -0
- data/.yardopts +1 -0
- data/ChangeLog.md +14 -0
- data/Gemfile +15 -0
- data/LICENSE.txt +20 -0
- data/README.md +149 -0
- data/Rakefile +13 -0
- data/gemspec.yml +25 -0
- data/lib/python/pickle/byte_array.rb +40 -0
- data/lib/python/pickle/deserializer.rb +595 -0
- data/lib/python/pickle/exceptions.rb +12 -0
- data/lib/python/pickle/instruction.rb +52 -0
- data/lib/python/pickle/instructions/add_items.rb +26 -0
- data/lib/python/pickle/instructions/append.rb +24 -0
- data/lib/python/pickle/instructions/appends.rb +26 -0
- data/lib/python/pickle/instructions/bin_bytes.rb +32 -0
- data/lib/python/pickle/instructions/bin_bytes8.rb +32 -0
- data/lib/python/pickle/instructions/bin_float.rb +29 -0
- data/lib/python/pickle/instructions/bin_get.rb +27 -0
- data/lib/python/pickle/instructions/bin_int1.rb +29 -0
- data/lib/python/pickle/instructions/bin_put.rb +29 -0
- data/lib/python/pickle/instructions/bin_string.rb +32 -0
- data/lib/python/pickle/instructions/bin_unicode.rb +32 -0
- data/lib/python/pickle/instructions/bin_unicode8.rb +32 -0
- data/lib/python/pickle/instructions/build.rb +24 -0
- data/lib/python/pickle/instructions/byte_array8.rb +32 -0
- data/lib/python/pickle/instructions/dict.rb +17 -0
- data/lib/python/pickle/instructions/dup.rb +24 -0
- data/lib/python/pickle/instructions/empty_dict.rb +26 -0
- data/lib/python/pickle/instructions/empty_list.rb +26 -0
- data/lib/python/pickle/instructions/empty_set.rb +26 -0
- data/lib/python/pickle/instructions/empty_tuple.rb +26 -0
- data/lib/python/pickle/instructions/ext1.rb +29 -0
- data/lib/python/pickle/instructions/ext2.rb +29 -0
- data/lib/python/pickle/instructions/ext4.rb +29 -0
- data/lib/python/pickle/instructions/float.rb +24 -0
- data/lib/python/pickle/instructions/frame.rb +29 -0
- data/lib/python/pickle/instructions/frozen_set.rb +26 -0
- data/lib/python/pickle/instructions/get.rb +27 -0
- data/lib/python/pickle/instructions/global.rb +62 -0
- data/lib/python/pickle/instructions/has_length_and_value.rb +58 -0
- data/lib/python/pickle/instructions/has_value.rb +50 -0
- data/lib/python/pickle/instructions/int.rb +24 -0
- data/lib/python/pickle/instructions/list.rb +24 -0
- data/lib/python/pickle/instructions/long.rb +24 -0
- data/lib/python/pickle/instructions/long1.rb +32 -0
- data/lib/python/pickle/instructions/long4.rb +32 -0
- data/lib/python/pickle/instructions/long_bin_get.rb +27 -0
- data/lib/python/pickle/instructions/mark.rb +24 -0
- data/lib/python/pickle/instructions/memoize.rb +26 -0
- data/lib/python/pickle/instructions/new_false.rb +24 -0
- data/lib/python/pickle/instructions/new_obj.rb +26 -0
- data/lib/python/pickle/instructions/new_obj_ex.rb +26 -0
- data/lib/python/pickle/instructions/new_true.rb +24 -0
- data/lib/python/pickle/instructions/next_buffer.rb +26 -0
- data/lib/python/pickle/instructions/none.rb +24 -0
- data/lib/python/pickle/instructions/pop.rb +24 -0
- data/lib/python/pickle/instructions/pop_mark.rb +24 -0
- data/lib/python/pickle/instructions/proto.rb +29 -0
- data/lib/python/pickle/instructions/put.rb +24 -0
- data/lib/python/pickle/instructions/readonly_buffer.rb +26 -0
- data/lib/python/pickle/instructions/reduce.rb +24 -0
- data/lib/python/pickle/instructions/set_item.rb +24 -0
- data/lib/python/pickle/instructions/set_items.rb +26 -0
- data/lib/python/pickle/instructions/short_bin_bytes.rb +32 -0
- data/lib/python/pickle/instructions/short_bin_string.rb +32 -0
- data/lib/python/pickle/instructions/short_bin_unicode.rb +32 -0
- data/lib/python/pickle/instructions/stack_global.rb +26 -0
- data/lib/python/pickle/instructions/stop.rb +24 -0
- data/lib/python/pickle/instructions/string.rb +24 -0
- data/lib/python/pickle/instructions/tuple.rb +24 -0
- data/lib/python/pickle/instructions/tuple1.rb +24 -0
- data/lib/python/pickle/instructions/tuple2.rb +24 -0
- data/lib/python/pickle/instructions/tuple3.rb +24 -0
- data/lib/python/pickle/protocol.rb +56 -0
- data/lib/python/pickle/protocol0.rb +399 -0
- data/lib/python/pickle/protocol1.rb +183 -0
- data/lib/python/pickle/protocol2.rb +229 -0
- data/lib/python/pickle/protocol3.rb +163 -0
- data/lib/python/pickle/protocol4.rb +285 -0
- data/lib/python/pickle/protocol5.rb +218 -0
- data/lib/python/pickle/py_class.rb +75 -0
- data/lib/python/pickle/py_object.rb +141 -0
- data/lib/python/pickle/tuple.rb +19 -0
- data/lib/python/pickle/version.rb +6 -0
- data/lib/python/pickle.rb +226 -0
- data/python-pickle.gemspec +62 -0
- data/spec/byte_array_spec.rb +54 -0
- data/spec/deserializer_spec.rb +1201 -0
- data/spec/fixtures/ascii_str_v3.pkl +0 -0
- data/spec/fixtures/ascii_str_v4.pkl +0 -0
- data/spec/fixtures/ascii_str_v5.pkl +0 -0
- data/spec/fixtures/bin_str_v0.pkl +3 -0
- data/spec/fixtures/bin_str_v1.pkl +0 -0
- data/spec/fixtures/bin_str_v2.pkl +0 -0
- data/spec/fixtures/bin_str_v3.pkl +0 -0
- data/spec/fixtures/bin_str_v4.pkl +0 -0
- data/spec/fixtures/bin_str_v5.pkl +0 -0
- data/spec/fixtures/bytearray_v0.pkl +10 -0
- data/spec/fixtures/bytearray_v1.pkl +0 -0
- data/spec/fixtures/bytearray_v2.pkl +0 -0
- data/spec/fixtures/bytearray_v3.pkl +0 -0
- data/spec/fixtures/bytearray_v4.pkl +0 -0
- data/spec/fixtures/bytearray_v5.pkl +0 -0
- data/spec/fixtures/class_v0.pkl +4 -0
- data/spec/fixtures/class_v1.pkl +0 -0
- data/spec/fixtures/class_v2.pkl +0 -0
- data/spec/fixtures/class_v3.pkl +0 -0
- data/spec/fixtures/class_v4.pkl +0 -0
- data/spec/fixtures/class_v5.pkl +0 -0
- data/spec/fixtures/dict_v0.pkl +6 -0
- data/spec/fixtures/dict_v1.pkl +0 -0
- data/spec/fixtures/dict_v2.pkl +0 -0
- data/spec/fixtures/dict_v3.pkl +0 -0
- data/spec/fixtures/dict_v4.pkl +0 -0
- data/spec/fixtures/dict_v5.pkl +0 -0
- data/spec/fixtures/escaped_str_v0.pkl +3 -0
- data/spec/fixtures/escaped_str_v1.pkl +0 -0
- data/spec/fixtures/escaped_str_v2.pkl +0 -0
- data/spec/fixtures/false_v0.pkl +2 -0
- data/spec/fixtures/false_v1.pkl +2 -0
- data/spec/fixtures/false_v2.pkl +1 -0
- data/spec/fixtures/false_v3.pkl +1 -0
- data/spec/fixtures/false_v4.pkl +1 -0
- data/spec/fixtures/false_v5.pkl +1 -0
- data/spec/fixtures/float_v0.pkl +2 -0
- data/spec/fixtures/float_v1.pkl +1 -0
- data/spec/fixtures/float_v2.pkl +1 -0
- data/spec/fixtures/float_v3.pkl +1 -0
- data/spec/fixtures/float_v4.pkl +0 -0
- data/spec/fixtures/float_v5.pkl +0 -0
- data/spec/fixtures/function_v0.pkl +4 -0
- data/spec/fixtures/function_v1.pkl +0 -0
- data/spec/fixtures/function_v2.pkl +0 -0
- data/spec/fixtures/function_v3.pkl +0 -0
- data/spec/fixtures/function_v4.pkl +0 -0
- data/spec/fixtures/function_v5.pkl +0 -0
- data/spec/fixtures/hex_str_v0.pkl +3 -0
- data/spec/fixtures/hex_str_v1.pkl +0 -0
- data/spec/fixtures/hex_str_v2.pkl +0 -0
- data/spec/fixtures/int_v0.pkl +2 -0
- data/spec/fixtures/int_v1.pkl +1 -0
- data/spec/fixtures/int_v2.pkl +1 -0
- data/spec/fixtures/int_v3.pkl +1 -0
- data/spec/fixtures/int_v4.pkl +1 -0
- data/spec/fixtures/int_v5.pkl +1 -0
- data/spec/fixtures/list_v0.pkl +7 -0
- data/spec/fixtures/list_v1.pkl +0 -0
- data/spec/fixtures/list_v2.pkl +0 -0
- data/spec/fixtures/list_v3.pkl +0 -0
- data/spec/fixtures/list_v4.pkl +0 -0
- data/spec/fixtures/list_v5.pkl +0 -0
- data/spec/fixtures/long_v0.pkl +2 -0
- data/spec/fixtures/long_v1.pkl +2 -0
- data/spec/fixtures/long_v2.pkl +0 -0
- data/spec/fixtures/long_v3.pkl +0 -0
- data/spec/fixtures/long_v4.pkl +0 -0
- data/spec/fixtures/long_v5.pkl +0 -0
- data/spec/fixtures/nested_dict_v0.pkl +12 -0
- data/spec/fixtures/nested_dict_v1.pkl +0 -0
- data/spec/fixtures/nested_dict_v2.pkl +0 -0
- data/spec/fixtures/nested_dict_v3.pkl +0 -0
- data/spec/fixtures/nested_dict_v4.pkl +0 -0
- data/spec/fixtures/nested_dict_v5.pkl +0 -0
- data/spec/fixtures/nested_list_v0.pkl +9 -0
- data/spec/fixtures/nested_list_v1.pkl +0 -0
- data/spec/fixtures/nested_list_v2.pkl +0 -0
- data/spec/fixtures/nested_list_v3.pkl +0 -0
- data/spec/fixtures/nested_list_v4.pkl +0 -0
- data/spec/fixtures/nested_list_v5.pkl +0 -0
- data/spec/fixtures/none_v0.pkl +1 -0
- data/spec/fixtures/none_v1.pkl +1 -0
- data/spec/fixtures/none_v2.pkl +1 -0
- data/spec/fixtures/none_v3.pkl +1 -0
- data/spec/fixtures/none_v4.pkl +1 -0
- data/spec/fixtures/none_v5.pkl +1 -0
- data/spec/fixtures/object_v0.pkl +19 -0
- data/spec/fixtures/object_v1.pkl +0 -0
- data/spec/fixtures/object_v2.pkl +0 -0
- data/spec/fixtures/object_v3.pkl +0 -0
- data/spec/fixtures/object_v4.pkl +0 -0
- data/spec/fixtures/object_v5.pkl +0 -0
- data/spec/fixtures/str_v0.pkl +3 -0
- data/spec/fixtures/str_v1.pkl +0 -0
- data/spec/fixtures/str_v2.pkl +0 -0
- data/spec/fixtures/str_v3.pkl +0 -0
- data/spec/fixtures/str_v4.pkl +0 -0
- data/spec/fixtures/str_v5.pkl +0 -0
- data/spec/fixtures/true_v0.pkl +2 -0
- data/spec/fixtures/true_v1.pkl +2 -0
- data/spec/fixtures/true_v2.pkl +1 -0
- data/spec/fixtures/true_v3.pkl +1 -0
- data/spec/fixtures/true_v4.pkl +1 -0
- data/spec/fixtures/true_v5.pkl +1 -0
- data/spec/fixtures/unicode_str_v0.pkl +3 -0
- data/spec/fixtures/unicode_str_v1.pkl +0 -0
- data/spec/fixtures/unicode_str_v2.pkl +0 -0
- data/spec/fixtures/unicode_str_v3.pkl +0 -0
- data/spec/fixtures/unicode_str_v4.pkl +0 -0
- data/spec/fixtures/unicode_str_v5.pkl +0 -0
- data/spec/generate_pickles2.py +41 -0
- data/spec/generate_pickles3.py +40 -0
- data/spec/integration/load/protocol0_spec.rb +258 -0
- data/spec/integration/load/protocol1_spec.rb +258 -0
- data/spec/integration/load/protocol2_spec.rb +258 -0
- data/spec/integration/load/protocol3_spec.rb +258 -0
- data/spec/integration/load/protocol4_spec.rb +258 -0
- data/spec/integration/load/protocol5_spec.rb +258 -0
- data/spec/integration/parse/protocol0_spec.rb +467 -0
- data/spec/integration/parse/protocol1_spec.rb +459 -0
- data/spec/integration/parse/protocol2_spec.rb +471 -0
- data/spec/integration/parse/protocol3_spec.rb +407 -0
- data/spec/integration/parse/protocol4_spec.rb +439 -0
- data/spec/integration/parse/protocol5_spec.rb +419 -0
- data/spec/pickle_spec.rb +163 -0
- data/spec/protocol0_read_instruction_examples.rb +211 -0
- data/spec/protocol0_spec.rb +445 -0
- data/spec/protocol1_read_instruction_examples.rb +156 -0
- data/spec/protocol1_spec.rb +59 -0
- data/spec/protocol2_read_instruction_examples.rb +135 -0
- data/spec/protocol2_spec.rb +128 -0
- data/spec/protocol3_read_instruction_examples.rb +29 -0
- data/spec/protocol3_spec.rb +32 -0
- data/spec/protocol4_read_instruction_examples.rb +142 -0
- data/spec/protocol4_spec.rb +58 -0
- data/spec/protocol5_spec.rb +68 -0
- data/spec/py_class_spec.rb +62 -0
- data/spec/py_object_spec.rb +149 -0
- data/spec/spec_helper.rb +3 -0
- data/spec/tuple_spec.rb +18 -0
- metadata +325 -0
@@ -0,0 +1,218 @@
|
|
1
|
+
require 'python/pickle/protocol4'
|
2
|
+
require 'python/pickle/instructions/byte_array8'
|
3
|
+
require 'python/pickle/instructions/next_buffer'
|
4
|
+
require 'python/pickle/instructions/readonly_buffer'
|
5
|
+
|
6
|
+
module Python
|
7
|
+
module Pickle
|
8
|
+
class Protocol5 < Protocol4
|
9
|
+
|
10
|
+
# Opcodes for Pickle protocol 5.
|
11
|
+
#
|
12
|
+
# @see https://peps.python.org/pep-0574/
|
13
|
+
OPCODES = Protocol4::OPCODES + Set[
|
14
|
+
150, # BYTEARRAY8
|
15
|
+
151, # NEXT_BUFFER
|
16
|
+
152, # READONLY_BUFFER
|
17
|
+
]
|
18
|
+
|
19
|
+
#
|
20
|
+
# Reads an instruction from the pickle stream.
|
21
|
+
#
|
22
|
+
# @return [Instruction]
|
23
|
+
# The decoded instruction.
|
24
|
+
#
|
25
|
+
# @raise [InvalidFormat]
|
26
|
+
# The pickle stream could not be parsed.
|
27
|
+
#
|
28
|
+
def read_instruction
|
29
|
+
case (opcode = @io.getbyte)
|
30
|
+
#
|
31
|
+
# Protocol 0 instructions
|
32
|
+
#
|
33
|
+
when 40 # MARK
|
34
|
+
Instructions::MARK
|
35
|
+
when 46 # STOP
|
36
|
+
Instructions::STOP
|
37
|
+
when 48 # POP
|
38
|
+
Instructions::POP
|
39
|
+
when 49 # POP_MARK
|
40
|
+
Instructions::POP_MARK
|
41
|
+
when 50 # DUP
|
42
|
+
Instructions::DUP
|
43
|
+
when 70 # FLOAT
|
44
|
+
Instructions::Float.new(read_float)
|
45
|
+
when 73 # INT
|
46
|
+
Instructions::Int.new(read_int)
|
47
|
+
when 76 # LONG
|
48
|
+
Instructions::Long.new(read_long)
|
49
|
+
when 78 # NONE
|
50
|
+
Instructions::NONE
|
51
|
+
when 82 # REDUCE
|
52
|
+
Instructions::REDUCE
|
53
|
+
when 83 # STRING
|
54
|
+
Instructions::String.new(read_string)
|
55
|
+
when 86 # UNICODE
|
56
|
+
Instructions::String.new(read_unicode_string)
|
57
|
+
when 97 # APPEND
|
58
|
+
Instructions::APPEND
|
59
|
+
when 98 # BUILD
|
60
|
+
Instructions::BUILD
|
61
|
+
when 99 # GLOBAL
|
62
|
+
Instructions::Global.new(read_nl_string,read_nl_string)
|
63
|
+
when 100 # DICT
|
64
|
+
Instructions::DICT
|
65
|
+
when 103 # GET
|
66
|
+
Instructions::Get.new(read_int)
|
67
|
+
when 108 # LIST
|
68
|
+
Instructions::LIST
|
69
|
+
when 112 # PUT
|
70
|
+
Instructions::Put.new(read_int)
|
71
|
+
when 115 # SETITEM
|
72
|
+
Instructions::SETITEM
|
73
|
+
when 116 # TUPLE
|
74
|
+
Instructions::TUPLE
|
75
|
+
#
|
76
|
+
# Protocol 1 instructions
|
77
|
+
#
|
78
|
+
when 41 # EMPTY_TUPLE
|
79
|
+
Instructions::EMPTY_TUPLE
|
80
|
+
when 71 # BINFLOAT
|
81
|
+
Instructions::BinFloat.new(read_float64_be)
|
82
|
+
when 75 # BININT1
|
83
|
+
Instructions::BinInt1.new(read_uint8)
|
84
|
+
when 84 # BINSTRING
|
85
|
+
length = read_uint32_le
|
86
|
+
string = @io.read(length)
|
87
|
+
|
88
|
+
Instructions::BinString.new(length,string)
|
89
|
+
when 85 # SHORT_BINSTRING
|
90
|
+
length = read_uint8
|
91
|
+
string = @io.read(length)
|
92
|
+
|
93
|
+
Instructions::ShortBinString.new(length,string)
|
94
|
+
when 88 # BINUNICODE
|
95
|
+
length = read_uint32_le
|
96
|
+
string = @io.read(length).force_encoding(Encoding::UTF_8)
|
97
|
+
|
98
|
+
Instructions::BinUnicode.new(length,string)
|
99
|
+
when 93 # EMPTY_LIST
|
100
|
+
Instructions::EMPTY_LIST
|
101
|
+
when 101 # APPENDS
|
102
|
+
Instructions::APPENDS
|
103
|
+
when 104 # BINGET
|
104
|
+
Instructions::BinGet.new(read_uint8)
|
105
|
+
when 106 # LONG_BINGET
|
106
|
+
Instructions::LongBinGet.new(read_uint32_le)
|
107
|
+
when 113 # BINPUT
|
108
|
+
Instructions::BinPut.new(read_uint8)
|
109
|
+
when 117 # SETITEMS
|
110
|
+
Instructions::SETITEMS
|
111
|
+
when 125 # EMPTY_DICT
|
112
|
+
Instructions::EMPTY_DICT
|
113
|
+
#
|
114
|
+
# Protocol 2 instructions
|
115
|
+
#
|
116
|
+
when 128 # PROT
|
117
|
+
Instructions::Proto.new(read_uint8)
|
118
|
+
when 129 # NEWOBJ
|
119
|
+
Instructions::NEWOBJ
|
120
|
+
when 130 # EXT1
|
121
|
+
Instructions::Ext1.new(read_uint8)
|
122
|
+
when 131 # EXT2
|
123
|
+
Instructions::Ext2.new(read_uint16_le)
|
124
|
+
when 132 # EXT4
|
125
|
+
Instructions::Ext4.new(read_uint32_le)
|
126
|
+
when 133 # TUPLE1
|
127
|
+
Instructions::TUPLE1
|
128
|
+
when 134 # TUPLE2
|
129
|
+
Instructions::TUPLE2
|
130
|
+
when 135 # TUPLE3
|
131
|
+
Instructions::TUPLE3
|
132
|
+
when 136 # NEWTRUE
|
133
|
+
Instructions::NEWTRUE
|
134
|
+
when 137 # NEWFALSE
|
135
|
+
Instructions::NEWFALSE
|
136
|
+
when 138 # LONG1
|
137
|
+
length = read_uint8
|
138
|
+
long = read_int_le(length)
|
139
|
+
|
140
|
+
Instructions::Long1.new(length,long)
|
141
|
+
when 139 # LONG4
|
142
|
+
length = read_uint32_le
|
143
|
+
long = read_int_le(length)
|
144
|
+
|
145
|
+
Instructions::Long4.new(length,long)
|
146
|
+
#
|
147
|
+
# Protocol 3 instructions
|
148
|
+
#
|
149
|
+
when 66 # BINBYTES
|
150
|
+
length = read_uint32_le
|
151
|
+
bytes = @io.read(length)
|
152
|
+
|
153
|
+
Instructions::BinBytes.new(length,bytes)
|
154
|
+
when 67 # SHORT_BINBYTES
|
155
|
+
length = read_uint8
|
156
|
+
bytes = @io.read(length)
|
157
|
+
|
158
|
+
Instructions::ShortBinBytes.new(length,bytes)
|
159
|
+
#
|
160
|
+
# Protocol 4 instructions
|
161
|
+
#
|
162
|
+
when 140 # SHORT_BINUNICODE
|
163
|
+
length = read_uint8
|
164
|
+
string = read_utf8_string(length)
|
165
|
+
|
166
|
+
Instructions::ShortBinUnicode.new(length,string)
|
167
|
+
when 141 # BINUNICODE8
|
168
|
+
length = read_uint64_le
|
169
|
+
string = read_utf8_string(length)
|
170
|
+
|
171
|
+
Instructions::BinUnicode8.new(length,string)
|
172
|
+
when 142 # BINBYTES8
|
173
|
+
length = read_uint64_le
|
174
|
+
bytes = @io.read(length)
|
175
|
+
|
176
|
+
Instructions::BinBytes8.new(length,bytes)
|
177
|
+
when 143 # EMPTY_SET
|
178
|
+
Instructions::EMPTY_SET
|
179
|
+
when 144 # ADDITEMS
|
180
|
+
Instructions::ADDITEMS
|
181
|
+
when 145 # FROZENSET
|
182
|
+
Instructions::FROZENSET
|
183
|
+
when 146 # NEWOBJ_EX
|
184
|
+
Instructions::NEWOBJ_EX
|
185
|
+
when 147 # STACK_GLOBAL
|
186
|
+
Instructions::STACK_GLOBAL
|
187
|
+
when 148 # MEMOIZE
|
188
|
+
Instructions::MEMOIZE
|
189
|
+
when 149 # FRAME
|
190
|
+
length = read_uint64_le
|
191
|
+
|
192
|
+
enter_frame(read_frame(length))
|
193
|
+
|
194
|
+
Instructions::Frame.new(length)
|
195
|
+
#
|
196
|
+
# Protocol 5 instructions.
|
197
|
+
#
|
198
|
+
when 150 # BYTEARRAY8
|
199
|
+
length = read_uint64_le
|
200
|
+
bytes = @io.read(length)
|
201
|
+
|
202
|
+
Instructions::ByteArray8.new(length,bytes)
|
203
|
+
when 151 # NEXT_BUFFER
|
204
|
+
Instructions::NEXT_BUFFER
|
205
|
+
when 152 # READONLY_BUFFER
|
206
|
+
Instructions::READONLY_BUFFER
|
207
|
+
else
|
208
|
+
raise(InvalidFormat,"invalid opcode (#{opcode.inspect}) for protocol 5")
|
209
|
+
end
|
210
|
+
ensure
|
211
|
+
if @io.eof? && !@io_stack.empty?
|
212
|
+
leave_frame
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'python/pickle/py_object'
|
2
|
+
|
3
|
+
module Python
|
4
|
+
module Pickle
|
5
|
+
#
|
6
|
+
# Represents a Python class.
|
7
|
+
#
|
8
|
+
class PyClass
|
9
|
+
|
10
|
+
# The namespace the Python class is defined within.
|
11
|
+
#
|
12
|
+
# @return [String]
|
13
|
+
attr_reader :namespace
|
14
|
+
|
15
|
+
# The name of the Python class.
|
16
|
+
#
|
17
|
+
# @return [String]
|
18
|
+
attr_reader :name
|
19
|
+
|
20
|
+
#
|
21
|
+
# Initializes the Python class.
|
22
|
+
#
|
23
|
+
# @param [String, nil] namespace
|
24
|
+
# The namespace of the Python class.
|
25
|
+
#
|
26
|
+
# @param [String] name
|
27
|
+
# The name of the Python class.
|
28
|
+
#
|
29
|
+
# @api private
|
30
|
+
#
|
31
|
+
def initialize(namespace=nil,name)
|
32
|
+
@namespace = namespace
|
33
|
+
@name = name
|
34
|
+
end
|
35
|
+
|
36
|
+
#
|
37
|
+
# Initializes a new Python object from the Python class.
|
38
|
+
#
|
39
|
+
# @param [Array] args
|
40
|
+
# Additional `__init__` arguments.
|
41
|
+
#
|
42
|
+
# @param [Hash{Symbol => Object}] kwargs
|
43
|
+
# Additional `__init__` keyword arguments.
|
44
|
+
#
|
45
|
+
# @api private
|
46
|
+
#
|
47
|
+
def new(*args,**kwargs)
|
48
|
+
PyObject.new(self,*args,**kwargs)
|
49
|
+
end
|
50
|
+
|
51
|
+
#
|
52
|
+
# Converts the Python class into a String.
|
53
|
+
#
|
54
|
+
# @return [String]
|
55
|
+
#
|
56
|
+
def to_s
|
57
|
+
if @namespace
|
58
|
+
"#{@namespace}.#{@name}"
|
59
|
+
else
|
60
|
+
@name.to_s
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
#
|
65
|
+
# Inspects the Python object.
|
66
|
+
#
|
67
|
+
# @return [String]
|
68
|
+
#
|
69
|
+
def inspect
|
70
|
+
"#<#{self.class}: #{self}>"
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
require 'python/pickle/py_class'
|
2
|
+
|
3
|
+
module Python
|
4
|
+
module Pickle
|
5
|
+
#
|
6
|
+
# Represents a Python object.
|
7
|
+
#
|
8
|
+
class PyObject
|
9
|
+
|
10
|
+
# The Python class of the Python object.
|
11
|
+
#
|
12
|
+
# @return [PyClass]
|
13
|
+
attr_reader :py_class
|
14
|
+
|
15
|
+
# The arguments used to initialize the Python object.
|
16
|
+
#
|
17
|
+
# @return [Array]
|
18
|
+
attr_reader :init_args
|
19
|
+
|
20
|
+
# The keyword arguments used to initialize the Python object.
|
21
|
+
#
|
22
|
+
# @return [Array]
|
23
|
+
attr_reader :init_kwargs
|
24
|
+
|
25
|
+
# The populated attributes of the Python object.
|
26
|
+
#
|
27
|
+
# @return [Hash{String => Object}]
|
28
|
+
attr_reader :attributes
|
29
|
+
|
30
|
+
#
|
31
|
+
# Initializes the Python object.
|
32
|
+
#
|
33
|
+
# @param [PyClass] py_class
|
34
|
+
# The Python class of the Python object.
|
35
|
+
#
|
36
|
+
# @param [Array] args
|
37
|
+
# Additional arguments used to initialize the Python object.
|
38
|
+
#
|
39
|
+
# @param [Hash{Symbol => Object}] kwargs
|
40
|
+
# Additional keyword arguments used to initialize the Python object.
|
41
|
+
#
|
42
|
+
# @api private
|
43
|
+
#
|
44
|
+
def initialize(py_class,*args,**kwargs)
|
45
|
+
@py_class = py_class
|
46
|
+
@init_args = args
|
47
|
+
@init_kwargs = kwargs
|
48
|
+
|
49
|
+
@attributes = {}
|
50
|
+
end
|
51
|
+
|
52
|
+
#
|
53
|
+
# Fetches the attribute of the Python object.
|
54
|
+
#
|
55
|
+
# @param [String] attribute
|
56
|
+
# The attribute name.
|
57
|
+
#
|
58
|
+
# @return [Object]
|
59
|
+
# The attributes value.
|
60
|
+
#
|
61
|
+
# @raise [ArgumentError]
|
62
|
+
# The Python object does not have an attribute of the given name.
|
63
|
+
#
|
64
|
+
# @example
|
65
|
+
# obj.getattr('x')
|
66
|
+
# # => 2
|
67
|
+
#
|
68
|
+
def getattr(attribute)
|
69
|
+
@attributes.fetch(attribute) do
|
70
|
+
raise(ArgumentError,"Python object has no attribute #{attribute.inspect}: #{self.inspect}")
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
#
|
75
|
+
# Sets an attribute in the Python object.
|
76
|
+
#
|
77
|
+
# @param [String] name
|
78
|
+
# The attribute name.
|
79
|
+
#
|
80
|
+
# @param [Object] value
|
81
|
+
# The new value for the attribute.
|
82
|
+
#
|
83
|
+
# @example
|
84
|
+
# obj.setattr('x',2)
|
85
|
+
# # => 2
|
86
|
+
#
|
87
|
+
def setattr(name,value)
|
88
|
+
@attributes[name] = value
|
89
|
+
end
|
90
|
+
|
91
|
+
#
|
92
|
+
# Sets the state of the Python object.
|
93
|
+
#
|
94
|
+
# @api private
|
95
|
+
#
|
96
|
+
def __setstate__(new_attributes)
|
97
|
+
@attributes = new_attributes
|
98
|
+
end
|
99
|
+
|
100
|
+
#
|
101
|
+
# Converts the Python object to a Hash.
|
102
|
+
#
|
103
|
+
# @return [Hash]
|
104
|
+
#
|
105
|
+
def to_h
|
106
|
+
@attributes
|
107
|
+
end
|
108
|
+
|
109
|
+
protected
|
110
|
+
|
111
|
+
#
|
112
|
+
# Allows for direct access to attributes.
|
113
|
+
#
|
114
|
+
# @example
|
115
|
+
# obj.x = 2
|
116
|
+
# obj.x
|
117
|
+
# # => 2
|
118
|
+
#
|
119
|
+
def method_missing(method_name,*arguments,&block)
|
120
|
+
if method_name.end_with?('=')
|
121
|
+
attr = method_name[0..-2]
|
122
|
+
|
123
|
+
if arguments.length == 1 && !block
|
124
|
+
return @attributes[attr] = arguments[0]
|
125
|
+
else
|
126
|
+
super(method_name,*arguments,&block)
|
127
|
+
end
|
128
|
+
else
|
129
|
+
attr = method_name.to_s
|
130
|
+
|
131
|
+
if @attributes.has_key?(attr) && arguments.empty? && !block
|
132
|
+
return @attributes[attr]
|
133
|
+
else
|
134
|
+
super(method_name,*arguments,&block)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,226 @@
|
|
1
|
+
require 'python/pickle/protocol0'
|
2
|
+
require 'python/pickle/protocol1'
|
3
|
+
require 'python/pickle/protocol2'
|
4
|
+
require 'python/pickle/protocol3'
|
5
|
+
require 'python/pickle/protocol4'
|
6
|
+
require 'python/pickle/protocol5'
|
7
|
+
require 'python/pickle/exceptions'
|
8
|
+
require 'python/pickle/deserializer'
|
9
|
+
require 'stringio'
|
10
|
+
|
11
|
+
module Python
|
12
|
+
#
|
13
|
+
# A modern Ruby implementation of the Python Pickle serialization format.
|
14
|
+
#
|
15
|
+
module Pickle
|
16
|
+
# Mapping of protocol versions to protocol parsers.
|
17
|
+
#
|
18
|
+
# @api private
|
19
|
+
PROTOCOL_VERSIONS = {
|
20
|
+
0 => Protocol0,
|
21
|
+
1 => Protocol1,
|
22
|
+
2 => Protocol2,
|
23
|
+
3 => Protocol3,
|
24
|
+
4 => Protocol4,
|
25
|
+
5 => Protocol5
|
26
|
+
}
|
27
|
+
|
28
|
+
# The default protocol version to use.
|
29
|
+
#
|
30
|
+
# @api public
|
31
|
+
DEFAULT_PROTCOL = 4
|
32
|
+
|
33
|
+
# The highest protocol version supported.
|
34
|
+
#
|
35
|
+
# @api public
|
36
|
+
HIGHEST_PROTOCOL = 5
|
37
|
+
|
38
|
+
#
|
39
|
+
# Parses a Python pickle stream.
|
40
|
+
#
|
41
|
+
# @param [String, IO] data
|
42
|
+
# The Python pickle stream to parse.
|
43
|
+
#
|
44
|
+
# @param [Integer, nil] protocol
|
45
|
+
# The explicit protocol version to use. If `nil` the protocol version will
|
46
|
+
# be inferred by inspecting the first two bytes of the stream.
|
47
|
+
#
|
48
|
+
# @yield [instruction]
|
49
|
+
# If a block is given, it will be passed each parsed Pickle instruction.
|
50
|
+
#
|
51
|
+
# @yieldparam [Instruction] instruction
|
52
|
+
# A parsed Pickle instruction from the Pickle stream.
|
53
|
+
#
|
54
|
+
# @return [Array<Instruction>]
|
55
|
+
# All parsed Pickle instructions from the Pickle stream.
|
56
|
+
#
|
57
|
+
# @api public
|
58
|
+
#
|
59
|
+
def self.parse(data, protocol: nil, &block)
|
60
|
+
io = case data
|
61
|
+
when String then StringIO.new(data)
|
62
|
+
when IO then data
|
63
|
+
else
|
64
|
+
raise(ArgumentError,"argument must be either an IO object or a String: #{io.inspect}")
|
65
|
+
end
|
66
|
+
|
67
|
+
if protocol
|
68
|
+
if (protocol < 0) || (protocol > HIGHEST_PROTOCOL)
|
69
|
+
raise(ArgumentError,"protocol must be between 0 or #{HIGHEST_PROTOCOL}, but was #{protocol.inspect}")
|
70
|
+
end
|
71
|
+
else
|
72
|
+
protocol = infer_protocol_version(io)
|
73
|
+
end
|
74
|
+
|
75
|
+
protocol_class = PROTOCOL_VERSIONS.fetch(protocol)
|
76
|
+
protocol = protocol_class.new(io)
|
77
|
+
|
78
|
+
return protocol.read(&block)
|
79
|
+
end
|
80
|
+
|
81
|
+
#
|
82
|
+
# Deserializes the Python Pickle stream into a Ruby object.
|
83
|
+
#
|
84
|
+
# @param [String, IO] data
|
85
|
+
# The Python pickle stream to parse.
|
86
|
+
#
|
87
|
+
# @param [Integer, nil] protocol
|
88
|
+
# The explicit protocol version to use. If `nil` the protocol version will
|
89
|
+
# be inferred by inspecting the first two bytes of the stream.
|
90
|
+
#
|
91
|
+
# @api public
|
92
|
+
#
|
93
|
+
def self.load(data,**kwargs)
|
94
|
+
deserializer = Deserializer.new(**kwargs)
|
95
|
+
|
96
|
+
parse(data) do |instruction|
|
97
|
+
status, object = deserializer.execute(instruction)
|
98
|
+
|
99
|
+
if status == :halt
|
100
|
+
return object
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
raise(DeserializationError,"failed to deserialize any object data from stream")
|
105
|
+
end
|
106
|
+
|
107
|
+
#
|
108
|
+
# Deserializes a Python Pickle file.
|
109
|
+
#
|
110
|
+
# @param [String] path
|
111
|
+
# The path of the file.
|
112
|
+
#
|
113
|
+
# @return [Object]
|
114
|
+
# The deserialized object.
|
115
|
+
#
|
116
|
+
def self.load_file(path,**kwargs)
|
117
|
+
load(File.open(path,'rb'),**kwargs)
|
118
|
+
end
|
119
|
+
|
120
|
+
#
|
121
|
+
# Serializes the Ruby object into Python Pickle data.
|
122
|
+
#
|
123
|
+
# @param [Object] object
|
124
|
+
# The Ruby object to serialize.
|
125
|
+
#
|
126
|
+
# @param [IO] output
|
127
|
+
# The option output to write the Pickle data to.
|
128
|
+
#
|
129
|
+
# @param [Integer] protocol
|
130
|
+
# The desired Python Pickle protocol to use.
|
131
|
+
#
|
132
|
+
# @api public
|
133
|
+
#
|
134
|
+
def self.dump(object,output=nil, protocol: DEFAULT_PROTOCOL)
|
135
|
+
if (protocol < 0) || (protocol > HIGHEST_PROTOCOL)
|
136
|
+
raise(ArgumentError,"protocol must be between 0 or #{HIGHEST_PROTOCOL}, but was #{protocol.inspect}")
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
#
|
141
|
+
# Infers the protocol version from the IO stream.
|
142
|
+
#
|
143
|
+
# @param [IO] io
|
144
|
+
# The IO stream to inspect.
|
145
|
+
#
|
146
|
+
# @return [Integer]
|
147
|
+
# The inferred Python Pickle protocol version.
|
148
|
+
#
|
149
|
+
# @raise [InvalidFormat]
|
150
|
+
# Could not determine the Pickle version from the first two bytes of the
|
151
|
+
# IO stream.
|
152
|
+
#
|
153
|
+
# @api private
|
154
|
+
#
|
155
|
+
def self.infer_protocol_version(io)
|
156
|
+
opcode = io.getbyte
|
157
|
+
|
158
|
+
begin
|
159
|
+
case opcode
|
160
|
+
when 0x80 # PROTO (added in protocol 2)
|
161
|
+
version = io.getbyte
|
162
|
+
io.ungetbyte(version)
|
163
|
+
return version
|
164
|
+
when 48, # POP (protocol 0)
|
165
|
+
50, # DUP (protocol 0)
|
166
|
+
70, # FLOAT (protocol 0)
|
167
|
+
83, # STRING (protocol 0)
|
168
|
+
86, # UNICODE (protocol 0)
|
169
|
+
100, # DICT (protocol 0)
|
170
|
+
103, # GET (protocol 0)
|
171
|
+
108, # LIST (protocol 0)
|
172
|
+
112 # PUT (protocol 0)
|
173
|
+
0
|
174
|
+
when 41, # EMPTY_TUPLE (protocol 1)
|
175
|
+
71, # BINFLOAT (protocol 1)
|
176
|
+
75, # BININT1 (protocol 1)
|
177
|
+
84, # BINSTRING (protocol 1)
|
178
|
+
85, # SHORT_BINSTRING (protocol 1)
|
179
|
+
88, # BINUNICODE (protocol 1)
|
180
|
+
93, # EMPTY_LIST (protocol 1)
|
181
|
+
101, # APPENDS (protocol 1)
|
182
|
+
113, # BINPUT (protocol 1)
|
183
|
+
117, # SETITEMS (protocol 1)
|
184
|
+
125 # EMPTY_DICT (protocol 1)
|
185
|
+
1
|
186
|
+
when 46 # STOP
|
187
|
+
# if we've read all the way to the end of the stream and still cannot
|
188
|
+
# find any protocol 0 or protocol 1 specific opcodes, assume protocol 0
|
189
|
+
0
|
190
|
+
when 73, # INT (identical in both protocol 0 and 1)
|
191
|
+
76 # LONG (identical in both protocol 0 and 1)
|
192
|
+
chars = io.gets
|
193
|
+
|
194
|
+
begin
|
195
|
+
infer_protocol_version(io)
|
196
|
+
ensure
|
197
|
+
chars.each_byte.reverse_each { |b| io.ungetbyte(b) }
|
198
|
+
end
|
199
|
+
when 40, # MARK (identical in both protocol 0 and 1)
|
200
|
+
78, # NONE (identical in both protocol 0 and 1)
|
201
|
+
82, # REDUCE (identical in both protocol 0 and 1)
|
202
|
+
97, # APPEND (identical in both protocol 0 and 1)
|
203
|
+
98, # BUILD (identical in both protocol 0 and 1)
|
204
|
+
115, # SETITEM (identical in both protocol 0 and 1)
|
205
|
+
116 # TUPLE (identical in both protocol 0 and 1)
|
206
|
+
infer_protocol_version(io)
|
207
|
+
when 99 # GLOBAL
|
208
|
+
first_nl_string = io.gets
|
209
|
+
second_nl_string = io.gets
|
210
|
+
|
211
|
+
begin
|
212
|
+
infer_protocol_version(io)
|
213
|
+
ensure
|
214
|
+
# push the read bytes back into the IO stream
|
215
|
+
second_nl_string.each_byte.reverse_each { |b| io.ungetbyte(b) }
|
216
|
+
first_nl_string.each_byte.reverse_each { |b| io.ungetbyte(b) }
|
217
|
+
end
|
218
|
+
else
|
219
|
+
raise(InvalidFormat,"cannot infer protocol version from opcode (#{opcode.inspect}) at position #{io.pos}")
|
220
|
+
end
|
221
|
+
ensure
|
222
|
+
io.ungetbyte(opcode)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|