2010-10-19 Andrey Kosyakov <caseq@chromium.org>
authorcaseq@chromium.org <caseq@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 20 Oct 2010 09:14:09 +0000 (09:14 +0000)
committercaseq@chromium.org <caseq@chromium.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 20 Oct 2010 09:14:09 +0000 (09:14 +0000)
        Reviewed by Pavel Feldman.

        Web Inspector: expose request/response cookies in HAR
        https://bugs.webkit.org/show_bug.cgi?id=47894

        Test: inspector/cookie-parser.html

        * WebCore.gypi:
        * WebCore.vcproj/WebCore.vcproj:
        * inspector/front-end/CookieParser.js: Added.
        * inspector/front-end/HAREntry.js:
        (WebInspector.HAREntry.prototype._buildRequest):
        (WebInspector.HAREntry.prototype._buildResponse):
        (WebInspector.HAREntry.prototype._buildCookies):
        (WebInspector.HAREntry.prototype._buildCookie):
        * inspector/front-end/Resource.js: Parse Cookie/Set-Cookie headers and expose cookies.
        (WebInspector.Resource.prototype.set requestHeaders):
        (WebInspector.Resource.prototype.get requestCookies):
        (WebInspector.Resource.prototype.set responseHeaders):
        (WebInspector.Resource.prototype.get responseCookies):
        (WebInspector.Resource.prototype._parseRequestCookies):
        (WebInspector.Resource.prototype._parseResponseCookies):
        * inspector/front-end/WebKit.qrc:
        * inspector/front-end/inspector.html:

2010-10-19  Andrey Kosyakov  <caseq@chromium.org>

        Reviewed by Pavel Feldman.

        Web Inspector: expose request/response cookies in HAR
        https://bugs.webkit.org/show_bug.cgi?id=47894

        * src/WebResourceRawHeaders.cpp:
        (WebKit::addHeader): paste coalescent headers using "\n: as a separator instead of ", "

2010-10-19  Andrey Kosyakov  <caseq@chromium.org>

        Reviewed by Pavel Feldman.

        Web Inspector: expose request/response cookies in HAR
        https://bugs.webkit.org/show_bug.cgi?id=47894

        * http/tests/inspector/inspector-test.js:
        (dumpObject): Handle null objects as a special case (display null instead of empty object)
        * http/tests/inspector/inspector-test2.js:
        (initialize_InspectorTest.InspectorTest.addObject): Ditto.
        * http/tests/inspector/resource-har-conversion-expected.txt:
        * http/tests/inspector/resource-har-conversion.html:
        * inspector/cookie-parser-expected.txt: Added.
        * inspector/cookie-parser.html: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@70137 268f45cc-cd09-0410-ab3c-d52691b4dbfc

17 files changed:
LayoutTests/ChangeLog
LayoutTests/http/tests/inspector/inspector-test.js
LayoutTests/http/tests/inspector/inspector-test2.js
LayoutTests/http/tests/inspector/resource-har-conversion-expected.txt
LayoutTests/http/tests/inspector/resource-har-conversion.html
LayoutTests/inspector/cookie-parser-expected.txt [new file with mode: 0644]
LayoutTests/inspector/cookie-parser.html [new file with mode: 0755]
WebCore/ChangeLog
WebCore/WebCore.gypi
WebCore/WebCore.vcproj/WebCore.vcproj
WebCore/inspector/front-end/CookieParser.js [new file with mode: 0755]
WebCore/inspector/front-end/HAREntry.js
WebCore/inspector/front-end/Resource.js
WebCore/inspector/front-end/WebKit.qrc
WebCore/inspector/front-end/inspector.html
WebKit/chromium/ChangeLog
WebKit/chromium/src/WebResourceRawHeaders.cpp

index 4d90a9f..97281ec 100644 (file)
@@ -1,3 +1,19 @@
+2010-10-19  Andrey Kosyakov  <caseq@chromium.org>
+
+        Reviewed by Pavel Feldman.
+
+        Web Inspector: expose request/response cookies in HAR
+        https://bugs.webkit.org/show_bug.cgi?id=47894
+
+        * http/tests/inspector/inspector-test.js:
+        (dumpObject): Handle null objects as a special case (display null instead of empty object)
+        * http/tests/inspector/inspector-test2.js:
+        (initialize_InspectorTest.InspectorTest.addObject): Ditto.
+        * http/tests/inspector/resource-har-conversion-expected.txt:
+        * http/tests/inspector/resource-har-conversion.html:
+        * inspector/cookie-parser-expected.txt: Added.
+        * inspector/cookie-parser.html: Added.
+
 2010-10-20  Shinichiro Hamaji  <hamaji@chromium.org>
 
         Reviewed by Kent Tamura.
