Commit 956c8f643219778c445d7062d1d0e7e3b96c7676
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.
Showing
9 changed files
with
54 additions
and
14 deletions
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 |
TODO
| @@ -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& obj_copier, | @@ -2313,7 +2313,8 @@ QPDF::reserveObjects(QPDFObjectHandle foreign, ObjCopier& 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->runtest("check output", | @@ -2417,7 +2417,7 @@ $td->runtest("check output", | ||
| 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->runtest("copy objects error", | @@ -2438,6 +2438,19 @@ $td->runtest("copy objects error", | ||
| 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 ") + |