summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMiklos Vajna <vmiklos@collabora.com>2020-06-25 10:58:25 +0200
committerMiklos Vajna <vmiklos@collabora.com>2020-06-25 14:29:54 +0200
commit022f6bb1f7ed06edb126a2b85719d8622104bb57 (patch)
treec646fd43f4d30cba1d45ea117fac4a62273110d8
parent798f8c2efac28ff15a2f7aa5e39c3744756de5b2 (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.hxx4
-rw-r--r--vcl/CppunitTest_vcl_filter_ipdf.mk49
-rw-r--r--vcl/Module_vcl.mk1
-rw-r--r--vcl/qa/cppunit/filter/ipdf/data/add-visible-signature-last-page.pdf111
-rw-r--r--vcl/qa/cppunit/filter/ipdf/ipdf.cxx149
-rw-r--r--vcl/source/filter/ipdf/pdfdocument.cxx22
-rw-r--r--xmlsecurity/source/helper/pdfsignaturehelper.cxx34
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妁
++Rw 2Q#XQ˲sv`7Vܸ$}dFڜTXI+8+]HN+kC"Tta@+"]9ZY Rq 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;1 D{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);