schemes.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. 'use strict'
  2. const UUID_REG = /^[\da-f]{8}\b-[\da-f]{4}\b-[\da-f]{4}\b-[\da-f]{4}\b-[\da-f]{12}$/iu
  3. const URN_REG = /([\da-z][\d\-a-z]{0,31}):((?:[\w!$'()*+,\-.:;=@]|%[\da-f]{2})+)/iu
  4. function isSecure (wsComponents) {
  5. return typeof wsComponents.secure === 'boolean' ? wsComponents.secure : String(wsComponents.scheme).toLowerCase() === 'wss'
  6. }
  7. function httpParse (components) {
  8. if (!components.host) {
  9. components.error = components.error || 'HTTP URIs must have a host.'
  10. }
  11. return components
  12. }
  13. function httpSerialize (components) {
  14. const secure = String(components.scheme).toLowerCase() === 'https'
  15. // normalize the default port
  16. if (components.port === (secure ? 443 : 80) || components.port === '') {
  17. components.port = undefined
  18. }
  19. // normalize the empty path
  20. if (!components.path) {
  21. components.path = '/'
  22. }
  23. // NOTE: We do not parse query strings for HTTP URIs
  24. // as WWW Form Url Encoded query strings are part of the HTML4+ spec,
  25. // and not the HTTP spec.
  26. return components
  27. }
  28. function wsParse (wsComponents) {
  29. // indicate if the secure flag is set
  30. wsComponents.secure = isSecure(wsComponents)
  31. // construct resouce name
  32. wsComponents.resourceName = (wsComponents.path || '/') + (wsComponents.query ? '?' + wsComponents.query : '')
  33. wsComponents.path = undefined
  34. wsComponents.query = undefined
  35. return wsComponents
  36. }
  37. function wsSerialize (wsComponents) {
  38. // normalize the default port
  39. if (wsComponents.port === (isSecure(wsComponents) ? 443 : 80) || wsComponents.port === '') {
  40. wsComponents.port = undefined
  41. }
  42. // ensure scheme matches secure flag
  43. if (typeof wsComponents.secure === 'boolean') {
  44. wsComponents.scheme = (wsComponents.secure ? 'wss' : 'ws')
  45. wsComponents.secure = undefined
  46. }
  47. // reconstruct path from resource name
  48. if (wsComponents.resourceName) {
  49. const [path, query] = wsComponents.resourceName.split('?')
  50. wsComponents.path = (path && path !== '/' ? path : undefined)
  51. wsComponents.query = query
  52. wsComponents.resourceName = undefined
  53. }
  54. // forbid fragment component
  55. wsComponents.fragment = undefined
  56. return wsComponents
  57. }
  58. function urnParse (urnComponents, options) {
  59. if (!urnComponents.path) {
  60. urnComponents.error = 'URN can not be parsed'
  61. return urnComponents
  62. }
  63. const matches = urnComponents.path.match(URN_REG)
  64. if (matches) {
  65. const scheme = options.scheme || urnComponents.scheme || 'urn'
  66. urnComponents.nid = matches[1].toLowerCase()
  67. urnComponents.nss = matches[2]
  68. const urnScheme = `${scheme}:${options.nid || urnComponents.nid}`
  69. const schemeHandler = SCHEMES[urnScheme]
  70. urnComponents.path = undefined
  71. if (schemeHandler) {
  72. urnComponents = schemeHandler.parse(urnComponents, options)
  73. }
  74. } else {
  75. urnComponents.error = urnComponents.error || 'URN can not be parsed.'
  76. }
  77. return urnComponents
  78. }
  79. function urnSerialize (urnComponents, options) {
  80. const scheme = options.scheme || urnComponents.scheme || 'urn'
  81. const nid = urnComponents.nid.toLowerCase()
  82. const urnScheme = `${scheme}:${options.nid || nid}`
  83. const schemeHandler = SCHEMES[urnScheme]
  84. if (schemeHandler) {
  85. urnComponents = schemeHandler.serialize(urnComponents, options)
  86. }
  87. const uriComponents = urnComponents
  88. const nss = urnComponents.nss
  89. uriComponents.path = `${nid || options.nid}:${nss}`
  90. options.skipEscape = true
  91. return uriComponents
  92. }
  93. function urnuuidParse (urnComponents, options) {
  94. const uuidComponents = urnComponents
  95. uuidComponents.uuid = uuidComponents.nss
  96. uuidComponents.nss = undefined
  97. if (!options.tolerant && (!uuidComponents.uuid || !UUID_REG.test(uuidComponents.uuid))) {
  98. uuidComponents.error = uuidComponents.error || 'UUID is not valid.'
  99. }
  100. return uuidComponents
  101. }
  102. function urnuuidSerialize (uuidComponents) {
  103. const urnComponents = uuidComponents
  104. // normalize UUID
  105. urnComponents.nss = (uuidComponents.uuid || '').toLowerCase()
  106. return urnComponents
  107. }
  108. const http = {
  109. scheme: 'http',
  110. domainHost: true,
  111. parse: httpParse,
  112. serialize: httpSerialize
  113. }
  114. const https = {
  115. scheme: 'https',
  116. domainHost: http.domainHost,
  117. parse: httpParse,
  118. serialize: httpSerialize
  119. }
  120. const ws = {
  121. scheme: 'ws',
  122. domainHost: true,
  123. parse: wsParse,
  124. serialize: wsSerialize
  125. }
  126. const wss = {
  127. scheme: 'wss',
  128. domainHost: ws.domainHost,
  129. parse: ws.parse,
  130. serialize: ws.serialize
  131. }
  132. const urn = {
  133. scheme: 'urn',
  134. parse: urnParse,
  135. serialize: urnSerialize,
  136. skipNormalize: true
  137. }
  138. const urnuuid = {
  139. scheme: 'urn:uuid',
  140. parse: urnuuidParse,
  141. serialize: urnuuidSerialize,
  142. skipNormalize: true
  143. }
  144. const SCHEMES = {
  145. http,
  146. https,
  147. ws,
  148. wss,
  149. urn,
  150. 'urn:uuid': urnuuid
  151. }
  152. module.exports = SCHEMES