fast-uri 2.4.3 → 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 CHANGED
@@ -1,8 +1,23 @@
1
1
  'use strict'
2
2
 
3
- const { normalizeIPv6, normalizeIPv4, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, escapePreservingEscapes, reescapeHostDelimiters } = require('./lib/utils')
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,7 +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 resolved = resolveComponents(parse(baseURI, schemelessOptions), parse(relativeURI, schemelessOptions), schemelessOptions, true)
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
+ ) {
60
+ throw new Error(baseParsed.error || relativeParsed.error || 'URI is malformed.')
61
+ }
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
+ }
18
76
  return serialize(resolved, { ...schemelessOptions, skipEscape: true })
19
77
  }
20
78
 
@@ -82,7 +140,7 @@ function equal (uriA, uriB, options) {
82
140
  const normalizedA = normalizeComparableURI(uriA, options)
83
141
  const normalizedB = normalizeComparableURI(uriB, options)
84
142
 
85
- return normalizedA !== undefined && normalizedB !== undefined && normalizedA.toLowerCase() === normalizedB.toLowerCase()
143
+ return normalizedA !== undefined && normalizedB !== undefined && normalizedA === normalizedB
86
144
  }
87
145
 
88
146
  function serialize (cmpts, opts) {
@@ -105,25 +163,30 @@ function serialize (cmpts, opts) {
105
163
  const options = Object.assign({}, opts)
106
164
  const uriTokens = []
107
165
 
166
+ if (components.scheme) {
167
+ components.scheme = decodeValidScheme(components.scheme)
168
+ }
169
+
108
170
  // find scheme handler
109
171
  const schemeHandler = SCHEMES[(options.scheme || components.scheme || '').toLowerCase()]
110
172
 
111
173
  // perform scheme specific serialization
112
174
  if (schemeHandler && schemeHandler.serialize) schemeHandler.serialize(components, options)
113
175
 
176
+ const hasAuthority = components.userinfo !== undefined || components.host !== undefined || components.port !== undefined
177
+ const pathNoScheme = !options.skipEscape && components.scheme === undefined && !hasAuthority
178
+
114
179
  if (components.path !== undefined) {
115
180
  if (!options.skipEscape) {
116
- components.path = escapePreservingEscapes(components.path)
117
-
118
- if (components.scheme !== undefined) {
119
- components.path = components.path.split('%3A').join(':')
120
- }
181
+ components.path = serializePathEncoding(components.path, pathNoScheme)
121
182
  } else {
122
183
  components.path = normalizePercentEncoding(components.path)
123
184
  }
124
185
  }
125
186
 
126
187
  if (options.reference !== 'suffix' && components.scheme) {
188
+ // Scheme handlers may replace the scheme during serialization.
189
+ components.scheme = decodeValidScheme(components.scheme)
127
190
  uriTokens.push(components.scheme)
128
191
  uriTokens.push(':')
129
192
  }
@@ -147,6 +210,13 @@ function serialize (cmpts, opts) {
147
210
  s = removeDotSegments(s)
148
211
  }
149
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
+
150
220
  if (authority === undefined) {
151
221
  s = s.replace(/^\/\//u, '/%2F') // don't allow the path to start with "//"
152
222
  }
@@ -156,35 +226,31 @@ function serialize (cmpts, opts) {
156
226
 
157
227
  if (components.query !== undefined) {
158
228
  uriTokens.push('?')
159
- uriTokens.push(components.query)
229
+ uriTokens.push(encodeQuery(components.query))
160
230
  }
161
231
 
162
232
  if (components.fragment !== undefined) {
163
233
  uriTokens.push('#')
164
- uriTokens.push(components.fragment)
234
+ uriTokens.push(encodeFragment(components.fragment))
165
235
  }
166
236
  return uriTokens.join('')
167
237
  }
168
238
 
169
- const hexLookUp = Array.from({ length: 127 }, (v, k) => /[^!"$&'()*+,\-.;=_`a-z{}~]/u.test(String.fromCharCode(k)))
170
-
171
- function nonSimpleDomain (value) {
172
- let code = 0
173
- for (let i = 0, len = value.length; i < len; ++i) {
174
- code = value.charCodeAt(i)
175
- if (code > 126 || hexLookUp[code]) {
176
- return true
177
- }
178
- }
179
- return false
180
- }
181
-
182
239
  const URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:?]*)(?::(\d*))?))?([^#?]*)(?:\?([^#]*))?(?:#((?:.|[\n\r])*))?/u
183
240
 
184
241
  // Captures the authority component (between "//" and the next "/", "?" or "#"),
185
242
  // with or without a scheme prefix, for the literal-backslash rejection below.
186
243
  const AUTHORITY_PREFIX = /^(?:[^#/:?]+:)?\/\/([^/?#]*)/
187
244
 
245
+ // Captures the leading authority-introducer region after an optional scheme: a
246
+ // run of forward slashes, backslashes, and the characters the WHATWG URL parser
247
+ // removes before parsing (TAB U+0009, LF U+000A, CR U+000D). A valid introducer
248
+ // is exactly "//". Node treats "\\" as "/" on special schemes and strips those
249
+ // characters first, so forms like "\\", "/\", "\/", "/<TAB>/", or a leading
250
+ // "<TAB>//" reach an authority in Node while fast-uri's URI_PARSE folds them into
251
+ // the path group (host confusion / SSRF / redirect bypass).
252
+ const AUTHORITY_INTRODUCER_REGION = /^(?:[^#/:?]+:)?([/\\\t\n\r]*)/
253
+
188
254
  function getParseError (parsed, matches) {
189
255
  if (matches[2] !== undefined && parsed.path && parsed.path[0] !== '/') {
190
256
  return 'URI path must start with "/" when authority is present.'
@@ -197,6 +263,70 @@ function getParseError (parsed, matches) {
197
263
  return undefined
198
264
  }
199
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
+
200
330
  function parseWithStatus (uri, opts) {
201
331
  const options = Object.assign({}, opts)
202
332
  const parsed = {
@@ -208,8 +338,12 @@ function parseWithStatus (uri, opts) {
208
338
  query: undefined,
209
339
  fragment: undefined
210
340
  }
211
- const gotEncoding = uri.indexOf('%') !== -1
212
341
  let malformedAuthorityOrPort = false
342
+ let malformedPercentEncoding = false
343
+ let malformedSchemeSpecific = false
344
+ let malformedHost = false
345
+ let malformedIPLiteral = false
346
+ let malformedScheme = false
213
347
  let isIP = false
214
348
  if (options.reference === 'suffix') uri = (options.scheme ? options.scheme + ':' : '') + '//' + uri
215
349
 
@@ -226,6 +360,28 @@ function parseWithStatus (uri, opts) {
226
360
  malformedAuthorityOrPort = true
227
361
  }
228
362
 
363
+ // Reject a malformed or whitespace-smuggled authority introducer. fast-uri
364
+ // only recognizes a literal "//"; anything else in the leading separator run
365
+ // (a backslash, or a "//" that appears only after removing the TAB/LF/CR that
366
+ // Node strips) means the authority fast-uri parses differs from the one Node's
367
+ // URL resolves. Reject rather than rewrite, mirroring the literal-backslash
368
+ // guard above. Percent-encoded forms (%5C, %09) are untouched, valid data.
369
+ const introducerMatch = uri.match(AUTHORITY_INTRODUCER_REGION)
370
+ if (introducerMatch !== null) {
371
+ const region = introducerMatch[1]
372
+ const normalizedRegion = region.replace(/[\t\n\r]/g, '')
373
+ // Two or more leading separators introduce an authority.
374
+ if (normalizedRegion.length >= 2) {
375
+ if (normalizedRegion.slice(0, 2) !== '//') {
376
+ parsed.error = parsed.error || 'URI authority must not contain a literal backslash.'
377
+ malformedAuthorityOrPort = true
378
+ } else if (region.length !== normalizedRegion.length) {
379
+ parsed.error = parsed.error || 'URI authority introducer must not contain whitespace.'
380
+ malformedAuthorityOrPort = true
381
+ }
382
+ }
383
+ }
384
+
229
385
  const matches = uri.match(URI_PARSE)
230
386
 
231
387
  if (matches) {
@@ -238,6 +394,21 @@ function parseWithStatus (uri, opts) {
238
394
  parsed.query = matches[7]
239
395
  parsed.fragment = matches[8]
240
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
+
241
412
  // fix port number
242
413
  if (isNaN(parsed.port)) {
243
414
  parsed.port = matches[5]
@@ -250,13 +421,19 @@ function parseWithStatus (uri, opts) {
250
421
  }
251
422
 
252
423
  if (parsed.host) {
253
- const ipv4result = normalizeIPv4(parsed.host)
254
- if (ipv4result.isIPV4 === false) {
255
- const ipv6result = normalizeIPv6(ipv4result.host, { isIPV4: false })
256
- parsed.host = ipv6result.host.toLowerCase()
257
- 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
+ }
258
436
  } else {
259
- parsed.host = ipv4result.host
260
437
  isIP = true
261
438
  }
262
439
  }
@@ -278,46 +455,36 @@ function parseWithStatus (uri, opts) {
278
455
  // find scheme handler
279
456
  const schemeHandler = SCHEMES[(options.scheme || parsed.scheme || '').toLowerCase()]
280
457
 
281
- // check if scheme can't handle IRIs
282
- if (!options.unicodeSupport && (!schemeHandler || !schemeHandler.unicodeSupport)) {
283
- // if host component is a domain name
284
- if (parsed.host && (options.domainHost || (schemeHandler && schemeHandler.domainHost)) && isIP === false && nonSimpleDomain(parsed.host)) {
285
- // convert Unicode IDN -> ASCII IDN
286
- try {
287
- parsed.host = new URL('http://' + parsed.host).hostname
288
- } catch (e) {
289
- parsed.error = parsed.error || "Host's domain name can not be converted to ASCII: " + e
290
- }
291
- }
292
- // convert IRI -> URI
293
- }
458
+ // convert Unicode IDN -> ASCII IDN when the effective scheme uses domain hosts
459
+ malformedHost = canonicalizeHost(parsed, options, schemeHandler, isIP)
294
460
 
295
461
  if (!schemeHandler || (schemeHandler && !schemeHandler.skipNormalize)) {
296
- if (gotEncoding && parsed.scheme !== undefined) {
297
- parsed.scheme = unescape(parsed.scheme)
298
- }
299
- if (gotEncoding && parsed.userinfo !== undefined) {
300
- parsed.userinfo = unescape(parsed.userinfo)
301
- }
302
- if (gotEncoding && parsed.host !== undefined) {
303
- 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)
304
465
  }
305
466
  if (parsed.path !== undefined && parsed.path.length) {
306
467
  parsed.path = normalizePathEncoding(parsed.path)
307
468
  }
469
+ if (parsed.query !== undefined && parsed.query.length) {
470
+ parsed.query = normalizeQueryFragmentEncoding(parsed.query)
471
+ }
308
472
  if (parsed.fragment !== undefined && parsed.fragment.length) {
309
- parsed.fragment = encodeURI(decodeURI(parsed.fragment))
473
+ parsed.fragment = normalizeQueryFragmentEncoding(parsed.fragment)
310
474
  }
311
475
  }
312
476
 
313
477
  // perform scheme specific parsing
314
478
  if (schemeHandler && schemeHandler.parse) {
315
479
  schemeHandler.parse(parsed, options)
480
+ if (schemeHandler === SCHEMES.urn && parsed.nid === undefined) {
481
+ malformedSchemeSpecific = true
482
+ }
316
483
  }
317
484
  } else {
318
485
  parsed.error = parsed.error || 'URI can not be parsed.'
319
486
  }
320
- return { parsed, malformedAuthorityOrPort }
487
+ return { parsed, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme }
321
488
  }
322
489
 
323
490
  function parse (uri, opts) {
@@ -329,22 +496,30 @@ function normalizeString (uri, opts) {
329
496
  }
330
497
 
331
498
  function normalizeStringWithStatus (uri, opts) {
332
- const { parsed, malformedAuthorityOrPort } = parseWithStatus(uri, opts)
499
+ const { parsed, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme } = parseWithStatus(uri, opts)
333
500
  return {
334
- normalized: malformedAuthorityOrPort ? uri : serialize(parsed, opts),
335
- malformedAuthorityOrPort
501
+ normalized: malformedAuthorityOrPort || malformedPercentEncoding || malformedSchemeSpecific || malformedHost || malformedScheme ? uri : serialize(parsed, opts),
502
+ malformedAuthorityOrPort,
503
+ malformedPercentEncoding,
504
+ malformedSchemeSpecific,
505
+ malformedHost,
506
+ malformedScheme
336
507
  }
337
508
  }
338
509
 
339
510
  function normalizeComparableURI (uri, opts) {
340
- if (typeof uri === 'string') {
341
- const { normalized, malformedAuthorityOrPort } = normalizeStringWithStatus(uri, opts)
342
- return malformedAuthorityOrPort ? undefined : normalized
511
+ if (typeof uri !== 'string' && typeof uri !== 'object') {
512
+ return undefined
343
513
  }
344
514
 
345
- if (typeof uri === 'object') {
346
- return serialize(uri, opts)
515
+ let value
516
+ try {
517
+ value = typeof uri === 'string' ? uri : serialize(uri, opts)
518
+ } catch {
519
+ return undefined
347
520
  }
521
+ const { normalized, malformedAuthorityOrPort, malformedPercentEncoding, malformedSchemeSpecific, malformedHost, malformedScheme } = normalizeStringWithStatus(value, opts)
522
+ return malformedAuthorityOrPort || malformedPercentEncoding || malformedSchemeSpecific || malformedHost || malformedScheme ? undefined : normalized
348
523
  }
349
524
 
350
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 = /([\da-z][\d\-a-z]{0,31}):((?:[\w!$'()*+,\-.:;=@]|%[\da-f]{2})+)/iu
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 [path, query] = wsComponents.resourceName.split('?')
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 = 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]