@@ -114,27 +114,34 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
114114 /// </summary>
115115 private PngChunk ? nextChunk ;
116116
117+ /// <summary>
118+ /// How to handle CRC errors.
119+ /// </summary>
120+ private readonly PngCrcChunkHandling pngCrcChunkHandling ;
121+
117122 /// <summary>
118123 /// Initializes a new instance of the <see cref="PngDecoderCore"/> class.
119124 /// </summary>
120125 /// <param name="options">The decoder options.</param>
121- public PngDecoderCore ( DecoderOptions options )
126+ public PngDecoderCore ( PngDecoderOptions options )
122127 {
123- this . Options = options ;
124- this . configuration = options . Configuration ;
125- this . maxFrames = options . MaxFrames ;
126- this . skipMetadata = options . SkipMetadata ;
128+ this . Options = options . GeneralOptions ;
129+ this . configuration = options . GeneralOptions . Configuration ;
130+ this . maxFrames = options . GeneralOptions . MaxFrames ;
131+ this . skipMetadata = options . GeneralOptions . SkipMetadata ;
127132 this . memoryAllocator = this . configuration . MemoryAllocator ;
133+ this . pngCrcChunkHandling = options . PngCrcChunkHandling ;
128134 }
129135
130- internal PngDecoderCore ( DecoderOptions options , bool colorMetadataOnly )
136+ internal PngDecoderCore ( PngDecoderOptions options , bool colorMetadataOnly )
131137 {
132- this . Options = options ;
138+ this . Options = options . GeneralOptions ;
133139 this . colorMetadataOnly = colorMetadataOnly ;
134- this . maxFrames = options . MaxFrames ;
140+ this . maxFrames = options . GeneralOptions . MaxFrames ;
135141 this . skipMetadata = true ;
136- this . configuration = options . Configuration ;
142+ this . configuration = options . GeneralOptions . Configuration ;
137143 this . memoryAllocator = this . configuration . MemoryAllocator ;
144+ this . pngCrcChunkHandling = options . PngCrcChunkHandling ;
138145 }
139146
140147 /// <inheritdoc/>
@@ -576,11 +583,23 @@ private static void ReadGammaChunk(PngMetadata pngMetadata, ReadOnlySpan<byte> d
576583 private void InitializeImage < TPixel > ( ImageMetadata metadata , FrameControl frameControl , out Image < TPixel > image )
577584 where TPixel : unmanaged, IPixel < TPixel >
578585 {
579- image = Image . CreateUninitialized < TPixel > (
580- this . configuration ,
581- this . header . Width ,
582- this . header . Height ,
583- metadata ) ;
586+ // When ignoring data CRCs, we can't use the image constructor that leaves the buffer uncleared.
587+ if ( this . pngCrcChunkHandling is PngCrcChunkHandling . IgnoreData or PngCrcChunkHandling . IgnoreAll )
588+ {
589+ image = new Image < TPixel > (
590+ this . configuration ,
591+ this . header . Width ,
592+ this . header . Height ,
593+ metadata ) ;
594+ }
595+ else
596+ {
597+ image = Image . CreateUninitialized < TPixel > (
598+ this . configuration ,
599+ this . header . Width ,
600+ this . header . Height ,
601+ metadata ) ;
602+ }
584603
585604 PngFrameMetadata frameMetadata = image . Frames . RootFrame . Metadata . GetPngMetadata ( ) ;
586605 frameMetadata . FromChunk ( in frameControl ) ;
@@ -803,6 +822,11 @@ private void DecodePixelData<TPixel>(
803822 break ;
804823
805824 default :
825+ if ( this . pngCrcChunkHandling is PngCrcChunkHandling . IgnoreData or PngCrcChunkHandling . IgnoreAll )
826+ {
827+ goto EXIT ;
828+ }
829+
806830 PngThrowHelper . ThrowUnknownFilter ( ) ;
807831 break ;
808832 }
@@ -812,6 +836,7 @@ private void DecodePixelData<TPixel>(
812836 currentRow ++ ;
813837 }
814838
839+ EXIT :
815840 blendMemory ? . Dispose ( ) ;
816841 }
817842
@@ -903,6 +928,11 @@ private void DecodeInterlacedPixelData<TPixel>(
903928 break ;
904929
905930 default :
931+ if ( this . pngCrcChunkHandling is PngCrcChunkHandling . IgnoreData or PngCrcChunkHandling . IgnoreAll )
932+ {
933+ goto EXIT ;
934+ }
935+
906936 PngThrowHelper . ThrowUnknownFilter ( ) ;
907937 break ;
908938 }
@@ -937,6 +967,7 @@ private void DecodeInterlacedPixelData<TPixel>(
937967 }
938968 }
939969
970+ EXIT :
940971 blendMemory ? . Dispose ( ) ;
941972 }
942973
@@ -1364,7 +1395,7 @@ private void ReadCompressedTextChunk(ImageMetadata baseMetadata, PngMetadata met
13641395
13651396 ReadOnlySpan < byte > compressedData = data [ ( zeroIndex + 2 ) ..] ;
13661397
1367- if ( this . TryUncompressTextData ( compressedData , PngConstants . Encoding , out string ? uncompressed )
1398+ if ( this . TryDecompressTextData ( compressedData , PngConstants . Encoding , out string ? uncompressed )
13681399 && ! TryReadTextChunkMetadata ( baseMetadata , name , uncompressed ) )
13691400 {
13701401 metadata . TextData . Add ( new PngTextData ( name , uncompressed , string . Empty , string . Empty ) ) ;
@@ -1508,19 +1539,19 @@ private void ReadColorProfileChunk(ImageMetadata metadata, ReadOnlySpan<byte> da
15081539
15091540 ReadOnlySpan < byte > compressedData = data [ ( zeroIndex + 2 ) ..] ;
15101541
1511- if ( this . TryUncompressZlibData ( compressedData , out byte [ ] iccpProfileBytes ) )
1542+ if ( this . TryDecompressZlibData ( compressedData , out byte [ ] iccpProfileBytes ) )
15121543 {
15131544 metadata . IccProfile = new IccProfile ( iccpProfileBytes ) ;
15141545 }
15151546 }
15161547
15171548 /// <summary>
1518- /// Tries to un-compress zlib compressed data.
1549+ /// Tries to decompress zlib compressed data.
15191550 /// </summary>
15201551 /// <param name="compressedData">The compressed data.</param>
15211552 /// <param name="uncompressedBytesArray">The uncompressed bytes array.</param>
15221553 /// <returns>True, if de-compressing was successful.</returns>
1523- private unsafe bool TryUncompressZlibData ( ReadOnlySpan < byte > compressedData , out byte [ ] uncompressedBytesArray )
1554+ private unsafe bool TryDecompressZlibData ( ReadOnlySpan < byte > compressedData , out byte [ ] uncompressedBytesArray )
15241555 {
15251556 fixed ( byte * compressedDataBase = compressedData )
15261557 {
@@ -1657,7 +1688,7 @@ private void ReadInternationalTextChunk(ImageMetadata metadata, ReadOnlySpan<byt
16571688 {
16581689 ReadOnlySpan < byte > compressedData = data [ dataStartIdx ..] ;
16591690
1660- if ( this . TryUncompressTextData ( compressedData , PngConstants . TranslatedEncoding , out string ? uncompressed ) )
1691+ if ( this . TryDecompressTextData ( compressedData , PngConstants . TranslatedEncoding , out string ? uncompressed ) )
16611692 {
16621693 pngMetadata . TextData . Add ( new PngTextData ( keyword , uncompressed , language , translatedKeyword ) ) ;
16631694 }
@@ -1680,9 +1711,9 @@ private void ReadInternationalTextChunk(ImageMetadata metadata, ReadOnlySpan<byt
16801711 /// <param name="encoding">The string encoding to use.</param>
16811712 /// <param name="value">The uncompressed value.</param>
16821713 /// <returns>The <see cref="bool"/>.</returns>
1683- private bool TryUncompressTextData ( ReadOnlySpan < byte > compressedData , Encoding encoding , [ NotNullWhen ( true ) ] out string ? value )
1714+ private bool TryDecompressTextData ( ReadOnlySpan < byte > compressedData , Encoding encoding , [ NotNullWhen ( true ) ] out string ? value )
16841715 {
1685- if ( this . TryUncompressZlibData ( compressedData , out byte [ ] uncompressedData ) )
1716+ if ( this . TryDecompressZlibData ( compressedData , out byte [ ] uncompressedData ) )
16861717 {
16871718 value = encoding . GetString ( uncompressedData ) ;
16881719 return true ;
@@ -1705,7 +1736,11 @@ private int ReadNextDataChunk()
17051736
17061737 Span < byte > buffer = stackalloc byte [ 20 ] ;
17071738
1708- _ = this . currentStream . Read ( buffer , 0 , 4 ) ;
1739+ int length = this . currentStream . Read ( buffer , 0 , 4 ) ;
1740+ if ( length == 0 )
1741+ {
1742+ return 0 ;
1743+ }
17091744
17101745 if ( this . TryReadChunk ( buffer , out PngChunk chunk ) )
17111746 {
@@ -1734,7 +1769,11 @@ private int ReadNextFrameDataChunk()
17341769
17351770 Span < byte > buffer = stackalloc byte [ 20 ] ;
17361771
1737- _ = this . currentStream . Read ( buffer , 0 , 4 ) ;
1772+ int length = this . currentStream . Read ( buffer , 0 , 4 ) ;
1773+ if ( length == 0 )
1774+ {
1775+ return 0 ;
1776+ }
17381777
17391778 if ( this . TryReadChunk ( buffer , out PngChunk chunk ) )
17401779 {
@@ -1771,21 +1810,27 @@ private bool TryReadChunk(Span<byte> buffer, out PngChunk chunk)
17711810 return true ;
17721811 }
17731812
1774- if ( ! this . TryReadChunkLength ( buffer , out int length ) )
1813+ if ( this . currentStream . Position >= this . currentStream . Length - 1 )
17751814 {
1815+ // IEND
17761816 chunk = default ;
1817+ return false ;
1818+ }
17771819
1820+ if ( ! this . TryReadChunkLength ( buffer , out int length ) )
1821+ {
17781822 // IEND
1823+ chunk = default ;
17791824 return false ;
17801825 }
17811826
1782- while ( length < 0 || length > ( this . currentStream . Length - this . currentStream . Position ) )
1827+ while ( length < 0 )
17831828 {
17841829 // Not a valid chunk so try again until we reach a known chunk.
17851830 if ( ! this . TryReadChunkLength ( buffer , out length ) )
17861831 {
1832+ // IEND
17871833 chunk = default ;
1788-
17891834 return false ;
17901835 }
17911836 }
@@ -1797,13 +1842,14 @@ private bool TryReadChunk(Span<byte> buffer, out PngChunk chunk)
17971842 if ( this . colorMetadataOnly && type != PngChunkType . Header && type != PngChunkType . Transparency && type != PngChunkType . Palette )
17981843 {
17991844 chunk = new PngChunk ( length , type ) ;
1800-
18011845 return true ;
18021846 }
18031847
1804- long pos = this . currentStream . Position ;
1848+ // A chunk might report a length that exceeds the length of the stream.
1849+ // Take the minimum of the two values to ensure we don't read past the end of the stream.
1850+ long position = this . currentStream . Position ;
18051851 chunk = new PngChunk (
1806- length : length ,
1852+ length : ( int ) Math . Min ( length , this . currentStream . Length - position ) ,
18071853 type : type ,
18081854 data : this . ReadChunkData ( length ) ) ;
18091855
@@ -1813,7 +1859,7 @@ private bool TryReadChunk(Span<byte> buffer, out PngChunk chunk)
18131859 // was only read to verifying the CRC is correct.
18141860 if ( type is PngChunkType . Data or PngChunkType . FrameData )
18151861 {
1816- this . currentStream . Position = pos ;
1862+ this . currentStream . Position = position ;
18171863 }
18181864
18191865 return true ;
@@ -1827,8 +1873,7 @@ private bool TryReadChunk(Span<byte> buffer, out PngChunk chunk)
18271873 private void ValidateChunk ( in PngChunk chunk , Span < byte > buffer )
18281874 {
18291875 uint inputCrc = this . ReadChunkCrc ( buffer ) ;
1830-
1831- if ( chunk . IsCritical )
1876+ if ( chunk . IsCritical ( this . pngCrcChunkHandling ) )
18321877 {
18331878 Span < byte > chunkType = stackalloc byte [ 4 ] ;
18341879 BinaryPrimitives . WriteUInt32BigEndian ( chunkType , ( uint ) chunk . Type ) ;
0 commit comments