Skip to content
File

Blob: src/wpt/harness/common.ts

typescript180 lines
1// Copyright (c) 2017-2022 Cloudflare, Inc.
2// Licensed under the Apache 2.0 license found in the LICENSE file or at:
3// https://opensource.org/licenses/Apache-2.0
4// Copyright © web-platform-tests contributors. BSD license
5// Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors.
6//
7// Permission is hereby granted, free of charge, to any person obtaining a
8// copy of this software and associated documentation files (the
9// "Software"), to deal in the Software without restriction, including
10// without limitation the rights to use, copy, modify, merge, publish,
11// distribute, sublicense, and/or sell copies of the Software, and to permit
12// persons to whom the Software is furnished to do so, subject to the
13// following conditions:
14//
15// The above copyright notice and this permission notice shall be included
16// in all copies or substantial portions of the Software.
17//
18// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
19// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
21// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
22// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
23// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
24// USE OR OTHER DEALINGS IN THE SOFTWARE.
25 
26import path from 'node:path';
27 
28export type UnknownFunc = (...args: unknown[]) => unknown;
29export type TestFn = UnknownFunc;
30export type PromiseTestFn = () => Promise<unknown>;
31 
32export class FilterList {
33 // Matches any input
34 #matchesAll: boolean = false;
35 
36 // List of strings to match exactly
37 #strings: Set<string> = new Set();
38 
39 // List of regexps to match against
40 #regexps: RegExp[] = [];
41 
42 // Regexes which never matched any of the inputs
43 // We keep this set so we can warn the user about this.
44 #unmatchedRegexps: Set<RegExp> = new Set();
45 
46 constructor(filters: (string | RegExp)[] | true | undefined) {
47 if (filters === undefined) {
48 return;
49 }
50 
51 if (filters === true) {
52 this.#matchesAll = true;
53 return;
54 }
55 
56 for (const filter of filters) {
57 if (typeof filter === 'string') {
58 this.#strings.add(filter);
59 } else {
60 this.#regexps.push(filter);
61 }
62 }
63 
64 this.#unmatchedRegexps = new Set(this.#regexps);
65 }
66 
67 has(input: string): boolean {
68 if (this.#matchesAll) {
69 return true;
70 }
71 
72 if (this.#strings.has(input)) {
73 return true;
74 }
75 
76 return this.#regexps.some((r) => r.test(input));
77 }
78 
79 delete(input: string): boolean {
80 if (this.#matchesAll) {
81 return true;
82 }
83 
84 if (this.#strings.delete(input)) {
85 return true;
86 }
87 
88 const maybeMatch = this.#regexps.find((r) => r.test(input));
89 
90 if (maybeMatch !== undefined) {
91 this.#unmatchedRegexps.delete(maybeMatch);
92 return true;
93 }
94 
95 return false;
96 }
97 
98 getUnmatched(): Set<string | RegExp> {
99 return this.#strings.union(this.#unmatchedRegexps);
100 }
101}
102 
103export function sanitize_unpaired_surrogates(str: string): string {
104 // Test logs will be exported to XML, so we must escape any characters that
105 // are forbidden in an XML CDATA section, namely "[...] the surrogate blocks,
106 // FFFE, and FFFF".
107 // See https://www.w3.org/TR/REC-xml/#NT-Char
108 
109 return str
110 .replace(
111 /([\ud800-\udbff]+)(?![\udc00-\udfff])|(^|[^\ud800-\udbff])([\udc00-\udfff]+)/g,
112 function (_, low?: string, prefix?: string, high?: string) {
113 let output = prefix || ''; // prefix may be undefined
114 const string: string = low || high || ''; // only one of these alternates can match
115 
116 for (const ch of string) {
117 output += code_unit_str(ch);
118 }
119 return output;
120 }
121 )
122 .replace(/(\uffff|\ufffe)/g, function (_, invalid_chars?: string) {
123 let output = '';
124 
125 for (const ch of invalid_chars || '') {
126 output += code_unit_str(ch);
127 }
128 return output;
129 });
130}
131 
132function code_unit_str(char: string): string {
133 return 'U+' + char.charCodeAt(0).toString(16);
134}
135 
136export type HostInfo = {
137 REMOTE_HOST: string;
138 HTTP_ORIGIN: string;
139 HTTP_REMOTE_ORIGIN: string;
140 HTTPS_ORIGIN: string;
141 ORIGIN: string;
142 HTTPS_REMOTE_ORIGIN: string;
143 HTTP_PORT: string;
144 HTTPS_PORT: string;
145 WS_PORT: string;
146 WSS_PORT: string;
147};
148 
149export function getHostInfo(): HostInfo {
150 const httpUrl = globalThis.state.testUrl;
151 
152 const httpsUrl = new URL(httpUrl);
153 httpsUrl.protocol = 'https';
154 
155 // If the environment variable HTTPS_PORT is set, the wpt server is running as a sidecar.
156 // Update the URL's port so we can connect to it
157 httpsUrl.port = globalThis.state.env.HTTPS_PORT ?? '';
158 
159 return {
160 REMOTE_HOST: httpUrl.hostname,
161 HTTP_ORIGIN: httpUrl.origin,
162 ORIGIN: httpUrl.origin,
163 HTTP_REMOTE_ORIGIN: httpUrl.origin,
164 HTTPS_ORIGIN: httpsUrl.origin,
165 HTTPS_REMOTE_ORIGIN: httpsUrl.origin,
166 HTTP_PORT: httpUrl.port,
167 HTTPS_PORT: httpsUrl.port,
168 WS_PORT: (globalThis.state.env.WS_PORT as string | undefined) ?? '',
169 WSS_PORT: (globalThis.state.env.WSS_PORT as string | undefined) ?? '',
170 };
171}
172 
173export function getBindingPath(base: string, rawPath: string): string {
174 if (path.isAbsolute(rawPath)) {
175 return rawPath;
176 }
177 
178 return path.relative('/', path.resolve(base, rawPath));
179}