Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
Ben Nadel at Scotch On The Rocks (SOTR) 2011 (Edinburgh) with: James Allen
Ben Nadel at Scotch On The Rocks (SOTR) 2011 (Edinburgh) with: James Allen@CFJamesAllen )

Use preserveCase Consistently When Setting And Expiring Cookies In ColdFusion

By Ben Nadel on

Yesterday, I demonstrated that using structDelete() with the cookie scope doesn't actually delete the key from the cookie scope in ColdFusion. This was just one of a number of little hurdles that I recently came across when dealing with cookies. Another feature that I tripped over was the use of preserveCase, which tells ColdFusion to use the explicitly provided key-casing for the cookie name. This feature works. But, I didn't realize (at first) that I needed to use it consistently for both setting and expiring a cookie.


 
 
 

 
 
 
 
 

To see what I mean, let's look at a rather trite example. In the following code, we're going to toggle the existence of a cookie by setting it and then expiring in on alternating requests to the server. What you should notice is that I'm using preserveCase when creating the cookie, but omitting it when I am deleting the cookie:

  • <cfscript>
  •  
  • // We are toggling a cookie on and off on each request. If this works, you
  • // should see it submitted in the REQUEST HEADERS of one request but then
  • // not in the request headers of the next request.
  •  
  • if ( structKeyExists( cookie, "theTick" ) ) {
  •  
  • writeOutput( "Cookie exists, expiring it." );
  •  
  • // NOTE: Behind the scenes, expiring a cookie sends Set-Cookie response headers.
  • cfcookie(
  • name = "theTick",
  • expires = "now"
  • );
  •  
  • } else {
  •  
  • writeOutput( "Cookie missing, creating it." );
  •  
  • cfcookie(
  • name = "theTick",
  • value = "Sanity, you're a madman!",
  • expires = "never",
  • preserveCase = true
  • );
  •  
  • }
  •  
  • </cfscript>

If I clear my cookies and then run this page a few times, here's the (concatenated) page output that I get:

Cookie missing, creating it.
Cookie exists, expiring it.
Cookie exists, expiring it.
Cookie exists, expiring it.

As you can see, the cookie is not toggling on and off because we are not actually deleting it. At first, it may not be obvious why this is happening. What you have to understand is that when you delete or expire a cookie, what you're actually doing is sending a "Set-Cookie" header in the response. And, this is why you have to use preserveCase consistently. In this demo, if you examine the "Set-Cookie" header for the first request, we get:

Set-Cookie:theTick=Sanity%2C%20you%27re%20a%20madman%21; Expires=Sun, 03-Dec-2045 11:20:31 GMT; Path=/

Notice the key-casing of the cookie name is "theTicket." Now, let's look at the "Set-Cookie" header of the subsequent "delete" requests:

Set-Cookie:THETICK=; Max-Age=0; Path=/

Here, you an see that the key-casing is "THETICK". Cookie names are case-sensitive (at least in the browser). Which means that "theTick" and "THETICK" are actually two different cookies. Which is why we are never able to toggle the cookie using the above code.

To fix this, we need to add the preserveCase flag to the expires action as well:

  • <cfscript>
  •  
  • // We are toggling a cookie on and off on each request. If this works, you
  • // should see it submitted in the REQUEST HEADERS of one request but then
  • // not in the request headers of the next request.
  •  
  • if ( structKeyExists( cookie, "theTick" ) ) {
  •  
  • writeOutput( "Cookie exists, expiring it." );
  •  
  • // NOTE: Behind the scenes, expiring a cookie sends Set-Cookie response headers.
  • cfcookie(
  • name = "theTick",
  • expires = "now",
  • preserveCase = true // NOTE: Using even for expires action.
  • );
  •  
  • } else {
  •  
  • writeOutput( "Cookie missing, creating it." );
  •  
  • cfcookie(
  • name = "theTick",
  • value = "Sanity, you're a madman!",
  • expires = "never",
  • preserveCase = true
  • );
  •  
  • }
  •  
  • </cfscript>

As you can see, I'm consistently passing preserveCase to all CFCookie actions. And, this time, when we clear cookies and refresh the page, we get the following (concatenated) page output:

Cookie missing, creating it.
Cookie exists, expiring it.
Cookie missing, creating it.
Cookie exists, expiring it.

As you can see, the cookie is being set and then successfully deleted on alternating requests. And, if we look at the "Set-Cookie" header for the expires action, we can see that it matches the original set action:

Set-Cookie:theTick=; Max-Age=0; Path=/

Now that we know that we have to use preserveCase to delete a cookie (that was set using preserveCase), I wanted to see how this affects the use of structDelete() as a means to expire a cookie. As I discussed in my previous post, calling structDelete() on the Cookie scope sends a "Set-Cookie" header behind the scenes. So, let's see how this works when we are use preserveCase with the setting of the cookie:

  • <cfscript>
  •  
  • // We are toggling a cookie on and off on each request. If this works, you
  • // should see it submitted in the REQUEST HEADERS of one request but then
  • // not in the request headers of the next request.
  •  
  • if ( structKeyExists( cookie, "theTick" ) ) {
  •  
  • writeOutput( "Cookie exists, expiring it." );
  •  
  • // CAUTION: When deleting the cookie via structDelete(), we do not have
  • // the opportunity to enable preserveCase. As such, it may not actually
  • // be able to delete the cookie, depending on how the cookie was created.
  • // --
  • // NOTE: Behind the scenes, structDelete() on the cookie scope will send
  • // Set-Cookie response headers.
  • structDelete( cookie, "theTick" );
  •  
  • } else {
  •  
  • writeOutput( "Cookie missing, creating it." );
  •  
  • cfcookie(
  • name = "theTick",
  • value = "Sanity, you're a madman!",
  • expires = "never",
  • preserveCase = true
  • );
  •  
  • }
  •  
  • </cfscript>

