xmlsecurity PDF verify: add support for object streams

Adobe Acrobat uses object streams (PDF 1.6) when it signs a PDF exported
from LO (PDF 1.4), with this we can verify that signature.

If the PDF had at least one signature in LO, then the doc is not
upgraded from PDF 1.4, so that was working already.

Change-Id: I54b4447ca965a8ba1ffc69bde228ab6f0bda59ee
This commit is contained in:
Miklos Vajna
2016-11-02 11:10:35 +01:00
parent 2734a6ce77
commit b0d1a39e99
4 changed files with 382 additions and 121 deletions

View File

@@ -45,7 +45,40 @@ enum class TokenizeMode
/// Till the first %%EOF token. /// Till the first %%EOF token.
EOF_TOKEN, EOF_TOKEN,
/// Till the end of the current object. /// Till the end of the current object.
END_OF_OBJECT END_OF_OBJECT,
/// Same as END_OF_OBJECT, but for object streams (no endobj keyword).
STORED_OBJECT
};
/// The type column of an entry in a cross-reference stream.
enum class XRefEntryType
{
/// xref "n" or xref stream "1".
NOT_COMPRESSED,
/// xref stream "2.
COMPRESSED
};
/// An entry in a cross-reference stream.
struct XRefEntry
{
XRefEntryType m_eType;
/**
* Non-compressed: The byte offset of the object, starting from the
* beginning of the file.
* Compressed: The object number of the object stream in which this object is
* stored.
*/
sal_uInt64 m_nOffset;
/**
* Non-compressed: The generation number of the object.
* Compressed: The index of this object within the object stream.
*/
sal_uInt64 m_nGenerationNumber;
/// Are changed as part of an incremental update?.
bool m_bDirty;
XRefEntry();
}; };
/** /**
@@ -60,9 +93,7 @@ class XMLSECURITY_DLLPUBLIC PDFDocument
/// This vector owns all elements. /// This vector owns all elements.
std::vector< std::unique_ptr<PDFElement> > m_aElements; std::vector< std::unique_ptr<PDFElement> > m_aElements;
/// Object ID <-> object offset map. /// Object ID <-> object offset map.
std::map<size_t, size_t> m_aXRef; std::map<size_t, XRefEntry> m_aXRef;
/// Object ID <-> "are changed as part of an incremental update?" map.
std::map<size_t, bool> m_aXRefDirty;
/// Object offset <-> Object pointer map. /// Object offset <-> Object pointer map.
std::map<size_t, PDFObjectElement*> m_aOffsetObjects; std::map<size_t, PDFObjectElement*> m_aOffsetObjects;
/// Object ID <-> Object pointer map. /// Object ID <-> Object pointer map.
@@ -80,8 +111,6 @@ class XMLSECURITY_DLLPUBLIC PDFDocument
static int AsHex(char ch); static int AsHex(char ch);
/// Decode a hex dump. /// Decode a hex dump.
static std::vector<unsigned char> DecodeHexString(PDFHexStringElement* pElement); static std::vector<unsigned char> DecodeHexString(PDFHexStringElement* pElement);
/// Tokenize elements from current offset.
bool Tokenize(SvStream& rStream, TokenizeMode eMode);
public: public:
PDFDocument(); PDFDocument();
@@ -99,7 +128,14 @@ public:
std::vector<PDFObjectElement*> GetPages(); std::vector<PDFObjectElement*> GetPages();
/// Remember the end location of an EOF token. /// Remember the end location of an EOF token.
void PushBackEOF(size_t nOffset); void PushBackEOF(size_t nOffset);
const std::map<size_t, PDFObjectElement*>& GetIDObjects() const; /// Look up object based on object number, possibly by parsing object streams.
PDFObjectElement* LookupObject(size_t nObjectNumber);
/// Access to the input document, even after the inpust ream is gone.
SvMemoryStream& GetEditBuffer();
/// Tokenize elements from current offset.
bool Tokenize(SvStream& rStream, TokenizeMode eMode, std::vector< std::unique_ptr<PDFElement> >& rElements, PDFObjectElement* pObject);
/// Register an object (owned directly or indirectly by m_aElements) as a provder for a given ID.
void SetIDObject(size_t nID, PDFObjectElement* pObject);
/// Read elements from the start of the stream till its end. /// Read elements from the start of the stream till its end.
bool Read(SvStream& rStream); bool Read(SvStream& rStream);

Binary file not shown.

View File

@@ -57,6 +57,8 @@ public:
void testPDFRemoveAll(); void testPDFRemoveAll();
/// Test a PDF 1.4 document, signed by Adobe. /// Test a PDF 1.4 document, signed by Adobe.
void testPDF14Adobe(); void testPDF14Adobe();
/// Test a PDF 1.6 document, signed by Adobe.
void testPDF16Adobe();
CPPUNIT_TEST_SUITE(PDFSigningTest); CPPUNIT_TEST_SUITE(PDFSigningTest);
CPPUNIT_TEST(testPDFAdd); CPPUNIT_TEST(testPDFAdd);
@@ -64,6 +66,7 @@ public:
CPPUNIT_TEST(testPDFRemove); CPPUNIT_TEST(testPDFRemove);
CPPUNIT_TEST(testPDFRemoveAll); CPPUNIT_TEST(testPDFRemoveAll);
CPPUNIT_TEST(testPDF14Adobe); CPPUNIT_TEST(testPDF14Adobe);
CPPUNIT_TEST(testPDF16Adobe);
CPPUNIT_TEST_SUITE_END(); CPPUNIT_TEST_SUITE_END();
}; };
@@ -254,6 +257,17 @@ void PDFSigningTest::testPDF14Adobe()
#endif #endif
} }
void PDFSigningTest::testPDF16Adobe()
{
#ifndef _WIN32
// Contains a cross-reference stream, object streams and a compressed
// stream with a predictor. And a valid signature.
// Found signatures was 0, as parsing failed due to lack of support for
// these features.
verify(m_directories.getURLFromSrc(DATA_DIRECTORY) + "pdf16adobe.pdf", 1);
#endif
}
CPPUNIT_TEST_SUITE_REGISTRATION(PDFSigningTest); CPPUNIT_TEST_SUITE_REGISTRATION(PDFSigningTest);
CPPUNIT_PLUGIN_IMPLEMENT(); CPPUNIT_PLUGIN_IMPLEMENT();

View File

@@ -78,6 +78,7 @@ public:
class PDFReferenceElement; class PDFReferenceElement;
class PDFDictionaryElement; class PDFDictionaryElement;
class PDFArrayElement; class PDFArrayElement;
class PDFStreamElement;
/// Indirect object: something with a unique ID. /// Indirect object: something with a unique ID.
class PDFObjectElement : public PDFElement class PDFObjectElement : public PDFElement
@@ -93,6 +94,12 @@ class PDFObjectElement : public PDFElement
PDFDictionaryElement* m_pDictionaryElement; PDFDictionaryElement* m_pDictionaryElement;
/// The contained direct array, if any. /// The contained direct array, if any.
PDFArrayElement* m_pArrayElement; PDFArrayElement* m_pArrayElement;
/// The stream of this object, used when this is an object stream.
PDFStreamElement* m_pStreamElement;
/// Objects of an object stream.
std::vector< std::unique_ptr<PDFObjectElement> > m_aStoredElements;
/// Elements of an object in an object stream.
std::vector< std::unique_ptr<PDFElement> > m_aElements;
public: public:
PDFObjectElement(PDFDocument& rDoc, double fObjectValue, double fGenerationValue); PDFObjectElement(PDFDocument& rDoc, double fObjectValue, double fGenerationValue);
@@ -107,7 +114,11 @@ public:
PDFDictionaryElement* GetDictionary() const; PDFDictionaryElement* GetDictionary() const;
void SetDictionary(PDFDictionaryElement* pDictionaryElement); void SetDictionary(PDFDictionaryElement* pDictionaryElement);
void SetArray(PDFArrayElement* pArrayElement); void SetArray(PDFArrayElement* pArrayElement);
void SetStream(PDFStreamElement* pStreamElement);
PDFArrayElement* GetArray() const; PDFArrayElement* GetArray() const;
/// Parse objects stored in this object stream.
void ParseStoredObjects();
std::vector< std::unique_ptr<PDFElement> >& GetStoredElements();
}; };
/// Dictionary object: a set key-value pairs. /// Dictionary object: a set key-value pairs.
@@ -175,7 +186,7 @@ public:
/// Assuming the reference points to a number object, return its value. /// Assuming the reference points to a number object, return its value.
double LookupNumber(SvStream& rStream) const; double LookupNumber(SvStream& rStream) const;
/// Lookup referenced object, without assuming anything about its contents. /// Lookup referenced object, without assuming anything about its contents.
PDFObjectElement* LookupObject() const; PDFObjectElement* LookupObject();
int GetObjectValue() const; int GetObjectValue() const;
int GetGenerationValue() const; int GetGenerationValue() const;
}; };
@@ -275,6 +286,14 @@ public:
PDFElement* Lookup(const OString& rDictionaryKey); PDFElement* Lookup(const OString& rDictionaryKey);
}; };
XRefEntry::XRefEntry()
: m_eType(XRefEntryType::NOT_COMPRESSED),
m_nOffset(0),
m_nGenerationNumber(0),
m_bDirty(false)
{
}
PDFDocument::PDFDocument() PDFDocument::PDFDocument()
: m_pTrailer(nullptr), : m_pTrailer(nullptr),
m_pXRefStream(nullptr) m_pXRefStream(nullptr)
@@ -315,14 +334,15 @@ bool PDFDocument::Sign(const uno::Reference<security::XCertificate>& xCertificat
// Write signature object. // Write signature object.
sal_Int32 nSignatureId = m_aXRef.size(); sal_Int32 nSignatureId = m_aXRef.size();
sal_uInt64 nSignatureOffset = m_aEditBuffer.Tell(); XRefEntry aSignatureEntry;
m_aXRef[nSignatureId] = nSignatureOffset; aSignatureEntry.m_nOffset = m_aEditBuffer.Tell();
m_aXRefDirty[nSignatureId] = true; aSignatureEntry.m_bDirty = true;
m_aXRef[nSignatureId] = aSignatureEntry;
OStringBuffer aSigBuffer; OStringBuffer aSigBuffer;
aSigBuffer.append(nSignatureId); aSigBuffer.append(nSignatureId);
aSigBuffer.append(" 0 obj\n"); aSigBuffer.append(" 0 obj\n");
aSigBuffer.append("<</Contents <"); aSigBuffer.append("<</Contents <");
sal_Int64 nSignatureContentOffset = nSignatureOffset + aSigBuffer.getLength(); sal_Int64 nSignatureContentOffset = aSignatureEntry.m_nOffset + aSigBuffer.getLength();
// Reserve space for the PKCS#7 object. // Reserve space for the PKCS#7 object.
const int MAX_SIGNATURE_CONTENT_LENGTH = 50000; const int MAX_SIGNATURE_CONTENT_LENGTH = 50000;
OStringBuffer aContentFiller(MAX_SIGNATURE_CONTENT_LENGTH); OStringBuffer aContentFiller(MAX_SIGNATURE_CONTENT_LENGTH);
@@ -337,7 +357,7 @@ bool PDFDocument::Sign(const uno::Reference<security::XCertificate>& xCertificat
aSigBuffer.append(" "); aSigBuffer.append(" ");
aSigBuffer.append(nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1); aSigBuffer.append(nSignatureContentOffset + MAX_SIGNATURE_CONTENT_LENGTH + 1);
aSigBuffer.append(" "); aSigBuffer.append(" ");
sal_uInt64 nSignatureLastByteRangeOffset = nSignatureOffset + aSigBuffer.getLength(); sal_uInt64 nSignatureLastByteRangeOffset = aSignatureEntry.m_nOffset + aSigBuffer.getLength();
// We don't know how many bytes we need for the last ByteRange value, this // We don't know how many bytes we need for the last ByteRange value, this
// should be enough. // should be enough.
OStringBuffer aByteRangeFiller; OStringBuffer aByteRangeFiller;
@@ -358,8 +378,10 @@ bool PDFDocument::Sign(const uno::Reference<security::XCertificate>& xCertificat
// Write appearance object. // Write appearance object.
sal_Int32 nAppearanceId = m_aXRef.size(); sal_Int32 nAppearanceId = m_aXRef.size();
m_aXRef[nAppearanceId] = m_aEditBuffer.Tell(); XRefEntry aAppearanceEntry;
m_aXRefDirty[nAppearanceId] = true; aAppearanceEntry.m_nOffset = m_aEditBuffer.Tell();
aAppearanceEntry.m_bDirty = true;
m_aXRef[nAppearanceId] = aAppearanceEntry;
m_aEditBuffer.WriteUInt32AsString(nAppearanceId); m_aEditBuffer.WriteUInt32AsString(nAppearanceId);
m_aEditBuffer.WriteCharPtr(" 0 obj\n"); m_aEditBuffer.WriteCharPtr(" 0 obj\n");
m_aEditBuffer.WriteCharPtr("<</Type/XObject\n/Subtype/Form\n"); m_aEditBuffer.WriteCharPtr("<</Type/XObject\n/Subtype/Form\n");
@@ -368,8 +390,10 @@ bool PDFDocument::Sign(const uno::Reference<security::XCertificate>& xCertificat
// Write the Annot object, references nSignatureId and nAppearanceId. // Write the Annot object, references nSignatureId and nAppearanceId.
sal_Int32 nAnnotId = m_aXRef.size(); sal_Int32 nAnnotId = m_aXRef.size();
m_aXRef[nAnnotId] = m_aEditBuffer.Tell(); XRefEntry aAnnotEntry;
m_aXRefDirty[nAnnotId] = true; aAnnotEntry.m_nOffset = m_aEditBuffer.Tell();
aAnnotEntry.m_bDirty = true;
m_aXRef[nAnnotId] = aAnnotEntry;
m_aEditBuffer.WriteUInt32AsString(nAnnotId); m_aEditBuffer.WriteUInt32AsString(nAnnotId);
m_aEditBuffer.WriteCharPtr(" 0 obj\n"); m_aEditBuffer.WriteCharPtr(" 0 obj\n");
m_aEditBuffer.WriteCharPtr("<</Type/Annot/Subtype/Widget/F 132\n"); m_aEditBuffer.WriteCharPtr("<</Type/Annot/Subtype/Widget/F 132\n");
@@ -406,8 +430,8 @@ bool PDFDocument::Sign(const uno::Reference<security::XCertificate>& xCertificat
SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Sign: invalid first page obj id"); SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Sign: invalid first page obj id");
return false; return false;
} }
m_aXRef[nFirstPageId] = m_aEditBuffer.Tell(); m_aXRef[nFirstPageId].m_nOffset = m_aEditBuffer.Tell();
m_aXRefDirty[nFirstPageId] = true; m_aXRef[nFirstPageId].m_bDirty = true;
m_aEditBuffer.WriteUInt32AsString(nFirstPageId); m_aEditBuffer.WriteUInt32AsString(nFirstPageId);
m_aEditBuffer.WriteCharPtr(" 0 obj\n"); m_aEditBuffer.WriteCharPtr(" 0 obj\n");
m_aEditBuffer.WriteCharPtr("<<"); m_aEditBuffer.WriteCharPtr("<<");
@@ -459,8 +483,8 @@ bool PDFDocument::Sign(const uno::Reference<security::XCertificate>& xCertificat
SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Sign: invalid catalog obj id"); SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Sign: invalid catalog obj id");
return false; return false;
} }
m_aXRef[nCatalogId] = m_aEditBuffer.Tell(); m_aXRef[nCatalogId].m_nOffset = m_aEditBuffer.Tell();
m_aXRefDirty[nCatalogId] = true; m_aXRef[nCatalogId].m_bDirty = true;
m_aEditBuffer.WriteUInt32AsString(nCatalogId); m_aEditBuffer.WriteUInt32AsString(nCatalogId);
m_aEditBuffer.WriteCharPtr(" 0 obj\n"); m_aEditBuffer.WriteCharPtr(" 0 obj\n");
m_aEditBuffer.WriteCharPtr("<<"); m_aEditBuffer.WriteCharPtr("<<");
@@ -510,8 +534,8 @@ bool PDFDocument::Sign(const uno::Reference<security::XCertificate>& xCertificat
for (const auto& rXRef : m_aXRef) for (const auto& rXRef : m_aXRef)
{ {
size_t nObject = rXRef.first; size_t nObject = rXRef.first;
size_t nOffset = rXRef.second; size_t nOffset = rXRef.second.m_nOffset;
if (!m_aXRefDirty[nObject]) if (!rXRef.second.m_bDirty)
continue; continue;
m_aEditBuffer.WriteUInt32AsString(nObject); m_aEditBuffer.WriteUInt32AsString(nObject);
@@ -632,13 +656,13 @@ bool PDFDocument::Write(SvStream& rStream)
return rStream.good(); return rStream.good();
} }
bool PDFDocument::Tokenize(SvStream& rStream, TokenizeMode eMode) bool PDFDocument::Tokenize(SvStream& rStream, TokenizeMode eMode, std::vector< std::unique_ptr<PDFElement> >& rElements, PDFObjectElement* pObjectElement)
{ {
// Last seen object token.
PDFObjectElement* pObject = pObjectElement;
bool bInXRef = false; bool bInXRef = false;
// The next number will be an xref offset. // The next number will be an xref offset.
bool bInStartXRef = false; bool bInStartXRef = false;
// Last seen object token.
PDFObjectElement* pObject = nullptr;
// Dictionary depth, so we know when we're outside any dictionaries. // Dictionary depth, so we know when we're outside any dictionaries.
int nDictionaryDepth = 0; int nDictionaryDepth = 0;
// Last seen array token that's outside any dictionaries. // Last seen array token that's outside any dictionaries.
@@ -655,9 +679,9 @@ bool PDFDocument::Tokenize(SvStream& rStream, TokenizeMode eMode)
case '%': case '%':
{ {
auto pComment = new PDFCommentElement(*this); auto pComment = new PDFCommentElement(*this);
m_aElements.push_back(std::unique_ptr<PDFElement>(pComment)); rElements.push_back(std::unique_ptr<PDFElement>(pComment));
rStream.SeekRel(-1); rStream.SeekRel(-1);
if (!m_aElements.back()->Read(rStream)) if (!rElements.back()->Read(rStream))
return false; return false;
if (eMode == TokenizeMode::EOF_TOKEN && !m_aEOFs.empty() && m_aEOFs.back() == rStream.Tell()) if (eMode == TokenizeMode::EOF_TOKEN && !m_aEOFs.empty() && m_aEOFs.back() == rStream.Tell())
{ {
@@ -673,28 +697,28 @@ bool PDFDocument::Tokenize(SvStream& rStream, TokenizeMode eMode)
rStream.SeekRel(-2); rStream.SeekRel(-2);
if (ch == '<') if (ch == '<')
{ {
m_aElements.push_back(std::unique_ptr<PDFElement>(new PDFDictionaryElement())); rElements.push_back(std::unique_ptr<PDFElement>(new PDFDictionaryElement()));
++nDictionaryDepth; ++nDictionaryDepth;
} }
else else
m_aElements.push_back(std::unique_ptr<PDFElement>(new PDFHexStringElement())); rElements.push_back(std::unique_ptr<PDFElement>(new PDFHexStringElement()));
if (!m_aElements.back()->Read(rStream)) if (!rElements.back()->Read(rStream))
return false; return false;
break; break;
} }
case '>': case '>':
{ {
m_aElements.push_back(std::unique_ptr<PDFElement>(new PDFEndDictionaryElement())); rElements.push_back(std::unique_ptr<PDFElement>(new PDFEndDictionaryElement()));
--nDictionaryDepth; --nDictionaryDepth;
rStream.SeekRel(-1); rStream.SeekRel(-1);
if (!m_aElements.back()->Read(rStream)) if (!rElements.back()->Read(rStream))
return false; return false;
break; break;
} }
case '[': case '[':
{ {
auto pArr = new PDFArrayElement(); auto pArr = new PDFArrayElement();
m_aElements.push_back(std::unique_ptr<PDFElement>(pArr)); rElements.push_back(std::unique_ptr<PDFElement>(pArr));
if (nDictionaryDepth == 0) if (nDictionaryDepth == 0)
{ {
// The array is attached directly, inform the object. // The array is attached directly, inform the object.
@@ -703,32 +727,32 @@ bool PDFDocument::Tokenize(SvStream& rStream, TokenizeMode eMode)
pObject->SetArray(pArray); pObject->SetArray(pArray);
} }
rStream.SeekRel(-1); rStream.SeekRel(-1);
if (!m_aElements.back()->Read(rStream)) if (!rElements.back()->Read(rStream))
return false; return false;
break; break;
} }
case ']': case ']':
{ {
m_aElements.push_back(std::unique_ptr<PDFElement>(new PDFEndArrayElement())); rElements.push_back(std::unique_ptr<PDFElement>(new PDFEndArrayElement()));
pArray = nullptr; pArray = nullptr;
rStream.SeekRel(-1); rStream.SeekRel(-1);
if (!m_aElements.back()->Read(rStream)) if (!rElements.back()->Read(rStream))
return false; return false;
break; break;
} }
case '/': case '/':
{ {
m_aElements.push_back(std::unique_ptr<PDFElement>(new PDFNameElement())); rElements.push_back(std::unique_ptr<PDFElement>(new PDFNameElement()));
rStream.SeekRel(-1); rStream.SeekRel(-1);
if (!m_aElements.back()->Read(rStream)) if (!rElements.back()->Read(rStream))
return false; return false;
break; break;
} }
case '(': case '(':
{ {
m_aElements.push_back(std::unique_ptr<PDFElement>(new PDFLiteralStringElement())); rElements.push_back(std::unique_ptr<PDFElement>(new PDFLiteralStringElement()));
rStream.SeekRel(-1); rStream.SeekRel(-1);
if (!m_aElements.back()->Read(rStream)) if (!rElements.back()->Read(rStream))
return false; return false;
break; break;
} }
@@ -738,7 +762,7 @@ bool PDFDocument::Tokenize(SvStream& rStream, TokenizeMode eMode)
{ {
// Numbering object: an integer or a real. // Numbering object: an integer or a real.
PDFNumberElement* pNumberElement = new PDFNumberElement(); PDFNumberElement* pNumberElement = new PDFNumberElement();
m_aElements.push_back(std::unique_ptr<PDFElement>(pNumberElement)); rElements.push_back(std::unique_ptr<PDFElement>(pNumberElement));
rStream.SeekRel(-1); rStream.SeekRel(-1);
if (!pNumberElement->Read(rStream)) if (!pNumberElement->Read(rStream))
return false; return false;
@@ -761,15 +785,15 @@ bool PDFDocument::Tokenize(SvStream& rStream, TokenizeMode eMode)
bool bObj = aKeyword == "obj"; bool bObj = aKeyword == "obj";
if (bObj || aKeyword == "R") if (bObj || aKeyword == "R")
{ {
size_t nElements = m_aElements.size(); size_t nElements = rElements.size();
if (nElements < 2) if (nElements < 2)
{ {
SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Tokenize: expected at least two tokens before 'obj' or 'R' keyword"); SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Tokenize: expected at least two tokens before 'obj' or 'R' keyword");
return false; return false;
} }
auto pObjectNumber = dynamic_cast<PDFNumberElement*>(m_aElements[nElements - 2].get()); auto pObjectNumber = dynamic_cast<PDFNumberElement*>(rElements[nElements - 2].get());
auto pGenerationNumber = dynamic_cast<PDFNumberElement*>(m_aElements[nElements - 1].get()); auto pGenerationNumber = dynamic_cast<PDFNumberElement*>(rElements[nElements - 1].get());
if (!pObjectNumber || !pGenerationNumber) if (!pObjectNumber || !pGenerationNumber)
{ {
SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Tokenize: missing object or generation number before 'obj' or 'R' keyword"); SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Tokenize: missing object or generation number before 'obj' or 'R' keyword");
@@ -779,34 +803,34 @@ bool PDFDocument::Tokenize(SvStream& rStream, TokenizeMode eMode)
if (bObj) if (bObj)
{ {
pObject = new PDFObjectElement(*this, pObjectNumber->GetValue(), pGenerationNumber->GetValue()); pObject = new PDFObjectElement(*this, pObjectNumber->GetValue(), pGenerationNumber->GetValue());
m_aElements.push_back(std::unique_ptr<PDFElement>(pObject)); rElements.push_back(std::unique_ptr<PDFElement>(pObject));
m_aOffsetObjects[pObjectNumber->GetLocation()] = pObject; m_aOffsetObjects[pObjectNumber->GetLocation()] = pObject;
m_aIDObjects[pObjectNumber->GetValue()] = pObject; m_aIDObjects[pObjectNumber->GetValue()] = pObject;
} }
else else
{ {
m_aElements.push_back(std::unique_ptr<PDFElement>(new PDFReferenceElement(*this, pObjectNumber->GetValue(), pGenerationNumber->GetValue()))); rElements.push_back(std::unique_ptr<PDFElement>(new PDFReferenceElement(*this, pObjectNumber->GetValue(), pGenerationNumber->GetValue())));
if (pArray) if (pArray)
// Reference is part of a direct (non-dictionary) array, inform the array. // Reference is part of a direct (non-dictionary) array, inform the array.
pArray->PushBack(m_aElements.back().get()); pArray->PushBack(rElements.back().get());
} }
if (!m_aElements.back()->Read(rStream)) if (!rElements.back()->Read(rStream))
return false; return false;
} }
else if (aKeyword == "stream") else if (aKeyword == "stream")
{ {
// Look up the length of the stream from the parent object's dictionary. // Look up the length of the stream from the parent object's dictionary.
size_t nLength = 0; size_t nLength = 0;
for (size_t nElement = 0; nElement < m_aElements.size(); ++nElement) for (size_t nElement = 0; nElement < rElements.size(); ++nElement)
{ {
// Iterate in reverse order. // Iterate in reverse order.
size_t nIndex = m_aElements.size() - nElement - 1; size_t nIndex = rElements.size() - nElement - 1;
PDFElement* pElement = m_aElements[nIndex].get(); PDFElement* pElement = rElements[nIndex].get();
auto pObjectElement = dynamic_cast<PDFObjectElement*>(pElement); auto pObj = dynamic_cast<PDFObjectElement*>(pElement);
if (!pObjectElement) if (!pObj)
continue; continue;
PDFElement* pLookup = pObjectElement->Lookup("Length"); PDFElement* pLookup = pObj->Lookup("Length");
auto pReference = dynamic_cast<PDFReferenceElement*>(pLookup); auto pReference = dynamic_cast<PDFReferenceElement*>(pLookup);
if (pReference) if (pReference)
{ {
@@ -828,20 +852,23 @@ bool PDFDocument::Tokenize(SvStream& rStream, TokenizeMode eMode)
} }
PDFDocument::SkipLineBreaks(rStream); PDFDocument::SkipLineBreaks(rStream);
m_aElements.push_back(std::unique_ptr<PDFElement>(new PDFStreamElement(nLength))); auto pStreamElement = new PDFStreamElement(nLength);
if (!m_aElements.back()->Read(rStream)) if (pObject)
pObject->SetStream(pStreamElement);
rElements.push_back(std::unique_ptr<PDFElement>(pStreamElement));
if (!rElements.back()->Read(rStream))
return false; return false;
} }
else if (aKeyword == "endstream") else if (aKeyword == "endstream")
{ {
m_aElements.push_back(std::unique_ptr<PDFElement>(new PDFEndStreamElement())); rElements.push_back(std::unique_ptr<PDFElement>(new PDFEndStreamElement()));
if (!m_aElements.back()->Read(rStream)) if (!rElements.back()->Read(rStream))
return false; return false;
} }
else if (aKeyword == "endobj") else if (aKeyword == "endobj")
{ {
m_aElements.push_back(std::unique_ptr<PDFElement>(new PDFEndObjectElement())); rElements.push_back(std::unique_ptr<PDFElement>(new PDFEndObjectElement()));
if (!m_aElements.back()->Read(rStream)) if (!rElements.back()->Read(rStream))
return false; return false;
if (eMode == TokenizeMode::END_OF_OBJECT) if (eMode == TokenizeMode::END_OF_OBJECT)
{ {
@@ -850,9 +877,9 @@ bool PDFDocument::Tokenize(SvStream& rStream, TokenizeMode eMode)
} }
} }
else if (aKeyword == "true" || aKeyword == "false") else if (aKeyword == "true" || aKeyword == "false")
m_aElements.push_back(std::unique_ptr<PDFElement>(new PDFBooleanElement(aKeyword.toBoolean()))); rElements.push_back(std::unique_ptr<PDFElement>(new PDFBooleanElement(aKeyword.toBoolean())));
else if (aKeyword == "null") else if (aKeyword == "null")
m_aElements.push_back(std::unique_ptr<PDFElement>(new PDFNullElement())); rElements.push_back(std::unique_ptr<PDFElement>(new PDFNullElement()));
else if (aKeyword == "xref") else if (aKeyword == "xref")
// Allow 'f' and 'n' keywords. // Allow 'f' and 'n' keywords.
bInXRef = true; bInXRef = true;
@@ -862,7 +889,7 @@ bool PDFDocument::Tokenize(SvStream& rStream, TokenizeMode eMode)
else if (aKeyword == "trailer") else if (aKeyword == "trailer")
{ {
m_pTrailer = new PDFTrailerElement(*this); m_pTrailer = new PDFTrailerElement(*this);
m_aElements.push_back(std::unique_ptr<PDFElement>(m_pTrailer)); rElements.push_back(std::unique_ptr<PDFElement>(m_pTrailer));
} }
else if (aKeyword == "startxref") else if (aKeyword == "startxref")
{ {
@@ -890,6 +917,11 @@ bool PDFDocument::Tokenize(SvStream& rStream, TokenizeMode eMode)
return true; return true;
} }
void PDFDocument::SetIDObject(size_t nID, PDFObjectElement* pObject)
{
m_aIDObjects[nID] = pObject;
}
bool PDFDocument::Read(SvStream& rStream) bool PDFDocument::Read(SvStream& rStream)
{ {
// Check file magic. // Check file magic.
@@ -917,16 +949,30 @@ bool PDFDocument::Read(SvStream& rStream)
while (true) while (true)
{ {
rStream.Seek(nStartXRef); rStream.Seek(nStartXRef);
OString aKeyword = ReadKeyword(rStream);
if (aKeyword.isEmpty())
ReadXRefStream(rStream);
else
{
if (aKeyword != "xref")
{
SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Read: xref is not the first keyword");
return false;
}
ReadXRef(rStream); ReadXRef(rStream);
if (!Tokenize(rStream, TokenizeMode::EOF_TOKEN)) if (!Tokenize(rStream, TokenizeMode::EOF_TOKEN, m_aElements, nullptr))
{ {
SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Read: failed to tokenizer trailer after xref"); SAL_WARN("xmlsecurity.pdfio", "PDFDocument::Read: failed to tokenizer trailer after xref");
return false; return false;
} }
}
PDFNumberElement* pPrev = nullptr; PDFNumberElement* pPrev = nullptr;
if (m_pTrailer) if (m_pTrailer)
pPrev = dynamic_cast<PDFNumberElement*>(m_pTrailer->Lookup("Prev")); pPrev = dynamic_cast<PDFNumberElement*>(m_pTrailer->Lookup("Prev"));
else if (m_pXRefStream)
pPrev = dynamic_cast<PDFNumberElement*>(m_pXRefStream->Lookup("Prev"));
if (pPrev) if (pPrev)
nStartXRef = pPrev->GetValue(); nStartXRef = pPrev->GetValue();
@@ -942,7 +988,7 @@ bool PDFDocument::Read(SvStream& rStream)
// Then we can tokenize the stream. // Then we can tokenize the stream.
rStream.Seek(0); rStream.Seek(0);
return Tokenize(rStream, TokenizeMode::END_OF_STREAM); return Tokenize(rStream, TokenizeMode::END_OF_STREAM, m_aElements, nullptr);
} }
OString PDFDocument::ReadKeyword(SvStream& rStream) OString PDFDocument::ReadKeyword(SvStream& rStream)
@@ -997,7 +1043,7 @@ size_t PDFDocument::FindStartXRef(SvStream& rStream)
void PDFDocument::ReadXRefStream(SvStream& rStream) void PDFDocument::ReadXRefStream(SvStream& rStream)
{ {
// Look up the stream length in the object dictionary. // Look up the stream length in the object dictionary.
if (!Tokenize(rStream, TokenizeMode::END_OF_OBJECT)) if (!Tokenize(rStream, TokenizeMode::END_OF_OBJECT, m_aElements, nullptr))
{ {
SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRefStream: failed to read object"); SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRefStream: failed to read object");
return; return;
@@ -1024,6 +1070,9 @@ void PDFDocument::ReadXRefStream(SvStream& rStream)
return; return;
} }
// So that the Prev key can be looked up later.
m_pXRefStream = pObject;
PDFElement* pLookup = pObject->Lookup("Length"); PDFElement* pLookup = pObject->Lookup("Length");
auto pNumber = dynamic_cast<PDFNumberElement*>(pLookup); auto pNumber = dynamic_cast<PDFNumberElement*>(pLookup);
if (!pNumber) if (!pNumber)
@@ -1095,12 +1144,21 @@ void PDFDocument::ReadXRefStream(SvStream& rStream)
// Look up the first and the last entry we need to read. // Look up the first and the last entry we need to read.
auto pIndex = dynamic_cast<PDFArrayElement*>(pObject->Lookup("Index")); auto pIndex = dynamic_cast<PDFArrayElement*>(pObject->Lookup("Index"));
size_t nFirstObject = 0;
size_t nNumberOfObjects = 0;
if (!pIndex || pIndex->GetElements().size() < 2) if (!pIndex || pIndex->GetElements().size() < 2)
{
auto pSize = dynamic_cast<PDFNumberElement*>(pObject->Lookup("Size"));
if (pSize)
nNumberOfObjects = pSize->GetValue();
else
{ {
SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRefStream: Index not found or has < 2 elements"); SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRefStream: Index not found or has < 2 elements");
return; return;
} }
}
else
{
const std::vector<PDFElement*>& rIndexElements = pIndex->GetElements(); const std::vector<PDFElement*>& rIndexElements = pIndex->GetElements();
auto pFirstObject = dynamic_cast<PDFNumberElement*>(rIndexElements[0]); auto pFirstObject = dynamic_cast<PDFNumberElement*>(rIndexElements[0]);
if (!pFirstObject) if (!pFirstObject)
@@ -1108,6 +1166,7 @@ void PDFDocument::ReadXRefStream(SvStream& rStream)
SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRefStream: Index has no first object"); SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRefStream: Index has no first object");
return; return;
} }
nFirstObject = pFirstObject->GetValue();
auto pNumberOfObjects = dynamic_cast<PDFNumberElement*>(rIndexElements[1]); auto pNumberOfObjects = dynamic_cast<PDFNumberElement*>(rIndexElements[1]);
if (!pNumberOfObjects) if (!pNumberOfObjects)
@@ -1115,6 +1174,8 @@ void PDFDocument::ReadXRefStream(SvStream& rStream)
SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRefStream: Index has no number of objects"); SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRefStream: Index has no number of objects");
return; return;
} }
nNumberOfObjects = pNumberOfObjects->GetValue();
}
// Look up the format of a single entry. // Look up the format of a single entry.
const int nWSize = 3; const int nWSize = 3;
@@ -1145,15 +1206,14 @@ void PDFDocument::ReadXRefStream(SvStream& rStream)
return; return;
} }
size_t nSize = pNumberOfObjects->GetValue();
aStream.Seek(0); aStream.Seek(0);
// This is the line as read from the stream. // This is the line as read from the stream.
std::vector<unsigned char> aOrigLine(nLineLength); std::vector<unsigned char> aOrigLine(nLineLength);
// This is the line as it appears after tweaking according to nPredictor. // This is the line as it appears after tweaking according to nPredictor.
std::vector<unsigned char> aFilteredLine(nLineLength); std::vector<unsigned char> aFilteredLine(nLineLength);
for (size_t nEntry = 0; nEntry < nSize; ++nEntry) for (size_t nEntry = 0; nEntry < nNumberOfObjects; ++nEntry)
{ {
size_t nIndex = pFirstObject->GetValue() + nEntry; size_t nIndex = nFirstObject + nEntry;
aStream.ReadBytes(aOrigLine.data(), aOrigLine.size()); aStream.ReadBytes(aOrigLine.data(), aOrigLine.size());
if (aOrigLine[0] + 10 != nPredictor) if (aOrigLine[0] + 10 != nPredictor)
@@ -1210,12 +1270,15 @@ void PDFDocument::ReadXRefStream(SvStream& rStream)
} }
// "n" entry of the xref table // "n" entry of the xref table
if (nType == 1) if (nType == 1 || nType == 2)
{ {
if (m_aXRef.find(nIndex) == m_aXRef.end()) if (m_aXRef.find(nIndex) == m_aXRef.end())
{ {
m_aXRef[nIndex] = nStreamOffset; XRefEntry aEntry;
m_aXRefDirty[nIndex] = false; aEntry.m_eType = nType == 1 ? XRefEntryType::NOT_COMPRESSED : XRefEntryType::COMPRESSED;
aEntry.m_nOffset = nStreamOffset;
aEntry.m_nGenerationNumber = nGenerationNumber;
m_aXRef[nIndex] = aEntry;
} }
} }
} }
@@ -1223,19 +1286,6 @@ void PDFDocument::ReadXRefStream(SvStream& rStream)
void PDFDocument::ReadXRef(SvStream& rStream) void PDFDocument::ReadXRef(SvStream& rStream)
{ {
OString aKeyword = ReadKeyword(rStream);
if (aKeyword.isEmpty())
{
ReadXRefStream(rStream);
return;
}
if (aKeyword != "xref")
{
SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRef: xref is not the first keyword");
return;
}
PDFDocument::SkipWhitespace(rStream); PDFDocument::SkipWhitespace(rStream);
while (true) while (true)
@@ -1288,7 +1338,7 @@ void PDFDocument::ReadXRef(SvStream& rStream)
} }
PDFDocument::SkipWhitespace(rStream); PDFDocument::SkipWhitespace(rStream);
aKeyword = ReadKeyword(rStream); OString aKeyword = ReadKeyword(rStream);
if (aKeyword != "f" && aKeyword != "n") if (aKeyword != "f" && aKeyword != "n")
{ {
SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRef: unexpected keyword"); SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRef: unexpected keyword");
@@ -1298,9 +1348,13 @@ void PDFDocument::ReadXRef(SvStream& rStream)
// offset with an older one. // offset with an older one.
if (m_aXRef.find(nIndex) == m_aXRef.end()) if (m_aXRef.find(nIndex) == m_aXRef.end())
{ {
m_aXRef[nIndex] = aOffset.GetValue(); XRefEntry aEntry;
aEntry.m_nOffset = aOffset.GetValue();
aEntry.m_nGenerationNumber = aGenerationNumber.GetValue();
// Initially only the first entry is dirty. // Initially only the first entry is dirty.
m_aXRefDirty[nIndex] = nIndex == 0; if (nIndex == 0)
aEntry.m_bDirty = true;
m_aXRef[nIndex] = aEntry;
} }
PDFDocument::SkipWhitespace(rStream); PDFDocument::SkipWhitespace(rStream);
} }
@@ -1346,13 +1400,13 @@ void PDFDocument::SkipLineBreaks(SvStream& rStream)
size_t PDFDocument::GetObjectOffset(size_t nIndex) const size_t PDFDocument::GetObjectOffset(size_t nIndex) const
{ {
auto it = m_aXRef.find(nIndex); auto it = m_aXRef.find(nIndex);
if (it == m_aXRef.end()) if (it == m_aXRef.end() || it->second.m_eType == XRefEntryType::COMPRESSED)
{ {
SAL_WARN("xmlsecurity.pdfio", "PDFDocument::GetObjectOffset: wanted to look up index #" << nIndex << ", but failed"); SAL_WARN("xmlsecurity.pdfio", "PDFDocument::GetObjectOffset: wanted to look up index #" << nIndex << ", but failed");
return 0; return 0;
} }
return it->second; return it->second.m_nOffset;
} }
const std::vector< std::unique_ptr<PDFElement> >& PDFDocument::GetElements() const std::vector< std::unique_ptr<PDFElement> >& PDFDocument::GetElements()
@@ -1360,11 +1414,6 @@ const std::vector< std::unique_ptr<PDFElement> >& PDFDocument::GetElements()
return m_aElements; return m_aElements;
} }
const std::map<size_t, PDFObjectElement*>& PDFDocument::GetIDObjects() const
{
return m_aIDObjects;
}
std::vector<PDFObjectElement*> PDFDocument::GetPages() std::vector<PDFObjectElement*> PDFDocument::GetPages()
{ {
std::vector<PDFObjectElement*> aRet; std::vector<PDFObjectElement*> aRet;
@@ -2011,7 +2060,8 @@ PDFObjectElement::PDFObjectElement(PDFDocument& rDoc, double fObjectValue, doubl
m_nDictionaryOffset(0), m_nDictionaryOffset(0),
m_nDictionaryLength(0), m_nDictionaryLength(0),
m_pDictionaryElement(nullptr), m_pDictionaryElement(nullptr),
m_pArrayElement(nullptr) m_pArrayElement(nullptr),
m_pStreamElement(nullptr)
{ {
} }
@@ -2236,7 +2286,14 @@ PDFElement* PDFDictionaryElement::Lookup(const std::map<OString, PDFElement*>& r
PDFElement* PDFObjectElement::Lookup(const OString& rDictionaryKey) PDFElement* PDFObjectElement::Lookup(const OString& rDictionaryKey)
{ {
if (m_aDictionary.empty()) if (m_aDictionary.empty())
{
if (!m_aElements.empty())
// This is a stored object in an object stream.
PDFDictionaryElement::Parse(m_aElements, this, m_aDictionary);
else
// Normal object: elements are stored as members of the document itself.
PDFDictionaryElement::Parse(m_rDoc.GetElements(), this, m_aDictionary); PDFDictionaryElement::Parse(m_rDoc.GetElements(), this, m_aDictionary);
}
return PDFDictionaryElement::Lookup(m_aDictionary, rDictionaryKey); return PDFDictionaryElement::Lookup(m_aDictionary, rDictionaryKey);
} }
@@ -2332,11 +2389,139 @@ void PDFObjectElement::SetArray(PDFArrayElement* pArrayElement)
m_pArrayElement = pArrayElement; m_pArrayElement = pArrayElement;
} }
void PDFObjectElement::SetStream(PDFStreamElement* pStreamElement)
{
m_pStreamElement = pStreamElement;
}
PDFArrayElement* PDFObjectElement::GetArray() const PDFArrayElement* PDFObjectElement::GetArray() const
{ {
return m_pArrayElement; return m_pArrayElement;
} }
void PDFObjectElement::ParseStoredObjects()
{
if (!m_pStreamElement)
{
SAL_WARN("xmlsecurity.pdfio", "PDFObjectElement::ParseStoredObjects: no stream");
return;
}
auto pType = dynamic_cast<PDFNameElement*>(Lookup("Type"));
if (!pType || pType->GetValue() != "ObjStm")
{
SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRefStream: missing or unexpected type: " << pType->GetValue());
return;
}
auto pFilter = dynamic_cast<PDFNameElement*>(Lookup("Filter"));
if (!pFilter || pFilter->GetValue() != "FlateDecode")
{
SAL_WARN("xmlsecurity.pdfio", "PDFDocument::ReadXRefStream: missing or unexpected filter");
return;
}
auto pFirst = dynamic_cast<PDFNumberElement*>(Lookup("First"));
if (!pFirst)
{
SAL_WARN("xmlsecurity.pdfio", "PDFObjectElement::ParseStoredObjects: no First");
return;
}
auto pN = dynamic_cast<PDFNumberElement*>(Lookup("N"));
if (!pN)
{
SAL_WARN("xmlsecurity.pdfio", "PDFObjectElement::ParseStoredObjects: no N");
return;
}
size_t nN = pN->GetValue();
auto pLength = dynamic_cast<PDFNumberElement*>(Lookup("Length"));
if (!pLength)
{
SAL_WARN("xmlsecurity.pdfio", "PDFObjectElement::ParseStoredObjects: no length");
return;
}
size_t nLength = pLength->GetValue();
// Read and decompress it.
SvMemoryStream& rEditBuffer = m_rDoc.GetEditBuffer();
rEditBuffer.Seek(m_pStreamElement->GetOffset());
std::vector<char> aBuf(nLength);
rEditBuffer.ReadBytes(aBuf.data(), aBuf.size());
SvMemoryStream aSource(aBuf.data(), aBuf.size(), StreamMode::READ);
SvMemoryStream aStream;
ZCodec aZCodec;
aZCodec.BeginCompression();
aZCodec.Decompress(aSource, aStream);
if (!aZCodec.EndCompression())
{
SAL_WARN("xmlsecurity.pdfio", "PDFObjectElement::ParseStoredObjects: decompression failed");
return;
}
aStream.Seek(STREAM_SEEK_TO_END);
nLength = aStream.Tell();
aStream.Seek(0);
std::vector<size_t> aObjNums;
std::vector<size_t> aOffsets;
std::vector<size_t> aLengths;
// First iterate over and find out the lengths.
for (size_t nObject = 0; nObject < nN; ++nObject)
{
PDFNumberElement aObjNum;
if (!aObjNum.Read(aStream))
{
SAL_WARN("xmlsecurity.pdfio", "PDFObjectElement::ParseStoredObjects: failed to read object number");
return;
}
aObjNums.push_back(aObjNum.GetValue());
PDFDocument::SkipWhitespace(aStream);
PDFNumberElement aByteOffset;
if (!aByteOffset.Read(aStream))
{
SAL_WARN("xmlsecurity.pdfio", "PDFObjectElement::ParseStoredObjects: failed to read byte offset");
return;
}
aOffsets.push_back(pFirst->GetValue() + aByteOffset.GetValue());
if (aOffsets.size() > 1)
aLengths.push_back(aOffsets.back() - aOffsets[aOffsets.size() - 2]);
if (nObject + 1 == nN)
aLengths.push_back(nLength - aOffsets.back());
PDFDocument::SkipWhitespace(aStream);
}
// Now create streams with the proper length and tokenize the data.
for (size_t nObject = 0; nObject < nN; ++nObject)
{
size_t nObjNum = aObjNums[nObject];
size_t nOffset = aOffsets[nObject];
size_t nLen = aLengths[nObject];
aStream.Seek(nOffset);
m_aStoredElements.push_back(std::unique_ptr<PDFObjectElement>(new PDFObjectElement(m_rDoc, nObjNum, 0)));
PDFObjectElement* pStored = m_aStoredElements.back().get();
aBuf.clear();
aBuf.resize(nLen);
aStream.ReadBytes(aBuf.data(), aBuf.size());
SvMemoryStream aStoredStream(aBuf.data(), aBuf.size(), StreamMode::READ);
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);
}
}
std::vector< std::unique_ptr<PDFElement> >& PDFObjectElement::GetStoredElements()
{
return m_aElements;
}
PDFReferenceElement::PDFReferenceElement(PDFDocument& rDoc, int fObjectValue, int fGenerationValue) PDFReferenceElement::PDFReferenceElement(PDFDocument& rDoc, int fObjectValue, int fGenerationValue)
: m_rDoc(rDoc), : m_rDoc(rDoc),
m_fObjectValue(fObjectValue), m_fObjectValue(fObjectValue),
@@ -2409,17 +2594,43 @@ double PDFReferenceElement::LookupNumber(SvStream& rStream) const
return aNumber.GetValue(); return aNumber.GetValue();
} }
PDFObjectElement* PDFReferenceElement::LookupObject() const PDFObjectElement* PDFReferenceElement::LookupObject()
{ {
const std::map<size_t, PDFObjectElement*>& rIDObjects = m_rDoc.GetIDObjects(); return m_rDoc.LookupObject(m_fObjectValue);
auto it = rIDObjects.find(m_fObjectValue); }
if (it != rIDObjects.end())
return it->second;
SAL_WARN("xmlsecurity.pdfio", "PDFReferenceElement::LookupObject: can't find obj " << m_fObjectValue); PDFObjectElement* PDFDocument::LookupObject(size_t nObjectNumber)
{
auto itIDObjects = m_aIDObjects.find(nObjectNumber);
auto itXRef = m_aXRef.find(nObjectNumber);
if (itIDObjects == m_aIDObjects.end() && itXRef != m_aXRef.end())
{
// We don't have an object for this number yet, but there is an xref
// entry for it.
const XRefEntry& rEntry = itXRef->second;
if (rEntry.m_eType == XRefEntryType::COMPRESSED)
{
// It's a compressed entry, try parsing the stored objects.
if (PDFObjectElement* pObjectStream = LookupObject(rEntry.m_nOffset))
// This registers new IDs.
pObjectStream->ParseStoredObjects();
}
// Find again, now that the new objects are registered.
itIDObjects = m_aIDObjects.find(nObjectNumber);
}
if (itIDObjects != m_aIDObjects.end())
return itIDObjects->second;
SAL_WARN("xmlsecurity.pdfio", "PDFDocument::LookupObject: can't find obj " << nObjectNumber);
return nullptr; return nullptr;
} }
SvMemoryStream& PDFDocument::GetEditBuffer()
{
return m_aEditBuffer;
}
int PDFReferenceElement::GetObjectValue() const int PDFReferenceElement::GetObjectValue() const
{ {
return m_fObjectValue; return m_fObjectValue;