combine_pdf 0.2.5 → 0.2.37

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.
@@ -5,258 +5,268 @@
5
5
  ## is subject to the same license.
6
6
  ########################################################
7
7
 
8
+ module CombinePDF
9
+ #:nodoc: all
8
10
 
11
+ protected
9
12
 
10
- module CombinePDF
11
- #:nodoc: all
13
+ # @private
14
+ # @!visibility private
12
15
 
13
- protected
14
-
15
- # @private
16
- # @!visibility private
16
+ # This is an internal class. you don't need it.
17
+ class PDFDecrypt
18
+ # include CombinePDF::Renderer
17
19
 
18
- # This is an internal class. you don't need it.
19
- class PDFDecrypt
20
- include CombinePDF::Renderer
20
+ # @!visibility private
21
21
 
22
- # @!visibility private
23
-
24
- # make a new Decrypt object. requires:
25
- # objects:: an array containing the encrypted objects.
26
- # root_dictionary:: the root PDF dictionary, containing the Encrypt dictionary.
27
- def initialize objects=[], root_dictionary = {}
28
- @objects = objects
29
- @encryption_dictionary = actual_object(root_dictionary[:Encrypt])
30
- raise "Cannot decrypt an encrypted file without an encryption dictionary!" unless @encryption_dictionary
31
- @root_dictionary = actual_object(root_dictionary)
32
- @padding_key = [ 0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41,
33
- 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08,
34
- 0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80,
35
- 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A ]
36
- @key_crypt_first_iv_store = nil
37
- @encryption_iv = nil
38
- change_references_to_actual_values @encryption_dictionary
39
- end
22
+ # make a new Decrypt object. requires:
23
+ # objects:: an array containing the encrypted objects.
24
+ # root_dictionary:: the root PDF dictionary, containing the Encrypt dictionary.
25
+ def initialize(objects = [], root_dictionary = {})
26
+ @objects = objects
27
+ @encryption_dictionary = actual_object(root_dictionary[:Encrypt])
28
+ raise EncryptionError, 'Cannot decrypt an encrypted file without an encryption dictionary!' unless @encryption_dictionary
29
+ @root_dictionary = actual_object(root_dictionary)
30
+ @padding_key = [0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41,
31
+ 0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08,
32
+ 0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80,
33
+ 0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A]
40
34
 
41
- # call this to start the decryption.
42
- def decrypt
43
- raise_encrypted_error @encryption_dictionary unless @encryption_dictionary[:Filter] == :Standard
44
- @key = set_general_key
45
- case actual_object(@encryption_dictionary[:V])
46
- when 1,2
47
- # raise_encrypted_error
48
- _perform_decrypt_proc_ @objects, self.method(:decrypt_RC4)
49
- when 4
50
- # raise unsupported error for now
51
- raise_encrypted_error
52
- # make sure CF is a Hash (as required by the PDF standard for this type of encryption).
53
- raise_encrypted_error unless actual_object(@encryption_dictionary[:CF]).is_a?(Hash)
35
+ change_references_to_actual_values @encryption_dictionary
36
+ end
54
37
 
55
- # do nothing if there is no data to decrypt except embeded files...?
56
- return true unless (actual_object(@encryption_dictionary[:CF]).values.select { |v| !v[:AuthEvent] || v[:AuthEvent] == :DocOpen } ).empty?
38
+ # call this to start the decryption.
39
+ def decrypt
40
+ raise_encrypted_error @encryption_dictionary unless @encryption_dictionary[:Filter] == :Standard
41
+ @key = set_general_key
42
+ case actual_object(@encryption_dictionary[:V])
43
+ when 1, 2
44
+ # raise_encrypted_error
45
+ _perform_decrypt_proc_ @objects, method(:decrypt_RC4)
46
+ when 4
47
+ # make sure CF is a Hash (as required by the PDF standard for this type of encryption).
48
+ raise_encrypted_error unless actual_object(@encryption_dictionary[:CF]).is_a?(Hash)
57
49
 
58
- # attempt to decrypt all strings?
59
- # attempt to decrypy all streams
60
- # attempt to decrypt all embeded files?
50
+ # support trivial case for now
51
+ # - same filter for streams (Stmf) and strings(Strf)
52
+ # - AND :CFM == :V2 (use algorithm 1)
53
+ raise_encrypted_error unless (@encryption_dictionary[:StmF] == @encryption_dictionary[:StrF])
61
54
 
