33

I found two functions to get cookie data with Javascript, one on w3schools.com and one on quirksmode.org
I would like to know which one I should use?

For example I believe I read somewhere that there was a problem with some browsers splitting the ; semicolon?

w3schools:

function getCookie(c_name) {
    if (document.cookie.length > 0) {
        c_start = document.cookie.indexOf(c_name + "=");
        if (c_start != -1) {
            c_start = c_start + c_name.length + 1;
            c_end = document.cookie.indexOf(";", c_start);
            if (c_end == -1) c_end = document.cookie.length;
            return unescape(document.cookie.substring(c_start, c_end));
        }
    }
    return "";
}

quirksmode:

function readCokie(name) {
    var nameEQ = name + "=";
    var ca = document.cookie.split(';');
    for(var i = 0; i < ca.length; i++) {
        var c = ca[i];
        while (c.charAt(0) == ' ') c = c.substring(1, c.length);
        if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
    }
    return null;
}
FFish
  • 10,964
  • 34
  • 95
  • 136

5 Answers5

92

The function from W3CSchool is wrong. It fails if there are multiple cookies that have the same suffix like:

ffoo=bar; foo=baz

When you search for foo it will return the value of ffoo instead of foo.

Now here is what I would do: First of all, you need get to know the syntax of how cookies are transported. Netscape’s original specification (there are only copies available like this one at haxx.se) uses semicolons to separate multiple cookies while each name/value pair has the following syntax:

NAME=VALUE
This string is a sequence of characters excluding semi-colon, comma and white space. If there is a need to place such data in the name or value, some encoding method such as URL style %XX encoding is recommended, though no encoding is defined or required.

So splitting document.cookie string at semi-colons or commas is a viable option.

Besides that, RFC 2109 does also specify that cookies are separated by either semi-colons or commas:

cookie          =       "Cookie:" cookie-version
                        1*((";" | ",") cookie-value)
cookie-value    =       NAME "=" VALUE [";" path] [";" domain]
cookie-version  =       "$Version" "=" value
NAME            =       attr
VALUE           =       value
path            =       "$Path" "=" value
domain          =       "$Domain" "=" value

Although both are allowed, commas are preferred as they are the default separator of list items in HTTP.

Note: For backward compatibility, the separator in the Cookie header is semi-colon (;) everywhere. A server should also accept comma (,) as the separator between cookie-values for future compatibility.

Furthermore, the name/value pair has some further restrictions as the VALUE can also be a quoted string as specified in RFC 2616:

attr        =     token
value       =     token | quoted-string

So these two cookie versions need to be treated separately:

