diff options
-rw-r--r-- | sdext/source/pdfimport/config/pdf_import_filter.xcu | 2 | ||||
-rw-r--r-- | sfx2/source/doc/objserv.cxx | 22 | ||||
-rw-r--r-- | xmlsecurity/Executable_pdfverify.mk | 15 | ||||
-rw-r--r-- | xmlsecurity/Library_xmlsecurity.mk | 15 | ||||
-rw-r--r-- | xmlsecurity/inc/pdfio/pdfdocument.hxx | 70 | ||||
-rw-r--r-- | xmlsecurity/inc/pdfsignaturehelper.hxx | 33 | ||||
-rw-r--r-- | xmlsecurity/source/component/documentdigitalsignatures.cxx | 9 | ||||
-rw-r--r-- | xmlsecurity/source/helper/pdfsignaturehelper.cxx | 65 | ||||
-rw-r--r-- | xmlsecurity/source/pdfio/pdfdocument.cxx | 1461 | ||||
-rw-r--r-- | xmlsecurity/source/pdfio/pdfverify.cxx | 1486 |
10 files changed, 1678 insertions, 1500 deletions
diff --git a/sdext/source/pdfimport/config/pdf_import_filter.xcu b/sdext/source/pdfimport/config/pdf_import_filter.xcu index d454d6f19ddb..3909f9fc1855 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 2ba4ffdbe08f..f6243ccf5cf4 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 c62698f1c4f9..bc08d5620dcc 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 1997010344e9..77368ab39616 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 000000000000..9d072615b599 --- /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 000000000000..fb928340817d --- /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 cfa3d247634f..2fa08a442cd3 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 000000000000..d8e6cd5eb6ac --- /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 000000000000..3bd90db5e478 --- /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 5bf50b602bd2..cbb9a897b705 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) - : 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 - SAL_IMPLEMENT_MAIN_WITH_ARGS(nArgc, pArgv) { if (nArgc < 2) |