You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@hc.apache.org by ol...@apache.org on 2011/01/10 11:57:30 UTC

svn commit: r1057148 - in /httpcomponents/httpclient/trunk: ./ httpclient/src/main/java/org/apache/http/impl/cookie/ httpclient/src/test/java/org/apache/http/impl/cookie/

Author: olegk
Date: Mon Jan 10 10:57:29 2011
New Revision: 1057148

URL: http://svn.apache.org/viewvc?rev=1057148&view=rev
Log:
Changed Browser-Compatibility and Best-Match cookie policies to emulate the behaviour of FireFox more closely when parsing Netscape style cookies. Comma will no longer be treated as a header element separator if Set-Cookie does not contain a Version attribute mandated by the RFC2109 / RFC 2965 cookie specifications

Modified:
    httpcomponents/httpclient/trunk/RELEASE_NOTES.txt
    httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/cookie/BestMatchSpec.java
    httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/cookie/BrowserCompatSpec.java
    httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/cookie/NetscapeDraftHeaderParser.java
    httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/cookie/NetscapeDraftSpec.java
    httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/cookie/TestBrowserCompatSpec.java
    httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/cookie/TestCookieBestMatchSpec.java
    httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/cookie/TestNetscapeDraftHeaderParser.java

Modified: httpcomponents/httpclient/trunk/RELEASE_NOTES.txt
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/RELEASE_NOTES.txt?rev=1057148&r1=1057147&r2=1057148&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/RELEASE_NOTES.txt (original)
+++ httpcomponents/httpclient/trunk/RELEASE_NOTES.txt Mon Jan 10 10:57:29 2011
@@ -32,6 +32,13 @@ maintained and supported by Apache HttpC
 
 Changelog
 -------------------
+
+* Changed Browser-Compatibility and Best-Match cookie policies to emulate the behaviour of FireFox
+  more closely when parsing Netscape style cookies. Comma will no longer be treated as a header
+  element separator if Set-Cookie does not contain a Version attribute mandated by the 
+  RFC2109 / RFC 2965 cookie specifications.
+  Contributed by Oleg Kalnichevski <olegk at apache.org>
+
 * [HTTPCLIENT-1036] StringBody has incorrect default for characterset. (Default changed 
   to US-ASCII)
   Contributed by Sebastian Bazley <sebb at apache.org>

Modified: httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/cookie/BestMatchSpec.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/cookie/BestMatchSpec.java?rev=1057148&r1=1057147&r2=1057148&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/cookie/BestMatchSpec.java (original)
+++ httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/cookie/BestMatchSpec.java Mon Jan 10 10:57:29 2011
@@ -31,6 +31,7 @@ import java.util.List;
 
 import org.apache.http.annotation.NotThreadSafe;
 
+import org.apache.http.FormattedHeader;
 import org.apache.http.Header;
 import org.apache.http.HeaderElement;
 import org.apache.http.cookie.Cookie;
@@ -39,6 +40,8 @@ import org.apache.http.cookie.CookieSpec
 import org.apache.http.cookie.MalformedCookieException;
 import org.apache.http.cookie.SM;
 import org.apache.http.cookie.SetCookie2;
