Skip to content

Commit 5b73284

Browse files
Merge pull request #1005 from SixLabors/issue/1004
Fix #1004
2 parents 00e0508 + 221000e commit 5b73284

File tree

5 files changed

+92
-46
lines changed

5 files changed

+92
-46
lines changed

src/ImageSharp/Formats/Png/PngDecoderCore.cs

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Buffers.Binary;
66
using System.Collections.Generic;
77
using System.IO;
8+
using System.IO.Compression;
89
using System.Runtime.CompilerServices;
910
using System.Runtime.InteropServices;
1011
using System.Text;
@@ -175,11 +176,7 @@ public Image<TPixel> Decode<TPixel>(Stream stream)
175176
this.InitializeImage(metadata, out image);
176177
}
177178

178-
using (var deframeStream = new ZlibInflateStream(this.currentStream, this.ReadNextDataChunk))
179-
{
180-
deframeStream.AllocateNewBytes(chunk.Length);
181-
this.ReadScanlines(deframeStream.CompressedStream, image.Frames.RootFrame, pngMetadata);
182-
}
179+
this.ReadScanlines(chunk, image.Frames.RootFrame, pngMetadata);
183180

184181
break;
185182
case PngChunkType.Palette:
@@ -465,19 +462,25 @@ private int CalculateScanlineLength(int width)
465462
/// Reads the scanlines within the image.
466463
/// </summary>
467464
/// <typeparam name="TPixel">The pixel format.</typeparam>
468-
/// <param name="dataStream">The <see cref="MemoryStream"/> containing data.</param>
465+
/// <param name="chunk">The png chunk containing the compressed scanline data.</param>
469466
/// <param name="image"> The pixel data.</param>
470467
/// <param name="pngMetadata">The png metadata</param>
471-
private void ReadScanlines<TPixel>(Stream dataStream, ImageFrame<TPixel> image, PngMetadata pngMetadata)
468+
private void ReadScanlines<TPixel>(PngChunk chunk, ImageFrame<TPixel> image, PngMetadata pngMetadata)
472469
where TPixel : struct, IPixel<TPixel>
473470
{
474-
if (this.header.InterlaceMethod == PngInterlaceMode.Adam7)
475-
{
476-
this.DecodeInterlacedPixelData(dataStream, image, pngMetadata);
477-
}
478-
else
471+
using (var deframeStream = new ZlibInflateStream(this.currentStream, this.ReadNextDataChunk))
479472
{
480-
this.DecodePixelData(dataStream, image, pngMetadata);
473+
deframeStream.AllocateNewBytes(chunk.Length, true);
474+
DeflateStream dataStream = deframeStream.CompressedStream;
475+
476+
if (this.header.InterlaceMethod == PngInterlaceMode.Adam7)
477+
{
478+
this.DecodeInterlacedPixelData(dataStream, image, pngMetadata);
479+
}
480+
else
481+
{
482+
this.DecodePixelData(dataStream, image, pngMetadata);
483+
}
481484
}
482485
}
483486

@@ -924,7 +927,11 @@ private void ReadCompressedTextChunk(PngMetadata metadata, ReadOnlySpan<byte> da
924927
}
925928

926929
ReadOnlySpan<byte> compressedData = data.Slice(zeroIndex + 2);
927-
metadata.TextData.Add(new PngTextData(name, this.UncompressTextData(compressedData, PngConstants.Encoding), string.Empty, string.Empty));
930+
931+
if (this.TryUncompressTextData(compressedData, PngConstants.Encoding, out string uncompressed))
932+
{
933+
metadata.TextData.Add(new PngTextData(name, uncompressed, string.Empty, string.Empty));
934+
}
928935
}
929936

