Turn Image Hotlinking to Your Advantage

As a webmaster, you know that hotlinking can be pretty annoying. You've worked hard on your web site, its content, and of course the images, so it makes sense that you don't like someone else taking credit for your work.

No doubt you've also seen many articles that teach you how to replace the hotlinked image with a humorous one. Although this may provide you (and others) with hours of amusement, it's not helping you at all. The entire purpose of handling hotlinked images is to save your bandwidth, so replacing a hotlinked image with another one is still costing you bandwidth. And yet, this is the most commonly recommended practice as far as hotlinking goes. As Captain Kirk once said to Spock, "I submit to you that your universe is illogical. I submit to you that YOU are illogical."

Let's reconsider hotlinking just for a minute (I'll get to the code soon!). Back in the 1990s, bandwidth and web hosting were not cheap. The Internet had just opened up to the world and everyone was one their baby feet. Becoming a popular web site overnight could actually shut your site down if your server wasn't prepared to handle the load. Fast forward to the 2000s; things are cheaper now. You actually can get web sites with amounts of space and bandwidth that you will probably never use up. This is known, in the web hosting industry, as "overselling."

Taking these facts into consideration, hotlinking isn't actually that much of an issue now—in terms of bandwidth costs. However, it is still an issue in terms of stealing credit. This is where you can turn it to your advantage: When someone uses your image on their web page, you can dynamically manipulate it to add a 'label' to the image so that anyone looking at that image will see the message that you want on it. I'll use the recent life-on-mars image as an example. The image could be on your web site like this:

But, you can make the same image appear on someone else's web site like this:

Granted, it isn't the most attractive label, but it's noticeable and you've just turned hotlinkers into advertisers for your web site! And, you can always change the rendering method depending on your graphics skills in .NET.

The most common way to do this in ASP.NET is to use HttpHandlers. Unfortunately, web hosts are wary of HttpHandlers and trust them as much as a Jedi would trust a Sith lord to hold his light saber while he ties his shoelaces. Luckily, there are a few methods that I shall present here; these will work for you depending on the amount of control you have over your hosting environment.

These are:

  • The HttpHandler method (full control over server)
  • The ASHX method (limited control)
  • The Dynamic ASP.NET Image method (very limited control)
  • The .htaccess and ASP.NET Image method (very limited control)

There is a code sample provided as an attachment with this article; you can use it to test out the various methods, created in ASP.NET 2.0. However, I suggest that you understand the code first and modify it to suit your needs if required. The code can be transferred to an equivalent ASP.NET 1.1 project as well.

All of the methods are more or less similar inasmuch as the guts of the code is the same. I first will explain the code used to draw the image with the label and then move on to configuring your application to use each of the methods mentioned above.

Turn Image Hotlinking to Your Advantage

Redrawing the Labeled Image

