Following on from my article about checking for the file signature to ensure that the file is who it says it is
I decide to take it further and produce a Data Validation attribute, I found that the Microsoft.Web.Mvc.dll library already has one ,but it only checks for the file names, which is generally okay.
So I thought I’d enhance the Microsoft FileExtension attribute, but it was sealed……! Grrrrrrrr But Microsoft now have most of its code as open source, yaaaaa 🙂
So I downloaded the source and created my own variation of the FileExtensionsAttribute
First we need to generate DataTypeAttribute, and IClientValidatable to inherit.
IClientValidatable
using System.Collections.Generic; using System.Web.Mvc; public interface IClientValidatable { IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context); }
ModelClientValidationRule
using System; using System.Collections.Generic; using System.Runtime.CompilerServices; [TypeForwardedFrom("System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35")] public class ModelClientValidationRule { private readonly Dictionary<string, object> _validationParameters = new Dictionary<string, object>(); private string _validationType; public string ErrorMessage { get; set; } public IDictionary<string, object> ValidationParameters { get { return _validationParameters; } } public string ValidationType { get { return _validationType jQuery15201478678032162548_1380881227039 String.Empty; } set { _validationType = value; } } }
Now the actual code for FileExtensionsAttribute
FileExtensionsAttribute
using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Linq; using System.Web; using System.Web.Mvc; using DataValidation.Properties; /// <summary> /// The file extensions, default is png,jpg,jpeg,gif. /// </summary> [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] public class FileExtensionsAttribute : DataTypeAttribute, IClientValidatable { private string _extensions; private string _extensionsSignatures; public FileExtensionsAttribute() : base("upload") { ErrorMessage = DataValidationResource.FileExtensionsAttribute_Invalid; } /// <summary> /// The file extensions, default is png,jpg,jpeg,gif. /// </summary> /// <value> /// The extensions. /// </value> public string Extensions { get { return String.IsNullOrWhiteSpace(_extensions) ? "png,jpg,jpeg,gif" : _extensions; } set { _extensions = value; } } /// <summary> /// The extensions signatures, this is the fileclass signature within the actual file. /// </summary> /// <value> /// The extensions signatures. /// </value> public string ExtensionsSignatures { get { return String.IsNullOrWhiteSpace(_extensionsSignatures) ? "" : _extensionsSignatures; } set { _extensionsSignatures = value; } } private string ExtensionsFormatted { get { return ExtensionsParsed.Aggregate((left, right) => left + ", " + right); } } [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "These strings are normalized to lowercase because they are presented to the user in lowercase format")] private string ExtensionsNormalized { get { return Extensions.Replace(" ", String.Empty).Replace(".", String.Empty).ToLowerInvariant(); } } [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "These strings are normalized to lowercase because they are presented to the user in lowercase format")] private string ExtensionsSignaturesNormalized { get { return ExtensionsSignatures.Replace(" ", String.Empty).Replace(".", String.Empty).ToLowerInvariant(); } } private IEnumerable<string> ExtensionsParsed { get { return ExtensionsNormalized.Split(',').Select(e => "." + e); } } private IEnumerable<string> ExtensionsSignaturesParsed { get { return ExtensionsSignaturesNormalized.Split(','); } } public override string FormatErrorMessage(string name) { return String.Format(CultureInfo.CurrentCulture, ErrorMessageString, name, ExtensionsFormatted); } public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context) { var rule = new ModelClientValidationRule { ValidationType = "extension", ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()) }; rule.ValidationParameters["extension"] = ExtensionsNormalized; yield return rule; } public override bool IsValid(object value) { if (value == null) { return true; } var valueAsFileBase = value as HttpPostedFileBase; if (valueAsFileBase != null) { return ValidateExtension(valueAsFileBase.FileName) && ValidateFileContent(valueAsFileBase); } var valueAsString = value as string; return valueAsString != null && ValidateExtension(valueAsString); } [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "These strings are normalized to lowercase because they are presented to the user in lowercase format")] private bool ValidateExtension(string fileName) { try { return ExtensionsParsed.Contains(Path.GetExtension(fileName).ToLowerInvariant()); } catch (ArgumentException) { return false; } } private bool ValidateFileContent(HttpPostedFileBase file) { var validExtensions = GetValidExtenstions(); var br = new BinaryReader(file.InputStream); string fileclass; byte buffer; try { buffer = br.ReadByte(); fileclass = buffer.ToString(); buffer = br.ReadByte(); fileclass += buffer.ToString(); } catch { return false; } return string.IsNullOrWhiteSpace(ExtensionsSignatures) ? ExtensionsParsed.Where(validExtensions.ContainsKey).Any(extension => validExtensions[extension].ToString() == fileclass) : ExtensionsSignaturesParsed.Any(extensionsSignatures => extensionsSignatures == fileclass); } private Dictionary<string, int> GetValidExtenstions() { var validExtenstions = new Dictionary<string, int> { {".gif", 7173}, {".jpg", 255216}, {".png", 13780}, {".bmp", 6677}, {".txt", 239187}, {"/", 239187}, {".asp", 239187}, {".sql", 239187}, {".xls", 208207}, {".doc", 208207}, {".docx", 208207}, {".dot", 208207}, {".dotx", 208207}, {".ppt", 208207}, {".xml", 6063}, {".html", 6033}, {".js", 4742}, {".xlsx", 8075}, {".zip", 8075}, {".ptx", 8075}, {".mmap", 8075}, {".rar", 8297}, {".accdb", 01}, {".mdb", 01}, {".exe", 7790}, {".dll", 7790}, {".bat", 64101}, {".tiff", 7373}, {".tif", 7373} }; return validExtenstions; } }
You’ll notice the ValidateFileContent method which is the additional feature I’ve added to check for the file signature.
That’s it job done
A sample project can be found here