+import org.apache.http.message.ParserCursor;
+import org.apache.http.util.CharArrayBuffer;
 
 /**
  * 'Meta' cookie specification that picks up a cookie policy based on
@@ -56,7 +59,6 @@ public class BestMatchSpec implements Co
     private RFC2965Spec strict; // @NotThreadSafe
     private RFC2109Spec obsoleteStrict; // @NotThreadSafe
     private BrowserCompatSpec compat; // @NotThreadSafe
-    private NetscapeDraftSpec netscape; // @NotThreadSafe
 
     public BestMatchSpec(final String[] datepatterns, boolean oneHeader) {
         super();
@@ -89,13 +91,6 @@ public class BestMatchSpec implements Co
         return compat;
     }
 
-    private NetscapeDraftSpec getNetscape() {
-        if (this.netscape == null) {
-            this.netscape = new NetscapeDraftSpec(this.datepatterns);
-        }
-        return netscape;
-    }
-
     public List<Cookie> parse(
             final Header header,
             final CookieOrigin origin) throws MalformedCookieException {
@@ -116,20 +111,34 @@ public class BestMatchSpec implements Co
                netscape = true;
             }
         }
-        // Do we have a cookie with a version attribute?
-        if (versioned) {
+        if (netscape || !versioned) {
+            // Need to parse the header again, because Netscape style cookies do not correctly
+            // support multiple header elements (comma cannot be treated as an element separator)
+            NetscapeDraftHeaderParser parser = NetscapeDraftHeaderParser.DEFAULT;
+            CharArrayBuffer buffer;
+            ParserCursor cursor;
+            if (header instanceof FormattedHeader) {
+                buffer = ((FormattedHeader) header).getBuffer();
+                cursor = new ParserCursor(
+                        ((FormattedHeader) header).getValuePos(),
+                        buffer.length());
+            } else {
+                String s = header.getValue();
+                if (s == null) {
+                    throw new MalformedCookieException("Header value is null");
+                }
+                buffer = new CharArrayBuffer(s.length());
+                buffer.append(s);
+                cursor = new ParserCursor(0, buffer.length());
+            }
+            helems = new HeaderElement[] { parser.parseHeader(buffer, cursor) };
+            return getCompat().parse(helems, origin);
+        } else {
             if (SM.SET_COOKIE2.equals(header.getName())) {
                 return getStrict().parse(helems, origin);
             } else {
                 return getObsoleteStrict().parse(helems, origin);
             }
-        } else if (netscape) {
-            // Need to parse the header again,
-            // because Netscape draft cannot handle
-            // comma separators
-            return getNetscape().parse(header, origin);
-        } else {
-            return getCompat().parse(helems, origin);
         }
     }
 

Modified: httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/cookie/BrowserCompatSpec.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/cookie/BrowserCompatSpec.java?rev=1057148&r1=1057147&r2=1057148&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/cookie/BrowserCompatSpec.java (original)
+++ httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/cookie/BrowserCompatSpec.java Mon Jan 10 10:57:29 2011
@@ -29,7 +29,6 @@ package org.apache.http.impl.cookie;
 
 import java.util.ArrayList;
 import java.util.List;
-import java.util.Locale;
 
 import org.apache.http.annotation.NotThreadSafe;
 
@@ -124,28 +123,24 @@ public class BrowserCompatSpec extends C
             throw new IllegalArgumentException("Cookie origin may not be null");
         }
         String headername = header.getName();
-        String headervalue = header.getValue();
         if (!headername.equalsIgnoreCase(SM.SET_COOKIE)) {
             throw new MalformedCookieException("Unrecognized cookie header '"
                     + header.toString() + "'");
         }
-        boolean isNetscapeCookie = false;
-        int i1 = headervalue.toLowerCase(Locale.ENGLISH).indexOf("expires=");
-        if (i1 != -1) {
-            i1 += "expires=".length();
-            int i2 = headervalue.indexOf(';', i1);
-            if (i2 == -1) {
-                i2 = headervalue.length();
+        HeaderElement[] helems = header.getElements();
+        boolean versioned = false;
+        boolean netscape = false;
+        for (HeaderElement helem: helems) {
+            if (helem.getParameterByName("version") != null) {
+                versioned = true;
             }
-            try {
-                DateUtils.parseDate(headervalue.substring(i1, i2), this.datepatterns);
-                isNetscapeCookie = true;
-            } catch (DateParseException e) {
-                // Does not look like a valid expiry date
+            if (helem.getParameterByName("expires") != null) {
+               netscape = true;
             }
         }
-        HeaderElement[] elems = null;
-        if (isNetscapeCookie) {
+        if (netscape || !versioned) {
+            // Need to parse the header again, because Netscape style cookies do not correctly
+            // support multiple header elements (comma cannot be treated as an element separator)
             NetscapeDraftHeaderParser parser = NetscapeDraftHeaderParser.DEFAULT;
             CharArrayBuffer buffer;
             ParserCursor cursor;
@@ -163,11 +158,9 @@ public class BrowserCompatSpec extends C
                 buffer.append(s);
                 cursor = new ParserCursor(0, buffer.length());
             }
-            elems = new HeaderElement[] { parser.parseHeader(buffer, cursor) };
-        } else {
-            elems = header.getElements();
+            helems = new HeaderElement[] { parser.parseHeader(buffer, cursor) };
         }
-        return parse(elems, origin);
+        return parse(helems, origin);
     }
 
     public List<Header> formatCookies(final List<Cookie> cookies) {

Modified: httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/cookie/NetscapeDraftHeaderParser.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/cookie/NetscapeDraftHeaderParser.java?rev=1057148&r1=1057147&r2=1057148&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/cookie/NetscapeDraftHeaderParser.java (original)
+++ httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/cookie/NetscapeDraftHeaderParser.java Mon Jan 10 10:57:29 2011
@@ -36,8 +36,9 @@ import org.apache.http.HeaderElement;
 import org.apache.http.NameValuePair;
 import org.apache.http.ParseException;
 import org.apache.http.message.BasicHeaderElement;
-import org.apache.http.message.BasicHeaderValueParser;
+import org.apache.http.message.BasicNameValuePair;
 import org.apache.http.message.ParserCursor;
+import org.apache.http.protocol.HTTP;
 import org.apache.http.util.CharArrayBuffer;
 
 /**
@@ -49,13 +50,8 @@ public class NetscapeDraftHeaderParser {
 
     public final static NetscapeDraftHeaderParser DEFAULT = new NetscapeDraftHeaderParser();
 
-    private final static char[] DELIMITERS = new char[] { ';' };
-
-    private final BasicHeaderValueParser nvpParser;
-
     public NetscapeDraftHeaderParser() {
         super();
-        this.nvpParser = BasicHeaderValueParser.DEFAULT;
     }
 
     public HeaderElement parseHeader(
@@ -67,10 +63,10 @@ public class NetscapeDraftHeaderParser {
         if (cursor == null) {
             throw new IllegalArgumentException("Parser cursor may not be null");
         }
-        NameValuePair nvp = this.nvpParser.parseNameValuePair(buffer, cursor, DELIMITERS);
+        NameValuePair nvp = parseNameValuePair(buffer, cursor);
         List<NameValuePair> params = new ArrayList<NameValuePair>();
         while (!cursor.atEnd()) {
-            NameValuePair param = this.nvpParser.parseNameValuePair(buffer, cursor, DELIMITERS);
+            NameValuePair param = parseNameValuePair(buffer, cursor);
             params.add(param);
         }
         return new BasicHeaderElement(
@@ -78,4 +74,69 @@ public class NetscapeDraftHeaderParser {
                 nvp.getValue(), params.toArray(new NameValuePair[params.size()]));
     }
 
+    private NameValuePair parseNameValuePair(
+            final CharArrayBuffer buffer, final ParserCursor cursor) {
+        boolean terminated = false;
+
+        int pos = cursor.getPos();
+        int indexFrom = cursor.getPos();
+        int indexTo = cursor.getUpperBound();
+
+        // Find name
+        String name = null;
+        while (pos < indexTo) {
+            char ch = buffer.charAt(pos);
+            if (ch == '=') {
+                break;
+            }
+            if (ch == ';') {
+                terminated = true;
+                break;
+            }
+            pos++;
+        }
+
+        if (pos == indexTo) {
+            terminated = true;
+            name = buffer.substringTrimmed(indexFrom, indexTo);
+        } else {
+            name = buffer.substringTrimmed(indexFrom, pos);
+            pos++;
+        }
+
+        if (terminated) {
+            cursor.updatePos(pos);
+            return new BasicNameValuePair(name, null);
+        }
+
+        // Find value
+        String value = null;
+        int i1 = pos;
+
+        while (pos < indexTo) {
+            char ch = buffer.charAt(pos);
+            if (ch == ';') {
+                terminated = true;
+                break;
+            }
+            pos++;
+        }
+
+        int i2 = pos;
+        // Trim leading white spaces
+        while (i1 < i2 && (HTTP.isWhitespace(buffer.charAt(i1)))) {
+            i1++;
+        }
+        // Trim trailing white spaces
+        while ((i2 > i1) && (HTTP.isWhitespace(buffer.charAt(i2 - 1)))) {
+            i2--;
+        }
+        value = buffer.substring(i1, i2);
+        if (terminated) {
+            pos++;
+        }
+        cursor.updatePos(pos);
+        return new BasicNameValuePair(name, value);
+    }
+
 }

Modified: httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/cookie/NetscapeDraftSpec.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/cookie/NetscapeDraftSpec.java?rev=1057148&r1=1057147&r2=1057148&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/cookie/NetscapeDraftSpec.java (original)
+++ httpcomponents/httpclient/trunk/httpclient/src/main/java/org/apache/http/impl/cookie/NetscapeDraftSpec.java Mon Jan 10 10:57:29 2011
@@ -94,13 +94,11 @@ public class NetscapeDraftSpec extends C
       *  Set-Cookie: NAME=VALUE; expires=DATE; path=PATH; domain=DOMAIN_NAME; secure
       * </PRE>
       *
-      * <p>Please note that Netscape draft specification does not fully
-      * conform to the HTTP header format. Netscape draft does not specify
-      * whether multiple cookies may be sent in one header. Hence, comma
-      * character may be present in unquoted cookie value or unquoted
-      * parameter value.</p>
+      * <p>Please note that the Netscape draft specification does not fully conform to the HTTP
+      * header format. Comma character if present in <code>Set-Cookie</code> will not be treated
+      * as a header element separator</p>
       *
-      * @see <a href="http://wp.netscape.com/newsref/std/cookie_spec.html">
+      * @see <a href="http://web.archive.org/web/20020803110822/http://wp.netscape.com/newsref/std/cookie_spec.html">
       *  The Cookie Spec.</a>
       *
       * @param header the <tt>Set-Cookie</tt> received from the server

Modified: httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/cookie/TestBrowserCompatSpec.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/cookie/TestBrowserCompatSpec.java?rev=1057148&r1=1057147&r2=1057148&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/cookie/TestBrowserCompatSpec.java (original)
+++ httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/cookie/TestBrowserCompatSpec.java Mon Jan 10 10:57:29 2011
@@ -328,7 +328,7 @@ public class TestBrowserCompatSpec {
         }
         Assert.assertEquals("Found 1 cookie.",1,cookies.size());
         Assert.assertEquals("Name","cookie-name",cookies.get(0).getName());
-        Assert.assertEquals("Value"," cookie-value ",cookies.get(0).getValue());
+        Assert.assertEquals("Value","\" cookie-value \"",cookies.get(0).getValue());
         Assert.assertEquals("Domain","127.0.0.1",cookies.get(0).getDomain());
         Assert.assertEquals("Path","/",cookies.get(0).getPath());
         Assert.assertTrue("Secure",!cookies.get(0).isSecure());
@@ -414,7 +414,7 @@ public class TestBrowserCompatSpec {
         Assert.assertEquals("Path","/",cookies.get(0).getPath());
         Assert.assertTrue("Secure",!cookies.get(0).isSecure());
         Assert.assertTrue("ExpiryDate",null == cookies.get(0).getExpiryDate());
-        Assert.assertEquals("Comment","This is a comment.",cookies.get(0).getComment());
+        Assert.assertEquals("Comment","\"This is a comment.\"",cookies.get(0).getComment());
     }
 
     @Test
@@ -927,7 +927,7 @@ public class TestBrowserCompatSpec {
     @Test
     public void testFormatSeveralCookies() throws Exception {
         Header header = new BasicHeader("Set-Cookie",
-            "name1=value1; path=/; domain=.mydomain.com, name2 = value2 ; path=/; domain=.mydomain.com");
+            "name1=value1; path=/; domain=.mydomain.com, name2 = value2 ; path=/; domain=.mydomain.com; version=0");
         CookieSpec cookiespec = new BrowserCompatSpec();
         CookieOrigin origin = new CookieOrigin("myhost.mydomain.com", 80, "/", false);
         List<Cookie> cookies = cookiespec.parse(header, origin);

Modified: httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/cookie/TestCookieBestMatchSpec.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/cookie/TestCookieBestMatchSpec.java?rev=1057148&r1=1057147&r2=1057148&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/cookie/TestCookieBestMatchSpec.java (original)
+++ httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/cookie/TestCookieBestMatchSpec.java Mon Jan 10 10:57:29 2011
@@ -70,16 +70,12 @@ public class TestCookieBestMatchSpec {
             "name=value; path=/; domain=.mydomain.com; expires=Thu, 01-Jan-2070 00:00:10 GMT; comment=no_comment");
         List<Cookie> cookies = cookiespec.parse(header, origin);
         cookiespec.validate(cookies.get(0), origin);
-
+        Assert.assertEquals(1, cookies.size());
         header = new BasicHeader("Set-Cookie",
             "name=value; path=/; domain=.mydomain.com; expires=Thu, 01-Jan-2070 00:00:10 GMT; version=1");
-        try {
-            cookies = cookiespec.parse(header, origin);
-            cookiespec.validate(cookies.get(0), origin);
-            Assert.fail("MalformedCookieException exception should have been thrown");
-        } catch (MalformedCookieException e) {
-            // expected
-        }
+        cookies = cookiespec.parse(header, origin);
+        cookiespec.validate(cookies.get(0), origin);
+        Assert.assertEquals(1, cookies.size());
     }
 
     @Test

Modified: httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/cookie/TestNetscapeDraftHeaderParser.java
URL: http://svn.apache.org/viewvc/httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/cookie/TestNetscapeDraftHeaderParser.java?rev=1057148&r1=1057147&r2=1057148&view=diff
==============================================================================
--- httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/cookie/TestNetscapeDraftHeaderParser.java (original)
+++ httpcomponents/httpclient/trunk/httpclient/src/test/java/org/apache/http/impl/cookie/TestNetscapeDraftHeaderParser.java Mon Jan 10 10:57:29 2011
@@ -43,8 +43,8 @@ public class TestNetscapeDraftHeaderPars
     public void testNetscapeCookieParsing() throws Exception {
         NetscapeDraftHeaderParser parser = NetscapeDraftHeaderParser.DEFAULT;
 
-        String s =
-            "name  = value; test; test1 =  stuff,with,commas   ; test2 =  \"stuff; stuff\"; test3=\"stuff";
+        String s = "name  = value; test; test1 =  stuff,with,commas   ;" +
+                " test2 =  \"stuff, stuff\"; test3=\"stuff";
         CharArrayBuffer buffer = new CharArrayBuffer(16);
         buffer.append(s);
         ParserCursor cursor = new ParserCursor(0, s.length());
@@ -58,7 +58,7 @@ public class TestNetscapeDraftHeaderPars
         Assert.assertEquals("test1", params[1].getName());
         Assert.assertEquals("stuff,with,commas", params[1].getValue());
         Assert.assertEquals("test2", params[2].getName());
-        Assert.assertEquals("stuff; stuff", params[2].getValue());
+        Assert.assertEquals("\"stuff, stuff\"", params[2].getValue());
         Assert.assertEquals("test3", params[3].getName());
         Assert.assertEquals("\"stuff", params[3].getValue());
         Assert.assertEquals(s.length(), cursor.getPos());