CookieParser.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465
  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. // Ideally, we would rely on platform support for parsing a cookie, since
  31. // this would save us from any potential inconsistency. However, exposing
  32. // platform cookie parsing logic would require quite a bit of additional
  33. // plumbing, and at least some platforms lack support for parsing Cookie,
  34. // which is in a format slightly different from Set-Cookie and is normally
  35. // only required on the server side.
  36. /**
  37. * @constructor
  38. */
  39. WebInspector.CookieParser = function()
  40. {
  41. }
  42. /**
  43. * @constructor
  44. * @param {string} key
  45. * @param {string|undefined} value
  46. * @param {number} position
  47. */
  48. WebInspector.CookieParser.KeyValue = function(key, value, position)
  49. {
  50. this.key = key;
  51. this.value = value;
  52. this.position = position;
  53. }
  54. WebInspector.CookieParser.prototype = {
  55. /**
  56. * @return {Array.<WebInspector.Cookie>}
  57. */
  58. cookies: function()
  59. {
  60. return this._cookies;
  61. },
  62. /**
  63. * @param {string|undefined} cookieHeader
  64. * @return {?Array.<WebInspector.Cookie>}
  65. */
  66. parseCookie: function(cookieHeader)
  67. {
  68. if (!this._initialize(cookieHeader))
  69. return null;
  70. for (var kv = this._extractKeyValue(); kv; kv = this._extractKeyValue()) {
  71. if (kv.key.charAt(0) === "$" && this._lastCookie)
  72. this._lastCookie.addAttribute(kv.key.slice(1), kv.value);
  73. else if (kv.key.toLowerCase() !== "$version" && typeof kv.value === "string")
  74. this._addCookie(kv, WebInspector.Cookie.Type.Request);
  75. this._advanceAndCheckCookieDelimiter();
  76. }
  77. this._flushCookie();
  78. return this._cookies;
  79. },
  80. /**
  81. * @param {string|undefined} setCookieHeader
  82. * @return {?Array.<WebInspector.Cookie>}
  83. */
  84. parseSetCookie: function(setCookieHeader)
  85. {
  86. if (!this._initialize(setCookieHeader))
  87. return null;
  88. for (var kv = this._extractKeyValue(); kv; kv = this._extractKeyValue()) {
  89. if (this._lastCookie)
  90. this._lastCookie.addAttribute(kv.key, kv.value);
  91. else
  92. this._addCookie(kv, WebInspector.Cookie.Type.Response);
  93. if (this._advanceAndCheckCookieDelimiter())
  94. this._flushCookie();
  95. }
  96. this._flushCookie();
  97. return this._cookies;
  98. },
  99. /**
  100. * @param {string|undefined} headerValue
  101. * @return {boolean}
  102. */
  103. _initialize: function(headerValue)
  104. {
  105. this._input = headerValue;
  106. if (typeof headerValue !== "string")
  107. return false;
  108. this._cookies = [];
  109. this._lastCookie = null;
  110. this._originalInputLength = this._input.length;
  111. return true;
  112. },
  113. _flushCookie: function()
  114. {
  115. if (this._lastCookie)
  116. this._lastCookie.setSize(this._originalInputLength - this._input.length - this._lastCookiePosition);
  117. this._lastCookie = null;
  118. },
  119. /**
  120. * @return {WebInspector.CookieParser.KeyValue}
  121. */
  122. _extractKeyValue: function()
  123. {
  124. if (!this._input || !this._input.length)
  125. return null;
  126. // Note: RFCs offer an option for quoted values that may contain commas and semicolons.
  127. // Many browsers/platforms do not support this, however (see http://webkit.org/b/16699
  128. // and http://crbug.com/12361). The logic below matches latest versions of IE, Firefox,
  129. // Chrome and Safari on some old platforms. The latest version of Safari supports quoted
  130. // cookie values, though.
  131. var keyValueMatch = /^[ \t]*([^\s=;]+)[ \t]*(?:=[ \t]*([^;\n]*))?/.exec(this._input);
  132. if (!keyValueMatch) {
  133. console.log("Failed parsing cookie header before: " + this._input);
  134. return null;
  135. }
  136. var result = new WebInspector.CookieParser.KeyValue(keyValueMatch[1], keyValueMatch[2] && keyValueMatch[2].trim(), this._originalInputLength - this._input.length);
  137. this._input = this._input.slice(keyValueMatch[0].length);
  138. return result;
  139. },
  140. /**
  141. * @return {boolean}
  142. */
  143. _advanceAndCheckCookieDelimiter: function()
  144. {
  145. var match = /^\s*[\n;]\s*/.exec(this._input);
  146. if (!match)
  147. return false;
  148. this._input = this._input.slice(match[0].length);
  149. return match[0].match("\n") !== null;
  150. },
  151. /**
  152. * @param {!WebInspector.CookieParser.KeyValue} keyValue
  153. * @param {!WebInspector.Cookie.Type} type
  154. */
  155. _addCookie: function(keyValue, type)
  156. {
  157. if (this._lastCookie)
  158. this._lastCookie.setSize(keyValue.position - this._lastCookiePosition);
  159. // Mozilla bug 169091: Mozilla, IE and Chrome treat single token (w/o "=") as
  160. // specifying a value for a cookie with empty name.
  161. this._lastCookie = typeof keyValue.value === "string" ? new WebInspector.Cookie(keyValue.key, keyValue.value, type) :
  162. new WebInspector.Cookie("", keyValue.key, type);
  163. this._lastCookiePosition = keyValue.position;
  164. this._cookies.push(this._lastCookie);
  165. }
  166. };
  167. /**
  168. * @param {string|undefined} header
  169. * @return {?Array.<WebInspector.Cookie>}
  170. */
  171. WebInspector.CookieParser.parseCookie = function(header)
  172. {
  173. return (new WebInspector.CookieParser()).parseCookie(header);
  174. }
  175. /**
  176. * @param {string|undefined} header
  177. * @return {?Array.<WebInspector.Cookie>}
  178. */
  179. WebInspector.CookieParser.parseSetCookie = function(header)
  180. {
  181. return (new WebInspector.CookieParser()).parseSetCookie(header);
  182. }
  183. /**
  184. * @constructor
  185. * @param {string} name
  186. * @param {string} value
  187. * @param {?WebInspector.Cookie.Type} type
  188. */
  189. WebInspector.Cookie = function(name, value, type)
  190. {
  191. this._name = name;
  192. this._value = value;
  193. this._type = type;
  194. this._attributes = {};
  195. }
  196. WebInspector.Cookie.prototype = {
  197. /**
  198. * @return {string}
  199. */
  200. name: function()
  201. {
  202. return this._name;
  203. },
  204. /**
  205. * @return {string}
  206. */
  207. value: function()
  208. {
  209. return this._value;
  210. },
  211. /**
  212. * @return {?WebInspector.Cookie.Type}
  213. */
  214. type: function()
  215. {
  216. return this._type;
  217. },
  218. /**
  219. * @return {boolean}
  220. */
  221. httpOnly: function()
  222. {
  223. return "httponly" in this._attributes;
  224. },
  225. /**
  226. * @return {boolean}
  227. */
  228. secure: function()
  229. {
  230. return "secure" in this._attributes;
  231. },
  232. /**
  233. * @return {boolean}
  234. */
  235. session: function()
  236. {
  237. // RFC 2965 suggests using Discard attribute to mark session cookies, but this does not seem to be widely used.
  238. // Check for absence of explicitly max-age or expiry date instead.
  239. return !("expires" in this._attributes || "max-age" in this._attributes);
  240. },
  241. /**
  242. * @return {string}
  243. */
  244. path: function()
  245. {
  246. return this._attributes["path"];
  247. },
  248. /**
  249. * @return {string}
  250. */
  251. port: function()
  252. {
  253. return this._attributes["port"];
  254. },
  255. /**
  256. * @return {string}
  257. */
  258. domain: function()
  259. {
  260. return this._attributes["domain"];
  261. },
  262. /**
  263. * @return {string}
  264. */
  265. expires: function()
  266. {
  267. return this._attributes["expires"];
  268. },
  269. /**
  270. * @return {string}
  271. */
  272. maxAge: function()
  273. {
  274. return this._attributes["max-age"];
  275. },
  276. /**
  277. * @return {number}
  278. */
  279. size: function()
  280. {
  281. return this._size;
  282. },
  283. /**
  284. * @param {number} size
  285. */
  286. setSize: function(size)
  287. {
  288. this._size = size;
  289. },
  290. /**
  291. * @return {Date}
  292. */
  293. expiresDate: function(requestDate)
  294. {
  295. // RFC 6265 indicates that the max-age attribute takes precedence over the expires attribute
  296. if (this.maxAge()) {
  297. var targetDate = requestDate === null ? new Date() : requestDate;
  298. return new Date(targetDate.getTime() + 1000 * this.maxAge());
  299. }
  300. if (this.expires())
  301. return new Date(this.expires());
  302. return null;
  303. },
  304. /**
  305. * @return {Object}
  306. */
  307. attributes: function()
  308. {
  309. return this._attributes;
  310. },
  311. /**
  312. * @param {string} key
  313. * @param {string=} value
  314. */
  315. addAttribute: function(key, value)
  316. {
  317. this._attributes[key.toLowerCase()] = value;
  318. },
  319. /**
  320. * @param {function(?Protocol.Error)=} callback
  321. */
  322. remove: function(callback)
  323. {
  324. PageAgent.deleteCookie(this.name(), (this.secure() ? "https://" : "http://") + this.domain() + this.path(), callback);
  325. }
  326. }
  327. /**
  328. * @enum {number}
  329. */
  330. WebInspector.Cookie.Type = {
  331. Request: 0,
  332. Response: 1
  333. };
  334. WebInspector.Cookies = {}
  335. /**
  336. * @param {function(!Array.<!WebInspector.Cookie>, boolean)} callback
  337. */
  338. WebInspector.Cookies.getCookiesAsync = function(callback)
  339. {
  340. /**
  341. * @param {?Protocol.Error} error
  342. * @param {Array.<PageAgent.Cookie>} cookies
  343. * @param {string} cookiesString
  344. */
  345. function mycallback(error, cookies, cookiesString)
  346. {
  347. if (error)
  348. return;
  349. if (cookiesString)
  350. callback(WebInspector.Cookies.buildCookiesFromString(cookiesString), false);
  351. else
  352. callback(cookies.map(WebInspector.Cookies.buildCookieProtocolObject), true);
  353. }
  354. PageAgent.getCookies(mycallback);
  355. }
  356. /**
  357. * @param {string} rawCookieString
  358. * @return {!Array.<!WebInspector.Cookie>}
  359. */
  360. WebInspector.Cookies.buildCookiesFromString = function(rawCookieString)
  361. {
  362. var rawCookies = rawCookieString.split(/;\s*/);
  363. var cookies = [];
  364. if (!(/^\s*$/.test(rawCookieString))) {
  365. for (var i = 0; i < rawCookies.length; ++i) {
  366. var rawCookie = rawCookies[i];
  367. var delimIndex = rawCookie.indexOf("=");
  368. var name = rawCookie.substring(0, delimIndex);
  369. var value = rawCookie.substring(delimIndex + 1);
  370. var size = name.length + value.length;
  371. var cookie = new WebInspector.Cookie(name, value, null);
  372. cookie.setSize(size);
  373. cookies.push(cookie);
  374. }
  375. }
  376. return cookies;
  377. }
  378. /**
  379. * @param {!PageAgent.Cookie} protocolCookie
  380. * @return {!WebInspector.Cookie}
  381. */
  382. WebInspector.Cookies.buildCookieProtocolObject = function(protocolCookie)
  383. {
  384. var cookie = new WebInspector.Cookie(protocolCookie.name, protocolCookie.value, null);
  385. cookie.addAttribute("domain", protocolCookie["domain"]);
  386. cookie.addAttribute("path", protocolCookie["path"]);
  387. cookie.addAttribute("port", protocolCookie["port"]);
  388. if (protocolCookie["expires"])
  389. cookie.addAttribute("expires", protocolCookie["expires"]);
  390. if (protocolCookie["httpOnly"])
  391. cookie.addAttribute("httpOnly");
  392. if (protocolCookie["secure"])
  393. cookie.addAttribute("secure");
  394. cookie.setSize(protocolCookie["size"]);
  395. return cookie;
  396. }
  397. /**
  398. * @param {WebInspector.Cookie} cookie
  399. * @param {string} resourceURL
  400. */
  401. WebInspector.Cookies.cookieMatchesResourceURL = function(cookie, resourceURL)
  402. {
  403. var url = resourceURL.asParsedURL();
  404. if (!url || !WebInspector.Cookies.cookieDomainMatchesResourceDomain(cookie.domain(), url.host))
  405. return false;
  406. return (url.path.startsWith(cookie.path())
  407. && (!cookie.port() || url.port == cookie.port())
  408. && (!cookie.secure() || url.scheme === "https"));
  409. }
  410. /**
  411. * @param {string} cookieDomain
  412. * @param {string} resourceDomain
  413. */
  414. WebInspector.Cookies.cookieDomainMatchesResourceDomain = function(cookieDomain, resourceDomain)
  415. {
  416. if (cookieDomain.charAt(0) !== '.')
  417. return resourceDomain === cookieDomain;
  418. return !!resourceDomain.match(new RegExp("^([^\\.]+\\.)*" + cookieDomain.substring(1).escapeForRegExp() + "$", "i"));
  419. }