origami 2.0.3 → 2.0.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 747446efde7ffefe478ec34908b8555b3a5e2596
4
- data.tar.gz: 8d92eb219be13b1070e421a6570c587a16fb9a68
3
+ metadata.gz: 67f206ada90cab96c6551107f6e1b7c882608a9c
4
+ data.tar.gz: c64e6272dd9512390470c9e5c56454f569162913
5
5
  SHA512:
6
- metadata.gz: d3f3997e8181f448f74e9a2bc84855652b71b4ea66a2ede9f1b3f8cc696eb77e82c72f436cc430c838972e43cab0566ce52bf6c8fb0fd0ebee47889a2ff3de9f
7
- data.tar.gz: e8895bcc5b5b4ae96bcb24d66b4befea45cf2068493814859092f2b56b25dd3d9467df3fcd72b73a6cbbb835e2cae8910a0725f3c85b042127d14aef1c57fa80
6
+ metadata.gz: a5335917cb27a2fba1c7e36cd805b924d225d436b62be76411d43c51102c651eb5cbdc931cf12bfd4de206e93a421afcf01edae11b20816a4d6815d06b7aba3f
7
+ data.tar.gz: aa3eaf290b1c5ece1b06630dcb1440e81403865d753355dbccac57ba7f395486eb2bd3b14ba0a96baa8b15c2d4666072d37dead6a54de7c44a40d212ebb67bb8
@@ -96,10 +96,8 @@ def objectToRuby(obj, inclevel = 0, internalname = nil, do_convert = false)
96
96
  case obj
97
97
  when Origami::Null
98
98
  "Null.new"
99
- when Origami::Boolean, Origami::Number
100
- obj.value.to_s
101
- when Origami::String
102
- obj.inspect
99
+ when Origami::Boolean, Origami::Number, Origami::Name, Origami::String
100
+ literalToRuby(obj)
103
101
  when Origami::Dictionary
104
102
  customclass = nil
105
103
  if obj.class != Origami::Dictionary
@@ -111,8 +109,6 @@ def objectToRuby(obj, inclevel = 0, internalname = nil, do_convert = false)
111
109
  arrayToRuby(obj, inclevel, internalname)
112
110
  when Origami::Stream
113
111
  streamToRuby(obj, internalname) unless obj.is_a?(ObjectStream) or obj.is_a?(XRefStream)
114
- when Origami::Name
115
- nameToRuby(obj)
116
112
  when Origami::Reference
117
113
  referenceToRuby(obj, internalname)
118
114
  else
@@ -144,15 +140,8 @@ def referenceToRuby(ref, internalname)
144
140
  end
145
141
  end
146
142
 
147
- def nameToRuby(name)
148
- code = ':'
149
- valid = (name.value.to_s =~ /[+.:-]/).nil?
150
-
151
- code << '"' unless valid
152
- code << name.value.to_s
153
- code << '"' unless valid
154
-
155
- code
143
+ def literalToRuby(obj)
144
+ obj.value.inspect
156
145
  end
157
146
 
158
147
  def arrayToRuby(arr, inclevel, internalname)
@@ -180,7 +169,7 @@ def dictionaryToRuby(dict, inclevel, internalname, customtype = nil)
180
169
  else
181
170
  code << "{\n"
182
171
  dict.each_pair do |key, val|
183
- rubyname = nameToRuby(key)
172
+ rubyname = literalToRuby(key)
184
173
  subintname = "#{internalname}[#{rubyname}]"
185
174
 
186
175
  if val.is_a?(Origami::Reference) and @var_hash[val] and @var_hash[val][0,3] == "obj"
@@ -212,7 +201,7 @@ def dictionaryToHashMap(dict, inclevel, internalname)
212
201
  i = 0
213
202
  code = "\n"
214
203
  dict.each_pair do |key, val|
215
- rubyname = nameToRuby(key)
204
+ rubyname = literalToRuby(key)
216
205
  subintname = "#{internalname}[#{rubyname}]"
217
206
 
218
207
  if val.is_a?(Origami::Reference) and @var_hash[val] and @var_hash[val][0,3] == "obj"
data/bin/pdfcop CHANGED
@@ -36,7 +36,8 @@ end
36
36
  require 'optparse'
37
37
  require 'yaml'
38
38
  require 'rexml/document'
39
- require 'digest/md5'
39
+ require 'digest/sha2'
40
+ require 'fileutils'
40
41
  require 'colorize'
