The Location() Function URL-Encodes The Hash If The URL Also Contains A Query-String In Lucee 5.3.1.102
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>
—
<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.
Want to use code from this post? Check out the license.
Reader Comments
Hey Ben,
This actually came up a month or two back on the CFML Slack. There's actually a ticket being worked on in relation to this incompatibility: https://luceeserver.atlassian.net/browse/LDEV-2164
Cheers.
@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).
Thank you Ben, for this post. I ran into this problem on a clients website. After a lot of debugging, I found your blog post, as so often, with the answer to my question.
Since the ticket ist open for quite some time, I hope it will soon be fixed.
@Sarah,
Awesome! So glad this could help. That's why I love to write about every little thing that I trip over - you never know who else might trip over the same problem 💪