From ef27365d720095bf0c5a44f6f5b7394f70b7cd00 Mon Sep 17 00:00:00 2001 From: funkypierre Date: Wed, 30 Mar 2022 16:29:45 +0200 Subject: [PATCH 1/2] swallow index out of range error --- .tool-versions | 1 + lib/combine_pdf/parser.rb | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .tool-versions diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 0000000..38ec151 --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +ruby 3.0.3 diff --git a/lib/combine_pdf/parser.rb b/lib/combine_pdf/parser.rb index 7ec0e5e..7cebd14 100644 --- a/lib/combine_pdf/parser.rb +++ b/lib/combine_pdf/parser.rb @@ -361,7 +361,11 @@ def _parse_ # advance by the publshed stream length (if any) old_pos = @scanner.pos if(out.last.is_a?(Hash) && out.last[:Length].is_a?(Integer) && out.last[:Length] > 2) - @scanner.pos += out.last[:Length] - 2 + begin + @scanner.pos += out.last[:Length] - 2 + rescue RangeError + # do nothing + end end # the following was dicarded because some PDF files didn't have an EOL marker as required From 3a7dda189f8e5df8d137bccbc3459e4be4c63763 Mon Sep 17 00:00:00 2001 From: Edouard Piron <34060090+BigBigDoudou@users.noreply.github.com> Date: Tue, 20 Jun 2023 11:24:39 +0200 Subject: [PATCH 2/2] Merge source (#1) * introduce raise_on_encrypted option * add some detail to the exception * update the README to specify the raise_on_encrypted option * :knife: the CombinePDF.encrypted? method that is no longer needed * apply patch from @kimyu92 * add guards * docs + cleanup * fix release date in changelog * Adds Github Actions workflow for tests - Allows us to test the gem on multiple versions of Ruby --------- Co-authored-by: Levi Wilson Co-authored-by: Bo Co-authored-by: David Wessman --- .github/workflows/main.yml | 28 ++++++ .travis.yml | 1 + CHANGELOG.md | 4 +- README.md | 2 +- Rakefile | 9 +- combine_pdf.gemspec | 1 + lib/combine_pdf/parser.rb | 4 +- lib/combine_pdf/pdf_public.rb | 5 +- lib/combine_pdf/version.rb | 2 +- test/automated | 92 +++++++++---------- test/combine_pdf/load_test.rb | 48 ++++++++++ test/combine_pdf/renderer_test.rb | 2 - test/fixtures/files/sample_encrypted_pdf.pdf | Bin 0 -> 13250 bytes test/fixtures/files/sample_pdf.pdf | Bin 0 -> 13902 bytes 14 files changed, 144 insertions(+), 54 deletions(-) create mode 100644 .github/workflows/main.yml create mode 100644 .travis.yml create mode 100644 test/combine_pdf/load_test.rb create mode 100644 test/fixtures/files/sample_encrypted_pdf.pdf create mode 100644 test/fixtures/files/sample_pdf.pdf diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..6e2e1e1 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,28 @@ +name: Main +on: + push: + +jobs: + tests: + name: Tests + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + ruby: ["2.7", "3.0", "3.1", "3.2"] + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Setup Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + bundler-cache: true + + - name: Generate lockfile + run: bundle lock + + - name: Run tests + run: bundle exec rake test diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..f819a51 --- /dev/null +++ b/.travis.yml @@ -0,0 +1 @@ +language: ruby diff --git a/CHANGELOG.md b/CHANGELOG.md index e31a92f..3a9b229 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Change Log -*** +#### Change log v.1.0.23 (2023-04-04) + +**Feature**: merged PR #177 for the `raise_on_encrypted: true` option support. Credit to @leviwilson and @kimyu92 for the PR. #### Change log v.1.0.22 diff --git a/README.md b/README.md index c61045e..6887909 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ Quick rundown: Some links will be lost when ripping pages out of PDF files and merging them with another PDF. -* Some encrypted PDF files (usually the ones you can't view without a password) will fail quietly instead of noisily. +* Some encrypted PDF files (usually the ones you can't view without a password) will fail quietly instead of noisily. If you prefer to choose the noisy route, you can specify the `raise_on_encrypted` option using `CombinePDF.load(pdf_file, raise_on_encrypted: true)` which will raise a `CombinePDF::EncryptionError`. * Sometimes the CombinePDF will raise an exception even if the PDF could be parsed (i.e., when PDF optional content exists)... I find it better to err on the side of caution, although for optional content PDFs an exception is avoidable using `CombinePDF.load(pdf_file, allow_optional_content: true)`. diff --git a/Rakefile b/Rakefile index 809eb56..df70ff0 100644 --- a/Rakefile +++ b/Rakefile @@ -1,2 +1,9 @@ -require "bundler/gem_tasks" +require 'bundler/gem_tasks' +require 'rake/testtask' + +task default: 'test' + +Rake::TestTask.new do |t| + t.test_files = FileList['test/**/*_test.rb'] +end diff --git a/combine_pdf.gemspec b/combine_pdf.gemspec index e89dfb4..cc324f1 100644 --- a/combine_pdf.gemspec +++ b/combine_pdf.gemspec @@ -24,4 +24,5 @@ Gem::Specification.new do |spec| # spec.add_development_dependency "bundler", ">= 1.7" spec.add_development_dependency "rake", ">= 12.3.3" spec.add_development_dependency "minitest" + spec.add_development_dependency "minitest-around" end diff --git a/lib/combine_pdf/parser.rb b/lib/combine_pdf/parser.rb index 7cebd14..a3a0d69 100644 --- a/lib/combine_pdf/parser.rb +++ b/lib/combine_pdf/parser.rb @@ -33,7 +33,7 @@ class PDFParser # they are mainly to used to know if the file is (was) encrypted and to get more details. attr_reader :info_object, :root_object, :names_object, :forms_object, :outlines_object, :metadata - attr_reader :allow_optional_content + attr_reader :allow_optional_content, :raise_on_encrypted # when creating a parser, it is important to set the data (String) we wish to parse. # # the data is required and it is not possible to set the data at a later stage @@ -58,6 +58,7 @@ def initialize(string, options = {}) @version = nil @scanner = nil @allow_optional_content = options[:allow_optional_content] + @raise_on_encrypted = options[:raise_on_encrypted] end # parse the data in the new parser (the data already set through the initialize / new method) @@ -96,6 +97,7 @@ def parse end if @root_object[:Encrypt] + raise EncryptionError, 'the file is encrypted' if @raise_on_encrypted # change_references_to_actual_values @root_object warn 'PDF is Encrypted! Attempting to decrypt - not yet fully supported.' decryptor = PDFDecrypt.new @parsed, @root_object diff --git a/lib/combine_pdf/pdf_public.rb b/lib/combine_pdf/pdf_public.rb index 95f5872..d2e732e 100644 --- a/lib/combine_pdf/pdf_public.rb +++ b/lib/combine_pdf/pdf_public.rb @@ -96,6 +96,7 @@ def initialize(parser = nil) parser ||= PDFParser.new('') raise TypeError, "initialization error, expecting CombinePDF::PDFParser or nil, but got #{parser.class.name}" unless parser.is_a? PDFParser @objects = parser.parse + # remove any existing id's remove_old_ids # set data from parser @@ -261,10 +262,12 @@ def fonts(limit_to_type0 = false) next if !r r = r[:referenced_object] if r[:referenced_object] r = r[:Font] - next if !r + next if !r r = r[:referenced_object] if r[:referenced_object] r.values.each do |f| + next if f.class != Hash f = f[:referenced_object] if f[:referenced_object] + next if f.class != Hash if (limit_to_type0 || f[:Subtype] == :Type0) && f[:Type] == :Font && !fonts_array.include?(f) fonts_array << f end diff --git a/lib/combine_pdf/version.rb b/lib/combine_pdf/version.rb index 5b3a91e..50ad470 100644 --- a/lib/combine_pdf/version.rb +++ b/lib/combine_pdf/version.rb @@ -1,3 +1,3 @@ module CombinePDF - VERSION = '1.0.22'.freeze + VERSION = '1.0.23'.freeze end diff --git a/test/automated b/test/automated index ff884d2..584b94d 100755 --- a/test/automated +++ b/test/automated @@ -5,7 +5,7 @@ $VERBOSE = true require 'benchmark' $LOAD_PATH.unshift File.expand_path(File.join('..', '..', 'lib'), __FILE__) require 'combine_pdf' -require 'bundler/setup' +# require 'bundler/setup' # You can add fixtures and/or initialization code here to make experimenting # with your gem easier. You can also use a different console, if you like. @@ -14,54 +14,54 @@ require 'bundler/setup' # require "pry" # Pry.start -pdf = CombinePDF.load "./Ruby/test\ pdfs/filled_form.pdf" +pdf = CombinePDF.load "../../test\ pdfs/filled_form.pdf" pdf.save '01_check_radio_buttuns.pdf' -pdf = CombinePDF.load "./Ruby/test\ pdfs/filled_form.pdf" -pdf << CombinePDF.load("./Ruby/test\ pdfs/empty_form.pdf") -pdf << CombinePDF.load("./Ruby/test\ pdfs/filled_form.pdf") +pdf = CombinePDF.load "../../test\ pdfs/filled_form.pdf" +pdf << CombinePDF.load("../../test\ pdfs/empty_form.pdf") +pdf << CombinePDF.load("../../test\ pdfs/filled_form.pdf") pdf.save '02_check_form_unification_middle_is_empty.pdf' -pdf = CombinePDF.load "./Ruby/test\ pdfs/check_form_data__objstreams_w_versions.pdf" +pdf = CombinePDF.load "../../test\ pdfs/check_form_data__objstreams_w_versions.pdf" pdf.save '02_01_check_form_data_ordering_issue.pdf' -pdf = CombinePDF.load './Ruby/test pdfs/share-font-background.pdf' -pdf2 = CombinePDF.load './Ruby/test pdfs/share-font-foreground.pdf' +pdf = CombinePDF.load '../../test pdfs/share-font-background.pdf' +pdf2 = CombinePDF.load '../../test pdfs/share-font-foreground.pdf' i = 0 pdf.pages.each { |pg| pg << pdf2.pages[i] } pdf.save '03_check_font_conflict.pdf' -pdf = CombinePDF.load './Ruby/test pdfs/nil_1.pdf' -pdf2 = CombinePDF.load './Ruby/test pdfs/nil_2.pdf' +pdf = CombinePDF.load '../../test pdfs/nil_1.pdf' +pdf2 = CombinePDF.load '../../test pdfs/nil_2.pdf' pdf << pdf2 pdf.save '03_01_nil_value_conflict.pdf' -pdf = CombinePDF.load './Ruby/test pdfs/space_after_streram_keyword.pdf' +pdf = CombinePDF.load '../../test pdfs/space_after_streram_keyword.pdf' pdf.save '03_02_extra_space_after_stream_keyword.pdf' -pdf = CombinePDF.load './Ruby/test pdfs/nested_difference.pdf' +pdf = CombinePDF.load '../../test pdfs/nested_difference.pdf' pdf.save '03_03_nested_difference.pdf' -pdf = CombinePDF.load './Ruby/test pdfs/names_go_haywire_0.pdf' -pdf << CombinePDF.load('./Ruby/test pdfs/names_go_haywire_1.pdf') +pdf = CombinePDF.load '../../test pdfs/names_go_haywire_0.pdf' +pdf << CombinePDF.load('../../test pdfs/names_go_haywire_1.pdf') pdf.save '04_check_view_and_names_reference.pdf' -pdf = CombinePDF.load('./Ruby/test pdfs/outlines/self_merge_err.pdf') +pdf = CombinePDF.load('../../test pdfs/outlines/self_merge_err.pdf') pdf.save '05_x1_scribus_test.pdf' -pdf = CombinePDF.load('./Ruby/test pdfs/outlines/self_merge_err.pdf') -pdf << CombinePDF.load('./Ruby/test pdfs/outlines/self_merge_err.pdf') +pdf = CombinePDF.load('../../test pdfs/outlines/self_merge_err.pdf') +pdf << CombinePDF.load('../../test pdfs/outlines/self_merge_err.pdf') pdf.save '05_x2_scribus_test.pdf' -# pdf = CombinePDF.load "./Ruby/test pdfs/named_dest.pdf";nil -# pdf.save '05_check_named_dest_links.pdf' # this will take a while -# pdf = CombinePDF.load "./Ruby/test pdfs/named_dest.pdf";nil -pdf << CombinePDF.load('./Ruby/test pdfs/named_dest.pdf'); nil +pdf = CombinePDF.load "../../test pdfs/outlines/named_dest.pdf";nil +pdf.save '05_check_named_dest_links.pdf' # this will take a while +pdf = CombinePDF.load "../../test pdfs/outlines/named_dest.pdf";nil +pdf << CombinePDF.load('../../test pdfs/outlines/named_dest.pdf'); nil pdf.save '05_1_timeless_check_named_dest_links.pdf' # never ends... :-( -pdf = CombinePDF.load './Ruby/test pdfs/outline_small.pdf' -pdf << CombinePDF.load('./Ruby/test pdfs/outline_small.pdf') +pdf = CombinePDF.load '../../test pdfs/outline_small.pdf' +pdf << CombinePDF.load('../../test pdfs/outline_small.pdf') pdf.save '06_check_links_to_second_copy.pdf' -lists = %w(./Ruby/test\ pdfs/outlines/self_merge_err.pdf ./Ruby/test\ pdfs/outlines/big_toc.pdf ./Ruby/test\ pdfs/outlines/bigger_toc.pdf ./Ruby/test\ pdfs/outlines/named_dest_no_toc.pdf ./Ruby/test\ pdfs/outlines/named_dest_no_toc2.pdf ./Ruby/test\ pdfs/outlines/named_dest.pdf ./Ruby/test\ pdfs/outlines/named_dest2.pdf) +lists = %w(../../test\ pdfs/outlines/self_merge_err.pdf ../../test\ pdfs/outlines/big_toc.pdf ../../test\ pdfs/outlines/bigger_toc.pdf ../../test\ pdfs/outlines/named_dest_no_toc.pdf ../../test\ pdfs/outlines/named_dest_no_toc2.pdf ../../test\ pdfs/outlines/named_dest.pdf ../../test\ pdfs/outlines/named_dest2.pdf) i = 0 lists.each do |n| @@ -92,19 +92,19 @@ pdf.number_pages(start_at: 1, pdf.save('07_named destinations_numbered.pdf') -CombinePDF.load("./Ruby/test\ pdfs/Scribus-unknown_err.pdf").save '08_1-unknown-err-empty-str.pdf' -CombinePDF.load("./Ruby/test\ pdfs/Scribus-unknown_err2.pdf").save '08_2-unknown-err-empty-str.pdf' -CombinePDF.load("./Ruby/test\ pdfs/Scribus-unknown_err3.pdf").save '08_3-unknown-err-empty-str.pdf' -CombinePDF.load("./Ruby/test\ pdfs/xref_in_middle.pdf").save '08_4-xref-in-middle.pdf' -CombinePDF.load("./Ruby/test\ pdfs/xref_split.pdf").save '08_5-xref-fragmented.pdf' +CombinePDF.load("../../test\ pdfs/Scribus-unknown_err.pdf").save '08_1-unknown-err-empty-str.pdf' +CombinePDF.load("../../test\ pdfs/Scribus-unknown_err2.pdf").save '08_2-unknown-err-empty-str.pdf' +CombinePDF.load("../../test\ pdfs/Scribus-unknown_err3.pdf").save '08_3-unknown-err-empty-str.pdf' +CombinePDF.load("../../test\ pdfs/xref_in_middle.pdf").save '08_4-xref-in-middle.pdf' +CombinePDF.load("../../test\ pdfs/xref_split.pdf").save '08_5-xref-fragmented.pdf' -CombinePDF.load("/Users/2Be/Ruby/test\ pdfs/nil_object.pdf").save('09_nil_in_parsed_array.pdf') +CombinePDF.load("../../test\ pdfs/nil_object.pdf").save('09_nil_in_parsed_array.pdf') -encrypted = [ "./Ruby/test\ pdfs/pdf-reader/encrypted_version4_revision4_128bit_aes_user_pass_apples_enc_metadata.pdf", - "./Ruby/test\ pdfs/AESv2\ encrypted.pdf", - "./Ruby/test\ pdfs/pdf-reader/encrypted_version2_revision3_128bit_rc4_blank_user_pass.pdf", - "./Ruby/test\ pdfs/AES\ enc.pdf", - "./Ruby/test\ pdfs/RC4\ enc.pdf"] +encrypted = [ "../../test\ pdfs/pdf-reader/encrypted_version4_revision4_128bit_aes_user_pass_apples_enc_metadata.pdf", + "../../test\ pdfs/AESv2\ encrypted.pdf", + "../../test\ pdfs/pdf-reader/encrypted_version2_revision3_128bit_rc4_blank_user_pass.pdf", + "../../test\ pdfs/AES\ enc.pdf", + "../../test\ pdfs/RC4\ enc.pdf"] encrypted.length.times do |i| fname = File.basename encrypted[i] @@ -132,17 +132,17 @@ pdf.save('11_AcrobatReader_is_unique_page.pdf') puts GC.stat.inspect # unify = [ -# "./Ruby/test\ pdfs/AESv2\ encrypted.pdf", -# "./Ruby/test\ pdfs/data-in-comment.pdf", -# "./Ruby/test\ pdfs/file_name.pdf", -# "./Ruby/test\ pdfs/garbage_after_eof.pdf", -# "./Ruby/test\ pdfs/Many\ comments.pdf", -# "./Ruby/test\ pdfs/nested\ contents\ array.PDF", -# "./Ruby/test\ pdfs/nested_resources.pdf", -# "./Ruby/test\ pdfs/original-missing-endobje.pdf", -# "./Ruby/test\ pdfs/original-multi-issue.pdf", -# "./Ruby/test\ pdfs/page_stap_nil_secure.pdf", -# "./Ruby/test\ pdfs/referenced\ decryption.pdf", +# "../../test\ pdfs/AESv2\ encrypted.pdf", +# "../../test\ pdfs/data-in-comment.pdf", +# "../../test\ pdfs/file_name.pdf", +# "../../test\ pdfs/garbage_after_eof.pdf", +# "../../test\ pdfs/Many\ comments.pdf", +# "../../test\ pdfs/nested\ contents\ array.PDF", +# "../../test\ pdfs/nested_resources.pdf", +# "../../test\ pdfs/original-missing-endobje.pdf", +# "../../test\ pdfs/original-multi-issue.pdf", +# "../../test\ pdfs/page_stap_nil_secure.pdf", +# "../../test\ pdfs/referenced\ decryption.pdf", # '', # '', # '', diff --git a/test/combine_pdf/load_test.rb b/test/combine_pdf/load_test.rb new file mode 100644 index 0000000..47ee57a --- /dev/null +++ b/test/combine_pdf/load_test.rb @@ -0,0 +1,48 @@ +require 'bundler/setup' +require 'minitest/autorun' +require 'minitest/around/spec' +require 'combine_pdf' + +describe 'CombinePDF.load' do + let(:options) { {} } + + subject { CombinePDF.load "test/fixtures/files/#{file}", options } + + describe 'raise_on_encrypted option' do + let(:file) { 'sample_encrypted_pdf.pdf' } + let(:options) { { raise_on_encrypted: raise_on_encrypted } } + + describe 'when raise_on_encrypted: true' do + let(:raise_on_encrypted) { true } + + describe 'with encrypted file' do + it('raises an CombinePDF::EncryptionError') do + error = assert_raises(CombinePDF::EncryptionError) { subject } + assert_match 'the file is encrypted', error.message + end + end + + describe 'with unencrypted file' do + let(:file) { 'sample_pdf.pdf' } + + it('has a PDF') { assert_instance_of CombinePDF::PDF, subject } + end + end + + describe 'when raise_on_encrypted: false' do + let(:raise_on_encrypted) { false } + + describe 'with encrypted file' do + it('does not raise an CombinePDF::EncryptionError') do + assert_instance_of CombinePDF::PDF, subject + end + end + + describe 'with unencrypted file' do + let(:file) { 'sample_pdf.pdf' } + + it('has a PDF') { assert_instance_of CombinePDF::PDF, subject } + end + end + end +end diff --git a/test/combine_pdf/renderer_test.rb b/test/combine_pdf/renderer_test.rb index 882d20d..53ec41a 100644 --- a/test/combine_pdf/renderer_test.rb +++ b/test/combine_pdf/renderer_test.rb @@ -1,6 +1,4 @@ -require 'bundler/setup' require 'minitest/autorun' -require 'combine_pdf/renderer' class CombinePDFRendererTest < Minitest::Test diff --git a/test/fixtures/files/sample_encrypted_pdf.pdf b/test/fixtures/files/sample_encrypted_pdf.pdf new file mode 100644 index 0000000000000000000000000000000000000000..71db79347343b838318a11726197848282b8df21 GIT binary patch literal 13250 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 dFZqK+{2ww0t_A=A literal 0 HcmV?d00001 diff --git a/test/fixtures/files/sample_pdf.pdf b/test/fixtures/files/sample_pdf.pdf new file mode 100644 index 0000000000000000000000000000000000000000..8b9f0496f89cabc8769ca91403363340054d0f10 GIT binary patch literal 13902 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 F`+uoz5g`Bo literal 0 HcmV?d00001