diff --git a/VERSION.txt b/VERSION.txt index 9289ad7dc29..a8bff1116f4 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -8,6 +8,7 @@ jetty-7.1.4-SNAPSHOT + 314299 Create test harness for JDBCLoginService + 314581 Implement the Sec-Websocket handshake + 315190 CrossOriginFilter adds headers not understood by Chrome 5 WebSocket implementation + + 315715 Improved Cookie version handling. Server.setMaxCookieVersion jetty-7.1.3.v20100526 + 296567 HttpClient RedirectListener handles new HttpDestination diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java index 5ef0bb7a69e..0856aca40df 100644 --- a/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java +++ b/jetty-http/src/main/java/org/eclipse/jetty/http/HttpFields.java @@ -283,17 +283,15 @@ public class HttpFields }; - - - - public final static String __01Jan1970=formatCookieDate(0); public final static Buffer __01Jan1970_BUFFER=new ByteArrayBuffer(__01Jan1970); /* -------------------------------------------------------------- */ - protected final ArrayList _fields = new ArrayList(20); - protected final HashMap _bufferMap = new HashMap(32); - protected int _revision; + private final ArrayList _fields = new ArrayList(20); + private final HashMap _bufferMap = new HashMap(32); + private final int _maxCookieVersion; + private int _revision; + @@ -303,8 +301,18 @@ public class HttpFields */ public HttpFields() { + _maxCookieVersion=1; } + /* ------------------------------------------------------------ */ + /** + * Constructor. + */ + public HttpFields(int maxCookieVersion) + { + _maxCookieVersion=maxCookieVersion; + } + /* -------------------------------------------------------------- */ /** * Get enumeration of header _names. Returns an enumeration of strings representing the header @@ -973,20 +981,29 @@ public class HttpFields final String comment, final boolean isSecure, final boolean isHttpOnly, - final int version) + int version) { + String delim=_maxCookieVersion==0?"":"\"\\\n\r\t\f\b%+ ;="; + // Check arguments if (name == null || name.length() == 0) throw new IllegalArgumentException("Bad cookie name"); // Format value and params StringBuilder buf = new StringBuilder(128); String name_value_params; - QuotedStringTokenizer.quoteIfNeeded(buf, name); + boolean quoted = QuotedStringTokenizer.quoteIfNeeded(buf, name, delim); buf.append('='); String start=buf.toString(); if (value != null && value.length() > 0) - QuotedStringTokenizer.quoteIfNeeded(buf, value); + quoted|=QuotedStringTokenizer.quoteIfNeeded(buf, value, delim); + + // upgrade to version 1 cookies if quoted. + if (quoted&&version==0 && _maxCookieVersion>=1) + version=1; + if (version>_maxCookieVersion) + version=_maxCookieVersion; + if (version > 0) { buf.append(";Version="); @@ -994,7 +1011,7 @@ public class HttpFields if (comment != null && comment.length() > 0) { buf.append(";Comment="); - QuotedStringTokenizer.quoteIfNeeded(buf, comment); + QuotedStringTokenizer.quoteIfNeeded(buf, comment, delim); } } if (path != null && path.length() > 0) @@ -1003,25 +1020,24 @@ public class HttpFields if (path.trim().startsWith("\"")) buf.append(path); else - QuotedStringTokenizer.quoteIfNeeded(buf,path); + QuotedStringTokenizer.quoteIfNeeded(buf,path,delim); } if (domain != null && domain.length() > 0) { buf.append(";Domain="); - QuotedStringTokenizer.quoteIfNeeded(buf,domain.toLowerCase()); + QuotedStringTokenizer.quoteIfNeeded(buf,domain.toLowerCase(),delim); } if (maxAge >= 0) { - if (version == 0) - { - buf.append(";Expires="); - if (maxAge == 0) - buf.append(__01Jan1970); - else - formatCookieDate(buf, System.currentTimeMillis() + 1000L * maxAge); - } - else + // Always add the expires param as some browsers still don't handle max-age + buf.append(";Expires="); + if (maxAge == 0) + buf.append(__01Jan1970); + else + formatCookieDate(buf, System.currentTimeMillis() + 1000L * maxAge); + + if (version >0) { buf.append(";Max-Age="); buf.append(maxAge); @@ -1039,7 +1055,6 @@ public class HttpFields // TODO - straight to Buffer? name_value_params = buf.toString(); - put(HttpHeaders.EXPIRES_BUFFER, __01Jan1970_BUFFER); // look for existing cookie Field field = getField(HttpHeaders.SET_COOKIE_BUFFER); @@ -1059,6 +1074,9 @@ public class HttpFields } add(HttpHeaders.SET_COOKIE_BUFFER, new ByteArrayBuffer(name_value_params)); + + // Expire responses with set-cookie headers so they do not get cached. + put(HttpHeaders.EXPIRES_BUFFER, __01Jan1970_BUFFER); } /* -------------------------------------------------------------- */ diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java index 79c36c725f3..80860841985 100644 --- a/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java +++ b/jetty-http/src/test/java/org/eclipse/jetty/http/HttpFieldsTest.java @@ -13,6 +13,7 @@ package org.eclipse.jetty.http; +import java.net.CookieHandler; import java.util.Enumeration; import java.util.HashSet; import java.util.Set; @@ -367,8 +368,19 @@ public class HttpFieldsTest fields.clear(); fields.addSetCookie("ev erything","va lue","do main","pa th",1,"co mment",true,true,2); - assertEquals("\"ev erything\"=\"va lue\";Version=2;Comment=\"co mment\";Path=\"pa th\";Domain=\"do main\";Max-Age=1;Secure;HttpOnly",fields.getStringField("Set-Cookie")); + String setCookie=fields.getStringField("Set-Cookie"); + assertTrue(setCookie.startsWith("\"ev erything\"=\"va lue\";Version=1;Comment=\"co mment\";Path=\"pa th\";Domain=\"do main\";Expires=")); + assertTrue(setCookie.endsWith("GMT;Max-Age=1;Secure;HttpOnly")); + fields.clear(); + fields.addSetCookie("name","value",null,null,-1,null,false,false,0); + setCookie=fields.getStringField("Set-Cookie"); + assertEquals(-1,setCookie.indexOf("Version=")); + fields.clear(); + fields.addSetCookie("name","v a l u e",null,null,-1,null,false,false,0); + setCookie=fields.getStringField("Set-Cookie"); + assertEquals(17,setCookie.indexOf("Version=1")); + fields.clear(); fields.addSetCookie("json","{\"services\":[\"cwa\", \"aa\"]}",null,null,-1,null,false,false,-1); assertEquals("json=\"{\\\"services\\\":[\\\"cwa\\\", \\\"aa\\\"]}\"",fields.getStringField("Set-Cookie")); @@ -386,6 +398,12 @@ public class HttpFieldsTest Enumeration e=fields.getValues("Set-Cookie"); assertEquals("name=more;Domain=domain",e.nextElement()); assertEquals("foo=bob;Domain=domain",e.nextElement()); + + fields=new HttpFields(0); + fields.addSetCookie("name","value==",null,null,-1,null,false,false,0); + setCookie=fields.getStringField("Set-Cookie"); + assertEquals("name=value==",setCookie); + } private Set enum2set(Enumeration e) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java index 336b5ac5139..e08dcf65a7a 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java @@ -147,7 +147,7 @@ public class HttpConnection implements Connection HttpBuffers ab = (HttpBuffers)_connector; _parser = new HttpParser(ab.getRequestBuffers(), endpoint, new RequestHandler()); _requestFields = new HttpFields(); - _responseFields = new HttpFields(); + _responseFields = new HttpFields(server.getMaxCookieVersion()); _request = new Request(this); _response = new Response(this); _generator = new HttpGenerator(ab.getResponseBuffers(), _endp); @@ -164,7 +164,7 @@ public class HttpConnection implements Connection _endp = endpoint; _parser = parser; _requestFields = new HttpFields(); - _responseFields = new HttpFields(); + _responseFields = new HttpFields(server.getMaxCookieVersion()); _request = request; _response = new Response(this); _generator = generator; @@ -1175,13 +1175,13 @@ public class HttpConnection implements Connection else { _responseFields.put(HttpHeaders.CONTENT_TYPE_BUFFER, - contentType+";charset="+QuotedStringTokenizer.quote(enc,";= ")); + contentType+";charset="+QuotedStringTokenizer.quoteIfNeeded(enc,";= ")); } } else { _responseFields.put(HttpHeaders.CONTENT_TYPE_BUFFER, - contentType+";charset="+QuotedStringTokenizer.quote(enc,";= ")); + contentType+";charset="+QuotedStringTokenizer.quoteIfNeeded(enc,";= ")); } } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java index 9fe3d70a887..5e6e8f09663 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Response.java @@ -711,7 +711,7 @@ public class Response implements HttpServletResponse if (_contentType==null) { - _contentType = _mimeType+";charset="+QuotedStringTokenizer.quote(_characterEncoding,";= "); + _contentType = _mimeType+";charset="+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= "); _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType); } } @@ -720,16 +720,16 @@ public class Response implements HttpServletResponse int i1=_contentType.indexOf("charset=",i0); if (i1<0) { - _contentType = _contentType+";charset="+QuotedStringTokenizer.quote(_characterEncoding,";= "); + _contentType = _contentType+";charset="+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= "); } else { int i8=i1+8; int i2=_contentType.indexOf(" ",i8); if (i2<0) - _contentType=_contentType.substring(0,i8)+QuotedStringTokenizer.quote(_characterEncoding,";= "); + _contentType=_contentType.substring(0,i8)+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= "); else - _contentType=_contentType.substring(0,i8)+QuotedStringTokenizer.quote(_characterEncoding,";= ")+_contentType.substring(i2); + _contentType=_contentType.substring(0,i8)+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= ")+_contentType.substring(i2); } _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType); } @@ -857,12 +857,12 @@ public class Response implements HttpServletResponse } else if (i2<0) { - _contentType=contentType.substring(0,i1)+";charset="+QuotedStringTokenizer.quote(_characterEncoding,";= "); + _contentType=contentType.substring(0,i1)+";charset="+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= "); _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType); } else { - _contentType=contentType.substring(0,i1)+contentType.substring(i2)+";charset="+QuotedStringTokenizer.quote(_characterEncoding,";= "); + _contentType=contentType.substring(0,i1)+contentType.substring(i2)+";charset="+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= "); _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType); } } @@ -908,7 +908,7 @@ public class Response implements HttpServletResponse else // No encoding in the params. { _cachedMimeType=null; - _contentType=_characterEncoding==null?contentType:contentType+";charset="+QuotedStringTokenizer.quote(_characterEncoding,";= "); + _contentType=_characterEncoding==null?contentType:contentType+";charset="+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= "); _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType); } } @@ -929,13 +929,13 @@ public class Response implements HttpServletResponse } else { - _contentType=_mimeType+";charset="+QuotedStringTokenizer.quote(_characterEncoding,";= "); + _contentType=_mimeType+";charset="+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= "); _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType); } } else { - _contentType=contentType+";charset="+QuotedStringTokenizer.quote(_characterEncoding,";= "); + _contentType=contentType+";charset="+QuotedStringTokenizer.quoteIfNeeded(_characterEncoding,";= "); _connection.getResponseFields().put(HttpHeaders.CONTENT_TYPE_BUFFER,_contentType); } } diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java index b0d70160ec8..9c380253e6c 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Server.java @@ -69,7 +69,9 @@ public class Server extends HandlerWrapper implements Attributes private boolean _sendDateHeader = false; //send Date: header private int _graceful=0; private boolean _stopAtShutdown; + private int _maxCookieVersion=1; + /* ------------------------------------------------------------ */ public Server() { @@ -433,7 +435,24 @@ public class Server extends HandlerWrapper implements Attributes { return _sendDateHeader; } - + + /* ------------------------------------------------------------ */ + /** Get the maximum cookie version. + * @return the maximum set-cookie version sent by this server + */ + public int getMaxCookieVersion() + { + return _maxCookieVersion; + } + + /* ------------------------------------------------------------ */ + /** Set the maximum cookie version. + * @param maxCookieVersion the maximum set-cookie version sent by this server + */ + public void setMaxCookieVersion(int maxCookieVersion) + { + _maxCookieVersion = maxCookieVersion; + } /* ------------------------------------------------------------ */ /** diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/QuotedStringTokenizer.java b/jetty-util/src/main/java/org/eclipse/jetty/util/QuotedStringTokenizer.java index aaff8a5946f..78689e24090 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/QuotedStringTokenizer.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/QuotedStringTokenizer.java @@ -267,7 +267,7 @@ public class QuotedStringTokenizer * @param s The string to quote. * @return quoted string */ - public static String quote(String s, String delim) + public static String quoteIfNeeded(String s, String delim) { if (s==null) return null; @@ -413,88 +413,30 @@ public class QuotedStringTokenizer /* ------------------------------------------------------------ */ - /** Quote a string into a StringBuffer. - * The characters ", \, \n, \r, \t, \f, \b are escaped. - * Quotes are forced if any escaped characters are present or there - * is a ", ', space, +, =, ; or % character. + /** Quote a string into a StringBuffer only if needed. + * Quotes are forced if any delim characters are present. * * @param buf The StringBuffer * @param s The String to quote. + * @param delim String of characters that must be quoted. + * @return true if quoted; */ - public static void quoteIfNeeded(Appendable buf, String s) + public static boolean quoteIfNeeded(Appendable buf, String s,String delim) { + for (int i=0;i=0) + { + quote(buf,s); + return true; + } + } + try { - int e=-1; - - search: for (int i=0;i