utils.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. 'use strict';
  2. var formats = require('./formats');
  3. var getSideChannel = require('side-channel');
  4. var defineProperty = require('es-define-property');
  5. var has = Object.prototype.hasOwnProperty;
  6. var isArray = Array.isArray;
  7. // Track objects created from arrayLimit overflow using side-channel
  8. // Stores the current max numeric index for O(1) lookup
  9. var overflowChannel = getSideChannel();
  10. var markOverflow = function markOverflow(obj, maxIndex) {
  11. overflowChannel.set(obj, maxIndex);
  12. return obj;
  13. };
  14. var isOverflow = function isOverflow(obj) {
  15. return overflowChannel.has(obj);
  16. };
  17. var getMaxIndex = function getMaxIndex(obj) {
  18. return overflowChannel.get(obj);
  19. };
  20. var setMaxIndex = function setMaxIndex(obj, maxIndex) {
  21. overflowChannel.set(obj, maxIndex);
  22. };
  23. var hexTable = (function () {
  24. var array = [];
  25. for (var i = 0; i < 256; ++i) {
  26. array[array.length] = '%' + ((i < 16 ? '0' : '') + i.toString(16)).toUpperCase();
  27. }
  28. return array;
  29. }());
  30. var compactQueue = function compactQueue(queue) {
  31. while (queue.length > 1) {
  32. var item = queue.pop();
  33. var obj = item.obj[item.prop];
  34. if (isArray(obj)) {
  35. var compacted = [];
  36. for (var j = 0; j < obj.length; ++j) {
  37. if (typeof obj[j] !== 'undefined') {
  38. compacted[compacted.length] = obj[j];
  39. }
  40. }
  41. item.obj[item.prop] = compacted;
  42. }
  43. }
  44. };
  45. var arrayToObject = function arrayToObject(source, options) {
  46. var obj = options && options.plainObjects ? { __proto__: null } : {};
  47. for (var i = 0; i < source.length; ++i) {
  48. if (typeof source[i] !== 'undefined') {
  49. obj[i] = source[i];
  50. }
  51. }
  52. return obj;
  53. };
  54. var setProperty = function setProperty(obj, key, value) {
  55. if (key === '__proto__' && defineProperty) {
  56. defineProperty(obj, key, {
  57. configurable: true,
  58. enumerable: true,
  59. value: value,
  60. writable: true
  61. });
  62. } else {
  63. obj[key] = value;
  64. }
  65. };
  66. var merge = function merge(target, source, options) {
  67. /* eslint no-param-reassign: 0 */
  68. if (!source) {
  69. return target;
  70. }
  71. if (typeof source !== 'object' && typeof source !== 'function') {
  72. if (isArray(target)) {
  73. var nextIndex = target.length;
  74. if (options && typeof options.arrayLimit === 'number' && nextIndex >= options.arrayLimit) {
  75. if (options.throwOnLimitExceeded) {
  76. throw new RangeError('Array limit exceeded. Only ' + options.arrayLimit + ' element' + (options.arrayLimit === 1 ? '' : 's') + ' allowed in an array.');
  77. }
  78. return markOverflow(arrayToObject(target.concat(source), options), nextIndex);
  79. }
  80. target[nextIndex] = source;
  81. } else if (target && typeof target === 'object') {
  82. if (isOverflow(target)) {
  83. // Add at next numeric index for overflow objects
  84. var newIndex = getMaxIndex(target) + 1;
  85. target[newIndex] = source;
  86. setMaxIndex(target, newIndex);
  87. } else if (options && options.strictMerge) {
  88. return [target, source];
  89. } else if (
  90. (options && (options.plainObjects || options.allowPrototypes))
  91. || !has.call(Object.prototype, source)
  92. ) {
  93. target[source] = true;
  94. }
  95. } else {
  96. return [target, source];
  97. }
  98. return target;
  99. }
  100. if (!target || typeof target !== 'object') {
  101. if (isOverflow(source)) {
  102. // Create new object with target at 0, source values shifted by 1
  103. var sourceKeys = Object.keys(source);
  104. var result = options && options.plainObjects
  105. ? { __proto__: null, 0: target }
  106. : { 0: target };
  107. for (var m = 0; m < sourceKeys.length; m++) {
  108. var oldKey = parseInt(sourceKeys[m], 10);
  109. result[oldKey + 1] = source[sourceKeys[m]];
  110. }
  111. return markOverflow(result, getMaxIndex(source) + 1);
  112. }
  113. var combined = [target].concat(source);
  114. if (options && typeof options.arrayLimit === 'number' && combined.length > options.arrayLimit) {
  115. if (options.throwOnLimitExceeded) {
  116. throw new RangeError('Array limit exceeded. Only ' + options.arrayLimit + ' element' + (options.arrayLimit === 1 ? '' : 's') + ' allowed in an array.');
  117. }
  118. return markOverflow(arrayToObject(combined, options), combined.length - 1);
  119. }
  120. return combined;
  121. }
  122. var mergeTarget = target;
  123. if (isArray(target) && !isArray(source)) {
  124. mergeTarget = arrayToObject(target, options);
  125. }
  126. if (isArray(target) && isArray(source)) {
  127. source.forEach(function (item, i) {
  128. if (has.call(target, i)) {
  129. var targetItem = target[i];
  130. if (targetItem && typeof targetItem === 'object' && item && typeof item === 'object') {
  131. target[i] = merge(targetItem, item, options);
  132. } else {
  133. target[target.length] = item;
  134. }
  135. } else {
  136. target[i] = item;
  137. }
  138. });
  139. if (options && typeof options.arrayLimit === 'number' && target.length > options.arrayLimit) {
  140. if (options.throwOnLimitExceeded) {
  141. throw new RangeError('Array limit exceeded. Only ' + options.arrayLimit + ' element' + (options.arrayLimit === 1 ? '' : 's') + ' allowed in an array.');
  142. }
  143. return markOverflow(arrayToObject(target, options), target.length - 1);
  144. }
  145. return target;
  146. }
  147. return Object.keys(source).reduce(function (acc, key) {
  148. var value = source[key];
  149. if (has.call(acc, key)) {
  150. setProperty(acc, key, merge(acc[key], value, options));
  151. } else {
  152. setProperty(acc, key, value);
  153. }
  154. if (isOverflow(source) && !isOverflow(acc)) {
  155. markOverflow(acc, getMaxIndex(source));
  156. }
  157. if (isOverflow(acc)) {
  158. var keyNum = parseInt(key, 10);
  159. if (String(keyNum) === key && keyNum >= 0 && keyNum > getMaxIndex(acc)) {
  160. setMaxIndex(acc, keyNum);
  161. }
  162. }
  163. return acc;
  164. }, mergeTarget);
  165. };
  166. var assign = function assignSingleSource(target, source) {
  167. return Object.keys(source).reduce(function (acc, key) {
  168. setProperty(acc, key, source[key]);
  169. return acc;
  170. }, target);
  171. };
  172. var decode = function (str, defaultDecoder, charset) {
  173. var strWithoutPlus = str.replace(/\+/g, ' ');
  174. if (charset === 'iso-8859-1') {
  175. // unescape never throws, no try...catch needed:
  176. return strWithoutPlus.replace(/%[0-9a-f]{2}/gi, unescape);
  177. }
  178. // utf-8
  179. try {
  180. return decodeURIComponent(strWithoutPlus);
  181. } catch (e) {
  182. return strWithoutPlus;
  183. }
  184. };
  185. var limit = 1024;
  186. /* eslint operator-linebreak: [2, "before"] */
  187. var encode = function encode(str, defaultEncoder, charset, kind, format) {
  188. // This code was originally written by Brian White (mscdex) for the io.js core querystring library.
  189. // It has been adapted here for stricter adherence to RFC 3986
  190. if (str.length === 0) {
  191. return str;
  192. }
  193. var string = str;
  194. if (typeof str === 'symbol') {
  195. string = Symbol.prototype.toString.call(str);
  196. } else if (typeof str !== 'string') {
  197. string = String(str);
  198. }
  199. if (charset === 'iso-8859-1') {
  200. return escape(string).replace(/%u[0-9a-f]{4}/gi, function ($0) {
  201. return '%26%23' + parseInt($0.slice(2), 16) + '%3B';
  202. });
  203. }
  204. var out = '';
  205. for (var j = 0; j < string.length; j += limit) {
  206. var segment = string.length >= limit ? string.slice(j, j + limit) : string;
  207. if (j + limit < string.length) {
  208. var last = segment.charCodeAt(segment.length - 1);
  209. if (last >= 0xD800 && last <= 0xDBFF) {
  210. segment = segment.slice(0, -1);
  211. j -= 1;
  212. }
  213. }
  214. var arr = [];
  215. for (var i = 0; i < segment.length; ++i) {
  216. var c = segment.charCodeAt(i);
  217. if (
  218. c === 0x2D // -
  219. || c === 0x2E // .
  220. || c === 0x5F // _
  221. || c === 0x7E // ~
  222. || (c >= 0x30 && c <= 0x39) // 0-9
  223. || (c >= 0x41 && c <= 0x5A) // a-z
  224. || (c >= 0x61 && c <= 0x7A) // A-Z
  225. || (format === formats.RFC1738 && (c === 0x28 || c === 0x29)) // ( )
  226. ) {
  227. arr[arr.length] = segment.charAt(i);
  228. continue;
  229. }
  230. if (c < 0x80) {
  231. arr[arr.length] = hexTable[c];
  232. continue;
  233. }
  234. if (c < 0x800) {
  235. arr[arr.length] = hexTable[0xC0 | (c >> 6)]
  236. + hexTable[0x80 | (c & 0x3F)];
  237. continue;
  238. }
  239. if (c < 0xD800 || c >= 0xE000) {
  240. arr[arr.length] = hexTable[0xE0 | (c >> 12)]
  241. + hexTable[0x80 | ((c >> 6) & 0x3F)]
  242. + hexTable[0x80 | (c & 0x3F)];
  243. continue;
  244. }
  245. i += 1;
  246. c = 0x10000 + (((c & 0x3FF) << 10) | (segment.charCodeAt(i) & 0x3FF));
  247. arr[arr.length] = hexTable[0xF0 | (c >> 18)]
  248. + hexTable[0x80 | ((c >> 12) & 0x3F)]
  249. + hexTable[0x80 | ((c >> 6) & 0x3F)]
  250. + hexTable[0x80 | (c & 0x3F)];
  251. }
  252. out += arr.join('');
  253. }
  254. return out;
  255. };
  256. var compact = function compact(value) {
  257. var queue = [{ obj: { o: value }, prop: 'o' }];
  258. var refs = getSideChannel();
  259. for (var i = 0; i < queue.length; ++i) {
  260. var item = queue[i];
  261. var obj = item.obj[item.prop];
  262. var keys = Object.keys(obj);
  263. for (var j = 0; j < keys.length; ++j) {
  264. var key = keys[j];
  265. var val = obj[key];
  266. if (typeof val === 'object' && val !== null && !refs.has(val)) {
  267. queue[queue.length] = { obj: obj, prop: key };
  268. refs.set(val, true);
  269. }
  270. }
  271. }
  272. compactQueue(queue);
  273. return value;
  274. };
  275. var isRegExp = function isRegExp(obj) {
  276. return Object.prototype.toString.call(obj) === '[object RegExp]';
  277. };
  278. var isBuffer = function isBuffer(obj) {
  279. if (!obj || typeof obj !== 'object') {
  280. return false;
  281. }
  282. return !!(obj.constructor && obj.constructor.isBuffer && obj.constructor.isBuffer(obj));
  283. };
  284. var combine = function combine(a, b, arrayLimit, plainObjects, throwOnLimitExceeded) {
  285. // If 'a' is already an overflow object, add to it
  286. if (isOverflow(a)) {
  287. if (throwOnLimitExceeded) {
  288. throw new RangeError('Array limit exceeded. Only ' + arrayLimit + ' element' + (arrayLimit === 1 ? '' : 's') + ' allowed in an array.');
  289. }
  290. var newIndex = getMaxIndex(a) + 1;
  291. a[newIndex] = b;
  292. setMaxIndex(a, newIndex);
  293. return a;
  294. }
  295. var result = [].concat(a, b);
  296. if (result.length > arrayLimit) {
  297. if (throwOnLimitExceeded) {
  298. throw new RangeError('Array limit exceeded. Only ' + arrayLimit + ' element' + (arrayLimit === 1 ? '' : 's') + ' allowed in an array.');
  299. }
  300. return markOverflow(arrayToObject(result, { plainObjects: plainObjects }), result.length - 1);
  301. }
  302. return result;
  303. };
  304. var maybeMap = function maybeMap(val, fn) {
  305. if (isArray(val)) {
  306. var mapped = [];
  307. for (var i = 0; i < val.length; i += 1) {
  308. mapped[mapped.length] = fn(val[i]);
  309. }
  310. return mapped;
  311. }
  312. return fn(val);
  313. };
  314. module.exports = {
  315. arrayToObject: arrayToObject,
  316. assign: assign,
  317. combine: combine,
  318. compact: compact,
  319. decode: decode,
  320. encode: encode,
  321. isBuffer: isBuffer,
  322. isOverflow: isOverflow,
  323. isRegExp: isRegExp,
  324. markOverflow: markOverflow,
  325. maybeMap: maybeMap,
  326. merge: merge
  327. };