1/* 2 * Copyright (C) 2010 Google Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google Inc. nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31// Ideally, we would rely on platform support for parsing a cookie, since 32// this would save us from any potential inconsistency. However, exposing 33// platform cookie parsing logic would require quite a bit of additional 34// plumbing, and at least some platforms lack support for parsing Cookie, 35// which is in a format slightly different from Set-Cookie and is normally 36// only required on the server side. 37 38WebInspector.CookieParser = function() 39{ 40} 41 42WebInspector.CookieParser.prototype = { 43 get cookies() 44 { 45 return this._cookies; 46 }, 47 48 parseCookie: function(cookieHeader) 49 { 50 if (!this._initialize(cookieHeader)) 51 return; 52 53 for (var kv = this._extractKeyValue(); kv; kv = this._extractKeyValue()) { 54 if (kv.key.charAt(0) === "$" && this._lastCookie) 55 this._lastCookie.addAttribute(kv.key.slice(1), kv.value); 56 else if (kv.key.toLowerCase() !== "$version" && typeof kv.value === "string") 57 this._addCookie(kv, WebInspector.Cookie.Type.Request); 58 this._advanceAndCheckCookieDelimiter(); 59 } 60 this._flushCookie(); 61 return this._cookies; 62 }, 63 64 parseSetCookie: function(setCookieHeader) 65 { 66 if (!this._initialize(setCookieHeader)) 67 return; 68 for (var kv = this._extractKeyValue(); kv; kv = this._extractKeyValue()) { 69 if (this._lastCookie) 70 this._lastCookie.addAttribute(kv.key, kv.value); 71 else 72 this._addCookie(kv, WebInspector.Cookie.Type.Response); 73 if (this._advanceAndCheckCookieDelimiter()) 74 this._flushCookie(); 75 } 76 this._flushCookie(); 77 return this._cookies; 78 }, 79 80 _initialize: function(headerValue) 81 { 82 this._input = headerValue; 83 if (typeof headerValue !== "string") 84 return false; 85 this._cookies = []; 86 this._lastCookie = null; 87 this._originalInputLength = this._input.length; 88 return true; 89 }, 90 91 _flushCookie: function() 92 { 93 if (this._lastCookie) 94 this._lastCookie.size = this._originalInputLength - this._input.length - this._lastCookiePosition; 95 this._lastCookie = null; 96 }, 97 98 _extractKeyValue: function() 99 { 100 if (!this._input || !this._input.length) 101 return null; 102 // Note: RFCs offer an option for quoted values that may contain commas and semicolons. 103 // Many browsers/platforms do not support this, however (see http://webkit.org/b/16699 104 // and http://crbug.com/12361). The logic below matches latest versions of IE, Firefox, 105 // Chrome and Safari on some old platforms. The latest version of Safari supports quoted 106 // cookie values, though. 107 var keyValueMatch = /^[ \t]*([^\s=;]+)[ \t]*(?:=[ \t]*([^;\n]*))?/.exec(this._input); 108 if (!keyValueMatch) { 109 console.log("Failed parsing cookie header before: " + this._input); 110 return null; 111 } 112 113 var result = { 114 key: keyValueMatch[1], 115 value: keyValueMatch[2] && keyValueMatch[2].trim(), 116 position: this._originalInputLength - this._input.length 117 }; 118 this._input = this._input.slice(keyValueMatch[0].length); 119 return result; 120 }, 121 122 _advanceAndCheckCookieDelimiter: function() 123 { 124 var match = /^\s*[\n;]\s*/.exec(this._input); 125 if (!match) 126 return false; 127 this._input = this._input.slice(match[0].length); 128 return match[0].match("\n") !== null; 129 }, 130 131 _addCookie: function(keyValue, type) 132 { 133 if (this._lastCookie) 134 this._lastCookie.size = keyValue.position - this._lastCookiePosition; 135 // Mozilla bug 169091: Mozilla, IE and Chrome treat single token (w/o "=") as 136 // specifying a value for a cookie with empty name. 137 this._lastCookie = keyValue.value ? new WebInspector.Cookie(keyValue.key, keyValue.value, type) : 138 new WebInspector.Cookie("", keyValue.key, type); 139 this._lastCookiePosition = keyValue.position; 140 this._cookies.push(this._lastCookie); 141 } 142}; 143 144WebInspector.CookieParser.parseCookie = function(header) 145{ 146 return (new WebInspector.CookieParser()).parseCookie(header); 147} 148 149WebInspector.CookieParser.parseSetCookie = function(header) 150{ 151 return (new WebInspector.CookieParser()).parseSetCookie(header); 152} 153 154WebInspector.Cookie = function(name, value, type) 155{ 156 this.name = name; 157 this.value = value; 158 this.type = type; 159 this._attributes = {}; 160} 161 162WebInspector.Cookie.prototype = { 163 get httpOnly() 164 { 165 return "httponly" in this._attributes; 166 }, 167 168 get secure() 169 { 170 return "secure" in this._attributes; 171 }, 172 173 get session() 174 { 175 // RFC 2965 suggests using Discard attribute to mark session cookies, but this does not seem to be widely used. 176 // Check for absence of explicity max-age or expiry date instead. 177 return !("expries" in this._attributes || "max-age" in this._attributes); 178 }, 179 180 get path() 181 { 182 return this._attributes.path; 183 }, 184 185 get domain() 186 { 187 return this._attributes.domain; 188 }, 189 190 expires: function(requestDate) 191 { 192 return this._attributes.expires ? new Date(this._attributes.expires) : 193 (this._attributes["max-age"] ? new Date(requestDate.getTime() + 1000 * this._attributes["max-age"]) : null); 194 }, 195 196 get attributes() 197 { 198 return this._attributes; 199 }, 200 201 addAttribute: function(key, value) 202 { 203 this._attributes[key.toLowerCase()] = value; 204 } 205} 206 207WebInspector.Cookie.Type = { 208 Request: 0, 209 Response: 1 210}; 211