41
42
 
42
43
  DEFAULT_CONFIG_FILE = "#{File.dirname(__FILE__)}/config/pdfcop.conf.yml"
@@ -74,6 +75,10 @@ USAGE
74
75
  options[:policy] = policy
75
76
  end
76
77
 
78
+ opts.on("-m", "--move PATH", "Move rejected documents to the specified directory.") do |dir|
79
+ options[:move_dir] = dir
80
+ end
81
+
77
82
  opts.on("-P", "--password PASSWORD", "Password to use if the document is encrypted") do |passwd|
78
83
  options[:password] = passwd
79
84
  end
@@ -95,20 +100,24 @@ USAGE
95
100
  end
96
101
 
97
102
  @options = OptParser.parse(ARGV)
98
- if @options.has_key?(:output_log)
103
+ if @options.key?(:output_log)
99
104
  LOGGER = File.open(@options[:output_log], "a+")
100
105
  else
101
106
  LOGGER = STDOUT
102
107
  end
103
108
 
104
- if not @options.has_key?(:policy)
109
+ if not @options.key?(:policy)
105
110
  @options[:policy] = DEFAULT_POLICY
106
111
  end
107
112
 
113
+ if @options.key?(:move_dir) and not File.directory?(@options[:move_dir])
114
+ abort "Error: #{@options[:move_dir]} is not a valid directory."
115
+ end
116
+
108
117
  String.disable_colorization @options[:disable_colors]
109
118
 
110
119
  load_config_file(@options[:config_file] || DEFAULT_CONFIG_FILE)
111
- unless SECURITY_POLICIES.has_key?("POLICY_#{@options[:policy].upcase}")
120
+ unless SECURITY_POLICIES.key?("POLICY_#{@options[:policy].upcase}")
112
121
  abort "Undeclared policy `#{@options[:policy]}'"
113
122
  end
114
123
 
@@ -124,9 +133,23 @@ end
124
133
 
125
134
  def reject(cause)
126
135
  log("Document rejected by policy `#{@options[:policy]}', caused by #{cause.inspect}.", :red)
136
+
137
+ if @options.key?(:move_dir)
138
+ quarantine(TARGET, @options[:move_dir])
139
+ end
140
+
127
141
  abort
128
142
  end
129
143
 
144
+ def quarantine(file, quarantine_folder)
145
+ digest = Digest::SHA256.file(TARGET)
146
+ ext = File.extname(TARGET)
147
+ dest_name = "#{File.basename(TARGET, ext)}_#{digest}#{ext}"
148
+ dest_path = File.join(@options[:move_dir], dest_name)
149
+
150
+ FileUtils.move(TARGET, dest_path)
151
+ end
152
+
130
153
  def check_rights(*required_rights)
131
154
  current_rights = SECURITY_POLICIES["POLICY_#{@options[:policy].upcase}"]
132
155
 
@@ -165,7 +188,7 @@ end
165
188
  def analyze_annotation(annot, _level = 0)
166
189
  check_rights(:allowAnnotations)
167
190
 
168
- if annot.is_a?(Origami::Dictionary) and annot.has_key?(:Subtype)
191
+ if annot.is_a?(Origami::Dictionary) and annot.key?(:Subtype)
169
192
  case annot[:Subtype].solve.value
170
193
  when :FileAttachment
171
194
  check_rights(:allowAttachments, :allowFileAttachmentAnnotation)
@@ -186,7 +209,7 @@ def analyze_annotation(annot, _level = 0)
186
209
  check_rights(:allow3DAnnotation)
187
210
 
188
211
  # 3D annotation might pull in JavaScript for real-time driven behavior.
189
- if annot.has_key?(:"3DD")
212
+ if annot.key?(:"3DD")
190
213
  dd = annot[:"3DD"].solve
191
214
  u3dstream = nil
192
215
 
@@ -200,7 +223,7 @@ def analyze_annotation(annot, _level = 0)
200
223
  if u3dstream and u3dstream.key?(:OnInstantiate)
201
224
  check_rights(:allowJS)
202
225
 
203
- if annot.has_key?(:"3DA") # is 3d view instantiated automatically?
226
+ if annot.key?(:"3DA") # is 3d view instantiated automatically?
204
227
  u3dactiv = annot[:"3DA"].solve
205
228
 
