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 /src | |
| parent | 352dbfa38fca660a80d6fae2c6e810f820247791 (diff) | |
| download | angular.js-9e9bdbdc405b6afecd2e536e375c9d8fe40f110b.tar.bz2 | |
JSON parser is now strict (ie, expressions are not allowed for security)
Close #57
Diffstat (limited to 'src')
| -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 | 
8 files changed, 249 insertions, 242 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) {  | 
