diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs b/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs index e31842c53..610e52d1c 100644 --- a/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs +++ b/src/ImageSharp/Metadata/Profiles/IPTC/IptcProfile.cs @@ -16,6 +16,10 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc { private Collection values; + private const byte IptcTagMarkerByte = 0x1c; + + private const uint MaxStandardDataTagSize = 0x7FFF; + /// /// Initializes a new instance of the class. /// @@ -78,7 +82,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc } /// - public IptcProfile DeepClone() => new IptcProfile(this); + public IptcProfile DeepClone() => new(this); /// /// Returns all values with the specified tag. @@ -207,7 +211,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc throw new ArgumentException("iptc tag is not a time or date type"); } - var formattedDate = tag.IsDate() + string formattedDate = tag.IsDate() ? dateTimeOffset.ToString("yyyyMMdd", System.Globalization.CultureInfo.InvariantCulture) : dateTimeOffset.ToString("HHmmsszzzz", System.Globalization.CultureInfo.InvariantCulture) .Replace(":", string.Empty); @@ -231,7 +235,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc /// public void UpdateData() { - var length = 0; + int length = 0; foreach (IptcValue value in this.Values) { length += value.Length + 5; @@ -242,7 +246,24 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc int i = 0; foreach (IptcValue value in this.Values) { - this.Data[i++] = 28; + // Standard DataSet Tag + // +-----------+----------------+---------------------------------------------------------------------------------+ + // | Octet Pos | Name | Description | + // +==========-+================+=================================================================================+ + // | 1 | Tag Marker | Is the tag marker that initiates the start of a DataSet 0x1c. | + // +-----------+----------------+---------------------------------------------------------------------------------+ + // | 2 | Record Number | Octet 2 is the binary representation of the record number. Note that the | + // | | | envelope record number is always 1, and that the application records are | + // | | | numbered 2 through 6, the pre-object descriptor record is 7, the object record | + // | | | is 8, and the post - object descriptor record is 9. | + // +-----------+----------------+---------------------------------------------------------------------------------+ + // | 3 | DataSet Number | Octet 3 is the binary representation of the DataSet number. | + // +-----------+----------------+---------------------------------------------------------------------------------+ + // | 4 and 5 | Data Field | Octets 4 and 5, taken together, are the binary count of the number of octets in | + // | | Octet Count | the following data field(32767 or fewer octets). Note that the value of bit 7 of| + // | | | octet 4(most significant bit) always will be 0. | + // +-----------+----------------+---------------------------------------------------------------------------------+ + this.Data[i++] = IptcTagMarkerByte; this.Data[i++] = 2; this.Data[i++] = (byte)value.Tag; this.Data[i++] = (byte)(value.Length >> 8); @@ -264,34 +285,36 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc this.values = new Collection(); - if (this.Data == null || this.Data[0] != 0x1c) + if (this.Data == null || this.Data[0] != IptcTagMarkerByte) { return; } - int i = 0; - while (i + 4 < this.Data.Length) + int offset = 0; + while (offset < this.Data.Length - 4) { - if (this.Data[i++] != 28) + bool isValidTagMarker = this.Data[offset++] == IptcTagMarkerByte; + byte recordNumber = this.Data[offset++]; + bool isValidRecordNumber = recordNumber is >= 1 and <= 9; + var tag = (IptcTag)this.Data[offset++]; + bool isValidEntry = isValidTagMarker && isValidRecordNumber; + + uint byteCount = BinaryPrimitives.ReadUInt16BigEndian(this.Data.AsSpan(offset, 2)); + offset += 2; + if (byteCount > MaxStandardDataTagSize) { - continue; + // Extended data set tag's are not supported. + break; } - i++; - - var tag = (IptcTag)this.Data[i++]; - - int count = BinaryPrimitives.ReadInt16BigEndian(this.Data.AsSpan(i, 2)); - i += 2; - - var iptcData = new byte[count]; - if ((count > 0) && (i + count <= this.Data.Length)) + if (isValidEntry && byteCount > 0 && (offset <= this.Data.Length - byteCount)) { - Buffer.BlockCopy(this.Data, i, iptcData, 0, count); + var iptcData = new byte[byteCount]; + Buffer.BlockCopy(this.Data, offset, iptcData, 0, (int)byteCount); this.values.Add(new IptcValue(tag, iptcData, false)); } - i += count; + offset += (int)byteCount; } } } diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IptcTagExtensions.cs b/src/ImageSharp/Metadata/Profiles/IPTC/IptcTagExtensions.cs index b670591df..4b18add74 100644 --- a/src/ImageSharp/Metadata/Profiles/IPTC/IptcTagExtensions.cs +++ b/src/ImageSharp/Metadata/Profiles/IPTC/IptcTagExtensions.cs @@ -13,60 +13,57 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc /// /// The tag to check the max length for. /// The maximum length. - public static int MaxLength(this IptcTag tag) + public static int MaxLength(this IptcTag tag) => tag switch { - return tag switch - { - IptcTag.RecordVersion => 2, - IptcTag.ObjectType => 67, - IptcTag.ObjectAttribute => 68, - IptcTag.Name => 64, - IptcTag.EditStatus => 64, - IptcTag.EditorialUpdate => 2, - IptcTag.Urgency => 1, - IptcTag.SubjectReference => 236, - IptcTag.Category => 3, - IptcTag.SupplementalCategories => 32, - IptcTag.FixtureIdentifier => 32, - IptcTag.Keywords => 64, - IptcTag.LocationCode => 3, - IptcTag.LocationName => 64, - IptcTag.ReleaseDate => 8, - IptcTag.ReleaseTime => 11, - IptcTag.ExpirationDate => 8, - IptcTag.ExpirationTime => 11, - IptcTag.SpecialInstructions => 256, - IptcTag.ActionAdvised => 2, - IptcTag.ReferenceService => 10, - IptcTag.ReferenceDate => 8, - IptcTag.ReferenceNumber => 8, - IptcTag.CreatedDate => 8, - IptcTag.CreatedTime => 11, - IptcTag.DigitalCreationDate => 8, - IptcTag.DigitalCreationTime => 11, - IptcTag.OriginatingProgram => 32, - IptcTag.ProgramVersion => 10, - IptcTag.ObjectCycle => 1, - IptcTag.Byline => 32, - IptcTag.BylineTitle => 32, - IptcTag.City => 32, - IptcTag.SubLocation => 32, - IptcTag.ProvinceState => 32, - IptcTag.CountryCode => 3, - IptcTag.Country => 64, - IptcTag.OriginalTransmissionReference => 32, - IptcTag.Headline => 256, - IptcTag.Credit => 32, - IptcTag.Source => 32, - IptcTag.CopyrightNotice => 128, - IptcTag.Contact => 128, - IptcTag.Caption => 2000, - IptcTag.CaptionWriter => 32, - IptcTag.ImageType => 2, - IptcTag.ImageOrientation => 1, - _ => 256 - }; - } + IptcTag.RecordVersion => 2, + IptcTag.ObjectType => 67, + IptcTag.ObjectAttribute => 68, + IptcTag.Name => 64, + IptcTag.EditStatus => 64, + IptcTag.EditorialUpdate => 2, + IptcTag.Urgency => 1, + IptcTag.SubjectReference => 236, + IptcTag.Category => 3, + IptcTag.SupplementalCategories => 32, + IptcTag.FixtureIdentifier => 32, + IptcTag.Keywords => 64, + IptcTag.LocationCode => 3, + IptcTag.LocationName => 64, + IptcTag.ReleaseDate => 8, + IptcTag.ReleaseTime => 11, + IptcTag.ExpirationDate => 8, + IptcTag.ExpirationTime => 11, + IptcTag.SpecialInstructions => 256, + IptcTag.ActionAdvised => 2, + IptcTag.ReferenceService => 10, + IptcTag.ReferenceDate => 8, + IptcTag.ReferenceNumber => 8, + IptcTag.CreatedDate => 8, + IptcTag.CreatedTime => 11, + IptcTag.DigitalCreationDate => 8, + IptcTag.DigitalCreationTime => 11, + IptcTag.OriginatingProgram => 32, + IptcTag.ProgramVersion => 10, + IptcTag.ObjectCycle => 1, + IptcTag.Byline => 32, + IptcTag.BylineTitle => 32, + IptcTag.City => 32, + IptcTag.SubLocation => 32, + IptcTag.ProvinceState => 32, + IptcTag.CountryCode => 3, + IptcTag.Country => 64, + IptcTag.OriginalTransmissionReference => 32, + IptcTag.Headline => 256, + IptcTag.Credit => 32, + IptcTag.Source => 32, + IptcTag.CopyrightNotice => 128, + IptcTag.Contact => 128, + IptcTag.Caption => 2000, + IptcTag.CaptionWriter => 32, + IptcTag.ImageType => 2, + IptcTag.ImageOrientation => 1, + _ => 256 + }; /// /// Determines if the given tag can be repeated according to the specification. diff --git a/src/ImageSharp/Metadata/Profiles/IPTC/IptcValue.cs b/src/ImageSharp/Metadata/Profiles/IPTC/IptcValue.cs index 9e409ca06..5ba81bea7 100644 --- a/src/ImageSharp/Metadata/Profiles/IPTC/IptcValue.cs +++ b/src/ImageSharp/Metadata/Profiles/IPTC/IptcValue.cs @@ -101,7 +101,7 @@ namespace SixLabors.ImageSharp.Metadata.Profiles.Iptc byte[] valueBytes; if (this.Strict && value.Length > maxLength) { - var cappedValue = value.Substring(0, maxLength); + string cappedValue = value.Substring(0, maxLength); valueBytes = this.encoding.GetBytes(cappedValue); // It is still possible that the bytes of the string exceed the limit. diff --git a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs index 0ef5090cc..53c81631d 100644 --- a/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs +++ b/tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Metadata.cs @@ -288,5 +288,17 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg Assert.Equal(72, imageInfo.Metadata.HorizontalResolution); Assert.Equal(72, imageInfo.Metadata.VerticalResolution); }); + + [Theory] + [WithFile(TestImages.Jpeg.Issues.InvalidIptcTag, PixelTypes.Rgba32)] + public void Decode_WithInvalidIptcTag_DoesNotThrowException(TestImageProvider provider) + where TPixel : unmanaged, IPixel + { + Exception ex = Record.Exception(() => + { + using Image image = provider.GetImage(JpegDecoder); + }); + Assert.Null(ex); + } } } diff --git a/tests/ImageSharp.Tests/TestImages.cs b/tests/ImageSharp.Tests/TestImages.cs index 0860bb5ae..bce22799d 100644 --- a/tests/ImageSharp.Tests/TestImages.cs +++ b/tests/ImageSharp.Tests/TestImages.cs @@ -261,6 +261,7 @@ namespace SixLabors.ImageSharp.Tests public const string WrongColorSpace = "Jpg/issues/Issue1732-WrongColorSpace.jpg"; public const string MalformedUnsupportedComponentCount = "Jpg/issues/issue-1900-malformed-unsupported-255-components.jpg"; public const string MultipleApp01932 = "Jpg/issues/issue-1932-app0-resolution.jpg"; + public const string InvalidIptcTag = "Jpg/issues/Issue1942InvalidIptcTag.jpg"; public static class Fuzz { diff --git a/tests/Images/Input/Jpg/issues/Issue1942InvalidIptcTag.jpg b/tests/Images/Input/Jpg/issues/Issue1942InvalidIptcTag.jpg new file mode 100644 index 000000000..8b1926128 --- /dev/null +++ b/tests/Images/Input/Jpg/issues/Issue1942InvalidIptcTag.jpg @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9c9db428c4d9d7d1aea6778f263d8deaeeabdcfa63c77ef6ce36ab0e47b364dd +size 93374