Determine if uploaded file is image (any format) on MVC

82,139

Solution 1

In case it can helps anyone, Here is a static method for HttpPostedFileBase that checks if a given uploaded file is an image:

public static class HttpPostedFileBaseExtensions
{
    public const int ImageMinimumBytes = 512;

    public static bool IsImage(this HttpPostedFileBase postedFile)
    {
        //-------------------------------------------
        //  Check the image mime types
        //-------------------------------------------
        if (!string.Equals(postedFile.ContentType, "image/jpg", StringComparison.OrdinalIgnoreCase) &&
            !string.Equals(postedFile.ContentType, "image/jpeg", StringComparison.OrdinalIgnoreCase) &&
            !string.Equals(postedFile.ContentType, "image/pjpeg", StringComparison.OrdinalIgnoreCase) &&
            !string.Equals(postedFile.ContentType, "image/gif", StringComparison.OrdinalIgnoreCase) &&
            !string.Equals(postedFile.ContentType, "image/x-png", StringComparison.OrdinalIgnoreCase) &&
            !string.Equals(postedFile.ContentType, "image/png", StringComparison.OrdinalIgnoreCase))
        {
            return false;
        }

        //-------------------------------------------
        //  Check the image extension
        //-------------------------------------------
        var postedFileExtension = Path.GetExtension(postedFile.FileName);
        if (!string.Equals(postedFileExtension , ".jpg", StringComparison.OrdinalIgnoreCase)
            && !string.Equals(postedFileExtension , ".png", StringComparison.OrdinalIgnoreCase)
            && !string.Equals(postedFileExtension , ".gif", StringComparison.OrdinalIgnoreCase)
            && !string.Equals(postedFileExtension , ".jpeg", StringComparison.OrdinalIgnoreCase))
        {
            return false;
        }

        //-------------------------------------------
        //  Attempt to read the file and check the first bytes
        //-------------------------------------------
        try
        {
            if (!postedFile.InputStream.CanRead)
            {
                return false;
            }
            //------------------------------------------
            //   Check whether the image size exceeding the limit or not
            //------------------------------------------ 
            if (postedFile.ContentLength < ImageMinimumBytes)
            {
                return false;
            }

            byte[] buffer = new byte[ImageMinimumBytes];
            postedFile.InputStream.Read(buffer, 0, ImageMinimumBytes);
            string content = System.Text.Encoding.UTF8.GetString(buffer);
            if (Regex.IsMatch(content, @"<script|<html|<head|<title|<body|<pre|<table|<a\s+href|<img|<plaintext|<cross\-domain\-policy",
                RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Multiline))
            {
                return false;
            }
        }
        catch (Exception)
        {
            return false;
        }

        //-------------------------------------------
        //  Try to instantiate new Bitmap, if .NET will throw exception
        //  we can assume that it's not a valid image
        //-------------------------------------------

        try
        {
            using (var bitmap = new System.Drawing.Bitmap(postedFile.InputStream))
            {
            }
        }
        catch (Exception)
        {
            return false;
        }
        finally
        {
             postedFile.InputStream.Position = 0;
        }

        return true;
    }
}

Edit 2/10/2017: According to a suggested edit, added a finally statement to reset the stream, so we can use it later.

Solution 2

It's 2018 and the accepted answer does not work with .NET CORE 2.1 because we now have IFormFile instead of HttpPostedFileBase.

Here comes the adaption of the accepted answer to .NET CORE 2.1 (I also fixed the bug/typo mentioned by TomSelleck in his comment to the accepted answer):

public static class FormFileExtensions
{
    public const int ImageMinimumBytes = 512;

