@fluent/bundle
Advanced tools
Comparing version 0.14.0 to 0.14.1
# Changelog | ||
## @fluent/bundle 0.14.1 (December 20, 2019) | ||
- Fix a big which made placeables which resolved to long strings format as | ||
`{???}`. (#439) | ||
Expressions which resolved to strings over 2500 characters long used to be | ||
considered dangerous. This is no longer the case. Instead, there's a limit | ||
on how many placeable can be resolved during a single call to | ||
`formatPattern`, to protect from high CPU usage in deeply nested patterns. | ||
- Fix a bug which made it impossible to pass a variable called `hasOwnProperty` | ||
to `formatPattern`. (#428) | ||
## @fluent/bundle 0.14.0 (July 30, 2019) | ||
@@ -4,0 +17,0 @@ |
360
compat.js
@@ -1,2 +0,2 @@ | ||
/* @fluent/bundle@0.14.0 */ | ||
/* @fluent/bundle@0.14.1 */ | ||
(function (global, factory) { | ||
@@ -6,3 +6,3 @@ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : | ||
(global = global || self, factory(global.FluentBundle = {})); | ||
}(this, function (exports) { 'use strict'; | ||
}(this, (function (exports) { 'use strict'; | ||
@@ -69,3 +69,3 @@ /* global Intl */ | ||
constructor() { | ||
let value = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "???"; | ||
var value = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "???"; | ||
super(value); | ||
@@ -111,3 +111,3 @@ } | ||
try { | ||
const nf = scope.memoizeIntlObject(Intl.NumberFormat, this.opts); | ||
var nf = scope.memoizeIntlObject(Intl.NumberFormat, this.opts); | ||
return nf.format(this.value); | ||
@@ -148,3 +148,3 @@ } catch (err) { | ||
try { | ||
const dtf = scope.memoizeIntlObject(Intl.DateTimeFormat, this.opts); | ||
var dtf = scope.memoizeIntlObject(Intl.DateTimeFormat, this.opts); | ||
return dtf.format(this.value); | ||
@@ -174,16 +174,31 @@ } catch (err) { | ||
function _objectSpread(target) { | ||
function ownKeys(object, enumerableOnly) { | ||
var keys = Object.keys(object); | ||
if (Object.getOwnPropertySymbols) { | ||
var symbols = Object.getOwnPropertySymbols(object); | ||
if (enumerableOnly) symbols = symbols.filter(function (sym) { | ||
return Object.getOwnPropertyDescriptor(object, sym).enumerable; | ||
}); | ||
keys.push.apply(keys, symbols); | ||
} | ||
return keys; | ||
} | ||
function _objectSpread2(target) { | ||
for (var i = 1; i < arguments.length; i++) { | ||
var source = arguments[i] != null ? arguments[i] : {}; | ||
var ownKeys = Object.keys(source); | ||
if (typeof Object.getOwnPropertySymbols === 'function') { | ||
ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { | ||
return Object.getOwnPropertyDescriptor(source, sym).enumerable; | ||
})); | ||
if (i % 2) { | ||
ownKeys(Object(source), true).forEach(function (key) { | ||
_defineProperty(target, key, source[key]); | ||
}); | ||
} else if (Object.getOwnPropertyDescriptors) { | ||
Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); | ||
} else { | ||
ownKeys(Object(source)).forEach(function (key) { | ||
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); | ||
}); | ||
} | ||
ownKeys.forEach(function (key) { | ||
_defineProperty(target, key, source[key]); | ||
}); | ||
} | ||
@@ -203,2 +218,6 @@ | ||
function _iterableToArrayLimit(arr, i) { | ||
if (!(Symbol.iterator in Object(arr) || Object.prototype.toString.call(arr) === "[object Arguments]")) { | ||
return; | ||
} | ||
var _arr = []; | ||
@@ -238,8 +257,8 @@ var _n = true; | ||
function values(opts) { | ||
const unwrapped = {}; | ||
var unwrapped = {}; | ||
for (var _i = 0, _Object$entries = Object.entries(opts); _i < _Object$entries.length; _i++) { | ||
const _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2), | ||
name = _Object$entries$_i[0], | ||
opt = _Object$entries$_i[1]; | ||
var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2), | ||
name = _Object$entries$_i[0], | ||
opt = _Object$entries$_i[1]; | ||
@@ -253,3 +272,3 @@ unwrapped[name] = opt.valueOf(); | ||
function NUMBER(_ref, opts) { | ||
let _ref2 = _slicedToArray(_ref, 1), | ||
var _ref2 = _slicedToArray(_ref, 1), | ||
arg = _ref2[0]; | ||
@@ -261,3 +280,3 @@ | ||
let value = Number(arg.valueOf()); | ||
var value = Number(arg.valueOf()); | ||
@@ -271,3 +290,3 @@ if (Number.isNaN(value)) { | ||
function DATETIME(_ref3, opts) { | ||
let _ref4 = _slicedToArray(_ref3, 1), | ||
var _ref4 = _slicedToArray(_ref3, 1), | ||
arg = _ref4[0]; | ||
@@ -279,3 +298,3 @@ | ||
let value = Number(arg.valueOf()); | ||
var value = Number(arg.valueOf()); | ||
@@ -290,2 +309,3 @@ if (Number.isNaN(value)) { | ||
var builtins = /*#__PURE__*/Object.freeze({ | ||
__proto__: null, | ||
NUMBER: NUMBER, | ||
@@ -295,7 +315,11 @@ DATETIME: DATETIME | ||
const MAX_PLACEABLE_LENGTH = 2500; // Unicode bidi isolation characters. | ||
/* global Intl */ | ||
// `formatPattern`. The limit protects against the Billion Laughs and Quadratic | ||
// Blowup attacks. See https://msdn.microsoft.com/en-us/magazine/ee335713.aspx. | ||
const FSI = "\u2068"; | ||
const PDI = "\u2069"; // Helper: match a variant key to the given selector. | ||
var MAX_PLACEABLES = 100; // Unicode bidi isolation characters. | ||
var FSI = "\u2068"; | ||
var PDI = "\u2069"; // Helper: match a variant key to the given selector. | ||
function match(scope, selector, key) { | ||
@@ -313,3 +337,3 @@ if (key === selector) { | ||
if (selector instanceof FluentNumber && typeof key === "string") { | ||
let category = scope.memoizeIntlObject(Intl.PluralRules, selector.opts).select(selector.value); | ||
var category = scope.memoizeIntlObject(Intl.PluralRules, selector.opts).select(selector.value); | ||
@@ -336,4 +360,4 @@ if (key === category) { | ||
function getArguments(scope, args) { | ||
const positional = []; | ||
const named = {}; | ||
var positional = []; | ||
var named = Object.create(null); | ||
var _iteratorNormalCompletion = true; | ||
@@ -345,3 +369,3 @@ var _didIteratorError = false; | ||
for (var _iterator = args[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { | ||
const arg = _step.value; | ||
var arg = _step.value; | ||
@@ -369,3 +393,6 @@ if (arg.type === "narg") { | ||
return [positional, named]; | ||
return { | ||
positional, | ||
named | ||
}; | ||
} // Resolve an expression to a Fluent type. | ||
@@ -406,13 +433,21 @@ | ||
function VariableReference(scope, _ref) { | ||
let name = _ref.name; | ||
var name = _ref.name; | ||
var arg; | ||
if (!scope.args || !scope.args.hasOwnProperty(name)) { | ||
if (scope.insideTermReference === false) { | ||
scope.reportError(new ReferenceError("Unknown variable: $".concat(name))); | ||
if (scope.params) { | ||
// We're inside a TermReference. It's OK to reference undefined parameters. | ||
if (Object.prototype.hasOwnProperty.call(scope.params, name)) { | ||
arg = scope.params[name]; | ||
} else { | ||
return new FluentNone("$".concat(name)); | ||
} | ||
} else if (scope.args && Object.prototype.hasOwnProperty.call(scope.args, name)) { | ||
// We're in the top-level Pattern or inside a MessageReference. Missing | ||
// variables references produce ReferenceErrors. | ||
arg = scope.args[name]; | ||
} else { | ||
scope.reportError(new ReferenceError("Unknown variable: $".concat(name))); | ||
return new FluentNone("$".concat(name)); | ||
} | ||
} // Return early if the argument already is an instance of FluentType. | ||
const arg = scope.args[name]; // Return early if the argument already is an instance of FluentType. | ||
@@ -444,6 +479,6 @@ if (arg instanceof FluentType) { | ||
function MessageReference(scope, _ref2) { | ||
let name = _ref2.name, | ||
var name = _ref2.name, | ||
attr = _ref2.attr; | ||
const message = scope.bundle._messages.get(name); | ||
var message = scope.bundle._messages.get(name); | ||
@@ -456,3 +491,3 @@ if (!message) { | ||
if (attr) { | ||
const attribute = message.attributes[attr]; | ||
var attribute = message.attributes[attr]; | ||
@@ -477,8 +512,8 @@ if (attribute) { | ||
function TermReference(scope, _ref3) { | ||
let name = _ref3.name, | ||
var name = _ref3.name, | ||
attr = _ref3.attr, | ||
args = _ref3.args; | ||
const id = "-".concat(name); | ||
var id = "-".concat(name); | ||
const term = scope.bundle._terms.get(id); | ||
var term = scope.bundle._terms.get(id); | ||
@@ -488,16 +523,15 @@ if (!term) { | ||
return new FluentNone(id); | ||
} // Every TermReference has its own variables. | ||
} | ||
if (attr) { | ||
var attribute = term.attributes[attr]; | ||
const _getArguments = getArguments(scope, args), | ||
_getArguments2 = _slicedToArray(_getArguments, 2), | ||
params = _getArguments2[1]; | ||
if (attribute) { | ||
// Every TermReference has its own variables. | ||
scope.params = getArguments(scope, args).named; | ||
const local = scope.cloneForTermReference(params); | ||
var _resolved = resolvePattern(scope, attribute); | ||
if (attr) { | ||
const attribute = term.attributes[attr]; | ||
if (attribute) { | ||
return resolvePattern(local, attribute); | ||
scope.params = null; | ||
return _resolved; | ||
} | ||
@@ -509,3 +543,6 @@ | ||
return resolvePattern(local, term.value); | ||
scope.params = getArguments(scope, args).named; | ||
var resolved = resolvePattern(scope, term.value); | ||
scope.params = null; | ||
return resolved; | ||
} // Resolve a call to a Function with positional and key-value arguments. | ||
@@ -515,7 +552,7 @@ | ||
function FunctionReference(scope, _ref4) { | ||
let name = _ref4.name, | ||
var name = _ref4.name, | ||
args = _ref4.args; | ||
// Some functions are built-in. Others may be provided by the runtime via | ||
// the `FluentBundle` constructor. | ||
const func = scope.bundle._functions[name] || builtins[name]; | ||
var func = scope.bundle._functions[name] || builtins[name]; | ||
@@ -533,3 +570,4 @@ if (!func) { | ||
try { | ||
return func(...getArguments(scope, args)); | ||
var resolved = getArguments(scope, args); | ||
return func(resolved.positional, resolved.named); | ||
} catch (err) { | ||
@@ -543,6 +581,6 @@ scope.reportError(err); | ||
function SelectExpression(scope, _ref5) { | ||
let selector = _ref5.selector, | ||
var selector = _ref5.selector, | ||
variants = _ref5.variants, | ||
star = _ref5.star; | ||
let sel = resolveExpression(scope, selector); | ||
var sel = resolveExpression(scope, selector); | ||
@@ -560,4 +598,4 @@ if (sel instanceof FluentNone) { | ||
for (var _iterator2 = variants[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) { | ||
const variant = _step2.value; | ||
const key = resolveExpression(scope, variant.key); | ||
var variant = _step2.value; | ||
var key = resolveExpression(scope, variant.key); | ||
@@ -595,6 +633,6 @@ if (match(scope, sel, key)) { | ||
scope.dirty.add(ptn); | ||
const result = []; // Wrap interpolations with Directional Isolate Formatting characters | ||
var result = []; // Wrap interpolations with Directional Isolate Formatting characters | ||
// only when the pattern has more than one element. | ||
const useIsolating = scope.bundle._useIsolating && ptn.length > 1; | ||
var useIsolating = scope.bundle._useIsolating && ptn.length > 1; | ||
var _iteratorNormalCompletion3 = true; | ||
@@ -606,3 +644,3 @@ var _didIteratorError3 = false; | ||
for (var _iterator3 = ptn[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) { | ||
const elem = _step3.value; | ||
var elem = _step3.value; | ||
@@ -614,9 +652,5 @@ if (typeof elem === "string") { | ||
const part = resolveExpression(scope, elem).toString(scope); | ||
scope.placeables++; | ||
if (useIsolating) { | ||
result.push(FSI); | ||
} | ||
if (part.length > MAX_PLACEABLE_LENGTH) { | ||
if (scope.placeables > MAX_PLACEABLES) { | ||
scope.dirty.delete(ptn); // This is a fatal error which causes the resolver to instantly bail out | ||
@@ -627,7 +661,11 @@ // on this pattern. The length check protects against excessive memory | ||
throw new RangeError("Too many characters in placeable " + "(".concat(part.length, ", max allowed is ").concat(MAX_PLACEABLE_LENGTH, ")")); | ||
throw new RangeError("Too many placeables expanded: ".concat(scope.placeables, ", ") + "max allowed is ".concat(MAX_PLACEABLES)); | ||
} | ||
result.push(part); | ||
if (useIsolating) { | ||
result.push(FSI); | ||
} | ||
result.push(resolveExpression(scope, elem).toString(scope)); | ||
if (useIsolating) { | ||
@@ -668,5 +706,2 @@ result.push(PDI); | ||
constructor(bundle, errors, args) { | ||
let insideTermReference = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false; | ||
let dirty = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : new WeakSet(); | ||
/** The bundle for which the given resolution is happening. */ | ||
@@ -680,13 +715,13 @@ this.bundle = bundle; | ||
this.args = args; | ||
/** Term references require different variable lookup logic. */ | ||
this.insideTermReference = insideTermReference; | ||
/** The Set of patterns already encountered during this resolution. | ||
* Used to detect and prevent cyclic resolutions. */ | ||
this.dirty = dirty; | ||
} | ||
this.dirty = new WeakSet(); | ||
/** A dict of parameters passed to a TermReference. */ | ||
cloneForTermReference(args) { | ||
return new Scope(this.bundle, this.errors, args, true, this.dirty); | ||
this.params = null; | ||
/** The running count of placeables resolved so far. Used to detect the | ||
* Billion Laughs and Quadratic Blowup attacks. */ | ||
this.placeables = 0; | ||
} | ||
@@ -703,3 +738,3 @@ | ||
memoizeIntlObject(ctor, opts) { | ||
let cache = this.bundle._intls.get(ctor); | ||
var cache = this.bundle._intls.get(ctor); | ||
@@ -712,3 +747,3 @@ if (!cache) { | ||
let id = JSON.stringify(opts); | ||
var id = JSON.stringify(opts); | ||
@@ -764,3 +799,3 @@ if (!cache[id]) { | ||
constructor(locales) { | ||
let _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, | ||
var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, | ||
_ref$functions = _ref.functions, | ||
@@ -834,10 +869,10 @@ functions = _ref$functions === void 0 ? {} : _ref$functions, | ||
addResource(res) { | ||
let _ref2 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, | ||
var _ref2 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, | ||
_ref2$allowOverrides = _ref2.allowOverrides, | ||
allowOverrides = _ref2$allowOverrides === void 0 ? false : _ref2$allowOverrides; | ||
const errors = []; | ||
var errors = []; | ||
for (let i = 0; i < res.body.length; i++) { | ||
let entry = res.body[i]; | ||
for (var i = 0; i < res.body.length; i++) { | ||
var entry = res.body[i]; | ||
@@ -907,6 +942,6 @@ if (entry.id.startsWith("-")) { | ||
let scope = new Scope(this, errors, args); | ||
var scope = new Scope(this, errors, args); | ||
try { | ||
let value = resolveComplexPattern(scope, pattern); | ||
var value = resolveComplexPattern(scope, pattern); | ||
return value.toString(scope); | ||
@@ -929,11 +964,11 @@ } catch (err) { | ||
const RE_MESSAGE_START = /^(-?[a-zA-Z][\w-]*) *= */mg; // Both Attributes and Variants are parsed in while loops. These regexes are | ||
var RE_MESSAGE_START = /^(-?[a-zA-Z][\w-]*) *= */mg; // Both Attributes and Variants are parsed in while loops. These regexes are | ||
// used to break out of them. | ||
const RE_ATTRIBUTE_START = /\.([a-zA-Z][\w-]*) *= */y; | ||
const RE_VARIANT_START = /\*?\[/y; | ||
const RE_NUMBER_LITERAL = /(-?[0-9]+(?:\.([0-9]+))?)/y; | ||
const RE_IDENTIFIER = /([a-zA-Z][\w-]*)/y; | ||
const RE_REFERENCE = /([$-])?([a-zA-Z][\w-]*)(?:\.([a-zA-Z][\w-]*))?/y; | ||
const RE_FUNCTION_NAME = /^[A-Z][A-Z0-9_-]*$/; // A "run" is a sequence of text or string literal characters which don't | ||
var RE_ATTRIBUTE_START = /\.([a-zA-Z][\w-]*) *= */y; | ||
var RE_VARIANT_START = /\*?\[/y; | ||
var RE_NUMBER_LITERAL = /(-?[0-9]+(?:\.([0-9]+))?)/y; | ||
var RE_IDENTIFIER = /([a-zA-Z][\w-]*)/y; | ||
var RE_REFERENCE = /([$-])?([a-zA-Z][\w-]*)(?:\.([a-zA-Z][\w-]*))?/y; | ||
var RE_FUNCTION_NAME = /^[A-Z][A-Z0-9_-]*$/; // A "run" is a sequence of text or string literal characters which don't | ||
// require any special handling. For TextElements such special characters are: { | ||
@@ -945,29 +980,26 @@ // (starts a placeable), and line breaks which require additional logic to check | ||
const RE_TEXT_RUN = /([^{}\n\r]+)/y; | ||
const RE_STRING_RUN = /([^\\"\n\r]*)/y; // Escape sequences. | ||
var RE_TEXT_RUN = /([^{}\n\r]+)/y; | ||
var RE_STRING_RUN = /([^\\"\n\r]*)/y; // Escape sequences. | ||
const RE_STRING_ESCAPE = /\\([\\"])/y; | ||
const RE_UNICODE_ESCAPE = /\\u([a-fA-F0-9]{4})|\\U([a-fA-F0-9]{6})/y; // Used for trimming TextElements and indents. | ||
var RE_STRING_ESCAPE = /\\([\\"])/y; | ||
var RE_UNICODE_ESCAPE = /\\u([a-fA-F0-9]{4})|\\U([a-fA-F0-9]{6})/y; // Used for trimming TextElements and indents. | ||
const RE_LEADING_NEWLINES = /^\n+/; | ||
const RE_TRAILING_SPACES = / +$/; // Used in makeIndent to strip spaces from blank lines and normalize CRLF to LF. | ||
var RE_LEADING_NEWLINES = /^\n+/; | ||
var RE_TRAILING_SPACES = / +$/; // Used in makeIndent to strip spaces from blank lines and normalize CRLF to LF. | ||
const RE_BLANK_LINES = / *\r?\n/g; // Used in makeIndent to measure the indentation. | ||
var RE_BLANK_LINES = / *\r?\n/g; // Used in makeIndent to measure the indentation. | ||
const RE_INDENT = /( *)$/; // Common tokens. | ||
var RE_INDENT = /( *)$/; // Common tokens. | ||
const TOKEN_BRACE_OPEN = /{\s*/y; | ||
const TOKEN_BRACE_CLOSE = /\s*}/y; | ||
const TOKEN_BRACKET_OPEN = /\[\s*/y; | ||
const TOKEN_BRACKET_CLOSE = /\s*] */y; | ||
const TOKEN_PAREN_OPEN = /\s*\(\s*/y; | ||
const TOKEN_ARROW = /\s*->\s*/y; | ||
const TOKEN_COLON = /\s*:\s*/y; // Note the optional comma. As a deviation from the Fluent EBNF, the parser | ||
var TOKEN_BRACE_OPEN = /{\s*/y; | ||
var TOKEN_BRACE_CLOSE = /\s*}/y; | ||
var TOKEN_BRACKET_OPEN = /\[\s*/y; | ||
var TOKEN_BRACKET_CLOSE = /\s*] */y; | ||
var TOKEN_PAREN_OPEN = /\s*\(\s*/y; | ||
var TOKEN_ARROW = /\s*->\s*/y; | ||
var TOKEN_COLON = /\s*:\s*/y; // Note the optional comma. As a deviation from the Fluent EBNF, the parser | ||
// doesn't enforce commas between call arguments. | ||
const TOKEN_COMMA = /\s*,?\s*/y; | ||
const TOKEN_BLANK = /\s+/y; // Maximum number of placeables in a single Pattern to protect against Quadratic | ||
// Blowup attacks. See https://msdn.microsoft.com/en-us/magazine/ee335713.aspx. | ||
const MAX_PLACEABLES = 100; | ||
var TOKEN_COMMA = /\s*,?\s*/y; | ||
var TOKEN_BLANK = /\s+/y; | ||
/** | ||
@@ -984,8 +1016,8 @@ * Fluent Resource is a structure storing parsed localization entries. | ||
RE_MESSAGE_START.lastIndex = 0; | ||
let resource = []; | ||
let cursor = 0; // Iterate over the beginnings of messages and terms to efficiently skip | ||
var resource = []; | ||
var cursor = 0; // Iterate over the beginnings of messages and terms to efficiently skip | ||
// comments and recover from errors. | ||
while (true) { | ||
let next = RE_MESSAGE_START.exec(source); | ||
var next = RE_MESSAGE_START.exec(source); | ||
@@ -1063,3 +1095,3 @@ if (next === null) { | ||
re.lastIndex = cursor; | ||
let result = re.exec(source); | ||
var result = re.exec(source); | ||
@@ -1080,4 +1112,4 @@ if (result === null) { | ||
function parseMessage(id) { | ||
let value = parsePattern(); | ||
let attributes = parseAttributes(); | ||
var value = parsePattern(); | ||
var attributes = parseAttributes(); | ||
@@ -1096,7 +1128,7 @@ if (value === null && Object.keys(attributes).length === 0) { | ||
function parseAttributes() { | ||
let attrs = Object.create(null); | ||
var attrs = Object.create(null); | ||
while (test(RE_ATTRIBUTE_START)) { | ||
let name = match1(RE_ATTRIBUTE_START); | ||
let value = parsePattern(); | ||
var name = match1(RE_ATTRIBUTE_START); | ||
var value = parsePattern(); | ||
@@ -1127,3 +1159,3 @@ if (value === null) { | ||
let indent = parseIndent(); | ||
var indent = parseIndent(); | ||
@@ -1154,5 +1186,4 @@ if (indent) { | ||
function parsePatternElements() { | ||
let elements = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; | ||
let commonIndent = arguments.length > 1 ? arguments[1] : undefined; | ||
let placeableCount = 0; | ||
var elements = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : []; | ||
var commonIndent = arguments.length > 1 ? arguments[1] : undefined; | ||
@@ -1166,6 +1197,2 @@ while (true) { | ||
if (source[cursor] === "{") { | ||
if (++placeableCount > MAX_PLACEABLES) { | ||
throw new FluentError("Too many placeables"); | ||
} | ||
elements.push(parsePlaceable()); | ||
@@ -1179,3 +1206,3 @@ continue; | ||
let indent = parseIndent(); | ||
var indent = parseIndent(); | ||
@@ -1191,3 +1218,3 @@ if (indent) { | ||
let lastIndex = elements.length - 1; // Trim the trailing spaces in the last element if it's a TextElement. | ||
var lastIndex = elements.length - 1; // Trim the trailing spaces in the last element if it's a TextElement. | ||
@@ -1198,3 +1225,3 @@ if (typeof elements[lastIndex] === "string") { | ||
let baked = []; | ||
var baked = []; | ||
var _iteratorNormalCompletion = true; | ||
@@ -1206,3 +1233,3 @@ var _didIteratorError = false; | ||
for (var _iterator = elements[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { | ||
let element = _step.value; | ||
var element = _step.value; | ||
@@ -1238,3 +1265,3 @@ if (element.type === "indent") { | ||
consumeToken(TOKEN_BRACE_OPEN, FluentError); | ||
let selector = parseInlineExpression(); | ||
var selector = parseInlineExpression(); | ||
@@ -1246,5 +1273,5 @@ if (consumeToken(TOKEN_BRACE_CLOSE)) { | ||
if (consumeToken(TOKEN_ARROW)) { | ||
let variants = parseVariants(); | ||
var variants = parseVariants(); | ||
consumeToken(TOKEN_BRACE_CLOSE, FluentError); | ||
return _objectSpread({ | ||
return _objectSpread2({ | ||
type: "select", | ||
@@ -1265,3 +1292,3 @@ selector | ||
if (test(RE_REFERENCE)) { | ||
let _match = match(RE_REFERENCE), | ||
var _match = match(RE_REFERENCE), | ||
_match2 = _slicedToArray(_match, 4), | ||
@@ -1281,3 +1308,3 @@ sigil = _match2[1], | ||
if (consumeToken(TOKEN_PAREN_OPEN)) { | ||
let args = parseArguments(); | ||
var args = parseArguments(); | ||
@@ -1326,3 +1353,3 @@ if (sigil === "-") { | ||
function parseArguments() { | ||
let args = []; | ||
var args = []; | ||
@@ -1348,3 +1375,3 @@ while (true) { | ||
function parseArgument() { | ||
let expr = parseInlineExpression(); | ||
var expr = parseInlineExpression(); | ||
@@ -1369,5 +1396,5 @@ if (expr.type !== "mesg") { | ||
function parseVariants() { | ||
let variants = []; | ||
let count = 0; | ||
let star; | ||
var variants = []; | ||
var count = 0; | ||
var star; | ||
@@ -1379,4 +1406,4 @@ while (test(RE_VARIANT_START)) { | ||
let key = parseVariantKey(); | ||
let value = parsePattern(); | ||
var key = parseVariantKey(); | ||
var value = parsePattern(); | ||
@@ -1409,3 +1436,3 @@ if (value === null) { | ||
consumeToken(TOKEN_BRACKET_OPEN, FluentError); | ||
let key = test(RE_NUMBER_LITERAL) ? parseNumberLiteral() : { | ||
var key = test(RE_NUMBER_LITERAL) ? parseNumberLiteral() : { | ||
type: "str", | ||
@@ -1431,3 +1458,3 @@ value: match1(RE_IDENTIFIER) | ||
function parseNumberLiteral() { | ||
let _match3 = match(RE_NUMBER_LITERAL), | ||
var _match3 = match(RE_NUMBER_LITERAL), | ||
_match4 = _slicedToArray(_match3, 3), | ||
@@ -1438,3 +1465,3 @@ value = _match4[1], | ||
let precision = fraction.length; | ||
var precision = fraction.length; | ||
return { | ||
@@ -1449,3 +1476,3 @@ type: "num", | ||
consumeChar("\"", FluentError); | ||
let value = ""; | ||
var value = ""; | ||
@@ -1479,3 +1506,3 @@ while (true) { | ||
if (test(RE_UNICODE_ESCAPE)) { | ||
let _match5 = match(RE_UNICODE_ESCAPE), | ||
var _match5 = match(RE_UNICODE_ESCAPE), | ||
_match6 = _slicedToArray(_match5, 3), | ||
@@ -1485,3 +1512,3 @@ codepoint4 = _match6[1], | ||
let codepoint = parseInt(codepoint4 || codepoint6, 16); | ||
var codepoint = parseInt(codepoint4 || codepoint6, 16); | ||
return codepoint <= 0xD7FF || 0xE000 <= codepoint // It's a Unicode scalar value. | ||
@@ -1499,3 +1526,3 @@ ? String.fromCodePoint(codepoint) // Lonely surrogates can cause trouble when the parsing result is | ||
function parseIndent() { | ||
let start = cursor; | ||
var start = cursor; | ||
consumeToken(TOKEN_BLANK); // Check the first non-blank character after the indent. | ||
@@ -1540,4 +1567,4 @@ | ||
function makeIndent(blank) { | ||
let value = blank.replace(RE_BLANK_LINES, "\n"); | ||
let length = RE_INDENT.exec(blank)[1].length; | ||
var value = blank.replace(RE_BLANK_LINES, "\n"); | ||
var length = RE_INDENT.exec(blank)[1].length; | ||
return { | ||
@@ -1553,11 +1580,2 @@ type: "indent", | ||
/** | ||
* @module fluent | ||
* @overview | ||
* | ||
* `fluent` is a JavaScript implementation of Project Fluent, a localization | ||
* framework designed to unleash the expressive power of the natural language. | ||
* | ||
*/ | ||
exports.FluentBundle = FluentBundle; | ||
@@ -1572,2 +1590,2 @@ exports.FluentDateTime = FluentDateTime; | ||
})); | ||
}))); |
114
index.js
@@ -1,2 +0,2 @@ | ||
/* @fluent/bundle@0.14.0 */ | ||
/* @fluent/bundle@0.14.1 */ | ||
(function (global, factory) { | ||
@@ -6,3 +6,3 @@ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : | ||
(global = global || self, factory(global.FluentBundle = {})); | ||
}(this, function (exports) { 'use strict'; | ||
}(this, (function (exports) { 'use strict'; | ||
@@ -195,2 +195,3 @@ /* global Intl */ | ||
var builtins = /*#__PURE__*/Object.freeze({ | ||
__proto__: null, | ||
NUMBER: NUMBER, | ||
@@ -202,4 +203,6 @@ DATETIME: DATETIME | ||
// Prevent expansion of too long placeables. | ||
const MAX_PLACEABLE_LENGTH = 2500; | ||
// The maximum number of placeables which can be expanded in a single call to | ||
// `formatPattern`. The limit protects against the Billion Laughs and Quadratic | ||
// Blowup attacks. See https://msdn.microsoft.com/en-us/magazine/ee335713.aspx. | ||
const MAX_PLACEABLES = 100; | ||
@@ -250,3 +253,3 @@ // Unicode bidi isolation characters. | ||
const positional = []; | ||
const named = {}; | ||
const named = Object.create(null); | ||
@@ -261,3 +264,3 @@ for (const arg of args) { | ||
return [positional, named]; | ||
return {positional, named}; | ||
} | ||
@@ -291,11 +294,22 @@ | ||
function VariableReference(scope, {name}) { | ||
if (!scope.args || !scope.args.hasOwnProperty(name)) { | ||
if (scope.insideTermReference === false) { | ||
scope.reportError(new ReferenceError(`Unknown variable: $${name}`)); | ||
let arg; | ||
if (scope.params) { | ||
// We're inside a TermReference. It's OK to reference undefined parameters. | ||
if (Object.prototype.hasOwnProperty.call(scope.params, name)) { | ||
arg = scope.params[name]; | ||
} else { | ||
return new FluentNone(`$${name}`); | ||
} | ||
} else if ( | ||
scope.args | ||
&& Object.prototype.hasOwnProperty.call(scope.args, name) | ||
) { | ||
// We're in the top-level Pattern or inside a MessageReference. Missing | ||
// variables references produce ReferenceErrors. | ||
arg = scope.args[name]; | ||
} else { | ||
scope.reportError(new ReferenceError(`Unknown variable: $${name}`)); | ||
return new FluentNone(`$${name}`); | ||
} | ||
const arg = scope.args[name]; | ||
// Return early if the argument already is an instance of FluentType. | ||
@@ -358,10 +372,10 @@ if (arg instanceof FluentType) { | ||
// Every TermReference has its own variables. | ||
const [, params] = getArguments(scope, args); | ||
const local = scope.cloneForTermReference(params); | ||
if (attr) { | ||
const attribute = term.attributes[attr]; | ||
if (attribute) { | ||
return resolvePattern(local, attribute); | ||
// Every TermReference has its own variables. | ||
scope.params = getArguments(scope, args).named; | ||
const resolved = resolvePattern(scope, attribute); | ||
scope.params = null; | ||
return resolved; | ||
} | ||
@@ -372,3 +386,6 @@ scope.reportError(new ReferenceError(`Unknown attribute: ${attr}`)); | ||
return resolvePattern(local, term.value); | ||
scope.params = getArguments(scope, args).named; | ||
const resolved = resolvePattern(scope, term.value); | ||
scope.params = null; | ||
return resolved; | ||
} | ||
@@ -392,3 +409,4 @@ | ||
try { | ||
return func(...getArguments(scope, args)); | ||
let resolved = getArguments(scope, args); | ||
return func(resolved.positional, resolved.named); | ||
} catch (err) { | ||
@@ -439,9 +457,4 @@ scope.reportError(err); | ||
const part = resolveExpression(scope, elem).toString(scope); | ||
if (useIsolating) { | ||
result.push(FSI); | ||
} | ||
if (part.length > MAX_PLACEABLE_LENGTH) { | ||
scope.placeables++; | ||
if (scope.placeables > MAX_PLACEABLES) { | ||
scope.dirty.delete(ptn); | ||
@@ -453,9 +466,13 @@ // This is a fatal error which causes the resolver to instantly bail out | ||
throw new RangeError( | ||
"Too many characters in placeable " + | ||
`(${part.length}, max allowed is ${MAX_PLACEABLE_LENGTH})` | ||
`Too many placeables expanded: ${scope.placeables}, ` + | ||
`max allowed is ${MAX_PLACEABLES}` | ||
); | ||
} | ||
result.push(part); | ||
if (useIsolating) { | ||
result.push(FSI); | ||
} | ||
result.push(resolveExpression(scope, elem).toString(scope)); | ||
if (useIsolating) { | ||
@@ -482,9 +499,3 @@ result.push(PDI); | ||
class Scope { | ||
constructor( | ||
bundle, | ||
errors, | ||
args, | ||
insideTermReference = false, | ||
dirty = new WeakSet() | ||
) { | ||
constructor(bundle, errors, args) { | ||
/** The bundle for which the given resolution is happening. */ | ||
@@ -497,13 +508,12 @@ this.bundle = bundle; | ||
/** Term references require different variable lookup logic. */ | ||
this.insideTermReference = insideTermReference; | ||
/** The Set of patterns already encountered during this resolution. | ||
* Used to detect and prevent cyclic resolutions. */ | ||
this.dirty = dirty; | ||
this.dirty = new WeakSet(); | ||
/** A dict of parameters passed to a TermReference. */ | ||
this.params = null; | ||
/** The running count of placeables resolved so far. Used to detect the | ||
* Billion Laughs and Quadratic Blowup attacks. */ | ||
this.placeables = 0; | ||
} | ||
cloneForTermReference(args) { | ||
return new Scope(this.bundle, this.errors, args, true, this.dirty); | ||
} | ||
reportError(error) { | ||
@@ -762,6 +772,2 @@ if (!this.errors) { | ||
// Maximum number of placeables in a single Pattern to protect against Quadratic | ||
// Blowup attacks. See https://msdn.microsoft.com/en-us/magazine/ee335713.aspx. | ||
const MAX_PLACEABLES = 100; | ||
/** | ||
@@ -931,4 +937,2 @@ * Fluent Resource is a structure storing parsed localization entries. | ||
function parsePatternElements(elements = [], commonIndent) { | ||
let placeableCount = 0; | ||
while (true) { | ||
@@ -941,5 +945,2 @@ if (test(RE_TEXT_RUN)) { | ||
if (source[cursor] === "{") { | ||
if (++placeableCount > MAX_PLACEABLES) { | ||
throw new FluentError("Too many placeables"); | ||
} | ||
elements.push(parsePlaceable()); | ||
@@ -1215,11 +1216,2 @@ continue; | ||
/** | ||
* @module fluent | ||
* @overview | ||
* | ||
* `fluent` is a JavaScript implementation of Project Fluent, a localization | ||
* framework designed to unleash the expressive power of the natural language. | ||
* | ||
*/ | ||
exports.FluentBundle = FluentBundle; | ||
@@ -1234,2 +1226,2 @@ exports.FluentDateTime = FluentDateTime; | ||
})); | ||
}))); |
{ | ||
"name": "@fluent/bundle", | ||
"description": "Localization library for expressive translations.", | ||
"version": "0.14.0", | ||
"homepage": "http://projectfluent.org", | ||
"version": "0.14.1", | ||
"homepage": "https://projectfluent.org", | ||
"author": "Mozilla <l10n-drivers@mozilla.org>", | ||
@@ -7,0 +7,0 @@ "license": "Apache-2.0", |
@@ -43,3 +43,3 @@ # @fluent/bundle | ||
The API reference is available at http://projectfluent.org/fluent.js/fluent. | ||
The API reference is available at https://projectfluent.org/fluent.js/bundle. | ||
@@ -82,3 +82,3 @@ | ||
[env preset]: https://babeljs.io/docs/plugins/preset-env/ | ||
[projectfluent.org]: http://projectfluent.org | ||
[FTL]: http://projectfluent.org/fluent/guide/ | ||
[projectfluent.org]: https://projectfluent.org | ||
[FTL]: https://projectfluent.org/fluent/guide/ |
@@ -32,4 +32,6 @@ /* global Intl */ | ||
// Prevent expansion of too long placeables. | ||
const MAX_PLACEABLE_LENGTH = 2500; | ||
// The maximum number of placeables which can be expanded in a single call to | ||
// `formatPattern`. The limit protects against the Billion Laughs and Quadratic | ||
// Blowup attacks. See https://msdn.microsoft.com/en-us/magazine/ee335713.aspx. | ||
const MAX_PLACEABLES = 100; | ||
@@ -80,3 +82,3 @@ // Unicode bidi isolation characters. | ||
const positional = []; | ||
const named = {}; | ||
const named = Object.create(null); | ||
@@ -91,3 +93,3 @@ for (const arg of args) { | ||
return [positional, named]; | ||
return {positional, named}; | ||
} | ||
@@ -121,11 +123,22 @@ | ||
function VariableReference(scope, {name}) { | ||
if (!scope.args || !scope.args.hasOwnProperty(name)) { | ||
if (scope.insideTermReference === false) { | ||
scope.reportError(new ReferenceError(`Unknown variable: $${name}`)); | ||
let arg; | ||
if (scope.params) { | ||
// We're inside a TermReference. It's OK to reference undefined parameters. | ||
if (Object.prototype.hasOwnProperty.call(scope.params, name)) { | ||
arg = scope.params[name]; | ||
} else { | ||
return new FluentNone(`$${name}`); | ||
} | ||
} else if ( | ||
scope.args | ||
&& Object.prototype.hasOwnProperty.call(scope.args, name) | ||
) { | ||
// We're in the top-level Pattern or inside a MessageReference. Missing | ||
// variables references produce ReferenceErrors. | ||
arg = scope.args[name]; | ||
} else { | ||
scope.reportError(new ReferenceError(`Unknown variable: $${name}`)); | ||
return new FluentNone(`$${name}`); | ||
} | ||
const arg = scope.args[name]; | ||
// Return early if the argument already is an instance of FluentType. | ||
@@ -188,10 +201,10 @@ if (arg instanceof FluentType) { | ||
// Every TermReference has its own variables. | ||
const [, params] = getArguments(scope, args); | ||
const local = scope.cloneForTermReference(params); | ||
if (attr) { | ||
const attribute = term.attributes[attr]; | ||
if (attribute) { | ||
return resolvePattern(local, attribute); | ||
// Every TermReference has its own variables. | ||
scope.params = getArguments(scope, args).named; | ||
const resolved = resolvePattern(scope, attribute); | ||
scope.params = null; | ||
return resolved; | ||
} | ||
@@ -202,3 +215,6 @@ scope.reportError(new ReferenceError(`Unknown attribute: ${attr}`)); | ||
return resolvePattern(local, term.value); | ||
scope.params = getArguments(scope, args).named; | ||
const resolved = resolvePattern(scope, term.value); | ||
scope.params = null; | ||
return resolved; | ||
} | ||
@@ -222,3 +238,4 @@ | ||
try { | ||
return func(...getArguments(scope, args)); | ||
let resolved = getArguments(scope, args); | ||
return func(resolved.positional, resolved.named); | ||
} catch (err) { | ||
@@ -269,9 +286,4 @@ scope.reportError(err); | ||
const part = resolveExpression(scope, elem).toString(scope); | ||
if (useIsolating) { | ||
result.push(FSI); | ||
} | ||
if (part.length > MAX_PLACEABLE_LENGTH) { | ||
scope.placeables++; | ||
if (scope.placeables > MAX_PLACEABLES) { | ||
scope.dirty.delete(ptn); | ||
@@ -283,9 +295,13 @@ // This is a fatal error which causes the resolver to instantly bail out | ||
throw new RangeError( | ||
"Too many characters in placeable " + | ||
`(${part.length}, max allowed is ${MAX_PLACEABLE_LENGTH})` | ||
`Too many placeables expanded: ${scope.placeables}, ` + | ||
`max allowed is ${MAX_PLACEABLES}` | ||
); | ||
} | ||
result.push(part); | ||
if (useIsolating) { | ||
result.push(FSI); | ||
} | ||
result.push(resolveExpression(scope, elem).toString(scope)); | ||
if (useIsolating) { | ||
@@ -292,0 +308,0 @@ result.push(PDI); |
@@ -51,6 +51,2 @@ import FluentError from "./error.js"; | ||
// Maximum number of placeables in a single Pattern to protect against Quadratic | ||
// Blowup attacks. See https://msdn.microsoft.com/en-us/magazine/ee335713.aspx. | ||
const MAX_PLACEABLES = 100; | ||
/** | ||
@@ -220,4 +216,2 @@ * Fluent Resource is a structure storing parsed localization entries. | ||
function parsePatternElements(elements = [], commonIndent) { | ||
let placeableCount = 0; | ||
while (true) { | ||
@@ -230,5 +224,2 @@ if (test(RE_TEXT_RUN)) { | ||
if (source[cursor] === "{") { | ||
if (++placeableCount > MAX_PLACEABLES) { | ||
throw new FluentError("Too many placeables"); | ||
} | ||
elements.push(parsePlaceable()); | ||
@@ -235,0 +226,0 @@ continue; |
export default class Scope { | ||
constructor( | ||
bundle, | ||
errors, | ||
args, | ||
insideTermReference = false, | ||
dirty = new WeakSet() | ||
) { | ||
constructor(bundle, errors, args) { | ||
/** The bundle for which the given resolution is happening. */ | ||
@@ -16,13 +10,12 @@ this.bundle = bundle; | ||
/** Term references require different variable lookup logic. */ | ||
this.insideTermReference = insideTermReference; | ||
/** The Set of patterns already encountered during this resolution. | ||
* Used to detect and prevent cyclic resolutions. */ | ||
this.dirty = dirty; | ||
this.dirty = new WeakSet(); | ||
/** A dict of parameters passed to a TermReference. */ | ||
this.params = null; | ||
/** The running count of placeables resolved so far. Used to detect the | ||
* Billion Laughs and Quadratic Blowup attacks. */ | ||
this.placeables = 0; | ||
} | ||
cloneForTermReference(args) { | ||
return new Scope(this.bundle, this.errors, args, true, this.dirty); | ||
} | ||
reportError(error) { | ||
@@ -29,0 +22,0 @@ if (!this.errors) { |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
144012
3357