Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
436 changes: 436 additions & 0 deletions browser.js

Large diffs are not rendered by default.

363 changes: 363 additions & 0 deletions encoder/browser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,363 @@
/**
* This module helps to encode different URL components and expose utility
* methods to percent-encode a given string using an {@link EncodeSet}.
*
* @example
* const encoder = require('postman-url-encoder/encoder')
*
* // returns 'xn--48jwgn17gdel797d.com'
* encoder.encodeHost('郵便屋さん.com')
*
* @example <caption>Using EncodeSet</caption>
* var EncodeSet = require('postman-url-encoder/encoder').EncodeSet
*
* var fragmentEncodeSet = new EncodeSet([' ', '"', '<', '>', '`'])
*
* // returns false
* fragmentEncodeSet.has('['.charCodeAt(0))
*
* // returns true
* fragmentEncodeSet.has('<'.charCodeAt(0))
*
* @module postman-url-encoder/encoder
* @see {@link https://url.spec.whatwg.org/#url-representation}
*/

/**
* @fileoverview
* This module determines which of the reserved characters in the different
* URL components should be percent-encoded and which can be safely used.
*
* The generic URI syntax consists of a hierarchical sequence of components
* referred to as the scheme, authority, path, query, and fragment.
*
* URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
*
* hier-part = "//" authority path-abempty
* / path-absolute
* / path-rootless
* / path-empty
*
* authority = [ userinfo "@" ] host [ ":" port ]
*
* @see {@link https://tools.ietf.org/html/rfc3986#section-2}
* @see {@link https://tools.ietf.org/html/rfc3986#section-3}
*/

const encodeSet = require('./encode-set'),

punycode = require('punycode/'),
_percentEncode = require('./percent-encode').encode,
_percentEncodeCharCode = require('./percent-encode').encodeCharCode,

EncodeSet = encodeSet.EncodeSet,

PATH_ENCODE_SET = encodeSet.PATH_ENCODE_SET,
QUERY_ENCODE_SET = encodeSet.QUERY_ENCODE_SET,
USERINFO_ENCODE_SET = encodeSet.USERINFO_ENCODE_SET,
FRAGMENT_ENCODE_SET = encodeSet.FRAGMENT_ENCODE_SET,
C0_CONTROL_ENCODE_SET = encodeSet.C0_CONTROL_ENCODE_SET,

PARAM_VALUE_ENCODE_SET = EncodeSet.extend(QUERY_ENCODE_SET, ['&']).seal(),
PARAM_KEY_ENCODE_SET = EncodeSet.extend(QUERY_ENCODE_SET, ['&', '=']).seal(),

E = '',
EQUALS = '=',
AMPERSAND = '&',
STRING = 'string',
OBJECT = 'object',

PATH_SEPARATOR = '/',
DOMAIN_SEPARATOR = '.',

/**
* Returns the Punycode ASCII serialization of the domain.
*
* @private
* @function
* @param {String} domain domain name
* @returns {String} punycode encoded domain name
*/
domainToASCII = function (domain) {
const domainWithProtocol = domain.startsWith('http') ? domain : `https://${domain}`;

try {
return new URL(domainWithProtocol).hostname;
}
catch (error) {
return punycode.toASCII(domain);
}
};

/**
* Returns the Punycode ASCII serialization of the domain.
*
* @note Returns input hostname on invalid domain.
*
* @example
* // returns 'xn--fiq228c.com'
* encodeHost('中文.com')
*
* // returns 'xn--48jwgn17gdel797d.com'
* encodeHost(['郵便屋さん', 'com'])
*
* // returns '127.0.0.1'
* encodeHost('127.1')
*
* // returns 'xn--iñvalid.com'
* encodeHost('xn--iñvalid.com')
*
* @param {String|String[]} hostName host or domain name
* @returns {String} Punycode-encoded hostname
*/
function encodeHost (hostName) {
if (Array.isArray(hostName)) {
hostName = hostName.join(DOMAIN_SEPARATOR);
}

if (typeof hostName !== STRING) {
return E;
}

// return input host name if `domainToASCII` returned an empty string
return domainToASCII(hostName) || hostName;
}

/**
* Encodes URL path or individual path segments.
*
* @example
* // returns 'foo/bar&baz'
* encodePath('foo/bar&baz')
*
* // returns 'foo/bar/%20%22%3C%3E%60%23%3F%7B%7D'
* encodePath(['foo', 'bar', ' "<>\`#?{}'])
*
* @param {String|String[]} path Path or path segments
* @returns {String} Percent-encoded path
*/
function encodePath (path) {
if (Array.isArray(path) && path.length) {
path = path.join(PATH_SEPARATOR);
}

if (typeof path !== STRING) {
return E;
}

return _percentEncode(path, PATH_ENCODE_SET);
}

/**
* Encodes URL userinfo (username / password) fields.
*
* @example
* // returns 'info~%20%22%3C%3E%60%23%3F%7B%7D%2F%3A%3B%3D%40%5B%5C%5D%5E%7C'
* encodeAuth('info~ "<>`#?{}/:;=@[\\]^|')
*
* @param {String} param Parameter to encode
* @returns {String} Percent-encoded parameter
*/
function encodeUserInfo (param) {
if (typeof param !== STRING) {
return E;
}

return _percentEncode(param, USERINFO_ENCODE_SET);
}