You do not need to know how the redrawing works (you can skip ahead to the next page if you're not interested), but it's always interesting to know. When you want to label the image, you must:

  1. Load the image into an Image object.
  2. Calculate the area for the label.
  3. Draw the label (and text) onto the image.
  4. Send the image to the Response stream.

Loading the image is simple enough.

// initialise our image, bitmap, and graphic
System.Drawing.Image bgimage =
   System.Drawing.Image.FromFile(FileName);

Where FileName is obviously the requested file, such as lifeonmars.jpg. The next part is a little more work. To calculate the area for the label, you will use the Graphics.MeasureString() property and pass it the text that you want written on the image.

//The text is in the web.config file so that you can change it!
string imageLabel =
   ConfigurationSettings.AppSettings["ImageLabel"].ToString();

Graphics graphic = System.Drawing.Graphics.FromImage(bmp);

// our font for the writing
Font font = new Font("Arial", 14, FontStyle.Bold);

// measure the text height;
// you need this to position it at the base
StringFormat stringformat =
   new StringFormat(StringFormat.GenericTypographic);
int textheight = Convert.ToInt32(graphic.MeasureString(imageLabel,
   font, new PointF(0, 0), stringformat).Height) + 6;
int textwidth = Convert.ToInt32(graphic.MeasureString(imageLabel,
   font, new PointF(0, 0), stringformat).Width) + 20;

So, from this you've managed to calculate, based upon the text and specified font parameters, what the height of the label on the image are going to be. Next, you do the actual drawing onto the image.

graphic = System.Drawing.Graphics.FromImage(bmp);

// add our background
graphic.DrawImage(bgimage, 0, 0, width, height);

// add our text background's bar.  We create a rectangle at the
// base of the image
SolidBrush greyfill = new SolidBrush(Color.FromArgb(167, 167, 167));
Rectangle rect = new Rectangle(0, height - textheight, width,
   textheight);

graphic.FillRectangle(greyfill, rect);

// aliasing mode
graphic.TextRenderingHint = TextRenderingHint.SystemDefault;

// our brush which is the writing colour
SolidBrush blackbrush = new SolidBrush(Color.Black);

// create our text
graphic.DrawString(imageLabel, font, blackbrush, new
   Rectangle(width - textwidth, height - textheight, width,
      textheight));

First, the rectangle was drawn on, followed by the gray background. The color then was changed to black and then the actual text written within that area, along with a few other parameters. Feel free to play around with these to obtain the look you need. You can go a step further and draw a watermark onto the image if you like, but that is beyond the scope of this article.

The easy bit is to then send the new image on its merry way down the response stream.

// Set the content type and return the image
context.Response.ContentType = "image/JPEG";

bmp.Save(context.Response.OutputStream, ImageFormat.Jpeg);

// dispose of our objects
font.Dispose();
stringformat.Dispose();
bgimage.Dispose();
graphic.Dispose();
bmp.Dispose();

Now that you're familiar with how the drawing will work, it's time to put it to use.

Turn Image Hotlinking to Your Advantage

The HttpHandler Method

The great thing about ASP.NET is that it allows you to hook into the HTTP processing pipeline. This means that you can instruct IIS to send requests for .jpg file types to some class in your application. To do this,

  1. Open IIS and expand the web site tree until you get to your application.
  2. Right-click your application and select Properties.
  3. Then, click on the "Configuration..." button under the Directory/Home Directory tab.
  4. You will see a list of file types that are handled by various ISAPI dlls. You need to add .jpg to this list.
  5. Click on the "Add" button and you will be presented with a dialog box.
  6. The Executable needs to point to the ASP.NET ISAPI DLL. In default installations, this will be located at c:\windows\microsoft.net\framework\v2.0.50727\aspnet_isapi.dll.
  7. Enter ".jpg" (without the quotes) into the Extension textbox, and uncheck the box that says "Check that file exists".

[iisconfigjpg.jpg]

Any requests that come in now for .jpg files will now be handled by ASP.NET, which in turn will look at your application to see whether you do indeed have a class that can handle this. To tell ASP.NET that you have such a class ready, you must add an entry into your web.config file.

<httpHandlers>
   <add verb="*" path="*.jpg" type="HotlinkingHandler.JpgHandler,
      HotlinkingHandler"/>
</httpHandlers>

The verb="*" means "All requests for JPG are acceptable". The path is "*.jpg", which obviously means any request for a .jpg file. And, the type attribute is telling ASP.NET to look in the assembly named "HotlinkingHandler" for a class named "HotlinkingHandler.JpgHandler" (by namespace). The next step is, of course, to create this class. Right-click on your solution to add a new class library project next to your web application. You could add the code to your web application, but I prefer keeping this code outside the web application so that it can be used elsewhere. Name your class library project "HotlinkingHandler" and add a class to this project, called "JpgHandler". This is the class that will process all the .jpg requests that come through now.

All HttpHandlers must implement the IHttpHandler interface because the request is given to the ProcessRequest method that is a part of this interface. It's in this method that you start applying a bit of logic:

  • The image must be displayed as-is on your own web site.
  • If it's on another web site, it must be labeled.
  • If it is being viewed standalone (the user has gone straight to the image), they may be about to save it to disk or copy it into some other application, so it'd be nice to have it labeled then too.
  • You should also be able to completely prevent hotlinking. In such a case, it's prudent to throw a 404 exception to indicate that the image isn't available for them to use. As discussed earlier, there is no point in serving an alternate image for the purpose of this article.

With this logic in mind, you will need a few new keys in your web.config file. The first will be "HotLinkPrevent", which if true, will completely disable hotlinking, or if false will show them the drawn-over image. You'll also need the "ImageLabel" key to specify what text needs to be written onto the image.

<!-- "HotLinkPrevent" can be either true (don't display image when
   hotlinked) or false (display image with label when hotlinked) -->
<add key="HotLinkPrevent" value="false"/>

<!-- "ImageLabel" specifies the text to write on the image when
   hotlinked -->
<add key="ImageLabel" value="From www.mendhak.com"/>

The ProcessRequest event then will begin by checking where the request is coming from and whether you want to throw a 404 error.

public void ProcessRequest(HttpContext context)
{
   //Get the name of the requested .jpg image
   string FileName =
      context.Server.MapPath(context.Request.FilePath);

   //prevent hotlinking completely?
   bool hotLinkPrevent =
      bool.Parse(ConfigurationSettings.AppSettings
      ["HotLinkPrevent"].ToString());

   //If this is a legitimate request for the image from our own
   //web site.
   if (context.Request.UrlReferrer !=
      null && context.Request.UrlReferrer.Host ==
      context.Request.Url.Host)
   {
      context.Response.ContentType = "image/JPEG";
      context.Response.WriteFile(FileName);
   }
   else
   {

      //If the image is being displayed on another web site, but
      //not standalone and we want to prevent hotlinking
      if (hotLinkPrevent && context.Request.UrlReferrer !=
          null && context.Request.UrlReferrer.Host !=
          context.Request.Url.Host)
      {
         throw new HttpException(404, "Not Found");
      }
      else
      {
         //else, create the image with text on it.
         //... rest of the drawing code
      }
   }
}

If the UrlReferrer (where the request is coming from) is null, the image is being viewed on its own; if the UrlReferrer host isn't null, it should match the Url host itself because that would mean that it was requested from a page that's on your own web site. Everything else is a hotlink attempt. The rest of the code would be the bit that draws the label onto the image as shown earlier.

Your image handler is ready. Add a reference to this project/DLL from your web application, compile, and try browsing to an image. If you view the image on a page, it should display fine, and if you go straight to the URL, it should show up with the label on it.

To use this method would, of course, mean that you have full control over your web server. Unfortunately, some of us are on a shared server; this means that we aren't allowed to specify configuration settings and that's where the ASHX method comes in handy, as shown on the next page.

Turn Image Hotlinking to Your Advantage

The ASHX method

ASP.NET 2.0 was a vast improvement over the previous versions of ASP.NET, as Microsoft added features to it based upon a lot of developer feedback. One of the additions was the .ashx file extension. The .ashx file extension isn't really associated with anything in a web application. An .aspx file may be a web page, an .ascx is a web user control, but an .ashx is essentially a placeholder extension that you can use for anything you want. In default installations of ASP.NET 2.0, the .ashx extension is already mapped to the ASP.NET ISAPI DLL, which means that you do not need to ask your web hosts to add this extension for you as you would for the case of .jpg. You can just start off by adding the httpHandler to your web.config file.

    <httpHandlers>
      <add verb="*" path="*.ashx" type="HotlinkingHandler.ASHXHandler, HotlinkingHandler"/>
    </httpHandlers>

The rest is the same as the method outlined for handling .jpg files, but an image is referenced by appending .ashx to the extension.

   <img src="lifeonmars.jpg.ashx" alt="Life on Mars" />
However, we can take it a step further. You can now keep all your images in a folder that is not in the application directory itself. Most web hosts usually have a home directory in which your application directories sit. This may take the form of /www/yourapplication. The www directory is not accessible by a browser and you can create a folder in there, such as /www/myimages/ to store all your images. Then, you can serve up all your images via the ASHX handler which actually reads the images from this folder. Anyone who decide to head over to your domain's images folder will find nothing, and you can sleep peacefully knowing that you've left someone utterly perplexed.

Add a new key to your web.config file called "ImagesFolder".

    <!-- "ImagesFolder" is used only for .ashx or ShowImage.aspx -->
    <add key="ImagesFolder" value="c:\temp"/>

The rest of the code is the same as the case of the JPGHandler, except that you must construct the FileName path yourself:

   string imagesFolder = ConfigurationSettings.AppSettings["ImagesFolder"].ToString();
            
   string FileName = Path.Combine(imagesFolder, 
   context.Request.RawUrl.ToLower().Replace(context.Request.ApplicationPath.ToLower(), 
             "").Replace(".ashx", 
             "").TrimStart('/').TrimStart('\\'));
            

The calls to .TrimStart() at the end of the statement are required, because the Path.Combine() gets confused if the second argument starts with a leading slash (it's almost like being in a pub!).

Compile your code and place an image tag on your page, but make sure that that you append .ashx to the end of the image name as shown above. Also, ensure that the physical image file is in C:\temp or whatever you have specified for the ImagesFolder value in web.config.

As you'll see, the ASHXHandler class is now handling these image calls by reading them from the ImagesFolder folder and behaving exactly as we want it to. It's possible to use the same method by working with the images in their default folders within your application, but then there's nothing stopping some of the smarter hotlinkers from going straight to the .jpg files rather than .jpg.ashx files; so it makes sense to use the non-browsable folder.

If .ashx is not available to you, there's the .resources extension, which has also been mapped to the ASP.NET ISAPI DLL. Analogously, your images would be called lifeonmars.jpg.resources rather than lifeonmars.jpg.ashx.

If this doesn't work for you at all, then your host may have removed these mappings in their paranoid meanderings. But don't lose hope, there's still another way - the Dynamic ASP.NET page method, presented on the next page.

Turn Image Hotlinking to Your Advantage

The Dynamic ASP.NET Image Method

Again, thanks to ASP.NET's versatility, you can work around the lack of any sort of file extension mappings that your hosts may have presented you with. You can get an ASP.NET page to handle your images for you, wherein you specify the image to load in the querystring parameter for the page.

<img src="ShowImage.aspx?image=lifeonmars.jpg"
     alt="Life on Mars" />

Your ShowImage.aspx page will look at the querystring parameter, get the image from the ImageFolder file specified in the previous step (you still don't want the images to be browsable), and serve it out. Create a page called ShowImage.aspx in your project and remove everything in design view except for the @Page directive. The codebehind for ShowImage.aspx will look almost exactly the same as it did for the ASHXHandler. The only difference is that you get your FileName from the querystring parameters and combine it with what you've specified in web.config for ImagesFolder.

//Get the folder where the images are actually stored.
string imagesFolder =
   ConfigurationSettings.AppSettings["ImagesFolder"].ToString();
bool hotLinkPrevent =
   bool.Parse(ConfigurationSettings.AppSettings
   ["HotLinkPrevent"].ToString());

//Get the requested image
string FileName = Path.Combine(imagesFolder,
   Request.QueryString["image"]);

This is the simplest method to implement. There are no configuration changes anywhere; it's just a matter of calling the ShowImage.aspx page and passing the right parameter. It's a little bit more work than simply calling the image, but it helps you with the main purpose.

That said, there is a 'bonus' for those of you who are on a shared hosting platform on the next page.

Turn Image Hotlinking to Your Advantage

The .htaccess and Dynamic Image Method

The Dynamic ASP.NET Page method shown on the previous page is well and good, the only disadvantage being that you may not like having to always type ShowImage.aspx?image=lifeonmars.jpg. You would just like to keep things simple in design and simply type out the name of the image you want. This is possible if you have an .htaccess file. If your hosting plan is a Windows+Linux plan, that is, if it supports ASP.NET via IIS as well as CGI/PHP via Apache, chances are that you can create an .htaccess file. Please consult your hosting support to find out more. If this is the case, things can get easier for you.

You will still use the ShowImage.aspx page to display your images, but when actually creating your web pages, you only need to name the image rather than mentioning ShowImage.aspx.

<img src="lifeonmars.jpg" alt="Life on Mars" />

You then can get your .htaccess file to send all .jpg file requests to ShowImage.aspx. Add a RedirectMatch to your .htaccess file.

RedirectMatch 301 (.*)\.jpg$ /ShowImage.aspx?image=$1.jpg

If you are familiar with regular expressions, you can see what's happening here: The name of the .jpg file is captured and passed as a matching parameter to your dynamic image page, ShowImage.aspx. This works because a browser displaying a web page that in turn references your .jpg file will be smart enough to understand the 301 Redirect message it receives and will then attempt to get the image from ShowImage.aspx. This is a more elegant solution because the image tags are easier to write when creating a page, while in fact you have a dynamic page handling them and pulling them out of a non-browsable folder.

Turn Image Hotlinking to Your Advantage

Conclusion

You now have four different methods of dealing with hotlinking. No matter what your hosting restrictions, one of them is bound to work for you (unless you don't have ASP.NET hosting at all). Although it's a little more work to get this working for existing web sites, the effort is definitely worth it once you see the results, and how they benefit you in the long run.

Notes about the Attached Sample

The code sample attached is a web project. It is an ASP.NET 2.0 project, but the code itself will work with ASP.NET 1.1 as well.

  1. Unzip the folder to your default web site home directory (such as c:\inetpub\wwwroot).
  2. Go to IIS (start > run > inetmgr).
  3. Right-click on the PreventHotlinking folder under your Default Website.
  4. Choose Properties.
  5. Click "Create" to make it a virtual directory and ensure that the ASP.NET tab has ASP.NET 2.0 selected.
  6. Click on the Configuration... button and add a new extension for .jpg. Uncheck "Verify that file exists". The executable path is C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll (by default).
  7. Open the Solution by double-clicking on the .SLN file.
  8. Press Ctrl+Shift+B to build the solution.
  9. Copy the lifeonmars.jpg file to C:\temp\.
  10. Browse to Default.aspx: http://localhost/PreventHotlinking/Default.aspx.

As a final note, I have kept the various method implementations as 'modular' as possible, so I have not actually moved the code for drawing over the images out into another class. Each handler has all the code within itself so that you can concentrate on one implementation at a time.



About the Author

SM Altaf

Mendhak is a web developer and a Microsoft MVP who works with ASP.NET and PHP among the usual array[] of web technologies. He is also rumored to be a three eyed frog, but the evidence is lacking. He can be contacted via his website, www.mendhak.com.


Downloads

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: December 11, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT Market pressures to move more quickly and develop innovative applications are forcing organizations to rethink how they develop and release applications. The combination of public clouds and physical back-end infrastructures are a means to get applications out faster. However, these hybrid solutions complicate DevOps adoption, with application delivery pipelines that span across complex hybrid cloud and non-cloud environments. Check out this …

  • Relying on outside companies to manage your network and server environments for your business and applications to meet the needs and demands of your users can be stressful. This is especially true as many Managed Hosting organizations fail to meet their service level agreements. Read this Forrester total economic impact report and learn what makes INetU different and how they exceed their customers' managed hosting expectations.

Most Popular Programming Stories

More for Developers

RSS Feeds