Browse Source

Make PngChunk an immutable struct

pull/528/head
Jason Nelson 9 years ago
parent
commit
76568a7093
  1. 30
      src/ImageSharp/Formats/Png/PngChunk.cs
  2. 95
      src/ImageSharp/Formats/Png/PngDecoderCore.cs

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

@ -8,11 +8,14 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary> /// <summary>
/// Stores header information about a chunk. /// Stores header information about a chunk.
/// </summary> /// </summary>
internal sealed class PngChunk internal readonly struct PngChunk
{ {
public PngChunk(int length) public PngChunk(int length, string type, IManagedByteBuffer data = null, uint crc = default)
{ {
this.Length = length; this.Length = length;
this.Type = type;
this.Data = data;
this.Crc = crc;
} }
/// <summary> /// <summary>
@ -24,21 +27,30 @@ namespace SixLabors.ImageSharp.Formats.Png
public int Length { get; } public int Length { get; }
/// <summary> /// <summary>
/// Gets or sets the chunk type as string with 4 chars. /// Gets the chunk type as string with 4 chars.
/// </summary> /// </summary>
public string Type { get; set; } public string Type { get; }
/// <summary> /// <summary>
/// Gets or sets the data bytes appropriate to the chunk type, if any. /// Gets the data bytes appropriate to the chunk type, if any.
/// This field can be of zero length. /// This field can be of zero length or null.
/// </summary> /// </summary>
public IManagedByteBuffer Data { get; set; } public IManagedByteBuffer Data { get; }
/// <summary> /// <summary>
/// Gets or sets a CRC (Cyclic Redundancy Check) calculated on the preceding bytes in the chunk, /// 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. /// 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 /// The CRC is always present, even for chunks containing no data
/// </summary> /// </summary>
public uint Crc { get; set; } public uint Crc { get; }
/// <summary>
/// Gets a value indicating whether the given chunk is critical to decoding
/// </summary>
public bool IsCritical =>
this.Type == PngChunkTypes.Header ||
this.Type == PngChunkTypes.Palette ||
this.Type == PngChunkTypes.Data ||
this.Type == PngChunkTypes.End;
} }
} }

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