62
- else
63
- raise_encrypted_error
64
- end
65
- #rebuild stream lengths?
66
- @objects
67
- rescue => e
68
- raise_encrypted_error
69
- end
55
+ cfilter = actual_object(@encryption_dictionary[:CF])[@encryption_dictionary[:StrF]]
56
+ raise_encrypted_error unless cfilter
57
+ raise_encrypted_error unless (cfilter[:AuthEvent] == :DocOpen)
58
+ if (cfilter[:CFM] == :V2)
59
+ _perform_decrypt_proc_ @objects, method(:decrypt_RC4)
60
+ elsif (cfilter[:CFM] == :AESV2)
61
+ _perform_decrypt_proc_ @objects, method(:decrypt_AES)
62
+ else
63
+ raise_encrypted_error
64
+ end
65
+ end
66
+ # rebuild stream lengths?
67
+ @objects
68
+ rescue => e
69
+ puts e
70
+ puts e.message
71
+ puts e.backtrace.join("\n")
72
+ raise_encrypted_error
73
+ end
70
74
 
71
- protected
75
+ protected
72
76
 
73
- def set_general_key(password = "")
74
- # 1) make sure the initial key is 32 byte long (if no password, uses padding).
75
- key = (password.bytes[0..32].to_a + @padding_key)[0..31].to_a.pack('C*').force_encoding(Encoding::ASCII_8BIT)
76
- # 2) add the value of the encryption dictionary’s O entry
77
- key << actual_object(@encryption_dictionary[:O]).to_s
78
- # 3) Convert the integer value of the P entry to a 32-bit unsigned binary number
79
- # and pass these bytes low-order byte first
80
- key << [actual_object(@encryption_dictionary[:P])].pack('i')
81
- # 4) Pass the first element of the file’s file identifier array
82
- # (the value of the ID entry in the document’s trailer dictionary
83
- key << actual_object(@root_dictionary[:ID])[0]
84
- # # 4(a) (Security handlers of revision 4 or greater)
85
- # # if document metadata is not being encrypted, add 4 bytes with the value 0xFFFFFFFF.
86
- if actual_object(@encryption_dictionary[:R]) >= 4
87
- unless actual_object(@encryption_dictionary)[:EncryptMetadata] == false #default is true and nil != false
88
- key << "\x00\x00\x00\x00"
89
- else
90
- key << "\xFF\xFF\xFF\xFF"
91
- end
92
- end
93
- # 5) pass everything as a MD5 hash
94
- key = Digest::MD5.digest(key)
95
- # 5(a) h) (Security handlers of revision 3 or greater) Do the following 50 times:
96
- # Take the output from the previous MD5 hash and
97
- # pass the first n bytes of the output as input into a new MD5 hash,
98
- # where n is the number of bytes of the encryption key as defined by the value of
99
- # the encryption dictionary’s Length entry.
100
- if actual_object(@encryption_dictionary[:R]) >= 3
101
- 50.times do|i|
102
- key = Digest::MD5.digest(key[0...actual_object(@encryption_dictionary[:Length])])
103
- end
104
- end
105
- # 6) Set the encryption key to the first n bytes of the output from the final MD5 hash,
106
- # where n shall always be 5 for security handlers of revision 2 but,
107
- # for security handlers of revision 3 or greater,
108
- # shall depend on the value of the encryption dictionary’s Length entry.
109
- if actual_object(@encryption_dictionary[:R]) >= 3
110
- @key = key[0..(actual_object(@encryption_dictionary[:Length])/8)]
111
- else
112
- @key = key[0..4]
113
- end
114
- @key
115
- end
116
- def decrypt_none(encrypted, encrypted_id, encrypted_generation, encrypted_filter)
117
- "encrypted"
118
- end
119
- def decrypt_RC4(encrypted, encrypted_id, encrypted_generation, encrypted_filter)
120
- ## start decryption using padding strings
121
- object_key = @key.dup
122
- object_key << [encrypted_id].pack('i')[0..2]
123
- object_key << [encrypted_generation].pack('i')[0..1]
124
- # (0..2).each { |e| object_key << (encrypted_id >> e*8 & 0xFF ) }
125
- # (0..1).each { |e| object_key << (encrypted_generation >> e*8 & 0xFF ) }
126
- key_length = object_key.length < 16 ? object_key.length : 16
127
- rc4 = ::RC4.new( Digest::MD5.digest(object_key)[(0...key_length)] )
128
- rc4.decrypt(encrypted)
129
- end
130
- def decrypt_AES(encrypted, encrypted_id, encrypted_generation, encrypted_filter)
131
- ## extract encryption_iv if it wasn't extracted yet
132
- unless @encryption_iv
133
- @encryption_iv = encrypted[0..15].to_i
134
- #raise "Tryed decrypting using AES and couldn't extract iv" if @encryption_iv == 0
135
- @encryption_iv = 0.chr * 16
136
- #encrypted = encrypted[16..-1]
137
- end
138
- ## start decryption using padding strings
139
- object_key = @key.dup
140
- (0..2).each { |e| object_key << (encrypted_id >> e*8 & 0xFF ) }
141
- (0..1).each { |e| object_key << (encrypted_generation >> e*8 & 0xFF ) }
142
- object_key << "sAlT"
143
- key_length = object_key.length < 16 ? object_key.length : 16
144
- cipher = OpenSSL::Cipher::Cipher.new("aes-#{object_key.length << 3}-cbc").decrypt
145
- cipher.padding = 0
146
- (cipher.update(encrypted) + cipher.final).unpack("C*")
147
- end
77
+ def set_general_key(password = '')
78
+ # 1) make sure the initial key is 32 byte long (if no password, uses padding).
79
+ key = (password.bytes[0..32].to_a + @padding_key)[0..31].to_a.pack('C*').force_encoding(Encoding::ASCII_8BIT)
80
+ # 2) add the value of the encryption dictionary’s O entry
81
+ key << actual_object(@encryption_dictionary[:O]).to_s
82
+ # 3) Convert the integer value of the P entry to a 32-bit unsigned binary number
83
+ # and pass these bytes low-order byte first
84
+ key << [actual_object(@encryption_dictionary[:P])].pack('i')
85
+ # 4) Pass the first element of the file’s file identifier array
86
+ # (the value of the ID entry in the document’s trailer dictionary
87
+ key << actual_object(@root_dictionary[:ID])[0]
88
+ # # 4(a) (Security handlers of revision 4 or greater)
89
+ # # if document metadata is not being encrypted, add 4 bytes with the value 0xFFFFFFFF.
90
+ if actual_object(@encryption_dictionary[:R]) >= 4
91
+ if actual_object(@encryption_dictionary)[:EncryptMetadata] == false
92
+ key << "\xFF\xFF\xFF\xFF".force_encoding(Encoding::ASCII_8BIT)
93
+ end
94
+ end
95
+ # 5) pass everything as a MD5 hash
96
+ key = Digest::MD5.digest(key)
97
+ # 5(a) h) (Security handlers of revision 3 or greater) Do the following 50 times:
98
+ # Take the output from the previous MD5 hash and
99
+ # pass the first n bytes of the output as input into a new MD5 hash,
100
+ # where n is the number of bytes of the encryption key as defined by the value of
101
+ # the encryption dictionary’s Length entry.
102
+ if actual_object(@encryption_dictionary[:R]) >= 3
103
+ 50.times do |_i|
104
+ key = Digest::MD5.digest(key[0...actual_object(@encryption_dictionary[:Length])])
105
+ end
106
+ end
107
+ # 6) Set the encryption key to the first n bytes of the output from the final MD5 hash,
108
+ # where n shall always be 5 for security handlers of revision 2 but,
109
+ # for security handlers of revision 3 or greater,
110
+ # shall depend on the value of the encryption dictionary’s Length entry.
111
+ @key = if actual_object(@encryption_dictionary[:R]) >= 3
112
+ key[0..(actual_object(@encryption_dictionary[:Length]) / 8)]
113
+ else
114
+ key[0..4]
115
+ end
116
+ @key
117
+ end
148
118
 
