python-pickle 0.1.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.
- 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
|