1

I want to check if the file is an actual .png/.jpg file that can be displayed (for example not being a .txt file with a changed file extension to .png). I also want to check if the width and height of the image is in a valid range. How can I achieve this?

        [HttpPost]
        [ValidateAntiForgeryToken]
        [Authorize]
        public async Task<IActionResult> Create(IFormFile image)
        {
          // validate image
        }
Nikola Develops
  • 151
  • 3
  • 12
  • does [this help](https://stackoverflow.com/questions/670546/determine-if-file-is-an-image)? at least there are two schools, one using `magic bytes` and the other one using `System.Drawing` - each with their own limitations. – Bagus Tesa Mar 28 '22 at 07:29
  • 1
    Don't use `System.Drawing` in .NET Core applications. [It's deprecated](https://learn.microsoft.com/en-us/dotnet/core/compatibility/core-libraries/6.0/system-drawing-common-windows-only). The MSDN docs themselves suggest using ImageSharp or SkiaSharp instead. To check the file size you'll have to load it as an image anyway – Panagiotis Kanavos Mar 28 '22 at 07:34
  • You need some image processing library. There are many to choose from. Have a look at this post: https://devblogs.microsoft.com/dotnet/net-core-image-processing/ – freakish Mar 28 '22 at 07:42
  • Mmm.. my first thought would be to hand the job off to something else, like "imagemagick identify" – Caius Jard Mar 28 '22 at 08:41
  • For PNG you can have a look at this question: https://stackoverflow.com/questions/16724849/how-do-i-extract-the-width-and-height-of-a-png-from-looking-at-the-header-in-obj I suppose you can translate it from objective C to c# – derpirscher Mar 28 '22 at 08:48
  • For JPG this question https://stackoverflow.com/questions/18264357/how-to-get-the-width-height-of-jpeg-file-without-using-library should give you the basic idea – derpirscher Mar 28 '22 at 08:52

2 Answers2

4

First of all, don't try to use System.Drawing in .NET Core applications. It's deprecated and works only on Windows anyway. The MSDN docs themselves suggest using ImageSharp or SkiaSharp instead.

Image files start with bytes that identify the file format. You'll have to read at least some of the file's contents to read image metadata like the image size, resolution etc. You can use ImageSharp's Identify method to read only the format and image properties, without loading the entire image.

You can read an uploaded file's contents using IFormFile.OpenReadStream.

using var stream=image.OpenReadStream();
try
{
    var imageInfo=Image.Identify(stream, out var format);
    if(imageInfo!=null)
    {
        var formatName=format.Name;
        var width=imageInfo.Width;
        var height=imageInfo.Height;
    }
}
catch(InvalidImageContentException exc)
{
   //Invalid content ?
}

The format parameter is an IImageFormat value that contains information about the image format, including its name and mime types.

The IImageInfo object returned contains the image dimensions, pixel type, resolution etc.

The method documentation explains that the return value will be null if no suitable decoder is found:

The IImageInfo or null if a suitable info detector is not found.

But an exception will be thrown if the content is invalid:

InvalidImageContentException Image contains invalid content.

Without testing this, I assume that a text file will result in a null but a file with just a GIF header without valid content will result in an exception.

You can use ImageSharp to resize the image or convert it to another format. In that case it's not enough to just load the metadata. You can use Load to load the image from the stream, detect its format and then manipulate it.

using var stream=image.OpenReadStream();
var image=Image.Load(stream, out var format);

var formatName=format.Name;
if (notOk(formatName,image.Height,image.Width))
{
    using var outStream=new MemoryStream();
    image.Mutate(x => x.Resize(desiredWidth, desiredHeight));
    image.SaveAsPng(outStream);
    outStream.Position=0;
    //Store the contents of `outStream`
}
Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236
  • Okay, so in order to determine if it's an actual image that can be displayed I need to wrap the Identify() method in a try catch and see if it throws an exception? – Nikola Develops Mar 28 '22 at 08:14
  • Yes, the method will throw if the file is invalid – Panagiotis Kanavos Mar 28 '22 at 08:35
  • Oh, aha. From _"The IImageInfo or null if suitable info detector not found."_ I would have expected it to return null if no valid format can be detected instead of throwing. – Fildor Mar 28 '22 at 08:37
  • 1
    @Fildor the `Identify(Stream)` overload can also throw a `InvalidImageContentException` exception. I suspect you'd have to check for null *and* handle exceptions to cover every case, eg someone uploading a file with just the magic bytes and some garbage. – Panagiotis Kanavos Mar 28 '22 at 08:43
  • I see. Thx for the clarification! – Fildor Mar 28 '22 at 09:02
2

Check the file for a known header. (Info from link also mentioned in this answer)

The first eight bytes of a PNG file always contain the following (decimal) values: 137 80 78 71 13 10 26 10

You can also check Path.GetExtension Method

Here's a simple sample:

public static readonly List<string> ImageExtensions = new List<string> { ".JPG", ".JPE", ".BMP", ".GIF", ".PNG" };

private void button_Click(object sender, RoutedEventArgs e)
{
    var folder = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
    var files = Directory.GetFiles(folder);
    foreach(var f in files)
    {
        if (ImageExtensions.Contains(Path.GetExtension(f).ToUpperInvariant()))
        {
            // process image
        }
    }
}
Eren Temelli
  • 333
  • 1
  • 14