diff --git a/src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs b/src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs index 3d7510bc2a..e1fc9ef1fe 100644 --- a/src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs +++ b/src/ImageSharp/Formats/Bmp/BmpImageFormatDetector.cs @@ -22,9 +22,13 @@ public IImageFormat DetectFormat(ReadOnlySpan header) private bool IsSupportedFileFormat(ReadOnlySpan header) { - short fileTypeMarker = BinaryPrimitives.ReadInt16LittleEndian(header); - return header.Length >= this.HeaderSize && - (fileTypeMarker == BmpConstants.TypeMarkers.Bitmap || fileTypeMarker == BmpConstants.TypeMarkers.BitmapArray); + if (header.Length >= this.HeaderSize) + { + short fileTypeMarker = BinaryPrimitives.ReadInt16LittleEndian(header); + return fileTypeMarker == BmpConstants.TypeMarkers.Bitmap || fileTypeMarker == BmpConstants.TypeMarkers.BitmapArray; + } + + return false; } } } diff --git a/src/ImageSharp/Formats/Tga/TgaImageFormatDetector.cs b/src/ImageSharp/Formats/Tga/TgaImageFormatDetector.cs index bd9cfa900c..af40c333b6 100644 --- a/src/ImageSharp/Formats/Tga/TgaImageFormatDetector.cs +++ b/src/ImageSharp/Formats/Tga/TgaImageFormatDetector.cs @@ -23,7 +23,7 @@ private bool IsSupportedFileFormat(ReadOnlySpan header) { if (header.Length >= this.HeaderSize) { - // There is no magick bytes in a tga file, so at least the image type + // There are no magic bytes in a tga file, so at least the image type // and the colormap type in the header will be checked for a valid value. if (header[1] != 0 && header[1] != 1) { @@ -34,7 +34,7 @@ private bool IsSupportedFileFormat(ReadOnlySpan header) return imageType.IsValid(); } - return true; + return false; } } } diff --git a/src/ImageSharp/Image.Decode.cs b/src/ImageSharp/Image.Decode.cs index 8d0df599ea..db6a5e165e 100644 --- a/src/ImageSharp/Image.Decode.cs +++ b/src/ImageSharp/Image.Decode.cs @@ -1,6 +1,7 @@ -// Copyright (c) Six Labors and contributors. +// Copyright (c) Six Labors and contributors. // Licensed under the Apache License, Version 2.0. +using System; using System.IO; using System.Linq; using SixLabors.ImageSharp.Formats; @@ -47,19 +48,26 @@ internal static Image CreateUninitialized( /// The mime type or null if none found. private static IImageFormat InternalDetectFormat(Stream stream, Configuration config) { - // This is probably a candidate for making into a public API in the future! - int maxHeaderSize = config.MaxHeaderSize; - if (maxHeaderSize <= 0) + // We take a minimum of the stream length vs the max header size and always check below + // to ensure that only formats that headers fit within the given buffer length are tested. + int headerSize = (int)Math.Min(config.MaxHeaderSize, stream.Length); + if (headerSize <= 0) { return null; } - using (IManagedByteBuffer buffer = config.MemoryAllocator.AllocateManagedByteBuffer(maxHeaderSize, AllocationOptions.Clean)) + using (IManagedByteBuffer buffer = config.MemoryAllocator.AllocateManagedByteBuffer(headerSize, AllocationOptions.Clean)) { long startPosition = stream.Position; - stream.Read(buffer.Array, 0, maxHeaderSize); + stream.Read(buffer.Array, 0, headerSize); stream.Position = startPosition; - return config.ImageFormatsManager.FormatDetectors.Select(x => x.DetectFormat(buffer.GetSpan())).LastOrDefault(x => x != null); + + // Does the given stream contain enough data to fit in the header for the format + // and does that data match the format specification? + // Individual formats should still check since they are public. + return config.ImageFormatsManager.FormatDetectors + .Where(x => x.HeaderSize <= headerSize) + .Select(x => x.DetectFormat(buffer.GetSpan())).LastOrDefault(x => x != null); } } @@ -123,10 +131,14 @@ private static (Image img, IImageFormat format) Decode(Stream stream, Configurat /// /// The or null if suitable info detector not found. /// - private static IImageInfo InternalIdentity(Stream stream, Configuration config) + private static (IImageInfo info, IImageFormat format) InternalIdentity(Stream stream, Configuration config) { - var detector = DiscoverDecoder(stream, config, out IImageFormat _) as IImageInfoDetector; - return detector?.Identify(config, stream); + if (!(DiscoverDecoder(stream, config, out IImageFormat format) is IImageInfoDetector detector)) + { + return (null, null); + } + + return (detector?.Identify(config, stream), format); } } -} \ No newline at end of file +} diff --git a/src/ImageSharp/Image.FromStream.cs b/src/ImageSharp/Image.FromStream.cs index c4336c9aca..60db45f215 100644 --- a/src/ImageSharp/Image.FromStream.cs +++ b/src/ImageSharp/Image.FromStream.cs @@ -16,20 +16,20 @@ namespace SixLabors.ImageSharp public abstract partial class Image { /// - /// By reading the header on the provided stream this calculates the images mime type. + /// By reading the header on the provided stream this calculates the images format type. /// /// The image stream to read the header from. /// Thrown if the stream is not readable. - /// The mime type or null if none found. + /// The format type or null if none found. public static IImageFormat DetectFormat(Stream stream) => DetectFormat(Configuration.Default, stream); /// - /// By reading the header on the provided stream this calculates the images mime type. + /// By reading the header on the provided stream this calculates the images format type. /// /// The configuration. /// The image stream to read the header from. /// Thrown if the stream is not readable. - /// The mime type or null if none found. + /// The format type or null if none found. public static IImageFormat DetectFormat(Configuration config, Stream stream) => WithSeekableStream(config, stream, s => InternalDetectFormat(s, config)); @@ -41,26 +41,43 @@ public static IImageFormat DetectFormat(Configuration config, Stream stream) /// /// The or null if suitable info detector not found. /// - public static IImageInfo Identify(Stream stream) => Identify(Configuration.Default, stream); + public static IImageInfo Identify(Stream stream) => Identify(stream, out IImageFormat _); + + /// + /// By reading the header on the provided stream this reads the raw image information. + /// + /// The image stream to read the header from. + /// The format type of the decoded image. + /// Thrown if the stream is not readable. + /// + /// The or null if suitable info detector not found. + /// + public static IImageInfo Identify(Stream stream, out IImageFormat format) => Identify(Configuration.Default, stream, out format); /// /// Reads the raw image information from the specified stream without fully decoding it. /// /// The configuration. /// The image stream to read the information from. + /// The format type of the decoded image. /// Thrown if the stream is not readable. /// /// The or null if suitable info detector is not found. /// - public static IImageInfo Identify(Configuration config, Stream stream) - => WithSeekableStream(config, stream, s => InternalIdentity(s, config ?? Configuration.Default)); + public static IImageInfo Identify(Configuration config, Stream stream, out IImageFormat format) + { + (IImageInfo info, IImageFormat format) data = WithSeekableStream(config, stream, s => InternalIdentity(s, config ?? Configuration.Default)); + + format = data.format; + return data.info; + } /// /// Decode a new instance of the class from the given stream. /// The pixel format is selected by the decoder. /// /// The stream containing image information. - /// the mime type of the decoded image. + /// The format type of the decoded image. /// Thrown if the stream is not readable. /// Image cannot be loaded. /// A new .> @@ -126,7 +143,7 @@ public static Image Load(Stream stream) /// Create a new instance of the class from the given stream. /// /// The stream containing image information. - /// the mime type of the decoded image. + /// The format type of the decoded image. /// Thrown if the stream is not readable. /// Image cannot be loaded. /// The pixel format. @@ -180,7 +197,7 @@ public static Image Load(Configuration config, Stream stream) /// /// The configuration options. /// The stream containing image information. - /// the mime type of the decoded image. + /// The format type of the decoded image. /// Thrown if the stream is not readable. /// Image cannot be loaded. /// The pixel format. @@ -215,7 +232,7 @@ public static Image Load(Configuration config, Stream stream, ou /// /// The configuration options. /// The stream containing image information. - /// the mime type of the decoded image. + /// The format type of the decoded image. /// Thrown if the stream is not readable. /// Image cannot be loaded. /// A new . diff --git a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs index 62e9acf747..1e28469101 100644 --- a/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs +++ b/tests/ImageSharp.Tests/Formats/GeneralFormatTests.cs @@ -3,9 +3,6 @@ using System.IO; using SixLabors.ImageSharp.Formats; -using SixLabors.ImageSharp.Formats.Bmp; -using SixLabors.ImageSharp.Formats.Gif; -using SixLabors.ImageSharp.Formats.Jpeg; using SixLabors.ImageSharp.Formats.Png; using SixLabors.ImageSharp.PixelFormats; using Xunit; @@ -13,6 +10,7 @@ namespace SixLabors.ImageSharp.Tests { using System; + using System.Linq; using System.Reflection; using SixLabors.ImageSharp.Processing; using SixLabors.ImageSharp.Processing.Processors.Quantization; @@ -167,38 +165,49 @@ public void ImageShouldPreservePixelByteOrderWhenSerialized() [InlineData(100, 100, "jpg")] [InlineData(100, 10, "jpg")] [InlineData(10, 100, "jpg")] - public void CanIdentifyImageLoadedFromBytes(int width, int height, string format) + [InlineData(100, 100, "tga")] + [InlineData(100, 10, "tga")] + [InlineData(10, 100, "tga")] + public void CanIdentifyImageLoadedFromBytes(int width, int height, string extension) { using (var image = Image.LoadPixelData(new Rgba32[width * height], width, height)) { using (var memoryStream = new MemoryStream()) { - image.Save(memoryStream, GetEncoder(format)); + IImageFormat format = GetFormat(extension); + image.Save(memoryStream, format); memoryStream.Position = 0; IImageInfo imageInfo = Image.Identify(memoryStream); Assert.Equal(imageInfo.Width, width); Assert.Equal(imageInfo.Height, height); + memoryStream.Position = 0; + + imageInfo = Image.Identify(memoryStream, out IImageFormat detectedFormat); + + Assert.Equal(format, detectedFormat); } } } - private static IImageEncoder GetEncoder(string format) + [Fact] + public void IdentifyReturnsNullWithInvalidStream() { - switch (format) + byte[] invalid = new byte[10]; + + using (var memoryStream = new MemoryStream(invalid)) { - case "png": - return new PngEncoder(); - case "gif": - return new GifEncoder(); - case "bmp": - return new BmpEncoder(); - case "jpg": - return new JpegEncoder(); - default: - throw new ArgumentOutOfRangeException(nameof(format), format, null); + IImageInfo imageInfo = Image.Identify(memoryStream, out IImageFormat format); + + Assert.Null(imageInfo); + Assert.Null(format); } } + + private static IImageFormat GetFormat(string format) + { + return Configuration.Default.ImageFormats.FirstOrDefault(x => x.FileExtensions.Contains(format)); + } } }