summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJay Berkenbilt <ejb@ql.org>2012-07-08 20:19:19 +0200
committerJay Berkenbilt <ejb@ql.org>2012-07-11 05:34:32 +0200
commit8a217eb3a26931453b4f003c6c18ad8569230cf1 (patch)
treedfca77568e0640be6555bfe550a1e334dfb0710e
parentaf64668ad190a3f28fbeb233238cb4a76db67d7c (diff)
downloadqpdf-8a217eb3a26931453b4f003c6c18ad8569230cf1.tar.zst
Add concept of reserved objects
QPDFObjectHandle::{new,is,assert}Reserved, QPDF::replaceReserved provide a mechanism to add objects to a PDF file when there are circular references. This is a prerequisite to copying objects from one PDF to another.
-rw-r--r--ChangeLog9
-rw-r--r--include/qpdf/QPDF.hh12
-rw-r--r--include/qpdf/QPDFObjectHandle.hh23
-rw-r--r--libqpdf/QPDF.cc12
-rw-r--r--libqpdf/QPDFObjectHandle.cc62
-rw-r--r--libqpdf/QPDF_Reserved.cc13
-rw-r--r--libqpdf/build.mk1
-rw-r--r--libqpdf/qpdf/QPDF_Reserved.hh13
-rw-r--r--qpdf/qpdf.testcov1
-rw-r--r--qpdf/qtest/qpdf.test9
-rw-r--r--qpdf/qtest/qpdf/reserved-objects.out8
-rw-r--r--qpdf/qtest/qpdf/reserved-objects.pdf48
-rw-r--r--qpdf/test_driver.cc76
13 files changed, 280 insertions, 7 deletions
diff --git a/ChangeLog b/ChangeLog
index 6fa4c45d..377d79bf 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,12 @@
+2012-07-08 Jay Berkenbilt <ejb@ql.org>
+
+ * Add QPDFObjectHandle::newReserved to create a reserved object
+ and QPDF::replaceReserved to replace it with a real object.
+ QPDFObjectHandle::newReserved reserves an object ID in a QPDF
+ object and ensures that any references to it remain unresolved.
+ When QPDF::replaceReserved is later called, previous references to
+ the reserved object will properly resolve to the replaced object.
+
2012-07-07 Jay Berkenbilt <ejb@ql.org>
* NOTE: BREAKING API CHANGE. Remove previously required length
diff --git a/include/qpdf/QPDF.hh b/include/qpdf/QPDF.hh
index fbac2ab2..b5c07abb 100644
--- a/include/qpdf/QPDF.hh
+++ b/include/qpdf/QPDF.hh
@@ -161,7 +161,8 @@ class QPDF
// be associated with the PDF file. Note that replacing an object
// with QPDFObjectHandle::newNull() effectively removes the object
// from the file since a non-existent object is treated as a null
- // object.
+ // object. To replace a reserved object, call replaceReserved
+ // instead.
QPDF_DLL
void replaceObject(int objid, int generation, QPDFObjectHandle);
@@ -180,6 +181,15 @@ class QPDF
void swapObjects(int objid1, int generation1,
int objid2, int generation2);
+ // Replace a reserved object. This is a wrapper around
+ // replaceObject but it guarantees that the underlying object is a
+ // reserved object. After this call, reserved will be a reference
+ // to replacement.
+ QPDF_DLL
+ void
+ replaceReserved(QPDFObjectHandle reserved,
+ QPDFObjectHandle replacement);
+
// Encryption support
enum encryption_method_e { e_none, e_unknown, e_rc4, e_aes };
diff --git a/include/qpdf/QPDFObjectHandle.hh b/include/qpdf/QPDFObjectHandle.hh
index daa71faa..b21a3b0c 100644
--- a/include/qpdf/QPDFObjectHandle.hh
+++ b/include/qpdf/QPDFObjectHandle.hh
@@ -81,6 +81,8 @@ class QPDFObjectHandle
bool isDictionary();
QPDF_DLL
bool isStream();
+ QPDF_DLL
+ bool isReserved();
// This returns true in addition to the query for the specific
// type for indirect objects.
@@ -148,6 +150,24 @@ class QPDFObjectHandle
QPDF_DLL
static QPDFObjectHandle newStream(QPDF* qpdf, std::string const& data);
+ // A reserved object is a special sentinel used for qpdf to
+ // reserve a spot for an object that is going to be added to the
+ // QPDF object. Normally you don't have to use this type since
+ // you can just call QPDF::makeIndirectObject. However, in some
+ // cases, if you have to create objects with circular references,
+ // you may need to create a reserved object so that you can have a
+ // reference to it and then replace the object later. Reserved
+ // objects have the special property that they can't be resolved
+ // to direct objects. This makes it possible to replace a
+ // reserved object with a new object while preserving existing
+ // references to them. When you are ready to replace a reserved
+ // object with its replacement, use QPDF::replaceReserved for this
+ // purpose rather than the more general QPDF::replaceObject. It
+ // is an error to try to write a QPDF with QPDFWriter if it has
+ // any reserved objects in it.
+ QPDF_DLL
+ static QPDFObjectHandle newReserved(QPDF* qpdf);
+
// Accessor methods. If an accessor method that is valid for only
// a particular object type is called on an object of the wrong
// type, an exception is thrown.
@@ -430,6 +450,8 @@ class QPDFObjectHandle
void assertDictionary();
QPDF_DLL
void assertStream();
+ QPDF_DLL
+ void assertReserved();
QPDF_DLL
void assertScalar();
@@ -459,6 +481,7 @@ class QPDFObjectHandle
int objid; // 0 for direct object
int generation;
PointerHolder<QPDFObject> obj;
+ bool reserved;
};
#endif // __QPDFOBJECTHANDLE_HH__
diff --git a/libqpdf/QPDF.cc b/libqpdf/QPDF.cc
index a66b4f15..1c4e5d8d 100644
--- a/libqpdf/QPDF.cc
+++ b/libqpdf/QPDF.cc
@@ -2057,6 +2057,18 @@ QPDF::replaceObject(int objid, int generation, QPDFObjectHandle oh)
}
void
+QPDF::replaceReserved(QPDFObjectHandle reserved,
+ QPDFObjectHandle replacement)
+{
+ QTC::TC("qpdf", "QPDF replaceReserved");
+ reserved.assertReserved();
+ replaceObject(reserved.getObjectID(),
+ reserved.getGeneration(),
+ replacement);
+}
+
+
+void
QPDF::swapObjects(int objid1, int generation1, int objid2, int generation2)
{
// Force objects to be loaded into cache; then swap them in the
diff --git a/libqpdf/QPDFObjectHandle.cc b/libqpdf/QPDFObjectHandle.cc
index 73d0019c..25298bee 100644
--- a/libqpdf/QPDFObjectHandle.cc
+++ b/libqpdf/QPDFObjectHandle.cc
@@ -10,6 +10,7 @@
#include <qpdf/QPDF_Array.hh>
#include <qpdf/QPDF_Dictionary.hh>
#include <qpdf/QPDF_Stream.hh>
+#include <qpdf/QPDF_Reserved.hh>
#include <qpdf/QTC.hh>
#include <qpdf/QUtil.hh>
@@ -20,7 +21,8 @@
QPDFObjectHandle::QPDFObjectHandle() :
initialized(false),
objid(0),
- generation(0)
+ generation(0),
+ reserved(false)
{
}
@@ -28,7 +30,8 @@ QPDFObjectHandle::QPDFObjectHandle(QPDF* qpdf, int objid, int generation) :
initialized(true),
qpdf(qpdf),
objid(objid),
- generation(generation)
+ generation(generation),
+ reserved(false)
{
}
@@ -37,7 +40,8 @@ QPDFObjectHandle::QPDFObjectHandle(QPDFObject* data) :
qpdf(0),
objid(0),
generation(0),
- obj(data)
+ obj(data),
+ reserved(false)
{
}
@@ -166,6 +170,14 @@ QPDFObjectHandle::isStream()
}
bool
+QPDFObjectHandle::isReserved()
+{
+ // dereference will clear reserved if this has been replaced
+ dereference();
+ return this->reserved;
+}
+
+bool
QPDFObjectHandle::isIndirect()
{
assertInitialized();
@@ -568,6 +580,11 @@ QPDFObjectHandle::unparse()
std::string
QPDFObjectHandle::unparseResolved()
{
+ if (this->reserved)
+ {
+ throw std::logic_error(
+ "QPDFObjectHandle: attempting to unparse a reserved object");
+ }
dereference();
return this->obj->unparse();
}
@@ -690,6 +707,19 @@ QPDFObjectHandle::newStream(QPDF* qpdf, std::string const& data)
}
QPDFObjectHandle
+QPDFObjectHandle::newReserved(QPDF* qpdf)
+{
+ // Reserve a spot for this object by assigning it an object
+ // number, but then return an unresolved handle to the object.
+ QPDFObjectHandle reserved = qpdf->makeIndirectObject(
+ QPDFObjectHandle(new QPDF_Reserved()));
+ QPDFObjectHandle result =
+ newIndirect(qpdf, reserved.objid, reserved.generation);
+ result.reserved = true;
+ return result;
+}
+
+QPDFObjectHandle
QPDFObjectHandle::shallowCopy()
{
assertInitialized();
@@ -746,6 +776,13 @@ QPDFObjectHandle::makeDirectInternal(std::set<int>& visited)
visited.insert(cur_objid);
}
+ if (isReserved())
+ {
+ throw std::logic_error(
+ "QPDFObjectHandle: attempting to make a"
+ " reserved object handle direct");
+ }
+
dereference();
this->objid = 0;
this->generation = 0;
@@ -903,6 +940,12 @@ QPDFObjectHandle::assertStream()
}
void
+QPDFObjectHandle::assertReserved()
+{
+ assertType("Reserved", isReserved());
+}
+
+void
QPDFObjectHandle::assertScalar()
{
assertType("Scalar", isScalar());
@@ -929,12 +972,21 @@ QPDFObjectHandle::dereference()
{
if (this->obj.getPointer() == 0)
{
- this->obj = QPDF::Resolver::resolve(
+ PointerHolder<QPDFObject> obj = QPDF::Resolver::resolve(
this->qpdf, this->objid, this->generation);
- if (this->obj.getPointer() == 0)
+ if (obj.getPointer() == 0)
{
QTC::TC("qpdf", "QPDFObjectHandle indirect to unknown");
this->obj = new QPDF_Null();
}
+ else if (dynamic_cast<QPDF_Reserved*>(obj.getPointer()))
+ {
+ // Do not resolve
+ }
+ else
+ {
+ this->reserved = false;
+ this->obj = obj;
+ }
}
}
diff --git a/libqpdf/QPDF_Reserved.cc b/libqpdf/QPDF_Reserved.cc
new file mode 100644
index 00000000..368db3b4
--- /dev/null
+++ b/libqpdf/QPDF_Reserved.cc
@@ -0,0 +1,13 @@
+#include <qpdf/QPDF_Reserved.hh>
+#include <stdexcept>
+
+QPDF_Reserved::~QPDF_Reserved()
+{
+}
+
+std::string
+QPDF_Reserved::unparse()
+{
+ throw std::logic_error("attempt to unparse QPDF_Reserved");
+ return "";
+}
diff --git a/libqpdf/build.mk b/libqpdf/build.mk
index 7efbbd85..422878f2 100644
--- a/libqpdf/build.mk
+++ b/libqpdf/build.mk
@@ -39,6 +39,7 @@ SRCS_libqpdf = \
libqpdf/QPDF_Name.cc \
libqpdf/QPDF_Null.cc \
libqpdf/QPDF_Real.cc \
+ libqpdf/QPDF_Reserved.cc \
libqpdf/QPDF_Stream.cc \
libqpdf/QPDF_String.cc \
libqpdf/QPDF_encryption.cc \
diff --git a/libqpdf/qpdf/QPDF_Reserved.hh b/libqpdf/qpdf/QPDF_Reserved.hh
new file mode 100644
index 00000000..b149f776
--- /dev/null
+++ b/libqpdf/qpdf/QPDF_Reserved.hh
@@ -0,0 +1,13 @@
+#ifndef __QPDF_RESERVED_HH__
+#define __QPDF_RESERVED_HH__
+
+#include <qpdf/QPDFObject.hh>
+
+class QPDF_Reserved: public QPDFObject
+{
+ public:
+ virtual ~QPDF_Reserved();
+ virtual std::string unparse();
+};
+
+#endif // __QPDF_RESERVED_HH__
diff --git a/qpdf/qpdf.testcov b/qpdf/qpdf.testcov
index b2c98496..eea5475a 100644
--- a/qpdf/qpdf.testcov
+++ b/qpdf/qpdf.testcov
@@ -217,3 +217,4 @@ QPDFObjectHandle newStream with string 0
QPDF unknown key not inherited 0
QPDF_Stream provider length not provided 0
QPDF_Stream unknown stream length 0
+QPDF replaceReserved 0
diff --git a/qpdf/qtest/qpdf.test b/qpdf/qtest/qpdf.test
index 9a7f7b99..b4171735 100644
--- a/qpdf/qtest/qpdf.test
+++ b/qpdf/qtest/qpdf.test
@@ -149,7 +149,7 @@ $td->runtest("remove page we don't have",
$td->NORMALIZE_NEWLINES);
# ----------
$td->notify("--- Miscellaneous Tests ---");
-$n_tests += 41;
+$n_tests += 43;
$td->runtest("qpdf version",
{$td->COMMAND => "qpdf --version"},
@@ -358,6 +358,13 @@ $td->runtest("warn for unknown key in Pages",
{$td->COMMAND => "test_driver 23 lin-special.pdf"},
{$td->FILE => "pages-warning.out", $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
+$td->runtest("reserved objects",
+ {$td->COMMAND => "test_driver 24 minimal.pdf"},
+ {$td->FILE => "reserved-objects.out", $td->EXIT_STATUS => 0},
+ $td->NORMALIZE_NEWLINES);
+$td->runtest("check output",
+ {$td->FILE => "a.pdf"},
+ {$td->FILE => "reserved-objects.pdf"});
show_ntests();
# ----------
diff --git a/qpdf/qtest/qpdf/reserved-objects.out b/qpdf/qtest/qpdf/reserved-objects.out
new file mode 100644
index 00000000..a1611e11
--- /dev/null
+++ b/qpdf/qtest/qpdf/reserved-objects.out
@@ -0,0 +1,8 @@
+res1 is still reserved after checking if array
+res1 is no longer reserved
+res1 is an array
+logic error: QPDFObjectHandle: attempting to unparse a reserved object
+logic error: QPDFObjectHandle: attempting to make a reserved object handle direct
+res2 is an array
+circular access and lazy resolution worked
+test 24 done
diff --git a/qpdf/qtest/qpdf/reserved-objects.pdf b/qpdf/qtest/qpdf/reserved-objects.pdf
new file mode 100644
index 00000000..592a51de
--- /dev/null
+++ b/qpdf/qtest/qpdf/reserved-objects.pdf
@@ -0,0 +1,48 @@
+%PDF-1.3
+%¿÷¢þ
+1 0 obj
+<< /Pages 4 0 R /Type /Catalog >>
+endobj
+2 0 obj
+[ 3 0 R 1 ]
+endobj
+3 0 obj
+[ 2 0 R 2 ]
+endobj
+4 0 obj
+<< /Count 1 /Kids [ 5 0 R ] /Type /Pages >>
+endobj
+5 0 obj
+<< /Contents 6 0 R /MediaBox [ 0 0 612 792 ] /Parent 4 0 R /Resources << /Font << /F1 7 0 R >> /ProcSet 8 0 R >> /Type /Page >>
+endobj
+6 0 obj
+<< /Length 44 >>
+stream
+BT
+ /F1 24 Tf
+ 72 720 Td
+ (Potato) Tj
+ET
+endstream
+endobj
+7 0 obj
+<< /BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font >>
+endobj
+8 0 obj
+[ /PDF /Text ]
+endobj
+xref
+0 9
+0000000000 65535 f
+0000000015 00000 n
+0000000064 00000 n
+0000000091 00000 n
+0000000118 00000 n
+0000000177 00000 n
+0000000320 00000 n
+0000000413 00000 n
+0000000520 00000 n
+trailer << /Root 1 0 R /Size 9 Array1 2 0 R Array2 3 0 R /ID [<31415926535897932384626433832795><31415926535897932384626433832795>] >>
+startxref
+550
+%%EOF
diff --git a/qpdf/test_driver.cc b/qpdf/test_driver.cc
index dd8de73e..1e1bd7d8 100644
--- a/qpdf/test_driver.cc
+++ b/qpdf/test_driver.cc
@@ -840,6 +840,82 @@ void runtest(int n, char const* filename)
std::vector<QPDFObjectHandle> const& pages = pdf.getAllPages();
pdf.removePage(pages.back());
}
+ else if (n == 24)
+ {
+ // Test behavior of reserved objects
+ QPDFObjectHandle res1 = QPDFObjectHandle::newReserved(&pdf);
+ QPDFObjectHandle res2 = QPDFObjectHandle::newReserved(&pdf);
+ QPDFObjectHandle trailer = pdf.getTrailer();
+ trailer.replaceKey("Array1", res1);
+ trailer.replaceKey("Array2", res2);
+
+ QPDFObjectHandle array1 = QPDFObjectHandle::newArray();
+ QPDFObjectHandle array2 = QPDFObjectHandle::newArray();
+ array1.appendItem(res2);
+ array1.appendItem(QPDFObjectHandle::newInteger(1));
+ array2.appendItem(res1);
+ array2.appendItem(QPDFObjectHandle::newInteger(2));
+ // Make sure trying to ask questions about a reserved object
+ // doesn't break it.
+ if (res1.isArray())
+ {
+ std::cout << "oops -- res1 is an array" << std::endl;
+ }
+ if (res1.isReserved())
+ {
+ std::cout << "res1 is still reserved after checking if array"
+ << std::endl;
+ }
+ pdf.replaceReserved(res1, array1);
+ if (res1.isReserved())
+ {
+ std::cout << "oops -- res1 is still reserved" << std::endl;
+ }
+ else
+ {
+ std::cout << "res1 is no longer reserved" << std::endl;
+ }
+ res1.assertArray();
+ std::cout << "res1 is an array" << std::endl;
+
+ try
+ {
+ res2.unparseResolved();
+ std::cout << "oops -- didn't throw" << std::endl;
+ }
+ catch (std::logic_error e)
+ {
+ std::cout << "logic error: " << e.what() << std::endl;
+ }
+ try
+ {
+ res2.makeDirect();
+ std::cout << "oops -- didn't throw" << std::endl;
+ }
+ catch (std::logic_error e)
+ {
+ std::cout << "logic error: " << e.what() << std::endl;
+ }
+
+ pdf.replaceReserved(res2, array2);
+
+ res2.assertArray();
+ std::cout << "res2 is an array" << std::endl;
+
+ // Verify that the previously added reserved keys can be
+ // dereferenced properly now
+ int i1 = res1.getArrayItem(0).getArrayItem(1).getIntValue();
+ int i2 = res2.getArrayItem(0).getArrayItem(1).getIntValue();
+ if ((i1 == 2) && (i2 == 1))
+ {
+ std::cout << "circular access and lazy resolution worked" << std::endl;
+ }
+
+ QPDFWriter w(pdf, "a.pdf");
+ w.setStaticID(true);
+ w.setStreamDataMode(qpdf_s_preserve);
+ w.write();
+ }
else
{
throw std::runtime_error(std::string("invalid test ") +