Repository navigation
Description
PdfSharp.Pdf.Filters.FlateDecode.Encode writes the 2-byte zlib header (0x78 0xDA) and then raw deflate data via System.IO.Compression.DeflateStream. The 4-byte Adler-32 checksum that RFC 1950 requires at the end of a zlib stream is never written. /FlateDecode streams created by PDFsharp are therefore incomplete zlib streams (ISO 32000-1, 7.4.4.1 refers to RFC 1950).
Impact
Lenient readers (Acrobat, pdf.js, PDFsharp itself) stop after the final deflate block and never look for the checksum. Strict decoders reject the streams. For example, zlib's inflate() as used by Node.js zlib.inflateSync throws Z_BUF_ERROR: unexpected end of file.
In practice this broke ZUGFeRD/Factur-X invoices for us. An online e-invoice validator could not decompress the embedded factur-x.xml and reported that the PDF contained no XML attachment. After appending the 4 missing bytes to the attachment stream, without any other change, the same file validated successfully. All other Flate streams (fonts, images, content) are affected the same way.
Reproduction
var data = Encoding.ASCII.GetBytes("Wikipedia");
var encoded = new FlateDecode().Encode(data);
// Expected: encoded ends with 11 E6 03 98 (Adler-32 of "Wikipedia")
// Actual: encoded ends with the last byte of the deflate data
import zlib
d = zlib.decompressobj()
d.decompress(encoded)
print(d.eof) # False - stream incomplete; zlib.decompress(encoded) raises an error
Proposed fix
After zip.Close(), append the Adler-32 of the uncompressed data in big-endian order. A small manual implementation works on all target frameworks, including netstandard2.0 and net462, where ZLibStream is not available:
zip.Close();
// RFC 1950 requires the Adler-32 checksum of the uncompressed data after the deflate data
// (big-endian). DeflateStream writes raw deflate only, so it must be appended here.
var adler = ComputeAdler32(data);
ms.WriteByte((byte)(adler >> 24));
ms.WriteByte((byte)(adler >> 16));
ms.WriteByte((byte)(adler >> 8));
ms.WriteByte((byte)adler);
ms.Capacity = (int)ms.Length;
return ms.GetBuffer();
}
internal static uint ComputeAdler32(byte[] data)
{
const uint modAdler = 65521;
// Largest n for which the sums cannot overflow before the modulo is applied.
const int maxBlock = 5552;
uint a = 1, b = 0;
var offset = 0;
var remaining = data.Length;
while (remaining > 0)
{
var count = Math.Min(remaining, maxBlock);
remaining -= count;
while (count-- > 0)
{
a += data[offset++];
b += a;
}
a %= modAdler;
b %= modAdler;
}
return (b << 16) | a;
}
Activity
test: pin the zlib header and Adler-32 trailer on FlateDecode output
test: pin the zlib header and Adler-32 trailer on FlateDecode output (#…