PageRenderTime 25ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 0ms

/src/ng/interpolate.js

https://github.com/TEHEK/angular.js
JavaScript | 353 lines | 148 code | 28 blank | 177 comment | 24 complexity | eb783ee20db0bd1e87eee09cbedff0c2 MD5 | raw file
  1. 'use strict';
  2. var $interpolateMinErr = angular.$interpolateMinErr = minErr('$interpolate');
  3. $interpolateMinErr.throwNoconcat = function(text) {
  4. throw $interpolateMinErr('noconcat',
  5. "Error while interpolating: {0}\nStrict Contextual Escaping disallows " +
  6. "interpolations that concatenate multiple expressions when a trusted value is " +
  7. "required. See http://docs.angularjs.org/api/ng.$sce", text);
  8. };
  9. $interpolateMinErr.interr = function(text, err) {
  10. return $interpolateMinErr('interr', "Can't interpolate: {0}\n{1}", text, err.toString());
  11. };
  12. /**
  13. * @ngdoc provider
  14. * @name $interpolateProvider
  15. *
  16. * @description
  17. *
  18. * Used for configuring the interpolation markup. Defaults to `{{` and `}}`.
  19. *
  20. * @example
  21. <example module="customInterpolationApp">
  22. <file name="index.html">
  23. <script>
  24. var customInterpolationApp = angular.module('customInterpolationApp', []);
  25. customInterpolationApp.config(function($interpolateProvider) {
  26. $interpolateProvider.startSymbol('//');
  27. $interpolateProvider.endSymbol('//');
  28. });
  29. customInterpolationApp.controller('DemoController', function() {
  30. this.label = "This binding is brought you by // interpolation symbols.";
  31. });
  32. </script>
  33. <div ng-app="App" ng-controller="DemoController as demo">
  34. //demo.label//
  35. </div>
  36. </file>
  37. <file name="protractor.js" type="protractor">
  38. it('should interpolate binding with custom symbols', function() {
  39. expect(element(by.binding('demo.label')).getText()).toBe('This binding is brought you by // interpolation symbols.');
  40. });
  41. </file>
  42. </example>
  43. */
  44. function $InterpolateProvider() {
  45. var startSymbol = '{{';
  46. var endSymbol = '}}';
  47. /**
  48. * @ngdoc method
  49. * @name $interpolateProvider#startSymbol
  50. * @description
  51. * Symbol to denote start of expression in the interpolated string. Defaults to `{{`.
  52. *
  53. * @param {string=} value new value to set the starting symbol to.
  54. * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
  55. */
  56. this.startSymbol = function(value) {
  57. if (value) {
  58. startSymbol = value;
  59. return this;
  60. } else {
  61. return startSymbol;
  62. }
  63. };
  64. /**
  65. * @ngdoc method
  66. * @name $interpolateProvider#endSymbol
  67. * @description
  68. * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
  69. *
  70. * @param {string=} value new value to set the ending symbol to.
  71. * @returns {string|self} Returns the symbol when used as getter and self if used as setter.
  72. */
  73. this.endSymbol = function(value) {
  74. if (value) {
  75. endSymbol = value;
  76. return this;
  77. } else {
  78. return endSymbol;
  79. }
  80. };
  81. this.$get = ['$parse', '$exceptionHandler', '$sce', function($parse, $exceptionHandler, $sce) {
  82. var startSymbolLength = startSymbol.length,
  83. endSymbolLength = endSymbol.length,
  84. escapedStartRegexp = new RegExp(startSymbol.replace(/./g, escape), 'g'),
  85. escapedEndRegexp = new RegExp(endSymbol.replace(/./g, escape), 'g');
  86. function escape(ch) {
  87. return '\\\\\\' + ch;
  88. }
  89. function unescapeText(text) {
  90. return text.replace(escapedStartRegexp, startSymbol).
  91. replace(escapedEndRegexp, endSymbol);
  92. }
  93. function stringify(value) {
  94. if (value == null) { // null || undefined
  95. return '';
  96. }
  97. switch (typeof value) {
  98. case 'string':
  99. break;
  100. case 'number':
  101. value = '' + value;
  102. break;
  103. default:
  104. value = toJson(value);
  105. }
  106. return value;
  107. }
  108. /**
  109. * @ngdoc service
  110. * @name $interpolate
  111. * @kind function
  112. *
  113. * @requires $parse
  114. * @requires $sce
  115. *
  116. * @description
  117. *
  118. * Compiles a string with markup into an interpolation function. This service is used by the
  119. * HTML {@link ng.$compile $compile} service for data binding. See
  120. * {@link ng.$interpolateProvider $interpolateProvider} for configuring the
  121. * interpolation markup.
  122. *
  123. *
  124. * ```js
  125. * var $interpolate = ...; // injected
  126. * var exp = $interpolate('Hello {{name | uppercase}}!');
  127. * expect(exp({name:'Angular'}).toEqual('Hello ANGULAR!');
  128. * ```
  129. *
  130. * `$interpolate` takes an optional fourth argument, `allOrNothing`. If `allOrNothing` is
  131. * `true`, the interpolation function will return `undefined` unless all embedded expressions
  132. * evaluate to a value other than `undefined`.
  133. *
  134. * ```js
  135. * var $interpolate = ...; // injected
  136. * var context = {greeting: 'Hello', name: undefined };
  137. *
  138. * // default "forgiving" mode
  139. * var exp = $interpolate('{{greeting}} {{name}}!');
  140. * expect(exp(context)).toEqual('Hello !');
  141. *
  142. * // "allOrNothing" mode
  143. * exp = $interpolate('{{greeting}} {{name}}!', false, null, true);
  144. * expect(exp(context)).toBeUndefined();
  145. * context.name = 'Angular';
  146. * expect(exp(context)).toEqual('Hello Angular!');
  147. * ```
  148. *
  149. * `allOrNothing` is useful for interpolating URLs. `ngSrc` and `ngSrcset` use this behavior.
  150. *
  151. * ####Escaped Interpolation
  152. * $interpolate provides a mechanism for escaping interpolation markers. Start and end markers
  153. * can be escaped by preceding each of their characters with a REVERSE SOLIDUS U+005C (backslash).
  154. * It will be rendered as a regular start/end marker, and will not be interpreted as an expression
  155. * or binding.
  156. *
  157. * This enables web-servers to prevent script injection attacks and defacing attacks, to some
  158. * degree, while also enabling code examples to work without relying on the
  159. * {@link ng.directive:ngNonBindable ngNonBindable} directive.
  160. *
  161. * **For security purposes, it is strongly encouraged that web servers escape user-supplied data,
  162. * replacing angle brackets (&lt;, &gt;) with &amp;lt; and &amp;gt; respectively, and replacing all
  163. * interpolation start/end markers with their escaped counterparts.**
  164. *
  165. * Escaped interpolation markers are only replaced with the actual interpolation markers in rendered
  166. * output when the $interpolate service processes the text. So, for HTML elements interpolated
  167. * by {@link ng.$compile $compile}, or otherwise interpolated with the `mustHaveExpression` parameter
  168. * set to `true`, the interpolated text must contain an unescaped interpolation expression. As such,
  169. * this is typically useful only when user-data is used in rendering a template from the server, or
  170. * when otherwise untrusted data is used by a directive.
  171. *
  172. * <example>
  173. * <file name="index.html">
  174. * <div ng-init="username='A user'">
  175. * <p ng-init="apptitle='Escaping demo'">{{apptitle}}: \{\{ username = "defaced value"; \}\}
  176. * </p>
  177. * <p><strong>{{username}}</strong> attempts to inject code which will deface the
  178. * application, but fails to accomplish their task, because the server has correctly
  179. * escaped the interpolation start/end markers with REVERSE SOLIDUS U+005C (backslash)
  180. * characters.</p>
  181. * <p>Instead, the result of the attempted script injection is visible, and can be removed
  182. * from the database by an administrator.</p>
  183. * </div>
  184. * </file>
  185. * </example>
  186. *
  187. * @param {string} text The text with markup to interpolate.
  188. * @param {boolean=} mustHaveExpression if set to true then the interpolation string must have
  189. * embedded expression in order to return an interpolation function. Strings with no
  190. * embedded expression will return null for the interpolation function.
  191. * @param {string=} trustedContext when provided, the returned function passes the interpolated
  192. * result through {@link ng.$sce#getTrusted $sce.getTrusted(interpolatedResult,
  193. * trustedContext)} before returning it. Refer to the {@link ng.$sce $sce} service that
  194. * provides Strict Contextual Escaping for details.
  195. * @param {boolean=} allOrNothing if `true`, then the returned function returns undefined
  196. * unless all embedded expressions evaluate to a value other than `undefined`.
  197. * @returns {function(context)} an interpolation function which is used to compute the
  198. * interpolated string. The function has these parameters:
  199. *
  200. * - `context`: evaluation context for all expressions embedded in the interpolated text
  201. */
  202. function $interpolate(text, mustHaveExpression, trustedContext, allOrNothing) {
  203. allOrNothing = !!allOrNothing;
  204. var startIndex,
  205. endIndex,
  206. index = 0,
  207. expressions = [],
  208. parseFns = [],
  209. textLength = text.length,
  210. exp,
  211. concat = [],
  212. expressionPositions = [];
  213. while (index < textLength) {
  214. if (((startIndex = text.indexOf(startSymbol, index)) != -1) &&
  215. ((endIndex = text.indexOf(endSymbol, startIndex + startSymbolLength)) != -1)) {
  216. if (index !== startIndex) {
  217. concat.push(unescapeText(text.substring(index, startIndex)));
  218. }
  219. exp = text.substring(startIndex + startSymbolLength, endIndex);
  220. expressions.push(exp);
  221. parseFns.push($parse(exp, parseStringifyInterceptor));
  222. index = endIndex + endSymbolLength;
  223. expressionPositions.push(concat.length);
  224. concat.push('');
  225. } else {
  226. // we did not find an interpolation, so we have to add the remainder to the separators array
  227. if (index !== textLength) {
  228. concat.push(unescapeText(text.substring(index)));
  229. }
  230. break;
  231. }
  232. }
  233. // Concatenating expressions makes it hard to reason about whether some combination of
  234. // concatenated values are unsafe to use and could easily lead to XSS. By requiring that a
  235. // single expression be used for iframe[src], object[src], etc., we ensure that the value
  236. // that's used is assigned or constructed by some JS code somewhere that is more testable or
  237. // make it obvious that you bound the value to some user controlled value. This helps reduce
  238. // the load when auditing for XSS issues.
  239. if (trustedContext && concat.length > 1) {
  240. $interpolateMinErr.throwNoconcat(text);
  241. }
  242. if (!mustHaveExpression || expressions.length) {
  243. var compute = function(values) {
  244. for (var i = 0, ii = expressions.length; i < ii; i++) {
  245. if (allOrNothing && isUndefined(values[i])) return;
  246. concat[expressionPositions[i]] = values[i];
  247. }
  248. return concat.join('');
  249. };
  250. var getValue = function(value) {
  251. return trustedContext ?
  252. $sce.getTrusted(trustedContext, value) :
  253. $sce.valueOf(value);
  254. };
  255. return extend(function interpolationFn(context) {
  256. var i = 0;
  257. var ii = expressions.length;
  258. var values = new Array(ii);
  259. try {
  260. for (; i < ii; i++) {
  261. values[i] = parseFns[i](context);
  262. }
  263. return compute(values);
  264. } catch (err) {
  265. $exceptionHandler($interpolateMinErr.interr(text, err));
  266. }
  267. }, {
  268. // all of these properties are undocumented for now
  269. exp: text, //just for compatibility with regular watchers created via $watch
  270. expressions: expressions,
  271. $$watchDelegate: function(scope, listener) {
  272. var lastValue;
  273. return scope.$watchGroup(parseFns, function interpolateFnWatcher(values, oldValues) {
  274. var currValue = compute(values);
  275. if (isFunction(listener)) {
  276. listener.call(this, currValue, values !== oldValues ? lastValue : currValue, scope);
  277. }
  278. lastValue = currValue;
  279. });
  280. }
  281. });
  282. }
  283. function parseStringifyInterceptor(value) {
  284. try {
  285. value = getValue(value);
  286. return allOrNothing && !isDefined(value) ? value : stringify(value);
  287. } catch (err) {
  288. $exceptionHandler($interpolateMinErr.interr(text, err));
  289. }
  290. }
  291. }
  292. /**
  293. * @ngdoc method
  294. * @name $interpolate#startSymbol
  295. * @description
  296. * Symbol to denote the start of expression in the interpolated string. Defaults to `{{`.
  297. *
  298. * Use {@link ng.$interpolateProvider#startSymbol `$interpolateProvider.startSymbol`} to change
  299. * the symbol.
  300. *
  301. * @returns {string} start symbol.
  302. */
  303. $interpolate.startSymbol = function() {
  304. return startSymbol;
  305. };
  306. /**
  307. * @ngdoc method
  308. * @name $interpolate#endSymbol
  309. * @description
  310. * Symbol to denote the end of expression in the interpolated string. Defaults to `}}`.
  311. *
  312. * Use {@link ng.$interpolateProvider#endSymbol `$interpolateProvider.endSymbol`} to change
  313. * the symbol.
  314. *
  315. * @returns {string} end symbol.
  316. */
  317. $interpolate.endSymbol = function() {
  318. return endSymbol;
  319. };
  320. return $interpolate;
  321. }];
  322. }