Browse Source

Only read the crc of critical chunks.

af/merge-core
James Jackson-South 7 years ago
parent
commit
98f802f617
  1. 17
      src/ImageSharp/Formats/Png/PngChunk.cs
  2. 59
      src/ImageSharp/Formats/Png/PngDecoderCore.cs
  3. 29
      src/ImageSharp/Formats/Png/PngThrowHelper.cs

17
src/ImageSharp/Formats/Png/PngChunk.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using SixLabors.Memory; using SixLabors.Memory;
@ -10,12 +10,11 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary> /// </summary>
internal readonly struct PngChunk internal readonly struct PngChunk
{ {
public PngChunk(int length, PngChunkType type, IManagedByteBuffer data = null, uint crc = 0) public PngChunk(int length, PngChunkType type, IManagedByteBuffer data = null)
{ {
this.Length = length; this.Length = length;
this.Type = type; this.Type = type;
this.Data = data; this.Data = data;
this.Crc = crc;
} }
/// <summary> /// <summary>
@ -38,20 +37,12 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </summary> /// </summary>
public IManagedByteBuffer Data { get; } public IManagedByteBuffer Data { get; }
/// <summary>
/// Gets a CRC (Cyclic Redundancy Check) calculated on the preceding bytes in the chunk,
/// including the chunk type code and chunk data fields, but not including the length field.
/// The CRC is always present, even for chunks containing no data
/// </summary>
public uint Crc { get; }
/// <summary> /// <summary>
/// Gets a value indicating whether the given chunk is critical to decoding /// Gets a value indicating whether the given chunk is critical to decoding
/// </summary> /// </summary>
public bool IsCritical => public bool IsCritical =>
this.Type == PngChunkType.Header || this.Type == PngChunkType.Header ||
this.Type == PngChunkType.Palette || this.Type == PngChunkType.Palette ||
this.Type == PngChunkType.Data || this.Type == PngChunkType.Data;
this.Type == PngChunkType.End;
} }
} }

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

