@suveren/gateway 0.7.4 → 0.7.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.
Files changed (171) hide show
  1. package/dist/control-plane/index.mjs +248 -58
  2. package/dist/mcp-server/http.mjs +408 -65
  3. package/dist/ui/assets/{index-CkWaRck1.css → index-BYVb_0oG.css} +1 -1
  4. package/dist/ui/assets/index-CsrM6RQr.js +110 -0
  5. package/dist/ui/index.html +2 -2
  6. package/node_modules/@hap/core/dist/index.d.mts +258 -9
  7. package/node_modules/@hap/core/dist/index.d.ts +258 -9
  8. package/node_modules/@hap/core/dist/index.js +227 -5
  9. package/node_modules/@hap/core/dist/index.mjs +213 -4
  10. package/node_modules/@hap/core/package.json +3 -2
  11. package/node_modules/@hap/core/src/did-key.ts +126 -0
  12. package/node_modules/@hap/core/src/identity.ts +21 -5
  13. package/node_modules/@hap/core/src/index.ts +3 -0
  14. package/node_modules/@hap/core/src/mandate.ts +207 -0
  15. package/node_modules/@hap/core/src/receipt.ts +74 -0
  16. package/node_modules/@hap/core/src/types.ts +81 -5
  17. package/node_modules/@modelcontextprotocol/sdk/node_modules/negotiator/lib/accept.js +54 -0
  18. package/node_modules/@modelcontextprotocol/sdk/node_modules/negotiator/lib/charset.js +10 -34
  19. package/node_modules/@modelcontextprotocol/sdk/node_modules/negotiator/lib/encoding.js +9 -30
  20. package/node_modules/@modelcontextprotocol/sdk/node_modules/negotiator/lib/language.js +15 -36
  21. package/node_modules/@modelcontextprotocol/sdk/node_modules/negotiator/lib/mediaType.js +16 -134
  22. package/node_modules/@modelcontextprotocol/sdk/node_modules/negotiator/node_modules/content-type/LICENSE +22 -0
  23. package/node_modules/@modelcontextprotocol/sdk/node_modules/negotiator/node_modules/content-type/README.md +71 -0
  24. package/node_modules/@modelcontextprotocol/sdk/node_modules/negotiator/node_modules/content-type/dist/index.d.ts +46 -0
  25. package/node_modules/@modelcontextprotocol/sdk/node_modules/negotiator/node_modules/content-type/dist/index.js +176 -0
  26. package/node_modules/@modelcontextprotocol/sdk/node_modules/negotiator/node_modules/content-type/dist/index.js.map +1 -0
  27. package/node_modules/@modelcontextprotocol/sdk/node_modules/negotiator/node_modules/content-type/package.json +52 -0
  28. package/node_modules/@modelcontextprotocol/sdk/node_modules/negotiator/package.json +16 -10
  29. package/node_modules/@types/node/README.md +1 -1
  30. package/node_modules/@types/node/buffer.buffer.d.ts +5 -5
  31. package/node_modules/@types/node/crypto.d.ts +2 -2
  32. package/node_modules/@types/node/http.d.ts +16 -0
  33. package/node_modules/@types/node/http2.d.ts +10 -2
  34. package/node_modules/@types/node/package.json +2 -2
  35. package/node_modules/@types/node/process.d.ts +52 -0
  36. package/node_modules/@types/node/quic.d.ts +210 -28
  37. package/node_modules/@types/node/sqlite.d.ts +6 -2
  38. package/node_modules/@types/node/test.d.ts +36 -0
  39. package/node_modules/fast-uri/index.js +201 -48
  40. package/node_modules/fast-uri/lib/schemes.js +9 -4
  41. package/node_modules/fast-uri/lib/utils.js +388 -91
  42. package/node_modules/fast-uri/package.json +1 -1
  43. package/node_modules/fast-uri/test/component-safe-serialization.test.js +105 -0
  44. package/node_modules/fast-uri/test/equal.test.js +31 -3
  45. package/node_modules/fast-uri/test/fixtures/uri-js-parse.json +2 -0
  46. package/node_modules/fast-uri/test/ipv6-canonical.test.js +34 -0
  47. package/node_modules/fast-uri/test/ipv6-validation.test.js +90 -0
  48. package/node_modules/fast-uri/test/malformed-percent.test.js +77 -0
  49. package/node_modules/fast-uri/test/malformed-urn.test.js +61 -0
  50. package/node_modules/fast-uri/test/parse.test.js +7 -3
  51. package/node_modules/fast-uri/test/query-fragment-normalization.test.js +33 -0
  52. package/node_modules/fast-uri/test/reserved-path-normalization.test.js +109 -0
  53. package/node_modules/fast-uri/test/scheme-validation.test.js +124 -0
  54. package/node_modules/fast-uri/test/security-normalization.test.js +101 -0
  55. package/node_modules/fast-uri/test/security.test.js +75 -3
  56. package/node_modules/fast-uri/test/urn-full-input.test.js +29 -0
  57. package/node_modules/fast-uri/test/websocket-query-preservation.test.js +24 -0
  58. package/node_modules/hono/dist/cjs/client/client.js +25 -11
  59. package/node_modules/hono/dist/cjs/client/utils.js +4 -1
  60. package/node_modules/hono/dist/cjs/context.js +4 -0
  61. package/node_modules/hono/dist/cjs/helper/accepts/accepts.js +36 -2
  62. package/node_modules/hono/dist/cjs/helper/ssg/ssg.js +1 -1
  63. package/node_modules/hono/dist/cjs/helper/ssg/utils.js +30 -10
  64. package/node_modules/hono/dist/cjs/jsx/dom/render.js +2 -0
  65. package/node_modules/hono/dist/cjs/middleware/cache/index.js +1 -1
  66. package/node_modules/hono/dist/cjs/middleware/cors/index.js +1 -1
  67. package/node_modules/hono/dist/cjs/middleware/csrf/index.js +1 -1
  68. package/node_modules/hono/dist/cjs/middleware/etag/digest.js +1 -1
  69. package/node_modules/hono/dist/cjs/middleware/etag/index.js +3 -3
  70. package/node_modules/hono/dist/cjs/middleware/pretty-json/index.js +3 -1
  71. package/node_modules/hono/dist/cjs/request.js +8 -4
  72. package/node_modules/hono/dist/cjs/router/linear-router/router.js +7 -2
  73. package/node_modules/hono/dist/cjs/router/pattern-router/router.js +3 -9
  74. package/node_modules/hono/dist/cjs/router/reg-exp-router/node.js +10 -3
  75. package/node_modules/hono/dist/cjs/router/reg-exp-router/router.js +47 -83
  76. package/node_modules/hono/dist/cjs/router/reg-exp-router/trie.js +2 -1
  77. package/node_modules/hono/dist/cjs/router/trie-router/node.js +46 -69
  78. package/node_modules/hono/dist/cjs/router/trie-router/router.js +3 -11
  79. package/node_modules/hono/dist/cjs/router/utils.js +27 -0
  80. package/node_modules/hono/dist/cjs/utils/body.js +15 -3
  81. package/node_modules/hono/dist/cjs/utils/cookie.js +1 -1
  82. package/node_modules/hono/dist/cjs/utils/ipaddr.js +5 -3
  83. package/node_modules/hono/dist/cjs/utils/stream.js +7 -1
  84. package/node_modules/hono/dist/cjs/utils/url.js +9 -1
  85. package/node_modules/hono/dist/client/client.js +25 -11
  86. package/node_modules/hono/dist/client/utils.js +4 -1
  87. package/node_modules/hono/dist/context.js +4 -0
  88. package/node_modules/hono/dist/helper/accepts/accepts.js +36 -2
  89. package/node_modules/hono/dist/helper/ssg/ssg.js +1 -1
  90. package/node_modules/hono/dist/helper/ssg/utils.js +30 -10
  91. package/node_modules/hono/dist/jsx/dom/render.js +2 -0
  92. package/node_modules/hono/dist/middleware/cache/index.js +1 -1
  93. package/node_modules/hono/dist/middleware/cors/index.js +1 -1
  94. package/node_modules/hono/dist/middleware/csrf/index.js +1 -1
  95. package/node_modules/hono/dist/middleware/etag/digest.js +1 -1
  96. package/node_modules/hono/dist/middleware/etag/index.js +3 -3
  97. package/node_modules/hono/dist/middleware/pretty-json/index.js +3 -1
  98. package/node_modules/hono/dist/request.js +8 -4
  99. package/node_modules/hono/dist/router/linear-router/router.js +7 -2
  100. package/node_modules/hono/dist/router/pattern-router/router.js +3 -9
  101. package/node_modules/hono/dist/router/reg-exp-router/node.js +6 -2
  102. package/node_modules/hono/dist/router/reg-exp-router/router.js +53 -84
  103. package/node_modules/hono/dist/router/reg-exp-router/trie.js +2 -1
  104. package/node_modules/hono/dist/router/trie-router/node.js +46 -69
  105. package/node_modules/hono/dist/router/trie-router/router.js +3 -11
  106. package/node_modules/hono/dist/router/utils.js +5 -0
  107. package/node_modules/hono/dist/types/context.d.ts +4 -0
  108. package/node_modules/hono/dist/types/router/reg-exp-router/node.d.ts +3 -0
  109. package/node_modules/hono/dist/types/router/trie-router/node.d.ts +1 -2
  110. package/node_modules/hono/dist/types/router/trie-router/router.d.ts +0 -1
  111. package/node_modules/hono/dist/types/router/utils.d.ts +1 -0
  112. package/node_modules/hono/dist/types/utils/url.d.ts +4 -0
  113. package/node_modules/hono/dist/utils/body.js +15 -3
  114. package/node_modules/hono/dist/utils/cookie.js +1 -1
  115. package/node_modules/hono/dist/utils/ipaddr.js +5 -3
  116. package/node_modules/hono/dist/utils/stream.js +7 -1
  117. package/node_modules/hono/dist/utils/url.js +9 -1
  118. package/node_modules/hono/package.json +1 -1
  119. package/node_modules/is-plain-object/README.md +39 -95
  120. package/node_modules/is-plain-object/dist/is-plain-object.js +0 -2
  121. package/node_modules/is-plain-object/is-plain-object.d.ts +6 -1
  122. package/node_modules/is-plain-object/package.json +8 -34
  123. package/node_modules/jose/dist/types/jwe/compact/encrypt.d.ts +4 -3
  124. package/node_modules/jose/dist/types/jwe/flattened/encrypt.d.ts +4 -3
  125. package/node_modules/jose/dist/types/jwe/general/encrypt.d.ts +4 -3
  126. package/node_modules/jose/dist/types/jwks/remote.d.ts +4 -4
  127. package/node_modules/jose/dist/types/jws/general/verify.d.ts +3 -3
  128. package/node_modules/jose/dist/types/jwt/encrypt.d.ts +12 -17
  129. package/node_modules/jose/dist/types/jwt/sign.d.ts +8 -14
  130. package/node_modules/jose/dist/types/jwt/unsecured.d.ts +9 -15
  131. package/node_modules/jose/dist/types/types.d.ts +4 -2
  132. package/node_modules/jose/dist/webapi/jwe/compact/encrypt.js +30 -8
  133. package/node_modules/jose/dist/webapi/jwe/flattened/decrypt.js +8 -4
  134. package/node_modules/jose/dist/webapi/jwe/flattened/encrypt.js +6 -13
  135. package/node_modules/jose/dist/webapi/jwe/general/decrypt.js +19 -19
  136. package/node_modules/jose/dist/webapi/jwe/general/encrypt.js +32 -21
  137. package/node_modules/jose/dist/webapi/jwk/embedded.js +15 -1
  138. package/node_modules/jose/dist/webapi/jwk/thumbprint.js +11 -5
  139. package/node_modules/jose/dist/webapi/jwks/local.js +45 -53
  140. package/node_modules/jose/dist/webapi/jwks/remote.js +82 -103
  141. package/node_modules/jose/dist/webapi/jws/compact/sign.js +10 -9
  142. package/node_modules/jose/dist/webapi/jws/flattened/sign.js +2 -1
  143. package/node_modules/jose/dist/webapi/jws/flattened/verify.js +8 -7
  144. package/node_modules/jose/dist/webapi/jws/general/sign.js +5 -3
  145. package/node_modules/jose/dist/webapi/jws/general/verify.js +51 -21
  146. package/node_modules/jose/dist/webapi/jwt/decrypt.js +7 -9
  147. package/node_modules/jose/dist/webapi/jwt/encrypt.js +21 -50
  148. package/node_modules/jose/dist/webapi/jwt/sign.js +8 -41
  149. package/node_modules/jose/dist/webapi/jwt/unsecured.js +21 -42
  150. package/node_modules/jose/dist/webapi/key/generate_key_pair.js +6 -2
  151. package/node_modules/jose/dist/webapi/key/generate_secret.js +7 -6
  152. package/node_modules/jose/dist/webapi/key/import.js +16 -12
  153. package/node_modules/jose/dist/webapi/lib/asn1.js +7 -2
  154. package/node_modules/jose/dist/webapi/lib/content_encryption.js +7 -15
  155. package/node_modules/jose/dist/webapi/lib/deflate.js +8 -0
  156. package/node_modules/jose/dist/webapi/lib/helpers.js +1 -1
  157. package/node_modules/jose/dist/webapi/lib/jwe_decrypt.js +85 -28
  158. package/node_modules/jose/dist/webapi/lib/jwe_encrypt.js +45 -13
  159. package/node_modules/jose/dist/webapi/lib/jwk_metadata.js +20 -0
  160. package/node_modules/jose/dist/webapi/lib/jws_sign.js +47 -36
  161. package/node_modules/jose/dist/webapi/lib/jws_verify.js +77 -45
  162. package/node_modules/jose/dist/webapi/lib/jwt_claims_set.js +82 -47
  163. package/node_modules/jose/dist/webapi/lib/key.js +24 -9
  164. package/node_modules/jose/dist/webapi/lib/key_management.js +10 -4
  165. package/node_modules/jose/dist/webapi/lib/key_options.js +6 -0
  166. package/node_modules/jose/dist/webapi/lib/options.js +26 -0
  167. package/node_modules/jose/dist/webapi/lib/type_checks.js +11 -13
  168. package/node_modules/jose/package.json +1 -1
  169. package/package.json +2 -2
  170. package/dist/ui/assets/index-0Q2eNytX.js +0 -110
  171. package/node_modules/@modelcontextprotocol/sdk/node_modules/negotiator/HISTORY.md +0 -114