149
- protected
119
+ def decrypt_none(_encrypted, _encrypted_id, _encrypted_generation, _encrypted_filter)
120
+ 'encrypted'
121
+ end
150
122
 
151
- def _perform_decrypt_proc_ (object, decrypt_proc, encrypted_id = nil, encrypted_generation = nil, encrypted_filter = nil)
152
- case
153
- when object.is_a?(Array)
154
- object.map! { |item| _perform_decrypt_proc_(item, decrypt_proc, encrypted_id, encrypted_generation, encrypted_filter) }
155
- when object.is_a?(Hash)
156
- encrypted_id ||= actual_object(object[:indirect_reference_id])
157
- encrypted_generation ||= actual_object(object[:indirect_generation_number])
158
- encrypted_filter ||= actual_object(object[:Filter])
159
- if object[:raw_stream_content]
160
- stream_length = actual_object(object[:Length])
161
- actual_length = object[:raw_stream_content].length
162
- warn "Stream registeded length was #{object[:Length].to_s} and the actual length was #{actual_length}." if actual_length < stream_length
163
- length = [ stream_length, actual_length].min
164
- object[:raw_stream_content] = decrypt_proc.call( (object[:raw_stream_content][0...length]), encrypted_id, encrypted_generation, encrypted_filter)
165
- end
166
- object.each {|k, v| object[k] = _perform_decrypt_proc_(v, decrypt_proc, encrypted_id, encrypted_generation, encrypted_filter) if k != :raw_stream_content && (v.is_a?(Hash) || v.is_a?(Array) || v.is_a?(String))} # assumes no decrypting is ever performed on keys
167
- when object.is_a?(String)
168
- return decrypt_proc.call(object, encrypted_id, encrypted_generation, encrypted_filter)
169
- else
170
- return object
171
- end
172
-
173
- end
123
+ def decrypt_RC4(encrypted, encrypted_id, encrypted_generation, _encrypted_filter)
124
+ ## start decryption using padding strings
125
+ object_key = @key.dup
126
+ object_key << [encrypted_id].pack('i')[0..2]
127
+ object_key << [encrypted_generation].pack('i')[0..1]
128
+ # (0..2).each { |e| object_key << (encrypted_id >> e*8 & 0xFF ) }
129
+ # (0..1).each { |e| object_key << (encrypted_generation >> e*8 & 0xFF ) }
130
+ key_length = object_key.length < 16 ? object_key.length : 16
131
+ rc4 = ::RC4.new(Digest::MD5.digest(object_key)[(0...key_length)])
132
+ rc4.decrypt(encrypted)
133
+ end
174
134
 