@ -221,7 +221,7 @@ namespace SixLabors.ImageSharp.Formats.Png
if (image is null) if (image is null)
{ {
throw new ImageFormatException("PNG Image does not contain a data chunk"); PngThrowHelper.ThrowNoData();
} }
return image; return image;
@ -285,7 +285,7 @@ namespace SixLabors.ImageSharp.Formats.Png
if (this.header.Width == 0 && this.header.Height == 0) if (this.header.Width == 0 && this.header.Height == 0)
{ {
throw new ImageFormatException("PNG Image does not contain a header chunk"); PngThrowHelper.ThrowNoHeader();
} }
return new ImageInfo(new PixelTypeInfo(this.CalculateBitsPerPixel()), this.header.Width, this.header.Height, metadata); return new ImageInfo(new PixelTypeInfo(this.CalculateBitsPerPixel()), this.header.Width, this.header.Height, metadata);
@ -407,7 +407,8 @@ namespace SixLabors.ImageSharp.Formats.Png
case PngColorType.RgbWithAlpha: case PngColorType.RgbWithAlpha:
return this.header.BitDepth * 4; return this.header.BitDepth * 4;
default: default:
throw new NotSupportedException("Unsupported PNG color type"); PngThrowHelper.ThrowNotSupportedColor();
return -1;
} }
} }
@ -528,7 +529,8 @@ namespace SixLabors.ImageSharp.Formats.Png
break; break;
default: default:
throw new ImageFormatException("Unknown filter type."); PngThrowHelper.ThrowUnknownFilter();
break;
} }
this.ProcessDefilteredScanline(scanlineSpan, image, pngMetadata); this.ProcessDefilteredScanline(scanlineSpan, image, pngMetadata);
@ -601,7 +603,8 @@ namespace SixLabors.ImageSharp.Formats.Png
break; break;
default: default:
throw new ImageFormatException("Unknown filter type."); PngThrowHelper.ThrowUnknownFilter();
break;
} }
Span<TPixel> rowSpan = image.GetPixelRowSpan(this.currentRow); Span<TPixel> rowSpan = image.GetPixelRowSpan(this.currentRow);
@ -1119,13 +1122,9 @@ namespace SixLabors.ImageSharp.Formats.Png
chunk = new PngChunk( chunk = new PngChunk(
length: length, length: length,
type: type, type: type,
data: this.ReadChunkData(length), data: this.ReadChunkData(length));
crc: this.ReadChunkCrc());
if (chunk.IsCritical) this.ValidateChunk(chunk);
{
this.ValidateChunk(chunk);
}
return true; return true;
} }
@ -1136,6 +1135,11 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <param name="chunk">The <see cref="PngChunk"/>.</param> /// <param name="chunk">The <see cref="PngChunk"/>.</param>
private void ValidateChunk(in PngChunk chunk) private void ValidateChunk(in PngChunk chunk)
{ {
if (!chunk.IsCritical)
{
return;
}
Span<byte> chunkType = stackalloc byte[4]; Span<byte> chunkType = stackalloc byte[4];
BinaryPrimitives.WriteUInt32BigEndian(chunkType, (uint)chunk.Type); BinaryPrimitives.WriteUInt32BigEndian(chunkType, (uint)chunk.Type);
@ -1144,25 +1148,26 @@ namespace SixLabors.ImageSharp.Formats.Png
this.crc.Update(chunkType); this.crc.Update(chunkType);
this.crc.Update(chunk.Data.GetSpan()); this.crc.Update(chunk.Data.GetSpan());
if (this.crc.Value != chunk.Crc) uint crc = this.ReadChunkCrc();
if (this.crc.Value != crc)
{ {
string chunkTypeName = Encoding.ASCII.GetString(chunkType); string chunkTypeName = Encoding.ASCII.GetString(chunkType);
PngThrowHelper.ThrowInvalidChunkCrc(chunkTypeName);
throw new ImageFormatException($"CRC Error. PNG {chunkTypeName} chunk is corrupt!");
} }
} }
/// <summary> /// <summary>
/// Reads the cycle redundancy chunk from the data. /// Reads the cycle redundancy chunk from the data.
/// </summary> /// </summary>
/// <exception cref="ImageFormatException">
/// Thrown if the input stream is not valid or corrupt.
/// </exception>
private uint ReadChunkCrc() private uint ReadChunkCrc()
{ {
return this.currentStream.Read(this.buffer, 0, 4) == 4 uint crc = 0;
? BinaryPrimitives.ReadUInt32BigEndian(this.buffer) if (this.currentStream.Read(this.buffer, 0, 4) == 4)
: throw new ImageFormatException("Image stream is not valid!"); {
crc = BinaryPrimitives.ReadUInt32BigEndian(this.buffer);
}
return crc;
} }
/// <summary> /// <summary>
@ -1197,9 +1202,17 @@ namespace SixLabors.ImageSharp.Formats.Png
/// </exception> /// </exception>
private PngChunkType ReadChunkType() private PngChunkType ReadChunkType()
{ {
return this.currentStream.Read(this.buffer, 0, 4) == 4 if (this.currentStream.Read(this.buffer, 0, 4) == 4)
? (PngChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.buffer) {
: throw new ImageFormatException("Invalid PNG data."); return (PngChunkType)BinaryPrimitives.ReadUInt32BigEndian(this.buffer);
}
else
{
PngThrowHelper.ThrowInvalidChunkType();
// The IDE cannot detect the throw here.
return default;
}
} }
/// <summary> /// <summary>

29
src/ImageSharp/Formats/Png/PngThrowHelper.cs

@ -0,0 +1,29 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System;
using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.Formats.Png
{
/// <summary>
/// Cold path optimizations for throwing png format based exceptions.
/// </summary>
internal static class PngThrowHelper
{
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowNoHeader() => throw new ImageFormatException("PNG Image does not contain a header chunk");
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowNoData() => throw new ImageFormatException("PNG Image does not contain a data chunk");
public static void ThrowInvalidChunkType() => throw new ImageFormatException("Invalid PNG data.");
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowInvalidChunkCrc(string chunkTypeName) => throw new ImageFormatException($"CRC Error. PNG {chunkTypeName} chunk is corrupt!");
public static void ThrowNotSupportedColor() => new NotSupportedException("Unsupported PNG color type");
public static void ThrowUnknownFilter() => throw new ImageFormatException("Unknown filter type.");
}
}
Loading…
Cancel
Save