if (typeof String.prototype.trimLeft !== "function") {
    String.prototype.trimLeft = function() {
        return this.replace(/^\s+/, "");
    };
}
if (typeof String.prototype.trimRight !== "function") {
    String.prototype.trimRight = function() {
        return this.replace(/\s+$/, "");
    };
}
if (typeof Array.prototype.map !== "function") {
    Array.prototype.map = function(callback, thisArg) {
        for (var i=0, n=this.length, a=[]; i<n; i++) {
            if (i in this) a[i] = callback.call(thisArg, this[i]);
        }
        return a;
    };
}
function getCookies() {
    var c = document.cookie, v = 0, cookies = {};
    if (document.cookie.match(/^\s*\$Version=(?:"1"|1);\s*(.*)/)) {
        c = RegExp.$1;
        v = 1;
    }
    if (v === 0) {
        c.split(/[,;]/).map(function(cookie) {
            var parts = cookie.split(/=/, 2),
                name = decodeURIComponent(parts[0].trimLeft()),
                value = parts.length > 1 ? decodeURIComponent(parts[1].trimRight()) : null;
            cookies[name] = value;
        });
    } else {
        c.match(/(?:^|\s+)([!#$%&'*+\-.0-9A-Z^`a-z|~]+)=([!#$%&'*+\-.0-9A-Z^`a-z|~]*|"(?:[\x20-\x7E\x80\xFF]|\\[\x00-\x7F])*")(?=\s*[,;]|$)/g).map(function($0, $1) {
            var name = $0,
                value = $1.charAt(0) === '"'
                          ? $1.substr(1, -1).replace(/\\(.)/g, "$1")
                          : $1;
            cookies[name] = value;
        });
    }
    return cookies;
}
function getCookie(name) {
    return getCookies()[name];
}
Community
  • 1
  • 1
Gumbo
  • 643,351
  • 109
  • 780
  • 844
  • 1
    I'd move `trimRight` and `trimLeft` out of the `getCookie` function: you only need to set them once. – Tim Down Oct 23 '10 at 15:51
  • what's that part of the code where you check the $Version? It's the 1st time I see it in getCookie like functions. Is it still useful? – Marco Demaio Mar 22 '11 at 19:17
  • @Marco Demaio: The `$Version=1` attribute is part of the RFC specifications to identify the cookies of those specifications and might get important when browsers start to implement them (although I’m not sure how the `document.cookie` value will reflect that). – Gumbo Mar 22 '11 at 19:36
  • Internet Explorer 8 doesn't support the javascript array map function, (added it in the code above as IE8 is unfortunately still around) code taken from http://www.xinotes.org/notes/note/884/ – reevesy Feb 01 '12 at 12:18
  • getCookie(name) should use hasOwnProperty to avoid returning something in Object.prototype as a cookie – Mark Fowler Jan 28 '13 at 15:13
  • 11
    This answer is a prime example of overengineering. Why include all of that code, when the problem can easily be solved by creating a cookie name that is very likely to be unique. eg. NAMESPACE_KEY – Anthony Martin Feb 21 '13 at 17:07
  • 1
    This answer appears to choke on anything with '=' as part of the cookie's value. Compare the answer's `getCookies()` function with `document.cookie.split('; ')` – user456584 Aug 12 '13 at 21:08
  • "A server should also accept comma (,) as the separator between cookie-values for future compatibility." How is this possible if "expires" values can contain commas? – Rag Aug 19 '15 at 19:58
  • This code didn't work for double-quoted cookie values containing equal signs. The js-cookie library proposed in another answer was able to handle that case. – dlauzon Feb 19 '18 at 15:38
  • I think this answer is extremly outdated. [RFC 6265](https://datatracker.ietf.org/doc/html/rfc6265) specifies semicolons as the separator, not commas. – nog642 Apr 17 '22 at 04:39
12

Yes, the W3Schools solution is incorrect.

For those that would like it, here is a simpler solution that works. It just prepends a space so the single call to indexOf() only returns the correct cookie.

function getCookie(c_name) {
    var c_value = " " + document.cookie;
    var c_start = c_value.indexOf(" " + c_name + "=");
    if (c_start == -1) {
        c_value = null;
    }
    else {
        c_start = c_value.indexOf("=", c_start) + 1;
        var c_end = c_value.indexOf(";", c_start);
        if (c_end == -1) {
            c_end = c_value.length;
        }
        c_value = unescape(c_value.substring(c_start,c_end));
    }
    return c_value;
}
Craig Smedley
  • 2,548
  • 1
  • 14
  • 10
  • Nowadays decodeURIComponent should be preferred over unescape. See [here](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/unescape) for details. – BornToCode Dec 25 '21 at 21:06
6

This, from w3schools, is incorrect in that it may lead to getting the wrong cookie:

c_start = document.cookie.indexOf(c_name + "=");

If you go looking for a cookie named foo (which we'll suppose is an existing cookie) then somewhere in document.cookie will be the string foo=bar.

However, there's no guarantee there won't also be the string xfoo=something. Notice that this still contains the substring foo= so the w3schools code will find it. And if the xfoo cookie happens to be listed first, you'll get back the something value (incorrectly!) instead of the expected bar.

Given the choice between two pieces of code, never go with the one that's fundamentally broken.

VoteyDisciple
  • 37,319
  • 5
  • 97
  • 97
2

All of the code shown above is BROKEN. The two common problems are (1) the getcookie function may return the wrong value if one cookie name is a proper suffix of another cookie name; and (2) the setcookie function does not protect the cookie value, which means that if the cookie value includes (for example) a ";" then all the cookies are corrupted and cannot be parsed.

TL;DR Use this well-written library instead: https://github.com/js-cookie/js-cookie

B Factor
  • 36
  • 2
  • 1
    Tried the accepted answer for double-quoted cookie values containing equal signs, and it didn't work, but js-cookie did the job. – dlauzon Feb 19 '18 at 15:33
1

Here is my version, it covers the edge case of quoted values.

function getCookies() {
  const REGEXP = /([\w\.]+)\s*=\s*(?:"((?:\\"|[^"])*)"|(.*?))\s*(?:[;,]|$)/g;
  let cookies = {};
  let match;
  while( (match = REGEXP.exec(document.cookie)) !== null ) {
    let value = match[2] || match[3];
    cookies[match[1]] = decodeURIComponent(value);
  }
  return cookies;
}
Vincent Robert
  • 35,564
  • 14
  • 82
  • 119