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.
Files changed (367) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +87 -2
  3. package/dist/bytes.js +37 -0
  4. package/dist/channel-placeholder.js +5 -0
  5. package/dist/collection-scan.js +17 -0
  6. package/dist/collections.html +353 -0
  7. package/dist/compare.html +358 -0
  8. package/dist/composed-source.js +205 -0
  9. package/dist/connect-prompt.html +47 -0
  10. package/dist/debug/icons.html +211 -0
  11. package/dist/debug/storage.html +163 -0
  12. package/dist/debug-log.js +75 -0
  13. package/dist/diagnostic-location.js +64 -0
  14. package/dist/eval-hook.js +308 -0
  15. package/dist/extension/background.js +5263 -0
  16. package/dist/extension/channel.js +133 -0
  17. package/dist/extension/code-links.js +187 -0
  18. package/dist/extension/code-tabs.js +460 -0
  19. package/dist/extension/collection-dialog.js +619 -0
  20. package/dist/extension/collection-display.js +322 -0
  21. package/dist/extension/collections.js +2905 -0
  22. package/dist/extension/compare.js +867 -0
  23. package/dist/extension/concurrency.js +186 -0
  24. package/dist/extension/connect-grants.js +109 -0
  25. package/dist/extension/connect-prompt-page.js +70 -0
  26. package/dist/extension/connect-prompt.js +266 -0
  27. package/dist/extension/copy-button.js +63 -0
  28. package/dist/extension/copy-dialog.js +502 -0
  29. package/dist/extension/copy-preference.js +66 -0
  30. package/dist/extension/debug/icons.js +203 -0
  31. package/dist/extension/debug/storage.js +365 -0
  32. package/dist/extension/deep-link.js +77 -0
  33. package/dist/extension/delete-dialog.js +432 -0
  34. package/dist/extension/delete-icon.js +40 -0
  35. package/dist/extension/details-tooltips.js +66 -0
  36. package/dist/extension/directories.js +679 -0
  37. package/dist/extension/export-backup.js +407 -0
  38. package/dist/extension/export-dialog.js +142 -0
  39. package/dist/extension/file-access.js +74 -0
  40. package/dist/extension/file-changes.js +346 -0
  41. package/dist/extension/frame-info.js +85 -0
  42. package/dist/extension/github-provider.js +336 -0
  43. package/dist/extension/glyph.js +84 -0
  44. package/dist/extension/gm-bindings.js +601 -0
  45. package/dist/extension/gm-download.js +419 -0
  46. package/dist/extension/gm-menu.js +228 -0
  47. package/dist/extension/gm-notification.js +141 -0
  48. package/dist/extension/gm-originator-map.js +23 -0
  49. package/dist/extension/gm-tab-data.js +121 -0
  50. package/dist/extension/gm-tab.js +160 -0
  51. package/dist/extension/gm-url-change.js +111 -0
  52. package/dist/extension/gm-url-scheme.js +27 -0
  53. package/dist/extension/gm-values.js +176 -0
  54. package/dist/extension/gm-window-control.js +104 -0
  55. package/dist/extension/gm-xhr-headers.js +333 -0
  56. package/dist/extension/gm-xhr.js +584 -0
  57. package/dist/extension/injector.js +3145 -0
  58. package/dist/extension/install-capture.js +277 -0
  59. package/dist/extension/line-diff.js +233 -0
  60. package/dist/extension/local-copy.js +560 -0
  61. package/dist/extension/local-delete.js +276 -0
  62. package/dist/extension/location-kinds.js +108 -0
  63. package/dist/extension/log-format.js +158 -0
  64. package/dist/extension/managed-changes.js +361 -0
  65. package/dist/extension/managed-items.js +57 -0
  66. package/dist/extension/match-index.js +375 -0
  67. package/dist/extension/missing-scripts.js +66 -0
  68. package/dist/extension/more-menu.js +109 -0
  69. package/dist/extension/move-dialog.js +197 -0
  70. package/dist/extension/offscreen.js +37 -0
  71. package/dist/extension/options.js +504 -0
  72. package/dist/extension/page-heading.js +170 -0
  73. package/dist/extension/page-links.js +199 -0
  74. package/dist/extension/parse-check-client.js +70 -0
  75. package/dist/extension/parse-check.js +161 -0
  76. package/dist/extension/parse-sandbox-host.js +172 -0
  77. package/dist/extension/parse-sandbox.js +175 -0
  78. package/dist/extension/popup-commands-open.js +41 -0
  79. package/dist/extension/popup-view.js +387 -0
  80. package/dist/extension/popup.js +703 -0
  81. package/dist/extension/prism-userscript.js +53 -0
  82. package/dist/extension/refresh-error.js +136 -0
  83. package/dist/extension/refresh-targets.js +160 -0
  84. package/dist/extension/related-versions.js +1144 -0
  85. package/dist/extension/resize-observer-loop.js +19 -0
  86. package/dist/extension/row-cache.js +387 -0
  87. package/dist/extension/script-blobs.js +413 -0
  88. package/dist/extension/script-details.js +658 -0
  89. package/dist/extension/script-enablement.js +285 -0
  90. package/dist/extension/script-identity.js +310 -0
  91. package/dist/extension/script-keys.js +54 -0
  92. package/dist/extension/script-log-id.js +155 -0
  93. package/dist/extension/script-log.js +290 -0
  94. package/dist/extension/script-rejection.js +104 -0
  95. package/dist/extension/script-sites.js +158 -0
  96. package/dist/extension/script-store.js +3507 -0
  97. package/dist/extension/script-updater.js +991 -0
  98. package/dist/extension/script-url-hash.js +61 -0
  99. package/dist/extension/select-all.js +37 -0
  100. package/dist/extension/select-open-guard.js +90 -0
  101. package/dist/extension/site-icon.js +1261 -0
  102. package/dist/extension/sm-dev-link.js +302 -0
  103. package/dist/extension/solo-click.js +58 -0
  104. package/dist/extension/sticky-numbers.js +188 -0
  105. package/dist/extension/storage-breakdown.js +253 -0
  106. package/dist/extension/storage-scan.js +86 -0
  107. package/dist/extension/storage-usage.js +194 -0
  108. package/dist/extension/structural-keys.js +29 -0
  109. package/dist/extension/sw-teardown.js +36 -0
  110. package/dist/extension/tab-status.js +403 -0
  111. package/dist/extension/toolbar-summary.js +105 -0
  112. package/dist/extension/user-scripts-access.js +43 -0
  113. package/dist/extension/userscript.js +3672 -0
  114. package/dist/extension/value-store.js +204 -0
  115. package/dist/extension/version-probe-fetch.js +68 -0
  116. package/dist/extension/worker-client.js +109 -0
  117. package/dist/extension/worker-ops.js +171 -0
  118. package/dist/extension/zip-writer.js +24 -0
  119. package/dist/external-files.js +878 -0
  120. package/dist/file-loader.js +436 -0
  121. package/dist/file-locations.js +439 -0
  122. package/dist/generated-wrappers.js +5 -0
  123. package/dist/github-auth.js +148 -0
  124. package/dist/github-urls.js +46 -0
  125. package/dist/gm-api-catalog.js +217 -0
  126. package/dist/gm-connect.js +273 -0
  127. package/dist/gm-xhr-types.js +5 -0
  128. package/dist/http-read.js +142 -0
  129. package/dist/icons/icon-128.png +0 -0
  130. package/dist/icons/icon-16.png +0 -0
  131. package/dist/icons/icon-48.png +0 -0
  132. package/dist/local-file-overrides.js +558 -0
  133. package/dist/manifest.json +38 -0
  134. package/dist/markup-response.js +126 -0
  135. package/dist/match-targets.js +305 -0
  136. package/dist/menu-captions.js +22 -0
  137. package/dist/offscreen.html +39 -0
  138. package/dist/options.html +197 -0
  139. package/dist/parse-check-protocol.js +51 -0
  140. package/dist/parse-location.js +171 -0
  141. package/dist/parse-sandbox.html +56 -0
  142. package/dist/phase-timing.js +155 -0
  143. package/dist/popup.html +197 -0
  144. package/dist/public-suffixes.js +6957 -0
  145. package/dist/run-context.js +31 -0
  146. package/dist/runtime-messages.js +34 -0
  147. package/dist/sandboxed-page.js +35 -0
  148. package/dist/script-build.js +787 -0
  149. package/dist/script-display.js +383 -0
  150. package/dist/script-icon.js +43 -0
  151. package/dist/script-links.js +204 -0
  152. package/dist/script-source-url.js +214 -0
  153. package/dist/script-world.js +65 -0
  154. package/dist/sm-dev-protocol.js +84 -0
  155. package/dist/source-map.js +177 -0
  156. package/dist/stack-link.js +138 -0
  157. package/dist/styles.css +825 -0
  158. package/dist/unsafe-window-use.js +229 -0
  159. package/dist/url-match.js +734 -0
  160. package/dist/userscript-header.js +248 -0
  161. package/dist/userscript.html +1165 -0
  162. package/dist/vendor/fflate.LICENSE +21 -0
  163. package/dist/vendor/fflate.js +2692 -0
  164. package/dist/vendor/prism/prism-line-numbers.css +1 -0
  165. package/dist/vendor/prism/prism-line-numbers.js +1 -0
  166. package/dist/vendor/prism/prism.css +1 -0
  167. package/dist/vendor/prism/prism.js +1946 -0
  168. package/dist/version-compare.js +141 -0
  169. package/dist/version-probe.js +120 -0
  170. package/lib/build-info.json +1 -0
  171. package/lib/bytes.d.ts +3 -0
  172. package/lib/bytes.js +37 -0
  173. package/lib/channel-placeholder.d.ts +1 -0
  174. package/lib/channel-placeholder.js +5 -0
  175. package/lib/collection-scan.d.ts +8 -0
  176. package/lib/collection-scan.js +17 -0
  177. package/lib/composed-source.d.ts +28 -0
  178. package/lib/composed-source.js +205 -0
  179. package/lib/debug-log.d.ts +9 -0
  180. package/lib/debug-log.js +75 -0
  181. package/lib/diagnostic-location.d.ts +8 -0
  182. package/lib/diagnostic-location.js +64 -0
  183. package/lib/eval-hook.d.ts +34 -0
  184. package/lib/eval-hook.js +308 -0
  185. package/lib/external-files.d.ts +111 -0
  186. package/lib/external-files.js +878 -0
  187. package/lib/file-loader.d.ts +48 -0
  188. package/lib/file-loader.js +436 -0
  189. package/lib/file-locations.d.ts +44 -0
  190. package/lib/file-locations.js +439 -0
  191. package/lib/generated-wrappers.d.ts +11 -0
  192. package/lib/generated-wrappers.js +5 -0
  193. package/lib/github-auth.d.ts +12 -0
  194. package/lib/github-auth.js +148 -0
  195. package/lib/github-urls.d.ts +1 -0
  196. package/lib/github-urls.js +46 -0
  197. package/lib/gm-api-catalog.d.ts +24 -0
  198. package/lib/gm-api-catalog.js +217 -0
  199. package/lib/gm-connect.d.ts +34 -0
  200. package/lib/gm-connect.js +273 -0
  201. package/lib/gm-xhr-types.d.ts +68 -0
  202. package/lib/gm-xhr-types.js +5 -0
  203. package/lib/harness/browser-extensions.d.ts +73 -0
  204. package/lib/harness/browser-extensions.js +285 -0
  205. package/lib/harness/browser-lang.d.ts +20 -0
  206. package/lib/harness/browser-lang.js +86 -0
  207. package/lib/harness/browser-launch.d.ts +14 -0
  208. package/lib/harness/browser-launch.js +184 -0
  209. package/lib/harness/checks.d.ts +110 -0
  210. package/lib/harness/checks.js +321 -0
  211. package/lib/harness/cli.d.ts +2 -0
  212. package/lib/harness/cli.js +2259 -0
  213. package/lib/harness/command-args.d.ts +91 -0
  214. package/lib/harness/command-args.js +369 -0
  215. package/lib/harness/command-lines.d.ts +80 -0
  216. package/lib/harness/command-lines.js +180 -0
  217. package/lib/harness/command-list.d.ts +206 -0
  218. package/lib/harness/command-list.js +1475 -0
  219. package/lib/harness/console-commands.d.ts +463 -0
  220. package/lib/harness/console-commands.js +3216 -0
  221. package/lib/harness/download-watch.d.ts +21 -0
  222. package/lib/harness/download-watch.js +156 -0
  223. package/lib/harness/extension-backend.d.ts +41 -0
  224. package/lib/harness/extension-backend.js +100 -0
  225. package/lib/harness/extension-host.d.ts +112 -0
  226. package/lib/harness/extension-host.js +552 -0
  227. package/lib/harness/extension-launch.d.ts +67 -0
  228. package/lib/harness/extension-launch.js +285 -0
  229. package/lib/harness/extension-link.d.ts +315 -0
  230. package/lib/harness/extension-link.js +607 -0
  231. package/lib/harness/external-manager.d.ts +146 -0
  232. package/lib/harness/external-manager.js +414 -0
  233. package/lib/harness/fixture.d.ts +165 -0
  234. package/lib/harness/fixture.js +461 -0
  235. package/lib/harness/frame-sessions.d.ts +71 -0
  236. package/lib/harness/frame-sessions.js +248 -0
  237. package/lib/harness/frames.d.ts +68 -0
  238. package/lib/harness/frames.js +216 -0
  239. package/lib/harness/help-format.d.ts +17 -0
  240. package/lib/harness/help-format.js +127 -0
  241. package/lib/harness/host.d.ts +352 -0
  242. package/lib/harness/host.js +423 -0
  243. package/lib/harness/index.d.ts +8 -0
  244. package/lib/harness/index.js +10 -0
  245. package/lib/harness/input-file.d.ts +18 -0
  246. package/lib/harness/input-file.js +61 -0
  247. package/lib/harness/json-output.d.ts +565 -0
  248. package/lib/harness/json-output.js +71 -0
  249. package/lib/harness/manager-actions.d.ts +21 -0
  250. package/lib/harness/manager-actions.js +129 -0
  251. package/lib/harness/manager-commands.d.ts +142 -0
  252. package/lib/harness/manager-commands.js +1196 -0
  253. package/lib/harness/manager-fetch.d.ts +33 -0
  254. package/lib/harness/manager-fetch.js +239 -0
  255. package/lib/harness/managers.d.ts +24 -0
  256. package/lib/harness/managers.js +27 -0
  257. package/lib/harness/output-style.d.ts +125 -0
  258. package/lib/harness/output-style.js +322 -0
  259. package/lib/harness/page-commands.d.ts +134 -0
  260. package/lib/harness/page-commands.js +1141 -0
  261. package/lib/harness/page-output.d.ts +67 -0
  262. package/lib/harness/page-output.js +139 -0
  263. package/lib/harness/page-watch.d.ts +171 -0
  264. package/lib/harness/page-watch.js +607 -0
  265. package/lib/harness/parse-check-node.d.ts +3 -0
  266. package/lib/harness/parse-check-node.js +132 -0
  267. package/lib/harness/path-args.d.ts +22 -0
  268. package/lib/harness/path-args.js +148 -0
  269. package/lib/harness/playwright-chromium.d.ts +16 -0
  270. package/lib/harness/playwright-chromium.js +86 -0
  271. package/lib/harness/prepare.d.ts +168 -0
  272. package/lib/harness/prepare.js +364 -0
  273. package/lib/harness/prompt-editor.d.ts +17 -0
  274. package/lib/harness/prompt-editor.js +354 -0
  275. package/lib/harness/prompt-history.d.ts +13 -0
  276. package/lib/harness/prompt-history.js +89 -0
  277. package/lib/harness/rebuild-advice.d.ts +6 -0
  278. package/lib/harness/rebuild-advice.js +39 -0
  279. package/lib/harness/run-log.d.ts +47 -0
  280. package/lib/harness/run-log.js +140 -0
  281. package/lib/harness/run-logs.d.ts +132 -0
  282. package/lib/harness/run-logs.js +241 -0
  283. package/lib/harness/run.d.ts +194 -0
  284. package/lib/harness/run.js +1973 -0
  285. package/lib/harness/runs.d.ts +229 -0
  286. package/lib/harness/runs.js +726 -0
  287. package/lib/harness/save-html.d.ts +39 -0
  288. package/lib/harness/save-html.js +89 -0
  289. package/lib/harness/saved-pages.d.ts +120 -0
  290. package/lib/harness/saved-pages.js +275 -0
  291. package/lib/harness/script-mark.d.ts +17 -0
  292. package/lib/harness/script-mark.js +56 -0
  293. package/lib/harness/snapshot.d.ts +143 -0
  294. package/lib/harness/snapshot.js +474 -0
  295. package/lib/harness/stuck-tabs.d.ts +19 -0
  296. package/lib/harness/stuck-tabs.js +131 -0
  297. package/lib/harness/tab-refs.d.ts +20 -0
  298. package/lib/harness/tab-refs.js +80 -0
  299. package/lib/harness/tampermonkey.d.ts +71 -0
  300. package/lib/harness/tampermonkey.js +356 -0
  301. package/lib/harness/tm-ids.d.ts +18 -0
  302. package/lib/harness/tm-ids.js +88 -0
  303. package/lib/harness/violentmonkey.d.ts +34 -0
  304. package/lib/harness/violentmonkey.js +162 -0
  305. package/lib/harness/wait.d.ts +77 -0
  306. package/lib/harness/wait.js +458 -0
  307. package/lib/http-read.d.ts +30 -0
  308. package/lib/http-read.js +142 -0
  309. package/lib/local-file-overrides.d.ts +35 -0
  310. package/lib/local-file-overrides.js +558 -0
  311. package/lib/manifest.json +38 -0
  312. package/lib/markup-response.d.ts +11 -0
  313. package/lib/markup-response.js +126 -0
  314. package/lib/match-targets.d.ts +18 -0
  315. package/lib/match-targets.js +305 -0
  316. package/lib/menu-captions.d.ts +1 -0
  317. package/lib/menu-captions.js +22 -0
  318. package/lib/parse-check-protocol.d.ts +27 -0
  319. package/lib/parse-check-protocol.js +51 -0
  320. package/lib/parse-location.d.ts +12 -0
  321. package/lib/parse-location.js +171 -0
  322. package/lib/phase-timing.d.ts +12 -0
  323. package/lib/phase-timing.js +155 -0
  324. package/lib/public-suffixes.d.ts +1 -0
  325. package/lib/public-suffixes.js +6957 -0
  326. package/lib/run-context.d.ts +2 -0
  327. package/lib/run-context.js +31 -0
  328. package/lib/runtime-messages.d.ts +10 -0
  329. package/lib/runtime-messages.js +34 -0
  330. package/lib/sandboxed-page.d.ts +5 -0
  331. package/lib/sandboxed-page.js +35 -0
  332. package/lib/script-build.d.ts +111 -0
  333. package/lib/script-build.js +787 -0
  334. package/lib/script-display.d.ts +51 -0
  335. package/lib/script-display.js +383 -0
  336. package/lib/script-icon.d.ts +16 -0
  337. package/lib/script-icon.js +43 -0
  338. package/lib/script-links.d.ts +24 -0
  339. package/lib/script-links.js +204 -0
  340. package/lib/script-source-url.d.ts +8 -0
  341. package/lib/script-source-url.js +214 -0
  342. package/lib/script-world.d.ts +6 -0
  343. package/lib/script-world.js +65 -0
  344. package/lib/sm-dev-protocol.d.ts +251 -0
  345. package/lib/sm-dev-protocol.js +84 -0
  346. package/lib/source-map.d.ts +32 -0
  347. package/lib/source-map.js +177 -0
  348. package/lib/stack-link.d.ts +15 -0
  349. package/lib/stack-link.js +138 -0
  350. package/lib/unsafe-window-use.d.ts +1 -0
  351. package/lib/unsafe-window-use.js +229 -0
  352. package/lib/url-match.d.ts +40 -0
  353. package/lib/url-match.js +734 -0
  354. package/lib/userscript-header.d.ts +33 -0
  355. package/lib/userscript-header.js +248 -0
  356. package/lib/version-compare.d.ts +6 -0
  357. package/lib/version-compare.js +141 -0
  358. package/lib/version-probe.d.ts +17 -0
  359. package/lib/version-probe.js +120 -0
  360. package/package.json +55 -3
  361. package/skills/SourceMonkey-dev/SKILL.md +868 -0
  362. package/skills/SourceMonkey-dev/references/cdp.md +293 -0
  363. package/skills/SourceMonkey-dev/references/commands.md +297 -0
  364. package/skills/SourceMonkey-dev/references/other-managers.md +63 -0
  365. package/skills/SourceMonkey-dev/references/specs.md +46 -0
  366. package/skills/SourceMonkey-friction/SKILL.md +103 -0
  367. 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,5 @@
1
+ // The `GM_xmlhttpRequest` wire types, between the page and the
2
+ // service worker's handler (gm-xhr.ts). Their own module so the
3
+ // harness, whose request fakes speak them, can use them without the
4
+ // handler.
5
+ export {};
@@ -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