diff --git a/src/Directory.Build.props b/src/Directory.Build.props index c49e7686c..0a08cdf12 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -2,7 +2,7 @@ CA1822;CS1591;CS0649;xUnit1026;xUnit1013;CS1573;VerifyTestsProjectDir;VerifySetParameters;PolyFillTargetsForNuget;xUnit1051;NU1608;NU1109 - 31.16.0 + 31.16.1 enable preview 1.0.0 diff --git a/src/Verify.Tests/Compare/Png/PngDecoderTests.cs b/src/Verify.Tests/Compare/Png/PngDecoderTests.cs index 56fff801e..675b5297c 100644 --- a/src/Verify.Tests/Compare/Png/PngDecoderTests.cs +++ b/src/Verify.Tests/Compare/Png/PngDecoderTests.cs @@ -144,6 +144,52 @@ public void Large_256x256() Assert.Equal(rgba, image.Rgba); } + [Fact] + public void Small_GrayAlpha() + { + const int width = 4; + const int height = 4; + var ga = new byte[width * height * 2]; + for (var i = 0; i < width * height; i++) + { + ga[i * 2] = (byte)(i * 16); + ga[i * 2 + 1] = (byte)(255 - i * 8); + } + + var png = PngTestHelper.EncodeGrayAlpha(width, height, ga); + var image = PngDecoder.Decode(new MemoryStream(png)); + Assert.Equal(width, image.Width); + Assert.Equal(height, image.Height); + for (var i = 0; i < width * height; i++) + { + var g = ga[i * 2]; + Assert.Equal(g, image.Rgba[i * 4]); + Assert.Equal(g, image.Rgba[i * 4 + 1]); + Assert.Equal(g, image.Rgba[i * 4 + 2]); + Assert.Equal(ga[i * 2 + 1], image.Rgba[i * 4 + 3]); + } + } + + [Fact] + public void Multiple_Idat_Chunks() + { + const int width = 32; + const int height = 32; + var rgba = new byte[width * height * 4]; + new Random(7).NextBytes(rgba); + var png = PngTestHelper.EncodeRgbaMultipleIdat(width, height, rgba, chunkSize: 64); + var image = PngDecoder.Decode(new MemoryStream(png)); + Assert.Equal(rgba, image.Rgba); + } + + [Fact] + public void Rejects_Missing_Idat() + { + var png = PngTestHelper.EncodeWithoutIdat(1, 1); + var exception = Assert.Throws(() => PngDecoder.Decode(new MemoryStream(png))); + Assert.Contains("IDAT", exception.Message); + } + [Fact] public void Rejects_Bad_Signature() { diff --git a/src/Verify.Tests/Compare/Png/PngTestHelper.cs b/src/Verify.Tests/Compare/Png/PngTestHelper.cs index 61252f8bd..4adab1cc8 100644 --- a/src/Verify.Tests/Compare/Png/PngTestHelper.cs +++ b/src/Verify.Tests/Compare/Png/PngTestHelper.cs @@ -29,6 +29,13 @@ public static byte[] EncodeGray(int width, int height, byte[] gray) return BuildPng(ihdr, null, null, raw); } + public static byte[] EncodeGrayAlpha(int width, int height, byte[] grayAlpha) + { + var raw = AddFilterBytes(grayAlpha, width * 2, height); + var ihdr = BuildIhdr(width, height, colorType: 4); + return BuildPng(ihdr, null, null, raw); + } + public static byte[] EncodePaletted(int width, int height, byte[] indices, byte[] palette, byte[]? trns = null) { var raw = AddFilterBytes(indices, width, height); @@ -61,6 +68,34 @@ static byte[] BuildIhdr(int width, int height, byte colorType) return data; } + public static byte[] EncodeWithoutIdat(int width, int height) + { + var ihdr = BuildIhdr(width, height, colorType: 6); + using var stream = new MemoryStream(); + stream.Write(signature, 0, signature.Length); + WriteChunk(stream, "IHDR", ihdr); + WriteChunk(stream, "IEND", []); + return stream.ToArray(); + } + + public static byte[] EncodeRgbaMultipleIdat(int width, int height, byte[] rgba, int chunkSize) + { + var raw = AddFilterBytes(rgba, width * 4, height); + var compressed = ZlibCompress(raw); + var ihdr = BuildIhdr(width, height, colorType: 6); + using var stream = new MemoryStream(); + stream.Write(signature, 0, signature.Length); + WriteChunk(stream, "IHDR", ihdr); + for (var offset = 0; offset < compressed.Length; offset += chunkSize) + { + var length = Math.Min(chunkSize, compressed.Length - offset); + WriteChunk(stream, "IDAT", compressed.AsSpan(offset, length).ToArray()); + } + + WriteChunk(stream, "IEND", []); + return stream.ToArray(); + } + static byte[] BuildPng(byte[] ihdr, byte[]? plte, byte[]? trns, byte[] raw) { var compressed = ZlibCompress(raw); diff --git a/src/Verify/Compare/Png/PngDecoder.cs b/src/Verify/Compare/Png/PngDecoder.cs index 1385ece9b..780896814 100644 --- a/src/Verify/Compare/Png/PngDecoder.cs +++ b/src/Verify/Compare/Png/PngDecoder.cs @@ -31,13 +31,17 @@ public static PngImage Decode(Stream stream) while (true) { - ReadExact(stream, header); + if (stream.ReadAtLeast(header, header.Length, throwOnEndOfStream: false) < header.Length) + { + break; + } + var length = ReadUInt32BigEndian(header); var type = ((uint)header[4] << 24) | ((uint)header[5] << 16) | ((uint)header[6] << 8) | header[7]; if (length > int.MaxValue) { - throw new("PNG chunk too large."); + break; } var intLength = (int)length; @@ -106,6 +110,11 @@ public static PngImage Decode(Stream stream) throw new("PNG missing IHDR."); } + if (idat.Length == 0) + { + throw new("PNG missing IDAT."); + } + ReadExact(stream, crc); idat.Position = 0; return Reconstruct(idat, width, height, colorType, palette, transparency); @@ -118,6 +127,19 @@ public static PngImage Decode(Stream stream) ReadExact(stream, crc); } + + if (!seenIhdr) + { + throw new("PNG missing IHDR."); + } + + if (idat.Length == 0) + { + throw new("PNG missing IDAT."); + } + + idat.Position = 0; + return Reconstruct(idat, width, height, colorType, palette, transparency); } static PngImage Reconstruct(Stream idat, int width, int height, byte colorType, byte[]? palette, byte[]? trns)