Search code examples
javascriptgoogle-chrome-extension

Is there a way to eval a stringified JSON object that contains control characters?


I am writing a toolbox extension for an online Javascript game. In order to affect the game, my extension generates a command as a string and passes it to an injected script which evals the contents. I'm running into an issue where I can't find the right combination of escape characters and format strings to be able to pass an object which contains a string including control characters to be evald. An example that works:

ObjectToPass = {a:"test"}
ObjectStringified = JSON.stringify(ObjectToPass) // '{"a":"test"}'
StringToPass = `Destination = JSON.parse('${ObjectStringified}')`
// Pass string from popup to main page
// In main page context
eval(StringToPass) // Destination is now {a:"test"}

If the attribute has a control character in it, the parsing gets broken. Per the example above

ObjectToPass = {a:"test\n"}
ObjectStringified = JSON.stringify(ObjectToPass) // '{"a":"test\\n"}'
StringToPass = `Destination = JSON.parse('${ObjectStringified}')`
// Pass string from popup to main page
// In main page context
eval(StringToPass) // Expected: Destination is now {a:"test\n"} Actual : Uncaught SyntaxError: Bad control character in string literal in JSON at position 10

I've tried every combination of additional escape characters and double stringify/parses that I can think of.

eval(`JSON.parse('{"a":"\\n"}')`)
VM8589:1 Uncaught SyntaxError: Invalid or unexpected token
at \<anonymous\>:1:1
(anonymous) @ VM8588:1
eval(`JSON.parse('{"a":"\\n"}')`)
VM8605:1 Uncaught SyntaxError: Bad control character in string literal in JSON at position 6
at JSON.parse (\<anonymous\>)
eval @ VM8604:1
(anonymous) @ VM8603:1
eval(`JSON.parse('{"a":"\\\n"}')`)
{a: ''}
eval(`JSON.parse(\`{"a":"\\n"}\`)`)
VM9187:1 Uncaught SyntaxError: Bad control character in string literal in JSON at position 6
at JSON.parse (\<anonymous\>)

I tried more than just these variations but they all yeilded similar results.


Solution

  • string has two forms here:

    1. literal
    2. value

    the literal in source code and console, the value is the actual value.

    For the json parser to succeed, you must make string actual value like literal. So it's correct that JSON.stringify({a:"test\n"}) return {"a":"test\\n"}. but when you pass to eval, it be escaped two times.

    eval(`JSON.parse('{"a":"\\n"}')`), for argument to the eval function in this code, the literal is JSON.parse('{"a":"\\n"}'), the value is

    JSON.parse('{"a":"\n"}')
    

    So it's equivalent to execution JSON.parse('{"a":"\n"}').

    JSON.parse('{"a":"\n"}'), for argument to the parse function in this code, the literal is {"a":"\n"}, the value is

    {"a":"
    "}
    

    So parser will throw error there.

    You should replace "\\n" with "\\\\n" before pass to eval, or you can add extra \ when you declaration object, like this:

    const obj = {a:"test\\n"}
    

    But it breaks the original meaning outside of eval