index f3028fb..f183f02 100755 (executable)
@@ -53,6 +53,8 @@ function dumpObject(object, nondeterministicProps, prefix, firstLinePrefix)
         var propValue = object[prop];
         if (nondeterministicProps && prop in nondeterministicProps)
             output(prefixWithName + "<" + typeof propValue + ">");
+        else if (propValue === null)
+            output(prefixWithName + "null");
         else if (typeof propValue === "object")
             dumpObject(propValue, nondeterministicProps, prefix + "    ", prefixWithName);
         else if (typeof propValue === "string")
index df65d8a..e73672c 100644 (file)
@@ -61,6 +61,8 @@ InspectorTest.addObject = function(object, nondeterministicProps, prefix, firstL
         var propValue = object[prop];
         if (nondeterministicProps && prop in nondeterministicProps)
             InspectorTest.addResult(prefixWithName + "<" + typeof propValue + ">");
+        else if (propValue === null)
+            InspectorTest.addResult(prefixWithName + "null");
         else if (typeof propValue === "object")
             InspectorTest.addObject(propValue, nondeterministicProps, prefix + "    ", prefixWithName);
         else if (typeof propValue === "string")
index 93daf90..8179431 100644 (file)
@@ -12,6 +12,35 @@ Page reloaded.
             headers : <object>
             headersSize : -1
             bodySize : <number>
+            cookies : {
+                0 : {
+                    name : "a"
+                    value : "b"
+                    path : "/path"
+                    domain : "example.com"
+                    expires : null
+                    httpOnly : false
+                    secure : false
+                }
+                1 : {
+                    name : "a1"
+                    value : "b1"
+                    path : undefined
+                    domain : undefined
+                    expires : null
+                    httpOnly : false
+                    secure : false
+                }
+                2 : {
+                    name : "c1"
+                    value : "d1"
+                    path : undefined
+                    domain : undefined
+                    expires : null
+                    httpOnly : false
+                    secure : false
+                }
+            }
         }
         response : {
             status : 304
@@ -24,6 +53,35 @@ Page reloaded.
             redirectURL : ""
             headersSize : -1
             bodySize : <number>
+            cookies : {
+                0 : {
+                    name : "x"
+                    value : "y"
+                    path : "/path"
+                    domain : "example.com"
+                    expires : null
+                    httpOnly : true
+                    secure : true
+                }
+                1 : {
+                    name : "x1"
+                    value : "y1"
+                    path : undefined
+                    domain : undefined
+                    expires : null
+                    httpOnly : false
+                    secure : false
+                }
+                2 : {
+                    name : "z2"
+                    value : "y2"
+                    path : undefined
+                    domain : undefined
+                    expires : null
+                    httpOnly : false
+                    secure : false
+                }
+            }
         }
         timings : <object>
     }
index 14273ad..0593834 100644 (file)
@@ -12,8 +12,27 @@ var test = function()
         return new WebInspector.HAREntry(WebInspector.resources[id]).build();
     }
 
