combine_pdf 0.2.21 → 0.2.27

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