123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267 |
- 'use strict'
- const { isUUID } = require('./utils')
- const URN_REG = /([\da-z][\d\-a-z]{0,31}):((?:[\w!$'()*+,\-.:;=@]|%[\da-f]{2})+)/iu
- const supportedSchemeNames = /** @type {const} */ (['http', 'https', 'ws',
- 'wss', 'urn', 'urn:uuid'])
- /** @typedef {supportedSchemeNames[number]} SchemeName */
- /**
- * @param {string} name
- * @returns {name is SchemeName}
- */
- function isValidSchemeName (name) {
- return supportedSchemeNames.indexOf(/** @type {*} */ (name)) !== -1
- }
- /**
- * @callback SchemeFn
- * @param {import('../types/index').URIComponent} component
- * @param {import('../types/index').Options} options
- * @returns {import('../types/index').URIComponent}
- */
- /**
- * @typedef {Object} SchemeHandler
- * @property {SchemeName} scheme - The scheme name.
- * @property {boolean} [domainHost] - Indicates if the scheme supports domain hosts.
- * @property {SchemeFn} parse - Function to parse the URI component for this scheme.
- * @property {SchemeFn} serialize - Function to serialize the URI component for this scheme.
- * @property {boolean} [skipNormalize] - Indicates if normalization should be skipped for this scheme.
- * @property {boolean} [absolutePath] - Indicates if the scheme uses absolute paths.
- * @property {boolean} [unicodeSupport] - Indicates if the scheme supports Unicode.
- */
- /**
- * @param {import('../types/index').URIComponent} wsComponent
- * @returns {boolean}
- */
- function wsIsSecure (wsComponent) {
- if (wsComponent.secure === true) {
- return true
- } else if (wsComponent.secure === false) {
- return false
- } else if (wsComponent.scheme) {
- return (
- wsComponent.scheme.length === 3 &&
- (wsComponent.scheme[0] === 'w' || wsComponent.scheme[0] === 'W') &&
- (wsComponent.scheme[1] === 's' || wsComponent.scheme[1] === 'S') &&
- (wsComponent.scheme[2] === 's' || wsComponent.scheme[2] === 'S')
- )
- } else {
- return false
- }
- }
- /** @type {SchemeFn} */
- function httpParse (component) {
- if (!component.host) {
- component.error = component.error || 'HTTP URIs must have a host.'
- }
- return component
- }
- /** @type {SchemeFn} */
- function httpSerialize (component) {
- const secure = String(component.scheme).toLowerCase() === 'https'
- // normalize the default port
- if (component.port === (secure ? 443 : 80) || component.port === '') {
- component.port = undefined
- }
- // normalize the empty path
- if (!component.path) {
- component.path = '/'
- }
- // NOTE: We do not parse query strings for HTTP URIs
- // as WWW Form Url Encoded query strings are part of the HTML4+ spec,
- // and not the HTTP spec.
- return component
- }
- /** @type {SchemeFn} */
- function wsParse (wsComponent) {
- // indicate if the secure flag is set
- wsComponent.secure = wsIsSecure(wsComponent)
- // construct resouce name
- wsComponent.resourceName = (wsComponent.path || '/') + (wsComponent.query ? '?' + wsComponent.query : '')
- wsComponent.path = undefined
- wsComponent.query = undefined
- return wsComponent
- }
- /** @type {SchemeFn} */
- function wsSerialize (wsComponent) {
- // normalize the default port
- if (wsComponent.port === (wsIsSecure(wsComponent) ? 443 : 80) || wsComponent.port === '') {
- wsComponent.port = undefined
- }
- // ensure scheme matches secure flag
- if (typeof wsComponent.secure === 'boolean') {
- wsComponent.scheme = (wsComponent.secure ? 'wss' : 'ws')
- wsComponent.secure = undefined
- }
- // reconstruct path from resource name
- if (wsComponent.resourceName) {
- const [path, query] = wsComponent.resourceName.split('?')
- wsComponent.path = (path && path !== '/' ? path : undefined)
- wsComponent.query = query
- wsComponent.resourceName = undefined
- }
- // forbid fragment component
- wsComponent.fragment = undefined
- return wsComponent
- }
- /** @type {SchemeFn} */
- function urnParse (urnComponent, options) {
- if (!urnComponent.path) {
- urnComponent.error = 'URN can not be parsed'
- return urnComponent
- }
- const matches = urnComponent.path.match(URN_REG)
- if (matches) {
- const scheme = options.scheme || urnComponent.scheme || 'urn'
- urnComponent.nid = matches[1].toLowerCase()
- urnComponent.nss = matches[2]
- const urnScheme = `${scheme}:${options.nid || urnComponent.nid}`
- const schemeHandler = getSchemeHandler(urnScheme)
- urnComponent.path = undefined
- if (schemeHandler) {
- urnComponent = schemeHandler.parse(urnComponent, options)
- }
- } else {
- urnComponent.error = urnComponent.error || 'URN can not be parsed.'
- }
- return urnComponent
- }
- /** @type {SchemeFn} */
- function urnSerialize (urnComponent, options) {
- if (urnComponent.nid === undefined) {
- throw new Error('URN without nid cannot be serialized')
- }
- const scheme = options.scheme || urnComponent.scheme || 'urn'
- const nid = urnComponent.nid.toLowerCase()
- const urnScheme = `${scheme}:${options.nid || nid}`
- const schemeHandler = getSchemeHandler(urnScheme)
- if (schemeHandler) {
- urnComponent = schemeHandler.serialize(urnComponent, options)
- }
- const uriComponent = urnComponent
- const nss = urnComponent.nss
- uriComponent.path = `${nid || options.nid}:${nss}`
- options.skipEscape = true
- return uriComponent
- }
- /** @type {SchemeFn} */
- function urnuuidParse (urnComponent, options) {
- const uuidComponent = urnComponent
- uuidComponent.uuid = uuidComponent.nss
- uuidComponent.nss = undefined
- if (!options.tolerant && (!uuidComponent.uuid || !isUUID(uuidComponent.uuid))) {
- uuidComponent.error = uuidComponent.error || 'UUID is not valid.'
- }
- return uuidComponent
- }
- /** @type {SchemeFn} */
- function urnuuidSerialize (uuidComponent) {
- const urnComponent = uuidComponent
- // normalize UUID
- urnComponent.nss = (uuidComponent.uuid || '').toLowerCase()
- return urnComponent
- }
- const http = /** @type {SchemeHandler} */ ({
- scheme: 'http',
- domainHost: true,
- parse: httpParse,
- serialize: httpSerialize
- })
- const https = /** @type {SchemeHandler} */ ({
- scheme: 'https',
- domainHost: http.domainHost,
- parse: httpParse,
- serialize: httpSerialize
- })
- const ws = /** @type {SchemeHandler} */ ({
- scheme: 'ws',
- domainHost: true,
- parse: wsParse,
- serialize: wsSerialize
- })
- const wss = /** @type {SchemeHandler} */ ({
- scheme: 'wss',
- domainHost: ws.domainHost,
- parse: ws.parse,
- serialize: ws.serialize
- })
- const urn = /** @type {SchemeHandler} */ ({
- scheme: 'urn',
- parse: urnParse,
- serialize: urnSerialize,
- skipNormalize: true
- })
- const urnuuid = /** @type {SchemeHandler} */ ({
- scheme: 'urn:uuid',
- parse: urnuuidParse,
- serialize: urnuuidSerialize,
- skipNormalize: true
- })
- const SCHEMES = /** @type {Record<SchemeName, SchemeHandler>} */ ({
- http,
- https,
- ws,
- wss,
- urn,
- 'urn:uuid': urnuuid
- })
- Object.setPrototypeOf(SCHEMES, null)
- /**
- * @param {string|undefined} scheme
- * @returns {SchemeHandler|undefined}
- */
- function getSchemeHandler (scheme) {
- return (
- scheme && (
- SCHEMES[/** @type {SchemeName} */ (scheme)] ||
- SCHEMES[/** @type {SchemeName} */(scheme.toLowerCase())])
- ) ||
- undefined
- }
- module.exports = {
- wsIsSecure,
- SCHEMES,
- isValidSchemeName,
- getSchemeHandler,
- }
|