fast-uri 3.0.6 → 3.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci.yml +53 -5
- package/.github/workflows/lock-threads.yml +19 -0
- package/.github/workflows/package-manager-ci.yml +6 -2
- package/LICENSE +1 -1
- package/README.md +48 -28
- package/benchmark/benchmark.mjs +159 -0
- package/benchmark/equal.mjs +51 -0
- package/benchmark/non-simple-domain.mjs +22 -0
- package/benchmark/package.json +17 -0
- package/benchmark/string-array-to-hex-stripped.mjs +24 -0
- package/benchmark/ws-is-secure.mjs +65 -0
- package/index.js +100 -65
- package/lib/schemes.js +160 -81
- package/lib/utils.js +304 -126
- package/package.json +8 -8
- package/test/ajv.test.js +7 -3
- package/test/equal.test.js +7 -2
- package/test/fixtures/uri-js-parse.json +501 -0
- package/test/fixtures/uri-js-serialize.json +120 -0
- package/test/parse.test.js +31 -31
- package/test/resolve.test.js +52 -49
- package/test/rfc-3986.test.js +90 -0
- package/test/security-normalization.test.js +39 -0
- package/test/serialize.test.js +55 -47
- package/test/uri-js-compatibility.test.js +33 -0
- package/test/uri-js.test.js +230 -230
- package/test/util.test.js +23 -8
- package/tsconfig.json +9 -0
- package/types/index.d.ts +7 -0
- package/.github/.stale.yml +0 -21
- package/.github/tests_checker.yml +0 -8
- package/benchmark.js +0 -105
- package/lib/scopedChars.js +0 -30
- package/test/.gitkeep +0 -0
- package/test/compatibility.test.js +0 -131
package/index.js
CHANGED
|
@@ -1,28 +1,49 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { normalizeIPv6,
|
|
4
|
-
const SCHEMES = require('./lib/schemes')
|
|
5
|
-
|
|
3
|
+
const { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, escapePreservingEscapes, isIPv4, nonSimpleDomain } = require('./lib/utils')
|
|
4
|
+
const { SCHEMES, getSchemeHandler } = require('./lib/schemes')
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @template {import('./types/index').URIComponent|string} T
|
|
8
|
+
* @param {T} uri
|
|
9
|
+
* @param {import('./types/index').Options} [options]
|
|
10
|
+
* @returns {T}
|
|
11
|
+
*/
|
|
6
12
|
function normalize (uri, options) {
|
|
7
13
|
if (typeof uri === 'string') {
|
|
8
|
-
uri = serialize(parse(uri, options), options)
|
|
14
|
+
uri = /** @type {T} */ (serialize(parse(uri, options), options))
|
|
9
15
|
} else if (typeof uri === 'object') {
|
|
10
|
-
uri = parse(serialize(uri, options), options)
|
|
16
|
+
uri = /** @type {T} */ (parse(serialize(uri, options), options))
|
|
11
17
|
}
|
|
12
18
|
return uri
|
|
13
19
|
}
|
|
14
20
|
|
|
21
|
+
/**
|
|
22
|
+
* @param {string} baseURI
|
|
23
|
+
* @param {string} relativeURI
|
|
24
|
+
* @param {import('./types/index').Options} [options]
|
|
25
|
+
* @returns {string}
|
|
26
|
+
*/
|
|
15
27
|
function resolve (baseURI, relativeURI, options) {
|
|
16
|
-
const schemelessOptions = Object.assign({ scheme: 'null' }, options)
|
|
17
|
-
const resolved =
|
|
18
|
-
|
|
28
|
+
const schemelessOptions = options ? Object.assign({ scheme: 'null' }, options) : { scheme: 'null' }
|
|
29
|
+
const resolved = resolveComponent(parse(baseURI, schemelessOptions), parse(relativeURI, schemelessOptions), schemelessOptions, true)
|
|
30
|
+
schemelessOptions.skipEscape = true
|
|
31
|
+
return serialize(resolved, schemelessOptions)
|
|
19
32
|
}
|
|
20
33
|
|
|
21
|
-
|
|
34
|
+
/**
|
|
35
|
+
* @param {import ('./types/index').URIComponent} base
|
|
36
|
+
* @param {import ('./types/index').URIComponent} relative
|
|
37
|
+
* @param {import('./types/index').Options} [options]
|
|
38
|
+
* @param {boolean} [skipNormalization=false]
|
|
39
|
+
* @returns {import ('./types/index').URIComponent}
|
|
40
|
+
*/
|
|
41
|
+
function resolveComponent (base, relative, options, skipNormalization) {
|
|
42
|
+
/** @type {import('./types/index').URIComponent} */
|
|
22
43
|
const target = {}
|
|
23
44
|
if (!skipNormalization) {
|
|
24
|
-
base = parse(serialize(base, options), options) // normalize base
|
|
25
|
-
relative = parse(serialize(relative, options), options) // normalize relative
|
|
45
|
+
base = parse(serialize(base, options), options) // normalize base component
|
|
46
|
+
relative = parse(serialize(relative, options), options) // normalize relative component
|
|
26
47
|
}
|
|
27
48
|
options = options || {}
|
|
28
49
|
|
|
@@ -51,7 +72,7 @@ function resolveComponents (base, relative, options, skipNormalization) {
|
|
|
51
72
|
target.query = base.query
|
|
52
73
|
}
|
|
53
74
|
} else {
|
|
54
|
-
if (relative.path
|
|
75
|
+
if (relative.path[0] === '/') {
|
|
55
76
|
target.path = removeDotSegments(relative.path)
|
|
56
77
|
} else {
|
|
57
78
|
if ((base.userinfo !== undefined || base.host !== undefined || base.port !== undefined) && !base.path) {
|
|
@@ -78,26 +99,35 @@ function resolveComponents (base, relative, options, skipNormalization) {
|
|
|
78
99
|
return target
|
|
79
100
|
}
|
|
80
101
|
|
|
102
|
+
/**
|
|
103
|
+
* @param {import ('./types/index').URIComponent|string} uriA
|
|
104
|
+
* @param {import ('./types/index').URIComponent|string} uriB
|
|
105
|
+
* @param {import ('./types/index').Options} options
|
|
106
|
+
* @returns {boolean}
|
|
107
|
+
*/
|
|
81
108
|
function equal (uriA, uriB, options) {
|
|
82
109
|
if (typeof uriA === 'string') {
|
|
83
|
-
uriA =
|
|
84
|
-
uriA = serialize(normalizeComponentEncoding(parse(uriA, options), true), { ...options, skipEscape: true })
|
|
110
|
+
uriA = serialize(parse(uriA, options), options)
|
|
85
111
|
} else if (typeof uriA === 'object') {
|
|
86
|
-
uriA = serialize(
|
|
112
|
+
uriA = serialize(uriA, options)
|
|
87
113
|
}
|
|
88
114
|
|
|
89
115
|
if (typeof uriB === 'string') {
|
|
90
|
-
uriB =
|
|
91
|
-
uriB = serialize(normalizeComponentEncoding(parse(uriB, options), true), { ...options, skipEscape: true })
|
|
116
|
+
uriB = serialize(parse(uriB, options), options)
|
|
92
117
|
} else if (typeof uriB === 'object') {
|
|
93
|
-
uriB = serialize(
|
|
118
|
+
uriB = serialize(uriB, options)
|
|
94
119
|
}
|
|
95
120
|
|
|
96
121
|
return uriA.toLowerCase() === uriB.toLowerCase()
|
|
97
122
|
}
|
|
98
123
|
|
|
124
|
+
/**
|
|
125
|
+
* @param {Readonly<import('./types/index').URIComponent>} cmpts
|
|
126
|
+
* @param {import('./types/index').Options} [opts]
|
|
127
|
+
* @returns {string}
|
|
128
|
+
*/
|
|
99
129
|
function serialize (cmpts, opts) {
|
|
100
|
-
const
|
|
130
|
+
const component = {
|
|
101
131
|
host: cmpts.host,
|
|
102
132
|
scheme: cmpts.scheme,
|
|
103
133
|
userinfo: cmpts.userinfo,
|
|
@@ -117,28 +147,28 @@ function serialize (cmpts, opts) {
|
|
|
117
147
|
const uriTokens = []
|
|
118
148
|
|
|
119
149
|
// find scheme handler
|
|
120
|
-
const schemeHandler =
|
|
150
|
+
const schemeHandler = getSchemeHandler(options.scheme || component.scheme)
|
|
121
151
|
|
|
122
152
|
// perform scheme specific serialization
|
|
123
|
-
if (schemeHandler && schemeHandler.serialize) schemeHandler.serialize(
|
|
153
|
+
if (schemeHandler && schemeHandler.serialize) schemeHandler.serialize(component, options)
|
|
124
154
|
|
|
125
|
-
if (
|
|
155
|
+
if (component.path !== undefined) {
|
|
126
156
|
if (!options.skipEscape) {
|
|
127
|
-
|
|
157
|
+
component.path = escapePreservingEscapes(component.path)
|
|
128
158
|
|
|
129
|
-
if (
|
|
130
|
-
|
|
159
|
+
if (component.scheme !== undefined) {
|
|
160
|
+
component.path = component.path.split('%3A').join(':')
|
|
131
161
|
}
|
|
132
162
|
} else {
|
|
133
|
-
|
|
163
|
+
component.path = normalizePercentEncoding(component.path)
|
|
134
164
|
}
|
|
135
165
|
}
|
|
136
166
|
|
|
137
|
-
if (options.reference !== 'suffix' &&
|
|
138
|
-
uriTokens.push(
|
|
167
|
+
if (options.reference !== 'suffix' && component.scheme) {
|
|
168
|
+
uriTokens.push(component.scheme, ':')
|
|
139
169
|
}
|
|
140
170
|
|
|
141
|
-
const authority = recomposeAuthority(
|
|
171
|
+
const authority = recomposeAuthority(component)
|
|
142
172
|
if (authority !== undefined) {
|
|
143
173
|
if (options.reference !== 'suffix') {
|
|
144
174
|
uriTokens.push('//')
|
|
@@ -146,51 +176,49 @@ function serialize (cmpts, opts) {
|
|
|
146
176
|
|
|
147
177
|
uriTokens.push(authority)
|
|
148
178
|
|
|
149
|
-
if (
|
|
179
|
+
if (component.path && component.path[0] !== '/') {
|
|
150
180
|
uriTokens.push('/')
|
|
151
181
|
}
|
|
152
182
|
}
|
|
153
|
-
if (
|
|
154
|
-
let s =
|
|
183
|
+
if (component.path !== undefined) {
|
|
184
|
+
let s = component.path
|
|
155
185
|
|
|
156
186
|
if (!options.absolutePath && (!schemeHandler || !schemeHandler.absolutePath)) {
|
|
157
187
|
s = removeDotSegments(s)
|
|
158
188
|
}
|
|
159
189
|
|
|
160
|
-
if (
|
|
161
|
-
|
|
190
|
+
if (
|
|
191
|
+
authority === undefined &&
|
|
192
|
+
s[0] === '/' &&
|
|
193
|
+
s[1] === '/'
|
|
194
|
+
) {
|
|
195
|
+
// don't allow the path to start with "//"
|
|
196
|
+
s = '/%2F' + s.slice(2)
|
|
162
197
|
}
|
|
163
198
|
|
|
164
199
|
uriTokens.push(s)
|
|
165
200
|
}
|
|
166
201
|
|
|
167
|
-
if (
|
|
168
|
-
uriTokens.push('?',
|
|
202
|
+
if (component.query !== undefined) {
|
|
203
|
+
uriTokens.push('?', component.query)
|
|
169
204
|
}
|
|
170
205
|
|
|
171
|
-
if (
|
|
172
|
-
uriTokens.push('#',
|
|
206
|
+
if (component.fragment !== undefined) {
|
|
207
|
+
uriTokens.push('#', component.fragment)
|
|
173
208
|
}
|
|
174
209
|
return uriTokens.join('')
|
|
175
210
|
}
|
|
176
211
|
|
|
177
|
-
const hexLookUp = Array.from({ length: 127 }, (_v, k) => /[^!"$&'()*+,\-.;=_`a-z{}~]/u.test(String.fromCharCode(k)))
|
|
178
|
-
|
|
179
|
-
function nonSimpleDomain (value) {
|
|
180
|
-
let code = 0
|
|
181
|
-
for (let i = 0, len = value.length; i < len; ++i) {
|
|
182
|
-
code = value.charCodeAt(i)
|
|
183
|
-
if (code > 126 || hexLookUp[code]) {
|
|
184
|
-
return true
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
return false
|
|
188
|
-
}
|
|
189
|
-
|
|
190
212
|
const URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:?]*)(?::(\d*))?))?([^#?]*)(?:\?([^#]*))?(?:#((?:.|[\n\r])*))?/u
|
|
191
213
|
|
|
214
|
+
/**
|
|
215
|
+
* @param {string} uri
|
|
216
|
+
* @param {import('./types/index').Options} [opts]
|
|
217
|
+
* @returns
|
|
218
|
+
*/
|
|
192
219
|
function parse (uri, opts) {
|
|
193
220
|
const options = Object.assign({}, opts)
|
|
221
|
+
/** @type {import('./types/index').URIComponent} */
|
|
194
222
|
const parsed = {
|
|
195
223
|
scheme: undefined,
|
|
196
224
|
userinfo: undefined,
|
|
@@ -200,9 +228,15 @@ function parse (uri, opts) {
|
|
|
200
228
|
query: undefined,
|
|
201
229
|
fragment: undefined
|
|
202
230
|
}
|
|
203
|
-
|
|
231
|
+
|
|
204
232
|
let isIP = false
|
|
205
|
-
if (options.reference === 'suffix')
|
|
233
|
+
if (options.reference === 'suffix') {
|
|
234
|
+
if (options.scheme) {
|
|
235
|
+
uri = options.scheme + ':' + uri
|
|
236
|
+
} else {
|
|
237
|
+
uri = '//' + uri
|
|
238
|
+
}
|
|
239
|
+
}
|
|
206
240
|
|
|
207
241
|
const matches = uri.match(URI_PARSE)
|
|
208
242
|
|
|
@@ -221,13 +255,12 @@ function parse (uri, opts) {
|
|
|
221
255
|
parsed.port = matches[5]
|
|
222
256
|
}
|
|
223
257
|
if (parsed.host) {
|
|
224
|
-
const ipv4result =
|
|
225
|
-
if (ipv4result
|
|
226
|
-
const ipv6result = normalizeIPv6(
|
|
258
|
+
const ipv4result = isIPv4(parsed.host)
|
|
259
|
+
if (ipv4result === false) {
|
|
260
|
+
const ipv6result = normalizeIPv6(parsed.host)
|
|
227
261
|
parsed.host = ipv6result.host.toLowerCase()
|
|
228
262
|
isIP = ipv6result.isIPV6
|
|
229
263
|
} else {
|
|
230
|
-
parsed.host = ipv4result.host
|
|
231
264
|
isIP = true
|
|
232
265
|
}
|
|
233
266
|
}
|
|
@@ -247,7 +280,7 @@ function parse (uri, opts) {
|
|
|
247
280
|
}
|
|
248
281
|
|
|
249
282
|
// find scheme handler
|
|
250
|
-
const schemeHandler =
|
|
283
|
+
const schemeHandler = getSchemeHandler(options.scheme || parsed.scheme)
|
|
251
284
|
|
|
252
285
|
// check if scheme can't handle IRIs
|
|
253
286
|
if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport)) {
|
|
@@ -264,14 +297,16 @@ function parse (uri, opts) {
|
|
|
264
297
|
}
|
|
265
298
|
|
|
266
299
|
if (!schemeHandler || (schemeHandler && !schemeHandler.skipNormalize)) {
|
|
267
|
-
if (
|
|
268
|
-
parsed.scheme
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
parsed.host
|
|
300
|
+
if (uri.indexOf('%') !== -1) {
|
|
301
|
+
if (parsed.scheme !== undefined) {
|
|
302
|
+
parsed.scheme = unescape(parsed.scheme)
|
|
303
|
+
}
|
|
304
|
+
if (parsed.host !== undefined) {
|
|
305
|
+
parsed.host = unescape(parsed.host)
|
|
306
|
+
}
|
|
272
307
|
}
|
|
273
308
|
if (parsed.path) {
|
|
274
|
-
parsed.path =
|
|
309
|
+
parsed.path = normalizePathEncoding(parsed.path)
|
|
275
310
|
}
|
|
276
311
|
if (parsed.fragment) {
|
|
277
312
|
parsed.fragment = encodeURI(decodeURIComponent(parsed.fragment))
|
|
@@ -292,7 +327,7 @@ const fastUri = {
|
|
|
292
327
|
SCHEMES,
|
|
293
328
|
normalize,
|
|
294
329
|
resolve,
|
|
295
|
-
|
|
330
|
+
resolveComponent,
|
|
296
331
|
equal,
|
|
297
332
|
serialize,
|
|
298
333
|
parse
|
package/lib/schemes.js
CHANGED
|
@@ -1,188 +1,267 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const
|
|
3
|
+
const { isUUID } = require('./utils')
|
|
4
4
|
const URN_REG = /([\da-z][\d\-a-z]{0,31}):((?:[\w!$'()*+,\-.:;=@]|%[\da-f]{2})+)/iu
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
const supportedSchemeNames = /** @type {const} */ (['http', 'https', 'ws',
|
|
7
|
+
'wss', 'urn', 'urn:uuid'])
|
|
8
|
+
|
|
9
|
+
/** @typedef {supportedSchemeNames[number]} SchemeName */
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {string} name
|
|
13
|
+
* @returns {name is SchemeName}
|
|
14
|
+
*/
|
|
15
|
+
function isValidSchemeName (name) {
|
|
16
|
+
return supportedSchemeNames.indexOf(/** @type {*} */ (name)) !== -1
|
|
8
17
|
}
|
|
9
18
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
19
|
+
/**
|
|
20
|
+
* @callback SchemeFn
|
|
21
|
+
* @param {import('../types/index').URIComponent} component
|
|
22
|
+
* @param {import('../types/index').Options} options
|
|
23
|
+
* @returns {import('../types/index').URIComponent}
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @typedef {Object} SchemeHandler
|
|
28
|
+
* @property {SchemeName} scheme - The scheme name.
|
|
29
|
+
* @property {boolean} [domainHost] - Indicates if the scheme supports domain hosts.
|
|
30
|
+
* @property {SchemeFn} parse - Function to parse the URI component for this scheme.
|
|
31
|
+
* @property {SchemeFn} serialize - Function to serialize the URI component for this scheme.
|
|
32
|
+
* @property {boolean} [skipNormalize] - Indicates if normalization should be skipped for this scheme.
|
|
33
|
+
* @property {boolean} [absolutePath] - Indicates if the scheme uses absolute paths.
|
|
34
|
+
* @property {boolean} [unicodeSupport] - Indicates if the scheme supports Unicode.
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @param {import('../types/index').URIComponent} wsComponent
|
|
39
|
+
* @returns {boolean}
|
|
40
|
+
*/
|
|
41
|
+
function wsIsSecure (wsComponent) {
|
|
42
|
+
if (wsComponent.secure === true) {
|
|
43
|
+
return true
|
|
44
|
+
} else if (wsComponent.secure === false) {
|
|
45
|
+
return false
|
|
46
|
+
} else if (wsComponent.scheme) {
|
|
47
|
+
return (
|
|
48
|
+
wsComponent.scheme.length === 3 &&
|
|
49
|
+
(wsComponent.scheme[0] === 'w' || wsComponent.scheme[0] === 'W') &&
|
|
50
|
+
(wsComponent.scheme[1] === 's' || wsComponent.scheme[1] === 'S') &&
|
|
51
|
+
(wsComponent.scheme[2] === 's' || wsComponent.scheme[2] === 'S')
|
|
52
|
+
)
|
|
53
|
+
} else {
|
|
54
|
+
return false
|
|
13
55
|
}
|
|
56
|
+
}
|
|
14
57
|
|
|
15
|
-
|
|
58
|
+
/** @type {SchemeFn} */
|
|
59
|
+
function httpParse (component) {
|
|
60
|
+
if (!component.host) {
|
|
61
|
+
component.error = component.error || 'HTTP URIs must have a host.'
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return component
|
|
16
65
|
}
|
|
17
66
|
|
|
18
|
-
|
|
19
|
-
|
|
67
|
+
/** @type {SchemeFn} */
|
|
68
|
+
function httpSerialize (component) {
|
|
69
|
+
const secure = String(component.scheme).toLowerCase() === 'https'
|
|
20
70
|
|
|
21
71
|
// normalize the default port
|
|
22
|
-
if (
|
|
23
|
-
|
|
72
|
+
if (component.port === (secure ? 443 : 80) || component.port === '') {
|
|
73
|
+
component.port = undefined
|
|
24
74
|
}
|
|
25
75
|
|
|
26
76
|
// normalize the empty path
|
|
27
|
-
if (!
|
|
28
|
-
|
|
77
|
+
if (!component.path) {
|
|
78
|
+
component.path = '/'
|
|
29
79
|
}
|
|
30
80
|
|
|
31
81
|
// NOTE: We do not parse query strings for HTTP URIs
|
|
32
82
|
// as WWW Form Url Encoded query strings are part of the HTML4+ spec,
|
|
33
83
|
// and not the HTTP spec.
|
|
34
84
|
|
|
35
|
-
return
|
|
85
|
+
return component
|
|
36
86
|
}
|
|
37
87
|
|
|
38
|
-
|
|
88
|
+
/** @type {SchemeFn} */
|
|
89
|
+
function wsParse (wsComponent) {
|
|
39
90
|
// indicate if the secure flag is set
|
|
40
|
-
|
|
91
|
+
wsComponent.secure = wsIsSecure(wsComponent)
|
|
41
92
|
|
|
42
93
|
// construct resouce name
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
94
|
+
wsComponent.resourceName = (wsComponent.path || '/') + (wsComponent.query ? '?' + wsComponent.query : '')
|
|
95
|
+
wsComponent.path = undefined
|
|
96
|
+
wsComponent.query = undefined
|
|
46
97
|
|
|
47
|
-
return
|
|
98
|
+
return wsComponent
|
|
48
99
|
}
|
|
49
100
|
|
|
50
|
-
|
|
101
|
+
/** @type {SchemeFn} */
|
|
102
|
+
function wsSerialize (wsComponent) {
|
|
51
103
|
// normalize the default port
|
|
52
|
-
if (
|
|
53
|
-
|
|
104
|
+
if (wsComponent.port === (wsIsSecure(wsComponent) ? 443 : 80) || wsComponent.port === '') {
|
|
105
|
+
wsComponent.port = undefined
|
|
54
106
|
}
|
|
55
107
|
|
|
56
108
|
// ensure scheme matches secure flag
|
|
57
|
-
if (typeof
|
|
58
|
-
|
|
59
|
-
|
|
109
|
+
if (typeof wsComponent.secure === 'boolean') {
|
|
110
|
+
wsComponent.scheme = (wsComponent.secure ? 'wss' : 'ws')
|
|
111
|
+
wsComponent.secure = undefined
|
|
60
112
|
}
|
|
61
113
|
|
|
62
114
|
// reconstruct path from resource name
|
|
63
|
-
if (
|
|
64
|
-
const [path, query] =
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
115
|
+
if (wsComponent.resourceName) {
|
|
116
|
+
const [path, query] = wsComponent.resourceName.split('?')
|
|
117
|
+
wsComponent.path = (path && path !== '/' ? path : undefined)
|
|
118
|
+
wsComponent.query = query
|
|
119
|
+
wsComponent.resourceName = undefined
|
|
68
120
|
}
|
|
69
121
|
|
|
70
122
|
// forbid fragment component
|
|
71
|
-
|
|
123
|
+
wsComponent.fragment = undefined
|
|
72
124
|
|
|
73
|
-
return
|
|
125
|
+
return wsComponent
|
|
74
126
|
}
|
|
75
127
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
128
|
+
/** @type {SchemeFn} */
|
|
129
|
+
function urnParse (urnComponent, options) {
|
|
130
|
+
if (!urnComponent.path) {
|
|
131
|
+
urnComponent.error = 'URN can not be parsed'
|
|
132
|
+
return urnComponent
|
|
80
133
|
}
|
|
81
|
-
const matches =
|
|
134
|
+
const matches = urnComponent.path.match(URN_REG)
|
|
82
135
|
if (matches) {
|
|
83
|
-
const scheme = options.scheme ||
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
const urnScheme = `${scheme}:${options.nid ||
|
|
87
|
-
const schemeHandler =
|
|
88
|
-
|
|
136
|
+
const scheme = options.scheme || urnComponent.scheme || 'urn'
|
|
137
|
+
urnComponent.nid = matches[1].toLowerCase()
|
|
138
|
+
urnComponent.nss = matches[2]
|
|
139
|
+
const urnScheme = `${scheme}:${options.nid || urnComponent.nid}`
|
|
140
|
+
const schemeHandler = getSchemeHandler(urnScheme)
|
|
141
|
+
urnComponent.path = undefined
|
|
89
142
|
|
|
90
143
|
if (schemeHandler) {
|
|
91
|
-
|
|
144
|
+
urnComponent = schemeHandler.parse(urnComponent, options)
|
|
92
145
|
}
|
|
93
146
|
} else {
|
|
94
|
-
|
|
147
|
+
urnComponent.error = urnComponent.error || 'URN can not be parsed.'
|
|
95
148
|
}
|
|
96
149
|
|
|
97
|
-
return
|
|
150
|
+
return urnComponent
|
|
98
151
|
}
|
|
99
152
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
153
|
+
/** @type {SchemeFn} */
|
|
154
|
+
function urnSerialize (urnComponent, options) {
|
|
155
|
+
if (urnComponent.nid === undefined) {
|
|
156
|
+
throw new Error('URN without nid cannot be serialized')
|
|
157
|
+
}
|
|
158
|
+
const scheme = options.scheme || urnComponent.scheme || 'urn'
|
|
159
|
+
const nid = urnComponent.nid.toLowerCase()
|
|
103
160
|
const urnScheme = `${scheme}:${options.nid || nid}`
|
|
104
|
-
const schemeHandler =
|
|
161
|
+
const schemeHandler = getSchemeHandler(urnScheme)
|
|
105
162
|
|
|
106
163
|
if (schemeHandler) {
|
|
107
|
-
|
|
164
|
+
urnComponent = schemeHandler.serialize(urnComponent, options)
|
|
108
165
|
}
|
|
109
166
|
|
|
110
|
-
const
|
|
111
|
-
const nss =
|
|
112
|
-
|
|
167
|
+
const uriComponent = urnComponent
|
|
168
|
+
const nss = urnComponent.nss
|
|
169
|
+
uriComponent.path = `${nid || options.nid}:${nss}`
|
|
113
170
|
|
|
114
171
|
options.skipEscape = true
|
|
115
|
-
return
|
|
172
|
+
return uriComponent
|
|
116
173
|
}
|
|
117
174
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
175
|
+
/** @type {SchemeFn} */
|
|
176
|
+
function urnuuidParse (urnComponent, options) {
|
|
177
|
+
const uuidComponent = urnComponent
|
|
178
|
+
uuidComponent.uuid = uuidComponent.nss
|
|
179
|
+
uuidComponent.nss = undefined
|
|
122
180
|
|
|
123
|
-
if (!options.tolerant && (!
|
|
124
|
-
|
|
181
|
+
if (!options.tolerant && (!uuidComponent.uuid || !isUUID(uuidComponent.uuid))) {
|
|
182
|
+
uuidComponent.error = uuidComponent.error || 'UUID is not valid.'
|
|
125
183
|
}
|
|
126
184
|
|
|
127
|
-
return
|
|
185
|
+
return uuidComponent
|
|
128
186
|
}
|
|
129
187
|
|
|
130
|
-
|
|
131
|
-
|
|
188
|
+
/** @type {SchemeFn} */
|
|
189
|
+
function urnuuidSerialize (uuidComponent) {
|
|
190
|
+
const urnComponent = uuidComponent
|
|
132
191
|
// normalize UUID
|
|
133
|
-
|
|
134
|
-
return
|
|
192
|
+
urnComponent.nss = (uuidComponent.uuid || '').toLowerCase()
|
|
193
|
+
return urnComponent
|
|
135
194
|
}
|
|
136
195
|
|
|
137
|
-
const http = {
|
|
196
|
+
const http = /** @type {SchemeHandler} */ ({
|
|
138
197
|
scheme: 'http',
|
|
139
198
|
domainHost: true,
|
|
140
199
|
parse: httpParse,
|
|
141
200
|
serialize: httpSerialize
|
|
142
|
-
}
|
|
201
|
+
})
|
|
143
202
|
|
|
144
|
-
const https = {
|
|
203
|
+
const https = /** @type {SchemeHandler} */ ({
|
|
145
204
|
scheme: 'https',
|
|
146
205
|
domainHost: http.domainHost,
|
|
147
206
|
parse: httpParse,
|
|
148
207
|
serialize: httpSerialize
|
|
149
|
-
}
|
|
208
|
+
})
|
|
150
209
|
|
|
151
|
-
const ws = {
|
|
210
|
+
const ws = /** @type {SchemeHandler} */ ({
|
|
152
211
|
scheme: 'ws',
|
|
153
212
|
domainHost: true,
|
|
154
213
|
parse: wsParse,
|
|
155
214
|
serialize: wsSerialize
|
|
156
|
-
}
|
|
215
|
+
})
|
|
157
216
|
|
|
158
|
-
const wss = {
|
|
217
|
+
const wss = /** @type {SchemeHandler} */ ({
|
|
159
218
|
scheme: 'wss',
|
|
160
219
|
domainHost: ws.domainHost,
|
|
161
220
|
parse: ws.parse,
|
|
162
221
|
serialize: ws.serialize
|
|
163
|
-
}
|
|
222
|
+
})
|
|
164
223
|
|
|
165
|
-
const urn = {
|
|
224
|
+
const urn = /** @type {SchemeHandler} */ ({
|
|
166
225
|
scheme: 'urn',
|
|
167
226
|
parse: urnParse,
|
|
168
227
|
serialize: urnSerialize,
|
|
169
228
|
skipNormalize: true
|
|
170
|
-
}
|
|
229
|
+
})
|
|
171
230
|
|
|
172
|
-
const urnuuid = {
|
|
231
|
+
const urnuuid = /** @type {SchemeHandler} */ ({
|
|
173
232
|
scheme: 'urn:uuid',
|
|
174
233
|
parse: urnuuidParse,
|
|
175
234
|
serialize: urnuuidSerialize,
|
|
176
235
|
skipNormalize: true
|
|
177
|
-
}
|
|
236
|
+
})
|
|
178
237
|
|
|
179
|
-
const SCHEMES = {
|
|
238
|
+
const SCHEMES = /** @type {Record<SchemeName, SchemeHandler>} */ ({
|
|
180
239
|
http,
|
|
181
240
|
https,
|
|
182
241
|
ws,
|
|
183
242
|
wss,
|
|
184
243
|
urn,
|
|
185
244
|
'urn:uuid': urnuuid
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
Object.setPrototypeOf(SCHEMES, null)
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* @param {string|undefined} scheme
|
|
251
|
+
* @returns {SchemeHandler|undefined}
|
|
252
|
+
*/
|
|
253
|
+
function getSchemeHandler (scheme) {
|
|
254
|
+
return (
|
|
255
|
+
scheme && (
|
|
256
|
+
SCHEMES[/** @type {SchemeName} */ (scheme)] ||
|
|
257
|
+
SCHEMES[/** @type {SchemeName} */(scheme.toLowerCase())])
|
|
258
|
+
) ||
|
|
259
|
+
undefined
|
|
186
260
|
}
|
|
187
261
|
|
|
188
|
-
module.exports =
|
|
262
|
+
module.exports = {
|
|
263
|
+
wsIsSecure,
|
|
264
|
+
SCHEMES,
|
|
265
|
+
isValidSchemeName,
|
|
266
|
+
getSchemeHandler,
|
|
267
|
+
}
|