175
- def raise_encrypted_error object = nil
176
- object ||= @encryption_dictionary.to_s.split(',').join("\n")
177
- warn "Data raising exception:\n #{object.to_s.split(',').join("\n")}"
178
- raise "File is encrypted - not supported."
179
- end
135
+ def decrypt_AES(encrypted, encrypted_id, encrypted_generation, _encrypted_filter)
136
+ ## start decryption using padding strings
137
+ object_key = @key.dup
138
+ object_key << [encrypted_id].pack('i')[0..2]
139
+ object_key << [encrypted_generation].pack('i')[0..1]
140
+ object_key << 'sAlT'.force_encoding(Encoding::ASCII_8BIT)
141
+ key_length = object_key.length < 16 ? object_key.length : 16
180
142
 
181
- def change_references_to_actual_values(hash_with_references = {})
182
- hash_with_references.each do |k,v|
183
- if v.is_a?(Hash) && v[:is_reference_only]
184
- hash_with_references[k] = get_refernced_object(v)
185
- hash_with_references[k] = hash_with_references[k][:indirect_without_dictionary] if hash_with_references[k].is_a?(Hash) && hash_with_references[k][:indirect_without_dictionary]
186
- warn "Couldn't connect all values from references - didn't find reference #{hash_with_references}!!!" if hash_with_references[k] == nil
187
- hash_with_references[k] = v unless hash_with_references[k]
188
- end
189
- end
190
- hash_with_references
191
- end
192
- def get_refernced_object(reference_hash = {})
193
- @objects.each do |stored_object|
194
- return stored_object if ( stored_object.is_a?(Hash) &&
195
- reference_hash[:indirect_reference_id] == stored_object[:indirect_reference_id] &&
196
- reference_hash[:indirect_generation_number] == stored_object[:indirect_generation_number] )
197
- end
198
- warn "didn't find reference #{reference_hash}"
199
- nil
200
- end
143
+ begin
144
+ cipher = OpenSSL::Cipher.new("aes-#{key_length << 3}-cbc")
145
+ cipher.decrypt
146
+ cipher.key = Digest::MD5.digest(object_key)[(0...key_length)]
147
+ cipher.iv = encrypted[0..15]
148
+ cipher.padding = 0
149
+ cipher.update(encrypted[16..-1]) + cipher.final
150
+ rescue StandardError => e
151
+ # puts e.class.name
152
+ encrypted
153
+ end
154
+ end
201
155
 
