diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 7fb96241a3..84eea9a5a5 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -1395,7 +1395,7 @@ private void ReadCompressedTextChunk(ImageMetadata baseMetadata, PngMetadata met ReadOnlySpan compressedData = data[(zeroIndex + 2)..]; - if (this.TryUncompressTextData(compressedData, PngConstants.Encoding, out string? uncompressed) + if (this.TryDecompressTextData(compressedData, PngConstants.Encoding, out string? uncompressed) && !TryReadTextChunkMetadata(baseMetadata, name, uncompressed)) { metadata.TextData.Add(new PngTextData(name, uncompressed, string.Empty, string.Empty)); @@ -1539,19 +1539,19 @@ private void ReadColorProfileChunk(ImageMetadata metadata, ReadOnlySpan da ReadOnlySpan compressedData = data[(zeroIndex + 2)..]; - if (this.TryUncompressZlibData(compressedData, out byte[] iccpProfileBytes)) + if (this.TryDecompressZlibData(compressedData, out byte[] iccpProfileBytes)) { metadata.IccProfile = new IccProfile(iccpProfileBytes); } } /// - /// Tries to un-compress zlib compressed data. + /// Tries to decompress zlib compressed data. /// /// The compressed data. /// The uncompressed bytes array. /// True, if de-compressing was successful. - private unsafe bool TryUncompressZlibData(ReadOnlySpan compressedData, out byte[] uncompressedBytesArray) + private unsafe bool TryDecompressZlibData(ReadOnlySpan compressedData, out byte[] uncompressedBytesArray) { fixed (byte* compressedDataBase = compressedData) { @@ -1688,7 +1688,7 @@ private void ReadInternationalTextChunk(ImageMetadata metadata, ReadOnlySpan compressedData = data[dataStartIdx..]; - if (this.TryUncompressTextData(compressedData, PngConstants.TranslatedEncoding, out string? uncompressed)) + if (this.TryDecompressTextData(compressedData, PngConstants.TranslatedEncoding, out string? uncompressed)) { pngMetadata.TextData.Add(new PngTextData(keyword, uncompressed, language, translatedKeyword)); } @@ -1711,9 +1711,9 @@ private void ReadInternationalTextChunk(ImageMetadata metadata, ReadOnlySpanThe string encoding to use. /// The uncompressed value. /// The . - private bool TryUncompressTextData(ReadOnlySpan compressedData, Encoding encoding, [NotNullWhen(true)] out string? value) + private bool TryDecompressTextData(ReadOnlySpan compressedData, Encoding encoding, [NotNullWhen(true)] out string? value) { - if (this.TryUncompressZlibData(compressedData, out byte[] uncompressedData)) + if (this.TryDecompressZlibData(compressedData, out byte[] uncompressedData)) { value = encoding.GetString(uncompressedData); return true; @@ -1736,7 +1736,11 @@ private int ReadNextDataChunk() Span buffer = stackalloc byte[20]; - _ = this.currentStream.Read(buffer, 0, 4); + int length = this.currentStream.Read(buffer, 0, 4); + if (length == 0) + { + return 0; + } if (this.TryReadChunk(buffer, out PngChunk chunk)) { @@ -1765,7 +1769,11 @@ private int ReadNextFrameDataChunk() Span buffer = stackalloc byte[20]; - _ = this.currentStream.Read(buffer, 0, 4); + int length = this.currentStream.Read(buffer, 0, 4); + if (length == 0) + { + return 0; + } if (this.TryReadChunk(buffer, out PngChunk chunk)) { @@ -1802,8 +1810,9 @@ private bool TryReadChunk(Span buffer, out PngChunk chunk) return true; } - if (this.currentStream.Position == this.currentStream.Length) + if (this.currentStream.Position >= this.currentStream.Length - 1) { + // IEND chunk = default; return false; } @@ -1820,6 +1829,7 @@ private bool TryReadChunk(Span buffer, out PngChunk chunk) // Not a valid chunk so try again until we reach a known chunk. if (!this.TryReadChunkLength(buffer, out length)) { + // IEND chunk = default; return false; } @@ -1832,10 +1842,11 @@ private bool TryReadChunk(Span buffer, out PngChunk chunk) if (this.colorMetadataOnly && type != PngChunkType.Header && type != PngChunkType.Transparency && type != PngChunkType.Palette) { chunk = new PngChunk(length, type); - return true; } + // A chunk might report a length that exceeds the length of the stream. + // Take the minimum of the two values to ensure we don't read past the end of the stream. long position = this.currentStream.Position; chunk = new PngChunk( length: (int)Math.Min(length, this.currentStream.Length - position), diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index ee6e15d7dc..bc277bf485 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -661,8 +661,8 @@ public void Binary_PrematureEof() PngDecoderOptions options = new() { PngCrcChunkHandling = PngCrcChunkHandling.IgnoreData }; using EofHitCounter eofHitCounter = EofHitCounter.RunDecoder(TestImages.Png.Bad.FlagOfGermany0000016446, decoder, options); - // TODO: Undo this. - Assert.True(eofHitCounter.EofHitCount <= 6); + // TODO: Try to reduce this to 1. + Assert.True(eofHitCounter.EofHitCount <= 3); Assert.Equal(new Size(200, 120), eofHitCounter.Image.Size); } }