Wrox Home  
Search
ASP.NET 2.0 MVP Hacks and Tips
by David Yack, Joe Mayo, Scott Hanselman, Fredrik Normén, Dan Wahlin, J. Ambrose Little, Jonathan Goodyear
May 2006, Paperback


Excerpt from ASP.NET 2.0 MVP Hacks and Tips

Two ASP.NET HttpHandler Image Hacks

By Scott Hanselman

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.