1function urlString({ scheme = "https",
2                     username = "username",
3                     password = "password",
4                     host = "host",
5                     port = "8000",
6                     pathname = "path",
7                     search = "query",
8                     hash = "fragment" }) {
9  return `${scheme}://${username}:${password}@${host}:${port}/${pathname}?${search}#${hash}`;
10}
11
12function urlRecord(scheme) {
13  return new URL(urlString({ scheme }));
14}
15
16for(const scheme of ["https", "wpt++"]) {
17  for(let i = 0; i < 0x20; i++) {
18    const stripped = i === 0x09 || i === 0x0A || i === 0x0D;
19
20    // It turns out that user agents are surprisingly similar for these ranges so generate fewer
21    // tests. If this is changed also change the logic for host below.
22    if (i !== 0 && i !== 0x1F && !stripped) {
23      continue;
24    }
25
26    const cpString = String.fromCodePoint(i);
27    const cpReference = "U+" + i.toString(16).toUpperCase().padStart(4, "0");
28
29    test(() => {
30      const expected = scheme === "https" ? (stripped ? "http" : "https") : (stripped ? "wpt--" : "wpt++");
31      const url = urlRecord(scheme);
32      url.protocol = String.fromCodePoint(i) + (scheme === "https" ? "http" : "wpt--");
33      assert_equals(url.protocol, expected + ":", "property");
34      assert_equals(url.href, urlString({ scheme: expected }), "href");
35    }, `Setting protocol with leading ${cpReference} (${scheme}:)`);
36
37    test(() => {
38      const expected = scheme === "https" ? (stripped ? "http" : "https") : (stripped ? "wpt--" : "wpt++");
39      const url = urlRecord(scheme);
40      url.protocol = (scheme === "https" ? "http" : "wpt--") + String.fromCodePoint(i);
41      assert_equals(url.protocol, expected + ":", "property");
42      assert_equals(url.href, urlString({ scheme: expected }), "href");
43    }, `Setting protocol with ${cpReference} before inserted colon (${scheme}:)`);
44
45    // Cannot test protocol with trailing as the algorithm inserts a colon before proceeding
46
47    // These do no stripping
48    for (const property of ["username", "password"]) {
49      for (const [type, expected, input] of [
50        ["leading", encodeURIComponent(cpString) + "test", String.fromCodePoint(i) + "test"],
51        ["middle", "te" + encodeURIComponent(cpString) + "st", "te" + String.fromCodePoint(i) + "st"],
52        ["trailing", "test" + encodeURIComponent(cpString), "test" + String.fromCodePoint(i)]
53      ]) {
54        test(() => {
55          const url = urlRecord(scheme);
56          url[property] = input;
57          assert_equals(url[property], expected, "property");
58          assert_equals(url.href, urlString({ scheme, [property]: expected }), "href");
59        }, `Setting ${property} with ${type} ${cpReference} (${scheme}:)`);
60      }
61    }
62
63    for (const [type, expectedPart, input] of [
64      ["leading", (scheme === "https" ? cpString : encodeURIComponent(cpString)) + "test", String.fromCodePoint(i) + "test"],
65      ["middle", "te" + (scheme === "https" ? cpString : encodeURIComponent(cpString)) + "st", "te" + String.fromCodePoint(i) + "st"],
66      ["trailing", "test" + (scheme === "https" ? cpString : encodeURIComponent(cpString)), "test" + String.fromCodePoint(i)]
67    ]) {
68      test(() => {
69        const expected = i === 0x00 || (scheme === "https" && i === 0x1F) ? "host" : stripped ? "test" : expectedPart;
70        const url = urlRecord(scheme);
71        url.host = input;
72        assert_equals(url.host, expected + ":8000", "property");
73        assert_equals(url.href, urlString({ scheme, host: expected }), "href");
74      }, `Setting host with ${type} ${cpReference} (${scheme}:)`);
75
76      test(() => {
77        const expected = i === 0x00 || (scheme === "https" && i === 0x1F) ? "host" : stripped ? "test" : expectedPart;
78        const url = urlRecord(scheme);
79        url.hostname = input;
80        assert_equals(url.hostname, expected, "property");
81        assert_equals(url.href, urlString({ scheme, host: expected }), "href");
82      }, `Setting hostname with ${type} ${cpReference} (${scheme}:)`);
83    }
84
85    test(() => {
86      const expected = stripped ? "9000" : "8000";
87      const url = urlRecord(scheme);
88      url.port = String.fromCodePoint(i) + "9000";
89      assert_equals(url.port, expected, "property");
90      assert_equals(url.href, urlString({ scheme, port: expected }), "href");
91    }, `Setting port with leading ${cpReference} (${scheme}:)`);
92
93    test(() => {
94      const expected = stripped ? "9000" : "90";
95      const url = urlRecord(scheme);
96      url.port = "90" + String.fromCodePoint(i) + "00";
97      assert_equals(url.port, expected, "property");
98      assert_equals(url.href, urlString({ scheme, port: expected }), "href");
99    }, `Setting port with middle ${cpReference} (${scheme}:)`);
100
101    test(() => {
102      const expected = "9000";
103      const url = urlRecord(scheme);
104      url.port = "9000" + String.fromCodePoint(i);
105      assert_equals(url.port, expected, "property");
106      assert_equals(url.href, urlString({ scheme, port: expected }), "href");
107    }, `Setting port with trailing ${cpReference} (${scheme}:)`);
108
109    for (const [property, separator] of [["pathname", "/"], ["search", "?"], ["hash", "#"]]) {
110      for (const [type, expectedPart, input] of [
111        ["leading", encodeURIComponent(cpString) + "test", String.fromCodePoint(i) + "test"],
112        ["middle", "te" + encodeURIComponent(cpString) + "st", "te" + String.fromCodePoint(i) + "st"],
113        ["trailing", "test" + encodeURIComponent(cpString), "test" + String.fromCodePoint(i)]
114      ]) {
115        test(() => {
116          const expected = stripped ? "test" : expectedPart;
117          const url = urlRecord(scheme);
118          url[property] = input;
119          assert_equals(url[property], separator + expected, "property");
120          assert_equals(url.href, urlString({ scheme, [property]: expected }), "href");
121        }, `Setting ${property} with ${type} ${cpReference} (${scheme}:)`);
122      }
123    }
124  }
125}
126