@@ -41,6 +41,8 @@ var $interpolateMinErr = minErr('$interpolate');
41
41
function $InterpolateProvider ( ) {
42
42
var startSymbol = '{{' ;
43
43
var endSymbol = '}}' ;
44
+ var escapedStartSymbol = '{{{{' ;
45
+ var escapedEndSymbol = '}}}}' ;
44
46
45
47
/**
46
48
* @ngdoc method
@@ -49,11 +51,15 @@ function $InterpolateProvider() {
49
51
* Symbol to denote start of expression in the interpolated string. Defaults to `{{`.
50
52
*
51
53
* @param {string= } value new value to set the starting symbol to.
54
+ * @param {string= } escaped new value to set the escaped starting symbol to.
52
55
* @returns {string|self } Returns the symbol when used as getter and self if used as setter.
53
56
*/
54
- this . startSymbol = function ( value ) {
57
+ this . startSymbol = function ( value , escaped ) {
55
58
if ( value ) {
56
59
startSymbol = value ;
60
+ if ( escaped ) {
61
+ escapedStartSymbol = escaped ;
62
+ }
57
63
return this ;
58
64
} else {
59
65
return startSymbol ;
@@ -69,9 +75,12 @@ function $InterpolateProvider() {
69
75
* @param {string= } value new value to set the ending symbol to.
70
76
* @returns {string|self } Returns the symbol when used as getter and self if used as setter.
71
77
*/
72
- this . endSymbol = function ( value ) {
78
+ this . endSymbol = function ( value , escaped ) {
73
79
if ( value ) {
74
80
endSymbol = value ;
81
+ if ( escaped ) {
82
+ escapedEndSymbol = escaped ;
83
+ }
75
84
return this ;
76
85
} else {
77
86
return endSymbol ;
@@ -81,7 +90,32 @@ function $InterpolateProvider() {
81
90
82
91
this . $get = [ '$parse' , '$exceptionHandler' , '$sce' , function ( $parse , $exceptionHandler , $sce ) {
83
92
var startSymbolLength = startSymbol . length ,
84
- endSymbolLength = endSymbol . length ;
93
+ endSymbolLength = endSymbol . length ,
94
+ escapedStartLength = escapedStartSymbol . length ,
95
+ escapedEndLength = escapedEndSymbol . length ,
96
+ escapedLength = escapedStartLength + escapedEndLength ,
97
+ ESCAPED_EXPR_REGEXP = new RegExp ( lit ( escapedStartSymbol ) + '((?:.|\n)*?)' + lit ( escapedEndSymbol ) , 'm' ) ,
98
+ EXPR_REGEXP = new RegExp ( lit ( startSymbol ) + '((?:.|\n)*?)' + lit ( endSymbol ) , 'm' ) ;
99
+
100
+ function lit ( str ) {
101
+ return str . replace ( / ( [ \( \) \[ \] \{ \} \+ \\ \^ \$ \. \! \? \* \= \: \| \- ] ) / g, function ( op ) {
102
+ return '\\' + op ;
103
+ } ) ;
104
+ }
105
+
106
+ function Piece ( text , isExpr ) {
107
+ this . text = text ;
108
+ this . isExpr = isExpr ;
109
+ }
110
+
111
+ function addPiece ( text , isExpr , pieces ) {
112
+ var lastPiece = pieces . length ? pieces [ pieces . length - 1 ] : null ;
113
+ if ( ! isExpr && lastPiece && ! lastPiece . isExpr ) {
114
+ lastPiece . text += text ;
115
+ } else {
116
+ pieces . push ( new Piece ( text , isExpr , pieces ) ) ;
117
+ }
118
+ }
85
119
86
120
/**
87
121
* @ngdoc service
@@ -152,34 +186,67 @@ function $InterpolateProvider() {
152
186
textLength = text . length ,
153
187
hasInterpolation = false ,
154
188
hasText = false ,
155
- exp ,
156
- concat = [ ] ,
157
- lastValuesCache = { values : { } , results : { } } ;
158
-
159
- while ( index < textLength ) {
160
- if ( ( ( startIndex = text . indexOf ( startSymbol , index ) ) != - 1 ) &&
161
- ( ( endIndex = text . indexOf ( endSymbol , startIndex + startSymbolLength ) ) != - 1 ) ) {
162
- if ( index !== startIndex ) hasText = true ;
163
- separators . push ( text . substring ( index , startIndex ) ) ;
164
- exp = text . substring ( startIndex + startSymbolLength , endIndex ) ;
165
- expressions . push ( exp ) ;
166
- parseFns . push ( $parse ( exp ) ) ;
167
- index = endIndex + endSymbolLength ;
189
+ exp = text ,
190
+ concat ,
191
+ lastValuesCache = { values : { } , results : { } } ,
192
+ pieces = [ ] ,
193
+ piece ,
194
+ indexes = [ ] ;
195
+
196
+ while ( text . length ) {
197
+ var expr = EXPR_REGEXP . exec ( text ) ;
198
+ var escaped = ESCAPED_EXPR_REGEXP . exec ( text ) ;
199
+ var until = text . length ;
200
+ var chunk ;
201
+ var from = 0 ;
202
+
203
+ if ( expr ) {
204
+ until = expr . index ;
205
+ }
206
+
207
+ if ( escaped && escaped . index <= until ) {
208
+ from = escaped . index ;
209
+ until = escaped . index + escaped [ 0 ] . length ;
210
+ escaped = startSymbol + escaped [ 1 ] + endSymbol ;
211
+ expr = null ;
212
+ }
213
+
214
+ if ( until > 0 ) {
215
+ chunk = isString ( escaped ) ? text . substring ( 0 , from ) + escaped : text . substring ( 0 , until ) ;
216
+ text = text . slice ( until ) ;
217
+ addPiece ( chunk , false , pieces ) ;
218
+ separators . push ( chunk ) ;
219
+ hasText = true ;
220
+ } else {
221
+ separators . push ( '' ) ;
222
+ }
223
+
224
+ if ( expr ) {
225
+ text = text . slice ( expr [ 0 ] . length ) ;
226
+ addPiece ( expr [ 1 ] , true , pieces ) ;
227
+ expr = null ;
168
228
hasInterpolation = true ;
229
+ }
230
+ }
231
+
232
+ concat = new Array ( pieces . length ) ;
233
+ for ( index = 0 ; index < pieces . length ; ++ index ) {
234
+ piece = pieces [ index ] ;
235
+ if ( piece . isExpr ) {
236
+ parseFns . push ( $parse ( piece . text ) ) ;
237
+ expressions . push ( piece . text ) ;
238
+ indexes . push ( index ) ;
169
239
} else {
170
- // we did not find an interpolation, so we have to add the remainder to the separators array
171
- if ( index !== textLength ) {
172
- hasText = true ;
173
- separators . push ( text . substring ( index ) ) ;
174
- }
175
- break ;
240
+ concat [ index ] = piece . text ;
176
241
}
177
242
}
178
243
179
244
if ( separators . length === expressions . length ) {
180
245
separators . push ( '' ) ;
181
246
}
182
247
248
+ pieces = null ;
249
+
183
250
// Concatenating expressions makes it hard to reason about whether some combination of
184
251
// concatenated values are unsafe to use and could easily lead to XSS. By requiring that a
185
252
// single expression be used for iframe[src], object[src], etc., we ensure that the value
@@ -190,18 +257,14 @@ function $InterpolateProvider() {
190
257
throw $interpolateMinErr ( 'noconcat' ,
191
258
"Error while interpolating: {0}\nStrict Contextual Escaping disallows " +
192
259
"interpolations that concatenate multiple expressions when a trusted value is " +
193
- "required. See http://docs.angularjs.org/api/ng.$sce" , text ) ;
260
+ "required. See http://docs.angularjs.org/api/ng.$sce" , exp ) ;
194
261
}
195
262
196
263
if ( ! mustHaveExpression || hasInterpolation ) {
197
- concat . length = separators . length + expressions . length ;
198
-
199
264
var compute = function ( values ) {
200
265
for ( var i = 0 , ii = expressions . length ; i < ii ; i ++ ) {
201
- concat [ 2 * i ] = separators [ i ] ;
202
- concat [ ( 2 * i ) + 1 ] = values [ i ] ;
266
+ concat [ indexes [ i ] ] = values [ i ] ;
203
267
}
204
- concat [ 2 * ii ] = separators [ ii ] ;
205
268
return concat . join ( '' ) ;
206
269
} ;
207
270
@@ -278,15 +341,15 @@ function $InterpolateProvider() {
278
341
lastValuesCache . results [ scopeId ] = lastResult = compute ( values ) ;
279
342
}
280
343
} catch ( err ) {
281
- var newErr = $interpolateMinErr ( 'interr' , "Can't interpolate: {0}\n{1}" , text ,
344
+ var newErr = $interpolateMinErr ( 'interr' , "Can't interpolate: {0}\n{1}" , exp ,
282
345
err . toString ( ) ) ;
283
346
$exceptionHandler ( newErr ) ;
284
347
}
285
348
286
349
return lastResult ;
287
350
} , {
288
351
// all of these properties are undocumented for now
289
- exp : text , //just for compatibility with regular watchers created via $watch
352
+ exp : exp , //just for compatibility with regular watchers created via $watch
290
353
separators : separators ,
291
354
expressions : expressions
292
355
} ) ;
0 commit comments