| 'use strict' |
| |
| const { normalizeIPv6, normalizeIPv4, removeDotSegments, recomposeAuthority, normalizeComponentEncoding } = require('./lib/utils') |
| const SCHEMES = require('./lib/schemes') |
| |
| function normalize (uri, options) { |
| if (typeof uri === 'string') { |
| uri = serialize(parse(uri, options), options) |
| } else if (typeof uri === 'object') { |
| uri = parse(serialize(uri, options), options) |
| } |
| return uri |
| } |
| |
| function resolve (baseURI, relativeURI, options) { |
| const schemelessOptions = Object.assign({ scheme: 'null' }, options) |
| const resolved = resolveComponents(parse(baseURI, schemelessOptions), parse(relativeURI, schemelessOptions), schemelessOptions, true) |
| return serialize(resolved, { ...schemelessOptions, skipEscape: true }) |
| } |
| |
| function resolveComponents (base, relative, options, skipNormalization) { |
| const target = {} |
| if (!skipNormalization) { |
| base = parse(serialize(base, options), options) // normalize base components |
| relative = parse(serialize(relative, options), options) // normalize relative components |
| } |
| options = options || {} |
| |
| if (!options.tolerant && relative.scheme) { |
| target.scheme = relative.scheme |
| // target.authority = relative.authority; |
| target.userinfo = relative.userinfo |
| target.host = relative.host |
| target.port = relative.port |
| target.path = removeDotSegments(relative.path || '') |
| target.query = relative.query |
| } else { |
| if (relative.userinfo !== undefined || relative.host !== undefined || relative.port !== undefined) { |
| // target.authority = relative.authority; |
| target.userinfo = relative.userinfo |
| target.host = relative.host |
| target.port = relative.port |
| target.path = removeDotSegments(relative.path || '') |
| target.query = relative.query |
| } else { |
| if (!relative.path) { |
| target.path = base.path |
| if (relative.query !== undefined) { |
| target.query = relative.query |
| } else { |
| target.query = base.query |
| } |
| } else { |
| if (relative.path.charAt(0) === '/') { |
| target.path = removeDotSegments(relative.path) |
| } else { |
| if ((base.userinfo !== undefined || base.host !== undefined || base.port !== undefined) && !base.path) { |
| target.path = '/' + relative.path |
| } else if (!base.path) { |
| target.path = relative.path |
| } else { |
| target.path = base.path.slice(0, base.path.lastIndexOf('/') + 1) + relative.path |
| } |
| target.path = removeDotSegments(target.path) |
| } |
| target.query = relative.query |
| } |
| // target.authority = base.authority; |
| target.userinfo = base.userinfo |
| target.host = base.host |
| target.port = base.port |
| } |
| target.scheme = base.scheme |
| } |
| |
| target.fragment = relative.fragment |
| |
| return target |
| } |
| |
| function equal (uriA, uriB, options) { |
| if (typeof uriA === 'string') { |
| uriA = unescape(uriA) |
| uriA = serialize(normalizeComponentEncoding(parse(uriA, options), true), { ...options, skipEscape: true }) |
| } else if (typeof uriA === 'object') { |
| uriA = serialize(normalizeComponentEncoding(uriA, true), { ...options, skipEscape: true }) |
| } |
| |
| if (typeof uriB === 'string') { |
| uriB = unescape(uriB) |
| uriB = serialize(normalizeComponentEncoding(parse(uriB, options), true), { ...options, skipEscape: true }) |
| } else if (typeof uriB === 'object') { |
| uriB = serialize(normalizeComponentEncoding(uriB, true), { ...options, skipEscape: true }) |
| } |
| |
| return uriA.toLowerCase() === uriB.toLowerCase() |
| } |
| |
| function serialize (cmpts, opts) { |
| const components = { |
| host: cmpts.host, |
| scheme: cmpts.scheme, |
| userinfo: cmpts.userinfo, |
| port: cmpts.port, |
| path: cmpts.path, |
| query: cmpts.query, |
| nid: cmpts.nid, |
| nss: cmpts.nss, |
| uuid: cmpts.uuid, |
| fragment: cmpts.fragment, |
| reference: cmpts.reference, |
| resourceName: cmpts.resourceName, |
| secure: cmpts.secure, |
| error: '' |
| } |
| const options = Object.assign({}, opts) |
| const uriTokens = [] |
| |
| // find scheme handler |
| const schemeHandler = SCHEMES[(options.scheme || components.scheme || '').toLowerCase()] |
| |
| // perform scheme specific serialization |
| if (schemeHandler && schemeHandler.serialize) schemeHandler.serialize(components, options) |
| |
| if (components.path !== undefined) { |
| if (!options.skipEscape) { |
| components.path = escape(components.path) |
| |
| if (components.scheme !== undefined) { |
| components.path = components.path.split('%3A').join(':') |
| } |
| } else { |
| components.path = unescape(components.path) |
| } |
| } |
| |
| if (options.reference !== 'suffix' && components.scheme) { |
| uriTokens.push(components.scheme, ':') |
| } |
| |
| const authority = recomposeAuthority(components, options) |
| if (authority !== undefined) { |
| if (options.reference !== 'suffix') { |
| uriTokens.push('//') |
| } |
| |
| uriTokens.push(authority) |
| |
| if (components.path && components.path.charAt(0) !== '/') { |
| uriTokens.push('/') |
| } |
| } |
| if (components.path !== undefined) { |
| let s = components.path |
| |
| if (!options.absolutePath && (!schemeHandler || !schemeHandler.absolutePath)) { |
| s = removeDotSegments(s) |
| } |
| |
| if (authority === undefined) { |
| s = s.replace(/^\/\//u, '/%2F') // don't allow the path to start with "//" |
| } |
| |
| uriTokens.push(s) |
| } |
| |
| if (components.query !== undefined) { |
| uriTokens.push('?', components.query) |
| } |
| |
| if (components.fragment !== undefined) { |
| uriTokens.push('#', components.fragment) |
| } |
| return uriTokens.join('') |
| } |
| |
| const hexLookUp = Array.from({ length: 127 }, (v, k) => /[^!"$&'()*+,\-.;=_`a-z{}~]/u.test(String.fromCharCode(k))) |
| |
| function nonSimpleDomain (value) { |
| let code = 0 |
| for (let i = 0, len = value.length; i < len; ++i) { |
| code = value.charCodeAt(i) |
| if (code > 126 || hexLookUp[code]) { |
| return true |
| } |
| } |
| return false |
| } |
| |
| const URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:?]*)(?::(\d*))?))?([^#?]*)(?:\?([^#]*))?(?:#((?:.|[\n\r])*))?/u |
| |
| function parse (uri, opts) { |
| const options = Object.assign({}, opts) |
| const parsed = { |
| scheme: undefined, |
| userinfo: undefined, |
| host: '', |
| port: undefined, |
| path: '', |
| query: undefined, |
| fragment: undefined |
| } |
| const gotEncoding = uri.indexOf('%') !== -1 |
| let isIP = false |
| if (options.reference === 'suffix') uri = (options.scheme ? options.scheme + ':' : '') + '//' + uri |
| |
| const matches = uri.match(URI_PARSE) |
| |
| if (matches) { |
| // store each component |
| parsed.scheme = matches[1] |
| parsed.userinfo = matches[3] |
| parsed.host = matches[4] |
| parsed.port = parseInt(matches[5], 10) |
| parsed.path = matches[6] || '' |
| parsed.query = matches[7] |
| parsed.fragment = matches[8] |
| |
| // fix port number |
| if (isNaN(parsed.port)) { |
| parsed.port = matches[5] |
| } |
| if (parsed.host) { |
| const ipv4result = normalizeIPv4(parsed.host) |
| if (ipv4result.isIPV4 === false) { |
| const ipv6result = normalizeIPv6(ipv4result.host, { isIPV4: false }) |
| parsed.host = ipv6result.host.toLowerCase() |
| isIP = ipv6result.isIPV6 |
| } else { |
| parsed.host = ipv4result.host |
| isIP = true |
| } |
| } |
| if (parsed.scheme === undefined && parsed.userinfo === undefined && parsed.host === undefined && parsed.port === undefined && !parsed.path && parsed.query === undefined) { |
| parsed.reference = 'same-document' |
| } else if (parsed.scheme === undefined) { |
| parsed.reference = 'relative' |
| } else if (parsed.fragment === undefined) { |
| parsed.reference = 'absolute' |
| } else { |
| parsed.reference = 'uri' |
| } |
| |
| // check for reference errors |
| if (options.reference && options.reference !== 'suffix' && options.reference !== parsed.reference) { |
| parsed.error = parsed.error || 'URI is not a ' + options.reference + ' reference.' |
| } |
| |
| // find scheme handler |
| const schemeHandler = SCHEMES[(options.scheme || parsed.scheme || '').toLowerCase()] |
| |
| // check if scheme can't handle IRIs |
| if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport)) { |
| // if host component is a domain name |
| if (parsed.host && (options.domainHost || (schemeHandler && schemeHandler.domainHost)) && isIP === false && nonSimpleDomain(parsed.host)) { |
| // convert Unicode IDN -> ASCII IDN |
| try { |
| parsed.host = URL.domainToASCII(parsed.host.toLowerCase()) |
| } catch (e) { |
| parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e |
| } |
| } |
| // convert IRI -> URI |
| } |
| |
| if (!schemeHandler || (schemeHandler && !schemeHandler.skipNormalize)) { |
| if (gotEncoding && parsed.scheme !== undefined) { |
| parsed.scheme = unescape(parsed.scheme) |
| } |
| if (gotEncoding && parsed.host !== undefined) { |
| parsed.host = unescape(parsed.host) |
| } |
| if (parsed.path !== undefined && parsed.path.length) { |
| parsed.path = escape(unescape(parsed.path)) |
| } |
| if (parsed.fragment !== undefined && parsed.fragment.length) { |
| parsed.fragment = encodeURI(decodeURIComponent(parsed.fragment)) |
| } |
| } |
| |
| // perform scheme specific parsing |
| if (schemeHandler && schemeHandler.parse) { |
| schemeHandler.parse(parsed, options) |
| } |
| } else { |
| parsed.error = parsed.error || 'URI can not be parsed.' |
| } |
| return parsed |
| } |
| |
| const fastUri = { |
| SCHEMES, |
| normalize, |
| resolve, |
| resolveComponents, |
| equal, |
| serialize, |
| parse |
| } |
| |
| module.exports = fastUri |
| module.exports.default = fastUri |
| module.exports.fastUri = fastUri |