36

I'm using ASP.NET Core 2.2 and I'm using model binding for uploading file.

This is my UserViewModel

public class UserViewModel
{
    [Required(ErrorMessage = "Please select a file.")]
    [DataType(DataType.Upload)]
    public IFormFile Photo { get; set; }
}

This is MyView

@model UserViewModel

<form method="post"
      asp-action="UploadPhoto"
      asp-controller="TestFileUpload"
      enctype="multipart/form-data">
    <div asp-validation-summary="ModelOnly" class="text-danger"></div>

    <input asp-for="Photo" />
    <span asp-validation-for="Photo" class="text-danger"></span>
    <input type="submit" value="Upload"/>
</form>

And finally this is MyController

[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> UploadPhoto(UserViewModel userViewModel)
{
    if (ModelState.IsValid)
    {
        var formFile = userViewModel.Photo;
        if (formFile == null || formFile.Length == 0)
        {
            ModelState.AddModelError("", "Uploaded file is empty or null.");
            return View(viewName: "Index");
        }

        var uploadsRootFolder = Path.Combine(_environment.WebRootPath, "uploads");
        if (!Directory.Exists(uploadsRootFolder))
        {
            Directory.CreateDirectory(uploadsRootFolder);
        }

        var filePath = Path.Combine(uploadsRootFolder, formFile.FileName);
        using (var fileStream = new FileStream(filePath, FileMode.Create))
        {
            await formFile.CopyToAsync(fileStream).ConfigureAwait(false);
        }

        RedirectToAction("Index");
    }
    return View(viewName: "Index");
}

How can I limit uploaded files to lower than 5MB with specific extensions like .jpeg and .png ? I think both of these validations are done in the ViewModel. But I don't know how to do that.

Arian Shahalami
  • 1,369
  • 2
  • 13
  • 25
  • 1
    Have you read https://learn.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads?view=aspnetcore-2.2? Are you running on IIS? – mjwills Jun 13 '19 at 21:48
  • @mjwills yes I'm running on IIS. I've read Microsoft docs but I didn't find anything useful for this situation – Arian Shahalami Jun 13 '19 at 22:18
  • What did that page say about `maxAllowedContentLength`? – mjwills Jun 13 '19 at 23:11
  • 1
    @mjwills it limits **all** upload requests heavier than 50 MB. I just want to limit upload files lower than 5MB in a specific page. And in another page for example 20 MB and in another page 30MB did u got it ? – Arian Shahalami Jun 14 '19 at 08:39
  • 2
    https://www.google.com/search?q=maxallowedcontentlength+for+specific+page – mjwills Jun 14 '19 at 09:03

8 Answers8

106

You could custom validation attribute MaxFileSizeAttribute like below

MaxFileSizeAttribute

public class MaxFileSizeAttribute : ValidationAttribute
{
    private readonly int _maxFileSize;
    public MaxFileSizeAttribute(int maxFileSize)
    {
        _maxFileSize = maxFileSize;
    }

    protected override ValidationResult IsValid(
    object value, ValidationContext validationContext)
    {
        var file = value as IFormFile;
        if (file != null)
        {
           if (file.Length > _maxFileSize)
            {
                return new ValidationResult(GetErrorMessage());
            }
        }

        return ValidationResult.Success;
    }

    public string GetErrorMessage()
    {
        return $"Maximum allowed file size is { _maxFileSize} bytes.";
    }
}

AllowedExtensionsAttribute

public class AllowedExtensionsAttribute : ValidationAttribute
{
    private readonly string[] _extensions;
    public AllowedExtensionsAttribute(string[] extensions)
    {
        _extensions = extensions;
    }
    
    protected override ValidationResult IsValid(
    object value, ValidationContext validationContext)
    {
        var file = value as IFormFile;
        if (file != null)
        {
            var extension = Path.GetExtension(file.FileName);
            if (!_extensions.Contains(extension.ToLower()))
            {
                return new ValidationResult(GetErrorMessage());
            }
        }
        
        return ValidationResult.Success;
    }

    public string GetErrorMessage()
    {
        return $"This photo extension is not allowed!";
    }
}

Add MaxFileSize attribute and AllowedExtensions attribute to Photo property

public class UserViewModel
{
        [Required(ErrorMessage = "Please select a file.")]
        [DataType(DataType.Upload)]
        [MaxFileSize(5* 1024 * 1024)]
        [AllowedExtensions(new string[] { ".jpg", ".png" })]
        public IFormFile Photo { get; set; }
 }
ThomasArdal
  • 4,999
  • 4
  • 33
  • 73
Xueli Chen
  • 11,987
  • 3
  • 25
  • 36
  • 4
    Good solution! But could you Edit ur answer and make two separate validation Attributes. One for **MaxFileSize** and another for **AllowedExtensions**. Cause based on **SOLID** principles every attribute should have a single responsiblity. – Arian Shahalami Jun 14 '19 at 08:50
  • @ArianShahalami , Check the updated in my reply . If you find my workaround works, could you mark my reply as the answer ? – Xueli Chen Jun 14 '19 at 10:01
  • Really cool answer, had no idea you could make custom validation attributes! – perustaja Jan 16 '20 at 04:46
  • 5
    This is extension-base. What about spoofing? I take `exe`, change extension to `txt` . . – T.S. Jan 30 '20 at 03:28
  • How do I get this to interrupt my controller and display an error on the UI? I'm hitting the GetErrorMessage() method but my controller just keeps running. – DMur Dec 24 '21 at 16:25
  • @XueliChen I need to validated mp4 video for max size how can I validate it, in need to send video max length 100-MB and extension only mp4. And I need to send custom message for different model, how can I send it. – Vipin Jha Apr 04 '22 at 11:18
  • 2
    Validating file by its extension (name) is not secure. See [File signature validation](https://learn.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads?view=aspnetcore-6.0#validation) section in official MS docs. You should be comparing _signature_ of the file with a pre-defined list. – FFFffff Jul 21 '22 at 14:39
  • @FFFffff edit this answer and include your signature validation, which would make this the ideal answer please :) – Kappacake Sep 02 '22 at 10:08
  • Note that this is server-side validation only. If you want client-side validation, you need to implement your own. Follow this to implement client-side validation for cutom attributes: https://learn.microsoft.com/en-us/archive/blogs/mvpawardprogram/asp-net-core-mvc – Kappacake Sep 02 '22 at 14:55
  • Not sure why my MaxFileSizeAttribute never gets called even though it exists and MaxFileSize is already applied. Breakpoint doesn't get caught. I haven't tried the extension one. Any clue? – Reza Taba May 30 '23 at 15:54
11

Above solutions are validating file by its extension (which is just the filename really). That's not secure, as it allows loading executables (pretending to be image or so).

You should be comparing signature of the file with a pre-defined list of allowed signatures. See the File signature validation section in MS docs.

I then call the IsFileValid() method from controllers.


public static bool IsFileValid(IFormFile file)
{
    using (var reader = new BinaryReader(file.OpenReadStream()))
    {
        var signatures = _fileSignatures.Values.SelectMany(x => x).ToList();  // flatten all signatures to single list
        var headerBytes = reader.ReadBytes(_fileSignatures.Max(m => m.Value.Max(n => n.Length)));
        bool result = signatures.Any(signature => headerBytes.Take(signature.Length).SequenceEqual(signature));
        return result;
    }
}

private static readonly Dictionary<string, List<byte[]>> _fileSignatures = new()
{
    { ".gif", new List<byte[]> { new byte[] { 0x47, 0x49, 0x46, 0x38 } } },
    { ".png", new List<byte[]> { new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A } } },
    { ".jpeg", new List<byte[]>
        {
            new byte[] { 0xFF, 0xD8, 0xFF, 0xE0 },
            new byte[] { 0xFF, 0xD8, 0xFF, 0xE2 },
            new byte[] { 0xFF, 0xD8, 0xFF, 0xE3 },
            new byte[] { 0xFF, 0xD8, 0xFF, 0xEE },
            new byte[] { 0xFF, 0xD8, 0xFF, 0xDB },
        }
    },
    { ".jpeg2000", new List<byte[]> { new byte[] { 0x00, 0x00, 0x00, 0x0C, 0x6A, 0x50, 0x20, 0x20, 0x0D, 0x0A, 0x87, 0x0A } } },
    
    { ".jpg", new List<byte[]>
        {
            new byte[] { 0xFF, 0xD8, 0xFF, 0xE0 },
            new byte[] { 0xFF, 0xD8, 0xFF, 0xE1 },
            new byte[] { 0xFF, 0xD8, 0xFF, 0xE8 },
            new byte[] { 0xFF, 0xD8, 0xFF, 0xEE },
            new byte[] { 0xFF, 0xD8, 0xFF, 0xDB },
        }
    },
    { ".zip", new List<byte[]> //also docx, xlsx, pptx, ...
        {
            new byte[] { 0x50, 0x4B, 0x03, 0x04 },
            new byte[] { 0x50, 0x4B, 0x4C, 0x49, 0x54, 0x45 },
            new byte[] { 0x50, 0x4B, 0x53, 0x70, 0x58 },
            new byte[] { 0x50, 0x4B, 0x05, 0x06 },
            new byte[] { 0x50, 0x4B, 0x07, 0x08 },
            new byte[] { 0x57, 0x69, 0x6E, 0x5A, 0x69, 0x70 },
        }
    },

    { ".pdf", new List<byte[]> { new byte[] { 0x25, 0x50, 0x44, 0x46 } } },
    { ".z", new List<byte[]>
        {
            new byte[] { 0x1F, 0x9D },
            new byte[] { 0x1F, 0xA0 }
        }
    },
    { ".tar", new List<byte[]>
        {
            new byte[] { 0x75, 0x73, 0x74, 0x61, 0x72, 0x00, 0x30 , 0x30 },
            new byte[] { 0x75, 0x73, 0x74, 0x61, 0x72, 0x20, 0x20 , 0x00 },
        }
    },
    { ".tar.z", new List<byte[]>
        {
            new byte[] { 0x1F, 0x9D },
            new byte[] { 0x1F, 0xA0 }
        }
    },
    { ".tif", new List<byte[]>
        {
            new byte[] { 0x49, 0x49, 0x2A, 0x00 },
            new byte[] { 0x4D, 0x4D, 0x00, 0x2A }
        }
    },
    { ".tiff", new List<byte[]>
        {
            new byte[] { 0x49, 0x49, 0x2A, 0x00 },
            new byte[] { 0x4D, 0x4D, 0x00, 0x2A }
        }
    },
    { ".rar", new List<byte[]>
        {
            new byte[] { 0x52, 0x61, 0x72, 0x21, 0x1A, 0x07 , 0x00 },
            new byte[] { 0x52, 0x61, 0x72, 0x21, 0x1A, 0x07 , 0x01, 0x00 },
        }
    },
    { ".7z", new List<byte[]>
        {
            new byte[] { 0x37, 0x7A, 0xBC, 0xAF, 0x27 , 0x1C },
        }
    },
    { ".txt", new List<byte[]>
        {
            new byte[] { 0xEF, 0xBB , 0xBF },
            new byte[] { 0xFF, 0xFE},
            new byte[] { 0xFE, 0xFF },
            new byte[] { 0x00, 0x00, 0xFE, 0xFF },
        }
    },
    { ".mp3", new List<byte[]>
        {
            new byte[] { 0xFF, 0xFB },
            new byte[] { 0xFF, 0xF3},
            new byte[] { 0xFF, 0xF2},
            new byte[] { 0x49, 0x44, 0x43},
        }
    },
};

