3

I'm setting a cookie in Flask like follows :

response.set_cookie('notice', value = json.dumps(someObject), max_age=None)

If i print json.dumps(someObject) on the server I get :

{"message": "hello", "type": "success"}

On the client side it becomes :

"{\"message\": \"hello\"\054 \"type\": \"success\"}"

I want to decode it on a javascript client what format is it exactly ?

I'd like to decode it and pass it to angular.fromJson(), it looks like there is at least unescaping (of the the \") to do, but I'm surprize to see \054 (the octal ASCII code for the comma)

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Max L.
  • 9,774
  • 15
  • 56
  • 86

3 Answers3

6

The cookie value is quoted by the Werkzeug library to be safe for use in Cookie headers; this includes quoting any commas, semicolons, double quotes and backslashes:

cookie_quoting_map = {
    b',' : b'\\054',
    b';' : b'\\073',
    b'"' : b'\\"',
    b'\\' : b'\\\\',
}

Anything else outside of letters, digits and the characters !#%&'~_`><@,:/$*+-.^|)(?}{= is encoded to an octal codepoint as well. If there are any escaped values in the cookie, the whole cookie is also surrounded by double quotes.

If you need access to the cookie value in JavaScript, you'll have to decode this again. Values that start with a slash and 3 digits are octal values; a String.replace() call should do:

function decode_flask_cookie(val) {
    if (val.indexOf('\\') === -1) {
        return val;  // not encoded
    }
    val = val.slice(1, -1).replace(/\\"/g, '"');
    val = val.replace(/\\(\d{3})/g, function(match, octal) { 
        return String.fromCharCode(parseInt(octal, 8));
    });
    return val.replace(/\\\\/g, '\\');
}

Demo:

> // recreate properly escaped value
> var cookie = "\"{\\\"message\\\": \\\"hello\\\"\\054 \\\"type\\\": \\\"success\\\"}\""
> cookie
""{\"message\": \"hello\"\054 \"type\": \"success\"}""
> decode_flask_cookie(cookie)
"{"message": "hello", "type": "success"}"
> JSON.parse(decode_flask_cookie(cookie))
Object {message: "hello", type: "success"}
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • Such escaping is discouraged by [RFC 6265](https://tools.ietf.org/html/rfc6265#section-4.1.1https://tools.ietf.org/html/rfc6265#section-4.1.1) and Base64 encoding should be used instead – Erbureth Jan 16 '15 at 15:06
  • @Erbureth: and it is my fault that the Werkzeug project is not doing that? The escaping is not my choice, nor can the OP alter that. All I am doing is *documenting* what the project does, and how to decode that again. – Martijn Pieters Jan 16 '15 at 15:15
  • @Erbureth: I *suspect* that the Werkzeug maintainers have their reasons for this mechanism over base64 encoding (perhaps because base64 adds a significant overhead to a method of storage that is already constraint in how much space is available). – Martijn Pieters Jan 16 '15 at 15:17
  • @MartijnPieters that would make the storage unfit for the job. OP can workaround it by wrapping it into Base64 himself. – Erbureth Jan 16 '15 at 15:20
  • @Erbureth: perhaps, but that's a different matter altogether. – Martijn Pieters Jan 16 '15 at 15:37
  • @Erbureth: checking the RFC, the wording is *SHOULD*, not *MUST*. Escaping is a perfectly valid alternative, if the escaping leads to data that is legal in a cookie value. – Martijn Pieters Jan 16 '15 at 15:40
  • @MartijnPieters, yes, however it should be used as a last resort and doesn't mean such cookie values should be encouraged. – Erbureth Jan 16 '15 at 16:01
  • Works very well, thank you! The only one thing, regex should be changed to `/\\(\d{3})/` -> `/\\(\d{3})/g`. Otherwise only first place will be replaced instead of all. – scabbiaza Sep 16 '15 at 09:37
  • @scabbiaza: thanks; indeed, a global search and replace is required here. – Martijn Pieters Sep 16 '15 at 09:40
0

Dealing with the same issue today, but it seems the facts on the ground have changed.

First of all, sending the cookie back is as simple as:

cookie_str = json.dumps(mydictionary, separators=(',', ':'))
resp.set_cookie('cookiename', cookie_str, None, None, '/') 

On the Javascript side, in browser, I read the cookie as follows using the js-cookies project:

var cookie2 = Cookies.noConflict();
var cookiejs = cookie2.getJSON('cookiename');
var one = ca.field1;
var two = ca.field2;

It does appear that the curly's are not getting escaped properly in my case. As a result, I have to find and replace the curley's with escaped versions \{ and \}. However, after that, instead of using getJSON, I had to resort to jquery's $.parseJSON() function, which returns a usable json object.

Still haven't figured out how to get the cookie json string to behave correctly without re-escaping the curley's on the javascript side. The escaping and jquery method is a little hacky... but it works.

Also I'm using Zappa-Flask in AWS Lambda, not sure if that is messing up my cookie.

Atif
  • 1,438
  • 16
  • 25
0

bi-directional JSON cookie between flask (python) and javascript

Python - need to decode on reading using urllib

if "sensorcookie" in request.cookies:
    ck = json.loads(urllib.parse.unquote(request.cookies.get("sensorcookie")))
else:
    ck = {}
tz = "undefined" if os.environ.get("TZ") is None else os.environ.get("TZ")
ck = {"HOST_ENV":os.environ["HOST_ENV"], "TZ": tz, **ck}
resp.set_cookie("sensorcookie", json.dumps(ck))

Javascript - using js-cookies. Have to replace escaped commas and triple backslashes before JSON parse. I've constructed a wrapper "module" to do this

// https://yuiblog.com/blog/2007/06/12/module-pattern/
var sensorcookies = function () {
    const defaultopts = {righttoleft:true, graphtype:"line", freq:"", pointsslider:100, pointstoplot:20, tickinterval:1, TZ:""};
    const name = "sensorcookie";
    function convertraw() {
        var t = Cookies.get(name);
        return (!t) ? defaultopts : { ...defaultopts, ...JSON.parse(t.replace(/\\054/g, ",").replace(/\\/g, ""))};
    }
    function privateget(attr) {
        var opts = convertraw();
        return opts[attr];
    }
    function privateset(attr, value) {
        var opts = convertraw();
        opts[attr] = value;
        Cookies.set(name, JSON.stringify(opts));
    }
    // waitforcookies();
    return {
        version: "1.0",
        get: (attr) => { return privateget(attr);},
        set: (attr, value) => { privateset(attr, value);},
        log: () => { Cookies.set(name, convertraw()); console.log(JSON.stringify(convertraw()));}, 
        val: () => { return convertraw(); }
    };
}();  // NB parens so makes it feel like a "module"
Rob Raymond
  • 29,118
  • 3
  • 14
  • 30