combine_pdf 0.2.34 → 0.2.35

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 978b43d9e325d4e5eba06ceebb396565c0413bc1
4
- data.tar.gz: b3f85e2ff8d5fa57643f4aa516c620a0b9dadafc
3
+ metadata.gz: 9fcaee4b0d0bc2b6991c41c570b300120038e6bd
4
+ data.tar.gz: 9045bd35b291cd33fb9d8f5a1e5611e64c4b81bb
5
5
  SHA512:
6
- metadata.gz: f0b111a144fe3b0c8ea54bb7a138d1d97e3d0f59af48e296e6286ea40dfb575c7f1573061e526f278c84765c75c6d56c1ee8d96dccb7c93c2972e296c568fd24
7
- data.tar.gz: 0bfbf5060a5f5210fd2349acf898b2feced9e2610cf9c734cb9a20c5c969dfbd361f81ac769e48e4274b23c7217e4476d651daf608ce8a25c139288deecbab03
6
+ metadata.gz: a82447b8e8caf898149b868818aa5c61f99f30f974297d8ed19e2afdb3ebb2f48f86f03909cbd03a698b23288455ec2743ab6a3fec73a489e73e3c47a6b46769
7
+ data.tar.gz: f1d0d133c2a84c82cfe7920b626ae56cf8a58e40a73da5ca5bff5fb5c9c07574e404fe5ebcb913fcc14464ca9029cba971c7f581a60af891d0cb555e0be9abb0
@@ -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.
@@ -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
- @key_crypt_first_iv_store = nil
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
- # 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?
55
-
56
- # attempt to decrypt all strings?
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
- else
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
- 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
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
- (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'
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
- cipher = OpenSSL::Cipher::Cipher.new("aes-#{object_key.length << 3}-cbc").decrypt
149
- cipher.padding = 0
150
- (cipher.update(encrypted) + cipher.final).unpack('C*')
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 registeded length was #{object[:Length]} and the actual length was #{actual_length}." if actual_length < stream_length
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).
@@ -1,3 +1,3 @@
1
1
  module CombinePDF
2
- VERSION = '0.2.34'.freeze
2
+ VERSION = '0.2.35'.freeze
3
3
  end
@@ -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 '10_prawn.pdf', (Prawn::Document.new { text 'Hello World!' }).render
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 '10_parsed_from_prawn.pdf'
85
+ pdf.save '11_parsed_from_prawn.pdf'
66
86
  pdf = CombinePDF.new
67
87
  pdf << page << page
68
- pdf.save('10_AcrobatReader_is_unique_page.pdf')
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.34
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-02-12 00:00:00.000000000 Z
11
+ date: 2017-03-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ruby-rc4