mirror of
https://github.com/qpdf/qpdf.git
synced 2024-12-23 11:28:56 +00:00
9b28933647
When adding a QPDFObjectHandle to an array or dictionary, if possible, check if the new object belongs to the same QPDF. This makes it much easier to find incorrect code than waiting for the situation to be detected when the file is written.
3536 lines
92 KiB
C++
3536 lines
92 KiB
C++
#include <qpdf/QPDFObjectHandle.hh>
|
|
|
|
#include <qpdf/QPDF.hh>
|
|
#include <qpdf/QPDF_Bool.hh>
|
|
#include <qpdf/QPDF_Null.hh>
|
|
#include <qpdf/QPDF_Integer.hh>
|
|
#include <qpdf/QPDF_Real.hh>
|
|
#include <qpdf/QPDF_Name.hh>
|
|
#include <qpdf/QPDF_String.hh>
|
|
#include <qpdf/QPDF_Operator.hh>
|
|
#include <qpdf/QPDF_InlineImage.hh>
|
|
#include <qpdf/QPDF_Array.hh>
|
|
#include <qpdf/QPDF_Dictionary.hh>
|
|
#include <qpdf/QPDF_Stream.hh>
|
|
#include <qpdf/QPDF_Reserved.hh>
|
|
#include <qpdf/Pl_Buffer.hh>
|
|
#include <qpdf/Pl_QPDFTokenizer.hh>
|
|
#include <qpdf/BufferInputSource.hh>
|
|
#include <qpdf/QPDFExc.hh>
|
|
#include <qpdf/QPDFPageObjectHelper.hh>
|
|
#include <qpdf/SparseOHArray.hh>
|
|
#include <qpdf/QPDFMatrix.hh>
|
|
|
|
#include <qpdf/QTC.hh>
|
|
#include <qpdf/QUtil.hh>
|
|
#include <qpdf/QIntC.hh>
|
|
|
|
#include <stdexcept>
|
|
#include <stdlib.h>
|
|
#include <ctype.h>
|
|
#include <limits.h>
|
|
#include <cstring>
|
|
#include <algorithm>
|
|
|
|
class TerminateParsing
|
|
{
|
|
};
|
|
|
|
QPDFObjectHandle::StreamDataProvider::StreamDataProvider(
|
|
bool supports_retry) :
|
|
supports_retry(supports_retry)
|
|
{
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::StreamDataProvider::provideStreamData(
|
|
int objid, int generation, Pipeline* pipeline)
|
|
{
|
|
throw std::logic_error(
|
|
"you must override provideStreamData -- see QPDFObjectHandle.hh");
|
|
}
|
|
|
|
bool
|
|
QPDFObjectHandle::StreamDataProvider::provideStreamData(
|
|
int objid, int generation, Pipeline* pipeline,
|
|
bool suppress_warnings, bool will_retry)
|
|
{
|
|
throw std::logic_error(
|
|
"you must override provideStreamData -- see QPDFObjectHandle.hh");
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
QPDFObjectHandle::StreamDataProvider::supportsRetry()
|
|
{
|
|
return this->supports_retry;
|
|
}
|
|
|
|
class CoalesceProvider: public QPDFObjectHandle::StreamDataProvider
|
|
{
|
|
public:
|
|
CoalesceProvider(QPDFObjectHandle containing_page,
|
|
QPDFObjectHandle old_contents) :
|
|
containing_page(containing_page),
|
|
old_contents(old_contents)
|
|
{
|
|
}
|
|
virtual ~CoalesceProvider()
|
|
{
|
|
}
|
|
virtual void provideStreamData(int objid, int generation,
|
|
Pipeline* pipeline);
|
|
|
|
private:
|
|
QPDFObjectHandle containing_page;
|
|
QPDFObjectHandle old_contents;
|
|
};
|
|
|
|
void
|
|
CoalesceProvider::provideStreamData(int, int, Pipeline* p)
|
|
{
|
|
QTC::TC("qpdf", "QPDFObjectHandle coalesce provide stream data");
|
|
std::string description = "page object " +
|
|
QUtil::int_to_string(containing_page.getObjectID()) + " " +
|
|
QUtil::int_to_string(containing_page.getGeneration());
|
|
std::string all_description;
|
|
old_contents.pipeContentStreams(p, description, all_description);
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::TokenFilter::handleEOF()
|
|
{
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::TokenFilter::setPipeline(Pipeline* p)
|
|
{
|
|
this->pipeline = p;
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::TokenFilter::write(char const* data, size_t len)
|
|
{
|
|
if (! this->pipeline)
|
|
{
|
|
return;
|
|
}
|
|
if (len)
|
|
{
|
|
this->pipeline->write(QUtil::unsigned_char_pointer(data), len);
|
|
}
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::TokenFilter::write(std::string const& str)
|
|
{
|
|
write(str.c_str(), str.length());
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::TokenFilter::writeToken(QPDFTokenizer::Token const& token)
|
|
{
|
|
std::string value = token.getRawValue();
|
|
write(value.c_str(), value.length());
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::ParserCallbacks::handleObject(QPDFObjectHandle)
|
|
{
|
|
throw std::logic_error("You must override one of the"
|
|
" handleObject methods in ParserCallbacks");
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::ParserCallbacks::handleObject(
|
|
QPDFObjectHandle oh, size_t, size_t)
|
|
{
|
|
// This version of handleObject was added in qpdf 9. If the
|
|
// developer did not override it, fall back to the older
|
|
// interface.
|
|
handleObject(oh);
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::ParserCallbacks::contentSize(size_t)
|
|
{
|
|
// Ignore by default; overriding this is optional.
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::ParserCallbacks::terminateParsing()
|
|
{
|
|
throw TerminateParsing();
|
|
}
|
|
|
|
class LastChar: public Pipeline
|
|
{
|
|
public:
|
|
LastChar(Pipeline* next);
|
|
virtual ~LastChar() = default;
|
|
virtual void write(unsigned char* data, size_t len);
|
|
virtual void finish();
|
|
unsigned char getLastChar();
|
|
|
|
private:
|
|
unsigned char last_char;
|
|
};
|
|
|
|
LastChar::LastChar(Pipeline* next) :
|
|
Pipeline("lastchar", next),
|
|
last_char(0)
|
|
{
|
|
}
|
|
|
|
void
|
|
LastChar::write(unsigned char* data, size_t len)
|
|
{
|
|
if (len > 0)
|
|
{
|
|
this->last_char = data[len - 1];
|
|
}
|
|
getNext()->write(data, len);
|
|
}
|
|
|
|
void
|
|
LastChar::finish()
|
|
{
|
|
getNext()->finish();
|
|
}
|
|
|
|
unsigned char
|
|
LastChar::getLastChar()
|
|
{
|
|
return this->last_char;
|
|
}
|
|
|
|
QPDFObjectHandle::QPDFObjectHandle() :
|
|
initialized(false),
|
|
qpdf(0),
|
|
objid(0),
|
|
generation(0),
|
|
reserved(false)
|
|
{
|
|
}
|
|
|
|
QPDFObjectHandle::QPDFObjectHandle(QPDF* qpdf, int objid, int generation) :
|
|
initialized(true),
|
|
qpdf(qpdf),
|
|
objid(objid),
|
|
generation(generation),
|
|
reserved(false)
|
|
{
|
|
}
|
|
|
|
QPDFObjectHandle::QPDFObjectHandle(QPDFObject* data) :
|
|
initialized(true),
|
|
qpdf(0),
|
|
objid(0),
|
|
generation(0),
|
|
obj(data),
|
|
reserved(false)
|
|
{
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::releaseResolved()
|
|
{
|
|
// Recursively break any resolved references to indirect objects.
|
|
// Do not cross over indirect object boundaries to avoid an
|
|
// infinite loop. This method may only be called during final
|
|
// destruction. See comments in QPDF::~QPDF().
|
|
if (isIndirect())
|
|
{
|
|
if (this->obj.getPointer())
|
|
{
|
|
this->obj = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
QPDFObject::ObjAccessor::releaseResolved(this->obj.getPointer());
|
|
}
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::setObjectDescriptionFromInput(
|
|
QPDFObjectHandle object, QPDF* context,
|
|
std::string const& description, PointerHolder<InputSource> input,
|
|
qpdf_offset_t offset)
|
|
{
|
|
object.setObjectDescription(
|
|
context,
|
|
input->getName() + ", " + description +
|
|
" at offset " + QUtil::int_to_string(offset));
|
|
}
|
|
|
|
bool
|
|
QPDFObjectHandle::isInitialized() const
|
|
{
|
|
return this->initialized;
|
|
}
|
|
|
|
QPDFObject::object_type_e
|
|
QPDFObjectHandle::getTypeCode()
|
|
{
|
|
if (this->initialized)
|
|
{
|
|
dereference();
|
|
return this->obj->getTypeCode();
|
|
}
|
|
else
|
|
{
|
|
return QPDFObject::ot_uninitialized;
|
|
}
|
|
}
|
|
|
|
char const*
|
|
QPDFObjectHandle::getTypeName()
|
|
{
|
|
if (this->initialized)
|
|
{
|
|
dereference();
|
|
return this->obj->getTypeName();
|
|
}
|
|
else
|
|
{
|
|
return "uninitialized";
|
|
}
|
|
}
|
|
|
|
template <class T>
|
|
class QPDFObjectTypeAccessor
|
|
{
|
|
public:
|
|
static bool check(QPDFObject* o)
|
|
{
|
|
return (o && dynamic_cast<T*>(o));
|
|
}
|
|
static bool check(QPDFObject const* o)
|
|
{
|
|
return (o && dynamic_cast<T const*>(o));
|
|
}
|
|
};
|
|
|
|
bool
|
|
QPDFObjectHandle::isBool()
|
|
{
|
|
if (! this->initialized)
|
|
{
|
|
return false;
|
|
}
|
|
dereference();
|
|
return QPDFObjectTypeAccessor<QPDF_Bool>::check(obj.getPointer());
|
|
}
|
|
|
|
bool
|
|
QPDFObjectHandle::isDirectNull() const
|
|
{
|
|
// Don't call dereference() -- this is a const method, and we know
|
|
// objid == 0, so there's nothing to resolve.
|
|
return (this->initialized && (this->objid == 0) &&
|
|
QPDFObjectTypeAccessor<QPDF_Null>::check(obj.getPointer()));
|
|
}
|
|
|
|
bool
|
|
QPDFObjectHandle::isNull()
|
|
{
|
|
if (! this->initialized)
|
|
{
|
|
return false;
|
|
}
|
|
dereference();
|
|
return QPDFObjectTypeAccessor<QPDF_Null>::check(obj.getPointer());
|
|
}
|
|
|
|
bool
|
|
QPDFObjectHandle::isInteger()
|
|
{
|
|
if (! this->initialized)
|
|
{
|
|
return false;
|
|
}
|
|
dereference();
|
|
return QPDFObjectTypeAccessor<QPDF_Integer>::check(obj.getPointer());
|
|
}
|
|
|
|
bool
|
|
QPDFObjectHandle::isReal()
|
|
{
|
|
if (! this->initialized)
|
|
{
|
|
return false;
|
|
}
|
|
dereference();
|
|
return QPDFObjectTypeAccessor<QPDF_Real>::check(obj.getPointer());
|
|
}
|
|
|
|
bool
|
|
QPDFObjectHandle::isNumber()
|
|
{
|
|
return (isInteger() || isReal());
|
|
}
|
|
|
|
double
|
|
QPDFObjectHandle::getNumericValue()
|
|
{
|
|
double result = 0.0;
|
|
if (isInteger())
|
|
{
|
|
result = static_cast<double>(getIntValue());
|
|
}
|
|
else if (isReal())
|
|
{
|
|
result = atof(getRealValue().c_str());
|
|
}
|
|
else
|
|
{
|
|
typeWarning("number", "returning 0");
|
|
QTC::TC("qpdf", "QPDFObjectHandle numeric non-numeric");
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool
|
|
QPDFObjectHandle::isName()
|
|
{
|
|
if (! this->initialized)
|
|
{
|
|
return false;
|
|
}
|
|
dereference();
|
|
return QPDFObjectTypeAccessor<QPDF_Name>::check(obj.getPointer());
|
|
}
|
|
|
|
bool
|
|
QPDFObjectHandle::isString()
|
|
{
|
|
if (! this->initialized)
|
|
{
|
|
return false;
|
|
}
|
|
dereference();
|
|
return QPDFObjectTypeAccessor<QPDF_String>::check(obj.getPointer());
|
|
}
|
|
|
|
bool
|
|
QPDFObjectHandle::isOperator()
|
|
{
|
|
if (! this->initialized)
|
|
{
|
|
return false;
|
|
}
|
|
dereference();
|
|
return QPDFObjectTypeAccessor<QPDF_Operator>::check(obj.getPointer());
|
|
}
|
|
|
|
bool
|
|
QPDFObjectHandle::isInlineImage()
|
|
{
|
|
if (! this->initialized)
|
|
{
|
|
return false;
|
|
}
|
|
dereference();
|
|
return QPDFObjectTypeAccessor<QPDF_InlineImage>::check(obj.getPointer());
|
|
}
|
|
|
|
bool
|
|
QPDFObjectHandle::isArray()
|
|
{
|
|
if (! this->initialized)
|
|
{
|
|
return false;
|
|
}
|
|
dereference();
|
|
return QPDFObjectTypeAccessor<QPDF_Array>::check(obj.getPointer());
|
|
}
|
|
|
|
bool
|
|
QPDFObjectHandle::isDictionary()
|
|
{
|
|
if (! this->initialized)
|
|
{
|
|
return false;
|
|
}
|
|
dereference();
|
|
return QPDFObjectTypeAccessor<QPDF_Dictionary>::check(obj.getPointer());
|
|
}
|
|
|
|
bool
|
|
QPDFObjectHandle::isStream()
|
|
{
|
|
if (! this->initialized)
|
|
{
|
|
return false;
|
|
}
|
|
dereference();
|
|
return QPDFObjectTypeAccessor<QPDF_Stream>::check(obj.getPointer());
|
|
}
|
|
|
|
bool
|
|
QPDFObjectHandle::isReserved()
|
|
{
|
|
if (! this->initialized)
|
|
{
|
|
return false;
|
|
}
|
|
// dereference will clear reserved if this has been replaced
|
|
dereference();
|
|
return this->reserved;
|
|
}
|
|
|
|
bool
|
|
QPDFObjectHandle::isIndirect()
|
|
{
|
|
if (! this->initialized)
|
|
{
|
|
return false;
|
|
}
|
|
return (this->objid != 0);
|
|
}
|
|
|
|
bool
|
|
QPDFObjectHandle::isScalar()
|
|
{
|
|
return (! (isArray() || isDictionary() || isStream() ||
|
|
isOperator() || isInlineImage()));
|
|
}
|
|
|
|
// Bool accessors
|
|
|
|
bool
|
|
QPDFObjectHandle::getBoolValue()
|
|
{
|
|
if (isBool())
|
|
{
|
|
return dynamic_cast<QPDF_Bool*>(obj.getPointer())->getVal();
|
|
}
|
|
else
|
|
{
|
|
typeWarning("boolean", "returning false");
|
|
QTC::TC("qpdf", "QPDFObjectHandle boolean returning false");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Integer accessors
|
|
|
|
long long
|
|
QPDFObjectHandle::getIntValue()
|
|
{
|
|
if (isInteger())
|
|
{
|
|
return dynamic_cast<QPDF_Integer*>(obj.getPointer())->getVal();
|
|
}
|
|
else
|
|
{
|
|
typeWarning("integer", "returning 0");
|
|
QTC::TC("qpdf", "QPDFObjectHandle integer returning 0");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
int
|
|
QPDFObjectHandle::getIntValueAsInt()
|
|
{
|
|
int result = 0;
|
|
long long v = getIntValue();
|
|
if (v < INT_MIN)
|
|
{
|
|
QTC::TC("qpdf", "QPDFObjectHandle int returning INT_MIN");
|
|
warnIfPossible(
|
|
"requested value of integer is too small; returning INT_MIN",
|
|
false);
|
|
result = INT_MIN;
|
|
}
|
|
else if (v > INT_MAX)
|
|
{
|
|
QTC::TC("qpdf", "QPDFObjectHandle int returning INT_MAX");
|
|
warnIfPossible(
|
|
"requested value of integer is too big; returning INT_MAX",
|
|
false);
|
|
result = INT_MAX;
|
|
}
|
|
else
|
|
{
|
|
result = static_cast<int>(v);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
unsigned long long
|
|
QPDFObjectHandle::getUIntValue()
|
|
{
|
|
unsigned long long result = 0;
|
|
long long v = getIntValue();
|
|
if (v < 0)
|
|
{
|
|
QTC::TC("qpdf", "QPDFObjectHandle uint returning 0");
|
|
warnIfPossible(
|
|
"unsigned value request for negative number; returning 0",
|
|
false);
|
|
}
|
|
else
|
|
{
|
|
result = static_cast<unsigned long long>(v);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
unsigned int
|
|
QPDFObjectHandle::getUIntValueAsUInt()
|
|
{
|
|
unsigned int result = 0;
|
|
long long v = getIntValue();
|
|
if (v < 0)
|
|
{
|
|
QTC::TC("qpdf", "QPDFObjectHandle uint uint returning 0");
|
|
warnIfPossible(
|
|
"unsigned integer value request for negative number; returning 0",
|
|
false);
|
|
result = 0;
|
|
}
|
|
else if (v > UINT_MAX)
|
|
{
|
|
QTC::TC("qpdf", "QPDFObjectHandle uint returning UINT_MAX");
|
|
warnIfPossible(
|
|
"requested value of unsigned integer is too big;"
|
|
" returning UINT_MAX",
|
|
false);
|
|
result = UINT_MAX;
|
|
}
|
|
else
|
|
{
|
|
result = static_cast<unsigned int>(v);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// Real accessors
|
|
|
|
std::string
|
|
QPDFObjectHandle::getRealValue()
|
|
{
|
|
if (isReal())
|
|
{
|
|
return dynamic_cast<QPDF_Real*>(obj.getPointer())->getVal();
|
|
}
|
|
else
|
|
{
|
|
typeWarning("real", "returning 0.0");
|
|
QTC::TC("qpdf", "QPDFObjectHandle real returning 0.0");
|
|
return "0.0";
|
|
}
|
|
}
|
|
|
|
// Name accessors
|
|
|
|
std::string
|
|
QPDFObjectHandle::getName()
|
|
{
|
|
if (isName())
|
|
{
|
|
return dynamic_cast<QPDF_Name*>(obj.getPointer())->getName();
|
|
}
|
|
else
|
|
{
|
|
typeWarning("name", "returning dummy name");
|
|
QTC::TC("qpdf", "QPDFObjectHandle name returning dummy name");
|
|
return "/QPDFFakeName";
|
|
}
|
|
}
|
|
|
|
// String accessors
|
|
|
|
std::string
|
|
QPDFObjectHandle::getStringValue()
|
|
{
|
|
if (isString())
|
|
{
|
|
return dynamic_cast<QPDF_String*>(obj.getPointer())->getVal();
|
|
}
|
|
else
|
|
{
|
|
typeWarning("string", "returning empty string");
|
|
QTC::TC("qpdf", "QPDFObjectHandle string returning empty string");
|
|
return "";
|
|
}
|
|
}
|
|
|
|
std::string
|
|
QPDFObjectHandle::getUTF8Value()
|
|
{
|
|
if (isString())
|
|
{
|
|
return dynamic_cast<QPDF_String*>(obj.getPointer())->getUTF8Val();
|
|
}
|
|
else
|
|
{
|
|
typeWarning("string", "returning empty string");
|
|
QTC::TC("qpdf", "QPDFObjectHandle string returning empty utf8");
|
|
return "";
|
|
}
|
|
}
|
|
|
|
// Operator and Inline Image accessors
|
|
|
|
std::string
|
|
QPDFObjectHandle::getOperatorValue()
|
|
{
|
|
if (isOperator())
|
|
{
|
|
return dynamic_cast<QPDF_Operator*>(obj.getPointer())->getVal();
|
|
}
|
|
else
|
|
{
|
|
typeWarning("operator", "returning fake value");
|
|
QTC::TC("qpdf", "QPDFObjectHandle operator returning fake value");
|
|
return "QPDFFAKE";
|
|
}
|
|
}
|
|
|
|
std::string
|
|
QPDFObjectHandle::getInlineImageValue()
|
|
{
|
|
if (isInlineImage())
|
|
{
|
|
return dynamic_cast<QPDF_InlineImage*>(obj.getPointer())->getVal();
|
|
}
|
|
else
|
|
{
|
|
typeWarning("inlineimage", "returning empty data");
|
|
QTC::TC("qpdf", "QPDFObjectHandle inlineimage returning empty data");
|
|
return "";
|
|
}
|
|
}
|
|
|
|
// Array accessors
|
|
|
|
QPDFObjectHandle::QPDFArrayItems
|
|
QPDFObjectHandle::aitems()
|
|
{
|
|
return QPDFArrayItems(*this);
|
|
}
|
|
|
|
int
|
|
QPDFObjectHandle::getArrayNItems()
|
|
{
|
|
if (isArray())
|
|
{
|
|
return dynamic_cast<QPDF_Array*>(obj.getPointer())->getNItems();
|
|
}
|
|
else
|
|
{
|
|
typeWarning("array", "treating as empty");
|
|
QTC::TC("qpdf", "QPDFObjectHandle array treating as empty");
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
QPDFObjectHandle
|
|
QPDFObjectHandle::getArrayItem(int n)
|
|
{
|
|
QPDFObjectHandle result;
|
|
if (isArray() && (n < getArrayNItems()) && (n >= 0))
|
|
{
|
|
result = dynamic_cast<QPDF_Array*>(obj.getPointer())->getItem(n);
|
|
}
|
|
else
|
|
{
|
|
result = newNull();
|
|
if (isArray())
|
|
{
|
|
objectWarning("returning null for out of bounds array access");
|
|
QTC::TC("qpdf", "QPDFObjectHandle array bounds");
|
|
}
|
|
else
|
|
{
|
|
typeWarning("array", "returning null");
|
|
QTC::TC("qpdf", "QPDFObjectHandle array null for non-array");
|
|
}
|
|
QPDF* context = 0;
|
|
std::string description;
|
|
if (this->obj->getDescription(context, description))
|
|
{
|
|
result.setObjectDescription(
|
|
context,
|
|
description +
|
|
" -> null returned from invalid array access");
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool
|
|
QPDFObjectHandle::isRectangle()
|
|
{
|
|
if (! isArray())
|
|
{
|
|
return false;
|
|
}
|
|
if (getArrayNItems() != 4)
|
|
{
|
|
return false;
|
|
}
|
|
for (int i = 0; i < 4; ++i)
|
|
{
|
|
if (! getArrayItem(i).isNumber())
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
QPDFObjectHandle::isMatrix()
|
|
{
|
|
if (! isArray())
|
|
{
|
|
return false;
|
|
}
|
|
if (getArrayNItems() != 6)
|
|
{
|
|
return false;
|
|
}
|
|
for (int i = 0; i < 6; ++i)
|
|
{
|
|
if (! getArrayItem(i).isNumber())
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
QPDFObjectHandle::Rectangle
|
|
QPDFObjectHandle::getArrayAsRectangle()
|
|
{
|
|
Rectangle result;
|
|
if (isRectangle())
|
|
{
|
|
// Rectangle coordinates are always supposed to be llx, lly,
|
|
// urx, ury, but files have been found in the wild where
|
|
// llx > urx or lly > ury.
|
|
double i0 = getArrayItem(0).getNumericValue();
|
|
double i1 = getArrayItem(1).getNumericValue();
|
|
double i2 = getArrayItem(2).getNumericValue();
|
|
double i3 = getArrayItem(3).getNumericValue();
|
|
result = Rectangle(std::min(i0, i2),
|
|
std::min(i1, i3),
|
|
std::max(i0, i2),
|
|
std::max(i1, i3));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
QPDFObjectHandle::Matrix
|
|
QPDFObjectHandle::getArrayAsMatrix()
|
|
{
|
|
Matrix result;
|
|
if (isMatrix())
|
|
{
|
|
result = Matrix(getArrayItem(0).getNumericValue(),
|
|
getArrayItem(1).getNumericValue(),
|
|
getArrayItem(2).getNumericValue(),
|
|
getArrayItem(3).getNumericValue(),
|
|
getArrayItem(4).getNumericValue(),
|
|
getArrayItem(5).getNumericValue());
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::vector<QPDFObjectHandle>
|
|
QPDFObjectHandle::getArrayAsVector()
|
|
{
|
|
std::vector<QPDFObjectHandle> result;
|
|
if (isArray())
|
|
{
|
|
dynamic_cast<QPDF_Array*>(obj.getPointer())->getAsVector(result);
|
|
}
|
|
else
|
|
{
|
|
typeWarning("array", "treating as empty");
|
|
QTC::TC("qpdf", "QPDFObjectHandle array treating as empty vector");
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// Array mutators
|
|
|
|
void
|
|
QPDFObjectHandle::setArrayItem(int n, QPDFObjectHandle const& item)
|
|
{
|
|
if (isArray())
|
|
{
|
|
checkOwnership(item);
|
|
dynamic_cast<QPDF_Array*>(obj.getPointer())->setItem(n, item);
|
|
}
|
|
else
|
|
{
|
|
typeWarning("array", "ignoring attempt to set item");
|
|
QTC::TC("qpdf", "QPDFObjectHandle array ignoring set item");
|
|
}
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::setArrayFromVector(std::vector<QPDFObjectHandle> const& items)
|
|
{
|
|
if (isArray())
|
|
{
|
|
for (auto const& item: items)
|
|
{
|
|
checkOwnership(item);
|
|
}
|
|
dynamic_cast<QPDF_Array*>(obj.getPointer())->setFromVector(items);
|
|
}
|
|
else
|
|
{
|
|
typeWarning("array", "ignoring attempt to replace items");
|
|
QTC::TC("qpdf", "QPDFObjectHandle array ignoring replace items");
|
|
}
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::insertItem(int at, QPDFObjectHandle const& item)
|
|
{
|
|
if (isArray())
|
|
{
|
|
dynamic_cast<QPDF_Array*>(obj.getPointer())->insertItem(at, item);
|
|
}
|
|
else
|
|
{
|
|
typeWarning("array", "ignoring attempt to insert item");
|
|
QTC::TC("qpdf", "QPDFObjectHandle array ignoring insert item");
|
|
}
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::appendItem(QPDFObjectHandle const& item)
|
|
{
|
|
if (isArray())
|
|
{
|
|
checkOwnership(item);
|
|
dynamic_cast<QPDF_Array*>(obj.getPointer())->appendItem(item);
|
|
}
|
|
else
|
|
{
|
|
typeWarning("array", "ignoring attempt to append item");
|
|
QTC::TC("qpdf", "QPDFObjectHandle array ignoring append item");
|
|
}
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::eraseItem(int at)
|
|
{
|
|
if (isArray() && (at < getArrayNItems()) && (at >= 0))
|
|
{
|
|
dynamic_cast<QPDF_Array*>(obj.getPointer())->eraseItem(at);
|
|
}
|
|
else
|
|
{
|
|
if (isArray())
|
|
{
|
|
objectWarning("ignoring attempt to erase out of bounds array item");
|
|
QTC::TC("qpdf", "QPDFObjectHandle erase array bounds");
|
|
}
|
|
else
|
|
{
|
|
typeWarning("array", "ignoring attempt to erase item");
|
|
QTC::TC("qpdf", "QPDFObjectHandle array ignoring erase item");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Dictionary accessors
|
|
|
|
QPDFObjectHandle::QPDFDictItems
|
|
QPDFObjectHandle::ditems()
|
|
{
|
|
return QPDFDictItems(*this);
|
|
}
|
|
|
|
bool
|
|
QPDFObjectHandle::hasKey(std::string const& key)
|
|
{
|
|
if (isDictionary())
|
|
{
|
|
return dynamic_cast<QPDF_Dictionary*>(obj.getPointer())->hasKey(key);
|
|
}
|
|
else
|
|
{
|
|
typeWarning("dictionary",
|
|
"returning false for a key containment request");
|
|
QTC::TC("qpdf", "QPDFObjectHandle dictionary false for hasKey");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
QPDFObjectHandle
|
|
QPDFObjectHandle::getKey(std::string const& key)
|
|
{
|
|
QPDFObjectHandle result;
|
|
if (isDictionary())
|
|
{
|
|
result = dynamic_cast<QPDF_Dictionary*>(
|
|
obj.getPointer())->getKey(key);
|
|
}
|
|
else
|
|
{
|
|
typeWarning(
|
|
"dictionary", "returning null for attempted key retrieval");
|
|
QTC::TC("qpdf", "QPDFObjectHandle dictionary null for getKey");
|
|
result = newNull();
|
|
QPDF* qpdf = 0;
|
|
std::string description;
|
|
if (this->obj->getDescription(qpdf, description))
|
|
{
|
|
result.setObjectDescription(
|
|
qpdf,
|
|
description +
|
|
" -> null returned from getting key " +
|
|
key + " from non-Dictionary");
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::set<std::string>
|
|
QPDFObjectHandle::getKeys()
|
|
{
|
|
std::set<std::string> result;
|
|
if (isDictionary())
|
|
{
|
|
result = dynamic_cast<QPDF_Dictionary*>(obj.getPointer())->getKeys();
|
|
}
|
|
else
|
|
{
|
|
typeWarning("dictionary", "treating as empty");
|
|
QTC::TC("qpdf", "QPDFObjectHandle dictionary empty set for getKeys");
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::map<std::string, QPDFObjectHandle>
|
|
QPDFObjectHandle::getDictAsMap()
|
|
{
|
|
std::map<std::string, QPDFObjectHandle> result;
|
|
if (isDictionary())
|
|
{
|
|
result = dynamic_cast<QPDF_Dictionary*>(
|
|
obj.getPointer())->getAsMap();
|
|
}
|
|
else
|
|
{
|
|
typeWarning("dictionary", "treating as empty");
|
|
QTC::TC("qpdf", "QPDFObjectHandle dictionary empty map for asMap");
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// Array and Name accessors
|
|
bool
|
|
QPDFObjectHandle::isOrHasName(std::string const& value)
|
|
{
|
|
if (isName() && (getName() == value))
|
|
{
|
|
return true;
|
|
}
|
|
else if (isArray())
|
|
{
|
|
int n = getArrayNItems();
|
|
for (int i = 0; i < n; ++i)
|
|
{
|
|
QPDFObjectHandle item = getArrayItem(0);
|
|
if (item.isName() && (item.getName() == value))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::makeResourcesIndirect(QPDF& owning_qpdf)
|
|
{
|
|
if (! isDictionary())
|
|
{
|
|
return;
|
|
}
|
|
for (auto const& i1: ditems())
|
|
{
|
|
QPDFObjectHandle sub = i1.second;
|
|
if (! sub.isDictionary())
|
|
{
|
|
continue;
|
|
}
|
|
for (auto i2: sub.ditems())
|
|
{
|
|
std::string const& key = i2.first;
|
|
QPDFObjectHandle val = i2.second;
|
|
if (! val.isIndirect())
|
|
{
|
|
sub.replaceKey(key, owning_qpdf.makeIndirectObject(val));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::mergeResources(QPDFObjectHandle other)
|
|
{
|
|
mergeResources(other, nullptr);
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::mergeResources(
|
|
QPDFObjectHandle other,
|
|
std::map<std::string, std::map<std::string, std::string>>* conflicts)
|
|
{
|
|
if (! (isDictionary() && other.isDictionary()))
|
|
{
|
|
QTC::TC("qpdf", "QPDFObjectHandle merge top type mismatch");
|
|
return;
|
|
}
|
|
|
|
auto make_og_to_name = [](
|
|
QPDFObjectHandle& dict,
|
|
std::map<QPDFObjGen, std::string>& og_to_name)
|
|
{
|
|
for (auto i: dict.ditems())
|
|
{
|
|
if (i.second.isIndirect())
|
|
{
|
|
og_to_name[i.second.getObjGen()] = i.first;
|
|
}
|
|
}
|
|
};
|
|
|
|
// This algorithm is described in comments in QPDFObjectHandle.hh
|
|
// above the declaration of mergeResources.
|
|
for (auto o_top: other.ditems())
|
|
{
|
|
std::string const& rtype = o_top.first;
|
|
QPDFObjectHandle other_val = o_top.second;
|
|
if (hasKey(rtype))
|
|
{
|
|
QPDFObjectHandle this_val = getKey(rtype);
|
|
if (this_val.isDictionary() && other_val.isDictionary())
|
|
{
|
|
if (this_val.isIndirect())
|
|
{
|
|
// Do this even if there are no keys. Various
|
|
// places in the code call mergeResources with
|
|
// resource dictionaries that contain empty
|
|
// subdictionaries just to get this shallow copy
|
|
// functionality.
|
|
QTC::TC("qpdf", "QPDFObjectHandle replace with copy");
|
|
this_val = this_val.shallowCopy();
|
|
replaceKey(rtype, this_val);
|
|
}
|
|
std::map<QPDFObjGen, std::string> og_to_name;
|
|
std::set<std::string> rnames;
|
|
int min_suffix = 1;
|
|
bool initialized_maps = false;
|
|
for (auto ov_iter: other_val.ditems())
|
|
{
|
|
std::string const& key = ov_iter.first;
|
|
QPDFObjectHandle rval = ov_iter.second;
|
|
if (! this_val.hasKey(key))
|
|
{
|
|
if (! rval.isIndirect())
|
|
{
|
|
QTC::TC("qpdf", "QPDFObjectHandle merge shallow copy");
|
|
rval = rval.shallowCopy();
|
|
}
|
|
this_val.replaceKey(key, rval);
|
|
}
|
|
else if (conflicts)
|
|
{
|
|
if (! initialized_maps)
|
|
{
|
|
make_og_to_name(this_val, og_to_name);
|
|
rnames = this_val.getResourceNames();
|
|
initialized_maps = true;
|
|
}
|
|
auto rval_og = rval.getObjGen();
|
|
if (rval.isIndirect() &&
|
|
og_to_name.count(rval_og))
|
|
{
|
|
QTC::TC("qpdf", "QPDFObjectHandle merge reuse");
|
|
auto new_key = og_to_name[rval_og];
|
|
if (new_key != key)
|
|
{
|
|
(*conflicts)[rtype][key] = new_key;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
QTC::TC("qpdf", "QPDFObjectHandle merge generate");
|
|
std::string new_key = getUniqueResourceName(
|
|
key + "_", min_suffix, &rnames);
|
|
(*conflicts)[rtype][key] = new_key;
|
|
this_val.replaceKey(new_key, rval);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (this_val.isArray() && other_val.isArray())
|
|
{
|
|
std::set<std::string> scalars;
|
|
for (auto this_item: this_val.aitems())
|
|
{
|
|
if (this_item.isScalar())
|
|
{
|
|
scalars.insert(this_item.unparse());
|
|
}
|
|
}
|
|
for (auto other_item: other_val.aitems())
|
|
{
|
|
if (other_item.isScalar())
|
|
{
|
|
if (scalars.count(other_item.unparse()) == 0)
|
|
{
|
|
QTC::TC("qpdf", "QPDFObjectHandle merge array");
|
|
this_val.appendItem(other_item);
|
|
}
|
|
else
|
|
{
|
|
QTC::TC("qpdf", "QPDFObjectHandle merge array dup");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
QTC::TC("qpdf", "QPDFObjectHandle merge copy from other");
|
|
replaceKey(rtype, other_val.shallowCopy());
|
|
}
|
|
}
|
|
}
|
|
|
|
std::set<std::string>
|
|
QPDFObjectHandle::getResourceNames()
|
|
{
|
|
// Return second-level dictionary keys
|
|
std::set<std::string> result;
|
|
if (! isDictionary())
|
|
{
|
|
return result;
|
|
}
|
|
std::set<std::string> keys = getKeys();
|
|
for (std::set<std::string>::iterator iter = keys.begin();
|
|
iter != keys.end(); ++iter)
|
|
{
|
|
std::string const& key = *iter;
|
|
QPDFObjectHandle val = getKey(key);
|
|
if (val.isDictionary())
|
|
{
|
|
std::set<std::string> val_keys = val.getKeys();
|
|
for (std::set<std::string>::iterator i2 = val_keys.begin();
|
|
i2 != val_keys.end(); ++i2)
|
|
{
|
|
result.insert(*i2);
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::string
|
|
QPDFObjectHandle::getUniqueResourceName(std::string const& prefix,
|
|
int& min_suffix)
|
|
{
|
|
return getUniqueResourceName(prefix, min_suffix, nullptr);
|
|
}
|
|
|
|
std::string
|
|
QPDFObjectHandle::getUniqueResourceName(std::string const& prefix,
|
|
int& min_suffix,
|
|
std::set<std::string>* namesp)
|
|
|
|
{
|
|
std::set<std::string> names = (namesp ? *namesp : getResourceNames());
|
|
int max_suffix = min_suffix + QIntC::to_int(names.size());
|
|
while (min_suffix <= max_suffix)
|
|
{
|
|
std::string candidate = prefix + QUtil::int_to_string(min_suffix);
|
|
if (names.count(candidate) == 0)
|
|
{
|
|
return candidate;
|
|
}
|
|
// Increment after return; min_suffix should be the value
|
|
// used, not the next value.
|
|
++min_suffix;
|
|
}
|
|
// This could only happen if there is a coding error.
|
|
// The number of candidates we test is more than the
|
|
// number of keys we're checking against.
|
|
throw std::logic_error("unable to find unconflicting name in"
|
|
" QPDFObjectHandle::getUniqueResourceName");
|
|
}
|
|
|
|
// Indirect object accessors
|
|
QPDF*
|
|
QPDFObjectHandle::getOwningQPDF()
|
|
{
|
|
// Will be null for direct objects
|
|
return this->qpdf;
|
|
}
|
|
|
|
// Dictionary mutators
|
|
|
|
void
|
|
QPDFObjectHandle::replaceKey(std::string const& key,
|
|
QPDFObjectHandle value)
|
|
{
|
|
if (isDictionary())
|
|
{
|
|
checkOwnership(value);
|
|
dynamic_cast<QPDF_Dictionary*>(
|
|
obj.getPointer())->replaceKey(key, value);
|
|
}
|
|
else
|
|
{
|
|
typeWarning("dictionary", "ignoring key replacement request");
|
|
QTC::TC("qpdf", "QPDFObjectHandle dictionary ignoring replaceKey");
|
|
}
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::removeKey(std::string const& key)
|
|
{
|
|
if (isDictionary())
|
|
{
|
|
dynamic_cast<QPDF_Dictionary*>(obj.getPointer())->removeKey(key);
|
|
}
|
|
else
|
|
{
|
|
typeWarning("dictionary", "ignoring key removal request");
|
|
QTC::TC("qpdf", "QPDFObjectHandle dictionary ignoring removeKey");
|
|
}
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::replaceOrRemoveKey(std::string const& key,
|
|
QPDFObjectHandle value)
|
|
{
|
|
if (isDictionary())
|
|
{
|
|
checkOwnership(value);
|
|
dynamic_cast<QPDF_Dictionary*>(
|
|
obj.getPointer())->replaceOrRemoveKey(key, value);
|
|
}
|
|
else
|
|
{
|
|
typeWarning("dictionary", "ignoring key removal/replacement request");
|
|
QTC::TC("qpdf", "QPDFObjectHandle dictionary ignoring removereplace");
|
|
}
|
|
}
|
|
|
|
// Stream accessors
|
|
QPDFObjectHandle
|
|
QPDFObjectHandle::getDict()
|
|
{
|
|
assertStream();
|
|
return dynamic_cast<QPDF_Stream*>(obj.getPointer())->getDict();
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::setFilterOnWrite(bool val)
|
|
{
|
|
assertStream();
|
|
dynamic_cast<QPDF_Stream*>(obj.getPointer())->setFilterOnWrite(val);
|
|
}
|
|
|
|
bool
|
|
QPDFObjectHandle::getFilterOnWrite()
|
|
{
|
|
assertStream();
|
|
return dynamic_cast<QPDF_Stream*>(obj.getPointer())->getFilterOnWrite();
|
|
}
|
|
|
|
bool
|
|
QPDFObjectHandle::isDataModified()
|
|
{
|
|
assertStream();
|
|
return dynamic_cast<QPDF_Stream*>(obj.getPointer())->isDataModified();
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::replaceDict(QPDFObjectHandle new_dict)
|
|
{
|
|
assertStream();
|
|
dynamic_cast<QPDF_Stream*>(obj.getPointer())->replaceDict(new_dict);
|
|
}
|
|
|
|
PointerHolder<Buffer>
|
|
QPDFObjectHandle::getStreamData(qpdf_stream_decode_level_e level)
|
|
{
|
|
assertStream();
|
|
return dynamic_cast<QPDF_Stream*>(
|
|
obj.getPointer())->getStreamData(level);
|
|
}
|
|
|
|
PointerHolder<Buffer>
|
|
QPDFObjectHandle::getRawStreamData()
|
|
{
|
|
assertStream();
|
|
return dynamic_cast<QPDF_Stream*>(obj.getPointer())->getRawStreamData();
|
|
}
|
|
|
|
bool
|
|
QPDFObjectHandle::pipeStreamData(Pipeline* p, bool* filtering_attempted,
|
|
int encode_flags,
|
|
qpdf_stream_decode_level_e decode_level,
|
|
bool suppress_warnings, bool will_retry)
|
|
{
|
|
assertStream();
|
|
return dynamic_cast<QPDF_Stream*>(obj.getPointer())->pipeStreamData(
|
|
p, filtering_attempted, encode_flags, decode_level,
|
|
suppress_warnings, will_retry);
|
|
}
|
|
|
|
bool
|
|
QPDFObjectHandle::pipeStreamData(Pipeline* p,
|
|
int encode_flags,
|
|
qpdf_stream_decode_level_e decode_level,
|
|
bool suppress_warnings, bool will_retry)
|
|
{
|
|
assertStream();
|
|
bool filtering_attempted;
|
|
dynamic_cast<QPDF_Stream*>(obj.getPointer())->pipeStreamData(
|
|
p, &filtering_attempted, encode_flags, decode_level,
|
|
suppress_warnings, will_retry);
|
|
return filtering_attempted;
|
|
}
|
|
|
|
bool
|
|
QPDFObjectHandle::pipeStreamData(Pipeline* p, bool filter,
|
|
bool normalize, bool compress)
|
|
{
|
|
int encode_flags = 0;
|
|
qpdf_stream_decode_level_e decode_level = qpdf_dl_none;
|
|
if (filter)
|
|
{
|
|
decode_level = qpdf_dl_generalized;
|
|
if (normalize)
|
|
{
|
|
encode_flags |= qpdf_ef_normalize;
|
|
}
|
|
if (compress)
|
|
{
|
|
encode_flags |= qpdf_ef_compress;
|
|
}
|
|
}
|
|
return pipeStreamData(p, encode_flags, decode_level, false);
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::replaceStreamData(PointerHolder<Buffer> data,
|
|
QPDFObjectHandle const& filter,
|
|
QPDFObjectHandle const& decode_parms)
|
|
{
|
|
assertStream();
|
|
dynamic_cast<QPDF_Stream*>(obj.getPointer())->replaceStreamData(
|
|
data, filter, decode_parms);
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::replaceStreamData(std::string const& data,
|
|
QPDFObjectHandle const& filter,
|
|
QPDFObjectHandle const& decode_parms)
|
|
{
|
|
assertStream();
|
|
PointerHolder<Buffer> b = new Buffer(data.length());
|
|
unsigned char* bp = b->getBuffer();
|
|
memcpy(bp, data.c_str(), data.length());
|
|
dynamic_cast<QPDF_Stream*>(obj.getPointer())->replaceStreamData(
|
|
b, filter, decode_parms);
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::replaceStreamData(PointerHolder<StreamDataProvider> provider,
|
|
QPDFObjectHandle const& filter,
|
|
QPDFObjectHandle const& decode_parms)
|
|
{
|
|
assertStream();
|
|
dynamic_cast<QPDF_Stream*>(obj.getPointer())->replaceStreamData(
|
|
provider, filter, decode_parms);
|
|
}
|
|
|
|
class FunctionProvider: public QPDFObjectHandle::StreamDataProvider
|
|
{
|
|
public:
|
|
FunctionProvider(std::function<void(Pipeline*)> provider) :
|
|
StreamDataProvider(false),
|
|
p1(provider),
|
|
p2(nullptr)
|
|
{
|
|
}
|
|
FunctionProvider(std::function<bool(Pipeline*, bool, bool)> provider) :
|
|
StreamDataProvider(true),
|
|
p1(nullptr),
|
|
p2(provider)
|
|
{
|
|
}
|
|
|
|
virtual void provideStreamData(int, int, Pipeline* pipeline) override
|
|
{
|
|
p1(pipeline);
|
|
}
|
|
|
|
virtual bool provideStreamData(int, int, Pipeline* pipeline,
|
|
bool suppress_warnings,
|
|
bool will_retry) override
|
|
{
|
|
return p2(pipeline, suppress_warnings, will_retry);
|
|
}
|
|
|
|
private:
|
|
std::function<void(Pipeline*)> p1;
|
|
std::function<bool(Pipeline*, bool, bool)> p2;
|
|
};
|
|
|
|
void
|
|
QPDFObjectHandle::replaceStreamData(std::function<void(Pipeline*)> provider,
|
|
QPDFObjectHandle const& filter,
|
|
QPDFObjectHandle const& decode_parms)
|
|
{
|
|
assertStream();
|
|
PointerHolder<StreamDataProvider> sdp = new FunctionProvider(provider);
|
|
dynamic_cast<QPDF_Stream*>(obj.getPointer())->replaceStreamData(
|
|
sdp, filter, decode_parms);
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::replaceStreamData(
|
|
std::function<bool(Pipeline*, bool, bool)> provider,
|
|
QPDFObjectHandle const& filter,
|
|
QPDFObjectHandle const& decode_parms)
|
|
{
|
|
assertStream();
|
|
PointerHolder<StreamDataProvider> sdp = new FunctionProvider(provider);
|
|
dynamic_cast<QPDF_Stream*>(obj.getPointer())->replaceStreamData(
|
|
sdp, filter, decode_parms);
|
|
}
|
|
|
|
QPDFObjGen
|
|
QPDFObjectHandle::getObjGen() const
|
|
{
|
|
return QPDFObjGen(this->objid, this->generation);
|
|
}
|
|
|
|
int
|
|
QPDFObjectHandle::getObjectID() const
|
|
{
|
|
return this->objid;
|
|
}
|
|
|
|
int
|
|
QPDFObjectHandle::getGeneration() const
|
|
{
|
|
return this->generation;
|
|
}
|
|
|
|
std::map<std::string, QPDFObjectHandle>
|
|
QPDFObjectHandle::getPageImages()
|
|
{
|
|
return QPDFPageObjectHelper(*this).getImages();
|
|
}
|
|
|
|
std::vector<QPDFObjectHandle>
|
|
QPDFObjectHandle::arrayOrStreamToStreamArray(
|
|
std::string const& description, std::string& all_description)
|
|
{
|
|
all_description = description;
|
|
std::vector<QPDFObjectHandle> result;
|
|
if (isArray())
|
|
{
|
|
int n_items = getArrayNItems();
|
|
for (int i = 0; i < n_items; ++i)
|
|
{
|
|
QPDFObjectHandle item = getArrayItem(i);
|
|
if (item.isStream())
|
|
{
|
|
result.push_back(item);
|
|
}
|
|
else
|
|
{
|
|
QTC::TC("qpdf", "QPDFObjectHandle non-stream in stream array");
|
|
warn(item.getOwningQPDF(),
|
|
QPDFExc(qpdf_e_damaged_pdf, description,
|
|
"item index " + QUtil::int_to_string(i) +
|
|
" (from 0)", 0,
|
|
"ignoring non-stream in an array of streams"));
|
|
}
|
|
}
|
|
}
|
|
else if (isStream())
|
|
{
|
|
result.push_back(*this);
|
|
}
|
|
else if (! isNull())
|
|
{
|
|
warn(getOwningQPDF(),
|
|
QPDFExc(qpdf_e_damaged_pdf, "", description, 0,
|
|
" object is supposed to be a stream or an"
|
|
" array of streams but is neither"));
|
|
}
|
|
|
|
bool first = true;
|
|
for (std::vector<QPDFObjectHandle>::iterator iter = result.begin();
|
|
iter != result.end(); ++iter)
|
|
{
|
|
QPDFObjectHandle item = *iter;
|
|
std::string og =
|
|
QUtil::int_to_string(item.getObjectID()) + " " +
|
|
QUtil::int_to_string(item.getGeneration());
|
|
if (first)
|
|
{
|
|
first = false;
|
|
}
|
|
else
|
|
{
|
|
all_description += ",";
|
|
}
|
|
all_description += " stream " + og;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
std::vector<QPDFObjectHandle>
|
|
QPDFObjectHandle::getPageContents()
|
|
{
|
|
std::string description = "page object " +
|
|
QUtil::int_to_string(this->objid) + " " +
|
|
QUtil::int_to_string(this->generation);
|
|
std::string all_description;
|
|
return this->getKey("/Contents").arrayOrStreamToStreamArray(
|
|
description, all_description);
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::addPageContents(QPDFObjectHandle new_contents, bool first)
|
|
{
|
|
new_contents.assertStream();
|
|
|
|
std::vector<QPDFObjectHandle> orig_contents = getPageContents();
|
|
|
|
std::vector<QPDFObjectHandle> content_streams;
|
|
if (first)
|
|
{
|
|
QTC::TC("qpdf", "QPDFObjectHandle prepend page contents");
|
|
content_streams.push_back(new_contents);
|
|
}
|
|
for (std::vector<QPDFObjectHandle>::iterator iter = orig_contents.begin();
|
|
iter != orig_contents.end(); ++iter)
|
|
{
|
|
QTC::TC("qpdf", "QPDFObjectHandle append page contents");
|
|
content_streams.push_back(*iter);
|
|
}
|
|
if (! first)
|
|
{
|
|
content_streams.push_back(new_contents);
|
|
}
|
|
|
|
QPDFObjectHandle contents = QPDFObjectHandle::newArray(content_streams);
|
|
this->replaceKey("/Contents", contents);
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::rotatePage(int angle, bool relative)
|
|
{
|
|
if ((angle % 90) != 0)
|
|
{
|
|
throw std::runtime_error(
|
|
"QPDF::rotatePage called with an"
|
|
" angle that is not a multiple of 90");
|
|
}
|
|
int new_angle = angle;
|
|
if (relative)
|
|
{
|
|
int old_angle = 0;
|
|
bool found_rotate = false;
|
|
QPDFObjectHandle cur_obj = *this;
|
|
bool searched_parent = false;
|
|
std::set<QPDFObjGen> visited;
|
|
while (! found_rotate)
|
|
{
|
|
if (visited.count(cur_obj.getObjGen()))
|
|
{
|
|
// Don't get stuck in an infinite loop
|
|
break;
|
|
}
|
|
if (! visited.empty())
|
|
{
|
|
searched_parent = true;
|
|
}
|
|
visited.insert(cur_obj.getObjGen());
|
|
if (cur_obj.getKey("/Rotate").isInteger())
|
|
{
|
|
found_rotate = true;
|
|
old_angle = cur_obj.getKey("/Rotate").getIntValueAsInt();
|
|
}
|
|
else if (cur_obj.getKey("/Parent").isDictionary())
|
|
{
|
|
cur_obj = cur_obj.getKey("/Parent");
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
QTC::TC("qpdf", "QPDFObjectHandle found old angle",
|
|
searched_parent ? 0 : 1);
|
|
if ((old_angle % 90) != 0)
|
|
{
|
|
old_angle = 0;
|
|
}
|
|
new_angle += old_angle;
|
|
}
|
|
new_angle = (new_angle + 360) % 360;
|
|
// Make this explicit even with new_angle == 0 since /Rotate can
|
|
// be inherited.
|
|
replaceKey("/Rotate", QPDFObjectHandle::newInteger(new_angle));
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::coalesceContentStreams()
|
|
{
|
|
QPDFObjectHandle contents = this->getKey("/Contents");
|
|
if (contents.isStream())
|
|
{
|
|
QTC::TC("qpdf", "QPDFObjectHandle coalesce called on stream");
|
|
return;
|
|
}
|
|
else if (! contents.isArray())
|
|
{
|
|
// /Contents is optional for pages, and some very damaged
|
|
// files may have pages that are invalid in other ways.
|
|
return;
|
|
}
|
|
QPDF* qpdf = getOwningQPDF();
|
|
if (qpdf == 0)
|
|
{
|
|
// Should not be possible for a page object to not have an
|
|
// owning PDF unless it was manually constructed in some
|
|
// incorrect way. However, it can happen in a PDF file whose
|
|
// page structure is direct, which is against spec but still
|
|
// possible to hand construct, as in fuzz issue 27393.
|
|
throw std::runtime_error("coalesceContentStreams called on object"
|
|
" with no associated PDF file");
|
|
}
|
|
QPDFObjectHandle new_contents = newStream(qpdf);
|
|
this->replaceKey("/Contents", new_contents);
|
|
|
|
PointerHolder<StreamDataProvider> provider =
|
|
new CoalesceProvider(*this, contents);
|
|
new_contents.replaceStreamData(provider, newNull(), newNull());
|
|
}
|
|
|
|
std::string
|
|
QPDFObjectHandle::unparse()
|
|
{
|
|
std::string result;
|
|
if (this->isIndirect())
|
|
{
|
|
result = QUtil::int_to_string(this->objid) + " " +
|
|
QUtil::int_to_string(this->generation) + " R";
|
|
}
|
|
else
|
|
{
|
|
result = unparseResolved();
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::string
|
|
QPDFObjectHandle::unparseResolved()
|
|
{
|
|
dereference();
|
|
if (this->reserved)
|
|
{
|
|
throw std::logic_error(
|
|
"QPDFObjectHandle: attempting to unparse a reserved object");
|
|
}
|
|
return this->obj->unparse();
|
|
}
|
|
|
|
std::string
|
|
QPDFObjectHandle::unparseBinary()
|
|
{
|
|
if (this->isString())
|
|
{
|
|
return dynamic_cast<QPDF_String*>(
|
|
this->obj.getPointer())->unparse(true);
|
|
}
|
|
else
|
|
{
|
|
return unparse();
|
|
}
|
|
}
|
|
|
|
JSON
|
|
QPDFObjectHandle::getJSON(bool dereference_indirect)
|
|
{
|
|
if ((! dereference_indirect) && this->isIndirect())
|
|
{
|
|
return JSON::makeString(unparse());
|
|
}
|
|
else
|
|
{
|
|
dereference();
|
|
if (this->reserved)
|
|
{
|
|
throw std::logic_error(
|
|
"QPDFObjectHandle: attempting to unparse a reserved object");
|
|
}
|
|
return this->obj->getJSON();
|
|
}
|
|
}
|
|
|
|
QPDFObjectHandle
|
|
QPDFObjectHandle::wrapInArray()
|
|
{
|
|
if (isArray())
|
|
{
|
|
return *this;
|
|
}
|
|
QPDFObjectHandle result = QPDFObjectHandle::newArray();
|
|
result.appendItem(*this);
|
|
return result;
|
|
}
|
|
|
|
QPDFObjectHandle
|
|
QPDFObjectHandle::parse(std::string const& object_str,
|
|
std::string const& object_description)
|
|
{
|
|
return parse(nullptr, object_str, object_description);
|
|
}
|
|
|
|
QPDFObjectHandle
|
|
QPDFObjectHandle::parse(QPDF* context,
|
|
std::string const& object_str,
|
|
std::string const& object_description)
|
|
{
|
|
PointerHolder<InputSource> input =
|
|
new BufferInputSource("parsed object", object_str);
|
|
QPDFTokenizer tokenizer;
|
|
bool empty = false;
|
|
QPDFObjectHandle result =
|
|
parse(input, object_description, tokenizer, empty, 0, context);
|
|
size_t offset = QIntC::to_size(input->tell());
|
|
while (offset < object_str.length())
|
|
{
|
|
if (! isspace(object_str.at(offset)))
|
|
{
|
|
QTC::TC("qpdf", "QPDFObjectHandle trailing data in parse");
|
|
throw QPDFExc(qpdf_e_damaged_pdf, input->getName(),
|
|
object_description,
|
|
input->getLastOffset(),
|
|
"trailing data found parsing object from string");
|
|
}
|
|
++offset;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::pipePageContents(Pipeline* p)
|
|
{
|
|
std::string description = "page object " +
|
|
QUtil::int_to_string(this->objid) + " " +
|
|
QUtil::int_to_string(this->generation);
|
|
std::string all_description;
|
|
this->getKey("/Contents").pipeContentStreams(
|
|
p, description, all_description);
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::pipeContentStreams(
|
|
Pipeline* p, std::string const& description, std::string& all_description)
|
|
{
|
|
std::vector<QPDFObjectHandle> streams =
|
|
arrayOrStreamToStreamArray(
|
|
description, all_description);
|
|
bool need_newline = false;
|
|
Pl_Buffer buf("concatenated content stream buffer");
|
|
for (std::vector<QPDFObjectHandle>::iterator iter = streams.begin();
|
|
iter != streams.end(); ++iter)
|
|
{
|
|
if (need_newline)
|
|
{
|
|
buf.write(QUtil::unsigned_char_pointer("\n"), 1);
|
|
}
|
|
LastChar lc(&buf);
|
|
QPDFObjectHandle stream = *iter;
|
|
std::string og =
|
|
QUtil::int_to_string(stream.getObjectID()) + " " +
|
|
QUtil::int_to_string(stream.getGeneration());
|
|
std::string w_description = "content stream object " + og;
|
|
if (! stream.pipeStreamData(&lc, 0, qpdf_dl_specialized))
|
|
{
|
|
QTC::TC("qpdf", "QPDFObjectHandle errors in parsecontent");
|
|
throw QPDFExc(qpdf_e_damaged_pdf, "content stream",
|
|
w_description, 0,
|
|
"errors while decoding content stream");
|
|
}
|
|
lc.finish();
|
|
need_newline = (lc.getLastChar() != static_cast<unsigned char>('\n'));
|
|
QTC::TC("qpdf", "QPDFObjectHandle need_newline",
|
|
need_newline ? 0 : 1);
|
|
}
|
|
std::unique_ptr<Buffer> b(buf.getBuffer());
|
|
p->write(b->getBuffer(), b->getSize());
|
|
p->finish();
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::parsePageContents(ParserCallbacks* callbacks)
|
|
{
|
|
std::string description = "page object " +
|
|
QUtil::int_to_string(this->objid) + " " +
|
|
QUtil::int_to_string(this->generation);
|
|
this->getKey("/Contents").parseContentStream_internal(
|
|
description, callbacks);
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::parseAsContents(ParserCallbacks* callbacks)
|
|
{
|
|
std::string description = "object " +
|
|
QUtil::int_to_string(this->objid) + " " +
|
|
QUtil::int_to_string(this->generation);
|
|
this->parseContentStream_internal(description, callbacks);
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::filterPageContents(TokenFilter* filter, Pipeline* next)
|
|
{
|
|
std::string description = "token filter for page object " +
|
|
QUtil::int_to_string(this->objid) + " " +
|
|
QUtil::int_to_string(this->generation);
|
|
Pl_QPDFTokenizer token_pipeline(description.c_str(), filter, next);
|
|
this->pipePageContents(&token_pipeline);
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::filterAsContents(TokenFilter* filter, Pipeline* next)
|
|
{
|
|
std::string description = "token filter for object " +
|
|
QUtil::int_to_string(this->objid) + " " +
|
|
QUtil::int_to_string(this->generation);
|
|
Pl_QPDFTokenizer token_pipeline(description.c_str(), filter, next);
|
|
this->pipeStreamData(&token_pipeline, 0, qpdf_dl_specialized);
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::parseContentStream(QPDFObjectHandle stream_or_array,
|
|
ParserCallbacks* callbacks)
|
|
{
|
|
stream_or_array.parseContentStream_internal(
|
|
"content stream objects", callbacks);
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::parseContentStream_internal(
|
|
std::string const& description,
|
|
ParserCallbacks* callbacks)
|
|
{
|
|
Pl_Buffer buf("concatenated stream data buffer");
|
|
std::string all_description;
|
|
pipeContentStreams(&buf, description, all_description);
|
|
PointerHolder<Buffer> stream_data = buf.getBuffer();
|
|
callbacks->contentSize(stream_data->getSize());
|
|
try
|
|
{
|
|
parseContentStream_data(stream_data, all_description,
|
|
callbacks, getOwningQPDF());
|
|
}
|
|
catch (TerminateParsing&)
|
|
{
|
|
return;
|
|
}
|
|
callbacks->handleEOF();
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::parseContentStream_data(
|
|
PointerHolder<Buffer> stream_data,
|
|
std::string const& description,
|
|
ParserCallbacks* callbacks,
|
|
QPDF* context)
|
|
{
|
|
size_t stream_length = stream_data->getSize();
|
|
PointerHolder<InputSource> input =
|
|
new BufferInputSource(description, stream_data.getPointer());
|
|
QPDFTokenizer tokenizer;
|
|
tokenizer.allowEOF();
|
|
bool empty = false;
|
|
while (QIntC::to_size(input->tell()) < stream_length)
|
|
{
|
|
// Read a token and seek to the beginning. The offset we get
|
|
// from this process is the beginning of the next
|
|
// non-ignorable (space, comment) token. This way, the offset
|
|
// and don't including ignorable content.
|
|
tokenizer.readToken(input, "content", true);
|
|
qpdf_offset_t offset = input->getLastOffset();
|
|
input->seek(offset, SEEK_SET);
|
|
QPDFObjectHandle obj =
|
|
parseInternal(input, "content", tokenizer,
|
|
empty, 0, context, true);
|
|
if (! obj.isInitialized())
|
|
{
|
|
// EOF
|
|
break;
|
|
}
|
|
size_t length = QIntC::to_size(input->tell() - offset);
|
|
|
|
callbacks->handleObject(obj, QIntC::to_size(offset), length);
|
|
if (obj.isOperator() && (obj.getOperatorValue() == "ID"))
|
|
{
|
|
// Discard next character; it is the space after ID that
|
|
// terminated the token. Read until end of inline image.
|
|
char ch;
|
|
input->read(&ch, 1);
|
|
tokenizer.expectInlineImage(input);
|
|
QPDFTokenizer::Token t =
|
|
tokenizer.readToken(input, description, true);
|
|
offset = input->getLastOffset();
|
|
length = QIntC::to_size(input->tell() - offset);
|
|
if (t.getType() == QPDFTokenizer::tt_bad)
|
|
{
|
|
QTC::TC("qpdf", "QPDFObjectHandle EOF in inline image");
|
|
warn(context,
|
|
QPDFExc(qpdf_e_damaged_pdf, input->getName(),
|
|
"stream data", input->tell(),
|
|
"EOF found while reading inline image"));
|
|
}
|
|
else
|
|
{
|
|
std::string inline_image = t.getValue();
|
|
QTC::TC("qpdf", "QPDFObjectHandle inline image token");
|
|
callbacks->handleObject(
|
|
QPDFObjectHandle::newInlineImage(inline_image),
|
|
QIntC::to_size(offset), length);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::addContentTokenFilter(PointerHolder<TokenFilter> filter)
|
|
{
|
|
coalesceContentStreams();
|
|
this->getKey("/Contents").addTokenFilter(filter);
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::addTokenFilter(PointerHolder<TokenFilter> filter)
|
|
{
|
|
assertStream();
|
|
return dynamic_cast<QPDF_Stream*>(
|
|
obj.getPointer())->addTokenFilter(filter);
|
|
}
|
|
|
|
QPDFObjectHandle
|
|
QPDFObjectHandle::parse(PointerHolder<InputSource> input,
|
|
std::string const& object_description,
|
|
QPDFTokenizer& tokenizer, bool& empty,
|
|
StringDecrypter* decrypter, QPDF* context)
|
|
{
|
|
return parseInternal(input, object_description, tokenizer, empty,
|
|
decrypter, context, false);
|
|
}
|
|
|
|
QPDFObjectHandle
|
|
QPDFObjectHandle::parseInternal(PointerHolder<InputSource> input,
|
|
std::string const& object_description,
|
|
QPDFTokenizer& tokenizer, bool& empty,
|
|
StringDecrypter* decrypter, QPDF* context,
|
|
bool content_stream)
|
|
{
|
|
// This method must take care not to resolve any objects. Don't
|
|
// check the type of any object without first ensuring that it is
|
|
// a direct object. Otherwise, doing so may have the side effect
|
|
// of reading the object and changing the file pointer. If you do
|
|
// this, it will cause a logic error to be thrown from
|
|
// QPDF::inParse().
|
|
|
|
QPDF::ParseGuard pg(context);
|
|
|
|
empty = false;
|
|
|
|
QPDFObjectHandle object;
|
|
bool set_offset = false;
|
|
|
|
std::vector<SparseOHArray> olist_stack;
|
|
olist_stack.push_back(SparseOHArray());
|
|
std::vector<parser_state_e> state_stack;
|
|
state_stack.push_back(st_top);
|
|
std::vector<qpdf_offset_t> offset_stack;
|
|
qpdf_offset_t offset = input->tell();
|
|
offset_stack.push_back(offset);
|
|
bool done = false;
|
|
int bad_count = 0;
|
|
int good_count = 0;
|
|
bool b_contents = false;
|
|
std::vector<std::string> contents_string_stack;
|
|
contents_string_stack.push_back("");
|
|
std::vector<qpdf_offset_t> contents_offset_stack;
|
|
contents_offset_stack.push_back(-1);
|
|
while (! done)
|
|
{
|
|
bool bad = false;
|
|
SparseOHArray& olist = olist_stack.back();
|
|
parser_state_e state = state_stack.back();
|
|
offset = offset_stack.back();
|
|
std::string& contents_string = contents_string_stack.back();
|
|
qpdf_offset_t& contents_offset = contents_offset_stack.back();
|
|
|
|
object = QPDFObjectHandle();
|
|
set_offset = false;
|
|
|
|
QPDFTokenizer::Token token =
|
|
tokenizer.readToken(input, object_description, true);
|
|
std::string const& token_error_message = token.getErrorMessage();
|
|
if (! token_error_message.empty())
|
|
{
|
|
// Tokens other than tt_bad can still generate warnings.
|
|
warn(context,
|
|
QPDFExc(qpdf_e_damaged_pdf, input->getName(),
|
|
object_description,
|
|
input->getLastOffset(),
|
|
token_error_message));
|
|
}
|
|
|
|
switch (token.getType())
|
|
{
|
|
case QPDFTokenizer::tt_eof:
|
|
if (! content_stream)
|
|
{
|
|
QTC::TC("qpdf", "QPDFObjectHandle eof in parseInternal");
|
|
warn(context,
|
|
QPDFExc(qpdf_e_damaged_pdf, input->getName(),
|
|
object_description,
|
|
input->getLastOffset(),
|
|
"unexpected EOF"));
|
|
}
|
|
bad = true;
|
|
state = st_eof;
|
|
break;
|
|
|
|
case QPDFTokenizer::tt_bad:
|
|
QTC::TC("qpdf", "QPDFObjectHandle bad token in parse");
|
|
bad = true;
|
|
object = newNull();
|
|
break;
|
|
|
|
case QPDFTokenizer::tt_brace_open:
|
|
case QPDFTokenizer::tt_brace_close:
|
|
QTC::TC("qpdf", "QPDFObjectHandle bad brace");
|
|
warn(context,
|
|
QPDFExc(qpdf_e_damaged_pdf, input->getName(),
|
|
object_description,
|
|
input->getLastOffset(),
|
|
"treating unexpected brace token as null"));
|
|
bad = true;
|
|
object = newNull();
|
|
break;
|
|
|
|
case QPDFTokenizer::tt_array_close:
|
|
if (state == st_array)
|
|
{
|
|
state = st_stop;
|
|
}
|
|
else
|
|
{
|
|
QTC::TC("qpdf", "QPDFObjectHandle bad array close");
|
|
warn(context,
|
|
QPDFExc(qpdf_e_damaged_pdf, input->getName(),
|
|
object_description,
|
|
input->getLastOffset(),
|
|
"treating unexpected array close token as null"));
|
|
bad = true;
|
|
object = newNull();
|
|
}
|
|
break;
|
|
|
|
case QPDFTokenizer::tt_dict_close:
|
|
if (state == st_dictionary)
|
|
{
|
|
state = st_stop;
|
|
}
|
|
else
|
|
{
|
|
QTC::TC("qpdf", "QPDFObjectHandle bad dictionary close");
|
|
warn(context,
|
|
QPDFExc(qpdf_e_damaged_pdf, input->getName(),
|
|
object_description,
|
|
input->getLastOffset(),
|
|
"unexpected dictionary close token"));
|
|
bad = true;
|
|
object = newNull();
|
|
}
|
|
break;
|
|
|
|
case QPDFTokenizer::tt_array_open:
|
|
case QPDFTokenizer::tt_dict_open:
|
|
if (olist_stack.size() > 500)
|
|
{
|
|
QTC::TC("qpdf", "QPDFObjectHandle too deep");
|
|
warn(context,
|
|
QPDFExc(qpdf_e_damaged_pdf, input->getName(),
|
|
object_description,
|
|
input->getLastOffset(),
|
|
"ignoring excessively deeply nested data structure"));
|
|
bad = true;
|
|
object = newNull();
|
|
state = st_top;
|
|
}
|
|
else
|
|
{
|
|
olist_stack.push_back(SparseOHArray());
|
|
state = st_start;
|
|
offset_stack.push_back(input->tell());
|
|
state_stack.push_back(
|
|
(token.getType() == QPDFTokenizer::tt_array_open) ?
|
|
st_array : st_dictionary);
|
|
b_contents = false;
|
|
contents_string_stack.push_back("");
|
|
contents_offset_stack.push_back(-1);
|
|
}
|
|
break;
|
|
|
|
case QPDFTokenizer::tt_bool:
|
|
object = newBool((token.getValue() == "true"));
|
|
break;
|
|
|
|
case QPDFTokenizer::tt_null:
|
|
object = newNull();
|
|
break;
|
|
|
|
case QPDFTokenizer::tt_integer:
|
|
object = newInteger(QUtil::string_to_ll(token.getValue().c_str()));
|
|
break;
|
|
|
|
case QPDFTokenizer::tt_real:
|
|
object = newReal(token.getValue());
|
|
break;
|
|
|
|
case QPDFTokenizer::tt_name:
|
|
{
|
|
std::string name = token.getValue();
|
|
object = newName(name);
|
|
|
|
if (name == "/Contents")
|
|
{
|
|
b_contents = true;
|
|
}
|
|
else
|
|
{
|
|
b_contents = false;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case QPDFTokenizer::tt_word:
|
|
{
|
|
std::string const& value = token.getValue();
|
|
if (content_stream)
|
|
{
|
|
object = QPDFObjectHandle::newOperator(value);
|
|
}
|
|
else if ((value == "R") && (state != st_top) &&
|
|
(olist.size() >= 2) &&
|
|
(! olist.at(olist.size() - 1).isIndirect()) &&
|
|
(olist.at(olist.size() - 1).isInteger()) &&
|
|
(! olist.at(olist.size() - 2).isIndirect()) &&
|
|
(olist.at(olist.size() - 2).isInteger()))
|
|
{
|
|
if (context == 0)
|
|
{
|
|
QTC::TC("qpdf", "QPDFObjectHandle indirect without context");
|
|
throw std::logic_error(
|
|
"QPDFObjectHandle::parse called without context"
|
|
" on an object with indirect references");
|
|
}
|
|
// Try to resolve indirect objects
|
|
object = newIndirect(
|
|
context,
|
|
olist.at(olist.size() - 2).getIntValueAsInt(),
|
|
olist.at(olist.size() - 1).getIntValueAsInt());
|
|
olist.remove_last();
|
|
olist.remove_last();
|
|
}
|
|
else if ((value == "endobj") && (state == st_top))
|
|
{
|
|
// We just saw endobj without having read
|
|
// anything. Treat this as a null and do not move
|
|
// the input source's offset.
|
|
object = newNull();
|
|
input->seek(input->getLastOffset(), SEEK_SET);
|
|
empty = true;
|
|
}
|
|
else
|
|
{
|
|
QTC::TC("qpdf", "QPDFObjectHandle treat word as string");
|
|
warn(context,
|
|
QPDFExc(qpdf_e_damaged_pdf, input->getName(),
|
|
object_description,
|
|
input->getLastOffset(),
|
|
"unknown token while reading object;"
|
|
" treating as string"));
|
|
bad = true;
|
|
object = newString(value);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case QPDFTokenizer::tt_string:
|
|
{
|
|
std::string val = token.getValue();
|
|
if (decrypter)
|
|
{
|
|
if (b_contents)
|
|
{
|
|
contents_string = val;
|
|
contents_offset = input->getLastOffset();
|
|
b_contents = false;
|
|
}
|
|
decrypter->decryptString(val);
|
|
}
|
|
object = QPDFObjectHandle::newString(val);
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
warn(context,
|
|
QPDFExc(qpdf_e_damaged_pdf, input->getName(),
|
|
object_description,
|
|
input->getLastOffset(),
|
|
"treating unknown token type as null while "
|
|
"reading object"));
|
|
bad = true;
|
|
object = newNull();
|
|
break;
|
|
}
|
|
|
|
if ((! object.isInitialized()) &&
|
|
(! ((state == st_start) ||
|
|
(state == st_stop) ||
|
|
(state == st_eof))))
|
|
{
|
|
throw std::logic_error(
|
|
"QPDFObjectHandle::parseInternal: "
|
|
"unexpected uninitialized object");
|
|
object = newNull();
|
|
}
|
|
|
|
if (bad)
|
|
{
|
|
++bad_count;
|
|
good_count = 0;
|
|
}
|
|
else
|
|
{
|
|
++good_count;
|
|
if (good_count > 3)
|
|
{
|
|
bad_count = 0;
|
|
}
|
|
}
|
|
if (bad_count > 5)
|
|
{
|
|
// We had too many consecutive errors without enough
|
|
// intervening successful objects. Give up.
|
|
warn(context,
|
|
QPDFExc(qpdf_e_damaged_pdf, input->getName(),
|
|
object_description,
|
|
input->getLastOffset(),
|
|
"too many errors; giving up on reading object"));
|
|
state = st_top;
|
|
object = newNull();
|
|
}
|
|
|
|
switch (state)
|
|
{
|
|
case st_eof:
|
|
if (state_stack.size() > 1)
|
|
{
|
|
warn(context,
|
|
QPDFExc(qpdf_e_damaged_pdf, input->getName(),
|
|
object_description,
|
|
input->getLastOffset(),
|
|
"parse error while reading object"));
|
|
}
|
|
done = true;
|
|
// In content stream mode, leave object uninitialized to
|
|
// indicate EOF
|
|
if (! content_stream)
|
|
{
|
|
object = newNull();
|
|
}
|
|
break;
|
|
|
|
case st_dictionary:
|
|
case st_array:
|
|
setObjectDescriptionFromInput(
|
|
object, context, object_description, input,
|
|
input->getLastOffset());
|
|
object.setParsedOffset(input->getLastOffset());
|
|
set_offset = true;
|
|
olist.append(object);
|
|
break;
|
|
|
|
case st_top:
|
|
done = true;
|
|
break;
|
|
|
|
case st_start:
|
|
break;
|
|
|
|
case st_stop:
|
|
if ((state_stack.size() < 2) || (olist_stack.size() < 2))
|
|
{
|
|
throw std::logic_error(
|
|
"QPDFObjectHandle::parseInternal: st_stop encountered"
|
|
" with insufficient elements in stack");
|
|
}
|
|
parser_state_e old_state = state_stack.back();
|
|
state_stack.pop_back();
|
|
if (old_state == st_array)
|
|
{
|
|
// There's no newArray(SparseOHArray) since
|
|
// SparseOHArray is not part of the public API.
|
|
object = QPDFObjectHandle(new QPDF_Array(olist));
|
|
setObjectDescriptionFromInput(
|
|
object, context, object_description, input, offset);
|
|
// The `offset` points to the next of "[". Set the
|
|
// rewind offset to point to the beginning of "[".
|
|
// This has been explicitly tested with whitespace
|
|
// surrounding the array start delimiter.
|
|
// getLastOffset points to the array end token and
|
|
// therefore can't be used here.
|
|
object.setParsedOffset(offset - 1);
|
|
set_offset = true;
|
|
}
|
|
else if (old_state == st_dictionary)
|
|
{
|
|
// Convert list to map. Alternating elements are keys.
|
|
// Attempt to recover more or less gracefully from
|
|
// invalid dictionaries.
|
|
std::set<std::string> names;
|
|
size_t n_elements = olist.size();
|
|
for (size_t i = 0; i < n_elements; ++i)
|
|
{
|
|
QPDFObjectHandle oh = olist.at(i);
|
|
if ((! oh.isIndirect()) && oh.isName())
|
|
{
|
|
names.insert(oh.getName());
|
|
}
|
|
}
|
|
|
|
std::map<std::string, QPDFObjectHandle> dict;
|
|
int next_fake_key = 1;
|
|
for (unsigned int i = 0; i < olist.size(); ++i)
|
|
{
|
|
QPDFObjectHandle key_obj = olist.at(i);
|
|
QPDFObjectHandle val;
|
|
if (key_obj.isIndirect() || (! key_obj.isName()))
|
|
{
|
|
bool found_fake = false;
|
|
std::string candidate;
|
|
while (! found_fake)
|
|
{
|
|
candidate =
|
|
"/QPDFFake" +
|
|
QUtil::int_to_string(next_fake_key++);
|
|
found_fake = (names.count(candidate) == 0);
|
|
QTC::TC("qpdf", "QPDFObjectHandle found fake",
|
|
(found_fake ? 0 : 1));
|
|
}
|
|
warn(context,
|
|
QPDFExc(
|
|
qpdf_e_damaged_pdf,
|
|
input->getName(), object_description, offset,
|
|
"expected dictionary key but found"
|
|
" non-name object; inserting key " +
|
|
candidate));
|
|
val = key_obj;
|
|
key_obj = newName(candidate);
|
|
}
|
|
else if (i + 1 >= olist.size())
|
|
{
|
|
QTC::TC("qpdf", "QPDFObjectHandle no val for last key");
|
|
warn(context,
|
|
QPDFExc(
|
|
qpdf_e_damaged_pdf,
|
|
input->getName(), object_description, offset,
|
|
"dictionary ended prematurely; "
|
|
"using null as value for last key"));
|
|
val = newNull();
|
|
setObjectDescriptionFromInput(
|
|
val, context, object_description, input, offset);
|
|
}
|
|
else
|
|
{
|
|
val = olist.at(++i);
|
|
}
|
|
std::string key = key_obj.getName();
|
|
if (dict.count(key) > 0)
|
|
{
|
|
QTC::TC("qpdf", "QPDFObjectHandle duplicate dict key");
|
|
warn(context,
|
|
QPDFExc(
|
|
qpdf_e_damaged_pdf,
|
|
input->getName(), object_description, offset,
|
|
"dictionary has duplicated key " + key +
|
|
"; last occurrence overrides earlier ones"));
|
|
}
|
|
dict[key] = val;
|
|
}
|
|
if (!contents_string.empty() &&
|
|
dict.count("/Type") &&
|
|
dict["/Type"].isName() &&
|
|
dict["/Type"].getName() == "/Sig" &&
|
|
dict.count("/ByteRange") &&
|
|
dict.count("/Contents") &&
|
|
dict["/Contents"].isString())
|
|
{
|
|
dict["/Contents"]
|
|
= QPDFObjectHandle::newString(contents_string);
|
|
dict["/Contents"].setParsedOffset(contents_offset);
|
|
}
|
|
object = newDictionary(dict);
|
|
setObjectDescriptionFromInput(
|
|
object, context, object_description, input, offset);
|
|
// The `offset` points to the next of "<<". Set the
|
|
// rewind offset to point to the beginning of "<<".
|
|
// This has been explicitly tested with whitespace
|
|
// surrounding the dictionary start delimiter.
|
|
// getLastOffset points to the dictionary end token
|
|
// and therefore can't be used here.
|
|
object.setParsedOffset(offset - 2);
|
|
set_offset = true;
|
|
}
|
|
olist_stack.pop_back();
|
|
offset_stack.pop_back();
|
|
if (state_stack.back() == st_top)
|
|
{
|
|
done = true;
|
|
}
|
|
else
|
|
{
|
|
olist_stack.back().append(object);
|
|
}
|
|
contents_string_stack.pop_back();
|
|
contents_offset_stack.pop_back();
|
|
}
|
|
}
|
|
|
|
if (! set_offset)
|
|
{
|
|
setObjectDescriptionFromInput(
|
|
object, context, object_description, input, offset);
|
|
object.setParsedOffset(offset);
|
|
}
|
|
return object;
|
|
}
|
|
|
|
qpdf_offset_t
|
|
QPDFObjectHandle::getParsedOffset()
|
|
{
|
|
dereference();
|
|
return this->obj->getParsedOffset();
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::setParsedOffset(qpdf_offset_t offset)
|
|
{
|
|
// This is called during parsing on newly created direct objects,
|
|
// so we can't call dereference() here.
|
|
if (this->obj.getPointer())
|
|
{
|
|
this->obj->setParsedOffset(offset);
|
|
}
|
|
}
|
|
|
|
QPDFObjectHandle
|
|
QPDFObjectHandle::newIndirect(QPDF* qpdf, int objid, int generation)
|
|
{
|
|
if (objid == 0)
|
|
{
|
|
// Special case: QPDF uses objid 0 as a sentinel for direct
|
|
// objects, and the PDF specification doesn't allow for object
|
|
// 0. Treat indirect references to object 0 as null so that we
|
|
// never create an indirect object with objid 0.
|
|
QTC::TC("qpdf", "QPDFObjectHandle indirect with 0 objid");
|
|
return newNull();
|
|
}
|
|
|
|
return QPDFObjectHandle(qpdf, objid, generation);
|
|
}
|
|
|
|
QPDFObjectHandle
|
|
QPDFObjectHandle::newBool(bool value)
|
|
{
|
|
return QPDFObjectHandle(new QPDF_Bool(value));
|
|
}
|
|
|
|
QPDFObjectHandle
|
|
QPDFObjectHandle::newNull()
|
|
{
|
|
return QPDFObjectHandle(new QPDF_Null());
|
|
}
|
|
|
|
QPDFObjectHandle
|
|
QPDFObjectHandle::newInteger(long long value)
|
|
{
|
|
return QPDFObjectHandle(new QPDF_Integer(value));
|
|
}
|
|
|
|
QPDFObjectHandle
|
|
QPDFObjectHandle::newReal(std::string const& value)
|
|
{
|
|
return QPDFObjectHandle(new QPDF_Real(value));
|
|
}
|
|
|
|
QPDFObjectHandle
|
|
QPDFObjectHandle::newReal(double value, int decimal_places)
|
|
{
|
|
return QPDFObjectHandle(
|
|
new QPDF_Real(value, decimal_places, true));
|
|
}
|
|
|
|
QPDFObjectHandle
|
|
QPDFObjectHandle::newReal(double value, int decimal_places,
|
|
bool trim_trailing_zeroes)
|
|
{
|
|
return QPDFObjectHandle(
|
|
new QPDF_Real(value, decimal_places, trim_trailing_zeroes));
|
|
}
|
|
|
|
QPDFObjectHandle
|
|
QPDFObjectHandle::newName(std::string const& name)
|
|
{
|
|
return QPDFObjectHandle(new QPDF_Name(name));
|
|
}
|
|
|
|
QPDFObjectHandle
|
|
QPDFObjectHandle::newString(std::string const& str)
|
|
{
|
|
return QPDFObjectHandle(new QPDF_String(str));
|
|
}
|
|
|
|
QPDFObjectHandle
|
|
QPDFObjectHandle::newUnicodeString(std::string const& utf8_str)
|
|
{
|
|
return QPDFObjectHandle(QPDF_String::new_utf16(utf8_str));
|
|
}
|
|
|
|
QPDFObjectHandle
|
|
QPDFObjectHandle::newOperator(std::string const& value)
|
|
{
|
|
return QPDFObjectHandle(new QPDF_Operator(value));
|
|
}
|
|
|
|
QPDFObjectHandle
|
|
QPDFObjectHandle::newInlineImage(std::string const& value)
|
|
{
|
|
return QPDFObjectHandle(new QPDF_InlineImage(value));
|
|
}
|
|
|
|
QPDFObjectHandle
|
|
QPDFObjectHandle::newArray()
|
|
{
|
|
return newArray(std::vector<QPDFObjectHandle>());
|
|
}
|
|
|
|
QPDFObjectHandle
|
|
QPDFObjectHandle::newArray(std::vector<QPDFObjectHandle> const& items)
|
|
{
|
|
return QPDFObjectHandle(new QPDF_Array(items));
|
|
}
|
|
|
|
QPDFObjectHandle
|
|
QPDFObjectHandle::newArray(Rectangle const& rect)
|
|
{
|
|
std::vector<QPDFObjectHandle> items;
|
|
items.push_back(newReal(rect.llx));
|
|
items.push_back(newReal(rect.lly));
|
|
items.push_back(newReal(rect.urx));
|
|
items.push_back(newReal(rect.ury));
|
|
return newArray(items);
|
|
}
|
|
|
|
QPDFObjectHandle
|
|
QPDFObjectHandle::newArray(Matrix const& matrix)
|
|
{
|
|
std::vector<QPDFObjectHandle> items;
|
|
items.push_back(newReal(matrix.a));
|
|
items.push_back(newReal(matrix.b));
|
|
items.push_back(newReal(matrix.c));
|
|
items.push_back(newReal(matrix.d));
|
|
items.push_back(newReal(matrix.e));
|
|
items.push_back(newReal(matrix.f));
|
|
return newArray(items);
|
|
}
|
|
|
|
QPDFObjectHandle
|
|
QPDFObjectHandle::newArray(QPDFMatrix const& matrix)
|
|
{
|
|
std::vector<QPDFObjectHandle> items;
|
|
items.push_back(newReal(matrix.a));
|
|
items.push_back(newReal(matrix.b));
|
|
items.push_back(newReal(matrix.c));
|
|
items.push_back(newReal(matrix.d));
|
|
items.push_back(newReal(matrix.e));
|
|
items.push_back(newReal(matrix.f));
|
|
return newArray(items);
|
|
}
|
|
|
|
QPDFObjectHandle
|
|
QPDFObjectHandle::newFromRectangle(Rectangle const& rect)
|
|
{
|
|
return newArray(rect);
|
|
}
|
|
|
|
QPDFObjectHandle
|
|
QPDFObjectHandle::newFromMatrix(Matrix const& m)
|
|
{
|
|
return newArray(m);
|
|
}
|
|
|
|
QPDFObjectHandle
|
|
QPDFObjectHandle::newFromMatrix(QPDFMatrix const& m)
|
|
{
|
|
return newArray(m);
|
|
}
|
|
|
|
QPDFObjectHandle
|
|
QPDFObjectHandle::newDictionary()
|
|
{
|
|
return newDictionary(std::map<std::string, QPDFObjectHandle>());
|
|
}
|
|
|
|
QPDFObjectHandle
|
|
QPDFObjectHandle::newDictionary(
|
|
std::map<std::string, QPDFObjectHandle> const& items)
|
|
{
|
|
return QPDFObjectHandle(new QPDF_Dictionary(items));
|
|
}
|
|
|
|
|
|
QPDFObjectHandle
|
|
QPDFObjectHandle::newStream(QPDF* qpdf, int objid, int generation,
|
|
QPDFObjectHandle stream_dict,
|
|
qpdf_offset_t offset, size_t length)
|
|
{
|
|
QPDFObjectHandle result = QPDFObjectHandle(new QPDF_Stream(
|
|
qpdf, objid, generation,
|
|
stream_dict, offset, length));
|
|
if (offset)
|
|
{
|
|
result.setParsedOffset(offset);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
QPDFObjectHandle
|
|
QPDFObjectHandle::newStream(QPDF* qpdf)
|
|
{
|
|
if (qpdf == 0)
|
|
{
|
|
throw std::runtime_error(
|
|
"attempt to create stream in null qpdf object");
|
|
}
|
|
QTC::TC("qpdf", "QPDFObjectHandle newStream");
|
|
QPDFObjectHandle stream_dict = newDictionary();
|
|
QPDFObjectHandle result = qpdf->makeIndirectObject(
|
|
QPDFObjectHandle(
|
|
new QPDF_Stream(qpdf, 0, 0, stream_dict, 0, 0)));
|
|
result.dereference();
|
|
QPDF_Stream* stream =
|
|
dynamic_cast<QPDF_Stream*>(result.obj.getPointer());
|
|
stream->setObjGen(result.getObjectID(), result.getGeneration());
|
|
return result;
|
|
}
|
|
|
|
QPDFObjectHandle
|
|
QPDFObjectHandle::newStream(QPDF* qpdf, PointerHolder<Buffer> data)
|
|
{
|
|
QTC::TC("qpdf", "QPDFObjectHandle newStream with data");
|
|
QPDFObjectHandle result = newStream(qpdf);
|
|
result.replaceStreamData(data, newNull(), newNull());
|
|
return result;
|
|
}
|
|
|
|
QPDFObjectHandle
|
|
QPDFObjectHandle::newStream(QPDF* qpdf, std::string const& data)
|
|
{
|
|
QTC::TC("qpdf", "QPDFObjectHandle newStream with string");
|
|
QPDFObjectHandle result = newStream(qpdf);
|
|
result.replaceStreamData(data, newNull(), newNull());
|
|
return result;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::setObjectDescription(QPDF* owning_qpdf,
|
|
std::string const& object_description)
|
|
{
|
|
// This is called during parsing on newly created direct objects,
|
|
// so we can't call dereference() here.
|
|
if (isInitialized() && this->obj.getPointer())
|
|
{
|
|
this->obj->setDescription(owning_qpdf, object_description);
|
|
}
|
|
}
|
|
|
|
bool
|
|
QPDFObjectHandle::hasObjectDescription()
|
|
{
|
|
if (isInitialized())
|
|
{
|
|
dereference();
|
|
if (this->obj.getPointer())
|
|
{
|
|
return this->obj->hasDescription();
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
QPDFObjectHandle
|
|
QPDFObjectHandle::shallowCopy()
|
|
{
|
|
QPDFObjectHandle result;
|
|
shallowCopyInternal(result, false);
|
|
return result;
|
|
}
|
|
|
|
QPDFObjectHandle
|
|
QPDFObjectHandle::unsafeShallowCopy()
|
|
{
|
|
QPDFObjectHandle result;
|
|
shallowCopyInternal(result, true);
|
|
return result;
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::shallowCopyInternal(QPDFObjectHandle& new_obj,
|
|
bool first_level_only)
|
|
{
|
|
assertInitialized();
|
|
|
|
if (isStream())
|
|
{
|
|
QTC::TC("qpdf", "QPDFObjectHandle ERR shallow copy stream");
|
|
throw std::runtime_error(
|
|
"attempt to make a shallow copy of a stream");
|
|
}
|
|
|
|
if (isArray())
|
|
{
|
|
QTC::TC("qpdf", "QPDFObjectHandle shallow copy array");
|
|
// No newArray for shallow copying the sparse array
|
|
QPDF_Array* arr = dynamic_cast<QPDF_Array*>(obj.getPointer());
|
|
new_obj = QPDFObjectHandle(
|
|
new QPDF_Array(arr->getElementsForShallowCopy()));
|
|
}
|
|
else if (isDictionary())
|
|
{
|
|
QTC::TC("qpdf", "QPDFObjectHandle shallow copy dictionary");
|
|
new_obj = newDictionary(getDictAsMap());
|
|
}
|
|
else
|
|
{
|
|
QTC::TC("qpdf", "QPDFObjectHandle shallow copy scalar");
|
|
new_obj = *this;
|
|
}
|
|
|
|
std::set<QPDFObjGen> visited;
|
|
new_obj.copyObject(visited, false, first_level_only, false);
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::copyObject(std::set<QPDFObjGen>& visited,
|
|
bool cross_indirect, bool first_level_only,
|
|
bool stop_at_streams)
|
|
{
|
|
assertInitialized();
|
|
|
|
if (isStream())
|
|
{
|
|
QTC::TC("qpdf", "QPDFObjectHandle copy stream",
|
|
stop_at_streams ? 0 : 1);
|
|
if (stop_at_streams)
|
|
{
|
|
return;
|
|
}
|
|
throw std::runtime_error(
|
|
"attempt to make a stream into a direct object");
|
|
}
|
|
|
|
QPDFObjGen cur_og(this->objid, this->generation);
|
|
if (cur_og.getObj() != 0)
|
|
{
|
|
if (visited.count(cur_og))
|
|
{
|
|
QTC::TC("qpdf", "QPDFObjectHandle makeDirect loop");
|
|
throw std::runtime_error(
|
|
"loop detected while converting object from "
|
|
"indirect to direct");
|
|
}
|
|
visited.insert(cur_og);
|
|
}
|
|
|
|
if (isReserved())
|
|
{
|
|
throw std::logic_error(
|
|
"QPDFObjectHandle: attempting to make a"
|
|
" reserved object handle direct");
|
|
}
|
|
|
|
dereference();
|
|
this->qpdf = 0;
|
|
this->objid = 0;
|
|
this->generation = 0;
|
|
|
|
PointerHolder<QPDFObject> new_obj;
|
|
|
|
if (isBool())
|
|
{
|
|
QTC::TC("qpdf", "QPDFObjectHandle clone bool");
|
|
new_obj = new QPDF_Bool(getBoolValue());
|
|
}
|
|
else if (isNull())
|
|
{
|
|
QTC::TC("qpdf", "QPDFObjectHandle clone null");
|
|
new_obj = new QPDF_Null();
|
|
}
|
|
else if (isInteger())
|
|
{
|
|
QTC::TC("qpdf", "QPDFObjectHandle clone integer");
|
|
new_obj = new QPDF_Integer(getIntValue());
|
|
}
|
|
else if (isReal())
|
|
{
|
|
QTC::TC("qpdf", "QPDFObjectHandle clone real");
|
|
new_obj = new QPDF_Real(getRealValue());
|
|
}
|
|
else if (isName())
|
|
{
|
|
QTC::TC("qpdf", "QPDFObjectHandle clone name");
|
|
new_obj = new QPDF_Name(getName());
|
|
}
|
|
else if (isString())
|
|
{
|
|
QTC::TC("qpdf", "QPDFObjectHandle clone string");
|
|
new_obj = new QPDF_String(getStringValue());
|
|
}
|
|
else if (isArray())
|
|
{
|
|
QTC::TC("qpdf", "QPDFObjectHandle clone array");
|
|
std::vector<QPDFObjectHandle> items;
|
|
int n = getArrayNItems();
|
|
for (int i = 0; i < n; ++i)
|
|
{
|
|
items.push_back(getArrayItem(i));
|
|
if ((! first_level_only) &&
|
|
(cross_indirect || (! items.back().isIndirect())))
|
|
{
|
|
items.back().copyObject(
|
|
visited, cross_indirect,
|
|
first_level_only, stop_at_streams);
|
|
}
|
|
}
|
|
new_obj = new QPDF_Array(items);
|
|
}
|
|
else if (isDictionary())
|
|
{
|
|
QTC::TC("qpdf", "QPDFObjectHandle clone dictionary");
|
|
std::set<std::string> keys = getKeys();
|
|
std::map<std::string, QPDFObjectHandle> items;
|
|
for (std::set<std::string>::iterator iter = keys.begin();
|
|
iter != keys.end(); ++iter)
|
|
{
|
|
items[*iter] = getKey(*iter);
|
|
if ((! first_level_only) &&
|
|
(cross_indirect || (! items[*iter].isIndirect())))
|
|
{
|
|
items[*iter].copyObject(
|
|
visited, cross_indirect,
|
|
first_level_only, stop_at_streams);
|
|
}
|
|
}
|
|
new_obj = new QPDF_Dictionary(items);
|
|
}
|
|
else
|
|
{
|
|
throw std::logic_error("QPDFObjectHandle::makeDirectInternal: "
|
|
"unknown object type");
|
|
}
|
|
|
|
this->obj = new_obj;
|
|
|
|
if (cur_og.getObj())
|
|
{
|
|
visited.erase(cur_og);
|
|
}
|
|
}
|
|
|
|
QPDFObjectHandle
|
|
QPDFObjectHandle::copyStream()
|
|
{
|
|
assertStream();
|
|
QPDFObjectHandle result = newStream(this->getOwningQPDF());
|
|
QPDFObjectHandle dict = result.getDict();
|
|
QPDFObjectHandle old_dict = getDict();
|
|
for (auto& iter: QPDFDictItems(old_dict))
|
|
{
|
|
if (iter.second.isIndirect())
|
|
{
|
|
dict.replaceKey(iter.first, iter.second);
|
|
}
|
|
else
|
|
{
|
|
dict.replaceKey(iter.first, iter.second.shallowCopy());
|
|
}
|
|
}
|
|
QPDF::StreamCopier::copyStreamData(getOwningQPDF(), result, *this);
|
|
return result;
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::makeDirect()
|
|
{
|
|
makeDirect(false);
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::makeDirect(bool allow_streams)
|
|
{
|
|
std::set<QPDFObjGen> visited;
|
|
copyObject(visited, true, false, allow_streams);
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::assertInitialized() const
|
|
{
|
|
if (! this->initialized)
|
|
{
|
|
throw std::logic_error("operation attempted on uninitialized "
|
|
"QPDFObjectHandle");
|
|
}
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::typeWarning(char const* expected_type,
|
|
std::string const& warning)
|
|
{
|
|
QPDF* context = 0;
|
|
std::string description;
|
|
dereference();
|
|
if (this->obj->getDescription(context, description))
|
|
{
|
|
warn(context,
|
|
QPDFExc(
|
|
qpdf_e_damaged_pdf,
|
|
"", description, 0,
|
|
std::string("operation for ") + expected_type +
|
|
" attempted on object of type " +
|
|
getTypeName() + ": " + warning));
|
|
}
|
|
else
|
|
{
|
|
assertType(expected_type, false);
|
|
}
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::warnIfPossible(std::string const& warning,
|
|
bool throw_if_no_description)
|
|
{
|
|
QPDF* context = 0;
|
|
std::string description;
|
|
dereference();
|
|
if (this->obj->getDescription(context, description))
|
|
{
|
|
warn(context,
|
|
QPDFExc(
|
|
qpdf_e_damaged_pdf,
|
|
"", description, 0,
|
|
warning));
|
|
}
|
|
else if (throw_if_no_description)
|
|
{
|
|
throw std::runtime_error(warning);
|
|
}
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::objectWarning(std::string const& warning)
|
|
{
|
|
warnIfPossible(warning, true);
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::assertType(char const* type_name, bool istype)
|
|
{
|
|
if (! istype)
|
|
{
|
|
throw std::runtime_error(std::string("operation for ") + type_name +
|
|
" attempted on object of type " +
|
|
getTypeName());
|
|
}
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::assertNull()
|
|
{
|
|
assertType("null", isNull());
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::assertBool()
|
|
{
|
|
assertType("boolean", isBool());
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::assertInteger()
|
|
{
|
|
assertType("integer", isInteger());
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::assertReal()
|
|
{
|
|
assertType("real", isReal());
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::assertName()
|
|
{
|
|
assertType("name", isName());
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::assertString()
|
|
{
|
|
assertType("string", isString());
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::assertOperator()
|
|
{
|
|
assertType("operator", isOperator());
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::assertInlineImage()
|
|
{
|
|
assertType("inlineimage", isInlineImage());
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::assertArray()
|
|
{
|
|
assertType("array", isArray());
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::assertDictionary()
|
|
{
|
|
assertType("dictionary", isDictionary());
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::assertStream()
|
|
{
|
|
assertType("stream", isStream());
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::assertReserved()
|
|
{
|
|
assertType("reserved", isReserved());
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::assertIndirect()
|
|
{
|
|
if (! isIndirect())
|
|
{
|
|
throw std::logic_error(
|
|
"operation for indirect object attempted on direct object");
|
|
}
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::assertScalar()
|
|
{
|
|
assertType("scalar", isScalar());
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::assertNumber()
|
|
{
|
|
assertType("number", isNumber());
|
|
}
|
|
|
|
bool
|
|
QPDFObjectHandle::isPageObject()
|
|
{
|
|
// See comments in QPDFObjectHandle.hh.
|
|
if (getOwningQPDF() == nullptr)
|
|
{
|
|
return false;
|
|
}
|
|
// getAllPages repairs /Type when traversing the page tree.
|
|
getOwningQPDF()->getAllPages();
|
|
if (! this->isDictionary())
|
|
{
|
|
return false;
|
|
}
|
|
if (this->hasKey("/Type"))
|
|
{
|
|
QPDFObjectHandle type = this->getKey("/Type");
|
|
if (type.isName() && (type.getName() == "/Page"))
|
|
{
|
|
return true;
|
|
}
|
|
// Files have been seen in the wild that have /Type (Page)
|
|
else if (type.isString() && (type.getStringValue() == "Page"))
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
QPDFObjectHandle::isPagesObject()
|
|
{
|
|
if (getOwningQPDF() == nullptr)
|
|
{
|
|
return false;
|
|
}
|
|
// getAllPages repairs /Type when traversing the page tree.
|
|
getOwningQPDF()->getAllPages();
|
|
return (this->isDictionary() &&
|
|
this->hasKey("/Type") &&
|
|
this->getKey("/Type").isName() &&
|
|
this->getKey("/Type").getName() == "/Pages");
|
|
}
|
|
|
|
bool
|
|
QPDFObjectHandle::isFormXObject()
|
|
{
|
|
if (! this->isStream())
|
|
{
|
|
return false;
|
|
}
|
|
QPDFObjectHandle dict = this->getDict();
|
|
return (dict.getKey("/Type").isName() &&
|
|
("/XObject" == dict.getKey("/Type").getName()) &&
|
|
dict.getKey("/Subtype").isName() &&
|
|
("/Form" == dict.getKey("/Subtype").getName()));
|
|
}
|
|
|
|
bool
|
|
QPDFObjectHandle::isImage(bool exclude_imagemask)
|
|
{
|
|
if (! this->isStream())
|
|
{
|
|
return false;
|
|
}
|
|
QPDFObjectHandle dict = this->getDict();
|
|
return (dict.hasKey("/Subtype") &&
|
|
(dict.getKey("/Subtype").getName() == "/Image") &&
|
|
((! exclude_imagemask) ||
|
|
(! (dict.getKey("/ImageMask").isBool() &&
|
|
dict.getKey("/ImageMask").getBoolValue()))));
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::checkOwnership(QPDFObjectHandle const& item) const
|
|
{
|
|
if ((this->qpdf != nullptr) &&
|
|
(item.qpdf != nullptr) &&
|
|
(this->qpdf != item.qpdf))
|
|
{
|
|
QTC::TC("qpdf", "QPDFObjectHandle check ownership");
|
|
throw std::logic_error(
|
|
"Attempting to add an object from a different QPDF."
|
|
" Use QPDF::copyForeignObject to add objects from another file.");
|
|
}
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::assertPageObject()
|
|
{
|
|
if (! isPageObject())
|
|
{
|
|
throw std::runtime_error("page operation called on non-Page object");
|
|
}
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::dereference()
|
|
{
|
|
if (! this->initialized)
|
|
{
|
|
throw std::logic_error(
|
|
"attempted to dereference an uninitialized QPDFObjectHandle");
|
|
}
|
|
if (this->obj.getPointer() && this->objid &&
|
|
QPDF::Resolver::objectChanged(
|
|
this->qpdf, QPDFObjGen(this->objid, this->generation), this->obj))
|
|
{
|
|
this->obj = nullptr;
|
|
}
|
|
if (this->obj.getPointer() == 0)
|
|
{
|
|
PointerHolder<QPDFObject> obj = QPDF::Resolver::resolve(
|
|
this->qpdf, this->objid, this->generation);
|
|
if (obj.getPointer() == 0)
|
|
{
|
|
// QPDF::resolve never returns an uninitialized object, but
|
|
// check just in case.
|
|
this->obj = new QPDF_Null();
|
|
}
|
|
else if (dynamic_cast<QPDF_Reserved*>(obj.getPointer()))
|
|
{
|
|
// Do not resolve
|
|
}
|
|
else
|
|
{
|
|
this->reserved = false;
|
|
this->obj = obj;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::warn(QPDF* qpdf, QPDFExc const& e)
|
|
{
|
|
// If parsing on behalf of a QPDF object and want to give a
|
|
// warning, we can warn through the object. If parsing for some
|
|
// other reason, such as an explicit creation of an object from a
|
|
// string, then just throw the exception.
|
|
if (qpdf)
|
|
{
|
|
qpdf->warn(e);
|
|
}
|
|
else
|
|
{
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
QPDFObjectHandle::QPDFDictItems::QPDFDictItems(QPDFObjectHandle const& oh) :
|
|
oh(oh)
|
|
{
|
|
}
|
|
|
|
QPDFObjectHandle::QPDFDictItems::iterator&
|
|
QPDFObjectHandle::QPDFDictItems::iterator::operator++()
|
|
{
|
|
++this->m->iter;
|
|
updateIValue();
|
|
return *this;
|
|
}
|
|
|
|
QPDFObjectHandle::QPDFDictItems::iterator&
|
|
QPDFObjectHandle::QPDFDictItems::iterator::operator--()
|
|
{
|
|
--this->m->iter;
|
|
updateIValue();
|
|
return *this;
|
|
}
|
|
|
|
QPDFObjectHandle::QPDFDictItems::iterator::reference
|
|
QPDFObjectHandle::QPDFDictItems::iterator:: operator*()
|
|
{
|
|
updateIValue();
|
|
return this->ivalue;
|
|
}
|
|
|
|
QPDFObjectHandle::QPDFDictItems::iterator::pointer
|
|
QPDFObjectHandle::QPDFDictItems::iterator::operator->()
|
|
{
|
|
updateIValue();
|
|
return &this->ivalue;
|
|
}
|
|
|
|
bool
|
|
QPDFObjectHandle::QPDFDictItems::iterator::operator==(
|
|
iterator const& other) const
|
|
{
|
|
if (this->m->is_end && other.m->is_end)
|
|
{
|
|
return true;
|
|
}
|
|
if (this->m->is_end || other.m->is_end)
|
|
{
|
|
return false;
|
|
}
|
|
return (this->ivalue.first == other.ivalue.first);
|
|
}
|
|
|
|
QPDFObjectHandle::QPDFDictItems::iterator::iterator(
|
|
QPDFObjectHandle& oh, bool for_begin) :
|
|
m(new Members(oh, for_begin))
|
|
{
|
|
updateIValue();
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::QPDFDictItems::iterator::updateIValue()
|
|
{
|
|
this->m->is_end = (this->m->iter == this->m->keys.end());
|
|
if (this->m->is_end)
|
|
{
|
|
this->ivalue.first = "";
|
|
this->ivalue.second = QPDFObjectHandle();
|
|
}
|
|
else
|
|
{
|
|
this->ivalue.first = *(this->m->iter);
|
|
this->ivalue.second = this->m->oh.getKey(this->ivalue.first);
|
|
}
|
|
}
|
|
|
|
QPDFObjectHandle::QPDFDictItems::iterator::Members::Members(
|
|
QPDFObjectHandle& oh, bool for_begin) :
|
|
oh(oh)
|
|
{
|
|
this->keys = oh.getKeys();
|
|
this->iter = for_begin ? this->keys.begin() : this->keys.end();
|
|
}
|
|
|
|
QPDFObjectHandle::QPDFDictItems::iterator
|
|
QPDFObjectHandle::QPDFDictItems::begin()
|
|
{
|
|
return iterator(oh, true);
|
|
}
|
|
|
|
QPDFObjectHandle::QPDFDictItems::iterator
|
|
QPDFObjectHandle::QPDFDictItems::end()
|
|
{
|
|
return iterator(oh, false);
|
|
}
|
|
|
|
QPDFObjectHandle::QPDFArrayItems::QPDFArrayItems(QPDFObjectHandle const& oh) :
|
|
oh(oh)
|
|
{
|
|
}
|
|
|
|
QPDFObjectHandle::QPDFArrayItems::iterator&
|
|
QPDFObjectHandle::QPDFArrayItems::iterator::operator++()
|
|
{
|
|
if (! this->m->is_end)
|
|
{
|
|
++this->m->item_number;
|
|
updateIValue();
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
QPDFObjectHandle::QPDFArrayItems::iterator&
|
|
QPDFObjectHandle::QPDFArrayItems::iterator::operator--()
|
|
{
|
|
if (this->m->item_number > 0)
|
|
{
|
|
--this->m->item_number;
|
|
updateIValue();
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
QPDFObjectHandle::QPDFArrayItems::iterator::reference
|
|
QPDFObjectHandle::QPDFArrayItems::iterator:: operator*()
|
|
{
|
|
updateIValue();
|
|
return this->ivalue;
|
|
}
|
|
|
|
QPDFObjectHandle::QPDFArrayItems::iterator::pointer
|
|
QPDFObjectHandle::QPDFArrayItems::iterator::operator->()
|
|
{
|
|
updateIValue();
|
|
return &this->ivalue;
|
|
}
|
|
|
|
bool
|
|
QPDFObjectHandle::QPDFArrayItems::iterator::operator==(
|
|
iterator const& other) const
|
|
{
|
|
return (this->m->item_number == other.m->item_number);
|
|
}
|
|
|
|
QPDFObjectHandle::QPDFArrayItems::iterator::iterator(
|
|
QPDFObjectHandle& oh, bool for_begin) :
|
|
m(new Members(oh, for_begin))
|
|
{
|
|
updateIValue();
|
|
}
|
|
|
|
void
|
|
QPDFObjectHandle::QPDFArrayItems::iterator::updateIValue()
|
|
{
|
|
this->m->is_end = (this->m->item_number >= this->m->oh.getArrayNItems());
|
|
if (this->m->is_end)
|
|
{
|
|
this->ivalue = QPDFObjectHandle();
|
|
}
|
|
else
|
|
{
|
|
this->ivalue = this->m->oh.getArrayItem(this->m->item_number);
|
|
}
|
|
}
|
|
|
|
QPDFObjectHandle::QPDFArrayItems::iterator::Members::Members(
|
|
QPDFObjectHandle& oh, bool for_begin) :
|
|
oh(oh)
|
|
{
|
|
this->item_number = for_begin ? 0 : oh.getArrayNItems();
|
|
}
|
|
|
|
QPDFObjectHandle::QPDFArrayItems::iterator
|
|
QPDFObjectHandle::QPDFArrayItems::begin()
|
|
{
|
|
return iterator(oh, true);
|
|
}
|
|
|
|
QPDFObjectHandle::QPDFArrayItems::iterator
|
|
QPDFObjectHandle::QPDFArrayItems::end()
|
|
{
|
|
return iterator(oh, false);
|
|
}
|