Use RFC6265 rules for cookie path & path matching
authorShane Kearns <ext-shane.2.kearns@nokia.com>
Fri, 8 Jun 2012 13:56:11 +0000 (14:56 +0100)
committerQt by Nokia <qt-info@nokia.com>
Tue, 26 Jun 2012 22:46:35 +0000 (00:46 +0200)
Url encoding of paths is no longer used. This matches the
current release behaviour of Firefox, Chrome and MSIE browsers.
RFC6265 does not allow this type of encoding.

This fixes remaining path test cases in the IETF test suite.
Currently the path0027 test is passed by Firefox but failed by
Chrome and MSIE, so there is a potential compatibility issue.
However it is a corner case with a malformed cookie.

Task-number: QTBUG-15794
Change-Id: I9b02bb5adc32d614f512d314d06f2c60894aa2b0
Reviewed-by: Richard J. Moore <rich@kde.org>
src/network/access/qnetworkcookie.cpp
src/network/access/qnetworkcookiejar.cpp
tests/auto/network/access/qnetworkcookie/tst_qnetworkcookie.cpp

index 306195a..5a75dd5 100644 (file)
@@ -466,7 +466,7 @@ QByteArray QNetworkCookie::toRawForm(RawForm form) const
         }
         if (!d->path.isEmpty()) {
             result += "; path=";
-            result += QUrl::toPercentEncoding(d->path, "/");
+            result += d->path.toUtf8();
         }
     }
     return result;
