Commit 956c8f643219778c445d7062d1d0e7e3b96c7676

Authored by Jay Berkenbilt
1 parent ad96e1ad

Obscure bug fix copying foreign streams in special cases (fixes #449)

Specifically, if a stream had its stream data replaced and had
indirect /Filter or /DecodeParms, it would result in non-silent loss
of data and/or internal error.
ChangeLog
1 2020-10-21 Jay Berkenbilt <ejb@ql.org> 1 2020-10-21 Jay Berkenbilt <ejb@ql.org>
2 2
  3 + * Bug fix: properly handle copying foreign streams that have
  4 + indirect /Filter or /DecodeParms keys when stream data has been
  5 + replaced. The circumstances leading to this bug are very unusual
  6 + but would cause qpdf to either generate an internal error or some
  7 + other kind of warning situation if it would occur. Fixes #449.
  8 +
3 * Qpdf's build and CI has been migrated from Azure Pipelines 9 * Qpdf's build and CI has been migrated from Azure Pipelines
4 (Azure Devops) to GitHub Actions. 10 (Azure Devops) to GitHub Actions.
5 11
@@ -7,7 +7,6 @@ Candidates for upcoming release @@ -7,7 +7,6 @@ Candidates for upcoming release
7 * Open "next" issues 7 * Open "next" issues
8 * bugs 8 * bugs
9 * #473: zsh completion with directories 9 * #473: zsh completion with directories
10 - * #449: internal error with case to reproduce (from pikepdf)  
11 * #444: concatenated stream/whitespace bug 10 * #444: concatenated stream/whitespace bug
12 * Non-bugs 11 * Non-bugs
13 * #446: recognize edited QDF files 12 * #446: recognize edited QDF files
libqpdf/QPDF.cc
@@ -2313,7 +2313,8 @@ QPDF::reserveObjects(QPDFObjectHandle foreign, ObjCopier&amp; obj_copier, @@ -2313,7 +2313,8 @@ QPDF::reserveObjects(QPDFObjectHandle foreign, ObjCopier&amp; obj_copier,
2313 if (foreign.isReserved()) 2313 if (foreign.isReserved())
2314 { 2314 {
2315 throw std::logic_error( 2315 throw std::logic_error(
2316 - "QPDF: attempting to copy a foreign reserved object"); 2316 + "QPDF: attempting to copy a foreign reserved object: " +
  2317 + QUtil::int_to_string(foreign.getObjectID()));
2317 } 2318 }
2318 2319
2319 if (foreign.isPagesObject()) 2320 if (foreign.isPagesObject())
@@ -2486,6 +2487,16 @@ QPDF::replaceForeignIndirectObjects( @@ -2486,6 +2487,16 @@ QPDF::replaceForeignIndirectObjects(
2486 } 2487 }
2487 PointerHolder<Buffer> stream_buffer = 2488 PointerHolder<Buffer> stream_buffer =
2488 stream->getStreamDataBuffer(); 2489 stream->getStreamDataBuffer();
  2490 + // Note: at this stage, dictionary keys may still be reserved.
  2491 + // We have to handle that explicitly if we access anything.
  2492 + auto get_as_direct = [&old_dict] (std::string const& key) {
  2493 + QPDFObjectHandle obj = old_dict.getKey(key);
  2494 + obj.makeDirect();
  2495 + return obj;
  2496 + };
  2497 +
  2498 + QPDFObjectHandle filter = get_as_direct("/Filter");
  2499 + QPDFObjectHandle decode_parms = get_as_direct("/DecodeParms");
2489 if ((foreign_stream_qpdf->m->immediate_copy_from) && 2500 if ((foreign_stream_qpdf->m->immediate_copy_from) &&
2490 (stream_buffer.getPointer() == 0)) 2501 (stream_buffer.getPointer() == 0))
2491 { 2502 {
@@ -2495,8 +2506,7 @@ QPDF::replaceForeignIndirectObjects( @@ -2495,8 +2506,7 @@ QPDF::replaceForeignIndirectObjects(
2495 // have to keep duplicating the memory. 2506 // have to keep duplicating the memory.
2496 QTC::TC("qpdf", "QPDF immediate copy stream data"); 2507 QTC::TC("qpdf", "QPDF immediate copy stream data");
2497 foreign.replaceStreamData(foreign.getRawStreamData(), 2508 foreign.replaceStreamData(foreign.getRawStreamData(),
2498 - dict.getKey("/Filter"),  
2499 - dict.getKey("/DecodeParms")); 2509 + filter, decode_parms);
2500 stream_buffer = stream->getStreamDataBuffer(); 2510 stream_buffer = stream->getStreamDataBuffer();
2501 } 2511 }
2502 PointerHolder<QPDFObjectHandle::StreamDataProvider> stream_provider = 2512 PointerHolder<QPDFObjectHandle::StreamDataProvider> stream_provider =
@@ -2504,9 +2514,7 @@ QPDF::replaceForeignIndirectObjects( @@ -2504,9 +2514,7 @@ QPDF::replaceForeignIndirectObjects(
2504 if (stream_buffer.getPointer()) 2514 if (stream_buffer.getPointer())
2505 { 2515 {
2506 QTC::TC("qpdf", "QPDF copy foreign stream with buffer"); 2516 QTC::TC("qpdf", "QPDF copy foreign stream with buffer");
2507 - result.replaceStreamData(stream_buffer,  
2508 - dict.getKey("/Filter"),  
2509 - dict.getKey("/DecodeParms")); 2517 + result.replaceStreamData(stream_buffer, filter, decode_parms);
2510 } 2518 }
2511 else if (stream_provider.getPointer()) 2519 else if (stream_provider.getPointer())
2512 { 2520 {
@@ -2515,8 +2523,7 @@ QPDF::replaceForeignIndirectObjects( @@ -2515,8 +2523,7 @@ QPDF::replaceForeignIndirectObjects(
2515 this->m->copied_stream_data_provider->registerForeignStream( 2523 this->m->copied_stream_data_provider->registerForeignStream(
2516 local_og, foreign); 2524 local_og, foreign);
2517 result.replaceStreamData(this->m->copied_streams, 2525 result.replaceStreamData(this->m->copied_streams,
2518 - dict.getKey("/Filter"),  
2519 - dict.getKey("/DecodeParms")); 2526 + filter, decode_parms);
2520 } 2527 }
2521 else 2528 else
2522 { 2529 {
@@ -2534,8 +2541,7 @@ QPDF::replaceForeignIndirectObjects( @@ -2534,8 +2541,7 @@ QPDF::replaceForeignIndirectObjects(
2534 this->m->copied_stream_data_provider->registerForeignStream( 2541 this->m->copied_stream_data_provider->registerForeignStream(
2535 local_og, foreign_stream_data); 2542 local_og, foreign_stream_data);
2536 result.replaceStreamData(this->m->copied_streams, 2543 result.replaceStreamData(this->m->copied_streams,
2537 - dict.getKey("/Filter"),  
2538 - dict.getKey("/DecodeParms")); 2544 + filter, decode_parms);
2539 } 2545 }
2540 } 2546 }
2541 else 2547 else
make/msvc.mk
@@ -66,7 +66,7 @@ endef @@ -66,7 +66,7 @@ endef
66 # Usage: $(call makelib,objs,library,ldflags,libs,current,revision,age) 66 # Usage: $(call makelib,objs,library,ldflags,libs,current,revision,age)
67 define makelib 67 define makelib
68 cl -nologo -O2 -Zi -Gy -EHsc -MD -LD -Fe$(basename $(2))$(shell expr $(5) - $(7)).dll $(1) \ 68 cl -nologo -O2 -Zi -Gy -EHsc -MD -LD -Fe$(basename $(2))$(shell expr $(5) - $(7)).dll $(1) \
69 - -link -SUBSYSTEM:CONSOLE,5.01 -incremental:no \ 69 + -link -SUBSYSTEM:CONSOLE -incremental:no \
70 $(foreach L,$(subst -L,,$(3)),-LIBPATH:$(L)) \ 70 $(foreach L,$(subst -L,,$(3)),-LIBPATH:$(L)) \
71 $(foreach L,$(subst -l,,$(4)),$(L).lib) 71 $(foreach L,$(subst -l,,$(4)),$(L).lib)
72 if [ -f $(basename $(2))$(shell expr $(5) - $(7)).dll.manifest ]; then \ 72 if [ -f $(basename $(2))$(shell expr $(5) - $(7)).dll.manifest ]; then \
@@ -81,7 +81,7 @@ endef @@ -81,7 +81,7 @@ endef
81 define makebin 81 define makebin
82 cl -nologo -O2 -Zi -Gy -EHsc -MD $(1) \ 82 cl -nologo -O2 -Zi -Gy -EHsc -MD $(1) \
83 $(if $(5),$(5),$(WINDOWS_MAIN_XLINK_FLAGS)) \ 83 $(if $(5),$(5),$(WINDOWS_MAIN_XLINK_FLAGS)) \
84 - -link -SUBSYSTEM:CONSOLE,5.01 -incremental:no -OUT:$(2) \ 84 + -link -SUBSYSTEM:CONSOLE -incremental:no -OUT:$(2) \
85 $(foreach L,$(subst -L,,$(3)),-LIBPATH:$(L)) \ 85 $(foreach L,$(subst -L,,$(3)),-LIBPATH:$(L)) \
86 $(foreach L,$(subst -l,,$(4)),$(L).lib) 86 $(foreach L,$(subst -l,,$(4)),$(L).lib)
87 if [ -f $(2).manifest ]; then \ 87 if [ -f $(2).manifest ]; then \
qpdf/qtest/qpdf.test
@@ -2417,7 +2417,7 @@ $td-&gt;runtest(&quot;check output&quot;, @@ -2417,7 +2417,7 @@ $td-&gt;runtest(&quot;check output&quot;,
2417 show_ntests(); 2417 show_ntests();
2418 # ---------- 2418 # ----------
2419 $td->notify("--- Copy Foreign Objects ---"); 2419 $td->notify("--- Copy Foreign Objects ---");
2420 -$n_tests += 7; 2420 +$n_tests += 10;
2421 2421
2422 foreach my $d ([25, 1], [26, 2], [27, 3]) 2422 foreach my $d ([25, 1], [26, 2], [27, 3])
2423 { 2423 {
@@ -2438,6 +2438,19 @@ $td-&gt;runtest(&quot;copy objects error&quot;, @@ -2438,6 +2438,19 @@ $td-&gt;runtest(&quot;copy objects error&quot;,
2438 {$td->FILE => "copy-foreign-objects-errors.out", 2438 {$td->FILE => "copy-foreign-objects-errors.out",
2439 $td->EXIT_STATUS => 0}, 2439 $td->EXIT_STATUS => 0},
2440 $td->NORMALIZE_NEWLINES); 2440 $td->NORMALIZE_NEWLINES);
  2441 +
  2442 +$td->runtest("indirect filters",
  2443 + {$td->COMMAND => "test_driver 69 indirect-filter.pdf"},
  2444 + {$td->STRING => "test 69 done\n", $td->EXIT_STATUS => 0},
  2445 + $td->NORMALIZE_NEWLINES);
  2446 +foreach my $i (0, 1)
  2447 +{
  2448 + $td->runtest("check output",
  2449 + {$td->FILE => "auto-$i.pdf"},
  2450 + {$td->FILE => "indirect-filter-out-$i.pdf"});
  2451 +}
  2452 +
  2453 +
2441 show_ntests(); 2454 show_ntests();
2442 # ---------- 2455 # ----------
2443 $td->notify("--- Error Condition Tests ---"); 2456 $td->notify("--- Error Condition Tests ---");
qpdf/qtest/qpdf/indirect-filter-out-0.pdf 0 โ†’ 100644
No preview for this file type
qpdf/qtest/qpdf/indirect-filter-out-1.pdf 0 โ†’ 100644
No preview for this file type
qpdf/qtest/qpdf/indirect-filter.pdf 0 โ†’ 100644
No preview for this file type
qpdf/test_driver.cc
@@ -2195,6 +2195,22 @@ void runtest(int n, char const* filename1, char const* arg2) @@ -2195,6 +2195,22 @@ void runtest(int n, char const* filename1, char const* arg2)
2195 std::cout << "raw stream data okay" << std::endl; 2195 std::cout << "raw stream data okay" << std::endl;
2196 } 2196 }
2197 } 2197 }
  2198 + else if (n == 69)
  2199 + {
  2200 + pdf.setImmediateCopyFrom(true);
  2201 + auto pages = pdf.getAllPages();
  2202 + for (size_t i = 0; i < pages.size(); ++i)
  2203 + {
  2204 + QPDF out;
  2205 + out.emptyPDF();
  2206 + out.addPage(pages.at(i), false);
  2207 + std::string outname = std::string("auto-") +
  2208 + QUtil::uint_to_string(i) + ".pdf";
  2209 + QPDFWriter w(out, outname.c_str());
  2210 + w.setStaticID(true);
  2211 + w.write();
  2212 + }
  2213 + }
2198 else 2214 else
2199 { 2215 {
2200 throw std::runtime_error(std::string("invalid test ") + 2216 throw std::runtime_error(std::string("invalid test ") +