156
+ protected
202
157
 
203
- # # returns the PDF Object Hash holding the acutal data (if exists) or the original hash (if it wasn't a reference)
204
- # #
205
- # # works only AFTER references have been connected.
206
- # def get_referenced object
207
- # object[:referenced_object] || object
208
- # end
158
+ def _perform_decrypt_proc_(object, decrypt_proc, encrypted_id = nil, encrypted_generation = nil, encrypted_filter = nil)
159
+ if object.is_a?(Array)
160
+ object.map! { |item| _perform_decrypt_proc_(item, decrypt_proc, encrypted_id, encrypted_generation, encrypted_filter) }
161
+ elsif object.is_a?(Hash)
162
+ encrypted_id ||= actual_object(object[:indirect_reference_id])
163
+ encrypted_generation ||= actual_object(object[:indirect_generation_number])
164
+ encrypted_filter ||= actual_object(object[:Filter])
165
+ if object[:raw_stream_content] && !object[:raw_stream_content].empty?
166
+ stream_length = actual_object(object[:Length])
167
+ actual_length = object[:raw_stream_content].bytesize
168
+ # p stream_length
169
+ # p actual_length
170
+ # p object[:Length]
171
+ # p object
172
+ warn "Stream registered length was #{object[:Length]} and the actual length was #{actual_length}." if actual_length < stream_length
173
+ length = [stream_length, actual_length].min
174
+ object[:raw_stream_content] = decrypt_proc.call((object[:raw_stream_content][0...length]), encrypted_id, encrypted_generation, encrypted_filter)
175
+ end
176
+ object.each { |k, v| object[k] = _perform_decrypt_proc_(v, decrypt_proc, encrypted_id, encrypted_generation, encrypted_filter) if k != :raw_stream_content && (v.is_a?(Hash) || v.is_a?(Array) || v.is_a?(String)) } # assumes no decrypting is never performed on keys
177
+ elsif object.is_a?(String)
178
+ return decrypt_proc.call(object, encrypted_id, encrypted_generation, encrypted_filter)
179
+ else
180
+ return object
181
+ end
182
+ end
209
183
 
210
- end
211
- #####################################################
212
- ## The following isn't my code!!!!
213
- ## It is subject to a different license and copyright.
214
- ## This was the code for the RC4 Gem,
215
- ## ... I had a bad internet connection so I ended up
216
- ## copying it from the web page I had in my cache.
217
- ## This wonderful work was done by Caige Nichols.
218
- #####################################################
219
- # class RC4
220
- # def initialize(str)
221
- # begin
222
- # raise SyntaxError, "RC4: Key supplied is blank" if str.eql?('')
184
+ def raise_encrypted_error(object = nil)
185
+ object ||= @encryption_dictionary.to_s.split(',').join("\n")
186
+ warn "Data raising exception:\n #{object.to_s.split(',').join("\n")}"
187
+ raise EncryptionError, 'File is encrypted - not supported.'
188
+ end
223
189
 
224
- # @q1, @q2 = 0, 0
225
- # @key = []
226
- # str.each_byte { |elem| @key << elem } while @key.size < 256
227
- # @key.slice!(256..@key.size-1) if @key.size >= 256
228
- # @s = (0..255).to_a
229
- # j = 0
230
- # 0.upto(255) do |i|
231
- # j = (j + @s[i] + @key[i] ) % 256
232
- # @s[i], @s[j] = @s[j], @s[i]
233
- # end
234
- # end
235
- # end
190
+ def change_references_to_actual_values(hash_with_references = {})
191
+ hash_with_references.each do |k, v|
192
+ next unless v.is_a?(Hash) && v[:is_reference_only]
193
+ hash_with_references[k] = get_refernced_object(v)
194
+ hash_with_references[k] = hash_with_references[k][:indirect_without_dictionary] if hash_with_references[k].is_a?(Hash) && hash_with_references[k][:indirect_without_dictionary]
195
+ warn "Couldn't connect all values from references - didn't find reference #{hash_with_references}!!!" if hash_with_references[k].nil?
196
+ hash_with_references[k] = v unless hash_with_references[k]
197
+ end
198
+ hash_with_references
199
+ end
236
200
 
