diff options
| author | Misko Hevery | 2010-10-15 15:28:58 -0700 |
|---|---|---|
| committer | Misko Hevery | 2010-10-18 08:50:36 -0700 |
| commit | 9e9bdbdc405b6afecd2e536e375c9d8fe40f110b (patch) | |
| tree | 89bd9cdeb19782a6d449c931ae4688e5617a562e | |
| parent | 352dbfa38fca660a80d6fae2c6e810f820247791 (diff) | |
| download | angular.js-9e9bdbdc405b6afecd2e536e375c9d8fe40f110b.tar.bz2 | |
JSON parser is now strict (ie, expressions are not allowed for security)
Close #57
| -rw-r--r-- | src/Angular.js | 1 | ||||
| -rw-r--r-- | src/JSON.js | 6 | ||||
| -rw-r--r-- | src/Parser.js | 470 | ||||
| -rw-r--r-- | src/Scope.js | 6 | ||||
| -rw-r--r-- | src/angular-bootstrap.js | 2 | ||||
| -rw-r--r-- | src/directives.js | 2 | ||||
| -rw-r--r-- | src/filters.js | 2 | ||||
| -rw-r--r-- | src/widgets.js | 2 | ||||
| -rw-r--r-- | test/JsonSpec.js | 134 | ||||
| -rw-r--r-- | test/JsonTest.js | 102 | ||||
| -rw-r--r-- | test/ParserSpec.js | 2 |
11 files changed, 384 insertions, 345 deletions
diff --git a/src/Angular.js b/src/Angular.js index 3be61673..79557c2c 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -99,6 +99,7 @@ function inherit(parent, extra) { function noop() {} function identity($) {return $;} +function valueFn(value) {return function(){ return value; };} function extensionMap(angular, name, transform) { var extPoint; return angular[name] || (extPoint = angular[name] = function (name, fn, prop){ diff --git a/src/JSON.js b/src/JSON.js index 4b1326b2..8606319a 100644 --- a/src/JSON.js +++ b/src/JSON.js @@ -9,9 +9,9 @@ function toJson(obj, pretty){ function fromJson(json) { if (!json) return json; try { - var parser = new Parser(json, true); - var expression = parser.primary(); - parser.assertAllConsumed(); + var p = parser(json, true); + var expression = p.primary(); + p.assertAllConsumed(); return expression(); } catch (e) { error("fromJson error: ", json, e); diff --git a/src/Parser.js b/src/Parser.js index 9082bb2a..0970144f 100644 --- a/src/Parser.js +++ b/src/Parser.js @@ -25,52 +25,37 @@ var OPERATORS = { }; var ESCAPE = {"n":"\n", "f":"\f", "r":"\r", "t":"\t", "v":"\v", "'":"'", '"':'"'}; -function lex(text, parseStrings){ - var dateParseLength = parseStrings ? 20 : -1, +function lex(text, parseStringsForObjects){ + var dateParseLength = parseStringsForObjects ? 20 : -1, tokens = [], + token, index = 0, - canStartRegExp = true; + json = [], + ch, + lastCh = ','; // can start regexp while (index < text.length) { - var ch = text.charAt(index); - if (ch == '"' || ch == "'") { + ch = text.charAt(index); + if (is('"\'')) { readString(ch); - canStartRegExp = true; - } 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())) { + } else if (isNumber(ch) || is('.') && isNumber(peek())) { readNumber(); - canStartRegExp = false; - } else if ( ch == ':' || ch == '.' || ch == ',' || ch == ';') { - tokens.push({index:index, text:ch}); - index++; - canStartRegExp = true; - } else if ( canStartRegExp && ch == '/' ) { + } else if ( was('({[:,;') && is('/') ) { readRegexp(); - canStartRegExp = false; - } else if ( isNumber(ch) ) { - readNumber(); - canStartRegExp = false; } else if (isIdent(ch)) { 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)) { index++; + continue; } else { var ch2 = ch + peek(), fn = OPERATORS[ch], @@ -87,11 +72,19 @@ function lex(text, parseStrings){ "] in expression '" + text + "' at column '" + (index+1) + "'."; } - canStartRegExp = true; } + lastCh = ch; } return tokens; + function is(chars) { + return chars.indexOf(ch) != -1; + } + + function was(chars) { + return chars.indexOf(lastCh) != -1; + } + function peek() { return index + 1 < text.length ? text.charAt(index + 1) : false; } @@ -136,7 +129,7 @@ function lex(text, parseStrings){ index++; } number = 1 * number; - tokens.push({index:start, text:number, + tokens.push({index:start, text:number, json:true, fn:function(){return number;}}); } function readIdent() { @@ -156,8 +149,9 @@ function lex(text, parseStrings){ fn = getterFn(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) { var start = index; index++; @@ -189,7 +183,7 @@ function lex(text, parseStrings){ escape = true; } else if (ch == quote) { index++; - tokens.push({index:start, text:rawString, string:string, + tokens.push({index:start, text:rawString, string:string, json:true, fn:function(){ return (string.length == dateParseLength) ? angular['String']['toDate'](string) : string; @@ -241,32 +235,34 @@ function lex(text, parseStrings){ ///////////////////////////////////////// -function Parser(text, parseStrings){ - this.text = text; - this.tokens = lex(text, parseStrings); - this.index = 0; -} - -var ZERO = function(){ - return 0; -}; - -Parser.prototype = { - error: function(msg, token) { +function parser(text, json){ + var ZERO = valueFn(0), + tokens = lex(text, json); + return { + assertAllConsumed: assertAllConsumed, + primary: primary, + statements: statements, + validator: validator, + filter: filter, + watch: watch + }; + + /////////////////////////////////// + + function error(msg, token) { throw "Token '" + token.text + "' is " + msg + " at column='" + (token.index + 1) + "' of expression '" + - this.text + "' starting at '" + this.text.substring(token.index) + "'."; - }, + text + "' starting at '" + text.substring(token.index) + "'."; + } - peekToken: function() { - if (this.tokens.length === 0) - throw "Unexpected end of expression: " + this.text; - return this.tokens[0]; - }, + function peekToken() { + if (tokens.length === 0) + throw "Unexpected end of expression: " + text; + return tokens[0]; + } - peek: function(e1, e2, e3, e4) { - var tokens = this.tokens; + function peek(e1, e2, e3, e4) { if (tokens.length > 0) { var token = tokens[0]; var t = token.text; @@ -276,57 +272,64 @@ Parser.prototype = { } } return false; - }, + } - expect: function(e1, e2, e3, e4){ - var token = this.peek(e1, e2, e3, e4); + function expect(e1, e2, e3, e4){ + var token = peek(e1, e2, e3, e4); 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; return token; } return false; - }, + } - consume: function(e1){ - if (!this.expect(e1)) { - var token = this.peek(); + function consume(e1){ + if (!expect(e1)) { + var token = peek(); throw "Expecting '" + e1 + "' at column '" + (token.index+1) + "' in '" + - this.text + "' got '" + - this.text.substring(token.index) + "'."; + text + "' got '" + + text.substring(token.index) + "'."; } - }, + } - _unary: function(fn, right) { + function unaryFn(fn, right) { return function(self) { return fn(self, right(self)); }; - }, + } - _binary: function(left, fn, right) { + function binaryFn(left, fn, right) { return function(self) { return fn(self, left(self), right(self)); }; - }, + } - hasTokens: function () { - return this.tokens.length > 0; - }, + function hasTokens () { + return tokens.length > 0; + } - assertAllConsumed: function(){ - if (this.tokens.length !== 0) { - throw "Did not understand '" + this.text.substring(this.tokens[0].index) + - "' while evaluating '" + this.text + "'."; + function assertAllConsumed(){ + if (tokens.length !== 0) { + throw "Did not understand '" + text.substring(tokens[0].index) + + "' while evaluating '" + text + "'."; } - }, + } - statements: function(){ + function statements(){ var statements = []; while(true) { - if (this.tokens.length > 0 && !this.peek('}', ')', ';', ']')) - statements.push(this.filterChain()); - if (!this.expect(';')) { + if (tokens.length > 0 && !peek('}', ')', ';', ']')) + statements.push(filterChain()); + if (!expect(';')) { return function (self){ var value; for ( var i = 0; i < statements.length; i++) { @@ -338,35 +341,35 @@ Parser.prototype = { }; } } - }, + } - filterChain: function(){ - var left = this.expression(); + function filterChain(){ + var left = expression(); var token; while(true) { - if ((token = this.expect('|'))) { - left = this._binary(left, token.fn, this.filter()); + if ((token = expect('|'))) { + left = binaryFn(left, token.fn, filter()); } else { return left; } } - }, + } - filter: function(){ - return this._pipeFunction(angularFilter); - }, + function filter(){ + return pipeFunction(angularFilter); + } - validator: function(){ - return this._pipeFunction(angularValidator); - }, + function validator(){ + return pipeFunction(angularValidator); + } - _pipeFunction: function(fnScope){ - var fn = this.functionIdent(fnScope); + function pipeFunction(fnScope){ + var fn = functionIdent(fnScope); var argsFn = []; var token; while(true) { - if ((token = this.expect(':'))) { - argsFn.push(this.expression()); + if ((token = expect(':'))) { + argsFn.push(expression()); } else { var fnInvoke = function(self, input){ var args = [input]; @@ -380,111 +383,111 @@ Parser.prototype = { }; } } - }, + } - expression: function(){ - return this.throwStmt(); - }, + function expression(){ + return throwStmt(); + } - throwStmt: function(){ - if (this.expect('throw')) { - var throwExp = this.assignment(); + function throwStmt(){ + if (expect('throw')) { + var throwExp = assignment(); return function (self) { throw throwExp(self); }; } else { - return this.assignment(); + return assignment(); } - }, + } - assignment: function(){ - var left = this.logicalOR(); + function assignment(){ + var left = logicalOR(); var token; - if (token = this.expect('=')) { + if (token = expect('=')) { if (!left.isAssignable) { throw "Left hand side '" + - this.text.substring(0, token.index) + "' of assignment '" + - this.text.substring(token.index) + "' is not assignable."; + text.substring(0, token.index) + "' of assignment '" + + text.substring(token.index) + "' is not assignable."; } var ident = function(){return left.isAssignable;}; - return this._binary(ident, token.fn, this.logicalOR()); + return binaryFn(ident, token.fn, logicalOR()); } else { - return left; + return left; } - }, + } - logicalOR: function(){ - var left = this.logicalAND(); + function logicalOR(){ + var left = logicalAND(); var token; while(true) { - if ((token = this.expect('||'))) { - left = this._binary(left, token.fn, this.logicalAND()); + if ((token = expect('||'))) { + left = binaryFn(left, token.fn, logicalAND()); } else { return left; } } - }, + } - logicalAND: function(){ - var left = this.equality(); + function logicalAND(){ + var left = equality(); var token; - if ((token = this.expect('&&'))) { - left = this._binary(left, token.fn, this.logicalAND()); + if ((token = expect('&&'))) { + left = binaryFn(left, token.fn, logicalAND()); } return left; - }, + } - equality: function(){ - var left = this.relational(); + function equality(){ + var left = relational(); var token; - if ((token = this.expect('==','!='))) { - left = this._binary(left, token.fn, this.equality()); + if ((token = expect('==','!='))) { + left = binaryFn(left, token.fn, equality()); } return left; - }, + } - relational: function(){ - var left = this.additive(); + function relational(){ + var left = additive(); var token; - if (token = this.expect('<', '>', '<=', '>=')) { - left = this._binary(left, token.fn, this.relational()); + if (token = expect('<', '>', '<=', '>=')) { + left = binaryFn(left, token.fn, relational()); } return left; - }, + } - additive: function(){ - var left = this.multiplicative(); + function additive(){ + var left = multiplicative(); var token; - while(token = this.expect('+','-')) { - left = this._binary(left, token.fn, this.multiplicative()); + while(token = expect('+','-')) { + left = binaryFn(left, token.fn, multiplicative()); } return left; - }, + } - multiplicative: function(){ - var left = this.unary(); + function multiplicative(){ + var left = unary(); var token; - while(token = this.expect('*','/','%')) { - left = this._binary(left, token.fn, this.unary()); + while(token = expect('*','/','%')) { + left = binaryFn(left, token.fn, unary()); } return left; - }, + } - unary: function(){ + function unary(){ var token; - if (this.expect('+')) { - return this.primary(); - } else if (token = this.expect('-')) { - return this._binary(ZERO, token.fn, this.unary()); - } else if (token = this.expect('!')) { - return this._unary(token.fn, this.unary()); + if (expect('+')) { + return primary(); + } else if (token = expect('-')) { + return binaryFn(ZERO, token.fn, unary()); + } else if (token = expect('!')) { + return unaryFn(token.fn, unary()); } else { - return this.primary(); + return primary(); } - }, + } - functionIdent: function(fnScope) { - var token = this.expect(); + function functionIdent(fnScope) { + var token = expect(); var element = token.text.split('.'); var instance = fnScope; var key; @@ -495,58 +498,58 @@ Parser.prototype = { } if (typeof instance != $function) { 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; - }, + } - primary: function() { + function primary() { var primary; - if (this.expect('(')) { - var expression = this.filterChain(); - this.consume(')'); + if (expect('(')) { + var expression = filterChain(); + consume(')'); primary = expression; - } else if (this.expect('[')) { - primary = this.arrayDeclaration(); - } else if (this.expect('{')) { - primary = this.object(); + } else if (expect('[')) { + primary = arrayDeclaration(); + } else if (expect('{')) { + primary = object(); } else { - var token = this.expect(); + var token = expect(); primary = token.fn; if (!primary) { - this.error("not a primary expression", token); + error("not a primary expression", token); } } var next; - while (next = this.expect('(', '[', '.')) { + while (next = expect('(', '[', '.')) { if (next.text === '(') { - primary = this.functionCall(primary); + primary = functionCall(primary); } else if (next.text === '[') { - primary = this.objectIndex(primary); + primary = objectIndex(primary); } else if (next.text === '.') { - primary = this.fieldAccess(primary); + primary = fieldAccess(primary); } else { throw "IMPOSSIBLE"; } } return primary; - }, + } - fieldAccess: function(object) { - var field = this.expect().text; + function fieldAccess(object) { + var field = expect().text; var getter = getterFn(field); var fn = function (self){ return getter(object(self)); }; fn.isAssignable = field; return fn; - }, + } - objectIndex: function(obj) { - var indexFn = this.expression(); - this.consume(']'); - if (this.expect('=')) { - var rhs = this.expression(); + function objectIndex(obj) { + var indexFn = expression(); + consume(']'); + if (expect('=')) { + var rhs = expression(); return function (self){ return obj(self)[indexFn(self)] = rhs(self); }; @@ -557,38 +560,38 @@ Parser.prototype = { return (o) ? o[i] : _undefined; }; } - }, + } - functionCall: function(fn) { + function functionCall(fn) { var argsFn = []; - if (this.peekToken().text != ')') { + if (peekToken().text != ')') { do { - argsFn.push(this.expression()); - } while (this.expect(',')); + argsFn.push(expression()); + } while (expect(',')); } - this.consume(')'); + consume(')'); return function (self){ var args = []; for ( var i = 0; i < argsFn.length; i++) { args.push(argsFn[i](self)); } - var fnPtr = fn(self) || noop; - // IE stupidity! - return fnPtr.apply ? - fnPtr.apply(self, args) : - fnPtr(args[0], args[1], args[2], args[3], args[4]); + var fnPtr = fn(self) || noop; + // IE stupidity! + return fnPtr.apply ? + fnPtr.apply(self, args) : + fnPtr(args[0], args[1], args[2], args[3], args[4]); }; - }, + } // This is used with json array declaration - arrayDeclaration: function () { + function arrayDeclaration () { var elementFns = []; - if (this.peekToken().text != ']') { + if (peekToken().text != ']') { do { - elementFns.push(this.expression()); - } while (this.expect(',')); + elementFns.push(expression()); + } while (expect(',')); } - this.consume(']'); + consume(']'); return function (self){ var array = []; for ( var i = 0; i < elementFns.length; i++) { @@ -596,20 +599,20 @@ Parser.prototype = { } return array; }; - }, + } - object: function () { + function object () { var keyValues = []; - if (this.peekToken().text != '}') { + if (peekToken().text != '}') { do { - var token = this.expect(), - key = token.string || token.text; - this.consume(":"); - var value = this.expression(); + var token = expect(), + key = token.string || token.text; + consume(":"); + var value = expression(); keyValues.push({key:key, value:value}); - } while (this.expect(',')); + } while (expect(',')); } - this.consume('}'); + consume('}'); return function (self){ var object = {}; for ( var i = 0; i < keyValues.length; i++) { @@ -619,39 +622,42 @@ Parser.prototype = { } return object; }; - }, + } - watch: function () { + function watch () { var decl = []; - while(this.hasTokens()) { - decl.push(this.watchDecl()); - if (!this.expect(';')) { - this.assertAllConsumed(); + while(hasTokens()) { + decl.push(watchDecl()); + if (!expect(';')) { + assertAllConsumed(); } } - this.assertAllConsumed(); + assertAllConsumed(); return function (self){ for ( var i = 0; i < decl.length; i++) { var d = decl[i](self); self.addListener(d.name, d.fn); } }; - }, - - watchDecl: function () { - var anchorName = this.expect().text; - this.consume(":"); - var expression; - if (this.peekToken().text == '{') { - this.consume("{"); - expression = this.statements(); - this.consume("}"); + } + + function watchDecl () { + var anchorName = expect().text; + consume(":"); + var expressionFn; + if (peekToken().text == '{') { + consume("{"); + expressionFn = statements(); + consume("}"); } else { - expression = this.expression(); + expressionFn = expression(); } return function(self) { - return {name:anchorName, fn:expression}; + return {name:anchorName, fn:expressionFn}; }; } -}; +} + + + diff --git a/src/Scope.js b/src/Scope.js index c7de097b..0c6205a5 100644 --- a/src/Scope.js +++ b/src/Scope.js @@ -90,9 +90,9 @@ function expressionCompile(exp){ if (typeof exp === $function) return exp; var fn = compileCache[exp]; if (!fn) { - var parser = new Parser(exp); - var fnSelf = parser.statements(); - parser.assertAllConsumed(); + var p = parser(exp); + var fnSelf = p.statements(); + p.assertAllConsumed(); fn = compileCache[exp] = extend( function(){ return fnSelf(this);}, {fnSelf: fnSelf}); diff --git a/src/angular-bootstrap.js b/src/angular-bootstrap.js index 581f4ff9..4c95e8b0 100644 --- a/src/angular-bootstrap.js +++ b/src/angular-bootstrap.js @@ -43,7 +43,7 @@ addScript("/Scope.js"); addScript("/Injector.js"); addScript("/jqLite.js"); - addScript("/Parser.js"); + addScript("/parser.js"); addScript("/Resource.js"); addScript("/Browser.js"); addScript("/AngularPublic.js"); diff --git a/src/directives.js b/src/directives.js index c3d732a2..829be3e1 100644 --- a/src/directives.js +++ b/src/directives.js @@ -221,7 +221,7 @@ angularDirective("ng:click", function(expression, element){ angularDirective("ng:watch", function(expression, element){ return function(element){ var self = this; - new Parser(expression).watch()({ + parser(expression).watch()({ addListener:function(watch, exp){ self.$watch(watch, function(){ return exp(self); diff --git a/src/filters.js b/src/filters.js index 16d1f591..56f350ed 100644 --- a/src/filters.js +++ b/src/filters.js @@ -47,7 +47,7 @@ function dateGetter(name, size, offset, trim) { var value = date['get' + name].call(date); if (offset > 0 || value > -offset) value += offset; - if (value == 0 && offset == -12 ) value = 12; + if (value === 0 && offset == -12 ) value = 12; return padNumber(value, size, trim); }; } diff --git a/src/widgets.js b/src/widgets.js index 83a784f1..877f4a72 100644 --- a/src/widgets.js +++ b/src/widgets.js @@ -29,7 +29,7 @@ function modelFormattedAccessor(scope, element) { } function compileValidator(expr) { - return new Parser(expr).validator()(); + return parser(expr).validator()(); } function valueAccessor(scope, element) { diff --git a/test/JsonSpec.js b/test/JsonSpec.js new file mode 100644 index 00000000..6fc40e09 --- /dev/null +++ b/test/JsonSpec.js @@ -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."); + }); + }); + +}); + diff --git a/test/JsonTest.js b/test/JsonTest.js deleted file mode 100644 index c723121f..00000000 --- a/test/JsonTest.js +++ /dev/null @@ -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'}); -}; diff --git a/test/ParserSpec.js b/test/ParserSpec.js index ac359cb0..6c45d52f 100644 --- a/test/ParserSpec.js +++ b/test/ParserSpec.js @@ -443,7 +443,7 @@ describe('parser', function(){ assertEquals(12/6/2, scope.$eval("12/6/2")); }); - it('should parse BugStringConfusesParser', function(){ + it('should parse BugStringConfusesparser', function(){ var scope = createScope(); assertEquals('!', scope.$eval('suffix = "!"')); }); |