@@ -21,6 +21,36 @@ test('URI Equals', (t) => {
21
21
  t.end()
22
22
  })
23
23
 
24
+ test('URI Equals rejects malformed object input even when identical', (t) => {
25
+ const malformed = { scheme: 'http', host: 'example.com', port: 99999, path: '/x' }
26
+ t.equal(fn(malformed, malformed), false)
27
+ t.end()
28
+ })
29
+
30
+ test('URI Equals preserves case-sensitive components', (t) => {
31
+ const suite = [
32
+ { pair: ['http://example.com/Admin', 'http://example.com/admin'], result: false },
33
+ { pair: ['http://example.com/?token=SECRET', 'http://example.com/?token=secret'], result: false },
34
+ { pair: ['http://example.com/#Section', 'http://example.com/#section'], result: false },
35
+ { pair: ['http://User@example.com/', 'http://user@example.com/'], result: false },
36
+ { pair: ['urn:foo:CaseSensitive', 'urn:foo:casesensitive'], result: false },
37
+ { pair: ['ws://example.com/Chat', 'ws://example.com/chat'], result: false },
38
+ { pair: ['ws://example.com/?token=SECRET', 'ws://example.com/?token=secret'], result: false },
39
+ { pair: [{ scheme: 'http', host: 'example.com', path: '/Admin' }, { scheme: 'http', host: 'example.com', path: '/admin' }], result: false }
40
+ ]
41
+ runTest(t, suite)
42
+ t.end()
43
+ })
44
+
45
+ test('URI Equals folds normalized scheme and host case', (t) => {
46
+ const suite = [
47
+ { pair: ['HTTP://EXAMPLE.COM/resource', 'http://example.com/resource'], result: true },
48
+ { pair: [{ scheme: 'HTTP', host: 'EXAMPLE.COM', path: '/resource' }, { scheme: 'http', host: 'example.com', path: '/resource' }], result: true }
49
+ ]
50
+ runTest(t, suite)
51
+ t.end()
52
+ })
53
+
24
54
  // test('IRI Equals', (t) => {
