[Libreoffice-commits] core.git: sdext/source sfx2/source xmlsecurity/Executable_pdfverify.mk xmlsecurity/inc xmlsecurity/Library_xmlsecurity.mk xmlsecurity/source

Miklos Vajna vmiklos at collabora.co.uk
Thu Oct 13 10:47:34 UTC 2016


 sdext/source/pdfimport/config/pdf_import_filter.xcu        |    2 
 sfx2/source/doc/objserv.cxx                                |   22 
 xmlsecurity/Executable_pdfverify.mk                        |   15 
 xmlsecurity/Library_xmlsecurity.mk                         |   15 
 xmlsecurity/inc/pdfio/pdfdocument.hxx                      |   70 
 xmlsecurity/inc/pdfsignaturehelper.hxx                     |   33 
 xmlsecurity/source/component/documentdigitalsignatures.cxx |    9 
 xmlsecurity/source/helper/pdfsignaturehelper.cxx           |   65 
 xmlsecurity/source/pdfio/pdfdocument.cxx                   | 1461 ++++++++++++
 xmlsecurity/source/pdfio/pdfverify.cxx                     | 1486 -------------
 10 files changed, 1678 insertions(+), 1500 deletions(-)

New commits:
commit 0f613adbfa44fb92e84e73a3fa7ea050c072944c
Author: Miklos Vajna <vmiklos at collabora.co.uk>
Date:   Thu Oct 13 10:37:02 2016 +0200

    xmlsecurity: add initial PDFSignatureHelper
    
    This splits most of the PDF signature code out of the pdfverify
    executable, and puts it into the xmlsecurity library instead.
    
    The PDFSignatureHelper now attempts to verify PDF signatures, and code
    in sdext / sfx2 also calls it (even if PDF is not a ZIP-based format).
    
    Change-Id: I7b8b3ac9c976e4ea4f3796b1cda07c8a2c97bd02
    Reviewed-on: https://gerrit.libreoffice.org/29751
    Reviewed-by: Miklos Vajna <vmiklos at collabora.co.uk>
    Tested-by: Jenkins <ci at libreoffice.org>

diff --git a/sdext/source/pdfimport/config/pdf_import_filter.xcu b/sdext/source/pdfimport/config/pdf_import_filter.xcu
index d454d6f1..3909f9f 100644
--- a/sdext/source/pdfimport/config/pdf_import_filter.xcu
+++ b/sdext/source/pdfimport/config/pdf_import_filter.xcu
@@ -31,7 +31,7 @@
     <value>com.sun.star.comp.Writer.XmlFilterAdaptor</value>
    </prop>
    <prop oor:name="Flags" oor:type="oor:string-list">
-    <value>3RDPARTYFILTER ALIEN IMPORT PREFERRED</value>
+    <value>3RDPARTYFILTER ALIEN IMPORT PREFERRED SUPPORTSSIGNING</value>
    </prop>
    <prop oor:name="Type" oor:type="xs:string">
     <value>pdf_Portable_Document_Format</value>
diff --git a/sfx2/source/doc/objserv.cxx b/sfx2/source/doc/objserv.cxx
index 2ba4ffd..f6243cc 100644
--- a/sfx2/source/doc/objserv.cxx
+++ b/sfx2/source/doc/objserv.cxx
@@ -102,6 +102,8 @@
 #include <sfx2/saveastemplatedlg.hxx>
 #include <memory>
 #include <cppuhelper/implbase.hxx>
+#include <unotools/ucbstreamhelper.hxx>
+#include <unotools/streamwrap.hxx>
 
 using namespace ::com::sun::star;
 using namespace ::com::sun::star::lang;
@@ -1291,7 +1293,7 @@ uno::Sequence< security::DocumentSignatureInformation > SfxObjectShell::ImplAnal
     uno::Reference< security::XDocumentDigitalSignatures > xLocSigner = xSigner;
 
     bool bSupportsSigning = GetMedium() && GetMedium()->GetFilter() && GetMedium()->GetFilter()->GetSupportsSigning();
