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