mirror of
https://github.com/nodejs/node.git
synced 2025-05-17 18:26:24 +00:00

BUGFIXES * [`27cccfbda`](27cccfbdac
) [#223](https://github.com/npm/cli/pull/223) vulns → vulnerabilities in npm audit output ([@sapegin](https://github.com/sapegin)) * [`d5e865eb7`](d5e865eb79
) [#222](https://github.com/npm/cli/pull/222) [#226](https://github.com/npm/cli/pull/226) install, doctor: don't crash if registry unset ([@dmitrydvorkin](https://github.com/dmitrydvorkin), [@isaacs](https://github.com/isaacs)) * [`5b3890226`](5b38902265
) [#227](https://github.com/npm/cli/pull/227) [npm.community#9167](https://npm.community/t/npm-err-cb-never-called-permission-denied/9167/5) Handle unhandledRejections, tell user what to do when encountering an `EACCES` error in the cache. ([@isaacs](https://github.com/isaacs)) DEPENDENCIES * [`77516df6e`](77516df6ea
) `licensee@7.0.3` ([@isaacs](https://github.com/isaacs)) * [`ceb993590`](ceb993590e
) `query-string@6.8.2` ([@isaacs](https://github.com/isaacs)) * [`4050b9189`](4050b91898
) `hosted-git-info@2.8.2` * [#46](https://github.com/npm/hosted-git-info/issues/46) [#43](https://github.com/npm/hosted-git-info/issues/43) [#47](https://github.com/npm/hosted-git-info/pull/47) [#44](https://github.com/npm/hosted-git-info/pull/44) Add support for GitLab subgroups ([@mterrel](https://github.com/mterrel), [@isaacs](https://github.com/isaacs), [@ybiquitous](https://github.com/ybiquitous)) * [`3b1d629`](https://github.com/npm/hosted-git-info/commit/3b1d629) [#48](https://github.com/npm/hosted-git-info/issues/48) fix http protocol using sshurl by default ([@fengmk2](https://github.com/fengmk2)) * [`5d4a8d7`](https://github.com/npm/hosted-git-info/commit/5d4a8d7) ignore noCommittish on tarball url generation ([@isaacs](https://github.com/isaacs)) * [`1692435`](https://github.com/npm/hosted-git-info/commit/1692435) use gist tarball url that works for anonymous gists ([@isaacs](https://github.com/isaacs)) * [`d5cf830`](d5cf8309be
) Do not allow invalid gist urls ([@isaacs](https://github.com/isaacs)) * [`e518222`](e518222435
) Use LRU cache to prevent unbounded memory consumption ([@iarna](https://github.com/iarna)) PR-URL: https://github.com/nodejs/node/pull/29023 Reviewed-By: Jiawen Geng <technicalcute@gmail.com> Reviewed-By: Rich Trott <rtrott@gmail.com>
279 lines
5.9 KiB
JavaScript
279 lines
5.9 KiB
JavaScript
'use strict';
|
|
const strictUriEncode = require('strict-uri-encode');
|
|
const decodeComponent = require('decode-uri-component');
|
|
const splitOnFirst = require('split-on-first');
|
|
|
|
function encoderForArrayFormat(options) {
|
|
switch (options.arrayFormat) {
|
|
case 'index':
|
|
return key => (result, value) => {
|
|
const index = result.length;
|
|
if (value === undefined) {
|
|
return result;
|
|
}
|
|
|
|
if (value === null) {
|
|
return [...result, [encode(key, options), '[', index, ']'].join('')];
|
|
}
|
|
|
|
return [
|
|
...result,
|
|
[encode(key, options), '[', encode(index, options), ']=', encode(value, options)].join('')
|
|
];
|
|
};
|
|
|
|
case 'bracket':
|
|
return key => (result, value) => {
|
|
if (value === undefined) {
|
|
return result;
|
|
}
|
|
|
|
if (value === null) {
|
|
return [...result, [encode(key, options), '[]'].join('')];
|
|
}
|
|
|
|
return [...result, [encode(key, options), '[]=', encode(value, options)].join('')];
|
|
};
|
|
|
|
case 'comma':
|
|
return key => (result, value, index) => {
|
|
if (value === null || value === undefined || value.length === 0) {
|
|
return result;
|
|
}
|
|
|
|
if (index === 0) {
|
|
return [[encode(key, options), '=', encode(value, options)].join('')];
|
|
}
|
|
|
|
return [[result, encode(value, options)].join(',')];
|
|
};
|
|
|
|
default:
|
|
return key => (result, value) => {
|
|
if (value === undefined) {
|
|
return result;
|
|
}
|
|
|
|
if (value === null) {
|
|
return [...result, encode(key, options)];
|
|
}
|
|
|
|
return [...result, [encode(key, options), '=', encode(value, options)].join('')];
|
|
};
|
|
}
|
|
}
|
|
|
|
function parserForArrayFormat(options) {
|
|
let result;
|
|
|
|
switch (options.arrayFormat) {
|
|
case 'index':
|
|
return (key, value, accumulator) => {
|
|
result = /\[(\d*)\]$/.exec(key);
|
|
|
|
key = key.replace(/\[\d*\]$/, '');
|
|
|
|
if (!result) {
|
|
accumulator[key] = value;
|
|
return;
|
|
}
|
|
|
|
if (accumulator[key] === undefined) {
|
|
accumulator[key] = {};
|
|
}
|
|
|
|
accumulator[key][result[1]] = value;
|
|
};
|
|
|
|
case 'bracket':
|
|
return (key, value, accumulator) => {
|
|
result = /(\[\])$/.exec(key);
|
|
key = key.replace(/\[\]$/, '');
|
|
|
|
if (!result) {
|
|
accumulator[key] = value;
|
|
return;
|
|
}
|
|
|
|
if (accumulator[key] === undefined) {
|
|
accumulator[key] = [value];
|
|
return;
|
|
}
|
|
|
|
accumulator[key] = [].concat(accumulator[key], value);
|
|
};
|
|
|
|
case 'comma':
|
|
return (key, value, accumulator) => {
|
|
const isArray = typeof value === 'string' && value.split('').indexOf(',') > -1;
|
|
const newValue = isArray ? value.split(',') : value;
|
|
accumulator[key] = newValue;
|
|
};
|
|
|
|
default:
|
|
return (key, value, accumulator) => {
|
|
if (accumulator[key] === undefined) {
|
|
accumulator[key] = value;
|
|
return;
|
|
}
|
|
|
|
accumulator[key] = [].concat(accumulator[key], value);
|
|
};
|
|
}
|
|
}
|
|
|
|
function encode(value, options) {
|
|
if (options.encode) {
|
|
return options.strict ? strictUriEncode(value) : encodeURIComponent(value);
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
function decode(value, options) {
|
|
if (options.decode) {
|
|
return decodeComponent(value);
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
function keysSorter(input) {
|
|
if (Array.isArray(input)) {
|
|
return input.sort();
|
|
}
|
|
|
|
if (typeof input === 'object') {
|
|
return keysSorter(Object.keys(input))
|
|
.sort((a, b) => Number(a) - Number(b))
|
|
.map(key => input[key]);
|
|
}
|
|
|
|
return input;
|
|
}
|
|
|
|
function removeHash(input) {
|
|
const hashStart = input.indexOf('#');
|
|
if (hashStart !== -1) {
|
|
input = input.slice(0, hashStart);
|
|
}
|
|
|
|
return input;
|
|
}
|
|
|
|
function extract(input) {
|
|
input = removeHash(input);
|
|
const queryStart = input.indexOf('?');
|
|
if (queryStart === -1) {
|
|
return '';
|
|
}
|
|
|
|
return input.slice(queryStart + 1);
|
|
}
|
|
|
|
function parse(input, options) {
|
|
options = Object.assign({
|
|
decode: true,
|
|
sort: true,
|
|
arrayFormat: 'none',
|
|
parseNumbers: false,
|
|
parseBooleans: false
|
|
}, options);
|
|
|
|
const formatter = parserForArrayFormat(options);
|
|
|
|
// Create an object with no prototype
|
|
const ret = Object.create(null);
|
|
|
|
if (typeof input !== 'string') {
|
|
return ret;
|
|
}
|
|
|
|
input = input.trim().replace(/^[?#&]/, '');
|
|
|
|
if (!input) {
|
|
return ret;
|
|
}
|
|
|
|
for (const param of input.split('&')) {
|
|
let [key, value] = splitOnFirst(param.replace(/\+/g, ' '), '=');
|
|
|
|
// Missing `=` should be `null`:
|
|
// http://w3.org/TR/2012/WD-url-20120524/#collect-url-parameters
|
|
value = value === undefined ? null : decode(value, options);
|
|
|
|
if (options.parseNumbers && !Number.isNaN(Number(value)) && (typeof value === 'string' && value.trim() !== '')) {
|
|
value = Number(value);
|
|
} else if (options.parseBooleans && value !== null && (value.toLowerCase() === 'true' || value.toLowerCase() === 'false')) {
|
|
value = value.toLowerCase() === 'true';
|
|
}
|
|
|
|
formatter(decode(key, options), value, ret);
|
|
}
|
|
|
|
if (options.sort === false) {
|
|
return ret;
|
|
}
|
|
|
|
return (options.sort === true ? Object.keys(ret).sort() : Object.keys(ret).sort(options.sort)).reduce((result, key) => {
|
|
const value = ret[key];
|
|
if (Boolean(value) && typeof value === 'object' && !Array.isArray(value)) {
|
|
// Sort object keys, not values
|
|
result[key] = keysSorter(value);
|
|
} else {
|
|
result[key] = value;
|
|
}
|
|
|
|
return result;
|
|
}, Object.create(null));
|
|
}
|
|
|
|
exports.extract = extract;
|
|
exports.parse = parse;
|
|
|
|
exports.stringify = (object, options) => {
|
|
if (!object) {
|
|
return '';
|
|
}
|
|
|
|
options = Object.assign({
|
|
encode: true,
|
|
strict: true,
|
|
arrayFormat: 'none'
|
|
}, options);
|
|
|
|
const formatter = encoderForArrayFormat(options);
|
|
const keys = Object.keys(object);
|
|
|
|
if (options.sort !== false) {
|
|
keys.sort(options.sort);
|
|
}
|
|
|
|
return keys.map(key => {
|
|
const value = object[key];
|
|
|
|
if (value === undefined) {
|
|
return '';
|
|
}
|
|
|
|
if (value === null) {
|
|
return encode(key, options);
|
|
}
|
|
|
|
if (Array.isArray(value)) {
|
|
return value
|
|
.reduce(formatter(key), [])
|
|
.join('&');
|
|
}
|
|
|
|
return encode(key, options) + '=' + encode(value, options);
|
|
}).filter(x => x.length > 0).join('&');
|
|
};
|
|
|
|
exports.parseUrl = (input, options) => {
|
|
return {
|
|
url: removeHash(input).split('?')[0] || '',
|
|
query: parse(extract(input), options)
|
|
};
|
|
};
|