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.
Files changed (235) hide show
  1. checksums.yaml +7 -0
  2. data/.document +3 -0
  3. data/.github/workflows/ruby.yml +27 -0
  4. data/.gitignore +5 -0
  5. data/.rspec +1 -0
  6. data/.yardopts +1 -0
  7. data/ChangeLog.md +14 -0
  8. data/Gemfile +15 -0
  9. data/LICENSE.txt +20 -0
  10. data/README.md +149 -0
  11. data/Rakefile +13 -0
  12. data/gemspec.yml +25 -0
  13. data/lib/python/pickle/byte_array.rb +40 -0
  14. data/lib/python/pickle/deserializer.rb +595 -0
  15. data/lib/python/pickle/exceptions.rb +12 -0
  16. data/lib/python/pickle/instruction.rb +52 -0
  17. data/lib/python/pickle/instructions/add_items.rb +26 -0
  18. data/lib/python/pickle/instructions/append.rb +24 -0
  19. data/lib/python/pickle/instructions/appends.rb +26 -0
  20. data/lib/python/pickle/instructions/bin_bytes.rb +32 -0
  21. data/lib/python/pickle/instructions/bin_bytes8.rb +32 -0
  22. data/lib/python/pickle/instructions/bin_float.rb +29 -0
  23. data/lib/python/pickle/instructions/bin_get.rb +27 -0
  24. data/lib/python/pickle/instructions/bin_int1.rb +29 -0
  25. data/lib/python/pickle/instructions/bin_put.rb +29 -0
  26. data/lib/python/pickle/instructions/bin_string.rb +32 -0
  27. data/lib/python/pickle/instructions/bin_unicode.rb +32 -0
  28. data/lib/python/pickle/instructions/bin_unicode8.rb +32 -0
  29. data/lib/python/pickle/instructions/build.rb +24 -0
  30. data/lib/python/pickle/instructions/byte_array8.rb +32 -0
  31. data/lib/python/pickle/instructions/dict.rb +17 -0
  32. data/lib/python/pickle/instructions/dup.rb +24 -0
  33. data/lib/python/pickle/instructions/empty_dict.rb +26 -0
  34. data/lib/python/pickle/instructions/empty_list.rb +26 -0
  35. data/lib/python/pickle/instructions/empty_set.rb +26 -0
  36. data/lib/python/pickle/instructions/empty_tuple.rb +26 -0
  37. data/lib/python/pickle/instructions/ext1.rb +29 -0
  38. data/lib/python/pickle/instructions/ext2.rb +29 -0
  39. data/lib/python/pickle/instructions/ext4.rb +29 -0
  40. data/lib/python/pickle/instructions/float.rb +24 -0
  41. data/lib/python/pickle/instructions/frame.rb +29 -0
  42. data/lib/python/pickle/instructions/frozen_set.rb +26 -0
  43. data/lib/python/pickle/instructions/get.rb +27 -0
  44. data/lib/python/pickle/instructions/global.rb +62 -0
  45. data/lib/python/pickle/instructions/has_length_and_value.rb +58 -0
  46. data/lib/python/pickle/instructions/has_value.rb +50 -0
  47. data/lib/python/pickle/instructions/int.rb +24 -0
  48. data/lib/python/pickle/instructions/list.rb +24 -0
  49. data/lib/python/pickle/instructions/long.rb +24 -0
  50. data/lib/python/pickle/instructions/long1.rb +32 -0
  51. data/lib/python/pickle/instructions/long4.rb +32 -0
  52. data/lib/python/pickle/instructions/long_bin_get.rb +27 -0
  53. data/lib/python/pickle/instructions/mark.rb +24 -0
  54. data/lib/python/pickle/instructions/memoize.rb +26 -0
  55. data/lib/python/pickle/instructions/new_false.rb +24 -0
  56. data/lib/python/pickle/instructions/new_obj.rb +26 -0
  57. data/lib/python/pickle/instructions/new_obj_ex.rb +26 -0
  58. data/lib/python/pickle/instructions/new_true.rb +24 -0
  59. data/lib/python/pickle/instructions/next_buffer.rb +26 -0
  60. data/lib/python/pickle/instructions/none.rb +24 -0
  61. data/lib/python/pickle/instructions/pop.rb +24 -0
  62. data/lib/python/pickle/instructions/pop_mark.rb +24 -0
  63. data/lib/python/pickle/instructions/proto.rb +29 -0
  64. data/lib/python/pickle/instructions/put.rb +24 -0
  65. data/lib/python/pickle/instructions/readonly_buffer.rb +26 -0
  66. data/lib/python/pickle/instructions/reduce.rb +24 -0
  67. data/lib/python/pickle/instructions/set_item.rb +24 -0
  68. data/lib/python/pickle/instructions/set_items.rb +26 -0
  69. data/lib/python/pickle/instructions/short_bin_bytes.rb +32 -0
  70. data/lib/python/pickle/instructions/short_bin_string.rb +32 -0
  71. data/lib/python/pickle/instructions/short_bin_unicode.rb +32 -0
  72. data/lib/python/pickle/instructions/stack_global.rb +26 -0
  73. data/lib/python/pickle/instructions/stop.rb +24 -0
  74. data/lib/python/pickle/instructions/string.rb +24 -0
  75. data/lib/python/pickle/instructions/tuple.rb +24 -0
  76. data/lib/python/pickle/instructions/tuple1.rb +24 -0
  77. data/lib/python/pickle/instructions/tuple2.rb +24 -0
  78. data/lib/python/pickle/instructions/tuple3.rb +24 -0
  79. data/lib/python/pickle/protocol.rb +56 -0
  80. data/lib/python/pickle/protocol0.rb +399 -0
  81. data/lib/python/pickle/protocol1.rb +183 -0
  82. data/lib/python/pickle/protocol2.rb +229 -0
  83. data/lib/python/pickle/protocol3.rb +163 -0
  84. data/lib/python/pickle/protocol4.rb +285 -0
  85. data/lib/python/pickle/protocol5.rb +218 -0
  86. data/lib/python/pickle/py_class.rb +75 -0
  87. data/lib/python/pickle/py_object.rb +141 -0
  88. data/lib/python/pickle/tuple.rb +19 -0
  89. data/lib/python/pickle/version.rb +6 -0
  90. data/lib/python/pickle.rb +226 -0
  91. data/python-pickle.gemspec +62 -0
  92. data/spec/byte_array_spec.rb +54 -0
  93. data/spec/deserializer_spec.rb +1201 -0
  94. data/spec/fixtures/ascii_str_v3.pkl +0 -0
  95. data/spec/fixtures/ascii_str_v4.pkl +0 -0
  96. data/spec/fixtures/ascii_str_v5.pkl +0 -0
  97. data/spec/fixtures/bin_str_v0.pkl +3 -0
  98. data/spec/fixtures/bin_str_v1.pkl +0 -0
  99. data/spec/fixtures/bin_str_v2.pkl +0 -0
  100. data/spec/fixtures/bin_str_v3.pkl +0 -0
  101. data/spec/fixtures/bin_str_v4.pkl +0 -0
  102. data/spec/fixtures/bin_str_v5.pkl +0 -0
  103. data/spec/fixtures/bytearray_v0.pkl +10 -0
  104. data/spec/fixtures/bytearray_v1.pkl +0 -0
  105. data/spec/fixtures/bytearray_v2.pkl +0 -0
  106. data/spec/fixtures/bytearray_v3.pkl +0 -0
  107. data/spec/fixtures/bytearray_v4.pkl +0 -0
  108. data/spec/fixtures/bytearray_v5.pkl +0 -0
  109. data/spec/fixtures/class_v0.pkl +4 -0
  110. data/spec/fixtures/class_v1.pkl +0 -0
  111. data/spec/fixtures/class_v2.pkl +0 -0
  112. data/spec/fixtures/class_v3.pkl +0 -0
  113. data/spec/fixtures/class_v4.pkl +0 -0
  114. data/spec/fixtures/class_v5.pkl +0 -0
  115. data/spec/fixtures/dict_v0.pkl +6 -0
  116. data/spec/fixtures/dict_v1.pkl +0 -0
  117. data/spec/fixtures/dict_v2.pkl +0 -0
  118. data/spec/fixtures/dict_v3.pkl +0 -0
  119. data/spec/fixtures/dict_v4.pkl +0 -0
  120. data/spec/fixtures/dict_v5.pkl +0 -0
  121. data/spec/fixtures/escaped_str_v0.pkl +3 -0
  122. data/spec/fixtures/escaped_str_v1.pkl +0 -0
  123. data/spec/fixtures/escaped_str_v2.pkl +0 -0
  124. data/spec/fixtures/false_v0.pkl +2 -0
  125. data/spec/fixtures/false_v1.pkl +2 -0
  126. data/spec/fixtures/false_v2.pkl +1 -0
  127. data/spec/fixtures/false_v3.pkl +1 -0
  128. data/spec/fixtures/false_v4.pkl +1 -0
  129. data/spec/fixtures/false_v5.pkl +1 -0
  130. data/spec/fixtures/float_v0.pkl +2 -0
  131. data/spec/fixtures/float_v1.pkl +1 -0
  132. data/spec/fixtures/float_v2.pkl +1 -0
  133. data/spec/fixtures/float_v3.pkl +1 -0
  134. data/spec/fixtures/float_v4.pkl +0 -0
  135. data/spec/fixtures/float_v5.pkl +0 -0
  136. data/spec/fixtures/function_v0.pkl +4 -0
  137. data/spec/fixtures/function_v1.pkl +0 -0
  138. data/spec/fixtures/function_v2.pkl +0 -0
  139. data/spec/fixtures/function_v3.pkl +0 -0
  140. data/spec/fixtures/function_v4.pkl +0 -0
  141. data/spec/fixtures/function_v5.pkl +0 -0
  142. data/spec/fixtures/hex_str_v0.pkl +3 -0
  143. data/spec/fixtures/hex_str_v1.pkl +0 -0
  144. data/spec/fixtures/hex_str_v2.pkl +0 -0
  145. data/spec/fixtures/int_v0.pkl +2 -0
  146. data/spec/fixtures/int_v1.pkl +1 -0
  147. data/spec/fixtures/int_v2.pkl +1 -0
  148. data/spec/fixtures/int_v3.pkl +1 -0
  149. data/spec/fixtures/int_v4.pkl +1 -0
  150. data/spec/fixtures/int_v5.pkl +1 -0
  151. data/spec/fixtures/list_v0.pkl +7 -0
  152. data/spec/fixtures/list_v1.pkl +0 -0
  153. data/spec/fixtures/list_v2.pkl +0 -0
  154. data/spec/fixtures/list_v3.pkl +0 -0
  155. data/spec/fixtures/list_v4.pkl +0 -0
  156. data/spec/fixtures/list_v5.pkl +0 -0
  157. data/spec/fixtures/long_v0.pkl +2 -0
  158. data/spec/fixtures/long_v1.pkl +2 -0
  159. data/spec/fixtures/long_v2.pkl +0 -0
  160. data/spec/fixtures/long_v3.pkl +0 -0
  161. data/spec/fixtures/long_v4.pkl +0 -0
  162. data/spec/fixtures/long_v5.pkl +0 -0
  163. data/spec/fixtures/nested_dict_v0.pkl +12 -0
  164. data/spec/fixtures/nested_dict_v1.pkl +0 -0
  165. data/spec/fixtures/nested_dict_v2.pkl +0 -0
  166. data/spec/fixtures/nested_dict_v3.pkl +0 -0
  167. data/spec/fixtures/nested_dict_v4.pkl +0 -0
  168. data/spec/fixtures/nested_dict_v5.pkl +0 -0
  169. data/spec/fixtures/nested_list_v0.pkl +9 -0
  170. data/spec/fixtures/nested_list_v1.pkl +0 -0
  171. data/spec/fixtures/nested_list_v2.pkl +0 -0
  172. data/spec/fixtures/nested_list_v3.pkl +0 -0
  173. data/spec/fixtures/nested_list_v4.pkl +0 -0
  174. data/spec/fixtures/nested_list_v5.pkl +0 -0
  175. data/spec/fixtures/none_v0.pkl +1 -0
  176. data/spec/fixtures/none_v1.pkl +1 -0
  177. data/spec/fixtures/none_v2.pkl +1 -0
  178. data/spec/fixtures/none_v3.pkl +1 -0
  179. data/spec/fixtures/none_v4.pkl +1 -0
  180. data/spec/fixtures/none_v5.pkl +1 -0
  181. data/spec/fixtures/object_v0.pkl +19 -0
  182. data/spec/fixtures/object_v1.pkl +0 -0
  183. data/spec/fixtures/object_v2.pkl +0 -0
  184. data/spec/fixtures/object_v3.pkl +0 -0
  185. data/spec/fixtures/object_v4.pkl +0 -0
  186. data/spec/fixtures/object_v5.pkl +0 -0
  187. data/spec/fixtures/str_v0.pkl +3 -0
  188. data/spec/fixtures/str_v1.pkl +0 -0
  189. data/spec/fixtures/str_v2.pkl +0 -0
  190. data/spec/fixtures/str_v3.pkl +0 -0
  191. data/spec/fixtures/str_v4.pkl +0 -0
  192. data/spec/fixtures/str_v5.pkl +0 -0
  193. data/spec/fixtures/true_v0.pkl +2 -0
  194. data/spec/fixtures/true_v1.pkl +2 -0
  195. data/spec/fixtures/true_v2.pkl +1 -0
  196. data/spec/fixtures/true_v3.pkl +1 -0
  197. data/spec/fixtures/true_v4.pkl +1 -0
  198. data/spec/fixtures/true_v5.pkl +1 -0
  199. data/spec/fixtures/unicode_str_v0.pkl +3 -0
  200. data/spec/fixtures/unicode_str_v1.pkl +0 -0
  201. data/spec/fixtures/unicode_str_v2.pkl +0 -0
  202. data/spec/fixtures/unicode_str_v3.pkl +0 -0
  203. data/spec/fixtures/unicode_str_v4.pkl +0 -0
  204. data/spec/fixtures/unicode_str_v5.pkl +0 -0
  205. data/spec/generate_pickles2.py +41 -0
  206. data/spec/generate_pickles3.py +40 -0
  207. data/spec/integration/load/protocol0_spec.rb +258 -0
  208. data/spec/integration/load/protocol1_spec.rb +258 -0
  209. data/spec/integration/load/protocol2_spec.rb +258 -0
  210. data/spec/integration/load/protocol3_spec.rb +258 -0
  211. data/spec/integration/load/protocol4_spec.rb +258 -0
  212. data/spec/integration/load/protocol5_spec.rb +258 -0
  213. data/spec/integration/parse/protocol0_spec.rb +467 -0
  214. data/spec/integration/parse/protocol1_spec.rb +459 -0
  215. data/spec/integration/parse/protocol2_spec.rb +471 -0
  216. data/spec/integration/parse/protocol3_spec.rb +407 -0
  217. data/spec/integration/parse/protocol4_spec.rb +439 -0
  218. data/spec/integration/parse/protocol5_spec.rb +419 -0
  219. data/spec/pickle_spec.rb +163 -0
  220. data/spec/protocol0_read_instruction_examples.rb +211 -0
  221. data/spec/protocol0_spec.rb +445 -0
  222. data/spec/protocol1_read_instruction_examples.rb +156 -0
  223. data/spec/protocol1_spec.rb +59 -0
  224. data/spec/protocol2_read_instruction_examples.rb +135 -0
  225. data/spec/protocol2_spec.rb +128 -0
  226. data/spec/protocol3_read_instruction_examples.rb +29 -0
  227. data/spec/protocol3_spec.rb +32 -0
  228. data/spec/protocol4_read_instruction_examples.rb +142 -0
  229. data/spec/protocol4_spec.rb +58 -0
  230. data/spec/protocol5_spec.rb +68 -0
  231. data/spec/py_class_spec.rb +62 -0
  232. data/spec/py_object_spec.rb +149 -0
  233. data/spec/spec_helper.rb +3 -0
  234. data/spec/tuple_spec.rb +18 -0
  235. 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,19 @@
1
+ module Python
2
+ module Pickle
3
+ #
4
+ # Represents a Python `tuple` object.
5
+ #
6
+ class Tuple < Array
7
+
8
+ #
9
+ # Inspects the tuple.
10
+ #
11
+ # @return [String]
12
+ #
13
+ def inspect
14
+ "#<#{self.class}: #{super}>"
15
+ end
16
+
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,6 @@
1
+ module Python
2
+ module Pickle
3
+ # python-pickle version
4
+ VERSION = '0.1.0'
5
+ end
6
+ 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