@@ -954,8 +954,15 @@ QList<QNetworkCookie> QNetworkCookiePrivate::parseSetCookieHeaderLine(const QByt
                     }
                     //if unparsed, ignore the attribute but not the whole cookie (RFC6265 section 5.2.2)
                 } else if (field.first == "path") {
-                    QString path = QUrl::fromPercentEncoding(field.second);
-                    cookie.setPath(path);
+                    if (field.second.startsWith('/')) {
+                        // ### we should treat cookie paths as an octet sequence internally
+                        // However RFC6265 says we should assume UTF-8 for presentation as a string
+                        cookie.setPath(QString::fromUtf8(field.second));
+                    } else {
+                        // if the path doesn't start with '/' then set the default path (RFC6265 section 5.2.4)
+                        // and also IETF test case path0030 which has valid and empty path in the same cookie
+                        cookie.setPath(QString());
+                    }
                 } else if (field.first == "secure") {
                     cookie.setSecure(true);
                 } else if (field.first == "httponly") {
index 9e5dfe0..b4cf8ce 100644 (file)
@@ -140,11 +140,21 @@ void QNetworkCookieJar::setAllCookies(const QList<QNetworkCookie> &cookieList)
 
 static inline bool isParentPath(QString path, QString reference)
 {
-    if (!path.endsWith(QLatin1Char('/')))
-        path += QLatin1Char('/');
-    if (!reference.endsWith(QLatin1Char('/')))
-        reference += QLatin1Char('/');
-    return path.startsWith(reference);
+    if (path.startsWith(reference)) {
+        //The cookie-path and the request-path are identical.
+        if (path.length() == reference.length())
+            return true;
+        //The cookie-path is a prefix of the request-path, and the last
+        //character of the cookie-path is %x2F ("/").
+        if (reference.endsWith('/'))
+            return true;
+        //The cookie-path is a prefix of the request-path, and the first
+        //character of the request-path that is not included in the cookie-
+        //path is a %x2F ("/") character.
+        if (path.at(reference.length()) == '/')
+            return true;
+    }
+    return false;
 }
 
 static inline bool isParentDomain(QString domain, QString reference)
index 77afd23..7896003 100644 (file)
@@ -235,20 +235,24 @@ void tst_QNetworkCookie::parseSingleCookie_data()
     QTest::newRow("path9") << "a=b;path=/foo" << cookie;
 
     // some weird paths:
-    cookie.setPath("/with spaces");
+    cookie.setPath("/with%20spaces");
     QTest::newRow("path-with-spaces") << "a=b;path=/with%20spaces" << cookie;
     QTest::newRow("path-with-spaces2") << "a=b; path=/with%20spaces " << cookie;
-    cookie.setPath("\"/with spaces\"");
-    QTest::newRow("path-with-spaces3") << "a=b; path=\"/with spaces\"" << cookie;
-    QTest::newRow("path-with-spaces4") << "a=b; path = \"/with spaces\" " << cookie;
+    cookie.setPath(QString());
+    QTest::newRow("invalid-path-with-spaces3") << "a=b; path=\"/with spaces\"" << cookie;
+    QTest::newRow("invalid-path-with-spaces4") << "a=b; path = \"/with spaces\" " << cookie;
+    cookie.setPath("/with spaces");
+    QTest::newRow("path-with-spaces5") << "a=b; path=/with spaces" << cookie;
+    QTest::newRow("path-with-spaces6") << "a=b; path = /with spaces " << cookie;
 
     cookie.setPath("/with\"Quotes");
-    QTest::newRow("path-with-quotes") << "a=b; path = /with%22Quotes" << cookie;
-    cookie.setPath("\"/with\\\"Quotes\"");
-    QTest::newRow("path-with-quotes2") << "a=b; path = \"/with\\\"Quotes\"" << cookie;
+    QTest::newRow("path-with-quotes") << "a=b; path = /with\"Quotes" << cookie;
+    cookie.setPath(QString());
+    QTest::newRow("invalid-path-with-quotes2") << "a=b; path = \"/with\\\"Quotes\"" << cookie;
 
     cookie.setPath(QString::fromUtf8("/R\303\251sum\303\251"));
     QTest::newRow("path-with-utf8") << QString::fromUtf8("a=b;path=/R\303\251sum\303\251") << cookie;
+    cookie.setPath("/R%C3%A9sum%C3%A9");
     QTest::newRow("path-with-utf8-2") << "a=b;path=/R%C3%A9sum%C3%A9" << cookie;
 
     cookie.setPath(QString());
@@ -649,12 +653,21 @@ void tst_QNetworkCookie::parseMultipleCookies_data()
     QTest::newRow("invalid-06") << "=b" << list;
     QTest::newRow("invalid-07") << ";path=/" << list;
 
+    // these should be accepted by RFC6265 but ignoring the expires field
     // reason: malformed expiration date string
-    QTest::newRow("invalid-08") << "a=b;expires=" << list;
-    QTest::newRow("invalid-09") << "a=b;expires=foobar" << list;
-    QTest::newRow("invalid-10") << "a=b;expires=foobar, abc" << list;
-    QTest::newRow("invalid-11") << "a=b;expires=foobar, dd-mmm-yyyy hh:mm:ss GMT; path=/" << list;
-    QTest::newRow("invalid-12") << "a=b;expires=foobar, 32-Caz-1999 24:01:60 GMT; path=/" << list;
+    QNetworkCookie datelessCookie;
+    datelessCookie.setName("a");
+    datelessCookie.setValue("b");
+    list << datelessCookie;
+    QTest::newRow("expiration-empty") << "a=b;expires=" << list;
+    QTest::newRow("expiration-invalid-01") << "a=b;expires=foobar" << list;
+    QTest::newRow("expiration-invalid-02") << "a=b;expires=foobar, abc" << list;
+    QTest::newRow("expiration-invalid-03") << "a=b; expires=123" << list; // used to ASSERT
+    datelessCookie.setPath("/");
+    list.clear();
+    list << datelessCookie;
+    QTest::newRow("expiration-invalid-04") << "a=b;expires=foobar, dd-mmm-yyyy hh:mm:ss GMT; path=/" << list;
+    QTest::newRow("expiration-invalid-05") << "a=b;expires=foobar, 32-Caz-1999 24:01:60 GMT; path=/" << list;
 
     // cookies obtained from the network:
     QNetworkCookie cookie;
@@ -697,7 +710,6 @@ void tst_QNetworkCookie::parseMultipleCookies_data()
     cookie.setDomain("!@#$%^&*();:."); // the ';' is actually problematic, because it is a separator
     list = QList<QNetworkCookie>();
     QTest::newRow("domain-non-alpha-numeric") << "NonAlphNumDomName=NonAlphNumDomValue; domain=!@#$%^&*()" << list;
-    QTest::newRow("expiration-3digit1") << "a=b; expires=123" << list; // used to ASSERT
 }
 
 void tst_QNetworkCookie::parseMultipleCookies()