//========================================================================
//
// Parser.cc
//
// Copyright 1996-2003 Glyph & Cog, LLC
//
//========================================================================

//========================================================================
//
// Modified under the Poppler project - http://poppler.freedesktop.org
//
// All changes made under the Poppler project to this file are licensed
// under GPL version 2 or later
//
// Copyright (C) 2006, 2009, 201, 2010, 2013, 2014, 2017-2020 Albert Astals Cid <aacid@kde.org>
// Copyright (C) 2006 Krzysztof Kowalczyk <kkowalczyk@gmail.com>
// Copyright (C) 2009 Ilya Gorenbein <igorenbein@finjan.com>
// Copyright (C) 2012 Hib Eris <hib@hiberis.nl>
// Copyright (C) 2013 Adrian Johnson <ajohnson@redneon.com>
// Copyright (C) 2013 Thomas Freitag <Thomas.Freitag@alfa.de>
// Copyright (C) 2018 Klarälvdalens Datakonsult AB, a KDAB Group company, <info@kdab.com>. Work sponsored by the LiMux project of the city of Munich
// Copyright (C) 2018, 2019 Adam Reichold <adam.reichold@t-online.de>
// Copyright (C) 2018 Marek Kasik <mkasik@redhat.com>
//
// To see a description of the changes please see the Changelog file that
// came with your tarball or type make ChangeLog if you are building from git
//
//========================================================================

#include <config.h>

#include <cstddef>
#include "Object.h"
#include "Array.h"
#include "Dict.h"
#include "Decrypt.h"
#include "Parser.h"
#include "XRef.h"
#include "Error.h"

// Max number of nested objects.  This is used to catch infinite loops
// in the object structure. And also technically valid files with
// lots of nested arrays that made us consume all the stack
#define recursionLimit 500

Parser::Parser(XRef *xrefA, Stream *streamA, bool allowStreamsA) : lexer { xrefA, streamA }
{
    allowStreams = allowStreamsA;
    buf1 = lexer.getObj();
    buf2 = lexer.getObj();
    inlineImg = 0;
}

Parser::Parser(XRef *xrefA, Object *objectA, bool allowStreamsA) : lexer { xrefA, objectA }
{
    allowStreams = allowStreamsA;
    buf1 = lexer.getObj();
    buf2 = lexer.getObj();
    inlineImg = 0;
}

Parser::~Parser() = default;

Object Parser::getObj(int recursion)
{
    return getObj(false, nullptr, cryptRC4, 0, 0, 0, recursion);
}

Object Parser::getObj(bool simpleOnly, const unsigned char *fileKey, CryptAlgorithm encAlgorithm, int keyLength, int objNum, int objGen, int recursion, bool strict)
{
    Object obj;
    Stream *str;
    DecryptStream *decrypt;
    const GooString *s;
    GooString *s2;
    int c;

    // refill buffer after inline image data
    if (inlineImg == 2) {
        buf1 = lexer.getObj();
        buf2 = lexer.getObj();
        inlineImg = 0;
    }

    if (unlikely(recursion >= recursionLimit)) {
        return Object(objError);
    }

    // array
    if (!simpleOnly && buf1.isCmd("[")) {
        shift();
        obj = Object(new Array(lexer.getXRef()));
        while (!buf1.isCmd("]") && !buf1.isEOF() && recursion + 1 < recursionLimit) {
            Object obj2 = getObj(false, fileKey, encAlgorithm, keyLength, objNum, objGen, recursion + 1);
            obj.arrayAdd(std::move(obj2));
        }
        if (recursion + 1 >= recursionLimit && strict)
            goto err;
        if (buf1.isEOF()) {
            error(errSyntaxError, getPos(), "End of file inside array");
            if (strict)
                goto err;
        }
        shift();

        // dictionary or stream
    } else if (!simpleOnly && buf1.isCmd("<<")) {
        shift(objNum);
        obj = Object(new Dict(lexer.getXRef()));
        while (!buf1.isCmd(">>") && !buf1.isEOF()) {
            if (!buf1.isName()) {
                error(errSyntaxError, getPos(), "Dictionary key must be a name object");
                if (strict)
                    goto err;
                shift();
            } else {
                // buf1 will go away in shift(), so keep the key
                const auto key = std::move(buf1);
                shift();
                if (buf1.isEOF() || buf1.isError()) {
                    if (strict && buf1.isError())
                        goto err;
                    break;
                }
                Object obj2 = getObj(false, fileKey, encAlgorithm, keyLength, objNum, objGen, recursion + 1);
                if (unlikely(obj2.isError() && recursion + 1 >= recursionLimit)) {
                    break;
                }
                obj.dictAdd(key.getName(), std::move(obj2));
            }
        }
        if (buf1.isEOF()) {
            error(errSyntaxError, getPos(), "End of file inside dictionary");
            if (strict)
                goto err;
        }
        // stream objects are not allowed inside content streams or
        // object streams
        if (buf2.isCmd("stream")) {
            if (allowStreams && (str = makeStream(std::move(obj), fileKey, encAlgorithm, keyLength, objNum, objGen, recursion + 1, strict))) {
                return Object(str);
            } else {
                return Object(objError);
            }
        } else {
            shift();
        }

        // indirect reference or integer
    } else if (buf1.isInt()) {
        const int num = buf1.getInt();
        shift();
        if (buf1.isInt() && buf2.isCmd("R")) {
            const int gen = buf1.getInt();
            shift();
            shift();

            if (unlikely(num <= 0 || gen < 0)) {
                return Object();
            }

            Ref r;
            r.num = num;
            r.gen = gen;
            return Object(r);
        } else {
            return Object(num);
        }

        // string
    } else if (buf1.isString() && fileKey) {
        s = buf1.getString();
        s2 = new GooString();
        decrypt = new DecryptStream(new MemStream(s->c_str(), 0, s->getLength(), Object(objNull)), fileKey, encAlgorithm, keyLength, { objNum, objGen });
        decrypt->reset();
        while ((c = decrypt->getChar()) != EOF) {
            s2->append((char)c);
        }
        delete decrypt;
        obj = Object(s2);
        shift();

        // simple object
    } else {
        // avoid re-allocating memory for complex objects like strings by
        // shallow copy of <buf1> to <obj> and nulling <buf1> so that
        // subsequent buf1.free() won't free this memory
        obj = std::move(buf1);
        shift();
    }

    return obj;

err:
    return Object(objError);
}

Stream *Parser::makeStream(Object &&dict, const unsigned char *fileKey, CryptAlgorithm encAlgorithm, int keyLength, int objNum, int objGen, int recursion, bool strict)
{
    BaseStream *baseStr;
    Stream *str;
    Goffset length;
    Goffset pos, endPos;

    if (XRef *xref = lexer.getXRef()) {
        XRefEntry *entry = xref->getEntry(objNum, false);
        if (entry) {
            if (!entry->getFlag(XRefEntry::Parsing) || (objNum == 0 && objGen == 0)) {
                entry->setFlag(XRefEntry::Parsing, true);
            } else {
                error(errSyntaxError, getPos(), "Object '{0:d} {1:d} obj' is being already parsed", objNum, objGen);
                return nullptr;
            }
        }
    }

    // get stream start position
    lexer.skipToNextLine();
    if (!(str = lexer.getStream())) {
        return nullptr;
    }
    pos = str->getPos();

    // get length
    Object obj = dict.dictLookup("Length", recursion);
    if (obj.isInt()) {
        length = obj.getInt();
    } else if (obj.isInt64()) {
        length = obj.getInt64();
    } else {
        error(errSyntaxError, getPos(), "Bad 'Length' attribute in stream");
        if (strict)
            return nullptr;
        length = 0;
    }

    // check for length in damaged file
    if (lexer.hasXRef() && lexer.getXRef()->getStreamEnd(pos, &endPos)) {
        length = endPos - pos;
    }

    // in badly damaged PDF files, we can run off the end of the input
    // stream immediately after the "stream" token
    if (!lexer.getStream()) {
        return nullptr;
    }
    baseStr = lexer.getStream()->getBaseStream();

    // skip over stream data
    if (Lexer::LOOK_VALUE_NOT_CACHED != lexer.lookCharLastValueCached) {
        // take into account the fact that we've cached one value
        pos = pos - 1;
        lexer.lookCharLastValueCached = Lexer::LOOK_VALUE_NOT_CACHED;
    }
    if (unlikely(length < 0)) {
        return nullptr;
    }
    if (unlikely(pos > LLONG_MAX - length)) {
        return nullptr;
    }
    lexer.setPos(pos + length);

    // refill token buffers and check for 'endstream'
    shift(); // kill '>>'
    shift("endstream", objNum); // kill 'stream'
    if (buf1.isCmd("endstream")) {
        shift();
    } else {
        error(errSyntaxError, getPos(), "Missing 'endstream' or incorrect stream length");
        if (strict)
            return nullptr;
        if (lexer.hasXRef() && lexer.getStream()) {
            // shift until we find the proper endstream or we change to another object or reach eof
            length = lexer.getPos() - pos;
            if (buf1.isCmd("endstream")) {
                dict.dictSet("Length", Object(length));
            }
        } else {
            // When building the xref we can't use it so use this
            // kludge for broken PDF files: just add 5k to the length, and
            // hope its enough
            if (length < LLONG_MAX - pos - 5000)
                length += 5000;
        }
    }

    // make base stream
    str = baseStr->makeSubStream(pos, true, length, std::move(dict));

    // handle decryption
    if (fileKey) {
        str = new DecryptStream(str, fileKey, encAlgorithm, keyLength, { objNum, objGen });
    }

    // get filters
    str = str->addFilters(str->getDict(), recursion);

    if (XRef *xref = lexer.getXRef()) {
        // Don't try to reuse the entry from the block at the start
        // of the function, xref can change in the middle because of
        // reconstruction
        XRefEntry *entry = xref->getEntry(objNum, false);
        if (entry) {
            entry->setFlag(XRefEntry::Parsing, false);
        }
    }

    return str;
}

void Parser::shift(int objNum)
{
    if (inlineImg > 0) {
        if (inlineImg < 2) {
            ++inlineImg;
        } else {
            // in a damaged content stream, if 'ID' shows up in the middle
            // of a dictionary, we need to reset
            inlineImg = 0;
        }
    } else if (buf2.isCmd("ID")) {
        lexer.skipChar(); // skip char after 'ID' command
        inlineImg = 1;
    }
    buf1 = std::move(buf2);
    if (inlineImg > 0) // don't buffer inline image data
        buf2.setToNull();
    else {
        buf2 = lexer.getObj(objNum);
    }
}

void Parser::shift(const char *cmdA, int objNum)
{
    if (inlineImg > 0) {
        if (inlineImg < 2) {
            ++inlineImg;
        } else {
            // in a damaged content stream, if 'ID' shows up in the middle
            // of a dictionary, we need to reset
            inlineImg = 0;
        }
    } else if (buf2.isCmd("ID")) {
        lexer.skipChar(); // skip char after 'ID' command
        inlineImg = 1;
    }
    buf1 = std::move(buf2);
    if (inlineImg > 0) {
        buf2.setToNull();
    } else if (buf1.isCmd(cmdA)) {
        buf2 = lexer.getObj(objNum);
    } else {
        buf2 = lexer.getObj(cmdA, objNum);
    }
}