More signatures can be found on Wikipedia.

FFFffff
  • 776
  • 7
  • 18
7

This is how I use the custom validation attributes, almost the same as @xueli-chen 's answer but production-ready.

FileExtensionsAttribute

using System;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.IO;
using System.Linq;

using Microsoft.AspNetCore.Http;

using NewsPassWebApi.Properties;

namespace NewsPassWebApi.Models.DataAnnotaions
{
    /// <summary>
    /// Validation attribute to assert an <see cref="IFormFile">IFormFile</see> property, field or parameter has a specific extension.
    /// </summary>
    [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
    public sealed class FileExtensionsAttribute : ValidationAttribute
    {
        private string _extensions;

        /// <summary>
        /// Gets or sets the acceptable extensions of the file.
        /// </summary>
        public string Extensions
        {
            get
            {
                // Default file extensions match those from jquery validate.
                return string.IsNullOrEmpty(_extensions) ? "png,jpg,jpeg,gif" : _extensions;
            }
            set
            {
                _extensions = value;
            }
        }

        private string ExtensionsNormalized
        {
            get
            {
                return Extensions.Replace(" ", "", StringComparison.Ordinal).ToUpperInvariant();
            }
        }

        /// <summary>
        /// Parameterless constructor.
        /// </summary>
        public FileExtensionsAttribute() : base(() => Resources.FileExtensionsAttribute_ValidationError)
        { }

        /// <summary>
        /// Override of <see cref="ValidationAttribute.IsValid(object)"/>
        /// </summary>
        /// <remarks>
        /// This method returns <c>true</c> if the <paramref name="value"/> is null.  
        /// It is assumed the <see cref="RequiredAttribute"/> is used if the value may not be null.
        /// </remarks>
        /// <param name="value">The value to test.</param>
        /// <returns><c>true</c> if the value is null or its extension is included in the set extensions</returns>
        public override bool IsValid(object value)
        {
            // Automatically pass if value is null. RequiredAttribute should be used to assert a value is not null.
            if (value == null)
            {
                return true;
            }

            // We expect a cast exception if the passed value was not an IFormFile.
            return ExtensionsNormalized.Split(",").Contains(Path.GetExtension(((IFormFile)value).FileName).ToUpperInvariant());
        }

        /// <summary>
        /// Override of <see cref="ValidationAttribute.FormatErrorMessage"/>
        /// </summary>
        /// <param name="name">The name to include in the formatted string</param>
        /// <returns>A localized string to describe the acceptable extensions</returns>
        public override string FormatErrorMessage(string name)
        {
            return string.Format(CultureInfo.CurrentCulture, ErrorMessageString, name, Extensions);
        }
    }
}

FileSizeAttribute

using System;
using System.ComponentModel.DataAnnotations;
using System.Globalization;

using Microsoft.AspNetCore.Http;

using NewsPassWebApi.Properties;

namespace NewsPassWebApi.Models.DataAnnotaions
{
    /// <summary>
    /// Validation attribute to assert an <see cref="IFormFile">IFormFile</see> property, field or parameter does not exceed a maximum size.
    /// </summary>
    [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]
    public sealed class FileSizeAttribute : ValidationAttribute
    {
        /// <summary>
        /// Gets the maximum acceptable size of the file.
        /// </summary>
        public long MaximumSize { get; private set; }

