mirror of
https://github.com/qpdf/qpdf.git
synced 2025-02-02 20:08:24 +00:00
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.
This commit is contained in:
parent
ad96e1ad74
commit
956c8f6432
@ -1,5 +1,11 @@
|
|||||||
2020-10-21 Jay Berkenbilt <ejb@ql.org>
|
2020-10-21 Jay Berkenbilt <ejb@ql.org>
|
||||||
|
|
||||||
|
* Bug fix: properly handle copying foreign streams that have
|
||||||
|
indirect /Filter or /DecodeParms keys when stream data has been
|
||||||
|
replaced. The circumstances leading to this bug are very unusual
|
||||||
|
but would cause qpdf to either generate an internal error or some
|
||||||
|
other kind of warning situation if it would occur. Fixes #449.
|
||||||
|
|
||||||
* Qpdf's build and CI has been migrated from Azure Pipelines
|
* Qpdf's build and CI has been migrated from Azure Pipelines
|
||||||
(Azure Devops) to GitHub Actions.
|
(Azure Devops) to GitHub Actions.
|
||||||
|
|
||||||
|
1
TODO
1
TODO
@ -7,7 +7,6 @@ Candidates for upcoming release
|
|||||||
* Open "next" issues
|
* Open "next" issues
|
||||||
* bugs
|
* bugs
|
||||||
* #473: zsh completion with directories
|
* #473: zsh completion with directories
|
||||||
* #449: internal error with case to reproduce (from pikepdf)
|
|
||||||
* #444: concatenated stream/whitespace bug
|
* #444: concatenated stream/whitespace bug
|
||||||
* Non-bugs
|
* Non-bugs
|
||||||
* #446: recognize edited QDF files
|
* #446: recognize edited QDF files
|
||||||
|
@ -2313,7 +2313,8 @@ QPDF::reserveObjects(QPDFObjectHandle foreign, ObjCopier& obj_copier,
|
|||||||
if (foreign.isReserved())
|
if (foreign.isReserved())
|
||||||
{
|
{
|
||||||
throw std::logic_error(
|
throw std::logic_error(
|
||||||
"QPDF: attempting to copy a foreign reserved object");
|
"QPDF: attempting to copy a foreign reserved object: " +
|
||||||
|
QUtil::int_to_string(foreign.getObjectID()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (foreign.isPagesObject())
|
if (foreign.isPagesObject())
|
||||||
@ -2486,6 +2487,16 @@ QPDF::replaceForeignIndirectObjects(
|
|||||||
}
|
}
|
||||||
PointerHolder<Buffer> stream_buffer =
|
PointerHolder<Buffer> stream_buffer =
|
||||||
stream->getStreamDataBuffer();
|
stream->getStreamDataBuffer();
|
||||||
|
// Note: at this stage, dictionary keys may still be reserved.
|
||||||
|
// We have to handle that explicitly if we access anything.
|
||||||
|
auto get_as_direct = [&old_dict] (std::string const& key) {
|
||||||
|
QPDFObjectHandle obj = old_dict.getKey(key);
|
||||||
|
obj.makeDirect();
|
||||||
|
return obj;
|
||||||
|
};
|
||||||
|
|
||||||
|
QPDFObjectHandle filter = get_as_direct("/Filter");
|
||||||
|
QPDFObjectHandle decode_parms = get_as_direct("/DecodeParms");
|
||||||
if ((foreign_stream_qpdf->m->immediate_copy_from) &&
|
if ((foreign_stream_qpdf->m->immediate_copy_from) &&
|
||||||
(stream_buffer.getPointer() == 0))
|
(stream_buffer.getPointer() == 0))
|
||||||
{
|
{
|
||||||
@ -2495,8 +2506,7 @@ QPDF::replaceForeignIndirectObjects(
|
|||||||
// have to keep duplicating the memory.
|
// have to keep duplicating the memory.
|
||||||
QTC::TC("qpdf", "QPDF immediate copy stream data");
|
QTC::TC("qpdf", "QPDF immediate copy stream data");
|
||||||
foreign.replaceStreamData(foreign.getRawStreamData(),
|
foreign.replaceStreamData(foreign.getRawStreamData(),
|
||||||
dict.getKey("/Filter"),
|
filter, decode_parms);
|
||||||
dict.getKey("/DecodeParms"));
|
|
||||||
stream_buffer = stream->getStreamDataBuffer();
|
stream_buffer = stream->getStreamDataBuffer();
|
||||||
}
|
}
|
||||||
PointerHolder<QPDFObjectHandle::StreamDataProvider> stream_provider =
|
PointerHolder<QPDFObjectHandle::StreamDataProvider> stream_provider =
|
||||||
@ -2504,9 +2514,7 @@ QPDF::replaceForeignIndirectObjects(
|
|||||||
if (stream_buffer.getPointer())
|
if (stream_buffer.getPointer())
|
||||||
{
|
{
|
||||||
QTC::TC("qpdf", "QPDF copy foreign stream with buffer");
|
QTC::TC("qpdf", "QPDF copy foreign stream with buffer");
|
||||||
result.replaceStreamData(stream_buffer,
|
result.replaceStreamData(stream_buffer, filter, decode_parms);
|
||||||
dict.getKey("/Filter"),
|
|
||||||
dict.getKey("/DecodeParms"));
|
|
||||||
}
|
}
|
||||||
else if (stream_provider.getPointer())
|
else if (stream_provider.getPointer())
|
||||||
{
|
{
|
||||||
@ -2515,8 +2523,7 @@ QPDF::replaceForeignIndirectObjects(
|
|||||||
this->m->copied_stream_data_provider->registerForeignStream(
|
this->m->copied_stream_data_provider->registerForeignStream(
|
||||||
local_og, foreign);
|
local_og, foreign);
|
||||||
result.replaceStreamData(this->m->copied_streams,
|
result.replaceStreamData(this->m->copied_streams,
|
||||||
dict.getKey("/Filter"),
|
filter, decode_parms);
|
||||||
dict.getKey("/DecodeParms"));
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -2534,8 +2541,7 @@ QPDF::replaceForeignIndirectObjects(
|
|||||||
this->m->copied_stream_data_provider->registerForeignStream(
|
this->m->copied_stream_data_provider->registerForeignStream(
|
||||||
local_og, foreign_stream_data);
|
local_og, foreign_stream_data);
|
||||||
result.replaceStreamData(this->m->copied_streams,
|
result.replaceStreamData(this->m->copied_streams,
|
||||||
dict.getKey("/Filter"),
|
filter, decode_parms);
|
||||||
dict.getKey("/DecodeParms"));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -66,7 +66,7 @@ endef
|
|||||||
# Usage: $(call makelib,objs,library,ldflags,libs,current,revision,age)
|
# Usage: $(call makelib,objs,library,ldflags,libs,current,revision,age)
|
||||||
define makelib
|
define makelib
|
||||||
cl -nologo -O2 -Zi -Gy -EHsc -MD -LD -Fe$(basename $(2))$(shell expr $(5) - $(7)).dll $(1) \
|
cl -nologo -O2 -Zi -Gy -EHsc -MD -LD -Fe$(basename $(2))$(shell expr $(5) - $(7)).dll $(1) \
|
||||||
-link -SUBSYSTEM:CONSOLE,5.01 -incremental:no \
|
-link -SUBSYSTEM:CONSOLE -incremental:no \
|
||||||
$(foreach L,$(subst -L,,$(3)),-LIBPATH:$(L)) \
|
$(foreach L,$(subst -L,,$(3)),-LIBPATH:$(L)) \
|
||||||
$(foreach L,$(subst -l,,$(4)),$(L).lib)
|
$(foreach L,$(subst -l,,$(4)),$(L).lib)
|
||||||
if [ -f $(basename $(2))$(shell expr $(5) - $(7)).dll.manifest ]; then \
|
if [ -f $(basename $(2))$(shell expr $(5) - $(7)).dll.manifest ]; then \
|
||||||
@ -81,7 +81,7 @@ endef
|
|||||||
define makebin
|
define makebin
|
||||||
cl -nologo -O2 -Zi -Gy -EHsc -MD $(1) \
|
cl -nologo -O2 -Zi -Gy -EHsc -MD $(1) \
|
||||||
$(if $(5),$(5),$(WINDOWS_MAIN_XLINK_FLAGS)) \
|
$(if $(5),$(5),$(WINDOWS_MAIN_XLINK_FLAGS)) \
|
||||||
-link -SUBSYSTEM:CONSOLE,5.01 -incremental:no -OUT:$(2) \
|
-link -SUBSYSTEM:CONSOLE -incremental:no -OUT:$(2) \
|
||||||
$(foreach L,$(subst -L,,$(3)),-LIBPATH:$(L)) \
|
$(foreach L,$(subst -L,,$(3)),-LIBPATH:$(L)) \
|
||||||
$(foreach L,$(subst -l,,$(4)),$(L).lib)
|
$(foreach L,$(subst -l,,$(4)),$(L).lib)
|
||||||
if [ -f $(2).manifest ]; then \
|
if [ -f $(2).manifest ]; then \
|
||||||
|
@ -2417,7 +2417,7 @@ $td->runtest("check output",
|
|||||||
show_ntests();
|
show_ntests();
|
||||||
# ----------
|
# ----------
|
||||||
$td->notify("--- Copy Foreign Objects ---");
|
$td->notify("--- Copy Foreign Objects ---");
|
||||||
$n_tests += 7;
|
$n_tests += 10;
|
||||||
|
|
||||||
foreach my $d ([25, 1], [26, 2], [27, 3])
|
foreach my $d ([25, 1], [26, 2], [27, 3])
|
||||||
{
|
{
|
||||||
@ -2438,6 +2438,19 @@ $td->runtest("copy objects error",
|
|||||||
{$td->FILE => "copy-foreign-objects-errors.out",
|
{$td->FILE => "copy-foreign-objects-errors.out",
|
||||||
$td->EXIT_STATUS => 0},
|
$td->EXIT_STATUS => 0},
|
||||||
$td->NORMALIZE_NEWLINES);
|
$td->NORMALIZE_NEWLINES);
|
||||||
|
|
||||||
|
$td->runtest("indirect filters",
|
||||||
|
{$td->COMMAND => "test_driver 69 indirect-filter.pdf"},
|
||||||
|
{$td->STRING => "test 69 done\n", $td->EXIT_STATUS => 0},
|
||||||
|
$td->NORMALIZE_NEWLINES);
|
||||||
|
foreach my $i (0, 1)
|
||||||
|
{
|
||||||
|
$td->runtest("check output",
|
||||||
|
{$td->FILE => "auto-$i.pdf"},
|
||||||
|
{$td->FILE => "indirect-filter-out-$i.pdf"});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
show_ntests();
|
show_ntests();
|
||||||
# ----------
|
# ----------
|
||||||
$td->notify("--- Error Condition Tests ---");
|
$td->notify("--- Error Condition Tests ---");
|
||||||
|
BIN
qpdf/qtest/qpdf/indirect-filter-out-0.pdf
Normal file
BIN
qpdf/qtest/qpdf/indirect-filter-out-0.pdf
Normal file
Binary file not shown.
BIN
qpdf/qtest/qpdf/indirect-filter-out-1.pdf
Normal file
BIN
qpdf/qtest/qpdf/indirect-filter-out-1.pdf
Normal file
Binary file not shown.
BIN
qpdf/qtest/qpdf/indirect-filter.pdf
Normal file
BIN
qpdf/qtest/qpdf/indirect-filter.pdf
Normal file
Binary file not shown.
@ -2195,6 +2195,22 @@ void runtest(int n, char const* filename1, char const* arg2)
|
|||||||
std::cout << "raw stream data okay" << std::endl;
|
std::cout << "raw stream data okay" << std::endl;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (n == 69)
|
||||||
|
{
|
||||||
|
pdf.setImmediateCopyFrom(true);
|
||||||
|
auto pages = pdf.getAllPages();
|
||||||
|
for (size_t i = 0; i < pages.size(); ++i)
|
||||||
|
{
|
||||||
|
QPDF out;
|
||||||
|
out.emptyPDF();
|
||||||
|
out.addPage(pages.at(i), false);
|
||||||
|
std::string outname = std::string("auto-") +
|
||||||
|
QUtil::uint_to_string(i) + ".pdf";
|
||||||
|
QPDFWriter w(out, outname.c_str());
|
||||||
|
w.setStaticID(true);
|
||||||
|
w.write();
|
||||||
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw std::runtime_error(std::string("invalid test ") +
|
throw std::runtime_error(std::string("invalid test ") +
|
||||||
|
Loading…
x
Reference in New Issue
Block a user