diff --git a/src/libraries/Common/tests/System/IO/Compression/EncoderDecoderTestBase.cs b/src/libraries/Common/tests/System/IO/Compression/EncoderDecoderTestBase.cs index 9492c2bb185b16..af66929496225b 100644 --- a/src/libraries/Common/tests/System/IO/Compression/EncoderDecoderTestBase.cs +++ b/src/libraries/Common/tests/System/IO/Compression/EncoderDecoderTestBase.cs @@ -776,7 +776,6 @@ public void RoundTrip_AllCompressionLevels() } [Fact] - [ActiveIssue("https://github.com/dotnet/runtime/issues/127563", TestPlatforms.iOS | TestPlatforms.tvOS | TestPlatforms.MacCatalyst | TestPlatforms.Android)] public void RoundTrip_AllWindowLogs() { byte[] input = CreateTestData(); diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/DeflateEncoder.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/DeflateEncoder.cs index f90a62291459f7..c1a96a48a88aab 100644 --- a/src/libraries/System.IO.Compression/src/System/IO/Compression/DeflateEncoder.cs +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/DeflateEncoder.cs @@ -144,30 +144,16 @@ public static long GetMaxCompressedLength(long inputLength) { ArgumentOutOfRangeException.ThrowIfNegative(inputLength); - // For inputs up to 2 GiB, delegate to the native compressBound() function, which returns - // the exact upper bound for the zlib implementation linked into the current process - // (either classic zlib or zlib-ng, depending on platform and build flags). The 2^31 - // threshold keeps the value within the uint P/Invoke signature on all platforms. - - // Browser/WASI builds do not link the native compression library, - // so fall through to the managed formula on those platforms. - if (inputLength <= (1L << 31) && !OperatingSystem.IsBrowser() && !OperatingSystem.IsWasi()) - { - return Interop.ZLib.compressBound((uint)inputLength); - } - - // For larger inputs, compute the bound in managed code using zlib-ng's quick-strategy - // formula. It is strictly larger than classic zlib's compressBound(), so it is a safe - // upper bound regardless of which implementation is linked at runtime. - // See: src/native/external/zlib-ng/compress.c and zutil.h. - // Use ulong to avoid overflow; reject inputs whose bound does not fit in long. + // This method does not know which windowLog2 or quality the caller will use, so + // compute the maximum of the bounds used by zlib-ng's deflateBound(). The code below mimics the logic + // to compute the maximum possible size returned by deflateBound. Our API doesn't expose all knobs + // so z_stream's strstart and gzhead are always null. We also don't handle the s390 corner case. ulong sourceLength = (ulong)inputLength; - ulong maxCompressedLength = sourceLength - + (sourceLength == 0 ? 1u : 0u) - + (sourceLength < 9 ? 1u : 0u) - + ((sourceLength + 7) >> 3) - + 3 // DEFLATE_BLOCK_OVERHEAD: (3 + 15 + 6) >> 3 - + 6; // ZLIB_WRAPLEN: zlib header (2 bytes) + Adler32 trailer (4 bytes) + const ulong wrapLength = 6; // GZIP_WRAPLEN is 18, GZipEncoder compensates for the rest + ulong maxCompressedLength = sourceLength + ((sourceLength + 7) >> 3) + ((sourceLength + 63) >> 6) + 5; + ulong storedBlockBound = sourceLength + (sourceLength >> 5) + (sourceLength >> 7) + (sourceLength >> 11) + 7; + + maxCompressedLength = Math.Max(maxCompressedLength, storedBlockBound) + wrapLength; if (maxCompressedLength > long.MaxValue) { diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/GZipEncoder.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/GZipEncoder.cs index 8c5442bf497f76..32aa377da137c6 100644 --- a/src/libraries/System.IO.Compression/src/System/IO/Compression/GZipEncoder.cs +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/GZipEncoder.cs @@ -78,10 +78,9 @@ private void EnsureNotDisposed() /// is negative. public static long GetMaxCompressedLength(long inputLength) { - // compressBound() returns the upper bound for zlib-wrapped deflate, which includes - // 6 bytes of zlib overhead (2-byte header + 4-byte Adler32 trailer). - // GZip format uses 18 bytes of overhead (10-byte header + 8-byte CRC32/size trailer), - // which is 12 bytes more than the zlib overhead already included in compressBound(). + // DeflateEncoder.GetMaxCompressedLength() returns the bound for zlib-wrapped deflate, + // where wrap length is at most 6 bytes. GZip format uses 18 bytes of overhead, which is + // 12 bytes more than the conservative zlib overhead already included in that bound. long maxCompressedLength = DeflateEncoder.GetMaxCompressedLength(inputLength); if (maxCompressedLength > long.MaxValue - 12) diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZLibEncoder.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZLibEncoder.cs index 0749efc0b55ffa..891d49510ae2e7 100644 --- a/src/libraries/System.IO.Compression/src/System/IO/Compression/ZLibEncoder.cs +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/ZLibEncoder.cs @@ -78,9 +78,9 @@ private void EnsureNotDisposed() /// is negative. public static long GetMaxCompressedLength(long inputLength) { - // compressBound() returns the upper bound for zlib-wrapped deflate output, which already + // DeflateEncoder.GetMaxCompressedLength() returns a bound for deflate output that already // accounts for zlib's 2-byte header and 4-byte Adler32 trailer. ZLibEncoder produces - // zlib-format output, so this value is the exact upper bound with no additional overhead needed. + // zlib-format output, so this value is a valid upper bound with no additional overhead needed. return DeflateEncoder.GetMaxCompressedLength(inputLength); }