fast-uri 2.4.4 → 2.4.6
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/index.js +216 -62
- package/lib/schemes.js +9 -4
- package/lib/utils.js +432 -117
- package/package.json +2 -1
- package/test/compatibility.test.js +12 -0
- package/test/component-safe-serialization.test.js +163 -0
- package/test/equal.test.js +32 -0
- package/test/ipv6-validation.test.js +40 -0
- package/test/malformed-percent.test.js +77 -0
- package/test/malformed-urn.test.js +61 -0
- package/test/parse.test.js +3 -3
- package/test/query-fragment-normalization.test.js +33 -0
- package/test/reserved-path-normalization.test.js +109 -0
- package/test/scheme-validation.test.js +124 -0
- package/test/security-normalization.test.js +102 -2
- package/test/security.test.js +78 -7
- package/test/urn-full-input.test.js +29 -0
- package/test/websocket-query-preservation.test.js +24 -0
package/index.js
CHANGED
|
@@ -1,8 +1,23 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
-
const { normalizeIPv6,
|
|
3
|
+
const { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, serializePathEncoding, normalizeQueryFragmentEncoding, encodeQuery, encodeFragment, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require('./lib/utils')
|
|
4
4
|
const SCHEMES = require('./lib/schemes')
|
|
5
5
|
|
|
6
|
+
const VALID_SCHEME = /^[A-Za-z][A-Za-z0-9+.-]*$/u
|
|
7
|
+
const MALFORMED_SCHEME_ERROR = 'URI scheme is malformed.'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param {string} scheme
|
|
11
|
+
* @returns {string}
|
|
12
|
+
*/
|
|
13
|
+
function decodeValidScheme (scheme) {
|
|
14
|
+
const decodedScheme = unescape(String(scheme))
|
|
15
|
+
if (!VALID_SCHEME.test(decodedScheme)) {
|
|
16
|
+
throw new TypeError(MALFORMED_SCHEME_ERROR)
|
|
17
|
+
}
|
|
18
|
+
return decodedScheme
|
|
19
|
+
}
|
|
20
|
+
|
|
6
21
|
function normalize (uri, options) {
|
|
7
22
|
if (typeof uri === 'string') {
|
|
8
23
|
uri = normalizeString(uri, options)
|
|
@@ -14,12 +29,50 @@ function normalize (uri, options) {
|
|
|
14
29
|
|
|
15
30
|
function resolve (baseURI, relativeURI, options) {
|
|
16
31
|
const schemelessOptions = Object.assign({ scheme: 'null' }, options)
|
|
17
|
-
const {
|
|
18
|
-
|
|
19
|
-
|
|
32
|
+
const {
|
|
33
|
+
parsed: baseParsed,
|
|
34
|
+
malformedAuthorityOrPort: baseMalformed,
|
|
35
|
+
malformedPercentEncoding: baseMalformedPercentEncoding,
|
|
36
|
+
malformedSchemeSpecific: baseMalformedSchemeSpecific,
|
|
37
|
+
malformedHost: baseMalformedHost,
|
|
38
|
+
malformedScheme: baseMalformedScheme
|
|
39
|
+
} = parseWithStatus(baseURI, schemelessOptions)
|
|
40
|
+
const {
|
|
41
|
+
parsed: relativeParsed,
|
|
42
|
+
malformedAuthorityOrPort: relativeMalformed,
|
|
43
|
+
malformedPercentEncoding: relativeMalformedPercentEncoding,
|
|
44
|
+
malformedSchemeSpecific: relativeMalformedSchemeSpecific,
|
|
45
|
+
malformedHost: relativeMalformedHost,
|
|
46
|
+
malformedScheme: relativeMalformedScheme
|
|
47
|
+
} = parseWithStatus(relativeURI, schemelessOptions)
|
|
48
|
+
if (
|
|
49
|
+
baseMalformed ||
|
|
50
|
+
relativeMalformed ||
|
|
51
|
+
baseMalformedPercentEncoding ||
|
|
52
|
+
relativeMalformedPercentEncoding ||
|
|
53
|
+
baseMalformedSchemeSpecific ||
|
|
54
|
+
relativeMalformedSchemeSpecific ||
|
|
55
|
+
baseMalformedHost ||
|
|
56
|
+
relativeMalformedHost ||
|
|
57
|
+
baseMalformedScheme ||
|
|
58
|
+
relativeMalformedScheme
|
|
59
|
+
) {
|
|
20
60
|
throw new Error(baseParsed.error || relativeParsed.error || 'URI is malformed.')
|
|
21
61
|
}
|
|
22
62
|
const resolved = resolveComponents(baseParsed, relativeParsed, schemelessOptions, true)
|
|
63
|
+
const resolvedSchemeHandler = SCHEMES[((options && options.scheme) || resolved.scheme || '').toLowerCase()]
|
|
64
|
+
const resolvedHost = resolved.host
|
|
65
|
+
const resolvedHostIsIP = resolvedHost !== undefined && resolvedHost !== '' &&
|
|
66
|
+
(isIPv4(resolvedHost) || normalizeIPv6(resolvedHost).isIPV6)
|
|
67
|
+
canonicalizeHost(resolved, options || {}, resolvedSchemeHandler, resolvedHostIsIP)
|
|
68
|
+
// Percent escapes in an ASCII reg-name are encoded data. The WHATWG hostname
|
|
69
|
+
// parser can reject them even though fast-uri preserves them safely as RFC
|
|
70
|
+
// 3986 data. A raw non-ASCII host must still fail closed if conversion fails.
|
|
71
|
+
const encodedASCIIHost = resolvedHost && resolvedHost.indexOf('%') !== -1 &&
|
|
72
|
+
!/\P{ASCII}/u.test(resolvedHost)
|
|
73
|
+
if (resolved.error && !encodedASCIIHost) {
|
|
74
|
+
throw new Error(resolved.error)
|
|
75
|
+
}
|
|
23
76
|
return serialize(resolved, { ...schemelessOptions, skipEscape: true })
|
|
24
77
|
}
|
|
25
78
|
|
|
@@ -87,7 +140,7 @@ function equal (uriA, uriB, options) {
|
|
|
87
140
|
const normalizedA = normalizeComparableURI(uriA, options)
|
|
88
141
|
const normalizedB = normalizeComparableURI(uriB, options)
|
|
89
142
|
|
|
90
|
-
return normalizedA !== undefined && normalizedB !== undefined && normalizedA
|
|
143
|
+
return normalizedA !== undefined && normalizedB !== undefined && normalizedA === normalizedB
|
|
91
144
|
}
|
|
92
145
|
|
|
93
146
|
function serialize (cmpts, opts) {
|
|
@@ -110,25 +163,30 @@ function serialize (cmpts, opts) {
|
|
|
110
163
|
const options = Object.assign({}, opts)
|
|
111
164
|
const uriTokens = []
|
|
112
165
|
|
|
166
|
+
if (components.scheme) {
|
|
167
|
+
components.scheme = decodeValidScheme(components.scheme)
|
|
168
|
+
}
|
|
169
|
+
|
|
113
170
|
// find scheme handler
|
|
114
171
|
const schemeHandler = SCHEMES[(options.scheme || components.scheme || '').toLowerCase()]
|
|
115
172
|
|
|
116
173
|
// perform scheme specific serialization
|
|
117
174
|
if (schemeHandler && schemeHandler.serialize) schemeHandler.serialize(components, options)
|
|
118
175
|
|
|
176
|
+
const hasAuthority = components.userinfo !== undefined || components.host !== undefined || components.port !== undefined
|
|
177
|
+
const pathNoScheme = !options.skipEscape && components.scheme === undefined && !hasAuthority
|
|
178
|
+
|
|
119
179
|
if (components.path !== undefined) {
|
|
120
180
|
if (!options.skipEscape) {
|
|
121
|
-
components.path =
|
|
122
|
-
|
|
123
|
-
if (components.scheme !== undefined) {
|
|
124
|
-
components.path = components.path.split('%3A').join(':')
|
|
125
|
-
}
|
|
181
|
+
components.path = serializePathEncoding(components.path, pathNoScheme)
|
|
126
182
|
} else {
|
|
127
183
|
components.path = normalizePercentEncoding(components.path)
|
|
128
184
|
}
|
|
129
185
|
}
|
|
130
186
|
|
|
131
187
|
if (options.reference !== 'suffix' && components.scheme) {
|
|
188
|
+
// Scheme handlers may replace the scheme during serialization.
|
|
189
|
+
components.scheme = decodeValidScheme(components.scheme)
|
|
132
190
|
uriTokens.push(components.scheme)
|
|
133
191
|
uriTokens.push(':')
|
|
134
192
|
}
|
|
@@ -152,6 +210,13 @@ function serialize (cmpts, opts) {
|
|
|
152
210
|
s = removeDotSegments(s)
|
|
153
211
|
}
|
|
154
212
|
|
|
213
|
+
// Dot-segment removal can expose a colon that was not originally in the
|
|
214
|
+
// first segment (for example, "./a:b"). Reapply path-noscheme encoding so
|
|
215
|
+
// the serialized relative reference cannot be reparsed as a URI scheme.
|
|
216
|
+
if (pathNoScheme) {
|
|
217
|
+
s = serializePathEncoding(s, true)
|
|
218
|
+
}
|
|
219
|
+
|
|
155
220
|
if (authority === undefined) {
|
|
156
221
|
s = s.replace(/^\/\//u, '/%2F') // don't allow the path to start with "//"
|
|
157
222
|
}
|
|
@@ -161,29 +226,16 @@ function serialize (cmpts, opts) {
|
|
|
161
226
|
|
|
162
227
|
if (components.query !== undefined) {
|
|
163
228
|
uriTokens.push('?')
|
|
164
|
-
uriTokens.push(components.query)
|
|
229
|
+
uriTokens.push(encodeQuery(components.query))
|
|
165
230
|
}
|
|
166
231
|
|
|
167
232
|
if (components.fragment !== undefined) {
|
|
168
233
|
uriTokens.push('#')
|
|
169
|
-
uriTokens.push(components.fragment)
|
|
234
|
+
uriTokens.push(encodeFragment(components.fragment))
|
|
170
235
|
}
|
|
171
236
|
return uriTokens.join('')
|
|
172
237
|
}
|
|
173
238
|
|
|
174
|
-
const hexLookUp = Array.from({ length: 127 }, (v, k) => /[^!"$&'()*+,\-.;=_`a-z{}~]/u.test(String.fromCharCode(k)))
|
|
175
|
-
|
|
176
|
-
function nonSimpleDomain (value) {
|
|
177
|
-
let code = 0
|
|
178
|
-
for (let i = 0, len = value.length; i < len; ++i) {
|
|
179
|
-
code = value.charCodeAt(i)
|
|
180
|
-
if (code > 126 || hexLookUp[code]) {
|
|
181
|
-
return true
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
return false
|
|
185
|
-
}
|
|
186
|
-
|
|
187
239
|
const URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:?]*)(?::(\d*))?))?([^#?]*)(?:\?([^#]*))?(?:#((?:.|[\n\r])*))?/u
|
|
188
240
|
|
|
189
241
|
// Captures the authority component (between "//" and the next "/", "?" or "#"),
|
|
@@ -211,6 +263,82 @@ function getParseError (parsed, matches) {
|
|
|
211
263
|
return undefined
|
|
212
264
|
}
|
|
213
265
|
|
|
266
|
+
/**
|
|
267
|
+
* Checks percent syntax without decoding the represented octets. RFC 3986
|
|
268
|
+
* percent-encoding is byte-oriented, so sequences such as `%FF` are valid even
|
|
269
|
+
* though they are not independently valid UTF-8.
|
|
270
|
+
*
|
|
271
|
+
* @param {string|undefined} component
|
|
272
|
+
* @returns {boolean}
|
|
273
|
+
*/
|
|
274
|
+
/**
|
|
275
|
+
* @param {import('./types/index').URIComponent} parsed
|
|
276
|
+
* @param {import('./types/index').Options} options
|
|
277
|
+
* @param {{ domainHost?: boolean, unicodeSupport?: boolean }|undefined} schemeHandler
|
|
278
|
+
* @param {boolean} isIP
|
|
279
|
+
* @returns {boolean} whether host conversion failed
|
|
280
|
+
*/
|
|
281
|
+
function canonicalizeHost (parsed, options, schemeHandler, isIP) {
|
|
282
|
+
if (
|
|
283
|
+
!options.unicodeSupport &&
|
|
284
|
+
(!schemeHandler || !schemeHandler.unicodeSupport) &&
|
|
285
|
+
parsed.host &&
|
|
286
|
+
!isIPLiteral(parsed.host) &&
|
|
287
|
+
(options.domainHost || (schemeHandler && schemeHandler.domainHost)) &&
|
|
288
|
+
isIP === false &&
|
|
289
|
+
nonSimpleDomain(parsed.host)
|
|
290
|
+
) {
|
|
291
|
+
try {
|
|
292
|
+
parsed.host = new URL('http://' + parsed.host).hostname
|
|
293
|
+
} catch (e) {
|
|
294
|
+
parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e
|
|
295
|
+
return true
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
return false
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function hasMalformedPercentEncoding (component) {
|
|
302
|
+
if (component === undefined) return false
|
|
303
|
+
|
|
304
|
+
let percent = component.indexOf('%')
|
|
305
|
+
while (percent !== -1) {
|
|
306
|
+
if (percent + 2 >= component.length || !/^[\da-f]{2}$/iu.test(component.slice(percent + 1, percent + 3))) {
|
|
307
|
+
return true
|
|
308
|
+
}
|
|
309
|
+
percent = component.indexOf('%', percent + 3)
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return false
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Whether the host is a bracketed IP literal (RFC 3986 `IP-literal`).
|
|
317
|
+
* An unterminated `[` is not a literal, so it must still be validated as a
|
|
318
|
+
* reg-name instead of being waved through as an IP.
|
|
319
|
+
*
|
|
320
|
+
* @param {string} host
|
|
321
|
+
* @returns {boolean}
|
|
322
|
+
*/
|
|
323
|
+
function isIPLiteral (host) {
|
|
324
|
+
return host[0] === '[' && host[host.length - 1] === ']'
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* @param {RegExpMatchArray} matches
|
|
329
|
+
* @returns {boolean}
|
|
330
|
+
*/
|
|
331
|
+
function hasMalformedComponentPercentEncoding (matches) {
|
|
332
|
+
// Bracketed IP literals use a raw "%" as the zone separator for historical
|
|
333
|
+
// compatibility. Their parsing is intentionally left to normalizeIPv6.
|
|
334
|
+
const host = matches[4]
|
|
335
|
+
return hasMalformedPercentEncoding(matches[3]) ||
|
|
336
|
+
(host !== undefined && !isIPLiteral(host) && hasMalformedPercentEncoding(host)) ||
|
|
337
|
+
hasMalformedPercentEncoding(matches[6]) ||
|
|
338
|
+
hasMalformedPercentEncoding(matches[7]) ||
|
|
339
|
+
hasMalformedPercentEncoding(matches[8])
|
|
340
|
+
}
|
|
341
|
+
|
|
214
342
|
function parseWithStatus (uri, opts) {
|
|
215
343
|
const options = Object.assign({}, opts)
|
|
216
344
|
const parsed = {
|
|
@@ -222,8 +350,12 @@ function parseWithStatus (uri, opts) {
|
|
|
222
350
|
query: undefined,
|
|
223
351
|
fragment: undefined
|
|
224
352
|
}
|
|
225
|
-
const gotEncoding = uri.indexOf('%') !== -1
|
|
226
353
|
let malformedAuthorityOrPort = false
|
|
354
|
+
let malformedPercentEncoding = false
|
|
355
|
+
let malformedSchemeSpecific = false
|
|
356
|
+
let malformedHost = false
|
|
357
|
+
let malformedIPLiteral = false
|
|
358
|
+
let malformedScheme = false
|
|
227
359
|
let isIP = false
|
|
228
360
|
if (options.reference === 'suffix') uri = (options.scheme ? options.scheme + ':' : '') + '//' + uri
|
|
229
361
|
|
|
@@ -274,6 +406,21 @@ function parseWithStatus (uri, opts) {
|
|
|
274
406
|
parsed.query = matches[7]
|
|
275
407
|
parsed.fragment = matches[8]
|
|
276
408
|
|
|
409
|
+
if (parsed.scheme !== undefined) {
|
|
410
|
+
const decodedScheme = unescape(parsed.scheme)
|
|
411
|
+
if (VALID_SCHEME.test(decodedScheme)) {
|
|
412
|
+
parsed.scheme = decodedScheme.toLowerCase()
|
|
413
|
+
} else {
|
|
414
|
+
parsed.error = parsed.error || MALFORMED_SCHEME_ERROR
|
|
415
|
+
malformedScheme = true
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
malformedPercentEncoding = hasMalformedComponentPercentEncoding(matches)
|
|
420
|
+
if (malformedPercentEncoding) {
|
|
421
|
+
parsed.error = parsed.error || 'URI contains malformed percent-encoding.'
|
|
422
|
+
}
|
|
423
|
+
|
|
277
424
|
// fix port number
|
|
278
425
|
if (isNaN(parsed.port)) {
|
|
279
426
|
parsed.port = matches[5]
|
|
@@ -286,13 +433,20 @@ function parseWithStatus (uri, opts) {
|
|
|
286
433
|
}
|
|
287
434
|
|
|
288
435
|
if (parsed.host) {
|
|
289
|
-
const ipv4result =
|
|
290
|
-
if (ipv4result
|
|
291
|
-
const
|
|
292
|
-
parsed.host
|
|
293
|
-
|
|
436
|
+
const ipv4result = isIPv4(parsed.host)
|
|
437
|
+
if (ipv4result === false) {
|
|
438
|
+
const bracketedIPLiteral = isIPLiteral(parsed.host)
|
|
439
|
+
const hasIPLiteralBracket = parsed.host.indexOf('[') !== -1 || parsed.host.indexOf(']') !== -1
|
|
440
|
+
const ipv6result = normalizeIPv6(parsed.host)
|
|
441
|
+
isIP = ipv6result.isIPV6 || ipv6result.isIPVFuture === true
|
|
442
|
+
malformedIPLiteral = hasIPLiteralBracket && (!bracketedIPLiteral || ipv6result.error === true)
|
|
443
|
+
parsed.host = isIP ? ipv6result.host : ipv6result.host.toLowerCase()
|
|
444
|
+
|
|
445
|
+
if (malformedIPLiteral) {
|
|
446
|
+
parsed.error = parsed.error || 'URI host is malformed.'
|
|
447
|
+
malformedAuthorityOrPort = true
|
|
448
|
+
}
|
|
294
449
|
} else {
|
|
295
|
-
parsed.host = ipv4result.host
|
|
296
450
|
isIP = true
|
|
297
451
|
}
|
|
298
452
|
}
|
|
@@ -314,46 +468,38 @@ function parseWithStatus (uri, opts) {
|
|
|
314
468
|
// find scheme handler
|
|
315
469
|
const schemeHandler = SCHEMES[(options.scheme || parsed.scheme || '').toLowerCase()]
|
|
316
470
|
|
|
317
|
-
//
|
|
318
|
-
if (!
|
|
319
|
-
|
|
320
|
-
if (parsed.host && (options.domainHost || (schemeHandler && schemeHandler.domainHost)) && isIP === false && nonSimpleDomain(parsed.host)) {
|
|
321
|
-
// convert Unicode IDN -> ASCII IDN
|
|
322
|
-
try {
|
|
323
|
-
parsed.host = new URL('http://' + parsed.host).hostname
|
|
324
|
-
} catch (e) {
|
|
325
|
-
parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
// convert IRI -> URI
|
|
471
|
+
// convert Unicode IDN -> ASCII IDN when the effective scheme uses domain hosts
|
|
472
|
+
if (!malformedIPLiteral) {
|
|
473
|
+
malformedHost = canonicalizeHost(parsed, options, schemeHandler, isIP)
|
|
329
474
|
}
|
|
330
475
|
|
|
331
476
|
if (!schemeHandler || (schemeHandler && !schemeHandler.skipNormalize)) {
|
|
332
|
-
if (
|
|
333
|
-
parsed.
|
|
334
|
-
|
|
335
|
-
if (gotEncoding && parsed.userinfo !== undefined) {
|
|
336
|
-
parsed.userinfo = unescape(parsed.userinfo)
|
|
337
|
-
}
|
|
338
|
-
if (gotEncoding && parsed.host !== undefined) {
|
|
339
|
-
parsed.host = reescapeHostDelimiters(unescape(parsed.host), isIP)
|
|
477
|
+
if (parsed.host !== undefined && !malformedIPLiteral) {
|
|
478
|
+
const host = isIP ? parsed.host : normalizePercentEncoding(parsed.host, true)
|
|
479
|
+
parsed.host = reescapeHostDelimiters(host, isIP)
|
|
340
480
|
}
|
|
341
481
|
if (parsed.path !== undefined && parsed.path.length) {
|
|
342
482
|
parsed.path = normalizePathEncoding(parsed.path)
|
|
343
483
|
}
|
|
484
|
+
if (parsed.query !== undefined && parsed.query.length) {
|
|
485
|
+
parsed.query = normalizeQueryFragmentEncoding(parsed.query)
|
|
486
|
+
}
|
|
344
487
|
if (parsed.fragment !== undefined && parsed.fragment.length) {
|
|
345
|
-
parsed.fragment =
|
|
488
|
+
parsed.fragment = normalizeQueryFragmentEncoding(parsed.fragment)
|
|
346
489
|
}
|
|
347
490
|
}
|
|
348
491
|
|
|
349
492
|
// perform scheme specific parsing
|
|
350
493
|
if (schemeHandler && schemeHandler.parse) {
|
|
351
494
|
schemeHandler.parse(parsed, options)
|
|
495
|
+
if (schemeHandler === SCHEMES.urn && parsed.nid === undefined) {
|
|
496
|
+
malformedSchemeSpecific = true
|
|
497
|
+
}
|
|
352
498
|
}
|
|
353
499
|
} else {
|
|
354
500
|
parsed.error = parsed.error || 'URI can not be parsed.'
|
|
355
501
|
}
|
|
356
|
-
return { parsed, malformedAuthorityOrPort }
|
|
502
|
+
return { parsed, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme }
|
|
357
503
|
}
|
|
358
504
|
|
|
359
505
|
function parse (uri, opts) {
|
|
@@ -365,22 +511,30 @@ function normalizeString (uri, opts) {
|
|
|
365
511
|
}
|
|
366
512
|
|
|
367
513
|
function normalizeStringWithStatus (uri, opts) {
|
|
368
|
-
const { parsed, malformedAuthorityOrPort } = parseWithStatus(uri, opts)
|
|
514
|
+
const { parsed, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme } = parseWithStatus(uri, opts)
|
|
369
515
|
return {
|
|
370
|
-
normalized: malformedAuthorityOrPort ? uri : serialize(parsed, opts),
|
|
371
|
-
malformedAuthorityOrPort
|
|
516
|
+
normalized: malformedAuthorityOrPort || malformedPercentEncoding || malformedSchemeSpecific || malformedHost || malformedScheme ? uri : serialize(parsed, opts),
|
|
517
|
+
malformedAuthorityOrPort,
|
|
518
|
+
malformedPercentEncoding,
|
|
519
|
+
malformedSchemeSpecific,
|
|
520
|
+
malformedHost,
|
|
521
|
+
malformedScheme
|
|
372
522
|
}
|
|
373
523
|
}
|
|
374
524
|
|
|
375
525
|
function normalizeComparableURI (uri, opts) {
|
|
376
|
-
if (typeof uri
|
|
377
|
-
|
|
378
|
-
return malformedAuthorityOrPort ? undefined : normalized
|
|
526
|
+
if (typeof uri !== 'string' && typeof uri !== 'object') {
|
|
527
|
+
return undefined
|
|
379
528
|
}
|
|
380
529
|
|
|
381
|
-
|
|
382
|
-
|
|
530
|
+
let value
|
|
531
|
+
try {
|
|
532
|
+
value = typeof uri === 'string' ? uri : serialize(uri, opts)
|
|
533
|
+
} catch {
|
|
534
|
+
return undefined
|
|
383
535
|
}
|
|
536
|
+
const { normalized, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme } = normalizeStringWithStatus(value, opts)
|
|
537
|
+
return malformedAuthorityOrPort || malformedPercentEncoding || malformedSchemeSpecific || malformedHost || malformedScheme ? undefined : normalized
|
|
384
538
|
}
|
|
385
539
|
|
|
386
540
|
const fastUri = {
|
package/lib/schemes.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
3
|
const UUID_REG = /^[\da-f]{8}\b-[\da-f]{4}\b-[\da-f]{4}\b-[\da-f]{4}\b-[\da-f]{12}$/iu
|
|
4
|
-
const URN_REG =
|
|
4
|
+
const URN_REG = /^([\da-z][\d\-a-z]{0,31}):((?:[\w!$'()*+,\-./:;=@]|%[\da-f]{2})+)$/iu
|
|
5
5
|
|
|
6
6
|
function isSecure (wsComponents) {
|
|
7
7
|
return typeof wsComponents.secure === 'boolean' ? wsComponents.secure : String(wsComponents.scheme).toLowerCase() === 'wss'
|
|
@@ -61,9 +61,14 @@ function wsSerialize (wsComponents) {
|
|
|
61
61
|
|
|
62
62
|
// reconstruct path from resource name
|
|
63
63
|
if (wsComponents.resourceName) {
|
|
64
|
-
const
|
|
64
|
+
const queryIndex = wsComponents.resourceName.indexOf('?')
|
|
65
|
+
const path = queryIndex === -1
|
|
66
|
+
? wsComponents.resourceName
|
|
67
|
+
: wsComponents.resourceName.slice(0, queryIndex)
|
|
65
68
|
wsComponents.path = (path && path !== '/' ? path : undefined)
|
|
66
|
-
wsComponents.query =
|
|
69
|
+
wsComponents.query = queryIndex === -1
|
|
70
|
+
? undefined
|
|
71
|
+
: wsComponents.resourceName.slice(queryIndex + 1)
|
|
67
72
|
wsComponents.resourceName = undefined
|
|
68
73
|
}
|
|
69
74
|
|
|
@@ -79,7 +84,7 @@ function urnParse (urnComponents, options) {
|
|
|
79
84
|
return urnComponents
|
|
80
85
|
}
|
|
81
86
|
const matches = urnComponents.path.match(URN_REG)
|
|
82
|
-
if (matches) {
|
|
87
|
+
if (matches && matches[0] === urnComponents.path) {
|
|
83
88
|
const scheme = options.scheme || urnComponents.scheme || 'urn'
|
|
84
89
|
urnComponents.nid = matches[1].toLowerCase()
|
|
85
90
|
urnComponents.nss = matches[2]
|