Skip to main content
Ben Nadel at cf.Objective() 2010 (Minneapolis, MN) with: Kris Jones and Anne Porosoff
Ben Nadel at cf.Objective() 2010 (Minneapolis, MN) with: Kris Jones Anne Porosoff

Fixing Protocols In My ColdFusion Custom Tag DSL For HTML Emails

By
Published in

Last week, I looked at the fact that [Yahoo! Mail won't render href attributes with encoded protocols]. In that post, I created a ColdFusion user defined function (UDF) to un-encoded the https:// portion of the href attribute. After letting that approach bake in production at InVison for a while without an issues, I've decided to move the fix into my ColdFusion custom tag DSL for HTML emails. With this new implementation, I've pushed the logic down into the <html:a> and <html:src> custom tag implementations so that the developer can continue to use these tags without any changes to their code.

View this code in my ColdFusion Custom Tag Emails project on GitHub.

To see this in action, I've created a new example that include both an anchor tag and an image tag that each use the encodeForHtmlAttribute() when rendering their href and src tags, respectively:

<!--- Import custom tag libraries. --->
<cfimport prefix="core" taglib="./core/" />
<cfimport prefix="html" taglib="./core/html/" />

<!--- // ------------------------------------------------------------------------- // --->
<!--- // ------------------------------------------------------------------------- // --->

<core:Email
	subject="Testing encoded HREF and SRC attributes."
	teaser="Because Yahoo! Mail">
	<core:Body>

		<html:h1>
			Testing encoded HREF and SRC attributes
		</html:h1>

		<html:p>
			It seems that some email clients
			<html:strong>*cough*</html:strong> Yahoo! Mail <html:strong>*cough*</html:strong>
			will strip-out <html:code>href</html:code> attributes if they contain an
			encoded protocol. As such, I've updated the <html:code>&lt;A&gt;</html:code>
			and <html:code>&lt;IMG&gt;</html:code> tags to use some additional logic in
			their rendering to <html:em>un-escape</html:em> both <html:code>href</html:code>
			and <html:code>src</html:code> attributes.
		</html:p>

		<html:h2>
			Some test tags:
		</html:h2>

		<html:p>
			<html:a href="#encodeForHtmlAttribute( 'https://www.bennadel.com/people/who-rock-my-world.htm' )#">A test anchor</html:a>
		</html:p>

		<html:p>
			<html:img
				src="#encodeForHtmlAttribute( 'https://bennadel-cdn.com/images/header/photos/jessica_eisner.jpg' )#"
				width="500"
				height="254"
			/>
		</html:p>

	</core:Body>
</core:Email>

As you can see, there's nothing special in this mark-up logic. The developer is just passing-in encoded attribute values the way they would normally do in order to prevent malicious values from breaking the HTML tree structure. And, if we render this HTML email body in the browser and look at the rendered page source, we'll see that the anchor tag href attribute is:

href="https://www .bennadel.com&#x2f;people&#x2f;who-rock-my-world.htm"

... and the image tag src attribute is:

src="https://bennadel-cdn.com&#x2f;images&#x2f;header&#x2f;photos&#x2f;jessica_eisner.jpg"

Notice that in both cases, the https:// value is fully un-encoded while the rest of the "special characters" within the attribute values remain fully encoded.

To see how this is implemented, here's the current source code for the <html:a> anchor tag - you'll see that the href attribute value is being passed-through a new function, fixProtocol():

<!---
	CAUTION: For INLINE ELEMENTS, we have to be fanatical in the way that we manage the
	output of the ColdFusion custom tag since we don't want to inadvertently add any
	leading or trailing whitespace around the tag content. As such, we're going to wrap
	the output in a custom tag that will trim the output.
---><cfmodule template="../TrimOutput.cfm">

<!--- Define custom tag attributes. --->
<cfparam name="attributes.class" type="string" default="" />
<cfparam name="attributes.href" type="string" />
<cfparam name="attributes.decoration" type="boolean" default="true" />
<cfparam name="attributes.style" type="string" default="" />
<cfparam name="attributes.target" type="string" default="_blank" />

<!--- // ------------------------------------------------------------------------- // --->
<!--- // ------------------------------------------------------------------------- // --->

<cfswitch expression="#thistag.executionMode#">
	<cfcase value="end">
		<cfoutput>

			<cfmodule
				template="../Styles.cfm"
				variable="inlineStyle"
				entityName="a"
				entityClass="#attributes.class#"
				entityStyle="#attributes.style#">
				<cfif ! attributes.decoration>
					text-decoration: none ;
				</cfif>
			</cfmodule>

			<a
				href="#fixProtocol( attributes.href )#"
				target="#attributes.target#"
				class="#trim( 'html-entity-a #attributes.class#' )#"
				style="#inlineStyle#"
				>#thistag.generatedContent#</a>

			<!--- Reset the generated content since we're overriding the output. --->
			<cfset thistag.generatedContent = "" />

		</cfoutput>
	</cfcase>
</cfswitch>

<!--- End of fanatical whitespace management. --->
</cfmodule><cfexit method="exitTemplate" />

<!--- // ------------------------------------------------------------------------- // --->
<!--- // ------------------------------------------------------------------------- // --->

<cfscript>

	/**
	* I unencode the protocol contained within the given attribute. Some email clients,
	* like Yahoo! Mail, will strip-out the HREF attribute if it contains an encoded
	* protocol.
	* 
	* @encodedAttribute I am the HREF value being "fixed".
	*/
	public string function fixProtocol( required string encodedAttribute ) {

		var unencodedSuffix = "://";
		var encodedSuffix = encodeForHtmlAttribute( unencodedSuffix );

		return(
			reReplaceNoCase(
				arguments.encodedAttribute,
				"^([a-z0-9]+)#encodedSuffix#",
				"\1#unencodedSuffix#",
				"one"
			)
		);

	}

</cfscript>

And now, my ColdFusion custom tag DSL (Domain Specific Language) for HTML emails should render anchor tags properly in Yahoo! Mail. And, do so in a way that is completely transparent to the developer.

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