        /// <summary>
        /// Gets or sets the minimum acceptable size of the file.
        /// </summary>
        public int MinimumSize { get; set; }

        /// <summary>
        /// Constructor that accepts the maximum size of the file.
        /// </summary>
        /// <param name="maximumSize">The maximum size, inclusive.  It may not be negative.</param>
        public FileSizeAttribute(int maximumSize) : base(() => Resources.FileSizeAttribute_ValidationError)
        {
            MaximumSize = maximumSize;
        }

        /// <summary>
        /// Override of <see cref="ValidationAttribute.IsValid(object)"/>
        /// </summary>
        /// <remarks>
        /// This method returns <c>true</c> if the <paramref name="value"/> is null.  
        /// It is assumed the <see cref="RequiredAttribute"/> is used if the value may not be null.
        /// </remarks>
        /// <param name="value">The value to test.</param>
        /// <returns><c>true</c> if the value is null or it's size is less than or equal to the set maximum size</returns>
        /// <exception cref="InvalidOperationException"> is thrown if the current attribute is ill-formed.</exception>
        public override bool IsValid(object value)
        {
            // Check the lengths for legality
            EnsureLegalSizes();

            // Automatically pass if value is null. RequiredAttribute should be used to assert a value is not null.
            // We expect a cast exception if the passed value was not an IFormFile.
            var length = value == null ? 0 : ((IFormFile)value).Length;

            return value == null || (length >= MinimumSize && length <= MaximumSize);
        }

        /// <summary>
        /// Override of <see cref="ValidationAttribute.FormatErrorMessage"/>
        /// </summary>
        /// <param name="name">The name to include in the formatted string</param>
        /// <returns>A localized string to describe the maximum acceptable size</returns>
        /// <exception cref="InvalidOperationException"> is thrown if the current attribute is ill-formed.</exception>
        public override string FormatErrorMessage(string name)
        {
            EnsureLegalSizes();

            string errorMessage = MinimumSize != 0 ? Resources.FileSizeAttribute_ValidationErrorIncludingMinimum : ErrorMessageString;

            // it's ok to pass in the minLength even for the error message without a {2} param since String.Format will just ignore extra arguments
            return string.Format(CultureInfo.CurrentCulture, errorMessage, name, MaximumSize, MinimumSize);
        }

        /// <summary>
        /// Checks that MinimumSize and MaximumSize have legal values.  Throws InvalidOperationException if not.
        /// </summary>
        private void EnsureLegalSizes()
        {
            if (MaximumSize < 0)
            {
                throw new InvalidOperationException(Resources.FileSizeAttribute_InvalidMaxSize);
            }

            if (MaximumSize < MinimumSize)
            {
                throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Resources.RangeAttribute_MinGreaterThanMax, MaximumSize, MinimumSize));
            }
        }
    }
}

