Index: connectors/util/java/org/apache/tomcat/util/http/ServerCookie.java =================================================================== --- connectors/util/java/org/apache/tomcat/util/http/ServerCookie.java (revision 765287) +++ connectors/util/java/org/apache/tomcat/util/http/ServerCookie.java (working copy) @@ -18,11 +18,14 @@ package org.apache.tomcat.util.http; import java.io.Serializable; +import java.text.DateFormat; import java.text.FieldPosition; +import java.text.SimpleDateFormat; import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; import org.apache.tomcat.util.buf.ByteChunk; -import org.apache.tomcat.util.buf.DateTool; import org.apache.tomcat.util.buf.MessageBytes; @@ -50,6 +53,37 @@ private int maxAge = -1; private int version = 0; + // Other fields + private static final String OLD_COOKIE_PATTERN = + "EEE, dd-MMM-yyyy HH:mm:ss z"; + private static final ThreadLocal OLD_COOKIE_FORMAT = + new ThreadLocal() { + protected DateFormat initialValue() { + DateFormat df = + new SimpleDateFormat(OLD_COOKIE_PATTERN, Locale.US); + df.setTimeZone(TimeZone.getTimeZone("GMT")); + return df; + } + }; + private static final String ancientDate; + + + static { + ancientDate = OLD_COOKIE_FORMAT.get().format(new Date(10000)); + } + + /** + * If set to true, we parse cookies according to the servlet spec, + */ + public static final boolean STRICT_SERVLET_COMPLIANCE = + Boolean.valueOf(System.getProperty("org.apache.catalina.STRICT_SERVLET_COMPLIANCE", "false")).booleanValue(); + + /** + * If set to false, we don't use the IE6/7 Max-Age/Expires work around + */ + public static final boolean ALWAYS_ADD_EXPIRES = + Boolean.valueOf(System.getProperty("org.apache.tomcat.util.http.ServerCookie.ALWAYS_ADD_EXPIRES", "true")).booleanValue(); + // Note: Servlet Spec =< 2.5 only refers to Netscape and RFC2109, // not RFC2965 @@ -127,6 +161,7 @@ private static final String tspecials = ",; "; private static final String tspecials2 = "()<>@,;:\\\"/[]?={} \t"; + private static final String tspecials2NoSlash = "()<>@,;:\\\"[]?={} \t"; /* * Tests a string and returns true if the string counts as a @@ -139,6 +174,11 @@ * if it is not */ public static boolean isToken(String value) { + return isToken(value,null); + } + + public static boolean isToken(String value, String literals) { + String tspecials = (literals==null?ServerCookie.tspecials:literals); if( value==null) return true; int len = value.length(); @@ -164,9 +204,13 @@ } return false; } - - + public static boolean isToken2(String value) { + return isToken2(value,null); + } + + public static boolean isToken2(String value, String literals) { + String tspecials2 = (literals==null?ServerCookie.tspecials2:literals); if( value==null) return true; int len = value.length(); @@ -230,9 +274,6 @@ } } - private static final String ancientDate = - DateTool.formatOldCookie(new Date(10000)); - // TODO RFC2965 fields also need to be passed public static void appendCookieValue( StringBuffer headerBuf, int version, @@ -250,7 +291,7 @@ buf.append("="); // Servlet implementation does not check anything else - maybeQuote2(version, buf, value); + version = maybeQuote2(version, buf, value,true); // Add version 1 specific information if (version == 1) { @@ -273,28 +314,34 @@ // Max-Age=secs ... or use old "Expires" format // TODO RFC2965 Discard if (maxAge >= 0) { - if (version == 0) { + if (version > 0) { + buf.append ("; Max-Age="); + buf.append (maxAge); + } + // IE6, IE7 and possibly other browsers don't understand Max-Age. + // They do understand Expires, even with V1 cookies! + if (version == 0 || ALWAYS_ADD_EXPIRES) { // Wdy, DD-Mon-YY HH:MM:SS GMT ( Expires Netscape format ) buf.append ("; Expires="); // To expire immediately we need to set the time in past if (maxAge == 0) buf.append( ancientDate ); else - DateTool.formatOldCookie - (new Date( System.currentTimeMillis() + - maxAge *1000L), buf, - new FieldPosition(0)); - - } else { - buf.append ("; Max-Age="); - buf.append (maxAge); + OLD_COOKIE_FORMAT.get().format( + new Date(System.currentTimeMillis() + + maxAge*1000L), + buf, new FieldPosition(0)); } } // Path=path if (path!=null) { buf.append ("; Path="); - maybeQuote2(version, buf, path); + if (version==0) { + maybeQuote2(version, buf, path); + } else { + maybeQuote2(version, buf, path, ServerCookie.tspecials2NoSlash, false); + } } // Secure @@ -332,27 +379,40 @@ * @param buf * @param value */ - public static void maybeQuote2(int version, StringBuffer buf, - String value) { + public static int maybeQuote2 (int version, StringBuffer buf, String value) { + return maybeQuote2(version,buf,value,false); + } + + public static int maybeQuote2 (int version, StringBuffer buf, String value, boolean allowVersionSwitch) { + return maybeQuote2(version,buf,value,null,allowVersionSwitch); + } + + public static int maybeQuote2 (int version, StringBuffer buf, String value, String literals, boolean allowVersionSwitch) { if (value==null || value.length()==0) { buf.append("\"\""); } else if (containsCTL(value,version)) throw new IllegalArgumentException("Control character in cookie value, consider BASE64 encoding your value"); else if (alreadyQuoted(value)) { buf.append('"'); - buf.append(escapeDoubleQuotes(value,1,value.length()-1)); buf.append('"'); + buf.append(escapeDoubleQuotes(value,1,value.length()-1)); buf.append('"'); - } else if (version==0 && !isToken(value)) { + } else if (allowVersionSwitch && (!STRICT_SERVLET_COMPLIANCE) && version==0 && !isToken2(value, literals)) { buf.append('"'); buf.append(escapeDoubleQuotes(value,0,value.length())); buf.append('"'); - } else if (version==1 && !isToken2(value)) { + version = 1; + } else if (version==0 && !isToken(value,literals)) { buf.append('"'); buf.append(escapeDoubleQuotes(value,0,value.length())); buf.append('"'); + } else if (version==1 && !isToken2(value,literals)) { + buf.append('"'); + buf.append(escapeDoubleQuotes(value,0,value.length())); + buf.append('"'); } else { buf.append(value); } + return version; } /** Index: container/webapps/docs/changelog.xml =================================================================== --- container/webapps/docs/changelog.xml (revision 763174) +++ container/webapps/docs/changelog.xml (working copy) @@ -67,6 +67,10 @@ logging at the context level but the security policy prevents this. (markt/rjung) + + 46597: Port all cookie handling changes from Tomcat 6.0.x. + (markt) + Index: container/webapps/docs/config/systemprops.xml =================================================================== --- container/webapps/docs/config/systemprops.xml (revision 763174) +++ container/webapps/docs/config/systemprops.xml (working copy) @@ -101,17 +101,32 @@

If this is true the following actions will occur:

    -
  • any wrapped request or response object passed to an application - dispatcher will be checked to ensure that it has wrapped the original - request or response. (SRV.8.2 / SRV.14.2.5.1) -
  • -
  • when updating the access count for the session, the update will be - synchronized. -
  • +
  • any wrapped request or response object passed to an application + dispatcher will be checked to ensure that it has wrapped the original + request or response. (SRV.8.2 / SRV.14.2.5.1) +
  • +
  • when updating the access count for the session, the update will be + synchronized. +
  • +
  • + cookies will be parsed strictly, by default v0 cookies will not work + with any invalid characters.
    If set to false, any v0 cookie with + invalid character will be switched to a v1 cookie and the value will + be quoted. +

+ +

If this is true Tomcat will always add an expires + parameter to a SetCookie header even for cookies with version greater than + zero. This is to work around a known IE6 and IE7 bug that causes IE to + ignore the Max-Age parameter in a SetCookie header.If not specified, the + default value of true will be used.

+
+