diff --git a/src/ImageSharp/Formats/DecoderOptions.cs b/src/ImageSharp/Formats/DecoderOptions.cs index 6243a071d5..3b16159b7e 100644 --- a/src/ImageSharp/Formats/DecoderOptions.cs +++ b/src/ImageSharp/Formats/DecoderOptions.cs @@ -55,5 +55,10 @@ public sealed class DecoderOptions /// public uint MaxFrames { get => this.maxFrames; init => this.maxFrames = Math.Clamp(value, 1, int.MaxValue); } + /// + /// Gets the segment error handling strategy to use during decoding. + /// + public SegmentIntegrityHandling SegmentIntegrityHandling { get; init; } = SegmentIntegrityHandling.IgnoreNonCritical; + internal void SetConfiguration(Configuration configuration) => this.configuration = configuration; } diff --git a/src/ImageSharp/Formats/Png/PngChunk.cs b/src/ImageSharp/Formats/Png/PngChunk.cs index 6ec0df9ad6..3883986d06 100644 --- a/src/ImageSharp/Formats/Png/PngChunk.cs +++ b/src/ImageSharp/Formats/Png/PngChunk.cs @@ -41,13 +41,13 @@ public PngChunk(int length, PngChunkType type, IMemoryOwner data = null) /// /// Gets a value indicating whether the given chunk is critical to decoding /// - /// The chunk CRC handling behavior. - public bool IsCritical(PngCrcChunkHandling handling) + /// The segment handling behavior. + public bool IsCritical(SegmentIntegrityHandling handling) => handling switch { - PngCrcChunkHandling.IgnoreNone => true, - PngCrcChunkHandling.IgnoreNonCritical => this.Type is PngChunkType.Header or PngChunkType.Palette or PngChunkType.Data or PngChunkType.FrameData, - PngCrcChunkHandling.IgnoreData => this.Type is PngChunkType.Header or PngChunkType.Palette, + SegmentIntegrityHandling.IgnoreNone => true, + SegmentIntegrityHandling.IgnoreNonCritical => this.Type is PngChunkType.Header or PngChunkType.Palette or PngChunkType.Data or PngChunkType.FrameData, + SegmentIntegrityHandling.IgnoreData => this.Type is PngChunkType.Header or PngChunkType.Palette, _ => false, }; } diff --git a/src/ImageSharp/Formats/Png/PngCrcChunkHandling.cs b/src/ImageSharp/Formats/Png/PngCrcChunkHandling.cs deleted file mode 100644 index 264d737fdc..0000000000 --- a/src/ImageSharp/Formats/Png/PngCrcChunkHandling.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Six Labors. -// Licensed under the Six Labors Split License. - -namespace SixLabors.ImageSharp.Formats.Png; - -/// -/// Specifies how to handle validation of any CRC (Cyclic Redundancy Check) data within the encoded PNG. -/// -public enum PngCrcChunkHandling -{ - /// - /// Do not ignore any CRC chunk errors. - /// - IgnoreNone, - - /// - /// Ignore CRC errors in non critical chunks. - /// - IgnoreNonCritical, - - /// - /// Ignore CRC errors in data chunks. - /// - IgnoreData, - - /// - /// Ignore CRC errors in all chunks. - /// - IgnoreAll -} diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 01f038141a..484241d52f 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -119,7 +119,7 @@ internal sealed class PngDecoderCore : ImageDecoderCore /// /// How to handle CRC errors. /// - private readonly PngCrcChunkHandling pngCrcChunkHandling; + private readonly SegmentIntegrityHandling segmentIntegrityHandling; /// /// A reusable Crc32 hashing instance. @@ -142,7 +142,7 @@ public PngDecoderCore(PngDecoderOptions options) this.maxFrames = options.GeneralOptions.MaxFrames; this.skipMetadata = options.GeneralOptions.SkipMetadata; this.memoryAllocator = this.configuration.MemoryAllocator; - this.pngCrcChunkHandling = options.PngCrcChunkHandling; + this.segmentIntegrityHandling = options.GeneralOptions.SegmentIntegrityHandling; this.maxUncompressedLength = options.MaxUncompressedAncillaryChunkSizeBytes; } @@ -154,7 +154,7 @@ internal PngDecoderCore(PngDecoderOptions options, bool colorMetadataOnly) this.skipMetadata = true; this.configuration = options.GeneralOptions.Configuration; this.memoryAllocator = this.configuration.MemoryAllocator; - this.pngCrcChunkHandling = options.PngCrcChunkHandling; + this.segmentIntegrityHandling = options.GeneralOptions.SegmentIntegrityHandling; this.maxUncompressedLength = options.MaxUncompressedAncillaryChunkSizeBytes; } @@ -833,7 +833,7 @@ private void DecodePixelData( break; default: - if (this.pngCrcChunkHandling is PngCrcChunkHandling.IgnoreData or PngCrcChunkHandling.IgnoreAll) + if (this.segmentIntegrityHandling is SegmentIntegrityHandling.IgnoreData or SegmentIntegrityHandling.IgnoreAll) { goto EXIT; } @@ -939,7 +939,7 @@ private void DecodeInterlacedPixelData( break; default: - if (this.pngCrcChunkHandling is PngCrcChunkHandling.IgnoreData or PngCrcChunkHandling.IgnoreAll) + if (this.segmentIntegrityHandling is SegmentIntegrityHandling.IgnoreData or SegmentIntegrityHandling.IgnoreAll) { goto EXIT; } @@ -1927,7 +1927,7 @@ private bool TryReadChunk(Span buffer, out PngChunk chunk) private void ValidateChunk(in PngChunk chunk, Span buffer) { uint inputCrc = this.ReadChunkCrc(buffer); - if (chunk.IsCritical(this.pngCrcChunkHandling)) + if (chunk.IsCritical(this.segmentIntegrityHandling)) { Span chunkType = stackalloc byte[4]; BinaryPrimitives.WriteUInt32BigEndian(chunkType, (uint)chunk.Type); diff --git a/src/ImageSharp/Formats/Png/PngDecoderOptions.cs b/src/ImageSharp/Formats/Png/PngDecoderOptions.cs index abfa4b1da8..a73db87774 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderOptions.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderOptions.cs @@ -11,11 +11,6 @@ public sealed class PngDecoderOptions : ISpecializedDecoderOptions /// public DecoderOptions GeneralOptions { get; init; } = new DecoderOptions(); - /// - /// Gets a value indicating how to handle validation of any CRC (Cyclic Redundancy Check) data within the encoded PNG. - /// - public PngCrcChunkHandling PngCrcChunkHandling { get; init; } = PngCrcChunkHandling.IgnoreNonCritical; - /// /// Gets the maximum memory in bytes that a zTXt, sPLT, iTXt, iCCP, or unknown chunk can occupy when decompressed. /// Defaults to 8MB diff --git a/src/ImageSharp/Formats/SegmentIntegrityHandling.cs b/src/ImageSharp/Formats/SegmentIntegrityHandling.cs new file mode 100644 index 0000000000..977aee4ad5 --- /dev/null +++ b/src/ImageSharp/Formats/SegmentIntegrityHandling.cs @@ -0,0 +1,30 @@ +// Copyright (c) Six Labors. +// Licensed under the Six Labors Split License. + +namespace SixLabors.ImageSharp.Formats; + +/// +/// Specifies how to handle validation of errors in different segments of encoded image files. +/// +public enum SegmentIntegrityHandling +{ + /// + /// Do not ignore any errors. + /// + IgnoreNone, + + /// + /// Ignore errors in non-critical segments of the encoded image. + /// + IgnoreNonCritical, + + /// + /// Ignore errors in data segments (e.g., image data, metadata). + /// + IgnoreData, + + /// + /// Ignore errors in all segments. + /// + IgnoreAll +} diff --git a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs index 8492d78f86..9f3c5f6828 100644 --- a/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs @@ -381,6 +381,20 @@ public void Identify(string imagePath, int expectedPixelSize) Assert.Equal(expectedPixelSize, imageInfo.PixelType.BitsPerPixel); } + [Theory] + [InlineData(TestImages.Png.Bad.WrongCrcDataChunk, 1)] + [InlineData(TestImages.Png.Bad.Issue2589, 24)] + public void Identify_IgnoreCrcErrors(string imagePath, int expectedPixelSize) + { + TestFile testFile = TestFile.Create(imagePath); + using MemoryStream stream = new(testFile.Bytes, false); + + ImageInfo imageInfo = Image.Identify(new DecoderOptions() { SegmentIntegrityHandling = SegmentIntegrityHandling.IgnoreData }, stream); + + Assert.NotNull(imageInfo); + Assert.Equal(expectedPixelSize, imageInfo.PixelType.BitsPerPixel); + } + [Theory] [WithFile(TestImages.Png.Bad.MissingDataChunk, PixelTypes.Rgba32)] public void Decode_MissingDataChunk_ThrowsException(TestImageProvider provider) @@ -479,7 +493,7 @@ public void Decode_InvalidDataChunkCrc_ThrowsException(TestImageProvider public void Decode_InvalidDataChunkCrc_IgnoreCrcErrors(TestImageProvider provider, bool compare) where TPixel : unmanaged, IPixel { - using Image image = provider.GetImage(PngDecoder.Instance, new PngDecoderOptions() { PngCrcChunkHandling = PngCrcChunkHandling.IgnoreData }); + using Image image = provider.GetImage(PngDecoder.Instance, new DecoderOptions() { SegmentIntegrityHandling = SegmentIntegrityHandling.IgnoreData }); image.DebugSave(provider); if (compare) @@ -660,7 +674,7 @@ static void RunTest(string providerDump, string nonContiguousBuffersStr) public void Binary_PrematureEof() { PngDecoder decoder = PngDecoder.Instance; - PngDecoderOptions options = new() { PngCrcChunkHandling = PngCrcChunkHandling.IgnoreData }; + PngDecoderOptions options = new() { GeneralOptions = new() { SegmentIntegrityHandling = SegmentIntegrityHandling.IgnoreData } }; using EofHitCounter eofHitCounter = EofHitCounter.RunDecoder(TestImages.Png.Bad.FlagOfGermany0000016446, decoder, options); // TODO: Try to reduce this to 1.