206
229
  check_rights(:allowJSAtOpening) if u3dactiv.is_a?(Origami::Dictionary) and (u3dactiv[:A] == :PO or u3dactiv[:A] == :PV)
@@ -223,13 +246,13 @@ def analyze_page(page, level = 0)
223
246
  #
224
247
  # Checking page additional actions.
225
248
  #
226
- if page.has_key?(:AA)
249
+ if page.key?(:AA)
227
250
  if page.AA.is_a?(Origami::Dictionary)
228
251
  log(text_prefix + " Page has an action dictionary.")
229
252
 
230
253
  aa = Origami::Page::AdditionalActions.new(page.AA); aa.parent = page.AA.parent
231
- analyze_action(aa.O, true, level + 1) if aa.has_key?(:O)
232
- analyze_action(aa.C, false, level + 1) if aa.has_key?(:C)
254
+ analyze_action(aa.O, true, level + 1) if aa.key?(:O)
255
+ analyze_action(aa.C, false, level + 1) if aa.key?(:C)
233
256
  end
234
257
  end
235
258
 
@@ -280,7 +303,7 @@ def analyze_action(action, triggered_at_opening, level = 0)
280
303
  check_rights(:allowGoToRAction)
281
304
 
282
305
  when :Thread
283
- check_rights(:allowGoToRAction) if action.has_key?(:F)
306
+ check_rights(:allowGoToRAction) if action.key?(:F)
284
307
 
285
308
  when :URI
286
309
  check_rights(:allowURIAction)
@@ -307,7 +330,7 @@ def analyze_action(action, triggered_at_opening, level = 0)
307
330
  check_rights(:allow3DAnnotation,:allowGoTo3DAction)
308
331
  end
309
332
 
310
- if action.has_key?(:Next)
333
+ if action.key?(:Next)
311
334
  log(text_prefix + "This action is chained to another action!")
312
335
  check_rights(:allowChainedActions)
313
336
  analyze_action(action.Next)
@@ -329,7 +352,7 @@ end
329
352
  begin
330
353
  log("PDFcop is running on target `#{TARGET}', policy = `#{@options[:policy]}'", :green)
331
354
  log(" File size: #{File.size(TARGET)} bytes", :magenta)
332
- log(" MD5: #{Digest::MD5.hexdigest(File.read(TARGET))}", :magenta)
355
+ log(" SHA256: #{Digest::SHA256.file(TARGET)}", :magenta)
333
356
 
