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.
+
+