@freedomofpress/webcat 0.0.0-stage → 3.0.1

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 (82) hide show
  1. package/Readme.md +45 -0
  2. package/data/block.json +101 -0
  3. package/data/list.json +80 -0
  4. package/dist/hooks/content.js +1 -0
  5. package/dist/lib/browser/chrome.d.ts +86 -0
  6. package/dist/lib/browser/chrome.js +138 -0
  7. package/dist/lib/browser/kvstore.d.ts +70 -0
  8. package/dist/lib/browser/kvstore.js +241 -0
  9. package/dist/lib/browser/permissions.d.ts +113 -0
  10. package/dist/lib/browser/permissions.js +182 -0
  11. package/dist/lib/browser/requests.d.ts +145 -0
  12. package/dist/lib/browser/requests.js +292 -0
  13. package/dist/lib/browser/scripting.d.ts +19 -0
  14. package/dist/lib/browser/scripting.js +92 -0
  15. package/dist/lib/browser/sync.d.ts +68 -0
  16. package/dist/lib/browser/sync.js +114 -0
  17. package/dist/lib/browser/utils.d.ts +5 -0
  18. package/dist/lib/browser/utils.js +11 -0
  19. package/dist/lib/config.d.ts +45 -0
  20. package/dist/lib/config.js +45 -0
  21. package/dist/lib/validator_set.d.ts +4 -0
  22. package/dist/lib/validator_set.js +20 -0
  23. package/dist/lib/webcat/cache.d.ts +200 -0
  24. package/dist/lib/webcat/cache.js +308 -0
  25. package/dist/lib/webcat/canonicalize.d.ts +1 -0
  26. package/dist/lib/webcat/canonicalize.js +58 -0
  27. package/dist/lib/webcat/db.d.ts +30 -0
  28. package/dist/lib/webcat/db.js +131 -0
  29. package/dist/lib/webcat/encoding.d.ts +8 -0
  30. package/dist/lib/webcat/encoding.js +57 -0
  31. package/dist/lib/webcat/handler.d.ts +26 -0
  32. package/dist/lib/webcat/handler.js +419 -0
  33. package/dist/lib/webcat/hookbuilder.d.ts +47 -0
  34. package/dist/lib/webcat/hookbuilder.js +170 -0
  35. package/dist/lib/webcat/interfaces/bundle.d.ts +46 -0
  36. package/dist/lib/webcat/interfaces/bundle.js +8 -0
  37. package/dist/lib/webcat/interfaces/database.d.ts +68 -0
  38. package/dist/lib/webcat/interfaces/database.js +1 -0
  39. package/dist/lib/webcat/interfaces/errors.d.ts +451 -0
  40. package/dist/lib/webcat/interfaces/errors.js +109 -0
  41. package/dist/lib/webcat/interfaces/log.d.ts +14 -0
  42. package/dist/lib/webcat/interfaces/log.js +10 -0
  43. package/dist/lib/webcat/interfaces/originstate.d.ts +40 -0
  44. package/dist/lib/webcat/interfaces/originstate.js +1 -0
  45. package/dist/lib/webcat/interfaces/requeststate.d.ts +10 -0
  46. package/dist/lib/webcat/interfaces/requeststate.js +1 -0
  47. package/dist/lib/webcat/logger.d.ts +64 -0
  48. package/dist/lib/webcat/logger.js +112 -0
  49. package/dist/lib/webcat/originstate.d.ts +45 -0
  50. package/dist/lib/webcat/originstate.js +302 -0
  51. package/dist/lib/webcat/parsers.d.ts +21 -0
  52. package/dist/lib/webcat/parsers.js +36 -0
  53. package/dist/lib/webcat/request.d.ts +11 -0
  54. package/dist/lib/webcat/request.js +78 -0
  55. package/dist/lib/webcat/resources.d.ts +3 -0
  56. package/dist/lib/webcat/resources.js +41 -0
  57. package/dist/lib/webcat/response.d.ts +39 -0
  58. package/dist/lib/webcat/response.js +394 -0
  59. package/dist/lib/webcat/ui.d.ts +70 -0
  60. package/dist/lib/webcat/ui.js +282 -0
  61. package/dist/lib/webcat/updater.d.ts +115 -0
  62. package/dist/lib/webcat/updater.js +296 -0
  63. package/dist/lib/webcat/utils.d.ts +8 -0
  64. package/dist/lib/webcat/utils.js +74 -0
  65. package/dist/lib/webcat/validators.d.ts +16 -0
  66. package/dist/lib/webcat/validators.js +457 -0
  67. package/dist/lib/webcat.d.ts +11 -0
  68. package/dist/lib/webcat.js +130 -0
  69. package/icons/dark/webcat-error.SVG +1 -0
  70. package/icons/dark/webcat-ok.SVG +1 -0
  71. package/icons/dark/webcat.SVG +1 -0
  72. package/icons/light/webcat-error.SVG +1 -0
  73. package/icons/light/webcat-ok.SVG +1 -0
  74. package/icons/light/webcat.SVG +1 -0
  75. package/package.json +72 -4
  76. package/pages/error.css +9 -0
  77. package/pages/error.html +70 -0
  78. package/pages/error.js +51 -0
  79. package/pages/popup.css +16 -0
  80. package/pages/popup.html +14 -0
  81. package/pages/popup.js +48 -0
  82. package/README.md +0 -3
