identifier-js 0.0.5 → 0.0.7

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
@@ -5,7 +5,7 @@ const patterns = new Map();
5
5
  // RFC3986/RFC3987 common rules + https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2:~:text=DNS%29%2E-,A,of%20%5BRFC1123%5D%2E
6
6
  const commonRules = {
7
7
  scheme: '[a-zA-Z][a-zA-Z0-9+.-]*',
8
- port: '(?:0|[1-9]\\d{0,3}|[1-5]\\d{4}|6[0-4]\\d{3}|65[0-4]\\d{2}|655[0-2]\\d|6553[0-5])',
8
+ port: '(?:0|[1-9]\\d{0,3}|[1-5]\\d{4}|6[0-4]\\d{3}|65[0-4]\\d{2}|655[0-2]\\d|6553[0-5])?',
9
9
  IP_literal: '\\[(?:{IPv6address}|{IPvFuture})\\]',
10
10
  IPv6address: '(?:(?:{h16}:){6}{ls32}|::(?:{h16}:){5}{ls32}|(?:(?:{h16})?)::(?:{h16}:){4}{ls32}|(?:(?:{h16}:)?{h16})?::(?:{h16}:){3}{ls32}|(?:(?:{h16}:){0,2}{h16})?::(?:{h16}:){2}{ls32}|(?:(?:{h16}:){0,3}{h16})?::(?:{h16}:){1}{ls32}|(?:(?:{h16}:){0,4}{h16})?::{ls32}|(?:(?:{h16}:){0,5}{h16})?::{h16}|(?:(?:{h16}:){0,6}{h16})?::)',
11
11
  ls32: '(?:{h16}:{h16}|{IPv4address})',
@@ -35,7 +35,7 @@ const uriRules = {
35
35
  userinfo: '(?:{unreserved}|{pct_encoded}|{sub_delims}|:)*',
36
36
  host: '(?:{IP_literal}|{IPv4address}|{reg_name})',
37
37
  a_label: '(?:{alpha_digit})(?:(?:{alpha_digit}|-){0,61}(?:{alpha_digit}))?',
38
- reg_name: "(?:(?=.{1,255}(?:[:/?#]|$))(?:(?:{a_label})(?:\\.{a_label}))*)",
38
+ reg_name: "(?:(?=.{1,255}(?:[:/?#]|$))(?:{a_label})(?:\\.{a_label})*)",
39
39
  path: '(?:{path_abempty}|{path_absolute}|{path_noscheme}|{path_rootless}|{path_empty})',
40
40
  path_abempty: '(?:\/{segment})*',
41
41
  path_absolute: '\/(?:{segment_nz}(?:\/{segment})*)?',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "identifier-js",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "description": "A RFC3986 / RFC3987 compliant fast parser/validator/resolver/composer for NodeJS and browser.",
5
5
  "keywords": [
6
6
  "IRI",
@@ -0,0 +1,21 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import id from '../index.js';
3
+ // not yet done
4
+ const resolveTests = [
5
+ ['', 'hTTp://example.com/b/c/d/e', 'http://example.com/b/c/d/e'], // scheme to lowercase
6
+ ['', 'http://eXaMpLe.com/b/c/d/e', 'http://example.com/b/c/d/e'], // authority to lowercase
7
+ ['', 'http://example.com/b%2Ec/d/e', 'http://example.com/b.c/d/e'], // Unnecessary encoding segment
8
+ ['', 'http://example.com/b%2Fc/d/e', 'http://example.com/b%2Fc/d/e'], // Necessary encoding segment
9
+ ['', 'http://example.com/b%2fc/d/e', 'http://example.com/b%2Fc/d/e'], // Case normalization of encoding segment
10
+ ['', 'http://example.com/b?c%2Fd%3Fe', 'http://example.com/b?c/d?e'], // Unnecessary encoding query
11
+ ['#c%2Fd%3Fe', 'http://example.com/b', 'http://example.com/b#c/d?e'], // Unnecessary encoding fragment
12
+ ];
13
+
14
+ describe('resolveReference', () => {
15
+ resolveTests.forEach(([reference, base, expected]) => {
16
+ test(`resolveReference('${reference}', '${base}') === '${expected}'`, () => {
17
+ const subject = id.normalizeReference(reference, base);
18
+ expect(subject).to.equal(expected);
19
+ });
20
+ });
21
+ });
@@ -44,6 +44,7 @@ const refs = {
44
44
  "g?y/../x" : "http://a/b/c/g?y/../x",
45
45
  "g#s/./x" : "http://a/b/c/g#s/./x",
46
46
  "g#s/../x" : "http://a/b/c/g#s/../x",
47
+ "http:g" : "http:g",
47
48
  }
48
49
 
49
50
  describe('resolveReference', () => {
@@ -4,13 +4,6 @@ import id from '../index.js';
4
4
  const resolveTests = [
5
5
  ['urn:some:ip:prop', 'urn:some:ip:prop', 'urn:some:ip:prop'],
6
6
  ['urn:some:ip:prop', 'urn:some:other:prop', 'urn:some:ip:prop'],
7
- ['', 'hTTp://example.com/b/c/d/e', 'http://example.com/b/c/d/e'], // scheme to lowercase
8
- ['', 'http://eXaMpLe.com/b/c/d/e', 'http://example.com/b/c/d/e'], // authority to lowercase
9
- ['', 'http://example.com/b%2Ec/d/e', 'http://example.com/b.c/d/e'], // Unnecessary encoding segment
10
- ['', 'http://example.com/b%2Fc/d/e', 'http://example.com/b%2Fc/d/e'], // Necessary encoding segment
11
- ['', 'http://example.com/b%2fc/d/e', 'http://example.com/b%2Fc/d/e'], // Case normalization of encoding segment
12
- ['', 'http://example.com/b?c%2Fd%3Fe', 'http://example.com/b?c/d?e'], // Unnecessary encoding query
13
- ['#c%2Fd%3Fe', 'http://example.com/b', 'http://example.com/b#c/d?e'], // Unnecessary encoding fragment
14
7
  ];
15
8
 
16
9
  describe('resolveReference', () => {
@@ -0,0 +1,94 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import id from '../index.js';
3
+
4
+ describe('isUri with hostnames', () => {
5
+ test('Valid label - alphanumeric', () => {
6
+ expect(id.isUri('https://example')).to.equal(true);
7
+ });
8
+
9
+ test('Valid label - hyphen in middle', () => {
10
+ expect(id.isUri('https://exa-mple')).to.equal(true);
11
+ });
12
+
13
+ test('Invalid label - hyphen at start', () => {
14
+ expect(() => id.isUri('https://-example')).to.throw(Error, 'Invalid URI: https://-example');
15
+ });
16
+
17
+ test('Invalid label - hyphen at end', () => {
18
+ expect(() => id.isUri('https://example-')).to.throw(Error, 'Invalid URI: https://example-');
19
+ });
20
+
21
+ test('Valid multiple labels', () => {
22
+ expect(id.isUri('https://example-domain.com')).to.equal(true);
23
+ });
24
+
25
+ test('Invalid - leading dot', () => {
26
+ expect(() => id.isUri('https://.example.com')).to.throw(Error, 'Invalid URI: https://.example.com');
27
+ });
28
+
29
+ test('Invalid - trailing dot', () => {
30
+ expect(() => id.isUri('https://example.com.')).to.throw(Error, 'Invalid URI: https://example.com.');
31
+ });
32
+
33
+ test('Invalid - consecutive dots', () => {
34
+ expect(() => id.isUri('https://example..com')).to.throw(Error, 'Invalid URI: https://example..com');
35
+ });
36
+
37
+ test('Invalid - unicode character', () => {
38
+ expect(() => id.isUri('https://exämple')).to.throw(Error, 'Invalid URI: https://exämple');
39
+ });
40
+ });
41
+
42
+ describe('isIri with hostnames', () => {
43
+ test('Valid label - alphanumeric', () => {
44
+ expect(id.isIri('https://example')).to.equal(true);
45
+ });
46
+
47
+ test('Valid label - hyphen in middle', () => {
48
+ expect(id.isIri('https://exa-mple')).to.equal(true);
49
+ });
50
+
51
+ test('Invalid label - hyphen at start', () => {
52
+ expect(() => id.isIri('https://-example')).to.throw(Error, 'Invalid IRI: https://-example');
53
+ });
54
+
55
+ test('Invalid label - hyphen at end', () => {
56
+ expect(() => id.isIri('https://example-')).to.throw(Error, 'Invalid IRI: https://example-');
57
+ });
58
+
59
+ test('Valid unicode label - Latin extended', () => {
60
+ expect(id.isIri('https://exämple')).to.equal(true);
61
+ });
62
+
63
+ test('Valid unicode label - Chinese', () => {
64
+ expect(id.isIri('https://例子')).to.equal(true);
65
+ });
66
+
67
+ test('Valid unicode label - Hindi', () => {
68
+ expect(id.isIri('https://उदाहरण')).to.equal(true);
69
+ });
70
+
71
+ test('Valid unicode label - Japanese', () => {
72
+ expect(id.isIri('https://例え.テスト')).to.equal(true);
73
+ });
74
+
75
+ test('Valid label with middle dot', () => {
76
+ expect(id.isIri('https://exa·mple')).to.equal(true);
77
+ });
78
+
79
+ test('Invalid - leading dot', () => {
80
+ expect(() => id.isIri('https://.example')).to.throw(Error, 'Invalid IRI: https://.example');
81
+ });
82
+
83
+ test('Invalid - trailing dot', () => {
84
+ expect(() => id.isIri('https://example.')).to.throw(Error, 'Invalid IRI: https://example.');
85
+ });
86
+
87
+ test('Invalid - consecutive dots', () => {
88
+ expect(() => id.isIri('https://example..test')).to.throw(Error, 'Invalid IRI: https://example..test');
89
+ });
90
+
91
+ test('Invalid - emoji in label', () => {
92
+ expect(() => id.isIri('https://example😀')).to.throw(Error, 'Invalid IRI: https://example😀');
93
+ });
94
+ });
@@ -0,0 +1,246 @@
1
+ import { describe, expect, test } from 'vitest';
2
+ import id from '../index.js';
3
+
4
+ describe('isUri – IPv4 host validation', () => {
5
+ test('Valid full IPv4 address', () => {
6
+ expect(id.isUri('https://1.2.3.4')).to.equal(true);
7
+ });
8
+
9
+ test('Invalid IPv4 address, but valid as hostname', () => {
10
+ expect(id.isUri('https://999.999.999.999')).to.equal(true);
11
+ });
12
+ });
13
+
14
+ describe('isIri – IPv4 host validation', () => {
15
+ test('Valid full IPv4 address', () => {
16
+ expect(id.isIri('https://1.2.3.4')).to.equal(true);
17
+ });
18
+
19
+ test('Invalid IPv4 address, but valid as hostname', () => {
20
+ expect(id.isIri('https://999.999.999.999')).to.equal(true);
21
+ });
22
+ });
23
+
24
+ describe('isUri – IPv6 host validation', () => {
25
+ test('Valid full (uncompressed) IPv6 address', () => {
26
+ expect(id.isUri('https://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]')).to.equal(true);
27
+ });
28
+
29
+ test('Valid full IPv6 address (no zero compression)', () => {
30
+ expect(id.isUri('https://[2001:db8:85a3:0:0:8a2e:370:7334]')).to.equal(true);
31
+ });
32
+
33
+ test('Valid IPv6 address with zero compression (::)', () => {
34
+ expect(id.isUri('https://[2001:db8:85a3::8a2e:370:7334]')).to.equal(true);
35
+ });
36
+
37
+ test('Valid loopback IPv6 address (::1)', () => {
38
+ expect(id.isUri('https://[::1]')).to.equal(true);
39
+ });
40
+
41
+ test('Valid unspecified IPv6 address (::)', () => {
42
+ expect(id.isUri('https://[::]')).to.equal(true);
43
+ });
44
+
45
+ test('Valid compressed IPv6 address (trailing compression)', () => {
46
+ expect(id.isUri('https://[2001:db8::]')).to.equal(true);
47
+ });
48
+
49
+ test('Valid IPv6 with embedded IPv4 (IPv4-mapped)', () => {
50
+ expect(id.isUri('https://[::ffff:192.168.1.1]')).to.equal(true);
51
+ });
52
+
53
+ test('Valid IPv6 with embedded IPv4 (mixed notation)', () => {
54
+ expect(id.isUri('https://[2001:db8::192.168.1.1]')).to.equal(true);
55
+ });
56
+
57
+ test('Valid link-local IPv6 address', () => {
58
+ expect(id.isUri('https://[fe80::1]')).to.equal(true);
59
+ });
60
+
61
+ test('Valid maximum-length IPv6 address', () => {
62
+ expect(id.isUri('https://[ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]')).to.equal(true);
63
+ });
64
+
65
+ test('Invalid IPv6 – excessive colon sequence', () => {
66
+ expect(() => id.isUri('https://[2001:db8:85a3:::8a2e:370:7334]')).to.throw(Error, 'Invalid URI: https://[2001:db8:85a3:::8a2e:370:7334]');
67
+ });
68
+
69
+ test('Invalid IPv6 – multiple zero compressions (::)', () => {
70
+ expect(() => id.isUri('https://[2001:db8:85a3::8a2e::7334]')).to.throw(Error, 'Invalid URI: https://[2001:db8:85a3::8a2e::7334]');
71
+ });
72
+
73
+ test('Invalid IPv6 – segment exceeds 16-bit limit', () => {
74
+ expect(() => id.isUri('https://[12345::]')).to.throw(Error, 'Invalid URI: https://[12345::]');
75
+ });
76
+
77
+ test('Invalid IPv6 – insufficient segments without compression', () => {
78
+ expect(() => id.isUri('https://[2001:db8:85a3:8a2e:370:7334]')).to.throw(Error, 'Invalid URI: https://[2001:db8:85a3:8a2e:370:7334]');
79
+ });
80
+
81
+ test('Invalid IPv6 – too many segments', () => {
82
+ expect(() => id.isUri('https://[2001:db8:85a3:0000:0000:8a2e:0370:7334:1234]')).to.throw(Error, 'Invalid URI: https://[2001:db8:85a3:0000:0000:8a2e:0370:7334:1234]');
83
+ });
84
+
85
+ test('Invalid IPv6 – embedded IPv4 out of range', () => {
86
+ expect(() => id.isUri('https://[::ffff:999.168.1.1]')).to.throw(Error, 'Invalid URI: https://[::ffff:999.168.1.1]');
87
+ });
88
+
89
+ test('Invalid IPv6 – non-hexadecimal character in segment', () => {
90
+ expect(() => id.isUri('https://[2001:db8::g1]')).to.throw(Error, 'Invalid URI: https://[2001:db8::g1]');
91
+ });
92
+
93
+ test('Invalid IPv6 – leading colon without compression', () => {
94
+ expect(() => id.isUri('https://[:2001:db8::1]')).to.throw(Error, 'Invalid URI: https://[:2001:db8::1]');
95
+ });
96
+
97
+ test('Invalid IPv6 – trailing colon without compression', () => {
98
+ expect(() => id.isUri('https://[2001:db8::1:]')).to.throw(Error, 'Invalid URI: https://[2001:db8::1:]');
99
+ });
100
+
101
+ test('Invalid IPv6 – empty address literal', () => {
102
+ expect(() => id.isUri('https://[]')).to.throw(Error, 'Invalid URI: https://[]');
103
+ });
104
+ });
105
+
106
+ describe('isIri – IPv6 host validation', () => {
107
+ test('Valid full (uncompressed) IPv6 address', () => {
108
+ expect(id.isIri('https://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]')).to.equal(true);
109
+ });
110
+
111
+ test('Valid full IPv6 address (no zero compression)', () => {
112
+ expect(id.isIri('https://[2001:db8:85a3:0:0:8a2e:370:7334]')).to.equal(true);
113
+ });
114
+
115
+ test('Valid IPv6 address with zero compression (::)', () => {
116
+ expect(id.isIri('https://[2001:db8:85a3::8a2e:370:7334]')).to.equal(true);
117
+ });
118
+
119
+ test('Valid loopback IPv6 address (::1)', () => {
120
+ expect(id.isIri('https://[::1]')).to.equal(true);
121
+ });
122
+
123
+ test('Valid unspecified IPv6 address (::)', () => {
124
+ expect(id.isIri('https://[::]')).to.equal(true);
125
+ });
126
+
127
+ test('Valid compressed IPv6 address (trailing compression)', () => {
128
+ expect(id.isIri('https://[2001:db8::]')).to.equal(true);
129
+ });
130
+
131
+ test('Valid IPv6 with embedded IPv4 (IPv4-mapped)', () => {
132
+ expect(id.isIri('https://[::ffff:192.168.1.1]')).to.equal(true);
133
+ });
134
+
135
+ test('Valid IPv6 with embedded IPv4 (mixed notation)', () => {
136
+ expect(id.isIri('https://[2001:db8::192.168.1.1]')).to.equal(true);
137
+ });
138
+
139
+ test('Valid link-local IPv6 address', () => {
140
+ expect(id.isIri('https://[fe80::1]')).to.equal(true);
141
+ });
142
+
143
+ test('Valid maximum-length IPv6 address', () => {
144
+ expect(id.isIri('https://[ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]')).to.equal(true);
145
+ });
146
+
147
+ test('Invalid IPv6 – excessive colon sequence', () => {
148
+ expect(() => id.isIri('https://[2001:db8:85a3:::8a2e:370:7334]')).to.throw(Error, 'Invalid IRI: https://[2001:db8:85a3:::8a2e:370:7334]');
149
+ });
150
+
151
+ test('Invalid IPv6 – multiple zero compressions (::)', () => {
152
+ expect(() => id.isIri('https://[2001:db8:85a3::8a2e::7334]')).to.throw(Error, 'Invalid IRI: https://[2001:db8:85a3::8a2e::7334]');
153
+ });
154
+
155
+ test('Invalid IPv6 – segment exceeds 16-bit limit', () => {
156
+ expect(() => id.isIri('https://[12345::]')).to.throw(Error, 'Invalid IRI: https://[12345::]');
157
+ });
158
+
159
+ test('Invalid IPv6 – insufficient segments without compression', () => {
160
+ expect(() => id.isIri('https://[2001:db8:85a3:8a2e:370:7334]')).to.throw(Error, 'Invalid IRI: https://[2001:db8:85a3:8a2e:370:7334]');
161
+ });
162
+
163
+ test('Invalid IPv6 – too many segments', () => {
164
+ expect(() => id.isIri('https://[2001:db8:85a3:0000:0000:8a2e:0370:7334:1234]')).to.throw(Error, 'Invalid IRI: https://[2001:db8:85a3:0000:0000:8a2e:0370:7334:1234]');
165
+ });
166
+
167
+ test('Invalid IPv6 – embedded IPv4 out of range', () => {
168
+ expect(() => id.isIri('https://[::ffff:999.168.1.1]')).to.throw(Error, 'Invalid IRI: https://[::ffff:999.168.1.1]');
169
+ });
170
+
171
+ test('Invalid IPv6 – non-hexadecimal character in segment', () => {
172
+ expect(() => id.isIri('https://[2001:db8::g1]')).to.throw(Error, 'Invalid IRI: https://[2001:db8::g1]');
173
+ });
174
+
175
+ test('Invalid IPv6 – leading colon without compression', () => {
176
+ expect(() => id.isIri('https://[:2001:db8::1]')).to.throw(Error, 'Invalid IRI: https://[:2001:db8::1]');
177
+ });
178
+
179
+ test('Invalid IPv6 – trailing colon without compression', () => {
180
+ expect(() => id.isIri('https://[2001:db8::1:]')).to.throw(Error, 'Invalid IRI: https://[2001:db8::1:]');
181
+ });
182
+
183
+ test('Invalid IPv6 – empty address literal', () => {
184
+ expect(() => id.isIri('https://[]')).to.throw(Error, 'Invalid IRI: https://[]');
185
+ });
186
+ });
187
+
188
+ describe('isUri – port validation', () => {
189
+ test('Valid port', () => {
190
+ expect(id.isUri('https://example.com:80')).to.equal(true);
191
+ });
192
+
193
+ test('Valid port zero', () => {
194
+ expect(id.isUri('https://example.com:0')).to.equal(true);
195
+ });
196
+
197
+ test('Valid port at max edge', () => {
198
+ expect(id.isUri('https://example.com:65535')).to.equal(true);
199
+ });
200
+
201
+ test('Valid port empty', () => {
202
+ expect(id.isUri('https://example.com:/')).to.equal(true);
203
+ });
204
+
205
+ test('Invalid "space" port', () => {
206
+ expect(() => id.isUri('https://example.com: /')).to.throw(Error, 'Invalid URI: https://example.com: /');
207
+ });
208
+
209
+ test('Invalid port char', () => {
210
+ expect(() => id.isUri('https://example.com:ff')).to.throw(Error, 'Invalid URI: https://example.com:ff');
211
+ });
212
+
213
+ test('Invalid port beyond edge', () => {
214
+ expect(() => id.isUri('https://example.com:65536')).to.throw(Error, 'Invalid URI: https://example.com:65536');
215
+ });
216
+ });
217
+
218
+ describe('isIri – port validation', () => {
219
+ test('Valid port', () => {
220
+ expect(id.isIri('https://example.com:80')).to.equal(true);
221
+ });
222
+
223
+ test('Valid port zero', () => {
224
+ expect(id.isIri('https://example.com:0')).to.equal(true);
225
+ });
226
+
227
+ test('Valid port at max edge', () => {
228
+ expect(id.isIri('https://example.com:65535')).to.equal(true);
229
+ });
230
+
231
+ test('Valid port empty', () => {
232
+ expect(id.isIri('https://example.com:/')).to.equal(true);
233
+ });
234
+
235
+ test('Invalid "space" port', () => {
236
+ expect(() => id.isIri('https://example.com: /')).to.throw(Error, 'Invalid IRI: https://example.com: /');
237
+ });
238
+
239
+ test('Invalid port char', () => {
240
+ expect(() => id.isIri('https://example.com:ff')).to.throw(Error, 'Invalid IRI: https://example.com:ff');
241
+ });
242
+
243
+ test('Invalid port beyond edge', () => {
244
+ expect(() => id.isIri('https://example.com:65536')).to.throw(Error, 'Invalid IRI: https://example.com:65536');
245
+ });
246
+ });