sourcemonkey 0.0.0-stage → 0.5.0
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/LICENSE +21 -0
- package/README.md +87 -2
- package/dist/bytes.js +37 -0
- package/dist/channel-placeholder.js +5 -0
- package/dist/collection-scan.js +17 -0
- package/dist/collections.html +353 -0
- package/dist/compare.html +358 -0
- package/dist/composed-source.js +205 -0
- package/dist/connect-prompt.html +47 -0
- package/dist/debug/icons.html +211 -0
- package/dist/debug/storage.html +163 -0
- package/dist/debug-log.js +75 -0
- package/dist/diagnostic-location.js +64 -0
- package/dist/eval-hook.js +308 -0
- package/dist/extension/background.js +5263 -0
- package/dist/extension/channel.js +133 -0
- package/dist/extension/code-links.js +187 -0
- package/dist/extension/code-tabs.js +460 -0
- package/dist/extension/collection-dialog.js +619 -0
- package/dist/extension/collection-display.js +322 -0
- package/dist/extension/collections.js +2905 -0
- package/dist/extension/compare.js +867 -0
- package/dist/extension/concurrency.js +186 -0
- package/dist/extension/connect-grants.js +109 -0
- package/dist/extension/connect-prompt-page.js +70 -0
- package/dist/extension/connect-prompt.js +266 -0
- package/dist/extension/copy-button.js +63 -0
- package/dist/extension/copy-dialog.js +502 -0
- package/dist/extension/copy-preference.js +66 -0
- package/dist/extension/debug/icons.js +203 -0
- package/dist/extension/debug/storage.js +365 -0
- package/dist/extension/deep-link.js +77 -0
- package/dist/extension/delete-dialog.js +432 -0
- package/dist/extension/delete-icon.js +40 -0
- package/dist/extension/details-tooltips.js +66 -0
- package/dist/extension/directories.js +679 -0
- package/dist/extension/export-backup.js +407 -0
- package/dist/extension/export-dialog.js +142 -0
- package/dist/extension/file-access.js +74 -0
- package/dist/extension/file-changes.js +346 -0
- package/dist/extension/frame-info.js +85 -0
- package/dist/extension/github-provider.js +336 -0
- package/dist/extension/glyph.js +84 -0
- package/dist/extension/gm-bindings.js +601 -0
- package/dist/extension/gm-download.js +419 -0
- package/dist/extension/gm-menu.js +228 -0
- package/dist/extension/gm-notification.js +141 -0
- package/dist/extension/gm-originator-map.js +23 -0
- package/dist/extension/gm-tab-data.js +121 -0
- package/dist/extension/gm-tab.js +160 -0
- package/dist/extension/gm-url-change.js +111 -0
- package/dist/extension/gm-url-scheme.js +27 -0
- package/dist/extension/gm-values.js +176 -0
- package/dist/extension/gm-window-control.js +104 -0
- package/dist/extension/gm-xhr-headers.js +333 -0
- package/dist/extension/gm-xhr.js +584 -0
- package/dist/extension/injector.js +3145 -0
- package/dist/extension/install-capture.js +277 -0
- package/dist/extension/line-diff.js +233 -0
- package/dist/extension/local-copy.js +560 -0
- package/dist/extension/local-delete.js +276 -0
- package/dist/extension/location-kinds.js +108 -0
- package/dist/extension/log-format.js +158 -0
- package/dist/extension/managed-changes.js +361 -0
- package/dist/extension/managed-items.js +57 -0
- package/dist/extension/match-index.js +375 -0
- package/dist/extension/missing-scripts.js +66 -0
- package/dist/extension/more-menu.js +109 -0
- package/dist/extension/move-dialog.js +197 -0
- package/dist/extension/offscreen.js +37 -0
- package/dist/extension/options.js +504 -0
- package/dist/extension/page-heading.js +170 -0
- package/dist/extension/page-links.js +199 -0
- package/dist/extension/parse-check-client.js +70 -0
- package/dist/extension/parse-check.js +161 -0
- package/dist/extension/parse-sandbox-host.js +172 -0
- package/dist/extension/parse-sandbox.js +175 -0
- package/dist/extension/popup-commands-open.js +41 -0
- package/dist/extension/popup-view.js +387 -0
- package/dist/extension/popup.js +703 -0
- package/dist/extension/prism-userscript.js +53 -0
- package/dist/extension/refresh-error.js +136 -0
- package/dist/extension/refresh-targets.js +160 -0
- package/dist/extension/related-versions.js +1144 -0
- package/dist/extension/resize-observer-loop.js +19 -0
- package/dist/extension/row-cache.js +387 -0
- package/dist/extension/script-blobs.js +413 -0
- package/dist/extension/script-details.js +658 -0
- package/dist/extension/script-enablement.js +285 -0
- package/dist/extension/script-identity.js +310 -0
- package/dist/extension/script-keys.js +54 -0
- package/dist/extension/script-log-id.js +155 -0
- package/dist/extension/script-log.js +290 -0
- package/dist/extension/script-rejection.js +104 -0
- package/dist/extension/script-sites.js +158 -0
- package/dist/extension/script-store.js +3507 -0
- package/dist/extension/script-updater.js +991 -0
- package/dist/extension/script-url-hash.js +61 -0
- package/dist/extension/select-all.js +37 -0
- package/dist/extension/select-open-guard.js +90 -0
- package/dist/extension/site-icon.js +1261 -0
- package/dist/extension/sm-dev-link.js +302 -0
- package/dist/extension/solo-click.js +58 -0
- package/dist/extension/sticky-numbers.js +188 -0
- package/dist/extension/storage-breakdown.js +253 -0
- package/dist/extension/storage-scan.js +86 -0
- package/dist/extension/storage-usage.js +194 -0
- package/dist/extension/structural-keys.js +29 -0
- package/dist/extension/sw-teardown.js +36 -0
- package/dist/extension/tab-status.js +403 -0
- package/dist/extension/toolbar-summary.js +105 -0
- package/dist/extension/user-scripts-access.js +43 -0
- package/dist/extension/userscript.js +3672 -0
- package/dist/extension/value-store.js +204 -0
- package/dist/extension/version-probe-fetch.js +68 -0
- package/dist/extension/worker-client.js +109 -0
- package/dist/extension/worker-ops.js +171 -0
- package/dist/extension/zip-writer.js +24 -0
- package/dist/external-files.js +878 -0
- package/dist/file-loader.js +436 -0
- package/dist/file-locations.js +439 -0
- package/dist/generated-wrappers.js +5 -0
- package/dist/github-auth.js +148 -0
- package/dist/github-urls.js +46 -0
- package/dist/gm-api-catalog.js +217 -0
- package/dist/gm-connect.js +273 -0
- package/dist/gm-xhr-types.js +5 -0
- package/dist/http-read.js +142 -0
- package/dist/icons/icon-128.png +0 -0
- package/dist/icons/icon-16.png +0 -0
- package/dist/icons/icon-48.png +0 -0
- package/dist/local-file-overrides.js +558 -0
- package/dist/manifest.json +38 -0
- package/dist/markup-response.js +126 -0
- package/dist/match-targets.js +305 -0
- package/dist/menu-captions.js +22 -0
- package/dist/offscreen.html +39 -0
- package/dist/options.html +197 -0
- package/dist/parse-check-protocol.js +51 -0
- package/dist/parse-location.js +171 -0
- package/dist/parse-sandbox.html +56 -0
- package/dist/phase-timing.js +155 -0
- package/dist/popup.html +197 -0
- package/dist/public-suffixes.js +6957 -0
- package/dist/run-context.js +31 -0
- package/dist/runtime-messages.js +34 -0
- package/dist/sandboxed-page.js +35 -0
- package/dist/script-build.js +787 -0
- package/dist/script-display.js +383 -0
- package/dist/script-icon.js +43 -0
- package/dist/script-links.js +204 -0
- package/dist/script-source-url.js +214 -0
- package/dist/script-world.js +65 -0
- package/dist/sm-dev-protocol.js +84 -0
- package/dist/source-map.js +177 -0
- package/dist/stack-link.js +138 -0
- package/dist/styles.css +825 -0
- package/dist/unsafe-window-use.js +229 -0
- package/dist/url-match.js +734 -0
- package/dist/userscript-header.js +248 -0
- package/dist/userscript.html +1165 -0
- package/dist/vendor/fflate.LICENSE +21 -0
- package/dist/vendor/fflate.js +2692 -0
- package/dist/vendor/prism/prism-line-numbers.css +1 -0
- package/dist/vendor/prism/prism-line-numbers.js +1 -0
- package/dist/vendor/prism/prism.css +1 -0
- package/dist/vendor/prism/prism.js +1946 -0
- package/dist/version-compare.js +141 -0
- package/dist/version-probe.js +120 -0
- package/lib/build-info.json +1 -0
- package/lib/bytes.d.ts +3 -0
- package/lib/bytes.js +37 -0
- package/lib/channel-placeholder.d.ts +1 -0
- package/lib/channel-placeholder.js +5 -0
- package/lib/collection-scan.d.ts +8 -0
- package/lib/collection-scan.js +17 -0
- package/lib/composed-source.d.ts +28 -0
- package/lib/composed-source.js +205 -0
- package/lib/debug-log.d.ts +9 -0
- package/lib/debug-log.js +75 -0
- package/lib/diagnostic-location.d.ts +8 -0
- package/lib/diagnostic-location.js +64 -0
- package/lib/eval-hook.d.ts +34 -0
- package/lib/eval-hook.js +308 -0
- package/lib/external-files.d.ts +111 -0
- package/lib/external-files.js +878 -0
- package/lib/file-loader.d.ts +48 -0
- package/lib/file-loader.js +436 -0
- package/lib/file-locations.d.ts +44 -0
- package/lib/file-locations.js +439 -0
- package/lib/generated-wrappers.d.ts +11 -0
- package/lib/generated-wrappers.js +5 -0
- package/lib/github-auth.d.ts +12 -0
- package/lib/github-auth.js +148 -0
- package/lib/github-urls.d.ts +1 -0
- package/lib/github-urls.js +46 -0
- package/lib/gm-api-catalog.d.ts +24 -0
- package/lib/gm-api-catalog.js +217 -0
- package/lib/gm-connect.d.ts +34 -0
- package/lib/gm-connect.js +273 -0
- package/lib/gm-xhr-types.d.ts +68 -0
- package/lib/gm-xhr-types.js +5 -0
- package/lib/harness/browser-extensions.d.ts +73 -0
- package/lib/harness/browser-extensions.js +285 -0
- package/lib/harness/browser-lang.d.ts +20 -0
- package/lib/harness/browser-lang.js +86 -0
- package/lib/harness/browser-launch.d.ts +14 -0
- package/lib/harness/browser-launch.js +184 -0
- package/lib/harness/checks.d.ts +110 -0
- package/lib/harness/checks.js +321 -0
- package/lib/harness/cli.d.ts +2 -0
- package/lib/harness/cli.js +2259 -0
- package/lib/harness/command-args.d.ts +91 -0
- package/lib/harness/command-args.js +369 -0
- package/lib/harness/command-lines.d.ts +80 -0
- package/lib/harness/command-lines.js +180 -0
- package/lib/harness/command-list.d.ts +206 -0
- package/lib/harness/command-list.js +1475 -0
- package/lib/harness/console-commands.d.ts +463 -0
- package/lib/harness/console-commands.js +3216 -0
- package/lib/harness/download-watch.d.ts +21 -0
- package/lib/harness/download-watch.js +156 -0
- package/lib/harness/extension-backend.d.ts +41 -0
- package/lib/harness/extension-backend.js +100 -0
- package/lib/harness/extension-host.d.ts +112 -0
- package/lib/harness/extension-host.js +552 -0
- package/lib/harness/extension-launch.d.ts +67 -0
- package/lib/harness/extension-launch.js +285 -0
- package/lib/harness/extension-link.d.ts +315 -0
- package/lib/harness/extension-link.js +607 -0
- package/lib/harness/external-manager.d.ts +146 -0
- package/lib/harness/external-manager.js +414 -0
- package/lib/harness/fixture.d.ts +165 -0
- package/lib/harness/fixture.js +461 -0
- package/lib/harness/frame-sessions.d.ts +71 -0
- package/lib/harness/frame-sessions.js +248 -0
- package/lib/harness/frames.d.ts +68 -0
- package/lib/harness/frames.js +216 -0
- package/lib/harness/help-format.d.ts +17 -0
- package/lib/harness/help-format.js +127 -0
- package/lib/harness/host.d.ts +352 -0
- package/lib/harness/host.js +423 -0
- package/lib/harness/index.d.ts +8 -0
- package/lib/harness/index.js +10 -0
- package/lib/harness/input-file.d.ts +18 -0
- package/lib/harness/input-file.js +61 -0
- package/lib/harness/json-output.d.ts +565 -0
- package/lib/harness/json-output.js +71 -0
- package/lib/harness/manager-actions.d.ts +21 -0
- package/lib/harness/manager-actions.js +129 -0
- package/lib/harness/manager-commands.d.ts +142 -0
- package/lib/harness/manager-commands.js +1196 -0
- package/lib/harness/manager-fetch.d.ts +33 -0
- package/lib/harness/manager-fetch.js +239 -0
- package/lib/harness/managers.d.ts +24 -0
- package/lib/harness/managers.js +27 -0
- package/lib/harness/output-style.d.ts +125 -0
- package/lib/harness/output-style.js +322 -0
- package/lib/harness/page-commands.d.ts +134 -0
- package/lib/harness/page-commands.js +1141 -0
- package/lib/harness/page-output.d.ts +67 -0
- package/lib/harness/page-output.js +139 -0
- package/lib/harness/page-watch.d.ts +171 -0
- package/lib/harness/page-watch.js +607 -0
- package/lib/harness/parse-check-node.d.ts +3 -0
- package/lib/harness/parse-check-node.js +132 -0
- package/lib/harness/path-args.d.ts +22 -0
- package/lib/harness/path-args.js +148 -0
- package/lib/harness/playwright-chromium.d.ts +16 -0
- package/lib/harness/playwright-chromium.js +86 -0
- package/lib/harness/prepare.d.ts +168 -0
- package/lib/harness/prepare.js +364 -0
- package/lib/harness/prompt-editor.d.ts +17 -0
- package/lib/harness/prompt-editor.js +354 -0
- package/lib/harness/prompt-history.d.ts +13 -0
- package/lib/harness/prompt-history.js +89 -0
- package/lib/harness/rebuild-advice.d.ts +6 -0
- package/lib/harness/rebuild-advice.js +39 -0
- package/lib/harness/run-log.d.ts +47 -0
- package/lib/harness/run-log.js +140 -0
- package/lib/harness/run-logs.d.ts +132 -0
- package/lib/harness/run-logs.js +241 -0
- package/lib/harness/run.d.ts +194 -0
- package/lib/harness/run.js +1973 -0
- package/lib/harness/runs.d.ts +229 -0
- package/lib/harness/runs.js +726 -0
- package/lib/harness/save-html.d.ts +39 -0
- package/lib/harness/save-html.js +89 -0
- package/lib/harness/saved-pages.d.ts +120 -0
- package/lib/harness/saved-pages.js +275 -0
- package/lib/harness/script-mark.d.ts +17 -0
- package/lib/harness/script-mark.js +56 -0
- package/lib/harness/snapshot.d.ts +143 -0
- package/lib/harness/snapshot.js +474 -0
- package/lib/harness/stuck-tabs.d.ts +19 -0
- package/lib/harness/stuck-tabs.js +131 -0
- package/lib/harness/tab-refs.d.ts +20 -0
- package/lib/harness/tab-refs.js +80 -0
- package/lib/harness/tampermonkey.d.ts +71 -0
- package/lib/harness/tampermonkey.js +356 -0
- package/lib/harness/tm-ids.d.ts +18 -0
- package/lib/harness/tm-ids.js +88 -0
- package/lib/harness/violentmonkey.d.ts +34 -0
- package/lib/harness/violentmonkey.js +162 -0
- package/lib/harness/wait.d.ts +77 -0
- package/lib/harness/wait.js +458 -0
- package/lib/http-read.d.ts +30 -0
- package/lib/http-read.js +142 -0
- package/lib/local-file-overrides.d.ts +35 -0
- package/lib/local-file-overrides.js +558 -0
- package/lib/manifest.json +38 -0
- package/lib/markup-response.d.ts +11 -0
- package/lib/markup-response.js +126 -0
- package/lib/match-targets.d.ts +18 -0
- package/lib/match-targets.js +305 -0
- package/lib/menu-captions.d.ts +1 -0
- package/lib/menu-captions.js +22 -0
- package/lib/parse-check-protocol.d.ts +27 -0
- package/lib/parse-check-protocol.js +51 -0
- package/lib/parse-location.d.ts +12 -0
- package/lib/parse-location.js +171 -0
- package/lib/phase-timing.d.ts +12 -0
- package/lib/phase-timing.js +155 -0
- package/lib/public-suffixes.d.ts +1 -0
- package/lib/public-suffixes.js +6957 -0
- package/lib/run-context.d.ts +2 -0
- package/lib/run-context.js +31 -0
- package/lib/runtime-messages.d.ts +10 -0
- package/lib/runtime-messages.js +34 -0
- package/lib/sandboxed-page.d.ts +5 -0
- package/lib/sandboxed-page.js +35 -0
- package/lib/script-build.d.ts +111 -0
- package/lib/script-build.js +787 -0
- package/lib/script-display.d.ts +51 -0
- package/lib/script-display.js +383 -0
- package/lib/script-icon.d.ts +16 -0
- package/lib/script-icon.js +43 -0
- package/lib/script-links.d.ts +24 -0
- package/lib/script-links.js +204 -0
- package/lib/script-source-url.d.ts +8 -0
- package/lib/script-source-url.js +214 -0
- package/lib/script-world.d.ts +6 -0
- package/lib/script-world.js +65 -0
- package/lib/sm-dev-protocol.d.ts +251 -0
- package/lib/sm-dev-protocol.js +84 -0
- package/lib/source-map.d.ts +32 -0
- package/lib/source-map.js +177 -0
- package/lib/stack-link.d.ts +15 -0
- package/lib/stack-link.js +138 -0
- package/lib/unsafe-window-use.d.ts +1 -0
- package/lib/unsafe-window-use.js +229 -0
- package/lib/url-match.d.ts +40 -0
- package/lib/url-match.js +734 -0
- package/lib/userscript-header.d.ts +33 -0
- package/lib/userscript-header.js +248 -0
- package/lib/version-compare.d.ts +6 -0
- package/lib/version-compare.js +141 -0
- package/lib/version-probe.d.ts +17 -0
- package/lib/version-probe.js +120 -0
- package/package.json +55 -3
- package/skills/SourceMonkey-dev/SKILL.md +868 -0
- package/skills/SourceMonkey-dev/references/cdp.md +293 -0
- package/skills/SourceMonkey-dev/references/commands.md +297 -0
- package/skills/SourceMonkey-dev/references/other-managers.md +63 -0
- package/skills/SourceMonkey-dev/references/specs.md +46 -0
- package/skills/SourceMonkey-friction/SKILL.md +103 -0
- package/skills/SourceMonkey-friction/references/template.md +59 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
// `@connect` URL gating for `GM_xmlhttpRequest`.
|
|
2
|
+
//
|
|
3
|
+
// @connect grammar (following Tampermonkey's docs):
|
|
4
|
+
// - `*` — wildcard, allow any host.
|
|
5
|
+
// - `self` — allow same-origin requests (matched against the
|
|
6
|
+
// script's @match / @include hosts).
|
|
7
|
+
// - `example.com` — that host OR any subdomain of it
|
|
8
|
+
// (`api.example.com`), as Tampermonkey documents. A label-boundary
|
|
9
|
+
// match: `evilexample.com` does not match. IP addresses and
|
|
10
|
+
// single-label names (`127.0.0.1`, `localhost`, `com`) match only
|
|
11
|
+
// exactly, so a bare rule can't stand for a whole TLD or IP range.
|
|
12
|
+
// Multi-label public suffixes (`co.uk`) are not special-cased.
|
|
13
|
+
// - `*.example.com` — `example.com` itself OR any subdomain.
|
|
14
|
+
//
|
|
15
|
+
// SW-side validation: when a script issues `GM_xmlhttpRequest`, the
|
|
16
|
+
// SW pulls the script's stored header, parses out the `@connect`
|
|
17
|
+
// lines, and rejects the request if the URL's host doesn't satisfy
|
|
18
|
+
// any of them.
|
|
19
|
+
//
|
|
20
|
+
// A host no rule covers isn't refused outright: the extension asks the
|
|
21
|
+
// user, as Tampermonkey does, and remembers the answer per script (see
|
|
22
|
+
// `decideConnect` and docs/connect-permissions.md). Violentmonkey doesn't enforce
|
|
23
|
+
// @connect at all.
|
|
24
|
+
import { getValues } from './userscript-header.js';
|
|
25
|
+
// Lower-case, normalize, classify a single raw @connect value.
|
|
26
|
+
// Returns null for empty / malformed entries.
|
|
27
|
+
export function parseConnectEntry(raw) {
|
|
28
|
+
const t = raw.trim().toLowerCase();
|
|
29
|
+
if (t === '')
|
|
30
|
+
return null;
|
|
31
|
+
if (t === '*')
|
|
32
|
+
return { kind: 'wildcard' };
|
|
33
|
+
if (t === 'self')
|
|
34
|
+
return { kind: 'self' };
|
|
35
|
+
if (t.startsWith('*.')) {
|
|
36
|
+
const host = t.slice(2);
|
|
37
|
+
if (!host)
|
|
38
|
+
return null;
|
|
39
|
+
return { kind: 'subdomain', host };
|
|
40
|
+
}
|
|
41
|
+
// Strip a leading scheme if the user pasted a URL by mistake — TM
|
|
42
|
+
// accepts both `example.com` and `https://example.com` in @connect
|
|
43
|
+
// lines; we normalize to the host.
|
|
44
|
+
try {
|
|
45
|
+
if (/^[a-z]+:\/\//.test(t)) {
|
|
46
|
+
const u = new URL(t);
|
|
47
|
+
return { kind: 'host', host: u.hostname };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// Fall through — treat the raw value as a host.
|
|
52
|
+
}
|
|
53
|
+
return { kind: 'host', host: t };
|
|
54
|
+
}
|
|
55
|
+
// Parse every `@connect` line from a header, dropping empties.
|
|
56
|
+
export function parseConnectRules(header) {
|
|
57
|
+
const out = [];
|
|
58
|
+
for (const raw of getValues(header, 'connect')) {
|
|
59
|
+
const rule = parseConnectEntry(raw);
|
|
60
|
+
if (rule)
|
|
61
|
+
out.push(rule);
|
|
62
|
+
}
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
// Match `host` (lowercased; no port) against one rule. The `self`
|
|
66
|
+
// rule is matched against the union of `selfHosts` — the set of
|
|
67
|
+
// hostnames the script declared via @match / @include.
|
|
68
|
+
export function hostMatchesRule(host, rule, selfHosts) {
|
|
69
|
+
const h = host.toLowerCase();
|
|
70
|
+
switch (rule.kind) {
|
|
71
|
+
case 'wildcard':
|
|
72
|
+
return true;
|
|
73
|
+
case 'self':
|
|
74
|
+
// TM/VM both treat `self` as "the script's own origins or
|
|
75
|
+
// their subdomains". We're conservative: exact match only.
|
|
76
|
+
// See TODO.md "@connect self subdomain handling" — known
|
|
77
|
+
// cross-manager divergence pending a real compat report.
|
|
78
|
+
return selfHosts.has(h);
|
|
79
|
+
case 'host':
|
|
80
|
+
// A bare `example.com` covers its subdomains too, as Tampermonkey
|
|
81
|
+
// documents. But not a bare IP or single label: `@connect 0.0.1`
|
|
82
|
+
// must not reach `127.0.0.1`, nor `@connect com` all of `.com`.
|
|
83
|
+
if (!rule.host)
|
|
84
|
+
return false;
|
|
85
|
+
if (h === rule.host)
|
|
86
|
+
return true;
|
|
87
|
+
return !isExactOnlyHost(rule.host) && h.endsWith('.' + rule.host);
|
|
88
|
+
case 'subdomain':
|
|
89
|
+
// The `.` prefix keeps it to whole labels, so `evilexample.com`
|
|
90
|
+
// doesn't match `*.example.com`.
|
|
91
|
+
if (!rule.host)
|
|
92
|
+
return false;
|
|
93
|
+
return h === rule.host || h.endsWith('.' + rule.host);
|
|
94
|
+
default:
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// A bare `@connect` host that has no subdomains to cover: an IP
|
|
99
|
+
// address (all-numeric dotted, or IPv6 with `:`), or a single label
|
|
100
|
+
// (`localhost`, `com`).
|
|
101
|
+
function isExactOnlyHost(host) {
|
|
102
|
+
return /^[\d.]+$/.test(host) || host.includes(':') || !host.includes('.');
|
|
103
|
+
}
|
|
104
|
+
// True when the header has no usable `@connect` rule, so all its
|
|
105
|
+
// `GM_xmlhttpRequest` calls to other sites need the user's say-so. The
|
|
106
|
+
// same parse enforcement uses, so a header whose `@connect` lines are
|
|
107
|
+
// all malformed counts.
|
|
108
|
+
export function hasNoConnectRules(header) {
|
|
109
|
+
return parseConnectRules(header).length === 0;
|
|
110
|
+
}
|
|
111
|
+
// Extract the set of "self" hosts from a script header's match/
|
|
112
|
+
// include lines. Used to resolve the `self` @connect token.
|
|
113
|
+
// Patterns we don't understand (regex includes, malformed globs)
|
|
114
|
+
// are skipped — they don't contribute to "self".
|
|
115
|
+
export function extractSelfHosts(header) {
|
|
116
|
+
const out = new Set();
|
|
117
|
+
const all = [...getValues(header, 'match'), ...getValues(header, 'include')];
|
|
118
|
+
for (const pat of all) {
|
|
119
|
+
const host = extractHostFromPattern(pat);
|
|
120
|
+
if (host)
|
|
121
|
+
out.add(host.toLowerCase());
|
|
122
|
+
}
|
|
123
|
+
return out;
|
|
124
|
+
}
|
|
125
|
+
// Pull a hostname out of a `@match` / `@include` glob if it has one.
|
|
126
|
+
// `<all_urls>`, regex literals, and host-less patterns return null.
|
|
127
|
+
function extractHostFromPattern(pat) {
|
|
128
|
+
if (pat === '<all_urls>')
|
|
129
|
+
return null;
|
|
130
|
+
if (pat.startsWith('/') && pat.endsWith('/'))
|
|
131
|
+
return null; // regex literal
|
|
132
|
+
// Match an URL-like glob: <scheme>://<host>/...
|
|
133
|
+
const m = /^[\w*]+:\/\/([^/]+)\//.exec(pat);
|
|
134
|
+
if (!m)
|
|
135
|
+
return null;
|
|
136
|
+
const host = m[1];
|
|
137
|
+
// A pure `*` host doesn't tell us anything useful for "self".
|
|
138
|
+
if (host === '*')
|
|
139
|
+
return null;
|
|
140
|
+
// `*.example.com` → use `example.com` as the self host.
|
|
141
|
+
if (host.startsWith('*.'))
|
|
142
|
+
return host.slice(2);
|
|
143
|
+
return host;
|
|
144
|
+
}
|
|
145
|
+
export function decideConnect(url, rules, selfHosts, pageOrigin) {
|
|
146
|
+
let parsed;
|
|
147
|
+
try {
|
|
148
|
+
parsed = new URL(url);
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
return { verdict: 'invalid', reason: `${url} is not a valid URL` };
|
|
152
|
+
}
|
|
153
|
+
const web = parsed.protocol === 'http:' || parsed.protocol === 'https:';
|
|
154
|
+
const page = webOrigin(pageOrigin);
|
|
155
|
+
if (web && page && parsed.origin === page.origin)
|
|
156
|
+
return { verdict: 'allow' };
|
|
157
|
+
const host = parsed.hostname.toLowerCase();
|
|
158
|
+
const selfs = page ? new Set([...selfHosts, page.hostname]) : selfHosts;
|
|
159
|
+
for (const rule of rules) {
|
|
160
|
+
// A host-less URL has nothing for a host rule to match, but `*`
|
|
161
|
+
// means any URL at all.
|
|
162
|
+
if (!host && rule.kind !== 'wildcard')
|
|
163
|
+
continue;
|
|
164
|
+
if (hostMatchesRule(host, rule, selfs))
|
|
165
|
+
return { verdict: 'allow' };
|
|
166
|
+
}
|
|
167
|
+
if (web && host)
|
|
168
|
+
return { verdict: 'ask', host };
|
|
169
|
+
return {
|
|
170
|
+
verdict: 'deny',
|
|
171
|
+
reason: host
|
|
172
|
+
? `Only http(s) sites can be allowed, so only @connect * covers a ${parsed.protocol} URL.`
|
|
173
|
+
: `A ${parsed.protocol} URL has no host to allow, so only @connect * covers it.`,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
// The page's origin when it is an http(s) one, else null.
|
|
177
|
+
function webOrigin(origin) {
|
|
178
|
+
if (!origin)
|
|
179
|
+
return null;
|
|
180
|
+
try {
|
|
181
|
+
const u = new URL(origin);
|
|
182
|
+
return u.protocol === 'http:' || u.protocol === 'https:' ? u : null;
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
// Every error `GM_xmlhttpRequest` hands back to a userscript starts
|
|
189
|
+
// here, so the three paths that can fail it — the pre-flight check, the
|
|
190
|
+
// post-redirect re-check (`gm-xhr.ts`), and a policy we couldn't even
|
|
191
|
+
// load (`background.ts`) — read the same way.
|
|
192
|
+
export function gmXhrError(problem) {
|
|
193
|
+
return `[SourceMonkey] GM_xmlhttpRequest: ${problem}`;
|
|
194
|
+
}
|
|
195
|
+
// The one phrasing for "we could not set the headers you asked for".
|
|
196
|
+
// Says the request was not sent, because the alternative a reader would
|
|
197
|
+
// assume — sent without them — is exactly what we refuse to do: the
|
|
198
|
+
// headers here are the ones the browser won't let a script set, and
|
|
199
|
+
// dropping a `Cookie` override would send the user's own session
|
|
200
|
+
// instead.
|
|
201
|
+
export function headerRuleFailedMessage() {
|
|
202
|
+
return gmXhrError('The headers this request asked for could not be applied, so it was not sent');
|
|
203
|
+
}
|
|
204
|
+
// The one phrasing for a request the server never started answering.
|
|
205
|
+
// Says "no response headers" rather than "timed out" because the script
|
|
206
|
+
// may have set no timeout at all — this is the worker's own backstop,
|
|
207
|
+
// and a request left waiting forever would hold its header rule and
|
|
208
|
+
// block later requests to the same URL.
|
|
209
|
+
export function headerStallMessage(stallMs) {
|
|
210
|
+
const minutes = Math.round(stallMs / 60000);
|
|
211
|
+
const howLong = minutes === 1 ? '1 minute' : `${minutes} minutes`;
|
|
212
|
+
return gmXhrError(`The server sent no response headers within ${howLong}, so this request was given up on`);
|
|
213
|
+
}
|
|
214
|
+
// For a request the script asked to fail on a redirect
|
|
215
|
+
// (`redirect: 'error'`), which got one.
|
|
216
|
+
export function redirectRefusedMessage() {
|
|
217
|
+
return gmXhrError('The server redirected the request, and the script asked to fail on a redirect (redirect: "error")');
|
|
218
|
+
}
|
|
219
|
+
// For a request that never got a response: Chrome's `fetch` rejects
|
|
220
|
+
// with a bare "Failed to fetch" for a name that doesn't resolve, a
|
|
221
|
+
// refused or dropped connection, a failed TLS handshake, and a request
|
|
222
|
+
// another extension blocked alike, so the causes are listed rather than
|
|
223
|
+
// one guessed (see TODO.md for telling them apart). The host is the
|
|
224
|
+
// request's own; a redirect `fetch` followed to a dead host lands here
|
|
225
|
+
// too, with nothing to tell us which hop failed, so the message covers
|
|
226
|
+
// a site the host redirected to.
|
|
227
|
+
export function unreachableMessage(host) {
|
|
228
|
+
return gmXhrError(`Could not reach ${host} or a site it redirected to. The name may not resolve, or the connection was refused, failed or blocked.`);
|
|
229
|
+
}
|
|
230
|
+
// The one phrasing for a request the script's own header forbids: it
|
|
231
|
+
// names whose rules blocked the call rather than a "policy" object the
|
|
232
|
+
// reader can't see. `reason` is a sentence of its own.
|
|
233
|
+
//
|
|
234
|
+
// Only for a real denial. A URL we couldn't parse, or a failure to
|
|
235
|
+
// *evaluate* the rules, is not the header's doing and goes through
|
|
236
|
+
// `gmXhrError` directly.
|
|
237
|
+
export function connectBlockedMessage(reason) {
|
|
238
|
+
return gmXhrError(`This script's @connect rules don't allow this request. ${reason}`);
|
|
239
|
+
}
|
|
240
|
+
// The one phrasing for a host the user refused this script in the
|
|
241
|
+
// prompt. Says where the refusal can be undone, since it lasts until
|
|
242
|
+
// reset.
|
|
243
|
+
export function connectDeniedMessage(host) {
|
|
244
|
+
return gmXhrError(`Connecting to ${host} was denied for this script. `
|
|
245
|
+
+ 'Manage permissions on the script\'s page in SourceMonkey.');
|
|
246
|
+
}
|
|
247
|
+
// The one phrasing for a request whose prompt the user cancelled (or
|
|
248
|
+
// closed). Nothing is kept, so the next request asks again, which goes
|
|
249
|
+
// without saying.
|
|
250
|
+
export function connectCancelledMessage(host) {
|
|
251
|
+
return gmXhrError(`SourceMonkey's permission prompt for connecting to ${host} was cancelled, so this request was blocked`);
|
|
252
|
+
}
|
|
253
|
+
// The one phrasing for a prompt that couldn't be shown. `cause` is
|
|
254
|
+
// Chrome's reason.
|
|
255
|
+
export function connectPromptFailedMessage(host, cause) {
|
|
256
|
+
// Chrome's reason, capitalized as a tail after `: ` is.
|
|
257
|
+
const tail = cause.charAt(0).toUpperCase() + cause.slice(1);
|
|
258
|
+
return gmXhrError(`SourceMonkey could not open its permission prompt for connecting to ${host}: ${tail}`);
|
|
259
|
+
}
|
|
260
|
+
// The one phrasing for a request refused because the script already has
|
|
261
|
+
// as many prompts open as it may (`MAX_PROMPTS_PER_SCRIPT`).
|
|
262
|
+
export function connectTooManyPromptsMessage(host, open) {
|
|
263
|
+
return gmXhrError(`SourceMonkey is already asking about ${open} other sites for this script, so this request to ${host} was blocked. `
|
|
264
|
+
+ 'Answer those permission prompts, then try again.');
|
|
265
|
+
}
|
|
266
|
+
// The one phrasing for a request whose prompt went unanswered too long.
|
|
267
|
+
// The prompt closes with it, so the next request asks again.
|
|
268
|
+
export function connectUnansweredMessage(host, waitMs) {
|
|
269
|
+
const minutes = Math.round(waitMs / 60000);
|
|
270
|
+
const howLong = minutes === 1 ? '1 minute' : `${minutes} minutes`;
|
|
271
|
+
return gmXhrError(`SourceMonkey's permission prompt for connecting to ${host} had no answer within ${howLong}, `
|
|
272
|
+
+ 'so this request was stopped');
|
|
273
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// One http(s) read, made where it is safe to make it.
|
|
2
|
+
//
|
|
3
|
+
// ---------- Why this module exists ----------
|
|
4
|
+
//
|
|
5
|
+
// A `fetch()` issued by a DOCUMENT hands that document the response's
|
|
6
|
+
// `Link: <…>; rel=preload; as=script` headers, and Chrome acts on them
|
|
7
|
+
// — it tries to preload the named script into the page that asked. Our
|
|
8
|
+
// pages are extension pages, whose CSP is `script-src 'self'`, so the
|
|
9
|
+
// preload is refused, and Chrome files the refusal under
|
|
10
|
+
// `chrome://extensions/?errors`: an uncatchable error, about a script
|
|
11
|
+
// the page never asked for, that reads as SourceMonkey being broken.
|
|
12
|
+
//
|
|
13
|
+
// Nothing exotic triggers it. Any site that preloads its own bundles
|
|
14
|
+
// sends the header on every page, and we fetch site HTML (the site-icon
|
|
15
|
+
// probe) and arbitrary script URLs (Add / Refresh / the install
|
|
16
|
+
// preview) from pages all the time. A response that turns out to be a
|
|
17
|
+
// login page — the case `fetchHttpText` already has a branch for — is
|
|
18
|
+
// among the most likely of all to carry one.
|
|
19
|
+
//
|
|
20
|
+
// A service worker has no document for a hint to apply to, so the same
|
|
21
|
+
// read there is inert. Hence this module: pages hand the read to the
|
|
22
|
+
// worker and get the response back over `chrome.runtime`. Both
|
|
23
|
+
// directions are pinned by `tests/e2e/preload-hints.spec.ts`.
|
|
24
|
+
//
|
|
25
|
+
// ---------- Shape ----------
|
|
26
|
+
//
|
|
27
|
+
// The worker performs the fetch and reports what came back; every
|
|
28
|
+
// caller keeps its own rules about what the response MEANS (which
|
|
29
|
+
// statuses are failures, which content types are acceptable, how a 304
|
|
30
|
+
// is reported). That split is deliberate: the two callers
|
|
31
|
+
// (`fetchResourceBytes` for bytes, `fetchHttpText` for script and
|
|
32
|
+
// manifest text) disagree about all of it, and a handler that tried to
|
|
33
|
+
// decide for both would have to grow their union.
|
|
34
|
+
//
|
|
35
|
+
// `as: 'text'` decodes in the worker via `Response.text()` rather than
|
|
36
|
+
// shipping bytes for the page to decode, so a response's charset is
|
|
37
|
+
// honoured exactly as it was before the hop existed.
|
|
38
|
+
import { base64ToBytes, bytesToBase64 } from './bytes.js';
|
|
39
|
+
import { isPageContext } from './run-context.js';
|
|
40
|
+
import { ensureGithubAuthTokenLoaded, githubAuthHeaders } from './github-auth.js';
|
|
41
|
+
export const HTTP_READ_MESSAGE = 'sm:http-read';
|
|
42
|
+
// Service-worker side. `background.ts` owns the listener (and the
|
|
43
|
+
// sender check that keeps this from being an open fetch proxy — the
|
|
44
|
+
// extension holds `<all_urls>`).
|
|
45
|
+
export async function handleHttpReadRequest(req) {
|
|
46
|
+
// The worker may have woken up to answer this, with its startup token
|
|
47
|
+
// read still in flight — a private GitHub raw URL would 404 on a race
|
|
48
|
+
// the page-side fetch never had.
|
|
49
|
+
await ensureGithubAuthTokenLoaded();
|
|
50
|
+
const headers = { ...(req.headers ?? {}), ...githubAuthHeaders(req.url) };
|
|
51
|
+
const controller = new AbortController();
|
|
52
|
+
const timer = req.timeoutMs != null
|
|
53
|
+
? setTimeout(() => controller.abort(), req.timeoutMs)
|
|
54
|
+
: null;
|
|
55
|
+
let response;
|
|
56
|
+
try {
|
|
57
|
+
response = await fetch(req.url, {
|
|
58
|
+
cache: 'no-store',
|
|
59
|
+
redirect: 'follow',
|
|
60
|
+
referrerPolicy: 'no-referrer',
|
|
61
|
+
signal: controller.signal,
|
|
62
|
+
headers,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
// Our own timer is the only thing that aborts this request, so an
|
|
67
|
+
// aborted signal identifies the timeout exactly rather than leaving
|
|
68
|
+
// it merged with a network failure.
|
|
69
|
+
return controller.signal.aborted
|
|
70
|
+
? { kind: 'error', reason: 'timeout' }
|
|
71
|
+
: { kind: 'error', reason: 'no-response', message: errorText(err) };
|
|
72
|
+
}
|
|
73
|
+
finally {
|
|
74
|
+
if (timer != null)
|
|
75
|
+
clearTimeout(timer);
|
|
76
|
+
}
|
|
77
|
+
// Size checks are for a body we are about to read. A 304 has none —
|
|
78
|
+
// its `Content-Length`, where a server sends one at all, describes the
|
|
79
|
+
// entity that WOULD have been sent. Rejecting on it would fail a
|
|
80
|
+
// conditional read as "too large" for a file that didn't change, and
|
|
81
|
+
// only on this leg: the local path tests 304 before any size check, so
|
|
82
|
+
// the two would disagree about the same response.
|
|
83
|
+
const contentLength = response.headers.get('content-length');
|
|
84
|
+
if (response.status !== 304 &&
|
|
85
|
+
req.maxBytes != null && contentLength && Number(contentLength) > req.maxBytes) {
|
|
86
|
+
return { kind: 'error', reason: 'oversize' };
|
|
87
|
+
}
|
|
88
|
+
const base = {
|
|
89
|
+
kind: 'response',
|
|
90
|
+
status: response.status,
|
|
91
|
+
// Verbatim, not lowercased: callers quote it back as evidence of
|
|
92
|
+
// what the server actually sent.
|
|
93
|
+
contentType: response.headers.get('content-type') ?? '',
|
|
94
|
+
etag: response.headers.get('ETag'),
|
|
95
|
+
lastModified: response.headers.get('Last-Modified'),
|
|
96
|
+
finalUrl: response.url || req.url,
|
|
97
|
+
};
|
|
98
|
+
try {
|
|
99
|
+
if (req.as === 'text')
|
|
100
|
+
return { ...base, text: await response.text() };
|
|
101
|
+
const body = await response.arrayBuffer();
|
|
102
|
+
if (req.maxBytes != null && body.byteLength > req.maxBytes) {
|
|
103
|
+
return { kind: 'error', reason: 'oversize' };
|
|
104
|
+
}
|
|
105
|
+
return { ...base, bodyBase64: bytesToBase64(new Uint8Array(body)) };
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
// The body never finished arriving (connection dropped mid-read).
|
|
109
|
+
return { kind: 'error', reason: 'no-response', message: errorText(err) };
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// True when this context must not fetch http(s) itself — i.e. it is a
|
|
113
|
+
// page rather than the service worker.
|
|
114
|
+
export function mustHopToWorker() {
|
|
115
|
+
return isPageContext();
|
|
116
|
+
}
|
|
117
|
+
// Page side. Returns null — not an error — when the request got no
|
|
118
|
+
// answer at all, so the caller can fall back to fetching locally. That
|
|
119
|
+
// reopens the preload leak for that one read, which beats failing a
|
|
120
|
+
// read the user asked for.
|
|
121
|
+
export async function readHttpViaWorker(req) {
|
|
122
|
+
let reply;
|
|
123
|
+
try {
|
|
124
|
+
reply = await chrome.runtime.sendMessage({
|
|
125
|
+
type: HTTP_READ_MESSAGE,
|
|
126
|
+
...req,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return null; // no receiving end
|
|
131
|
+
}
|
|
132
|
+
return reply ?? null;
|
|
133
|
+
}
|
|
134
|
+
// The bytes of a `kind: 'response'` reply, whichever form it arrived in.
|
|
135
|
+
export function replyBytes(reply) {
|
|
136
|
+
if (reply.bodyBase64 != null)
|
|
137
|
+
return base64ToBytes(reply.bodyBase64);
|
|
138
|
+
return new TextEncoder().encode(reply.text ?? '').buffer;
|
|
139
|
+
}
|
|
140
|
+
function errorText(err) {
|
|
141
|
+
return err instanceof Error ? err.message : String(err);
|
|
142
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|