@@ -0,0 +1,41 @@
1
+ const ALL_TYPES = [
2
+ "main_frame",
3
+ "sub_frame",
4
+ "stylesheet",
5
+ "script",
6
+ "image",
7
+ "font",
8
+ "object",
9
+ "xmlhttprequest",
10
+ "ping",
11
+ "csp_report",
12
+ "media",
13
+ "websocket",
14
+ "other",
15
+ "xslt",
16
+ "beacon",
17
+ "imageset",
18
+ "speculative",
19
+ "web_manifest"
20
+ ];
21
+ const FRAME_TYPES = [
22
+ "main_frame",
23
+ "sub_frame"
24
+ ];
25
+ const NON_FRAME_TYPES = ALL_TYPES.filter((t) => !FRAME_TYPES.includes(t));
26
+ const PASS_THROUGH_TYPES = /* @__PURE__ */ new Set([
27
+ "image",
28
+ "imageset",
29
+ "media",
30
+ "font",
31
+ "websocket",
32
+ "xmlhttprequest",
33
+ "ping",
34
+ "speculative",
35
+ "other"
36
+ ]);
37
+ export {
38
+ FRAME_TYPES,
39
+ NON_FRAME_TYPES,
40
+ PASS_THROUGH_TYPES
41
+ };
@@ -0,0 +1,39 @@
1
+ import { BeforeRequestDetails, HeadersReceivedDetails } from "../browser/requests";
2
+ import { HookBuilder } from "./hookbuilder";
3
+ import { Database } from "./interfaces/database";
4
+ import { WebcatError } from "./interfaces/errors";
5
+ import { Stateful } from "./interfaces/requeststate";
6
+ import { WebcatUI } from "./ui";
7
+ /**
8
+ * Validates responses from WEBCAT-enrolled origins.
9
+ */
10
+ export declare class ResponseValidator {
11
+ #private;
12
+ constructor(db: Database, ui: WebcatUI, hooks: HookBuilder);
13
+ /**
14
+ * Validates response headers.
15
+ *
16
+ * @param details The request details object to validate.
17
+ * @returns A Promise that resolves to undefined on successful validation, or
18
+ * to a {@link WebcatError} on validation failure.
19
+ */
20
+ validateHeaders(details: Stateful<HeadersReceivedDetails>): Promise<WebcatError | undefined>;
21
+ /** @internal */
22
+ extractAndValidateHeaders(details: Stateful<HeadersReceivedDetails>): Map<string, string> | WebcatError;
23
+ /**
24
+ * Validates response content.
25
+ *
26
+ * @param details The request details object to validate.
27
+ */
28
+ validateContent(details: Stateful<BeforeRequestDetails>): Promise<void>;
29
+ /**
30
+ * Marks the start of response content by injecting a secret token. Must be
31
+ * called *later* than {@link validateContent}, which will remove the marker.
32
+ * The counter-intuitive ordering is inherited from
33
+ * {@link browser.webRequest.StreamFilter | StreamFilter} behavior.
34
+ *
35
+ * @param details
36
+ * @returns
37
+ */
38
+ markContent(details: HeadersReceivedDetails): void;
39
+ }
@@ -0,0 +1,394 @@
1
+ var __typeError = (msg) => {
2
+ throw TypeError(msg);
3
+ };
4
+ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
5
+ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
6
+ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
7
+ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
8
+ var _marker, _db, _ui, _hooks;
9
+ import permissions from "../browser/permissions.js";
10
+ import { CacheKey } from "./cache.js";
11
+ import { stringToUint8Array, Uint8ArrayToBase64Url, Uint8ArrayToString, base64UrlToUint8Array } from "./encoding.js";
12
+ import { WebcatError, WebcatErrorCode } from "./interfaces/errors.js";
13
+ import { logger } from "./logger.js";
14
+ import { PASS_THROUGH_TYPES } from "./resources.js";
15
+ import { isNewerSemver, clearBrowserCaches, isSameOriginURL, arraysEqual, SHA256 } from "./utils.js";
16
+ import "../browser/sync.js";
17
+ var __create = Object.create;
18
+ var __defProp = Object.defineProperty;
19
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
20
+ var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
21
+ var __typeError2 = (msg) => {
22
+ throw TypeError(msg);
23
+ };
24
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
25
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
26
+ var __decoratorStart = (base) => [, , , __create(null)];
27
+ var __decoratorStrings = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"];
28
+ var __expectFn = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError2("Function expected") : fn;
29
+ var __decoratorContext = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError2("Already initialized") : fns.push(__expectFn(fn || null)) });
30
+ var __decoratorMetadata = (array, target) => __defNormalProp(target, __knownSymbol("metadata"), array[3]);
31
+ var __runInitializers = (array, flags, self, value) => {
32
+ for (var i = 0, fns = array[flags >> 1], n = fns && fns.length; i < n; i++) fns[i].call(self);
33
+ return value;
34
+ };
35
+ var __decorateElement = (array, flags, name, decorators, target, extra) => {
36
+ var it, done, ctx, k = flags & 7, p = false;
37
+ var j = 0;
38
+ var extraInitializers = array[j] || (array[j] = []);
39
+ var desc = k && (target = target.prototype, k < 5 && (k > 3 || !p) && __getOwnPropDesc(target, name));
40
+ __name(target, name);
41
+ for (var i = decorators.length - 1; i >= 0; i--) {
42
+ ctx = __decoratorContext(k, name, done = {}, array[3], extraInitializers);
43
+ it = (0, decorators[i])(target, ctx), done._ = 1;
44
+ __expectFn(it) && (target = it);
45
+ }
46
+ return __decoratorMetadata(array, target), desc && __defProp(target, name, desc), p ? k ^ 4 ? extra : desc : target;
47
+ };
48
+ var _ResponseValidator_decorators, _init;
49
+ const WORKER_FETCH_DESTINATIONS = [
50
+ "worker",
51
+ "serviceworker",
52
+ "sharedworker",
53
+ "audioworklet",
54
+ "paintworklet"
55
+ ];
56
+ function assertVerifiedManifest(originState) {
57
+ if (!originState.isManifestVerified()) {
58
+ throw new Error("origin is not populated when it was expected");
59
+ }
60
+ }
61
+ function assertHeadersAvailable(details) {
62
+ if (!("responseHeaders" in details)) {
63
+ throw new Error("response headers not available when expected");
64
+ }
65
+ }
66
+ _ResponseValidator_decorators = [permissions.require("webRequest"), permissions.require("webRequestBlocking"), permissions.require("webRequestFilterResponse"), permissions.require("webRequestFilterResponse.serviceWorkerScript")];
67
+ class ResponseValidator {
68
+ constructor(db, ui, hooks) {
69
+ // #marker is ephemeral, not persisted anywhere, but that's ok:
70
+ // ResponseValidator attaches a StreamFilter that prevents the
71
+ // background service worker from terminating while a response
72
+ // is being processed
73
+ __privateAdd(this, _marker, stringToUint8Array(
74
+ `__WEBCAT_END__{${Uint8ArrayToBase64Url(crypto.getRandomValues(new Uint8Array(32)))}}
75
+ `
76
+ ));
77
+ __privateAdd(this, _db);
78
+ __privateAdd(this, _ui);
79
+ __privateAdd(this, _hooks);
80
+ __privateSet(this, _db, db);
81
+ __privateSet(this, _ui, ui);
82
+ __privateSet(this, _hooks, hooks);
83
+ }
84
+ /**
85
+ * Validates response headers.
86
+ *
87
+ * @param details The request details object to validate.
88
+ * @returns A Promise that resolves to undefined on successful validation, or
89
+ * to a {@link WebcatError} on validation failure.
90
+ */
91
+ async validateHeaders(details) {
92
+ if (!details.state.pendingOrigin) {
93
+ throw new Error("missing pendingOrigin in request state");
94
+ }
95
+ logger.info(`Validating response headers, url: ${details.url}`, details);
96
+ const result = this.extractAndValidateHeaders(details);
97
+ if (result instanceof WebcatError) {
98
+ return result;
99
+ }
100
+ const normalizedHeaders = result;
101
+ const csp = normalizedHeaders.get("content-security-policy");
102
+ const version = normalizedHeaders.get("x-webcat-version");
103
+ const delegation = normalizedHeaders.get("x-webcat-delegation");
104
+ const enrollment_header = normalizedHeaders.get("x-webcat-enrollment");
105
+ if (details.state.pendingOrigin.isRequestSent()) {
106
+ let enrollment;
107
+ if (enrollment_header) {
108
+ try {
109
+ enrollment = JSON.parse(
110
+ Uint8ArrayToString(base64UrlToUint8Array(enrollment_header))
111
+ );
112
+ } catch {
113
+ return new WebcatError(WebcatErrorCode.Headers.ENROLLMENT_MALFORMED);
114
+ }
115
+ await details.state.pendingOrigin.verifyEnrollment(
116
+ enrollment,
117
+ delegation
118
+ );
119
+ } else {
120
+ await details.state.pendingOrigin.verifyEnrollment(
121
+ void 0,
122
+ delegation
123
+ );
124
+ }
125
+ if (details.state.pendingOrigin.isFailed()) {
126
+ return details.state.pendingOrigin.error;
127
+ }
128
+ logger.debug("Header parsing complete", details);
129
+ await details.state.pendingOrigin.verifyManifest();
130
+ if (details.state.pendingOrigin.isFailed()) {
131
+ return details.state.pendingOrigin.error;
132
+ }
133
+ if (!details.state.pendingOrigin.isManifestVerified()) {
134
+ throw new Error(
135
+ `Error with the origin state: expected origin to be in state verified_manifest`
136
+ );
137
+ }
138
+ logger.info(`Metadata for ${details.url} loaded`, details);
139
+ }
140
+ if (!details.state.pendingOrigin.isManifestVerified()) {
141
+ throw new Error(
142
+ "Validating headers, but no valid manifest for the origin has been found."
143
+ );
144
+ }
145
+ if (version && isNewerSemver(version, details.state.pendingOrigin.manifest.version)) {
146
+ logger.info(
147
+ `Detected new version ${version}, current_version ${details.state.pendingOrigin.manifest.version}`,
148
+ details
149
+ );
150
+ await __privateGet(this, _db).origins.delete(
151
+ CacheKey(details.state.fqdn, details.state.cachePartition)
152
+ );
153
+ details.state.pendingOrigin.stale = true;
154
+ await clearBrowserCaches([details.state.fqdn]);
155
+ browser.tabs.reload(details.tabId);
156
+ }
157
+ const pathname = new URL(details.url).pathname;
158
+ if (csp) {
159
+ if (!details.state.pendingOrigin.verifyCSP(csp, pathname)) {
160
+ return new WebcatError(WebcatErrorCode.CSP.MISMATCH, [
161
+ String(pathname)
162
+ ]);
163
+ }
164
+ logger.info(`CSP validated for path ${pathname}`, details);
165
+ } else if (details.fromCache === true || details.statusCode === 304) {
166
+ logger.debug(
167
+ `Skipping CSP check for cached/304 response on path ${pathname}`,
168
+ details
169
+ );
170
+ } else {
171
+ return new WebcatError(WebcatErrorCode.Headers.MISSING_CRITICAL, [
172
+ "content-security-policy"
173
+ ]);
174
+ }
175
+ if (details.type === "main_frame") {
176
+ __privateGet(this, _ui).showOKIcon(
177
+ details.tabId,
178
+ details.state.pendingOrigin.delegation
179
+ );
180
+ }
181
+ }
182
+ /** @internal */
183
+ extractAndValidateHeaders(details) {
184
+ if (!details.responseHeaders) {
185
+ return new WebcatError(WebcatErrorCode.Headers.MISSING);
186
+ }
187
+ const criticalHeaders = /* @__PURE__ */ new Set(["content-security-policy"]);
188
+ const forbiddenHeaders = /* @__PURE__ */ new Set([
189
+ // See https://github.com/freedomofpress/webcat/issues/23
190
+ // Furthermore, as reported by TBD there's the risk of TBD
191
+ //"location",
192
+ // See https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Refresh
193
+ // It's just another way to achieve redirects
194
+ "refresh",
195
+ // See https://github.com/freedomofpress/webcat/issues/24
196
+ "link"
197
+ ]);
198
+ const seenCriticalHeaders = /* @__PURE__ */ new Set();
199
+ const normalizedHeaders = /* @__PURE__ */ new Map();
200
+ for (const header of details.responseHeaders) {
201
+ if (header.name && header.value) {
202
+ const lowerName = header.name.toLowerCase();
203
+ const value = header.value;
204
+ if (lowerName === "location") {
205
+ if (!details.state.isFrame) {
206
+ return new WebcatError(
207
+ WebcatErrorCode.Headers.LOCATION_SUBRESOURCE,
208
+ [String(value)]
209
+ );
210
+ }
211
+ if (!isSameOriginURL(value, details.url)) {
212
+ return new WebcatError(WebcatErrorCode.Headers.LOCATION_EXTERNAL, [
213
+ String(value)
214
+ ]);
215
+ }
216
+ } else if (lowerName === "origin-agent-cluster" && value.trim() !== "?1") {
217
+ return new WebcatError(WebcatErrorCode.Headers.FORBIDDEN, [
218
+ `${lowerName}: ${value}`
219
+ ]);
220
+ } else if (forbiddenHeaders.has(lowerName)) {
221
+ return new WebcatError(WebcatErrorCode.Headers.FORBIDDEN, [
222
+ String(lowerName)
223
+ ]);
224
+ }
225
+ if (criticalHeaders.has(lowerName)) {
226
+ if (seenCriticalHeaders.has(lowerName)) {
227
+ return new WebcatError(WebcatErrorCode.Headers.DUPLICATE, [
228
+ String(lowerName)
229
+ ]);
230
+ }
231
+ seenCriticalHeaders.add(lowerName);
232
+ }
233
+ normalizedHeaders.set(lowerName, header.value);
234
+ }
235
+ }
236
+ if (details.fromCache !== true && details.statusCode !== 304) {
237
+ for (const criticalHeader of criticalHeaders) {
238
+ if (!normalizedHeaders.has(criticalHeader)) {
239
+ return new WebcatError(WebcatErrorCode.Headers.MISSING_CRITICAL, [
240
+ String(criticalHeader)
241
+ ]);
242
+ }
243
+ }
244
+ }
245
+ return normalizedHeaders;
246
+ }
247
+ /**
248
+ * Validates response content.
249
+ *
250
+ * @param details The request details object to validate.
251
+ */
252
+ async validateContent(details) {
253
+ function deny(filter2) {
254
+ filter2.write(new Uint8Array([68, 69, 78, 73, 69, 68]));
255
+ }
256
+ const pathname = new URL(details.url).pathname;
257
+ const originState = details.state.pendingOrigin;
258
+ if (!originState) {
259
+ throw new Error("missing pendingOrigin in request state");
260
+ }
261
+ let manifest;
262
+ const filter = browser.webRequest.filterResponseData(details.requestId);
263
+ const source = [];
264
+ filter.onstart = () => {
265
+ assertVerifiedManifest(originState);
266
+ assertHeadersAvailable(details);
267
+ manifest = originState.manifest;
268
+ if (!manifest.files[pathname] && !(pathname.endsWith("/") && manifest.files[pathname + manifest.default_index]) && PASS_THROUGH_TYPES.has(details.type)) {
269
+ filter.disconnect();
270
+ }
271
+ if (details.type === "script" && details.requestHeaders) {
272
+ for (const header of details.requestHeaders) {
273
+ if (header.name.toLowerCase() === "sec-fetch-dest" && header.value !== void 0) {
274
+ if (WORKER_FETCH_DESTINATIONS.includes(header.value)) {
275
+ const hooks = __privateGet(this, _hooks).getPageHooks(
276
+ manifest.wasm,
277
+ details.state.cachePartition.firstParty,
278
+ // Hooks are only injected to workers, and CSP restrictions only allow
279
+ // same-origin workers, so the request's web origin is the URL's origin;
280
+ // determine whether the URL is same-origin with the first party
281
+ details.state.cachePartition.firstParty === new URL(details.url).origin
282
+ );
283
+ source.push(hooks.then((h) => stringToUint8Array(h).buffer));
284
+ }
285
+ break;
286
+ }
287
+ }
288
+ }
289
+ };
290
+ let writeQueue = Promise.resolve();
291
+ let markerSeen = false;
292
+ filter.ondata = (event) => {
293
+ if (arraysEqual(__privateGet(this, _marker), new Uint8Array(event.data))) {
294
+ source.forEach((hook) => {
295
+ writeQueue = writeQueue.then(async () => filter.write(await hook));
296
+ });
297
+ source.length = 0;
298
+ markerSeen = true;
299
+ } else {
300
+ source.push(Promise.resolve(event.data));
301
+ }
302
+ };
303
+ filter.onstop = async () => {
304
+ if (!markerSeen && !PASS_THROUGH_TYPES.has(details.type)) {
305
+ filter.close();
306
+ return;
307
+ }
308
+ try {
309
+ if (details.tabId !== -1) {
310
+ await details.completed;
311
+ }
312
+ } catch {
313
+ logger.warn(`Request canceled, url: ${details.url}`, details);
314
+ filter.close();
315
+ return;
316
+ }
317
+ const blob = await new Blob(await Promise.all(source)).arrayBuffer();
318
+ let manifest_hash;
319
+ if (manifest.files[pathname]) {
320
+ manifest_hash = manifest.files[pathname];
321
+ } else if (pathname.endsWith("/")) {
322
+ manifest_hash = manifest.files[pathname + manifest.default_index];
323
+ }
324
+ if (!manifest_hash && details.type === "main_frame") {
325
+ manifest_hash = manifest.files[manifest.default_fallback];
326
+ }
327
+ if (!manifest_hash) {
328
+ deny(filter);
329
+ filter.close();
330
+ __privateGet(this, _ui).showErrorPage(
331
+ details,
332
+ new WebcatError(WebcatErrorCode.File.MISSING, [pathname]),
333
+ true
334
+ );
335
+ return;
336
+ }
337
+ const content_hash = await SHA256(blob);
338
+ if (!arraysEqual(
339
+ base64UrlToUint8Array(manifest_hash),
340
+ new Uint8Array(content_hash)
341
+ ) && blob.byteLength !== 0) {
342
+ deny(filter);
343
+ filter.close();
344
+ __privateGet(this, _ui).showErrorPage(
345
+ details,
346
+ new WebcatError(WebcatErrorCode.File.MISMATCH, [
347
+ pathname,
348
+ String(manifest_hash),
349
+ String(Uint8ArrayToBase64Url(new Uint8Array(content_hash)))
350
+ ]),
351
+ true
352
+ );
353
+ return;
354
+ }
355
+ logger.info(`${pathname} verified.`, details);
356
+ await writeQueue;
357
+ filter.write(blob);
358
+ filter.close();
359
+ if (details.type === "main_frame") {
360
+ __privateGet(this, _ui).showOKIcon(details.tabId, originState.delegation);
361
+ }
362
+ };
363
+ }
364
+ /**
365
+ * Marks the start of response content by injecting a secret token. Must be
366
+ * called *later* than {@link validateContent}, which will remove the marker.
367
+ * The counter-intuitive ordering is inherited from
368
+ * {@link browser.webRequest.StreamFilter | StreamFilter} behavior.
369
+ *
370
+ * @param details
371
+ * @returns
372
+ */
373
+ markContent(details) {
374
+ if (PASS_THROUGH_TYPES.has(details.type)) return;
375
+ const endMarkerInjector = browser.webRequest.filterResponseData(
376
+ details.requestId
377
+ );
378
+ endMarkerInjector.onstart = () => {
379
+ endMarkerInjector.write(__privateGet(this, _marker));
380
+ };
381
+ endMarkerInjector.ondata = (event) => endMarkerInjector.write(event.data);
382
+ endMarkerInjector.onstop = () => endMarkerInjector.close();
383
+ }
384
+ }
385
+ _marker = new WeakMap();
386
+ _db = new WeakMap();
387
+ _ui = new WeakMap();
388
+ _hooks = new WeakMap();
389
+ _init = __decoratorStart();
390
+ ResponseValidator = __decorateElement(_init, 0, "ResponseValidator", _ResponseValidator_decorators, ResponseValidator);
391
+ __runInitializers(_init, 1, ResponseValidator);
392
+ export {
393
+ ResponseValidator
394
+ };
@@ -0,0 +1,70 @@
1
+ import { BrowserChromeController, BrowserChromeControllerConfig, IconOptions } from "../browser/chrome";
2
+ import { NamespacedKVStore } from "../browser/kvstore";
3
+ import { RequestDetails } from "../browser/requests";
4
+ import { Database } from "./interfaces/database";
5
+ import { WebcatError } from "./interfaces/errors";
6
+ import { Stateful } from "./interfaces/requeststate";
7
+ /**
8
+ * Manages the UI for WEBCAT.
9
+ */
10
+ export declare class WebcatUI extends BrowserChromeController {
11
+ #private;
12
+ /**
13
+ * @param db The database to load enrollments from.
14
+ * @param store The store to use for persistence.
15
+ * @param config
16
+ */
17
+ constructor(db: Database, store: NamespacedKVStore, config: BrowserChromeControllerConfig);
18
+ /**
19
+ * Displays the default WEBCAT icon in the browser's URL bar. If the UI has
20
+ * warnings, shows the warning icon instead.
21
+ *
22
+ * @param tabId The ID of the tab to display the icon in.
23
+ * @param options Icon options.
24
+ */
25
+ showIcon(tabId: number, options?: IconOptions): Promise<void>;
26
+ /**
27
+ * Displays the warning icon in the browser's URL bar. Clicking on the icon
28
+ * opens the popup with full warning info.
29
+ *
30
+ * @param tabId The ID of the tab to display the icon in.
31
+ */
32
+ showWarningIcon(tabId: number): Promise<void>;
33
+ /**
34
+ * Displays the OK icon in the browser's URL bar. If the UI has warnings,
35
+ * shows the warning icon instead.
36
+ *
37
+ * @param tabId The ID of the tab to display the icon in.
38
+ * @param delegation Optional delegation information to include in the title
39
+ * text.
40
+ */
41
+ showOKIcon(tabId: number, delegation?: string): Promise<void>;
42
+ /**
43
+ * Displays the error icon in the browser's URL bar.
44
+ *
45
+ * @param tabId The ID of the tab to display the icon in.
46
+ */
47
+ showErrorIcon(tabId: number): Promise<void>;
48
+ /**
49
+ * Loads the error page in the tab(s) indicated by details.
50
+ *
51
+ * @param details Details for the request that triggered the error.
52
+ * @param error The error information.
53
+ * @param replace true to replace the current history entry, false to add a
54
+ * new one.
55
+ */
56
+ showErrorPage(details: Stateful<RequestDetails>, error: WebcatError, replace?: boolean): Promise<void>;
57
+ /**
58
+ * Sets the message for the specified warning.
59
+ *
60
+ * @param id The ID of the warning.
61
+ * @param args The arguments of the warning. Must be serializable as JSON.
62
+ */
63
+ setWarning(id: string, ...args: unknown[]): Promise<void>;
64
+ /**
65
+ * Clears a specific warning from the UI.
66
+ *
67
+ * @param id The ID of the warning to clear.
68
+ */
69
+ clearWarning(id: string): Promise<void>;
70
+ }