Browse Source

Merge branch 'release/3.1.x' into js/v3-updates

pull/2690/head
James Jackson-South 3 years ago
parent
commit
3e042a2c87
  1. 1
      .github/workflows/build-and-test.yml
  2. 37
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  3. 6
      src/ImageSharp/Formats/Png/PngDecoderOptions.cs
  4. 19
      tests/ImageSharp.Tests/Formats/Png/PngDecoderTests.cs
  5. 4
      tests/ImageSharp.Tests/TestImages.cs
  6. 3
      tests/Images/Input/Png/issues/bad-ztxt.png
  7. 3
      tests/Images/Input/Png/issues/bad-ztxt2.png

1
.github/workflows/build-and-test.yml

@ -4,6 +4,7 @@ on:
push: push:
branches: branches:
- main - main
- release/*
tags: tags:
- "v*" - "v*"
pull_request: pull_request:

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

@ -125,6 +125,10 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
/// A reusable Crc32 hashing instance. /// A reusable Crc32 hashing instance.
/// </summary> /// </summary>
private readonly Crc32 crc32 = new(); private readonly Crc32 crc32 = new();
/// The maximum memory in bytes that a zTXt, sPLT, iTXt, iCCP, or unknown chunk can occupy when decompressed.
/// </summary>
private readonly int maxUncompressedLength;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PngDecoderCore"/> class. /// Initializes a new instance of the <see cref="PngDecoderCore"/> class.
@ -138,6 +142,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
this.skipMetadata = options.GeneralOptions.SkipMetadata; this.skipMetadata = options.GeneralOptions.SkipMetadata;
this.memoryAllocator = this.configuration.MemoryAllocator; this.memoryAllocator = this.configuration.MemoryAllocator;
this.pngCrcChunkHandling = options.PngCrcChunkHandling; this.pngCrcChunkHandling = options.PngCrcChunkHandling;
this.maxUncompressedLength = options.MaxUncompressedAncillaryChunkSizeBytes;
} }
internal PngDecoderCore(PngDecoderOptions options, bool colorMetadataOnly) internal PngDecoderCore(PngDecoderOptions options, bool colorMetadataOnly)
@ -149,6 +154,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
this.configuration = options.GeneralOptions.Configuration; this.configuration = options.GeneralOptions.Configuration;
this.memoryAllocator = this.configuration.MemoryAllocator; this.memoryAllocator = this.configuration.MemoryAllocator;
this.pngCrcChunkHandling = options.PngCrcChunkHandling; this.pngCrcChunkHandling = options.PngCrcChunkHandling;
this.maxUncompressedLength = options.MaxUncompressedAncillaryChunkSizeBytes;
} }
/// <inheritdoc/> /// <inheritdoc/>
@ -602,23 +608,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
private void InitializeImage<TPixel>(ImageMetadata metadata, FrameControl frameControl, out Image<TPixel> image) private void InitializeImage<TPixel>(ImageMetadata metadata, FrameControl frameControl, out Image<TPixel> image)
where TPixel : unmanaged, IPixel<TPixel> where TPixel : unmanaged, IPixel<TPixel>
{ {
// When ignoring data CRCs, we can't use the image constructor that leaves the buffer uncleared. image = new Image<TPixel>(this.configuration, this.header.Width, this.header.Height, metadata);
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.GetPngMetadata(); PngFrameMetadata frameMetadata = image.Frames.RootFrame.Metadata.GetPngMetadata();
frameMetadata.FromChunk(in frameControl); frameMetadata.FromChunk(in frameControl);
@ -1575,7 +1565,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
ReadOnlySpan<byte> compressedData = data[(zeroIndex + 2)..]; ReadOnlySpan<byte> compressedData = data[(zeroIndex + 2)..];
if (this.TryDecompressZlibData(compressedData, out byte[] iccpProfileBytes)) if (this.TryDecompressZlibData(compressedData, this.maxUncompressedLength, out byte[] iccpProfileBytes))
{ {
metadata.IccProfile = new IccProfile(iccpProfileBytes); metadata.IccProfile = new IccProfile(iccpProfileBytes);
} }
@ -1585,9 +1575,10 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
/// Tries to decompress zlib compressed data. /// Tries to decompress zlib compressed data.
/// </summary> /// </summary>
/// <param name="compressedData">The compressed data.</param> /// <param name="compressedData">The compressed data.</param>
/// <param name="maxLength">The maximum uncompressed length.</param>
/// <param name="uncompressedBytesArray">The uncompressed bytes array.</param> /// <param name="uncompressedBytesArray">The uncompressed bytes array.</param>
/// <returns>True, if de-compressing was successful.</returns> /// <returns>True, if de-compressing was successful.</returns>
private unsafe bool TryDecompressZlibData(ReadOnlySpan<byte> compressedData, out byte[] uncompressedBytesArray) private unsafe bool TryDecompressZlibData(ReadOnlySpan<byte> compressedData, int maxLength, out byte[] uncompressedBytesArray)
{ {
fixed (byte* compressedDataBase = compressedData) fixed (byte* compressedDataBase = compressedData)
{ {
@ -1607,6 +1598,12 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
int bytesRead = inflateStream.CompressedStream.Read(destUncompressedData, 0, destUncompressedData.Length); int bytesRead = inflateStream.CompressedStream.Read(destUncompressedData, 0, destUncompressedData.Length);
while (bytesRead != 0) while (bytesRead != 0)
{ {
if (memoryStreamOutput.Length > maxLength)
{
uncompressedBytesArray = Array.Empty<byte>();
return false;
}
memoryStreamOutput.Write(destUncompressedData[..bytesRead]); memoryStreamOutput.Write(destUncompressedData[..bytesRead]);
bytesRead = inflateStream.CompressedStream.Read(destUncompressedData, 0, destUncompressedData.Length); bytesRead = inflateStream.CompressedStream.Read(destUncompressedData, 0, destUncompressedData.Length);
} }
@ -1749,7 +1746,7 @@ internal sealed class PngDecoderCore : IImageDecoderInternals
/// <returns>The <see cref="bool"/>.</returns> /// <returns>The <see cref="bool"/>.</returns>
private bool TryDecompressTextData(ReadOnlySpan<byte> compressedData, Encoding encoding, [NotNullWhen(true)] out string? value) private bool TryDecompressTextData(ReadOnlySpan<byte> compressedData, Encoding encoding, [NotNullWhen(true)] out string? value)
{ {
if (this.TryDecompressZlibData(compressedData, out byte[] uncompressedData)) if (this.TryDecompressZlibData(compressedData, this.maxUncompressedLength, out byte[] uncompressedData))
{ {
value = encoding.GetString(uncompressedData); value = encoding.GetString(uncompressedData);
return true; return true;

6
src/ImageSharp/Formats/Png/PngDecoderOptions.cs

@ -15,4 +15,10 @@ public sealed class PngDecoderOptions : ISpecializedDecoderOptions
/// Gets a value indicating how to handle validation of any CRC (Cyclic Redundancy Check) data within the encoded PNG. /// Gets a value indicating how to handle validation of any CRC (Cyclic Redundancy Check) data within the encoded PNG.
/// </summary> /// </summary>
public PngCrcChunkHandling PngCrcChunkHandling { get; init; } = PngCrcChunkHandling.IgnoreNonCritical; public PngCrcChunkHandling PngCrcChunkHandling { get; init; } = PngCrcChunkHandling.IgnoreNonCritical;
/// <summary>
/// Gets the maximum memory in bytes that a zTXt, sPLT, iTXt, iCCP, or unknown chunk can occupy when decompressed.
/// Defaults to 8MB
/// </summary>
public int MaxUncompressedAncillaryChunkSizeBytes { get; init; } = 8 * 1024 * 1024; // 8MB
} }

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

@ -672,4 +672,23 @@ public partial class PngDecoderTests
string path = Path.GetFullPath(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Png.Issue2666)); string path = Path.GetFullPath(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, TestImages.Png.Issue2666));
using Image image = Image.Load(path); using Image image = Image.Load(path);
} }
[Theory]
[InlineData(TestImages.Png.Bad.BadZTXT)]
[InlineData(TestImages.Png.Bad.BadZTXT2)]
public void Decode_BadZTXT(string file)
{
string path = Path.GetFullPath(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, file));
using Image image = Image.Load(path);
}
[Theory]
[InlineData(TestImages.Png.Bad.BadZTXT)]
[InlineData(TestImages.Png.Bad.BadZTXT2)]
public void Info_BadZTXT(string file)
{
string path = Path.GetFullPath(Path.Combine(TestEnvironment.InputImagesDirectoryFullPath, file));
_ = Image.Identify(path);
}
} }

4
tests/ImageSharp.Tests/TestImages.cs

@ -186,8 +186,10 @@ public static class TestImages
// Invalid color type. // Invalid color type.
public const string ColorTypeOne = "Png/xc1n0g08.png"; public const string ColorTypeOne = "Png/xc1n0g08.png";
public const string ColorTypeNine = "Png/xc9n2c08.png"; public const string ColorTypeNine = "Png/xc9n2c08.png";
public const string FlagOfGermany0000016446 = "Png/issues/flag_of_germany-0000016446.png"; public const string FlagOfGermany0000016446 = "Png/issues/flag_of_germany-0000016446.png";
public const string BadZTXT = "Png/issues/bad-ztxt.png";
public const string BadZTXT2 = "Png/issues/bad-ztxt2.png";
} }
} }

3
tests/Images/Input/Png/issues/bad-ztxt.png

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:132a70cf0ac458a55cf4a44f4c6c025587491d304595835959955de6682fa472
size 3913750

3
tests/Images/Input/Png/issues/bad-ztxt2.png

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