combine_pdf 0.2.34 → 0.2.35
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 +15 -0
- data/lib/combine_pdf/decrypt.rb +35 -32
- data/lib/combine_pdf/page_methods.rb +8 -2
- data/lib/combine_pdf/version.rb +1 -1
- data/test/automated +24 -3
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9fcaee4b0d0bc2b6991c41c570b300120038e6bd
|
4
|
+
data.tar.gz: 9045bd35b291cd33fb9d8f5a1e5611e64c4b81bb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a82447b8e8caf898149b868818aa5c61f99f30f974297d8ed19e2afdb3ebb2f48f86f03909cbd03a698b23288455ec2743ab6a3fec73a489e73e3c47a6b46769
|
7
|
+
data.tar.gz: f1d0d133c2a84c82cfe7920b626ae56cf8a58e40a73da5ca5bff5fb5c9c07574e404fe5ebcb913fcc14464ca9029cba971c7f581a60af891d0cb555e0be9abb0
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,21 @@
|
|
2
2
|
|
3
3
|
***
|
4
4
|
|
5
|
+
Change log v.0.2.35 (Release Candidate)
|
6
|
+
|
7
|
+
**Update**: Updated / upgraded our RC4 and AES PDF encryption support (for non-password protected PDFs). Credit to Gyuchang Jun (@gyuchang) for his work on providing CombinePDF with this extra encryption support. I have no idea what magic he used to make this happen, but it's beautiful!
|
8
|
+
|
9
|
+
**Release**: This gem had been using a development versioning scheme for far too long. The API is stable enough to switch to a production versioning scheme. This version is expected to be the last 0.x version. Assuming this version will be stable enough, it is expected to be re-released as v.1.0.
|
10
|
+
|
11
|
+
|
12
|
+
***
|
13
|
+
|
14
|
+
Change log v.0.2.34
|
15
|
+
|
16
|
+
**Fix**: issue #44 for wkhtmltopdf compatibility and PDF v.1.2 use of named destinations. Credit to Devin Wadsworth (@daymun) for exposing the issue.
|
17
|
+
|
18
|
+
***
|
19
|
+
|
5
20
|
Change log v.0.2.33
|
6
21
|
|
7
22
|
**Update**: Fix #97 to allow javascript support for interactive objects. Credit to @joshirashmics for exposing the issue.
|
data/lib/combine_pdf/decrypt.rb
CHANGED
@@ -31,8 +31,7 @@ module CombinePDF
|
|
31
31
|
0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08,
|
32
32
|
0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80,
|
33
33
|
0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A]
|
34
|
-
|
35
|
-
@encryption_iv = nil
|
34
|
+
|
36
35
|
change_references_to_actual_values @encryption_dictionary
|
37
36
|
end
|
38
37
|
|
@@ -45,20 +44,24 @@ module CombinePDF
|
|
45
44
|
# raise_encrypted_error
|
46
45
|
_perform_decrypt_proc_ @objects, method(:decrypt_RC4)
|
47
46
|
when 4
|
48
|
-
# raise unsupported error for now
|
49
|
-
raise_encrypted_error
|
50
47
|
# make sure CF is a Hash (as required by the PDF standard for this type of encryption).
|
51
48
|
raise_encrypted_error unless actual_object(@encryption_dictionary[:CF]).is_a?(Hash)
|
52
49
|
|
53
|
-
#
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
# attempt to decrypy all streams
|
58
|
-
# 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])
|
59
54
|
|
60
|
-
|
61
|
-
raise_encrypted_error
|
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
|
62
65
|
end
|
63
66
|
# rebuild stream lengths?
|
64
67
|
@objects
|
@@ -85,11 +88,9 @@ module CombinePDF
|
|
85
88
|
# # 4(a) (Security handlers of revision 4 or greater)
|
86
89
|
# # if document metadata is not being encrypted, add 4 bytes with the value 0xFFFFFFFF.
|
87
90
|
if actual_object(@encryption_dictionary[:R]) >= 4
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
"\x00\x00\x00\x00"
|
92
|
-
end
|
91
|
+
if actual_object(@encryption_dictionary)[:EncryptMetadata] == false
|
92
|
+
key << "\xFF\xFF\xFF\xFF".force_encoding(Encoding::ASCII_8BIT)
|
93
|
+
end
|
93
94
|
end
|
94
95
|
# 5) pass everything as a MD5 hash
|
95
96
|
key = Digest::MD5.digest(key)
|
@@ -132,22 +133,24 @@ module CombinePDF
|
|
132
133
|
end
|
133
134
|
|
134
135
|
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
136
|
## start decryption using padding strings
|
143
137
|
object_key = @key.dup
|
144
|
-
|
145
|
-
|
146
|
-
object_key << 'sAlT'
|
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)
|
147
141
|
key_length = object_key.length < 16 ? object_key.length : 16
|
148
|
-
|
149
|
-
|
150
|
-
|
142
|
+
|
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
|
151
154
|
end
|
152
155
|
|
153
156
|
protected
|
@@ -159,14 +162,14 @@ module CombinePDF
|
|
159
162
|
encrypted_id ||= actual_object(object[:indirect_reference_id])
|
160
163
|
encrypted_generation ||= actual_object(object[:indirect_generation_number])
|
161
164
|
encrypted_filter ||= actual_object(object[:Filter])
|
162
|
-
if object[:raw_stream_content]
|
165
|
+
if object[:raw_stream_content] && !object[:raw_stream_content].empty?
|
163
166
|
stream_length = actual_object(object[:Length])
|
164
167
|
actual_length = object[:raw_stream_content].bytesize
|
165
168
|
# p stream_length
|
166
169
|
# p actual_length
|
167
170
|
# p object[:Length]
|
168
171
|
# p object
|
169
|
-
warn "Stream
|
172
|
+
warn "Stream registered length was #{object[:Length]} and the actual length was #{actual_length}." if actual_length < stream_length
|
170
173
|
length = [stream_length, actual_length].min
|
171
174
|
object[:raw_stream_content] = decrypt_proc.call((object[:raw_stream_content][0...length]), encrypted_id, encrypted_generation, encrypted_filter)
|
172
175
|
end
|
@@ -182,6 +182,7 @@ module CombinePDF
|
|
182
182
|
# border_width:: border width in PDF units. defaults to nil (none).
|
183
183
|
# box_radius:: border radius in PDF units. defaults to 0 (no corner rounding).
|
184
184
|
# opacity:: textbox opacity, a float between 0 (transparent) and 1 (opaque)
|
185
|
+
# ctm:: A PDF complient CTM data array that will manipulate the axis and allow transformations. i.e. `[1,0,0,1,0,0]`
|
185
186
|
def textbox(text, properties = {})
|
186
187
|
options = {
|
187
188
|
x: 0,
|
@@ -392,6 +393,8 @@ module CombinePDF
|
|
392
393
|
# This method moves the Page[:Rotate] property into the page's data stream, so that
|
393
394
|
# "what you see is what you get".
|
394
395
|
#
|
396
|
+
# After using thie method, {#orientation} should return the absolute orientation rather than only the data's orientation (unless `:Rotate` is changed).
|
397
|
+
#
|
395
398
|
# This is usful in cases where there might be less control over the source PDF files,
|
396
399
|
# and the user assums that the PDF page's data is the same as the PDF's pages
|
397
400
|
# on screen display (Rotate rotates a page but leaves the data in the original orientation).
|
@@ -441,7 +444,7 @@ module CombinePDF
|
|
441
444
|
y_ratio = 1.0 * (new_size[3] - new_size[1]) / (c_size[3]) #-c_size[1])
|
442
445
|
x_move = new_size[0] - c_size[0]
|
443
446
|
y_move = new_size[1] - c_size[1]
|
444
|
-
puts "ctm will be: #{x_ratio.round(4)} 0 0 #{y_ratio.round(4)} #{x_move} #{y_move}"
|
447
|
+
# puts "ctm will be: #{x_ratio.round(4)} 0 0 #{y_ratio.round(4)} #{x_move} #{y_move}"
|
445
448
|
self[:MediaBox] = [(c_mediabox[0] + x_move), (c_mediabox[1] + y_move), ((c_mediabox[2] * x_ratio) + x_move), ((c_mediabox[3] * y_ratio) + y_move)]
|
446
449
|
self[:CropBox] = [(c_cropbox[0] + x_move), (c_cropbox[1] + y_move), ((c_cropbox[2] * x_ratio) + x_move), ((c_cropbox[3] * y_ratio) + y_move)] if c_cropbox
|
447
450
|
x_ratio = y_ratio = [x_ratio, y_ratio].min if conserve_aspect_ratio
|
@@ -506,7 +509,10 @@ module CombinePDF
|
|
506
509
|
fix_rotation
|
507
510
|
end
|
508
511
|
|
509
|
-
# get or set (by clockwise rotation) the page's orientation
|
512
|
+
# get or set (by clockwise rotation) the page's data orientation.
|
513
|
+
#
|
514
|
+
# note that the data's orientation is the way data is oriented on the page.
|
515
|
+
# The display orientati0n (which might different) is controlled by the `:Rotate` property. see {#fix_orientation} for more details.
|
510
516
|
#
|
511
517
|
# accepts one optional parameter:
|
512
518
|
# force:: to get the orientation, pass nil. to set the orientatiom, set fource to either :portrait or :landscape. defaults to nil (get orientation).
|
data/lib/combine_pdf/version.rb
CHANGED
data/test/automated
CHANGED
@@ -57,16 +57,37 @@ CombinePDF.load("./Ruby/test\ pdfs/Scribus-unknown_err3.pdf").save '08_3-unknown
|
|
57
57
|
|
58
58
|
CombinePDF.load("/Users/2Be/Ruby/test\ pdfs/nil_object.pdf").save('09_nil_in_parsed_array.pdf')
|
59
59
|
|
60
|
+
encrypted = [ "./Ruby/test\ pdfs/pdf-reader/encrypted_version4_revision4_128bit_aes_user_pass_apples_enc_metadata.pdf",
|
61
|
+
"./Ruby/test\ pdfs/AESv2\ encrypted.pdf",
|
62
|
+
"./Ruby/test\ pdfs/pdf-reader/encrypted_version2_revision3_128bit_rc4_blank_user_pass.pdf",
|
63
|
+
"./Ruby/test\ pdfs/AES\ enc.pdf",
|
64
|
+
"./Ruby/test\ pdfs/RC4\ enc.pdf"]
|
65
|
+
|
66
|
+
encrypted.length.times do |i|
|
67
|
+
fname = File.basename encrypted[i]
|
68
|
+
begin
|
69
|
+
CombinePDF.load(encrypted[i]).save "10_#{i}_#{fname}"
|
70
|
+
rescue => e
|
71
|
+
puts e.class.name, e.message
|
72
|
+
if(i == 0)
|
73
|
+
puts "CombinePDF expected to fail to read AESv2 #{fname}"
|
74
|
+
else
|
75
|
+
puts "ERROR: CombinePDF failed to open #{fname}"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
60
80
|
require 'prawn'
|
61
|
-
IO.binwrite '
|
81
|
+
IO.binwrite '11_prawn.pdf', (Prawn::Document.new { text 'Hello World!' }).render
|
62
82
|
page = CombinePDF.parse((Prawn::Document.new { text 'Hello World!' }).render)
|
63
83
|
pdf = CombinePDF.new
|
64
84
|
pdf << page
|
65
|
-
pdf.save '
|
85
|
+
pdf.save '11_parsed_from_prawn.pdf'
|
66
86
|
pdf = CombinePDF.new
|
67
87
|
pdf << page << page
|
68
|
-
pdf.save('
|
88
|
+
pdf.save('11_AcrobatReader_is_unique_page.pdf')
|
69
89
|
|
90
|
+
puts GC.stat.inspect
|
70
91
|
# unify = [
|
71
92
|
# "./Ruby/test\ pdfs/AESv2\ encrypted.pdf",
|
72
93
|
# "./Ruby/test\ pdfs/data-in-comment.pdf",
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: combine_pdf
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.35
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Boaz Segev
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-03-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ruby-rc4
|