Browse Source

Allow decoding early EOF

pull/2589/head
James Jackson-South 2 years ago
parent
commit
3d9ceaa8fa
  1. 50
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  2. 19
      tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
  3. 1
      tests/ImageSharp.Tests/TestImages.cs
  4. 15
      tests/ImageSharp.Tests/TestUtilities/EofHitCounter.cs
  5. 3
      tests/Images/Input/Png/issues/Issue_2589.png

50
src/ImageSharp/Formats/Png/PngDecoderCore.cs

@ -583,11 +583,23 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
private void InitializeImage<TPixel>(ImageMetadata metadata, FrameControl frameControl, out Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel>
{
image = Image.CreateUninitialized<TPixel>(
this.configuration,
this.header.Width,
this.header.Height,
metadata);
// When ignoring data CRCs, we can't use the image constructor that leaves the buffer uncleared.
if (this.pngCrcChunkHandling is PngCrcChunkHandling.IgnoreData or PngCrcChunkHandling.IgnoreAll)
{
image = new Image<TPixel>(
this.configuration,
this.header.Width,
this.header.Height,
metadata);
}
else
{
image = Image.CreateUninitialized<TPixel>(
this.configuration,
this.header.Width,
this.header.Height,
metadata);
}
PngFrameMetadata frameMetadata = image.Frames.RootFrame.Metadata.GetPngFrameMetadata();
frameMetadata.FromChunk(in frameControl);
@ -808,6 +820,11 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
break;
default:
if (this.pngCrcChunkHandling is PngCrcChunkHandling.IgnoreData or PngCrcChunkHandling.IgnoreAll)
{
goto EXIT;
}
PngThrowHelper.ThrowUnknownFilter();
break;
}
@ -817,6 +834,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
currentRow++;
}
EXIT:
blendMemory?.Dispose();
}
@ -908,6 +926,11 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
break;
default:
if (this.pngCrcChunkHandling is PngCrcChunkHandling.IgnoreData or PngCrcChunkHandling.IgnoreAll)
{
goto EXIT;
}
PngThrowHelper.ThrowUnknownFilter();
break;
}
@ -942,6 +965,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
}
}
EXIT:
blendMemory?.Dispose();
}
@ -1761,21 +1785,25 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
return true;
}
if (!this.TryReadChunkLength(buffer, out int length))
if (this.currentStream.Position == this.currentStream.Length)
{
chunk = default;
return false;
}
if (!this.TryReadChunkLength(buffer, out int length))
{
// IEND
chunk = default;
return false;
}
while (length < 0 || length > (this.currentStream.Length - this.currentStream.Position))
while (length < 0)
{
// Not a valid chunk so try again until we reach a known chunk.
if (!this.TryReadChunkLength(buffer, out length))
{
chunk = default;
return false;
}
}
@ -1791,9 +1819,9 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
return true;
}
long pos = this.currentStream.Position;
long position = this.currentStream.Position;
chunk = new PngChunk(
length: length,
length: (int)Math.Min(length, this.currentStream.Length - position),
type: type,
data: this.ReadChunkData(length));
@ -1803,7 +1831,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
// was only read to verifying the CRC is correct.
if (type is PngChunkType.Data or PngChunkType.FrameData)
{
this.currentStream.Position = pos;
this.currentStream.Position = position;
}
return true;

19
tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs

@ -470,15 +470,21 @@ public partial class PngDecoderTests
Assert.Contains("CRC Error. PNG IDAT chunk is corrupt!", ex.Message);
}
// https://github.com/SixLabors/ImageSharp/pull/2589
[Theory]
[WithFile(TestImages.Png.Bad.WrongCrcDataChunk, PixelTypes.Rgba32)]
public void Decode_InvalidDataChunkCrc_IgnoreCrcErrors<TPixel>(TestImageProvider<TPixel> provider)
[WithFile(TestImages.Png.Bad.WrongCrcDataChunk, PixelTypes.Rgba32, true)]
[WithFile(TestImages.Png.Bad.Issue2589, PixelTypes.Rgba32, false)]
public void Decode_InvalidDataChunkCrc_IgnoreCrcErrors<TPixel>(TestImageProvider<TPixel> provider, bool compare)
where TPixel : unmanaged, IPixel<TPixel>
{
using Image<TPixel> image = provider.GetImage(PngDecoder.Instance, new PngDecoderOptions() { PngCrcChunkHandling = PngCrcChunkHandling.IgnoreData });
image.DebugSave(provider);
image.CompareToOriginal(provider, new MagickReferenceDecoder(false));
if (compare)
{
// Magick cannot actually decode this image to compare.
image.CompareToOriginal(provider, new MagickReferenceDecoder(false));
}
}
// https://github.com/SixLabors/ImageSharp/issues/1014
@ -651,9 +657,12 @@ public partial class PngDecoderTests
[Fact]
public void Binary_PrematureEof()
{
using EofHitCounter eofHitCounter = EofHitCounter.RunDecoder(TestImages.Png.Bad.FlagOfGermany0000016446);
PngDecoder decoder = PngDecoder.Instance;
PngDecoderOptions options = new() { PngCrcChunkHandling = PngCrcChunkHandling.IgnoreData };
using EofHitCounter eofHitCounter = EofHitCounter.RunDecoder(TestImages.Png.Bad.FlagOfGermany0000016446, decoder, options);
Assert.True(eofHitCounter.EofHitCount <= 2);
// TODO: Undo this.
Assert.True(eofHitCounter.EofHitCount <= 6);
Assert.Equal(new Size(200, 120), eofHitCounter.Image.Size);
}
}

1
tests/ImageSharp.Tests/TestImages.cs

@ -157,6 +157,7 @@ public static class TestImages
public const string MissingPaletteChunk1 = "Png/missing_plte.png";
public const string MissingPaletteChunk2 = "Png/missing_plte_2.png";
public const string InvalidGammaChunk = "Png/length_gama.png";
public const string Issue2589 = "Png/issues/Issue_2589.png";
// Zlib errors.
public const string ZlibOverflow = "Png/zlib-overflow.png";

15
tests/ImageSharp.Tests/TestUtilities/EofHitCounter.cs

@ -1,6 +1,7 @@
// Copyright (c) Six Labors.
// Licensed under the Six Labors Split License.
using SixLabors.ImageSharp.Formats;
using SixLabors.ImageSharp.IO;
namespace SixLabors.ImageSharp.Tests.TestUtilities;
@ -21,6 +22,11 @@ internal class EofHitCounter : IDisposable
public static EofHitCounter RunDecoder(string testImage) => RunDecoder(TestFile.Create(testImage).Bytes);
public static EofHitCounter RunDecoder<T, TO>(string testImage, T decoder, TO options)
where T : SpecializedImageDecoder<TO>
where TO : ISpecializedDecoderOptions
=> RunDecoder(TestFile.Create(testImage).Bytes, decoder, options);
public static EofHitCounter RunDecoder(byte[] imageData)
{
BufferedReadStream stream = new(Configuration.Default, new MemoryStream(imageData));
@ -28,6 +34,15 @@ internal class EofHitCounter : IDisposable
return new EofHitCounter(stream, image);
}
public static EofHitCounter RunDecoder<T, TO>(byte[] imageData, T decoder, TO options)
where T : SpecializedImageDecoder<TO>
where TO : ISpecializedDecoderOptions
{
BufferedReadStream stream = new(options.GeneralOptions.Configuration, new MemoryStream(imageData));
Image image = decoder.Decode(options, stream);
return new EofHitCounter(stream, image);
}
public void Dispose()
{
this.stream.Dispose();

3
tests/Images/Input/Png/issues/Issue_2589.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f7e87701298da4ba0a5cc14e9c04d810125f4958aa338255b14fd19dec15b677
size 62662
Loading…
Cancel
Save