diff --git a/README.md b/README.md index 790c062e2..2d4f26cef 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# lodash-amd v4.17.15 +# lodash-amd v4.17.21 The [Lodash](https://lodash.com/) library exported as [AMD](https://github.com/amdjs/amdjs-api/wiki/AMD) modules. @@ -27,4 +27,4 @@ require({ }); ``` -See the [package source](https://github.com/lodash/lodash/tree/4.17.15-amd) for more details. +See the [package source](https://github.com/lodash/lodash/tree/4.17.21-amd) for more details. diff --git a/_baseClone.js b/_baseClone.js index 3dbfa1ee3..10853a625 100644 --- a/_baseClone.js +++ b/_baseClone.js @@ -1,4 +1,4 @@ -define(['./_Stack', './_arrayEach', './_assignValue', './_baseAssign', './_baseAssignIn', './_cloneBuffer', './_copyArray', './_copySymbols', './_copySymbolsIn', './_getAllKeys', './_getAllKeysIn', './_getTag', './_initCloneArray', './_initCloneByTag', './_initCloneObject', './isArray', './isBuffer', './isMap', './isObject', './isSet', './keys'], function(Stack, arrayEach, assignValue, baseAssign, baseAssignIn, cloneBuffer, copyArray, copySymbols, copySymbolsIn, getAllKeys, getAllKeysIn, getTag, initCloneArray, initCloneByTag, initCloneObject, isArray, isBuffer, isMap, isObject, isSet, keys) { +define(['./_Stack', './_arrayEach', './_assignValue', './_baseAssign', './_baseAssignIn', './_cloneBuffer', './_copyArray', './_copySymbols', './_copySymbolsIn', './_getAllKeys', './_getAllKeysIn', './_getTag', './_initCloneArray', './_initCloneByTag', './_initCloneObject', './isArray', './isBuffer', './isMap', './isObject', './isSet', './keys', './keysIn'], function(Stack, arrayEach, assignValue, baseAssign, baseAssignIn, cloneBuffer, copyArray, copySymbols, copySymbolsIn, getAllKeys, getAllKeysIn, getTag, initCloneArray, initCloneByTag, initCloneObject, isArray, isBuffer, isMap, isObject, isSet, keys, keysIn) { /** Used as a safe reference for `undefined` in pre-ES5 environments. */ var undefined; diff --git a/_baseOrderBy.js b/_baseOrderBy.js index 06fec4c6b..c400dc1a9 100644 --- a/_baseOrderBy.js +++ b/_baseOrderBy.js @@ -1,4 +1,4 @@ -define(['./_arrayMap', './_baseIteratee', './_baseMap', './_baseSortBy', './_baseUnary', './_compareMultiple', './identity'], function(arrayMap, baseIteratee, baseMap, baseSortBy, baseUnary, compareMultiple, identity) { +define(['./_arrayMap', './_baseGet', './_baseIteratee', './_baseMap', './_baseSortBy', './_baseUnary', './_compareMultiple', './identity', './isArray'], function(arrayMap, baseGet, baseIteratee, baseMap, baseSortBy, baseUnary, compareMultiple, identity, isArray) { /** * The base implementation of `_.orderBy` without param guards. @@ -10,8 +10,21 @@ define(['./_arrayMap', './_baseIteratee', './_baseMap', './_baseSortBy', './_bas * @returns {Array} Returns the new sorted array. */ function baseOrderBy(collection, iteratees, orders) { + if (iteratees.length) { + iteratees = arrayMap(iteratees, function(iteratee) { + if (isArray(iteratee)) { + return function(value) { + return baseGet(value, iteratee.length === 1 ? iteratee[0] : iteratee); + } + } + return iteratee; + }); + } else { + iteratees = [identity]; + } + var index = -1; - iteratees = arrayMap(iteratees.length ? iteratees : [identity], baseUnary(baseIteratee)); + iteratees = arrayMap(iteratees, baseUnary(baseIteratee)); var result = baseMap(collection, function(value, key, collection) { var criteria = arrayMap(iteratees, function(iteratee) { diff --git a/_baseSet.js b/_baseSet.js index c812b7f18..a60690cc4 100644 --- a/_baseSet.js +++ b/_baseSet.js @@ -28,6 +28,10 @@ define(['./_assignValue', './_castPath', './_isIndex', './isObject', './_toKey'] var key = toKey(path[index]), newValue = value; + if (key === '__proto__' || key === 'constructor' || key === 'prototype') { + return object; + } + if (index != lastIndex) { var objValue = nested[key]; newValue = customizer ? customizer(objValue, key, nested) : undefined; diff --git a/_baseSortedIndexBy.js b/_baseSortedIndexBy.js index 6d9c3e2ab..4ca70c0e2 100644 --- a/_baseSortedIndexBy.js +++ b/_baseSortedIndexBy.js @@ -25,11 +25,14 @@ define(['./isSymbol'], function(isSymbol) { * into `array`. */ function baseSortedIndexBy(array, value, iteratee, retHighest) { - value = iteratee(value); - var low = 0, - high = array == null ? 0 : array.length, - valIsNaN = value !== value, + high = array == null ? 0 : array.length; + if (high === 0) { + return 0; + } + + value = iteratee(value); + var valIsNaN = value !== value, valIsNull = value === null, valIsSymbol = isSymbol(value), valIsUndefined = value === undefined; diff --git a/_baseTrim.js b/_baseTrim.js new file mode 100644 index 000000000..77d694b3b --- /dev/null +++ b/_baseTrim.js @@ -0,0 +1,20 @@ +define(['./_trimmedEndIndex'], function(trimmedEndIndex) { + + /** Used to match leading whitespace. */ + var reTrimStart = /^\s+/; + + /** + * The base implementation of `_.trim`. + * + * @private + * @param {string} string The string to trim. + * @returns {string} Returns the trimmed string. + */ + function baseTrim(string) { + return string + ? string.slice(0, trimmedEndIndex(string) + 1).replace(reTrimStart, '') + : string; + } + + return baseTrim; +}); diff --git a/_baseUnset.js b/_baseUnset.js index 4b0047ef7..65d1cfe19 100644 --- a/_baseUnset.js +++ b/_baseUnset.js @@ -1,5 +1,11 @@ define(['./_castPath', './last', './_parent', './_toKey'], function(castPath, last, parent, toKey) { + /** Used for built-in method references. */ + var objectProto = Object.prototype; + + /** Used to check objects for own properties. */ + var hasOwnProperty = objectProto.hasOwnProperty; + /** * The base implementation of `_.unset`. * @@ -10,8 +16,47 @@ define(['./_castPath', './last', './_parent', './_toKey'], function(castPath, la */ function baseUnset(object, path) { path = castPath(path, object); - object = parent(object, path); - return object == null || delete object[toKey(last(path))]; + + // Prevent prototype pollution, see: https://github.com/lodash/lodash/security/advisories/GHSA-xxjr-mmjv-4gpg + var index = -1, + length = path.length; + + if (!length) { + return true; + } + + var isRootPrimitive = object == null || (typeof object !== 'object' && typeof object !== 'function'); + + while (++index < length) { + var key = path[index]; + + // skip non-string keys (e.g., Symbols, numbers) + if (typeof key !== 'string') { + continue; + } + + // Always block "__proto__" anywhere in the path if it's not expected + if (key === '__proto__' && !hasOwnProperty.call(object, '__proto__')) { + return false; + } + + // Block "constructor.prototype" chains + if (key === 'constructor' && + (index + 1) < length && + typeof path[index + 1] === 'string' && + path[index + 1] === 'prototype') { + + // Allow ONLY when the path starts at a primitive root, e.g., _.unset(0, 'constructor.prototype.a') + if (isRootPrimitive && index === 0) { + continue; + } + + return false; + } + } + + var obj = parent(object, path); + return obj == null || delete obj[toKey(last(path))]; } return baseUnset; diff --git a/_equalArrays.js b/_equalArrays.js index 383df5d1c..ddb317843 100644 --- a/_equalArrays.js +++ b/_equalArrays.js @@ -28,10 +28,11 @@ define(['./_SetCache', './_arraySome', './_cacheHas'], function(SetCache, arrayS if (arrLength != othLength && !(isPartial && othLength > arrLength)) { return false; } - // Assume cyclic values are equal. - var stacked = stack.get(array); - if (stacked && stack.get(other)) { - return stacked == other; + // Check that cyclic values are equal. + var arrStacked = stack.get(array); + var othStacked = stack.get(other); + if (arrStacked && othStacked) { + return arrStacked == other && othStacked == array; } var index = -1, result = true, diff --git a/_equalObjects.js b/_equalObjects.js index c1eba21fc..8f91594cc 100644 --- a/_equalObjects.js +++ b/_equalObjects.js @@ -42,10 +42,11 @@ define(['./_getAllKeys'], function(getAllKeys) { return false; } } - // Assume cyclic values are equal. - var stacked = stack.get(object); - if (stacked && stack.get(other)) { - return stacked == other; + // Check that cyclic values are equal. + var objStacked = stack.get(object); + var othStacked = stack.get(other); + if (objStacked && othStacked) { + return objStacked == other && othStacked == object; } var result = true; stack.set(object, other); diff --git a/_setCacheHas.js b/_setCacheHas.js index aeba2529b..cbd297782 100644 --- a/_setCacheHas.js +++ b/_setCacheHas.js @@ -7,7 +7,7 @@ define([], function() { * @name has * @memberOf SetCache * @param {*} value The value to search for. - * @returns {number} Returns `true` if `value` is found, else `false`. + * @returns {boolean} Returns `true` if `value` is found, else `false`. */ function setCacheHas(value) { return this.__data__.has(value); diff --git a/_trimmedEndIndex.js b/_trimmedEndIndex.js new file mode 100644 index 000000000..f8d18e49b --- /dev/null +++ b/_trimmedEndIndex.js @@ -0,0 +1,22 @@ +define([], function() { + + /** Used to match a single whitespace character. */ + var reWhitespace = /\s/; + + /** + * Used by `_.trim` and `_.trimEnd` to get the index of the last non-whitespace + * character of `string`. + * + * @private + * @param {string} string The string to inspect. + * @returns {number} Returns the index of the last non-whitespace character. + */ + function trimmedEndIndex(string) { + var index = string.length; + + while (index-- && reWhitespace.test(string.charAt(index))) {} + return index; + } + + return trimmedEndIndex; +}); diff --git a/filter.js b/filter.js index cde5c742d..3043642bc 100644 --- a/filter.js +++ b/filter.js @@ -36,6 +36,10 @@ define(['./_arrayFilter', './_baseFilter', './_baseIteratee', './isArray'], func * // The `_.property` iteratee shorthand. * _.filter(users, 'active'); * // => objects for ['barney'] + * + * // Combining several predicates using `_.overEvery` or `_.overSome`. + * _.filter(users, _.overSome([{ 'age': 36 }, ['age', 40]])); + * // => objects for ['fred', 'barney'] */ function filter(collection, predicate) { var func = isArray(collection) ? arrayFilter : baseFilter; diff --git a/main.js b/main.js index 99a17578d..b5a75b259 100644 --- a/main.js +++ b/main.js @@ -13,7 +13,7 @@ var undefined; /** Used as the semantic version number. */ - var VERSION = '4.17.15'; + var VERSION = '4.17.21'; /** Used as the size to enable large array optimizations. */ var LARGE_ARRAY_SIZE = 200; diff --git a/matches.js b/matches.js index 8b35ed768..b4d433a0d 100644 --- a/matches.js +++ b/matches.js @@ -15,6 +15,9 @@ define(['./_baseClone', './_baseMatches'], function(baseClone, baseMatches) { * values against any array or object value, respectively. See `_.isEqual` * for a list of supported value comparisons. * + * **Note:** Multiple values can be checked by combining several matchers + * using `_.overSome` + * * @static * @memberOf _ * @since 3.0.0 @@ -30,6 +33,10 @@ define(['./_baseClone', './_baseMatches'], function(baseClone, baseMatches) { * * _.filter(objects, _.matches({ 'a': 4, 'c': 6 })); * // => [{ 'a': 4, 'b': 5, 'c': 6 }] + * + * // Checking for several possible values + * _.filter(objects, _.overSome([_.matches({ 'a': 1 }), _.matches({ 'a': 4 })])); + * // => [{ 'a': 1, 'b': 2, 'c': 3 }, { 'a': 4, 'b': 5, 'c': 6 }] */ function matches(source) { return baseMatches(baseClone(source, CLONE_DEEP_FLAG)); diff --git a/matchesProperty.js b/matchesProperty.js index 766c28bd4..f0aed82dd 100644 --- a/matchesProperty.js +++ b/matchesProperty.js @@ -12,6 +12,9 @@ define(['./_baseClone', './_baseMatchesProperty'], function(baseClone, baseMatch * `srcValue` values against any array or object value, respectively. See * `_.isEqual` for a list of supported value comparisons. * + * **Note:** Multiple values can be checked by combining several matchers + * using `_.overSome` + * * @static * @memberOf _ * @since 3.2.0 @@ -28,6 +31,10 @@ define(['./_baseClone', './_baseMatchesProperty'], function(baseClone, baseMatch * * _.find(objects, _.matchesProperty('a', 4)); * // => { 'a': 4, 'b': 5, 'c': 6 } + * + * // Checking for several possible values + * _.filter(objects, _.overSome([_.matchesProperty('a', 1), _.matchesProperty('a', 4)])); + * // => [{ 'a': 1, 'b': 2, 'c': 3 }, { 'a': 4, 'b': 5, 'c': 6 }] */ function matchesProperty(path, srcValue) { return baseMatchesProperty(path, baseClone(srcValue, CLONE_DEEP_FLAG)); diff --git a/overEvery.js b/overEvery.js index 32e3b24d8..2e7380c17 100644 --- a/overEvery.js +++ b/overEvery.js @@ -4,6 +4,10 @@ define(['./_arrayEvery', './_createOver'], function(arrayEvery, createOver) { * Creates a function that checks if **all** of the `predicates` return * truthy when invoked with the arguments it receives. * + * Following shorthands are possible for providing predicates. + * Pass an `Object` and it will be used as an parameter for `_.matches` to create the predicate. + * Pass an `Array` of parameters for `_.matchesProperty` and the predicate will be created using them. + * * @static * @memberOf _ * @since 4.0.0 diff --git a/overSome.js b/overSome.js index 6721803f8..3b86bed39 100644 --- a/overSome.js +++ b/overSome.js @@ -4,6 +4,10 @@ define(['./_arraySome', './_createOver'], function(arraySome, createOver) { * Creates a function that checks if **any** of the `predicates` return * truthy when invoked with the arguments it receives. * + * Following shorthands are possible for providing predicates. + * Pass an `Object` and it will be used as an parameter for `_.matches` to create the predicate. + * Pass an `Array` of parameters for `_.matchesProperty` and the predicate will be created using them. + * * @static * @memberOf _ * @since 4.0.0 @@ -23,6 +27,9 @@ define(['./_arraySome', './_createOver'], function(arraySome, createOver) { * * func(NaN); * // => false + * + * var matchesFunc = _.overSome([{ 'a': 1 }, { 'a': 2 }]) + * var matchesPropertyFunc = _.overSome([['a', 1], ['a', 2]]) */ var overSome = createOver(arraySome); diff --git a/package.json b/package.json index 41d2fcc7e..4124a23b0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lodash-amd", - "version": "4.17.15", + "version": "4.17.21", "description": "Lodash exported as AMD modules.", "keywords": "amd, modules, stdlib, util", "homepage": "https://lodash.com/custom-builds", diff --git a/parseInt.js b/parseInt.js index 243224c0a..bdb697dc1 100644 --- a/parseInt.js +++ b/parseInt.js @@ -1,6 +1,6 @@ define(['./_root', './toString'], function(root, toString) { - /** Used to match leading and trailing whitespace. */ + /** Used to match leading whitespace. */ var reTrimStart = /^\s+/; /* Built-in method references for those with the same name as other `lodash` methods. */ diff --git a/sortBy.js b/sortBy.js index 18f167cf5..27f832fb0 100644 --- a/sortBy.js +++ b/sortBy.js @@ -19,15 +19,15 @@ define(['./_baseFlatten', './_baseOrderBy', './_baseRest', './_isIterateeCall'], * var users = [ * { 'user': 'fred', 'age': 48 }, * { 'user': 'barney', 'age': 36 }, - * { 'user': 'fred', 'age': 40 }, + * { 'user': 'fred', 'age': 30 }, * { 'user': 'barney', 'age': 34 } * ]; * * _.sortBy(users, [function(o) { return o.user; }]); - * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]] + * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 30]] * * _.sortBy(users, ['user', 'age']); - * // => objects for [['barney', 34], ['barney', 36], ['fred', 40], ['fred', 48]] + * // => objects for [['barney', 34], ['barney', 36], ['fred', 30], ['fred', 48]] */ var sortBy = baseRest(function(collection, iteratees) { if (collection == null) { diff --git a/template.js b/template.js index 31622d426..a2e93d0af 100644 --- a/template.js +++ b/template.js @@ -3,11 +3,26 @@ define(['./assignInWith', './attempt', './_baseValues', './_customDefaultsAssign /** Used as a safe reference for `undefined` in pre-ES5 environments. */ var undefined; + /** Error message constants. */ + var INVALID_TEMPL_VAR_ERROR_TEXT = 'Invalid `variable` option passed into `_.template`'; + /** Used to match empty string literals in compiled template source. */ var reEmptyStringLeading = /\b__p \+= '';/g, reEmptyStringMiddle = /\b(__p \+=) '' \+/g, reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g; + /** + * Used to validate the `validate` option in `_.template` variable. + * + * Forbids characters which could potentially change the meaning of the function argument definition: + * - "()," (modification of function parameters) + * - "=" (default value) + * - "[]{}" (destructuring of function parameters) + * - "/" (beginning of a comment) + * - whitespace + */ + var reForbiddenIdentifierChars = /[()=,{}\[\]\/\s]/; + /** * Used to match * [ES template delimiters](http://ecma-international.org/ecma-262/7.0/#sec-template-literal-lexical-components). @@ -162,11 +177,11 @@ define(['./assignInWith', './attempt', './_baseValues', './_customDefaultsAssign // Use a sourceURL for easier debugging. // The sourceURL gets injected into the source that's eval-ed, so be careful - // with lookup (in case of e.g. prototype pollution), and strip newlines if any. - // A newline wouldn't be a valid sourceURL anyway, and it'd enable code injection. + // to normalize all kinds of whitespace, so e.g. newlines (and unicode versions of it) can't sneak in + // and escape the comment, thus injecting code that gets evaled. var sourceURL = hasOwnProperty.call(options, 'sourceURL') ? ('//# sourceURL=' + - (options.sourceURL + '').replace(/[\r\n]/g, ' ') + + (options.sourceURL + '').replace(/\s/g, ' ') + '\n') : ''; @@ -199,12 +214,16 @@ define(['./assignInWith', './attempt', './_baseValues', './_customDefaultsAssign // If `variable` is not specified wrap a with-statement around the generated // code to add the data object to the top of the scope chain. - // Like with sourceURL, we take care to not check the option's prototype, - // as this configuration is a code injection vector. var variable = hasOwnProperty.call(options, 'variable') && options.variable; if (!variable) { source = 'with (obj) {\n' + source + '\n}\n'; } + // Throw an error if a forbidden character was found in `variable`, to prevent + // potential command injection attacks. + else if (reForbiddenIdentifierChars.test(variable)) { + throw new Error(INVALID_TEMPL_VAR_ERROR_TEXT); + } + // Cleanup code by stripping empty strings. source = (isEvaluating ? source.replace(reEmptyStringLeading, '') : source) .replace(reEmptyStringMiddle, '$1') diff --git a/toNumber.js b/toNumber.js index e4dcad758..29fc71c33 100644 --- a/toNumber.js +++ b/toNumber.js @@ -1,11 +1,8 @@ -define(['./isObject', './isSymbol'], function(isObject, isSymbol) { +define(['./_baseTrim', './isObject', './isSymbol'], function(baseTrim, isObject, isSymbol) { /** Used as references for various `Number` constants. */ var NAN = 0 / 0; - /** Used to match leading and trailing whitespace. */ - var reTrim = /^\s+|\s+$/g; - /** Used to detect bad signed hexadecimal string values. */ var reIsBadHex = /^[-+]0x[0-9a-f]+$/i; @@ -55,7 +52,7 @@ define(['./isObject', './isSymbol'], function(isObject, isSymbol) { if (typeof value != 'string') { return value === 0 ? value : +value; } - value = value.replace(reTrim, ''); + value = baseTrim(value); var isBinary = reIsBinary.test(value); return (isBinary || reIsOctal.test(value)) ? freeParseInt(value.slice(2), isBinary ? 2 : 8) diff --git a/trim.js b/trim.js index 7e54b080b..7fdfaaa5f 100644 --- a/trim.js +++ b/trim.js @@ -1,11 +1,8 @@ -define(['./_baseToString', './_castSlice', './_charsEndIndex', './_charsStartIndex', './_stringToArray', './toString'], function(baseToString, castSlice, charsEndIndex, charsStartIndex, stringToArray, toString) { +define(['./_baseTrim', './_baseToString', './_castSlice', './_charsEndIndex', './_charsStartIndex', './_stringToArray', './toString'], function(baseTrim, baseToString, castSlice, charsEndIndex, charsStartIndex, stringToArray, toString) { /** Used as a safe reference for `undefined` in pre-ES5 environments. */ var undefined; - /** Used to match leading and trailing whitespace. */ - var reTrim = /^\s+|\s+$/g; - /** * Removes leading and trailing whitespace or specified characters from `string`. * @@ -31,7 +28,7 @@ define(['./_baseToString', './_castSlice', './_charsEndIndex', './_charsStartInd function trim(string, chars, guard) { string = toString(string); if (string && (guard || chars === undefined)) { - return string.replace(reTrim, ''); + return baseTrim(string); } if (!string || !(chars = baseToString(chars))) { return string; diff --git a/trimEnd.js b/trimEnd.js index 17bf5c356..e96204c2e 100644 --- a/trimEnd.js +++ b/trimEnd.js @@ -1,11 +1,8 @@ -define(['./_baseToString', './_castSlice', './_charsEndIndex', './_stringToArray', './toString'], function(baseToString, castSlice, charsEndIndex, stringToArray, toString) { +define(['./_baseToString', './_castSlice', './_charsEndIndex', './_stringToArray', './_trimmedEndIndex', './toString'], function(baseToString, castSlice, charsEndIndex, stringToArray, trimmedEndIndex, toString) { /** Used as a safe reference for `undefined` in pre-ES5 environments. */ var undefined; - /** Used to match leading and trailing whitespace. */ - var reTrimEnd = /\s+$/; - /** * Removes trailing whitespace or specified characters from `string`. * @@ -28,7 +25,7 @@ define(['./_baseToString', './_castSlice', './_charsEndIndex', './_stringToArray function trimEnd(string, chars, guard) { string = toString(string); if (string && (guard || chars === undefined)) { - return string.replace(reTrimEnd, ''); + return string.slice(0, trimmedEndIndex(string) + 1); } if (!string || !(chars = baseToString(chars))) { return string; diff --git a/trimStart.js b/trimStart.js index 307875dca..556435b62 100644 --- a/trimStart.js +++ b/trimStart.js @@ -3,7 +3,7 @@ define(['./_baseToString', './_castSlice', './_charsStartIndex', './_stringToArr /** Used as a safe reference for `undefined` in pre-ES5 environments. */ var undefined; - /** Used to match leading and trailing whitespace. */ + /** Used to match leading whitespace. */ var reTrimStart = /^\s+/; /**