diff --git a/src/ImageSharp.Formats.Tiff/TiffDecoderCore.cs b/src/ImageSharp.Formats.Tiff/TiffDecoderCore.cs index 9a0920980..87dbf86da 100644 --- a/src/ImageSharp.Formats.Tiff/TiffDecoderCore.cs +++ b/src/ImageSharp.Formats.Tiff/TiffDecoderCore.cs @@ -8,6 +8,7 @@ namespace ImageSharp.Formats using System; using System.IO; using System.Runtime.CompilerServices; + using System.Text; using System.Threading.Tasks; /// @@ -250,6 +251,19 @@ namespace ImageSharp.Formats return result; } + public string ReadString(ref TiffIfdEntry entry) + { + if (entry.Type != TiffType.Ascii) + throw new ImageFormatException($"A value of type '{entry.Type}' cannot be converted to a string."); + + byte[] bytes = ReadBytes(ref entry); + + if (bytes[entry.Count - 1] != 0) + throw new ImageFormatException("The retrieved string is not null terminated."); + + return Encoding.UTF8.GetString(bytes, 0, (int)entry.Count - 1); + } + private SByte ToSByte(byte[] bytes, int offset) { return (sbyte)bytes[offset]; diff --git a/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderIfdEntryTests.cs b/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderIfdEntryTests.cs index 16581e367..ecdb375a5 100644 --- a/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderIfdEntryTests.cs +++ b/tests/ImageSharp.Formats.Tiff.Tests/Formats/Tiff/TiffDecoderIfdEntryTests.cs @@ -364,6 +364,65 @@ namespace ImageSharp.Tests Assert.Equal($"A value of type '{(TiffType)type}' cannot be converted to a signed integer.", e.Message); } + [Theory] + [InlineDataAttribute(true, new byte[] { 0 }, "")] + [InlineDataAttribute(true, new byte[] { (byte)'A', (byte)'B', (byte)'C', 0 }, "ABC")] + [InlineDataAttribute(true, new byte[] { (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', 0 }, "ABCDEF")] + [InlineDataAttribute(true, new byte[] { (byte)'A', (byte)'B', (byte)'C', (byte)'D', 0, (byte)'E', (byte)'F', (byte)'G', (byte)'H', 0 }, "ABCD\0EFGH")] + [InlineDataAttribute(false, new byte[] { 0 }, "")] + [InlineDataAttribute(false, new byte[] { (byte)'A', (byte)'B', (byte)'C', 0 }, "ABC")] + [InlineDataAttribute(false, new byte[] { (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', 0 }, "ABCDEF")] + [InlineDataAttribute(false, new byte[] { (byte)'A', (byte)'B', (byte)'C', (byte)'D', 0, (byte)'E', (byte)'F', (byte)'G', (byte)'H', 0 }, "ABCD\0EFGH")] + public void ReadString_ReturnsValue(bool isLittleEndian, byte[] bytes, string expectedValue) + { + (TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, TiffType.Ascii, (uint)bytes.Length, bytes), isLittleEndian); + + string result = decoder.ReadString(ref entry); + + Assert.Equal(expectedValue, result); + } + + [Theory] + [InlineDataAttribute(TiffType.Byte)] + [InlineDataAttribute(TiffType.Short)] + [InlineDataAttribute(TiffType.Long)] + [InlineDataAttribute(TiffType.Rational)] + [InlineDataAttribute(TiffType.SByte)] + [InlineDataAttribute(TiffType.Undefined)] + [InlineDataAttribute(TiffType.SShort)] + [InlineDataAttribute(TiffType.SLong)] + [InlineDataAttribute(TiffType.SRational)] + [InlineDataAttribute(TiffType.Float)] + [InlineDataAttribute(TiffType.Double)] + [InlineDataAttribute(TiffType.Ifd)] + [InlineDataAttribute((TiffType)99)] + public void ReadString_ThrowsExceptionIfInvalidType(ushort type) + { + (TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, (TiffType)type, 1, new byte[4]), true); + + var e = Assert.Throws(() => decoder.ReadString(ref entry)); + + Assert.Equal($"A value of type '{(TiffType)type}' cannot be converted to a string.", e.Message); + } + + [Theory] + [InlineDataAttribute(true, new byte[] { (byte)'A' })] + [InlineDataAttribute(true, new byte[] { (byte)'A', (byte)'B', (byte)'C' })] + [InlineDataAttribute(true, new byte[] { (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F' })] + [InlineDataAttribute(true, new byte[] { (byte)'A', (byte)'B', (byte)'C', (byte)'D', 0, (byte)'E', (byte)'F', (byte)'G', (byte)'H' })] + [InlineDataAttribute(false, new byte[] { (byte)'A' })] + [InlineDataAttribute(false, new byte[] { (byte)'A', (byte)'B', (byte)'C' })] + [InlineDataAttribute(false, new byte[] { (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F' })] + [InlineDataAttribute(false, new byte[] { (byte)'A', (byte)'B', (byte)'C', (byte)'D', 0, (byte)'E', (byte)'F', (byte)'G', (byte)'H' })] + public void ReadString_ThrowsExceptionIfStringIsNotNullTerminated(bool isLittleEndian, byte[] bytes) + { + (TiffDecoderCore decoder, TiffIfdEntry entry) = GenerateTestIfdEntry(TiffGenEntry.Bytes(TiffTags.ImageWidth, TiffType.Ascii, (uint)bytes.Length, bytes), isLittleEndian); + + var e = Assert.Throws(() => decoder.ReadString(ref entry)); + + Assert.Equal($"The retrieved string is not null terminated.", e.Message); + } + private (TiffDecoderCore, TiffIfdEntry) GenerateTestIfdEntry(TiffGenEntry entry, bool isLittleEndian) { Stream stream = new TiffGenIfd()