Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at Scotch On The Rock (SOTR) 2010 (London) with: Matt Gifford
Ben Nadel at Scotch On The Rock (SOTR) 2010 (London) with: Matt Gifford@coldfumonkeh )

The Location() Function URL-Encodes The Hash If The URL Also Contains A Query-String In Lucee 5.3.1.102

By Ben Nadel on
Tags: ColdFusion

Yesterday, I ran into a rather strange regression from our migration of Adobe ColdFusion to Lucee CFML. It seems that Lucee 5.3.1.102 will URL-encode the Hash (or Fragment) portion of a location() function call; but, if and only if the requested URL also contains a query-string. If the requested URL contains Path and Hash information only, the Hash is sent through as-is.

To see this in action, I used CommandBox to setup a simple test in which I use the location() function to forward the user to one-of-two URLs that contain a hash. One URL also contains a query-string; the other does not:

<cfif structKeyExists( url, "go1" )>
	
	<!--- This version has a single "?" (query-string) before the fragment. --->
	<cflocation
		url="./target.cfm?##/this/is/path?where=here"
		addtoken="false"
	/>

<cfelseif structKeyExists( url, "go2" )>

	<!--- This version only has the fragment, no query-string. --->
	<cflocation
		url="./target.cfm##/this/is/path?where=here"
		addtoken="false"
	/>

</cfif>

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

<p>
	<a href="./index.cfm?go1">Go With Query-String</a>
	&mdash;
	<a href="./index.cfm?go2">Go Without Query-String</a>
</p>

As you can see, both <cflocation> calls point to a URL that contains a hash. However, one of the URLs also contains a single ? query-string before the hash.

Now, if we run this in Adobe ColdFusion 2018, as a control, we get the following behavior:

./target.cfm?##/this/is/path?where=here redirects to the URL:

target.cfm?#/this/is/path?where=here

./target.cfm##/this/is/path?where=here redirects to the URL:

target.cfm#/this/is/path?where=here

As you can see, both <cflocation> calls work as you might expect.

Now, if we run this same code in Lucee 5.3.1.102, we get the following behavior:

./target.cfm?##/this/is/path?where=here redirects to the URL:

target.cfm#%2Fthis%2Fis%2Fpath%3Fwhere%3Dhere

./target.cfm##/this/is/path?where=here redirects to the URL:

target.cfm#/this/is/path?where=here

Notice that the first Lucee redirect - the one contains both the hash and the query-string - points to a URL in which the hash has been URL-encoded:

#%2Fthis%2Fis%2Fpath%3Fwhere%3Dhere

In my context, this hash is what is driving the location of an Angular.js application. As such, having a URL-encoded hash breaks the functionality of the Angular app. To put in a stop-gap solution for this issue, I placed this snippet of code at the very top of the CFML page that serves the Angular application shell:

<script type="text/javascript">
	// Attempt to fix Lucee hash-encoding bug. It seems that Lucee will incorrectly
	// encode the hash if the primary URL has a query-string. As such, if the Angular
	// route starts with an encoded-slash, we know that we have to decode the hash.
	(function() {

		var angularRoute = window.location.hash.slice( 1 );

		if ( angularRoute.indexOf( "%2F" ) === 0 ) {

			window.location.hash = decodeURIComponent( angularRoute );

		}

	})();
</script>

This code inspects the Hash to see if it looks like it has been URL-encoded. And, if so, swaps the hash with the decodeURIComponent() version. By doing this before the Angular.js app has a chance to bootstrap, it allows the Angular.js app to function normally.

It took me a while to figure out what was going on - which conditions were causing the hash / fragment to become URL-encoded. It wasn't happening consistently for all app-ingresses. So, hopefully this is helpful to anyone else that stumbles over this behavior. It feels like a bug to me, so I will see if I can open a Ticket in the Lucee project. I did try to look at the Lucee Java source code; but, I could not find anything that looked suspicious.



Reader Comments

@Tony,

Ah, very nice. I tried searching for an existing ticket and couldn't find anything. I suppose just searching in the wrong place or for the wrong thing. Looking at that ticket, though, I wonder what the spec says about the Fragment. I mean, I can clearly enter non-encoded characters in my Hash right now and the browser doesn't do anything special. So, I am not sure what the right answer is (ie, is this a bug or not).

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
NEW: Some basic markdown formatting is now supported: bold, italic, blockquotes, lists, fenced code-blocks. Read more about markdown syntax »
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.