combine_pdf 0.2.21 → 0.2.27

Sign up to get free protection for your applications and to get access to all the features.
@@ -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