Fixing Protocols In My ColdFusion Custom Tag DSL For HTML Emails
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><A></html:code>
and <html:code><IMG></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/people/who-rock-my-world.htm"
... and the image tag src
attribute is:
src="https://bennadel-cdn.com/images/header/photos/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