Two ASP.NET HttpHandler Image Hacks

Every HttpRequest that comes into ASP.NET is handled by an HttpHandler. It seems like a silly thing to say, but it's true. The IHttpHandler interface is the magic that makes it all happen. Every page you write is indirectly an HttpHandler. Every file that is requested is mapped to an HttpHandler. In this excerpt from Chapter 17, "Handlers and Modules," of ASP.NET 2.0 MVP Hacks and Tips you'll see two of the four specific HttpHandler hacks to get you started.

Discouraging Leeching Using an Image-Specific HttpHandler

Bandwidth leeching happens when you have an image hosted on your site and another site links directly to your image using an <img> tag. The page serves from the other site, but the image is served from yours. Browsers typically include the name of the host that obtained the request. Images that are requested from your web server by a page hosted elsewhere will have a referrer header that doesn't match your site's URL.

Interestingly, the HTTP Referrer header field is actually misspelled in the spec, and has always been that way, as "referer" with a single "r." See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html.

The static file handler in Listing 1 will check the current host with the referrer field of the current request. If they don't match, a "backoff.gif" file is returned no matter what file was originally requested! That means if you link to "scott.jpg" from your site, effectively leaching from me, you'll end up getting "backoff.gif" informing you and your readers that you were too lazy to just copy the file locally. It's a coarse technique, true, but these are crazy times on the open Internet and sometimes bandwidth leeches need a little reminder of the rules. The nice thing is that because it is a handler, it can be easily removed without recompiling the application by editing your web.config, so the functionality can be put up for a limited time.

The code is simple. Once ASP.NET and IIS are configured to handle image files, this handler will fire for every request image. First, the HTTP Referrer is checked to ensure that the site they are on is the same site requesting the image. If not, they receive the warning image. Otherwise, Response.WriteFile writes the image out to the client and no one notices the difference.

Listing 1: A leech-preventing HttpHandler for images
C#
using System.IO;
using System.Web;
using System.Globalization;
    
namespace MVPHacks
{
public class NoLeechImageHandler : IHttpHandler 
{
 public void ProcessRequest(System.Web.HttpContext ctx) 
 {
     HttpRequest req = ctx.Request;
     string path = req.PhysicalPath;
     string extension = null;
     
     if (req.UrlReferrer != null && req.UrlReferrer.Host.Length > 0)
     {
      if (
          CultureInfo.InvariantCulture.CompareInfo.Compare(req.Url.Host,
          req.UrlReferrer.Host, CompareOptions.IgnoreCase) != 0)
      {
          path = ctx.Server.MapPath("~/images/backoff.gif");
      }
     }            string contentType = null;
     extension = Path.GetExtension(path).ToLower();
     switch (extension)
     {
         case ".gif": 
             contentType = "image/gif"; 
             break;
         case ".jpg": 
             contentType = "image/jpeg"; 
             break;
         case ".png": 
             contentType = "image/png"; 
             break;
         default:
          throw new NotSupportedException("Unrecognized image type.");
     }    if (!File.Exists (path))
     {
         ctx.Response.Status = "Image not found";
         ctx.Response.StatusCode = 404;
     }
     else
     {
         ctx.Response.StatusCode = 200;
         ctx.Response.ContentType = contentType;
         ctx.Response.WriteFile (path);
     }
 }

     public bool IsReusable { get {return true; } }
 }
}
VB
Namespace MVPHacks
    Imports System.IO
    Imports System.Web
    Imports System.Globalization
    
    Public Class NoLeechImageHandler
        Inherits IHttpHandler
        
        Public ReadOnly Property IsReusable As Boolean
            Get
                Return true
            End Get
        End Property
        
        Public Sub ProcessRequest(ByVal ctx As System.Web.HttpContext)
            Dim req As HttpRequest = ctx.Request
            Dim path As String = req.PhysicalPath
            Dim extension As String = Nothing
            If ((Not (req.UrlReferrer) Is Nothing)  _
                        AndAlso (req.UrlReferrer.Host.Length > 0)) Then
                If (CultureInfo.InvariantCulture.CompareInfo.Compare(
                         req.Url.Host, req.UrlReferrer.Host,
                         CompareOptions.IgnoreCase) <> 0) Then
                    path = ctx.Server.MapPath("~/images/backoff.gif")
                End If
            End If
            Dim contentType As String = Nothing
            extension = Path.GetExtension(path).ToLower
            Select Case (extension)
                Case ".gif"
                    contentType = "image/gif"
                Case ".jpg"
                    contentType = "image/jpeg"
                Case ".png"
                    contentType = "image/png"
                Case Else
             Throw New NotSupportedException(
                "Unrecognized image type.")
            End Select
            If Not File.Exists(path) Then
                ctx.Response.Status = "Image not found"
                ctx.Response.StatusCode = 404
            Else
                ctx.Response.StatusCode = 200
                ctx.Response.ContentType = contentType
                ctx.Response.WriteFile(path)
            End If
        End Sub
    End Class
