268 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			268 lines
		
	
	
		
			7.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
'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,
 | 
						|
}
 |