vcl: size an appended cross-reference stream's offsets by the file

WriteXRef wrote /W[1 3 1] and three offset bytes whatever the document.
Signing a PDF larger than 16 MB recorded every offset in the appended update
modulo 16777216, so the updated catalog and the signature itself pointed into
the middle of the original body.

The field is as wide as the largest offset needs now. /DecodeParms/Columns
counts the bytes of a row apart from its predictor byte, so it follows the
field rather than staying at 5, and each byte is shifted out of the offset
rather than masked with an int too narrow to hold the mask.

Reachable only from a file whose cross-reference is already a stream, which is
why our own exports never met it. It has no test: the only path that appends a
cross-reference section is signing, and that needs a certificate.

Signed-off-by: Mike Kaganski <mike.kaganski@collabora.com>
Change-Id: I08279fcb43d0c538f66dc029890a58a76f6dab7e
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/13206
Tested-by: Jenkins CPCI <releng@collaboraoffice.com>
Reviewed-by: Miklos Vajna <vmiklos@collabora.com>
(cherry picked from commit 7e52e9eabbda9de67fedab5bf96a71902e53bc28)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/211445
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
Tested-by: Jenkins
This commit is contained in:
Mike Kaganski
2026-09-25 12:02:23 +02:00
committed by Xisco Fauli
parent 010611712f
commit 8343c6e702
+15 -6
View File
@@ -637,7 +637,14 @@ void PDFDocument::WriteXRef(sal_uInt64 nXRefOffset, PDFReferenceElement const* p
// Write stream data.
SvMemoryStream aXRefStream;
const size_t nOffsetLen = 3;
sal_uInt64 nMaxOffset = 0;
for (const auto& rXRef : m_aXRef)
if (rXRef.second.GetDirty())
nMaxOffset = std::max(nMaxOffset, rXRef.second.GetOffset());
// the second field has to hold the largest offset the update writes
size_t nOffsetLen = 1;
for (sal_uInt64 nRest = nMaxOffset; nRest > 0xff; nRest >>= 8)
++nOffsetLen;
// 3 additional bytes: predictor, the first and the third field.
const size_t nLineLength = nOffsetLen + 3;
// This is the line as it appears before tweaking according to the predictor.
@@ -680,8 +687,7 @@ void PDFDocument::WriteXRef(sal_uInt64 nXRefOffset, PDFReferenceElement const* p
size_t nByte = nOffsetLen - i - 1;
// Fields requiring more than one byte are stored with the
// high-order byte first.
unsigned char nCh = (rEntry.GetOffset() & (0xff << (nByte * 8))) >> (nByte * 8);
aOrigLine[nPos++] = nCh;
aOrigLine[nPos++] = static_cast<unsigned char>(rEntry.GetOffset() >> (nByte * 8));
}
// Third field.
@@ -701,8 +707,10 @@ void PDFDocument::WriteXRef(sal_uInt64 nXRefOffset, PDFReferenceElement const* p
}
m_aEditBuffer.WriteNumberAsString(nXRefStreamId);
m_aEditBuffer.WriteOString(
" 0 obj\n<</DecodeParms<</Columns 5/Predictor 12>>/Filter/FlateDecode");
m_aEditBuffer.WriteOString(" 0 obj\n<</DecodeParms<</Columns ");
// the bytes of a row apart from the predictor byte, so it follows /W
m_aEditBuffer.WriteNumberAsString(nLineLength - 1);
m_aEditBuffer.WriteOString("/Predictor 12>>/Filter/FlateDecode");
// ID.
auto pID = dynamic_cast<PDFArrayElement*>(m_pXRefStream->Lookup("ID"_ostr));
@@ -780,7 +788,8 @@ void PDFDocument::WriteXRef(sal_uInt64 nXRefOffset, PDFReferenceElement const* p
m_aEditBuffer.WriteOString("/Size ");
m_aEditBuffer.WriteNumberAsString(m_aXRef.size());
m_aEditBuffer.WriteOString("/Type/XRef/W[1 3 1]>>\nstream\n");
m_aEditBuffer.WriteOString(rtl::Concat2View(
"/Type/XRef/W[1 " + OString::number(sal_Int32(nOffsetLen)) + " 1]>>\nstream\n"));
aXRefStream.Seek(0);
m_aEditBuffer.WriteStream(aXRefStream);
m_aEditBuffer.WriteOString("\nendstream\nendobj\n\n");