End Namespace

Associating a file extension with a particular HttpHandler is a two-step process. First, IIS needs to know which extensions are routed to ASP.NET's worker process by associating the extension with aspnet_isapi.dll within IIS's administration tool. Then, within your application's web.config, each extension is associated with an assembly's qualified name (QN) to a specific HttpHandler:

<configuration>
    <system.web>
        <httpHandlers>
            <add verb="*" path="*.jpg" 
                type="MVPHacks.NoLeechImageHandler, MVPHacks "/>
            <add verb="*" path="*.gif" 
                type="MVPHacks.NoLeechImageHandler, MVPHacks "/>
            <add verb="*" path="*.png" 
                type="MVPHacks.NoLeechImageHandler, MVPHacks "/>
        </httpHandlers>
    </system.web>
</configuration>

The configuration of the web.config matches the switch statement within the code, so unless you associate an extension that isn't handled in the switch, you'll never throw the exception within Listing 1. This handler could be extended to handle other kinds of illicit leaching, including PDF or any other file that you feel is being stolen from your website.

Two ASP.NET HttpHandler Image Hacks

Compositing Images with an HttpHandler

Recently, I had a back-end black-box system that was sending me two images that were Base64 encoded — that is, binary data encoded as a string, within an XML response. The images were of the front and back of a check. However, the requirement from the client was to show a single composite check image in the user's browser with the front image stacked on top of the back image with a single HTTP GET request. Of course, it has to be secure because this is a check we're talking about, so no writing to temp files!

Here's my solution, implemented as an HttpHandler, so the HTML would include something like the following:

<img src="checkimage.ashx?whatever=4&something=6">

The checkimage.ashx endpoint could be configured in the application's web.config HttpHandlers section, or presented as a small text file like this:

<% @ webhandler language="C#"
   class="MVPHacks.ExampleCompositingImageHandler" %>

The point is to make sure that ASP.NET knows how to map a request to that request's handler. The simple one-line ashx file exists for one purpose and one purpose only — to be an endpoint, and in existing, to provide a mapping. Most folks prefer to do their mapping within the web.config because everything is centralized and easily changed. Others find this frustrating because their first instinct is to assume that every file that appears in the browser's address bar corresponds to a file on disk. Neither way is better than the other but I prefer the flexibility of the web.config method.

Listing 2 has a few details snipped out that are specific to the request I made to the back-end mainframe system. I've replaced that with the simple request/response model to imply an XML Web Services method. The check images are in the res.ImageFront and res.ImageBack properties as byte arrays.

Listing 2: An image compositing HttpHandler
C#
public class ExampleCompositingImageHandler : IHttpHandler
{
   public SomeCheckImageHandler(){}

 public void ProcessRequest(HttpContext context)
   {
    context.Response.ContentType = "image/jpeg";
    //some stuff snipped
    GetCheckImageRequest req = new GetCheckImageRequest();
    //some stuff snipped, get the params from the QueryString
    GetCheckImageResponse res = banking.GetCheckImage(req);
    //some stuff snipped
    if (res.ImageBack != null)
    {
        //merge them into one image
        using(MemoryStream m = new MemoryStream(res.BackImageBytes))
        using(Image backImage = System.Drawing.Image.FromStream(m))
        using(MemoryStream m2 = new MemoryStream(res.BrontImageBytes))
        using(Image frontImage = System.Drawing.Image.FromStream(m2))
        using(Bitmap compositeImage = new 
      Bitmap(frontImage.Width,frontImage.Height+backImage.Height))
using(Graphics compositeGraphics = Graphics.FromImage(compositeImage))
        {
     compositeGraphics.CompositingMode = CompositingMode.SourceCopy;
     compositeGraphics.DrawImageUnscaled(frontImage,0,0);
     compositeGraphics.DrawImageUnscaled(backImage,0,frontImage.Height);
     compositeImage.Save(context.Response.OutputStream,
        ImageFormat.Jpeg);
        }
    }
    else //just show the front, we've got no back
    {
        using(MemoryStream m = new MemoryStream(frontImageBytes))
        using(Image image = System.Drawing.Image.FromStream(m))
        {
            image.Save(context.Response.OutputStream, ImageFormat.Jpeg);
        }
    }
   }
}
VB
Public Class ExampleCompositingImageHandler
    Inherits IHttpHandler
    
  Public Sub ProcessRequest(ByVal context As HttpContext)
    context.Response.ContentType = "image/jpeg"
    'some stuff snipped
    Dim req As GetCheckImageRequest = New GetCheckImageRequest
    'some stuff snipped, get the params from the QueryString
    Dim res As GetCheckImageResponse = banking.GetCheckImage(req)
    'some stuff snipped
    If (Not (res.ImageBack) Is Nothing) Then
        'merge them into one image
   Using m As MemoryStream = New MemoryStream(res.BackImageBytes)
     Using backImage As Image = System.Drawing.Image.FromStream(m)
       Using m2 As MemoryStream = New MemoryStream(res.BrontImageBytes)
         Using frontImage As Image = System.Drawing.Image.FromStream(m2)
           Using compositeImage As Bitmap = 
