@@ -29,8 +29,10 @@ import { FluentType, FluentNone, FluentNumber, FluentDateTime }
29
29
from "./types.js" ;
30
30
import * as builtins from "./builtins.js" ;
31
31
32
- // Prevent expansion of too long placeables.
33
- const MAX_PLACEABLE_LENGTH = 2500 ;
32
+ // The maximum number of placeables which can be expanded in a single call to
33
+ // `formatPattern`. The limit protects against the Billion Laughs and Quadratic
34
+ // Blowup attacks. See https://msdn.microsoft.com/en-us/magazine/ee335713.aspx.
35
+ const MAX_PLACEABLES = 100 ;
34
36
35
37
// Unicode bidi isolation characters.
36
38
const FSI = "\u2068" ;
@@ -86,7 +88,7 @@ function getArguments(scope, args) {
86
88
}
87
89
}
88
90
89
- return [ positional , named ] ;
91
+ return { positional, named} ;
90
92
}
91
93
92
94
// Resolve an expression to a Fluent type.
@@ -115,15 +117,23 @@ function resolveExpression(scope, expr) {
115
117
116
118
// Resolve a reference to a variable.
117
119
function VariableReference ( scope , { name} ) {
118
- if ( ! scope . args || ! scope . args . hasOwnProperty ( name ) ) {
119
- if ( scope . insideTermReference === false ) {
120
- scope . reportError ( new ReferenceError ( `Unknown variable: $${ name } ` ) ) ;
120
+ let arg ;
121
+ if ( scope . params ) {
122
+ // We're inside a TermReference. It's OK to reference undefined parameters.
123
+ if ( scope . params . hasOwnProperty ( name ) ) {
124
+ arg = scope . params [ name ] ;
125
+ } else {
126
+ return new FluentNone ( `$${ name } ` ) ;
121
127
}
128
+ } else if ( scope . args && scope . args . hasOwnProperty ( name ) ) {
129
+ // We're in the top-level Pattern or inside a MessageReference. Missing
130
+ // variables references produce ReferenceErrors.
131
+ arg = scope . args [ name ] ;
132
+ } else {
133
+ scope . reportError ( new ReferenceError ( `Unknown variable: $${ name } ` ) ) ;
122
134
return new FluentNone ( `$${ name } ` ) ;
123
135
}
124
136
125
- const arg = scope . args [ name ] ;
126
-
127
137
// Return early if the argument already is an instance of FluentType.
128
138
if ( arg instanceof FluentType ) {
129
139
return arg ;
@@ -181,20 +191,23 @@ function TermReference(scope, {name, attr, args}) {
181
191
return new FluentNone ( id ) ;
182
192
}
183
193
184
- // Every TermReference has its own variables.
185
- const [ , params ] = getArguments ( scope , args ) ;
186
- const local = scope . cloneForTermReference ( params ) ;
187
-
188
194
if ( attr ) {
189
195
const attribute = term . attributes [ attr ] ;
190
196
if ( attribute ) {
191
- return resolvePattern ( local , attribute ) ;
197
+ // Every TermReference has its own variables.
198
+ scope . params = getArguments ( scope , args ) . named ;
199
+ const resolved = resolvePattern ( scope , attribute ) ;
200
+ scope . params = null ;
201
+ return resolved ;
192
202
}
193
203
scope . reportError ( new ReferenceError ( `Unknown attribute: ${ attr } ` ) ) ;
194
204
return new FluentNone ( `${ id } .${ attr } ` ) ;
195
205
}
196
206
197
- return resolvePattern ( local , term . value ) ;
207
+ scope . params = getArguments ( scope , args ) . named ;
208
+ const resolved = resolvePattern ( scope , term . value ) ;
209
+ scope . params = null ;
210
+ return resolved ;
198
211
}
199
212
200
213
// Resolve a call to a Function with positional and key-value arguments.
@@ -213,7 +226,8 @@ function FunctionReference(scope, {name, args}) {
213
226
}
214
227
215
228
try {
216
- return func ( ...getArguments ( scope , args ) ) ;
229
+ let resolved = getArguments ( scope , args ) ;
230
+ return func ( resolved . positional , resolved . named ) ;
217
231
} catch ( err ) {
218
232
scope . reportError ( err ) ;
219
233
return new FluentNone ( `${ name } ()` ) ;
@@ -259,25 +273,24 @@ export function resolveComplexPattern(scope, ptn) {
259
273
continue ;
260
274
}
261
275
262
- const part = resolveExpression ( scope , elem ) . toString ( scope ) ;
263
-
264
- if ( useIsolating ) {
265
- result . push ( FSI ) ;
266
- }
267
-
268
- if ( part . length > MAX_PLACEABLE_LENGTH ) {
276
+ scope . placeables ++ ;
277
+ if ( scope . placeables > MAX_PLACEABLES ) {
269
278
scope . dirty . delete ( ptn ) ;
270
279
// This is a fatal error which causes the resolver to instantly bail out
271
280
// on this pattern. The length check protects against excessive memory
272
281
// usage, and throwing protects against eating up the CPU when long
273
282
// placeables are deeply nested.
274
283
throw new RangeError (
275
- " Too many characters in placeable " +
276
- `( ${ part . length } , max allowed is ${ MAX_PLACEABLE_LENGTH } ) `
284
+ ` Too many placeables expanded: ${ scope . placeables } , ` +
285
+ `max allowed is ${ MAX_PLACEABLES } `
277
286
) ;
278
287
}
279
288
280
- result . push ( part ) ;
289
+ if ( useIsolating ) {
290
+ result . push ( FSI ) ;
291
+ }
292
+
293
+ result . push ( resolveExpression ( scope , elem ) . toString ( scope ) ) ;
281
294
282
295
if ( useIsolating ) {
283
296
result . push ( PDI ) ;
0 commit comments