aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ChangeLog18
-rw-r--r--include/qpdf/qpdf-c.h75
-rw-r--r--libqpdf/qpdf-c.cc663
-rw-r--r--manual/qpdf-manual.xml19
-rw-r--r--qpdf/qpdf-ctest.c119
-rw-r--r--qpdf/qpdf.testcov4
-rw-r--r--qpdf/qtest/qpdf.test14
-rw-r--r--qpdf/qtest/qpdf/c-object-handles.out19
-rw-r--r--qpdf/qtest/qpdf/c-oh-errors.out55
-rw-r--r--qpdf/qtest/qpdf/c-type-warning.out1
-rw-r--r--qpdf/qtest/qpdf/c-unhandled-error.out5
11 files changed, 672 insertions, 320 deletions
diff --git a/ChangeLog b/ChangeLog
index 299b6272..341ae8e4 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,21 @@
+2021-12-10 Jay Berkenbilt <ejb@ql.org>
+
+ * C API: Overhaul how errors are handle the C API's object handle
+ interfaces. Clarify documentation regarding object accessors and
+ how type errors and warnings are handled. Many cases that used to
+ crash code that used the C API can now be trapped and will be
+ written stderr if not trapped. The new method
+ qpdf_register_oh_error_handler can be used to specifically handle
+ errors that occur when accessing object handles. See qpdf-c.h for
+ details.
+
+ * C API: Add qpdf_oh_new_uninitialized to explicitly create
+ uninitialized object handles.
+
+ * Add new error code qpdf_e_object that is used for exceptions
+ (including warnings) that are caused by using QPDFObjectHandle
+ methods on object handles of the wrong type.
+
2021-12-02 Jay Berkenbilt <ejb@ql.org>
* C API: Add qpdf_oh_is_initialized.
diff --git a/include/qpdf/qpdf-c.h b/include/qpdf/qpdf-c.h
index 294166ac..90219123 100644
--- a/include/qpdf/qpdf-c.h
+++ b/include/qpdf/qpdf-c.h
@@ -62,22 +62,27 @@
* string was just returned.
*
* Many functions defined here merely set parameters and therefore
- * never return error conditions. Functions that may cause PDF
- * files to be read or written may return error conditions. Such
- * functions return an error code. If there were no errors or
- * warnings, they return QPDF_SUCCESS. If there were warnings,
- * the return value has the QPDF_WARNINGS bit set. If there
- * errors, the QPDF_ERRORS bit is set. In other words, if there
- * are both warnings and errors, then the return status will be
- * QPDF_WARNINGS | QPDF_ERRORS. You may also call the
+ * never return error conditions. Functions that access or return
+ * qpdf_oh object handles may generate warnings but have no way to
+ * return errors, but the errors may be checked afterwards or
+ * handled using a registered handler. This is discussed in more
+ * detail in the section on object handling. Functions that may
+ * cause PDF files to be read or written may return error
+ * conditions. Such functions return an error code. If there were
+ * no errors or warnings, they return QPDF_SUCCESS. If there were
+ * warnings, the return value has the QPDF_WARNINGS bit set. If
+ * there were errors, the QPDF_ERRORS bit is set. In other words,
+ * if there are both warnings and errors, then the return status
+ * will be QPDF_WARNINGS | QPDF_ERRORS. You may also call the
* qpdf_more_warnings and qpdf_more_errors functions to test
- * whether there are unseen warning or error conditions. By
+ * whether there are unseen warning or error conditions. By
* default, warnings are written to stderr when detected, but this
- * behavior can be suppressed. In all cases, errors and warnings
+ * behavior can be suppressed. In all cases, errors and warnings
* may be retrieved by calling qpdf_next_warning and
- * qpdf_next_error. All exceptions thrown by the C++ interface
- * are caught and converted into error messages by the C
- * interface.
+ * qpdf_get_error. All exceptions thrown by the C++ interface are
+ * caught and converted into error messages by the C interface.
+ * Any exceptions to this are qpdf bugs and should be reported at
+ * https://github.com/qpdf/qpdf/issues/new.
*
* Most functions defined here have obvious counterparts that are
* methods to either QPDF or QPDFWriter. Please see comments in
@@ -550,13 +555,51 @@ extern "C" {
* handle, the object is safely part of the dictionary or array.
* Similarly, any other object handle refering to the object remains
* valid. Explicitly releasing an object handle is essentially the
- * same as letting a QPDFObjectHandle go out of scope in the C++ API.
+ * same as letting a QPDFObjectHandle go out of scope in the C++
+ * API.
+ *
+ * Important note about error handling:
+ *
+ * While many of the functions that operate on the QPDF object
+ * return error codes, the qpdf_oh functions return values such as
+ * object handles or data. They have no way to return error codes.
+ * If they generate warnings, the warnings are handled using the
+ * error/warning handling functions described above. If the
+ * underlying C++ call throws an exception, the error handler
+ * registered with qpdf_register_oh_error_handler() will be
+ * called. If no handler is registered, the exception is written
+ * to STDERR. In either case, a sensible fallback value is
+ * returned (0 for numbers, QPDF_FALSE for booleans, "" for
+ * strings, or a null object). It is sensible for a C program to
+ * use setjmp and longjmp with this error handler since the C++
+ * code has raised an exception, but you can also just set a flag
+ * and check it after each call.
+ *
+ * All conditions under which exceptions would be thrown by object
+ * accessors are caused by programmer error or major problems such
+ * as running out of memory or not being able to read the input
+ * file. If they are ever caused by invalid data in the PDF file,
+ * it is a bug in qpdf, which should be reported at
+ * https://github.com/qpdf/qpdf/issues/new.
*/
/* For examples of using this API, see examples/pdf-c-objects.c */
typedef unsigned int qpdf_oh;
+ /* If an exception is thrown by the C++ code when any of the
+ * qpdf_oh functions are called, the registered handle_error
+ * function will be called. The value passed to data will be
+ * passed along to the error handler function. If any errors occur
+ * and no error handler is accessed, a single warning will be
+ * issued, and the error will be written to stderr.
+ */
+ QPDF_DLL
+ void qpdf_register_oh_error_handler(
+ qpdf_data qpdf,
+ void (*handle_error)(qpdf_data qpdf, qpdf_error error, void* data),
+ void* data);
+
/* Releasing objects -- see comments above. These functions have no
* equivalent in the C++ API.
*/
@@ -659,7 +702,7 @@ extern "C" {
/* The memory returned by qpdf_oh_dict_next_key is owned by
* qpdf_data. It is good until the next call to
* qpdf_oh_dict_next_key with the same qpdf_data object. Calling
- * the method again, even with a different dict, invalidates
+ * the function again, even with a different dict, invalidates
* previous return values.
*/
QPDF_DLL
@@ -677,6 +720,8 @@ extern "C" {
qpdf_data data, qpdf_oh oh, char const* key);
QPDF_DLL
+ qpdf_oh qpdf_oh_new_uninitialized(qpdf_data qpdf);
+ QPDF_DLL
qpdf_oh qpdf_oh_new_null(qpdf_data data);
QPDF_DLL
qpdf_oh qpdf_oh_new_bool(qpdf_data data, QPDF_BOOL value);
diff --git a/libqpdf/qpdf-c.cc b/libqpdf/qpdf-c.cc
index 063adaf2..b7e67e85 100644
--- a/libqpdf/qpdf-c.cc
+++ b/libqpdf/qpdf-c.cc
@@ -41,6 +41,9 @@ struct _qpdf_data
PointerHolder<Buffer> output_buffer;
// QPDFObjectHandle support
+ void (*oh_error_handler)(qpdf_data, qpdf_error, void*);
+ void* oh_error_handler_data;
+ bool default_oh_error_handler_called;
std::map<qpdf_oh, PointerHolder<QPDFObjectHandle>> oh_cache;
qpdf_oh next_oh;
std::set<std::string> cur_iter_dict_keys;
@@ -48,8 +51,32 @@ struct _qpdf_data
std::string cur_dict_key;
};
+static void default_oh_error_handler(qpdf_data qpdf, qpdf_error e, void* data)
+{
+ bool* called = reinterpret_cast<bool*>(data);
+ if (called != nullptr)
+ {
+ QTC::TC("qpdf", "qpdf-c warn about oh error", *called ? 0 : 1);
+ if (! *called)
+ {
+ qpdf->warnings.push_back(
+ QPDFExc(
+ qpdf_e_internal,
+ qpdf->qpdf->getFilename(),
+ "", 0,
+ "C API object handle accessor errors occurred,"
+ " and the application did not define an error handler"));
+ *called = true;
+ }
+ }
+ std::cerr << e->exc->what() << std::endl;
+}
+
_qpdf_data::_qpdf_data() :
write_memory(false),
+ oh_error_handler(default_oh_error_handler),
+ oh_error_handler_data(&this->default_oh_error_handler_called),
+ default_oh_error_handler_called(false),
next_oh(0)
{
}
@@ -170,6 +197,13 @@ void qpdf_cleanup(qpdf_data* qpdf)
{
QTC::TC("qpdf", "qpdf-c called qpdf_cleanup");
qpdf_oh_release_all(*qpdf);
+ if ((*qpdf)->error.getPointer())
+ {
+ QTC::TC("qpdf", "qpdf-c cleanup warned about unhandled error");
+ std::cerr << "WARNING: application did not handle error: "
+ << (*qpdf)->error->what() << std::endl;
+
+ }
delete *qpdf;
*qpdf = 0;
}
@@ -841,6 +875,38 @@ QPDF_ERROR_CODE qpdf_write(qpdf_data qpdf)
return status;
}
+void qpdf_register_oh_error_handler(
+ qpdf_data qpdf,
+ void (*handle_error)(qpdf_data qpdf, qpdf_error error, void* data),
+ void* data)
+{
+ QTC::TC("qpdf", "qpdf-c registered oh error handler");
+ qpdf->oh_error_handler = handle_error;
+ qpdf->oh_error_handler_data = data;
+}
+
+template<class RET>
+static RET trap_oh_errors(
+ qpdf_data qpdf,
+ std::function<RET()> fallback,
+ std::function<RET(qpdf_data)> fn)
+{
+ // Note: fallback is a function so we don't have to evaluate it
+ // unless needed. This is important because sometimes the fallback
+ // creates an object.
+ RET ret;
+ QPDF_ERROR_CODE status = trap_errors(qpdf, [&ret, &fn] (qpdf_data q) {
+ ret = fn(q);
+ });
+ if (status & QPDF_ERRORS)
+ {
+ (*qpdf->oh_error_handler)(
+ qpdf, qpdf_get_error(qpdf), qpdf->oh_error_handler_data);
+ return fallback();
+ }
+ return ret;
+}
+
static qpdf_oh
new_object(qpdf_data qpdf, QPDFObjectHandle const& qoh)
{
@@ -867,310 +933,367 @@ void qpdf_oh_release_all(qpdf_data qpdf)
qpdf->oh_cache.clear();
}
+template <class T>
+static std::function<T()> return_T(T const& r)
+{
+ return [&r]() { return r; };
+}
+
+static QPDF_BOOL return_false()
+{
+ return QPDF_FALSE;
+}
+
+static std::function<qpdf_oh()> return_uninitialized(qpdf_data qpdf)
+{
+ return [qpdf]() { return qpdf_oh_new_uninitialized(qpdf); };
+}
+
+static std::function<qpdf_oh()> return_null(qpdf_data qpdf)
+{
+ return [qpdf]() { return qpdf_oh_new_null(qpdf); };
+}
+
qpdf_oh qpdf_get_trailer(qpdf_data qpdf)
{
QTC::TC("qpdf", "qpdf-c called qpdf_get_trailer");
- return new_object(qpdf, qpdf->qpdf->getTrailer());
+ return trap_oh_errors<qpdf_oh>(
+ qpdf, return_uninitialized(qpdf), [] (qpdf_data q) {
+ return new_object(q, q->qpdf->getTrailer());
+ });
}
qpdf_oh qpdf_get_root(qpdf_data qpdf)
{
QTC::TC("qpdf", "qpdf-c called qpdf_get_root");
- return new_object(qpdf, qpdf->qpdf->getRoot());
-}
-
-static bool
-qpdf_oh_valid_internal(qpdf_data qpdf, qpdf_oh oh)
-{
- auto i = qpdf->oh_cache.find(oh);
- bool result = ((i != qpdf->oh_cache.end()) &&
- (i->second).getPointer());
- if (! result)
- {
- QTC::TC("qpdf", "qpdf-c invalid object handle");
- qpdf->warnings.push_back(
- QPDFExc(
- qpdf_e_damaged_pdf,
- qpdf->qpdf->getFilename(),
- std::string("C API object handle ") +
- QUtil::uint_to_string(oh),
- 0, "attempted access to unknown object handle"));
- }
- return result;
+ return trap_oh_errors<qpdf_oh>(
+ qpdf, return_uninitialized(qpdf), [] (qpdf_data q) {
+ return new_object(q, q->qpdf->getRoot());
+ });
+}
+
+template<class RET>
+static RET do_with_oh(
+ qpdf_data qpdf, qpdf_oh oh,
+ std::function<RET()> fallback,
+ std::function<RET(QPDFObjectHandle&)> fn)
+{
+ return trap_oh_errors<RET>(
+ qpdf, fallback, [&fn, &oh](qpdf_data q) {
+ auto i = q->oh_cache.find(oh);
+ bool result = ((i != q->oh_cache.end()) &&
+ (i->second).getPointer());
+ if (! result)
+ {
+ QTC::TC("qpdf", "qpdf-c invalid object handle");
+ throw QPDFExc(
+ qpdf_e_internal,
+ q->qpdf->getFilename(),
+ std::string("C API object handle ") +
+ QUtil::uint_to_string(oh),
+ 0, "attempted access to unknown object handle");
+ }
+ return fn(*(q->oh_cache[oh]));
+ });
+}
+
+static void do_with_oh_void(
+ qpdf_data qpdf, qpdf_oh oh,
+ std::function<void(QPDFObjectHandle&)> fn)
+{
+ do_with_oh<bool>(
+ qpdf, oh, return_T<bool>(false), [&fn](QPDFObjectHandle& o) {
+ fn(o);
+ return true; // unused
+ });
}
QPDF_BOOL qpdf_oh_is_initialized(qpdf_data qpdf, qpdf_oh oh)
{
QTC::TC("qpdf", "qpdf-c called qpdf_oh_is_initialized");
- return (qpdf_oh_valid_internal(qpdf, oh) &&
- qpdf->oh_cache[oh]->isInitialized());
+ return do_with_oh<QPDF_BOOL>(
+ qpdf, oh, return_false, [](QPDFObjectHandle& o) {
+ return o.isInitialized();
+ });
}
QPDF_BOOL qpdf_oh_is_bool(qpdf_data qpdf, qpdf_oh oh)
{
QTC::TC("qpdf", "qpdf-c called qpdf_oh_is_bool");
- return (qpdf_oh_valid_internal(qpdf, oh) &&
- qpdf->oh_cache[oh]->isBool());
+ return do_with_oh<QPDF_BOOL>(
+ qpdf, oh, return_false, [](QPDFObjectHandle& o) {
+ return o.isBool();
+ });
}
QPDF_BOOL qpdf_oh_is_null(qpdf_data qpdf, qpdf_oh oh)
{
QTC::TC("qpdf", "qpdf-c called qpdf_oh_is_null");
- return (qpdf_oh_valid_internal(qpdf, oh) &&
- qpdf->oh_cache[oh]->isNull());
+ return do_with_oh<QPDF_BOOL>(
+ qpdf, oh, return_false, [](QPDFObjectHandle& o) {
+ return o.isNull();
+ });
}
QPDF_BOOL qpdf_oh_is_integer(qpdf_data qpdf, qpdf_oh oh)
{
QTC::TC("qpdf", "qpdf-c called qpdf_oh_is_integer");
- return (qpdf_oh_valid_internal(qpdf, oh) &&
- qpdf->oh_cache[oh]->isInteger());
+ return do_with_oh<QPDF_BOOL>(
+ qpdf, oh, return_false, [](QPDFObjectHandle& o) {
+ return o.isInteger();
+ });
}
QPDF_BOOL qpdf_oh_is_real(qpdf_data qpdf, qpdf_oh oh)
{
QTC::TC("qpdf", "qpdf-c called qpdf_oh_is_real");
- return (qpdf_oh_valid_internal(qpdf, oh) &&
- qpdf->oh_cache[oh]->isReal());
+ return do_with_oh<QPDF_BOOL>(
+ qpdf, oh, return_false, [](QPDFObjectHandle& o) {
+ return o.isReal();
+ });
}
QPDF_BOOL qpdf_oh_is_name(qpdf_data qpdf, qpdf_oh oh)
{
QTC::TC("qpdf", "qpdf-c called qpdf_oh_is_name");
- return (qpdf_oh_valid_internal(qpdf, oh) &&
- qpdf->oh_cache[oh]->isName());
+ return do_with_oh<QPDF_BOOL>(
+ qpdf, oh, return_false, [](QPDFObjectHandle& o) {
+ return o.isName();
+ });
}
QPDF_BOOL qpdf_oh_is_string(qpdf_data qpdf, qpdf_oh oh)
{
QTC::TC("qpdf", "qpdf-c called qpdf_oh_is_string");
- return (qpdf_oh_valid_internal(qpdf, oh) &&
- qpdf->oh_cache[oh]->isString());
+ return do_with_oh<QPDF_BOOL>(
+ qpdf, oh, return_false, [](QPDFObjectHandle& o) {
+ return o.isString();
+ });
}
QPDF_BOOL qpdf_oh_is_operator(qpdf_data qpdf, qpdf_oh oh)
{
QTC::TC("qpdf", "qpdf-c called qpdf_oh_is_operator");
- return (qpdf_oh_valid_internal(qpdf, oh) &&
- qpdf->oh_cache[oh]->isOperator());
+ return do_with_oh<QPDF_BOOL>(
+ qpdf, oh, return_false, [](QPDFObjectHandle& o) {
+ return o.isOperator();
+ });
}
QPDF_BOOL qpdf_oh_is_inline_image(qpdf_data qpdf, qpdf_oh oh)
{
QTC::TC("qpdf", "qpdf-c called qpdf_oh_is_inline_image");
- return (qpdf_oh_valid_internal(qpdf, oh) &&
- qpdf->oh_cache[oh]->isInlineImage());
+ return do_with_oh<QPDF_BOOL>(
+ qpdf, oh, return_false, [](QPDFObjectHandle& o) {
+ return o.isInlineImage();
+ });
}
QPDF_BOOL qpdf_oh_is_array(qpdf_data qpdf, qpdf_oh oh)
{
QTC::TC("qpdf", "qpdf-c called qpdf_oh_is_array");
- return (qpdf_oh_valid_internal(qpdf, oh) &&
- qpdf->oh_cache[oh]->isArray());
+ return do_with_oh<QPDF_BOOL>(
+ qpdf, oh, return_false, [](QPDFObjectHandle& o) {
+ return o.isArray();
+ });
}
QPDF_BOOL qpdf_oh_is_dictionary(qpdf_data qpdf, qpdf_oh oh)
{
QTC::TC("qpdf", "qpdf-c called qpdf_oh_is_dictionary");
- return (qpdf_oh_valid_internal(qpdf, oh) &&
- qpdf->oh_cache[oh]->isDictionary());
+ return do_with_oh<QPDF_BOOL>(
+ qpdf, oh, return_false, [](QPDFObjectHandle& o) {
+ return o.isDictionary();
+ });
}
QPDF_BOOL qpdf_oh_is_stream(qpdf_data qpdf, qpdf_oh oh)
{
QTC::TC("qpdf", "qpdf-c called qpdf_oh_is_stream");
- return (qpdf_oh_valid_internal(qpdf, oh) &&
- qpdf->oh_cache[oh]->isStream());
+ return do_with_oh<QPDF_BOOL>(
+ qpdf, oh, return_false, [](QPDFObjectHandle& o) {
+ return o.isStream();
+ });
}
QPDF_BOOL qpdf_oh_is_indirect(qpdf_data qpdf, qpdf_oh oh)
{
QTC::TC("qpdf", "qpdf-c called qpdf_oh_is_indirect");
- return (qpdf_oh_valid_internal(qpdf, oh) &&
- qpdf->oh_cache[oh]->isIndirect());
+ return do_with_oh<QPDF_BOOL>(
+ qpdf, oh, return_false, [](QPDFObjectHandle& o) {
+ return o.isIndirect();
+ });
}
QPDF_BOOL qpdf_oh_is_scalar(qpdf_data qpdf, qpdf_oh oh)
{
QTC::TC("qpdf", "qpdf-c called qpdf_oh_is_scalar");
- return (qpdf_oh_valid_internal(qpdf, oh) &&
- qpdf->oh_cache[oh]->isScalar());
+ return do_with_oh<QPDF_BOOL>(
+ qpdf, oh, return_false, [](QPDFObjectHandle& o) {
+ return o.isScalar();
+ });
+}
+
+QPDF_BOOL qpdf_oh_is_number(qpdf_data qpdf, qpdf_oh oh)
+{
+ return do_with_oh<QPDF_BOOL>(
+ qpdf, oh, return_false, [](QPDFObjectHandle& o) {
+ QTC::TC("qpdf", "qpdf-c called qpdf_oh_is_number");
+ return o.isNumber();
+ });
}
qpdf_oh qpdf_oh_wrap_in_array(qpdf_data qpdf, qpdf_oh oh)
{
- if (! qpdf_oh_valid_internal(qpdf, oh))
- {
- return qpdf_oh_new_array(qpdf);
- }
- auto qoh = qpdf->oh_cache[oh];
- if (qoh->isArray())
- {
- QTC::TC("qpdf", "qpdf-c array to wrap_in_array");
- return new_object(qpdf, *qoh);
- }
- else
- {
- QTC::TC("qpdf", "qpdf-c non-array to wrap_in_array");
- return new_object(qpdf,
- QPDFObjectHandle::newArray(
- std::vector<QPDFObjectHandle>{
- *qpdf->oh_cache[oh]}));
- }
+ return do_with_oh<qpdf_oh>(
+ qpdf, oh,
+ [&qpdf](){ return qpdf_oh_new_array(qpdf); },
+ [&qpdf](QPDFObjectHandle& qoh) {
+ if (qoh.isArray())
+ {
+ QTC::TC("qpdf", "qpdf-c array to wrap_in_array");
+ return new_object(qpdf, qoh);
+ }
+ else
+ {
+ QTC::TC("qpdf", "qpdf-c non-array to wrap_in_array");
+ return new_object(qpdf,
+ QPDFObjectHandle::newArray(
+ std::vector<QPDFObjectHandle>{qoh}));
+ }
+ });
}
qpdf_oh qpdf_oh_parse(qpdf_data qpdf, char const* object_str)
{
QTC::TC("qpdf", "qpdf-c called qpdf_oh_parse");
- return new_object(qpdf, QPDFObjectHandle::parse(object_str));
+ return trap_oh_errors<qpdf_oh>(
+ qpdf, return_uninitialized(qpdf), [&object_str] (qpdf_data q) {
+ return new_object(q, QPDFObjectHandle::parse(object_str));
+ });
}
QPDF_BOOL qpdf_oh_get_bool_value(qpdf_data qpdf, qpdf_oh oh)
{
- if (! qpdf_oh_valid_internal(qpdf, oh))
- {
- return QPDF_FALSE;
- }
- QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_bool_value");
- return qpdf->oh_cache[oh]->getBoolValue();
+ return do_with_oh<QPDF_BOOL>(
+ qpdf, oh, return_false, [](QPDFObjectHandle& o) {
+ QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_bool_value");
+ return o.getBoolValue();
+ });
}
long long qpdf_oh_get_int_value(qpdf_data qpdf, qpdf_oh oh)
{
- if (! qpdf_oh_valid_internal(qpdf, oh))
- {
- return 0LL;
- }
- QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_int_value");
- return qpdf->oh_cache[oh]->getIntValue();
+ return do_with_oh<long long>(
+ qpdf, oh, return_T<long long>(0LL), [](QPDFObjectHandle& o) {
+ QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_int_value");
+ return o.getIntValue();
+ });
}
int qpdf_oh_get_int_value_as_int(qpdf_data qpdf, qpdf_oh oh)
{
- if (! qpdf_oh_valid_internal(qpdf, oh))
- {
- return 0;
- }
- QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_int_value_as_int");
- return qpdf->oh_cache[oh]->getIntValueAsInt();
+ return do_with_oh<int>(
+ qpdf, oh, return_T<int>(0), [](QPDFObjectHandle& o) {
+ QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_int_value_as_int");
+ return o.getIntValueAsInt();
+ });
}
unsigned long long qpdf_oh_get_uint_value(qpdf_data qpdf, qpdf_oh oh)
{
- if (! qpdf_oh_valid_internal(qpdf, oh))
- {
- return 0ULL;
- }
- QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_uint_value");
- return qpdf->oh_cache[oh]->getUIntValue();
+ return do_with_oh<unsigned long long>(
+ qpdf, oh, return_T<unsigned long long>(0ULL), [](QPDFObjectHandle& o) {
+ QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_uint_value");
+ return o.getUIntValue();
+ });
}
unsigned int qpdf_oh_get_uint_value_as_uint(qpdf_data qpdf, qpdf_oh oh)
{
- if (! qpdf_oh_valid_internal(qpdf, oh))
- {
- return 0U;
- }
- QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_uint_value_as_uint");
- return qpdf->oh_cache[oh]->getUIntValueAsUInt();
+ return do_with_oh<unsigned int>(
+ qpdf, oh, return_T<unsigned int>(0U), [](QPDFObjectHandle& o) {
+ QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_uint_value_as_uint");
+ return o.getUIntValueAsUInt();
+ });
}
char const* qpdf_oh_get_real_value(qpdf_data qpdf, qpdf_oh oh)
{
- if (! qpdf_oh_valid_internal(qpdf, oh))
- {
- return "";
- }
- QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_real_value");
- qpdf->tmp_string = qpdf->oh_cache[oh]->getRealValue();
- return qpdf->tmp_string.c_str();
-}
-
-QPDF_BOOL qpdf_oh_is_number(qpdf_data qpdf, qpdf_oh oh)
-{
- if (! qpdf_oh_valid_internal(qpdf, oh))
- {
- return QPDF_FALSE;
- }
- QTC::TC("qpdf", "qpdf-c called qpdf_oh_is_number");
- return qpdf->oh_cache[oh]->isNumber();
+ return do_with_oh<char const*>(
+ qpdf, oh, return_T<char const*>(""), [&qpdf](QPDFObjectHandle& o) {
+ QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_real_value");
+ qpdf->tmp_string = o.getRealValue();
+ return qpdf->tmp_string.c_str();
+ });
}
double qpdf_oh_get_numeric_value(qpdf_data qpdf, qpdf_oh oh)
{
- if (! qpdf_oh_valid_internal(qpdf, oh))
- {
- return 0.0;
- }
- QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_numeric_value");
- return qpdf->oh_cache[oh]->getNumericValue();
+ return do_with_oh<double>(
+ qpdf, oh, return_T<double>(0.0), [](QPDFObjectHandle& o) {
+ QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_numeric_value");
+ return o.getNumericValue();
+ });
}
char const* qpdf_oh_get_name(qpdf_data qpdf, qpdf_oh oh)
{
- if (! qpdf_oh_valid_internal(qpdf, oh))
- {
- return "";
- }
- QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_name");
- qpdf->tmp_string = qpdf->oh_cache[oh]->getName();
- return qpdf->tmp_string.c_str();
+ return do_with_oh<char const*>(
+ qpdf, oh, return_T<char const*>(""), [&qpdf](QPDFObjectHandle& o) {
+ QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_name");
+ qpdf->tmp_string = o.getName();
+ return qpdf->tmp_string.c_str();
+ });
}
char const* qpdf_oh_get_string_value(qpdf_data qpdf, qpdf_oh oh)
{
- if (! qpdf_oh_valid_internal(qpdf, oh))
- {
- return "";
- }
- QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_string_value");
- qpdf->tmp_string = qpdf->oh_cache[oh]->getStringValue();
- return qpdf->tmp_string.c_str();
+ return do_with_oh<char const*>(
+ qpdf, oh, return_T<char const*>(""), [&qpdf](QPDFObjectHandle& o) {
+ QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_string_value");
+ qpdf->tmp_string = o.getStringValue();
+ return qpdf->tmp_string.c_str();
+ });
}
char const* qpdf_oh_get_utf8_value(qpdf_data qpdf, qpdf_oh oh)
{
- if (! qpdf_oh_valid_internal(qpdf, oh))
- {
- return "";
- }
- QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_utf8_value");
- qpdf->tmp_string = qpdf->oh_cache[oh]->getUTF8Value();
- return qpdf->tmp_string.c_str();
+ return do_with_oh<char const*>(
+ qpdf, oh, return_T<char const*>(""), [&qpdf](QPDFObjectHandle& o) {
+ QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_utf8_value");
+ qpdf->tmp_string = o.getUTF8Value();
+ return qpdf->tmp_string.c_str();
+ });
}
int qpdf_oh_get_array_n_items(qpdf_data qpdf, qpdf_oh oh)
{
- if (! qpdf_oh_valid_internal(qpdf, oh))
- {
- return 0;
- }
- QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_array_n_items");
- return qpdf->oh_cache[oh]->getArrayNItems();
+ return do_with_oh<int>(
+ qpdf, oh, return_T<int>(0), [](QPDFObjectHandle& o) {
+ QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_array_n_items");
+ return o.getArrayNItems();
+ });
}
qpdf_oh qpdf_oh_get_array_item(qpdf_data qpdf, qpdf_oh oh, int n)
{
- if (! qpdf_oh_valid_internal(qpdf, oh))
- {
- return qpdf_oh_new_null(qpdf);
- }
- QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_array_item");
- return new_object(qpdf, qpdf->oh_cache[oh]->getArrayItem(n));
+ return do_with_oh<qpdf_oh>(
+ qpdf, oh, return_null(qpdf), [&qpdf, &n](QPDFObjectHandle& o) {
+ QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_array_item");
+ return new_object(qpdf, o.getArrayItem(n));
+ });
}
void qpdf_oh_begin_dict_key_iter(qpdf_data qpdf, qpdf_oh oh)
{
- if (qpdf_oh_valid_internal(qpdf, oh) &&
- qpdf_oh_is_dictionary(qpdf, oh))
- {
- QTC::TC("qpdf", "qpdf-c called qpdf_oh_begin_dict_key_iter");
- qpdf->cur_iter_dict_keys = qpdf->oh_cache[oh]->getKeys();
- }
- else
- {
- qpdf->cur_iter_dict_keys = {};
- }
+ qpdf->cur_iter_dict_keys = do_with_oh<std::set<std::string>>(
+ qpdf, oh,
+ [](){ return std::set<std::string>(); },
+ [](QPDFObjectHandle& o) {
+ QTC::TC("qpdf", "qpdf-c called qpdf_oh_begin_dict_key_iter");
+ return o.getKeys();
+ });
qpdf->dict_iter = qpdf->cur_iter_dict_keys.begin();
}
@@ -1197,32 +1320,35 @@ char const* qpdf_oh_dict_next_key(qpdf_data qpdf)
QPDF_BOOL qpdf_oh_has_key(qpdf_data qpdf, qpdf_oh oh, char const* key)
{
- if (! qpdf_oh_valid_internal(qpdf, oh))
- {
- return QPDF_FALSE;
- }
- QTC::TC("qpdf", "qpdf-c called qpdf_oh_has_key");
- return qpdf->oh_cache[oh]->hasKey(key);
+ return do_with_oh<QPDF_BOOL>(
+ qpdf, oh, return_false, [&key](QPDFObjectHandle& o) {
+ QTC::TC("qpdf", "qpdf-c called qpdf_oh_has_key");
+ return o.hasKey(key);
+ });
}
qpdf_oh qpdf_oh_get_key(qpdf_data qpdf, qpdf_oh oh, char const* key)
{
- if (! qpdf_oh_valid_internal(qpdf, oh))
- {
- return qpdf_oh_new_null(qpdf);
- }
- QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_key");
- return new_object(qpdf, qpdf->oh_cache[oh]->getKey(key));
+ return do_with_oh<qpdf_oh>(
+ qpdf, oh, return_null(qpdf), [&qpdf, &key](QPDFObjectHandle& o) {
+ QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_key");
+ return new_object(qpdf, o.getKey(key));
+ });
}
QPDF_BOOL qpdf_oh_is_or_has_name(qpdf_data qpdf, qpdf_oh oh, char const* key)
{
- if (! qpdf_oh_valid_internal(qpdf, oh))
- {
- return QPDF_FALSE;
- }
- QTC::TC("qpdf", "qpdf-c called qpdf_oh_is_or_has_name");
- return qpdf->oh_cache[oh]->isOrHasName(key);
+ return do_with_oh<QPDF_BOOL>(
+ qpdf, oh, return_false, [&key](QPDFObjectHandle& o) {
+ QTC::TC("qpdf", "qpdf-c called qpdf_oh_is_or_has_name");
+ return o.isOrHasName(key);
+ });
+}
+
+qpdf_oh qpdf_oh_new_uninitialized(qpdf_data qpdf)
+{
+ QTC::TC("qpdf", "qpdf-c called qpdf_oh_new_uninitialized");
+ return new_object(qpdf, QPDFObjectHandle());
}
qpdf_oh qpdf_oh_new_null(qpdf_data qpdf)
@@ -1288,156 +1414,143 @@ qpdf_oh qpdf_oh_new_dictionary(qpdf_data qpdf)
void qpdf_oh_make_direct(qpdf_data qpdf, qpdf_oh oh)
{
- if (qpdf_oh_valid_internal(qpdf, oh))
- {
- QTC::TC("qpdf", "qpdf-c called qpdf_oh_make_direct");
- qpdf->oh_cache[oh]->makeDirect();
- }
+ do_with_oh_void(
+ qpdf, oh, [](QPDFObjectHandle& o) {
+ QTC::TC("qpdf", "qpdf-c called qpdf_oh_make_direct");
+ o.makeDirect();
+ });
}
static QPDFObjectHandle
qpdf_oh_item_internal(qpdf_data qpdf, qpdf_oh item)
{
- if (qpdf_oh_valid_internal(qpdf, item))
- {
- return *(qpdf->oh_cache[item]);
- }
- else
- {
- return QPDFObjectHandle::newNull();
- }
+ return do_with_oh<QPDFObjectHandle>(
+ qpdf, item,
+ [](){return QPDFObjectHandle::newNull();},
+ [](QPDFObjectHandle& o) {
+ return o;
+ });
}
void qpdf_oh_set_array_item(qpdf_data qpdf, qpdf_oh oh,
int at, qpdf_oh item)
{
- if (qpdf_oh_is_array(qpdf, oh))
- {
- QTC::TC("qpdf", "qpdf-c called qpdf_oh_set_array_item");
- qpdf->oh_cache[oh]->setArrayItem(
- at, qpdf_oh_item_internal(qpdf, item));
- }
+ do_with_oh_void(
+ qpdf, oh, [&qpdf, &at, &item](QPDFObjectHandle& o) {
+ QTC::TC("qpdf", "qpdf-c called qpdf_oh_set_array_item");
+ o.setArrayItem(at, qpdf_oh_item_internal(qpdf, item));
+ });
}
void qpdf_oh_insert_item(qpdf_data qpdf, qpdf_oh oh, int at, qpdf_oh item)
{
- if (qpdf_oh_is_array(qpdf, oh))
- {
- QTC::TC("qpdf", "qpdf-c called qpdf_oh_insert_item");
- qpdf->oh_cache[oh]->insertItem(
- at, qpdf_oh_item_internal(qpdf, item));
- }
+ do_with_oh_void(
+ qpdf, oh, [&qpdf, &at, &item](QPDFObjectHandle& o) {
+ QTC::TC("qpdf", "qpdf-c called qpdf_oh_insert_item");
+ o.insertItem(at, qpdf_oh_item_internal(qpdf, item));
+ });
}
void qpdf_oh_append_item(qpdf_data qpdf, qpdf_oh oh, qpdf_oh item)
{
- if (qpdf_oh_is_array(qpdf, oh))
- {
- QTC::TC("qpdf", "qpdf-c called qpdf_oh_append_item");
- qpdf->oh_cache[oh]->appendItem(
- qpdf_oh_item_internal(qpdf, item));
- }
+ do_with_oh_void(
+ qpdf, oh, [&qpdf, &item](QPDFObjectHandle& o) {
+ QTC::TC("qpdf", "qpdf-c called qpdf_oh_append_item");
+ o.appendItem(qpdf_oh_item_internal(qpdf, item));
+ });
}
void qpdf_oh_erase_item(qpdf_data qpdf, qpdf_oh oh, int at)
{
- if (qpdf_oh_is_array(qpdf, oh))
- {
- QTC::TC("qpdf", "qpdf-c called qpdf_oh_erase_item");
- qpdf->oh_cache[oh]->eraseItem(at);
- }
+ do_with_oh_void(
+ qpdf, oh, [&at](QPDFObjectHandle& o) {
+ QTC::TC("qpdf", "qpdf-c called qpdf_oh_erase_item");
+ o.eraseItem(at);
+ });
}
void qpdf_oh_replace_key(qpdf_data qpdf, qpdf_oh oh,
char const* key, qpdf_oh item)
{
- if (qpdf_oh_is_dictionary(qpdf, oh))
- {
- QTC::TC("qpdf", "qpdf-c called qpdf_oh_replace_key");
- qpdf->oh_cache[oh]->replaceKey(
- key, qpdf_oh_item_internal(qpdf, item));
- }
+ do_with_oh_void(
+ qpdf, oh, [&qpdf, &key, &item](QPDFObjectHandle& o) {
+ QTC::TC("qpdf", "qpdf-c called qpdf_oh_replace_key");
+ o.replaceKey(key, qpdf_oh_item_internal(qpdf, item));
+ });
}
void qpdf_oh_remove_key(qpdf_data qpdf, qpdf_oh oh, char const* key)
{
- if (qpdf_oh_is_dictionary(qpdf, oh))
- {
- QTC::TC("qpdf", "qpdf-c called qpdf_oh_remove_key");
- qpdf->oh_cache[oh]->removeKey(key);
- }
+ do_with_oh_void(
+ qpdf, oh, [&key](QPDFObjectHandle& o) {
+ QTC::TC("qpdf", "qpdf-c called qpdf_oh_remove_key");
+ o.removeKey(key);
+ });
}
void qpdf_oh_replace_or_remove_key(qpdf_data qpdf, qpdf_oh oh,
char const* key, qpdf_oh item)
{
- if (qpdf_oh_is_dictionary(qpdf, oh))
- {
- QTC::TC("qpdf", "qpdf-c called qpdf_oh_replace_or_remove_key");
- qpdf->oh_cache[oh]->replaceOrRemoveKey(
- key, qpdf_oh_item_internal(qpdf, item));
- }
+ do_with_oh_void(
+ qpdf, oh, [&qpdf, &key, &item](QPDFObjectHandle& o) {
+ QTC::TC("qpdf", "qpdf-c called qpdf_oh_replace_or_remove_key");
+ o.replaceOrRemoveKey(key, qpdf_oh_item_internal(qpdf, item));
+ });
}
qpdf_oh qpdf_oh_get_dict(qpdf_data qpdf, qpdf_oh oh)
{
- if (! qpdf_oh_valid_internal(qpdf, oh))
- {
- return qpdf_oh_new_null(qpdf);
- }
- QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_dict");
- return new_object(qpdf, qpdf->oh_cache[oh]->getDict());
+ return do_with_oh<qpdf_oh>(
+ qpdf, oh, return_null(qpdf), [&qpdf](QPDFObjectHandle& o) {
+ QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_dict");
+ return new_object(qpdf, o.getDict());
+ });
}
int qpdf_oh_get_object_id(qpdf_data qpdf, qpdf_oh oh)
{
- if (! qpdf_oh_valid_internal(qpdf, oh))
- {
- return 0;
- }
- QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_object_id");
- return qpdf->oh_cache[oh]->getObjectID();
+ return do_with_oh<int>(
+ qpdf, oh, return_T<int>(0), [](QPDFObjectHandle& o) {
+ QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_object_id");
+ return o.getObjectID();
+ });
}
int qpdf_oh_get_generation(qpdf_data qpdf, qpdf_oh oh)
{
- if (! qpdf_oh_valid_internal(qpdf, oh))
- {
- return 0;
- }
- QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_generation");
- return qpdf->oh_cache[oh]->getGeneration();
+ return do_with_oh<int>(
+ qpdf, oh, return_T<int>(0), [](QPDFObjectHandle& o) {
+ QTC::TC("qpdf", "qpdf-c called qpdf_oh_get_generation");
+ return o.getGeneration();
+ });
}
char const* qpdf_oh_unparse(qpdf_data qpdf, qpdf_oh oh)
{
- if (! qpdf_oh_valid_internal(qpdf, oh))
- {
- return "";
- }
- QTC::TC("qpdf", "qpdf-c called qpdf_oh_unparse");
- qpdf->tmp_string = qpdf->oh_cache[oh]->unparse();
- return qpdf->tmp_string.c_str();
+ return do_with_oh<char const*>(
+ qpdf, oh, return_T<char const*>(""), [&qpdf](QPDFObjectHandle& o) {
+ QTC::TC("qpdf", "qpdf-c called qpdf_oh_unparse");
+ qpdf->tmp_string = o.unparse();
+ return qpdf->tmp_string.c_str();
+ });
}
char const* qpdf_oh_unparse_resolved(qpdf_data qpdf, qpdf_oh oh)
{
- if (! qpdf_oh_valid_internal(qpdf, oh))
- {
- return "";
- }
- QTC::TC("qpdf", "qpdf-c called qpdf_oh_unparse_resolved");
- qpdf->tmp_string = qpdf->oh_cache[oh]->unparseResolved();
- return qpdf->tmp_string.c_str();
+ return do_with_oh<char const*>(
+ qpdf, oh, return_T<char const*>(""), [&qpdf](QPDFObjectHandle& o) {
+ QTC::TC("qpdf", "qpdf-c called qpdf_oh_unparse_resolved");
+ qpdf->tmp_string = o.unparseResolved();
+ return qpdf->tmp_string.c_str();
+ });
}
char const* qpdf_oh_unparse_binary(qpdf_data qpdf, qpdf_oh oh)
{
- if (! qpdf_oh_valid_internal(qpdf, oh))
- {
- return "";
- }
- QTC::TC("qpdf", "qpdf-c called qpdf_oh_unparse_binary");
- qpdf->tmp_string = qpdf->oh_cache[oh]->unparseBinary();
- return qpdf->tmp_string.c_str();
+ return do_with_oh<char const*>(
+ qpdf, oh, return_T<char const*>(""), [&qpdf](QPDFObjectHandle& o) {
+ QTC::TC("qpdf", "qpdf-c called qpdf_oh_unparse_binary");
+ qpdf->tmp_string = o.unparseBinary();
+ return qpdf->tmp_string.c_str();
+ });
}
diff --git a/manual/qpdf-manual.xml b/manual/qpdf-manual.xml
index fd3bd6fb..f7d224e5 100644
--- a/manual/qpdf-manual.xml
+++ b/manual/qpdf-manual.xml
@@ -5231,6 +5231,19 @@ print "\n";
</listitem>
<listitem>
<para>
+ Overhaul error handling for the object handle functions in
+ the C API. See comments in the &ldquo;Object handling&rdquo;
+ section of <filename>include/qpdf/qpdf-c.h</filename> for
+ details. In particular, exceptions thrown by the underlying
+ C++ code when calling object accessors are caught and
+ converted into errors. The errors can be trapped by
+ registering an error handler with
+ <function>qpdf_register_oh_error_handler</function> or will
+ be written to stderr if no handler is registered.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
Add <function>qpdf_get_last_string_length</function> to the
C API to get the length of the last string that was
returned. This is needed to handle strings that contain
@@ -5239,9 +5252,9 @@ print "\n";
</listitem>
<listitem>
<para>
- Add <function>qpdf_oh_is_initialized</function> to the
- C API. While you can't directly create uninitialized objects
- from the C API, you still have to be able to detect them.
+ Add <function>qpdf_oh_is_initialized</function> and
+ <function>qpdf_oh_new_uninitialized</function> to the C API
+ to make it possible to work with uninitialized objects.
</para>
</listitem>
<listitem>
diff --git a/qpdf/qpdf-ctest.c b/qpdf/qpdf-ctest.c
index 4d30712e..6ee19004 100644
--- a/qpdf/qpdf-ctest.c
+++ b/qpdf/qpdf-ctest.c
@@ -35,28 +35,29 @@ static FILE* safe_fopen(char const* filename, char const* mode)
return f;
}
-static void report_errors()
+static void print_error(char const* label, qpdf_data qpdf, qpdf_error e)
{
#define POS_FMT " pos : " LL_FMT "\n"
+ printf("%s: %s\n", label, qpdf_get_error_full_text(qpdf, e));
+ printf(" code: %d\n", qpdf_get_error_code(qpdf, e));
+ printf(" file: %s\n", qpdf_get_error_filename(qpdf, e));
+ printf(POS_FMT, qpdf_get_error_file_position(qpdf, e));
+ printf(" text: %s\n", qpdf_get_error_message_detail(qpdf, e));
+}
+
+static void report_errors()
+{
qpdf_error e = 0;
while (qpdf_more_warnings(qpdf))
{
e = qpdf_next_warning(qpdf);
- printf("warning: %s\n", qpdf_get_error_full_text(qpdf, e));
- printf(" code: %d\n", qpdf_get_error_code(qpdf, e));
- printf(" file: %s\n", qpdf_get_error_filename(qpdf, e));
- printf(POS_FMT, qpdf_get_error_file_position(qpdf, e));
- printf(" text: %s\n", qpdf_get_error_message_detail(qpdf, e));
+ print_error("warning", qpdf, e);
}
if (qpdf_has_error(qpdf))
{
e = qpdf_get_error(qpdf);
assert(qpdf_has_error(qpdf) == QPDF_FALSE);
- printf("error: %s\n", qpdf_get_error_full_text(qpdf, e));
- printf(" code: %d\n", qpdf_get_error_code(qpdf, e));
- printf(" file: %s\n", qpdf_get_error_filename(qpdf, e));
- printf(POS_FMT, qpdf_get_error_file_position(qpdf, e));
- printf(" text: %s\n", qpdf_get_error_message_detail(qpdf, e));
+ print_error("error", qpdf, e);
}
else
{
@@ -72,6 +73,16 @@ static void report_errors()
}
}
+static void handle_oh_error(qpdf_data qpdf, qpdf_error error, void* data)
+{
+ char const* label = "oh error";
+ if (data)
+ {
+ label = *((char const**)data);
+ }
+ print_error(label, qpdf, error);
+}
+
static void read_file_into_memory(char const* filename,
char** buf, unsigned long* size)
{
@@ -615,8 +626,11 @@ static void test24(char const* infile,
*/
qpdf_oh_replace_key(qpdf, resources, "/ProcSet", procset);
- /* Release and access to exercise warnings and to show that write
- * still works after releasing.
+ /* Release and access to exercise handling of object handle errors
+ * and to show that write still works after releasing. This test
+ * uses the default oh error handler, so messages get written to
+ * stderr. The warning about using the default error handler only
+ * appears once.
*/
qpdf_oh_release(qpdf, page1);
contents = qpdf_oh_get_key(qpdf, page1, "/Contents");
@@ -791,6 +805,82 @@ static void test28(char const* infile,
}
}
+static void test29(char const* infile,
+ char const* password,
+ char const* outfile,
+ char const* outfile2)
+{
+ /* Trap exceptions thrown by object accessors. Type mismatches are
+ * errors rather than warnings when they don't have an owning QPDF
+ * object.
+ */
+ char const* label = "oh error";
+ qpdf_register_oh_error_handler(qpdf, handle_oh_error, (void*)&label);
+
+ /* get_root fails when we have no trailer */
+ label = "get root";
+ qpdf_oh root = qpdf_get_root(qpdf);
+ assert(root != 0);
+ assert(! qpdf_oh_is_initialized(qpdf, root));
+
+ label = "bad parse";
+ assert(! qpdf_oh_is_initialized(qpdf, qpdf_oh_parse(qpdf, "[oops")));
+ report_errors();
+
+ label = "type mismatch";
+ assert(qpdf_oh_get_int_value_as_int(
+ qpdf, qpdf_oh_new_string(qpdf, "x")) == 0);
+ qpdf_oh int_oh = qpdf_oh_new_integer(qpdf, 12);
+ assert(strlen(qpdf_oh_get_string_value(qpdf, int_oh)) == 0);
+
+ // This doesn't test every possible error flow, but it tests each
+ // way of handling errors in the library code.
+ label = "array type mismatch";
+ assert(qpdf_oh_get_array_n_items(qpdf, int_oh) == 0);
+ assert(qpdf_oh_is_null(qpdf, qpdf_oh_get_array_item(qpdf, int_oh, 3)));
+ label = "append to non-array";
+ qpdf_oh_append_item(qpdf, int_oh, qpdf_oh_new_null(qpdf));
+ qpdf_oh array = qpdf_oh_new_array(qpdf);
+ label = "array bounds";
+ assert(qpdf_oh_is_null(qpdf, qpdf_oh_get_array_item(qpdf, array, 3)));
+
+ label = "dictionary iter type mismatch";
+ qpdf_oh_begin_dict_key_iter(qpdf, int_oh);
+ assert(qpdf_oh_dict_more_keys(qpdf) == QPDF_FALSE);
+ label = "dictionary type mismatch";
+ assert(qpdf_oh_is_null(qpdf, qpdf_oh_get_key(qpdf, int_oh, "potato")));
+ assert(qpdf_oh_has_key(qpdf, int_oh, "potato") == QPDF_FALSE);
+
+ report_errors();
+}
+
+static void test30(char const* infile,
+ char const* password,
+ char const* outfile,
+ char const* outfile2)
+{
+ assert(qpdf_read(qpdf, infile, password) & QPDF_ERRORS);
+ /* Fail to handle error */
+}
+
+static void test31(char const* infile,
+ char const* password,
+ char const* outfile,
+ char const* outfile2)
+{
+ /* Make sure type warnings have a specific error code. This test
+ * case is designed for minimal.pdf.
+ */
+ qpdf_read(qpdf, infile, password);
+ qpdf_oh trailer = qpdf_get_trailer(qpdf);
+ assert(qpdf_oh_get_int_value(qpdf, trailer) == 0LL);
+ assert(! qpdf_has_error(qpdf));
+ assert(qpdf_more_warnings(qpdf));
+ qpdf_error e = qpdf_next_warning(qpdf);
+ assert(qpdf_get_error_code(qpdf, e) == qpdf_e_object);
+ report_errors();
+}
+
int main(int argc, char* argv[])
{
char* p = 0;
@@ -859,6 +949,9 @@ int main(int argc, char* argv[])
(n == 26) ? test26 :
(n == 27) ? test27 :
(n == 28) ? test28 :
+ (n == 29) ? test29 :
+ (n == 30) ? test30 :
+ (n == 31) ? test31 :
0);
if (fn == 0)
diff --git a/qpdf/qpdf.testcov b/qpdf/qpdf.testcov
index e3f6b00d..67311b01 100644
--- a/qpdf/qpdf.testcov
+++ b/qpdf/qpdf.testcov
@@ -602,3 +602,7 @@ QPDFObjectHandle check ownership 0
qpdf weak crypto warning 0
qpdf-c called qpdf_oh_is_initialized 0
qpdf-c registered progress reporter 0
+qpdf-c called qpdf_oh_new_uninitialized 0
+qpdf-c warn about oh error 1
+qpdf-c registered oh error handler 0
+qpdf-c cleanup warned about unhandled error 0
diff --git a/qpdf/qtest/qpdf.test b/qpdf/qtest/qpdf.test
index 21f82a22..384d9dca 100644
--- a/qpdf/qtest/qpdf.test
+++ b/qpdf/qtest/qpdf.test
@@ -4812,7 +4812,7 @@ foreach my $i (@c_check_types)
show_ntests();
# ----------
$td->notify("--- C API Object Handle ---");
-$n_tests += 7;
+$n_tests += 10;
$td->runtest("C check object handles",
{$td->COMMAND => "qpdf-ctest 24 minimal.pdf '' a.pdf"},
@@ -4843,6 +4843,18 @@ $td->runtest("C wrap and clone objects",
{$td->COMMAND => "qpdf-ctest 28 minimal.pdf '' ''"},
{$td->STRING => "", $td->EXIT_STATUS => 0},
$td->NORMALIZE_NEWLINES);
+$td->runtest("C object handle errors",
+ {$td->COMMAND => "qpdf-ctest 29 minimal.pdf '' ''"},
+ {$td->FILE => "c-oh-errors.out", $td->EXIT_STATUS => 0},
+ $td->NORMALIZE_NEWLINES);
+$td->runtest("C unhandled error warning",
+ {$td->COMMAND => "qpdf-ctest 30 bad1.pdf '' ''"},
+ {$td->FILE => "c-unhandled-error.out", $td->EXIT_STATUS => 0},
+ $td->NORMALIZE_NEWLINES);
+$td->runtest("C type mismatch warning",
+ {$td->COMMAND => "qpdf-ctest 31 minimal.pdf '' ''"},
+ {$td->FILE => "c-type-warning.out", $td->EXIT_STATUS => 0},
+ $td->NORMALIZE_NEWLINES);
show_ntests();
# ----------
diff --git a/qpdf/qtest/qpdf/c-object-handles.out b/qpdf/qtest/qpdf/c-object-handles.out
index 675bacac..feef275e 100644
--- a/qpdf/qtest/qpdf/c-object-handles.out
+++ b/qpdf/qtest/qpdf/c-object-handles.out
@@ -7,18 +7,11 @@ item 0: 0 0.00
item 1: 0 0.00
item 2: 612 612.00
item 3: 792 792.00
-warning: minimal.pdf (C API object handle 6): attempted access to unknown object handle
- code: 5
+minimal.pdf (C API object handle 6): attempted access to unknown object handle
+minimal.pdf (C API object handle 9): attempted access to unknown object handle
+minimal.pdf (C API object handle 9): attempted access to unknown object handle
+warning: minimal.pdf: C API object handle accessor errors occurred, and the application did not define an error handler
+ code: 1
file: minimal.pdf
pos : 0
- text: attempted access to unknown object handle
-warning: minimal.pdf (C API object handle 9): attempted access to unknown object handle
- code: 5
- file: minimal.pdf
- pos : 0
- text: attempted access to unknown object handle
-warning: minimal.pdf (C API object handle 9): attempted access to unknown object handle
- code: 5
- file: minimal.pdf
- pos : 0
- text: attempted access to unknown object handle
+ text: C API object handle accessor errors occurred, and the application did not define an error handler
diff --git a/qpdf/qtest/qpdf/c-oh-errors.out b/qpdf/qtest/qpdf/c-oh-errors.out
new file mode 100644
index 00000000..e1971759
--- /dev/null
+++ b/qpdf/qtest/qpdf/c-oh-errors.out
@@ -0,0 +1,55 @@
+get root: attempted to dereference an uninitialized QPDFObjectHandle
+ code: 1
+ file:
+ pos : 0
+ text: attempted to dereference an uninitialized QPDFObjectHandle
+bad parse: parsed object (offset 1): unknown token while reading object; treating as string
+ code: 5
+ file: parsed object
+ pos : 1
+ text: unknown token while reading object; treating as string
+type mismatch: operation for integer attempted on object of type string: returning 0
+ code: 7
+ file:
+ pos : 0
+ text: operation for integer attempted on object of type string: returning 0
+type mismatch: operation for string attempted on object of type integer: returning empty string
+ code: 7
+ file:
+ pos : 0
+ text: operation for string attempted on object of type integer: returning empty string
+array type mismatch: operation for array attempted on object of type integer: treating as empty
+ code: 7
+ file:
+ pos : 0
+ text: operation for array attempted on object of type integer: treating as empty
+array type mismatch: operation for array attempted on object of type integer: returning null
+ code: 7
+ file:
+ pos : 0
+ text: operation for array attempted on object of type integer: returning null
+append to non-array: operation for array attempted on object of type integer: ignoring attempt to append item
+ code: 7
+ file:
+ pos : 0
+ text: operation for array attempted on object of type integer: ignoring attempt to append item
+array bounds: returning null for out of bounds array access
+ code: 7
+ file:
+ pos : 0
+ text: returning null for out of bounds array access
+dictionary iter type mismatch: operation for dictionary attempted on object of type integer: treating as empty
+ code: 7
+ file:
+ pos : 0
+ text: operation for dictionary attempted on object of type integer: treating as empty
+dictionary type mismatch: operation for dictionary attempted on object of type integer: returning null for attempted key retrieval
+ code: 7
+ file:
+ pos : 0
+ text: operation for dictionary attempted on object of type integer: returning null for attempted key retrieval
+dictionary type mismatch: operation for dictionary attempted on object of type integer: returning false for a key containment request
+ code: 7
+ file:
+ pos : 0
+ text: operation for dictionary attempted on object of type integer: returning false for a key containment request
diff --git a/qpdf/qtest/qpdf/c-type-warning.out b/qpdf/qtest/qpdf/c-type-warning.out
new file mode 100644
index 00000000..f7829657
--- /dev/null
+++ b/qpdf/qtest/qpdf/c-type-warning.out
@@ -0,0 +1 @@
+WARNING: minimal.pdf, trailer at offset 715: operation for integer attempted on object of type dictionary: returning 0
diff --git a/qpdf/qtest/qpdf/c-unhandled-error.out b/qpdf/qtest/qpdf/c-unhandled-error.out
new file mode 100644
index 00000000..53109397
--- /dev/null
+++ b/qpdf/qtest/qpdf/c-unhandled-error.out
@@ -0,0 +1,5 @@
+WARNING: bad1.pdf: can't find PDF header
+WARNING: bad1.pdf: file is damaged
+WARNING: bad1.pdf: can't find startxref
+WARNING: bad1.pdf: Attempting to reconstruct cross-reference table
+WARNING: application did not handle error: bad1.pdf: unable to find trailer dictionary while recovering damaged file