Example: Protecting Your Images from Bandwidth Leeching
Let's apply the use of an HttpHandler to a real-world example. Imagine that you're analyzing your server logs, and you find that you have an enormous number of referrals from some site that you don't recognize. You check out the URL of the referrer and discover that some jerk is deep-linking to one of your prize photos of the Grand Canyon. The high-resolution photo is costing you 150kb toward your bandwidth limit on every request. It also demonstrates a high disregard for copyright.To combat the problem, we'll develop an HttpHandler that checks to see if incoming requests for .jpg files came from your own site, not someone else's. If the referrer is either blank (the URL of the image was entered into a new browser window directly) or from someone else's site (your leeching friend), we'll serve an alternate image indicating that the user should visit your site directly for the photograph.Listing 8.4 shows the finished class, as well as the web.config settings to make it work.
Listing 8.4. Anti-image leeching HttpHandler
C#
VB.NET
using System;
using System.Web;
public class JpgHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
string FileName = context.Server.MapPath(context.Request.FilePath);
if (context.Request.UrlReferrer.Host == null)
{
context.Response.ContentType = "image/JPEG";
context.Response.WriteFile("/no.jpg");
}
else
{
if (context.Request.UrlReferrer.Host.IndexOf("mydomain.com") > 0)
{
context.Response.ContentType = "image/JPEG";
context.Response.WriteFile(FileName);
}
else
{
context.Response.ContentType = "image/JPEG";
context.Response.WriteFile("/no.jpg");
}
}
}
public bool IsReusable
{
get
{ return true; }
}
}
web.config
Imports System
Imports System.Web
Public Class JpgHandler
Implements IHttpHandler
Public Sub ProcessRequest(context As HttpContext)
Dim FileName As String = _
context.Server.MapPath(context.Request.FilePath)
If context.Request.UrlReferrer.Host Is Nothing Then
context.Response.ContentType = "image/JPEG"
context.Response.WriteFile("/no.jpg")
Else
If context.Request.UrlReferrer.Host.IndexOf("mydomain.com")_
> 0 Then
context.Response.ContentType = "image/JPEG"
context.Response.WriteFile(FileName)
Else
context.Response.ContentType = "image/JPEG"
context.Response.WriteFile("/no.jpg")
End If
End If
End Sub
Public ReadOnly Property IsReusable() As Boolean
Get
Return True
End Get
End Property
End Class
The first step in our ProcessRequest() method is to identify the file name of the requested image. After we know that, we determine whether the UrlReferrer property of the Response object is null. If it is, that means the request isn't referred by a page on our site, so we set the content type of the response and use the Response object's WriteFile() method to send an image called "/no.jpg."If there is a referrer, the next step is to determine whether it contains our domain name. If it does, the referral comes from our own site, and it's acceptable for the image to be served. If the referrer doesn't have our domain name, the request came from another site, so we should serve the alternative image.Regardless, we set the IsReusable property to true because there's no reason why the class instance can't be reused for every request.This class must be compiled to an assembly, and that assembly should be placed in the /bin folder of the application. You can compile with the command line compilers or in Visual Studio by building the project.HttpHandlers can also be written in an .ashx file without being compiled, as shown in Listing 8.5, where we construct an image generator used to create a visual security code that helps prevent automated sign-ups in your Web application. The text generated by the images is stored in Session to be used for comparison to the user's input. Figure 8.2 shows the image generated when the page a user is viewing requests the handler.
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.web>
<httpHandlers>
<add verb="*" path="*.jpg" type="JpgHandler, MyDll" />
</httpHandlers>
</system.web>
</configuration>
Listing 8.5. Image generation HttpHandler
C#[View full width]
VB.NET[View full width]
<%@ WebHandler Language="C#" Class="RegisterImageHandler" %>
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Text;
using System.Web;
using System.Web.SessionState;
public class RegisterImageHandler : IHttpHandler, IRequiresSessionState
{
public void ProcessRequest (HttpContext context) {
context.Response.ContentType = "image/gif";
Bitmap b = new Bitmap(200, 60);
Graphics g = Graphics.FromImage(b);
g.FillRectangle(new SolidBrush(Color.White), 0, 0, 200, 60);
Font font = new Font(FontFamily.GenericSerif, 48, FontStyle.Bold, GraphicsUnit.Pixel);
Random r = new Random();
string letters = "ABCDEFGHIJKLMNPQRSTUVWXYZ";
string letter;
StringBuilder s = new StringBuilder();
for (int x = 0; x < 5; x++)
{
letter = letters.Substring(r.Next(0, letters.Length - 1), 1);
s.Append(letter);
g.DrawString(letter, font, new SolidBrush(Color.Black), x * 38, r.Next(0, 15));
}
Pen linePen = new Pen(new SolidBrush(Color.Black), 2);
for (int x = 0; x < 6; x++)
g.DrawLine(linePen, new Point(r.Next(0, 199), r.Next(0, 59)), new Point(r.Next(0,199), r.Next(0, 59)));
b.Save(context.Response.OutputStream, ImageFormat.Gif);
context.Session["pfregisterimage"] = s.ToString();
context.Response.End();
}
public bool IsReusable {
get { return true; }
}
}
HTML in a page calling the handler
<%@ WebHandler Language="VB.NET" Class="RegisterImageHandler" %>
Imports System
Imports System.Drawing
Imports System.Drawing.Imaging
Imports System.Text
Imports System.Web
Imports System.Web.SessionState
Public Class RegisterImageHandler
Implements IHttpHandler
Implements IRequiresSessionState
Public Sub ProcessRequest(context As HttpContext)
context.Response.ContentType = "image/gif"
Dim b As New Bitmap(200, 60)
Dim g As Graphics = Graphics.FromImage(b)
g.FillRectangle(New SolidBrush(Color.White), 0, 0, 200, 60)
Dim font As New Font(FontFamily.GenericSerif, 48, FontStyle.Bold, GraphicsUnit.Pixel)
Dim r As New Random()
Dim letters As String = "ABCDEFGHIJKLMNPQRSTUVWXYZ"
Dim letter As String
Dim s As New StringBuilder()
Dim x As Integer
For x = 0 To 4
letter = letters.Substring(r.Next(0, letters.Length - 1), 1)
s.Append(letter)
g.DrawString(letter, font, New SolidBrush(Color.Black), x * 38, r.Next(0, 15))
Next x
Dim linePen As New Pen(New SolidBrush(Color.Black), 2)
Dim x As Integer
For x = 0 To 5
g.DrawLine(linePen, New Point(r.Next(0, 199), r.Next(0, 59)), New Point(r.Next(0,199), r.Next(0, 59)))
Next x
b.Save(context.Response.OutputStream, ImageFormat.Gif)
context.Session("pfregisterimage") = s.ToString()
context.Response.End()
End Sub
Public ReadOnly Property IsReusable() As Boolean
Get
Return True
End Get
End Property
End Class
<img src=" />
Figure 8.2. Image generated by our handler.5
