JSON parser is now strict (ie, expressions are not allowed for security)

Close #57
This commit is contained in:
Misko Hevery 2010-10-15 15:28:58 -07:00
parent 352dbfa38f
commit 9e9bdbdc40
11 changed files with 381 additions and 342 deletions

View file

@ -99,6 +99,7 @@ function inherit(parent, extra) {
function noop() {} function noop() {}
function identity($) {return $;} function identity($) {return $;}
function valueFn(value) {return function(){ return value; };}
function extensionMap(angular, name, transform) { function extensionMap(angular, name, transform) {
var extPoint; var extPoint;
return angular[name] || (extPoint = angular[name] = function (name, fn, prop){ return angular[name] || (extPoint = angular[name] = function (name, fn, prop){

View file

@ -9,9 +9,9 @@ function toJson(obj, pretty){
function fromJson(json) { function fromJson(json) {
if (!json) return json; if (!json) return json;
try { try {
var parser = new Parser(json, true); var p = parser(json, true);
var expression = parser.primary(); var expression = p.primary();
parser.assertAllConsumed(); p.assertAllConsumed();
return expression(); return expression();
} catch (e) { } catch (e) {
error("fromJson error: ", json, e); error("fromJson error: ", json, e);

View file

@ -25,52 +25,37 @@ var OPERATORS = {
}; };
var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'}; var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'};
function lex(text, parseStrings){ function lex(text, parseStringsForObjects){
var dateParseLength = parseStrings ? 20 : -1, var dateParseLength = parseStringsForObjects ? 20 : -1,
tokens = [], tokens = [],
token,
index = 0, index = 0,
canStartRegExp = true; json = [],
ch,
lastCh = ','; // can start regexp
while (index < text.length) { while (index < text.length) {
var ch = text.charAt(index); ch = text.charAt(index);
if (ch == '"' || ch == "'") { if (is('"\'')) {
readString(ch); readString(ch);
canStartRegExp = true; } else if (isNumber(ch) || is('.') && isNumber(peek())) {
} else if (ch == '(' || ch == '[') {
tokens.push({index:index, text:ch});
index++;
} else if (ch == '{' ) {
var peekCh = peek();
if (peekCh == ':' || peekCh == '(') {
tokens.push({index:index, text:ch + peekCh});
index++;
} else {
tokens.push({index:index, text:ch});
}
index++;
canStartRegExp = true;
} else if (ch == ')' || ch == ']' || ch == '}' ) {
tokens.push({index:index, text:ch});
index++;
canStartRegExp = false;
} else if (ch == '.' && isNumber(peek())) {
readNumber(); readNumber();
canStartRegExp = false; } else if ( was('({[:,;') && is('/') ) {
} else if ( ch == ':' || ch == '.' || ch == ',' || ch == ';') {
tokens.push({index:index, text:ch});
index++;
canStartRegExp = true;
} else if ( canStartRegExp && ch == '/' ) {
readRegexp(); readRegexp();
canStartRegExp = false;
} else if ( isNumber(ch) ) {
readNumber();
canStartRegExp = false;
} else if (isIdent(ch)) { } else if (isIdent(ch)) {
readIdent(); readIdent();
canStartRegExp = false; if (was('{,') && json[0]=='{' &&
(token=tokens[tokens.length-1])) {
token.json = token.text.indexOf('.') == -1;
}
} else if (is('(){}[].,;:')) {
tokens.push({index:index, text:ch, json:is('{}[]:,')});
if (is('{[')) json.unshift(ch);
if (is('}]')) json.shift();
index++;
} else if (isWhitespace(ch)) { } else if (isWhitespace(ch)) {
index++; index++;
continue;
} else { } else {
var ch2 = ch + peek(), var ch2 = ch + peek(),
fn = OPERATORS[ch], fn = OPERATORS[ch],
@ -87,11 +72,19 @@ function lex(text, parseStrings){
"] in expression '" + text + "] in expression '" + text +
"' at column '" + (index+1) + "'."; "' at column '" + (index+1) + "'.";
} }
canStartRegExp = true;
} }
lastCh = ch;
} }
return tokens; return tokens;
function is(chars) {
return chars.indexOf(ch) != -1;
}
function was(chars) {
return chars.indexOf(lastCh) != -1;
}
function peek() { function peek() {
return index + 1 < text.length ? text.charAt(index + 1) : false; return index + 1 < text.length ? text.charAt(index + 1) : false;
} }
@ -136,7 +129,7 @@ function lex(text, parseStrings){
index++; index++;
} }
number = 1 * number; number = 1 * number;
tokens.push({index:start, text:number, tokens.push({index:start, text:number, json:true,
fn:function(){return number;}}); fn:function(){return number;}});
} }
function readIdent() { function readIdent() {
@ -156,8 +149,9 @@ function lex(text, parseStrings){
fn = getterFn(ident); fn = getterFn(ident);
fn.isAssignable = ident; fn.isAssignable = ident;
} }
tokens.push({index:start, text:ident, fn:fn}); tokens.push({index:start, text:ident, fn:fn, json: OPERATORS[ident]});
} }
function readString(quote) { function readString(quote) {
var start = index; var start = index;
index++; index++;
@ -189,7 +183,7 @@ function lex(text, parseStrings){
escape = true; escape = true;
} else if (ch == quote) { } else if (ch == quote) {
index++; index++;
tokens.push({index:start, text:rawString, string:string, tokens.push({index:start, text:rawString, string:string, json:true,
fn:function(){ fn:function(){
return (string.length == dateParseLength) ? return (string.length == dateParseLength) ?
angular['String']['toDate'](string) : string; angular['String']['toDate'](string) : string;
@ -241,32 +235,34 @@ function lex(text, parseStrings){
///////////////////////////////////////// /////////////////////////////////////////
function Parser(text, parseStrings){ function parser(text, json){
this.text = text; var ZERO = valueFn(0),
this.tokens = lex(text, parseStrings); tokens = lex(text, json);
this.index = 0; return {
} assertAllConsumed: assertAllConsumed,
primary: primary,
statements: statements,
validator: validator,
filter: filter,
watch: watch
};
var ZERO = function(){ ///////////////////////////////////
return 0;
};
Parser.prototype = { function error(msg, token) {
error: function(msg, token) {
throw "Token '" + token.text + throw "Token '" + token.text +
"' is " + msg + " at column='" + "' is " + msg + " at column='" +
(token.index + 1) + "' of expression '" + (token.index + 1) + "' of expression '" +
this.text + "' starting at '" + this.text.substring(token.index) + "'."; text + "' starting at '" + text.substring(token.index) + "'.";
}, }
peekToken: function() { function peekToken() {
if (this.tokens.length === 0) if (tokens.length === 0)
throw "Unexpected end of expression: " + this.text; throw "Unexpected end of expression: " + text;
return this.tokens[0]; return tokens[0];
}, }
peek: function(e1, e2, e3, e4) { function peek(e1, e2, e3, e4) {
var tokens = this.tokens;
if (tokens.length > 0) { if (tokens.length > 0) {
var token = tokens[0]; var token = tokens[0];
var t = token.text; var t = token.text;
@ -276,57 +272,64 @@ Parser.prototype = {
} }
} }
return false; return false;
}, }
expect: function(e1, e2, e3, e4){ function expect(e1, e2, e3, e4){
var token = this.peek(e1, e2, e3, e4); var token = peek(e1, e2, e3, e4);
if (token) { if (token) {
this.tokens.shift(); if (json && !token.json) {
index = token.index;
throw "Expression at column='" +
token.index + "' of expression '" +
text + "' starting at '" + text.substring(token.index) +
"' is not valid json.";
}
tokens.shift();
this.currentToken = token; this.currentToken = token;
return token; return token;
} }
return false; return false;
}, }
consume: function(e1){ function consume(e1){
if (!this.expect(e1)) { if (!expect(e1)) {
var token = this.peek(); var token = peek();
throw "Expecting '" + e1 + "' at column '" + throw "Expecting '" + e1 + "' at column '" +
(token.index+1) + "' in '" + (token.index+1) + "' in '" +
this.text + "' got '" + text + "' got '" +
this.text.substring(token.index) + "'."; text.substring(token.index) + "'.";
} }
}, }
_unary: function(fn, right) { function unaryFn(fn, right) {
return function(self) { return function(self) {
return fn(self, right(self)); return fn(self, right(self));
}; };
}, }
_binary: function(left, fn, right) { function binaryFn(left, fn, right) {
return function(self) { return function(self) {
return fn(self, left(self), right(self)); return fn(self, left(self), right(self));
}; };
}, }
hasTokens: function () { function hasTokens () {
return this.tokens.length > 0; return tokens.length > 0;
}, }
assertAllConsumed: function(){ function assertAllConsumed(){
if (this.tokens.length !== 0) { if (tokens.length !== 0) {
throw "Did not understand '" + this.text.substring(this.tokens[0].index) + throw "Did not understand '" + text.substring(tokens[0].index) +
"' while evaluating '" + this.text + "'."; "' while evaluating '" + text + "'.";
} }
}, }
statements: function(){ function statements(){
var statements = []; var statements = [];
while(true) { while(true) {
if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']')) if (tokens.length > 0 && !peek('}', ')', ';', ']'))
statements.push(this.filterChain()); statements.push(filterChain());
if (!this.expect(';')) { if (!expect(';')) {
return function (self){ return function (self){
var value; var value;
for ( var i = 0; i < statements.length; i++) { for ( var i = 0; i < statements.length; i++) {
@ -338,35 +341,35 @@ Parser.prototype = {
}; };
} }
} }
}, }
filterChain: function(){ function filterChain(){
var left = this.expression(); var left = expression();
var token; var token;
while(true) { while(true) {
if ((token = this.expect('|'))) { if ((token = expect('|'))) {
left = this._binary(left, token.fn, this.filter()); left = binaryFn(left, token.fn, filter());
} else { } else {
return left; return left;
} }
} }
}, }
filter: function(){ function filter(){
return this._pipeFunction(angularFilter); return pipeFunction(angularFilter);
}, }
validator: function(){ function validator(){
return this._pipeFunction(angularValidator); return pipeFunction(angularValidator);
}, }
_pipeFunction: function(fnScope){ function pipeFunction(fnScope){
var fn = this.functionIdent(fnScope); var fn = functionIdent(fnScope);
var argsFn = []; var argsFn = [];
var token; var token;
while(true) { while(true) {
if ((token = this.expect(':'))) { if ((token = expect(':'))) {
argsFn.push(this.expression()); argsFn.push(expression());
} else { } else {
var fnInvoke = function(self, input){ var fnInvoke = function(self, input){
var args = [input]; var args = [input];
@ -380,111 +383,111 @@ Parser.prototype = {
}; };
} }
} }
}, }
expression: function(){ function expression(){
return this.throwStmt(); return throwStmt();
}, }
throwStmt: function(){ function throwStmt(){
if (this.expect('throw')) { if (expect('throw')) {
var throwExp = this.assignment(); var throwExp = assignment();
return function (self) { return function (self) {
throw throwExp(self); throw throwExp(self);
}; };
} else { } else {
return this.assignment(); return assignment();
} }
}, }
assignment: function(){ function assignment(){
var left = this.logicalOR(); var left = logicalOR();
var token; var token;
if (token = this.expect('=')) { if (token = expect('=')) {
if (!left.isAssignable) { if (!left.isAssignable) {
throw "Left hand side '" + throw "Left hand side '" +
this.text.substring(0, token.index) + "' of assignment '" + text.substring(0, token.index) + "' of assignment '" +
this.text.substring(token.index) + "' is not assignable."; text.substring(token.index) + "' is not assignable.";
} }
var ident = function(){return left.isAssignable;}; var ident = function(){return left.isAssignable;};
return this._binary(ident, token.fn, this.logicalOR()); return binaryFn(ident, token.fn, logicalOR());
} else { } else {
return left; return left;
} }
}, }
logicalOR: function(){ function logicalOR(){
var left = this.logicalAND(); var left = logicalAND();
var token; var token;
while(true) { while(true) {
if ((token = this.expect('||'))) { if ((token = expect('||'))) {
left = this._binary(left, token.fn, this.logicalAND()); left = binaryFn(left, token.fn, logicalAND());
} else { } else {
return left; return left;
} }
} }
}, }
logicalAND: function(){ function logicalAND(){
var left = this.equality(); var left = equality();
var token; var token;
if ((token = this.expect('&&'))) { if ((token = expect('&&'))) {
left = this._binary(left, token.fn, this.logicalAND()); left = binaryFn(left, token.fn, logicalAND());
} }
return left; return left;
}, }
equality: function(){ function equality(){
var left = this.relational(); var left = relational();
var token; var token;
if ((token = this.expect('==','!='))) { if ((token = expect('==','!='))) {
left = this._binary(left, token.fn, this.equality()); left = binaryFn(left, token.fn, equality());
} }
return left; return left;
}, }
relational: function(){ function relational(){
var left = this.additive(); var left = additive();
var token; var token;
if (token = this.expect('<', '>', '<=', '>=')) { if (token = expect('<', '>', '<=', '>=')) {
left = this._binary(left, token.fn, this.relational()); left = binaryFn(left, token.fn, relational());
} }
return left; return left;
}, }
additive: function(){ function additive(){
var left = this.multiplicative(); var left = multiplicative();
var token; var token;
while(token = this.expect('+','-')) { while(token = expect('+','-')) {
left = this._binary(left, token.fn, this.multiplicative()); left = binaryFn(left, token.fn, multiplicative());
} }
return left; return left;
}, }
multiplicative: function(){ function multiplicative(){
var left = this.unary(); var left = unary();
var token; var token;
while(token = this.expect('*','/','%')) { while(token = expect('*','/','%')) {
left = this._binary(left, token.fn, this.unary()); left = binaryFn(left, token.fn, unary());
} }
return left; return left;
}, }
unary: function(){ function unary(){
var token; var token;
if (this.expect('+')) { if (expect('+')) {
return this.primary(); return primary();
} else if (token = this.expect('-')) { } else if (token = expect('-')) {
return this._binary(ZERO, token.fn, this.unary()); return binaryFn(ZERO, token.fn, unary());
} else if (token = this.expect('!')) { } else if (token = expect('!')) {
return this._unary(token.fn, this.unary()); return unaryFn(token.fn, unary());
} else { } else {
return this.primary(); return primary();
} }
}, }
functionIdent: function(fnScope) { function functionIdent(fnScope) {
var token = this.expect(); var token = expect();
var element = token.text.split('.'); var element = token.text.split('.');
var instance = fnScope; var instance = fnScope;
var key; var key;
@ -495,58 +498,58 @@ Parser.prototype = {
} }
if (typeof instance != $function) { if (typeof instance != $function) {
throw "Function '" + token.text + "' at column '" + throw "Function '" + token.text + "' at column '" +
(token.index+1) + "' in '" + this.text + "' is not defined."; (token.index+1) + "' in '" + text + "' is not defined.";
} }
return instance; return instance;
}, }
primary: function() { function primary() {
var primary; var primary;
if (this.expect('(')) { if (expect('(')) {
var expression = this.filterChain(); var expression = filterChain();
this.consume(')'); consume(')');
primary = expression; primary = expression;
} else if (this.expect('[')) { } else if (expect('[')) {
primary = this.arrayDeclaration(); primary = arrayDeclaration();
} else if (this.expect('{')) { } else if (expect('{')) {
primary = this.object(); primary = object();
} else { } else {
var token = this.expect(); var token = expect();
primary = token.fn; primary = token.fn;
if (!primary) { if (!primary) {
this.error("not a primary expression", token); error("not a primary expression", token);
} }
} }
var next; var next;
while (next = this.expect('(', '[', '.')) { while (next = expect('(', '[', '.')) {
if (next.text === '(') { if (next.text === '(') {
primary = this.functionCall(primary); primary = functionCall(primary);
} else if (next.text === '[') { } else if (next.text === '[') {
primary = this.objectIndex(primary); primary = objectIndex(primary);
} else if (next.text === '.') { } else if (next.text === '.') {
primary = this.fieldAccess(primary); primary = fieldAccess(primary);
} else { } else {
throw "IMPOSSIBLE"; throw "IMPOSSIBLE";
} }
} }
return primary; return primary;
}, }
fieldAccess: function(object) { function fieldAccess(object) {
var field = this.expect().text; var field = expect().text;
var getter = getterFn(field); var getter = getterFn(field);
var fn = function (self){ var fn = function (self){
return getter(object(self)); return getter(object(self));
}; };
fn.isAssignable = field; fn.isAssignable = field;
return fn; return fn;
}, }
objectIndex: function(obj) { function objectIndex(obj) {
var indexFn = this.expression(); var indexFn = expression();
this.consume(']'); consume(']');
if (this.expect('=')) { if (expect('=')) {
var rhs = this.expression(); var rhs = expression();
return function (self){ return function (self){
return obj(self)[indexFn(self)] = rhs(self); return obj(self)[indexFn(self)] = rhs(self);
}; };
@ -557,38 +560,38 @@ Parser.prototype = {
return (o) ? o[i] : _undefined; return (o) ? o[i] : _undefined;
}; };
} }
}, }
functionCall: function(fn) { function functionCall(fn) {
var argsFn = []; var argsFn = [];
if (this.peekToken().text != ')') { if (peekToken().text != ')') {
do { do {
argsFn.push(this.expression()); argsFn.push(expression());
} while (this.expect(',')); } while (expect(','));
} }
this.consume(')'); consume(')');
return function (self){ return function (self){
var args = []; var args = [];
for ( var i = 0; i < argsFn.length; i++) { for ( var i = 0; i < argsFn.length; i++) {
args.push(argsFn[i](self)); args.push(argsFn[i](self));
} }
var fnPtr = fn(self) || noop; var fnPtr = fn(self) || noop;
// IE stupidity! // IE stupidity!
return fnPtr.apply ? return fnPtr.apply ?
fnPtr.apply(self, args) : fnPtr.apply(self, args) :
fnPtr(args[0], args[1], args[2], args[3], args[4]); fnPtr(args[0], args[1], args[2], args[3], args[4]);
}; };
}, }
// This is used with json array declaration // This is used with json array declaration
arrayDeclaration: function () { function arrayDeclaration () {
var elementFns = []; var elementFns = [];
if (this.peekToken().text != ']') { if (peekToken().text != ']') {
do { do {
elementFns.push(this.expression()); elementFns.push(expression());
} while (this.expect(',')); } while (expect(','));
} }
this.consume(']'); consume(']');
return function (self){ return function (self){
var array = []; var array = [];
for ( var i = 0; i < elementFns.length; i++) { for ( var i = 0; i < elementFns.length; i++) {
@ -596,20 +599,20 @@ Parser.prototype = {
} }
return array; return array;
}; };
}, }
object: function () { function object () {
var keyValues = []; var keyValues = [];
if (this.peekToken().text != '}') { if (peekToken().text != '}') {
do { do {
var token = this.expect(), var token = expect(),
key = token.string || token.text; key = token.string || token.text;
this.consume(":"); consume(":");
var value = this.expression(); var value = expression();
keyValues.push({key:key, value:value}); keyValues.push({key:key, value:value});
} while (this.expect(',')); } while (expect(','));
} }
this.consume('}'); consume('}');
return function (self){ return function (self){
var object = {}; var object = {};
for ( var i = 0; i < keyValues.length; i++) { for ( var i = 0; i < keyValues.length; i++) {
@ -619,39 +622,42 @@ Parser.prototype = {
} }
return object; return object;
}; };
}, }
watch: function () { function watch () {
var decl = []; var decl = [];
while(this.hasTokens()) { while(hasTokens()) {
decl.push(this.watchDecl()); decl.push(watchDecl());
if (!this.expect(';')) { if (!expect(';')) {
this.assertAllConsumed(); assertAllConsumed();
} }
} }
this.assertAllConsumed(); assertAllConsumed();
return function (self){ return function (self){
for ( var i = 0; i < decl.length; i++) { for ( var i = 0; i < decl.length; i++) {
var d = decl[i](self); var d = decl[i](self);
self.addListener(d.name, d.fn); self.addListener(d.name, d.fn);
} }
}; };
}, }
watchDecl: function () { function watchDecl () {
var anchorName = this.expect().text; var anchorName = expect().text;
this.consume(":"); consume(":");
var expression; var expressionFn;
if (this.peekToken().text == '{') { if (peekToken().text == '{') {
this.consume("{"); consume("{");
expression = this.statements(); expressionFn = statements();
this.consume("}"); consume("}");
} else { } else {
expression = this.expression(); expressionFn = expression();
} }
return function(self) { return function(self) {
return {name:anchorName, fn:expression}; return {name:anchorName, fn:expressionFn};
}; };
} }
}; }

View file

@ -90,9 +90,9 @@ function expressionCompile(exp){
if (typeof exp === $function) return exp; if (typeof exp === $function) return exp;
var fn = compileCache[exp]; var fn = compileCache[exp];
if (!fn) { if (!fn) {
var parser = new Parser(exp); var p = parser(exp);
var fnSelf = parser.statements(); var fnSelf = p.statements();
parser.assertAllConsumed(); p.assertAllConsumed();
fn = compileCache[exp] = extend( fn = compileCache[exp] = extend(
function(){ return fnSelf(this);}, function(){ return fnSelf(this);},
{fnSelf: fnSelf}); {fnSelf: fnSelf});

View file

@ -43,7 +43,7 @@
addScript("/Scope.js"); addScript("/Scope.js");
addScript("/Injector.js"); addScript("/Injector.js");
addScript("/jqLite.js"); addScript("/jqLite.js");
addScript("/Parser.js"); addScript("/parser.js");
addScript("/Resource.js"); addScript("/Resource.js");
addScript("/Browser.js"); addScript("/Browser.js");
addScript("/AngularPublic.js"); addScript("/AngularPublic.js");

View file

@ -221,7 +221,7 @@ angularDirective("ng:click", function(expression, element){
angularDirective("ng:watch", function(expression, element){ angularDirective("ng:watch", function(expression, element){
return function(element){ return function(element){
var self = this; var self = this;
new Parser(expression).watch()({ parser(expression).watch()({
addListener:function(watch, exp){ addListener:function(watch, exp){
self.$watch(watch, function(){ self.$watch(watch, function(){
return exp(self); return exp(self);

View file

@ -47,7 +47,7 @@ function dateGetter(name, size, offset, trim) {
var value = date['get' + name].call(date); var value = date['get' + name].call(date);
if (offset > 0 || value > -offset) if (offset > 0 || value > -offset)
value += offset; value += offset;
if (value == 0 && offset == -12 ) value = 12; if (value === 0 && offset == -12 ) value = 12;
return padNumber(value, size, trim); return padNumber(value, size, trim);
}; };
} }

View file

@ -29,7 +29,7 @@ function modelFormattedAccessor(scope, element) {
} }
function compileValidator(expr) { function compileValidator(expr) {
return new Parser(expr).validator()(); return parser(expr).validator()();
} }
function valueAccessor(scope, element) { function valueAccessor(scope, element) {

134
test/JsonSpec.js Normal file
View file

@ -0,0 +1,134 @@
describe('json', function(){
it('should parse Primitives', function() {
assertEquals("null", toJson(0/0));
assertEquals("null", toJson(null));
assertEquals("true", toJson(true));
assertEquals("false", toJson(false));
assertEquals("123.45", toJson(123.45));
assertEquals('"abc"', toJson("abc"));
assertEquals('"a \\t \\n \\r b \\\\"', toJson("a \t \n \r b \\"));
});
it('should parse Escaping', function() {
assertEquals("\"7\\\\\\\"7\"", toJson("7\\\"7"));
});
it('should parse Objects', function() {
assertEquals('{"a":1,"b":2}', toJson({a:1,b:2}));
assertEquals('{"a":{"b":2}}', toJson({a:{b:2}}));
assertEquals('{"a":{"b":{"c":0}}}', toJson({a:{b:{c:0}}}));
assertEquals('{"a":{"b":null}}', toJson({a:{b:0/0}}));
});
it('should parse ObjectPretty', function() {
assertEquals('{\n "a":1,\n "b":2}', toJson({a:1,b:2}, true));
assertEquals('{\n "a":{\n "b":2}}', toJson({a:{b:2}}, true));
});
it('should parse Array', function() {
assertEquals('[]', toJson([]));
assertEquals('[1,"b"]', toJson([1,"b"]));
});
it('should parse IgnoreFunctions', function() {
assertEquals('[null,1]', toJson([function(){},1]));
assertEquals('{}', toJson({a:function(){}}));
});
it('should parse ParseNull', function() {
assertNull(fromJson("null"));
});
it('should parse ParseBoolean', function() {
assertTrue(fromJson("true"));
assertFalse(fromJson("false"));
});
it('should parse $$isIgnored', function() {
assertEquals("{}", toJson({$$:0}));
});
it('should parse ArrayWithEmptyItems', function() {
var a = [];
a[1] = "X";
assertEquals('[null,"X"]', toJson(a));
});
it('should parse ItShouldEscapeUnicode', function() {
assertEquals(1, "\u00a0".length);
assertEquals(8, toJson("\u00a0").length);
assertEquals(1, fromJson(toJson("\u00a0")).length);
});
it('should parse ItShouldUTCDates', function() {
var date = angular.String.toDate("2009-10-09T01:02:03Z");
assertEquals('"2009-10-09T01:02:03Z"', toJson(date));
assertEquals(date.getTime(),
fromJson('"2009-10-09T01:02:03Z"').getTime());
});
it('should parse ItShouldPreventRecursion', function() {
var obj = {a:'b'};
obj.recursion = obj;
assertEquals('{"a":"b","recursion":RECURSION}', angular.toJson(obj));
});
it('should parse ItShouldIgnore$Properties', function() {
var scope = createScope();
scope.a = 'a';
scope['$b'] = '$b';
scope.c = 'c';
expect(angular.toJson(scope)).toEqual('{"a":"a","c":"c","this":RECURSION}');
});
it('should parse ItShouldSerializeInheritedProperties', function() {
var scope = createScope({p:'p'});
scope.a = 'a';
expect(angular.toJson(scope)).toEqual('{"a":"a","p":"p","this":RECURSION}');
});
it('should parse ItShouldSerializeSameObjectsMultipleTimes', function() {
var obj = {a:'b'};
assertEquals('{"A":{"a":"b"},"B":{"a":"b"}}', angular.toJson({A:obj, B:obj}));
});
it('should parse ItShouldNotSerializeUndefinedValues', function() {
assertEquals('{}', angular.toJson({A:undefined}));
});
it('should parse ItShouldParseFloats', function() {
expect(fromJson("{value:2.55, name:'misko'}")).toEqual({value:2.55, name:'misko'});
});
describe('security', function(){
it('should not allow naked expressions', function(){
expect(function(){fromJson('1+2');}).toThrow("Did not understand '+2' while evaluating '1+2'.");
});
it('should not allow naked expressions group', function(){
expect(function(){fromJson('(1+2)');}).toThrow("Expression at column='0' of expression '(1+2)' starting at '(1+2)' is not valid json.");
});
it('should not allow expressions in objects', function(){
expect(function(){fromJson('{a:abc()}');}).toThrow("Expression at column='3' of expression '{a:abc()}' starting at 'abc()}' is not valid json.");
});
it('should not allow expressions in arrays', function(){
expect(function(){fromJson('[1+2]');}).toThrow("Expression at column='2' of expression '[1+2]' starting at '+2]' is not valid json.");
});
it('should not allow vars', function(){
expect(function(){fromJson('[1, x]');}).toThrow("Expression at column='4' of expression '[1, x]' starting at 'x]' is not valid json.");
});
it('should not allow dereference', function(){
expect(function(){fromJson('["".constructor]');}).toThrow("Expression at column='3' of expression '[\"\".constructor]' starting at '.constructor]' is not valid json.");
});
it('should not allow expressions ofter valid json', function(){
expect(function(){fromJson('[].constructor');}).toThrow("Expression at column='2' of expression '[].constructor' starting at '.constructor' is not valid json.");
});
});
});

View file

@ -1,102 +0,0 @@
JsonTest = TestCase("JsonTest");
JsonTest.prototype.testPrimitives = function () {
assertEquals("null", toJson(0/0));
assertEquals("null", toJson(null));
assertEquals("true", toJson(true));
assertEquals("false", toJson(false));
assertEquals("123.45", toJson(123.45));
assertEquals('"abc"', toJson("abc"));
assertEquals('"a \\t \\n \\r b \\\\"', toJson("a \t \n \r b \\"));
};
JsonTest.prototype.testEscaping = function () {
assertEquals("\"7\\\\\\\"7\"", toJson("7\\\"7"));
};
JsonTest.prototype.testObjects = function () {
assertEquals('{"a":1,"b":2}', toJson({a:1,b:2}));
assertEquals('{"a":{"b":2}}', toJson({a:{b:2}}));
assertEquals('{"a":{"b":{"c":0}}}', toJson({a:{b:{c:0}}}));
assertEquals('{"a":{"b":null}}', toJson({a:{b:0/0}}));
};
JsonTest.prototype.testObjectPretty = function () {
assertEquals('{\n "a":1,\n "b":2}', toJson({a:1,b:2}, true));
assertEquals('{\n "a":{\n "b":2}}', toJson({a:{b:2}}, true));
};
JsonTest.prototype.testArray = function () {
assertEquals('[]', toJson([]));
assertEquals('[1,"b"]', toJson([1,"b"]));
};
JsonTest.prototype.testIgnoreFunctions = function () {
assertEquals('[null,1]', toJson([function(){},1]));
assertEquals('{}', toJson({a:function(){}}));
};
JsonTest.prototype.testParseNull = function () {
assertNull(fromJson("null"));
};
JsonTest.prototype.testParseBoolean = function () {
assertTrue(fromJson("true"));
assertFalse(fromJson("false"));
};
JsonTest.prototype.test$$isIgnored = function () {
assertEquals("{}", toJson({$$:0}));
};
JsonTest.prototype.testArrayWithEmptyItems = function () {
var a = [];
a[1] = "X";
assertEquals('[null,"X"]', toJson(a));
};
JsonTest.prototype.testItShouldEscapeUnicode = function () {
assertEquals(1, "\u00a0".length);
assertEquals(8, toJson("\u00a0").length);
assertEquals(1, fromJson(toJson("\u00a0")).length);
};
JsonTest.prototype.testItShouldUTCDates = function() {
var date = angular.String.toDate("2009-10-09T01:02:03Z");
assertEquals('"2009-10-09T01:02:03Z"', toJson(date));
assertEquals(date.getTime(),
fromJson('"2009-10-09T01:02:03Z"').getTime());
};
JsonTest.prototype.testItShouldPreventRecursion = function () {
var obj = {a:'b'};
obj.recursion = obj;
assertEquals('{"a":"b","recursion":RECURSION}', angular.toJson(obj));
};
JsonTest.prototype.testItShouldIgnore$Properties = function() {
var scope = createScope();
scope.a = 'a';
scope['$b'] = '$b';
scope.c = 'c';
expect(angular.toJson(scope)).toEqual('{"a":"a","c":"c","this":RECURSION}');
};
JsonTest.prototype.testItShouldSerializeInheritedProperties = function() {
var scope = createScope({p:'p'});
scope.a = 'a';
expect(angular.toJson(scope)).toEqual('{"a":"a","p":"p","this":RECURSION}');
};
JsonTest.prototype.testItShouldSerializeSameObjectsMultipleTimes = function () {
var obj = {a:'b'};
assertEquals('{"A":{"a":"b"},"B":{"a":"b"}}', angular.toJson({A:obj, B:obj}));
};
JsonTest.prototype.testItShouldNotSerializeUndefinedValues = function () {
assertEquals('{}', angular.toJson({A:undefined}));
};
JsonTest.prototype.testItShouldParseFloats = function () {
expect(fromJson("{value:2.55, name:'misko'}")).toEqual({value:2.55, name:'misko'});
};

View file

@ -443,7 +443,7 @@ describe('parser', function(){
assertEquals(12/6/2, scope.$eval("12/6/2")); assertEquals(12/6/2, scope.$eval("12/6/2"));
}); });
it('should parse BugStringConfusesParser', function(){ it('should parse BugStringConfusesparser', function(){
var scope = createScope(); var scope = createScope();
assertEquals('!', scope.$eval('suffix = "!"')); assertEquals('!', scope.$eval('suffix = "!"'));
}); });