    public static bool IsImage(this IFormFile postedFile)
    {
        //-------------------------------------------
        //  Check the image mime types
        //-------------------------------------------
        if (postedFile.ContentType.ToLower() != "image/jpg" &&
                    postedFile.ContentType.ToLower() != "image/jpeg" &&
                    postedFile.ContentType.ToLower() != "image/pjpeg" &&
                    postedFile.ContentType.ToLower() != "image/gif" &&
                    postedFile.ContentType.ToLower() != "image/x-png" &&
                    postedFile.ContentType.ToLower() != "image/png")
        {
            return false;
        }

        //-------------------------------------------
        //  Check the image extension
        //-------------------------------------------
        if (Path.GetExtension(postedFile.FileName).ToLower() != ".jpg"
            && Path.GetExtension(postedFile.FileName).ToLower() != ".png"
            && Path.GetExtension(postedFile.FileName).ToLower() != ".gif"
            && Path.GetExtension(postedFile.FileName).ToLower() != ".jpeg")
        {
            return false;
        }

        //-------------------------------------------
        //  Attempt to read the file and check the first bytes
        //-------------------------------------------
        try
        {
            if (!postedFile.OpenReadStream().CanRead)
            {
                return false;
            }
            //------------------------------------------
            //check whether the image size exceeding the limit or not
            //------------------------------------------ 
            if (postedFile.Length < ImageMinimumBytes)
            {
                return false;
            }

            byte[] buffer = new byte[ImageMinimumBytes];
            postedFile.OpenReadStream().Read(buffer, 0, ImageMinimumBytes);
            string content = System.Text.Encoding.UTF8.GetString(buffer);
            if (Regex.IsMatch(content, @"<script|<html|<head|<title|<body|<pre|<table|<a\s+href|<img|<plaintext|<cross\-domain\-policy",
                RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Multiline))
            {
                return false;
            }
        }
        catch (Exception)
        {
            return false;
        }

        //-------------------------------------------
        //  Try to instantiate new Bitmap, if .NET will throw exception
        //  we can assume that it's not a valid image
        //-------------------------------------------

        try
        {
            using (var bitmap = new System.Drawing.Bitmap(postedFile.OpenReadStream()))
            {
            }
        }
        catch (Exception)
        {
            return false;
        }
        finally
        {
            postedFile.OpenReadStream().Position = 0;
        }

        return true;
    }
}

Solution 3

For anyone that runs into this.