@ -262,12 +262,7 @@ namespace SixLabors.ImageSharp.Formats.Png
} }
finally finally
{ {
// Data is rented in ReadChunkData() chunk.Data?.Dispose(); // Data is rented in ReadChunkData()
if (chunk.Data != null)
{
chunk.Data.Dispose();
chunk.Data = null;
}
} }
} }
} }
@ -383,20 +378,6 @@ namespace SixLabors.ImageSharp.Formats.Png
return result; return result;
} }
/// <summary>
/// Returns a value indicating whether the given chunk is critical to decoding
/// </summary>
/// <param name="chunk">The chunk</param>
/// <returns>The <see cref="bool"/></returns>
private static bool IsCriticalChunk(PngChunk chunk)
{
return
chunk.Type == PngChunkTypes.Header ||
chunk.Type == PngChunkTypes.Palette ||
chunk.Type == PngChunkTypes.Data ||
chunk.Type == PngChunkTypes.End;
}
/// <summary> /// <summary>
/// Reads an integer value from 2 consecutive bytes in LSB order /// Reads an integer value from 2 consecutive bytes in LSB order
/// </summary> /// </summary>
@ -1217,38 +1198,63 @@ namespace SixLabors.ImageSharp.Formats.Png
return false; return false;
} }
chunk = new PngChunk(length); while (length < 0 || length > (this.currentStream.Length - this.currentStream.Position))
if (chunk.Length < 0 || chunk.Length > this.currentStream.Length - this.currentStream.Position)
{ {
// Not a valid chunk so we skip back all but one of the four bytes we have just read. // Not a valid chunk so we skip back all but one of the four bytes we have just read.
// That lets us read one byte at a time until we reach a known chunk. // That lets us read one byte at a time until we reach a known chunk.
this.currentStream.Position -= 3; this.currentStream.Position -= 3;
return true; length = this.ReadChunkLength();
if (length == -1)
{
chunk = default;
return false;
}
} }
this.ReadChunkType(chunk); string type = this.ReadChunkType();
if (chunk.Type == PngChunkTypes.Data) // NOTE: Handling the chunk data is the responsible of the caller
// It is currently either skipped (in identification) or read during decoding
if (type == PngChunkTypes.Data)
{ {
chunk = new PngChunk(length, type);
return true; return true;
} }
this.ReadChunkData(chunk); chunk = new PngChunk(
this.ReadChunkCrc(chunk); length: length,
type: type,
data: this.ReadChunkData(length),
crc: this.ReadChunkCrc());
this.ValidateChunk(chunk);
return true; return true;
} }
private void ValidateChunk(in PngChunk chunk)
{
this.crc.Reset();
this.crc.Update(this.chunkTypeBuffer);
this.crc.Update(new ReadOnlySpan<byte>(chunk.Data.Array, 0, chunk.Length));
if (this.crc.Value != chunk.Crc && chunk.IsCritical)
{
throw new ImageFormatException($"CRC Error. PNG {chunk.Type} chunk is corrupt!");
}
}
/// <summary> /// <summary>
/// Reads the cycle redundancy chunk from the data. /// Reads the cycle redundancy chunk from the data.
/// </summary> /// </summary>
/// <param name="chunk">The chunk.</param>
/// <exception cref="ImageFormatException"> /// <exception cref="ImageFormatException">
/// Thrown if the input stream is not valid or corrupt. /// Thrown if the input stream is not valid or corrupt.
/// </exception> /// </exception>
private void ReadChunkCrc(PngChunk chunk) private uint ReadChunkCrc()
{ {
int numBytes = this.currentStream.Read(this.crcBuffer, 0, 4); int numBytes = this.currentStream.Read(this.crcBuffer, 0, 4);
@ -1257,22 +1263,13 @@ namespace SixLabors.ImageSharp.Formats.Png
throw new ImageFormatException("Image stream is not valid!"); throw new ImageFormatException("Image stream is not valid!");
} }
chunk.Crc = BinaryPrimitives.ReadUInt32BigEndian(this.crcBuffer); return BinaryPrimitives.ReadUInt32BigEndian(this.crcBuffer);
this.crc.Reset();
this.crc.Update(this.chunkTypeBuffer);
this.crc.Update(new ReadOnlySpan<byte>(chunk.Data.Array, 0, chunk.Length));
if (this.crc.Value != chunk.Crc && IsCriticalChunk(chunk))
{
throw new ImageFormatException($"CRC Error. PNG {chunk.Type} chunk is corrupt!");
}
} }
/// <summary> /// <summary>
/// Skips the chunk data and the cycle redundancy chunk read from the data. /// Skips the chunk data and the cycle redundancy chunk read from the data.
/// </summary> /// </summary>
private void SkipChunkDataAndCrc(PngChunk chunk) private void SkipChunkDataAndCrc(in PngChunk chunk)
{ {
this.currentStream.Skip(chunk.Length); this.currentStream.Skip(chunk.Length);
this.currentStream.Skip(4); this.currentStream.Skip(4);
@ -1281,22 +1278,24 @@ namespace SixLabors.ImageSharp.Formats.Png
/// <summary> /// <summary>
/// Reads the chunk data from the stream. /// Reads the chunk data from the stream.
/// </summary> /// </summary>
/// <param name="chunk">The chunk.</param> /// <param name="length">The length of the chunk data to read.</param>
private void ReadChunkData(PngChunk chunk) private IManagedByteBuffer ReadChunkData(int length)
{ {
// We rent the buffer here to return it afterwards in Decode() // We rent the buffer here to return it afterwards in Decode()
chunk.Data = this.configuration.MemoryManager.AllocateCleanManagedByteBuffer(chunk.Length); IManagedByteBuffer buffer = this.configuration.MemoryManager.AllocateCleanManagedByteBuffer(length);
this.currentStream.Read(chunk.Data.Array, 0, chunk.Length);
this.currentStream.Read(buffer.Array, 0, length);
return buffer;
} }
/// <summary> /// <summary>
/// Identifies the chunk type from the chunk. /// Identifies the chunk type from the chunk.
/// </summary> /// </summary>
/// <param name="chunk">The chunk.</param>
/// <exception cref="ImageFormatException"> /// <exception cref="ImageFormatException">
/// Thrown if the input stream is not valid. /// Thrown if the input stream is not valid.
/// </exception> /// </exception>
private void ReadChunkType(PngChunk chunk) private string ReadChunkType()
{ {
int numBytes = this.currentStream.Read(this.chunkTypeBuffer, 0, 4); int numBytes = this.currentStream.Read(this.chunkTypeBuffer, 0, 4);
if (numBytes >= 1 && numBytes <= 3) if (numBytes >= 1 && numBytes <= 3)
@ -1309,7 +1308,7 @@ namespace SixLabors.ImageSharp.Formats.Png
this.chars[2] = (char)this.chunkTypeBuffer[2]; this.chars[2] = (char)this.chunkTypeBuffer[2];
this.chars[3] = (char)this.chunkTypeBuffer[3]; this.chars[3] = (char)this.chunkTypeBuffer[3];
chunk.Type = new string(this.chars); return new string(this.chars);
} }
/// <summary> /// <summary>

Loading…
Cancel
Save