diff options
author | Miklos Vajna <vmiklos@collabora.com> | 2020-06-25 10:58:25 +0200 |
---|---|---|
committer | Miklos Vajna <vmiklos@collabora.com> | 2020-06-25 14:29:54 +0200 |
commit | 022f6bb1f7ed06edb126a2b85719d8622104bb57 (patch) | |
tree | c646fd43f4d30cba1d45ea117fac4a62273110d8 | |
parent | 798f8c2efac28ff15a2f7aa5e39c3744756de5b2 (diff) |
sd signature line: place shape on the correct page
PDFDocument::Sign() had this hardcoded to always place the signature
widget on the first page, add a way so that xmlsecurity/ can tell the
pdf signing code to put it on an other page.
This way in case the user created the signature line shape on the Nth
page, it'll end up on the Nth page of the PDF result as well, as
expected.
Change-Id: I63decba98774151e9634ea924c2fed0f7814cb28
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/97045
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
Tested-by: Jenkins
-rw-r--r-- | include/vcl/filter/pdfdocument.hxx | 4 | ||||
-rw-r--r-- | vcl/CppunitTest_vcl_filter_ipdf.mk | 49 | ||||
-rw-r--r-- | vcl/Module_vcl.mk | 1 | ||||
-rw-r--r-- | vcl/qa/cppunit/filter/ipdf/data/add-visible-signature-last-page.pdf | 111 | ||||
-rw-r--r-- | vcl/qa/cppunit/filter/ipdf/ipdf.cxx | 149 | ||||
-rw-r--r-- | vcl/source/filter/ipdf/pdfdocument.cxx | 22 | ||||
-rw-r--r-- | xmlsecurity/source/helper/pdfsignaturehelper.cxx | 34 |
7 files changed, 363 insertions, 7 deletions
diff --git a/include/vcl/filter/pdfdocument.hxx b/include/vcl/filter/pdfdocument.hxx index 70ca731d1d19..8fbd811a0429 100644 --- a/include/vcl/filter/pdfdocument.hxx +++ b/include/vcl/filter/pdfdocument.hxx @@ -353,6 +353,9 @@ class VCL_DLLPUBLIC PDFDocument : public PDFObjectContainer /// Signature line in PDF format, to be consumed by the next Sign() invocation. std::vector<sal_Int8> m_aSignatureLine; + /// 0-based page number where m_aSignatureLine should be placed. + size_t m_nSignaturePage = 0; + /// Suggest a minimal, yet free signature ID to use for the next signature. sal_uInt32 GetNextSignature(); /// Write the signature object as part of signing. @@ -409,6 +412,7 @@ public: /// Read elements from the start of the stream till its end. bool Read(SvStream& rStream); void SetSignatureLine(const std::vector<sal_Int8>& rSignatureLine); + void SetSignaturePage(size_t nPage); /// Sign the read document with xCertificate in the edit buffer. bool Sign(const css::uno::Reference<css::security::XCertificate>& xCertificate, const OUString& rDescription, bool bAdES); diff --git a/vcl/CppunitTest_vcl_filter_ipdf.mk b/vcl/CppunitTest_vcl_filter_ipdf.mk new file mode 100644 index 000000000000..403836ac781a --- /dev/null +++ b/vcl/CppunitTest_vcl_filter_ipdf.mk @@ -0,0 +1,49 @@ +# -*- Mode: makefile-gmake; tab-width: 4; indent-tabs-mode: t -*- +#************************************************************************* +# +# 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/. +# +#************************************************************************* + +$(eval $(call gb_CppunitTest_CppunitTest,vcl_filter_ipdf)) + +$(eval $(call gb_CppunitTest_use_externals,vcl_filter_ipdf,\ + boost_headers \ + pdfium \ +)) + +$(eval $(call gb_CppunitTest_add_exception_objects,vcl_filter_ipdf, \ + vcl/qa/cppunit/filter/ipdf/ipdf \ +)) + +$(eval $(call gb_CppunitTest_use_libraries,vcl_filter_ipdf, \ + comphelper \ + cppu \ + sal \ + sfx \ + svx \ + test \ + tl \ + unotest \ + utl \ + vcl \ +)) + +$(eval $(call gb_CppunitTest_use_sdk_api,vcl_filter_ipdf)) + +$(eval $(call gb_CppunitTest_use_ure,vcl_filter_ipdf)) +$(eval $(call gb_CppunitTest_use_vcl,vcl_filter_ipdf)) + +$(eval $(call gb_CppunitTest_use_rdb,vcl_filter_ipdf,services)) + +$(eval $(call gb_CppunitTest_use_custom_headers,vcl_filter_ipdf,\ + officecfg/registry \ +)) + +$(eval $(call gb_CppunitTest_use_configuration,vcl_filter_ipdf)) + +# vim: set noet sw=4 ts=4: diff --git a/vcl/Module_vcl.mk b/vcl/Module_vcl.mk index 08704e2b6254..31bfa0f65cf6 100644 --- a/vcl/Module_vcl.mk +++ b/vcl/Module_vcl.mk @@ -249,6 +249,7 @@ endif ifneq (,$(filter PDFIUM,$(BUILD_TYPE))) $(eval $(call gb_Module_add_slowcheck_targets,vcl,\ CppunitTest_vcl_pdfexport \ + CppunitTest_vcl_filter_ipdf \ )) endif diff --git a/vcl/qa/cppunit/filter/ipdf/data/add-visible-signature-last-page.pdf b/vcl/qa/cppunit/filter/ipdf/data/add-visible-signature-last-page.pdf new file mode 100644 index 000000000000..c321abd0959e --- /dev/null +++ b/vcl/qa/cppunit/filter/ipdf/data/add-visible-signature-last-page.pdf @@ -0,0 +1,111 @@ +%PDF-1.6 +%äüöß +2 0 obj +<</Length 3 0 R/Filter/FlateDecode>> +stream +xTn1]K/I8Mg\\PԐnf2eڨpBIB2~#QwDɶƿ#gzKkMkRؼ7妁 ++Rw2Q#XQ˲sv`7Vܸ$}dFڜTXI+8+]HN+kC"Tta@+"]9ZYRq a!O<ҫ +d\)WiKO.H/\$ώga-/w- !9cDc +ΩPqe:/ tqi08{{N10\N9UҸAoݵӏZ/_b7F߾_a_t{!J@ACjl4#}ԍH뭷Gc;2?cQ@ +endstream +endobj + +3 0 obj +426 +endobj + +5 0 obj +<</Length 6 0 R/Filter/FlateDecode>> +stream +x;1D{qr$ZfO#Q#+r&x #Y Z}>a;zg6 +xn:d8
aQ^D,;7#M3qĵ1Ɛ$}5+'pΏ5y^qYW]ٶuL#>"B +endstream +endobj + +6 0 obj +167 +endobj + +8 0 obj +<< +>> +endobj + +9 0 obj +<</Font 8 0 R +/ProcSet[/PDF/Text] +>> +endobj + +1 0 obj +<</Type/Page/Parent 7 0 R/Resources 9 0 R/MediaBox[0 0 612 792]/Group<</S/Transparency/CS/DeviceRGB/I true>>/Contents 2 0 R>> +endobj + +4 0 obj +<</Type/Page/Parent 7 0 R/Resources 9 0 R/MediaBox[0 0 612 792]/Group<</S/Transparency/CS/DeviceRGB/I true>>/Contents 5 0 R>> +endobj + +10 0 obj +<</Count 2/First 11 0 R/Last 12 0 R +>> +endobj + +11 0 obj +<</Count 0/Title<FEFF005000610067006500200031> +/Dest[1 0 R/XYZ 0 792 0]/Parent 10 0 R/Next 12 0 R>> +endobj + +12 0 obj +<</Count 0/Title<FEFF005000610067006500200032> +/Dest[4 0 R/XYZ 0 792 0]/Parent 10 0 R/Prev 11 0 R>> +endobj + +7 0 obj +<</Type/Pages +/Resources 9 0 R +/MediaBox[ 0 0 612 792 ] +/Kids[ 1 0 R 4 0 R ] +/Count 2>> +endobj + +13 0 obj +<</Type/Catalog/Pages 7 0 R +/OpenAction[1 0 R /XYZ null null 0] +/Outlines 10 0 R +>> +endobj + +14 0 obj +<</Author<FEFF004D0069006B006C006F0073002000560061006A006E0061> +/Creator<FEFF0044007200610077> +/Producer<FEFF004C0069006200720065004F0066006600690063006500440065007600200037002E0031> +/CreationDate(D:20200624113559+02'00')>> +endobj + +xref +0 15 +0000000000 65535 f +0000000869 00000 n +0000000019 00000 n +0000000516 00000 n +0000001011 00000 n +0000000536 00000 n +0000000774 00000 n +0000001443 00000 n +0000000794 00000 n +0000000816 00000 n +0000001153 00000 n +0000001209 00000 n +0000001326 00000 n +0000001547 00000 n +0000001648 00000 n +trailer +<</Size 15/Root 13 0 R +/Info 14 0 R +/ID [ <F52D3902B7388C216897409EFCC78884> +<F52D3902B7388C216897409EFCC78884> ] +/DocChecksum /67E881EB92900640250E0931504CE95E +>> +startxref +1889 +%%EOF diff --git a/vcl/qa/cppunit/filter/ipdf/ipdf.cxx b/vcl/qa/cppunit/filter/ipdf/ipdf.cxx new file mode 100644 index 000000000000..5d44cf9bbef2 --- /dev/null +++ b/vcl/qa/cppunit/filter/ipdf/ipdf.cxx @@ -0,0 +1,149 @@ +/* -*- 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 <test/bootstrapfixture.hxx> +#include <unotest/macros_test.hxx> + +#include <prewin.h> +#include <cpp/fpdf_scopers.h> +#include <postwin.h> + +#include <com/sun/star/drawing/XDrawPagesSupplier.hpp> +#include <com/sun/star/drawing/XDrawView.hpp> +#include <com/sun/star/frame/Desktop.hpp> +#include <com/sun/star/security/XCertificate.hpp> +#include <com/sun/star/view/XSelectionSupplier.hpp> +#include <com/sun/star/xml/crypto/SEInitializer.hpp> + +#include <comphelper/propertyvalue.hxx> +#include <osl/file.hxx> +#include <unotools/tempfile.hxx> +#include <sfx2/sfxbasemodel.hxx> +#include <svx/svdview.hxx> +#include <sfx2/viewsh.hxx> +#include <svx/signaturelinehelper.hxx> +#include <sfx2/objsh.hxx> +#include <vcl/filter/PDFiumLibrary.hxx> + +using namespace ::com::sun::star; + +namespace +{ +char const DATA_DIRECTORY[] = "/vcl/qa/cppunit/filter/ipdf/data/"; +} + +/// Covers vcl/source/filter/ipdf/ fixes. +class VclFilterIpdfTest : public test::BootstrapFixture, public unotest::MacrosTest +{ +private: + uno::Reference<lang::XComponent> mxComponent; + uno::Reference<xml::crypto::XSEInitializer> mxSEInitializer; + uno::Reference<xml::crypto::XXMLSecurityContext> mxSecurityContext; + +public: + void setUp() override; + void tearDown() override; + uno::Reference<lang::XComponent>& getComponent() { return mxComponent; } + uno::Reference<xml::crypto::XXMLSecurityContext>& getSecurityContext() + { + return mxSecurityContext; + } +}; + +void VclFilterIpdfTest::setUp() +{ + test::BootstrapFixture::setUp(); + MacrosTest::setUpNssGpg(m_directories, "vcl_filter_ipdf"); + + mxDesktop.set(frame::Desktop::create(mxComponentContext)); + mxSEInitializer = xml::crypto::SEInitializer::create(mxComponentContext); + mxSecurityContext = mxSEInitializer->createSecurityContext(OUString()); +} + +void VclFilterIpdfTest::tearDown() +{ + if (mxComponent.is()) + mxComponent->dispose(); + + MacrosTest::tearDownNssGpg(); + test::BootstrapFixture::tearDown(); +} + +CPPUNIT_TEST_FIXTURE(VclFilterIpdfTest, testPDFAddVisibleSignatureLastPage) +{ + // Given: copy the test document to a temporary file, as it'll be modified. + utl::TempFile aTempFile; + aTempFile.EnableKillingFile(); + OUString aSourceURL + = m_directories.getURLFromSrc(DATA_DIRECTORY) + "add-visible-signature-last-page.pdf"; + OUString aURL = aTempFile.GetURL(); + osl::File::RC eRet = osl::File::copy(aSourceURL, aURL); + CPPUNIT_ASSERT_EQUAL(osl::File::RC::E_None, eRet); + + // Open it. + uno::Sequence<beans::PropertyValue> aArgs = { comphelper::makePropertyValue("ReadOnly", true) }; + getComponent() = loadFromDesktop(aURL, "com.sun.star.drawing.DrawingDocument", aArgs); + SfxBaseModel* pBaseModel = dynamic_cast<SfxBaseModel*>(getComponent().get()); + CPPUNIT_ASSERT(pBaseModel); + SfxObjectShell* pObjectShell = pBaseModel->GetObjectShell(); + CPPUNIT_ASSERT(pObjectShell); + + // Add a signature line to the 2nd page. + uno::Reference<lang::XMultiServiceFactory> xFactory(getComponent(), uno::UNO_QUERY); + uno::Reference<drawing::XShape> xShape( + xFactory->createInstance("com.sun.star.drawing.GraphicObjectShape"), uno::UNO_QUERY); + xShape->setPosition(awt::Point(1000, 15000)); + xShape->setSize(awt::Size(10000, 10000)); + uno::Reference<drawing::XDrawPagesSupplier> xSupplier(getComponent(), uno::UNO_QUERY); + uno::Reference<drawing::XDrawPages> xDrawPages = xSupplier->getDrawPages(); + CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(2), xDrawPages->getCount()); + + uno::Reference<frame::XModel> xModel(getComponent(), uno::UNO_QUERY); + uno::Reference<drawing::XDrawView> xController(xModel->getCurrentController(), uno::UNO_QUERY); + uno::Reference<drawing::XDrawPage> xDrawPage(xDrawPages->getByIndex(1), uno::UNO_QUERY); + xController->setCurrentPage(xDrawPage); + xDrawPage->add(xShape); + + // Select it and assign a certificate. + uno::Reference<view::XSelectionSupplier> xSelectionSupplier(pBaseModel->getCurrentController(), + uno::UNO_QUERY); + xSelectionSupplier->select(uno::makeAny(xShape)); + uno::Sequence<uno::Reference<security::XCertificate>> aCertificates + = getSecurityContext()->getSecurityEnvironment()->getPersonalCertificates(); + if (!aCertificates.hasElements()) + { + return; + } + SdrView* pView = SfxViewShell::Current()->GetDrawView(); + svx::SignatureLineHelper::setShapeCertificate(pView, aCertificates[0]); + + // When: do the actual signing. + pObjectShell->SignDocumentContentUsingCertificate(aCertificates[0]); + + // Then: count the # of shapes on the signature widget/annotation. + std::shared_ptr<vcl::pdf::PDFium> pPDFium = vcl::pdf::PDFiumLibrary::get(); + SvFileStream aFile(aTempFile.GetURL(), StreamMode::READ); + SvMemoryStream aMemory; + aMemory.WriteStream(aFile); + // Last page. + ScopedFPDFDocument pPdfDocument( + FPDF_LoadMemDocument(aMemory.GetData(), aMemory.GetSize(), /*password=*/nullptr)); + ScopedFPDFPage pPdfPage(FPDF_LoadPage(pPdfDocument.get(), /*page_index=*/1)); + // Without the accompanying fix in place, this test would have failed with: + // - Expected: 1 + // - Actual : 0 + // i.e. the signature was there, but it was on the first page. + CPPUNIT_ASSERT_EQUAL(1, FPDFPage_GetAnnotCount(pPdfPage.get())); + ScopedFPDFAnnotation pAnnot(FPDFPage_GetAnnot(pPdfPage.get(), 0)); + CPPUNIT_ASSERT_EQUAL(4, FPDFAnnot_GetObjectCount(pAnnot.get())); +} + +CPPUNIT_PLUGIN_IMPLEMENT(); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/vcl/source/filter/ipdf/pdfdocument.cxx b/vcl/source/filter/ipdf/pdfdocument.cxx index 06324f22b4fd..541ec2b9a738 100644 --- a/vcl/source/filter/ipdf/pdfdocument.cxx +++ b/vcl/source/filter/ipdf/pdfdocument.cxx @@ -189,6 +189,8 @@ void PDFDocument::SetSignatureLine(const std::vector<sal_Int8>& rSignatureLine) m_aSignatureLine = rSignatureLine; } +void PDFDocument::SetSignaturePage(size_t nPage) { m_nSignaturePage = nPage; } + sal_uInt32 PDFDocument::GetNextSignature() { sal_uInt32 nRet = 0; @@ -960,17 +962,27 @@ bool PDFDocument::Sign(const uno::Reference<security::XCertificate>& xCertificat sal_Int32 nAppearanceId = WriteAppearanceObject(aSignatureRectangle); std::vector<PDFObjectElement*> aPages = GetPages(); - if (aPages.empty() || !aPages[0]) + if (aPages.empty()) { SAL_WARN("vcl.filter", "PDFDocument::Sign: found no pages"); return false; } - PDFObjectElement& rFirstPage = *aPages[0]; - sal_Int32 nAnnotId - = WriteAnnotObject(rFirstPage, nSignatureId, nAppearanceId, aSignatureRectangle); + size_t nPage = 0; + if (m_nSignaturePage < aPages.size()) + { + nPage = m_nSignaturePage; + } + if (!aPages[nPage]) + { + SAL_WARN("vcl.filter", "PDFDocument::Sign: failed to find page #" << nPage); + return false; + } + + PDFObjectElement& rPage = *aPages[nPage]; + sal_Int32 nAnnotId = WriteAnnotObject(rPage, nSignatureId, nAppearanceId, aSignatureRectangle); - if (!WritePageObject(rFirstPage, nAnnotId)) + if (!WritePageObject(rPage, nAnnotId)) { SAL_WARN("vcl.filter", "PDFDocument::Sign: failed to write the updated Page object"); return false; diff --git a/xmlsecurity/source/helper/pdfsignaturehelper.cxx b/xmlsecurity/source/helper/pdfsignaturehelper.cxx index beb5b7e800bb..79979c715bff 100644 --- a/xmlsecurity/source/helper/pdfsignaturehelper.cxx +++ b/xmlsecurity/source/helper/pdfsignaturehelper.cxx @@ -21,6 +21,7 @@ #include <com/sun/star/frame/XModel.hpp> #include <com/sun/star/frame/XStorable.hpp> #include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/drawing/XDrawView.hpp> #include <comphelper/propertysequence.hxx> #include <sal/log.hxx> @@ -37,8 +38,26 @@ using namespace ::com::sun::star; namespace { +/// Gets the current page of the current view from xModel and puts it to the 1-based rPage. +bool GetSignatureLinePage(const uno::Reference<frame::XModel>& xModel, sal_Int32& rPage) +{ + uno::Reference<drawing::XDrawView> xController(xModel->getCurrentController(), uno::UNO_QUERY); + if (!xController.is()) + { + return false; + } + + uno::Reference<beans::XPropertySet> xPage(xController->getCurrentPage(), uno::UNO_QUERY); + if (!xPage.is()) + { + return false; + } + + return xPage->getPropertyValue("Number") >>= rPage; +} + /// If the currently selected shape is a Draw signature line, export that to PDF. -void GetSignatureLineShape(std::vector<sal_Int8>& rSignatureLineShape) +void GetSignatureLineShape(sal_Int32& rPage, std::vector<sal_Int8>& rSignatureLineShape) { SfxObjectShell* pObjectShell = SfxObjectShell::Current(); if (!pObjectShell) @@ -52,6 +71,11 @@ void GetSignatureLineShape(std::vector<sal_Int8>& rSignatureLineShape) return; } + if (!GetSignatureLinePage(xModel, rPage)) + { + return; + } + uno::Reference<drawing::XShapes> xShapes(xModel->getCurrentSelection(), uno::UNO_QUERY); if (!xShapes.is() || xShapes->getCount() < 1) { @@ -200,8 +224,14 @@ bool PDFSignatureHelper::Sign(const uno::Reference<io::XInputStream>& xInputStre return false; } + sal_Int32 nPage = 0; std::vector<sal_Int8> aSignatureLineShape; - GetSignatureLineShape(aSignatureLineShape); + GetSignatureLineShape(nPage, aSignatureLineShape); + if (nPage > 0) + { + // UNO page number is 1-based. + aDocument.SetSignaturePage(nPage - 1); + } if (!aSignatureLineShape.empty()) { aDocument.SetSignatureLine(aSignatureLineShape); |