Commit message: Parse iteratively to avoid stack overflow (fixes #146)
CVE: CVE-2017-12595
CWE: CWE-20
Vulnerability description: The tokenizer in QPDF 6.0.0 and 7.0.b1 is recursive for arrays and dictionaries, which allows remote attackers to cause a denial of service (stack consumption and segmentation fault) or possibly have unspecified other impact via a PDF document with a deep data structure, as demonstrated by a crash in QPDFObjectHandle::parseInternal in libqpdf/QPDFObjectHandle.cc.

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::getTypeName()
{
    if (this->initialized)
    {
        dereference();
        return obj->getTypeName();
    }
    else
    {
        return "uninitialized";
    }
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::isName()
{
    dereference();
    return QPDFObjectTypeAccessor<QPDF_Name>::check(obj.getPointer());
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::getObjectID() const
{
    return this->objid;
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::eraseItem(int at)
{
    assertArray();
    return dynamic_cast<QPDF_Array*>(obj.getPointer())->eraseItem(at);
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::isReserved()
{
    // dereference will clear reserved if this has been replaced
    dereference();
    return this->reserved;
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::QPDFObjectHandle() :
    initialized(false),
    qpdf(0),
    objid(0),
    generation(0),
    reserved(false)
{
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::replaceDict(QPDFObjectHandle new_dict)
{
    assertStream();
    dynamic_cast<QPDF_Stream*>(obj.getPointer())->replaceDict(new_dict);
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::replaceOrRemoveKey(std::string const& key,
				     QPDFObjectHandle value)
{
    assertDictionary();
    return dynamic_cast<QPDF_Dictionary*>(
	obj.getPointer())->replaceOrRemoveKey(key, value);
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::setArrayItem(int n, QPDFObjectHandle const& item)
{
    assertArray();
    return dynamic_cast<QPDF_Array*>(obj.getPointer())->setItem(n, item);
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::replaceStreamData(PointerHolder<Buffer> data,
				    QPDFObjectHandle const& filter,
				    QPDFObjectHandle const& decode_parms)
{
    assertStream();
    dynamic_cast<QPDF_Stream*>(obj.getPointer())->replaceStreamData(
	data, filter, decode_parms);
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::getRealValue()
{
    assertReal();
    return dynamic_cast<QPDF_Real*>(obj.getPointer())->getVal();
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::dereference()
{
    if (this->obj.getPointer() == 0)
    {
        PointerHolder<QPDFObject> obj = QPDF::Resolver::resolve(
	    this->qpdf, this->objid, this->generation);
	if (obj.getPointer() == 0)
	{
	    QTC::TC("qpdf", "QPDFObjectHandle indirect to unknown");
	    this->obj = new QPDF_Null();
	}
        else if (dynamic_cast<QPDF_Reserved*>(obj.getPointer()))
        {
            // Do not resolve
        }
        else
        {
            this->reserved = false;
            this->obj = obj;
        }
    }
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::getPageContents()
{
    assertPageObject();

    std::vector<QPDFObjectHandle> result;
    QPDFObjectHandle contents = this->getKey("/Contents");
    if (contents.isArray())
    {
	int n_items = contents.getArrayNItems();
	for (int i = 0; i < n_items; ++i)
	{
	    QPDFObjectHandle item = contents.getArrayItem(i);
	    if (item.isStream())
	    {
		result.push_back(item);
	    }
	    else
	    {
		throw std::runtime_error(
		    "unknown item type while inspecting "
		    "element of /Contents array in page "
		    "dictionary");
	    }
	}
    }
    else if (contents.isStream())
    {
	result.push_back(contents);
    }
    else if (! contents.isNull())
    {
	throw std::runtime_error("unknown object type inspecting /Contents "
				 "key in page dictionary");
    }

    return result;
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::getStreamData(qpdf_stream_decode_level_e level)
{
    assertStream();
    return dynamic_cast<QPDF_Stream*>(obj.getPointer())->getStreamData(level);
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::getUTF8Value()
{
    assertString();
    return dynamic_cast<QPDF_String*>(obj.getPointer())->getUTF8Val();
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::assertScalar()
{
    assertType("Scalar", isScalar());
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::replaceKey(std::string const& key,
			    QPDFObjectHandle const& value)
{
    assertDictionary();
    return dynamic_cast<QPDF_Dictionary*>(
	obj.getPointer())->replaceKey(key, value);
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::assertBool()
{
    assertType("Boolean", isBool());
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::isStream()
{
    dereference();
    return QPDFObjectTypeAccessor<QPDF_Stream>::check(obj.getPointer());
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::getDictAsMap()
{
    assertDictionary();
    return dynamic_cast<QPDF_Dictionary*>(obj.getPointer())->getAsMap();
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::getTypeCode()
{
    if (this->initialized)
    {
        dereference();
        return obj->getTypeCode();
    }
    else
    {
        return QPDFObject::ot_uninitialized;
    }
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::hasKey(std::string const& key)
{
    assertDictionary();
    return dynamic_cast<QPDF_Dictionary*>(obj.getPointer())->hasKey(key);
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::isOperator()
{
    dereference();
    return QPDFObjectTypeAccessor<QPDF_Operator>::check(obj.getPointer());
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::getArrayAsVector()
{
    assertArray();
    return dynamic_cast<QPDF_Array*>(obj.getPointer())->getAsVector();
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::makeDirectInternal(std::set<int>& visited)
{
    assertInitialized();

    if (isStream())
    {
	QTC::TC("qpdf", "QPDFObjectHandle ERR clone stream");
	throw std::runtime_error(
	    "attempt to make a stream into a direct object");
    }

    int cur_objid = this->objid;
    if (cur_objid != 0)
    {
	if (visited.count(cur_objid))
	{
	    QTC::TC("qpdf", "QPDFObjectHandle makeDirect loop");
	    throw std::runtime_error(
		"loop detected while converting object from "
		"indirect to direct");
	}
	visited.insert(cur_objid);
    }

    if (isReserved())
    {
        throw std::logic_error(
            "QPDFObjectHandle: attempting to make a"
            " reserved object handle direct");
    }

    dereference();
    this->qpdf = 0;
    this->objid = 0;
    this->generation = 0;

    PointerHolder<QPDFObject> new_obj;

    if (isBool())
    {
	QTC::TC("qpdf", "QPDFObjectHandle clone bool");
	new_obj = new QPDF_Bool(getBoolValue());
    }
    else if (isNull())
    {
	QTC::TC("qpdf", "QPDFObjectHandle clone null");
	new_obj = new QPDF_Null();
    }
    else if (isInteger())
    {
	QTC::TC("qpdf", "QPDFObjectHandle clone integer");
	new_obj = new QPDF_Integer(getIntValue());
    }
    else if (isReal())
    {
	QTC::TC("qpdf", "QPDFObjectHandle clone real");
	new_obj = new QPDF_Real(getRealValue());
    }
    else if (isName())
    {
	QTC::TC("qpdf", "QPDFObjectHandle clone name");
	new_obj = new QPDF_Name(getName());
    }
    else if (isString())
    {
	QTC::TC("qpdf", "QPDFObjectHandle clone string");
	new_obj = new QPDF_String(getStringValue());
    }
    else if (isArray())
    {
	QTC::TC("qpdf", "QPDFObjectHandle clone array");
	std::vector<QPDFObjectHandle> items;
	int n = getArrayNItems();
	for (int i = 0; i < n; ++i)
	{
	    items.push_back(getArrayItem(i));
	    items.back().makeDirectInternal(visited);
	}
	new_obj = new QPDF_Array(items);
    }
    else if (isDictionary())
    {
	QTC::TC("qpdf", "QPDFObjectHandle clone dictionary");
	std::set<std::string> keys = getKeys();
	std::map<std::string, QPDFObjectHandle> items;
	for (std::set<std::string>::iterator iter = keys.begin();
	     iter != keys.end(); ++iter)
	{
	    items[*iter] = getKey(*iter);
	    items[*iter].makeDirectInternal(visited);
	}
	new_obj = new QPDF_Dictionary(items);
    }
    else
    {
	throw std::logic_error("QPDFObjectHandle::makeDirectInternal: "
			       "unknown object type");
    }

    this->obj = new_obj;

    if (cur_objid)
    {
	visited.erase(cur_objid);
    }
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::assertStream()
{
    assertType("Stream", isStream());
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::assertInlineImage()
{
    assertType("InlineImage", isInlineImage());
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::assertName()
{
    assertType("Name", isName());
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::assertInteger()
{
    assertType("Integer", isInteger());
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::insertItem(int at, QPDFObjectHandle const& item)
{
    assertArray();
    return dynamic_cast<QPDF_Array*>(obj.getPointer())->insertItem(at, item);
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::getStringValue()
{
    assertString();
    return dynamic_cast<QPDF_String*>(obj.getPointer())->getVal();
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::isInlineImage()
{
    dereference();
    return QPDFObjectTypeAccessor<QPDF_InlineImage>::check(obj.getPointer());
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::getOperatorValue()
{
    assertOperator();
    return dynamic_cast<QPDF_Operator*>(obj.getPointer())->getVal();
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::isArray()
{
    dereference();
    return QPDFObjectTypeAccessor<QPDF_Array>::check(obj.getPointer());
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::unparse()
{
    std::string result;
    if (this->isIndirect())
    {
	result = QUtil::int_to_string(this->objid) + " " +
	    QUtil::int_to_string(this->generation) + " R";
    }
    else
    {
	result = unparseResolved();
    }
    return result;
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::assertArray()
{
    assertType("Array", isArray());
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::getKeys()
{
    assertDictionary();
    return dynamic_cast<QPDF_Dictionary*>(obj.getPointer())->getKeys();
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::assertNull()
{
    assertType("Null", isNull());
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::QPDFObjectHandle(QPDF* qpdf, int objid, int generation) :
    initialized(true),
    qpdf(qpdf),
    objid(objid),
    generation(generation),
    reserved(false)
{
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::getRawStreamData()
{
    assertStream();
    return dynamic_cast<QPDF_Stream*>(obj.getPointer())->getRawStreamData();
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::pipeStreamData(Pipeline* p, bool filter,
				 bool normalize, bool compress)
{
    unsigned long 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);
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::getGeneration() const
{
    return this->generation;
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::parseContentStream(QPDFObjectHandle stream_or_array,
                                     ParserCallbacks* callbacks)
{
    std::vector<QPDFObjectHandle> streams;
    if (stream_or_array.isArray())
    {
        streams = stream_or_array.getArrayAsVector();
    }
    else
    {
        streams.push_back(stream_or_array);
    }
    Pl_Buffer buf("concatenated stream data buffer");
    std::string all_description = "content stream objects";
    bool first = true;
    for (std::vector<QPDFObjectHandle>::iterator iter = streams.begin();
         iter != streams.end(); ++iter)
    {
        QPDFObjectHandle stream = *iter;
        if (! stream.isStream())
        {
            QTC::TC("qpdf", "QPDFObjectHandle non-stream in parsecontent");
            warn(stream.getOwningQPDF(),
                 QPDFExc(qpdf_e_damaged_pdf, "content stream",
                         "", 0,
                         "ignoring non-stream while parsing content streams"));
        }
        else
        {
            std::string og = QUtil::int_to_string(stream.getObjectID()) + " " +
                QUtil::int_to_string(stream.getGeneration());
            std::string description = "content stream object " + og;
            if (first)
            {
                first = false;
            }
            else
            {
                all_description += ",";
            }
            all_description += " " + og;
            if (! stream.pipeStreamData(&buf, 0, qpdf_dl_specialized))
            {
                QTC::TC("qpdf", "QPDFObjectHandle errors in parsecontent");
                warn(stream.getOwningQPDF(),
                     QPDFExc(qpdf_e_damaged_pdf, "content stream",
                             description, 0,
                             "errors while decoding content stream"));
            }
        }
    }
    PointerHolder<Buffer> stream_data = buf.getBuffer();
    try
    {
        parseContentStream_internal(stream_data, all_description, callbacks);
    }
    catch (TerminateParsing&)
    {
        return;
    }
    callbacks->handleEOF();
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::assertReal()
{
    assertType("Real", isReal());
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::parseInternal(PointerHolder<InputSource> input,
                                std::string const& object_description,
                                QPDFTokenizer& tokenizer, bool& empty,
                                StringDecrypter* decrypter, QPDF* context,
                                bool content_stream)
{
    // This method must take care not to resolve any objects. Don't
    // check the type of any object without first ensuring that it is
    // a direct object. Otherwise, doing so may have the side effect
    // of reading the object and changing the file pointer.

    empty = false;

    QPDFObjectHandle object;

    std::vector<std::vector<QPDFObjectHandle> > olist_stack;
    olist_stack.push_back(std::vector<QPDFObjectHandle>());
    enum state_e { st_top, st_start, st_stop, st_eof, st_dictionary, st_array };
    std::vector<state_e> state_stack;
    state_stack.push_back(st_top);
    std::vector<qpdf_offset_t> offset_stack;
    offset_stack.push_back(input->tell());
    bool done = false;
    while (! done)
    {
        std::vector<QPDFObjectHandle>& olist = olist_stack.back();
        state_e state = state_stack.back();
        qpdf_offset_t offset = offset_stack.back();

	object = QPDFObjectHandle();

	QPDFTokenizer::Token token =
            tokenizer.readToken(input, object_description);

	switch (token.getType())
	{
          case QPDFTokenizer::tt_eof:
            if (content_stream)
            {
                state = st_eof;
            }
            else
            {
                // When not in content stream mode, EOF is tt_bad and
                // throws an exception before we get here.
                throw std::logic_error(
                    "EOF received while not in content stream mode");
            }
            break;

	  case QPDFTokenizer::tt_brace_open:
	  case QPDFTokenizer::tt_brace_close:
	    QTC::TC("qpdf", "QPDFObjectHandle bad brace");
            warn(context,
                 QPDFExc(qpdf_e_damaged_pdf, input->getName(),
                         object_description,
                         input->getLastOffset(),
                         "treating unexpected brace token as null"));
            object = newNull();
	    break;

	  case QPDFTokenizer::tt_array_close:
	    if (state == st_array)
	    {
                state = st_stop;
	    }
	    else
	    {
		QTC::TC("qpdf", "QPDFObjectHandle bad array close");
                warn(context,
                     QPDFExc(qpdf_e_damaged_pdf, input->getName(),
                             object_description,
                             input->getLastOffset(),
                             "treating unexpected array close token as null"));
                object = newNull();
	    }
	    break;

	  case QPDFTokenizer::tt_dict_close:
	    if (state == st_dictionary)
	    {
                state = st_stop;
	    }
	    else
	    {
		QTC::TC("qpdf", "QPDFObjectHandle bad dictionary close");
                warn(context,
                     QPDFExc(qpdf_e_damaged_pdf, input->getName(),
                             object_description,
                             input->getLastOffset(),
                             "unexpected dictionary close token"));
                object = newNull();
	    }
	    break;

	  case QPDFTokenizer::tt_array_open:
	  case QPDFTokenizer::tt_dict_open:
            olist_stack.push_back(std::vector<QPDFObjectHandle>());
            state = st_start;
            offset_stack.push_back(input->tell());
            state_stack.push_back(
                (token.getType() == QPDFTokenizer::tt_array_open) ?
                st_array : st_dictionary);
	    break;

	  case QPDFTokenizer::tt_bool:
	    object = newBool((token.getValue() == "true"));
	    break;

	  case QPDFTokenizer::tt_null:
	    object = newNull();
	    break;

	  case QPDFTokenizer::tt_integer:
	    object = newInteger(QUtil::string_to_ll(token.getValue().c_str()));
	    break;

	  case QPDFTokenizer::tt_real:
	    object = newReal(token.getValue());
	    break;

	  case QPDFTokenizer::tt_name:
	    object = newName(token.getValue());
	    break;

	  case QPDFTokenizer::tt_word:
	    {
		std::string const& value = token.getValue();
                if (content_stream)
                {
                    object = QPDFObjectHandle::newOperator(value);
                }
		else if ((value == "R") && (state != st_top) &&
                         (olist.size() >= 2) &&
                         (! olist.at(olist.size() - 1).isIndirect()) &&
                         (olist.at(olist.size() - 1).isInteger()) &&
                         (! olist.at(olist.size() - 2).isIndirect()) &&
                         (olist.at(olist.size() - 2).isInteger()))
		{
                    if (context == 0)
                    {
                        QTC::TC("qpdf", "QPDFObjectHandle indirect without context");
                        throw std::logic_error(
                            "QPDFObjectHandle::parse called without context"
                            " on an object with indirect references");
                    }
		    // Try to resolve indirect objects
		    object = newIndirect(
			context,
			olist.at(olist.size() - 2).getIntValue(),
			olist.at(olist.size() - 1).getIntValue());
		    olist.pop_back();
		    olist.pop_back();
		}
		else if ((value == "endobj") && (state == st_top))
		{
		    // We just saw endobj without having read
		    // anything.  Treat this as a null and do not move
		    // the input source's offset.
		    object = newNull();
		    input->seek(input->getLastOffset(), SEEK_SET);
                    empty = true;
		}
		else
		{
                    QTC::TC("qpdf", "QPDFObjectHandle treat word as string");
                    warn(context,
                         QPDFExc(qpdf_e_damaged_pdf, input->getName(),
                                 object_description,
                                 input->getLastOffset(),
                                 "unknown token while reading object;"
                                 " treating as string"));
                    object = newString(value);
		}
	    }
	    break;

	  case QPDFTokenizer::tt_string:
	    {
		std::string val = token.getValue();
                if (decrypter)
                {
                    decrypter->decryptString(val);
                }
		object = QPDFObjectHandle::newString(val);
	    }

	    break;

	  default:
            warn(context,
                 QPDFExc(qpdf_e_damaged_pdf, input->getName(),
                         object_description,
                         input->getLastOffset(),
                         "treating unknown token type as null while "
                         "reading object"));
            object = newNull();
	    break;
	}

        if ((! object.isInitialized()) &&
            (! ((state == st_start) ||
                (state == st_stop) ||
                (state == st_eof))))
        {
            throw std::logic_error(
                "QPDFObjectHandle::parseInternal: "
                "unexpected uninitialized object");
            object = newNull();
        }

        switch (state)
        {
          case st_eof:
            if (state_stack.size() > 1)
            {
                warn(context,
                     QPDFExc(qpdf_e_damaged_pdf, input->getName(),
                             object_description,
                             input->getLastOffset(),
                             "parse error while reading object"));
            }
            done = true;
            // Leave object uninitialized to indicate EOF
            break;

          case st_dictionary:
          case st_array:
            olist.push_back(object);
            break;

          case st_top:
            done = true;
            break;

          case st_start:
            break;

          case st_stop:
            if ((state_stack.size() < 2) || (olist_stack.size() < 2))
            {
                throw std::logic_error(
                    "QPDFObjectHandle::parseInternal: st_stop encountered"
                    " with insufficient elements in stack");
            }
            state_e old_state = state_stack.back();
            state_stack.pop_back();
            if (old_state == st_array)
            {
                object = newArray(olist);
            }
            else if (old_state == st_dictionary)
            {
                // Convert list to map. Alternating elements are keys.
                // Attempt to recover more or less gracefully from
                // invalid dictionaries.
                std::set<std::string> names;
                for (std::vector<QPDFObjectHandle>::iterator iter =
                         olist.begin();
                     iter != olist.end(); ++iter)
                {
                    if ((! (*iter).isIndirect()) && (*iter).isName())
                    {
                        names.insert((*iter).getName());
                    }
                }

                std::map<std::string, QPDFObjectHandle> dict;
                int next_fake_key = 1;
                for (unsigned int i = 0; i < olist.size(); ++i)
                {
                    QPDFObjectHandle key_obj = olist.at(i);
                    QPDFObjectHandle val;
                    if (key_obj.isIndirect() || (! key_obj.isName()))
                    {
                        bool found_fake = false;
                        std::string candidate;
                        while (! found_fake)
                        {
                            candidate =
                                "/QPDFFake" +
                                QUtil::int_to_string(next_fake_key++);
                            found_fake = (names.count(candidate) == 0);
                            QTC::TC("qpdf", "QPDFObjectHandle found fake",
                                    (found_fake ? 0 : 1));
                        }
                        warn(context,
                             QPDFExc(
                                 qpdf_e_damaged_pdf,
                                 input->getName(), object_description, offset,
                                 "expected dictionary key but found"
                                 " non-name object; inserting key " +
                                 candidate));
                        val = key_obj;
                        key_obj = newName(candidate);
                    }
                    else if (i + 1 >= olist.size())
                    {
                        QTC::TC("qpdf", "QPDFObjectHandle no val for last key");
                        warn(context,
                             QPDFExc(
                                 qpdf_e_damaged_pdf,
                                 input->getName(), object_description, offset,
                                 "dictionary ended prematurely; "
                                 "using null as value for last key"));
                        val = newNull();
                    }
                    else
                    {
                        val = olist.at(++i);
                    }
                    dict[key_obj.getName()] = val;
                }
                object = newDictionary(dict);
            }
            olist_stack.pop_back();
            offset_stack.pop_back();
            if (state_stack.back() == st_top)
            {
                done = true;
            }
            else
            {
                olist_stack.back().push_back(object);
            }
        }
    }

    return object;
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::shallowCopy()
{
    assertInitialized();

    if (isStream())
    {
	QTC::TC("qpdf", "QPDFObjectHandle ERR shallow copy stream");
	throw std::runtime_error(
	    "attempt to make a shallow copy of a stream");
    }

    QPDFObjectHandle new_obj;
    if (isArray())
    {
	QTC::TC("qpdf", "QPDFObjectHandle shallow copy array");
	new_obj = newArray(getArrayAsVector());
    }
    else if (isDictionary())
    {
	QTC::TC("qpdf", "QPDFObjectHandle shallow copy dictionary");
        new_obj = newDictionary(getDictAsMap());
    }
    else
    {
	QTC::TC("qpdf", "QPDFObjectHandle shallow copy scalar");
        new_obj = *this;
    }

    return new_obj;
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::getArrayNItems()
{
    assertArray();
    return dynamic_cast<QPDF_Array*>(obj.getPointer())->getNItems();
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::assertOperator()
{
    assertType("Operator", isOperator());
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::unparseResolved()
{
    if (this->reserved)
    {
        throw std::logic_error(
            "QPDFObjectHandle: attempting to unparse a reserved object");
    }
    dereference();
    return this->obj->unparse();
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::QPDFObjectHandle(QPDFObject* data) :
    initialized(true),
    qpdf(0),
    objid(0),
    generation(0),
    obj(data),
    reserved(false)
{
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::getIntValue()
{
    assertInteger();
    return dynamic_cast<QPDF_Integer*>(obj.getPointer())->getVal();
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::getKey(std::string const& key)
{
    assertDictionary();
    return dynamic_cast<QPDF_Dictionary*>(obj.getPointer())->getKey(key);
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::getDict()
{
    assertStream();
    return dynamic_cast<QPDF_Stream*>(obj.getPointer())->getDict();
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::parseContentStream_internal(PointerHolder<Buffer> stream_data,
                                              std::string const& description,
                                              ParserCallbacks* callbacks)
{
    size_t length = stream_data->getSize();
    PointerHolder<InputSource> input =
        new BufferInputSource(description, stream_data.getPointer());
    QPDFTokenizer tokenizer;
    tokenizer.allowEOF();
    bool empty = false;
    while (static_cast<size_t>(input->tell()) < length)
    {
        QPDFObjectHandle obj =
            parseInternal(input, "content", tokenizer, empty, 0, 0, true);
        if (! obj.isInitialized())
        {
            // EOF
            break;
        }

        callbacks->handleObject(obj);
        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);
            char buf[4];
            memset(buf, '\0', sizeof(buf));
            bool done = false;
            std::string inline_image;
            while (! done)
            {
                if (input->read(&ch, 1) == 0)
                {
                    QTC::TC("qpdf", "QPDFObjectHandle EOF in inline image");
                    throw QPDFExc(qpdf_e_damaged_pdf, input->getName(),
                                  "stream data", input->tell(),
                                  "EOF found while reading inline image");
                }
                inline_image += ch;
                memmove(buf, buf + 1, sizeof(buf) - 1);
                buf[sizeof(buf) - 1] = ch;
                if (strchr(" \t\n\v\f\r", buf[0]) &&
                    (buf[1] == 'E') &&
                    (buf[2] == 'I') &&
                    strchr(" \t\n\v\f\r", buf[3]))
                {
                    // We've found an EI operator.
                    done = true;
                    input->seek(-3, SEEK_CUR);
                    for (int i = 0; i < 4; ++i)
                    {
                        if (inline_image.length() > 0)
                        {
                            inline_image.erase(inline_image.length() - 1);
                        }
                    }
                }
            }
            QTC::TC("qpdf", "QPDFObjectHandle inline image token");
            callbacks->handleObject(
                QPDFObjectHandle::newInlineImage(inline_image));
        }
    }
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::getObjGen() const
{
    return QPDFObjGen(this->objid, this->generation);
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::isPagesObject()
{
    return (this->isDictionary() && this->hasKey("/Type") &&
            (this->getKey("/Type").getName() == "/Pages"));
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::assertInitialized() const
{
    if (! this->initialized)
    {
	throw std::logic_error("operation attempted on uninitialized "
			       "QPDFObjectHandle");
    }
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::assertNumber()
{
    assertType("Number", isNumber());
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::releaseResolved()
{
    // Recursively break any resolved references to indirect objects.
    // Do not cross over indirect object boundaries to avoid an
    // infinite loop.  This method may only be called during final
    // destruction.  See comments in QPDF::~QPDF().
    if (isIndirect())
    {
	if (this->obj.getPointer())
	{
	    this->obj = 0;
	}
    }
    else
    {
	QPDFObject::ObjAccessor::releaseResolved(this->obj.getPointer());
    }
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::newStream(QPDF* qpdf)
{
    QTC::TC("qpdf", "QPDFObjectHandle newStream");
    QPDFObjectHandle stream_dict = newDictionary();
    QPDFObjectHandle result = qpdf->makeIndirectObject(
	QPDFObjectHandle(
	    new QPDF_Stream(qpdf, 0, 0, stream_dict, 0, 0)));
    result.dereference();
    QPDF_Stream* stream = dynamic_cast<QPDF_Stream*>(result.obj.getPointer());
    stream->setObjGen(result.getObjectID(), result.getGeneration());
    return result;
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::removeKey(std::string const& key)
{
    assertDictionary();
    return dynamic_cast<QPDF_Dictionary*>(obj.getPointer())->removeKey(key);
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::getNumericValue()
{
    double result = 0.0;
    if (isInteger())
    {
	result = static_cast<double>(getIntValue());
    }
    else if (isReal())
    {
	result = atof(getRealValue().c_str());
    }
    else
    {
	throw std::logic_error("getNumericValue called for non-numeric object");
    }
    return result;
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::appendItem(QPDFObjectHandle const& item)
{
    assertArray();
    return dynamic_cast<QPDF_Array*>(obj.getPointer())->appendItem(item);
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::getPageImages()
{
    assertPageObject();

    // Note: this code doesn't handle inherited resources.  If this
    // page dictionary doesn't have a /Resources key or has one whose
    // value is null or an empty dictionary, you are supposed to walk
    // up the page tree until you find a /Resources dictionary.  As of
    // this writing, I don't have any test files that use inherited
    // resources, and hand-generating one won't be a good test because
    // any mistakes in my understanding would be present in both the
    // code and the test file.

    // NOTE: If support of inherited resources (see above comment) is
    // implemented, edit comment in QPDFObjectHandle.hh for this
    // function.  Also remove call to pushInheritedAttributesToPage
    // from qpdf.cc when show_page_images is true.

    std::map<std::string, QPDFObjectHandle> result;
    if (this->hasKey("/Resources"))
    {
	QPDFObjectHandle resources = this->getKey("/Resources");
	if (resources.hasKey("/XObject"))
	{
	    QPDFObjectHandle xobject = resources.getKey("/XObject");
	    std::set<std::string> keys = xobject.getKeys();
	    for (std::set<std::string>::iterator iter = keys.begin();
		 iter != keys.end(); ++iter)
	    {
		std::string key = (*iter);
		QPDFObjectHandle value = xobject.getKey(key);
		if (value.isStream())
		{
		    QPDFObjectHandle dict = value.getDict();
		    if (dict.hasKey("/Subtype") &&
			(dict.getKey("/Subtype").getName() == "/Image") &&
			(! dict.hasKey("/ImageMask")))
		    {
			result[key] = value;
		    }
		}
	    }
	}
    }

    return result;
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::assertType(char const* type_name, bool istype) const
{
    if (! istype)
    {
	throw std::logic_error(std::string("operation for ") + type_name +
			       " object attempted on object of wrong type");
    }
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::isInteger()
{
    dereference();
    return QPDFObjectTypeAccessor<QPDF_Integer>::check(obj.getPointer());
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::getName()
{
    assertName();
    return dynamic_cast<QPDF_Name*>(obj.getPointer())->getName();
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::assertDictionary()
{
    assertType("Dictionary", isDictionary());
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::assertReserved()
{
    assertType("Reserved", isReserved());
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::isPageObject()
{
    return (this->isDictionary() && this->hasKey("/Type") &&
            (this->getKey("/Type").getName() == "/Page"));
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::isDictionary()
{
    dereference();
    return QPDFObjectTypeAccessor<QPDF_Dictionary>::check(obj.getPointer());
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::replaceStreamData(std::string const& data,
				    QPDFObjectHandle const& filter,
				    QPDFObjectHandle const& decode_parms)
{
    assertStream();
    PointerHolder<Buffer> b = new Buffer(data.length());
    unsigned char* bp = b->getBuffer();
    memcpy(bp, data.c_str(), data.length());
    dynamic_cast<QPDF_Stream*>(obj.getPointer())->replaceStreamData(
	b, filter, decode_parms);
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::getInlineImageValue()
{
    assertInlineImage();
    return dynamic_cast<QPDF_InlineImage*>(obj.getPointer())->getVal();
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::pipeStreamData(Pipeline* p,
                                 unsigned long encode_flags,
                                 qpdf_stream_decode_level_e decode_level,
                                 bool suppress_warnings)
{
    assertStream();
    return dynamic_cast<QPDF_Stream*>(obj.getPointer())->pipeStreamData(
	p, encode_flags, decode_level, suppress_warnings);
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::isReal()
{
    dereference();
    return QPDFObjectTypeAccessor<QPDF_Real>::check(obj.getPointer());
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::assertString()
{
    assertType("String", isString());
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::replaceStreamData(PointerHolder<StreamDataProvider> provider,
				    QPDFObjectHandle const& filter,
				    QPDFObjectHandle const& decode_parms)
{
    assertStream();
    dynamic_cast<QPDF_Stream*>(obj.getPointer())->replaceStreamData(
	provider, filter, decode_parms);
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::getOwningQPDF()
{
    // Will be null for direct objects
    return this->qpdf;
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::addPageContents(QPDFObjectHandle new_contents, bool first)
{
    assertPageObject();
    new_contents.assertStream();

    std::vector<QPDFObjectHandle> orig_contents = getPageContents();

    std::vector<QPDFObjectHandle> content_streams;
    if (first)
    {
	QTC::TC("qpdf", "QPDFObjectHandle prepend page contents");
	content_streams.push_back(new_contents);
    }
    for (std::vector<QPDFObjectHandle>::iterator iter = orig_contents.begin();
	 iter != orig_contents.end(); ++iter)
    {
	QTC::TC("qpdf", "QPDFObjectHandle append page contents");
	content_streams.push_back(*iter);
    }
    if (! first)
    {
	content_streams.push_back(new_contents);
    }

    QPDFObjectHandle contents = QPDFObjectHandle::newArray(content_streams);
    this->replaceKey("/Contents", contents);
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::rotatePage(int angle, bool relative)
{
    assertPageObject();
    if ((angle % 90) != 0)
    {
        throw std::runtime_error(
            "QPDF::rotatePage called with an"
            " angle that is not a multiple of 90");
    }
    int new_angle = angle;
    if (relative)
    {
        int old_angle = 0;
        bool found_rotate = false;
        QPDFObjectHandle cur_obj = *this;
        bool searched_parent = false;
        std::set<QPDFObjGen> visited;
        while (! found_rotate)
        {
            if (visited.count(cur_obj.getObjGen()))
            {
                // Don't get stuck in an infinite loop
                break;
            }
            if (! visited.empty())
            {
                searched_parent = true;
            }
            visited.insert(cur_obj.getObjGen());
            if (cur_obj.getKey("/Rotate").isInteger())
            {
                found_rotate = true;
                old_angle = cur_obj.getKey("/Rotate").getIntValue();
            }
            else if (cur_obj.getKey("/Parent").isDictionary())
            {
                cur_obj = cur_obj.getKey("/Parent");
            }
            else
            {
                break;
            }
        }
        QTC::TC("qpdf", "QPDFObjectHandle found old angle",
                searched_parent ? 0 : 1);
        if ((old_angle % 90) != 0)
        {
            old_angle = 0;
        }
        new_angle += old_angle;
    }
    new_angle = (new_angle + 360) % 360;
    replaceKey("/Rotate", QPDFObjectHandle::newInteger(new_angle));
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::isIndirect()
{
    assertInitialized();
    return (this->objid != 0);
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::setArrayFromVector(std::vector<QPDFObjectHandle> const& items)
{
    assertArray();
    return dynamic_cast<QPDF_Array*>(obj.getPointer())->setFromVector(items);
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::getBoolValue()
{
    assertBool();
    return dynamic_cast<QPDF_Bool*>(obj.getPointer())->getVal();
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::isString()
{
    dereference();
    return QPDFObjectTypeAccessor<QPDF_String>::check(obj.getPointer());
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::newReserved(QPDF* qpdf)
{
    // Reserve a spot for this object by assigning it an object
    // number, but then return an unresolved handle to the object.
    QPDFObjectHandle reserved = qpdf->makeIndirectObject(
	QPDFObjectHandle(new QPDF_Reserved()));
    QPDFObjectHandle result =
        newIndirect(qpdf, reserved.objid, reserved.generation);
    result.reserved = true;
    return result;
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::makeDirect()
{
    std::set<int> visited;
    makeDirectInternal(visited);
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::isBool()
{
    dereference();
    return QPDFObjectTypeAccessor<QPDF_Bool>::check(obj.getPointer());
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::getArrayItem(int n)
{
    assertArray();
    return dynamic_cast<QPDF_Array*>(obj.getPointer())->getItem(n);
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::isInitialized() const
{
    return this->initialized;
}

Project: qpdf
File: QPDFObjectHandle.cc

QPDFObjectHandle::isNull()
{
    dereference();
    return QPDFObjectTypeAccessor<QPDF_Null>::check(obj.getPointer());
}