now you can use those like any built-in validation attribute including custom/localized error messages, max-min file sizes, and file extensions.

Katia
  • 679
  • 2
  • 15
  • 42
Ahmed Kamal
  • 2,660
  • 3
  • 21
  • 36
6

You can implement IValidatableObject to validate your model.

public class UserViewModel : IValidatableObject
    {
        [Required(ErrorMessage = "Please select a file.")]
        [DataType(DataType.Upload)]
        public IFormFile Photo { get; set; }

        public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
        {
            var photo = ((UserViewModel)validationContext.ObjectInstance).Photo;
            var extension = Path.GetExtension(photo.FileName);
            var size = photo.Length;

            if (!extension.ToLower().Equals(".jpg"))
                yield return new ValidationResult("File extension is not valid.");

           if(size > (5 * 1024 * 1024))
                yield return new ValidationResult("File size is bigger than 5MB.");
        }
    }
Yehor Androsov
  • 4,885
  • 2
  • 23
  • 40
Mofaggol Hoshen
  • 686
  • 1
  • 7
  • 20
2

Tested on .Net 5. How about more elastic attribute with custom object (you can use own ActionResult class)

[AttributeUsage(AttributeTargets.Method)]
public class MaxFileSizeAttribute : Attribute, IAsyncActionFilter
{
    public MaxFileSizeAttribute(int sizeInByte) { this._maxFileSize = sizeInByte; }
    private int _maxFileSize;
    public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        if (
            context
            .HttpContext
            .Request
            .Form != null
            && context.HttpContext.Request.Form.Files.Count > 0
            )
        {

            if (
                context
                .HttpContext
                .Request
                .Form
                .Files
                .Any(x => x.Length >= _maxFileSize)
                )
            {
                context
                    .Result = new ObjectResult($"Max file size is {_maxFileSize} bytes"
                    /*put here whatever object you want*/)
                    {
                        StatusCode = 413 //Payload Too Large
                    };
            } else { await next(); }
        } else { await next(); }

    }
}

