Faux Email Links Using ASP .NET Image Handler

I might not score points for originality on this, because email link obfuscation has been worked out in various bits and pieces using a great variety of approaches. Here is an comprehensive solution for ASP .NET using a generic handler to create an email link image in code. People may argue that this will not thwart spam bots that use OCR to scan images for links. They are right, but while this might not be a perfect solution, it should at least result in a reduction of spam. From this post, at least the reader will have an example of how to generate an image in code using ASP .NET.

Here are some examples on Code Project that gave me some of the ideas for this post:
Paul Riley at NoSpamEmailHyperlink: 1. Design
Ralph-Arvesen at Avoiding spam-bots

We all hate spam and having an email address showing up as plain link is a good way to get it. We all can have name <at> foo <dot> com and other types of obfuscation like that in our public web pages for the email address to be read, humanly interpreted, and manually typed in for an email. By using a <a /> style tag with an embedded image and an obfuscated href value, we can get a realistic looking link e.g. something that shows up like the real deal: <a href="mailto:myname@tempuri.tld">myname@tempuri.tld</a>. Instead of text for the link, we use an inline image representation of the link text. The image is generated on the fly using an ASP .NET generic handler and the href attribute for the link is entered as an encoded value. We do need some JavaScript to decode the actual link target. We also need some JavaScript, some snippet of code, to actually do the encoding of the value for the href attribute.

NOTE: For those of you who are concerned about spam bots using OCR, you don’t have to enter a valid email address for the text. You can of course further obfuscate the text in the image.

Here is an example screen cap of what such an image link would look like rendered on a page:

Such a link would be entered in HTML like:

[source language=”xml” collapse=”0″]
<a href="javascript:spockBlam.Email.mailSend(‘cd0d8dbd3eed9a2eedcd4deddd4dfa5dededd93e4dcdfd4e1e8dd92dedcd’)" class="stealthLink">
<img alt="" src="<%: ResolveUrl("~/paintEmail.ashx?addr=2eedcd4deddd4dfa5dededd93e4dcdfd4e1e8dd92dedcd&clr=F0F0F0&bkg=001040&fnt=Trebuchet%20MS&size=10") %>" />
</a>
[/source]

The addr value in the query string is the encoded text for the email link to show to the user. The href is decoded in JavaScript, but the email link text to be displayed has to be decoded by the image handler when rendering the text in the image. That does lead to a redundancy in code between the JavaScript and C# in the image handler;  it’s not a perfect world.

The code below decodes the llink in the image handler:

[source language=”csharp” collapse=”1″]
/// <summary>
/// Decodes the text for the email address, the same manner as the JavaScript does client side.
/// </summary>
private string decodeEmailAddress(string sEncoded)
{
int idxMax = sEncoded.Length;
Byte b;
string sTemp;
StringBuilder sResult = new StringBuilder(128);
for (int i = 0; i < idxMax; i += 2)
{
sTemp = sEncoded.Substring(i + 1, 1) + sEncoded.Substring(i, 1);
b = byte.Parse(sTemp, System.Globalization.NumberStyles.HexNumber, null);
b -= 0x6f;
sResult.Append(Convert.ToChar(b));
}
return sResult.ToString();
}
[/source]

The IHttpHandler.ProcessRequest is where all the fun happens when drawing the link text. This handler supports jpeg and png links.

