I was browsing through some tickets in Jira today when I found a couple of bugs related to tables not displaying correctly in MSIE. When I found an example, it basically looked like a <TR> tag had not been closed correctly. When I viewed the source HTML, imagine how surprised I was to find a <cfif> statement gumming up the works.

Disclaimer

I inherited this code.

The Problem

The report I was looking at pulls from a few different tables, including a Notes table. Some notes are entered manually and others are automatically created by the system. When I checked the table, I found quite a few notes that contained the same statement.

The Code

This is the line of code in question:

Let's make a string
view plain print about
1<cfset foo = structNew() />
2<cfset str = "" />
3
4<cfset foo.firstName = "Adrian" />
5<cfset foo.lastName = "Moreno" />
6<cfset foo.phone = "5551234567" />
7<cfset foo.fax = "5551234568" />
8
9<cfset str = str & "#foo.firstName# #foo.lastName# [<cfif foo.phone NEQ "">(#Left(foo.phone,3)#) #Mid(foo.phone,4,3)#-#Mid(foo.phone,7,4)# | </cfif>(#Left(foo.fax,3)#) #Mid(foo.fax,4,3)#-#Mid(foo.fax,7,4)# (f)]">

The actual code gets data from a query, not a struct, but the effect is the same. If you were to render this CFML, you'd get this output:

<cfoutput> Firefox:

view plain print about
1Adrian Moreno [(555) 123-4567 | (555) 123-4568 (f)]

MSIE:

view plain print about
1Adrian Moreno [<cfif foo.phone NEQ ">(555) 123-4567 | (555) 123-4568 (f)]

If you do a View Source, you'll see that the actual rendered value of the CFML variable str is:

Picture Kyle's mom going, "What, what, what?!?"
view plain print about
1Adrian Moreno [<cfif foo.phone NEQ ">(555) 123-4567 | </cfif>(555) 123-4568 (f)]

WTF?

It's this string that's stored in the Notes table. MSIE is getting to </cfif> and then pitches a fit. Firefox doesn't know what a CFIF tag is, so it just skips over it and displays the text alone.

If you'll notice, the opening cfif tag was checking if foo.phone was an empty string:

view plain print about
1<cfif foo.phone NEQ "">

but the rendered string only has the one double quote:

view plain print about
1<cfif foo.phone NEQ ">

So why didn't ColdFusion throw an error here?

Testing

Let's create another string that doesn't contain any CF variables:

view plain print about
1<cfset strb = "Hello there my name is not an empty string "" it's Adrian." />
2
3<cfoutput>#strb#</cfoutput>

And the output:

view plain print about
1Hello there my name is not an empty string " it's Adrian.

Ok, that makes sense. The outer double quotes define the string, so any pair of double quotes next to each other like this are replaced with a single double quote, which is the expected functionality.

So maybe losing the pair of double quotes within the CFIF tag causes ColdFusion to skip over parsing it?

Let's change the original code to use single quotes in the CFIF:

view plain print about
1<cfset str = str & "#foo.firstName# #foo.lastName# [<cfif foo.phone NEQ ''>(#Left(foo.phone,3)#) #Mid(foo.phone,4,3)#-#Mid(foo.phone,7,4)# | </cfif>(#Left(foo.fax,3)#) #Mid(foo.fax,4,3)#-#Mid(foo.fax,7,4)# (f)]">

And the rendered HTML source:

view plain print about
1Adrian Moreno [<cfif foo.phone NEQ ''>(555) 123-4567 | </cfif>(555) 123-4568 (f)]

Allllllllrighty then.

Conclusion

I think it's safe to say that ColdFusion can parse variables within a string definition, but it won't parse tags. In order to get the intended output from this code, it needs to be updated like this:

view plain print about
1<cfset str = str & "#foo.firstName# #foo.lastName# [" />
2<cfif foo.phone NEQ "">
3    <cfset str = str & "(#Left(foo.phone,3)#) #Mid(foo.phone,4,3)#-#Mid(foo.phone,7,4)# | " />
4</cfif>
5<cfset str = str & "(#Left(foo.fax,3)#) #Mid(foo.fax,4,3)#-#Mid(foo.fax,7,4)# (f)]" />

Rendered HTML source:

view plain print about
1Adrian Moreno [(555) 123-4567 | (555) 123-4568 (f)]

In conclusion, I think this shows that you can't just write your code and assume it will work as expected, even when no errors are thrown. Test, test and again test your output to make sure it's correct.

Now if you'll excuse me, I have to go update ~3 years of records in that table.