-    if (GetMedium() && !GetMedium()->GetName().isEmpty() && (IsOwnStorageFormat(*GetMedium()) || bSupportsSigning) && GetMedium()->GetStorage().is())
+    if (GetMedium() && !GetMedium()->GetName().isEmpty() && ((IsOwnStorageFormat(*GetMedium()) && GetMedium()->GetStorage().is()) || bSupportsSigning))
     {
         try
         {
@@ -1315,8 +1317,22 @@ uno::Sequence< security::DocumentSignatureInformation > SfxObjectShell::ImplAnal
                 aResult = xLocSigner->verifyScriptingContentSignatures( GetMedium()->GetZipStorageToSign_Impl(),
                                                                 uno::Reference< io::XInputStream >() );
             else
-                aResult = xLocSigner->verifyDocumentContentSignatures( GetMedium()->GetZipStorageToSign_Impl(),
-                                                                uno::Reference< io::XInputStream >() );
+            {
+                if (GetMedium()->GetStorage().is())
+                {
+                    // Something ZIP-based.
+                    aResult = xLocSigner->verifyDocumentContentSignatures( GetMedium()->GetZipStorageToSign_Impl(),
+                                                                    uno::Reference< io::XInputStream >() );
+                }
+                else
+                {
+                    // Not ZIP-based, e.g. PDF.
+                    SvStream* pStream = utl::UcbStreamHelper::CreateStream(GetMedium()->GetName(), StreamMode::READ);
+                    uno::Reference<io::XStream> xStream(new utl::OStreamWrapper(*pStream));
+                    uno::Reference<io::XInputStream> xInputStream(xStream, uno::UNO_QUERY);
+                    aResult = xLocSigner->verifyDocumentContentSignatures(uno::Reference<embed::XStorage>(), xInputStream);
+                }
+            }
         }
         catch( css::uno::Exception& )
         {
diff --git a/xmlsecurity/Executable_pdfverify.mk b/xmlsecurity/Executable_pdfverify.mk
index c62698f..bc08d56 100644
--- a/xmlsecurity/Executable_pdfverify.mk
+++ b/xmlsecurity/Executable_pdfverify.mk
@@ -13,29 +13,18 @@ $(eval $(call gb_Executable_use_sdk_api,pdfverify))
 
 $(eval $(call gb_Executable_set_include,pdfverify,\
     $$(INCLUDE) \
+    -I$(SRCDIR)/xmlsecurity/inc \
 ))
 
 $(eval $(call gb_Executable_use_libraries,pdfverify,\
     comphelper \
     sal \
     tl \
+    xmlsecurity \
 ))
 
 $(eval $(call gb_Executable_add_exception_objects,pdfverify,\
     xmlsecurity/source/pdfio/pdfverify \
 ))
 
-ifeq ($(OS)-$(COM),WNT-MSC)
-$(eval $(call gb_Executable_add_defs,pdfverify,\
-    -DXMLSEC_CRYPTO_MSCRYPTO \
-))
-else
-$(eval $(call gb_Executable_add_defs,pdfverify,\
-    -DXMLSEC_CRYPTO_NSS \
-))
-$(eval $(call gb_Executable_use_externals,pdfverify,\
-    nss3 \
-))
-endif
-
 # vim:set noet sw=4 ts=4:
diff --git a/xmlsecurity/Library_xmlsecurity.mk b/xmlsecurity/Library_xmlsecurity.mk
index 1997010..77368ab 100644
--- a/xmlsecurity/Library_xmlsecurity.mk
+++ b/xmlsecurity/Library_xmlsecurity.mk
@@ -57,12 +57,27 @@ $(eval $(call gb_Library_add_exception_objects,xmlsecurity,\
 	xmlsecurity/source/helper/documentsignaturemanager \
 	xmlsecurity/source/helper/ooxmlsecparser \
 	xmlsecurity/source/helper/ooxmlsecexporter \
+	xmlsecurity/source/helper/pdfsignaturehelper \
 	xmlsecurity/source/helper/xmlsignaturehelper2 \
 	xmlsecurity/source/helper/xmlsignaturehelper \
 	xmlsecurity/source/helper/xsecctl \
 	xmlsecurity/source/helper/xsecparser \
 	xmlsecurity/source/helper/xsecsign \
 	xmlsecurity/source/helper/xsecverify \
+	xmlsecurity/source/pdfio/pdfdocument \
 ))
 
+ifeq ($(OS)-$(COM),WNT-MSC)
+$(eval $(call gb_Library_add_defs,xmlsecurity,\
+    -DXMLSEC_CRYPTO_MSCRYPTO \
+))
+else
+$(eval $(call gb_Library_add_defs,xmlsecurity,\
+    -DXMLSEC_CRYPTO_NSS \
+))
+$(eval $(call gb_Library_use_externals,xmlsecurity,\
+    nss3 \
+))
+endif
+
 # vim: set noet sw=4 ts=4:
diff --git a/xmlsecurity/inc/pdfio/pdfdocument.hxx b/xmlsecurity/inc/pdfio/pdfdocument.hxx
new file mode 100644
index 0000000..9d07261
--- /dev/null
+++ b/xmlsecurity/inc/pdfio/pdfdocument.hxx
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#ifndef INCLUDED_XMLSECURITY_INC_PDFIO_PDFDOCUMENT_HXX
+#define INCLUDED_XMLSECURITY_INC_PDFIO_PDFDOCUMENT_HXX
+
+#include <vector>
+
+#include <tools/stream.hxx>
+
+#include <xmlsecuritydllapi.h>
+
+namespace xmlsecurity
+{
+namespace pdfio
+{
+
+class PDFTrailerElement;
+class PDFObjectElement;
+
+/// A byte range in a PDF file.
+class PDFElement
+{
+public:
+    virtual bool Read(SvStream& rStream) = 0;
+    virtual ~PDFElement() { }
+};
+
+/// In-memory representation of an on-disk PDF document.
+class XMLSECURITY_DLLPUBLIC PDFDocument
+{
+    /// This vector owns all elements.
+    std::vector< std::unique_ptr<PDFElement> > m_aElements;
+    // List of object offsets we know.
+    std::vector<size_t> m_aXRef;
+    PDFTrailerElement* m_pTrailer;
+
+    static int AsHex(char ch);
+
+public:
+    PDFDocument();
+    PDFDocument& operator=(const PDFDocument&) = delete;
+    PDFDocument(const PDFDocument&) = delete;
+    static OString ReadKeyword(SvStream& rStream);
+    static size_t FindStartXRef(SvStream& rStream);
+    void ReadXRef(SvStream& rStream);
+    static void SkipWhitespace(SvStream& rStream);
+    size_t GetObjectOffset(size_t nIndex) const;
+    const std::vector< std::unique_ptr<PDFElement> >& GetElements();
+    std::vector<PDFObjectElement*> GetPages();
+
+    bool Read(SvStream& rStream);
+    std::vector<PDFObjectElement*> GetSignatureWidgets();
+    /// Return value is about if we can determine a result, bDigestMatch is about the actual result.
+    static bool ValidateSignature(SvStream& rStream, PDFObjectElement* pSignature, bool& bDigestMatch);
+};
+
+} // namespace pdfio
+} // namespace xmlsecurity
+
+#endif // INCLUDED_XMLSECURITY_INC_PDFIO_PDFDOCUMENT_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/xmlsecurity/inc/pdfsignaturehelper.hxx b/xmlsecurity/inc/pdfsignaturehelper.hxx
new file mode 100644
index 0000000..fb92834
--- /dev/null
+++ b/xmlsecurity/inc/pdfsignaturehelper.hxx
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ */
+
+#ifndef INCLUDED_XMLSECURITY_INC_PDFSIGNATUREHELPER_HXX
+#define INCLUDED_XMLSECURITY_INC_PDFSIGNATUREHELPER_HXX
+
+#include <xmlsecuritydllapi.h>
+
+#include <vector>
+
+#include <com/sun/star/io/XInputStream.hpp>
+#include <com/sun/star/security/DocumentSignatureInformation.hpp>
+
+/// Handles signatures of a PDF file.
+class XMLSECURITY_DLLPUBLIC PDFSignatureHelper
+{
+    std::vector<css::security::DocumentSignatureInformation> m_aSignatureInfos;
+
+public:
+    bool ReadAndVerifySignature(const css::uno::Reference<css::io::XInputStream>& xInputStream);
+    css::uno::Sequence<css::security::DocumentSignatureInformation> GetDocumentSignatureInformations();
+};
+
+#endif // INCLUDED_XMLSECURITY_INC_PDFSIGNATUREHELPER_HXX
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/xmlsecurity/source/component/documentdigitalsignatures.cxx b/xmlsecurity/source/component/documentdigitalsignatures.cxx
index cfa3d24..2fa08a4 100644
--- a/xmlsecurity/source/component/documentdigitalsignatures.cxx
+++ b/xmlsecurity/source/component/documentdigitalsignatures.cxx
@@ -24,6 +24,7 @@
 #include <macrosecurity.hxx>
 #include <biginteger.hxx>
 #include <global.hrc>
+#include <pdfsignaturehelper.hxx>
 
 #include <sax/tools/converter.hxx>
 
@@ -259,6 +260,14 @@ DocumentDigitalSignatures::ImplVerifySignatures(
 {
     if (!rxStorage.is())
     {
+        if (xSignStream.is())
+        {
+            // Something not ZIP-based, try PDF.
+            PDFSignatureHelper aSignatureHelper;
+            if (aSignatureHelper.ReadAndVerifySignature(xSignStream))
+                return aSignatureHelper.GetDocumentSignatureInformations();
+        }
+
         SAL_WARN( "xmlsecurity.comp", "Error, no XStorage provided");
         return Sequence<css::security::DocumentSignatureInformation>();
     }
diff --git a/xmlsecurity/source/helper/pdfsignaturehelper.cxx b/xmlsecurity/source/helper/pdfsignaturehelper.cxx
new file mode 100644
index 0000000..d8e6cd5
--- /dev/null
+++ b/xmlsecurity/source/helper/pdfsignaturehelper.cxx
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <pdfsignaturehelper.hxx>
+
+#include <memory>
+
+#include <comphelper/sequence.hxx>
+#include <tools/stream.hxx>
+#include <unotools/ucbstreamhelper.hxx>
+
+#include <pdfio/pdfdocument.hxx>
+
+using namespace ::com::sun::star;
+
+bool PDFSignatureHelper::ReadAndVerifySignature(const uno::Reference<io::XInputStream>& xInputStream)
+{
+    if (!xInputStream.is())
+    {
+        SAL_WARN("xmlsecurity.helper", "input stream missing");
+        return false;
+    }
+
+    std::unique_ptr<SvStream> pStream(utl::UcbStreamHelper::CreateStream(xInputStream, true));
+    xmlsecurity::pdfio::PDFDocument aDocument;
+    if (!aDocument.Read(*pStream))
+    {
+        SAL_WARN("xmlsecurity.helper", "failed to read the document");
+        return false;
+    }
+
+    std::vector<xmlsecurity::pdfio::PDFObjectElement*> aSignatures = aDocument.GetSignatureWidgets();
+    if (aSignatures.empty())
+        return true;
+
+    for (size_t i = 0; i < aSignatures.size(); ++i)
+    {
+        security::DocumentSignatureInformation aInfo;
+
+        bool bDigestMatch;
+        if (!xmlsecurity::pdfio::PDFDocument::ValidateSignature(*pStream, aSignatures[i], bDigestMatch))
+        {
+            SAL_WARN("xmlsecurity.helper", "failed to determine digest match");
+            continue;
+        }
+
+        aInfo.SignatureIsValid = bDigestMatch;
+        m_aSignatureInfos.push_back(aInfo);
+    }
+
+    return true;
+}
+
+uno::Sequence<security::DocumentSignatureInformation> PDFSignatureHelper::GetDocumentSignatureInformations()
+{
+    return comphelper::containerToSequence(m_aSignatureInfos);
+}
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/xmlsecurity/source/pdfio/pdfdocument.cxx b/xmlsecurity/source/pdfio/pdfdocument.cxx
new file mode 100644
index 0000000..3bd90db
--- /dev/null
+++ b/xmlsecurity/source/pdfio/pdfdocument.cxx
@@ -0,0 +1,1461 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+#include <pdfio/pdfdocument.hxx>
+
+#include <map>
+#include <memory>
+#include <vector>
+
+#include <comphelper/scopeguard.hxx>
+#include <rtl/strbuf.hxx>
+#include <rtl/string.hxx>
+#include <sal/log.hxx>
+#include <sal/types.h>
+
+#ifdef XMLSEC_CRYPTO_NSS
+#include <cert.h>
+#include <cms.h>
+#include <nss.h>
+#include <sechash.h>
+#endif
+
+using namespace com::sun::star;
+
+namespace xmlsecurity
+{
+namespace pdfio
+{
+
+class PDFTrailerElement;
+class PDFObjectElement;
+
+/// A one-liner comment.
+class PDFCommentElement : public PDFElement
+{
+    OString m_aComment;
+
+public:
+    bool Read(SvStream& rStream) override;
+};
+
+/// Numbering object: an integer or a real.
+class PDFNumberElement : public PDFElement
+{
+    double m_fValue;
+
+public:
+    bool Read(SvStream& rStream) override;
+    double GetValue() const;
+};
+
+class PDFReferenceElement;
+
+/// Indirect object: something with a unique ID.
+class PDFObjectElement : public PDFElement
+{
+    PDFDocument& m_rDoc;
+    double m_fObjectValue;
+    double m_fGenerationValue;
+    std::map<OString, PDFElement*> m_aDictionary;
+
+public:
+    PDFObjectElement(PDFDocument& rDoc, double fObjectValue, double fGenerationValue);
+    bool Read(SvStream& rStream) override;
+    PDFElement* Lookup(const OString& rDictionaryKey);
+    PDFObjectElement* LookupObject(const OString& rDictionaryKey);
+    double GetObjectValue() const;
+    double GetGenerationValue() const;
+};
+
+/// Dictionary object: a set key-value pairs.
+class PDFDictionaryElement : public PDFElement
+{
+public:
+    bool Read(SvStream& rStream) override;
+
+    static void Parse(const std::vector< std::unique_ptr<PDFElement> >& rElements, PDFElement* pThis, std::map<OString, PDFElement*>& rDictionary);
+    static PDFElement* Lookup(const std::map<OString, PDFElement*>& rDictionary, const OString& rKey);
+};
+
+/// End of a dictionary: '>>'.
+class PDFEndDictionaryElement : public PDFElement
+{
+public:
+    bool Read(SvStream& rStream) override;
+};
+
+/// Name object: a key string.
+class PDFNameElement : public PDFElement
+{
+    OString m_aValue;
+public:
+    bool Read(SvStream& rStream) override;
+    const OString& GetValue() const;
+};
+
+/// Reference object: something with a unique ID.
+class PDFReferenceElement : public PDFElement
+{
+    PDFDocument& m_rDoc;
+    int m_fObjectValue;
+    int m_fGenerationValue;
+
+public:
+    PDFReferenceElement(PDFDocument& rDoc, int fObjectValue, int fGenerationValue);
+    bool Read(SvStream& rStream) override;
+    /// Assuming the reference points to a number object, return its value.
+    double LookupNumber(SvStream& rStream) const;
+    /// Lookup referenced object, without assuming anything about its contents.
+    PDFObjectElement* LookupObject() const;
+};
+
+/// Stream object: a byte array with a known length.
+class PDFStreamElement : public PDFElement
+{
+    size_t m_nLength;
+
+public:
+    PDFStreamElement(size_t nLength);
+    bool Read(SvStream& rStream) override;
+};
+
+/// End of a stream: 'endstream' keyword.
+class PDFEndStreamElement : public PDFElement
+{
+public:
+    bool Read(SvStream& rStream) override;
+};
+
+/// End of a object: 'endobj' keyword.
+class PDFEndObjectElement : public PDFElement
+{
+public:
+    bool Read(SvStream& rStream) override;
+};
+
+/// Array object: a list.
+class PDFArrayElement : public PDFElement
+{
+    std::vector<PDFElement*> m_aElements;
+public:
+    bool Read(SvStream& rStream) override;
+    void PushBack(PDFElement* pElement);
+    const std::vector<PDFElement*>& GetElements();
+};
+
+/// End of an array: ']'.
+class PDFEndArrayElement : public PDFElement
+{
+public:
+    bool Read(SvStream& rStream) override;
+};
+
+/// Boolean object: a 'true' or a 'false'.
+class PDFBooleanElement : public PDFElement
+{
+public:
+    PDFBooleanElement(bool bValue);
+    bool Read(SvStream& rStream) override;
+};
+
+/// Null object: the 'null' singleton.
+class PDFNullElement : public PDFElement
+{
+public:
+    bool Read(SvStream& rStream) override;
+};
+
+/// Hex string: in <AABB> form.
+class PDFHexStringElement : public PDFElement
+{
+    OString m_aValue;
+public:
+    bool Read(SvStream& rStream) override;
+    const OString& GetValue() const;
+};
+
+/// Literal string: in (asdf) form.
+class PDFLiteralStringElement : public PDFElement
+{
+    OString m_aValue;
+public:
+    bool Read(SvStream& rStream) override;
+};
+
+/// The trailer signleton is at the end of the doc.
+class PDFTrailerElement : public PDFElement
+{
+    PDFDocument& m_rDoc;
+    std::map<OString, PDFElement*> m_aDictionary;
+
+public:
+    PDFTrailerElement(PDFDocument& rDoc);
+    bool Read(SvStream& rStream) override;
+    PDFElement* Lookup(const OString& rDictionaryKey);
+};
+
+PDFDocument::PDFDocument()
+    : m_pTrailer(nullptr)
+{
+}
+
+bool PDFDocument::Read(SvStream& rStream)
+{
+    // First look up the offset of the xref table.
+    size_t nStartXRef = FindStartXRef(rStream);
+    SAL_INFO("xmlsecurity.pdfio", "PDFDocument::Read: nStartXRef is " << nStartXRef);
+    if (nStartXRef == 0)
+    {
+        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Read: found no xref statrt offset");
+        return false;
+    }
+    rStream.Seek(nStartXRef);
+    char ch;
+    ReadXRef(rStream);
+
+    // Then we can tokenize the stream.
+    rStream.Seek(0);
+    bool bInXRef = false;
+    while (true)
+    {
+        rStream.ReadChar(ch);
+        if (rStream.IsEof())
+            break;
+
+        switch (ch)
+        {
+        case '%':
+        {
+            m_aElements.push_back(std::unique_ptr<PDFElement>(new PDFCommentElement()));
+            rStream.SeekRel(-1);
+            if (!m_aElements.back()->Read(rStream))
+                return false;
+            break;
+        }
+        case '<':
+        {
+            // Dictionary or hex string.
+            rStream.ReadChar(ch);
+            rStream.SeekRel(-2);
+            if (ch == '<')
+                m_aElements.push_back(std::unique_ptr<PDFElement>(new PDFDictionaryElement()));
+            else
+                m_aElements.push_back(std::unique_ptr<PDFElement>(new PDFHexStringElement()));
+            if (!m_aElements.back()->Read(rStream))
+                return false;
+            break;
+        }
+        case '>':
+        {
+            m_aElements.push_back(std::unique_ptr<PDFElement>(new PDFEndDictionaryElement()));
+            rStream.SeekRel(-1);
+            if (!m_aElements.back()->Read(rStream))
+                return false;
+            break;
+        }
+        case '[':
+        {
+            m_aElements.push_back(std::unique_ptr<PDFElement>(new PDFArrayElement()));
+            rStream.SeekRel(-1);
+            if (!m_aElements.back()->Read(rStream))
+                return false;
+            break;
+        }
+        case ']':
+        {
+            m_aElements.push_back(std::unique_ptr<PDFElement>(new PDFEndArrayElement()));
+            rStream.SeekRel(-1);
+            if (!m_aElements.back()->Read(rStream))
+                return false;
+            break;
+        }
+        case '/':
+        {
+            m_aElements.push_back(std::unique_ptr<PDFElement>(new PDFNameElement()));
+            rStream.SeekRel(-1);
+            if (!m_aElements.back()->Read(rStream))
+                return false;
+            break;
+        }
+        case '(':
+        {
+            m_aElements.push_back(std::unique_ptr<PDFElement>(new PDFLiteralStringElement()));
+            rStream.SeekRel(-1);
+            if (!m_aElements.back()->Read(rStream))
+                return false;
+            break;
+        }
+        default:
+        {
+            if (isdigit(ch) || ch == '-')
+            {
+                // Numbering object: an integer or a real.
+                m_aElements.push_back(std::unique_ptr<PDFElement>(new PDFNumberElement()));
+                rStream.SeekRel(-1);
+                if (!m_aElements.back()->Read(rStream))
+                    return false;
+            }
+            else if (isalpha(ch))
+            {
+                // Possible keyword, like "obj".
+                rStream.SeekRel(-1);
+                OString aKeyword = ReadKeyword(rStream);
+
+                bool bObj = aKeyword == "obj";
+                if (bObj || aKeyword == "R")
+                {
+                    size_t nElements = m_aElements.size();
+                    if (nElements < 2)
+                    {
+                        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Read: expected at least two tokens before 'obj' or 'R' keyword");
+                        return false;
+                    }
+
+                    auto pObjectNumber = dynamic_cast<PDFNumberElement*>(m_aElements[nElements - 2].get());
+                    auto pGenerationNumber = dynamic_cast<PDFNumberElement*>(m_aElements[nElements - 1].get());
+                    if (!pObjectNumber || !pGenerationNumber)
+                    {
+                        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Read: missing object or generation number before 'obj' or 'R' keyword");
+                        return false;
+                    }
+
+                    if (bObj)
+                        m_aElements.push_back(std::unique_ptr<PDFElement>(new PDFObjectElement(*this, pObjectNumber->GetValue(), pGenerationNumber->GetValue())));
+                    else
+                        m_aElements.push_back(std::unique_ptr<PDFElement>(new PDFReferenceElement(*this, pObjectNumber->GetValue(), pGenerationNumber->GetValue())));
+                    if (!m_aElements.back()->Read(rStream))
+                        return false;
+                }
+                else if (aKeyword == "stream")
+                {
+                    // Look up the length of the stream from the parent object's dictionary.
+                    size_t nLength = 0;
+                    for (size_t nElement = 0; nElement < m_aElements.size(); ++nElement)
+                    {
+                        // Iterate in reverse order.
+                        size_t nIndex = m_aElements.size() - nElement - 1;
+                        PDFElement* pElement = m_aElements[nIndex].get();
+                        auto pObjectElement = dynamic_cast<PDFObjectElement*>(pElement);
+                        if (!pObjectElement)
+                            continue;
+
+                        PDFElement* pLookup = pObjectElement->Lookup("Length");
+                        auto pReference = dynamic_cast<PDFReferenceElement*>(pLookup);
+                        if (pReference)
+                        {
+                            // Length is provided as a reference.
+                            nLength = pReference->LookupNumber(rStream);
+                            break;
+                        }
+
+                        auto pNumber = dynamic_cast<PDFNumberElement*>(pLookup);
+                        if (pNumber)
+                        {
+                            // Length is provided directly.
+                            nLength = pNumber->GetValue();
+                            break;
+                        }
+
+                        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Read: found no Length key for stream keyword");
+                        return false;
+                    }
+
+                    PDFDocument::SkipWhitespace(rStream);
+                    m_aElements.push_back(std::unique_ptr<PDFElement>(new PDFStreamElement(nLength)));
+                    if (!m_aElements.back()->Read(rStream))
+                        return false;
+                }
+                else if (aKeyword == "endstream")
+                {
+                    m_aElements.push_back(std::unique_ptr<PDFElement>(new PDFEndStreamElement()));
+                    if (!m_aElements.back()->Read(rStream))
+                        return false;
+                }
+                else if (aKeyword == "endobj")
+                {
+                    m_aElements.push_back(std::unique_ptr<PDFElement>(new PDFEndObjectElement()));
+                    if (!m_aElements.back()->Read(rStream))
+                        return false;
+                }
+                else if (aKeyword == "true" || aKeyword == "false")
+                    m_aElements.push_back(std::unique_ptr<PDFElement>(new PDFBooleanElement(aKeyword.toBoolean())));
+                else if (aKeyword == "null")
+                    m_aElements.push_back(std::unique_ptr<PDFElement>(new PDFNullElement()));
+                else if (aKeyword == "xref")
+                    // Allow 'f' and 'n' keywords.
+                    bInXRef = true;
+                else if (bInXRef && (aKeyword == "f" || aKeyword == "n"))
+                {
+                }
+                else if (aKeyword == "trailer")
+                {
+                    m_pTrailer = new PDFTrailerElement(*this);
+                    m_aElements.push_back(std::unique_ptr<PDFElement>(m_pTrailer));
+                }
+                else if (aKeyword == "startxref")
+                {
+                }
+                else
+                {
+                    SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Read: unexpected '" << aKeyword << "' keyword");
+                    return false;
+                }
+            }
+            else
+            {
+                if (!isspace(ch))
+                {
+                    SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Read: unexpected character: " << ch);
+                    return false;
+                }
+            }
+            break;
+        }
+        }
+    }
+
+    return true;
+}
+
+OString PDFDocument::ReadKeyword(SvStream& rStream)
+{
+    OStringBuffer aBuf;
+    char ch;
+    rStream.ReadChar(ch);
+    while (isalpha(ch))
+    {
+        aBuf.append(ch);
+        rStream.ReadChar(ch);
+        if (rStream.IsEof())
+            break;
+    }
+    rStream.SeekRel(-1);
+    return aBuf.toString();
+}
+
+size_t PDFDocument::FindStartXRef(SvStream& rStream)
+{
+    // Find the "startxref" token, somewhere near the end of the document.
+    std::vector<char> aBuf(1024);
+    rStream.Seek(STREAM_SEEK_TO_END);
+    rStream.SeekRel(-1 * aBuf.size());
+    size_t nBeforePeek = rStream.Tell();
+    size_t nSize = rStream.ReadBytes(aBuf.data(), aBuf.size());
+    rStream.Seek(nBeforePeek);
+    if (nSize != aBuf.size())
+        aBuf.resize(nSize);
+    OString aPrefix("startxref");
+    char* pOffset = strstr(aBuf.data(), aPrefix.getStr());
+    if (!pOffset)
+    {
+        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::FindStartXRef: found no startxref");
+        return 0;
+    }
+
+    rStream.SeekRel(pOffset - aBuf.data() + aPrefix.getLength());
+    if (rStream.IsEof())
+    {
+        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::FindStartXRef: unexpected end of stream after startxref");
+        return 0;
+    }
+
+    PDFDocument::SkipWhitespace(rStream);
+    PDFNumberElement aNumber;
+    if (!aNumber.Read(rStream))
+        return 0;
+    return aNumber.GetValue();
+}
+
+void PDFDocument::ReadXRef(SvStream& rStream)
+{
+    if (ReadKeyword(rStream) != "xref")
+    {
+        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRef: xref is not the first keyword");
+        return;
+    }
+
+    PDFDocument::SkipWhitespace(rStream);
+    PDFNumberElement aFirstObject;
+    if (!aFirstObject.Read(rStream))
+    {
+        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRef: failed to read first object number");
+        return;
+    }
+
+    if (aFirstObject.GetValue() != 0)
+    {
+        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRef: expected first object number == 0");
+        return;
+    }
+
+    PDFDocument::SkipWhitespace(rStream);
+    PDFNumberElement aNumberOfEntries;
+    if (!aNumberOfEntries.Read(rStream))
+    {
+        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRef: failed to read number of entries");
+        return;
+    }
+
+    if (aNumberOfEntries.GetValue() <= 0)
+    {
+        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRef: expected one or more entries");
+        return;
+    }
+
+    size_t nSize = aNumberOfEntries.GetValue();
+    for (size_t nEntry = 0; nEntry < nSize; ++nEntry)
+    {
+        PDFDocument::SkipWhitespace(rStream);
+        PDFNumberElement aOffset;
+        if (!aOffset.Read(rStream))
+        {
+            SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRef: failed to read offset");
+            return;
+        }
+
+        PDFDocument::SkipWhitespace(rStream);
+        PDFNumberElement aGenerationNumber;
+        if (!aGenerationNumber.Read(rStream))
+        {
+            SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRef: failed to read genration number");
+            return;
+        }
+
+        PDFDocument::SkipWhitespace(rStream);
+        OString aKeyword = ReadKeyword(rStream);
+        if (aKeyword != "f" && aKeyword != "n")
+        {
+            SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRef: unexpected keyword");
+            return;
+        }
+        m_aXRef.push_back(aOffset.GetValue());
+    }
+}
+
+void PDFDocument::SkipWhitespace(SvStream& rStream)
+{
+    char ch = 0;
+
+    while (true)
+    {
+        rStream.ReadChar(ch);
+        if (rStream.IsEof())
+            break;
+
+        if (!isspace(ch))
+        {
+            rStream.SeekRel(-1);
+            return;
+        }
+    }
+}
+
+size_t PDFDocument::GetObjectOffset(size_t nIndex) const
+{
+    if (nIndex >= m_aXRef.size())
+    {
+        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::GetObjectOffset: wanted to look up index #" << nIndex);
+        return 0;
+    }
+
+    return m_aXRef[nIndex];
+}
+
+const std::vector< std::unique_ptr<PDFElement> >& PDFDocument::GetElements()
+{
+    return m_aElements;
+}
+
+std::vector<PDFObjectElement*> PDFDocument::GetPages()
+{
+    std::vector<PDFObjectElement*> aRet;
+
+    if (!m_pTrailer)
+    {
+        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::GetPages: found no trailer");
+        return aRet;
+    }
+
+    auto pRoot = dynamic_cast<PDFReferenceElement*>(m_pTrailer->Lookup("Root"));
+    if (!pRoot)
+    {
+        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::GetPages: trailer has no Root key");
+        return aRet;
+    }
+
+    PDFObjectElement* pCatalog = pRoot->LookupObject();
+    if (!pCatalog)
+    {
+        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::GetPages: trailer has no catalog");
+        return aRet;
+    }
+
+    PDFObjectElement* pPages = pCatalog->LookupObject("Pages");
+    if (!pPages)
+    {
+        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::GetPages: catalog has no pages");
+        return aRet;
+    }
+
+    auto pKids = dynamic_cast<PDFArrayElement*>(pPages->Lookup("Kids"));
+    if (!pKids)
+    {
+        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::GetPages: pages has no kids");
+        return aRet;
+    }
+
+    for (const auto& pKid : pKids->GetElements())
+    {
+        auto pReference = dynamic_cast<PDFReferenceElement*>(pKid);
+        if (!pReference)
+            continue;
+
+        aRet.push_back(pReference->LookupObject());
+    }
+
+    return aRet;
+}
+
+std::vector<PDFObjectElement*> PDFDocument::GetSignatureWidgets()
+{
+    std::vector<PDFObjectElement*> aRet;
+
+    std::vector<PDFObjectElement*> aPages = GetPages();
+
+    for (const auto& pPage : aPages)
+    {
+        auto pAnnots = dynamic_cast<PDFArrayElement*>(pPage->Lookup("Annots"));
+        if (!pAnnots)
+            continue;
+
+        for (const auto& pAnnot : pAnnots->GetElements())
+        {
+            auto pReference = dynamic_cast<PDFReferenceElement*>(pAnnot);
+            if (!pReference)
+                continue;
+
+            PDFObjectElement* pAnnotObject = pReference->LookupObject();
+            if (!pAnnotObject)
+                continue;
+
+            auto pFT = dynamic_cast<PDFNameElement*>(pAnnotObject->Lookup("FT"));
+            if (!pFT || pFT->GetValue() != "Sig")
+                continue;
+
+            aRet.push_back(pAnnotObject);
+        }
+    }
+
+    return aRet;
+}
+
+int PDFDocument::AsHex(char ch)
+{
+    int nRet = 0;
+    if (isdigit(ch))
+        nRet = ch - '0';
+    else
+    {
+        if (ch >= 'a' && ch <= 'f')
+            nRet = ch - 'a';
+        else if (ch >= 'A' && ch <= 'F')
+            nRet = ch - 'A';
+        else
+            return -1;
+        nRet += 10;
+    }
+    return nRet;
+}
+
+bool PDFDocument::ValidateSignature(SvStream& rStream, PDFObjectElement* pSignature, bool& bDigestMatch)
+{
+    PDFObjectElement* pValue = pSignature->LookupObject("V");
+    if (!pValue)
+    {
+        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: no value");
+        return false;
+    }
+
+    auto pContents = dynamic_cast<PDFHexStringElement*>(pValue->Lookup("Contents"));
+    if (!pContents)
+    {
+        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: no contents");
+        return false;
+    }
+
+    auto pByteRange = dynamic_cast<PDFArrayElement*>(pValue->Lookup("ByteRange"));
+    if (!pByteRange || pByteRange->GetElements().size() < 2)
+    {
+        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: no byte range or too few elements");
+        return false;
+    }
+
+    auto pSubFilter = dynamic_cast<PDFNameElement*>(pValue->Lookup("SubFilter"));
+    if (!pSubFilter || pSubFilter->GetValue() != "adbe.pkcs7.detached")
+    {
+        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: no or unsupported sub-filter");
+        return false;
+    }
+
+    // At this point there is no obviously missing info to validate the
+    // signature, so let's turn the hex dump of the signature into a memory
+    // stream.
+    const OString& rSignatureHex = pContents->GetValue();
+    size_t nSignatureHexLen = rSignatureHex.getLength();
+    std::vector<unsigned char> aSignature;
+    {
+        int nByte = 0;
+        int nCount = 2;
+        for (size_t i = 0; i < nSignatureHexLen; ++i)
+        {
+            nByte = nByte << 4;
+            sal_Int8 nParsed = AsHex(rSignatureHex[i]);
+            if (nParsed == -1)
+            {
+                SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: invalid hex value");
+                return false;
+            }
+            nByte += nParsed;
+            --nCount;
+            if (!nCount)
+            {
+                aSignature.push_back(nByte);
+                nCount = 2;
+                nByte = 0;
+            }
+        }
+    }
+
+#ifdef XMLSEC_CRYPTO_NSS
+    // Validate the signature.
+
+    const char* pEnv = getenv("MOZILLA_CERTIFICATE_FOLDER");
+    if (!pEnv)
+    {
+        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: no mozilla cert folder");
+        return false;
+    }
+
+    if (NSS_Init(pEnv) != SECSuccess)
+    {
+        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: NSS_Init() failed");
+        return false;
+    }
+
+    SECItem aSignatureItem;
+    aSignatureItem.data = aSignature.data();
+    aSignatureItem.len = aSignature.size();
+    NSSCMSMessage* pCMSMessage = NSS_CMSMessage_CreateFromDER(&aSignatureItem,
+                                 /*cb=*/nullptr,
+                                 /*cb_arg=*/nullptr,
+                                 /*pwfn=*/nullptr,
+                                 /*pwfn_arg=*/nullptr,
+                                 /*decrypt_key_cb=*/nullptr,
+                                 /*decrypt_key_cb_arg=*/nullptr);
+    if (!NSS_CMSMessage_IsSigned(pCMSMessage))
+    {
+        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: message is not signed");
+        return false;
+    }
+
+    NSSCMSContentInfo* pCMSContentInfo = NSS_CMSMessage_ContentLevel(pCMSMessage, 0);
+    if (!pCMSContentInfo)
+    {
+        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: NSS_CMSMessage_ContentLevel() failed");
+        return false;
+    }
+
+    NSSCMSSignedData* pCMSSignedData = static_cast<NSSCMSSignedData*>(NSS_CMSContentInfo_GetContent(pCMSContentInfo));
+    if (!pCMSSignedData)
+    {
+        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: NSS_CMSContentInfo_GetContent() failed");
+        return false;
+    }
+
+    NSSCMSSignerInfo* pCMSSignerInfo = NSS_CMSSignedData_GetSignerInfo(pCMSSignedData, 0);
+    if (!pCMSSignerInfo)
+    {
+        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: NSS_CMSSignedData_GetSignerInfo() failed");
+        return false;
+    }
+
+    SECItem aAlgorithm = NSS_CMSSignedData_GetDigestAlgs(pCMSSignedData)[0]->algorithm;
+    HASH_HashType eHashType = HASH_GetHashTypeByOidTag(SECOID_FindOIDTag(&aAlgorithm));
+    HASHContext* pHASHContext = HASH_Create(eHashType);
+    if (!pHASHContext)
+    {
+        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: HASH_Create() failed");
+        return false;
+    }
+
+    // We have a hash, update it with the byte ranges.
+    size_t nByteRangeOffset = 0;
+    const std::vector<PDFElement*>& rByteRangeElements = pByteRange->GetElements();
+    for (size_t i = 0; i < rByteRangeElements.size(); ++i)
+    {
+        auto pNumber = dynamic_cast<PDFNumberElement*>(rByteRangeElements[i]);
+        if (!pNumber)
+        {
+            SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: signature offset and length has to be a number");
+            return false;
+        }
+
+        if (i % 2 == 0)
+        {
+            nByteRangeOffset = pNumber->GetValue();
+            continue;
+        }
+
+        rStream.Seek(nByteRangeOffset);
+        size_t nByteRangeLength = pNumber->GetValue();
+
+        // And now hash this byte range.
+        const int nChunkLen = 4096;
+        std::vector<unsigned char> aBuffer(nChunkLen);
+        for (size_t nByte = 0; nByte < nByteRangeLength;)
+        {
+            size_t nRemainingSize = nByteRangeLength - nByte;
+            if (nRemainingSize < nChunkLen)
+            {
+                rStream.ReadBytes(aBuffer.data(), nRemainingSize);
+                HASH_Update(pHASHContext, aBuffer.data(), nRemainingSize);
+                nByte = nByteRangeLength;
+            }
+            else
+            {
+                rStream.ReadBytes(aBuffer.data(), nChunkLen);
+                HASH_Update(pHASHContext, aBuffer.data(), nChunkLen);
+                nByte += nChunkLen;
+            }
+        }
+    }
+
+    // Find out what is the expected length of the hash.
+    unsigned int nMaxResultLen = 0;
+    switch (SECOID_FindOIDTag(&aAlgorithm))
+    {
+    case SEC_OID_SHA1:
+        nMaxResultLen = 20;
+        break;
+    default:
+        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: unrecognized algorithm");
+        return false;
+    }
+
+    auto pActualResultBuffer = static_cast<unsigned char*>(PORT_Alloc(nMaxResultLen));
+    unsigned int nActualResultLen;
+    HASH_End(pHASHContext, pActualResultBuffer, &nActualResultLen, nMaxResultLen);
+
+    if (!NSS_CMSSignerInfo_GetSigningCertificate(pCMSSignerInfo, CERT_GetDefaultCertDB()))
+    {
+        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: NSS_CMSSignerInfo_GetSigningCertificate() failed");
+        return false;
+    }
+
+    SECItem* pContentInfoContentData = pCMSSignedData->contentInfo.content.data;
+    if (pContentInfoContentData && pContentInfoContentData->data)
+    {
+        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: expected nullptr content info");
+        return false;
+    }
+
+    SECItem aActualResultItem;
+    aActualResultItem.data = pActualResultBuffer;
+    aActualResultItem.len = nActualResultLen;
+    bDigestMatch = NSS_CMSSignerInfo_Verify(pCMSSignerInfo, &aActualResultItem, nullptr) == SECSuccess;
+
+    // Everything went fine
+    PORT_Free(pActualResultBuffer);
+    HASH_Destroy(pHASHContext);
+    NSS_CMSSignerInfo_Destroy(pCMSSignerInfo);
+    if (NSS_Shutdown() != SECSuccess)
+    {
+        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: NSS_Shutdown() failed");
+        return false;
+    }
+
+    return true;
+#else
+    // Not implemented.
+    (void)rStream;
+    (void)bDigestMatch;
+
+    return false;
+#endif
+}
+
+bool PDFCommentElement::Read(SvStream& rStream)
+{
+    // Read from (including) the % char till (excluding) the end of the line.
+    OStringBuffer aBuf;
+    char ch;
+    rStream.ReadChar(ch);
+    while (!rStream.IsEof())
+    {
+        if (ch == 0x0a)
+        {
+            m_aComment = aBuf.makeStringAndClear();
+            SAL_INFO("xmlsecurity.pdfio", "PDFCommentElement::Read: m_aComment is '" << m_aComment << "'");
+            return true;
+        }
+        aBuf.append(ch);
+        rStream.ReadChar(ch);
+    }
+
+    return false;
+}
+
+bool PDFNumberElement::Read(SvStream& rStream)
+{
+    OStringBuffer aBuf;
+    char ch;
+    rStream.ReadChar(ch);
+    while (!rStream.IsEof())
+    {
+        if (!isdigit(ch) && ch != '-')
+        {
+            rStream.SeekRel(-1);
+            m_fValue = aBuf.makeStringAndClear().toDouble();
+            SAL_INFO("xmlsecurity.pdfio", "PDFNumberElement::Read: m_fValue is '" << m_fValue << "'");
+            return true;
+        }
+        aBuf.append(ch);
+        rStream.ReadChar(ch);
+    }
+
+    return false;
+}
+
+PDFBooleanElement::PDFBooleanElement(bool /*bValue*/)
+{
+}
+
+bool PDFBooleanElement::Read(SvStream& /*rStream*/)
+{
+    return true;
+}
+
+bool PDFNullElement::Read(SvStream& /*rStream*/)
+{
+    return true;
+}
+
+bool PDFHexStringElement::Read(SvStream& rStream)
+{
+    char ch;
+    rStream.ReadChar(ch);
+    if (ch != '<')
+    {
+        SAL_INFO("xmlsecurity.pdfio", "PDFHexStringElement::Read: expected '<' as first character");
+        return false;
+    }
+    rStream.ReadChar(ch);
+
+    OStringBuffer aBuf;
+    while (!rStream.IsEof())
+    {
+        if (ch == '>')
+        {
+            m_aValue = aBuf.makeStringAndClear();
+            SAL_INFO("xmlsecurity.pdfio", "PDFHexStringElement::Read: m_aValue length is " << m_aValue.getLength());
+            return true;
+        }
+        aBuf.append(ch);
+        rStream.ReadChar(ch);
+    }
+
+    return false;
+}
+
+const OString& PDFHexStringElement::GetValue() const
+{
+    return m_aValue;
+}
+
+bool PDFLiteralStringElement::Read(SvStream& rStream)
+{
+    char ch;
+    rStream.ReadChar(ch);
+    if (ch != '(')
+    {
+        SAL_INFO("xmlsecurity.pdfio", "PDFHexStringElement::Read: expected '(' as first character");
+        return false;
+    }
+    rStream.ReadChar(ch);
+
+    OStringBuffer aBuf;
+    while (!rStream.IsEof())
+    {
+        if (ch == ')')
+        {
+            m_aValue = aBuf.makeStringAndClear();
+            SAL_INFO("xmlsecurity.pdfio", "PDFLiteralStringElement::Read: m_aValue is '" << m_aValue << "'");
+            return true;
+        }
+        aBuf.append(ch);
+        rStream.ReadChar(ch);
+    }
+
+    return false;
+}
+
+PDFTrailerElement::PDFTrailerElement(PDFDocument& rDoc)
+    : m_rDoc(rDoc)
+{
+}
+
+bool PDFTrailerElement::Read(SvStream& /*rStream*/)
+{
+    return true;
+}
+
+PDFElement* PDFTrailerElement::Lookup(const OString& rDictionaryKey)
+{
+    if (m_aDictionary.empty())
+        PDFDictionaryElement::Parse(m_rDoc.GetElements(), this, m_aDictionary);
+
+    return PDFDictionaryElement::Lookup(m_aDictionary, rDictionaryKey);
+}
+
+
+double PDFNumberElement::GetValue() const
+{
+    return m_fValue;
+}
+
+PDFObjectElement::PDFObjectElement(PDFDocument& rDoc, double fObjectValue, double fGenerationValue)
+    : m_rDoc(rDoc),
+      m_fObjectValue(fObjectValue),
+      m_fGenerationValue(fGenerationValue)
+{
+}
+
+bool PDFObjectElement::Read(SvStream& /*rStream*/)
+{
+    SAL_INFO("xmlsecurity.pdfio", "PDFObjectElement::Read: " << m_fObjectValue << " " << m_fGenerationValue << " obj");
+    return true;
+}
+
+void PDFDictionaryElement::Parse(const std::vector< std::unique_ptr<PDFElement> >& rElements, PDFElement* pThis, std::map<OString, PDFElement*>& rDictionary)
+{
+    if (!rDictionary.empty())
+        return;
+
+    // Find out where the dictionary for this object starts.
+    size_t nIndex = 0;
+    for (size_t i = 0; i < rElements.size(); ++i)
+    {
+        if (rElements[i].get() == pThis)
+        {
+            nIndex = i;
+            break;
+        }
+    }
+
+    OString aName;
+    std::vector<PDFNumberElement*> aNumbers;
+    // The array value we're in -- if any.
+    PDFArrayElement* pArray = nullptr;
+    for (size_t i = nIndex; i < rElements.size(); ++i)
+    {
+        auto pName = dynamic_cast<PDFNameElement*>(rElements[i].get());
+        if (pName)
+        {
+            if (!aNumbers.empty())
+            {
+                rDictionary[aName] = aNumbers.back();
+                aName.clear();
+                aNumbers.clear();
+            }
+
+            if (aName.isEmpty())
+            {
+                // Remember key.
+                aName = pName->GetValue();
+            }
+            else
+            {
+                // Name-name key-value.
+                rDictionary[aName] = pName;
+                aName.clear();
+            }
+            continue;
+        }
+
+        auto pArr = dynamic_cast<PDFArrayElement*>(rElements[i].get());
+        if (pArr)
+        {
+            pArray = pArr;
+            continue;
+        }
+
+        if (pArray && dynamic_cast<PDFEndArrayElement*>(rElements[i].get()))
+        {
+            if (!aNumbers.empty())
+            {
+                for (auto& pNumber : aNumbers)
+                    pArray->PushBack(pNumber);
+                aNumbers.clear();
+            }
+            rDictionary[aName] = pArray;
+            aName.clear();
+            pArray = nullptr;
+            continue;
+        }
+
+        auto pReference = dynamic_cast<PDFReferenceElement*>(rElements[i].get());
+        if (pReference)
+        {
+            if (!pArray)
+            {
+                rDictionary[aName] = pReference;
+                aName.clear();
+            }
+            else
+            {
+                pArray->PushBack(pReference);
+            }
+            aNumbers.clear();
+            continue;
+        }
+
+        auto pLiteralString = dynamic_cast<PDFLiteralStringElement*>(rElements[i].get());
+        if (pLiteralString)
+        {
+            rDictionary[aName] = pLiteralString;
+            aName.clear();
+            continue;
+        }
+
+        auto pHexString = dynamic_cast<PDFHexStringElement*>(rElements[i].get());
+        if (pHexString)
+        {
+            rDictionary[aName] = pHexString;
+            aName.clear();
+            continue;
+        }
+
+        if (dynamic_cast<PDFEndDictionaryElement*>(rElements[i].get()))
+            break;
+
+        if (dynamic_cast<PDFEndObjectElement*>(rElements[i].get()))
+            break;
+
+        // Just remember this, so that in case it's not a reference parameter,
+        // we can handle it later.
+        auto pNumber = dynamic_cast<PDFNumberElement*>(rElements[i].get());
+        if (pNumber)
+            aNumbers.push_back(pNumber);
+    }
+
+    if (!aNumbers.empty())
+    {
+        rDictionary[aName] = aNumbers.back();
+        aName.clear();
+        aNumbers.clear();
+    }
+}
+
+PDFElement* PDFDictionaryElement::Lookup(const std::map<OString, PDFElement*>& rDictionary, const OString& rKey)
+{
+    auto it = rDictionary.find(rKey);
+    if (it == rDictionary.end())
+        return nullptr;
+
+    return it->second;
+}
+
+PDFElement* PDFObjectElement::Lookup(const OString& rDictionaryKey)
+{
+    if (m_aDictionary.empty())
+        PDFDictionaryElement::Parse(m_rDoc.GetElements(), this, m_aDictionary);
+
+    return PDFDictionaryElement::Lookup(m_aDictionary, rDictionaryKey);
+}
+
+PDFObjectElement* PDFObjectElement::LookupObject(const OString& rDictionaryKey)
+{
+    auto pKey = dynamic_cast<PDFReferenceElement*>(Lookup(rDictionaryKey));
+    if (!pKey)
+    {
+        SAL_WARN("xmlsecurity.pdfio", "PDFObjectElement::LookupObject: no such key with reference value: " << rDictionaryKey);
+        return nullptr;
+    }
+
+    return pKey->LookupObject();
+}
+
+double PDFObjectElement::GetObjectValue() const
+{
+    return m_fObjectValue;
+}
+
+double PDFObjectElement::GetGenerationValue() const
+{
+    return m_fGenerationValue;
+}
+
+PDFReferenceElement::PDFReferenceElement(PDFDocument& rDoc, int fObjectValue, int fGenerationValue)
+    : m_rDoc(rDoc),
+      m_fObjectValue(fObjectValue),
+      m_fGenerationValue(fGenerationValue)
+{
+}
+
+bool PDFReferenceElement::Read(SvStream& /*rStream*/)
+{
+    SAL_INFO("xmlsecurity.pdfio", "PDFReferenceElement::Read: " << m_fObjectValue << " " << m_fGenerationValue << " R");
+    return true;
+}
+
+double PDFReferenceElement::LookupNumber(SvStream& rStream) const
+{
+    size_t nOffset = m_rDoc.GetObjectOffset(m_fObjectValue);
+    if (nOffset == 0)
+    {
+        SAL_WARN("xmlsecurity.pdfio", "PDFReferenceElement::LookupNumber: found no offset for object #" << m_fObjectValue);
+        return 0;
+    }
+
+    sal_uInt64 nOrigPos = rStream.Tell();
+    comphelper::ScopeGuard g([&]()
+    {
+        rStream.Seek(nOrigPos);
+    });
+
+    rStream.Seek(nOffset);
+    {
+        PDFDocument::SkipWhitespace(rStream);
+        PDFNumberElement aNumber;
+        bool bRet = aNumber.Read(rStream);
+        if (!bRet || aNumber.GetValue() != m_fObjectValue)
+        {
+            SAL_WARN("xmlsecurity.pdfio", "PDFReferenceElement::LookupNumber: offset points to not matching object");
+            return 0;
+        }
+    }
+
+    {
+        PDFDocument::SkipWhitespace(rStream);
+        PDFNumberElement aNumber;
+        bool bRet = aNumber.Read(rStream);
+        if (!bRet || aNumber.GetValue() != m_fGenerationValue)
+        {
+            SAL_WARN("xmlsecurity.pdfio", "PDFReferenceElement::LookupNumber: offset points to not matching generation");
+            return 0;
+        }
+    }
+
+    {
+        PDFDocument::SkipWhitespace(rStream);
+        OString aKeyword = PDFDocument::ReadKeyword(rStream);
+        if (aKeyword != "obj")
+        {
+            SAL_WARN("xmlsecurity.pdfio", "PDFReferenceElement::LookupNumber: offset doesn't point to an obj keyword");
+            return 0;
+        }
+    }
+
+    PDFDocument::SkipWhitespace(rStream);
+    PDFNumberElement aNumber;
+    if (!aNumber.Read(rStream))
+    {
+        SAL_WARN("xmlsecurity.pdfio", "PDFReferenceElement::LookupNumber: failed to read referenced number");
+        return 0;
+    }
+
+    return aNumber.GetValue();
+}
+
+PDFObjectElement* PDFReferenceElement::LookupObject() const
+{
+    const std::vector< std::unique_ptr<PDFElement> >& rElements = m_rDoc.GetElements();
+    for (const auto& rElement : rElements)
+    {
+        auto* pObjectElement = dynamic_cast<PDFObjectElement*>(rElement.get());
+        if (!pObjectElement)
+            continue;
+
+        if (pObjectElement->GetObjectValue() != m_fObjectValue)
+            continue;
+
+        if (pObjectElement->GetGenerationValue() != m_fGenerationValue)
+            continue;
+
+        return pObjectElement;
+    }
+
+    return nullptr;
+}
+
+bool PDFDictionaryElement::Read(SvStream& rStream)
+{
+    char ch;
+    rStream.ReadChar(ch);
+    if (ch != '<')
+    {
+        SAL_WARN("xmlsecurity.pdfio", "PDFDictionaryElement::Read: unexpected character: " << ch);
+        return false;
+    }
+
+    if (rStream.IsEof())
+    {
+        SAL_WARN("xmlsecurity.pdfio", "PDFDictionaryElement::Read: unexpected end of file");
+        return false;
+    }
+
+    rStream.ReadChar(ch);
+    if (ch != '<')
+    {
+        SAL_WARN("xmlsecurity.pdfio", "PDFDictionaryElement::Read: unexpected character: " << ch);
+        return false;
+    }
+
+    SAL_INFO("xmlsecurity.pdfio", "PDFDictionaryElement::Read: '<<'");
+
+    return true;
+}
+
+bool PDFEndDictionaryElement::Read(SvStream& rStream)
+{
+    char ch;
+    rStream.ReadChar(ch);
+    if (ch != '>')
+    {
+        SAL_WARN("xmlsecurity.pdfio", "PDFEndDictionaryElement::Read: unexpected character: " << ch);
+        return false;
+    }
+
+    if (rStream.IsEof())
+    {
+        SAL_WARN("xmlsecurity.pdfio", "PDFEndDictionaryElement::Read: unexpected end of file");
+        return false;
+    }
+
+    rStream.ReadChar(ch);
+    if (ch != '>')
+    {
+        SAL_WARN("xmlsecurity.pdfio", "PDFEndDictionaryElement::Read: unexpected character: " << ch);
+        return false;
+    }
+
+    SAL_INFO("xmlsecurity.pdfio", "PDFEndDictionaryElement::Read: '>>'");
+
+    return true;
+}
+
+bool PDFNameElement::Read(SvStream& rStream)
+{
+    char ch;
+    rStream.ReadChar(ch);
+    if (ch != '/')
+    {
+        SAL_WARN("xmlsecurity.pdfio", "PDFNameElement::Read: unexpected character: " << ch);
+        return false;
+    }
+
+    if (rStream.IsEof())
+    {
+        SAL_WARN("xmlsecurity.pdfio", "PDFNameElement::Read: unexpected end of file");
+        return false;
+    }
+
+    // Read till the first white-space.
+    OStringBuffer aBuf;
+    rStream.ReadChar(ch);
+    while (!rStream.IsEof())
+    {
+        if (isspace(ch) || ch == '/' || ch == '[' || ch == '<' || ch == '(')
+        {
+            rStream.SeekRel(-1);
+            m_aValue = aBuf.makeStringAndClear();
+            SAL_INFO("xmlsecurity.pdfio", "PDFNameElement::Read: m_aValue is '" << m_aValue << "'");
+            return true;
+        }
+        aBuf.append(ch);
+        rStream.ReadChar(ch);
+    }
+
+    return false;
+}
+
+const OString& PDFNameElement::GetValue() const
+{
+    return m_aValue;
+}
+
+PDFStreamElement::PDFStreamElement(size_t nLength)
+    : m_nLength(nLength)
+{
+}
+
+bool PDFStreamElement::Read(SvStream& rStream)
+{
+    SAL_INFO("xmlsecurity.pdfio", "PDFStreamElement::Read: length is " << m_nLength);
+    rStream.SeekRel(m_nLength);
+
+    return rStream.good();
+}
+
+bool PDFEndStreamElement::Read(SvStream& /*rStream*/)
+{
+    return true;
+}
+
+bool PDFEndObjectElement::Read(SvStream& /*rStream*/)
+{
+    return true;
+}
+
+bool PDFArrayElement::Read(SvStream& rStream)
+{
+    char ch;
+    rStream.ReadChar(ch);
+    if (ch != '[')
+    {
+        SAL_WARN("xmlsecurity.pdfio", "PDFArrayElement::Read: unexpected character: " << ch);
+        return false;
+    }
+
+    SAL_INFO("xmlsecurity.pdfio", "PDFArrayElement::Read: '['");
+
+    return true;
+}
+
+void PDFArrayElement::PushBack(PDFElement* pElement)
+{
+    m_aElements.push_back(pElement);
+}
+
+const std::vector<PDFElement*>& PDFArrayElement::GetElements()
+{
+    return m_aElements;
+}
+
+bool PDFEndArrayElement::Read(SvStream& rStream)
+{
+    char ch;
+    rStream.ReadChar(ch);
+    if (ch != ']')
+    {
+        SAL_WARN("xmlsecurity.pdfio", "PDFEndArrayElement::Read: unexpected character: " << ch);
+        return false;
+    }
+
+    SAL_INFO("xmlsecurity.pdfio", "PDFEndArrayElement::Read: ']'");
+
+    return true;
+}
+
+
+} // namespace pdfio
+} // namespace xmlsecurity
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/xmlsecurity/source/pdfio/pdfverify.cxx b/xmlsecurity/source/pdfio/pdfverify.cxx
index 5bf50b6..cbb9a89 100644
--- a/xmlsecurity/source/pdfio/pdfverify.cxx
+++ b/xmlsecurity/source/pdfio/pdfverify.cxx
@@ -7,1494 +7,14 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
  */
 
-#include <iostream>
-#include <map>
-#include <memory>
-#include <vector>
+#include <pdfio/pdfdocument.hxx>
 
-#include <comphelper/scopeguard.hxx>
-#include <osl/file.hxx>
-#include <rtl/strbuf.hxx>
-#include <rtl/string.hxx>
-#include <sal/log.hxx>
 #include <sal/main.h>
-#include <sal/types.h>
-#include <tools/stream.hxx>
-
-#ifdef XMLSEC_CRYPTO_NSS
-#include <cert.h>
-#include <cms.h>
-#include <nss.h>
-#include <sechash.h>
-#endif
+#include <osl/file.hxx>
+#include <iostream>
 
 using namespace com::sun::star;
 
-namespace xmlsecurity
-{
-namespace pdfio
-{
-
-/// A byte range in a PDF file.
-class PDFElement
-{
-public:
-    virtual bool Read(SvStream& rStream) = 0;
-    virtual ~PDFElement() { }
-};
-
-class PDFTrailerElement;
-class PDFObjectElement;
-
-/// In-memory representation of an on-disk PDF document.
-class PDFDocument
-{
-    /// This vector owns all elements.
-    std::vector< std::unique_ptr<PDFElement> > m_aElements;
-    // List of object offsets we know.
-    std::vector<size_t> m_aXRef;
-    PDFTrailerElement* m_pTrailer;
-
-    static int AsHex(char ch);
-
-public:
-    PDFDocument();
-    static OString ReadKeyword(SvStream& rStream);
-    static size_t FindStartXRef(SvStream& rStream);
-    void ReadXRef(SvStream& rStream);
-    static void SkipWhitespace(SvStream& rStream);
-    size_t GetObjectOffset(size_t nIndex) const;
-    const std::vector< std::unique_ptr<PDFElement> >& GetElements();
-    std::vector<PDFObjectElement*> GetPages();
-
-    bool Read(SvStream& rStream);
-    std::vector<PDFObjectElement*> GetSignatureWidgets();
-    /// Return value is about if we can determine a result, bDigestMatch is about the actual result.
-    static bool ValidateSignature(SvStream& rStream, PDFObjectElement* pSignature, bool& bDigestMatch);
-};
-
-/// A one-liner comment.
-class PDFCommentElement : public PDFElement
-{
-    OString m_aComment;
-
-public:
-    bool Read(SvStream& rStream) override;
-};
-
-/// Numbering object: an integer or a real.
-class PDFNumberElement : public PDFElement
-{
-    double m_fValue;
-
-public:
-    bool Read(SvStream& rStream) override;
-    double GetValue() const;
-};
-
-class PDFReferenceElement;
-
-/// Indirect object: something with a unique ID.
-class PDFObjectElement : public PDFElement
-{
-    PDFDocument& m_rDoc;
-    double m_fObjectValue;
-    double m_fGenerationValue;
-    std::map<OString, PDFElement*> m_aDictionary;
-
-public:
-    PDFObjectElement(PDFDocument& rDoc, double fObjectValue, double fGenerationValue);
-    bool Read(SvStream& rStream) override;
-    PDFElement* Lookup(const OString& rDictionaryKey);
-    PDFObjectElement* LookupObject(const OString& rDictionaryKey);
-    double GetObjectValue() const;
-    double GetGenerationValue() const;
-};
-
-/// Dictionary object: a set key-value pairs.
-class PDFDictionaryElement : public PDFElement
-{
-public:
-    bool Read(SvStream& rStream) override;
-
-    static void Parse(const std::vector< std::unique_ptr<PDFElement> >& rElements, PDFElement* pThis, std::map<OString, PDFElement*>& rDictionary);
-    static PDFElement* Lookup(const std::map<OString, PDFElement*>& rDictionary, const OString& rKey);
-};
-
-/// End of a dictionary: '>>'.
-class PDFEndDictionaryElement : public PDFElement
-{
-public:
-    bool Read(SvStream& rStream) override;
-};
-
-/// Name object: a key string.
-class PDFNameElement : public PDFElement
-{
-    OString m_aValue;
-public:
-    bool Read(SvStream& rStream) override;
-    const OString& GetValue() const;
-};
-
-/// Reference object: something with a unique ID.
-class PDFReferenceElement : public PDFElement
-{
-    PDFDocument& m_rDoc;
-    int m_fObjectValue;
-    int m_fGenerationValue;
-
-public:
-    PDFReferenceElement(PDFDocument& rDoc, int fObjectValue, int fGenerationValue);
-    bool Read(SvStream& rStream) override;
-    /// Assuming the reference points to a number object, return its value.
-    double LookupNumber(SvStream& rStream) const;
-    /// Lookup referenced object, without assuming anything about its contents.
-    PDFObjectElement* LookupObject() const;
-};
-
-/// Stream object: a byte array with a known length.
-class PDFStreamElement : public PDFElement
-{
-    size_t m_nLength;
-
-public:
-    PDFStreamElement(size_t nLength);
-    bool Read(SvStream& rStream) override;
-};
-
-/// End of a stream: 'endstream' keyword.
-class PDFEndStreamElement : public PDFElement
-{
-public:
-    bool Read(SvStream& rStream) override;
-};
-
-/// End of a object: 'endobj' keyword.
-class PDFEndObjectElement : public PDFElement
-{
-public:
-    bool Read(SvStream& rStream) override;
-};
-
-/// Array object: a list.
-class PDFArrayElement : public PDFElement
-{
-    std::vector<PDFElement*> m_aElements;
-public:
-    bool Read(SvStream& rStream) override;
-    void PushBack(PDFElement* pElement);
-    const std::vector<PDFElement*>& GetElements();
-};
-
-/// End of an array: ']'.
-class PDFEndArrayElement : public PDFElement
-{
-public:
-    bool Read(SvStream& rStream) override;
-};
-
-/// Boolean object: a 'true' or a 'false'.
-class PDFBooleanElement : public PDFElement
-{
-public:
-    PDFBooleanElement(bool bValue);
-    bool Read(SvStream& rStream) override;
-};
-
-/// Null object: the 'null' singleton.
-class PDFNullElement : public PDFElement
-{
-public:
-    bool Read(SvStream& rStream) override;
-};
-
-/// Hex string: in <AABB> form.
-class PDFHexStringElement : public PDFElement
-{
-    OString m_aValue;
-public:
-    bool Read(SvStream& rStream) override;
-    const OString& GetValue() const;
-};
-
-/// Literal string: in (asdf) form.
-class PDFLiteralStringElement : public PDFElement
-{
-    OString m_aValue;
-public:
-    bool Read(SvStream& rStream) override;
-};
-
-/// The trailer signleton is at the end of the doc.
-class PDFTrailerElement : public PDFElement
-{
-    PDFDocument& m_rDoc;
-    std::map<OString, PDFElement*> m_aDictionary;
-
-public:
-    PDFTrailerElement(PDFDocument& rDoc);
-    bool Read(SvStream& rStream) override;
-    PDFElement* Lookup(const OString& rDictionaryKey);
-};
-
-PDFDocument::PDFDocument()
-    : m_pTrailer(nullptr)
-{
-}
-
-bool PDFDocument::Read(SvStream& rStream)
-{
-    // First look up the offset of the xref table.
-    size_t nStartXRef = FindStartXRef(rStream);
-    SAL_INFO("xmlsecurity.pdfio", "PDFDocument::Read: nStartXRef is " << nStartXRef);
-    if (nStartXRef == 0)
-    {
-        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Read: found no xref statrt offset");
-        return false;
-    }
-    rStream.Seek(nStartXRef);
-    char ch;
-    ReadXRef(rStream);
-
-    // Then we can tokenize the stream.
-    rStream.Seek(0);
-    bool bInXRef = false;
-    while (true)
-    {
-        rStream.ReadChar(ch);
-        if (rStream.IsEof())
-            break;
-
-        switch (ch)
-        {
-        case '%':
-        {
-            m_aElements.push_back(std::unique_ptr<PDFElement>(new PDFCommentElement()));
-            rStream.SeekRel(-1);
-            if (!m_aElements.back()->Read(rStream))
-                return false;
-            break;
-        }
-        case '<':
-        {
-            // Dictionary or hex string.
-            rStream.ReadChar(ch);
-            rStream.SeekRel(-2);
-            if (ch == '<')
-                m_aElements.push_back(std::unique_ptr<PDFElement>(new PDFDictionaryElement()));
-            else
-                m_aElements.push_back(std::unique_ptr<PDFElement>(new PDFHexStringElement()));
-            if (!m_aElements.back()->Read(rStream))
-                return false;
-            break;
-        }
-        case '>':
-        {
-            m_aElements.push_back(std::unique_ptr<PDFElement>(new PDFEndDictionaryElement()));
-            rStream.SeekRel(-1);
-            if (!m_aElements.back()->Read(rStream))
-                return false;
-            break;
-        }
-        case '[':
-        {
-            m_aElements.push_back(std::unique_ptr<PDFElement>(new PDFArrayElement()));
-            rStream.SeekRel(-1);
-            if (!m_aElements.back()->Read(rStream))
-                return false;
-            break;
-        }
-        case ']':
-        {
-            m_aElements.push_back(std::unique_ptr<PDFElement>(new PDFEndArrayElement()));
-            rStream.SeekRel(-1);
-            if (!m_aElements.back()->Read(rStream))
-                return false;
-            break;
-        }
-        case '/':
-        {
-            m_aElements.push_back(std::unique_ptr<PDFElement>(new PDFNameElement()));
-            rStream.SeekRel(-1);
-            if (!m_aElements.back()->Read(rStream))
-                return false;
-            break;
-        }
-        case '(':
-        {
-            m_aElements.push_back(std::unique_ptr<PDFElement>(new PDFLiteralStringElement()));
-            rStream.SeekRel(-1);
-            if (!m_aElements.back()->Read(rStream))
-                return false;
-            break;
-        }
-        default:
-        {
-            if (isdigit(ch) || ch == '-')
-            {
-                // Numbering object: an integer or a real.
-                m_aElements.push_back(std::unique_ptr<PDFElement>(new PDFNumberElement()));
-                rStream.SeekRel(-1);
-                if (!m_aElements.back()->Read(rStream))
-                    return false;
-            }
-            else if (isalpha(ch))
-            {
-                // Possible keyword, like "obj".
-                rStream.SeekRel(-1);
-                OString aKeyword = ReadKeyword(rStream);
-
-                bool bObj = aKeyword == "obj";
-                if (bObj || aKeyword == "R")
-                {
-                    size_t nElements = m_aElements.size();
-                    if (nElements < 2)
-                    {
-                        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Read: expected at least two tokens before 'obj' or 'R' keyword");
-                        return false;
-                    }
-
-                    auto pObjectNumber = dynamic_cast<PDFNumberElement*>(m_aElements[nElements - 2].get());
-                    auto pGenerationNumber = dynamic_cast<PDFNumberElement*>(m_aElements[nElements - 1].get());
-                    if (!pObjectNumber || !pGenerationNumber)
-                    {
-                        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Read: missing object or generation number before 'obj' or 'R' keyword");
-                        return false;
-                    }
-
-                    if (bObj)
-                        m_aElements.push_back(std::unique_ptr<PDFElement>(new PDFObjectElement(*this, pObjectNumber->GetValue(), pGenerationNumber->GetValue())));
-                    else
-                        m_aElements.push_back(std::unique_ptr<PDFElement>(new PDFReferenceElement(*this, pObjectNumber->GetValue(), pGenerationNumber->GetValue())));
-                    if (!m_aElements.back()->Read(rStream))
-                        return false;
-                }
-                else if (aKeyword == "stream")
-                {
-                    // Look up the length of the stream from the parent object's dictionary.
-                    size_t nLength = 0;
-                    for (size_t nElement = 0; nElement < m_aElements.size(); ++nElement)
-                    {
-                        // Iterate in reverse order.
-                        size_t nIndex = m_aElements.size() - nElement - 1;
-                        PDFElement* pElement = m_aElements[nIndex].get();
-                        auto pObjectElement = dynamic_cast<PDFObjectElement*>(pElement);
-                        if (!pObjectElement)
-                            continue;
-
-                        PDFElement* pLookup = pObjectElement->Lookup("Length");
-                        auto pReference = dynamic_cast<PDFReferenceElement*>(pLookup);
-                        if (pReference)
-                        {
-                            // Length is provided as a reference.
-                            nLength = pReference->LookupNumber(rStream);
-                            break;
-                        }
-
-                        auto pNumber = dynamic_cast<PDFNumberElement*>(pLookup);
-                        if (pNumber)
-                        {
-                            // Length is provided directly.
-                            nLength = pNumber->GetValue();
-                            break;
-                        }
-
-                        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Read: found no Length key for stream keyword");
-                        return false;
-                    }
-
-                    PDFDocument::SkipWhitespace(rStream);
-                    m_aElements.push_back(std::unique_ptr<PDFElement>(new PDFStreamElement(nLength)));
-                    if (!m_aElements.back()->Read(rStream))
-                        return false;
-                }
-                else if (aKeyword == "endstream")
-                {
-                    m_aElements.push_back(std::unique_ptr<PDFElement>(new PDFEndStreamElement()));
-                    if (!m_aElements.back()->Read(rStream))
-                        return false;
-                }
-                else if (aKeyword == "endobj")
-                {
-                    m_aElements.push_back(std::unique_ptr<PDFElement>(new PDFEndObjectElement()));
-                    if (!m_aElements.back()->Read(rStream))
-                        return false;
-                }
-                else if (aKeyword == "true" || aKeyword == "false")
-                    m_aElements.push_back(std::unique_ptr<PDFElement>(new PDFBooleanElement(aKeyword.toBoolean())));
-                else if (aKeyword == "null")
-                    m_aElements.push_back(std::unique_ptr<PDFElement>(new PDFNullElement()));
-                else if (aKeyword == "xref")
-                    // Allow 'f' and 'n' keywords.
-                    bInXRef = true;
-                else if (bInXRef && (aKeyword == "f" || aKeyword == "n"))
-                {
-                }
-                else if (aKeyword == "trailer")
-                {
-                    m_pTrailer = new PDFTrailerElement(*this);
-                    m_aElements.push_back(std::unique_ptr<PDFElement>(m_pTrailer));
-                }
-                else if (aKeyword == "startxref")
-                {
-                }
-                else
-                {
-                    SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Read: unexpected '" << aKeyword << "' keyword");
-                    return false;
-                }
-            }
-            else
-            {
-                if (!isspace(ch))
-                {
-                    SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Read: unexpected character: " << ch);
-                    return false;
-                }
-            }
-            break;
-        }
-        }
-    }
-
-    return true;
-}
-
-OString PDFDocument::ReadKeyword(SvStream& rStream)
-{
-    OStringBuffer aBuf;
-    char ch;
-    rStream.ReadChar(ch);
-    while (isalpha(ch))
-    {
-        aBuf.append(ch);
-        rStream.ReadChar(ch);
-        if (rStream.IsEof())
-            break;
-    }
-    rStream.SeekRel(-1);
-    return aBuf.toString();
-}
-
-size_t PDFDocument::FindStartXRef(SvStream& rStream)
-{
-    // Find the "startxref" token, somewhere near the end of the document.
-    std::vector<char> aBuf(1024);
-    rStream.Seek(STREAM_SEEK_TO_END);
-    rStream.SeekRel(-1 * aBuf.size());
-    size_t nBeforePeek = rStream.Tell();
-    size_t nSize = rStream.ReadBytes(aBuf.data(), aBuf.size());
-    rStream.Seek(nBeforePeek);
-    if (nSize != aBuf.size())
-        aBuf.resize(nSize);
-    OString aPrefix("startxref");
-    char* pOffset = strstr(aBuf.data(), aPrefix.getStr());
-    if (!pOffset)
-    {
-        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::FindStartXRef: found no startxref");
-        return 0;
-    }
-
-    rStream.SeekRel(pOffset - aBuf.data() + aPrefix.getLength());
-    if (rStream.IsEof())
-    {
-        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::FindStartXRef: unexpected end of stream after startxref");
-        return 0;
-    }
-
-    PDFDocument::SkipWhitespace(rStream);
-    PDFNumberElement aNumber;
-    if (!aNumber.Read(rStream))
-        return 0;
-    return aNumber.GetValue();
-}
-
-void PDFDocument::ReadXRef(SvStream& rStream)
-{
-    if (ReadKeyword(rStream) != "xref")
-    {
-        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRef: xref is not the first keyword");
-        return;
-    }
-
-    PDFDocument::SkipWhitespace(rStream);
-    PDFNumberElement aFirstObject;
-    if (!aFirstObject.Read(rStream))
-    {
-        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRef: failed to read first object number");
-        return;
-    }
-
-    if (aFirstObject.GetValue() != 0)
-    {
-        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRef: expected first object number == 0");
-        return;
-    }
-
-    PDFDocument::SkipWhitespace(rStream);
-    PDFNumberElement aNumberOfEntries;
-    if (!aNumberOfEntries.Read(rStream))
-    {
-        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRef: failed to read number of entries");
-        return;
-    }
-
-    if (aNumberOfEntries.GetValue() <= 0)
-    {
-        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRef: expected one or more entries");
-        return;
-    }
-
-    size_t nSize = aNumberOfEntries.GetValue();
-    for (size_t nEntry = 0; nEntry < nSize; ++nEntry)
-    {
-        PDFDocument::SkipWhitespace(rStream);
-        PDFNumberElement aOffset;
-        if (!aOffset.Read(rStream))
-        {
-            SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRef: failed to read offset");
-            return;
-        }
-
-        PDFDocument::SkipWhitespace(rStream);
-        PDFNumberElement aGenerationNumber;
-        if (!aGenerationNumber.Read(rStream))
-        {
-            SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRef: failed to read genration number");
-            return;
-        }
-
-        PDFDocument::SkipWhitespace(rStream);
-        OString aKeyword = ReadKeyword(rStream);
-        if (aKeyword != "f" && aKeyword != "n")
-        {
-            SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRef: unexpected keyword");
-            return;
-        }
-        m_aXRef.push_back(aOffset.GetValue());
-    }
-}
-
-void PDFDocument::SkipWhitespace(SvStream& rStream)
-{
-    char ch = 0;
-
-    while (true)
-    {
-        rStream.ReadChar(ch);
-        if (rStream.IsEof())
-            break;
-
-        if (!isspace(ch))
-        {
-            rStream.SeekRel(-1);
-            return;
-        }
-    }
-}
-
-size_t PDFDocument::GetObjectOffset(size_t nIndex) const
-{
-    if (nIndex >= m_aXRef.size())
-    {
-        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::GetObjectOffset: wanted to look up index #" << nIndex);
-        return 0;
-    }
-
-    return m_aXRef[nIndex];
-}
-
-const std::vector< std::unique_ptr<PDFElement> >& PDFDocument::GetElements()
-{
-    return m_aElements;
-}
-
-std::vector<PDFObjectElement*> PDFDocument::GetPages()
-{
-    std::vector<PDFObjectElement*> aRet;
-
-    if (!m_pTrailer)
-    {
-        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::GetPages: found no trailer");
-        return aRet;
-    }
-
-    auto pRoot = dynamic_cast<PDFReferenceElement*>(m_pTrailer->Lookup("Root"));
-    if (!pRoot)
-    {
-        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::GetPages: trailer has no Root key");
-        return aRet;
-    }
-
-    PDFObjectElement* pCatalog = pRoot->LookupObject();
-    if (!pCatalog)
-    {
-        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::GetPages: trailer has no catalog");
-        return aRet;
-    }
-
-    PDFObjectElement* pPages = pCatalog->LookupObject("Pages");
-    if (!pPages)
-    {
-        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::GetPages: catalog has no pages");
-        return aRet;
-    }
-
-    auto pKids = dynamic_cast<PDFArrayElement*>(pPages->Lookup("Kids"));
-    if (!pKids)
-    {
-        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::GetPages: pages has no kids");
-        return aRet;
-    }
-
-    for (const auto& pKid : pKids->GetElements())
-    {
-        auto pReference = dynamic_cast<PDFReferenceElement*>(pKid);
-        if (!pReference)
-            continue;
-
-        aRet.push_back(pReference->LookupObject());
-    }
-
-    return aRet;
-}
-
-std::vector<PDFObjectElement*> PDFDocument::GetSignatureWidgets()
-{
-    std::vector<PDFObjectElement*> aRet;
-
-    std::vector<PDFObjectElement*> aPages = GetPages();
-
-    for (const auto& pPage : aPages)
-    {
-        auto pAnnots = dynamic_cast<PDFArrayElement*>(pPage->Lookup("Annots"));
-        if (!pAnnots)
-            continue;
-
-        for (const auto& pAnnot : pAnnots->GetElements())
-        {
-            auto pReference = dynamic_cast<PDFReferenceElement*>(pAnnot);
-            if (!pReference)
-                continue;
-
-            PDFObjectElement* pAnnotObject = pReference->LookupObject();
-            if (!pAnnotObject)
-                continue;
-
-            auto pFT = dynamic_cast<PDFNameElement*>(pAnnotObject->Lookup("FT"));
-            if (!pFT || pFT->GetValue() != "Sig")
-                continue;
-
-            aRet.push_back(pAnnotObject);
-        }
-    }
-
-    return aRet;
-}
-
-int PDFDocument::AsHex(char ch)
-{
-    int nRet = 0;
-    if (isdigit(ch))
-        nRet = ch - '0';
-    else
-    {
-        if (ch >= 'a' && ch <= 'f')
-            nRet = ch - 'a';
-        else if (ch >= 'A' && ch <= 'F')
-            nRet = ch - 'A';
-        else
-            return -1;
-        nRet += 10;
-    }
-    return nRet;
-}
-
-bool PDFDocument::ValidateSignature(SvStream& rStream, PDFObjectElement* pSignature, bool& bDigestMatch)
-{
-    PDFObjectElement* pValue = pSignature->LookupObject("V");
-    if (!pValue)
-    {
-        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: no value");
-        return false;
-    }
-
-    auto pContents = dynamic_cast<PDFHexStringElement*>(pValue->Lookup("Contents"));
-    if (!pContents)
-    {
-        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: no contents");
-        return false;
-    }
-
-    auto pByteRange = dynamic_cast<PDFArrayElement*>(pValue->Lookup("ByteRange"));
-    if (!pByteRange || pByteRange->GetElements().size() < 2)
-    {
-        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: no byte range or too few elements");
-        return false;
-    }
-
-    auto pSubFilter = dynamic_cast<PDFNameElement*>(pValue->Lookup("SubFilter"));
-    if (!pSubFilter || pSubFilter->GetValue() != "adbe.pkcs7.detached")
-    {
-        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: no or unsupported sub-filter");
-        return false;
-    }
-
-    // At this point there is no obviously missing info to validate the
-    // signature, so let's turn the hex dump of the signature into a memory
-    // stream.
-    const OString& rSignatureHex = pContents->GetValue();
-    size_t nSignatureHexLen = rSignatureHex.getLength();
-    std::vector<unsigned char> aSignature;
-    {
-        int nByte = 0;
-        int nCount = 2;
-        for (size_t i = 0; i < nSignatureHexLen; ++i)
-        {
-            nByte = nByte << 4;
-            sal_Int8 nParsed = AsHex(rSignatureHex[i]);
-            if (nParsed == -1)
-            {
-                SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: invalid hex value");
-                return false;
-            }
-            nByte += nParsed;
-            --nCount;
-            if (!nCount)
-            {
-                aSignature.push_back(nByte);
-                nCount = 2;
-                nByte = 0;
-            }
-        }
-    }
-
-#ifdef XMLSEC_CRYPTO_NSS
-    // Validate the signature.
-
-    const char* pEnv = getenv("MOZILLA_CERTIFICATE_FOLDER");
-    if (!pEnv)
-    {
-        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: no mozilla cert folder");
-        return false;
-    }
-
-    if (NSS_Init(pEnv) != SECSuccess)
-    {
-        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: NSS_Init() failed");
-        return false;
-    }
-
-    SECItem aSignatureItem;
-    aSignatureItem.data = aSignature.data();
-    aSignatureItem.len = aSignature.size();
-    NSSCMSMessage* pCMSMessage = NSS_CMSMessage_CreateFromDER(&aSignatureItem,
-                                 /*cb=*/nullptr,
-                                 /*cb_arg=*/nullptr,
-                                 /*pwfn=*/nullptr,
-                                 /*pwfn_arg=*/nullptr,
-                                 /*decrypt_key_cb=*/nullptr,
-                                 /*decrypt_key_cb_arg=*/nullptr);
-    if (!NSS_CMSMessage_IsSigned(pCMSMessage))
-    {
-        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: message is not signed");
-        return false;
-    }
-
-    NSSCMSContentInfo* pCMSContentInfo = NSS_CMSMessage_ContentLevel(pCMSMessage, 0);
-    if (!pCMSContentInfo)
-    {
-        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: NSS_CMSMessage_ContentLevel() failed");
-        return false;
-    }
-
-    NSSCMSSignedData* pCMSSignedData = static_cast<NSSCMSSignedData*>(NSS_CMSContentInfo_GetContent(pCMSContentInfo));
-    if (!pCMSSignedData)
-    {
-        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: NSS_CMSContentInfo_GetContent() failed");
-        return false;
-    }
-
-    NSSCMSSignerInfo* pCMSSignerInfo = NSS_CMSSignedData_GetSignerInfo(pCMSSignedData, 0);
-    if (!pCMSSignerInfo)
-    {
-        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: NSS_CMSSignedData_GetSignerInfo() failed");
-        return false;
-    }
-
-    SECItem aAlgorithm = NSS_CMSSignedData_GetDigestAlgs(pCMSSignedData)[0]->algorithm;
-    HASH_HashType eHashType = HASH_GetHashTypeByOidTag(SECOID_FindOIDTag(&aAlgorithm));
-    HASHContext* pHASHContext = HASH_Create(eHashType);
-    if (!pHASHContext)
-    {
-        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: HASH_Create() failed");
-        return false;
-    }
-
-    // We have a hash, update it with the byte ranges.
-    size_t nByteRangeOffset = 0;
-    const std::vector<PDFElement*>& rByteRangeElements = pByteRange->GetElements();
-    for (size_t i = 0; i < rByteRangeElements.size(); ++i)
-    {
-        auto pNumber = dynamic_cast<PDFNumberElement*>(rByteRangeElements[i]);
-        if (!pNumber)
-        {
-            SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: signature offset and length has to be a number");
-            return false;
-        }
-
-        if (i % 2 == 0)
-        {
-            nByteRangeOffset = pNumber->GetValue();
-            continue;
-        }
-
-        rStream.Seek(nByteRangeOffset);
-        size_t nByteRangeLength = pNumber->GetValue();
-
-        // And now hash this byte range.
-        const int nChunkLen = 4096;
-        std::vector<unsigned char> aBuffer(nChunkLen);
-        for (size_t nByte = 0; nByte < nByteRangeLength;)
-        {
-            size_t nRemainingSize = nByteRangeLength - nByte;
-            if (nRemainingSize < nChunkLen)
-            {
-                rStream.ReadBytes(aBuffer.data(), nRemainingSize);
-                HASH_Update(pHASHContext, aBuffer.data(), nRemainingSize);
-                nByte = nByteRangeLength;
-            }
-            else
-            {
-                rStream.ReadBytes(aBuffer.data(), nChunkLen);
-                HASH_Update(pHASHContext, aBuffer.data(), nChunkLen);
-                nByte += nChunkLen;
-            }
-        }
-    }
-
-    // Find out what is the expected length of the hash.
-    unsigned int nMaxResultLen = 0;
-    switch (SECOID_FindOIDTag(&aAlgorithm))
-    {
-    case SEC_OID_SHA1:
-        nMaxResultLen = 20;
-        break;
-    default:
-        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: unrecognized algorithm");
-        return false;
-    }
-
-    auto pActualResultBuffer = static_cast<unsigned char*>(PORT_Alloc(nMaxResultLen));
-    unsigned int nActualResultLen;
-    HASH_End(pHASHContext, pActualResultBuffer, &nActualResultLen, nMaxResultLen);
-
-    if (!NSS_CMSSignerInfo_GetSigningCertificate(pCMSSignerInfo, CERT_GetDefaultCertDB()))
-    {
-        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: NSS_CMSSignerInfo_GetSigningCertificate() failed");
-        return false;
-    }
-
-    SECItem* pContentInfoContentData = pCMSSignedData->contentInfo.content.data;
-    if (pContentInfoContentData && pContentInfoContentData->data)
-    {
-        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: expected nullptr content info");
-        return false;
-    }
-
-    SECItem aActualResultItem;
-    aActualResultItem.data = pActualResultBuffer;
-    aActualResultItem.len = nActualResultLen;
-    bDigestMatch = NSS_CMSSignerInfo_Verify(pCMSSignerInfo, &aActualResultItem, nullptr) == SECSuccess;
-
-    // Everything went fine
-    PORT_Free(pActualResultBuffer);
-    HASH_Destroy(pHASHContext);
-    NSS_CMSSignerInfo_Destroy(pCMSSignerInfo);
-    if (NSS_Shutdown() != SECSuccess)
-    {
-        SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ValidateSignature: NSS_Shutdown() failed");
-        return false;
-    }
-
-    return true;
-#else
-    // Not implemented.
-    (void)rStream;
-    (void)bDigestMatch;
-
-    return false;
-#endif
-}
-
-bool PDFCommentElement::Read(SvStream& rStream)
-{
-    // Read from (including) the % char till (excluding) the end of the line.
-    OStringBuffer aBuf;
-    char ch;
-    rStream.ReadChar(ch);
-    while (!rStream.IsEof())
-    {
-        if (ch == 0x0a)
-        {
-            m_aComment = aBuf.makeStringAndClear();
-            SAL_INFO("xmlsecurity.pdfio", "PDFCommentElement::Read: m_aComment is '" << m_aComment << "'");
-            return true;
-        }
-        aBuf.append(ch);
-        rStream.ReadChar(ch);
-    }
-
-    return false;
-}
-
-bool PDFNumberElement::Read(SvStream& rStream)
-{
-    OStringBuffer aBuf;
-    char ch;
-    rStream.ReadChar(ch);
-    while (!rStream.IsEof())
-    {
-        if (!isdigit(ch) && ch != '-')
-        {
-            rStream.SeekRel(-1);
-            m_fValue = aBuf.makeStringAndClear().toDouble();
-            SAL_INFO("xmlsecurity.pdfio", "PDFNumberElement::Read: m_fValue is '" << m_fValue << "'");
-            return true;
-        }
-        aBuf.append(ch);
-        rStream.ReadChar(ch);
-    }
-
-    return false;
-}
-
-PDFBooleanElement::PDFBooleanElement(bool /*bValue*/)
-{
-}
-
-bool PDFBooleanElement::Read(SvStream& /*rStream*/)
-{
-    return true;
-}
-
-bool PDFNullElement::Read(SvStream& /*rStream*/)
-{
-    return true;
-}
-
-bool PDFHexStringElement::Read(SvStream& rStream)
-{
-    char ch;
-    rStream.ReadChar(ch);
-    if (ch != '<')
-    {
-        SAL_INFO("xmlsecurity.pdfio", "PDFHexStringElement::Read: expected '<' as first character");
-        return false;
-    }
-    rStream.ReadChar(ch);
-
-    OStringBuffer aBuf;
-    while (!rStream.IsEof())
-    {
-        if (ch == '>')
-        {
-            m_aValue = aBuf.makeStringAndClear();
-            SAL_INFO("xmlsecurity.pdfio", "PDFHexStringElement::Read: m_aValue length is " << m_aValue.getLength());
-            return true;
-        }
-        aBuf.append(ch);
-        rStream.ReadChar(ch);
-    }
-
-    return false;
-}
-
-const OString& PDFHexStringElement::GetValue() const
-{
-    return m_aValue;
-}
-
-bool PDFLiteralStringElement::Read(SvStream& rStream)
-{
-    char ch;
-    rStream.ReadChar(ch);
-    if (ch != '(')
-    {
-        SAL_INFO("xmlsecurity.pdfio", "PDFHexStringElement::Read: expected '(' as first character");
-        return false;
-    }
-    rStream.ReadChar(ch);
-
-    OStringBuffer aBuf;
-    while (!rStream.IsEof())
-    {
-        if (ch == ')')
-        {
-            m_aValue = aBuf.makeStringAndClear();
-            SAL_INFO("xmlsecurity.pdfio", "PDFLiteralStringElement::Read: m_aValue is '" << m_aValue << "'");
-            return true;
-        }
-        aBuf.append(ch);
-        rStream.ReadChar(ch);
-    }
-
-    return false;
-}
-
-PDFTrailerElement::PDFTrailerElement(PDFDocument& rDoc)
-    : m_rDoc(rDoc)
-{
-}
-
-bool PDFTrailerElement::Read(SvStream& /*rStream*/)
-{
-    return true;
-}
-
-PDFElement* PDFTrailerElement::Lookup(const OString& rDictionaryKey)
-{
-    if (m_aDictionary.empty())
-        PDFDictionaryElement::Parse(m_rDoc.GetElements(), this, m_aDictionary);
-
-    return PDFDictionaryElement::Lookup(m_aDictionary, rDictionaryKey);
-}
-
-
-double PDFNumberElement::GetValue() const
-{
-    return m_fValue;
-}
-
-PDFObjectElement::PDFObjectElement(PDFDocument& rDoc, double fObjectValue, double fGenerationValue)
-    : m_rDoc(rDoc),
-      m_fObjectValue(fObjectValue),
-      m_fGenerationValue(fGenerationValue)
-{
-}
-
-bool PDFObjectElement::Read(SvStream& /*rStream*/)
-{
-    SAL_INFO("xmlsecurity.pdfio", "PDFObjectElement::Read: " << m_fObjectValue << " " << m_fGenerationValue << " obj");
-    return true;
-}
-
-void PDFDictionaryElement::Parse(const std::vector< std::unique_ptr<PDFElement> >& rElements, PDFElement* pThis, std::map<OString, PDFElement*>& rDictionary)
-{
-    if (!rDictionary.empty())
-        return;
-
-    // Find out where the dictionary for this object starts.
-    size_t nIndex = 0;
-    for (size_t i = 0; i < rElements.size(); ++i)
-    {
-        if (rElements[i].get() == pThis)
-        {
-            nIndex = i;
-            break;
-        }
-    }
-
-    OString aName;
-    std::vector<PDFNumberElement*> aNumbers;
-    // The array value we're in -- if any.
-    PDFArrayElement* pArray = nullptr;
-    for (size_t i = nIndex; i < rElements.size(); ++i)
-    {
-        auto pName = dynamic_cast<PDFNameElement*>(rElements[i].get());
-        if (pName)
-        {
-            if (!aNumbers.empty())
-            {
-                rDictionary[aName] = aNumbers.back();
-                aName.clear();
-                aNumbers.clear();
-            }
-
-            if (aName.isEmpty())
-            {
-                // Remember key.
-                aName = pName->GetValue();
-            }
-            else
-            {
-                // Name-name key-value.
-                rDictionary[aName] = pName;
-                aName.clear();
-            }
-            continue;
-        }
-
-        auto pArr = dynamic_cast<PDFArrayElement*>(rElements[i].get());
-        if (pArr)
-        {
-            pArray = pArr;
-            continue;
-        }
-
-        if (pArray && dynamic_cast<PDFEndArrayElement*>(rElements[i].get()))
-        {
-            if (!aNumbers.empty())
-            {
-                for (auto& pNumber : aNumbers)
-                    pArray->PushBack(pNumber);
-                aNumbers.clear();
-            }
-            rDictionary[aName] = pArray;
-            aName.clear();
-            pArray = nullptr;
-            continue;
-        }
-
-        auto pReference = dynamic_cast<PDFReferenceElement*>(rElements[i].get());
-        if (pReference)
-        {
-            if (!pArray)
-            {
-                rDictionary[aName] = pReference;
-                aName.clear();
-            }
-            else
-            {
-                pArray->PushBack(pReference);
-            }
-            aNumbers.clear();
-            continue;
-        }
-
-        auto pLiteralString = dynamic_cast<PDFLiteralStringElement*>(rElements[i].get());
-        if (pLiteralString)
-        {
-            rDictionary[aName] = pLiteralString;
-            aName.clear();
-            continue;
-        }
-
-        auto pHexString = dynamic_cast<PDFHexStringElement*>(rElements[i].get());
-        if (pHexString)
-        {
-            rDictionary[aName] = pHexString;
-            aName.clear();
-            continue;
-        }
-
-        if (dynamic_cast<PDFEndDictionaryElement*>(rElements[i].get()))
-            break;
-
-        if (dynamic_cast<PDFEndObjectElement*>(rElements[i].get()))
-            break;
-
-        // Just remember this, so that in case it's not a reference parameter,
-        // we can handle it later.
-        auto pNumber = dynamic_cast<PDFNumberElement*>(rElements[i].get());
-        if (pNumber)
-            aNumbers.push_back(pNumber);
-    }
-
-    if (!aNumbers.empty())
-    {
-        rDictionary[aName] = aNumbers.back();
-        aName.clear();
-        aNumbers.clear();
-    }
-}
-
-PDFElement* PDFDictionaryElement::Lookup(const std::map<OString, PDFElement*>& rDictionary, const OString& rKey)
-{
-    auto it = rDictionary.find(rKey);
-    if (it == rDictionary.end())
-        return nullptr;
-
-    return it->second;
-}
-
-PDFElement* PDFObjectElement::Lookup(const OString& rDictionaryKey)
-{
-    if (m_aDictionary.empty())
-        PDFDictionaryElement::Parse(m_rDoc.GetElements(), this, m_aDictionary);
-
-    return PDFDictionaryElement::Lookup(m_aDictionary, rDictionaryKey);
-}
-
-PDFObjectElement* PDFObjectElement::LookupObject(const OString& rDictionaryKey)
-{
-    auto pKey = dynamic_cast<PDFReferenceElement*>(Lookup(rDictionaryKey));
-    if (!pKey)
-    {
-        SAL_WARN("xmlsecurity.pdfio", "PDFObjectElement::LookupObject: no such key with reference value: " << rDictionaryKey);
-        return nullptr;
-    }
-
-    return pKey->LookupObject();
-}
-
-double PDFObjectElement::GetObjectValue() const
-{
-    return m_fObjectValue;
-}
-
-double PDFObjectElement::GetGenerationValue() const
-{
-    return m_fGenerationValue;
-}
-
-PDFReferenceElement::PDFReferenceElement(PDFDocument& rDoc, int fObjectValue, int fGenerationValue)
-    : m_rDoc(rDoc),
-      m_fObjectValue(fObjectValue),
-      m_fGenerationValue(fGenerationValue)
-{
-}
-
-bool PDFReferenceElement::Read(SvStream& /*rStream*/)
-{
-    SAL_INFO("xmlsecurity.pdfio", "PDFReferenceElement::Read: " << m_fObjectValue << " " << m_fGenerationValue << " R");
-    return true;
-}
-
-double PDFReferenceElement::LookupNumber(SvStream& rStream) const
-{
-    size_t nOffset = m_rDoc.GetObjectOffset(m_fObjectValue);
-    if (nOffset == 0)
-    {
-        SAL_WARN("xmlsecurity.pdfio", "PDFReferenceElement::LookupNumber: found no offset for object #" << m_fObjectValue);
-        return 0;
-    }
-
-    sal_uInt64 nOrigPos = rStream.Tell();
-    comphelper::ScopeGuard g([&]()
-    {
-        rStream.Seek(nOrigPos);
-    });
-
-    rStream.Seek(nOffset);
-    {
-        PDFDocument::SkipWhitespace(rStream);
-        PDFNumberElement aNumber;
-        bool bRet = aNumber.Read(rStream);
-        if (!bRet || aNumber.GetValue() != m_fObjectValue)
-        {
-            SAL_WARN("xmlsecurity.pdfio", "PDFReferenceElement::LookupNumber: offset points to not matching object");
-            return 0;
-        }
-    }
-
-    {
-        PDFDocument::SkipWhitespace(rStream);
-        PDFNumberElement aNumber;
-        bool bRet = aNumber.Read(rStream);
-        if (!bRet || aNumber.GetValue() != m_fGenerationValue)
-        {
-            SAL_WARN("xmlsecurity.pdfio", "PDFReferenceElement::LookupNumber: offset points to not matching generation");
-            return 0;
-        }
-    }
-
-    {
-        PDFDocument::SkipWhitespace(rStream);
-        OString aKeyword = PDFDocument::ReadKeyword(rStream);
-        if (aKeyword != "obj")
-        {
-            SAL_WARN("xmlsecurity.pdfio", "PDFReferenceElement::LookupNumber: offset doesn't point to an obj keyword");
-            return 0;
-        }
-    }
-
-    PDFDocument::SkipWhitespace(rStream);
-    PDFNumberElement aNumber;
-    if (!aNumber.Read(rStream))
-    {
-        SAL_WARN("xmlsecurity.pdfio", "PDFReferenceElement::LookupNumber: failed to read referenced number");
-        return 0;
-    }
-
-    return aNumber.GetValue();
-}
-
-PDFObjectElement* PDFReferenceElement::LookupObject() const
-{
-    const std::vector< std::unique_ptr<PDFElement> >& rElements = m_rDoc.GetElements();
-    for (const auto& rElement : rElements)
-    {
-        auto* pObjectElement = dynamic_cast<PDFObjectElement*>(rElement.get());
-        if (!pObjectElement)
-            continue;
-
-        if (pObjectElement->GetObjectValue() != m_fObjectValue)
-            continue;
-
-        if (pObjectElement->GetGenerationValue() != m_fGenerationValue)
-            continue;
-
-        return pObjectElement;
-    }
-
-    return nullptr;
-}
-
-bool PDFDictionaryElement::Read(SvStream& rStream)
-{
-    char ch;
-    rStream.ReadChar(ch);
-    if (ch != '<')
-    {
-        SAL_WARN("xmlsecurity.pdfio", "PDFDictionaryElement::Read: unexpected character: " << ch);
-        return false;
-    }
-
-    if (rStream.IsEof())
-    {
-        SAL_WARN("xmlsecurity.pdfio", "PDFDictionaryElement::Read: unexpected end of file");
-        return false;
-    }
-
-    rStream.ReadChar(ch);
-    if (ch != '<')
-    {
-        SAL_WARN("xmlsecurity.pdfio", "PDFDictionaryElement::Read: unexpected character: " << ch);
-        return false;
-    }
-
-    SAL_INFO("xmlsecurity.pdfio", "PDFDictionaryElement::Read: '<<'");
-
-    return true;
-}
-
-bool PDFEndDictionaryElement::Read(SvStream& rStream)
-{
-    char ch;
-    rStream.ReadChar(ch);
-    if (ch != '>')
-    {
-        SAL_WARN("xmlsecurity.pdfio", "PDFEndDictionaryElement::Read: unexpected character: " << ch);
-        return false;
-    }
-
-    if (rStream.IsEof())
-    {
-        SAL_WARN("xmlsecurity.pdfio", "PDFEndDictionaryElement::Read: unexpected end of file");
-        return false;
-    }
-
-    rStream.ReadChar(ch);
-    if (ch != '>')
-    {
-        SAL_WARN("xmlsecurity.pdfio", "PDFEndDictionaryElement::Read: unexpected character: " << ch);
-        return false;
-    }
-
-    SAL_INFO("xmlsecurity.pdfio", "PDFEndDictionaryElement::Read: '>>'");
-
-    return true;
-}
-
-bool PDFNameElement::Read(SvStream& rStream)
-{
-    char ch;
-    rStream.ReadChar(ch);
-    if (ch != '/')
-    {
-        SAL_WARN("xmlsecurity.pdfio", "PDFNameElement::Read: unexpected character: " << ch);
-        return false;
-    }
-
-    if (rStream.IsEof())
-    {
-        SAL_WARN("xmlsecurity.pdfio", "PDFNameElement::Read: unexpected end of file");
-        return false;
-    }
-
-    // Read till the first white-space.
-    OStringBuffer aBuf;
-    rStream.ReadChar(ch);
-    while (!rStream.IsEof())
-    {
-        if (isspace(ch) || ch == '/' || ch == '[' || ch == '<' || ch == '(')
-        {
-            rStream.SeekRel(-1);
-            m_aValue = aBuf.makeStringAndClear();
-            SAL_INFO("xmlsecurity.pdfio", "PDFNameElement::Read: m_aValue is '" << m_aValue << "'");
-            return true;
-        }
-        aBuf.append(ch);
-        rStream.ReadChar(ch);
-    }
-
-    return false;
-}
-
-const OString& PDFNameElement::GetValue() const
-{
-    return m_aValue;
-}
-
-PDFStreamElement::PDFStreamElement(size_t nLength)

... etc. - the rest is truncated


More information about the Libreoffice-commits mailing list