This time, when I flush the cookies and refresh the page a few times, I get the following (concatenated) output:

Cookie missing, creating it.
Cookie exists, expiring it.
Cookie exists, expiring it.
Cookie exists, expiring it.

As you can see, we a failing to toggle the cookie because the delete / expires action is failing. And if we look at the "Set-Cookie" header of the expires request we can see that key-casing has not been preserved:

Set-Cookie:THETICK=""; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/

As an aside, it's interesting to see that, in ColdFusion 11, when using structDelete() the "Set-Cookie" header is using "Expires" where as the CFCookie-based expiration uses "Max-Age". This contrasts with ColdFusion 10, which uses "Expires" for all expiration actions, whether you're using CFCookie or structDelete(). I wonder why the change in ColdFusion 11?

So, we see that we may not be able to use structDelete() in conjunction with a cookie set using preserveCase (depending on the casing used in the original key). But, ColdFusion 11 added a new per-application setting - preserveCaseForStructKey - to help preserve the casing of struct keys even if they weren't set using bracket-notation. As I final experiment, I wanted to see how this per-application setting applies to the cookie life-cycle.

First, let's set up our Application.cfc to maintain key-casing:

  • component
  • output = false
  • hint = "I define the application settings and event handlers."
  • {
  •  
  • // I am the application settings.
  • this.name = hash( getCurrentTemplatePath() );
  • this.applicationTimeout = createTimeSpan( 0, 0, 10, 0 );
  •  
  • // Enable key-case preservation to see how this affects Cookie names. While
  • // we are not serializing anything in this demo, this setting affects more
  • // than just serialization.
  • this.serialization = {
  • preserveCaseForStructKey: true
  • };
  •  
  • }

Now, let's try to toggle the cookies using nothing but Cookie struct access:

  • <cfscript>
  •  
  • // We are toggling a cookie on and off on each request. If this works, you
  • // should see it submitted in the REQUEST HEADERS of one request but then
  • // not in the request headers of the next request.
  •  
  • if ( structKeyExists( cookie, "theTick" ) ) {
  •  
  • writeOutput( "[ App.cfc Version ] Cookie exists, expiring it." );
  •  
  • // NOTE: Behind the scenes, structDelete() on the cookie scope will send
  • // Set-Cookie response headers.
  • structDelete( cookie, "theTick" );
  •  
  • } else {
  •  
  • writeOutput( "[ App.cfc Version ] Cookie missing, creating it." );
  •  
  • // By setting a key on the cookie scope, we're creating a "session cookie".
  • cookie.theTick = "Sanity, you're a madman!";
  •  
  • }
  •  
  • </cfscript>

By writing directly to the Cookie struct, we lose the ability to set any of the cookie-based attributes. Ultimately, this ends up creating a "session cookie" that is expunged when the browser is closed. When we go to run this application, here's the "Set-Cookie" header that we get:

Set-Cookie:theTick=Sanity%2C%20you%27re%20a%20madman%21; Path=/

As you can see, the key-casing of the cookie name was preserved as "theTick." However, when we refresh the page and issue the expiration, we get the following "Set-Cookie" header:

Set-Cookie:THETICK=""; Expires=Thu, 01-Jan-1970 00:00:10 GMT; Path=/

Unfortunately, you can see that, even when preserveCaseForStructKey is enabled, deleting a cookie using structDelete() fails to use the appropriate cookie name. Which fails to delete the cookie. Which means we get the following (concatenated) output when we refresh the page a number of times:

[ App.cfc Version ] Cookie missing, creating it.
[ App.cfc Version ] Cookie exists, expiring it.
[ App.cfc Version ] Cookie exists, expiring it.
[ App.cfc Version ] Cookie exists, expiring it.

In ColdFusion, there's a number of ways to both create and expire / delete cookies. But, based on these findings, it seems you either have to use preserveCase never or always. Trying to mix and match preserveCase (including the preserveCaseForStructKey per-application setting) is going to get you in trouble, when it comes to Cookies in ColdFusion. Definitely a minor note but, something to be aware of.




Reader Comments

I discovered a recent issue regarding unencoded unicode cookie values. A JWPlayer language drop-down for sub-titles was causing problems on a ColdFusion website. The JWPlayer labels for the languages are used to create cookies and the unencoded unicode "EspaƱol" cookie value caused subsequent web requests to ColdFusion 10 & 11 to be "empty".

Unicode characters are allowed if properly encoded. If not encoded, any web requests to ColdFusion 10 & 11 will return an immediate "500 Server Error". (I believe that I reported this bug prior to the release of ColdFusion 11u7 & 10u18.)
http://stackoverflow.com/a/33292348/693068
https://gist.github.com/JamoCA/f8586d0dafc462cfd5d1

The only way I've found to currently work around this ColdFusion bug is to either 1) manually delete the offending cookie using dev tools or 2) write a URL Rewrite rule to catch the bad request and/or use javascript to delete the cookie cookie.

Reply to this Comment

@James,

Very interesting. I did see that ColdFusion 10 added the "encodevalue" option for the CFCookie tag; but, the explanation of what it does it non-valuable. From what you're saying, it sounds like it encodes Unicode characters.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
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.