/**
* Encodes URL fragment identifier or hash.
*
* @example
* // returns 'fragment#%20%22%3C%3E%60'
* encodeHash('fragment# "<>`')
*
* @param {String} fragment Hash or fragment identifier to encode
* @returns {String} Percent-encoded fragment
*/
function encodeFragment (fragment) {
if (typeof fragment !== STRING) {
return E;
}

return _percentEncode(fragment, FRAGMENT_ENCODE_SET);
}

/**
* Encodes single query parameter and returns as a string.
*
* @example
* // returns 'param%20%22%23%27%3C%3E'
* encodeQueryParam('param "#\'<>')
*
* // returns 'foo=bar'
* encodeQueryParam({ key: 'foo', value: 'bar' })
*
* @param {Object|String} param Query param to encode
* @returns {String} Percent-encoded query param
*/
function encodeQueryParam (param) {
if (!param) {
return E;
}

if (typeof param === STRING) {
return _percentEncode(param, QUERY_ENCODE_SET);
}

let key = param.key,
value = param.value,
result;

if (typeof key === STRING) {
result = _percentEncode(key, PARAM_KEY_ENCODE_SET);
}
else {
result = E;
}

if (typeof value === STRING) {
result += EQUALS + _percentEncode(value, PARAM_VALUE_ENCODE_SET);
}

return result;
}

/**
* Encodes list of query parameters and returns encoded query string.
*
* @example
* // returns 'foo=bar&=foo%26bar'
* encodeQueryParams([{ key: 'foo', value: 'bar' }, { value: 'foo&bar' }])
*
* // returns 'q1=foo&q2=bar&q2=baz'
* encodeQueryParams({ q1: 'foo', q2: ['bar', 'baz'] })
*
* @param {Object|Object[]} params Query params to encode
* @returns {String} Percent-encoded query string
*/
function encodeQueryParams (params) {
let i,
j,
ii,
jj,
paramKey,
paramKeys,
paramValue,
result = E,
notFirstParam = false;

if (!(params && typeof params === OBJECT)) {
return E;
}

// handle array of query params
if (Array.isArray(params)) {
for (i = 0, ii = params.length; i < ii; i++) {
// @todo Add helper in PropertyList to filter disabled QueryParam
if (!params[i] || params[i].disabled === true) {
continue;
}

// don't add '&' for the very first enabled param
notFirstParam && (result += AMPERSAND);
notFirstParam = true;

result += encodeQueryParam(params[i]);
}

return result;
}

// handle object with query params
paramKeys = Object.keys(params);

for (i = 0, ii = paramKeys.length; i < ii; i++) {
paramKey = paramKeys[i];
paramValue = params[paramKey];

// { key: ['value1', 'value2', 'value3'] }
if (Array.isArray(paramValue)) {
for (j = 0, jj = paramValue.length; j < jj; j++) {
notFirstParam && (result += AMPERSAND);
notFirstParam = true;

result += encodeQueryParam({ key: paramKey, value: paramValue[j] });
}
}
// { key: 'value' }
else {
notFirstParam && (result += AMPERSAND);
notFirstParam = true;

result += encodeQueryParam({ key: paramKey, value: paramValue });
}
}

return result;
}

/**
* Percent-encode the given string with the given {@link EncodeSet}.
*
* @example <caption>Defaults to C0_CONTROL_ENCODE_SET</caption>
* // returns 'foo %00 bar'
* percentEncode('foo \u0000 bar')
*
* @example <caption>Encode literal @ using custom EncodeSet</caption>
* // returns 'foo%40bar'
* percentEncode('foo@bar', new EncodeSet(['@']))
*
* @param {String} value String to percent-encode
* @param {EncodeSet} [encodeSet=C0_CONTROL_ENCODE_SET] EncodeSet to use for encoding
* @returns {String} Percent-encoded string
*/
function percentEncode (value, encodeSet) {
if (!(value && typeof value === STRING)) {
return E;
}

// defaults to C0_CONTROL_ENCODE_SET
if (!EncodeSet.isEncodeSet(encodeSet)) {
encodeSet = C0_CONTROL_ENCODE_SET;
}

return _percentEncode(value, encodeSet);
}

/**
* Percent encode a character with given code.
*
* @example
* // returns '%20'
* percentEncodeCharCode(32)
*
* @param {Number} code Character code
* @returns {String} Percent-encoded character
*/
function percentEncodeCharCode (code) {
// ensure [0x00, 0xFF] range
if (!(Number.isInteger(code) && code >= 0 && code <= 0xFF)) {
return E;
}

return _percentEncodeCharCode(code);
}

module.exports = {
// URL components
encodeHost,
encodePath,
encodeUserInfo,
encodeFragment,
encodeQueryParam,
encodeQueryParams,

/** @type EncodeSet */
EncodeSet,

// Utilities
percentEncode,
percentEncodeCharCode
};
15 changes: 0 additions & 15 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@
*/

const querystring = require('querystring'),

legacy = require('./legacy'),
parser = require('./parser'),
encoder = require('./encoder'),
QUERY_ENCODE_SET = require('./encoder/encode-set').QUERY_ENCODE_SET,
Expand Down Expand Up @@ -426,22 +424,9 @@ function resolveNodeUrl (base, relative) {
return getUrlTill(base, 'host') + basePathname + relative;
}

/**
* Converts URL string into Node.js compatible Url object using the v1 encoder.
*
* @deprecated since version 2.0
*
* @param {String} url URL string
* @returns {Url} Node.js compatible Url object
*/
function toLegacyNodeUrl (url) {
return legacy.toNodeUrl(url);
}

module.exports = {
encode,
toNodeUrl,
resolveNodeUrl,
toLegacyNodeUrl,
encodeQueryString
};
Loading