A Javascript one-liner for retrieving cookie values
One useful Javascript convenience function I ran across a while back
is the javascript:alert(document.cookie)
browser scriptlet. If you type
this into the address bar of your browser, it will pop up a list of all of
the cookies currently set in this domain (try it!) I use this quite a bit,
but one thing that always bothered me was that it showed me all of
the cookies when I was usually only interested in the value of a single one,
whose key I knew. For instance, if I go over to amazon.com
and show all of
my cookies, I see:
aws-x-main=74TVFa0BSwhflf0oMuebP1IywvY4uek; __utma=3822618247.014730400.50 1177117.676853367.3513281.5; __utmz=37183305.21457436277.4.6.utmccn=(organic)| utmcsr=google|utmctr=|utmcmd=organic; aws-ubid-main=371-8330521-4574362; aws-session-id-time=5514168847l; aws-session-id=677-3752086-8645371; apn-user-id=xbkws5k4-xray-sai4-onop-tjj4ttyw7kee; x-main=bUcjNIertj6ERMXXGO 6b56jIyEBSRIbN; session-token=hy5a5/tPr/6r1ipwglUUF53fHF8l8PDu5J2RIva/SY0sL tElkhyWSa4BQjS1cgGcsb+IJDvtTuQhOrLMc4fPpXKVrqf4Mxdk1+mcgM4hoGRKbOKhBLrdCQXL PcrN474N1uC2EnDbf2/55NWdTYPEkQvNtIr5SxdnJ74OpamHKrSluwmUBVbPMV5nxTwS83gavSE pHtMURWM8+DEVJAYDeH8Uv8taAsMx3RF5FixOfSBm+pTnX7FrjXPHDk8Vul/r4QRiATtUeYviaL e+TfkXW==; csm-hit=456.92; ubid-main=400-3487403-7234182; session-id-time=4 0034874037l; session-id=778-6702530-6814121;(All of the above, of course, has been anonymized... thoroughly, I hope...) Supposing that I'm only interested in the value of the session token. This appears in the middle of the display, and I have to "hunt" for it to find it.
It would be nice if Javascript would parse the cookie into an object-like format for me so that I could say, for example:
javascript:alert( document.cookie[ "session-token" ] )But, unfortunately, Javascript doesn't work that way - although you can set a cookie value that way, you can't retrieve one that way. I figured a way to work around this using regular expression syntax:
javascript:alert( document.cookie.match( /(; )?session-token=([^;]*);?/ )[ 2 ] )But that always sort of bugged me because the string to match was in the middle of the scriptlet; if I had it copied somewhere for a quick cut-and-paste, I had to select the middle part of the scriptlet and replace the name of the cookie I was after there. It also errors out silently if the cookie isn't present, which is sort of annoying.
With the relatively recent standardization of map
and
reduce
in Javascript 5, I was able to put together an arguably
better implementation. The solution I came up with, for the impatient, is:
javascript:alert( document.cookie.split( ';' ).map( function( x ) { return x.trim().split( '=' ); } ).reduce( function( a, b ) { a[ b[ 0 ] ] = b[ 1 ]; return a; }, {} )[ "session-token" ] );Ok, so it's not exactly succinct, but it is something that you can type into a single line and get the browser to quickly report the value of a single cookie. Although by the time you type all this in, I imagine it's less time and effort to just pull up the developer console, this way is more fun (right?). And who knows? You may be working in an ancient browser that doesn't have a javascript console.
This works, but it's worth picking apart to see exactly why — I think it makes an interesting exposition of functional programming techniques in Javascript (and also highlights some of the ways Javascript could do a better job managing the intersection of object-oriented and functional programming).
First, of course, I split the cookie on the ';' separators to get an array containing:
javascript:alert( document.cookie.split( ';' ).map( function( x ) { return x.trim().split( '=' ); } ).reduce( function( a, b ) { a[ b[ 0 ] ] = b[ 1 ]; return a; }, {} )[ "session-token" ] );
a[ 0 ] = aws-x-main=74TVFa0BSwhflf0oMuebP1IywvY4uek a[ 1 ] = __utma=3822618247.014730400.501177117.676853367.3513281.5 a[ 2 ] = __utmz=37183305.21457436277.4.6.utmccn=(organic)|utmcsr=google|utmctr=|utmcmd=organic a[ 3 ] = aws-ubid-main=371-8330521-4574362 a[ 4 ] = aws-session-id-time=5514168847l a[ 5 ] = aws-session-id=677-3752086-8645371 a[ 6 ] = apn-user-id=xbkws5k4-xray-sai4-onop-tjj4ttyw7kee a[ 7 ] = x-main=bUcjNIertj6ERMXXGO6b56jIyEBSRIbN a[ 8 ] = session-token=hy5a5/tPr/6r1ipwglUUF53fHF8l8PDu5J2RIva/SY0sLtElkhyWS a4BQjS1cgGcsb+IJDvtTuQhOrLMc4fPpXKVrqf4Mxdk1+mcgM4hoGRKbOKhBLrdCQXLPcrN474N1 uC2EnDbf2/55NWdTYPEkQvNtIr5SxdnJ74OpamHKrSluwmUBVbPMV5nxTwS83gavSEpHtMURWM8+ DEVJAYDeH8Uv8taAsMx3RF5FixOfSBm+pTnX7FrjXPHDk8Vul/r4QRiATtUeYviaLe+TfkXW== a[ 9 ] = csm-hit=456.92 a[ 10 ] = ubid-main=400-3487403-7234182 a[ 11 ] = session-id-time=40034874037l a[ 12 ] = session-id=778-6702530-6814121Note, of course, that the array
a
doesn't actually exist as
an addressable variable; I just refer to it that way here because I have to
call it something. Now, at this point, I could actually go a couple of
different way — I'll show you a second way below. The way I chose to
go here was to convert this array into an object (which, in Javascript, is
equivalent to what other languages call a hashmap or associative array) and
then index that object.
To do that, first I apply a map to the initial array that splits it up into
an array of arrays, split on the '=' value. I also trim the whitespace on
both ends (if I didn't do this, I wouldn't be able to index the final
object). It would be nice if there were some primitive way to tell Javascript
to apply a trim()
and split()
to the object instances it were passed; you
can't do this without defining another function. You can get close with
bind
, but trim is a regular expression that can't be bound, and
you can't pass include arguments to a bound function.
javascript:alert( document.cookie.split( ';' ).map( function( x ) { return x.trim().split( '=' ); } ).reduce( function( a, b ) { a[ b[ 0 ] ] = b[ 1 ]; return a; }, {} )[ "session-token" ] );
a[ 0 ] = [ 'aws-x-main', '74TVFa0BSwhflf0oMuebP1IywvY4uek' ] a[ 1 ] = [ '__utma', '3822618247.014730400.501177117.676853367.3513281.5' ] a[ 2 ] = [ '__utmz', '37183305.21457436277.4.6.utmccn=(organic)|utmcsr=google|utmctr=|utmcmd=organic' ] a[ 3 ] = [ 'aws-ubid-main', '371-8330521-4574362' ] a[ 4 ] = [ 'aws-session-id-time', '5514168847l' ] a[ 5 ] = [ 'aws-session-id', '677-3752086-8645371' ] a[ 6 ] = [ 'apn-user-id', 'xbkws5k4-xray-sai4-onop-tjj4ttyw7kee' ] a[ 7 ] = [ 'x-main', 'bUcjNIertj6ERMXXGO6b56jIyEBSRIbN' ] a[ 8 ] = [ 'session-token', 'hy5a5/tPr/6r1ipwglUUF53fHF8l8PDu5J2RIva/SY0sL tElkhyWSa4BQjS1cgGcsb+IJDvtTuQhOrLMc4fPpXKVrqf4Mxdk1+mcgM4hoGRKbOKhBLrdCQX LPcrN474N1uC2EnDbf2/55NWdTYPEkQvNtIr5SxdnJ74OpamHKrSluwmUBVbPMV5nxTwS83gav SEpHtMURWM8+DEVJAYDeH8Uv8taAsMx3RF5FixOfSBm+pTnX7FrjXPHDk8Vul/r4QRiATtUeYv iaLe+TfkXW==' ] a[ 9 ] = [ 'csm-hit', '456.92' ] a[ 10 ] = [ 'ubid-main', '400-3487403-7234182' ] a[ 11 ] = [ 'session-id-time', '40034874037l' ] a[ 12 ] = [ 'session-id', '778-6702530-6814121' ]
Now for the coup-de-grace. I want to turn this array of arrays into a map
of key-value pairs. I can do this with the reduce
function:
javascript:alert( document.cookie.split( ';' ).map( function( x ) { return x.trim().split( '=' ); } ).reduce( function( a, b ) { a[ b[ 0 ] ] = b[ 1 ]; return a; }, {} )[ "session-token" ] );
reduce
is a built-in implementation of the following loop:
for ( x in a )
{
init = f( init, a[ x ] );
}
In other words, keep applying function f
to init
and each element of the array a
, assigning init
to the result each time. (If init
is not supplied, the first
value of the array is used as init
). By using an empty object
as init
in this case, this expands conceptually to:
var init = {}
for ( x in a )
{
init[ a[ 0 ] ] = a[ 1 ];
}
Since each element of a
in this case is a two-element array,
this distributes the contents of the array to the object as key-value pairs.
Finally, I index this new object to get back the value of the key that I'm
interested in — in this case, the session-token
value.
There's one problem here, though - notice that what I get back is:
hy5a5/tPr/6r1ipwglUUF53fHF8l8PDu5J2RIva/SY0sLtElkhyWSa4BQ
jS1cgGcsb+IJDvtTuQhOrLMc4fPpXKVrqf4Mxdk1+mcgM4hoGRKbOKhBL
rdCQXLPcrN474N1uC2EnDbf
2/55NWdTYPEkQvNtIr5SxdnJ74OpamHKrSluwmUBVbPMV5nxTwS83gavS
EpHtMURWM8+DEVJAYDeH8Uv
8taAsMx3RF5FixOfSBm+pTnX7FrjXPHDk8Vul/r4QRiATtUeYviaLe+TfkXW.
split
will split on all occurences
of the search string. As it turns out, although a cookie value can't legally
contain the ';' character, it can legally contain an '=' sign.
I can see a couple of ways to handle this. The first is to change the map
function to a more explicit split:
map( function( x ) { return x.trim().split( '=' ); } )
map( function( x ) { return [ x.substring( 0, x.indexOf( '=' ) ).trim(), x.substring( x.indexOf( '=' ) + 1 ).trim() ] } )
But yikes; that adds 67 characters to my implementation. A shorter (but
arguably harder to follow) approach is to return the delimiter in the split
array and then join the end part back together in the reduce
call:
alert( c.split( ';' ).map( function( x ) { return x.trim().split( /(=)/ ); } ).reduce( function( a, b ) { a[ b[ 0 ] ] = b.slice( 2 ).join( '' ); return a; }, {} )[ "session-token" ] );
This adds just 19 extra characters; by changing the split parameter to a regular expression, I get back the delimiters in my split array. Of course, that means that I have to join any equal signs back in at the end.
There's a related filter
function that could be used in a similar
way:
javascript:alert( document.cookie.split( ';' ).filter( function( s ) { return s.search( 'session-token=' ) == 1 } ) );Here the return value is an array, and it includes the "session-token=" part at the beginning of the response. I can work around that by splitting up the returned value as before:
javascript:alert( document.cookie.split( ';' ).filter( function( s ) { return s.search( 'session-token=' ) == 1 } )[ 0 ].split( /(=)/ ).slice( 2 ).join( '' ) );But that's not that much better (I only save 30 characters), and my search key is in the middle of the text, so I have to "hunt" for it if I want to change it. Also, it's important here that my search string include the trailing '=' character or it would match, say,
session-token-aws
; if I copy
this into my URL bar and double click
the word to change it, I might inadvertently overwrite the extra '='. Also,
this approach fails silently when the cookie isn't present... so isn't much
better than the regular expression approach I started out with.
Note that none of these approaches handles multiple values correctly. This can legitimately happen when you have cookies bound to different path or domain names and the current URL matches both (or more) of them. Although this probably represents some sort of an error condition, it's the sort of thing you, as the developer, would want to be aware of. A perfect implementation would deal with this cookie string:
a=1; a=2; a=3; b=4;In this case, the first example returns '3' and the second returns '1'. I can work around this and still stay on a single line with the first approach (but not, as far as I can tell, with the second), at the cost of a painful bit of repetition:
document.cookie.split( ';' ).map( function( x ) { return x.trim().split( /(=)/ ); } ).reduce( function( a, b ) { a[ b[ 0 ] ] = a[ b[ 0 ] ] ? a[ b[ 0 ] ] + ', ' + b.slice( 2 ).join( '' ) : b.slice( 2 ).join( '' ); return a; }, {} )[ "session-token" ];But... whoa.
I can take this even a step farther and prompt for the value of the cookie to return like this:
document.cookie.split( ';' ).map( function( x ) { return x.trim().split( /(=)/ ); } ).reduce( function( a, b ) { a[ b[ 0 ] ] = a[ b[ 0 ] ] ? a[ b[ 0 ] ] + ', ' + b.slice( 2 ).join( '' ) : b.slice( 2 ).join( '' ); return a; }, {} )[ prompt( 'Which Cookie?' ) ];In fact, this is bookmarklet territory — for example: cookielookup. Of course, you have to know the name of the cookie you're looking for... an even better implementation might present a drop-down of available cookies. On the other hand, if you've already gotten to that point, it's probably time to bite the bullet and install HttpFox anyway.
Add a comment:
Completely off-topic or spam comments will be removed at the discretion of the moderator.
You may preserve formatting (e.g. a code sample) by indenting with four spaces preceding the formatted line(s)
function( a, b ) { a[ b[ 0 ] ] = a[ b[ 0 ] ] ? a[ b[ 0 ] ] + ', ' + b.slice( 2 ).join( '' ) :
b.slice( 2 ).join( '' ); return a; }, {} )[ prompt( 'Which Cookie?' ) ];