bin/pdfcop in origami-2.0.3 vs bin/pdfcop in origami-2.0.4

- old
+ new

@@ -34,11 +34,12 @@ end require 'optparse' require 'yaml' require 'rexml/document' -require 'digest/md5' +require 'digest/sha2' +require 'fileutils' require 'colorize' DEFAULT_CONFIG_FILE = "#{File.dirname(__FILE__)}/config/pdfcop.conf.yml" DEFAULT_POLICY = "standard" SECURITY_POLICIES = {} @@ -72,10 +73,14 @@ opts.on("-p", "--policy POLICY_NAME", "Specify applied policy. Predefined policies: 'none', 'standard', 'strong', 'paranoid'") do |policy| options[:policy] = policy end + opts.on("-m", "--move PATH", "Move rejected documents to the specified directory.") do |dir| + options[:move_dir] = dir + end + opts.on("-P", "--password PASSWORD", "Password to use if the document is encrypted") do |passwd| options[:password] = passwd end opts.on("-n", "--no-color", "Turn off colorized output") do @@ -93,24 +98,28 @@ options end end @options = OptParser.parse(ARGV) -if @options.has_key?(:output_log) +if @options.key?(:output_log) LOGGER = File.open(@options[:output_log], "a+") else LOGGER = STDOUT end -if not @options.has_key?(:policy) +if not @options.key?(:policy) @options[:policy] = DEFAULT_POLICY end +if @options.key?(:move_dir) and not File.directory?(@options[:move_dir]) + abort "Error: #{@options[:move_dir]} is not a valid directory." +end + String.disable_colorization @options[:disable_colors] load_config_file(@options[:config_file] || DEFAULT_CONFIG_FILE) -unless SECURITY_POLICIES.has_key?("POLICY_#{@options[:policy].upcase}") +unless SECURITY_POLICIES.key?("POLICY_#{@options[:policy].upcase}") abort "Undeclared policy `#{@options[:policy]}'" end if ARGV.empty? abort "Error: No filename was specified. #{$0} --help for details." @@ -122,13 +131,27 @@ LOGGER.puts("[#{Time.now}]".cyan + " #{str.colorize(color)}") end def reject(cause) log("Document rejected by policy `#{@options[:policy]}', caused by #{cause.inspect}.", :red) + + if @options.key?(:move_dir) + quarantine(TARGET, @options[:move_dir]) + end + abort end +def quarantine(file, quarantine_folder) + digest = Digest::SHA256.file(TARGET) + ext = File.extname(TARGET) + dest_name = "#{File.basename(TARGET, ext)}_#{digest}#{ext}" + dest_path = File.join(@options[:move_dir], dest_name) + + FileUtils.move(TARGET, dest_path) +end + def check_rights(*required_rights) current_rights = SECURITY_POLICIES["POLICY_#{@options[:policy].upcase}"] reject(required_rights) if required_rights.any?{|right| current_rights[right.to_s] == false} end @@ -163,11 +186,11 @@ end def analyze_annotation(annot, _level = 0) check_rights(:allowAnnotations) - if annot.is_a?(Origami::Dictionary) and annot.has_key?(:Subtype) + if annot.is_a?(Origami::Dictionary) and annot.key?(:Subtype) case annot[:Subtype].solve.value when :FileAttachment check_rights(:allowAttachments, :allowFileAttachmentAnnotation) when :Sound @@ -184,11 +207,11 @@ when :"3D" check_rights(:allow3DAnnotation) # 3D annotation might pull in JavaScript for real-time driven behavior. - if annot.has_key?(:"3DD") + if annot.key?(:"3DD") dd = annot[:"3DD"].solve u3dstream = nil case dd when Origami::Stream @@ -198,11 +221,11 @@ end if u3dstream and u3dstream.key?(:OnInstantiate) check_rights(:allowJS) - if annot.has_key?(:"3DA") # is 3d view instantiated automatically? + if annot.key?(:"3DA") # is 3d view instantiated automatically? u3dactiv = annot[:"3DA"].solve check_rights(:allowJSAtOpening) if u3dactiv.is_a?(Origami::Dictionary) and (u3dactiv[:A] == :PO or u3dactiv[:A] == :PV) end end @@ -221,17 +244,17 @@ text_prefix = " " * 2 * (level + 1) + "." * (level + 1) if page.is_a?(Origami::Dictionary) # # Checking page additional actions. # - if page.has_key?(:AA) + if page.key?(:AA) if page.AA.is_a?(Origami::Dictionary) log(text_prefix + " Page has an action dictionary.") aa = Origami::Page::AdditionalActions.new(page.AA); aa.parent = page.AA.parent - analyze_action(aa.O, true, level + 1) if aa.has_key?(:O) - analyze_action(aa.C, false, level + 1) if aa.has_key?(:C) + analyze_action(aa.O, true, level + 1) if aa.key?(:O) + analyze_action(aa.C, false, level + 1) if aa.key?(:C) end end # # Looking for page annotations. @@ -278,11 +301,11 @@ when :GoToR check_rights(:allowGoToRAction) when :Thread - check_rights(:allowGoToRAction) if action.has_key?(:F) + check_rights(:allowGoToRAction) if action.key?(:F) when :URI check_rights(:allowURIAction) when :SubmitForm @@ -305,11 +328,11 @@ when :GoTo3DView check_rights(:allow3DAnnotation,:allowGoTo3DAction) end - if action.has_key?(:Next) + if action.key?(:Next) log(text_prefix + "This action is chained to another action!") check_rights(:allowChainedActions) analyze_action(action.Next) end @@ -327,11 +350,11 @@ end begin log("PDFcop is running on target `#{TARGET}', policy = `#{@options[:policy]}'", :green) log(" File size: #{File.size(TARGET)} bytes", :magenta) - log(" MD5: #{Digest::MD5.hexdigest(File.read(TARGET))}", :magenta) + log(" SHA256: #{Digest::SHA256.file(TARGET)}", :magenta) @pdf = Origami::PDF.read(TARGET, verbosity: Origami::Parser::VERBOSE_QUIET, ignore_errors: SECURITY_POLICIES["POLICY_#{@options[:policy].upcase}"]['allowParserErrors'], decrypt: SECURITY_POLICIES["POLICY_#{@options[:policy].upcase}"]['allowEncryption'], @@ -347,35 +370,35 @@ log("> Inspecting document catalog...", :yellow) catalog = @pdf.Catalog reject("Invalid document catalog") unless catalog.is_a?(Origami::Catalog) - if catalog.has_key?(:OpenAction) + if catalog.key?(:OpenAction) log(" . OpenAction entry = YES") check_rights(:allowOpenAction) action = catalog.OpenAction analyze_action(action, true, 1) end - if catalog.has_key?(:AA) + if catalog.key?(:AA) if catalog.AA.is_a?(Origami::Dictionary) aa = Origami::CatalogAdditionalActions.new(catalog.AA); aa.parent = catalog; log(" . Additional actions dictionary = YES") - analyze_action(aa.WC, false, 1) if aa.has_key?(:WC) - analyze_action(aa.WS, false, 1) if aa.has_key?(:WS) - analyze_action(aa.DS, false, 1) if aa.has_key?(:DS) - analyze_action(aa.WP, false, 1) if aa.has_key?(:WP) - analyze_action(aa.DP, false, 1) if aa.has_key?(:DP) + analyze_action(aa.WC, false, 1) if aa.key?(:WC) + analyze_action(aa.WS, false, 1) if aa.key?(:WS) + analyze_action(aa.DS, false, 1) if aa.key?(:DS) + analyze_action(aa.WP, false, 1) if aa.key?(:WP) + analyze_action(aa.DP, false, 1) if aa.key?(:DP) end end - if catalog.has_key?(:AcroForm) + if catalog.key?(:AcroForm) acroform = catalog.AcroForm if acroform.is_a?(Origami::Dictionary) log(" . AcroForm = YES") check_rights(:allowAcroForms) - if acroform.has_key?(:XFA) + if acroform.key?(:XFA) log(" . XFA = YES") check_rights(:allowXFAForms) analyze_xfa_forms(acroform[:XFA].solve) end @@ -398,10 +421,10 @@ analyze_page(page, 1) end log("> Inspecting document streams...", :yellow) @pdf.indirect_objects.find_all{|obj| obj.is_a?(Origami::Stream)}.each do |stream| - if stream.dictionary.has_key?(:Filter) + if stream.dictionary.key?(:Filter) filters = stream.Filter filters = [ filters ] if filters.is_a?(Origami::Name) if filters.is_a?(Origami::Array) filters.each do |filter|