930937
/// <summary>
@@ -987,7 +994,11 @@ private void ReadInternationalTextChunk(PngMetadata metadata, ReadOnlySpan<byte>
987994
if (compressionFlag == 1)
988995
{
989996
ReadOnlySpan<byte> compressedData = data.Slice(dataStartIdx);
990-
metadata.TextData.Add(new PngTextData(keyword, this.UncompressTextData(compressedData, PngConstants.TranslatedEncoding), language, translatedKeyword));
997+
998+
if (this.TryUncompressTextData(compressedData, PngConstants.TranslatedEncoding, out string uncompressed))
999+
{
1000+
metadata.TextData.Add(new PngTextData(keyword, uncompressed, language, translatedKeyword));
1001+
}
9911002
}
9921003
else
9931004
{
@@ -1001,13 +1012,19 @@ private void ReadInternationalTextChunk(PngMetadata metadata, ReadOnlySpan<byte>
10011012
/// </summary>
10021013
/// <param name="compressedData">Compressed text data bytes.</param>
10031014
/// <param name="encoding">The string encoding to use.</param>
1004-
/// <returns>A string.</returns>
1005-
private string UncompressTextData(ReadOnlySpan<byte> compressedData, Encoding encoding)
1015+
/// <param name="value">The uncompressed value.</param>
1016+
/// <returns>The <see cref="bool"/>.</returns>
1017+
private bool TryUncompressTextData(ReadOnlySpan<byte> compressedData, Encoding encoding, out string value)
10061018
{
10071019
using (var memoryStream = new MemoryStream(compressedData.ToArray()))
1008-
using (var inflateStream = new ZlibInflateStream(memoryStream, () => 0))
1020+
using (var inflateStream = new ZlibInflateStream(memoryStream))
10091021
{
1010-
inflateStream.AllocateNewBytes(compressedData.Length);
1022+
if (!inflateStream.AllocateNewBytes(compressedData.Length, false))
1023+
{
1024+
value = null;
1025+
return false;
1026+
}
1027+
10111028
var uncompressedBytes = new List<byte>();
10121029

10131030
// Note: this uses the a buffer which is only 4 bytes long to read the stream, maybe allocating a larger buffer makes sense here.
@@ -1018,7 +1035,8 @@ private string UncompressTextData(ReadOnlySpan<byte> compressedData, Encoding en
10181035
bytesRead = inflateStream.CompressedStream.Read(this.buffer, 0, this.buffer.Length);
10191036
}
10201037

1021-
return encoding.GetString(uncompressedBytes.ToArray());
1038+
value = encoding.GetString(uncompressedBytes.ToArray());
1039+
return true;
10221040
}
10231041
}
10241042

src/ImageSharp/Formats/Png/Zlib/ZlibInflateStream.cs

Lines changed: 51 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,14 @@ internal sealed class ZlibInflateStream : Stream
2020
private static readonly byte[] ChecksumBuffer = new byte[4];
2121

2222
/// <summary>
23-
/// The inner raw memory stream
23+
/// A default delegate to get more data from the inner stream.
2424
/// </summary>
25-
private readonly Stream innerStream;
25+
private static readonly Func<int> GetDataNoOp = () => 0;
2626

2727
/// <summary>
28-
/// The compressed stream sitting over the top of the deframer
28+
/// The inner raw memory stream
2929
/// </summary>
30-
private DeflateStream compressedStream;
30+
private readonly Stream innerStream;
3131

3232
/// <summary>
3333
/// A value indicating whether this instance of the given entity has been disposed.
@@ -55,8 +55,17 @@ internal sealed class ZlibInflateStream : Stream
5555
/// <summary>
5656
/// Initializes a new instance of the <see cref="ZlibInflateStream"/> class.
5757
/// </summary>
58-
/// <param name="innerStream">The inner raw stream</param>
59-
/// <param name="getData">A delegate to get more data from the inner stream</param>
58+
/// <param name="innerStream">The inner raw stream.</param>
59+
public ZlibInflateStream(Stream innerStream)
60+
: this(innerStream, GetDataNoOp)
61+
{
62+
}
63+
64+
/// <summary>
65+
/// Initializes a new instance of the <see cref="ZlibInflateStream"/> class.
66+
/// </summary>
67+
/// <param name="innerStream">The inner raw stream.</param>
68+
/// <param name="getData">A delegate to get more data from the inner stream.</param>
6069
public ZlibInflateStream(Stream innerStream, Func<int> getData)
6170
{
6271
this.innerStream = innerStream;
@@ -76,31 +85,32 @@ public ZlibInflateStream(Stream innerStream, Func<int> getData)
7685
public override long Length => throw new NotSupportedException();
7786

7887
/// <inheritdoc/>
79-
public override long Position { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
88+
public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }
8089

8190
/// <summary>
8291
/// Gets the compressed stream over the deframed inner stream
8392
/// </summary>
84-
public DeflateStream CompressedStream => this.compressedStream;
93+
public DeflateStream CompressedStream { get; private set; }
8594

8695
/// <summary>
8796
/// Adds new bytes from a frame found in the original stream
8897
/// </summary>
8998
/// <param name="bytes">blabla</param>
90-
public void AllocateNewBytes(int bytes)
99+
/// <param name="isCriticalChunk">Whether the chunk to be inflated is a critical chunk.</param>
100+
/// <returns>The <see cref="bool"/>.</returns>
101+
public bool AllocateNewBytes(int bytes, bool isCriticalChunk)
91102
{
92103
this.currentDataRemaining = bytes;
93-
if (this.compressedStream is null)
104+
if (this.CompressedStream is null)
94105
{
95-
this.InitializeInflateStream();
106+
return this.InitializeInflateStream(isCriticalChunk);
96107
}
108+
109+
return true;
97110
}
98111

99112
/// <inheritdoc/>
100-
public override void Flush()
101-
{
102-
throw new NotSupportedException();
103-
}
113+
public override void Flush() => throw new NotSupportedException();
104114

105115
/// <inheritdoc/>
106116
public override int ReadByte()
@@ -182,10 +192,10 @@ protected override void Dispose(bool disposing)
182192
if (disposing)
183193
{
184194
// dispose managed resources
185-
if (this.compressedStream != null)
195+
if (this.CompressedStream != null)
186196
{
187-
this.compressedStream.Dispose();
188-
this.compressedStream = null;
197+
this.CompressedStream.Dispose();
198+
this.CompressedStream = null;
189199
}
190200
}
191201

@@ -197,7 +207,7 @@ protected override void Dispose(bool disposing)
197207
this.isDisposed = true;
198208
}
199209

200-
private void InitializeInflateStream()
210+
private bool InitializeInflateStream(bool isCriticalChunk)
201211
{
202212
// Read the zlib header : http://tools.ietf.org/html/rfc1950
203213
// CMF(Compression Method and flags)
@@ -215,7 +225,7 @@ private void InitializeInflateStream()
215225
this.currentDataRemaining -= 2;
216226
if (cmf == -1 || flag == -1)
217227
{
218-
return;
228+
return false;
219229
}
220230

221231
if ((cmf & 0x0F) == 8)
@@ -225,14 +235,28 @@ private void InitializeInflateStream()
225235

226236
if (cinfo > 7)
227237
{
228-
// Values of CINFO above 7 are not allowed in RFC1950.
229-
// CINFO is not defined in this specification for CM not equal to 8.
230-
throw new ImageFormatException($"Invalid window size for ZLIB header: cinfo={cinfo}");
238+
if (isCriticalChunk)
239+
{
240+
// Values of CINFO above 7 are not allowed in RFC1950.
241+
// CINFO is not defined in this specification for CM not equal to 8.
242+
throw new ImageFormatException($"Invalid window size for ZLIB header: cinfo={cinfo}");
243+
}
244+
else
245+
{
246+
return false;
247+
}
231248
}
232249
}
233250
else
234251
{
235-
throw new ImageFormatException($"Bad method for ZLIB header: cmf={cmf}");
252+
if (isCriticalChunk)
253+
{
254+
throw new ImageFormatException($"Bad method for ZLIB header: cmf={cmf}");
255+
}
256+
else
257+
{
258+
return false;
259+
}
236260
}
237261

238262
// The preset dictionary.
@@ -246,7 +270,9 @@ private void InitializeInflateStream()
246270
}
247271

248272
// Initialize the deflate Stream.
249-
this.compressedStream = new DeflateStream(this, CompressionMode.Decompress, true);
273+
this.CompressedStream = new DeflateStream(this, CompressionMode.Decompress, true);
274+
275+
return true;
250276
}
251277
}
252278
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ public partial class PngDecoderTests
4040
TestImages.Png.GrayAlpha8Bit,
4141
TestImages.Png.Gray1BitTrans,
4242
TestImages.Png.Bad.ZlibOverflow,
43-
TestImages.Png.Bad.ZlibOverflow2
43+
TestImages.Png.Bad.ZlibOverflow2,
44+
TestImages.Png.Bad.ZlibZtxtBadHeader,
4445
};
4546

4647
public static readonly string[] TestImages48Bpp =

tests/ImageSharp.Tests/TestImages.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ public static class Bad
9090
public const string CorruptedChunk = "Png/big-corrupted-chunk.png";
9191
public const string ZlibOverflow = "Png/zlib-overflow.png";
9292
public const string ZlibOverflow2 = "Png/zlib-overflow2.png";
93+
public const string ZlibZtxtBadHeader = "Png/zlib-ztxt-bad-header.png";
9394
}
9495

9596
public static readonly string[] All =
22.2 KB
Loading

0 commit comments

Comments
 (0)