+    function addCookieHeadersToResource(resource)
+    {
+        resource.requestHeaders = {
+            "Cookie": "a=b; $Path=/path; $Domain=example.com; a1=b1\nc1=d1"
+        };
+        resource.responseHeaders = {
+            "Set-Cookie": "x=y; Path=/path; Domain=example.com; Discard; httpOnly; Secure; Version=1\nx1=y1\nz2=y2"
+        };
+    }
+
+    function findResourceByURL(url)
+    {
+        for (var resource in WebInspector.resources) {
+            if (url.test(WebInspector.resources[resource].url))
+                return WebInspector.resources[resource];
+        }
+    }
+
     InspectorTest.enableResourceTracking(function() {
         InjectedScriptAccess.getDefault().evaluate("doXHR()", "console", function() {
+            addCookieHeadersToResource(findResourceByURL(/inspector-test2\.js$/));
             var resources = Object.keys(WebInspector.resources).map(getHAR).sort(InspectorTest.resourceURLComparer);
             InspectorTest.addObject(resources, InspectorTest.HARNondeterministicPropertiesWithSize);
             InspectorTest.disableResourceTracking();
diff --git a/LayoutTests/inspector/cookie-parser-expected.txt b/LayoutTests/inspector/cookie-parser-expected.txt
new file mode 100644 (file)
index 0000000..02bb326
--- /dev/null
@@ -0,0 +1,133 @@
+Tests inspector cookie parser
+
+source: cookie=value
+name: cookie, value: value, httpOnly: false, secure: false, session: true, path: undefined, domain: undefined, port: undefined, expires: n/a, size: 12
+{
+}
+source: $version=1; a=b,c  =   d, e=f
+name: a, value: b,c  =   d, e=f, httpOnly: false, secure: false, session: true, path: undefined, domain: undefined, port: undefined, expires: n/a, size: 17
+{
+}
+source: $version=1; a=b;c  =   d; e =f
+name: a, value: b, httpOnly: false, secure: false, session: true, path: undefined, domain: undefined, port: undefined, expires: n/a, size: 4
+{
+}
+name: c, value: d, httpOnly: false, secure: false, session: true, path: undefined, domain: undefined, port: undefined, expires: n/a, size: 10
+{
+}
+name: e, value: f, httpOnly: false, secure: false, session: true, path: undefined, domain: undefined, port: undefined, expires: n/a, size: 4
+{
+}
+source: cooke1 = value1; another cookie = another value
+name: cooke1, value: value1, httpOnly: false, secure: false, session: true, path: undefined, domain: undefined, port: undefined, expires: n/a, size: 25
+{
+}
+name: cookie, value: another value, httpOnly: false, secure: false, session: true, path: undefined, domain: undefined, port: undefined, expires: n/a, size: 22
+{
+}
+source: cooke1 = value; $Path=/; $Domain=.example.com;
+name: cooke1, value: value, httpOnly: false, secure: false, session: true, path: /, domain: .example.com, port: undefined, expires: n/a, size: 46
+{
+    path : "/"
+    domain : ".example.com"
+}
+source: cooke1 = value; $Path=/; $Domain=.example.com ; Cookie2 = value2; $Path = /foo; $DOMAIN = foo.example.com;
+name: cooke1, value: value, httpOnly: false, secure: false, session: true, path: /, domain: .example.com, port: undefined, expires: n/a, size: 48
+{
+    path : "/"
+    domain : ".example.com"
+}
+name: Cookie2, value: value2, httpOnly: false, secure: false, session: true, path: /foo, domain: foo.example.com, port: undefined, expires: n/a, size: 58
+{
+    path : "/foo"
+    domain : "foo.example.com"
+}
+source: cooke1 = value; $Path=/; $Domain=.example.com
+Cookie2 = value2; $Path = /foo; $DOMAIN = foo.example.com; 
+name: cooke1, value: value, httpOnly: false, secure: false, session: true, path: /, domain: .example.com, port: undefined, expires: n/a, size: 46
+{
+    path : "/"
+    domain : ".example.com"
+}
+name: Cookie2, value: value2, httpOnly: false, secure: false, session: true, path: /foo, domain: foo.example.com, port: undefined, expires: n/a, size: 59
+{
+    path : "/foo"
+    domain : "foo.example.com"
+}
+source: $version =1; cooke1 = value; $Path=/; $Domain   =.example.com;  
+ Cookie2 = value2; $Path = /foo; $DOMAIN = foo.example.com;
+name: cooke1, value: value, httpOnly: false, secure: false, session: true, path: /, domain: .example.com, port: undefined, expires: n/a, size: 53
+{
+    path : "/"
+    domain : ".example.com"
+}
+name: Cookie2, value: value2, httpOnly: false, secure: false, session: true, path: /foo, domain: foo.example.com, port: undefined, expires: n/a, size: 58
+{
+    path : "/foo"
+    domain : "foo.example.com"
+}
+source: cookie=value
+name: cookie, value: value, httpOnly: false, secure: false, session: true, path: undefined, domain: undefined, port: undefined, expires: n/a, size: 12
+{
+}
+source: a=b
+ c=d
+ f
+name: a, value: b, httpOnly: false, secure: false, session: true, path: undefined, domain: undefined, port: undefined, expires: n/a, size: 5
+{
+}
+name: c, value: d, httpOnly: false, secure: false, session: true, path: undefined, domain: undefined, port: undefined, expires: n/a, size: 5
+{
+}
+name: , value: f, httpOnly: false, secure: false, session: true, path: undefined, domain: undefined, port: undefined, expires: n/a, size: 1
+{
+}
+source: cooke1 = value; Path=/; Domain=.example.com;
+name: cooke1, value: value, httpOnly: false, secure: false, session: true, path: /, domain: .example.com, port: undefined, expires: n/a, size: 44
+{
+    path : "/"
+    domain : ".example.com"
+}
+source: cooke1 = value; Path=/; Domain=  .example.com 
+Cookie2 = value2; Path = /foo; Domain = foo.example.com
+name: cooke1, value: value, httpOnly: false, secure: false, session: true, path: /, domain: .example.com, port: undefined, expires: n/a, size: 47
+{
+    path : "/"
+    domain : ".example.com"
+}
+name: Cookie2, value: value2, httpOnly: false, secure: false, session: true, path: /foo, domain: foo.example.com, port: undefined, expires: n/a, size: 55
+{
+    path : "/foo"
+    domain : "foo.example.com"
+}
+source: cooke1 = value; expires = Mon, Oct 18 2010 17:00 GMT+0000; Domain   =.example.com
+Cookie2 = value2; Path = /foo; DOMAIN = foo.example.com; HttpOnly; Secure; Discard;
+name: cooke1, value: value, httpOnly: false, secure: false, session: true, path: undefined, domain: .example.com, port: undefined, expires: 1287421200000, size: 82
+{
+    expires : "Mon, Oct 18 2010 17:00 GMT+0000"
+    domain : ".example.com"
+}
+name: Cookie2, value: value2, httpOnly: true, secure: true, session: true, path: /foo, domain: foo.example.com, port: undefined, expires: n/a, size: 83
+{
+    path : "/foo"
+    domain : "foo.example.com"
+    httponly : undefined
+    secure : undefined
+    discard : undefined
+}
+source: cooke1 = value; max-age= 1440; Domain   =.example.com
+ Cookie2 = value2; Path = /foo; DOMAIN = foo.example.com; HttpOnly; Secure; Discard;
+name: cooke1, value: value, httpOnly: false, secure: false, session: false, path: undefined, domain: .example.com, port: undefined, expires: 1287422640000, size: 55
+{
+    max-age : "1440"
+    domain : ".example.com"
+}
+name: Cookie2, value: value2, httpOnly: true, secure: true, session: true, path: /foo, domain: foo.example.com, port: undefined, expires: n/a, size: 83
+{
+    path : "/foo"
+    domain : "foo.example.com"
+    httponly : undefined
+    secure : undefined
+    discard : undefined
+}
+
diff --git a/LayoutTests/inspector/cookie-parser.html b/LayoutTests/inspector/cookie-parser.html
new file mode 100755 (executable)
index 0000000..fa7d479
--- /dev/null
@@ -0,0 +1,71 @@
+<html>
+<head>
+<script src="../http/tests/inspector/inspector-test2.js"></script>
+<script type="text/javascript">
+
+function initialize_CookieTests()
+{
+
+InspectorTest.dumpCookie = function(cookie)
+{
+    var requestDate = new Date("Mon Oct 18 2010 17:00:00 GMT+0000");
+    var expires = cookie.expires(requestDate);
+
+    var output = "name: " + cookie.name + ", value: " + cookie.value + ", httpOnly: " + cookie.httpOnly +
+        ", secure: " + cookie.secure + ", session: " + cookie.session + ", path: " + cookie.path +
+        ", domain: " + cookie.domain + ", port: " + cookie.port +
+        ", expires: " + (expires ? expires.getTime() : "n/a") +
+        ", size: " + cookie.size;
+
+    InspectorTest.addResult(output);
+    InspectorTest.addObject(cookie.attributes);
+}
+
+InspectorTest.dumpCookies = function(cookies)
+{
+    for (var i = 0; i < cookies.length; ++i)
+        InspectorTest.dumpCookie(cookies[i]);
+}
+
+InspectorTest.parseAndDumpCookie = function(header)
+{
+    var parser = new WebInspector.CookieParser();
+    InspectorTest.addResult("source: " + header);
+    InspectorTest.dumpCookies(parser.parseCookie(header));
+}
+
+InspectorTest.parseAndDumpSetCookie = function(header)
+{
+    var parser = new WebInspector.CookieParser();
+    InspectorTest.addResult("source: " + header);
+    InspectorTest.dumpCookies(parser.parseSetCookie(header));
+}
+
+}
+
+var test = function()
+{
+    InspectorTest.parseAndDumpCookie("cookie=value");
+    InspectorTest.parseAndDumpCookie("$version=1; a=b,c  =   d, e=f");
+    InspectorTest.parseAndDumpCookie("$version=1; a=b;c  =   d; e =f");
+    InspectorTest.parseAndDumpCookie("cooke1 = value1; another cookie = another value");
+    InspectorTest.parseAndDumpCookie("cooke1 = value; $Path=/; $Domain=.example.com;");
+    InspectorTest.parseAndDumpCookie("cooke1 = value; $Path=/; $Domain=.example.com ; Cookie2 = value2; $Path = /foo; $DOMAIN = foo.example.com;");
+    InspectorTest.parseAndDumpCookie("cooke1 = value; $Path=/; $Domain=.example.com\nCookie2 = value2; $Path = /foo; $DOMAIN = foo.example.com; ");
+    InspectorTest.parseAndDumpCookie("$version =1; cooke1 = value; $Path=/; $Domain   =.example.com;  \n Cookie2 = value2; $Path = /foo; $DOMAIN = foo.example.com;");
+
+    InspectorTest.parseAndDumpSetCookie("cookie=value");
+    InspectorTest.parseAndDumpSetCookie("a=b\n c=d\n f");
+    InspectorTest.parseAndDumpSetCookie("cooke1 = value; Path=/; Domain=.example.com;");
+    InspectorTest.parseAndDumpSetCookie("cooke1 = value; Path=/; Domain=  .example.com \nCookie2 = value2; Path = /foo; Domain = foo.example.com");
+    InspectorTest.parseAndDumpSetCookie("cooke1 = value; expires = Mon, Oct 18 2010 17:00 GMT+0000; Domain   =.example.com\nCookie2 = value2; Path = /foo; DOMAIN = foo.example.com; HttpOnly; Secure; Discard;");
+    InspectorTest.parseAndDumpSetCookie("cooke1 = value; max-age= 1440; Domain   =.example.com\n Cookie2 = value2; Path = /foo; DOMAIN = foo.example.com; HttpOnly; Secure; Discard;");
+    InspectorTest.completeTest();
+}
+
+</script>
+</head>
+<body onload="runTest()">
+<p>Tests inspector cookie parser</p>
+</body>
+</html>
index 9dc3719..1925e84 100644 (file)
@@ -1,3 +1,30 @@
+2010-10-19  Andrey Kosyakov  <caseq@chromium.org>
+
+        Reviewed by Pavel Feldman.
+
+        Web Inspector: expose request/response cookies in HAR
+        https://bugs.webkit.org/show_bug.cgi?id=47894
+
+        Test: inspector/cookie-parser.html
+
+        * WebCore.gypi:
+        * WebCore.vcproj/WebCore.vcproj:
+        * inspector/front-end/CookieParser.js: Added.
+        * inspector/front-end/HAREntry.js:
+        (WebInspector.HAREntry.prototype._buildRequest):
+        (WebInspector.HAREntry.prototype._buildResponse):
+        (WebInspector.HAREntry.prototype._buildCookies):
+        (WebInspector.HAREntry.prototype._buildCookie):
+        * inspector/front-end/Resource.js: Parse Cookie/Set-Cookie headers and expose cookies.
+        (WebInspector.Resource.prototype.set requestHeaders):
+        (WebInspector.Resource.prototype.get requestCookies):
+        (WebInspector.Resource.prototype.set responseHeaders):
+        (WebInspector.Resource.prototype.get responseCookies):
+        (WebInspector.Resource.prototype._parseRequestCookies):
+        (WebInspector.Resource.prototype._parseResponseCookies):
+        * inspector/front-end/WebKit.qrc:
+        * inspector/front-end/inspector.html:
+
 2010-10-20  Nikolas Zimmermann  <nzimmermann@rim.com>
 
         Not reviewed.
index 350bdac..48e0ed2 100644 (file)
             'inspector/front-end/ConsoleView.js',
             'inspector/front-end/ContextMenu.js',
             'inspector/front-end/CookieItemsView.js',
+            'inspector/front-end/CookieParser.js',
             'inspector/front-end/CSSCompletions.js',
             'inspector/front-end/CSSStyleModel.js',
             'inspector/front-end/Database.js',
index 488ecda..ebd9296 100644 (file)
                                        >\r
                                </File>\r
                                <File\r
+                                       RelativePath="..\inspector\front-end\CookieItemsView.js"\r
+                                       >\r
+                               </File>\r
+                               <File\r
+                                       RelativePath="..\inspector\front-end\CookieParser.js"\r
+                                       >\r
+                               </File>\r
+                               <File\r
                                        RelativePath="..\inspector\front-end\CSSStyleModel.js"\r
                                        >\r
                                </File>\r
diff --git a/WebCore/inspector/front-end/CookieParser.js b/WebCore/inspector/front-end/CookieParser.js
new file mode 100755 (executable)
index 0000000..2be5df7
--- /dev/null
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2010 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// Ideally, we would rely on platform support for parsing a cookie, since
+// this would save us from any potential inconsistency. However, exposing
+// platform cookie parsing logic would require quite a bit of additional 
+// plumbing, and at least some platforms lack support for parsing Cookie,
+// which is in a format slightly different from Set-Cookie and is normally 
+// only required on the server side.
+
+WebInspector.CookieParser = function()
+{
+}
+
+WebInspector.CookieParser.prototype = {
+    get cookies()
+    {
+        return this._cookies;
+    },
+
+    parseCookie: function(cookieHeader)
+    {
+        if (!this._initialize(cookieHeader))
+            return;
+
+        for (var kv = this._extractKeyValue(); kv; kv = this._extractKeyValue()) {
+            if (kv.key.charAt(0) === "$" && this._lastCookie)
+                this._lastCookie.addAttribute(kv.key.slice(1), kv.value);
+            else if (kv.key.toLowerCase() !== "$version" && typeof kv.value === "string")
+                this._addCookie(kv);
+            this._advanceAndCheckCookieDelimiter();
+        }
+        this._flushCookie();
+        return this._cookies;
+    },
+
+    parseSetCookie: function(setCookieHeader)
+    {
+        if (!this._initialize(setCookieHeader))
+            return;
+        for (var kv = this._extractKeyValue(); kv; kv = this._extractKeyValue()) {
+            if (this._lastCookie)
+                this._lastCookie.addAttribute(kv.key, kv.value);
+            else 
+                this._addCookie(kv);
+            if (this._advanceAndCheckCookieDelimiter())
+                this._flushCookie();
+        }
+        this._flushCookie();
+        return this._cookies;
+    },
+
+    _initialize: function(headerValue)
+    {
+        this._input = headerValue;
+        if (typeof headerValue !== "string")
+            return false;
+        this._cookies = [];
+        this._lastCookie = null;
+        this._originalInputLength = this._input.length;
+        return true;
+    },
+
+    _flushCookie: function()
+    {
+        if (this._lastCookie)
+            this._lastCookie.size = this._originalInputLength - this._input.length - this._lastCookiePosition;
+        this._lastCookie = null;
+    },
+
+    _extractKeyValue: function()
+    {
+        if (!this._input || !this._input.length)
+            return null;
+        // Note: RFCs offer an option for quoted values that may contain commas and semicolons.
+        // Many browsers/platforms do not support this, however (see http://webkit.org/b/16699
+        // and http://crbug.com/12361). The logic below matches latest versions of IE, Firefox,
+        // Chrome and Safari on some old platforms. The latest version of Safari supports quoted
+        // cookie values, though. 
+        var keyValueMatch = /^[ \t]*([^\s=;]+)[ \t]*(?:=[ \t]*([^;\n]*))?/.exec(this._input);
+        if (!keyValueMatch) {
+            console.log("Failed parsing cookie header before: " + this._input);
+            return null;
+        }
+
+        var result = {
+            key: keyValueMatch[1],
+            value: keyValueMatch[2] && keyValueMatch[2].trim(),
+            position: this._originalInputLength - this._input.length
+        };
+        this._input = this._input.slice(keyValueMatch[0].length);
+        return result;
+    },
+
+    _advanceAndCheckCookieDelimiter: function()
+    {
+        var match = /^\s*[\n;]\s*/.exec(this._input);
+        if (!match)
+            return false;
+        this._input = this._input.slice(match[0].length);
+        return match[0].match("\n") !== null;
+    },
+
+    _addCookie: function(keyValue)
+    {
+        if (this._lastCookie)
+            this._lastCookie.size = keyValue.position - this._lastCookiePosition;
+        // Mozilla bug 169091: Mozilla, IE and Chrome treat signle token (w/o "=") as
+        // specifying a value for a cookie with empty name.
+        this._lastCookie = keyValue.value ? new WebInspector.Cookie(keyValue.key, keyValue.value) :
+            new WebInspector.Cookie("", keyValue.key);
+        this._lastCookiePosition = keyValue.position;
+        this._cookies.push(this._lastCookie);
+    }
+};
+
+WebInspector.CookieParser.parseCookie = function(header)
+{
+    return (new WebInspector.CookieParser()).parseCookie(header);
+}
+
+WebInspector.CookieParser.parseSetCookie = function(header)
+{
+    return (new WebInspector.CookieParser()).parseSetCookie(header);
+}
+
+WebInspector.Cookie = function(name, value)
+{
+    this.name = name;
+    this.value = value;
+    this._attributes = {};
+}
+
+WebInspector.Cookie.prototype = {
+    get httpOnly()
+    {
+        return "httponly" in this._attributes;
+    },
+
+    get secure()
+    {
+        return "secure" in this._attributes;
+    },
+
+    get session()
+    {
+        // RFC 2965 suggests using Discard attribute to mark session cookies, but this does not seem to be widely used.
+        // Check for absence of explicity max-age or expiry date instead.
+        return  !("expries" in this._attributes || "max-age" in this._attributes);
+    },
+
+    get path()
+    {
+        return this._attributes.path;
+    },
+
+    get domain()
+    {
+        return this._attributes.domain;
+    },
+
+    expires: function(requestDate)
+    {
+        return this._attributes.expires ? new Date(this._attributes.expires) :
+            (this._attributes["max-age"] ? new Date(requestDate.getTime() + 1000 * this._attributes["max-age"]) : null);
+    },
+
+    get attributes()
+    {
+        return this._attributes;
+    },
+
+    addAttribute: function(key, value)
+    {
+        this._attributes[key.toLowerCase()] = value;
+    }
+}
index b9f75a2..2b8f41b 100644 (file)
@@ -56,7 +56,6 @@ WebInspector.HAREntry.prototype = {
             method: this._resource.requestMethod,
             url: this._resource.url,
             // httpVersion: "HTTP/1.1" -- Not available.
-            // cookies: [] -- Not available.
             headers: this._buildHeaders(this._resource.requestHeaders),
             headersSize: -1, // Not available.
             bodySize: -1 // Not available.
@@ -65,22 +64,26 @@ WebInspector.HAREntry.prototype = {
             res.queryString = this._buildParameters(this._resource.queryParameters);
         if (this._resource.requestFormData)
             res.postData = this._buildPostData();
+        if (this._resource.requestCookies)
+            res.cookies = this._buildCookies(this._resource.requestCookies);
         return res;
     },
 
     _buildResponse: function()
     {
-        return {
+        var res = {
             status: this._resource.statusCode,
             statusText: this._resource.statusText,
             // "httpVersion": "HTTP/1.1" -- Not available.
-            // "cookies": [],  -- Not available.
             headers: this._buildHeaders(this._resource.responseHeaders),
             content: this._buildContent(),
             redirectURL: this._resource.responseHeaderValue("Location") || "",
             headersSize: -1, // Not available.
             bodySize: this._resource.resourceSize
         };
+        if (this._resource.responseCookies)
+            res.cookies = this._buildCookies(this._resource.responseCookies);
+        return res;
     },
 
     _buildContent: function()
@@ -150,6 +153,25 @@ WebInspector.HAREntry.prototype = {
         return parameters.slice();
     },
 
+    _buildCookies: function(cookies)
+    {
+        return cookies.map(this._buildCookie.bind(this));
+    },
+
+    _buildCookie: function(cookie)
+    {
+        
+        return {
+            name: cookie.name,
+            value: cookie.value,
+            path: cookie.path,
+            domain: cookie.domain,
+            expires: cookie.expires(new Date(this._resource.startTime * 1000)),
+            httpOnly: cookie.httpOnly,
+            secure: cookie.secure
+        };
+    },
+
     _interval: function(start, end)
     {
         var timing = this._resource.timing;
index 1848a3a..fe2f7d2 100644 (file)
@@ -350,6 +350,7 @@ WebInspector.Resource.prototype = {
     {
         this._requestHeaders = x;
         delete this._sortedRequestHeaders;
+        delete this._requestCookies;
 
         this.dispatchEventToListeners("requestHeaders changed");
     },
@@ -372,6 +373,13 @@ WebInspector.Resource.prototype = {
         return this._headerValue(this.requestHeaders, headerName);
     },
 
+    get requestCookies()
+    {
+        if (!this._requestCookies)
+            this._requestCookies = WebInspector.CookieParser.parseCookie(this.requestHeaderValue("Cookie"));
+        return this._requestCookies;
+    },
+
     get requestFormData()
     {
         return this._requestFormData;
@@ -392,6 +400,7 @@ WebInspector.Resource.prototype = {
     {
         this._responseHeaders = x;
         delete this._sortedResponseHeaders;
+        delete this._responseCookies;
 
         this.dispatchEventToListeners("responseHeaders changed");
     },
@@ -414,6 +423,13 @@ WebInspector.Resource.prototype = {
         return this._headerValue(this.responseHeaders, headerName);
     },
 
+    get responseCookies()
+    {
+        if (!this._responseCookies)
+            this._responseCookies = WebInspector.CookieParser.parseSetCookie(this.responseHeaderValue("Set-Cookie"));
+        return this._responseCookies;
+    },
+
     get queryParameters()
     {
         if (this._parsedQueryParameters)
index a5933af..1e5a508 100644 (file)
@@ -20,6 +20,7 @@
     <file>ConsoleView.js</file>
     <file>ContextMenu.js</file>
     <file>CookieItemsView.js</file>
+    <file>CookieParser.js</file>
     <file>CSSCompletions.js</file>
     <file>CSSStyleModel.js</file>
     <file>Database.js</file>
index 59a5f79..96d0cfe 100644 (file)
@@ -144,6 +144,7 @@ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     <script type="text/javascript" src="GoToLineDialog.js"></script>
     <script type="text/javascript" src="ShortcutsHelp.js"></script>
     <script type="text/javascript" src="HAREntry.js"></script>
+    <script type="text/javascript" src="CookieParser.js"></script>
 </head>
 <body class="detached">
     <div id="toolbar">
index 541bd26..3fa8ec0 100644 (file)
@@ -1,3 +1,13 @@
+2010-10-19  Andrey Kosyakov  <caseq@chromium.org>
+
+        Reviewed by Pavel Feldman.
+
+        Web Inspector: expose request/response cookies in HAR
+        https://bugs.webkit.org/show_bug.cgi?id=47894
+
+        * src/WebResourceRawHeaders.cpp:
+        (WebKit::addHeader): paste coalescent headers using "\n: as a separator instead of ", "
+
 2010-10-19  Tony Chang  <tony@chromium.org>
 
         Reviewed by Kent Tamura.
index b597ce2..259365e 100644 (file)
@@ -69,7 +69,7 @@ static void addHeader(HTTPHeaderMap* map, const WebString& name, const WebString
 {
     pair<HTTPHeaderMap::iterator, bool> result = map->add(name, value);
     if (!result.second)
-        result.first->second += String("") + value;
+        result.first->second += String("\n") + value;
 }
 
 void WebResourceRawHeaders::addRequestHeader(const WebString& name, const WebString& value)