237
- # def encrypt!(text)
238
- # process text
239
- # end
201
+ def get_refernced_object(reference_hash = {})
202
+ @objects.each do |stored_object|
203
+ return (stored_object[:indirect_without_dictionary] || stored_object) if stored_object.is_a?(Hash) &&
204
+ reference_hash[:indirect_reference_id] == stored_object[:indirect_reference_id] &&
205
+ reference_hash[:indirect_generation_number] == stored_object[:indirect_generation_number]
206
+ end
207
+ warn "didn't find reference #{reference_hash}"
208
+ nil
209
+ end
240
210
 
241
- # def encrypt(text)
242
- # process text.dup
243
- # end
211
+ def actual_object(obj)
212
+ return get_refernced_object(obj) if obj.is_a?(Hash) && obj[:indirect_reference_id]
213
+ obj
214
+ end
244
215
 
245
- # alias_method :decrypt, :encrypt
216
+ # # returns the PDF Object Hash holding the acutal data (if exists) or the original hash (if it wasn't a reference)
217
+ # #
218
+ # # works only AFTER references have been connected.
219
+ # def get_referenced object
220
+ # object[:referenced_object] || object
221
+ # end
222
+ end
223
+ #####################################################
224
+ ## The following isn't my code!!!!
225
+ ## It is subject to a different license and copyright.
226
+ ## This was the code for the RC4 Gem,
227
+ ## ... I had a bad internet connection so I ended up
228
+ ## copying it from the web page I had in my cache.
229
+ ## This wonderful work was done by Caige Nichols.
230
+ #####################################################
231
+ # class RC4
232
+ # def initialize(str)
233
+ # begin
234
+ # raise SyntaxError, "RC4: Key supplied is blank" if str.eql?('')
246
235
 
247
- # private
236
+ # @q1, @q2 = 0, 0
237
+ # @key = []
238
+ # str.each_byte { |elem| @key << elem } while @key.size < 256
239
+ # @key.slice!(256..@key.size-1) if @key.size >= 256
240
+ # @s = (0..255).to_a
241
+ # j = 0
242
+ # 0.upto(255) do |i|
243
+ # j = (j + @s[i] + @key[i] ) % 256
244
+ # @s[i], @s[j] = @s[j], @s[i]
245
+ # end
246
+ # end
247
+ # end
248
248
 
249
- # def process(text)
250
- # text.unpack("C*").map { |c| c ^ round }.pack("C*")
251
- # end
249
+ # def encrypt!(text)
250
+ # process text
251
+ # end
252
252
 
253
- # def round
254
- # @q1 = (@q1 + 1) % 256
255
- # @q2 = (@q2 + @s[@q1]) % 256
256
- # @s[@q1], @s[@q2] = @s[@q2], @s[@q1]
257
- # @s[(@s[@q1]+@s[@q2]) % 256]
258
- # end
259
- # end
253
+ # def encrypt(text)
254
+ # process text.dup
255
+ # end
260
256
 
261
- end
257
+ # alias_method :decrypt, :encrypt
262
258
 
259
+ # private
260
+
261
+ # def process(text)
262
+ # text.unpack("C*").map { |c| c ^ round }.pack("C*")
263
+ # end
264
+
265
+ # def round
266
+ # @q1 = (@q1 + 1) % 256
267
+ # @q2 = (@q2 + @s[@q1]) % 256
268
+ # @s[@q1], @s[@q2] = @s[@q2], @s[@q1]
269
+ # @s[(@s[@q1]+@s[@q2]) % 256]
270
+ # end
271
+ # end
272
+ end
@@ -0,0 +1,4 @@
1
+ module CombinePDF
2
+ class EncryptionError < StandardError
3
+ end
4
+ end