Thursday, 28 August 2014

Resizing ASP.Net images with ImageResizer on the server-side

Very common scenario, you want to upload images and then resize them. I use ImageResizer from http://imageresizing.net, which is dead easy to use and has most options you would probably want (like setting max size or image format).

I want to describe, however, the entire journey from browser to server, because it's easy just to add the functionality and not to add security and checking to ensure that an attacker is not uploading an executable instead of an image. In some cases, this wouldn't matter, in others, you risk a malware attack on your servers.

1. Use a FileUpload control to upload your images. In order to change its appearance to have to resort to various tricks (basically make it invisible and use other controls to populate it and fire the event!). You can, and should, use a regularexpressionvalidator control to ensure that only files of the correct type are uploaded. This is only client side but provides quick feedback to the user if they try and upload something stupid. The regex I use is "^.+\.((jpg)|(JPG)|(jpeg)|(JPEG)|(png)|(PNG)|(BMP)|(bmp))$"
2. Because client validation can be bypassed and files can be renamed, you should provide additional controls on the server-side to ensure the contents of the file are correct, as far as you can. Firstly, I check that fileUpload.HasFile, something which can easily be missed and cause a crash! I then check the fileUpload.PostedFile.ContentType and ensure it starts with "image" or it is "application/octet-stream". If it is octet-stream, I also re-check the file extension is valid.
3. I then check that fileUpload.PostedFile.ContentLength is not greater than a certain amount (in bytes) to stop people uploading very large images to crash my server.
4. I then go one step further and check the uploaded file header to make sure it is what matches a bitmap, jpeg or png. This is a utility function that checks the first few bytes of the file to check for a relevant header. These look like this in my code:

var jpeg = new byte[] { Convert.ToByte("0xFF", 16), Convert.ToByte("0xD8", 16), Convert.ToByte("0xFF", 16) };
var png = new byte[] { Convert.ToByte("0x89", 16), Convert.ToByte("0x50", 16), Convert.ToByte("0x4E", 16), Convert.ToByte("0x47", 16), Convert.ToByte("0x0D", 16), Convert.ToByte("0x0A", 16), Convert.ToByte("0x1A", 16), Convert.ToByte("0x0A", 16) };
var bmp = new byte[] { Convert.ToByte("0x42", 16), Convert.ToByte("0x4D", 16) };

Using these variables and the uploaded file extension, I decide whether the file really is what it claims to be using something like this:

if ((inputFile.Take(png.Length).SequenceEqual(png) && fileName.EndsWith(".png", StringComparison.OrdinalIgnoreCase))
|| // jpeg etc...

5. I now need to obtain an ImageFormat value for the relevant file so I created a simple if/else function to map the file extension onto the ImageFormat enumeration.
6. Create an output memory stream with using() to make sure it is disposed afterwards.
7. I then avoid resizing images that are already a reasonable size (in my case 100K or less) just to reduce burden on the server. Otherwise I call a helper function to resize the image with a maximum width of 1280 and max height of 720.
8. In my case, I do some trickery to see whether somebody has renamed a png to a jpeg or vice-versa by resizing it to both formats and seeing which is smaller. This also means bitmaps will end up compressed in one format or another. Ignoring that little trick, the resizing is simply:

ImageResizer.ImageBuilder.Current.Build(fileUpload.FileContent, outputMemoryStream, new ImageResizer.ResizeSettings("maxwidth=1280&maxheight=720&format=png"), false);

The false is so that the operation does NOT dispose the original bitmap, because I still need to use it. There are plenty of other resize options here.

9. Make sure you finish using your image before leaving the using statement for the memory stream, otherwise something will go bang.

Simples!
Post a Comment