And use it in controller:

[MaxFileSize(1024)]
public async Task<ActionResult> SendImages([FromForm] SendAttachment request)
{
 return Ok();
}
Kamil
  • 782
  • 1
  • 9
  • 24
1

Following previous comment, you can add this class:

public class ValidateModelStateFilter : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        if (context.ModelState.IsValid)
        {
            return;
        }

        var validationErrors = context.ModelState
            .Keys
            .SelectMany(k => context.ModelState[k].Errors)
            .Select(e => e.ErrorMessage)
            .ToArray();

        var json = new JsonErrorResponse
        {
            Messages = validationErrors
        };

        context.Result = new BadRequestObjectResult(json);
    }
}

Finally, add the filter to the controllers in Startup class:

 services.AddControllers(options =>
            {
                options.Filters.Add(typeof(ValidateModelStateFilter));
            })
krlosmederos
  • 176
  • 3
  • 11
0

Secure Extension Validation

With the help of this solution and this solution I could come up with a combined solution which works as a DataAnnotation also prevents files like exe from being injected maliciously. As explained well by FFFffff, checking extensions strings only

allows loading executables (pretending to be image or so)

So Here it is:

public class AllowedFileExtensions : ValidationAttribute
{
    protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)
    {
        var file = value as IFormFile;
        if (file != null)
        {
            if (!IsFileValid(file))
            {
                // get the allowed extensions to show in ValidationResult
                string? keys = null;
                foreach (var key in _fileSignatures.Keys)
                {
                    keys += key + ", ";
                }

                return new ValidationResult($"File type is not allowed. These extensions are allowed only: {keys}");
            }
        }

        return ValidationResult.Success;
    }


    public static bool IsFileValid(IFormFile file)
    {
        using (var reader = new BinaryReader(file.OpenReadStream()))
        {
            var signatures = _fileSignatures.Values.SelectMany(x => x).ToList();  // flatten all signatures to single list
            var headerBytes = reader.ReadBytes(_fileSignatures.Max(m => m.Value.Max(n => n.Length)));
            bool result = signatures.Any(signature => headerBytes.Take(signature.Length).SequenceEqual(signature));
            return result;
        }
    }

    public static Dictionary<string, List<byte[]>> _fileSignatures = new()
    {
        { ".jpeg", new List<byte[]>
            {
                new byte[] { 0xFF, 0xD8, 0xFF, 0xE0 },
                new byte[] { 0xFF, 0xD8, 0xFF, 0xE2 },
                new byte[] { 0xFF, 0xD8, 0xFF, 0xE3 },
                new byte[] { 0xFF, 0xD8, 0xFF, 0xEE },
                new byte[] { 0xFF, 0xD8, 0xFF, 0xDB },
            }
        },
        { ".jpg", new List<byte[]>
            {
                new byte[] { 0xFF, 0xD8, 0xFF, 0xE0 },
                new byte[] { 0xFF, 0xD8, 0xFF, 0xE1 },
                new byte[] { 0xFF, 0xD8, 0xFF, 0xE8 },
                new byte[] { 0xFF, 0xD8, 0xFF, 0xEE },
                new byte[] { 0xFF, 0xD8, 0xFF, 0xDB },
            }
        },
        { ".jpeg2000", new List<byte[]> { new byte[] { 0x00, 0x00, 0x00, 0x0C, 0x6A, 0x50, 0x20, 0x20, 0x0D, 0x0A, 0x87, 0x0A } } },
        { ".png", new List<byte[]> { new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A } } },
        // { ".gif", new List<byte[]> { new byte[] { 0x47, 0x49, 0x46, 0x38 } } },
        // { ".zip", new List<byte[]> //also docx, xlsx, pptx, ...
        //     {
        //         new byte[] { 0x50, 0x4B, 0x03, 0x04 },
        //         new byte[] { 0x50, 0x4B, 0x4C, 0x49, 0x54, 0x45 },
        //         new byte[] { 0x50, 0x4B, 0x53, 0x70, 0x58 },
        //         new byte[] { 0x50, 0x4B, 0x05, 0x06 },
        //         new byte[] { 0x50, 0x4B, 0x07, 0x08 },
        //         new byte[] { 0x57, 0x69, 0x6E, 0x5A, 0x69, 0x70 },
        //     }
        // },

        // { ".pdf", new List<byte[]> { new byte[] { 0x25, 0x50, 0x44, 0x46 } } },
        // { ".z", new List<byte[]>
        //     {
        //         new byte[] { 0x1F, 0x9D },
        //         new byte[] { 0x1F, 0xA0 }
        //     }
        // },
        // { ".tar", new List<byte[]>
        //     {
        //         new byte[] { 0x75, 0x73, 0x74, 0x61, 0x72, 0x00, 0x30 , 0x30 },
        //         new byte[] { 0x75, 0x73, 0x74, 0x61, 0x72, 0x20, 0x20 , 0x00 },
        //     }
        // },
        // { ".tar.z", new List<byte[]>
        //     {
        //         new byte[] { 0x1F, 0x9D },
        //         new byte[] { 0x1F, 0xA0 }
        //     }
        // },
        // { ".tif", new List<byte[]>
        //     {
        //         new byte[] { 0x49, 0x49, 0x2A, 0x00 },
        //         new byte[] { 0x4D, 0x4D, 0x00, 0x2A }
        //     }
        // },
        // { ".tiff", new List<byte[]>
        //     {
        //         new byte[] { 0x49, 0x49, 0x2A, 0x00 },
        //         new byte[] { 0x4D, 0x4D, 0x00, 0x2A }
        //     }
        // },
        // { ".rar", new List<byte[]>
        //     {
        //         new byte[] { 0x52, 0x61, 0x72, 0x21, 0x1A, 0x07 , 0x00 },
        //         new byte[] { 0x52, 0x61, 0x72, 0x21, 0x1A, 0x07 , 0x01, 0x00 },
        //     }
        // },
        // { ".7z", new List<byte[]>
        //     {
        //         new byte[] { 0x37, 0x7A, 0xBC, 0xAF, 0x27 , 0x1C },
        //     }
        // },
        // { ".txt", new List<byte[]>
        //     {
        //         new byte[] { 0xEF, 0xBB , 0xBF },
        //         new byte[] { 0xFF, 0xFE},
        //         new byte[] { 0xFE, 0xFF },
        //         new byte[] { 0x00, 0x00, 0xFE, 0xFF },
        //     }
        // },
        // { ".mp3", new List<byte[]>
        //     {
        //         new byte[] { 0xFF, 0xFB },
        //         new byte[] { 0xFF, 0xF3},
        //         new byte[] { 0xFF, 0xF2},
        //         new byte[] { 0x49, 0x44, 0x43},
        //     }
        // },
    };
}

I purposely commented some extensions from the _fileSignatures Dictionary to block them.

Now just add [AllowedFileExtensions] to your Controller's method signature directly or Model:

public async Task<IActionResult> AddPhoto([AllowedFileExtensions] IFormFile file) { }

Here is the output if a non-allowed file is selected:

{
    "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1",
    "title": "One or more validation errors occurred.",
    "status": 400,
    "traceId": "00-94aa03b188cdf7c402ff22d809ad1bd4-4c1a49080c27213d-00",
    "errors": {
        "files": [
            "File type is not allowed. These extensions are allowed only: .jpeg, .jpg, .jpeg2000, .png, "
        ]
    }
}
Reza Taba
  • 1,151
  • 11
  • 15
-1

Just a quick one if anyone is looking for a PDF file validator:

 { ".pdf", new List<byte[]> { new byte[] { 0x25, 0x50, 0x44, 0x46 } } },

See the brilliant site:

https://www.garykessler.net/library/file_sigs.html

Jeremy Caney
  • 7,102
  • 69
  • 48
  • 77
petros
  • 1