You could also use a file.ContentType.Contains("image") to check if the content type is of image/*.

if(file.ContentLength > 0 && file.ContentType.Contains("image"))
{
    //valid image
}
else
{
    //not a valid image
}

Not sure if this is best practice, but it works for me.

Solution 4

Don't have the compiler at hand but something like this should do:

try
{
   var bitmap = Bitmap.FromStream( file.InputStream );
   // valid image stream
}
catch 
{
    // not an image
}

Solution 5

Use in static helper class:

public static bool IsImage(HttpPostedFileBase postedFile)
    {
        try  {
              using (var bitmap = new System.Drawing.Bitmap(postedFile.InputStream))
                    {                        
                            return !bitmap.Size.IsEmpty;
                    }
                }
                catch (Exception)
                {
                    return false;
                }
            }
    }

Use in ASP.NET MVC viewmodel:

public class UploadFileViewModel
    {
        public HttpPostedFileBase postedFile { get; set; }

        public  bool IsImage()
        {
            try  {
                  using (var bitmap = new System.Drawing.Bitmap(this.postedFile.InputStream))
                        {                        
                                return !bitmap.Size.IsEmpty;
                        }
                    }
                    catch (Exception)
                    {
                        return false;
                    }
                }
        }
    }

This example checks to see whether the image is a real image, and you can modify and convert it.

It eats memory as an example of six-liter V8, so it should be used when you really want to know what this image.

Share:
82,139
Erre Efe
Author by

Erre Efe

"When you're doing something you're passionate about, stress becomes a feature not a bug."

Updated on July 19, 2021

Comments

  • Erre Efe
    Erre Efe almost 3 years

    So I'm using this code for view:

    <form action="" method="post" enctype="multipart/form-data">
    
      <label for="file">Filename:</label>
      <input type="file" name="file" id="file" />
    
      <input type="submit" />
    </form>
    

    This for model:

    [HttpPost]
    public ActionResult Index(HttpPostedFileBase file) {
    
      if (file.ContentLength > 0) {
        var fileName = Path.GetFileName(file.FileName);
        var path = Path.Combine(Server.MapPath("~/App_Data/uploads"), fileName);
        file.SaveAs(path);
      }
    
      return RedirectToAction("Index");
    }
    

    Works great unless the user add a file which isn't an image. How can I assure the file uploaded is an image. Thanks

  • Wiktor Zychla
    Wiktor Zychla almost 12 years
    That won't work for crafted requests where mime type does not correspond to the stream content.
  • Rasool Ghafari
    Rasool Ghafari over 10 years
    What's your idea for these file types: ".pdf", ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".pps", ".ppsx", please.
  • OzB
    OzB over 10 years
    Besides from validating the file extension, you can validate the first bytes as well (see for example serverfault.com/questions/338087/…). In this regard, please note that they're compressed ZIP (so remember the mime-type) files, which gives you an XML, you can try to validate this XML format, though I think that it'll hard unlike the image validation above. An idea that might work is to use the Office COM and try to load the documents to see if you got any errors. This approach, though, won't work in all environments.
  • Rohan Kandwal
    Rohan Kandwal over 10 years
    please provide a little bit of explanation too. :D
  • refactor
    refactor over 8 years
    This will not restrict users from selecting other MIME types.
  • MonsterMMORPG
    MonsterMMORPG almost 8 years
    i think this is the only solution that can be trusted
  • Techy
    Techy about 7 years
    Can you modify this to allow for ASP.NET Core as there is no drawing or bitmap class? Thanks a lot
  • TomSelleck
    TomSelleck almost 6 years
    if (postedFile.ContentLength > ImageMinimumBytes) This is a typo no? It should read if (ContentLength < ImageMinimumBytes)
  • René
    René over 5 years
    I have corrected the bug/typo mentioned by TomSelleck in my adaption of this answer for 2018 using .NET CORE 2.1 (see below).
  • OzB
    OzB over 5 years
    You're absolutely right, @TomSelleck . Fixed here as well.
  • TomSelleck
    TomSelleck over 5 years
    @OzB Thanks for posting, proved very useful when I needed it!
  • Juan
    Juan over 4 years
    This also perfectly valid in my case (well, IFormFile does not contain ContentLength but Length property)
  • Max Carroll
    Max Carroll about 4 years
    I'm using .net core 3.1 and I had to add reference the System.Drawing.Common library for this to work
  • Max Carroll
    Max Carroll about 4 years
    does this work on .net core on docker in ubuntu, Im getting some issues, looking into it it might also be reverse proxy its behind,
  • René
    René about 4 years
    @MaxCarroll I have not tried it on docker in ubuntu. I guess if you are "getting some issues" it's best to open a new question and give a description of those issues.
  • Boban Stojanovski
    Boban Stojanovski about 4 years
    This should be the accpeted answer
  • Boban Stojanovski
    Boban Stojanovski about 4 years
    If uploading a file, check the headers of the MultipartFileData if (file.Headers.ContentLength > 0 && file.Headers.Contains("image"))
  • Edward Olamisan
    Edward Olamisan about 4 years
    this code only validates file name. it does NOT validate the file is an image.
  • Edward Olamisan
    Edward Olamisan about 4 years
    this code only validates file name. it does NOT validate the file is an image.
  • Shawn Gavett
    Shawn Gavett about 4 years
    @EdwardOlamisan, are you talking about the answer or the comment above your comment? My code reviews the contentType which is not the filename
  • Edward Olamisan
    Edward Olamisan about 4 years
    @ShawnGavett sorry, my mistake, I copy-pasted my comment to another answer because they are so similar: stackoverflow.com/a/41395863/556649. Bottom line, this answer has the same problem: file name and headers, including ContentType, are provided by the user and can be crafted by a malicious user.
  • OwenP
    OwenP over 3 years
    @OzB: Compare what "exceeds the size limit" means to what your code does now.
  • RemarkLima
    RemarkLima about 3 years
    I'd suggest that this would be a lot better if it returned true for each condition - unless you want to check everything?
  • GAltelino
    GAltelino about 3 years
    same as @MaxCarroll, this breaks on Fargate. The issue is in System.Drawing: github.com/dotnet/runtime/issues/21980