From 1c778781eb99dc9de8bbec439ed05cdabaa8c1bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toma=C5=BE=20Vajngerl?= Date: Thu, 28 Nov 2024 12:31:19 +0900 Subject: [PATCH] pdf: use COSWriter to write literal and unicode strings Change-Id: I72f86ea48fd2d0cdaaca843e90b97e7286c34cdf Reviewed-on: https://gerrit.libreoffice.org/c/core/+/177453 Reviewed-by: Miklos Vajna Tested-by: Jenkins CollaboraOffice --- include/vcl/pdfwriter.hxx | 18 +++ vcl/inc/pdf/COSWriter.hxx | 174 +++++++++++++++++-------- vcl/source/filter/ipdf/pdfdocument.cxx | 2 +- vcl/source/gdi/pdfwriter_impl.cxx | 78 +++++------ 4 files changed, 179 insertions(+), 93 deletions(-) diff --git a/include/vcl/pdfwriter.hxx b/include/vcl/pdfwriter.hxx index aab2ff9acf5c5..e070ec00bd848 100644 --- a/include/vcl/pdfwriter.hxx +++ b/include/vcl/pdfwriter.hxx @@ -74,6 +74,13 @@ class VCL_DLLPUBLIC PDFOutputStream virtual void write( const css::uno::Reference< css::io::XOutputStream >& xStream ) = 0; }; +/** Parameters that are needed when encrypting */ +struct EncryptionParams +{ + bool mbCanEncrypt = false; + std::vector maKey; +}; + /* The following structure describes the permissions used in PDF security */ struct PDFEncryptionProperties { @@ -104,6 +111,8 @@ struct PDFEncryptionProperties std::vector EncryptionKey; std::vector DocumentIdentifier; + std::optional moParameters; + bool canEncrypt() const { return !OValue.empty() && !UValue.empty() && !DocumentIdentifier.empty(); @@ -133,6 +142,15 @@ struct PDFEncryptionProperties return nAccessPermissions; } + + EncryptionParams const& getParams() + { + if (!moParameters) + { + moParameters = EncryptionParams{ canEncrypt(), EncryptionKey }; + } + return *moParameters; + } }; class VCL_DLLPUBLIC PDFWriter diff --git a/vcl/inc/pdf/COSWriter.hxx b/vcl/inc/pdf/COSWriter.hxx index b47dabbe5c7ea..8cd7045f20116 100644 --- a/vcl/inc/pdf/COSWriter.hxx +++ b/vcl/inc/pdf/COSWriter.hxx @@ -10,6 +10,7 @@ #pragma once #include +#include namespace vcl::pdf { @@ -22,7 +23,9 @@ namespace vcl::pdf class COSWriter { std::shared_ptr mpPDFEncryptor; + EncryptionParams maParams; OStringBuffer maLine; + OStringBuffer& mrBuffer; void appendLiteralString(const char* pStr, sal_Int32 nLength) { @@ -31,28 +34,28 @@ class COSWriter switch (*pStr) { case '\n': - maLine.append("\\n"); + mrBuffer.append("\\n"); break; case '\r': - maLine.append("\\r"); + mrBuffer.append("\\r"); break; case '\t': - maLine.append("\\t"); + mrBuffer.append("\\t"); break; case '\b': - maLine.append("\\b"); + mrBuffer.append("\\b"); break; case '\f': - maLine.append("\\f"); + mrBuffer.append("\\f"); break; case '(': case ')': case '\\': - maLine.append("\\"); - maLine.append(static_cast(*pStr)); + mrBuffer.append("\\"); + mrBuffer.append(static_cast(*pStr)); break; default: - maLine.append(static_cast(*pStr)); + mrBuffer.append(static_cast(*pStr)); break; } pStr++; @@ -71,72 +74,107 @@ class COSWriter void appendHexArray(sal_uInt8* pArray, size_t nSize) { for (size_t i = 0; i < nSize; i++) - appendHex(pArray[i], maLine); + appendHex(pArray[i], mrBuffer); } public: - COSWriter(std::shared_ptr const& pPDFEncryptor = nullptr) + COSWriter(EncryptionParams aParams = EncryptionParams(), + std::shared_ptr const& pPDFEncryptor = nullptr) : mpPDFEncryptor(pPDFEncryptor) + , maParams(aParams) , maLine(1024) + , mrBuffer(maLine) + { + } + + COSWriter(OStringBuffer& rBuffer, EncryptionParams aParams = EncryptionParams(), + std::shared_ptr const& pPDFEncryptor = nullptr) + : mpPDFEncryptor(pPDFEncryptor) + , maParams(aParams) + , mrBuffer(rBuffer) { } void startObject(sal_Int32 nObjectID) { - maLine.append(nObjectID); - maLine.append(" 0 obj\n"); + mrBuffer.append(nObjectID); + mrBuffer.append(" 0 obj\n"); } - void endObject() { maLine.append("endobj\n\n"); } + void endObject() { mrBuffer.append("endobj\n\n"); } - OStringBuffer& getLine() { return maLine; } + OStringBuffer& getLine() { return mrBuffer; } - void startDict() { maLine.append("<<"); } - void endDict() { maLine.append(">>\n"); } + void startDict() { mrBuffer.append("<<"); } + void endDict() { mrBuffer.append(">>\n"); } - void startStream() { maLine.append("stream\n"); } - void endStream() { maLine.append("\nendstream\n"); } + void startStream() { mrBuffer.append("stream\n"); } + void endStream() { mrBuffer.append("\nendstream\n"); } void write(std::string_view key, std::string_view value) { - maLine.append(key); - maLine.append(value); + mrBuffer.append(key); + mrBuffer.append(value); } void write(std::string_view key, sal_Int32 value) { - maLine.append(key); - maLine.append(" "); - maLine.append(value); + mrBuffer.append(key); + mrBuffer.append(" "); + mrBuffer.append(value); } - void writeReference(std::string_view key, sal_Int32 nObjectID) + void writeReference(sal_Int32 nObjectID) { - maLine.append(key); - maLine.append(" "); - maLine.append(nObjectID); - maLine.append(" 0 R"); + mrBuffer.append(nObjectID); + mrBuffer.append(" 0 R"); + } + + void writeKeyAndReference(std::string_view key, sal_Int32 nObjectID) + { + mrBuffer.append(key); + mrBuffer.append(" "); + writeReference(nObjectID); } void writeKeyAndUnicode(std::string_view key, OUString const& rString) { - maLine.append(key); - maLine.append("<"); - appendUnicodeTextString(rString, maLine); - maLine.append(">"); + mrBuffer.append(key); + writeUnicode(rString); + } + + void writeUnicode(OUString const& rString) + { + mrBuffer.append("<"); + + mrBuffer.append("FEFF"); + const sal_Unicode* pString = rString.getStr(); + size_t nLength = rString.getLength(); + for (size_t i = 0; i < nLength; i++) + { + sal_Unicode aChar = pString[i]; + appendHex(sal_Int8(aChar >> 8), mrBuffer); + appendHex(sal_Int8(aChar & 255), mrBuffer); + } + + mrBuffer.append(">"); + } + + void writeKeyAndUnicodeEncrypt(std::string_view key, OUString const& rString, sal_Int32 nObject) + { + mrBuffer.append(key); + writeUnicodeEncrypt(rString, nObject); } - void writeKeyAndUnicodeEncrypt(std::string_view key, OUString const& rString, sal_Int32 nObject, - bool bEncrypt, std::vector& rKey) + void writeUnicodeEncrypt(OUString const& rString, sal_Int32 nObject) { - if (bEncrypt && mpPDFEncryptor) + if (maParams.mbCanEncrypt && mpPDFEncryptor) { - maLine.append(key); - maLine.append("<"); + mrBuffer.append("<"); const sal_Unicode* pString = rString.getStr(); size_t nLength = rString.getLength(); //prepare a unicode string, encrypt it - mpPDFEncryptor->setupEncryption(rKey, nObject); + mpPDFEncryptor->setupEncryption(maParams.maKey, nObject); sal_Int32 nChars = 2 + (nLength * 2); std::vector aEncryptionBuffer(nChars); sal_uInt8* pCopy = aEncryptionBuffer.data(); @@ -153,49 +191,75 @@ public: mpPDFEncryptor->encrypt(aEncryptionBuffer.data(), nChars, aNewBuffer, nChars); //now append, hexadecimal (appendHex), the encrypted result appendHexArray(aNewBuffer.data(), aNewBuffer.size()); - maLine.append(">"); + mrBuffer.append(">"); } else { - writeKeyAndUnicode(key, rString); + writeUnicode(rString); } } - void writeKeyAndLiteral(std::string_view key, std::string_view value) + void writeLiteral(std::string_view value) { - maLine.append(key); - maLine.append("("); + mrBuffer.append("("); appendLiteralString(value.data(), value.size()); - maLine.append(")"); + mrBuffer.append(")"); + } + + void writeLiteralEncrypt(std::u16string_view value, sal_Int32 nObject, + rtl_TextEncoding nEncoding = RTL_TEXTENCODING_ASCII_US) + { + OString aBufferString(OUStringToOString(value, nEncoding)); + sal_Int32 nLength = aBufferString.getLength(); + OStringBuffer aBuffer(nLength); + const char* pT = aBufferString.getStr(); + + for (sal_Int32 i = 0; i < nLength; i++, pT++) + { + if ((*pT & 0x80) == 0) + aBuffer.append(*pT); + else + { + aBuffer.append('<'); + appendHex(*pT, aBuffer); + aBuffer.append('>'); + } + } + writeLiteralEncrypt(aBuffer.makeStringAndClear(), nObject); } - void writeKeyAndLiteralEncrypt(std::string_view key, std::string_view value, sal_Int32 nObject, - bool bEncrypt, std::vector& rKey) + void writeLiteralEncrypt(std::string_view value, sal_Int32 nObject) { - if (bEncrypt && mpPDFEncryptor) + if (maParams.mbCanEncrypt && mpPDFEncryptor) { - maLine.append(key); - maLine.append("("); + mrBuffer.append("("); size_t nChars = value.size(); std::vector aEncryptionBuffer(nChars); - mpPDFEncryptor->setupEncryption(rKey, nObject); + mpPDFEncryptor->setupEncryption(maParams.maKey, nObject); mpPDFEncryptor->encrypt(value.data(), nChars, aEncryptionBuffer, nChars); appendLiteralString(reinterpret_cast(aEncryptionBuffer.data()), aEncryptionBuffer.size()); - maLine.append(")"); + mrBuffer.append(")"); } else { - writeKeyAndLiteral(key, value); + writeLiteral(value); } } + void writeKeyAndLiteralEncrypt(std::string_view key, std::string_view value, sal_Int32 nObject) + { + mrBuffer.append(key); + mrBuffer.append(" "); + writeLiteralEncrypt(value, nObject); + } + void writeHexArray(std::string_view key, sal_uInt8* pData, size_t nSize) { - maLine.append(key); - maLine.append(" <"); + mrBuffer.append(key); + mrBuffer.append(" <"); appendHexArray(pData, nSize); - maLine.append(">"); + mrBuffer.append(">"); } static void appendUnicodeTextString(const OUString& rString, OStringBuffer& rBuffer) diff --git a/vcl/source/filter/ipdf/pdfdocument.cxx b/vcl/source/filter/ipdf/pdfdocument.cxx index ff7663db8555e..c376f0c51d788 100644 --- a/vcl/source/filter/ipdf/pdfdocument.cxx +++ b/vcl/source/filter/ipdf/pdfdocument.cxx @@ -169,7 +169,7 @@ sal_Int32 PDFDocument::WriteSignatureObject(svl::crypto::SigningContext& rSignin if (!rDescription.isEmpty()) { - pdf::COSWriter aWriter(nullptr); + pdf::COSWriter aWriter; aWriter.writeKeyAndUnicode("/Reason", rDescription); aSigBuffer.append(aWriter.getLine()); } diff --git a/vcl/source/gdi/pdfwriter_impl.cxx b/vcl/source/gdi/pdfwriter_impl.cxx index 4746b53fdc0f2..7d5d1a01de97c 100644 --- a/vcl/source/gdi/pdfwriter_impl.cxx +++ b/vcl/source/gdi/pdfwriter_impl.cxx @@ -1844,11 +1844,12 @@ sal_Int32 PDFWriterImpl::emitStructIDTree(sal_Int32 const nObject) ids.emplace(GenerateID(n), n); } OStringBuffer buf; + COSWriter aWriter(buf, m_aContext.Encryption.getParams(), m_pPDFEncryptor); appendObjectID(nObject, buf); buf.append("<>"); // ISO 14289-1:2014, Clause: 7.18.6.2 aLine.append("/CT "); - appendLiteralStringEncrypt(rScreen.m_MimeType, rScreen.m_nObject, aLine); + aWriter.writeLiteralEncrypt(rScreen.m_MimeType, rScreen.m_nObject); // ISO 14289-1:2014, Clause: 7.18.6.2 // Alt text is a "Multi-language Text Array" aLine.append(" /Alt [ () "); @@ -3694,6 +3697,7 @@ bool PDFWriterImpl::emitLinkAnnotations() continue; OStringBuffer aLine( 1024 ); + COSWriter aWriter(aLine, m_aContext.Encryption.getParams(), m_pPDFEncryptor); aLine.append( rLink.m_nObject ); aLine.append( " 0 obj\n" ); // i59651: key /F set bits Print to 1 rest to 0. We don't set NoZoom NoRotate to 1, since it's a 'should' @@ -3820,7 +3824,7 @@ we check in the following sequence: { aLine.append( "/Launch/Win<>" ); } else @@ -3870,10 +3874,10 @@ we check in the following sequence: OUString aURLNoMark = aTargetURL.GetURLNoMark( INetURLObject::DecodeMechanism::WithCharset ); aLine.append("/GoToR"); aLine.append("/F"); - appendLiteralStringEncrypt( bSetRelative ? INetURLObject::GetRelURL( m_aContext.BaseURL, aURLNoMark, + aWriter.writeLiteralEncrypt(bSetRelative ? INetURLObject::GetRelURL( m_aContext.BaseURL, aURLNoMark, INetURLObject::EncodeMechanism::WasEncoded, INetURLObject::DecodeMechanism::WithCharset ) : - aURLNoMark, rLink.m_nObject, aLine, osl_getThreadTextEncoding() ); + aURLNoMark, rLink.m_nObject, osl_getThreadTextEncoding()); if( !aFragment.isEmpty() ) { aLine.append("/D/"); @@ -3895,11 +3899,11 @@ we check in the following sequence: OUString aURL = bUnparsedURI ? url : aTargetURL.GetMainURL( bFileSpec ? INetURLObject::DecodeMechanism::WithCharset : INetURLObject::DecodeMechanism::NONE ); - appendLiteralStringEncrypt(bSetRelative ? INetURLObject::GetRelURL( m_aContext.BaseURL, aURL, + aWriter.writeLiteralEncrypt(bSetRelative ? INetURLObject::GetRelURL( m_aContext.BaseURL, aURL, INetURLObject::EncodeMechanism::WasEncoded, bFileSpec ? INetURLObject::DecodeMechanism::WithCharset : INetURLObject::DecodeMechanism::NONE ) : - aURL , rLink.m_nObject, aLine, osl_getThreadTextEncoding() ); + aURL , rLink.m_nObject, osl_getThreadTextEncoding() ); } } aLine.append( ">>\n" ); @@ -4767,6 +4771,7 @@ bool PDFWriterImpl::emitWidgetAnnotations() } OStringBuffer aLine( 1024 ); + COSWriter aWriter(aLine, m_aContext.Encryption.getParams(), m_pPDFEncryptor); OStringBuffer aValue( 256 ); aLine.append( rWidget.m_nObject ); aLine.append( " 0 obj\n" @@ -4894,7 +4899,7 @@ bool PDFWriterImpl::emitWidgetAnnotations() if( !rWidget.m_aName.isEmpty() ) { aLine.append( "/T" ); - appendLiteralStringEncrypt( rWidget.m_aName, rWidget.m_nObject, aLine ); + aWriter.writeLiteralEncrypt(rWidget.m_aName, rWidget.m_nObject); aLine.append( "\n" ); } if (!rWidget.m_aDescription.isEmpty()) @@ -4902,7 +4907,7 @@ bool PDFWriterImpl::emitWidgetAnnotations() // the alternate field name should be unicode able since it is // supposed to be used in UI aLine.append( "/TU" ); - appendUnicodeTextStringEncrypt( rWidget.m_aDescription, rWidget.m_nObject, aLine ); + aWriter.writeUnicodeEncrypt(rWidget.m_aDescription, rWidget.m_nObject); aLine.append( "\n" ); } @@ -4929,7 +4934,7 @@ bool PDFWriterImpl::emitWidgetAnnotations() sal_Int32 i = 0; for (auto const& entry : rWidget.m_aListEntries) { - appendUnicodeTextStringEncrypt( entry, rWidget.m_nObject, aLine ); + aWriter.writeUnicodeEncrypt(entry, rWidget.m_nObject); aLine.append( "\n" ); if( entry == rWidget.m_aValue ) nTI = i; @@ -5038,7 +5043,7 @@ bool PDFWriterImpl::emitWidgetAnnotations() { // create a submit form action aLine.append( "/AA<>>>\n" ); } aLine.append( "/DA" ); - appendLiteralStringEncrypt( rWidget.m_aDAString, rWidget.m_nObject, aLine ); + aWriter.writeLiteralEncrypt(rWidget.m_aDAString, rWidget.m_nObject); aLine.append( "\n" ); if( rWidget.m_nTextStyle & DrawTextFlags::Center ) aLine.append( "/Q 1\n" ); @@ -5103,7 +5108,7 @@ bool PDFWriterImpl::emitWidgetAnnotations() aLine.append( "/MK<<" ); aLine.append( rWidget.m_aMKDict ); //add the CA string, encrypting it - appendLiteralStringEncrypt(rWidget.m_aMKDictCAString, rWidget.m_nObject, aLine); + aWriter.writeLiteralEncrypt(rWidget.m_aMKDictCAString, rWidget.m_nObject); aLine.append( ">>\n" ); } @@ -5270,6 +5275,7 @@ bool PDFWriterImpl::emitCatalog() // emit tree node OStringBuffer aLine( 2048 ); + COSWriter aWriter(aLine, m_aContext.Encryption.getParams(), m_pPDFEncryptor); aLine.append( nTreeNode ); aLine.append( " 0 obj\n" ); aLine.append( "<>\n" @@ -5590,6 +5594,7 @@ bool PDFWriterImpl::emitSignature() return false; OStringBuffer aLine( 0x5000 ); + COSWriter aWriter(aLine, m_aContext.Encryption.getParams(), m_pPDFEncryptor); aLine.append( m_nSignatureObject ); aLine.append( " 0 obj\n" ); aLine.append("<>\nendobj\n\n" ); @@ -5736,9 +5741,7 @@ sal_Int32 PDFWriterImpl::emitInfoDict( ) if (!updateObject(nObject)) return 0; - COSWriter aWriter(m_pPDFEncryptor); - bool bEncrypt = m_aContext.Encryption.canEncrypt(); - auto& rKey = m_aContext.Encryption.EncryptionKey; + COSWriter aWriter(m_aContext.Encryption.getParams(), m_pPDFEncryptor); aWriter.startObject(nObject); aWriter.startDict(); @@ -5748,31 +5751,31 @@ sal_Int32 PDFWriterImpl::emitInfoDict( ) { if (!m_aContext.DocumentInfo.Title.isEmpty()) { - aWriter.writeKeyAndUnicodeEncrypt("/Title", m_aContext.DocumentInfo.Title, nObject, bEncrypt, rKey); + aWriter.writeKeyAndUnicodeEncrypt("/Title", m_aContext.DocumentInfo.Title, nObject); } if (!m_aContext.DocumentInfo.Author.isEmpty()) { - aWriter.writeKeyAndUnicodeEncrypt("/Author", m_aContext.DocumentInfo.Author, nObject, bEncrypt, rKey); + aWriter.writeKeyAndUnicodeEncrypt("/Author", m_aContext.DocumentInfo.Author, nObject); } if (!m_aContext.DocumentInfo.Subject.isEmpty()) { - aWriter.writeKeyAndUnicodeEncrypt("/Subject", m_aContext.DocumentInfo.Subject, nObject, bEncrypt, rKey); + aWriter.writeKeyAndUnicodeEncrypt("/Subject", m_aContext.DocumentInfo.Subject, nObject); } if (!m_aContext.DocumentInfo.Keywords.isEmpty()) { - aWriter.writeKeyAndUnicodeEncrypt("/Keywords", m_aContext.DocumentInfo.Keywords, nObject, bEncrypt, rKey); + aWriter.writeKeyAndUnicodeEncrypt("/Keywords", m_aContext.DocumentInfo.Keywords, nObject); } if (!m_aContext.DocumentInfo.Creator.isEmpty()) { - aWriter.writeKeyAndUnicodeEncrypt("/Creator", m_aContext.DocumentInfo.Creator, nObject, bEncrypt, rKey); + aWriter.writeKeyAndUnicodeEncrypt("/Creator", m_aContext.DocumentInfo.Creator, nObject); } if (!m_aContext.DocumentInfo.Producer.isEmpty()) { - aWriter.writeKeyAndUnicodeEncrypt("/Producer", m_aContext.DocumentInfo.Producer, nObject, bEncrypt, rKey); + aWriter.writeKeyAndUnicodeEncrypt("/Producer", m_aContext.DocumentInfo.Producer, nObject); } } // Allowed in PDF 2.0 - aWriter.writeKeyAndLiteralEncrypt("/CreationDate", m_aCreationDateString, nObject, bEncrypt, rKey); + aWriter.writeKeyAndLiteralEncrypt("/CreationDate", m_aCreationDateString, nObject); aWriter.endDict(); aWriter.endObject(); @@ -5866,6 +5869,7 @@ sal_Int32 PDFWriterImpl::emitOutputIntent() //emit the sRGB standard profile, in ICC format, in a stream, per IEC61966-2.1 OStringBuffer aLine( 1024 ); + COSWriter aWriter(aLine, m_aContext.Encryption.getParams(), m_pPDFEncryptor); sal_Int32 nICCObject = createObject(); sal_Int32 nStreamLengthObject = createObject(); @@ -5926,7 +5930,7 @@ sal_Int32 PDFWriterImpl::emitOutputIntent() aLine.append( " 0 obj\n" "<>\nendobj\n\n" ); @@ -6043,7 +6047,7 @@ sal_Int32 PDFWriterImpl::emitEncrypt() if (updateObject(nObject)) { PDFEncryptionProperties& rProperties = m_aContext.Encryption; - COSWriter aWriter(m_pPDFEncryptor); + COSWriter aWriter(m_aContext.Encryption.getParams(), m_pPDFEncryptor); aWriter.startObject(nObject); aWriter.startDict(); aWriter.write("/Filter", "/Standard");