New Bitmap(frontImage.Width, (frontImage.Height + backImage.Height))
             Using compositeGraphics As Graphics = 
                 Graphics.FromImage(compositeImage)
compositeGraphics.CompositingMode = CompositingMode.SourceCopy
compositeGraphics.DrawImageUnscaled(frontImage, 0, 0)
compositeGraphics.DrawImageUnscaled(backImage, 0, frontImage.Height)
compositeImage.Save(context.Response.OutputStream, ImageFormat.Jpeg)
             End Using
           End Using
         End Using
       End Using
     End Using
   End Using
      Else
          Dim m As MemoryStream = New MemoryStream(frontImageBytes)
          Dim image As Image = System.Drawing.Image.FromStream(m)
          image.Save(context.Response.OutputStream, ImageFormat.Jpeg)
      End If
  End Sub
End Class

Note a few interesting things about this example. First, the manipulation of streams enables you to save the contents of the stream directly to the response object, as in this line:

image.Save(context.Response.OutputStream, ImageFormat.Jpeg)

Another thing of note is my potentially excessive use of the using statement. Many classes within the .NET Base Class Library are disposable. That is, they are thin wrappers around physical resources that really should be released as soon as you are done with them. Many of the classes within GDI+, the Graphics Device Interface, hold on to unmanaged resources. Rather than wait for the garbage collector to clean up after me, I prefer to be very explicit and dispose of these resources directly. The using statement provides determinism, and while it doesn't release the object reference itself, it does promise to clean up the resources that object manages. After an object is disposed, you mustn't use it. Some may call this stylistic and may disagree, but I feel that is deterministic and a best practice.

The last interesting thing to note is that in the interest of robustness this handler draws the front check image by itself if the back is not returned. This allows more flexibility and renders the hack reusable.

Note that the Using statement was added in VB in .NET 2.0. You'll need to remove it to use this code under VB.NET 1.1. The Using statement in VB is equivalent to a try/finally block in which the finally block calls Dispose() on the variable referenced.

The preceding article was an excerpt from ASP.NET 2.0 MVP Hacks and Tips. This section was written by Scott Hanselman. The excerpt was reprinted with the publisher's permission.



About the Author

Scott Hanselman

Scott Hanselman is a co-author of ASP.NET 2.0 MVP Hacks and Tips (Wrox, 2006, ISBN 0-7645-9766-3) along with fellow Microsoft MVPs (Microsoft Most Valuable Professionals) David Yack, Joe Mayo, Fredrik Normén, Dan Wahlin, J. Ambrose Little, Jonathan Goodyear. Scott is the chief architect at the Corillian Corporation, an eFinance enabler. He has over 13 years experience developing software in C, C++, VB, COM, and certainly VB.NET and C#. Scott is proud to be both a Microsoft RD (regional director) as well as an MVP for both ASP.NET and Solutions architecture.

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: May 7, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT This eSeminar will explore three popular games engines and how they empower developers to create exciting, graphically rich, and high-performance games for Android® on Intel® Architecture. Join us for a deep dive as experts describe the features, tools, and common challenges using Marmalade, App Game Kit, and Havok game engines, as well as a discussion of the pros and cons of each engine and how they fit into your development …

  • With JRebel, developers get to see their code changes immediately, fine-tune their code with incremental changes, debug, explore and deploy their code with ease (both locally and remotely), and ultimately spend more time coding instead of waiting for the dreaded application redeploy to finish. Every time a developer tests a code change it takes minutes to build and deploy the application. JRebel keeps the app server running at all times, so testing is instantaneous and interactive.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds