Author: remy.maucherat(a)jboss.com
Date: 2009-10-30 12:18:49 -0400 (Fri, 30 Oct 2009)
New Revision: 1232
Modified:
trunk/java/javax/servlet/http/Cookie.java
trunk/java/org/apache/tomcat/util/http/Cookies.java
trunk/java/org/apache/tomcat/util/http/ServerCookie.java
trunk/webapps/docs/changelog.xml
Log:
- Port Tomcat cookie compliance fixes.
Modified: trunk/java/javax/servlet/http/Cookie.java
===================================================================
--- trunk/java/javax/servlet/http/Cookie.java 2009-10-29 23:57:43 UTC (rev 1231)
+++ trunk/java/javax/servlet/http/Cookie.java 2009-10-30 16:18:49 UTC (rev 1232)
@@ -1,59 +1,19 @@
/*
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
- *
- * Copyright 1997-2008 Sun Microsystems, Inc. All rights reserved.
- *
- * The contents of this file are subject to the terms of either the GNU
- * General Public License Version 2 only ("GPL") or the Common Development
- * and Distribution License("CDDL") (collectively, the "License").
You
- * may not use this file except in compliance with the License. You can obtain
- * a copy of the License at
https://glassfish.dev.java.net/public/CDDL+GPL.html
- * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
- * language governing permissions and limitations under the License.
- *
- * When distributing the software, include this License Header Notice in each
- * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
- * Sun designates this particular file as subject to the "Classpath" exception
- * as provided by Sun in the GPL Version 2 section of the License file that
- * accompanied this code. If applicable, add the following below the License
- * Header, with the fields enclosed by brackets [] replaced by your own
- * identifying information: "Portions Copyrighted [year]
- * [name of copyright owner]"
- *
- * Contributor(s):
- *
- * If you wish your version of this file to be governed by only the CDDL or
- * only the GPL Version 2, indicate your decision by adding "[Contributor]
- * elects to include this software in this distribution under the [CDDL or GPL
- * Version 2] license." If you don't indicate a single choice of license, a
- * recipient has the option to distribute your version of this file under
- * either the CDDL, the GPL Version 2 or to extend the choice of license to
- * its licensees as provided above. However, if you add GPL Version 2 code
- * and therefore, elected the GPL Version 2 license, then the option applies
- * only if the new code is made subject to such option by the copyright
- * holder.
- *
- *
- * This file incorporates work covered by the following copyright and
- * permission notice:
- *
- * Copyright 2004 The Apache Software Foundation
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *
http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-
+* Licensed to the Apache Software Foundation (ASF) under one or more
+* contributor license agreements. See the NOTICE file distributed with
+* this work for additional information regarding copyright ownership.
+* The ASF licenses this file to You under the Apache License, Version 2.0
+* (the "License"); you may not use this file except in compliance with
+* the License. You may obtain a copy of the License at
+*
+*
http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
package javax.servlet.http;
import java.text.MessageFormat;
@@ -94,7 +54,9 @@
* created using Version 0 to ensure the best interoperability.
*
*
- * @author Various
+ * @author Various
+ * @version $Version$
+ *
*/
// XXX would implement java.io.Serializable too, but can't do that
@@ -104,28 +66,28 @@
public class Cookie implements Cloneable {
private static final String LSTRING_FILE =
- "javax.servlet.http.LocalStrings";
+ "javax.servlet.http.LocalStrings";
private static ResourceBundle lStrings =
- ResourceBundle.getBundle(LSTRING_FILE);
+ ResourceBundle.getBundle(LSTRING_FILE);
//
// The value of the cookie itself.
//
- private String name; // NAME= ... "$Name" style is reserved
- private String value; // value of NAME
+ private String name; // NAME= ... "$Name" style is reserved
+ private String value; // value of NAME
//
// Attributes encoded in the header's cookie fields.
//
- private String comment; // ;Comment=VALUE ... describes cookie's use
- // ;Discard ... implied by maxAge < 0
- private String domain; // ;Domain=VALUE ... domain that sees cookie
- private int maxAge = -1; // ;Max-Age=VALUE ... cookies auto-expire
- private String path; // ;Path=VALUE ... URLs that see the cookie
- private boolean secure; // ;Secure ... e.g. use SSL
- private int version = 0; // ;Version=1 ... means RFC 2109++ style
+ private String comment; // ;Comment=VALUE ... describes cookie's use
+ // ;Discard ... implied by maxAge < 0
+ private String domain; // ;Domain=VALUE ... domain that sees cookie
+ private int maxAge = -1; // ;Max-Age=VALUE ... cookies auto-expire
+ private String path; // ;Path=VALUE ... URLs that see the cookie
+ private boolean secure; // ;Secure ... e.g. use SSL
+ private int version = 0; // ;Version=1 ... means RFC 2109++ style
private boolean isHttpOnly = false;
@@ -148,40 +110,40 @@
* <code>setVersion</code> method.
*
*
- * @param name a <code>String</code> specifying the name of the
cookie
+ * @param name a <code>String</code> specifying the name of the
cookie
*
- * @param value a <code>String</code> specifying the value of the
cookie
+ * @param value a <code>String</code> specifying the value of the
cookie
*
- * @throws IllegalArgumentException if the cookie name contains illegal characters
- * (for example, a comma, space, or semicolon)
- * or it is one of the tokens reserved for use
- * by the cookie protocol
+ * @throws IllegalArgumentException if the cookie name contains illegal characters
+ * (for example, a comma, space, or semicolon)
+ * or it is one of the tokens reserved for use
+ * by the cookie protocol
* @see #setValue
* @see #setVersion
*
*/
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);
- }
+ 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);
+ }
- this.name = name;
- this.value = value;
+ this.name = name;
+ this.value = value;
}
@@ -195,15 +157,15 @@
* to the user. Comments
* are not supported by Netscape Version 0 cookies.
*
- * @param purpose a <code>String</code> specifying the comment
- * to display to the user
+ * @param purpose a <code>String</code> specifying the comment
+ * to display to the user
*
* @see #getComment
*
*/
public void setComment(String purpose) {
- comment = purpose;
+ comment = purpose;
}
@@ -213,15 +175,15 @@
* Returns the comment describing the purpose of this cookie, or
* <code>null</code> if the cookie has no comment.
*
- * @return a <code>String</code> containing the comment,
- * or <code>null</code> if none
+ * @return a <code>String</code> containing the comment,
+ * or <code>null</code> if none
*
* @see #setComment
*
*/
public String getComment() {
- return comment;
+ return comment;
}
@@ -240,16 +202,16 @@
* to the server that sent them.
*
*
- * @param pattern a <code>String</code> containing the domain name
- * within which this cookie is visible;
- * form is according to RFC 2109
+ * @param pattern a <code>String</code> containing the domain name
+ * within which this cookie is visible;
+ * form is according to RFC 2109
*
* @see #getDomain
*
*/
public void setDomain(String pattern) {
- domain = pattern.toLowerCase(); // IE allegedly needs this
+ domain = pattern.toLowerCase(); // IE allegedly needs this
}
@@ -260,14 +222,14 @@
* Returns the domain name set for this cookie. The form of
* the domain name is set by RFC 2109.
*
- * @return a <code>String</code> containing the domain name
+ * @return a <code>String</code> containing the domain name
*
* @see #setDomain
*
*/
public String getDomain() {
- return domain;
+ return domain;
}
@@ -286,10 +248,10 @@
* when the Web browser exits. A zero value causes the cookie
* to be deleted.
*
- * @param expiry an integer specifying the maximum age of the
- * cookie in seconds; if negative, means
- * the cookie is not stored; if zero, deletes
- * the cookie
+ * @param expiry an integer specifying the maximum age of the
+ * cookie in seconds; if negative, means
+ * the cookie is not stored; if zero, deletes
+ * the cookie
*
*
* @see #getMaxAge
@@ -297,7 +259,7 @@
*/
public void setMaxAge(int expiry) {
- maxAge = expiry;
+ maxAge = expiry;
}
@@ -309,9 +271,9 @@
* until browser shutdown.
*
*
- * @return an integer specifying the maximum age of the
- * cookie in seconds; if negative, means
- * the cookie persists until browser shutdown
+ * @return an integer specifying the maximum age of the
+ * cookie in seconds; if negative, means
+ * the cookie persists until browser shutdown
*
*
* @see #setMaxAge
@@ -319,7 +281,7 @@
*/
public int getMaxAge() {
- return maxAge;
+ return maxAge;
}
@@ -339,7 +301,7 @@
* information on setting path names for cookies.
*
*
- * @param uri a <code>String</code> specifying a path
+ * @param uri a <code>String</code> specifying a path
*
*
* @see #getPath
@@ -347,7 +309,7 @@
*/
public void setPath(String uri) {
- path = uri;
+ path = uri;
}
@@ -359,15 +321,15 @@
* cookie is visible to all subpaths on the server.
*
*
- * @return a <code>String</code> specifying a path that contains
- * a servlet name, for example, <i>/catalog</i>
+ * @return a <code>String</code> specifying a path that contains
+ * a servlet name, for example, <i>/catalog</i>
*
* @see #setPath
*
*/
public String getPath() {
- return path;
+ return path;
}
@@ -380,16 +342,16 @@
*
* <p>The default value is <code>false</code>.
*
- * @param flag if <code>true</code>, sends the cookie from the browser
- * to the server only when using a secure protocol;
- * if <code>false</code>, sent on any protocol
+ * @param flag if <code>true</code>, sends the cookie from the browser
+ * to the server only when using a secure protocol;
+ * if <code>false</code>, sent on any protocol
*
* @see #getSecure
*
*/
public void setSecure(boolean flag) {
- secure = flag;
+ secure = flag;
}
@@ -400,15 +362,15 @@
* only over a secure protocol, or <code>false</code> if the
* browser can send cookies using any protocol.
*
- * @return <code>true</code> if the browser uses a secure protocol;
- * otherwise, <code>true</code>
+ * @return <code>true</code> if the browser uses a secure protocol;
+ * otherwise, <code>true</code>
*
* @see #setSecure
*
*/
public boolean getSecure() {
- return secure;
+ return secure;
}
@@ -419,12 +381,12 @@
* Returns the name of the cookie. The name cannot be changed after
* creation.
*
- * @return a <code>String</code> specifying the cookie's name
+ * @return a <code>String</code> specifying the cookie's name
*
*/
public String getName() {
- return name;
+ return name;
}
@@ -442,7 +404,7 @@
* and semicolons. Empty values may not behave the same way
* on all browsers.
*
- * @param newValue a <code>String</code> specifying the new value
+ * @param newValue a <code>String</code> specifying the new value
*
*
* @see #getValue
@@ -451,7 +413,7 @@
*/
public void setValue(String newValue) {
- value = newValue;
+ value = newValue;
}
@@ -460,8 +422,8 @@
/**
* Returns the value of the cookie.
*
- * @return a <code>String</code> containing the cookie's
- * present value
+ * @return a <code>String</code> containing the cookie's
+ * present value
*
* @see #setValue
* @see Cookie
@@ -469,7 +431,7 @@
*/
public String getValue() {
- return value;
+ return value;
}
@@ -483,16 +445,16 @@
* by a browser use and identify the browser's cookie version.
*
*
- * @return 0 if the cookie complies with the
- * original Netscape specification; 1
- * if the cookie complies with RFC 2109
+ * @return 0 if the cookie complies with the
+ * original Netscape specification; 1
+ * if the cookie complies with RFC 2109
*
* @see #setVersion
*
*/
public int getVersion() {
- return version;
+ return version;
}
@@ -507,16 +469,16 @@
* version 1 as experimental; do not use it yet on production sites.
*
*
- * @param v 0 if the cookie should comply with
- * the original Netscape specification;
- * 1 if the cookie should comply with RFC 2109
+ * @param v 0 if the cookie should comply with
+ * the original Netscape specification;
+ * 1 if the cookie should comply with RFC 2109
*
* @see #getVersion
*
*/
public void setVersion(int v) {
- version = v;
+ version = v;
}
// Note -- disabled for now to allow full Netscape compatibility
@@ -525,83 +487,124 @@
// private static final String tspecials = "()<>@,;:\\\"/[]?={}
\t";
private static final String tspecials = ",; ";
+ private static final String tspecials2NoSlash = "()<>@,;:\\\"[]?={}
\t";
+ private static final String tspecials2WithSlash = tspecials2NoSlash + "/";
+ private static final String tspecials2;
+ /**
+ * If set to true, we parse cookies strictly according to the servlet,
+ * cookie and HTTP specs by default.
+ */
+ private static final boolean STRICT_SERVLET_COMPLIANCE;
+
+ /**
+ * If set to true, the <code>/</code> character will be treated as a
+ * separator. Default is usually false. If STRICT_SERVLET_COMPLIANCE==true
+ * then default is true. Explicitly setting always takes priority.
+ */
+ private static final boolean FWD_SLASH_IS_SEPARATOR;
+
+ /**
+ * If set to true, enforce the cookie naming rules in the spec that require
+ * no separators in the cookie name. Default is usually false. If
+ * STRICT_SERVLET_COMPLIANCE==true then default is true. Explicitly setting
+ * always takes priority.
+ */
+ private static final boolean STRICT_NAMING;
+
+
+ static {
+ STRICT_SERVLET_COMPLIANCE = Boolean.valueOf(System.getProperty(
+ "org.apache.catalina.STRICT_SERVLET_COMPLIANCE",
+ "false")).booleanValue();
+
+ String fwdSlashIsSeparator = System.getProperty(
+
"org.apache.tomcat.util.http.ServerCookie.FWD_SLASH_IS_SEPARATOR");
+ if (fwdSlashIsSeparator == null) {
+ FWD_SLASH_IS_SEPARATOR = STRICT_SERVLET_COMPLIANCE;
+ } else {
+ FWD_SLASH_IS_SEPARATOR =
+ Boolean.valueOf(fwdSlashIsSeparator).booleanValue();
+ }
+
+ if (FWD_SLASH_IS_SEPARATOR) {
+ tspecials2 = tspecials2WithSlash;
+ } else {
+ tspecials2 = tspecials2NoSlash;
+ }
+
+ String strictNaming = System.getProperty(
+ "org.apache.tomcat.util.http.ServerCookie.STRICT_NAMING");
+ if (strictNaming == null) {
+ STRICT_NAMING = STRICT_SERVLET_COMPLIANCE;
+ } else {
+ STRICT_NAMING =
+ Boolean.valueOf(strictNaming).booleanValue();
+ }
+
+ }
+
+
-
/*
* Tests a string and returns true if the string counts as a
* reserved token in the Java language.
*
- * @param value the <code>String</code> to be tested
+ * @param value the <code>String</code> to be tested
*
- * @return <code>true</code> if the <code>String</code> is
- * a reserved token; <code>false</code>
- * if it is not
+ * @return <code>true</code> if the
<code>String</code> is
+ * a reserved token; <code>false</code>
+ * if it is not
*/
-
private boolean isToken(String value) {
- int len = value.length();
+ int len = value.length();
- for (int i = 0; i < len; i++) {
- char c = value.charAt(i);
+ for (int i = 0; i < len; i++) {
+ char c = value.charAt(i);
- if (c < 0x20 || c >= 0x7f || tspecials.indexOf(c) != -1)
- return false;
- }
- return true;
+ if (c < 0x20 || c >= 0x7f || tspecials.indexOf(c) != -1 ||
+ (STRICT_NAMING && tspecials2.indexOf(c) != -1)) {
+ return false;
+ }
+ }
+ return true;
}
-
-
-
/**
*
* Overrides the standard <code>java.lang.Object.clone</code>
* method to return a copy of this cookie.
- *
+ *
*
*/
public Object clone() {
- try {
- return super.clone();
- } catch (CloneNotSupportedException e) {
- throw new RuntimeException(e.getMessage());
- }
+ try {
+ return super.clone();
+ } catch (CloneNotSupportedException e) {
+ throw new RuntimeException(e.getMessage());
}
- /**
- * Marks or unmarks this cookie as <i>HttpOnly</i>.
- *
- * <p>If <tt>isHttpOnly</tt> is set to <tt>true</tt>,
this cookie is
- * marked as <i>HttpOnly</i>, by adding the
<tt>HttpOnly</tt> attribute
- * to it.
- *
- * <p><i>HttpOnly</i> cookies are not supposed to be exposed to
- * client-side scripting code, and may therefore help mitigate certain
- * kinds of cross-site scripting attacks.
- *
- * @param isHttpOnly true if this cookie is to be marked as
- * <i>HttpOnly</i>, false otherwise
- *
- * @since Servlet 3.0
- */
- public void setHttpOnly(boolean isHttpOnly) {
- this.isHttpOnly = isHttpOnly;
- }
-
- /**
- * Checks whether this cookie has been marked as <i>HttpOnly</i>.
- *
- * @return true if this cookie has been marked as <i>HttpOnly</i>,
- * false otherwise
- *
- * @since Servlet 3.0
- */
- public boolean isHttpOnly() {
- return isHttpOnly;
- }
+ }
+
+ /**
+ *
+ * @return
+ * @since Servlet 3.0
+ */
+ public boolean isHttpOnly() {
+ return isHttpOnly;
+ }
+
+ /**
+ *
+ * @param httpOnly
+ * @since Servlet 3.0
+ */
+ public void setHttpOnly(boolean httpOnly) {
+ this.isHttpOnly = httpOnly;
+ }
}
Modified: trunk/java/org/apache/tomcat/util/http/Cookies.java
===================================================================
--- trunk/java/org/apache/tomcat/util/http/Cookies.java 2009-10-29 23:57:43 UTC (rev
1231)
+++ trunk/java/org/apache/tomcat/util/http/Cookies.java 2009-10-30 16:18:49 UTC (rev
1232)
@@ -37,7 +37,7 @@
private static org.jboss.logging.Logger log=
org.jboss.logging.Logger.getLogger(Cookies.class );
-
+
// expected average number of cookies per request
public static final int INITIAL_SIZE=4;
ServerCookie scookies[]=new ServerCookie[INITIAL_SIZE];
@@ -46,19 +46,54 @@
MimeHeaders headers;
+ /**
+ * If set to true, we parse cookies strictly according to the servlet,
+ * cookie and HTTP specs by default.
+ */
+ public static final boolean STRICT_SERVLET_COMPLIANCE;
+
+ /**
+ * If set to true, the <code>/</code> character will be treated as a
+ * separator. Default is usually false. If STRICT_SERVLET_COMPLIANCE==true
+ * then default is true. Explicitly setting always takes priority.
+ */
+ public static final boolean FWD_SLASH_IS_SEPARATOR;
+
/*
List of Separator Characters (see isSeparator())
- Excluding the '/' char violates the RFC, but
- it looks like a lot of people put '/'
- in unquoted values: '/': ; //47
- '\t':9 ' ':32 '\"':34 '\'':39 '(':40
')':41 ',':44 ':':58 ';':59 '<':60
- '=':61 '>':62 '?':63 '@':64 '[':91
'\\':92 ']':93 '{':123 '}':125
*/
- public static final char SEPARATORS[] = { '\t', ' ',
'\"', '\'', '(', ')', ',',
- ':', ';', '<', '=', '>',
'?', '@', '[', '\\', ']', '{', '}'
};
+ public static final char SEPARATORS[];
protected static final boolean separators[] = new boolean[128];
static {
+ STRICT_SERVLET_COMPLIANCE = Boolean.valueOf(System.getProperty(
+ "org.apache.catalina.STRICT_SERVLET_COMPLIANCE",
+ "false")).booleanValue();
+
+ String fwdSlashIsSeparator = System.getProperty(
+
"org.apache.tomcat.util.http.ServerCookie.FWD_SLASH_IS_SEPARATOR");
+ if (fwdSlashIsSeparator == null) {
+ FWD_SLASH_IS_SEPARATOR = STRICT_SERVLET_COMPLIANCE;
+ } else {
+ FWD_SLASH_IS_SEPARATOR =
+ Boolean.valueOf(fwdSlashIsSeparator).booleanValue();
+ }
+
+ /*
+ Excluding the '/' char by default violates the RFC, but
+ it looks like a lot of people put '/'
+ in unquoted values: '/': ; //47
+ '\t':9 ' ':32 '\"':34 '(':40 ')':41
',':44 ':':58 ';':59 '<':60
+ '=':61 '>':62 '?':63 '@':64 '[':91
'\\':92 ']':93 '{':123 '}':125
+ */
+ if (FWD_SLASH_IS_SEPARATOR) {
+ SEPARATORS = new char[] { '\t', ' ', '\"',
'(', ')', ',', '/',
+ ':', ';', '<', '=',
'>', '?', '@', '[', '\\', ']',
'{', '}' };
+ } else {
+ SEPARATORS = new char[] { '\t', ' ', '\"',
'(', ')', ',',
+ ':', ';', '<', '=',
'>', '?', '@', '[', '\\', ']',
'{', '}' };
+ }
+
for (int i = 0; i < 128; i++) {
separators[i] = false;
}
@@ -190,13 +225,15 @@
// Uncomment to test the new parsing code
if( cookieValue.getType() == MessageBytes.T_BYTES ) {
- if( dbg>0 ) log( "Parsing b[]: " + cookieValue.toString());
+ if(log.isDebugEnabled())
+ log.debug("Cookies: Parsing b[]: " +
cookieValue.toString());
ByteChunk bc=cookieValue.getByteChunk();
processCookieHeader( bc.getBytes(),
bc.getOffset(),
bc.getLength());
} else {
- if( dbg>0 ) log( "Parsing S: " + cookieValue.toString());
+ if(log.isDebugEnabled())
+ log.debug("Cookies: Parsing S: " +
cookieValue.toString());
processCookieHeader( cookieValue.toString() );
}
pos++;// search from the next position
@@ -224,7 +261,8 @@
private void processCookieHeader( String cookieString )
{
- if( dbg>0 ) log( "Parsing cookie header " + cookieString );
+ if(log.isDebugEnabled())
+ log.debug("Cookies: Parsing cookie header " + cookieString);
// normal cookie, with a string value.
// This is the original code, un-optimized - it shouldn't
// happen in normal case
@@ -248,7 +286,8 @@
cookie.getName().setString(name);
cookie.getValue().setString(value);
- if( dbg > 0 ) log( "Add cookie " + name + "=" +
value);
+ if(log.isDebugEnabled())
+ log.debug("Cookies: Add cookie " + name + "=" +
value);
} else {
// we have a bad cookie.... just let it go
}
@@ -278,15 +317,6 @@
return value;
}
-
- // log
- static final int dbg=0;
- public void log(String s ) {
- if (log.isDebugEnabled())
- log.debug("Cookies: " + s);
- }
-
-
/**
* Returns true if the byte is a separator character as
* defined in RFC2619. Since this is called often, this
@@ -370,7 +400,7 @@
pos = nameEnd = getTokenEndPosition(bytes,pos,end);
// Skip whitespace
- while (pos < end && isWhiteSpace(bytes[pos])) {pos++; };
+ while (pos < end && isWhiteSpace(bytes[pos])) {pos++; }
// Check for an '=' -- This could also be a name-only
@@ -390,7 +420,7 @@
// Determine what type of value this is, quoted value,
// token, name-only with an '=', or other (bad)
switch (bytes[pos]) {
- case '"':; // Quoted Value
+ case '"': // Quoted Value
isQuoted = true;
valueStart=pos + 1; // strip "
// getQuotedValue returns the position before
@@ -413,7 +443,7 @@
valueStart = valueEnd = -1;
// The position is OK (On a delimiter)
break;
- default:;
+ default:
if (!isSeparator(bytes[pos])) {
// Token
valueStart=pos;
@@ -426,10 +456,11 @@
// INVALID COOKIE, advance to next delimiter
// The starting character of the cookie value was
// not valid.
- log("Invalid cookie. Value not a token or quoted
value");
+ log.info("Cookies: Invalid cookie." +
+ "Value not a token or quoted value");
while (pos < end && bytes[pos] != ';'
&&
bytes[pos] != ',')
- {pos++; };
+ {pos++; }
pos++;
// Make sure no special avpairs can be attributed to
// the previous cookie by setting the current cookie
@@ -450,7 +481,7 @@
// in a good state.
// Skip whitespace
- while (pos < end && isWhiteSpace(bytes[pos])) {pos++; };
+ while (pos < end && isWhiteSpace(bytes[pos])) {pos++; }
// Make sure that after the cookie we have a separator. This
@@ -525,7 +556,7 @@
}
// Unknown cookie, complain
- log("Unknown Special Cookie");
+ log.info("Cookies: Unknown Special Cookie");
} else { // Normal Cookie
sc = addCookie();
@@ -557,7 +588,7 @@
*/
public static final int getTokenEndPosition(byte bytes[], int off, int end){
int pos = off;
- while (pos < end && !isSeparator(bytes[pos])) {pos++; };
+ while (pos < end && !isSeparator(bytes[pos])) {pos++; }
if (pos > end)
return end;
Modified: trunk/java/org/apache/tomcat/util/http/ServerCookie.java
===================================================================
--- trunk/java/org/apache/tomcat/util/http/ServerCookie.java 2009-10-29 23:57:43 UTC (rev
1231)
+++ trunk/java/org/apache/tomcat/util/http/ServerCookie.java 2009-10-30 16:18:49 UTC (rev
1232)
@@ -68,24 +68,54 @@
};
private static final String ancientDate;
+ /**
+ * If set to true, we parse cookies strictly according to the servlet,
+ * cookie and HTTP specs by default.
+ */
+ public static final boolean STRICT_SERVLET_COMPLIANCE;
- static {
- ancientDate = OLD_COOKIE_FORMAT.get().format(new Date(10000));
- }
-
/**
- * If set to true, we parse cookies according to the servlet spec,
+ * If set to false, we don't use the IE6/7 Max-Age/Expires work around.
+ * Default is usually true. If STRICT_SERVLET_COMPLIANCE==true then default
+ * is false. Explicitly setting always takes priority.
*/
- public static final boolean VERSION_SWITCH =
-
Boolean.valueOf(System.getProperty("org.apache.tomcat.util.http.ServerCookie.VERSION_SWITCH",
"true")).booleanValue();
+ public static final boolean ALWAYS_ADD_EXPIRES;
/**
- * If set to false, we don't use the IE6/7 Max-Age/Expires work around
+ * If set to true, the <code>/</code> character will be treated as a
+ * separator. Default is usually false. If STRICT_SERVLET_COMPLIANCE==true
+ * then default is true. Explicitly setting always takes priority.
*/
- public static final boolean ALWAYS_ADD_EXPIRES =
-
Boolean.valueOf(System.getProperty("org.apache.tomcat.util.http.ServerCookie.ALWAYS_ADD_EXPIRES",
"false")).booleanValue();
+ public static final boolean FWD_SLASH_IS_SEPARATOR;
+ static {
+ ancientDate = OLD_COOKIE_FORMAT.get().format(new Date(10000));
+
+ STRICT_SERVLET_COMPLIANCE = Boolean.valueOf(System.getProperty(
+ "org.apache.catalina.STRICT_SERVLET_COMPLIANCE",
+ "false")).booleanValue();
+
+
+ String alwaysAddExpires = System.getProperty(
+
"org.apache.tomcat.util.http.ServerCookie.ALWAYS_ADD_EXPIRES");
+ if (alwaysAddExpires == null) {
+ ALWAYS_ADD_EXPIRES = !STRICT_SERVLET_COMPLIANCE;
+ } else {
+ ALWAYS_ADD_EXPIRES =
+ Boolean.valueOf(alwaysAddExpires).booleanValue();
+ }
+
+ String fwdSlashIsSeparator = System.getProperty(
+
"org.apache.tomcat.util.http.ServerCookie.FWD_SLASH_IS_SEPARATOR");
+ if (fwdSlashIsSeparator == null) {
+ FWD_SLASH_IS_SEPARATOR = STRICT_SERVLET_COMPLIANCE;
+ } else {
+ FWD_SLASH_IS_SEPARATOR =
+ Boolean.valueOf(fwdSlashIsSeparator).booleanValue();
+ }
+ }
+
// Note: Servlet Spec =< 2.5 only refers to Netscape and RFC2109,
// not RFC2965
@@ -223,26 +253,6 @@
return true;
}
- /**
- * @deprecated - Not used
- */
- public static boolean checkName( String name ) {
- if (!isToken(name)
- || name.equalsIgnoreCase("Comment") // rfc2019
- || name.equalsIgnoreCase("Discard") // rfc2965
- || name.equalsIgnoreCase("Domain") // rfc2019
- || name.equalsIgnoreCase("Expires") // Netscape
- || name.equalsIgnoreCase("Max-Age") // rfc2019
- || name.equalsIgnoreCase("Path") // rfc2019
- || name.equalsIgnoreCase("Secure") // rfc2019
- || name.equalsIgnoreCase("Version") // rfc2019
- // TODO remaining RFC2965 attributes
- ) {
- return false;
- }
- return true;
- }
-
// -------------------- Cookie parsing tools
@@ -290,12 +300,7 @@
buf.append( name );
buf.append("=");
// Servlet implementation does not check anything else
-
- // Switch version if allowed and a comment has been configured
- if (version == 0 && comment != null && VERSION_SWITCH) {
- version = 1;
- }
-
+
version = maybeQuote2(version, buf, value,true);
// Add version 1 specific information
@@ -319,6 +324,10 @@
// Max-Age=secs ... or use old "Expires" format
// TODO RFC2965 Discard
if (maxAge >= 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) {
@@ -332,9 +341,6 @@
new Date(System.currentTimeMillis() +
maxAge*1000L),
buf, new FieldPosition(0));
- } else {
- buf.append ("; Max-Age=");
- buf.append (maxAge);
}
}
@@ -344,7 +350,13 @@
if (version==0) {
maybeQuote2(version, buf, path);
} else {
- maybeQuote2(version, buf, path, ServerCookie.tspecials2NoSlash, false);
+ if (FWD_SLASH_IS_SEPARATOR) {
+ maybeQuote2(version, buf, path, ServerCookie.tspecials,
+ false);
+ } else {
+ maybeQuote2(version, buf, path,
+ ServerCookie.tspecials2NoSlash, false);
+ }
}
}
@@ -360,21 +372,6 @@
headerBuf.append(buf);
}
- /**
- * @deprecated - Not used
- */
- @Deprecated
- public static void maybeQuote (int version, StringBuffer buf,String value) {
- // special case - a \n or \r shouldn't happen in any case
- if (isToken(value)) {
- buf.append(value);
- } else {
- buf.append('"');
- buf.append(escapeDoubleQuotes(value,0,value.length()));
- buf.append('"');
- }
- }
-
public static boolean alreadyQuoted (String value) {
if (value==null || value.length()==0) return false;
return (value.charAt(0)=='\"' &&
value.charAt(value.length()-1)=='\"');
@@ -403,7 +400,7 @@
buf.append('"');
buf.append(escapeDoubleQuotes(value,1,value.length()-1));
buf.append('"');
- } else if (allowVersionSwitch && VERSION_SWITCH && version==0
&& !isToken2(value, literals)) {
+ } else if (allowVersionSwitch && (!STRICT_SERVLET_COMPLIANCE) &&
version==0 && !isToken2(value, literals)) {
buf.append('"');
buf.append(escapeDoubleQuotes(value,0,value.length()));
buf.append('"');
Modified: trunk/webapps/docs/changelog.xml
===================================================================
--- trunk/webapps/docs/changelog.xml 2009-10-29 23:57:43 UTC (rev 1231)
+++ trunk/webapps/docs/changelog.xml 2009-10-30 16:18:49 UTC (rev 1232)
@@ -155,6 +155,9 @@
<fix>
<bug>47225</bug>: Fix bad length when redirecting in the mapper.
(markt)
</fix>
+ <fix>
+ Cookie separators and quoting compliance fixes. (markt, kkolinko)
+ </fix>
</changelog>
</subsection>
<subsection name="Jasper">