25
55
  // // example from RFC 3987
26
56
  // t.equal(URI.equal('example://a/b/c/%7Bfoo%7D/ros\xE9', 'eXAMPLE://a/./b/../b/%63/%7bfoo%7d/ros%C3%A9', IRI_OPTION), true)
@@ -66,9 +96,7 @@ test('URN Equals', (t) => {
66
96
 
67
97
  runTest(t, suite)
68
98
 
69
- t.throws(() => {
70
- fn('urn:', 'urn:FOO:a123,456')
71
- }, 'URN without nid cannot be serialized')
99
+ t.equal(fn('urn:', 'urn:FOO:a123,456'), false, 'malformed URN fails equality safely')
72
100
 
73
101
  t.end()
74
102
  })
@@ -53,6 +53,7 @@
53
53
  "host": "[2001:dbz::1]",
54
54
  "port": 80,
55
55
  "path": "",
56
+ "error": "URI host is malformed.",
56
57
  "reference": "relative"
57
58
  }
58
59
  ],
@@ -242,6 +243,7 @@
242
243
  {
243
244
  "host": "[2606:2800:220:1:248:1893:25c8:1946:43209]",
244
245
  "path": "",
246
+ "error": "URI host is malformed.",
245
247
  "reference": "relative"
246
248
  }
