fast-uri 2.4.4 → 2.4.5
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 +202 -63
- package/lib/schemes.js +9 -4
- package/lib/utils.js +424 -116
- package/package.json +2 -1
- package/test/compatibility.test.js +12 -0
- package/test/component-safe-serialization.test.js +105 -0
- package/test/equal.test.js +32 -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,70 @@ 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
|
+
parsed.host[0] !== '[' &&
|
|
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
|
+
* @param {RegExpMatchArray} matches
|
|
317
|
+
* @returns {boolean}
|
|
318
|
+
*/
|
|
319
|
+
function hasMalformedComponentPercentEncoding (matches) {
|
|
320
|
+
// Bracketed IP literals use a raw "%" as the zone separator for historical
|
|
321
|
+
// compatibility. Their parsing is intentionally left to normalizeIPv6.
|
|
322
|
+
const host = matches[4]
|
|
323
|
+
return hasMalformedPercentEncoding(matches[3]) ||
|
|
324
|
+
(host !== undefined && !(host[0] === '[' && host[host.length - 1] === ']') && hasMalformedPercentEncoding(host)) ||
|
|
325
|
+
hasMalformedPercentEncoding(matches[6]) ||
|
|
326
|
+
hasMalformedPercentEncoding(matches[7]) ||
|
|
327
|
+
hasMalformedPercentEncoding(matches[8])
|
|
328
|
+
}
|
|
329
|
+
|
|
214
330
|
function parseWithStatus (uri, opts) {
|
|
215
331
|
const options = Object.assign({}, opts)
|
|
216
332
|
const parsed = {
|
|
@@ -222,8 +338,12 @@ function parseWithStatus (uri, opts) {
|
|
|
222
338
|
query: undefined,
|
|
223
339
|
fragment: undefined
|
|
224
340
|
}
|
|
225
|
-
const gotEncoding = uri.indexOf('%') !== -1
|
|
226
341
|
let malformedAuthorityOrPort = false
|
|
342
|
+
let malformedPercentEncoding = false
|
|
343
|
+
let malformedSchemeSpecific = false
|
|
344
|
+
let malformedHost = false
|
|
345
|
+
let malformedIPLiteral = false
|
|
346
|
+
let malformedScheme = false
|
|
227
347
|
let isIP = false
|
|
228
348
|
if (options.reference === 'suffix') uri = (options.scheme ? options.scheme + ':' : '') + '//' + uri
|
|
229
349
|
|
|
@@ -274,6 +394,21 @@ function parseWithStatus (uri, opts) {
|
|
|
274
394
|
parsed.query = matches[7]
|
|
275
395
|
parsed.fragment = matches[8]
|
|
276
396
|
|
|
397
|
+
if (parsed.scheme !== undefined) {
|
|
398
|
+
const decodedScheme = unescape(parsed.scheme)
|
|
399
|
+
if (VALID_SCHEME.test(decodedScheme)) {
|
|
400
|
+
parsed.scheme = decodedScheme.toLowerCase()
|
|
401
|
+
} else {
|
|
402
|
+
parsed.error = parsed.error || MALFORMED_SCHEME_ERROR
|
|
403
|
+
malformedScheme = true
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
malformedPercentEncoding = hasMalformedComponentPercentEncoding(matches)
|
|
408
|
+
if (malformedPercentEncoding) {
|
|
409
|
+
parsed.error = parsed.error || 'URI contains malformed percent-encoding.'
|
|
410
|
+
}
|
|
411
|
+
|
|
277
412
|
// fix port number
|
|
278
413
|
if (isNaN(parsed.port)) {
|
|
279
414
|
parsed.port = matches[5]
|
|
@@ -286,13 +421,19 @@ function parseWithStatus (uri, opts) {
|
|
|
286
421
|
}
|
|
287
422
|
|
|
288
423
|
if (parsed.host) {
|
|
289
|
-
const ipv4result =
|
|
290
|
-
if (ipv4result
|
|
291
|
-
const
|
|
292
|
-
|
|
293
|
-
isIP = ipv6result.isIPV6
|
|
424
|
+
const ipv4result = isIPv4(parsed.host)
|
|
425
|
+
if (ipv4result === false) {
|
|
426
|
+
const bracketedIPLiteral = parsed.host[0] === '[' && parsed.host[parsed.host.length - 1] === ']'
|
|
427
|
+
const ipv6result = normalizeIPv6(parsed.host)
|
|
428
|
+
isIP = ipv6result.isIPV6 || ipv6result.isIPVFuture === true
|
|
429
|
+
malformedIPLiteral = bracketedIPLiteral && ipv6result.error === true
|
|
430
|
+
parsed.host = isIP ? ipv6result.host : ipv6result.host.toLowerCase()
|
|
431
|
+
|
|
432
|
+
if (malformedIPLiteral) {
|
|
433
|
+
parsed.error = parsed.error || 'URI host is malformed.'
|
|
434
|
+
malformedAuthorityOrPort = true
|
|
435
|
+
}
|
|
294
436
|
} else {
|
|
295
|
-
parsed.host = ipv4result.host
|
|
296
437
|
isIP = true
|
|
297
438
|
}
|
|
298
439
|
}
|
|
@@ -314,46 +455,36 @@ function parseWithStatus (uri, opts) {
|
|
|
314
455
|
// find scheme handler
|
|
315
456
|
const schemeHandler = SCHEMES[(options.scheme || parsed.scheme || '').toLowerCase()]
|
|
316
457
|
|
|
317
|
-
//
|
|
318
|
-
|
|
319
|
-
// if host component is a domain name
|
|
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
|
|
329
|
-
}
|
|
458
|
+
// convert Unicode IDN -> ASCII IDN when the effective scheme uses domain hosts
|
|
459
|
+
malformedHost = canonicalizeHost(parsed, options, schemeHandler, isIP)
|
|
330
460
|
|
|
331
461
|
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)
|
|
462
|
+
if (parsed.host !== undefined && !malformedIPLiteral) {
|
|
463
|
+
const host = isIP ? parsed.host : normalizePercentEncoding(parsed.host, true)
|
|
464
|
+
parsed.host = reescapeHostDelimiters(host, isIP)
|
|
340
465
|
}
|
|
341
466
|
if (parsed.path !== undefined && parsed.path.length) {
|
|
342
467
|
parsed.path = normalizePathEncoding(parsed.path)
|
|
343
468
|
}
|
|
469
|
+
if (parsed.query !== undefined && parsed.query.length) {
|
|
470
|
+
parsed.query = normalizeQueryFragmentEncoding(parsed.query)
|
|
471
|
+
}
|
|
344
472
|
if (parsed.fragment !== undefined && parsed.fragment.length) {
|
|
345
|
-
parsed.fragment =
|
|
473
|
+
parsed.fragment = normalizeQueryFragmentEncoding(parsed.fragment)
|
|
346
474
|
}
|
|
347
475
|
}
|
|
348
476
|
|
|
349
477
|
// perform scheme specific parsing
|
|
350
478
|
if (schemeHandler && schemeHandler.parse) {
|
|
351
479
|
schemeHandler.parse(parsed, options)
|
|
480
|
+
if (schemeHandler === SCHEMES.urn && parsed.nid === undefined) {
|
|
481
|
+
malformedSchemeSpecific = true
|
|
482
|
+
}
|
|
352
483
|
}
|
|
353
484
|
} else {
|
|
354
485
|
parsed.error = parsed.error || 'URI can not be parsed.'
|
|
355
486
|
}
|
|
356
|
-
return { parsed, malformedAuthorityOrPort }
|
|
487
|
+
return { parsed, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme }
|
|
357
488
|
}
|
|
358
489
|
|
|
359
490
|
function parse (uri, opts) {
|
|
@@ -365,22 +496,30 @@ function normalizeString (uri, opts) {
|
|
|
365
496
|
}
|
|
366
497
|
|
|
367
498
|
function normalizeStringWithStatus (uri, opts) {
|
|
368
|
-
const { parsed, malformedAuthorityOrPort } = parseWithStatus(uri, opts)
|
|
499
|
+
const { parsed, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme } = parseWithStatus(uri, opts)
|
|
369
500
|
return {
|
|
370
|
-
normalized: malformedAuthorityOrPort ? uri : serialize(parsed, opts),
|
|
371
|
-
malformedAuthorityOrPort
|
|
501
|
+
normalized: malformedAuthorityOrPort || malformedPercentEncoding || malformedSchemeSpecific || malformedHost || malformedScheme ? uri : serialize(parsed, opts),
|
|
502
|
+
malformedAuthorityOrPort,
|
|
503
|
+
malformedPercentEncoding,
|
|
504
|
+
malformedSchemeSpecific,
|
|
505
|
+
malformedHost,
|
|
506
|
+
malformedScheme
|
|
372
507
|
}
|
|
373
508
|
}
|
|
374
509
|
|
|
375
510
|
function normalizeComparableURI (uri, opts) {
|
|
376
|
-
if (typeof uri
|
|
377
|
-
|
|
378
|
-
return malformedAuthorityOrPort ? undefined : normalized
|
|
511
|
+
if (typeof uri !== 'string' && typeof uri !== 'object') {
|
|
512
|
+
return undefined
|
|
379
513
|
}
|
|
380
514
|
|
|
381
|
-
|
|
382
|
-
|
|
515
|
+
let value
|
|
516
|
+
try {
|
|
517
|
+
value = typeof uri === 'string' ? uri : serialize(uri, opts)
|
|
518
|
+
} catch {
|
|
519
|
+
return undefined
|
|
383
520
|
}
|
|
521
|
+
const { normalized, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme } = normalizeStringWithStatus(value, opts)
|
|
522
|
+
return malformedAuthorityOrPort || malformedPercentEncoding || malformedSchemeSpecific || malformedHost || malformedScheme ? undefined : normalized
|
|
384
523
|
}
|
|
385
524
|
|
|
386
525
|
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]
|