/* * 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. /** * @constructor */ WebInspector.CookieParser = function() { } /** * @constructor * @param {string} key * @param {string|undefined} value * @param {number} position */ WebInspector.CookieParser.KeyValue = function(key, value, position) { this.key = key; this.value = value; this.position = position; } WebInspector.CookieParser.prototype = { /** * @return {Array.} */ cookies: function() { return this._cookies; }, /** * @param {string|undefined} cookieHeader * @return {?Array.} */ parseCookie: function(cookieHeader) { if (!this._initialize(cookieHeader)) return null; 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, WebInspector.Cookie.Type.Request); this._advanceAndCheckCookieDelimiter(); } this._flushCookie(); return this._cookies; }, /** * @param {string|undefined} setCookieHeader * @return {?Array.} */ parseSetCookie: function(setCookieHeader) { if (!this._initialize(setCookieHeader)) return null; for (var kv = this._extractKeyValue(); kv; kv = this._extractKeyValue()) { if (this._lastCookie) this._lastCookie.addAttribute(kv.key, kv.value); else this._addCookie(kv, WebInspector.Cookie.Type.Response); if (this._advanceAndCheckCookieDelimiter()) this._flushCookie(); } this._flushCookie(); return this._cookies; }, /** * @param {string|undefined} headerValue * @return {boolean} */ _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.setSize(this._originalInputLength - this._input.length - this._lastCookiePosition); this._lastCookie = null; }, /** * @return {WebInspector.CookieParser.KeyValue} */ _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 = new WebInspector.CookieParser.KeyValue(keyValueMatch[1], keyValueMatch[2] && keyValueMatch[2].trim(), this._originalInputLength - this._input.length); this._input = this._input.slice(keyValueMatch[0].length); return result; }, /** * @return {boolean} */ _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; }, /** * @param {!WebInspector.CookieParser.KeyValue} keyValue * @param {!WebInspector.Cookie.Type} type */ _addCookie: function(keyValue, type) { if (this._lastCookie) this._lastCookie.setSize(keyValue.position - this._lastCookiePosition); // Mozilla bug 169091: Mozilla, IE and Chrome treat single token (w/o "=") as // specifying a value for a cookie with empty name. this._lastCookie = typeof keyValue.value === "string" ? new WebInspector.Cookie(keyValue.key, keyValue.value, type) : new WebInspector.Cookie("", keyValue.key, type); this._lastCookiePosition = keyValue.position; this._cookies.push(this._lastCookie); } }; /** * @param {string|undefined} header * @return {?Array.} */ WebInspector.CookieParser.parseCookie = function(header) { return (new WebInspector.CookieParser()).parseCookie(header); } /** * @param {string|undefined} header * @return {?Array.} */ WebInspector.CookieParser.parseSetCookie = function(header) { return (new WebInspector.CookieParser()).parseSetCookie(header); } /** * @constructor * @param {string} name * @param {string} value * @param {?WebInspector.Cookie.Type} type */ WebInspector.Cookie = function(name, value, type) { this._name = name; this._value = value; this._type = type; this._attributes = {}; } WebInspector.Cookie.prototype = { /** * @return {string} */ name: function() { return this._name; }, /** * @return {string} */ value: function() { return this._value; }, /** * @return {?WebInspector.Cookie.Type} */ type: function() { return this._type; }, /** * @return {boolean} */ httpOnly: function() { return "httponly" in this._attributes; }, /** * @return {boolean} */ secure: function() { return "secure" in this._attributes; }, /** * @return {boolean} */ session: function() { // RFC 2965 suggests using Discard attribute to mark session cookies, but this does not seem to be widely used. // Check for absence of explicitly max-age or expiry date instead. return !("expires" in this._attributes || "max-age" in this._attributes); }, /** * @return {string} */ path: function() { return this._attributes["path"]; }, /** * @return {string} */ port: function() { return this._attributes["port"]; }, /** * @return {string} */ domain: function() { return this._attributes["domain"]; }, /** * @return {string} */ expires: function() { return this._attributes["expires"]; }, /** * @return {string} */ maxAge: function() { return this._attributes["max-age"]; }, /** * @return {number} */ size: function() { return this._size; }, /** * @param {number} size */ setSize: function(size) { this._size = size; }, /** * @return {Date} */ expiresDate: function(requestDate) { // RFC 6265 indicates that the max-age attribute takes precedence over the expires attribute if (this.maxAge()) { var targetDate = requestDate === null ? new Date() : requestDate; return new Date(targetDate.getTime() + 1000 * this.maxAge()); } if (this.expires()) return new Date(this.expires()); return null; }, /** * @return {Object} */ attributes: function() { return this._attributes; }, /** * @param {string} key * @param {string=} value */ addAttribute: function(key, value) { this._attributes[key.toLowerCase()] = value; }, /** * @param {function(?Protocol.Error)=} callback */ remove: function(callback) { PageAgent.deleteCookie(this.name(), (this.secure() ? "https://" : "http://") + this.domain() + this.path(), callback); } } /** * @enum {number} */ WebInspector.Cookie.Type = { Request: 0, Response: 1 }; WebInspector.Cookies = {} /** * @param {function(!Array.)} callback */ WebInspector.Cookies.getCookiesAsync = function(callback) { /** * @param {?Protocol.Error} error * @param {Array.} cookies * @param {string} cookiesString */ function mycallback(error, cookies, cookiesString) { if (error) return; callback(cookies.map(WebInspector.Cookies.buildCookieProtocolObject)); } PageAgent.getCookies(mycallback); } /** * @param {!PageAgent.Cookie} protocolCookie * @return {!WebInspector.Cookie} */ WebInspector.Cookies.buildCookieProtocolObject = function(protocolCookie) { var cookie = new WebInspector.Cookie(protocolCookie.name, protocolCookie.value, null); cookie.addAttribute("domain", protocolCookie["domain"]); cookie.addAttribute("path", protocolCookie["path"]); cookie.addAttribute("port", protocolCookie["port"]); if (protocolCookie["expires"]) cookie.addAttribute("expires", protocolCookie["expires"]); if (protocolCookie["httpOnly"]) cookie.addAttribute("httpOnly"); if (protocolCookie["secure"]) cookie.addAttribute("secure"); cookie.setSize(protocolCookie["size"]); return cookie; } /** * @param {WebInspector.Cookie} cookie * @param {string} resourceURL */ WebInspector.Cookies.cookieMatchesResourceURL = function(cookie, resourceURL) { var url = resourceURL.asParsedURL(); if (!url || !WebInspector.Cookies.cookieDomainMatchesResourceDomain(cookie.domain(), url.host)) return false; return (url.path.startsWith(cookie.path()) && (!cookie.port() || url.port == cookie.port()) && (!cookie.secure() || url.scheme === "https")); } /** * @param {string} cookieDomain * @param {string} resourceDomain */ WebInspector.Cookies.cookieDomainMatchesResourceDomain = function(cookieDomain, resourceDomain) { if (cookieDomain.charAt(0) !== '.') return resourceDomain === cookieDomain; return !!resourceDomain.match(new RegExp("^([^\\.]+\\.)*" + cookieDomain.substring(1).escapeForRegExp() + "$", "i")); }