334
357
  @pdf = Origami::PDF.read(TARGET,
335
358
  verbosity: Origami::Parser::VERBOSE_QUIET,
@@ -349,31 +372,31 @@ begin
349
372
  catalog = @pdf.Catalog
350
373
  reject("Invalid document catalog") unless catalog.is_a?(Origami::Catalog)
351
374
 
352
- if catalog.has_key?(:OpenAction)
375
+ if catalog.key?(:OpenAction)
353
376
  log(" . OpenAction entry = YES")
354
377
  check_rights(:allowOpenAction)
355
378
  action = catalog.OpenAction
356
379
  analyze_action(action, true, 1)
357
380
  end
358
381
 
359
- if catalog.has_key?(:AA)
382
+ if catalog.key?(:AA)
360
383
  if catalog.AA.is_a?(Origami::Dictionary)
361
384
  aa = Origami::CatalogAdditionalActions.new(catalog.AA); aa.parent = catalog;
362
385
  log(" . Additional actions dictionary = YES")
363
- analyze_action(aa.WC, false, 1) if aa.has_key?(:WC)
364
- analyze_action(aa.WS, false, 1) if aa.has_key?(:WS)
365
- analyze_action(aa.DS, false, 1) if aa.has_key?(:DS)
366
- analyze_action(aa.WP, false, 1) if aa.has_key?(:WP)
367
- analyze_action(aa.DP, false, 1) if aa.has_key?(:DP)
386
+ analyze_action(aa.WC, false, 1) if aa.key?(:WC)
387
+ analyze_action(aa.WS, false, 1) if aa.key?(:WS)
388
+ analyze_action(aa.DS, false, 1) if aa.key?(:DS)
389
+ analyze_action(aa.WP, false, 1) if aa.key?(:WP)
390
+ analyze_action(aa.DP, false, 1) if aa.key?(:DP)
368
391
  end
369
392
  end
370
393
 
371
- if catalog.has_key?(:AcroForm)
394
+ if catalog.key?(:AcroForm)
372
395
  acroform = catalog.AcroForm
373
396
  if acroform.is_a?(Origami::Dictionary)
374
397
  log(" . AcroForm = YES")
375
398
  check_rights(:allowAcroForms)
376
- if acroform.has_key?(:XFA)
399
+ if acroform.key?(:XFA)
377
400
  log(" . XFA = YES")
378
401
  check_rights(:allowXFAForms)
379
402
 
@@ -400,7 +423,7 @@ begin
400
423
 
401
424
  log("> Inspecting document streams...", :yellow)
402
425
  @pdf.indirect_objects.find_all{|obj| obj.is_a?(Origami::Stream)}.each do |stream|
403
- if stream.dictionary.has_key?(:Filter)
426
+ if stream.dictionary.key?(:Filter)
404
427
  filters = stream.Filter
405
428
  filters = [ filters ] if filters.is_a?(Origami::Name)
406
429
 
@@ -659,22 +659,22 @@ module Origami
659
659
  data = fd.read
660
660
  else
661
661
  data = File.binread(File.expand_path(path))
662
- format ||= File.extname(path)
662
+ format ||= File.extname(path)[1..-1]
663
663
  end
664
664
 
665
665
  image = ImageXObject.new
666
666
 
667
667
  raise ArgumentError, "Missing file format" if format.nil?
668
668
  case format.downcase
669
- when '.jpg', 'jpeg', '.jpe', '.jif', '.jfif', '.jfi'
669
+ when 'jpg', 'jpeg', 'jpe', 'jif', 'jfif', 'jfi'
670
670
  image.setFilter :DCTDecode
671
671
  image.encoded_data = data
672
672
 
673
- when '.jp2','.jpx','.j2k','.jpf','.jpm','.mj2'
673
+ when 'jp2','jpx','j2k','jpf','jpm','mj2'
674
674
  image.setFilter :JPXDecode
675
675
  image.encoded_data = data
676
676
 
677
- when '.jb2', '.jbig', '.jbig2'
677
+ when '.b2', 'jbig', 'jbig2'
678
678
  image.setFilter :JBIG2Decode
679
679
  image.encoded_data = data
680
680
  else
@@ -188,26 +188,6 @@ module Origami
188
188
  @data.string.dup if @data
189
189
  end
190
190
 
191
- private
192
-
193
- #
194
- # Attempt to promote an object using the deferred casts.
195
- #
196
- def try_object_promotion(obj)
197
- return obj unless Origami::OPTIONS[:enable_type_propagation] and @deferred_casts.key?(obj.reference)
198
-
199
- types = @deferred_casts[obj.reference]
200
- types = [ types ] unless types.is_a?(::Array)
201
-
202
- # Promote object if a compatible type is found.
203
- cast_type = types.find {|type| type < obj.class }
204
- if cast_type
205
- obj = obj.cast_to(cast_type, self)
206
- else
207
- obj
208
- end
209
- end
210
-
211
191
  def error(msg = "") #:nodoc:
212
192
  log(VERBOSE_QUIET, 'error', :red, msg.red)
213
193
  end
@@ -228,6 +208,26 @@ module Origami
228
208
  log(VERBOSE_TRACE, 'trace', :cyan, msg)
229
209
  end
230
210
 
211
+ private
212
+
213
+ #
214
+ # Attempt to promote an object using the deferred casts.
215
+ #
216
+ def try_object_promotion(obj)
217
+ return obj unless Origami::OPTIONS[:enable_type_propagation] and @deferred_casts.key?(obj.reference)
218
+
219
+ types = @deferred_casts[obj.reference]
220
+ types = [ types ] unless types.is_a?(::Array)
221
+
222
+ # Promote object if a compatible type is found.
223
+ cast_type = types.find {|type| type < obj.class }
224
+ if cast_type
225
+ obj = obj.cast_to(cast_type, self)
226
+ else
227
+ obj
228
+ end
229
+ end
230
+
231
231
  def log(level, prefix, color, message) #:nodoc:
232
232
  return unless @options[:verbosity] >= level
233
233
 
@@ -543,7 +543,7 @@ module Origami
543
543
  #
544
544
  # Iterates over the children of an object, avoiding cycles.
545
545
  #
546
- def walk_object(object, excludes: [])
546
+ def walk_object(object, excludes: [], &block)
547
547
  return enum_for(__method__, object, excludes: excludes) unless block_given?
548
548
 
549
549
  return if excludes.include?(object)
@@ -553,18 +553,18 @@ module Origami
553
553
  when Dictionary
554
554
  object.each_value do |value|
555
555
  yield(value)
556
- walk_object(value, excludes: excludes)
556
+ walk_object(value, excludes: excludes, &block)
557
557
  end
558
558
 
559
559
  when Array
560
560
  object.each do |child|
561
561
  yield(child)
562
- walk_object(child, excludes: excludes)
562
+ walk_object(child, excludes: excludes, &block)
563
563
  end
564
564
 
565
565
  when Stream
566
566
  yield(object.dictionary)
567
- walk_object(object.dictionary, excludes: excludes)
567
+ walk_object(object.dictionary, excludes: excludes, &block)
568
568
  end
569
569
  end
570
570
 
@@ -123,13 +123,13 @@ module Origami
123
123
  end
124
124
 
125
125
  if not stream.scan(@@regexp_xref)
126
- #raise InvalidTrailerError, "Cannot get startxref value"
126
+ raise InvalidTrailerError, "Cannot get startxref value"
127
127
  end
128
128
 
129
129
  startxref = stream['startxref'].to_i
130
130
 
131
131
  if not stream.scan(@@regexp_close)
132
- #raise InvalidTrailerError, "No %%EOF token found"
132
+ parser.warn("No %%EOF token found") if parser
133
133
  end
134
134
 
135
135
  Trailer.new(startxref, dictionary)
@@ -19,5 +19,5 @@
19
19
  =end
20
20
 
21
21
  module Origami
22
- VERSION = "2.0.3"
22
+ VERSION = "2.0.4"
23
23
  end
@@ -0,0 +1,33 @@
1
+ require 'minitest/autorun'
2
+ require 'stringio'
3
+
4
+ class TestPDFObjects < Minitest::Test
5
+
6
+ def setup
7
+ @pdf = PDF.new.append_page
8
+ @contents = ContentStream.new("abc")
9
+ @pdf.pages.first.Contents = @contents
10
+ @pdf.Catalog.Loop = @pdf.Catalog
11
+ @pdf.save StringIO.new
12
+ end
13
+
14
+ def test_pdf_object_tree
15
+ assert_instance_of Catalog, @pdf.Catalog
16
+ assert_nil @pdf.Catalog.parent
17
+
18
+ @pdf.each_object(recursive: true) do |obj|
19
+ assert_kind_of Origami::Object, obj
20
+ assert_equal obj.document, @pdf
21
+
22
+ unless obj.indirect?
23
+ assert_kind_of Origami::Object, obj.parent
24
+ assert_equal obj.parent.document, @pdf
25
+ end
26
+ end
27
+
28
+ enum = @pdf.each_object(recursive: true)
29
+ assert_kind_of Enumerator, enum
30
+ assert enum.include?(@pdf.Catalog.Pages)
31
+ assert enum.include?(@contents.dictionary)
32
+ end
33
+ end
@@ -16,3 +16,4 @@ require_relative 'test_actions'
16
16
  require_relative 'test_annotations'
17
17
  require_relative 'test_forms'
18
18
  require_relative 'test_xrefs'
19
+ require_relative 'test_objects'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: origami
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.3
4
+ version: 2.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Guillaume Delugré
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-06-14 00:00:00.000000000 Z
11
+ date: 2017-10-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: colorize
@@ -39,7 +39,7 @@ dependencies:
39
39
  - !ruby/object:Gem::Version
40
40
  version: '5.0'
41
41
  description: Origami is a pure Ruby library to parse, modify and generate PDF documents.
42
- email: gdelugre@security-labs.org
42
+ email: origami@subvert.technology
43
43
  executables:
44
44
  - pdfsh
45
45
  - pdfwalker
@@ -194,6 +194,7 @@ files:
194
194
  - test/test_actions.rb
195
195
  - test/test_annotations.rb
196
196
  - test/test_forms.rb
197
+ - test/test_objects.rb
197
198
  - test/test_pages.rb
198
199
  - test/test_pdf.rb
199
200
  - test/test_pdf_attachment.rb
@@ -225,7 +226,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
225
226
  requirements:
226
227
  - gtk2 to run the graphical interface
227
228
  rubyforge_project:
228
- rubygems_version: 2.6.11
229
+ rubygems_version: 2.6.13
229
230
  signing_key:
230
231
  specification_version: 4
231
232
  summary: Ruby framework to manipulate PDF documents