243 lines
5.5 KiB
JavaScript
243 lines
5.5 KiB
JavaScript
var url = require('url'),
|
|
|
|
/**
|
|
* @private
|
|
* @const
|
|
* @type {String}
|
|
*/
|
|
E = '',
|
|
|
|
/**
|
|
* @private
|
|
* @const
|
|
* @type {String}
|
|
*/
|
|
QUERY_SEPARATOR = '?',
|
|
|
|
/**
|
|
* @private
|
|
* @const
|
|
* @type {String}
|
|
*/
|
|
AMPERSAND = '&',
|
|
|
|
/**
|
|
* @private
|
|
* @const
|
|
* @type {String}
|
|
*/
|
|
EQUALS = '=',
|
|
|
|
/**
|
|
* @private
|
|
* @const
|
|
* @type {String}
|
|
*/
|
|
PERCENT = '%',
|
|
|
|
/**
|
|
* @private
|
|
* @const
|
|
* @type {string}
|
|
*/
|
|
ZERO = '0',
|
|
|
|
/**
|
|
* @private
|
|
* @const
|
|
* @type {string}
|
|
*/
|
|
STRING = 'string',
|
|
|
|
encoder;
|
|
|
|
encoder = {
|
|
/**
|
|
* Percent encode a character with given code.
|
|
*
|
|
* @param {Number} c - character code of the character to encode
|
|
* @returns {String} - percent encoding of given character
|
|
*/
|
|
percentEncode (c) {
|
|
var hex = c.toString(16).toUpperCase();
|
|
|
|
(hex.length === 1) && (hex = ZERO + hex);
|
|
|
|
return PERCENT + hex;
|
|
},
|
|
|
|
/**
|
|
* Checks if character at given index in the buffer is already percent encoded or not.
|
|
*
|
|
* @param {Buffer} buffer -
|
|
* @param {Number} i -
|
|
* @returns {Boolean}
|
|
*/
|
|
isPreEncoded (buffer, i) {
|
|
// If it is % check next two bytes for percent encode characters
|
|
// looking for pattern %00 - %FF
|
|
return (buffer[i] === 0x25 &&
|
|
(encoder.isPreEncodedCharacter(buffer[i + 1]) &&
|
|
encoder.isPreEncodedCharacter(buffer[i + 2]))
|
|
);
|
|
},
|
|
|
|
/**
|
|
* Checks if character with given code is valid hexadecimal digit or not.
|
|
*
|
|
* @param {Number} byte -
|
|
* @returns {Boolean}
|
|
*/
|
|
isPreEncodedCharacter (byte) {
|
|
return (byte >= 0x30 && byte <= 0x39) || // 0-9
|
|
(byte >= 0x41 && byte <= 0x46) || // A-F
|
|
(byte >= 0x61 && byte <= 0x66); // a-f
|
|
},
|
|
|
|
/**
|
|
* Checks whether given character should be percent encoded or not for fixture.
|
|
*
|
|
* @param {Number} byte -
|
|
* @returns {Boolean}
|
|
*/
|
|
charactersToPercentEncode (byte) {
|
|
return (byte < 0x23 || byte > 0x7E || // Below # and after ~
|
|
byte === 0x3C || byte === 0x3E || // > and <
|
|
byte === 0x28 || byte === 0x29 || // ( and )
|
|
byte === 0x25 || // %
|
|
byte === 0x27 || // '
|
|
byte === 0x2A // *
|
|
);
|
|
},
|
|
|
|
/**
|
|
* Percent encode a query string according to RFC 3986.
|
|
* Note: This function is supposed to be used on top of node's inbuilt url encoding
|
|
* to solve issue https://github.com/nodejs/node/issues/8321
|
|
*
|
|
* @param {String} value -
|
|
* @returns {String}
|
|
*/
|
|
encode (value) {
|
|
if (!value) { return E; }
|
|
|
|
var buffer = Buffer.from(value),
|
|
ret = E,
|
|
i,
|
|
ii;
|
|
|
|
for (i = 0, ii = buffer.length; i < ii; ++i) {
|
|
if (encoder.charactersToPercentEncode(buffer[i]) && !encoder.isPreEncoded(buffer, i)) {
|
|
ret += encoder.percentEncode(buffer[i]);
|
|
}
|
|
else {
|
|
ret += String.fromCodePoint(buffer[i]); // Only works in ES6 (available in Node v4+)
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Parses a query string into an array, preserving parameter values
|
|
*
|
|
* @private
|
|
* @param {String} string -
|
|
* @returns {*}
|
|
*/
|
|
function parseQueryString (string) {
|
|
var parts;
|
|
|
|
if (typeof string === STRING) {
|
|
parts = string.split(AMPERSAND);
|
|
|
|
return parts.map(function (param, idx) {
|
|
if (param === E && idx !== (parts.length - 1)) {
|
|
return { key: null, value: null };
|
|
}
|
|
|
|
var index = (typeof param === STRING) ? param.indexOf(EQUALS) : -1,
|
|
paramObj = {};
|
|
|
|
// this means that there was no value for this key (not even blank,
|
|
// so we store this info) and the value is set to null
|
|
if (index < 0) {
|
|
paramObj.key = param.substr(0, param.length);
|
|
paramObj.value = null;
|
|
}
|
|
else {
|
|
paramObj.key = param.substr(0, index);
|
|
paramObj.value = param.substr(index + 1);
|
|
}
|
|
|
|
return paramObj;
|
|
});
|
|
}
|
|
|
|
return [];
|
|
}
|
|
|
|
/**
|
|
* Stringifies a query string, from an array of parameters
|
|
*
|
|
* @private
|
|
* @param {Object[]} parameters -
|
|
* @returns {string}
|
|
*/
|
|
function stringifyQueryParams (parameters) {
|
|
return parameters ? parameters.map(function (param) {
|
|
var key = param.key,
|
|
value = param.value;
|
|
|
|
if (value === undefined) {
|
|
return E;
|
|
}
|
|
|
|
if (key === null) {
|
|
key = E;
|
|
}
|
|
|
|
if (value === null) {
|
|
return encoder.encode(key);
|
|
}
|
|
|
|
return encoder.encode(key) + EQUALS + encoder.encode(value);
|
|
}).join(AMPERSAND) : E;
|
|
}
|
|
|
|
/**
|
|
* Converts URL string into Node's Url object with encoded values
|
|
*
|
|
* @private
|
|
* @param {String} urlString -
|
|
* @returns {Url}
|
|
*/
|
|
function toNodeUrl (urlString) {
|
|
var parsed = url.parse(urlString),
|
|
rawQs = parsed.query,
|
|
qs,
|
|
search,
|
|
path,
|
|
str;
|
|
|
|
if (!(rawQs && rawQs.length)) { return parsed; }
|
|
|
|
qs = stringifyQueryParams(parseQueryString(rawQs));
|
|
search = QUERY_SEPARATOR + qs;
|
|
path = parsed.pathname + search;
|
|
|
|
parsed.query = qs;
|
|
parsed.search = search;
|
|
parsed.path = path;
|
|
|
|
str = url.format(parsed);
|
|
|
|
// Parse again, because Node does not guarantee consistency of properties
|
|
return url.parse(str);
|
|
}
|
|
|
|
module.exports = {
|
|
toNodeUrl
|
|
};
|