[source language=”csharp” collapse=”1″]
/// <summary>
/// Renders a text string as an image.
/// </summary>
public void ProcessRequest(HttpContext context)
{

/*
* Get query string values
* bkg : background color in hex
* clr : foreground color in hex
* fnt : name of the font, optional, defaults to Arial
* addr : the encoded email address
* img : the image format, either ‘jpeg’ or ‘png’, default is ‘png’
* size : the font point size, default 10pt
*/
// get background color
Color bkgHex = this.getColorFromHexString(context.Request.QueryString["bkg"]);

// get foreground color
Color clrHex = this.getColorFromHexString(context.Request.QueryString["clr"]);

// get the name of the font, default to Arial if not supplied
string fontName = context.Request.QueryString["fnt"];
if (string.IsNullOrEmpty(fontName))
fontName = "Arial";

// encoded email address
string encAddr = context.Request.QueryString["addr"];

// get the optional font size, defaults to 10pt;
string fontSize = context.Request.QueryString["size"];
float fFontSize = 10.0F;
if (!string.IsNullOrEmpty(fontSize))
fFontSize = float.Parse(fontSize);

// get the image format, jpeg or png, defaults to jpeg
string img = context.Request.QueryString["img"];
ImageFormat fmt = ImageFormat.Jpeg;
if (!string.IsNullOrEmpty(img))
{
if ("png".Equals(img))
fmt = ImageFormat.Png;
}

// decode the email address
string sMail = this.decodeEmailAddress(encAddr);

// rectangle to hold dimensions of image
Rectangle rc;

using (Font fnt = new Font(fontName, fFontSize, FontStyle.Underline))
{
// we do this to measure the email address string. we want the image just big enough to fit
// the string.
using (Bitmap bmp = new Bitmap(1, 1, PixelFormat.Format32bppArgb))
{
using (Graphics gr = Graphics.FromImage(bmp))
{

gr.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAliasGridFit;
gr.PageUnit = GraphicsUnit.Pixel;
SizeF size = gr.MeasureString(sMail, fnt);
rc = new Rectangle(0, 0, (int)Math.Ceiling((double)size.Width), (int)Math.Ceiling((double)size.Height));
}
}

// draw our image
using (Bitmap bmp = new Bitmap(rc.Width, rc.Height, PixelFormat.Format32bppArgb))
{
using (Graphics gr = Graphics.FromImage(bmp))
{
gr.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
gr.SmoothingMode = SmoothingMode.AntiAlias;

// set the background color
gr.Clear(bkgHex);

// draw the email address
using (SolidBrush br = new SolidBrush(clrHex))
{
StringFormat sfmt = new StringFormat();

sfmt.Trimming = StringTrimming.None;
gr.DrawString(sMail, fnt, br, rc, sfmt);
}

// write the image into the response.
context.Response.ClearContent();
context.Response.ContentType = (fmt == ImageFormat.Png) ? "image/png" : "image/jpeg";
context.Response.Cache.SetCacheability(HttpCacheability.NoCache);
bmp.SetResolution(96.0f, 96.0f);

if (fmt == ImageFormat.Jpeg)
{

// bmp.Save(context.Response.OutputStream, fmt);
// If we save it like the commented out line above, it will look OK but not great.

// Save the jpeg like this, here we can increase the image quality
ImageCodecInfo ici = getCodecInfo("image/jpeg");
using (EncoderParameters eps = new EncoderParameters(1))
{
using (EncoderParameter ep = new EncoderParameter(System.Drawing.Imaging.Encoder.Quality, 85L))
{
eps.Param[0] = ep;
bmp.Save(context.Response.OutputStream, ici, eps);
}
}
}
else
{
bmp.Save(context.Response.OutputStream, fmt);
}

context.Response.End();
}
}
}
}
[/source]

When using the image handler to render an email link image in png format, a slight discrepancy was noticed in the background color for IE8. If you look hard enough, the blue background for the link does not exactly match the background of the parent div. Barely noticeable, but still a discrepancy since the color for the link background was set to be exactly the same as the parent div This discrepancy does not show you in Firefox. Curiously enough, if you save a PNG image in GIMP to duplicate that of the rendered link, using the same colors, the discrepancy no longer present for the IE8 rendering of the PNG image. This must be a flaw in the managed GDI+ rendering of the link. If anyone knows the answer to this, by all means, please comment.

Screen Cap of IE8 Rendered PNG Link – Note the slight color discrepancy in the backgrounds.

Yes, JavaScript has to be enabled for this to work, but if its not, you’ll at least see an image masquerading as a real mail hyperlink, one that shows the user the email text but one that is invisible to the spam bots. All of the JavaScript need for these email links is show below.

JavaScript For Email Decoding/Encoding

[source language=”javascript” collapse=”1″]
// reverse a string http://www.bytemycode.com/snippets/snippet/400/
String.prototype.reverse = function () {
splitext = this.split("");
revertext = splitext.reverse();
reversed = revertext.join("");
return reversed;
}

var spockBlam = {};
spockBlam.Email = {};

// This function obfuscates and email address, this can be called from the console to
// generate an encoded email. No need to call this from website displaying the email
// address to the user.
spockBlam.Email.encMail = function (sAddr) {

var s = "";
var c;
var i;
var retVal = "";

for (i = 0; i < sAddr.length; ++i) {

// convert character to number
c = sAddr.charAt(i).charCodeAt();

// add magic number
c += 0x6f;

// convert to hex and pad
s = c.toString(16);
if (s.length == 1) {
s = "0" + s;
}

// add to return value, but reverse the nibbles
retVal += s.reverse();
}

return retVal;
};

// unobfuscate an email address, this is what we use in our web site for the email link.
spockBlam.Email.decMail = function (sAddr) {

var s;
var n;
var retVal = "";

// parse hex encoded string
for (i = 0; i < sAddr.length; ) {

// the nibbles have been reversed in encMail
s = sAddr.charAt(i + 1) + sAddr.charAt(i);
n = parseInt(s, 16);

// subtract magic number added in encMail
n -= 0x6f;

// add to result
retVal += String.fromCharCode(n);
i += 2;
}

// return result
return retVal;
}

// this actually opens up the email client to send the email
spockBlam.Email.mailSend = function (sAddr) {
location.href = this.decMail(sAddr);
}

[/source]

The spockBlam.Email.encMail() function is used by the web developer to do the encoding for the href and link text before the page is published. For the operation of the web site, it does not need to be included at all in the script, but is provided for completeness. Using Windows Script Host, we can easily create a .WSF file that references this script and calls this function from the a job and echos the hex encoded string to the console. Such a script has been provided with the example solution. Obviously, the encoding function can be duplicated in a different scripting environment, such as PowerShell or php.

Sample VS 2010 solution for download:

spockBlam.Email soluction

This entry was posted in ASP .NET, C#, HTML, JavaScript and tagged , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *