char/avril

(wip) why'd you have to go and make things so complicaaaaated?

git clone https://git.t4t.associates/char/avril

Charlotte Somsnapshot: embed shared main and worker startup in the CLI688e96e

main
11.2 KiB201 linesraw
1mod common;
2
3use common::run;
4
5#[test]
6fn matches_components_and_returns_named_optional_and_numbered_groups() {
7    let output = run(
8        r#"
9        const p = new URLPattern({ protocol: 'https', hostname: ':sub.example.com',
10            pathname: '/books/:id(\\d+){/:slug}?', search: 'q=:term', hash: ':section' });
11        const url = 'https://api.example.com/books/42?q=rust#intro';
12        const result = p.exec(url);
13        console.log(p.protocol, p.username, p.password, p.hostname, p.port, p.pathname, p.search, p.hash, p.hasRegExpGroups);
14        console.log(p.test(url), p.test('https://api.example.com/books/no?q=rust#intro'));
15        console.log(JSON.stringify(result.inputs), result.hostname.groups.sub, result.pathname.groups.id,
16            Object.hasOwn(result.pathname.groups, 'slug'), result.pathname.groups.slug,
17            result.search.groups.term, result.hash.groups.section, result.port.groups[0]);
18        const second = p.exec('https://www.example.com/books/7/title?q=js#end');
19        console.log(second.pathname.groups.slug, result.pathname.groups.id, second !== result,
20            second.pathname !== result.pathname, second.pathname.groups !== result.pathname.groups);
21        const numbered = new URLPattern({pathname: '/(foo|bar)/*'}).exec({pathname:'/foo/rest/of/path'});
22        console.log(JSON.stringify(numbered.pathname));
23        console.log(Object.keys(result).join(','), Object.keys(result.pathname).join(','));
24        console.log(p.exec('https://api.example.com/nope'));
25        console.log(Object.prototype.toString.call(p), Object.getPrototypeOf(result) === Object.prototype,
26            Object.getPrototypeOf(result.pathname.groups) === Object.prototype);
27    "#,
28        &[],
29    );
30    assert_eq!(
31        String::from_utf8(output.stdout).unwrap(),
32        concat!(
33            "https * * :sub.example.com * /books/:id(\\d+)/:slug? q=:term :section true\n",
34            "true false\n",
35            "[\"https://api.example.com/books/42?q=rust#intro\"] api 42 true undefined rust intro \n",
36            "title 42 true true true\n",
37            "{\"groups\":{\"0\":\"foo\",\"1\":\"rest/of/path\"},\"input\":\"/foo/rest/of/path\"}\n",
38            "hash,hostname,inputs,password,pathname,port,protocol,search,username groups,input\n",
39            "null\n",
40            "[object URLPattern] true true\n",
41        )
42    );
43}
44
45#[test]
46fn accepts_constructor_overloads_relative_inputs_and_normalized_urls() {
47    let output = run(
48        r#"
49        const p = new URLPattern('./books/:id', 'https://example.com/store/index');
50        console.log(p.hostname, p.pathname, p.test('./books/42', 'https://example.com/store/index'));
51        console.log(JSON.stringify(p.exec('./books/42', 'https://example.com/store/index').inputs));
52        const init = { pathname: './books/:id', baseURL: 'https://example.com/store/index' };
53        console.log(new URLPattern(init).test('https://example.com/store/books/42'));
54        const international = new URLPattern('https://BÜCHER.example:443/café/:id');
55        console.log(international.hostname, international.port, international.pathname,
56            international.exec('https://xn--bcher-kva.example/caf%C3%A9/42').pathname.groups.id);
57        const ipv6 = new URLPattern({ protocol: 'https', hostname: '[\\:\\:1]', port: '443' });
58        console.log(ipv6.test('https://[::1]/'), ipv6.port);
59        console.log(new URLPattern(new URL('https://example.com/path')).test('https://example.com/path'));
60        console.log(new URLPattern().test(), new URLPattern(null).test(null), new URLPattern(undefined, undefined).test(undefined));
61        console.log(JSON.stringify(new URLPattern().exec().inputs));
62        console.log(new URLPattern({pathname:'/foo'}).test('/foo'), new URLPattern().test('http://['),
63            new URLPattern().exec({baseURL:'invalid'}));
64        const dictionary = { pathname: 42, extra: 'ignored' };
65        const r = new URLPattern({pathname:':id'}).exec(dictionary);
66        console.log(JSON.stringify(r.inputs), r.pathname.groups.id, r.inputs[0] !== dictionary);
67        console.log(new URLPattern(42, 'https://example.com/').pathname);
68    "#,
69        &[],
70    );
71    assert_eq!(
72        String::from_utf8(output.stdout).unwrap(),
73        concat!(
74            "example.com /store/books/:id true\n",
75            "[\"./books/42\",\"https://example.com/store/index\"]\n",
76            "true\n",
77            "xn--bcher-kva.example  /caf%C3%A9/:id 42\n",
78            "true \n",
79            "true\ntrue true true\n[{}]\n",
80            "false false null\n",
81            "[{\"pathname\":\"42\"}] 42 true\n",
82            "/42\n",
83        )
84    );
85}
86
87#[test]
88fn uses_ecmascript_regexps_unicode_sets_and_component_specific_case_rules() {
89    let output = run(
90        r#"
91        const p = new URLPattern({protocol:'((?=https)https)', pathname:'/((?!admin)[a-z]+)'});
92        console.log(p.test('https://example.com/public'), p.test('https://example.com/admin'), p.hasRegExpGroups);
93        const sets = new URLPattern({pathname:'/([[a-z]--a])'});
94        console.log(sets.test({pathname:'/a'}), sets.exec({pathname:'/z'}).pathname.groups[0]);
95        const intersection = new URLPattern({pathname:'/([\\d&&[0-1]])'});
96        console.log(intersection.test({pathname:'/0'}), intersection.test({pathname:'/3'}));
97        const unicode = new URLPattern({hash:':letter(\\p{Letter}+)' });
98        console.log(unicode.exec({hash:'abc'}).hash.groups.letter);
99        const opts = {ignoreCase: 'truthy'};
100        for (const component of ['protocol','username','password','hostname','pathname','search','hash']) {
101            const value = component === 'protocol' ? '(HTTPS)' : 'FOO';
102            const input = component === 'protocol' ? 'https' : 'foo';
103            console.log(component, new URLPattern({[component]:value}, opts).test({[component]:input}));
104        }
105        console.log(new URLPattern({pathname:'/x/:id?'}).hasRegExpGroups,
106            new URLPattern({pathname:'/x/:id(.*)'}).hasRegExpGroups,
107            new URLPattern({pathname:'/x/:id([a-z]+)'}).hasRegExpGroups);
108    "#,
109        &[],
110    );
111    assert_eq!(output.stdout, b"true false true\nfalse z\ntrue false\nabc\nprotocol false\nusername false\npassword false\nhostname true\npathname true\nsearch true\nhash true\nfalse false true\n");
112}
113
114#[test]
115fn validates_patterns_receivers_and_webidl_conversion_without_poisoning_later_calls() {
116    let output = run(
117        r#"
118        const typeError = fn => { try { fn(); } catch (e) { if (e instanceof TypeError) return; throw e; } throw new Error('Expected TypeError'); };
119        const p = new URLPattern();
120        for (const fn of [() => URLPattern(), () => new URLPattern('/relative'),
121            () => new URLPattern({pathname:'()'}), () => new URLPattern({pathname:'/(a(b))'}),
122            () => new URLPattern({pathname:'/(a{1,0})'}), () => new URLPattern({pathname:'/:x/:x'}),
123            () => new URLPattern({pathname:'\\'}), () => new URLPattern(Symbol()),
124            () => new URLPattern({}, 'https://example.com'), () => new URLPattern({}, null, {}),
125            () => new URLPattern({}, 1), () => new URLPattern({}, {}, {}),
126            () => p.test({}, 'https://example.com'), () => p.exec({}, ''), () => p.test(Symbol()),
127            () => URLPattern.prototype.test.call({}), () => URLPattern.prototype.exec.call({}),
128            () => Object.getOwnPropertyDescriptor(URLPattern.prototype,'pathname').get.call({}),
129            () => Object.getOwnPropertyDescriptor(URLPattern.prototype,'hasRegExpGroups').get.call({}),
130        ]) typeError(fn);
131        console.log(new URLPattern({pathname:'/x'}).test('https://example.com/x'));
132        const order = [];
133        const input = new Proxy({}, {get(_, key) {order.push(key); return undefined;}});
134        new URLPattern(input, {get ignoreCase() {order.push('ignoreCase'); return false;}});
135        console.log(order.join(','));
136        console.log(new URLPattern({pathname:'/\uD800'}).pathname);
137        const r = p.exec({pathname:'\uD800'}); console.log(r.inputs[0].pathname === '\uFFFD');
138        class Child extends URLPattern {}
139        console.log(new Child({pathname:'/x'}) instanceof Child, URLPattern.length, p.test.length, p.exec.length);
140        const descriptor = Object.getOwnPropertyDescriptor(URLPattern.prototype,'pathname');
141        console.log(descriptor.enumerable, descriptor.configurable, descriptor.set, descriptor.get.name);
142    "#,
143        &[],
144    );
145    assert_eq!(output.stdout, b"true\nbaseURL,hash,hostname,password,pathname,port,protocol,search,username,ignoreCase\n/%EF%BF%BD\ntrue\ntrue 0 0 0\ntrue true undefined get pathname\n");
146}
147
148#[test]
149fn captures_primordials_and_materializes_groups_without_prototype_setters() {
150    let output = run(
151        r#"
152        const p = new URLPattern({pathname:'/:__proto__/:constructor/:id'});
153        const re = RegExp.prototype, strings = String.prototype, weak = WeakMap.prototype;
154        const poison = () => {throw new Error('Poisoned intrinsic');};
155        globalThis.RegExp = poison; globalThis.URL = poison; globalThis.String = poison; globalThis.WeakMap = poison;
156        re.exec = poison; strings.toWellFormed = poison; weak.get = poison; weak.set = poison;
157        Object.defineProperty(Object.prototype,'id',{set:poison,configurable:true});
158        Object.defineProperty(Array.prototype,'1',{set:poison,configurable:true});
159        Object.defineProperty(p,'pathname',{value:'overridden'});
160        const r = p.exec('https://example.com/first/second/third');
161        const fresh = new URLPattern({protocol:'((?=https)https)', pathname:'/x/:id'});
162        const matched = fresh.exec('https://example.com/x/42');
163        delete Array.prototype[1]; delete Object.prototype.id;
164        console.log(r.pathname.groups.__proto__, r.pathname.groups.constructor, r.pathname.groups.id,
165            Object.getPrototypeOf(r.pathname.groups) === Object.prototype, Object.hasOwn(r.pathname.groups,'__proto__'));
166        console.log(fresh.test('https://example.com/x/7'), matched.pathname.groups.id,
167            new URLPattern({pathname:'/x'}).test('https://example.com/x'));
168    "#,
169        &[],
170    );
171    assert_eq!(
172        output.stdout,
173        b"first second third true true\ntrue 42 true\n"
174    );
175}
176
177#[test]
178fn native_protocol_checks_never_call_user_regexp_exec() {
179    let output = run(
180        r#"
181        const original = Object.getOwnPropertyDescriptor(RegExp.prototype, 'exec');
182        let calls = 0;
183        const build = () => [
184            new URLPattern({protocol:'(https)', pathname:'x y'}).pathname,
185            new URLPattern({protocol:'(foo)', pathname:'x y'}).pathname,
186        ].join('|');
187        const baseline = build();
188        for (const replacement of [() => {calls++; return {};}, () => {calls++; throw new Error('Poisoned exec');},
189            () => {calls++; new URLPattern({protocol:'(x)'}); return null;}]) {
190            Object.defineProperty(RegExp.prototype, 'exec', {value:replacement, writable:true, configurable:true});
191            if (build() !== baseline) throw new Error('User exec changed parsing');
192        }
193        Object.defineProperty(RegExp.prototype, 'exec', {get() {calls++; throw new Error('Poisoned exec getter');}, configurable:true});
194        if (build() !== baseline) throw new Error('User getter changed parsing');
195        Object.defineProperty(RegExp.prototype, 'exec', original);
196        console.log(baseline, calls);
197    "#,
198        &[],
199    );
200    assert_eq!(output.stdout, b"x%20y|x y 0\n");
201}