Index: connectors/util/java/org/apache/tomcat/util/http/ServerCookie.java
===================================================================
--- connectors/util/java/org/apache/tomcat/util/http/ServerCookie.java (revision 759131)
+++ 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,
@@ -242,7 +283,8 @@
String domain,
String comment,
int maxAge,
- boolean isSecure )
+ boolean isSecure,
+ boolean isHttpOnly)
{
StringBuffer buf = new StringBuffer();
// Servlet implementation checks name
@@ -250,7 +292,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 +315,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
@@ -302,6 +350,10 @@
buf.append ("; Secure");
}
+ // HttpOnly
+ if (isHttpOnly) {
+ buf.append("; HttpOnly");
+ }
headerBuf.append(buf);
}
@@ -332,27 +384,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/catalina/src/share/org/apache/catalina/Context.java
===================================================================
--- container/catalina/src/share/org/apache/catalina/Context.java (revision 759131)
+++ container/catalina/src/share/org/apache/catalina/Context.java (working copy)
@@ -181,8 +181,24 @@
*/
public void setCookies(boolean cookies);
+ /**
+ * Gets the value of the use HttpOnly cookies for session cookies flag.
+ *
+ * @return true
if the HttpOnly flag should be set on session
+ * cookies
+ */
+ public boolean getUseHttpOnly();
+
/**
+ * Sets the use HttpOnly cookies for session cookies flag.
+ *
+ * @param useHttpOnly Set to true
to use HttpOnly cookies
+ * for session cookies
+ */
+ public void setUseHttpOnly(boolean useHttpOnly);
+
+ /**
* Return the "allow crossing servlet contexts" flag.
*/
public boolean getCrossContext();
Index: container/catalina/src/share/org/apache/catalina/connector/Request.java
===================================================================
--- container/catalina/src/share/org/apache/catalina/connector/Request.java (revision 759131)
+++ container/catalina/src/share/org/apache/catalina/connector/Request.java (working copy)
@@ -2238,7 +2238,7 @@
Cookie cookie = new Cookie(Globals.SESSION_COOKIE_NAME,
session.getIdInternal());
configureSessionCookie(cookie);
- response.addCookie(cookie);
+ response.addCookieInternal(cookie, context.getUseHttpOnly());
}
if (session != null) {
Index: container/catalina/src/share/org/apache/catalina/connector/Response.java
===================================================================
--- container/catalina/src/share/org/apache/catalina/connector/Response.java (revision 759131)
+++ container/catalina/src/share/org/apache/catalina/connector/Response.java (working copy)
@@ -932,7 +932,18 @@
* @param cookie Cookie to be added
*/
public void addCookie(final Cookie cookie) {
+ addCookieInternal(cookie, false);
+ }
+ /**
+ * Add the specified Cookie to those that will be included with
+ * this Response.
+ *
+ * @param cookie Cookie to be added
+ * @param httpOnly Should the httpOnly flag be set on this cookie
+ */
+ public void addCookieInternal(final Cookie cookie, final boolean httpOnly) {
+
if (isCommitted())
return;
@@ -950,7 +961,8 @@
(sb, cookie.getVersion(), cookie.getName(),
cookie.getValue(), cookie.getPath(),
cookie.getDomain(), cookie.getComment(),
- cookie.getMaxAge(), cookie.getSecure());
+ cookie.getMaxAge(), cookie.getSecure(),
+ httpOnly);
return null;
}
});
@@ -958,7 +970,7 @@
ServerCookie.appendCookieValue
(sb, cookie.getVersion(), cookie.getName(), cookie.getValue(),
cookie.getPath(), cookie.getDomain(), cookie.getComment(),
- cookie.getMaxAge(), cookie.getSecure());
+ cookie.getMaxAge(), cookie.getSecure(), httpOnly);
}
// if we reached here, no exception, cookie is valid
Index: container/catalina/src/share/org/apache/catalina/core/StandardContext.java
===================================================================
--- container/catalina/src/share/org/apache/catalina/core/StandardContext.java (revision 759131)
+++ container/catalina/src/share/org/apache/catalina/core/StandardContext.java (working copy)
@@ -656,6 +656,10 @@
*/
private boolean saveConfig = true;
+ /**
+ * The flag that indicates that session cookies should use HttpOnly
+ */
+ private boolean useHttpOnly = false;
// ----------------------------------------------------- Context Properties
@@ -1045,9 +1049,36 @@
new Boolean(this.cookies));
}
+
+ /**
+ * Gets the value of the use HttpOnly cookies for session cookies flag.
+ *
+ * @return true
if the HttpOnly flag should be set on session
+ * cookies
+ */
+ public boolean getUseHttpOnly() {
+ return useHttpOnly;
+ }
/**
+ * Sets the use HttpOnly cookies for session cookies flag.
+ *
+ * @param useHttpOnly Set to true
to use HttpOnly cookies
+ * for session cookies
+ */
+ public void setUseHttpOnly(boolean useHttpOnly) {
+ boolean oldUseHttpOnly = this.useHttpOnly;
+ this.useHttpOnly = useHttpOnly;
+ support.firePropertyChange("useHttpOnly",
+ new Boolean(oldUseHttpOnly),
+ new Boolean(this.useHttpOnly));
+ }
+
+
+
+
+ /**
* Return the "allow crossing servlet contexts" flag.
*/
public boolean getCrossContext() {
Index: container/webapps/docs/changelog.xml
===================================================================
--- container/webapps/docs/changelog.xml (revision 759136)
+++ container/webapps/docs/changelog.xml (working copy)
@@ -54,6 +54,10 @@
42419: Add a system property that enables the name of the
session cookie and session path parameter to be configured. (markt)
+
+ 44382: Add support for using httpOnly for session cookies.
+ This is disabled by default. (markt/fhanik)
+
45576: JAAS Realm now works with DIGEST authentication.
(markt)
@@ -67,6 +71,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/context.xml
===================================================================
--- container/webapps/docs/config/context.xml (revision 759131)
+++ container/webapps/docs/config/context.xml (working copy)
@@ -235,6 +235,13 @@
implementation class that will be used for servlets managed by this
Context. If not specified, a standard default value will be used.
+
+
+ Should the HttpOnly flag be set on session cookies to prevent client
+ side script from accessing the session ID? Defaults to
+ false
.
+
+
Index: container/webapps/docs/config/systemprops.xml
===================================================================
--- container/webapps/docs/config/systemprops.xml (revision 759131)
+++ 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.
+
+