#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; using namespace qpdf; BaseHandle:: operator QPDFObjGen() const { return obj ? obj->getObjGen() : QPDFObjGen(); } 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 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) { pipeline = p; } void QPDFObjectHandle::TokenFilter::write(char const* data, size_t len) { if (!pipeline) { return; } if (len) { 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 const& 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(); } std::pair Name::analyzeJSONEncoding(const std::string& name) { int tail = 0; // Number of continuation characters expected. bool tail2 = false; // Potential overlong 3 octet utf-8. bool tail3 = false; // potential overlong 4 octet bool needs_escaping = false; for (auto const& it: name) { auto c = static_cast(it); if (tail) { if ((c & 0xc0) != 0x80) { return {false, false}; } if (tail2) { if ((c & 0xe0) == 0x80) { return {false, false}; } tail2 = false; } else if (tail3) { if ((c & 0xf0) == 0x80) { return {false, false}; } tail3 = false; } tail--; } else if (c < 0x80) { if (!needs_escaping) { needs_escaping = !((c > 34 && c != '\\') || c == ' ' || c == 33); } } else if ((c & 0xe0) == 0xc0) { if ((c & 0xfe) == 0xc0) { return {false, false}; } tail = 1; } else if ((c & 0xf0) == 0xe0) { tail2 = (c == 0xe0); tail = 2; } else if ((c & 0xf8) == 0xf0) { tail3 = (c == 0xf0); tail = 3; } else { return {false, false}; } } return {tail == 0, !needs_escaping}; } std::string Name::normalize(std::string const& name) { if (name.empty()) { return name; } std::string result; result += name.at(0); for (size_t i = 1; i < name.length(); ++i) { char ch = name.at(i); // Don't use locale/ctype here; follow PDF spec guidelines. if (ch == '\0') { // QPDFTokenizer embeds a null character to encode an invalid #. result += "#"; } else if ( ch < 33 || ch == '#' || ch == '/' || ch == '(' || ch == ')' || ch == '{' || ch == '}' || ch == '<' || ch == '>' || ch == '[' || ch == ']' || ch == '%' || ch > 126) { result += util::hex_encode_char(ch); } else { result += ch; } } return result; } std::shared_ptr BaseHandle::copy(bool shallow) const { switch (resolved_type_code()) { case ::ot_uninitialized: throw std::logic_error("QPDFObjectHandle: attempting to copy an uninitialized object"); return {}; // does not return case ::ot_reserved: return QPDFObject::create(); case ::ot_null: return QPDFObject::create(); case ::ot_boolean: return QPDFObject::create(std::get(obj->value).val); case ::ot_integer: return QPDFObject::create(std::get(obj->value).val); case ::ot_real: return QPDFObject::create(std::get(obj->value).val); case ::ot_string: return QPDFObject::create(std::get(obj->value).val); case ::ot_name: return QPDFObject::create(std::get(obj->value).name); case ::ot_array: { auto const& a = std::get(obj->value); if (shallow) { return QPDFObject::create(a); } else { QTC::TC("qpdf", "QPDF_Array copy", a.sp ? 0 : 1); if (a.sp) { QPDF_Array result; result.sp = std::make_unique(); result.sp->size = a.sp->size; for (auto const& [idx, oh]: a.sp->elements) { result.sp->elements[idx] = oh.indirect() ? oh : oh.copy(); } return QPDFObject::create(std::move(result)); } else { std::vector result; result.reserve(a.elements.size()); for (auto const& element: a.elements) { result.emplace_back( element ? (element.indirect() ? element : element.copy()) : element); } return QPDFObject::create(std::move(result), false); } } } case ::ot_dictionary: { auto const& d = std::get(obj->value); if (shallow) { return QPDFObject::create(d.items); } else { std::map new_items; for (auto const& [key, val]: d.items) { new_items[key] = val.indirect() ? val : val.copy(); } return QPDFObject::create(new_items); } } case ::ot_stream: QTC::TC("qpdf", "QPDF_Stream ERR shallow copy stream"); throw std::runtime_error("stream objects cannot be cloned"); return {}; // does not return case ::ot_operator: return QPDFObject::create(std::get(obj->value).val); case ::ot_inlineimage: return QPDFObject::create(std::get(obj->value).val); case ::ot_unresolved: throw std::logic_error("QPDFObjectHandle: attempting to unparse a reserved object"); return {}; // does not return case ::ot_destroyed: throw std::logic_error("attempted to shallow copy QPDFObjectHandle from destroyed QPDF"); return {}; // does not return case ::ot_reference: return obj->qpdf->getObject(obj->og).getObj(); } return {}; // unreachable } std::string BaseHandle::unparse() const { switch (resolved_type_code()) { case ::ot_uninitialized: throw std::logic_error("QPDFObjectHandle: attempting to unparse an uninitialized object"); return ""; // does not return case ::ot_reserved: throw std::logic_error("QPDFObjectHandle: attempting to unparse a reserved object"); return ""; // does not return case ::ot_null: return "null"; case ::ot_boolean: return std::get(obj->value).val ? "true" : "false"; case ::ot_integer: return std::to_string(std::get(obj->value).val); case ::ot_real: return std::get(obj->value).val; case ::ot_string: return std::get(obj->value).unparse(false); case ::ot_name: return Name::normalize(std::get(obj->value).name); case ::ot_array: { auto const& a = std::get(obj->value); std::string result = "[ "; if (a.sp) { size_t next = 0; for (auto& [key, value]: a.sp->elements) { for (size_t j = next; j < key; ++j) { result += "null "; } result += value.unparse() + " "; next = key + 1; } for (size_t j = next; j < a.sp->size; ++j) { result += "null "; } } else { for (auto const& item: a.elements) { result += item.unparse() + " "; } } result += "]"; return result; } case ::ot_dictionary: { auto const& items = std::get(obj->value).items; std::string result = "<< "; for (auto& iter: items) { if (!iter.second.null()) { result += Name::normalize(iter.first) + " " + iter.second.unparse() + " "; } } result += ">>"; return result; } case ::ot_stream: return obj->og.unparse(' ') + " R"; case ::ot_operator: return std::get(obj->value).val; case ::ot_inlineimage: return std::get(obj->value).val; case ::ot_unresolved: throw std::logic_error("QPDFObjectHandle: attempting to unparse a unresolved object"); return ""; // does not return case ::ot_destroyed: throw std::logic_error("attempted to unparse a QPDFObjectHandle from a destroyed QPDF"); return ""; // does not return case ::ot_reference: return obj->og.unparse(' ') + " R"; } return {}; // unreachable } void BaseHandle::write_json(int json_version, JSON::Writer& p) const { switch (resolved_type_code()) { case ::ot_uninitialized: throw std::logic_error( "QPDFObjectHandle: attempting to get JSON from a uninitialized object"); break; // unreachable case ::ot_null: case ::ot_operator: case ::ot_inlineimage: p << "null"; break; case ::ot_boolean: p << std::get(obj->value).val; break; case ::ot_integer: p << std::to_string(std::get(obj->value).val); break; case ::ot_real: { auto const& val = std::get(obj->value).val; if (val.empty()) { // Can't really happen... p << "0"; } else if (val.at(0) == '.') { p << "0" << val; } else if (val.length() >= 2 && val.at(0) == '-' && val.at(1) == '.') { p << "-0." << val.substr(2); } else { p << val; } if (val.back() == '.') { p << "0"; } } break; case ::ot_string: std::get(obj->value).writeJSON(json_version, p); break; case ::ot_name: { auto const& n = std::get(obj->value); // For performance reasons this code is duplicated in QPDF_Dictionary::writeJSON. When // updating this method make sure QPDF_Dictionary is also update. if (json_version == 1) { p << "\"" << JSON::Writer::encode_string(Name::normalize(n.name)) << "\""; } else { if (auto res = Name::analyzeJSONEncoding(n.name); res.first) { if (res.second) { p << "\"" << n.name << "\""; } else { p << "\"" << JSON::Writer::encode_string(n.name) << "\""; } } else { p << "\"n:" << JSON::Writer::encode_string(Name::normalize(n.name)) << "\""; } } } break; case ::ot_array: { auto const& a = std::get(obj->value); p.writeStart('['); if (a.sp) { size_t next = 0; for (auto& [key, value]: a.sp->elements) { for (size_t j = next; j < key; ++j) { p.writeNext() << "null"; } p.writeNext(); auto item_og = value.getObj()->getObjGen(); if (item_og.isIndirect()) { p << "\"" << item_og.unparse(' ') << " R\""; } else { value.write_json(json_version, p); } next = key + 1; } for (size_t j = next; j < a.sp->size; ++j) { p.writeNext() << "null"; } } else { for (auto const& item: a.elements) { p.writeNext(); auto item_og = item.getObj()->getObjGen(); if (item_og.isIndirect()) { p << "\"" << item_og.unparse(' ') << " R\""; } else { item.write_json(json_version, p); } } } p.writeEnd(']'); } break; case ::ot_dictionary: { auto const& d = std::get(obj->value); p.writeStart('{'); for (auto& iter: d.items) { if (!iter.second.null()) { p.writeNext(); if (json_version == 1) { p << "\"" << JSON::Writer::encode_string(Name::normalize(iter.first)) << "\": "; } else if (auto res = Name::analyzeJSONEncoding(iter.first); res.first) { if (res.second) { p << "\"" << iter.first << "\": "; } else { p << "\"" << JSON::Writer::encode_string(iter.first) << "\": "; } } else { p << "\"n:" << JSON::Writer::encode_string(Name::normalize(iter.first)) << "\": "; } iter.second.writeJSON(json_version, p); } } p.writeEnd('}'); } break; case ::ot_stream: std::get(obj->value).m->stream_dict.writeJSON(json_version, p); break; case ::ot_reference: p << "\"" << obj->og.unparse(' ') << " R\""; break; default: throw std::logic_error("attempted to write an unsuitable object as JSON"); } } void BaseHandle::disconnect(bool only_direct) { // QPDF::~QPDF() calls disconnect for indirect objects, so we don't do that here. if (only_direct && indirect()) { return; } switch (raw_type_code()) { case ::ot_array: { auto& a = std::get(obj->value); if (a.sp) { for (auto& item: a.sp->elements) { item.second.disconnect(); } } else { for (auto& oh: a.elements) { oh.disconnect(); } } } break; case ::ot_dictionary: for (auto& iter: std::get(obj->value).items) { iter.second.disconnect(); } break; case ::ot_stream: { auto& s = std::get(obj->value); s.m->stream_provider = nullptr; s.m->stream_dict.disconnect(); } break; case ::ot_uninitialized: return; default: break; } obj->qpdf = nullptr; obj->og = QPDFObjGen(); } std::string QPDFObject::getStringValue() const { switch (getResolvedTypeCode()) { case ::ot_real: return std::get(value).val; case ::ot_string: return std::get(value).val; case ::ot_name: return std::get(value).name; case ::ot_operator: return std::get(value).val; case ::ot_inlineimage: return std::get(value).val; case ::ot_reference: return std::get(value).obj->getStringValue(); default: throw std::logic_error("Internal error in QPDFObject::getStringValue"); } return ""; // unreachable } bool QPDFObjectHandle::isSameObjectAs(QPDFObjectHandle const& rhs) const { return obj == rhs.obj; } qpdf_object_type_e QPDFObjectHandle::getTypeCode() const { return type_code(); } char const* BaseHandle::type_name() const { static constexpr std::array tn{ "uninitialized", "reserved", "null", "boolean", "integer", "real", "string", "name", "array", "dictionary", "stream", "operator", "inline-image", "unresolved", "destroyed", "reference"}; return tn[type_code()]; } char const* QPDFObjectHandle::getTypeName() const { return type_name(); } bool QPDFObjectHandle::isDestroyed() const { return type_code() == ::ot_destroyed; } bool QPDFObjectHandle::isBool() const { return type_code() == ::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 !indirect() && raw_type_code() == ::ot_null; } bool QPDFObjectHandle::isNull() const { return type_code() == ::ot_null; } bool QPDFObjectHandle::isInteger() const { return type_code() == ::ot_integer; } bool QPDFObjectHandle::isReal() const { return type_code() == ::ot_real; } bool QPDFObjectHandle::isNumber() const { return (isInteger() || isReal()); } double QPDFObjectHandle::getNumericValue() const { if (isInteger()) { return static_cast(getIntValue()); } else if (isReal()) { return atof(getRealValue().c_str()); } else { typeWarning("number", "returning 0"); QTC::TC("qpdf", "QPDFObjectHandle numeric non-numeric"); return 0; } } bool QPDFObjectHandle::getValueAsNumber(double& value) const { if (!isNumber()) { return false; } value = getNumericValue(); return true; } bool QPDFObjectHandle::isName() const { return type_code() == ::ot_name; } bool QPDFObjectHandle::isString() const { return type_code() == ::ot_string; } bool QPDFObjectHandle::isOperator() const { return type_code() == ::ot_operator; } bool QPDFObjectHandle::isInlineImage() const { return type_code() == ::ot_inlineimage; } bool QPDFObjectHandle::isArray() const { return type_code() == ::ot_array; } bool QPDFObjectHandle::isDictionary() const { return type_code() == ::ot_dictionary; } bool QPDFObjectHandle::isStream() const { return type_code() == ::ot_stream; } bool QPDFObjectHandle::isReserved() const { return type_code() == ::ot_reserved; } bool QPDFObjectHandle::isScalar() const { return isBool() || isInteger() || isName() || isNull() || isReal() || isString(); } bool QPDFObjectHandle::isNameAndEquals(std::string const& name) const { return isName() && (getName() == name); } bool QPDFObjectHandle::isDictionaryOfType(std::string const& type, std::string const& subtype) const { 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) const { return isStream() && getDict().isDictionaryOfType(type, subtype); } // Bool accessors bool QPDFObjectHandle::getBoolValue() const { if (auto boolean = as()) { return boolean->val; } else { typeWarning("boolean", "returning false"); QTC::TC("qpdf", "QPDFObjectHandle boolean returning false"); return false; } } bool QPDFObjectHandle::getValueAsBool(bool& value) const { if (auto boolean = as()) { value = boolean->val; return true; } return false; } // Integer accessors long long QPDFObjectHandle::getIntValue() const { if (auto integer = as()) { return integer->val; } else { typeWarning("integer", "returning 0"); QTC::TC("qpdf", "QPDFObjectHandle integer returning 0"); return 0; } } bool QPDFObjectHandle::getValueAsInt(long long& value) const { if (auto integer = as()) { value = integer->val; return true; } return false; } int QPDFObjectHandle::getIntValueAsInt() const { long long v = getIntValue(); if (v < INT_MIN) { warn("requested value of integer is too small; returning INT_MIN"); return INT_MIN; } if (v > INT_MAX) { warn("requested value of integer is too big; returning INT_MAX"); return INT_MAX; } return static_cast(v); } bool QPDFObjectHandle::getValueAsInt(int& value) const { if (!isInteger()) { return false; } value = getIntValueAsInt(); return true; } unsigned long long QPDFObjectHandle::getUIntValue() const { long long v = getIntValue(); if (v < 0) { warn("unsigned value request for negative number; returning 0"); return 0; } else { return static_cast(v); } } bool QPDFObjectHandle::getValueAsUInt(unsigned long long& value) const { if (!isInteger()) { return false; } value = getUIntValue(); return true; } unsigned int QPDFObjectHandle::getUIntValueAsUInt() const { long long v = getIntValue(); if (v < 0) { warn("unsigned integer value request for negative number; returning 0"); return 0; } if (v > UINT_MAX) { warn("requested value of unsigned integer is too big; returning UINT_MAX"); return UINT_MAX; } return static_cast(v); } bool QPDFObjectHandle::getValueAsUInt(unsigned int& value) const { if (!isInteger()) { return false; } value = getUIntValueAsUInt(); return true; } // Real accessors std::string QPDFObjectHandle::getRealValue() const { 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) const { if (!isReal()) { return false; } value = obj->getStringValue(); return true; } // Name accessors std::string QPDFObjectHandle::getName() const { 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) const { if (!isName()) { return false; } value = obj->getStringValue(); return true; } // String accessors std::string QPDFObjectHandle::getStringValue() const { 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) const { if (!isString()) { return false; } value = obj->getStringValue(); return true; } std::string QPDFObjectHandle::getUTF8Value() const { if (auto str = as()) { return str->getUTF8Val(); } else { typeWarning("string", "returning empty string"); QTC::TC("qpdf", "QPDFObjectHandle string returning empty utf8"); return ""; } } bool QPDFObjectHandle::getValueAsUTF8(std::string& value) const { if (auto str = as()) { value = str->getUTF8Val(); return true; } return false; } // Operator and Inline Image accessors std::string QPDFObjectHandle::getOperatorValue() const { 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) const { if (!isOperator()) { return false; } value = obj->getStringValue(); return true; } std::string QPDFObjectHandle::getInlineImageValue() const { 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) const { if (!isInlineImage()) { return false; } value = obj->getStringValue(); return true; } // Array accessors and mutators are in QPDF_Array.cc QPDFObjectHandle::QPDFArrayItems QPDFObjectHandle::aitems() { return *this; } // Dictionary accessors are in QPDF_Dictionary.cc QPDFObjectHandle::QPDFDictItems QPDFObjectHandle::ditems() { return {*this}; } // Array and Name accessors bool QPDFObjectHandle::isOrHasName(std::string const& value) const { if (isNameAndEquals(value)) { return true; } else if (isArray()) { for (auto& item: getArrayAsVector()) { if (item.isNameAndEquals(value)) { return true; } } } return false; } void QPDFObjectHandle::makeResourcesIndirect(QPDF& owning_qpdf) { for (auto const& i1: as_dictionary()) { for (auto& i2: i1.second.as_dictionary()) { if (!i2.second.null() && !i2.second.isIndirect()) { i2.second = owning_qpdf.makeIndirectObject(i2.second); } } } } 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& [key, value]: dict.as_dictionary()) { if (!value.null() && value.isIndirect()) { og_to_name.insert_or_assign(value.getObjGen(), key); } } }; // This algorithm is described in comments in QPDFObjectHandle.hh // above the declaration of mergeResources. for (auto const& [rtype, value1]: other.as_dictionary()) { auto other_val = value1; 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& [key, value2]: other_val.as_dictionary()) { QPDFObjectHandle rval = value2; 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.contains(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.contains(other_item.unparse())) { 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() const { // Return second-level dictionary keys std::set result; for (auto const& item: as_dictionary(strict)) { for (auto const& [key2, val2]: item.second.as_dictionary(strict)) { if (!val2.null()) { result.insert(key2); } } } return result; } std::string QPDFObjectHandle::getUniqueResourceName( std::string const& prefix, int& min_suffix, std::set* namesp) const { 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.contains(candidate)) { 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 are in QPDF_Dictionary.cc // Stream accessors are in QPDF_Stream.cc 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 = as_array(strict)) { int n_items = static_cast(array.size()); for (int i = 0; i < n_items; ++i) { QPDFObjectHandle item = array.at(i).second; if (item.isStream()) { result.emplace_back(item); } else { QTC::TC("qpdf", "QPDFObjectHandle non-stream in stream array"); item.warn( {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.emplace_back(*this); } else if (!null()) { warn( {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 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); } 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 = 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); replaceKey("/Contents", new_contents); auto provider = std::shared_ptr(new CoalesceProvider(*this, contents)); new_contents.replaceStreamData(provider, newNull(), newNull()); } std::string QPDFObjectHandle::unparse() const { if (isIndirect()) { return getObjGen().unparse(' ') + " R"; } else { return unparseResolved(); } } std::string QPDFObjectHandle::unparseResolved() const { if (!obj) { throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle"); } return BaseHandle::unparse(); } std::string QPDFObjectHandle::unparseBinary() const { if (auto str = as()) { return str->unparse(true); } else { return unparse(); } } JSON QPDFObjectHandle::getJSON(int json_version, bool dereference_indirect) const { if ((!dereference_indirect) && isIndirect()) { return JSON::makeString(unparse()); } else if (!obj) { 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) const { if (!dereference_indirect && isIndirect()) { p << "\"" << getObjGen().unparse(' ') << " R\""; } else if (!obj) { throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle"); } else { write_json(json_version, p); } } void QPDFObjectHandle::writeJSON( int json_version, Pipeline* p, bool dereference_indirect, size_t depth) const { JSON::Writer jw{p, depth}; writeJSON(json_version, jw, dereference_indirect); } 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 = is::OffsetBuffer("parsed object", object_str); auto result = QPDFParser::parse(input, object_description, 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, "parsed object", 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; getKey("/Contents").pipeContentStreams(p, description, all_description); } void QPDFObjectHandle::pipeContentStreams( Pipeline* p, std::string const& description, std::string& all_description) { bool need_newline = false; std::string buffer; pl::String buf(buffer); for (auto stream: arrayOrStreamToStreamArray(description, all_description)) { if (need_newline) { buf.writeCStr("\n"); } if (!stream.pipeStreamData(&buf, 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"); } need_newline = buffer.empty() || buffer.back() != '\n'; QTC::TC("qpdf", "QPDFObjectHandle need_newline", need_newline ? 0 : 1); p->writeString(buffer); buffer.clear(); } p->finish(); } void QPDFObjectHandle::parsePageContents(ParserCallbacks* callbacks) { std::string description = "page object " + getObjGen().unparse(' '); getKey("/Contents").parseContentStream_internal(description, callbacks); } void QPDFObjectHandle::parseAsContents(ParserCallbacks* callbacks) { std::string description = "object " + getObjGen().unparse(' '); 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); 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); 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) { std::string stream_data; pl::String buf(stream_data); std::string all_description; pipeContentStreams(&buf, description, all_description); if (callbacks) { callbacks->contentSize(stream_data.size()); } try { parseContentStream_data(stream_data, all_description, callbacks, getOwningQPDF()); } catch (TerminateParsing&) { return; } if (callbacks) { callbacks->handleEOF(); } } void QPDFObjectHandle::parseContentStream_data( std::string_view stream_data, std::string const& description, ParserCallbacks* callbacks, QPDF* context) { size_t stream_length = stream_data.size(); auto input = is::OffsetBuffer(description, stream_data); Tokenizer tokenizer; tokenizer.allowEOF(); auto sp_description = QPDFParser::make_description(description, "content"); 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.nextToken(input, "content", true); qpdf_offset_t offset = input.getLastOffset(); input.seek(offset, SEEK_SET); auto obj = QPDFParser::parse_content(input, sp_description, tokenizer, context); if (!obj) { // EOF break; } size_t length = QIntC::to_size(input.tell() - offset); if (callbacks) { 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); tokenizer.nextToken(input, description); offset = input.getLastOffset(); length = QIntC::to_size(input.tell() - offset); if (tokenizer.getType() == QPDFTokenizer::tt_bad) { QTC::TC("qpdf", "QPDFObjectHandle EOF in inline image"); warn( context, QPDFExc( qpdf_e_damaged_pdf, description, "stream data", input.tell(), "EOF found while reading inline image")); } else { QTC::TC("qpdf", "QPDFObjectHandle inline image token"); if (callbacks) { callbacks->handleObject( QPDFObjectHandle::newInlineImage(tokenizer.getValue()), QIntC::to_size(offset), length); } } } } } void QPDFObjectHandle::addContentTokenFilter(std::shared_ptr filter) { coalesceContentStreams(); getKey("/Contents").addTokenFilter(filter); } void QPDFObjectHandle::addTokenFilter(std::shared_ptr filter) { return as_stream(error).addTokenFilter(filter); } QPDFObjectHandle QPDFObjectHandle::parse( std::shared_ptr input, std::string const& object_description, QPDFTokenizer& tokenizer, bool& empty, StringDecrypter* decrypter, QPDF* context) { return QPDFParser::parse(*input, object_description, tokenizer, empty, decrypter, context); } qpdf_offset_t QPDFObjectHandle::getParsedOffset() const { return obj ? obj->getParsedOffset() : -1; } QPDFObjectHandle QPDFObjectHandle::newBool(bool value) { return {QPDFObject::create(value)}; } QPDFObjectHandle QPDFObjectHandle::newNull() { return {QPDFObject::create()}; } QPDFObjectHandle QPDFObjectHandle::newInteger(long long value) { return {QPDFObject::create(value)}; } QPDFObjectHandle QPDFObjectHandle::newReal(std::string const& value) { return {QPDFObject::create(value)}; } QPDFObjectHandle QPDFObjectHandle::newReal(double value, int decimal_places, bool trim_trailing_zeroes) { return {QPDFObject::create(value, decimal_places, trim_trailing_zeroes)}; } QPDFObjectHandle QPDFObjectHandle::newName(std::string const& name) { return {QPDFObject::create(name)}; } QPDFObjectHandle QPDFObjectHandle::newString(std::string const& str) { return {QPDFObject::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 {QPDFObject::create(value)}; } QPDFObjectHandle QPDFObjectHandle::newInlineImage(std::string const& value) { return {QPDFObject::create(value)}; } QPDFObjectHandle QPDFObjectHandle::newArray() { return newArray(std::vector()); } QPDFObjectHandle QPDFObjectHandle::newArray(std::vector const& items) { return {QPDFObject::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 {QPDFObject::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(); } std::string BaseHandle::description() const { return obj ? obj->getDescription() : ""s; } void QPDFObjectHandle::setObjectDescription(QPDF* owning_qpdf, std::string const& object_description) { if (obj) { auto descr = std::make_shared(object_description); obj->setDescription(owning_qpdf, descr); } } bool QPDFObjectHandle::hasObjectDescription() const { return obj && obj->hasDescription(); } QPDFObjectHandle QPDFObjectHandle::shallowCopy() { if (!obj) { throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle"); } return {copy()}; } QPDFObjectHandle QPDFObjectHandle::unsafeShallowCopy() { if (!obj) { throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle"); } return {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()) { obj = copy(true); } else if (auto a = as_array(strict)) { std::vector items; for (auto const& item: a) { items.emplace_back(item); items.back().makeDirect(visited, stop_at_streams); } obj = QPDFObject::create(items); } else if (isDictionary()) { std::map items; for (auto const& [key, value]: as_dictionary(strict)) { if (!value.null()) { items.insert({key, value}); items[key].makeDirect(visited, stop_at_streams); } } obj = QPDFObject::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(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 (!obj) { throw std::logic_error("operation attempted on uninitialized QPDFObjectHandle"); } } std::runtime_error BaseHandle::type_error(char const* expected_type) const { return std::runtime_error( "operation for "s + expected_type + " attempted on object of type " + type_name()); } QPDFExc BaseHandle::type_error(char const* expected_type, std::string const& message) const { return { qpdf_e_object, "", description(), 0, "operation for "s + expected_type + " attempted on object of type " + type_name() + ": " + message}; } void QPDFObjectHandle::typeWarning(char const* expected_type, std::string const& message) const { if (!obj) { throw std::logic_error("attempted to dereference an uninitialized QPDFObjectHandle"); } warn(type_error(expected_type, message)); } void QPDFObjectHandle::warnIfPossible(std::string const& warning) const { warn(warning); } void QPDFObjectHandle::objectWarning(std::string const& warning) const { warn({qpdf_e_object, "", description(), 0, warning}); } void QPDFObjectHandle::assertType(char const* type_name, bool istype) const { if (!istype) { throw std::runtime_error( std::string("operation for ") + type_name + " attempted on object of type " + QPDFObjectHandle(*this).getTypeName()); } } void QPDFObjectHandle::assertNull() const { assertType("null", isNull()); } void QPDFObjectHandle::assertBool() const { assertType("boolean", isBool()); } void QPDFObjectHandle::assertInteger() const { assertType("integer", isInteger()); } void QPDFObjectHandle::assertReal() const { assertType("real", isReal()); } void QPDFObjectHandle::assertName() const { assertType("name", isName()); } void QPDFObjectHandle::assertString() const { assertType("string", isString()); } void QPDFObjectHandle::assertOperator() const { assertType("operator", isOperator()); } void QPDFObjectHandle::assertInlineImage() const { assertType("inlineimage", isInlineImage()); } void QPDFObjectHandle::assertArray() const { assertType("array", isArray()); } void QPDFObjectHandle::assertDictionary() const { assertType("dictionary", isDictionary()); } void QPDFObjectHandle::assertStream() const { assertType("stream", isStream()); } void QPDFObjectHandle::assertReserved() const { assertType("reserved", isReserved()); } void QPDFObjectHandle::assertIndirect() const { if (!isIndirect()) { throw std::logic_error("operation for indirect object attempted on direct object"); } } void QPDFObjectHandle::assertScalar() const { assertType("scalar", isScalar()); } void QPDFObjectHandle::assertNumber() const { assertType("number", isNumber()); } bool QPDFObjectHandle::isPageObject() const { // 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() const { if (getOwningQPDF() == nullptr) { return false; } // getAllPages repairs /Type when traversing the page tree. getOwningQPDF()->getAllPages(); return isDictionaryOfType("/Pages"); } bool QPDFObjectHandle::isFormXObject() const { return isStreamOfType("", "/Form"); } bool QPDFObjectHandle::isImage(bool exclude_imagemask) const { return ( isStreamOfType("", "/Image") && ((!exclude_imagemask) || (!(getDict().getKey("/ImageMask").isBool() && getDict().getKey("/ImageMask").getBoolValue())))); } void QPDFObjectHandle::assertPageObject() const { if (!isPageObject()) { throw std::runtime_error("page operation called on non-Page object"); } } void BaseHandle::warn(QPDF* qpdf, QPDFExc&& e) { if (!qpdf) { throw std::move(e); } qpdf->warn(std::move(e)); } void BaseHandle::warn(QPDFExc&& e) const { if (!qpdf()) { throw std::move(e); } qpdf()->warn(std::move(e)); } void BaseHandle::warn(std::string const& warning) const { if (qpdf()) { warn({qpdf_e_damaged_pdf, "", description(), 0, warning}); } else { *QPDFLogger::defaultLogger()->getError() << warning << "\n"; } } 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 ivalue; } QPDFObjectHandle::QPDFDictItems::iterator::pointer QPDFObjectHandle::QPDFDictItems::iterator::operator->() { updateIValue(); return &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 (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) { ivalue.first = ""; ivalue.second = QPDFObjectHandle(); } else { ivalue.first = *(m->iter); ivalue.second = m->oh.getKey(ivalue.first); } } QPDFObjectHandle::QPDFDictItems::iterator::Members::Members(QPDFObjectHandle& oh, bool for_begin) : oh(oh) { keys = oh.getKeys(); iter = for_begin ? keys.begin() : 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 ivalue; } QPDFObjectHandle::QPDFArrayItems::iterator::pointer QPDFObjectHandle::QPDFArrayItems::iterator::operator->() { updateIValue(); return &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) { ivalue = QPDFObjectHandle(); } else { ivalue = m->oh.getArrayItem(m->item_number); } } QPDFObjectHandle::QPDFArrayItems::iterator::Members::Members(QPDFObjectHandle& oh, bool for_begin) : oh(oh) { 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 obj ? obj->getObjGen() : QPDFObjGen(); } int QPDFObjectHandle::getObjectID() const { return getObjGen().getObj(); } int QPDFObjectHandle::getGeneration() const { return getObjGen().getGen(); } bool QPDFObjectHandle::isIndirect() const { return getObjectID() != 0; } // Indirect object accessors QPDF* QPDFObjectHandle::getOwningQPDF() const { return obj ? obj->getQPDF() : nullptr; } QPDF& QPDFObjectHandle::getQPDF(std::string const& error_msg) const { if (auto result = obj ? obj->getQPDF() : nullptr) { return *result; } throw std::runtime_error(error_msg.empty() ? "attempt to use a null qpdf object" : error_msg); } void QPDFObjectHandle::setParsedOffset(qpdf_offset_t offset) { if (obj) { obj->setParsedOffset(offset); } } QPDFObjectHandle operator""_qpdf(char const* v, size_t len) { return QPDFObjectHandle::parse(std::string(v, len), "QPDFObjectHandle literal"); }