#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace std::literals; namespace { class TerminateParsing { }; } // namespace QPDFObjectHandle::StreamDataProvider::StreamDataProvider(bool supports_retry) : supports_retry(supports_retry) { } QPDFObjectHandle::StreamDataProvider::~StreamDataProvider() // NOLINT (modernize-use-equals-default) { // Must be explicit and not inline -- see QPDF_DLL_CLASS in README-maintainer } void QPDFObjectHandle::StreamDataProvider::provideStreamData(QPDFObjGen const& og, Pipeline* pipeline) { return provideStreamData(og.getObj(), og.getGen(), pipeline); } bool QPDFObjectHandle::StreamDataProvider::provideStreamData( QPDFObjGen const& og, Pipeline* pipeline, bool suppress_warnings, bool will_retry) { return provideStreamData(og.getObj(), og.getGen(), pipeline, suppress_warnings, will_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; } namespace { class CoalesceProvider: public QPDFObjectHandle::StreamDataProvider { public: CoalesceProvider(QPDFObjectHandle containing_page, QPDFObjectHandle old_contents) : containing_page(containing_page), old_contents(old_contents) { } ~CoalesceProvider() override = default; void provideStreamData(QPDFObjGen const&, Pipeline* pipeline) override; private: QPDFObjectHandle containing_page; QPDFObjectHandle old_contents; }; } // namespace void CoalesceProvider::provideStreamData(QPDFObjGen const&, Pipeline* p) { QTC::TC("qpdf", "QPDFObjectHandle coalesce provide stream data"); std::string description = "page object " + containing_page.getObjGen().unparse(' '); 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(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(); } namespace { class LastChar: public Pipeline { public: LastChar(Pipeline* next); ~LastChar() override = default; void write(unsigned char const* data, size_t len) override; void finish() override; unsigned char getLastChar(); private: unsigned char last_char{0}; }; } // namespace LastChar::LastChar(Pipeline* next) : Pipeline("lastchar", next) { } void LastChar::write(unsigned char const* 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; } bool QPDFObjectHandle::isSameObjectAs(QPDFObjectHandle const& rhs) const { return this->obj == rhs.obj; } void QPDFObjectHandle::disconnect() { // Recursively remove association with any QPDF object. This method may only be called during // final destruction. QPDF::~QPDF() calls it for indirect objects using the object pointer // itself, so we don't do that here. Other objects call it through this method. if (obj && !isIndirect()) { this->obj->disconnect(); } } qpdf_object_type_e QPDFObjectHandle::getTypeCode() { return dereference() ? this->obj->getTypeCode() : ::ot_uninitialized; } char const* QPDFObjectHandle::getTypeName() { return dereference() ? this->obj->getTypeName() : "uninitialized"; } QPDF_Array* QPDFObjectHandle::asArray() { return dereference() ? obj->as() : nullptr; } QPDF_Bool* QPDFObjectHandle::asBool() { return dereference() ? obj->as() : nullptr; } QPDF_Dictionary* QPDFObjectHandle::asDictionary() { return dereference() ? obj->as() : nullptr; } QPDF_InlineImage* QPDFObjectHandle::asInlineImage() { return dereference() ? obj->as() : nullptr; } QPDF_Integer* QPDFObjectHandle::asInteger() { return dereference() ? obj->as() : nullptr; } QPDF_Name* QPDFObjectHandle::asName() { return dereference() ? obj->as() : nullptr; } QPDF_Null* QPDFObjectHandle::asNull() { return dereference() ? obj->as() : nullptr; } QPDF_Operator* QPDFObjectHandle::asOperator() { return dereference() ? obj->as() : nullptr; } QPDF_Real* QPDFObjectHandle::asReal() { return dereference() ? obj->as() : nullptr; } QPDF_Reserved* QPDFObjectHandle::asReserved() { return dereference() ? obj->as() : nullptr; } QPDF_Stream* QPDFObjectHandle::asStream() { return dereference() ? obj->as() : nullptr; } QPDF_Stream* QPDFObjectHandle::asStreamWithAssert() { auto stream = asStream(); assertType("stream", stream); return stream; } QPDF_String* QPDFObjectHandle::asString() { return dereference() ? obj->as() : nullptr; } bool QPDFObjectHandle::isDestroyed() { return dereference() && (obj->getTypeCode() == ::ot_destroyed); } bool QPDFObjectHandle::isBool() { return dereference() && (obj->getTypeCode() == ::ot_boolean); } 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 (isInitialized() && (getObjectID() == 0) && (obj->getTypeCode() == ::ot_null)); } bool QPDFObjectHandle::isNull() { return dereference() && (obj->getTypeCode() == ::ot_null); } bool QPDFObjectHandle::isInteger() { return dereference() && (obj->getTypeCode() == ::ot_integer); } bool QPDFObjectHandle::isReal() { return dereference() && (obj->getTypeCode() == ::ot_real); } bool QPDFObjectHandle::isNumber() { return (isInteger() || isReal()); } double QPDFObjectHandle::getNumericValue() { double result = 0.0; if (isInteger()) { result = static_cast(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::getValueAsNumber(double& value) { if (!isNumber()) { return false; } value = getNumericValue(); return true; } bool QPDFObjectHandle::isName() { return dereference() && (obj->getTypeCode() == ::ot_name); } bool QPDFObjectHandle::isString() { return dereference() && (obj->getTypeCode() == ::ot_string); } bool QPDFObjectHandle::isOperator() { return dereference() && (obj->getTypeCode() == ::ot_operator); } bool QPDFObjectHandle::isInlineImage() { return dereference() && (obj->getTypeCode() == ::ot_inlineimage); } bool QPDFObjectHandle::isArray() { return dereference() && (obj->getTypeCode() == ::ot_array); } bool QPDFObjectHandle::isDictionary() { return dereference() && (obj->getTypeCode() == ::ot_dictionary); } bool QPDFObjectHandle::isStream() { return dereference() && (obj->getTypeCode() == ::ot_stream); } bool QPDFObjectHandle::isReserved() { return dereference() && (obj->getTypeCode() == ::ot_reserved); } bool QPDFObjectHandle::isScalar() { return isBool() || isInteger() || isName() || isNull() || isReal() || isString(); } bool QPDFObjectHandle::isNameAndEquals(std::string const& name) { return isName() && (getName() == name); } bool QPDFObjectHandle::isDictionaryOfType(std::string const& type, std::string const& subtype) { return isDictionary() && (type.empty() || getKey("/Type").isNameAndEquals(type)) && (subtype.empty() || getKey("/Subtype").isNameAndEquals(subtype)); } bool QPDFObjectHandle::isStreamOfType(std::string const& type, std::string const& subtype) { return isStream() && getDict().isDictionaryOfType(type, subtype); } // Bool accessors bool QPDFObjectHandle::getBoolValue() { auto boolean = asBool(); if (boolean) { return boolean->getVal(); } else { typeWarning("boolean", "returning false"); QTC::TC("qpdf", "QPDFObjectHandle boolean returning false"); return false; } } bool QPDFObjectHandle::getValueAsBool(bool& value) { auto boolean = asBool(); if (boolean == nullptr) { return false; } value = boolean->getVal(); return true; } // Integer accessors long long QPDFObjectHandle::getIntValue() { auto integer = asInteger(); if (integer) { return integer->getVal(); } else { typeWarning("integer", "returning 0"); QTC::TC("qpdf", "QPDFObjectHandle integer returning 0"); return 0; } } bool QPDFObjectHandle::getValueAsInt(long long& value) { auto integer = asInteger(); if (integer == nullptr) { return false; } value = integer->getVal(); return true; } 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"); 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"); result = INT_MAX; } else { result = static_cast(v); } return result; } bool QPDFObjectHandle::getValueAsInt(int& value) { if (!isInteger()) { return false; } value = getIntValueAsInt(); return true; } 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"); } else { result = static_cast(v); } return result; } bool QPDFObjectHandle::getValueAsUInt(unsigned long long& value) { if (!isInteger()) { return false; } value = getUIntValue(); return true; } 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"); 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"); result = UINT_MAX; } else { result = static_cast(v); } return result; } bool QPDFObjectHandle::getValueAsUInt(unsigned int& value) { if (!isInteger()) { return false; } value = getUIntValueAsUInt(); return true; } // Real accessors std::string QPDFObjectHandle::getRealValue() { if (isReal()) { return obj->getStringValue(); } else { typeWarning("real", "returning 0.0"); QTC::TC("qpdf", "QPDFObjectHandle real returning 0.0"); return "0.0"; } } bool QPDFObjectHandle::getValueAsReal(std::string& value) { if (!isReal()) { return false; } value = obj->getStringValue(); return true; } // Name accessors std::string QPDFObjectHandle::getName() { if (isName()) { return obj->getStringValue(); } else { typeWarning("name", "returning dummy name"); QTC::TC("qpdf", "QPDFObjectHandle name returning dummy name"); return "/QPDFFakeName"; } } bool QPDFObjectHandle::getValueAsName(std::string& value) { if (!isName()) { return false; } value = obj->getStringValue(); return true; } // String accessors std::string QPDFObjectHandle::getStringValue() { if (isString()) { return obj->getStringValue(); } else { typeWarning("string", "returning empty string"); QTC::TC("qpdf", "QPDFObjectHandle string returning empty string"); return ""; } } bool QPDFObjectHandle::getValueAsString(std::string& value) { if (!isString()) { return false; } value = obj->getStringValue(); return true; } std::string QPDFObjectHandle::getUTF8Value() { auto str = asString(); if (str) { return str->getUTF8Val(); } else { typeWarning("string", "returning empty string"); QTC::TC("qpdf", "QPDFObjectHandle string returning empty utf8"); return ""; } } bool QPDFObjectHandle::getValueAsUTF8(std::string& value) { auto str = asString(); if (str == nullptr) { return false; } value = str->getUTF8Val(); return true; } // Operator and Inline Image accessors std::string QPDFObjectHandle::getOperatorValue() { if (isOperator()) { return obj->getStringValue(); } else { typeWarning("operator", "returning fake value"); QTC::TC("qpdf", "QPDFObjectHandle operator returning fake value"); return "QPDFFAKE"; } } bool QPDFObjectHandle::getValueAsOperator(std::string& value) { if (!isOperator()) { return false; } value = obj->getStringValue(); return true; } std::string QPDFObjectHandle::getInlineImageValue() { if (isInlineImage()) { return obj->getStringValue(); } else { typeWarning("inlineimage", "returning empty data"); QTC::TC("qpdf", "QPDFObjectHandle inlineimage returning empty data"); return ""; } } bool QPDFObjectHandle::getValueAsInlineImage(std::string& value) { if (!isInlineImage()) { return false; } value = obj->getStringValue(); return true; } // Array accessors QPDFObjectHandle::QPDFArrayItems QPDFObjectHandle::aitems() { return *this; } int QPDFObjectHandle::getArrayNItems() { if (auto array = asArray()) { return array->size(); } else { typeWarning("array", "treating as empty"); QTC::TC("qpdf", "QPDFObjectHandle array treating as empty"); return 0; } } QPDFObjectHandle QPDFObjectHandle::getArrayItem(int n) { if (auto array = asArray()) { if (auto result = array->at(n); result.obj != nullptr) { return result; } else { 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"); } static auto constexpr msg = " -> null returned from invalid array access"sv; return QPDF_Null::create(obj, msg, ""); } bool QPDFObjectHandle::isRectangle() { if (auto array = asArray()) { for (int i = 0; i < 4; ++i) { if (auto item = array->at(i); !(item.obj && item.isNumber())) { return false; } } return array->size() == 4; } return false; } bool QPDFObjectHandle::isMatrix() { if (auto array = asArray()) { for (int i = 0; i < 6; ++i) { if (auto item = array->at(i); !(item.obj && item.isNumber())) { return false; } } return array->size() == 6; } return false; } QPDFObjectHandle::Rectangle QPDFObjectHandle::getArrayAsRectangle() { if (auto array = asArray()) { if (array->size() != 4) { return {}; } double items[4]; for (int i = 0; i < 4; ++i) { if (!array->at(i).getValueAsNumber(items[i])) { return {}; } } return { std::min(items[0], items[2]), std::min(items[1], items[3]), std::max(items[0], items[2]), std::max(items[1], items[3])}; } return {}; } QPDFObjectHandle::Matrix QPDFObjectHandle::getArrayAsMatrix() { if (auto array = asArray()) { if (array->size() != 6) { return {}; } double items[6]; for (int i = 0; i < 6; ++i) { if (!array->at(i).getValueAsNumber(items[i])) { return {}; } } return {items[0], items[1], items[2], items[3], items[4], items[5]}; } return {}; } std::vector QPDFObjectHandle::getArrayAsVector() { auto array = asArray(); if (array) { return array->getAsVector(); } else { typeWarning("array", "treating as empty"); QTC::TC("qpdf", "QPDFObjectHandle array treating as empty vector"); } return {}; } // Array mutators void QPDFObjectHandle::setArrayItem(int n, QPDFObjectHandle const& item) { if (auto array = asArray()) { if (!array->setAt(n, item)) { objectWarning("ignoring attempt to set out of bounds array item"); QTC::TC("qpdf", "QPDFObjectHandle set array bounds"); } } else { typeWarning("array", "ignoring attempt to set item"); QTC::TC("qpdf", "QPDFObjectHandle array ignoring set item"); } } void QPDFObjectHandle::setArrayFromVector(std::vector const& items) { if (auto array = asArray()) { array->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 (auto array = asArray()) { if (!array->insert(at, item)) { objectWarning("ignoring attempt to insert out of bounds array item"); QTC::TC("qpdf", "QPDFObjectHandle insert array bounds"); } } else { typeWarning("array", "ignoring attempt to insert item"); QTC::TC("qpdf", "QPDFObjectHandle array ignoring insert item"); } } QPDFObjectHandle QPDFObjectHandle::insertItemAndGetNew(int at, QPDFObjectHandle const& item) { insertItem(at, item); return item; } void QPDFObjectHandle::appendItem(QPDFObjectHandle const& item) { if (auto array = asArray()) { array->push_back(item); } else { typeWarning("array", "ignoring attempt to append item"); QTC::TC("qpdf", "QPDFObjectHandle array ignoring append item"); } } QPDFObjectHandle QPDFObjectHandle::appendItemAndGetNew(QPDFObjectHandle const& item) { appendItem(item); return item; } void QPDFObjectHandle::eraseItem(int at) { if (auto array = asArray()) { if (!array->erase(at)) { 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"); } } QPDFObjectHandle QPDFObjectHandle::eraseItemAndGetOld(int at) { auto array = asArray(); auto result = (array && at < array->size() && at >= 0) ? array->at(at) : newNull(); eraseItem(at); return result; } // Dictionary accessors QPDFObjectHandle::QPDFDictItems QPDFObjectHandle::ditems() { return {*this}; } bool QPDFObjectHandle::hasKey(std::string const& key) { auto dict = asDictionary(); if (dict) { return dict->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) { if (auto dict = asDictionary()) { return dict->getKey(key); } else { typeWarning("dictionary", "returning null for attempted key retrieval"); QTC::TC("qpdf", "QPDFObjectHandle dictionary null for getKey"); static auto constexpr msg = " -> null returned from getting key $VD from non-Dictionary"sv; return QPDF_Null::create(obj, msg, ""); } } QPDFObjectHandle QPDFObjectHandle::getKeyIfDict(std::string const& key) { return isNull() ? newNull() : getKey(key); } std::set QPDFObjectHandle::getKeys() { std::set result; auto dict = asDictionary(); if (dict) { result = dict->getKeys(); } else { typeWarning("dictionary", "treating as empty"); QTC::TC("qpdf", "QPDFObjectHandle dictionary empty set for getKeys"); } return result; } std::map QPDFObjectHandle::getDictAsMap() { std::map result; auto dict = asDictionary(); if (dict) { result = dict->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 (isNameAndEquals(value)) { return true; } else if (isArray()) { for (auto& item: aitems()) { if (item.isNameAndEquals(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 const& 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, std::map>* conflicts) { if (!(isDictionary() && other.isDictionary())) { QTC::TC("qpdf", "QPDFObjectHandle merge top type mismatch"); return; } auto make_og_to_name = [](QPDFObjectHandle& dict, std::map& og_to_name) { for (auto const& 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 const& 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 = replaceKeyAndGetNew(rtype, this_val.shallowCopy()); } std::map og_to_name; std::set rnames; int min_suffix = 1; bool initialized_maps = false; for (auto const& 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 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 QPDFObjectHandle::getResourceNames() { // Return second-level dictionary keys std::set result; if (!isDictionary()) { return result; } for (auto const& key: getKeys()) { QPDFObjectHandle val = getKey(key); if (val.isDictionary()) { for (auto const& val_key: val.getKeys()) { result.insert(val_key); } } } return result; } std::string QPDFObjectHandle::getUniqueResourceName( std::string const& prefix, int& min_suffix, std::set* namesp) { std::set names = (namesp ? *namesp : getResourceNames()); int max_suffix = min_suffix + QIntC::to_int(names.size()); while (min_suffix <= max_suffix) { std::string candidate = prefix + std::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"); } // Dictionary mutators void QPDFObjectHandle::replaceKey(std::string const& key, QPDFObjectHandle const& value) { auto dict = asDictionary(); if (dict) { checkOwnership(value); dict->replaceKey(key, value); } else { typeWarning("dictionary", "ignoring key replacement request"); QTC::TC("qpdf", "QPDFObjectHandle dictionary ignoring replaceKey"); } } QPDFObjectHandle QPDFObjectHandle::replaceKeyAndGetNew(std::string const& key, QPDFObjectHandle const& value) { replaceKey(key, value); return value; } QPDFObjectHandle QPDFObjectHandle::replaceKeyAndGetOld(std::string const& key, QPDFObjectHandle const& value) { QPDFObjectHandle old = removeKeyAndGetOld(key); replaceKey(key, value); return old; } void QPDFObjectHandle::removeKey(std::string const& key) { auto dict = asDictionary(); if (dict) { dict->removeKey(key); } else { typeWarning("dictionary", "ignoring key removal request"); QTC::TC("qpdf", "QPDFObjectHandle dictionary ignoring removeKey"); } } QPDFObjectHandle QPDFObjectHandle::removeKeyAndGetOld(std::string const& key) { auto result = QPDFObjectHandle::newNull(); auto dict = asDictionary(); if (dict) { result = dict->getKey(key); } removeKey(key); return result; } void QPDFObjectHandle::replaceOrRemoveKey(std::string const& key, QPDFObjectHandle const& value) { replaceKey(key, value); } // Stream accessors QPDFObjectHandle QPDFObjectHandle::getDict() { return asStreamWithAssert()->getDict(); } void QPDFObjectHandle::setFilterOnWrite(bool val) { asStreamWithAssert()->setFilterOnWrite(val); } bool QPDFObjectHandle::getFilterOnWrite() { return asStreamWithAssert()->getFilterOnWrite(); } bool QPDFObjectHandle::isDataModified() { return asStreamWithAssert()->isDataModified(); } void QPDFObjectHandle::replaceDict(QPDFObjectHandle const& new_dict) { asStreamWithAssert()->replaceDict(new_dict); } std::shared_ptr QPDFObjectHandle::getStreamData(qpdf_stream_decode_level_e level) { return asStreamWithAssert()->getStreamData(level); } std::shared_ptr QPDFObjectHandle::getRawStreamData() { return asStreamWithAssert()->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) { return asStreamWithAssert()->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) { bool filtering_attempted; asStreamWithAssert()->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( std::shared_ptr data, QPDFObjectHandle const& filter, QPDFObjectHandle const& decode_parms) { asStreamWithAssert()->replaceStreamData(data, filter, decode_parms); } void QPDFObjectHandle::replaceStreamData( std::string const& data, QPDFObjectHandle const& filter, QPDFObjectHandle const& decode_parms) { auto b = std::make_shared(data.length()); unsigned char* bp = b->getBuffer(); if (bp) { memcpy(bp, data.c_str(), data.length()); } asStreamWithAssert()->replaceStreamData(b, filter, decode_parms); } void QPDFObjectHandle::replaceStreamData( std::shared_ptr provider, QPDFObjectHandle const& filter, QPDFObjectHandle const& decode_parms) { asStreamWithAssert()->replaceStreamData(provider, filter, decode_parms); } namespace { class FunctionProvider: public QPDFObjectHandle::StreamDataProvider { public: FunctionProvider(std::function provider) : StreamDataProvider(false), p1(provider), p2(nullptr) { } FunctionProvider(std::function provider) : StreamDataProvider(true), p1(nullptr), p2(provider) { } void provideStreamData(QPDFObjGen const&, Pipeline* pipeline) override { p1(pipeline); } bool provideStreamData( QPDFObjGen const&, Pipeline* pipeline, bool suppress_warnings, bool will_retry) override { return p2(pipeline, suppress_warnings, will_retry); } private: std::function p1; std::function p2; }; } // namespace void QPDFObjectHandle::replaceStreamData( std::function provider, QPDFObjectHandle const& filter, QPDFObjectHandle const& decode_parms) { auto sdp = std::shared_ptr(new FunctionProvider(provider)); asStreamWithAssert()->replaceStreamData(sdp, filter, decode_parms); } void QPDFObjectHandle::replaceStreamData( std::function provider, QPDFObjectHandle const& filter, QPDFObjectHandle const& decode_parms) { auto sdp = std::shared_ptr(new FunctionProvider(provider)); asStreamWithAssert()->replaceStreamData(sdp, filter, decode_parms); } std::map QPDFObjectHandle::getPageImages() { return QPDFPageObjectHelper(*this).getImages(); } std::vector QPDFObjectHandle::arrayOrStreamToStreamArray( std::string const& description, std::string& all_description) { all_description = description; std::vector result; if (auto array = asArray()) { int n_items = array->size(); for (int i = 0; i < n_items; ++i) { QPDFObjectHandle item = array->at(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 " + std::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 (auto const& item: result) { if (first) { first = false; } else { all_description += ","; } all_description += " stream " + item.getObjGen().unparse(' '); } return result; } std::vector QPDFObjectHandle::getPageContents() { std::string description = "page object " + getObjGen().unparse(' '); 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 content_streams; if (first) { QTC::TC("qpdf", "QPDFObjectHandle prepend page contents"); content_streams.push_back(new_contents); } for (auto const& iter: getPageContents()) { QTC::TC("qpdf", "QPDFObjectHandle append page contents"); content_streams.push_back(iter); } if (!first) { content_streams.push_back(new_contents); } this->replaceKey("/Contents", newArray(content_streams)); } 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; QPDFObjectHandle cur_obj = *this; QPDFObjGen::set visited; while (visited.add(cur_obj)) { // Don't get stuck in an infinite loop if (cur_obj.getKey("/Rotate").getValueAsInt(old_angle)) { break; } else if (cur_obj.getKey("/Parent").isDictionary()) { cur_obj = cur_obj.getKey("/Parent"); } else { break; } } QTC::TC("qpdf", "QPDFObjectHandle found old angle", visited.size() > 1 ? 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; } // 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. QPDF& qpdf = getQPDF("coalesceContentStreams called on object with no associated PDF file"); QPDFObjectHandle new_contents = newStream(&qpdf); this->replaceKey("/Contents", new_contents); auto provider = std::shared_ptr(new CoalesceProvider(*this, contents)); new_contents.replaceStreamData(provider, newNull(), newNull()); } std::string QPDFObjectHandle::unparse() { std::string result; if (this->isIndirect()) { result = getObjGen().unparse(' ') + " R"; } else { result = unparseResolved(); } return result; } std::string QPDFObjectHandle::unparseResolved() { if (!dereference()) { throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle"); } return obj->unparse(); } std::string QPDFObjectHandle::unparseBinary() { auto str = asString(); if (str) { return str->unparse(true); } else { return unparse(); } } // Deprecated versionless getJSON to be removed in qpdf 12 JSON QPDFObjectHandle::getJSON(bool dereference_indirect) { return getJSON(1, dereference_indirect); } JSON QPDFObjectHandle::getJSON(int json_version, bool dereference_indirect) { if ((!dereference_indirect) && isIndirect()) { return JSON::makeString(unparse()); } else if (!dereference()) { throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle"); } else { Pl_Buffer p{"json"}; JSON::Writer jw{&p, 0}; writeJSON(json_version, jw, dereference_indirect); p.finish(); return JSON::parse(p.getString()); } } void QPDFObjectHandle::writeJSON(int json_version, JSON::Writer& p, bool dereference_indirect) { if (!dereference_indirect && isIndirect()) { p << "\"" << getObjGen().unparse(' ') << " R\""; } else if (!dereference()) { throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle"); } else { obj->writeJSON(json_version, p); } } void QPDFObjectHandle::writeJSON(int json_version, Pipeline* p, bool dereference_indirect, size_t depth) { JSON::Writer jw{p, depth}; writeJSON(json_version, jw, dereference_indirect); } JSON QPDFObjectHandle::getStreamJSON( int json_version, qpdf_json_stream_data_e json_data, qpdf_stream_decode_level_e decode_level, Pipeline* p, std::string const& data_filename) { return asStreamWithAssert()->getStreamJSON( json_version, json_data, decode_level, p, data_filename); } 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) { auto input = std::shared_ptr(new BufferInputSource("parsed object", object_str)); QPDFTokenizer tokenizer; bool empty = false; QPDFObjectHandle result = parse(input, object_description, tokenizer, empty, nullptr, 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 " + getObjGen().unparse(' '); 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 streams = arrayOrStreamToStreamArray(description, all_description); bool need_newline = false; Pl_Buffer buf("concatenated content stream buffer"); for (auto stream: streams) { if (need_newline) { buf.writeCStr("\n"); } LastChar lc(&buf); if (!stream.pipeStreamData(&lc, 0, qpdf_dl_specialized)) { QTC::TC("qpdf", "QPDFObjectHandle errors in parsecontent"); throw QPDFExc( qpdf_e_damaged_pdf, "content stream", "content stream object " + stream.getObjGen().unparse(' '), 0, "errors while decoding content stream"); } lc.finish(); need_newline = (lc.getLastChar() != static_cast('\n')); QTC::TC("qpdf", "QPDFObjectHandle need_newline", need_newline ? 0 : 1); } p->writeString(buf.getString()); p->finish(); } void QPDFObjectHandle::parsePageContents(ParserCallbacks* callbacks) { std::string description = "page object " + getObjGen().unparse(' '); this->getKey("/Contents").parseContentStream_internal(description, callbacks); } void QPDFObjectHandle::parseAsContents(ParserCallbacks* callbacks) { std::string description = "object " + getObjGen().unparse(' '); this->parseContentStream_internal(description, callbacks); } void QPDFObjectHandle::filterPageContents(TokenFilter* filter, Pipeline* next) { auto description = "token filter for page object " + getObjGen().unparse(' '); Pl_QPDFTokenizer token_pipeline(description.c_str(), filter, next); this->pipePageContents(&token_pipeline); } void QPDFObjectHandle::filterAsContents(TokenFilter* filter, Pipeline* next) { auto description = "token filter for object " + getObjGen().unparse(' '); 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); auto stream_data = buf.getBufferSharedPointer(); callbacks->contentSize(stream_data->getSize()); try { parseContentStream_data(stream_data, all_description, callbacks, getOwningQPDF()); } catch (TerminateParsing&) { return; } callbacks->handleEOF(); } void QPDFObjectHandle::parseContentStream_data( std::shared_ptr stream_data, std::string const& description, ParserCallbacks* callbacks, QPDF* context) { size_t stream_length = stream_data->getSize(); auto input = std::shared_ptr(new BufferInputSource(description, stream_data.get())); 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); auto obj = QPDFParser(input, "content", tokenizer, nullptr, context).parse(empty, 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(std::shared_ptr filter) { coalesceContentStreams(); this->getKey("/Contents").addTokenFilter(filter); } void QPDFObjectHandle::addTokenFilter(std::shared_ptr filter) { return asStreamWithAssert()->addTokenFilter(filter); } QPDFObjectHandle QPDFObjectHandle::parse( std::shared_ptr input, std::string const& object_description, QPDFTokenizer& tokenizer, bool& empty, StringDecrypter* decrypter, QPDF* context) { return QPDFParser(input, object_description, tokenizer, decrypter, context).parse(empty, false); } qpdf_offset_t QPDFObjectHandle::getParsedOffset() { if (dereference()) { return this->obj->getParsedOffset(); } else { return -1; } } QPDFObjectHandle QPDFObjectHandle::newBool(bool value) { return {QPDF_Bool::create(value)}; } QPDFObjectHandle QPDFObjectHandle::newNull() { return {QPDF_Null::create()}; } QPDFObjectHandle QPDFObjectHandle::newInteger(long long value) { return {QPDF_Integer::create(value)}; } QPDFObjectHandle QPDFObjectHandle::newReal(std::string const& value) { return {QPDF_Real::create(value)}; } QPDFObjectHandle QPDFObjectHandle::newReal(double value, int decimal_places, bool trim_trailing_zeroes) { return {QPDF_Real::create(value, decimal_places, trim_trailing_zeroes)}; } QPDFObjectHandle QPDFObjectHandle::newName(std::string const& name) { return {QPDF_Name::create(name)}; } QPDFObjectHandle QPDFObjectHandle::newString(std::string const& str) { return {QPDF_String::create(str)}; } QPDFObjectHandle QPDFObjectHandle::newUnicodeString(std::string const& utf8_str) { return {QPDF_String::create_utf16(utf8_str)}; } QPDFObjectHandle QPDFObjectHandle::newOperator(std::string const& value) { return {QPDF_Operator::create(value)}; } QPDFObjectHandle QPDFObjectHandle::newInlineImage(std::string const& value) { return {QPDF_InlineImage::create(value)}; } QPDFObjectHandle QPDFObjectHandle::newArray() { return newArray(std::vector()); } QPDFObjectHandle QPDFObjectHandle::newArray(std::vector const& items) { return {QPDF_Array::create(items)}; } QPDFObjectHandle QPDFObjectHandle::newArray(Rectangle const& rect) { return newArray({newReal(rect.llx), newReal(rect.lly), newReal(rect.urx), newReal(rect.ury)}); } QPDFObjectHandle QPDFObjectHandle::newArray(Matrix const& matrix) { return newArray( {newReal(matrix.a), newReal(matrix.b), newReal(matrix.c), newReal(matrix.d), newReal(matrix.e), newReal(matrix.f)}); } QPDFObjectHandle QPDFObjectHandle::newArray(QPDFMatrix const& matrix) { return newArray( {newReal(matrix.a), newReal(matrix.b), newReal(matrix.c), newReal(matrix.d), newReal(matrix.e), newReal(matrix.f)}); } 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()); } QPDFObjectHandle QPDFObjectHandle::newDictionary(std::map const& items) { return {QPDF_Dictionary::create(items)}; } QPDFObjectHandle QPDFObjectHandle::newStream(QPDF* qpdf) { if (qpdf == nullptr) { throw std::runtime_error("attempt to create stream in null qpdf object"); } QTC::TC("qpdf", "QPDFObjectHandle newStream"); return qpdf->newStream(); } QPDFObjectHandle QPDFObjectHandle::newStream(QPDF* qpdf, std::shared_ptr data) { if (qpdf == nullptr) { throw std::runtime_error("attempt to create stream in null qpdf object"); } QTC::TC("qpdf", "QPDFObjectHandle newStream with data"); return qpdf->newStream(data); } QPDFObjectHandle QPDFObjectHandle::newStream(QPDF* qpdf, std::string const& data) { if (qpdf == nullptr) { throw std::runtime_error("attempt to create stream in null qpdf object"); } QTC::TC("qpdf", "QPDFObjectHandle newStream with string"); return qpdf->newStream(data); } QPDFObjectHandle QPDFObjectHandle::newReserved(QPDF* qpdf) { if (qpdf == nullptr) { throw std::runtime_error("attempt to create reserved object in null qpdf object"); } return qpdf->newReserved(); } 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() && obj.get()) { auto descr = std::make_shared(object_description); obj->setDescription(owning_qpdf, descr); } } bool QPDFObjectHandle::hasObjectDescription() { return dereference() && obj.get() && obj->hasDescription(); } QPDFObjectHandle QPDFObjectHandle::shallowCopy() { if (!dereference()) { throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle"); } return {obj->copy()}; } QPDFObjectHandle QPDFObjectHandle::unsafeShallowCopy() { if (!dereference()) { throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle"); } return {obj->copy(true)}; } void QPDFObjectHandle::makeDirect(QPDFObjGen::set& visited, bool stop_at_streams) { assertInitialized(); auto cur_og = getObjGen(); if (!visited.add(cur_og)) { QTC::TC("qpdf", "QPDFObjectHandle makeDirect loop"); throw std::runtime_error("loop detected while converting object from indirect to direct"); } if (isBool() || isInteger() || isName() || isNull() || isReal() || isString()) { this->obj = obj->copy(true); } else if (isArray()) { std::vector items; auto array = asArray(); int n = array->size(); for (int i = 0; i < n; ++i) { items.push_back(array->at(i)); items.back().makeDirect(visited, stop_at_streams); } this->obj = QPDF_Array::create(items); } else if (isDictionary()) { std::map items; auto dict = asDictionary(); for (auto const& key: getKeys()) { items[key] = dict->getKey(key); items[key].makeDirect(visited, stop_at_streams); } this->obj = QPDF_Dictionary::create(items); } else if (isStream()) { QTC::TC("qpdf", "QPDFObjectHandle copy stream", stop_at_streams ? 0 : 1); if (!stop_at_streams) { throw std::runtime_error("attempt to make a stream into a direct object"); } } else if (isReserved()) { throw std::logic_error( "QPDFObjectHandle: attempting to make a reserved object handle direct"); } else { throw std::logic_error("QPDFObjectHandle::makeDirectInternal: unknown object type"); } 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(bool allow_streams) { QPDFObjGen::set visited; makeDirect(visited, allow_streams); } void QPDFObjectHandle::assertInitialized() const { if (!isInitialized()) { throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle"); } } void QPDFObjectHandle::typeWarning(char const* expected_type, std::string const& warning) { QPDF* context = nullptr; std::string description; // Type checks above guarantee that the object has been dereferenced. Nevertheless, dereference // throws exceptions in the test suite if (!dereference()) { throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle"); } this->obj->getDescription(context, description); // Null context handled by warn warn( context, QPDFExc( qpdf_e_object, "", description, 0, std::string("operation for ") + expected_type + " attempted on object of type " + getTypeName() + ": " + warning)); } void QPDFObjectHandle::warnIfPossible(std::string const& warning) { QPDF* context = nullptr; std::string description; if (dereference() && obj->getDescription(context, description)) { warn(context, QPDFExc(qpdf_e_damaged_pdf, "", description, 0, warning)); } else { *QPDFLogger::defaultLogger()->getError() << warning << "\n"; } } void QPDFObjectHandle::objectWarning(std::string const& warning) { QPDF* context = nullptr; std::string description; // Type checks above guarantee that the object has been dereferenced. this->obj->getDescription(context, description); // Null context handled by warn warn(context, QPDFExc(qpdf_e_object, "", description, 0, warning)); } 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(); return isDictionaryOfType("/Page"); } bool QPDFObjectHandle::isPagesObject() { if (getOwningQPDF() == nullptr) { return false; } // getAllPages repairs /Type when traversing the page tree. getOwningQPDF()->getAllPages(); return isDictionaryOfType("/Pages"); } bool QPDFObjectHandle::isFormXObject() { return isStreamOfType("", "/Form"); } bool QPDFObjectHandle::isImage(bool exclude_imagemask) { return ( isStreamOfType("", "/Image") && ((!exclude_imagemask) || (!(getDict().getKey("/ImageMask").isBool() && getDict().getKey("/ImageMask").getBoolValue())))); } void QPDFObjectHandle::checkOwnership(QPDFObjectHandle const& item) const { auto qpdf = getOwningQPDF(); auto item_qpdf = item.getOwningQPDF(); if ((qpdf != nullptr) && (item_qpdf != nullptr) && (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"); } } inline bool QPDFObjectHandle::dereference() { if (!isInitialized()) { return false; } this->obj->resolve(); return true; } 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++() { ++m->iter; updateIValue(); return *this; } QPDFObjectHandle::QPDFDictItems::iterator& QPDFObjectHandle::QPDFDictItems::iterator::operator--() { --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 (m->is_end && other.m->is_end) { return true; } if (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() { m->is_end = (m->iter == m->keys.end()); if (m->is_end) { this->ivalue.first = ""; this->ivalue.second = QPDFObjectHandle(); } else { this->ivalue.first = *(m->iter); this->ivalue.second = 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 {oh, true}; } QPDFObjectHandle::QPDFDictItems::iterator QPDFObjectHandle::QPDFDictItems::end() { return {oh, false}; } QPDFObjectHandle::QPDFArrayItems::QPDFArrayItems(QPDFObjectHandle const& oh) : oh(oh) { } QPDFObjectHandle::QPDFArrayItems::iterator& QPDFObjectHandle::QPDFArrayItems::iterator::operator++() { if (!m->is_end) { ++m->item_number; updateIValue(); } return *this; } QPDFObjectHandle::QPDFArrayItems::iterator& QPDFObjectHandle::QPDFArrayItems::iterator::operator--() { if (m->item_number > 0) { --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 (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() { m->is_end = (m->item_number >= m->oh.getArrayNItems()); if (m->is_end) { this->ivalue = QPDFObjectHandle(); } else { this->ivalue = m->oh.getArrayItem(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 {oh, true}; } QPDFObjectHandle::QPDFArrayItems::iterator QPDFObjectHandle::QPDFArrayItems::end() { return {oh, false}; } QPDFObjGen QPDFObjectHandle::getObjGen() const { return isInitialized() ? obj->getObjGen() : QPDFObjGen(); } // Indirect object accessors QPDF* QPDFObjectHandle::getOwningQPDF() const { return isInitialized() ? this->obj->getQPDF() : nullptr; } QPDF& QPDFObjectHandle::getQPDF(std::string const& error_msg) const { auto result = isInitialized() ? this->obj->getQPDF() : nullptr; if (result == nullptr) { throw std::runtime_error( error_msg.empty() ? "attempt to use a null qpdf object" : error_msg); } return *result; } 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 (isInitialized()) { this->obj->setParsedOffset(offset); } } QPDFObjectHandle operator""_qpdf(char const* v, size_t len) { return QPDFObjectHandle::parse(std::string(v, len), "QPDFObjectHandle literal"); }