Home | History | Annotate | Download | only in front-end
      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 
     38 WebInspector.CookieParser = function()
     39 {
     40 }
     41 
     42 WebInspector.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 
    144 WebInspector.CookieParser.parseCookie = function(header)
    145 {
    146     return (new WebInspector.CookieParser()).parseCookie(header);
    147 }
    148 
    149 WebInspector.CookieParser.parseSetCookie = function(header)
    150 {
    151     return (new WebInspector.CookieParser()).parseSetCookie(header);
    152 }
    153 
    154 WebInspector.Cookie = function(name, value, type)
    155 {
    156     this.name = name;
    157     this.value = value;
    158     this.type = type;
    159     this._attributes = {};
    160 }
    161 
    162 WebInspector.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 
    207 WebInspector.Cookie.Type = {
    208     Request: 0,
    209     Response: 1
    210 };
    211