Skip to main content
Ben Nadel at CFUNITED 2008 (Washington, D.C.) with: Dee Sadler
Ben Nadel at CFUNITED 2008 (Washington, D.C.) with: Dee Sadler

Yahoo! Mail Does Not Render Anchor Tags With Encoded HREF Attributes

By
Published in

When rendering view templates in Lucee CFML (or any server-side language for that matter), rule number-one is don't trust the content. This is a corollary to an older rule, don't trust the user. As such, when I render content, I almost always wrap it in one of the many encoding methods that ColdFusion offers: encodeForHtml() or encodeForHtmlAttribute(). And, for the most part, this works wonderfully. However, I just discovered yesterday that Yahoo! Mail won't render Anchor (<a>) tags with fully-encoded href attributes. Specifically, it will strip-out the href attribute entirely if the link protocol is encoded. To fix this, I had to walk-back some of the encoding in my HTML emails. For anyone else that runs into this problem, I thought it might be helpful to put together a demo in Lucee CFML 5.3.8.201.

ASIDE: Encoding output serves two purposes: it protects the HTML structure by not allowing accidental closing of quoted-values and tags. This makes sure that the layout doesn't break in unintended ways. But, more importantly, this protects against persisted cross-site scripting (XSS) attacks in which a bad-actor purposefully breaks the HTML structure in order to get the browser to execute malicious code. Never trust anyone! Always encode your output!

First, let's look at what happens when I encode all of my output as I normally would when rendering any ColdFusion template. Here's a simplified version of what it might look like to render an HTML email body:

<cfscript>

	// Setup data to be used in email-template rendering.
	project = {
		owner: "Ben Nadel",
		name: "My Awesome Project",
		url: "https://projects.invisionapp.com/path/to/project##/spa/path"
	};

</cfscript>
<cfoutput>

	<h1>
		You've been invited to a project
	</h1>

	<p>
		#encodeForHtml( project.owner )# has invited you to join
		<a href="#encodeForHtmlAttribute( project.url )#">#encodeForHtml( project.name )#</a>,
		an InVision project.
	</p>

</cfoutput>

As you can see, first I setup my view-model - the data to be passed into my HTML email template rendering engine. And then, within my HTML email, I encoded all the output because, as I stated above, I don't trust anyone or any content!

Now, if we render this in the browser and look at the resultant page-source, we get the following:

<h1>
	You've been invited to a project
</h1>

<p>
	Ben Nadel has invited you to join
	<a href="https&#x3a;&#x2f;&#x2f;projects.invisionapp.com&#x2f;path&#x2f;to&#x2f;project&#x23;&#x2f;spa&#x2f;path">My Awesome Project</a>,
	an InVision project.
</p>

Notice that the https:// URL protocol has been encoded as https&#x3a;&#x2f;&#x2f;. It's this encoding that throws Yahoo! Mail into a tizzy. To get around this, I had to encode the href value and then unencode the protocol:

<cfscript>

	// Setup data to be used in email-template rendering.
	project = {
		owner: "Ben Nadel",
		name: "My Awesome Project",
		url: "https://projects.invisionapp.com/path/to/project##/spa/path"
	};

</cfscript>
<cfoutput>

	<h1>
		You've been invited to a project
	</h1>

	<p>
		#encodeForHtml( project.owner )# has invited you to join
		<a href="#encodeForHrefAttribute( project.url )#">#encodeForHtml( project.name )#</a>,
		an InVision project.
	</p>

</cfoutput>
<cfscript>

	/**
	* I encoded the given value for use in an HREF attribute. This is designed to reduce
	* the amount of encoded characters so as to make the resultant value a little more
	* mail-client friendly.
	*/
	public string function encodeForHrefAttribute( required string value ) {

		var encodedValue = encodeForHtmlAttribute( value );
		// The encodeForHtmlAttribute() function will encode just about any non-alpha-
		// numeric character. Which, in most cases is completely fine. But, for Yahoo!
		// Mail, we need to back that encoding off just a bit, unencoding the protocol
		// suffix.
		var unencodedSuffix = "://";
		var encodedSuffix = encodeForHtmlAttribute( unencodedSuffix );

		// Replace the encoded protocol with the unencoded version.
		var fixedValue = encodedValue.reReplaceNoCase(
			"^([a-z0-9]+)#encodedSuffix#",
			"\1#unencodedSuffix#"
		);

		return( fixedValue );

	}

</cfscript>

In this version, instead of calling encodeForHtmlAttribute(), I'm calling a custom user defined function (UDF), encodeForHrefAttribute(). This new method uses the normal encoder under the hood; and then walks-back the protocol encoding. Now, when we run this version in the browser and look at the page-source, we get the following:

<h1>
	You've been invited to a project
</h1>

<p>
	Ben Nadel has invited you to join
	<a href="https://projects.invisionapp.com&#x2f;path&#x2f;to&#x2f;project&#x23;&#x2f;spa&#x2f;path">My Awesome Project</a>,
	an InVision project.
</p>

This time, while most of the href attribute is properly encoded, you can see that the https:// protocol shows up unencoded. And, this is enough to fix the Yahoo! Mail anchor-tag rendering issue.

Don't trust anyone. Don't trust any content. Block all XSS attacks. But, for Yahoo! Mail, do allow href attributes to contain unencoded protocols. Even though encoding them is perfectly valid HTML. When it comes to HTML emails, all bets are off (or so it seems sometimes).

Want to use code from this post? Check out the license.

Reader Comments

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel