xmlsecurity/source/pdfio/pdfdocument.cxx | 380 +++++++++++++++++++++++-------- 1 file changed, 289 insertions(+), 91 deletions(-)
New commits: commit 2a7e39eac2f44ad48455c8a5c04242b1fc92c726 Author: Miklos Vajna <vmik...@collabora.co.uk> Date: Wed Nov 9 15:49:35 2016 +0100 xmlsecurity PDF sign: conditionally write xref stream In case the input document used a PDF 1.5 xref stream, not an old xref table, then write that as part of the incremental update. Acrobat seems to require this. Change-Id: I9f1f73140c26308f8720aa1ffe1b905d0e60ede0 Reviewed-on: https://gerrit.libreoffice.org/30724 Tested-by: Jenkins <c...@libreoffice.org> Reviewed-by: Miklos Vajna <vmik...@collabora.co.uk> diff --git a/xmlsecurity/source/pdfio/pdfdocument.cxx b/xmlsecurity/source/pdfio/pdfdocument.cxx index 3b90bf3..8bd942f 100644 --- a/xmlsecurity/source/pdfio/pdfdocument.cxx +++ b/xmlsecurity/source/pdfio/pdfdocument.cxx @@ -667,80 +667,197 @@ bool PDFDocument::Sign(const uno::Reference<security::XCertificate>& xCertificat m_aEditBuffer.WriteCharPtr(">>\nendobj\n\n"); } - // Write the xref table. sal_uInt64 nXRefOffset = m_aEditBuffer.Tell(); - m_aEditBuffer.WriteCharPtr("xref\n"); - for (const auto& rXRef : m_aXRef) - { - size_t nObject = rXRef.first; - size_t nOffset = rXRef.second.m_nOffset; - if (!rXRef.second.m_bDirty) - continue; - - m_aEditBuffer.WriteUInt32AsString(nObject); - m_aEditBuffer.WriteCharPtr(" 1\n"); - OStringBuffer aBuffer; - aBuffer.append(static_cast<sal_Int32>(nOffset)); - while (aBuffer.getLength() < 10) - aBuffer.insert(0, "0"); - if (nObject == 0) - aBuffer.append(" 65535 f \n"); - else - aBuffer.append(" 00000 n \n"); - m_aEditBuffer.WriteOString(aBuffer.toString()); - } - - // Write the trailer. - m_aEditBuffer.WriteCharPtr("trailer\n<</Size "); - m_aEditBuffer.WriteUInt32AsString(m_aXRef.size()); - m_aEditBuffer.WriteCharPtr("/Root "); - m_aEditBuffer.WriteUInt32AsString(pRoot->GetObjectValue()); - m_aEditBuffer.WriteCharPtr(" "); - m_aEditBuffer.WriteUInt32AsString(pRoot->GetGenerationValue()); - m_aEditBuffer.WriteCharPtr(" R\n"); - PDFReferenceElement* pInfo = nullptr; if (m_pXRefStream) - pInfo = dynamic_cast<PDFReferenceElement*>(m_pXRefStream->Lookup("Info")); - else - pInfo = dynamic_cast<PDFReferenceElement*>(m_pTrailer->Lookup("Info")); - if (pInfo) { - m_aEditBuffer.WriteCharPtr("/Info "); - m_aEditBuffer.WriteUInt32AsString(pInfo->GetObjectValue()); + // Write the xref stream. + // This is a bit meta: the xref stream stores its own offset. + sal_Int32 nXRefStreamId = m_aXRef.size(); + XRefEntry aXRefStreamEntry; + aXRefStreamEntry.m_nOffset = nXRefOffset; + aXRefStreamEntry.m_bDirty = true; + m_aXRef[nXRefStreamId] = aXRefStreamEntry; + + // Write stream data. + SvMemoryStream aXRefStream; + for (const auto& rXRef : m_aXRef) + { + const XRefEntry& rEntry = rXRef.second; + + if (!rEntry.m_bDirty) + continue; + + // First field. + unsigned char nType = 0; + switch (rEntry.m_eType) + { + case XRefEntryType::FREE: + nType = 0; + break; + case XRefEntryType::NOT_COMPRESSED: + nType = 1; + break; + case XRefEntryType::COMPRESSED: + nType = 2; + break; + } + aXRefStream.WriteUChar(nType); + + // Second field. + const size_t nOffsetLen = 3; + for (size_t i = 0; i < nOffsetLen; ++i) + { + size_t nByte = nOffsetLen - i - 1; + // Fields requiring more than one byte are stored with the + // high-order byte first. + unsigned char nCh = (rEntry.m_nOffset & (0xff << (nByte * 8))) >> (nByte * 8); + aXRefStream.WriteUChar(nCh); + } + + // Third field. + aXRefStream.WriteUChar(0); + } + + m_aEditBuffer.WriteUInt32AsString(nXRefStreamId); + m_aEditBuffer.WriteCharPtr(" 0 obj\n<<"); + + // ID. + auto pID = dynamic_cast<PDFArrayElement*>(m_pXRefStream->Lookup("ID")); + if (pID) + { + const std::vector<PDFElement*>& rElements = pID->GetElements(); + m_aEditBuffer.WriteCharPtr("/ID [ <"); + for (size_t i = 0; i < rElements.size(); ++i) + { + auto pIDString = dynamic_cast<PDFHexStringElement*>(rElements[i]); + if (!pIDString) + continue; + + m_aEditBuffer.WriteOString(pIDString->GetValue()); + if ((i + 1) < rElements.size()) + m_aEditBuffer.WriteCharPtr("> <"); + } + m_aEditBuffer.WriteCharPtr("> ] "); + } + + // Index. + m_aEditBuffer.WriteCharPtr("/Index [ "); + for (const auto& rXRef : m_aXRef) + { + if (!rXRef.second.m_bDirty) + continue; + + m_aEditBuffer.WriteUInt32AsString(rXRef.first); + m_aEditBuffer.WriteCharPtr(" 1 "); + } + m_aEditBuffer.WriteCharPtr("] "); + + // Info. + auto pInfo = dynamic_cast<PDFReferenceElement*>(m_pXRefStream->Lookup("Info")); + if (pInfo) + { + m_aEditBuffer.WriteCharPtr("/Info "); + m_aEditBuffer.WriteUInt32AsString(pInfo->GetObjectValue()); + m_aEditBuffer.WriteCharPtr(" "); + m_aEditBuffer.WriteUInt32AsString(pInfo->GetGenerationValue()); + m_aEditBuffer.WriteCharPtr(" R "); + } + + // Length. + m_aEditBuffer.WriteCharPtr("/Length "); + m_aEditBuffer.WriteUInt32AsString(aXRefStream.GetSize()); + + if (!m_aStartXRefs.empty()) + { + // Write location of the previous cross-reference section. + m_aEditBuffer.WriteCharPtr("/Prev "); + m_aEditBuffer.WriteUInt32AsString(m_aStartXRefs.back()); + } + + // Root. + m_aEditBuffer.WriteCharPtr("/Root "); + m_aEditBuffer.WriteUInt32AsString(pRoot->GetObjectValue()); m_aEditBuffer.WriteCharPtr(" "); - m_aEditBuffer.WriteUInt32AsString(pInfo->GetGenerationValue()); - m_aEditBuffer.WriteCharPtr(" R\n"); + m_aEditBuffer.WriteUInt32AsString(pRoot->GetGenerationValue()); + m_aEditBuffer.WriteCharPtr(" R "); + + // Size. + m_aEditBuffer.WriteCharPtr("/Size "); + m_aEditBuffer.WriteUInt32AsString(m_aXRef.size()); + + m_aEditBuffer.WriteCharPtr("/Type/XRef/W[1 3 1]>>\nstream\n"); + aXRefStream.Seek(0); + m_aEditBuffer.WriteStream(aXRefStream); + m_aEditBuffer.WriteCharPtr("\nendstream\nendobj\n\n"); } - PDFArrayElement* pID = nullptr; - if (m_pXRefStream) - pID = dynamic_cast<PDFArrayElement*>(m_pXRefStream->Lookup("ID")); else - pID = dynamic_cast<PDFArrayElement*>(m_pTrailer->Lookup("ID")); - if (pID) { - const std::vector<PDFElement*>& rElements = pID->GetElements(); - m_aEditBuffer.WriteCharPtr("/ID [ <"); - for (size_t i = 0; i < rElements.size(); ++i) + // Write the xref table. + m_aEditBuffer.WriteCharPtr("xref\n"); + for (const auto& rXRef : m_aXRef) { - auto pIDString = dynamic_cast<PDFHexStringElement*>(rElements[i]); - if (!pIDString) + size_t nObject = rXRef.first; + size_t nOffset = rXRef.second.m_nOffset; + if (!rXRef.second.m_bDirty) continue; - m_aEditBuffer.WriteOString(pIDString->GetValue()); - if ((i + 1) < rElements.size()) - m_aEditBuffer.WriteCharPtr(">\n<"); + m_aEditBuffer.WriteUInt32AsString(nObject); + m_aEditBuffer.WriteCharPtr(" 1\n"); + OStringBuffer aBuffer; + aBuffer.append(static_cast<sal_Int32>(nOffset)); + while (aBuffer.getLength() < 10) + aBuffer.insert(0, "0"); + if (nObject == 0) + aBuffer.append(" 65535 f \n"); + else + aBuffer.append(" 00000 n \n"); + m_aEditBuffer.WriteOString(aBuffer.toString()); } - m_aEditBuffer.WriteCharPtr("> ]\n"); - } - if (!m_aStartXRefs.empty()) - { - // Write location of the previous cross-reference section. - m_aEditBuffer.WriteCharPtr("/Prev "); - m_aEditBuffer.WriteUInt32AsString(m_aStartXRefs.back()); - } + // Write the trailer. + m_aEditBuffer.WriteCharPtr("trailer\n<</Size "); + m_aEditBuffer.WriteUInt32AsString(m_aXRef.size()); + m_aEditBuffer.WriteCharPtr("/Root "); + m_aEditBuffer.WriteUInt32AsString(pRoot->GetObjectValue()); + m_aEditBuffer.WriteCharPtr(" "); + m_aEditBuffer.WriteUInt32AsString(pRoot->GetGenerationValue()); + m_aEditBuffer.WriteCharPtr(" R\n"); + auto pInfo = dynamic_cast<PDFReferenceElement*>(m_pTrailer->Lookup("Info")); + if (pInfo) + { + m_aEditBuffer.WriteCharPtr("/Info "); + m_aEditBuffer.WriteUInt32AsString(pInfo->GetObjectValue()); + m_aEditBuffer.WriteCharPtr(" "); + m_aEditBuffer.WriteUInt32AsString(pInfo->GetGenerationValue()); + m_aEditBuffer.WriteCharPtr(" R\n"); + } + auto pID = dynamic_cast<PDFArrayElement*>(m_pTrailer->Lookup("ID")); + if (pID) + { + const std::vector<PDFElement*>& rElements = pID->GetElements(); + m_aEditBuffer.WriteCharPtr("/ID [ <"); + for (size_t i = 0; i < rElements.size(); ++i) + { + auto pIDString = dynamic_cast<PDFHexStringElement*>(rElements[i]); + if (!pIDString) + continue; - m_aEditBuffer.WriteCharPtr(">>\n"); + m_aEditBuffer.WriteOString(pIDString->GetValue()); + if ((i + 1) < rElements.size()) + m_aEditBuffer.WriteCharPtr(">\n<"); + } + m_aEditBuffer.WriteCharPtr("> ]\n"); + } + + if (!m_aStartXRefs.empty()) + { + // Write location of the previous cross-reference section. + m_aEditBuffer.WriteCharPtr("/Prev "); + m_aEditBuffer.WriteUInt32AsString(m_aStartXRefs.back()); + } + + m_aEditBuffer.WriteCharPtr(">>\n"); + } // Write startxref. m_aEditBuffer.WriteCharPtr("startxref\n"); commit 05ad6dfd4e7201793a6350b440173e4a6335c776 Author: Miklos Vajna <vmik...@collabora.co.uk> Date: Wed Nov 9 14:19:05 2016 +0100 xmlsecurity PDF sign: handle when Catalog's AcroForm is an indirect dictionary Normally it's a direct dictionary, but it's OK to have it as a reference, and then the referenced object is a dictionary. Change-Id: If09edaf23501883be68148e430c42e721ec68247 Reviewed-on: https://gerrit.libreoffice.org/30719 Tested-by: Jenkins <c...@libreoffice.org> Reviewed-by: Miklos Vajna <vmik...@collabora.co.uk> diff --git a/xmlsecurity/source/pdfio/pdfdocument.cxx b/xmlsecurity/source/pdfio/pdfdocument.cxx index 8c01cd9..3b90bf3 100644 --- a/xmlsecurity/source/pdfio/pdfdocument.cxx +++ b/xmlsecurity/source/pdfio/pdfdocument.cxx @@ -107,6 +107,8 @@ class PDFObjectElement : public PDFElement std::vector< std::unique_ptr<PDFObjectElement> > m_aStoredElements; /// Elements of an object in an object stream. std::vector< std::unique_ptr<PDFElement> > m_aElements; + /// Uncompressed buffer of an object in an object stream. + std::unique_ptr<SvMemoryStream> m_pStreamBuffer; public: PDFObjectElement(PDFDocument& rDoc, double fObjectValue, double fGenerationValue); @@ -126,6 +128,8 @@ public: /// Parse objects stored in this object stream. void ParseStoredObjects(); std::vector< std::unique_ptr<PDFElement> >& GetStoredElements(); + SvMemoryStream* GetStreamBuffer() const; + void SetStreamBuffer(std::unique_ptr<SvMemoryStream>& pStreamBuffer); }; /// Dictionary object: a set key-value pairs. @@ -557,50 +561,111 @@ bool PDFDocument::Sign(const uno::Reference<security::XCertificate>& xCertificat SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Sign: invalid catalog obj id"); return false; } - m_aXRef[nCatalogId].m_nOffset = m_aEditBuffer.Tell(); - m_aXRef[nCatalogId].m_bDirty = true; - m_aEditBuffer.WriteUInt32AsString(nCatalogId); - m_aEditBuffer.WriteCharPtr(" 0 obj\n"); - m_aEditBuffer.WriteCharPtr("<<"); - auto pAcroForm = dynamic_cast<PDFDictionaryElement*>(pCatalog->Lookup("AcroForm")); - if (!pAcroForm) + PDFElement* pAcroForm = pCatalog->Lookup("AcroForm"); + auto pAcroFormReference = dynamic_cast<PDFReferenceElement*>(pAcroForm); + if (pAcroFormReference) { - // No AcroForm key, assume no signatures. - m_aEditBuffer.WriteBytes(static_cast<const char*>(m_aEditBuffer.GetData()) + pCatalog->GetDictionaryOffset(), pCatalog->GetDictionaryLength()); - m_aEditBuffer.WriteCharPtr("/AcroForm<</Fields[\n"); - m_aEditBuffer.WriteUInt32AsString(nAnnotId); - m_aEditBuffer.WriteCharPtr(" 0 R\n]/SigFlags 3>>\n"); - } - else - { - // AcroForm key is already there, insert our reference at the Fields end. - auto it = pAcroForm->GetItems().find("Fields"); - if (it == pAcroForm->GetItems().end()) + // Write the updated AcroForm key of the Catalog object. + PDFObjectElement* pAcroFormObject = pAcroFormReference->LookupObject(); + if (!pAcroFormObject) { - SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Sign: AcroForm without required Fields key"); + SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Sign: invalid AcroForm reference"); return false; } - auto pFields = dynamic_cast<PDFArrayElement*>(it->second); - if (!pFields) + sal_uInt32 nAcroFormId = pAcroFormObject->GetObjectValue(); + m_aXRef[nAcroFormId].m_eType = XRefEntryType::NOT_COMPRESSED; + m_aXRef[nAcroFormId].m_nOffset = m_aEditBuffer.Tell(); + m_aXRef[nAcroFormId].m_nGenerationNumber = 0; + m_aXRef[nAcroFormId].m_bDirty = true; + m_aEditBuffer.WriteUInt32AsString(nAcroFormId); + m_aEditBuffer.WriteCharPtr(" 0 obj\n"); + + SvMemoryStream* pStreamBuffer = pAcroFormObject->GetStreamBuffer(); + if (!pStreamBuffer) { - SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Sign: AcroForm Fields is not an array"); + SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Sign: AcroForm object is in an object stream"); + return false; + } + + if (!pAcroFormObject->Lookup("Fields")) + { + SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Sign: AcroForm object without required Fields key"); + return false; + } + + PDFDictionaryElement* pAcroFormDictionary = pAcroFormObject->GetDictionary(); + if (!pAcroFormDictionary) + { + SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Sign: AcroForm object has no dictionary"); return false; } // Offset right before the end of the Fields array. - sal_uInt64 nFieldsEndOffset = pAcroForm->GetKeyOffset("Fields") + pAcroForm->GetKeyValueLength("Fields") - 1; - // Length of beginning of the Catalog dictionary -> Fields end. - sal_uInt64 nFieldsBeforeEndLength = nFieldsEndOffset - pCatalog->GetDictionaryOffset(); - m_aEditBuffer.WriteBytes(static_cast<const char*>(m_aEditBuffer.GetData()) + pCatalog->GetDictionaryOffset(), nFieldsBeforeEndLength); + sal_uInt64 nFieldsEndOffset = pAcroFormDictionary->GetKeyOffset("Fields") + pAcroFormDictionary->GetKeyValueLength("Fields") - strlen("]"); + // Length of beginning of the object dictionary -> Fields end. + sal_uInt64 nFieldsBeforeEndLength = nFieldsEndOffset; + m_aEditBuffer.WriteBytes(pStreamBuffer->GetData(), nFieldsBeforeEndLength); + + // Append our reference at the end of the Fields array. m_aEditBuffer.WriteCharPtr(" "); m_aEditBuffer.WriteUInt32AsString(nAnnotId); m_aEditBuffer.WriteCharPtr(" 0 R"); - // Length of Fields end -> end of the Catalog dictionary. - sal_uInt64 nFieldsAfterEndLength = pCatalog->GetDictionaryOffset() + pCatalog->GetDictionaryLength() - nFieldsEndOffset; - m_aEditBuffer.WriteBytes(static_cast<const char*>(m_aEditBuffer.GetData()) + nFieldsEndOffset, nFieldsAfterEndLength); + + // Length of Fields end -> end of the object dictionary. + sal_uInt64 nFieldsAfterEndLength = pStreamBuffer->GetSize() - nFieldsEndOffset; + m_aEditBuffer.WriteBytes(static_cast<const char*>(pStreamBuffer->GetData()) + nFieldsEndOffset, nFieldsAfterEndLength); + + m_aEditBuffer.WriteCharPtr("\nendobj\n\n"); + } + else + { + // Write the updated Catalog object, references nAnnotId. + auto pAcroFormDictionary = dynamic_cast<PDFDictionaryElement*>(pAcroForm); + m_aXRef[nCatalogId].m_nOffset = m_aEditBuffer.Tell(); + m_aXRef[nCatalogId].m_bDirty = true; + m_aEditBuffer.WriteUInt32AsString(nCatalogId); + m_aEditBuffer.WriteCharPtr(" 0 obj\n"); + m_aEditBuffer.WriteCharPtr("<<"); + if (!pAcroFormDictionary) + { + // No AcroForm key, assume no signatures. + m_aEditBuffer.WriteBytes(static_cast<const char*>(m_aEditBuffer.GetData()) + pCatalog->GetDictionaryOffset(), pCatalog->GetDictionaryLength()); + m_aEditBuffer.WriteCharPtr("/AcroForm<</Fields[\n"); + m_aEditBuffer.WriteUInt32AsString(nAnnotId); + m_aEditBuffer.WriteCharPtr(" 0 R\n]/SigFlags 3>>\n"); + } + else + { + // AcroForm key is already there, insert our reference at the Fields end. + auto it = pAcroFormDictionary->GetItems().find("Fields"); + if (it == pAcroFormDictionary->GetItems().end()) + { + SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Sign: AcroForm without required Fields key"); + return false; + } + + auto pFields = dynamic_cast<PDFArrayElement*>(it->second); + if (!pFields) + { + SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Sign: AcroForm Fields is not an array"); + return false; + } + + // Offset right before the end of the Fields array. + sal_uInt64 nFieldsEndOffset = pAcroFormDictionary->GetKeyOffset("Fields") + pAcroFormDictionary->GetKeyValueLength("Fields") - 1; + // Length of beginning of the Catalog dictionary -> Fields end. + sal_uInt64 nFieldsBeforeEndLength = nFieldsEndOffset - pCatalog->GetDictionaryOffset(); + m_aEditBuffer.WriteBytes(static_cast<const char*>(m_aEditBuffer.GetData()) + pCatalog->GetDictionaryOffset(), nFieldsBeforeEndLength); + m_aEditBuffer.WriteCharPtr(" "); + m_aEditBuffer.WriteUInt32AsString(nAnnotId); + m_aEditBuffer.WriteCharPtr(" 0 R"); + // Length of Fields end -> end of the Catalog dictionary. + sal_uInt64 nFieldsAfterEndLength = pCatalog->GetDictionaryOffset() + pCatalog->GetDictionaryLength() - nFieldsEndOffset; + m_aEditBuffer.WriteBytes(static_cast<const char*>(m_aEditBuffer.GetData()) + nFieldsEndOffset, nFieldsAfterEndLength); + } + m_aEditBuffer.WriteCharPtr(">>\nendobj\n\n"); } - m_aEditBuffer.WriteCharPtr(">>\nendobj\n\n"); // Write the xref table. sal_uInt64 nXRefOffset = m_aEditBuffer.Tell(); @@ -2736,6 +2801,12 @@ void PDFObjectElement::ParseStoredObjects() m_rDoc.Tokenize(aStoredStream, TokenizeMode::STORED_OBJECT, pStored->GetStoredElements(), pStored); // This is how references know the object is stored inside this object stream. m_rDoc.SetIDObject(nObjNum, pStored); + + // Store the stream of the object in the object stream for later use. + std::unique_ptr<SvMemoryStream> pStreamBuffer(new SvMemoryStream()); + aStoredStream.Seek(0); + pStreamBuffer->WriteStream(aStoredStream); + pStored->SetStreamBuffer(pStreamBuffer); } } @@ -2744,6 +2815,16 @@ std::vector< std::unique_ptr<PDFElement> >& PDFObjectElement::GetStoredElements( return m_aElements; } +SvMemoryStream* PDFObjectElement::GetStreamBuffer() const +{ + return m_pStreamBuffer.get(); +} + +void PDFObjectElement::SetStreamBuffer(std::unique_ptr<SvMemoryStream>& pStreamBuffer) +{ + m_pStreamBuffer = std::move(pStreamBuffer); +} + PDFReferenceElement::PDFReferenceElement(PDFDocument& rDoc, int fObjectValue, int fGenerationValue) : m_rDoc(rDoc), m_fObjectValue(fObjectValue), _______________________________________________ Libreoffice-commits mailing list libreoffice-comm...@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits