brane 0.0.2 → 0.3.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 (338) hide show
  1. package/AGENTS.md +362 -0
  2. package/CHANGELOG.md +33 -0
  3. package/CONTRIBUTING.md +91 -0
  4. package/DESIGN.md +402 -0
  5. package/Dockerfile +61 -0
  6. package/LICENSE +656 -17
  7. package/README.md +174 -2
  8. package/SECURITY.md +205 -0
  9. package/dist/absorb/brane-id.js +11 -0
  10. package/dist/absorb/chatgpt.js +161 -0
  11. package/dist/absorb/claude-code.js +504 -0
  12. package/dist/absorb/index.js +543 -0
  13. package/dist/absorb/organelle.js +345 -0
  14. package/dist/absorb/run.js +520 -0
  15. package/dist/absorb/sources.js +168 -0
  16. package/dist/absorb/types.js +3 -0
  17. package/dist/agent-env.js +45 -0
  18. package/dist/agents.js +1168 -0
  19. package/dist/claude-live-session.js +124 -0
  20. package/dist/cli.js +50 -0
  21. package/dist/codex-tool-log.js +144 -0
  22. package/dist/commands/absorb.js +291 -0
  23. package/dist/commands/attach.js +31 -0
  24. package/dist/commands/certify.js +62 -0
  25. package/dist/commands/config.js +116 -0
  26. package/dist/commands/cost.js +126 -0
  27. package/dist/commands/daemon.js +350 -0
  28. package/dist/commands/doctor.js +262 -0
  29. package/dist/commands/fold.js +95 -0
  30. package/dist/commands/list.js +89 -0
  31. package/dist/commands/mcp.js +84 -0
  32. package/dist/commands/meld.js +332 -0
  33. package/dist/commands/memory.js +215 -0
  34. package/dist/commands/revive.js +74 -0
  35. package/dist/commands/run.js +85 -0
  36. package/dist/commands/spawn.js +1094 -0
  37. package/dist/commands/token.js +153 -0
  38. package/dist/copilot-tool-log.js +145 -0
  39. package/dist/cost/pricing.js +116 -0
  40. package/dist/cost/reprice.js +136 -0
  41. package/dist/crush-tool-log.js +293 -0
  42. package/dist/daemon/agent-scope.js +116 -0
  43. package/dist/daemon/awaiting.js +54 -0
  44. package/dist/daemon/cert-manager.js +349 -0
  45. package/dist/daemon/client.js +262 -0
  46. package/dist/daemon/gated-routes.js +112 -0
  47. package/dist/daemon/keep-awake.js +103 -0
  48. package/dist/daemon/local-socket.js +145 -0
  49. package/dist/daemon/presence-gate.js +206 -0
  50. package/dist/daemon/server.js +6349 -0
  51. package/dist/daemon/signal-source.js +35 -0
  52. package/dist/daemon/tailscale-rebind.js +58 -0
  53. package/dist/daemon/token-ring.js +263 -0
  54. package/dist/daemon/token-routes.js +190 -0
  55. package/dist/db/events.js +152 -0
  56. package/dist/db/mentions.js +179 -0
  57. package/dist/db/schema.js +633 -0
  58. package/dist/db/tokens.js +583 -0
  59. package/dist/db/tool-uses.js +647 -0
  60. package/dist/diagram/index.js +85 -0
  61. package/dist/diagram/label.js +455 -0
  62. package/dist/diagram/layout.js +476 -0
  63. package/dist/diagram/links.js +50 -0
  64. package/dist/diagram/metrics.js +152 -0
  65. package/dist/diagram/parse.js +487 -0
  66. package/dist/diagram/svg.js +389 -0
  67. package/dist/diagram/types.js +9 -0
  68. package/dist/export.js +629 -0
  69. package/dist/files/archive-types.js +2 -0
  70. package/dist/files/archive.js +182 -0
  71. package/dist/files/browse.js +327 -0
  72. package/dist/files/bytes.js +162 -0
  73. package/dist/files/codecs.js +33 -0
  74. package/dist/files/find.js +212 -0
  75. package/dist/files/tar.js +223 -0
  76. package/dist/files/zip.js +209 -0
  77. package/dist/folds.js +72 -0
  78. package/dist/grammars/ebnf.js +92 -0
  79. package/dist/highlight.js +250 -0
  80. package/dist/ids.js +24 -0
  81. package/dist/markdown-diagnostics.js +233 -0
  82. package/dist/math.js +168 -0
  83. package/dist/mcp/server.js +886 -0
  84. package/dist/memory/embedder.js +145 -0
  85. package/dist/memory/embedding-worker.js +71 -0
  86. package/dist/memory/embeddings.js +91 -0
  87. package/dist/memory/index.js +232 -0
  88. package/dist/memory/storage.js +587 -0
  89. package/dist/memory/wiki.js +688 -0
  90. package/dist/memory/wordpiece.js +247 -0
  91. package/dist/mentions-scan.js +40 -0
  92. package/dist/presence.js +259 -0
  93. package/dist/session-affinity.js +185 -0
  94. package/dist/shared/api-request.js +124 -0
  95. package/dist/shared/block-glyphs.js +114 -0
  96. package/dist/shared/diagram-css.js +17 -0
  97. package/dist/shared/export-selection.js +101 -0
  98. package/dist/shared/find-in-text.js +110 -0
  99. package/dist/shared/glow-hues.js +225 -0
  100. package/dist/shared/hex-dump.js +62 -0
  101. package/dist/shared/hex-pages.js +52 -0
  102. package/dist/shared/hit-drum.js +79 -0
  103. package/dist/shared/known-nids.js +58 -0
  104. package/dist/shared/list-scroll-keys.js +56 -0
  105. package/dist/shared/markdown-lists.js +72 -0
  106. package/dist/shared/markdown-math.js +485 -0
  107. package/dist/shared/markdown-mentions.js +79 -0
  108. package/dist/shared/markdown-nids.js +41 -0
  109. package/dist/shared/markdown-protocols.js +53 -0
  110. package/dist/shared/markdown-refs.js +123 -0
  111. package/dist/shared/membrane-invasion.js +342 -0
  112. package/dist/shared/membrane-motion.js +83 -0
  113. package/dist/shared/membrane-palette.js +253 -0
  114. package/dist/shared/membrane-terrain.js +212 -0
  115. package/dist/shared/message-selection.js +123 -0
  116. package/dist/shared/mo-glyph.js +57 -0
  117. package/dist/shared/narrative-format.js +237 -0
  118. package/dist/shared/narrative-menu.js +82 -0
  119. package/dist/shared/narrative-sort.js +47 -0
  120. package/dist/shared/narrative-status.js +52 -0
  121. package/dist/shared/no-pan.js +85 -0
  122. package/dist/shared/pane-layout.js +50 -0
  123. package/dist/shared/payload-inline.js +83 -0
  124. package/dist/shared/speaker-color.js +61 -0
  125. package/dist/shared/token-rotation.js +14 -0
  126. package/dist/shared/view-url.js +197 -0
  127. package/dist/shared/wiki-links.js +70 -0
  128. package/dist/shared/wiki-window.js +4 -0
  129. package/dist/tailscale.js +55 -0
  130. package/dist/test/absorb-claude.test.js +892 -0
  131. package/dist/test/agent-scope.test.js +193 -0
  132. package/dist/test/agents.test.js +55 -0
  133. package/dist/test/api-request.test.js +122 -0
  134. package/dist/test/awaiting-glow.test.js +54 -0
  135. package/dist/test/awaiting.test.js +122 -0
  136. package/dist/test/bind-token.test.js +88 -0
  137. package/dist/test/block-glyphs.test.js +68 -0
  138. package/dist/test/cert-manager.test.js +262 -0
  139. package/dist/test/claude-live-session.test.js +110 -0
  140. package/dist/test/codex-tool-log.test.js +568 -0
  141. package/dist/test/config.test.js +71 -0
  142. package/dist/test/copilot-tool-log.test.js +93 -0
  143. package/dist/test/cors-preflight.test.js +49 -0
  144. package/dist/test/cost-linger.test.js +31 -0
  145. package/dist/test/cost-pricing.test.js +72 -0
  146. package/dist/test/cost-reprice.test.js +139 -0
  147. package/dist/test/cost-sessions.test.js +301 -0
  148. package/dist/test/crush-tool-log.test.js +289 -0
  149. package/dist/test/daemon-client.test.js +109 -0
  150. package/dist/test/daemon-socket.test.js +293 -0
  151. package/dist/test/diagram-arrows.test.js +205 -0
  152. package/dist/test/diagram-export.test.js +210 -0
  153. package/dist/test/diagram-labels.test.js +313 -0
  154. package/dist/test/diagram-parse.test.js +191 -0
  155. package/dist/test/diagram-render.test.js +175 -0
  156. package/dist/test/diagram-review.test.js +288 -0
  157. package/dist/test/diagram-self-loop-labels.test.js +303 -0
  158. package/dist/test/embedder.test.js +84 -0
  159. package/dist/test/events.test.js +123 -0
  160. package/dist/test/export-selection.test.js +102 -0
  161. package/dist/test/export.test.js +505 -0
  162. package/dist/test/files-browse.test.js +393 -0
  163. package/dist/test/files-find.test.js +272 -0
  164. package/dist/test/find-in-text.test.js +86 -0
  165. package/dist/test/first-paint-viewport.test.js +59 -0
  166. package/dist/test/fixtures/embedding-golden.js +23 -0
  167. package/dist/test/fixtures/wordpiece-golden.js +167 -0
  168. package/dist/test/glow-hues.test.js +202 -0
  169. package/dist/test/harness-cost.test.js +423 -0
  170. package/dist/test/helpers.js +21 -0
  171. package/dist/test/hex-pages.test.js +68 -0
  172. package/dist/test/highlight.test.js +69 -0
  173. package/dist/test/hit-drum.test.js +71 -0
  174. package/dist/test/hook-config.test.js +262 -0
  175. package/dist/test/json-gzip.test.js +70 -0
  176. package/dist/test/key-routing.test.js +58 -0
  177. package/dist/test/known-nids.test.js +106 -0
  178. package/dist/test/link-preview.test.js +67 -0
  179. package/dist/test/list-scroll-keys.test.js +66 -0
  180. package/dist/test/listen-own-words.test.js +147 -0
  181. package/dist/test/listen-timeout.test.js +33 -0
  182. package/dist/test/markdown-diagnostics.test.js +121 -0
  183. package/dist/test/markdown-lists.test.js +182 -0
  184. package/dist/test/markdown-math.test.js +256 -0
  185. package/dist/test/markdown-nids.test.js +146 -0
  186. package/dist/test/markdown-protocols.test.js +139 -0
  187. package/dist/test/mcp-bridge-cancel.test.js +98 -0
  188. package/dist/test/mcp-bridge-reconnect.test.js +121 -0
  189. package/dist/test/mcp-narrative.test.js +193 -0
  190. package/dist/test/mcp.test.js +548 -0
  191. package/dist/test/meld.test.js +85 -0
  192. package/dist/test/membrane-invasion.test.js +482 -0
  193. package/dist/test/membrane-motion.test.js +101 -0
  194. package/dist/test/membrane-palette.test.js +166 -0
  195. package/dist/test/membrane-terrain.test.js +170 -0
  196. package/dist/test/memory-log.test.js +202 -0
  197. package/dist/test/mentions-e2e.test.js +261 -0
  198. package/dist/test/mentions-ledger.test.js +265 -0
  199. package/dist/test/mentions-listen.test.js +265 -0
  200. package/dist/test/mentions.test.js +238 -0
  201. package/dist/test/message-selection.test.js +114 -0
  202. package/dist/test/mo-glyph.test.js +22 -0
  203. package/dist/test/model-resolution.test.js +166 -0
  204. package/dist/test/narrate-validate.test.js +77 -0
  205. package/dist/test/narrate-wait.test.js +160 -0
  206. package/dist/test/narrative-format.test.js +219 -0
  207. package/dist/test/narrative-menu.test.js +137 -0
  208. package/dist/test/narrative-sort.test.js +75 -0
  209. package/dist/test/no-pan.test.js +60 -0
  210. package/dist/test/pane-layout.test.js +153 -0
  211. package/dist/test/paths.test.js +37 -0
  212. package/dist/test/payload-inline.test.js +91 -0
  213. package/dist/test/precache-fonts.test.js +72 -0
  214. package/dist/test/presence-gate.test.js +440 -0
  215. package/dist/test/presence.test.js +160 -0
  216. package/dist/test/read-events.test.js +220 -0
  217. package/dist/test/remote-meld.test.js +115 -0
  218. package/dist/test/revive-cli.test.js +93 -0
  219. package/dist/test/search-floor.test.js +206 -0
  220. package/dist/test/search-fusion.test.js +168 -0
  221. package/dist/test/search.test.js +205 -0
  222. package/dist/test/security.test.js +48 -0
  223. package/dist/test/selection-wiring.test.js +61 -0
  224. package/dist/test/session-affinity.test.js +295 -0
  225. package/dist/test/signal-source.test.js +32 -0
  226. package/dist/test/socket-account.test.js +268 -0
  227. package/dist/test/spawn-toml.test.js +72 -0
  228. package/dist/test/speaker-color.test.js +90 -0
  229. package/dist/test/tailscale-rebind.test.js +79 -0
  230. package/dist/test/terminal-cursor.test.js +61 -0
  231. package/dist/test/terminal-resync.test.js +23 -0
  232. package/dist/test/thin-cli.test.js +382 -0
  233. package/dist/test/token-bootstrap.test.js +272 -0
  234. package/dist/test/token-devices.test.js +587 -0
  235. package/dist/test/token-lapse.test.js +94 -0
  236. package/dist/test/token-ring.test.js +188 -0
  237. package/dist/test/token-rotate-cli.test.js +58 -0
  238. package/dist/test/token-routes.test.js +95 -0
  239. package/dist/test/tool-uses.test.js +1006 -0
  240. package/dist/test/usage-ledger.test.js +126 -0
  241. package/dist/test/view-url.test.js +581 -0
  242. package/dist/test/wiki-links.test.js +57 -0
  243. package/dist/test/wiki-sources.test.js +85 -0
  244. package/dist/test/wiki-window.test.js +31 -0
  245. package/dist/test/wiki.test.js +399 -0
  246. package/dist/test/wordpiece.test.js +68 -0
  247. package/dist/tls.js +148 -0
  248. package/dist/tmux.js +202 -0
  249. package/dist/user-agent.js +46 -0
  250. package/docker-entrypoint.sh +141 -0
  251. package/docs/README.md +3 -0
  252. package/docs/absorb.md +231 -0
  253. package/docs/cost-ledger.md +125 -0
  254. package/docs/daemon-api.md +189 -0
  255. package/docs/diagrams.md +163 -0
  256. package/docs/doctor.md +36 -0
  257. package/docs/export-scope.md +69 -0
  258. package/docs/files-view.md +133 -0
  259. package/docs/link-previews.md +38 -0
  260. package/docs/math.md +32 -0
  261. package/docs/mcp.md +69 -0
  262. package/docs/memory-search.md +75 -0
  263. package/docs/mentions.md +101 -0
  264. package/docs/multi-brane.md +14 -0
  265. package/docs/passkeys.md +165 -0
  266. package/docs/presence.md +202 -0
  267. package/docs/releasing.md +53 -0
  268. package/docs/rendering-warnings.md +22 -0
  269. package/docs/request-layer.md +72 -0
  270. package/docs/selection-store.md +50 -0
  271. package/docs/session-affinity.md +61 -0
  272. package/docs/terminal-mirroring.md +211 -0
  273. package/docs/token-rotation.md +413 -0
  274. package/docs/view-urls.md +114 -0
  275. package/docs/web-app.md +23 -0
  276. package/docs/wiki.md +43 -0
  277. package/expo/dist/_expo/static/js/web/entry-e56efc1291dca7258ea00b8108b033a4.js +1097 -0
  278. package/expo/dist/apple-touch-icon.png +0 -0
  279. package/expo/dist/favicon.svg +3 -0
  280. package/expo/dist/index.html +177 -0
  281. package/expo/dist/katex/fonts/KaTeX_AMS-Regular.woff2 +0 -0
  282. package/expo/dist/katex/fonts/KaTeX_Caligraphic-Bold.woff2 +0 -0
  283. package/expo/dist/katex/fonts/KaTeX_Caligraphic-Regular.woff2 +0 -0
  284. package/expo/dist/katex/fonts/KaTeX_Fraktur-Bold.woff2 +0 -0
  285. package/expo/dist/katex/fonts/KaTeX_Fraktur-Regular.woff2 +0 -0
  286. package/expo/dist/katex/fonts/KaTeX_Main-Bold.woff2 +0 -0
  287. package/expo/dist/katex/fonts/KaTeX_Main-BoldItalic.woff2 +0 -0
  288. package/expo/dist/katex/fonts/KaTeX_Main-Italic.woff2 +0 -0
  289. package/expo/dist/katex/fonts/KaTeX_Main-Regular.woff2 +0 -0
  290. package/expo/dist/katex/fonts/KaTeX_Math-BoldItalic.woff2 +0 -0
  291. package/expo/dist/katex/fonts/KaTeX_Math-Italic.woff2 +0 -0
  292. package/expo/dist/katex/fonts/KaTeX_SansSerif-Bold.woff2 +0 -0
  293. package/expo/dist/katex/fonts/KaTeX_SansSerif-Italic.woff2 +0 -0
  294. package/expo/dist/katex/fonts/KaTeX_SansSerif-Regular.woff2 +0 -0
  295. package/expo/dist/katex/fonts/KaTeX_Script-Regular.woff2 +0 -0
  296. package/expo/dist/katex/fonts/KaTeX_Size1-Regular.woff2 +0 -0
  297. package/expo/dist/katex/fonts/KaTeX_Size2-Regular.woff2 +0 -0
  298. package/expo/dist/katex/fonts/KaTeX_Size3-Regular.woff2 +0 -0
  299. package/expo/dist/katex/fonts/KaTeX_Size4-Regular.woff2 +0 -0
  300. package/expo/dist/katex/fonts/KaTeX_Typewriter-Regular.woff2 +0 -0
  301. package/expo/dist/katex/katex.min.css +1 -0
  302. package/expo/dist/manifest.json +16 -0
  303. package/expo/dist/metadata.json +1 -0
  304. package/expo/dist/og/membrane.v1.png +0 -0
  305. package/expo/dist/pdf.min.mjs +28 -0
  306. package/expo/dist/pdf.worker.min.mjs +28 -0
  307. package/expo/dist/sw.js +154 -0
  308. package/package.json +81 -23
  309. package/protocols/LIVE_loop.md +407 -0
  310. package/protocols/bebrief.md +41 -0
  311. package/protocols/digest.md +231 -0
  312. package/protocols/fly.md +266 -0
  313. package/protocols/idle.md +81 -0
  314. package/protocols/keeper.md +829 -0
  315. package/protocols/markdown.md +80 -0
  316. package/protocols/memory.md +419 -0
  317. package/protocols/mentions.md +93 -0
  318. package/protocols/payloads.md +53 -0
  319. package/protocols/prelude.md +91 -0
  320. package/protocols/ramify.md +105 -0
  321. package/protocols/review.md +356 -0
  322. package/protocols/rrr.md +232 -0
  323. package/protocols/scribe.md +429 -0
  324. package/protocols/summarize.md +203 -0
  325. package/protocols/twin.md +174 -0
  326. package/protocols/wiki.md +438 -0
  327. package/protocols/worker.md +38 -0
  328. package/wiki/shared/append-only-event-log.md +148 -0
  329. package/wiki/shared/index.md +48 -0
  330. package/wiki/shared/owning-your-narratives.md +161 -0
  331. package/wiki/shared/premature-ontologies.md +435 -0
  332. package/wiki/shared/sedimentary-superintelligence.md +128 -0
  333. package/wiki/shared/sequestering-the-narrative.md +143 -0
  334. package/wiki/shared/why-wikis-work.md +216 -0
  335. package/.npmignore +0 -2
  336. package/.travis.yml +0 -5
  337. package/index.js +0 -8
  338. package/index.js~ +0 -12
@@ -0,0 +1,33 @@
1
+ // The decompressors the Files view uses, all of them Node's own zlib. No
2
+ // archive tool is ever shelled out to: `unzip` and `bsdtar` are C with CVE
3
+ // histories, and bsdtar in particular auto-detects dozens of formats nobody
4
+ // asked it to parse.
5
+ import { createReadStream } from "node:fs";
6
+ import { pipeline } from "node:stream";
7
+ import * as zlib from "node:zlib";
8
+ /** zstd arrived in Node 22.15 / 23.8. Below that, `.zst` files are simply
9
+ * not archives, and open as the binary they are. */
10
+ const createZstd = zlib
11
+ .createZstdDecompress;
12
+ export const HAS_ZSTD = typeof createZstd === "function";
13
+ function decompressor(codec) {
14
+ switch (codec) {
15
+ case "gzip": return zlib.createGunzip();
16
+ case "brotli": return zlib.createBrotliDecompress();
17
+ case "deflate-raw": return zlib.createInflateRaw();
18
+ case "zstd":
19
+ if (!createZstd)
20
+ throw new Error("zstd is not supported by this Node");
21
+ return createZstd();
22
+ }
23
+ }
24
+ /** Bytes of `path` (or of bytes [start, end] of it), decoded by `codec` —
25
+ * or as they are, for null. The pipeline is torn down whole when the returned
26
+ * stream is destroyed, so a reader that stops early leaves no handle behind. */
27
+ export function openDecoded(path, codec, start, end) {
28
+ const src = createReadStream(path, start === undefined ? {} : { start, end });
29
+ if (codec === null)
30
+ return src;
31
+ return pipeline(src, decompressor(codec), () => { });
32
+ }
33
+ //# sourceMappingURL=codecs.js.map
@@ -0,0 +1,212 @@
1
+ // GET /api/files/find: search a file's bytes, all of them, for the hex view.
2
+ //
3
+ // 🔎 on a hex page used to search the text in the DOM, which knew only the
4
+ // pages already loaded, split every string that wrapped a row, and read
5
+ // `cffaedfe` as four letters rather than four bytes. This reads the bytes
6
+ // themselves, through the same ByteSource the hex page does, so an archive
7
+ // member or a `.gz` body is searched as it is shown.
8
+ //
9
+ // What it returns is bounded by *pages*, not by the file: hits from at most
10
+ // the first FIND_PAGE_WINDOW hex pages that contain any. A one-byte query in
11
+ // a 2 GB binary stops after a megabyte of hits instead of reading two
12
+ // gigabytes to produce hits nobody will visit, and the reader asks for the
13
+ // next window if they want it (`from`).
14
+ import { HEX_PAGE_BYTES } from "../shared/hex-dump.js";
15
+ import { MAX_FIND_MATCHES, isCaseSensitive } from "../shared/find-in-text.js";
16
+ import { ReachError } from "./bytes.js";
17
+ /** Distinct hex pages a search window may put hits on. 64 pages is 1 MB: as
18
+ * much as the hex view will hold at once. */
19
+ export const FIND_PAGE_WINDOW = 64;
20
+ /** The longest needle, in bytes. */
21
+ export const MAX_NEEDLE_BYTES = 256;
22
+ /** How long one request may scan before it hands back a `next` offset. */
23
+ export const FIND_TIME_BUDGET_MS = 30_000;
24
+ /** How often a scan reports how far it has read. */
25
+ export const FIND_PROGRESS_MS = 250;
26
+ /** Scans running at once, daemon-wide. A new one past this supersedes the
27
+ * oldest rather than being refused: the likeliest reason there are three is
28
+ * a reader typing, whose earlier queries nobody is waiting on. */
29
+ export const MAX_CONCURRENT_FINDS = 2;
30
+ /** `cf fa ed fe`, `cffaedfe`, `0xCAFE`: an even run of hex digits, spaces
31
+ * allowed between bytes. */
32
+ const HEX_QUERY = /^\s*(?:0x)?((?:[0-9a-f]{2}\s*)+)$/i;
33
+ /** What a query asks for, as bytes. Always its UTF-8 text, smart-case as 🔎 is
34
+ * everywhere else; and, when it reads as hex, those bytes too — `cafe` is
35
+ * both the word and 0xCA 0xFE, and the reader gets both. The query is not
36
+ * trimmed, as in `findMatches`: a trailing space is part of what's asked. */
37
+ export function parseByteQuery(query) {
38
+ if (!query)
39
+ return [];
40
+ const needles = [];
41
+ const caseless = !isCaseSensitive(query);
42
+ const text = Buffer.from(caseless ? asciiLower(query) : query, "utf8");
43
+ needles.push({ kind: "text", bytes: text, caseless });
44
+ const hex = HEX_QUERY.exec(query);
45
+ if (hex) {
46
+ // Never the same bytes as the text: 2n hex digits spell n bytes.
47
+ needles.push({ kind: "hex", bytes: Buffer.from(hex[1].replace(/\s+/g, ""), "hex"), caseless: false });
48
+ }
49
+ return needles;
50
+ }
51
+ /** Lowercase A–Z only. `toLowerCase` would fold `İ` into two code points and
52
+ * shift every byte after it. */
53
+ function asciiLower(s) {
54
+ return s.replace(/[A-Z]+/g, (m) => m.toLowerCase());
55
+ }
56
+ function foldAscii(buf) {
57
+ const out = Buffer.allocUnsafe(buf.length);
58
+ for (let i = 0; i < buf.length; i++) {
59
+ const b = buf[i];
60
+ out[i] = b >= 0x41 && b <= 0x5a ? b | 0x20 : b;
61
+ }
62
+ return out;
63
+ }
64
+ /** Search `source` from `from` for any of `needles`, reporting hits in offset
65
+ * order. Hits of one needle don't overlap; hits of two needles may.
66
+ *
67
+ * Order is exact even across the chunks the file is read in: a hit is
68
+ * reported only once every needle that could start before it has been
69
+ * seen whole, so the tail of each chunk (one byte short of the longest
70
+ * needle) waits for the next. That's what lets the page count be a running
71
+ * count, and `next` a place to resume that skips nothing. */
72
+ export async function scanSource(source, needles, from, emit, opts = {}) {
73
+ const pageBytes = opts.pageBytes ?? HEX_PAGE_BYTES;
74
+ const maxPages = opts.maxPages ?? FIND_PAGE_WINDOW;
75
+ const maxHits = opts.maxHits ?? MAX_FIND_MATCHES;
76
+ const budgetMs = opts.budgetMs ?? FIND_TIME_BUDGET_MS;
77
+ const progressMs = opts.progressMs ?? FIND_PROGRESS_MS;
78
+ const now = opts.now ?? Date.now;
79
+ const started = now();
80
+ let lastProgress = started;
81
+ const done = (stop, scanned, next) => {
82
+ const event = { type: "done", stop, scanned, next };
83
+ emit(event);
84
+ return event;
85
+ };
86
+ if (needles.length === 0)
87
+ return done("eof", from, null);
88
+ const longest = Math.max(...needles.map((n) => n.bytes.length));
89
+ const anyCaseless = needles.some((n) => n.caseless);
90
+ // Per needle, where its next hit may start: the end of its last one.
91
+ const allowed = needles.map(() => from);
92
+ let hitCount = 0;
93
+ let pages = 0;
94
+ let lastPage = -1;
95
+ // `carry` holds the bytes at [base, readTo) not yet ruled out as the start
96
+ // of a hit.
97
+ let carry = Buffer.alloc(0);
98
+ let base = from;
99
+ let readTo = from;
100
+ /** Report the hits starting before `limit` in carry+chunk, or stop. */
101
+ const settle = (buf, limit) => {
102
+ const folded = anyCaseless ? foldAscii(buf) : null;
103
+ const found = [];
104
+ needles.forEach((needle, n) => {
105
+ const hay = needle.caseless ? folded : buf;
106
+ let i = Math.max(0, allowed[n] - base);
107
+ while (true) {
108
+ const at = hay.indexOf(needle.bytes, i);
109
+ if (at < 0 || base + at >= limit)
110
+ break;
111
+ found.push({ at: base + at, len: needle.bytes.length, n });
112
+ i = at + needle.bytes.length;
113
+ }
114
+ });
115
+ found.sort((a, b) => a.at - b.at || a.n - b.n);
116
+ const hits = [];
117
+ let stop = null;
118
+ for (const f of found) {
119
+ if (f.at < allowed[f.n])
120
+ continue;
121
+ const first = Math.floor(f.at / pageBytes);
122
+ const last = Math.floor((f.at + f.len - 1) / pageBytes);
123
+ const added = (last > lastPage ? last - Math.max(first, lastPage + 1) + 1 : 0);
124
+ if (hitCount >= maxHits) {
125
+ stop = { type: "done", stop: "hits", scanned: readTo, next: f.at };
126
+ break;
127
+ }
128
+ if (pages + added > maxPages) {
129
+ stop = { type: "done", stop: "pages", scanned: readTo, next: f.at };
130
+ break;
131
+ }
132
+ pages += added;
133
+ lastPage = Math.max(lastPage, last);
134
+ hitCount++;
135
+ allowed[f.n] = f.at + f.len;
136
+ hits.push([f.at, f.len]);
137
+ }
138
+ if (hits.length)
139
+ emit({ type: "hits", hits });
140
+ if (stop)
141
+ emit(stop);
142
+ return stop;
143
+ };
144
+ try {
145
+ for await (const chunk of source.chunks(from)) {
146
+ // Resume where nothing has been ruled out yet: `carry` still holds
147
+ // bytes that may start a hit not reported.
148
+ if (opts.signal?.aborted)
149
+ return done("cancelled", readTo, base);
150
+ const buf = carry.length ? Buffer.concat([carry, chunk]) : chunk;
151
+ readTo += chunk.length;
152
+ // A hit starting at or past `limit` might not be whole yet.
153
+ const limit = readTo - (longest - 1);
154
+ const stop = settle(buf, limit);
155
+ if (stop)
156
+ return stop;
157
+ const keepFrom = Math.max(base, limit);
158
+ carry = buf.subarray(keepFrom - base);
159
+ base = keepFrom;
160
+ const t = now();
161
+ if (t - started >= budgetMs) {
162
+ return done("time", readTo, base);
163
+ }
164
+ if (t - lastProgress >= progressMs) {
165
+ lastProgress = t;
166
+ emit({ type: "progress", scanned: readTo });
167
+ }
168
+ }
169
+ }
170
+ catch (e) {
171
+ if (!(e instanceof ReachError))
172
+ throw e;
173
+ return settle(carry, readTo) ?? done("reach", readTo, null);
174
+ }
175
+ if (opts.signal?.aborted)
176
+ return done("cancelled", readTo, base);
177
+ return settle(carry, Infinity) ?? done("eof", readTo, null);
178
+ }
179
+ /** At most MAX_CONCURRENT_FINDS scans at once; admitting one past that
180
+ * cancels the oldest. */
181
+ export class FindSlots {
182
+ max;
183
+ running = new Set();
184
+ constructor(max = MAX_CONCURRENT_FINDS) {
185
+ this.max = max;
186
+ }
187
+ /** A signal for the new scan, and the release to call when it ends. */
188
+ admit() {
189
+ while (this.running.size >= this.max) {
190
+ const oldest = this.running.values().next().value;
191
+ this.running.delete(oldest);
192
+ oldest.abort();
193
+ }
194
+ const c = new AbortController();
195
+ this.running.add(c);
196
+ return { signal: c.signal, release: () => this.running.delete(c) };
197
+ }
198
+ get size() {
199
+ return this.running.size;
200
+ }
201
+ }
202
+ /** Parse `q`: the query, or why it can't be searched. */
203
+ export function parseFindQuery(q) {
204
+ if (!q)
205
+ return { error: "q is required" };
206
+ const needles = parseByteQuery(q);
207
+ if (needles.some((n) => n.bytes.length > MAX_NEEDLE_BYTES)) {
208
+ return { error: `The query is longer than ${MAX_NEEDLE_BYTES} bytes` };
209
+ }
210
+ return needles;
211
+ }
212
+ //# sourceMappingURL=find.js.map
@@ -0,0 +1,223 @@
1
+ // Tar, read as a stream of 512-byte headers with the data between them skipped
2
+ // rather than kept. A `.tar` is read from the file directly; a `.tar.gz` (or
3
+ // `.br`, `.zst`) from its decoder, which has to run the whole archive through
4
+ // once to list it, since compressed tar has no index. The scan is bounded in
5
+ // bytes and members, and a member's body is decoded only as far as a request
6
+ // reaches (`bytes.ts`).
7
+ //
8
+ // Understood: ustar (with its name prefix), GNU long names and link names
9
+ // (`L`, `K`), pax extended headers (`x`: path, linkpath, size, mtime), and
10
+ // base-256 sizes. Everything else is listed as what it is and not opened.
11
+ const BLOCK = 512;
12
+ /** A long name or pax header bigger than this is not a name. */
13
+ const MAX_META_BYTES = 64 * 1024;
14
+ export class TarFormatError extends Error {
15
+ }
16
+ function cstr(b, start, len) {
17
+ const end = b.indexOf(0, start);
18
+ return b.toString("utf8", start, end < 0 || end > start + len ? start + len : end);
19
+ }
20
+ function octal(b, start, len) {
21
+ // Base-256: the high bit of the first byte set, big-endian after it.
22
+ if (b[start] & 0x80) {
23
+ let n = b[start] & 0x7f;
24
+ for (let i = 1; i < len; i++) {
25
+ n = n * 256 + b[start + i];
26
+ if (n > Number.MAX_SAFE_INTEGER)
27
+ throw new TarFormatError("size out of range");
28
+ }
29
+ return n;
30
+ }
31
+ const s = cstr(b, start, len).trim();
32
+ if (s === "")
33
+ return 0;
34
+ if (!/^[0-7]+$/.test(s))
35
+ throw new TarFormatError(`bad octal field "${s}"`);
36
+ return parseInt(s, 8);
37
+ }
38
+ function checksumOk(h) {
39
+ let sum = 0;
40
+ for (let i = 0; i < BLOCK; i++)
41
+ sum += i >= 148 && i < 156 ? 0x20 : h[i];
42
+ let stored;
43
+ try {
44
+ stored = octal(h, 148, 8);
45
+ }
46
+ catch {
47
+ return false;
48
+ }
49
+ return sum === stored;
50
+ }
51
+ function parsePax(body, into) {
52
+ let p = 0;
53
+ while (p < body.length) {
54
+ const sp = body.indexOf(0x20, p);
55
+ if (sp < 0)
56
+ break;
57
+ const len = parseInt(body.toString("utf8", p, sp), 10);
58
+ if (!Number.isFinite(len) || len <= 0 || p + len > body.length)
59
+ break;
60
+ const rec = body.toString("utf8", sp + 1, p + len - 1); // drop the "\n"
61
+ const eq = rec.indexOf("=");
62
+ if (eq > 0) {
63
+ const key = rec.slice(0, eq);
64
+ const value = rec.slice(eq + 1);
65
+ if (key === "path")
66
+ into.path = value;
67
+ else if (key === "linkpath")
68
+ into.linkpath = value;
69
+ else if (key === "size" && /^\d+$/.test(value))
70
+ into.size = Number(value);
71
+ else if (key === "mtime" && Number.isFinite(Number(value)))
72
+ into.mtimeMs = Number(value) * 1000;
73
+ }
74
+ p += len;
75
+ }
76
+ }
77
+ function kindOf(type) {
78
+ switch (type) {
79
+ case "0":
80
+ case "\0":
81
+ case "7": return "file";
82
+ case "5": return "dir";
83
+ case "2": return "symlink";
84
+ case "1": return "hardlink";
85
+ default: return "other";
86
+ }
87
+ }
88
+ /** Reads `stream` as tar, listing every member. The first header must pass
89
+ * its checksum, so a file that isn't tar is refused at the first block. */
90
+ export async function scanTar(stream, openBody, limits) {
91
+ const members = [];
92
+ let partial = false;
93
+ let pending = {};
94
+ // `buf` holds decoded bytes [pos, pos + buf.length): only ever what the next
95
+ // header or a pending metadata body still needs. Member data is never
96
+ // buffered; chunks that fall wholly inside it are dropped as they arrive.
97
+ let buf = Buffer.alloc(0);
98
+ let pos = 0;
99
+ let streamPos = 0;
100
+ /** Where the next header starts. */
101
+ let next = 0;
102
+ /** A metadata body (long name, pax) being collected, and what it is for. */
103
+ let meta = null;
104
+ let done = false;
105
+ const want = () => (meta ? meta.at : next);
106
+ try {
107
+ for await (const chunk of stream) {
108
+ const chunkStart = streamPos;
109
+ streamPos += chunk.length;
110
+ if (buf.length === 0) {
111
+ if (streamPos <= want()) {
112
+ pos = streamPos;
113
+ if (streamPos > limits.maxScanBytes) {
114
+ partial = true;
115
+ done = true;
116
+ break;
117
+ }
118
+ continue;
119
+ }
120
+ const from = Math.max(0, want() - chunkStart);
121
+ buf = chunk.subarray(from);
122
+ pos = chunkStart + from;
123
+ }
124
+ else {
125
+ buf = Buffer.concat([buf, chunk]);
126
+ }
127
+ for (;;) {
128
+ if (meta) {
129
+ const end = meta.at + meta.size;
130
+ if (pos + buf.length < end)
131
+ break;
132
+ const body = buf.subarray(meta.at - pos, end - pos);
133
+ if (meta.type === "x")
134
+ parsePax(body, pending);
135
+ else if (meta.type === "L")
136
+ pending.path = cstr(body, 0, body.length);
137
+ else if (meta.type === "K")
138
+ pending.linkpath = cstr(body, 0, body.length);
139
+ meta = null;
140
+ }
141
+ if (next > limits.maxScanBytes) {
142
+ partial = true;
143
+ done = true;
144
+ break;
145
+ }
146
+ if (pos + buf.length < next + BLOCK)
147
+ break;
148
+ const h = buf.subarray(next - pos, next - pos + BLOCK);
149
+ if (h.every((b) => b === 0)) {
150
+ done = true;
151
+ break;
152
+ }
153
+ if (!checksumOk(h)) {
154
+ if (next === 0)
155
+ throw new TarFormatError("not a tar archive");
156
+ // A corrupt header mid-archive: what came before it still stands.
157
+ partial = true;
158
+ done = true;
159
+ break;
160
+ }
161
+ const type = String.fromCharCode(h[156]);
162
+ const headerSize = octal(h, 124, 12);
163
+ const dataAt = next + BLOCK;
164
+ const isMeta = type === "x" || type === "L" || type === "K" || type === "g";
165
+ const size = isMeta ? headerSize : pending.size ?? headerSize;
166
+ next = dataAt + Math.ceil(size / BLOCK) * BLOCK;
167
+ if (type === "x" || type === "L" || type === "K") {
168
+ if (size > MAX_META_BYTES)
169
+ throw new TarFormatError("oversized extended header");
170
+ meta = { type, size, at: dataAt };
171
+ }
172
+ else if (type === "g") {
173
+ // Global pax header: nothing in it changes what we show.
174
+ }
175
+ else {
176
+ if (members.length >= limits.maxEntries) {
177
+ partial = true;
178
+ done = true;
179
+ break;
180
+ }
181
+ const magic = h.toString("latin1", 257, 262);
182
+ const prefix = magic === "ustar" ? cstr(h, 345, 155) : "";
183
+ const base = cstr(h, 0, 100);
184
+ const name = pending.path ?? (prefix ? `${prefix}/${base}` : base);
185
+ const kind = kindOf(type);
186
+ const mtimeMs = pending.mtimeMs ?? octal(h, 136, 12) * 1000;
187
+ const link = pending.linkpath ?? cstr(h, 157, 100);
188
+ members.push({
189
+ name,
190
+ kind,
191
+ size: kind === "file" ? size : null,
192
+ mtimeMs,
193
+ link: kind === "symlink" || kind === "hardlink" ? link : undefined,
194
+ open: kind === "file" ? () => openBody(dataAt, size, mtimeMs) : undefined,
195
+ });
196
+ pending = {};
197
+ }
198
+ const keepFrom = want();
199
+ if (keepFrom >= pos + buf.length) {
200
+ pos += buf.length;
201
+ buf = Buffer.alloc(0);
202
+ break;
203
+ }
204
+ buf = buf.subarray(keepFrom - pos);
205
+ pos = keepFrom;
206
+ }
207
+ if (done)
208
+ break;
209
+ }
210
+ }
211
+ finally {
212
+ stream.destroy();
213
+ }
214
+ if (!done) {
215
+ if (members.length === 0)
216
+ throw new TarFormatError("not a tar archive");
217
+ // Ended inside a member: the archive was cut short.
218
+ if (streamPos < next)
219
+ partial = true;
220
+ }
221
+ return { members, partial };
222
+ }
223
+ //# sourceMappingURL=tar.js.map
@@ -0,0 +1,209 @@
1
+ // Zip, read from its central directory. The directory at the end of the file
2
+ // lists every member with its size and where its bytes start, so a listing
3
+ // decompresses nothing, and opening a member inflates that member alone.
4
+ //
5
+ // Nothing here trusts the file: every offset is checked against the file's
6
+ // real size before it is read, sizes a member claims are only displayed, and
7
+ // a member's body is inflated only as far as a request reaches.
8
+ import { MAX_STREAM_REACH, deferredSource, fileSource, readAt, streamSource } from "./bytes.js";
9
+ import { openDecoded } from "./codecs.js";
10
+ const SIG_EOCD = 0x06054b50;
11
+ const SIG_EOCD64 = 0x06064b50;
12
+ const SIG_EOCD64_LOCATOR = 0x07064b50;
13
+ const SIG_CENTRAL = 0x02014b50;
14
+ const SIG_LOCAL = 0x04034b50;
15
+ /** A central directory past this is refused rather than read into memory. At
16
+ * ~80 bytes a record it is some 800,000 members. */
17
+ const MAX_CENTRAL_DIR = 64 * 1024 * 1024;
18
+ const EOCD_MIN = 22;
19
+ const EOCD_SEARCH = EOCD_MIN + 0xffff;
20
+ export class ZipFormatError extends Error {
21
+ }
22
+ export class MemberError extends Error {
23
+ }
24
+ async function findEocd(path, size) {
25
+ if (size < EOCD_MIN)
26
+ throw new ZipFormatError("too small to be a zip");
27
+ const tailLen = Math.min(size, EOCD_SEARCH);
28
+ const tailStart = size - tailLen;
29
+ const tail = await readAt(path, tailStart, tailLen);
30
+ for (let i = tail.length - EOCD_MIN; i >= 0; i--) {
31
+ if (tail.readUInt32LE(i) !== SIG_EOCD)
32
+ continue;
33
+ const commentLen = tail.readUInt16LE(i + 20);
34
+ if (i + EOCD_MIN + commentLen > tail.length)
35
+ continue;
36
+ const eocdPos = tailStart + i;
37
+ let entries = tail.readUInt16LE(i + 10);
38
+ let cdSize = tail.readUInt32LE(i + 12);
39
+ let cdOffset = tail.readUInt32LE(i + 16);
40
+ let recordEnd = eocdPos;
41
+ if (entries === 0xffff || cdSize === 0xffffffff || cdOffset === 0xffffffff) {
42
+ const z64 = await readZip64(path, eocdPos);
43
+ if (z64)
44
+ ({ entries, cdSize, cdOffset, recordEnd } = z64);
45
+ }
46
+ // In a well-formed zip the central directory ends where the end record
47
+ // (or its zip64 twin) begins. Past that is a prepended stub.
48
+ const shift = recordEnd - (cdOffset + cdSize);
49
+ if (shift < 0)
50
+ throw new ZipFormatError("central directory overruns the end record");
51
+ return { entries, cdSize, cdOffset, shift };
52
+ }
53
+ throw new ZipFormatError("no end-of-central-directory record");
54
+ }
55
+ async function readZip64(path, eocdPos) {
56
+ if (eocdPos < 20)
57
+ return null;
58
+ const loc = await readAt(path, eocdPos - 20, 20);
59
+ if (loc.readUInt32LE(0) !== SIG_EOCD64_LOCATOR)
60
+ return null;
61
+ const recordEnd = eocdPos - 20;
62
+ // The locator's offset is unshifted too; the record sits just before it.
63
+ const at = recordEnd - 56;
64
+ if (at < 0)
65
+ throw new ZipFormatError("zip64 record out of range");
66
+ const rec = await readAt(path, at, 56);
67
+ if (rec.readUInt32LE(0) !== SIG_EOCD64)
68
+ throw new ZipFormatError("bad zip64 end record");
69
+ return {
70
+ entries: safeNumber(rec.readBigUInt64LE(32)),
71
+ cdSize: safeNumber(rec.readBigUInt64LE(40)),
72
+ cdOffset: safeNumber(rec.readBigUInt64LE(48)),
73
+ recordEnd: at,
74
+ };
75
+ }
76
+ function safeNumber(n) {
77
+ if (n > BigInt(Number.MAX_SAFE_INTEGER))
78
+ throw new ZipFormatError("zip64 field out of range");
79
+ return Number(n);
80
+ }
81
+ function dosTime(date, time) {
82
+ if (date === 0)
83
+ return 0;
84
+ return new Date((date >> 9) + 1980, ((date >> 5) & 15) - 1, date & 31, time >> 11, (time >> 5) & 63, (time & 31) * 2).getTime();
85
+ }
86
+ /** Every member of the zip at `path`, in directory order, names undecoded by
87
+ * any policy — `archive.ts` decides which names are safe to show. */
88
+ export async function listZip(path, size, maxEntries) {
89
+ const eocd = await findEocd(path, size);
90
+ const { cdSize, shift } = eocd;
91
+ const cdStart = eocd.cdOffset + shift;
92
+ if (cdSize > MAX_CENTRAL_DIR)
93
+ throw new ZipFormatError("central directory too large");
94
+ if (cdStart + cdSize > size)
95
+ throw new ZipFormatError("central directory out of range");
96
+ const cd = await readAt(path, cdStart, cdSize);
97
+ const members = [];
98
+ let partial = false;
99
+ let p = 0;
100
+ while (p + 46 <= cd.length && members.length < eocd.entries) {
101
+ if (members.length >= maxEntries) {
102
+ partial = true;
103
+ break;
104
+ }
105
+ if (cd.readUInt32LE(p) !== SIG_CENTRAL)
106
+ throw new ZipFormatError("bad central directory record");
107
+ const madeBy = cd.readUInt16LE(p + 4);
108
+ const flags = cd.readUInt16LE(p + 8);
109
+ const method = cd.readUInt16LE(p + 10);
110
+ const mtimeMs = dosTime(cd.readUInt16LE(p + 14), cd.readUInt16LE(p + 12));
111
+ let csize = cd.readUInt32LE(p + 20);
112
+ let usize = cd.readUInt32LE(p + 24);
113
+ const nameLen = cd.readUInt16LE(p + 28);
114
+ const extraLen = cd.readUInt16LE(p + 30);
115
+ const commentLen = cd.readUInt16LE(p + 32);
116
+ const extAttr = cd.readUInt32LE(p + 38);
117
+ let localOffset = cd.readUInt32LE(p + 42);
118
+ const nameAt = p + 46;
119
+ const extraAt = nameAt + nameLen;
120
+ const next = extraAt + extraLen + commentLen;
121
+ if (next > cd.length)
122
+ throw new ZipFormatError("central directory record overruns");
123
+ const name = cd.toString("utf8", nameAt, extraAt);
124
+ // zip64: the fields that overflowed 32 bits appear, in this order, in
125
+ // extra block 0x0001.
126
+ if (usize === 0xffffffff || csize === 0xffffffff || localOffset === 0xffffffff) {
127
+ let e = extraAt;
128
+ const eEnd = extraAt + extraLen;
129
+ while (e + 4 <= eEnd) {
130
+ const id = cd.readUInt16LE(e);
131
+ const len = cd.readUInt16LE(e + 2);
132
+ if (id === 0x0001) {
133
+ let f = e + 4;
134
+ const fEnd = Math.min(e + 4 + len, eEnd);
135
+ const take = () => {
136
+ if (f + 8 > fEnd)
137
+ throw new ZipFormatError("short zip64 extra field");
138
+ const v = safeNumber(cd.readBigUInt64LE(f));
139
+ f += 8;
140
+ return v;
141
+ };
142
+ if (usize === 0xffffffff)
143
+ usize = take();
144
+ if (csize === 0xffffffff)
145
+ csize = take();
146
+ if (localOffset === 0xffffffff)
147
+ localOffset = take();
148
+ break;
149
+ }
150
+ e += 4 + len;
151
+ }
152
+ }
153
+ const unixMode = (madeBy >> 8) === 3 ? extAttr >>> 16 : 0;
154
+ const isDir = name.endsWith("/") || (unixMode & 0o170000) === 0o040000;
155
+ const isLink = (unixMode & 0o170000) === 0o120000;
156
+ const member = zipMember(path, size, {
157
+ localOffset: localOffset + shift, csize, usize, method, encrypted: (flags & 1) !== 0, mtimeMs,
158
+ });
159
+ members.push({
160
+ name,
161
+ kind: isDir ? "dir" : isLink ? "symlink" : "file",
162
+ size: isDir ? null : usize,
163
+ mtimeMs,
164
+ open: member.open,
165
+ readLink: isLink ? member.readLink : undefined,
166
+ });
167
+ p = next;
168
+ }
169
+ return { members, partial };
170
+ }
171
+ /** Where a member's bytes start: past its local header, whose name and extra
172
+ * lengths may differ from the central directory's copy. */
173
+ async function dataStart(path, fileSize, e) {
174
+ if (e.localOffset + 30 > fileSize)
175
+ throw new MemberError("member header out of range");
176
+ const h = await readAt(path, e.localOffset, 30);
177
+ if (h.length < 30 || h.readUInt32LE(0) !== SIG_LOCAL)
178
+ throw new MemberError("bad local header");
179
+ const start = e.localOffset + 30 + h.readUInt16LE(26) + h.readUInt16LE(28);
180
+ if (start + e.csize > fileSize)
181
+ throw new MemberError("member data out of range");
182
+ return start;
183
+ }
184
+ function zipMember(path, fileSize, e) {
185
+ const open = () => {
186
+ if (e.encrypted)
187
+ throw new MemberError("This member is encrypted");
188
+ if (e.method !== 0 && e.method !== 8) {
189
+ throw new MemberError(`Compression method ${e.method} is not supported (only stored and deflate)`);
190
+ }
191
+ // The local header is read on first use, not at listing time: a listing
192
+ // of 50,000 members should cost one read, not 50,001.
193
+ let start = null;
194
+ const where = () => (start ??= dataStart(path, fileSize, e));
195
+ if (e.method === 0) {
196
+ return deferredSource(e.usize, e.mtimeMs, async () => fileSource(path, e.csize, e.mtimeMs, await where()));
197
+ }
198
+ return deferredSource(e.usize, e.mtimeMs, async () => {
199
+ const s = await where();
200
+ return streamSource(() => openDecoded(path, "deflate-raw", s, s + Math.max(0, e.csize - 1)), e.csize === 0 ? 0 : null, e.mtimeMs, 0, MAX_STREAM_REACH);
201
+ });
202
+ };
203
+ const readLink = async () => {
204
+ const buf = await open().read(0, 4096);
205
+ return buf.toString("utf8");
206
+ };
207
+ return { open, readLink };
208
+ }
209
+ //# sourceMappingURL=zip.js.map