From 1c9f39918f8dbf4fc65ac1999ebfd55e4d71c5ce Mon Sep 17 00:00:00 2001 From: Andrew Wilkinson Date: Sat, 4 Mar 2017 11:50:00 +0000 Subject: [PATCH] Read and validate the TIFF file header. --- src/ImageSharp.Formats.Tiff/TiffConstants.cs | 6 +- .../TiffDecoderCore.cs | 71 +++++++++++++++++ .../Formats/Tiff/TiffDecoderHeaderTests.cs | 79 +++++++++++++++++++ .../TestUtilities/Tiff/TiffGenHeader.cs | 14 +++- 4 files changed, 163 insertions(+), 7 deletions(-) create mode 100644 tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderHeaderTests.cs diff --git a/src/ImageSharp.Formats.Tiff/TiffConstants.cs b/src/ImageSharp.Formats.Tiff/TiffConstants.cs index e5d2df044..0f9145208 100644 --- a/src/ImageSharp.Formats.Tiff/TiffConstants.cs +++ b/src/ImageSharp.Formats.Tiff/TiffConstants.cs @@ -10,17 +10,17 @@ namespace ImageSharp.Formats /// /// Defines constants defined in the TIFF specification. /// - internal static class GifConstants + internal static class TiffConstants { /// /// Byte order markers for indicating little endian encoding. /// - public const ushort ByteOrderLittleEndian = 0x4949; + public const byte ByteOrderLittleEndian = 0x49; /// /// Byte order markers for indicating big endian encoding. /// - public const ushort ByteOrderBigEndian = 0x4D4D; + public const byte ByteOrderBigEndian = 0x4D; /// /// Magic number used within the image file header to identify a TIFF format file. diff --git a/src/ImageSharp.Formats.Tiff/TiffDecoderCore.cs b/src/ImageSharp.Formats.Tiff/TiffDecoderCore.cs index e9bbb650b..18a5c3f5e 100644 --- a/src/ImageSharp.Formats.Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp.Formats.Tiff/TiffDecoderCore.cs @@ -20,6 +20,11 @@ namespace ImageSharp.Formats /// private readonly IDecoderOptions options; + /// + /// A flag indicating if the file is encoded in little-endian or big-endian format. + /// + private bool isLittleEndian; + /// /// Initializes a new instance of the class. /// @@ -46,6 +51,8 @@ namespace ImageSharp.Formats where TColor : struct, IPixel { this.InputStream = stream; + + uint firstIfdOffset = ReadHeader(); } /// @@ -54,5 +61,69 @@ namespace ImageSharp.Formats public void Dispose() { } + + private uint ReadHeader() + { + byte[] headerBytes = new byte[8]; + ReadBytes(headerBytes, 8); + + if (headerBytes[0] == TiffConstants.ByteOrderLittleEndian && headerBytes[1] == TiffConstants.ByteOrderLittleEndian) + isLittleEndian = true; + else if (headerBytes[0] != TiffConstants.ByteOrderBigEndian && headerBytes[1] != TiffConstants.ByteOrderBigEndian) + throw new ImageFormatException("Invalid TIFF file header."); + + if (ToUInt16(headerBytes, 2) != TiffConstants.HeaderMagicNumber) + throw new ImageFormatException("Invalid TIFF file header."); + + uint firstIfdOffset = ToUInt32(headerBytes, 4); + if (firstIfdOffset == 0) + throw new ImageFormatException("Invalid TIFF file header."); + + return firstIfdOffset; + } + + private byte[] ReadBytes(byte[] buffer, int count) + { + int offset = 0; + + while (count > 0) + { + int bytesRead = InputStream.Read(buffer, offset, count); + + if (bytesRead == 0) + break; + + offset += bytesRead; + count -= bytesRead; + } + + return buffer; + } + + private Int16 ToInt16(byte[] bytes, int offset) + { + if (isLittleEndian) + return (short)(bytes[offset + 0] | (bytes[offset + 1] << 8)); + else + return (short)((bytes[offset + 0] << 8) | bytes[offset + 1]); + } + + private Int32 ToInt32(byte[] bytes, int offset) + { + if (isLittleEndian) + return bytes[offset + 0] | (bytes[offset + 1] << 8) | (bytes[offset + 2] << 16) | (bytes[offset + 3] << 24); + else + return (bytes[offset + 0] << 24) | (bytes[offset + 1] << 16) | (bytes[offset + 2] << 8) | bytes[offset + 3]; + } + + private UInt32 ToUInt32(byte[] bytes, int offset) + { + return (uint)ToInt32(bytes, offset); + } + + private UInt16 ToUInt16(byte[] bytes, int offset) + { + return (ushort)ToInt16(bytes, offset); + } } } diff --git a/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderHeaderTests.cs b/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderHeaderTests.cs new file mode 100644 index 000000000..00b826ef0 --- /dev/null +++ b/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderHeaderTests.cs @@ -0,0 +1,79 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.IO; + using System.Linq; + using Xunit; + + using ImageSharp.Formats; + + public class TiffDecoderHeaderTests + { + public static object[][] IsLittleEndianValues = new[] { new object[] { false }, + new object[] { true } }; + + [Theory] + [MemberData(nameof(IsLittleEndianValues))] + public void Decode_ThrowsException_WithInvalidByteOrderMarkers(bool isLittleEndian) + { + Stream stream = new TiffGenHeader() + { + FirstIfd = new TiffGenIfd(), + ByteOrderMarker = 0x1234 + } + .ToStream(isLittleEndian); + + TiffDecoder decoder = new TiffDecoder(); + + ImageFormatException e = Assert.Throws(() => { TestDecode(decoder, stream); }); + + Assert.Equal("Invalid TIFF file header.", e.Message); + } + + [Theory] + [MemberData(nameof(IsLittleEndianValues))] + public void Decode_ThrowsException_WithIncorrectMagicNumber(bool isLittleEndian) + { + Stream stream = new TiffGenHeader() + { + FirstIfd = new TiffGenIfd(), + MagicNumber = 32 + } + .ToStream(isLittleEndian); + + TiffDecoder decoder = new TiffDecoder(); + + ImageFormatException e = Assert.Throws(() => { TestDecode(decoder, stream); }); + + Assert.Equal("Invalid TIFF file header.", e.Message); + } + + [Theory] + [MemberData(nameof(IsLittleEndianValues))] + public void Decode_ThrowsException_WithNoIfdZero(bool isLittleEndian) + { + Stream stream = new TiffGenHeader() + { + FirstIfd = null + } + .ToStream(isLittleEndian); + + TiffDecoder decoder = new TiffDecoder(); + + ImageFormatException e = Assert.Throws(() => { TestDecode(decoder, stream); }); + + Assert.Equal("Invalid TIFF file header.", e.Message); + } + + private void TestDecode(TiffDecoder decoder, Stream stream) + { + Configuration.Default.AddImageFormat(new TiffFormat()); + Image image = new Image(1,1); + decoder.Decode(image, stream, null); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Formats.Tiff.Tests/TestUtilities/Tiff/TiffGenHeader.cs b/tests/ImageSharp.Formats.Tiff.Tests/TestUtilities/Tiff/TiffGenHeader.cs index b270ff208..95322dc66 100644 --- a/tests/ImageSharp.Formats.Tiff.Tests/TestUtilities/Tiff/TiffGenHeader.cs +++ b/tests/ImageSharp.Formats.Tiff.Tests/TestUtilities/Tiff/TiffGenHeader.cs @@ -32,11 +32,17 @@ namespace ImageSharp.Tests bytes.AddUInt32(0); var headerData = new TiffGenDataBlock(bytes.ToArray()); - var firstIfdData = FirstIfd.GetData(isLittleEndian); - firstIfdData.First().AddReference(headerData.Bytes, 4); - - return new [] { headerData }.Concat(firstIfdData); + if (FirstIfd != null) + { + var firstIfdData = FirstIfd.GetData(isLittleEndian); + firstIfdData.First().AddReference(headerData.Bytes, 4); + return new [] { headerData }.Concat(firstIfdData); + } + else + { + return new [] { headerData }; + } } } } \ No newline at end of file