247
249
  ],
@@ -0,0 +1,34 @@
1
+ 'use strict'
2
+
3
+ const test = require('tape')
4
+ const fastURI = require('..')
5
+
6
+ test('IPv6 hosts normalize to the RFC 5952 canonical form', (t) => {
7
+ const cases = [
8
+ ['http://[0:0:0:0:0:0:0:1]/', 'http://[::1]/'],
9
+ ['http://[0:0:0:0:0:0:0:0]/', 'http://[::]/'],
10
+ ['http://[2001:0db8:0000:0000:0000:0000:0000:0001]/', 'http://[2001:db8::1]/'],
11
+ ['http://[2001:0:0:0:0:0:0:1]/', 'http://[2001::1]/'],
12
+ ['http://[fe80:0:0:0:0:0:0:1]/', 'http://[fe80::1]/'],
13
+ ['http://[1:0:0:0:2:0:0:3]/', 'http://[1::2:0:0:3]/']
14
+ ]
15
+
16
+ for (const [uri, normalized] of cases) {
17
+ t.equal(fastURI.normalize(uri), normalized, `${uri} normalizes to ${normalized}`)
18
+ }
19
+ t.end()
20
+ })
21
+
22
+ test('IPv6 equal() matches the same address across compressed and expanded forms', (t) => {
23
+ const pairs = [
24
+ ['http://[::1]/', 'http://[0:0:0:0:0:0:0:1]/'],
25
+ ['http://[::]/', 'http://[0:0:0:0:0:0:0:0]/'],
26
+ ['http://[2001:db8::1]/', 'http://[2001:0db8:0000:0000:0000:0000:0000:0001]/'],
27
+ ['http://[1::2:0:0:3]/', 'http://[1:0:0:0:2:0:0:3]/']
28
+ ]
29
+
30
+ for (const [a, b] of pairs) {
31
+ t.equal(fastURI.equal(a, b), true, `${a} equals ${b}`)
32
+ }
33
+ t.end()
34
+ })
@@ -0,0 +1,90 @@
1
+ 'use strict'
2
+
3
+ const test = require('tape')
4
+ const fastURI = require('..')
5
+
6
+ const HOST_ERROR = 'URI host is malformed.'
7
+
8
+ const malformedLiterals = [
9
+ '::not-valid',
10
+ 'fc00::not-hex',
11
+ 'fe80::not-hex',
12
+ '1:2:3',
13
+ '1:2:3:4:5:6:7',
14
+ '1:2:3:4:5:6:7:8:9',
15
+ '1::2::3',
16
+ '1:::2',
17
+ ':::1',
18
+ '12345::',
19
+ '1:2:3:4:5:6:7::8',
20
+ '::ffff:192.0.2.999',
21
+ '::ffff:192.0.2',
22
+ '::ffff:192.168.001.1',
23
+ '::192.0.2.1:1',
24
+ '1:2:3:4:5:192.0.2.1::',
25
+ 'v.foo',
26
+ 'v1.',
27
+ 'v1.foo%25bar',
28
+ 'v1.K',
29
+ 'fe80::1%25',
30
+ 'fe80::1%25eth 0',
31
+ 'fe80::1%25eth%ZZ',
32
+ 'fe80::1%25K',
33
+ 'not-an-ip'
34
+ ]
35
+
36
+ test('malformed bracketed IP literals fail without being rewritten', (t) => {
37
+ for (const literal of malformedLiterals) {
38
+ const uri = `http://[${literal}]/private`
39
+ const parsed = fastURI.parse(uri)
40
+
41
+ t.equal(parsed.error, HOST_ERROR, `parse rejects ${literal}`)
42
+ t.equal(parsed.host, `[${literal.toLowerCase()}]`, `parse does not truncate ${literal}`)
43
+ t.equal(fastURI.normalize(uri), uri, `normalize preserves ${literal}`)
44
+ t.equal(fastURI.equal(uri, uri), false, `equal rejects ${literal}`)
45
+ }
46
+ t.end()
47
+ })
48
+
49
+ test('resolve throws for malformed bracketed IP literals', (t) => {
50
+ for (const literal of malformedLiterals) {
51
+ const uri = `http://[${literal}]/private`
52
+
53
+ t.throws(
54
+ () => fastURI.resolve(uri, 'child'),
55
+ /URI host is malformed\./,
56
+ `rejects malformed base ${literal}`
57
+ )
58
+ t.throws(
59
+ () => fastURI.resolve('http://example.com/', uri),
60
+ /URI host is malformed\./,
61
+ `rejects malformed relative input ${literal}`
62
+ )
63
+ }
64
+ t.end()
65
+ })
66
+
67
+ test('valid IPv6, IPvFuture, embedded IPv4, and zone forms normalize safely', (t) => {
68
+ const cases = [
69
+ ['http://[::]/', 'http://[::]/', '::'],
70
+ ['http://[::1]/', 'http://[::1]/', '::1'],
71
+ ['http://[1::]/', 'http://[1::]/', '1::'],
72
+ ['http://[2001:0DB8::0001]/', 'http://[2001:db8::1]/', '2001:db8::1'],
73
+ ['http://[0:0:0:0:0:0:0:0]/', 'http://[::]/', '::'],
74
+ ['http://[::ffff:192.0.2.1]/', 'http://[::ffff:192.0.2.1]/', '::ffff:192.0.2.1'],
75
+ ['http://[1:2:3:4:5:6:192.0.2.1]/', 'http://[1:2:3:4:5:6:192.0.2.1]/', '1:2:3:4:5:6:192.0.2.1'],
76
+ ['http://[fe80::A%25EN1]/', 'http://[fe80::a%25EN1]/', 'fe80::a%EN1'],
77
+ ['http://[fe80::a%en1]/', 'http://[fe80::a%25en1]/', 'fe80::a%en1'],
78
+ ['http://[fe80::a%25eth%2D0]/', 'http://[fe80::a%25eth%2D0]/', 'fe80::a%eth%2D0'],
79
+ ['http://[v1.example]/', 'http://[v1.example]/', '[v1.example]'],
80
+ ['http://[vF.A:b]/', 'http://[vf.a:b]/', '[vf.a:b]']
81
+ ]
82
+
83
+ for (const [uri, normalized, host] of cases) {
84
+ const parsed = fastURI.parse(uri)
85
+ t.equal(parsed.error, undefined, `${uri} parses without error`)
86
+ t.equal(parsed.host, host, `${uri} has the expected host`)
87
+ t.equal(fastURI.normalize(uri), normalized, `${uri} normalizes safely`)
88
+ }
89
+ t.end()
90
+ })
@@ -0,0 +1,77 @@
1
+ 'use strict'
2
+
3
+ const test = require('tape')
4
+ const fastURI = require('..')
5
+
6
+ const MALFORMED_PERCENT_ERROR = 'URI contains malformed percent-encoding.'
7
+
8
+ const malformedComponents = [
9
+ ['path', 'x://example.test/a%'],
10
+ ['path with one trailing hex digit', 'x://example.test/a%2'],
11
+ ['path with non-hex digits', 'x://example.test/a%GG'],
12
+ ['host', 'x://exa%mple.test/path'],
13
+ ['userinfo', 'x://us%er@example.test/path'],
14
+ ['query', 'x://example.test/path?q=%G0'],
15
+ ['fragment', 'x://example.test/path#frag%1']
16
+ ]
17
+
18
+ test('parse reports malformed percent syntax in generic URI components', (t) => {
19
+ for (const [component, uri] of malformedComponents) {
20
+ t.equal(fastURI.parse(uri).error, MALFORMED_PERCENT_ERROR, component)
21
+ }
22
+ t.end()
23
+ })
24
+
25
+ test('normalize preserves malformed percent input unchanged', (t) => {
26
+ for (const [component, uri] of malformedComponents) {
27
+ t.equal(fastURI.normalize(uri), uri, component)
28
+ }
29
+ t.end()
30
+ })
31
+
32
+ test('equal does not equate malformed input with repaired valid input', (t) => {
33
+ const cases = [
34
+ ['x://example.test/a%', 'x://example.test/a%25'],
35
+ ['x://exa%mple.test/path', 'x://exa%25mple.test/path'],
36
+ ['x://us%er@example.test/path', 'x://us%25er@example.test/path'],
37
+ ['x://example.test/path?q=%', 'x://example.test/path?q=%25'],
38
+ ['x://example.test/path#%', 'x://example.test/path#%25']
39
+ ]
40
+
41
+ for (const [malformed, repaired] of cases) {
42
+ t.equal(fastURI.equal(malformed, repaired, {}), false, malformed)
43
+ }
44
+ t.end()
45
+ })
46
+
47
+ test('resolve rejects malformed percent syntax in either input', (t) => {
48
+ t.throws(
49
+ () => fastURI.resolve('x://example.test/base%', 'child'),
50
+ /URI contains malformed percent-encoding\./,
51
+ 'malformed base'
52
+ )
53
+ t.throws(
54
+ () => fastURI.resolve('x://example.test/base', 'child%'),
55
+ /URI contains malformed percent-encoding\./,
56
+ 'malformed relative reference'
57
+ )
58
+ t.end()
59
+ })
60
+
61
+ test('valid percent octets remain valid without UTF-8 validation', (t) => {
62
+ const uri = '/%ff?q=%80#%fe'
63
+ const parsed = fastURI.parse(uri)
64
+
65
+ t.equal(parsed.error, undefined, 'non-UTF-8 octets are valid percent syntax')
66
+ t.equal(parsed.path, '/%FF', 'path octet is preserved and hex is uppercased')
67
+ t.equal(parsed.query, 'q=%80', 'query octet is preserved')
68
+ t.equal(parsed.fragment, '%FE', 'fragment octet is preserved and hex is uppercased')
69
+ t.equal(fastURI.normalize(uri), '/%FF?q=%80#%FE', 'normalization preserves the octets')
70
+
71
+ const allGenericComponents = fastURI.parse('x://u%2f@exa%2fmple.test/%2f?q=%2f#%2f')
72
+ t.equal(allGenericComponents.error, undefined, 'valid escapes are accepted in every generic component')
73
+
74
+ const ipv6Zone = fastURI.parse('//[2001:db8::7%en0]')
75
+ t.equal(ipv6Zone.error, undefined, 'historically accepted raw IPv6 zone separator is unchanged')
76
+ t.end()
77
+ })
@@ -0,0 +1,61 @@
1
+ 'use strict'
2
+
3
+ const test = require('tape')
4
+ const fastURI = require('..')
5
+
6
+ const malformedURNs = [
7
+ 'urn:',
8
+ 'URN:',
9
+ 'urn:foo',
10
+ 'urn::foo',
11
+ 'urn:foo:',
12
+ 'urn:%66oo:bar'
13
+ ]
14
+
15
+ test('parse reports malformed ordinary URNs', (t) => {
16
+ for (const uri of malformedURNs) {
17
+ t.match(fastURI.parse(uri).error, /^URN can not be parsed\.?$/, uri)
18
+ }
19
+ t.end()
20
+ })
21
+
22
+ test('normalize preserves malformed ordinary URNs without throwing', (t) => {
23
+ for (const uri of malformedURNs) {
24
+ t.doesNotThrow(() => fastURI.normalize(uri), `${uri} does not throw`)
25
+ t.equal(fastURI.normalize(uri), uri, `${uri} is preserved`)
26
+ }
27
+ t.equal(
28
+ fastURI.normalize('urn:foo', { reference: 'relative' }),
29
+ 'urn:foo',
30
+ 'an earlier parse error does not hide the missing URN nid'
31
+ )
32
+ t.end()
33
+ })
34
+
35
+ test('equal returns false for malformed ordinary URNs', (t) => {
36
+ for (const uri of malformedURNs) {
37
+ t.equal(fastURI.equal(uri, uri, {}), false, `${uri} is not equal to itself as malformed input`)
38
+ t.equal(fastURI.equal(uri, 'urn:foo:bar', {}), false, `${uri} is not equal to a valid URN`)
39
+ }
40
+ t.end()
41
+ })
42
+
43
+ test('resolve handles malformed ordinary URNs without throwing', (t) => {
44
+ for (const uri of malformedURNs) {
45
+ t.doesNotThrow(() => fastURI.resolve('uri://base/', uri), `${uri} does not throw as a relative reference`)
46
+ t.doesNotThrow(() => fastURI.resolve(uri, ''), `${uri} does not throw as a base URI`)
47
+ }
48
+ // resolve preserves the malformed scheme-specific input rather than surfacing
49
+ // an uncaught 'URN without nid cannot be serialized' error (matches upstream uri-js)
50
+ t.equal(fastURI.resolve('uri://base/', 'urn:'), 'urn:', 'malformed relative URN is preserved')
51
+ t.equal(fastURI.resolve('URN:', ''), 'urn:', 'scheme case is normalized')
52
+ t.equal(fastURI.resolve('uri://base/', 'urn:%66oo:bar'), 'urn:foo:bar', 'percent-encoding is decoded')
53
+ t.end()
54
+ })
55
+
56
+ test('valid URNs retain their existing behavior', (t) => {
57
+ t.equal(fastURI.normalize('URN:FOO:a123,456'), 'urn:foo:a123,456')
58
+ t.equal(fastURI.equal('urn:foo:a123,456', 'URN:FOO:a123,456', {}), true)
59
+ t.equal(fastURI.resolve('uri://base/', 'urn:'), 'urn:', 'default resolve behavior is unchanged')
60
+ t.end()
61
+ })
@@ -150,10 +150,12 @@ test('URI parse', (t) => {
150
150
  t.equal(components.query, undefined, 'query')
151
151
  t.equal(components.fragment, '%0D', 'fragment')
152
152
 
153
- // malformed percent-encoded fragment must not throw
153
+ // a fragment whose decoded bytes are not valid UTF-8 is still valid
154
+ // percent-encoding (RFC 3986 §2.1 is byte-level), so it is preserved as-is
155
+ // rather than being flagged as malformed
154
156
  components = fastURI.parse('http://example.com/#%E0%A4A')
155
- t.equal(components.error, 'URI malformed', 'malformed fragment errors')
156
- t.equal(components.fragment, '%E0%A4A', 'malformed fragment is preserved')
157
+ t.equal(components.error, undefined, 'valid percent-encoding is not flagged as malformed')
158
+ t.equal(components.fragment, '%E0%A4A', 'fragment is preserved')
157
159
 
158
160
  // all
159
161
  components = fastURI.parse('uri://user:pass@example.com:123/one/two.three?q1=a1&q2=a2#body')
@@ -212,6 +214,7 @@ test('URI parse', (t) => {
212
214
  // invalid IPv6
213
215
  components = fastURI.parse('//[2001:dbZ::7]')
214
216
  t.equal(components.host, '[2001:dbz::7]')
217
+ t.equal(components.error, 'URI host is malformed.')
215
218
 
216
219
  // mixed IPv4address & IPv6address
217
220
  components = fastURI.parse('//[::ffff:129.144.52.38]')
@@ -316,6 +319,7 @@ test('URI parse', (t) => {
316
319
 
317
320
  components = fastURI.parse('//[2606:2800:220:1:248:1893:25c8:1946:43209]')
318
321
  t.equal(components.host, '[2606:2800:220:1:248:1893:25c8:1946:43209]')
322
+ t.equal(components.error, 'URI host is malformed.')
319
323
 
320
324
  components = fastURI.parse('urn:foo:|\\24fpl')
321
325
  t.equal(components.error, 'URN can not be parsed.')
@@ -0,0 +1,33 @@
1
+ 'use strict'
2
+
3
+ const test = require('tape')
4
+ const fastURI = require('..')
5
+
6
+ test('query percent-encoding is normalized (RFC 3986 §6.2.2)', (t) => {
7
+ // hex digits are uppercased, like the path component already does
8
+ t.equal(fastURI.normalize('x://h/p?a=%2a'), 'x://h/p?a=%2A', 'uppercases hex in query')
9
+ // unreserved characters are decoded
10
+ t.equal(fastURI.normalize('x://h/p?a=%7e%2e'), 'x://h/p?a=~.', 'decodes unreserved in query')
11
+ // reserved characters stay percent-encoded
12
+ t.equal(fastURI.normalize('x://h/p?a=%2F'), 'x://h/p?a=%2F', 'keeps reserved encoded in query')
13
+ // raw characters that are not allowed are percent-encoded
14
+ t.equal(fastURI.normalize('x://h/p?a b'), 'x://h/p?a%20b', 'encodes a raw space in query')
15
+ t.equal(fastURI.normalize('x://h/p?café'), 'x://h/p?caf%C3%A9', 'encodes raw non-ASCII in query')
16
+ // `?` is allowed unencoded in a query
17
+ t.equal(fastURI.normalize('x://h/p?a?b'), 'x://h/p?a?b', 'keeps `?` raw in query')
18
+
19
+ t.end()
20
+ })
21
+
22
+ test('fragment percent-encoding is normalized without decoding reserved characters', (t) => {
23
+ // reserved characters must NOT be decoded — `%2F` is not `/`
24
+ t.equal(fastURI.normalize('x://h/p#a%2Fb%2A'), 'x://h/p#a%2Fb%2A', 'keeps reserved encoded in fragment')
25
+ // hex is uppercased and unreserved is decoded
26
+ t.equal(fastURI.normalize('x://h/p#a%2a%7e'), 'x://h/p#a%2A~', 'uppercases hex and decodes unreserved in fragment')
27
+ // raw characters are still encoded
28
+ t.equal(fastURI.normalize('x://h/p#a b'), 'x://h/p#a%20b', 'encodes a raw space in fragment')
29
+ // `?` and `/` are allowed unencoded in a fragment
30
+ t.equal(fastURI.normalize('x://h/p#f?x/y'), 'x://h/p#f?x/y', 'keeps `?` and `/` raw in fragment')
31
+
32
+ t.end()
33
+ })
@@ -0,0 +1,109 @@
1
+ 'use strict'
2
+
3
+ const test = require('tape')
4
+ const fastURI = require('..')
5
+
6
+ const PATH_RESERVED = "!$&'()*+,;=:@/"
7
+
8
+ function percentEncode (character, lowerCase) {
9
+ const hex = character.charCodeAt(0).toString(16).padStart(2, '0')
10
+ return '%' + (lowerCase ? hex : hex.toUpperCase())
11
+ }
12
+
13
+ test('normalize preserves literal and escaped reserved path characters', (t) => {
14
+ t.equal(
15
+ fastURI.normalize('http://example.com/a;b'),
16
+ 'http://example.com/a;b',
17
+ 'literal semicolon remains literal'
18
+ )
19
+ t.equal(
20
+ fastURI.normalize('http://example.com/a%3ab'),
21
+ 'http://example.com/a%3Ab',
22
+ 'escaped colon remains escaped and its hex is uppercased'
23
+ )
24
+
25
+ for (const character of PATH_RESERVED) {
26
+ const literal = `http://example.com/a${character}b`
27
+ const escaped = `http://example.com/a${percentEncode(character, true)}b`
28
+ const normalizedEscape = `http://example.com/a${percentEncode(character, false)}b`
29
+
30
+ t.equal(fastURI.normalize(literal), literal, `preserves literal ${character}`)
31
+ t.equal(fastURI.normalize(escaped), normalizedEscape, `preserves escaped ${character}`)
32
+ }
33
+
34
+ t.equal(
35
+ fastURI.normalize('http://example.com/a/./café'),
36
+ 'http://example.com/a/caf%C3%A9',
37
+ 'removes real dot segments and UTF-8 encodes raw non-ASCII'
38
+ )
39
+ t.equal(
40
+ fastURI.normalize('http://example.com/a/%2e%2e/b'),
41
+ 'http://example.com/a/%2E%2E/b',
42
+ 'preserves escaped dots as path data'
43
+ )
44
+ t.equal(
45
+ fastURI.normalize('http://example.com/Kſ'),
46
+ 'http://example.com/%E2%84%AA%C5%BF',
47
+ 'UTF-8 encodes Unicode characters that case-fold to ASCII'
48
+ )
49
+ t.end()
50
+ })
51
+
52
+ test('serialize uses the RFC 3986 path character set without opening escapes', (t) => {
53
+ const rawPath = `/a${PATH_RESERVED}b`
54
+ t.equal(
55
+ fastURI.serialize({ scheme: 'http', host: 'example.com', path: rawPath }),
56
+ `http://example.com${rawPath}`,
57
+ 'keeps all legal raw path characters'
58
+ )
59
+
60
+ const escapedPath = Array.from(PATH_RESERVED, (character) => percentEncode(character, true)).join('')
61
+ const normalizedEscapedPath = Array.from(PATH_RESERVED, (character) => percentEncode(character, false)).join('')
62
+ t.equal(
63
+ fastURI.serialize({ scheme: 'http', host: 'example.com', path: '/' + escapedPath }),
64
+ 'http://example.com/' + normalizedEscapedPath,
65
+ 'uppercases valid escapes without decoding reserved characters'
66
+ )
67
+
68
+ t.equal(
69
+ fastURI.serialize({ scheme: 'http', host: 'example.com', path: '/a?b#c[d]' }),
70
+ 'http://example.com/a%3Fb%23c%5Bd%5D',
71
+ 'encodes characters that would leave the path component'
72
+ )
73
+ t.equal(
74
+ fastURI.serialize({ path: 'a:b/c:d' }),
75
+ 'a%3Ab/c:d',
76
+ 'only escapes a colon where path-noscheme requires it'
77
+ )
78
+ t.equal(
79
+ fastURI.serialize({ path: './a:b/c:d' }),
80
+ 'a%3Ab/c:d',
81
+ 'escapes a first-segment colon exposed by dot-segment removal'
82
+ )
83
+ t.equal(
84
+ fastURI.normalize('./a:b'),
85
+ 'a%3Ab',
86
+ 'normalization keeps a relative path from becoming a scheme'
87
+ )
88
+ t.equal(
89
+ fastURI.serialize({ path: '/Kſ' }),
90
+ '/%E2%84%AA%C5%BF',
91
+ 'UTF-8 encodes Unicode characters that case-fold to ASCII'
92
+ )
93
+ t.end()
94
+ })
95
+
96
+ test('equal distinguishes escaped reserved path data from literal syntax', (t) => {
97
+ for (const character of PATH_RESERVED) {
98
+ const literal = `http://example.com/a${character}b`
99
+ const escaped = `http://example.com/a${percentEncode(character, false)}b`
100
+ t.equal(fastURI.equal(literal, escaped, {}), false, `distinguishes literal and escaped ${character}`)
101
+ }
102
+
103
+ t.equal(
104
+ fastURI.equal('./a:b', 'a:b', {}),
105
+ false,
106
+ 'does not equate a relative path with an absolute URI after removing dot segments'
107
+ )
108
+ t.end()
109
+ })
@@ -0,0 +1,124 @@
1
+ 'use strict'
2
+
3
+ const test = require('tape')
4
+ const fastURI = require('..')
5
+
6
+ const MALFORMED_SCHEME_ERROR = 'URI scheme is malformed.'
7
+
8
+ const malformedSchemes = [
9
+ '%2f%2fevil.example:/pwn',
10
+ '%u002f%u002fevil.example:/pwn',
11
+ '%0d%0aSet-Cookie:%20sid=attacker:/p',
12
+ 'foo%3Abar:value',
13
+ 'foo%2Fbar:value',
14
+ '1http://example.com/',
15
+ 'foo_bar:value',
16
+ 'éxample:value',
17
+ 'Kttp://example.com/',
18
+ 'ſcheme:value'
19
+ ]
20
+
21
+ test('parse validates the decoded scheme against RFC 3986', (t) => {
22
+ const validSchemes = [
23
+ ['a:value', 'a'],
24
+ ['HTTP://example.com/', 'http'],
25
+ ['a1+.-:value', 'a1+.-'],
26
+ ['%4Aavascript:alert(1)', 'javascript'],
27
+ ['foo%2Bbar:value', 'foo+bar'],
28
+ ['%u006Aavascript:1', 'javascript'],
29
+ ['ht%74ps://example.com/', 'https']
30
+ ]
31
+
32
+ for (const [uri, scheme] of validSchemes) {
33
+ const parsed = fastURI.parse(uri)
34
+ t.equal(parsed.error, undefined, uri)
35
+ t.equal(parsed.scheme, scheme, uri + ' scheme')
36
+ }
37
+
38
+ for (const uri of malformedSchemes) {
39
+ const parsed = fastURI.parse(uri)
40
+ t.equal(parsed.error, MALFORMED_SCHEME_ERROR, uri)
41
+ }
42
+ t.end()
43
+ })
44
+
45
+ test('decoded schemes select their scheme handlers', (t) => {
46
+ t.equal(
47
+ fastURI.normalize('ht%74ps://example.com:443'),
48
+ 'https://example.com/',
49
+ 'HTTP normalization runs after decoding the scheme'
50
+ )
51
+
52
+ // 3.x has no mailto scheme handler (added in v4); use the ws handler to
53
+ // verify that a percent-encoded scheme still selects its scheme handler
54
+ const ws = fastURI.parse('we%62socket://example.com/')
55
+ t.equal(ws.scheme, 'websocket', 'ws parsing runs after decoding the scheme')
56
+ t.end()
57
+ })
58
+
59
+ test('normalize preserves schemes that decode to invalid identifiers', (t) => {
60
+ for (const uri of malformedSchemes) {
61
+ t.equal(fastURI.normalize(uri), uri, uri)
62
+ }
63
+ t.end()
64
+ })
65
+
66
+ test('scheme normalization cannot introduce authority or control delimiters', (t) => {
67
+ const authority = '%2f%2fevil.example:/pwn'
68
+ const crlf = '%0d%0aSet-Cookie:%20sid=attacker:/p'
69
+
70
+ t.equal(fastURI.parse(authority).host, undefined, 'original input has no authority')
71
+ t.equal(fastURI.normalize(authority), authority, 'normalization does not create an authority')
72
+ t.equal(fastURI.normalize(crlf), crlf, 'normalization does not emit raw CRLF')
73
+ t.equal(fastURI.normalize(crlf).includes('\r\n'), false, 'normalized output contains no raw CRLF')
74
+ t.end()
75
+ })
76
+
77
+ test('equal returns false for malformed decoded schemes', (t) => {
78
+ for (const uri of malformedSchemes) {
79
+ t.equal(fastURI.equal(uri, uri, {}), false, uri)
80
+ }
81
+ t.end()
82
+ })
83
+
84
+ test('resolve rejects malformed decoded schemes in either input', (t) => {
85
+ t.throws(
86
+ () => fastURI.resolve('%2f%2fevil.example:/base', 'child'),
87
+ /URI scheme is malformed\./,
88
+ 'malformed base'
89
+ )
90
+ t.throws(
91
+ () => fastURI.resolve('https://allowed.example/app/', '%2f%2fevil.example:/pwn'),
92
+ /URI scheme is malformed\./,
93
+ 'malformed relative reference'
94
+ )
95
+ t.end()
96
+ })
97
+
98
+ test('serialize validates decoded component schemes', (t) => {
99
+ t.equal(
100
+ fastURI.serialize({ scheme: 'foo%2Bbar', path: 'value' }),
101
+ 'foo+bar:value',
102
+ 'valid decoded scheme is serialized'
103
+ )
104
+ t.throws(
105
+ () => fastURI.serialize({ scheme: '//evil.example', path: '/pwn' }),
106
+ /URI scheme is malformed\./,
107
+ 'raw invalid scheme'
108
+ )
109
+ t.throws(
110
+ () => fastURI.serialize({ scheme: '%2f%2fevil.example', path: '/pwn' }),
111
+ /URI scheme is malformed\./,
112
+ 'encoded invalid scheme'
113
+ )
114
+ t.equal(
115
+ fastURI.equal(
116
+ { scheme: '%2f%2fevil.example', path: '/pwn' },
117
+ { scheme: '%2f%2fevil.example', path: '/pwn' },
118
+ {}
119
+ ),
120
+ false,
121
+ 'equality fails closed for malformed component objects'
122
+ )
123
+ t.end()
124
+ })