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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +54 -0
- data/LICENSE.txt +2 -1
- data/README.md +30 -0
- data/lib/combine_pdf.rb +12 -18
- data/lib/combine_pdf/api.rb +156 -153
- data/lib/combine_pdf/basic_writer.rb +41 -53
- data/lib/combine_pdf/decrypt.rb +235 -228
- data/lib/combine_pdf/filter.rb +79 -90
- data/lib/combine_pdf/fonts.rb +451 -459
- data/lib/combine_pdf/page_methods.rb +864 -877
- data/lib/combine_pdf/parser.rb +649 -578
- data/lib/combine_pdf/pdf_protected.rb +377 -218
- data/lib/combine_pdf/pdf_public.rb +490 -462
- data/lib/combine_pdf/renderer.rb +157 -163
- data/lib/combine_pdf/version.rb +1 -1
- data/test/automated +79 -0
- data/test/console +4 -4
- data/test/named_dest +84 -0
- metadata +6 -2
@@ -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
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
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
|
-
|
data/lib/combine_pdf/decrypt.rb
CHANGED
@@ -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
|
-
|
11
|
-
|
13
|
+
# @private
|
14
|
+
# @!visibility private
|
12
15
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
# @!visibility private
|
16
|
+
# This is an internal class. you don't need it.
|
17
|
+
class PDFDecrypt
|
18
|
+
# include CombinePDF::Renderer
|
17
19
|
|
18
|
-
|
19
|
-
class PDFDecrypt
|
20
|
-
include CombinePDF::Renderer
|
20
|
+
# @!visibility private
|
21
21
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
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
|
-
|
56
|
-
|
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
|
-
|
59
|
-
|
60
|
-
|
56
|
+
# attempt to decrypt all strings?
|
57
|
+
# attempt to decrypy all streams
|
58
|
+
# attempt to decrypt all embeded files?
|
61
59
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
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
|
-
|
72
|
+
protected
|
72
73
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
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
|
-
|
118
|
+
def decrypt_none(_encrypted, _encrypted_id, _encrypted_generation, _encrypted_filter)
|
119
|
+
'encrypted'
|
120
|
+
end
|
150
121
|
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
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
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
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
|
-
|
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
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
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
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
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
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
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
|
-
|
238
|
-
|
239
|
-
|
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
|
-
|
242
|
-
|
243
|
-
|
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
|
-
|
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
|
-
|
246
|
+
# def encrypt!(text)
|
247
|
+
# process text
|
248
|
+
# end
|
248
249
|
|
249
|
-
|
250
|
-
|
251
|
-
|
250
|
+
# def encrypt(text)
|
251
|
+
# process text.dup
|
252
|
+
# end
|
252
253
|
|
253
|
-
|
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
|
-
|
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
|