Re: Re: Re: Couple of things..
Originally posted by CousinLarry
<DrEvil> Riiiiiigggghhhhhttttt </DrEvil>
Obviously you have no clue.
Next.
And neither do you. But just to be nice, I'll provide one.
Since I see your site is hosted on a server running IIS5, I suggest looking at the following ASP Module:
http://www.irritatedvowel.com/Programming/DeepLinkingHttpModule.aspx
ASP.Net HTTPModule to Prevent Deep Linking
A common problem on IIS servers is how to prevent "deep linking". In my specific case, I wanted to prevent people from using my images as the background for their web site, or posting them out of context in email messages, bulletin boards etc. Of course, deep-linking is only one way to handle that. Anyone on the internet could very easily copy and paste the image into their email message or their web site directory. Nothing (not even those obnoxious right-click prevention scripts, that you can overcome in two seconds) can prevent that. However, by preventing deep-linking, I at least save my bandwidth.
I evaluated writing both HTTPModules and HTTPHandlers for this little project, and found that HTTPModules are appropriate for this task. For the purposes of this brief article, I will assume you have read about the ASP.Net processing pipeline via other sources. I will not repeat all of that information here. All the code here is in C#, but it translates quite well to Visual Basic .Net
How it Works
When a request is made to my site, the HTTPModule I wrote is part of the pipeline for processing the request. My HTTPModule checks the configuration file to see if the request matches criteria that is specified in the file, and if so, will either allow the request to proceed normally, or will redirect the request to this small 5k image :
Considering many of the larger images on my wallpaper site aer 500k+, this potentially saves me a ton of bandwidth.
Pretty simple stuff, especially if you're used to servers like Apache which allow you to handle this via configuration files with no code. Before ASP.Net, you were likely writing an ISAPI filter to handle this redirect.
Code
The code may not be beautiful, but I'm not planning on entering it in any beauty contests ;-)
Class Definition
public class DeepLinkingModule : IHttpModule
Interface Member Functions
You are required to implement both methods of the IHTTPModule interface. The Init method is where you should set up your event handlers. The Dispose method is where you should release any unmanaged resources.
public void Init(HttpApplication app)
{
app.BeginRequest += new EventHandler(BeginRequest);
}
public void Dispose()
{
// do nothing
}
BeginRequest Event Handler
The BeginRequest Event Handler simply handles the event and calls the AuthorizedReferrer function.
void BeginRequest(Object source, EventArgs e)
{
HttpApplication app = (HttpApplication) source;
Uri requestURL = app.Request.Url ;
Uri referrerURL = app.Request.UrlReferrer ;
string redirectURL = string.Empty;
if (!AuthorizedReferrer(requestURL, referrerURL, app, out redirectURL))
{
app.Response.Redirect(redirectURL) ;
}
}
AuthorizedReferrer Work Function
This is where the meat of the processing occurs. Watch out for line wrap.
bool AuthorizedReferrer(Uri requestURL, Uri referrerURL, HttpApplication app, out string redirectURL)
{
bool authorized = true ;
System.Xml.XmlDocument doc = new XmlDocument() ;
redirectURL = String.Empty ;
try
{
string requestFolder = GetFolderPathFromRequestURL(requestURL, app) ;
string fileName = GetFileNameFromRequestURL(requestURL, app) ;
string referrerName ;
if (referrerURL == null)
referrerName = "" ;
else
referrerName = referrerURL.ToString() ;
doc.Load(requestFolder + "YourSpecialConfigFile.xml") ;
try
{
System.Xml.XmlNode root = doc.DocumentElement ;
foreach (System.Xml.XmlNode fileNode in root.ChildNodes)
{
string fileMask = fileNode.Attributes["mask"].InnerText.ToLower().Trim() ;
string redirectOnFailure = fileNode.Attributes["failure"].InnerText.ToLower().Trim() ;
if (FileNameMatchesMask(fileName, fileMask))
{
authorized = false ; // assume failure
redirectURL = redirectOnFailure ;
foreach (System.Xml.XmlNode referrerNode in fileNode.ChildNodes)
{
string referrerMask = referrerNode.Attributes["referrer"].InnerText ;
if (referrerNode.Name.ToLower() == "allow")
{
if (ReferrerMatchesMask(referrerName, referrerMask))
{ authorized = true ;
}
}
else if (referrerNode.Name.ToLower() == "deny")
{
if (FileNameMatchesMask(fileName, fileMask) && ReferrerMatchesMask(referrerName, referrerMask))
{
authorized = false ;
}
}
}
}
}
}
catch (Exception)
{
authorized = true ;
}
}
catch (FileNotFoundException)
{
// swallow it. There is no deep link config file in the folder.
authorized = true ;
}
return authorized ;
}
Support Functions
Yes, I cheated and used the Visual Basic "Like" operator from C#. Why didn't I use regex? Simply because I wanted to use "normal" wildcards as are commonly used when dealing with filenames. Note the use of the Path object to save on LOC in the GetFolderPathFromRequestURL and GetFileNameFromRequestURL functions.
bool FileNameMatchesMask(string fileName, string mask)
{
return Microsoft.VisualBasic.CompilerServices.StringType.StrLike(fileName.ToLower(), mask.ToLower(), Microsoft.VisualBasic.CompareMethod.Text) ;
}
bool ReferrerMatchesMask(string referrerName, string mask)
{
return Microsoft.VisualBasic.CompilerServices.StringType.StrLike(referrerName.ToLower(), mask.ToLower(), Microsoft.VisualBasic.CompareMethod.Text) ;
}
string GetFolderPathFromRequestURL(Uri requestURL, HttpApplication app)
{
string fullPath = requestURL.AbsolutePath.Replace(@"/", @"\") ;
fullPath = app.Server.MapPath(fullPath) ;
return Path.GetDirectoryName(fullPath) + @"\" ;
}
string GetFileNameFromRequestURL(Uri requestURL, HttpApplication app)
{
string fullPath = requestURL.AbsolutePath.Replace(@"/", @"\") ;
fullPath = app.Server.MapPath(fullPath) ;
return Path.GetFileName(fullPath) ;
}
Configuration Files
This is what the configuration file looks like. I put one of these in each directory where I wish to prevent deep-linking. Since my wife posts bird photos to the Birds and Blooms bulletin board, I have specifically set that up as an allowed referrer. Notice I also had to set my own site as an allowed referrer. I originally named the files with a .XML extension, but later changed it to .config..
I also removed the "deny referrer=" bit later as well. As it turns out, many folks have either deliberately or accidentally broken http referrer logic in their browsers. Some firewall software also blocks http referrer as well.
<?xml version="1.0" encoding="utf-8" ?>
<authorization>
<file mask="*.jpg" failure="/Images/my_invalid_deep_link_jpg.jpg">
<allow referrer="http://www.irritatedvowel.*" />
<allow referrer="http://www.irritablevowel.*" />
<allow referrer="http://bbs.reimanpub.com/*" />
<deny referrer="" />
</file>
</authorization>
Web.Config
You need to register your HTTPModule with ASP.Net. You do this via either machine.config, or as I did in this case, web.config.
<system.web>
<httpModules>
<add name="DeepLinkingModule"
type="IrritatedVowel.DeepLinkingHandler.DeepLinkingModule, IrritatedVowel.DeepLinkingHandler"/>
</httpModules>
...
IIS Configuration
ASP.Net normally does not process JPG files or other static files. If ASP.Net does not process the request for the file, your HTTPModule will not be called. Therefore, you have to tell IIS to send all requests for, in this case JPG files, through ASP.Net. Please note, I have not investigated the performance implications of doing this on a busy site, you will need to evaluated that yourself.
In IIS, click on the web site you wish to configure, and select properties. Select the "Home Directory" tab, and then click the configuration button. You will see the dialog below :
Note that in this screenshot, JPG files have already been set to be processed by ASP.Net
Select the .aspx file and click "edit" to view the mapping. Write down the location of the ASP.Net Isapi extension. Close the edit dialog.
Click the "Add" button to add a new extension
In my version, on Windows XP remotely configuring a Windows 2000 box, I could not copy and paste the executable name, that is why I mentioned writing it down. Enter the full path to the aspnet_isapi.dll in the executable text box.
Type the file extension in the extension box. Do not use wildcards to the left of the period. If you do, OK will be grayed-out.
GET and HEAD are sufficient for the JPG files I am mapping. If you are mapping a different type of file, you will need to evaluate what types of requests you want to pass through to ASP.Net
Click OK to accept.
Restart IIS (may not always be necessary, but a good idea anyway)
Optimizations
I later added an optimization which uses the Cache API. You can see the article on the cache API here .
One additional optimization you can make is to hard-code acceptance of your home domain into the check routines, and therefore bypass reading the XML file for requests that come from your own site. I'll likely do that myself soon.
Conclusion
Once you set up IIS to route requests through ASP.Net, you will be off and running. There are many other uses for HTTPModules including translating files from one type to another, security, and any other "filtering"-type tasks. They're easy to write, much easier than ISAPI for normal folks, and you have access to all the same .Net tools you use to build any other .Net application. There's no reason not to write one
If you found this mini article to be helpful, be sure to drop me a note in my guestbook.
Now, since you have FULL CONTROL over preventing outsiders from linking to your files, kindly stop b!tching at those who enjoy the freedom of the internet. Fix your site, or deal with the consequences.
- Matt
P.S. - Had you been hosted on a server running Apache, I would have given you the .htaccess file lecture instead