No Cookie For You: Second Solution

No Cookie For You: Second Solution

Posted by Brad Wood
Jul 15, 2008 08:06:00 UTC
I have a solution that I have gotten working locally to rid my ColdFusion logs of the annoying "Cannot create cookie" errors. For those of you just joining us, Teeps blogged it, Jochem explained it, I took a stab and it and failed, and now I'm back for round two. I apologize for blogging this so much lately, but once I get a good puzzle in my craw, I just can't let up until I've solved it.This (working) solution involves updating the javax.servlet.http.Cookie class file in jrun.jar. I've been sitting on this since Saturday since I've been a little unsure of the legalities of modifying open source pieces of code contained within a closed source application. I'm going to share, but I'll start off with a big disclaimer:
  1. I did not decompile any parts of Macromedia JRUN, Adobe ColdFusion, or any other closed-source application.
  2. I did not directly modify any part of the code relating those applications
  3. I am not distributing any Jars or code which is licensed as part of those applications.
  4. The only code I modified is an open source Sun class which is released under the Apache License, Version 2.0
  5. If you feel the process I describe here violates the EULA for Macromedia JRUN or Adobe ColdFusion, please le me know.
  6. I will not be held responsible if you totally fubar your server.
Ok, with that out of the way let's get to the fix. We had narrowed down in previous posts that the source of the errors in our logs were due to clients sending HTTP requests to our servers that had cookies with "reserved" names. When JRUN parsed the headers of the request it attempted to create an instance of the javax.servlet.http.Cookie class for each cookie to be included in the servletRequest object that is ultimately passed on to the ColdFusion application. Due to Sun's interpretation of RFC 2019 the constructor of this class will throw an error if you attempt to create a cookie with a name that is considered "reserved". The first thing I did was download the code for the javax.servlet.http.Cookie class. I am running ColdFusin 8 on JDK 1.6 which I believe uses version 2.5 of the Servlet API. In the I edited the opening lines of the constructor to read as such:
[code]public Cookie(String name, String value) {
if (!isToken(name)
	//|| name.equalsIgnoreCase("Comment")	// rfc2019
	//|| name.equalsIgnoreCase("Discard")	// 2019++
	//|| name.equalsIgnoreCase("Domain")
	//|| name.equalsIgnoreCase("Expires")	// (old cookies)
	//|| name.equalsIgnoreCase("Max-Age")	// rfc2019
	//|| name.equalsIgnoreCase("Path")
	//|| name.equalsIgnoreCase("Secure")
	//|| name.equalsIgnoreCase("Version")
	|| name.startsWith("$")
    ) {
    String errMsg = lStrings.getString("err.cookie_name_is_token");
    Object[] errArgs = new Object[1];
    errArgs[0] = name;
    errMsg = MessageFormat.format(errMsg, errArgs);
    throw new IllegalArgumentException(errMsg);
} = name;
this.value = value;
The only two lines I left in are the isToken() check and the name.startsWith("$") line. I do not think the other lines are necessary to conform to RFC 2019. Note, I have NOT tested the far-reaching impact of this change. So far I have not seen any problem, but I have not tested with any old browsers. I then compiled into a class file. I downloaded the full SDK for Java 1.6 updater 1 (to match the version CF is running on). You can compile Java files with the "javac" command, but I'll admit I used Eclipse. I created a package called javax.servlet.http and placed the new file in there. Then I exported the whole thing to a .jar. Then I renamed the jar to a .zip file and extracted the Cookie.class file out of it. I know there has to be a shorter line between those two points but that is what worked for me. Now we have a new compiled version of the Cookie class which is much more lax in the cookie names it allows. But now we need to get the new Cookie class into the jrun.jar file. In my stand-alone install jrun.jar is located in <cf_root>/runtime/lib/jrun.jar The first thing you want to do is BACK UP a copy of jrun.jar. You have to change the extension to something other than .jar. A name like _jrun.jar will NOT work, because that jar file will still be loaded if it is in the lib directory. The command we want is called jar.exe and it is in the bin directory of your Java SDK. This utility let's you jar files together, but it also lets you update a file into an existing jar. A jar file is really nothing more than a glorified zip file. You can open jar files in a program like WinZip, but you can't make a jar file with WinZip. I've tried, but Java can never find the manifest file. I think it needs to be in a special place for it to work. I created folders called "javax", "servlet", and "http" inside of each other in that order. I then placed the Cookie.class file in the http folder. Assuming the jar.exe command is in your windows path, I ran the following:
[code]C:\>jar ufv jrun.jar javax/*[/code]
The "u" means I am updating an existing jar. The "f" means I am specifying a file name. (Without that you get some nasty characters spewed out to your console) The "v" tells it to be verbose. The output from the that operation is:
[code]adding: javax/servlet/(in = 0) (out= 0)(stored 0%)
adding: javax/servlet/http/(in = 0) (out= 0)(stored 0%)
adding: javax/servlet/http/Cookie.class(in = 3400) (out= 1593)(deflated 53%)[/code]
The jrun.jar file is now injected with our new version of the servlet Cookie class. Copy it back to where it came from (if you moved it) and start up ColdFusion. Look into the "out" log to see if it starts up properly. If it errors then you did something wrong. (In my stand alone install, the log file is located in <cf_root>\runtime\logs\coldfusion-out.log) Now test it by using a program like MS Fiddler to send an HTTP request to your server with cookies bearing reserved names. My test request looked like this:
[code]GET / HTTP/1.1
User-Agent: Fiddler
Cookie: expires=foo;path=bar;secure=hello;test=world
This request claims to have cookies called expires, path, secure, and test. Hit a test page with the following code:
	<cfloop collection="#cookie#" item="i">
		#i#: #cookie[i]#
Two things should happen. You should get the following output:
[code]expires: foo
path: bar
secure: hello
test: world
And your "out" log should NOT have the following three errors in it:
[code]&lt;date> &lt;time> error Cannot create cookie: expires = foo
&lt;date> &lt;time> error Cannot create cookie: path = bar
&lt;date> &lt;time> error Cannot create cookie: secure = hello
There you have it. This works for me, but use it at your own risk. I'll let you decide if the thousands of log file entries make this worth trying. Please let me know how it turns out if you do implement this. Also please tell me if you think of any horrible consequences to my solution that haven't occurred to me yet. Update: I noticed today that not only does this fix allow for your server to ACCEPT cookies with reserved names, it also allows it to SET cookies with reserved names. Like Jochem pointed out in his blog, the following code:
[code]<cfcookie name="expires" value="test" />[/code]
Would previously cause this error:
[code]Cookie name EXPIRES is a reserved token [/code]
This is undoubtedly because ColdFusion creates an instance of the javax.servlet.http.Cookie class for each cfcookie tag and includes it in the httpServletResponse object. After modifying the Cookie class to allow those names, ColdFusion will create cookies with any name AND accept them.


Ryan Stille

Wow, I'm impressed. Thanks for blogging this.

Mark Kruger

Wow... that is a real fine piece of work Brad. How many tries did it take to get this to work?

Brad Wood

I'll just say it took all Saturday. :)

My entry here has a more detailed anyalysis of how I zeroed in on the problem:

Site Updates

Entry Comments

Entries Search