@backblaze-labs/b2-sdk 0.2.0 → 0.4.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/CHANGELOG.md +86 -1
- package/README.md +38 -7
- package/dist/_virtual/_b2-sdk-version-json.cjs +1 -1
- package/dist/_virtual/_b2-sdk-version-json.js +1 -1
- package/dist/auth/file.cjs.map +1 -1
- package/dist/auth/reauth-coalescer.cjs +65 -0
- package/dist/auth/reauth-coalescer.cjs.map +1 -0
- package/dist/auth/reauth-coalescer.d.cts +34 -0
- package/dist/auth/reauth-coalescer.d.cts.map +1 -0
- package/dist/auth/reauth-coalescer.d.ts +34 -0
- package/dist/auth/reauth-coalescer.d.ts.map +1 -0
- package/dist/auth/reauth-coalescer.js +65 -0
- package/dist/auth/reauth-coalescer.js.map +1 -0
- package/dist/backup/client.cjs +206 -0
- package/dist/backup/client.cjs.map +1 -0
- package/dist/backup/client.d.cts +238 -0
- package/dist/backup/client.d.cts.map +1 -0
- package/dist/backup/client.d.ts +238 -0
- package/dist/backup/client.d.ts.map +1 -0
- package/dist/backup/client.js +206 -0
- package/dist/backup/client.js.map +1 -0
- package/dist/backup/index.cjs +15 -0
- package/dist/backup/index.d.cts +22 -0
- package/dist/backup/index.d.cts.map +1 -0
- package/dist/backup/index.d.ts +22 -0
- package/dist/backup/index.d.ts.map +1 -0
- package/dist/backup/index.js +7 -0
- package/dist/backup/raw.cjs +168 -0
- package/dist/backup/raw.cjs.map +1 -0
- package/dist/backup/raw.d.cts +122 -0
- package/dist/backup/raw.d.cts.map +1 -0
- package/dist/backup/raw.d.ts +122 -0
- package/dist/backup/raw.d.ts.map +1 -0
- package/dist/backup/raw.js +168 -0
- package/dist/backup/raw.js.map +1 -0
- package/dist/bucket-validation.cjs +111 -0
- package/dist/bucket-validation.cjs.map +1 -0
- package/dist/bucket-validation.d.cts +27 -0
- package/dist/bucket-validation.d.cts.map +1 -0
- package/dist/bucket-validation.d.ts +27 -0
- package/dist/bucket-validation.d.ts.map +1 -0
- package/dist/bucket-validation.js +109 -0
- package/dist/bucket-validation.js.map +1 -0
- package/dist/bucket.cjs +125 -74
- package/dist/bucket.cjs.map +1 -1
- package/dist/bucket.d.cts +156 -61
- package/dist/bucket.d.cts.map +1 -1
- package/dist/bucket.d.ts +156 -61
- package/dist/bucket.d.ts.map +1 -1
- package/dist/bucket.js +125 -74
- package/dist/bucket.js.map +1 -1
- package/dist/client.cjs +29 -8
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +30 -9
- package/dist/client.d.cts.map +1 -1
- package/dist/client.d.ts +30 -9
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +29 -8
- package/dist/client.js.map +1 -1
- package/dist/copy/large.cjs +5 -3
- package/dist/copy/large.cjs.map +1 -1
- package/dist/copy/large.d.cts.map +1 -1
- package/dist/copy/large.d.ts.map +1 -1
- package/dist/copy/large.js +5 -3
- package/dist/copy/large.js.map +1 -1
- package/dist/download/checksum.cjs +6 -2
- package/dist/download/checksum.cjs.map +1 -1
- package/dist/download/checksum.js +6 -2
- package/dist/download/checksum.js.map +1 -1
- package/dist/download/parallel.cjs +52 -15
- package/dist/download/parallel.cjs.map +1 -1
- package/dist/download/parallel.d.cts +8 -1
- package/dist/download/parallel.d.cts.map +1 -1
- package/dist/download/parallel.d.ts +8 -1
- package/dist/download/parallel.d.ts.map +1 -1
- package/dist/download/parallel.js +52 -15
- package/dist/download/parallel.js.map +1 -1
- package/dist/download/single.cjs +179 -19
- package/dist/download/single.cjs.map +1 -1
- package/dist/download/single.d.cts +10 -4
- package/dist/download/single.d.cts.map +1 -1
- package/dist/download/single.d.ts +10 -4
- package/dist/download/single.d.ts.map +1 -1
- package/dist/download/single.js +179 -19
- package/dist/download/single.js.map +1 -1
- package/dist/errors/index.cjs +196 -3
- package/dist/errors/index.cjs.map +1 -1
- package/dist/errors/index.d.cts +134 -0
- package/dist/errors/index.d.cts.map +1 -1
- package/dist/errors/index.d.ts +134 -0
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/index.js +183 -4
- package/dist/errors/index.js.map +1 -1
- package/dist/http/non-idempotent-mutations.cjs +38 -0
- package/dist/http/non-idempotent-mutations.cjs.map +1 -0
- package/dist/http/non-idempotent-mutations.d.cts +25 -0
- package/dist/http/non-idempotent-mutations.d.cts.map +1 -0
- package/dist/http/non-idempotent-mutations.d.ts +25 -0
- package/dist/http/non-idempotent-mutations.d.ts.map +1 -0
- package/dist/http/non-idempotent-mutations.js +35 -0
- package/dist/http/non-idempotent-mutations.js.map +1 -0
- package/dist/http/retry.cjs +1 -1
- package/dist/http/retry.cjs.map +1 -1
- package/dist/http/retry.js +1 -1
- package/dist/http/retry.js.map +1 -1
- package/dist/http/transport.cjs +62 -23
- package/dist/http/transport.cjs.map +1 -1
- package/dist/http/transport.d.cts +30 -3
- package/dist/http/transport.d.cts.map +1 -1
- package/dist/http/transport.d.ts +30 -3
- package/dist/http/transport.d.ts.map +1 -1
- package/dist/http/transport.js +60 -22
- package/dist/http/transport.js.map +1 -1
- package/dist/http/user-agent.cjs +12 -1
- package/dist/http/user-agent.cjs.map +1 -1
- package/dist/http/user-agent.d.cts +8 -0
- package/dist/http/user-agent.d.cts.map +1 -1
- package/dist/http/user-agent.d.ts +8 -0
- package/dist/http/user-agent.d.ts.map +1 -1
- package/dist/http/user-agent.js +13 -3
- package/dist/http/user-agent.js.map +1 -1
- package/dist/index.cjs +50 -2
- package/dist/index.d.cts +15 -12
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +15 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -10
- package/dist/notifications/index.cjs +3 -0
- package/dist/notifications/index.d.cts +2 -0
- package/dist/notifications/index.d.cts.map +1 -1
- package/dist/notifications/index.d.ts +2 -0
- package/dist/notifications/index.d.ts.map +1 -1
- package/dist/notifications/index.js +2 -1
- package/dist/object.cjs +13 -2
- package/dist/object.cjs.map +1 -1
- package/dist/object.d.cts +11 -3
- package/dist/object.d.cts.map +1 -1
- package/dist/object.d.ts +11 -3
- package/dist/object.d.ts.map +1 -1
- package/dist/object.js +13 -2
- package/dist/object.js.map +1 -1
- package/dist/partner/account-info.d.cts +43 -0
- package/dist/partner/account-info.d.cts.map +1 -0
- package/dist/partner/account-info.d.ts +43 -0
- package/dist/partner/account-info.d.ts.map +1 -0
- package/dist/partner/auth-clone.cjs +64 -0
- package/dist/partner/auth-clone.cjs.map +1 -0
- package/dist/partner/auth-clone.d.cts +27 -0
- package/dist/partner/auth-clone.d.cts.map +1 -0
- package/dist/partner/auth-clone.d.ts +27 -0
- package/dist/partner/auth-clone.d.ts.map +1 -0
- package/dist/partner/auth-clone.js +63 -0
- package/dist/partner/auth-clone.js.map +1 -0
- package/dist/partner/auth-core.cjs +156 -0
- package/dist/partner/auth-core.cjs.map +1 -0
- package/dist/partner/auth-core.d.cts +111 -0
- package/dist/partner/auth-core.d.cts.map +1 -0
- package/dist/partner/auth-core.d.ts +111 -0
- package/dist/partner/auth-core.d.ts.map +1 -0
- package/dist/partner/auth-core.js +156 -0
- package/dist/partner/auth-core.js.map +1 -0
- package/dist/partner/auth-shape.cjs +36 -0
- package/dist/partner/auth-shape.cjs.map +1 -0
- package/dist/partner/auth-shape.d.cts +12 -0
- package/dist/partner/auth-shape.d.cts.map +1 -0
- package/dist/partner/auth-shape.d.ts +12 -0
- package/dist/partner/auth-shape.d.ts.map +1 -0
- package/dist/partner/auth-shape.js +36 -0
- package/dist/partner/auth-shape.js.map +1 -0
- package/dist/partner/client.cjs +270 -0
- package/dist/partner/client.cjs.map +1 -0
- package/dist/partner/client.d.cts +357 -0
- package/dist/partner/client.d.cts.map +1 -0
- package/dist/partner/client.d.ts +357 -0
- package/dist/partner/client.d.ts.map +1 -0
- package/dist/partner/client.js +270 -0
- package/dist/partner/client.js.map +1 -0
- package/dist/partner/endpoint-url.cjs +109 -0
- package/dist/partner/endpoint-url.cjs.map +1 -0
- package/dist/partner/endpoint-url.d.cts +74 -0
- package/dist/partner/endpoint-url.d.cts.map +1 -0
- package/dist/partner/endpoint-url.d.ts +74 -0
- package/dist/partner/endpoint-url.d.ts.map +1 -0
- package/dist/partner/endpoint-url.js +106 -0
- package/dist/partner/endpoint-url.js.map +1 -0
- package/dist/partner/in-memory.cjs +135 -0
- package/dist/partner/in-memory.cjs.map +1 -0
- package/dist/partner/in-memory.d.cts +99 -0
- package/dist/partner/in-memory.d.cts.map +1 -0
- package/dist/partner/in-memory.d.ts +99 -0
- package/dist/partner/in-memory.d.ts.map +1 -0
- package/dist/partner/in-memory.js +135 -0
- package/dist/partner/in-memory.js.map +1 -0
- package/dist/partner/index.cjs +27 -0
- package/dist/partner/index.d.cts +32 -0
- package/dist/partner/index.d.cts.map +1 -0
- package/dist/partner/index.d.ts +32 -0
- package/dist/partner/index.d.ts.map +1 -0
- package/dist/partner/index.js +9 -0
- package/dist/partner/raw.cjs +400 -0
- package/dist/partner/raw.cjs.map +1 -0
- package/dist/partner/raw.d.cts +220 -0
- package/dist/partner/raw.d.cts.map +1 -0
- package/dist/partner/raw.d.ts +220 -0
- package/dist/partner/raw.d.ts.map +1 -0
- package/dist/partner/raw.js +398 -0
- package/dist/partner/raw.js.map +1 -0
- package/dist/partner/redaction-placeholders.cjs +10 -0
- package/dist/partner/redaction-placeholders.cjs.map +1 -0
- package/dist/partner/redaction-placeholders.d.cts +5 -0
- package/dist/partner/redaction-placeholders.d.cts.map +1 -0
- package/dist/partner/redaction-placeholders.d.ts +5 -0
- package/dist/partner/redaction-placeholders.d.ts.map +1 -0
- package/dist/partner/redaction-placeholders.js +9 -0
- package/dist/partner/redaction-placeholders.js.map +1 -0
- package/dist/partner/redaction.cjs +292 -0
- package/dist/partner/redaction.cjs.map +1 -0
- package/dist/partner/redaction.d.cts +158 -0
- package/dist/partner/redaction.d.cts.map +1 -0
- package/dist/partner/redaction.d.ts +158 -0
- package/dist/partner/redaction.d.ts.map +1 -0
- package/dist/partner/redaction.js +281 -0
- package/dist/partner/redaction.js.map +1 -0
- package/dist/raw/encoding.cjs +29 -10
- package/dist/raw/encoding.cjs.map +1 -1
- package/dist/raw/encoding.d.cts +3 -2
- package/dist/raw/encoding.d.cts.map +1 -1
- package/dist/raw/encoding.d.ts +3 -2
- package/dist/raw/encoding.d.ts.map +1 -1
- package/dist/raw/encoding.js +29 -10
- package/dist/raw/encoding.js.map +1 -1
- package/dist/raw/index.cjs +131 -44
- package/dist/raw/index.cjs.map +1 -1
- package/dist/raw/index.d.cts +21 -15
- package/dist/raw/index.d.cts.map +1 -1
- package/dist/raw/index.d.ts +21 -15
- package/dist/raw/index.d.ts.map +1 -1
- package/dist/raw/index.js +114 -45
- package/dist/raw/index.js.map +1 -1
- package/dist/raw/url.cjs +111 -0
- package/dist/raw/url.cjs.map +1 -0
- package/dist/raw/url.d.cts +41 -0
- package/dist/raw/url.d.cts.map +1 -0
- package/dist/raw/url.d.ts +41 -0
- package/dist/raw/url.d.ts.map +1 -0
- package/dist/raw/url.js +110 -0
- package/dist/raw/url.js.map +1 -0
- package/dist/s3/sigv4.cjs +15 -10
- package/dist/s3/sigv4.cjs.map +1 -1
- package/dist/s3/sigv4.js +15 -10
- package/dist/s3/sigv4.js.map +1 -1
- package/dist/simulator/index.cjs +1579 -257
- package/dist/simulator/index.cjs.map +1 -1
- package/dist/simulator/index.d.cts +124 -11
- package/dist/simulator/index.d.cts.map +1 -1
- package/dist/simulator/index.d.ts +124 -11
- package/dist/simulator/index.d.ts.map +1 -1
- package/dist/simulator/index.js +1568 -262
- package/dist/simulator/index.js.map +1 -1
- package/dist/simulator/partner-capabilities.cjs +77 -0
- package/dist/simulator/partner-capabilities.cjs.map +1 -0
- package/dist/simulator/partner-capabilities.d.cts +53 -0
- package/dist/simulator/partner-capabilities.d.cts.map +1 -0
- package/dist/simulator/partner-capabilities.d.ts +53 -0
- package/dist/simulator/partner-capabilities.d.ts.map +1 -0
- package/dist/simulator/partner-capabilities.js +73 -0
- package/dist/simulator/partner-capabilities.js.map +1 -0
- package/dist/simulator/partner.cjs +713 -0
- package/dist/simulator/partner.cjs.map +1 -0
- package/dist/simulator/partner.d.cts +186 -0
- package/dist/simulator/partner.d.cts.map +1 -0
- package/dist/simulator/partner.d.ts +186 -0
- package/dist/simulator/partner.d.ts.map +1 -0
- package/dist/simulator/partner.js +713 -0
- package/dist/simulator/partner.js.map +1 -0
- package/dist/simulator/validation.cjs +732 -25
- package/dist/simulator/validation.cjs.map +1 -1
- package/dist/simulator/validation.d.cts +145 -6
- package/dist/simulator/validation.d.cts.map +1 -1
- package/dist/simulator/validation.d.ts +145 -6
- package/dist/simulator/validation.d.ts.map +1 -1
- package/dist/simulator/validation.js +705 -26
- package/dist/simulator/validation.js.map +1 -1
- package/dist/streams/file-source.cjs +2 -2
- package/dist/streams/file-source.cjs.map +1 -1
- package/dist/streams/file-source.js +2 -2
- package/dist/streams/file-source.js.map +1 -1
- package/dist/streams/hash.cjs +246 -13
- package/dist/streams/hash.cjs.map +1 -1
- package/dist/streams/hash.d.cts +37 -12
- package/dist/streams/hash.d.cts.map +1 -1
- package/dist/streams/hash.d.ts +37 -12
- package/dist/streams/hash.d.ts.map +1 -1
- package/dist/streams/hash.js +246 -15
- package/dist/streams/hash.js.map +1 -1
- package/dist/streams/index.cjs +3 -1
- package/dist/streams/index.d.cts +1 -1
- package/dist/streams/index.d.cts.map +1 -1
- package/dist/streams/index.d.ts +1 -1
- package/dist/streams/index.d.ts.map +1 -1
- package/dist/streams/index.js +3 -3
- package/dist/streams/progress.cjs +63 -4
- package/dist/streams/progress.cjs.map +1 -1
- package/dist/streams/progress.d.cts +28 -2
- package/dist/streams/progress.d.cts.map +1 -1
- package/dist/streams/progress.d.ts +28 -2
- package/dist/streams/progress.d.ts.map +1 -1
- package/dist/streams/progress.js +63 -4
- package/dist/streams/progress.js.map +1 -1
- package/dist/streams/source.cjs +2 -1
- package/dist/streams/source.cjs.map +1 -1
- package/dist/streams/source.js +2 -1
- package/dist/streams/source.js.map +1 -1
- package/dist/sync/b2-sha1-reader.cjs +6 -2
- package/dist/sync/b2-sha1-reader.cjs.map +1 -1
- package/dist/sync/b2-sha1-reader.d.cts.map +1 -1
- package/dist/sync/b2-sha1-reader.d.ts.map +1 -1
- package/dist/sync/b2-sha1-reader.js +6 -2
- package/dist/sync/b2-sha1-reader.js.map +1 -1
- package/dist/sync/download-staging.cjs +156 -43
- package/dist/sync/download-staging.cjs.map +1 -1
- package/dist/sync/download-staging.d.cts +26 -2
- package/dist/sync/download-staging.d.cts.map +1 -1
- package/dist/sync/download-staging.d.ts +26 -2
- package/dist/sync/download-staging.d.ts.map +1 -1
- package/dist/sync/download-staging.js +155 -44
- package/dist/sync/download-staging.js.map +1 -1
- package/dist/sync/filters.cjs.map +1 -1
- package/dist/sync/filters.js.map +1 -1
- package/dist/sync/index.d.cts +1 -1
- package/dist/sync/index.d.cts.map +1 -1
- package/dist/sync/index.d.ts +1 -1
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/local-file-io.cjs +15 -6
- package/dist/sync/local-file-io.cjs.map +1 -1
- package/dist/sync/local-file-io.d.cts +3 -0
- package/dist/sync/local-file-io.d.cts.map +1 -1
- package/dist/sync/local-file-io.d.ts +3 -0
- package/dist/sync/local-file-io.d.ts.map +1 -1
- package/dist/sync/local-file-io.js +15 -6
- package/dist/sync/local-file-io.js.map +1 -1
- package/dist/sync/local-sha1.cjs +6 -15
- package/dist/sync/local-sha1.cjs.map +1 -1
- package/dist/sync/local-sha1.d.cts +0 -8
- package/dist/sync/local-sha1.d.cts.map +1 -1
- package/dist/sync/local-sha1.d.ts +0 -8
- package/dist/sync/local-sha1.d.ts.map +1 -1
- package/dist/sync/local-sha1.js +7 -15
- package/dist/sync/local-sha1.js.map +1 -1
- package/dist/sync/pairing.cjs +1 -0
- package/dist/sync/pairing.cjs.map +1 -1
- package/dist/sync/pairing.js +1 -0
- package/dist/sync/pairing.js.map +1 -1
- package/dist/sync/policies/compare.cjs +5 -3
- package/dist/sync/policies/compare.cjs.map +1 -1
- package/dist/sync/policies/compare.d.cts.map +1 -1
- package/dist/sync/policies/compare.d.ts.map +1 -1
- package/dist/sync/policies/compare.js +6 -4
- package/dist/sync/policies/compare.js.map +1 -1
- package/dist/sync/policies/index.cjs +4 -10
- package/dist/sync/policies/index.cjs.map +1 -1
- package/dist/sync/policies/index.js +4 -10
- package/dist/sync/policies/index.js.map +1 -1
- package/dist/sync/scanners/b2.cjs +4 -2
- package/dist/sync/scanners/b2.cjs.map +1 -1
- package/dist/sync/scanners/b2.d.cts.map +1 -1
- package/dist/sync/scanners/b2.d.ts.map +1 -1
- package/dist/sync/scanners/b2.js +4 -2
- package/dist/sync/scanners/b2.js.map +1 -1
- package/dist/sync/scanners/local.cjs +150 -33
- package/dist/sync/scanners/local.cjs.map +1 -1
- package/dist/sync/scanners/local.d.cts +6 -0
- package/dist/sync/scanners/local.d.cts.map +1 -1
- package/dist/sync/scanners/local.d.ts +6 -0
- package/dist/sync/scanners/local.d.ts.map +1 -1
- package/dist/sync/scanners/local.js +150 -33
- package/dist/sync/scanners/local.js.map +1 -1
- package/dist/sync/synchronizer.cjs +79 -31
- package/dist/sync/synchronizer.cjs.map +1 -1
- package/dist/sync/synchronizer.d.cts +1 -0
- package/dist/sync/synchronizer.d.cts.map +1 -1
- package/dist/sync/synchronizer.d.ts +1 -0
- package/dist/sync/synchronizer.d.ts.map +1 -1
- package/dist/sync/synchronizer.js +79 -31
- package/dist/sync/synchronizer.js.map +1 -1
- package/dist/sync/types.d.cts +19 -3
- package/dist/sync/types.d.cts.map +1 -1
- package/dist/sync/types.d.ts +19 -3
- package/dist/sync/types.d.ts.map +1 -1
- package/dist/types/auth.cjs +4 -0
- package/dist/types/auth.cjs.map +1 -1
- package/dist/types/auth.d.cts +4 -0
- package/dist/types/auth.d.cts.map +1 -1
- package/dist/types/auth.d.ts +4 -0
- package/dist/types/auth.d.ts.map +1 -1
- package/dist/types/auth.js +4 -0
- package/dist/types/auth.js.map +1 -1
- package/dist/types/backup.d.cts +85 -0
- package/dist/types/backup.d.cts.map +1 -0
- package/dist/types/backup.d.ts +85 -0
- package/dist/types/backup.d.ts.map +1 -0
- package/dist/types/bucket.cjs +81 -11
- package/dist/types/bucket.cjs.map +1 -1
- package/dist/types/bucket.d.cts +194 -41
- package/dist/types/bucket.d.cts.map +1 -1
- package/dist/types/bucket.d.ts +194 -41
- package/dist/types/bucket.d.ts.map +1 -1
- package/dist/types/bucket.js +67 -12
- package/dist/types/bucket.js.map +1 -1
- package/dist/types/download.cjs +62 -0
- package/dist/types/download.cjs.map +1 -0
- package/dist/types/download.d.cts +100 -16
- package/dist/types/download.d.cts.map +1 -1
- package/dist/types/download.d.ts +100 -16
- package/dist/types/download.d.ts.map +1 -1
- package/dist/types/download.js +61 -0
- package/dist/types/download.js.map +1 -0
- package/dist/types/encryption.cjs +76 -229
- package/dist/types/encryption.cjs.map +1 -1
- package/dist/types/encryption.d.cts +48 -6
- package/dist/types/encryption.d.cts.map +1 -1
- package/dist/types/encryption.d.ts +48 -6
- package/dist/types/encryption.d.ts.map +1 -1
- package/dist/types/encryption.js +74 -229
- package/dist/types/encryption.js.map +1 -1
- package/dist/types/errors.cjs +9 -0
- package/dist/types/errors.cjs.map +1 -1
- package/dist/types/errors.d.cts +1 -1
- package/dist/types/errors.d.cts.map +1 -1
- package/dist/types/errors.d.ts +1 -1
- package/dist/types/errors.d.ts.map +1 -1
- package/dist/types/errors.js +9 -0
- package/dist/types/errors.js.map +1 -1
- package/dist/types/file.cjs +3 -0
- package/dist/types/file.cjs.map +1 -1
- package/dist/types/file.d.cts +140 -18
- package/dist/types/file.d.cts.map +1 -1
- package/dist/types/file.d.ts +140 -18
- package/dist/types/file.d.ts.map +1 -1
- package/dist/types/file.js +3 -1
- package/dist/types/file.js.map +1 -1
- package/dist/types/ids.cjs +30 -0
- package/dist/types/ids.cjs.map +1 -1
- package/dist/types/ids.d.cts +32 -0
- package/dist/types/ids.d.cts.map +1 -1
- package/dist/types/ids.d.ts +32 -0
- package/dist/types/ids.d.ts.map +1 -1
- package/dist/types/ids.js +28 -1
- package/dist/types/ids.js.map +1 -1
- package/dist/types/index.d.cts +16 -12
- package/dist/types/index.d.cts.map +1 -1
- package/dist/types/index.d.ts +16 -12
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/key.d.cts +4 -3
- package/dist/types/key.d.cts.map +1 -1
- package/dist/types/key.d.ts +4 -3
- package/dist/types/key.d.ts.map +1 -1
- package/dist/types/lock.cjs.map +1 -1
- package/dist/types/lock.d.cts +14 -0
- package/dist/types/lock.d.cts.map +1 -1
- package/dist/types/lock.d.ts +14 -0
- package/dist/types/lock.d.ts.map +1 -1
- package/dist/types/lock.js.map +1 -1
- package/dist/types/notifications.cjs +31 -1
- package/dist/types/notifications.cjs.map +1 -1
- package/dist/types/notifications.d.cts +31 -4
- package/dist/types/notifications.d.cts.map +1 -1
- package/dist/types/notifications.d.ts +31 -4
- package/dist/types/notifications.d.ts.map +1 -1
- package/dist/types/notifications.js +30 -2
- package/dist/types/notifications.js.map +1 -1
- package/dist/types/partner.cjs +32 -0
- package/dist/types/partner.cjs.map +1 -0
- package/dist/types/partner.d.cts +386 -0
- package/dist/types/partner.d.cts.map +1 -0
- package/dist/types/partner.d.ts +386 -0
- package/dist/types/partner.d.ts.map +1 -0
- package/dist/types/partner.js +29 -0
- package/dist/types/partner.js.map +1 -0
- package/dist/types/upload.d.cts +35 -18
- package/dist/types/upload.d.cts.map +1 -1
- package/dist/types/upload.d.ts +35 -18
- package/dist/types/upload.d.ts.map +1 -1
- package/dist/upload/abort-scope.cjs +0 -45
- package/dist/upload/abort-scope.cjs.map +1 -1
- package/dist/upload/abort-scope.d.cts +0 -22
- package/dist/upload/abort-scope.d.cts.map +1 -1
- package/dist/upload/abort-scope.d.ts +0 -22
- package/dist/upload/abort-scope.d.ts.map +1 -1
- package/dist/upload/abort-scope.js +1 -44
- package/dist/upload/abort-scope.js.map +1 -1
- package/dist/upload/finish.cjs +3 -8
- package/dist/upload/finish.cjs.map +1 -1
- package/dist/upload/finish.d.cts.map +1 -1
- package/dist/upload/finish.d.ts.map +1 -1
- package/dist/upload/finish.js +1 -6
- package/dist/upload/finish.js.map +1 -1
- package/dist/upload/large.cjs +15 -8
- package/dist/upload/large.cjs.map +1 -1
- package/dist/upload/large.d.cts +31 -8
- package/dist/upload/large.d.cts.map +1 -1
- package/dist/upload/large.d.ts +31 -8
- package/dist/upload/large.d.ts.map +1 -1
- package/dist/upload/large.js +15 -8
- package/dist/upload/large.js.map +1 -1
- package/dist/upload/options.cjs.map +1 -1
- package/dist/upload/options.d.cts +24 -0
- package/dist/upload/options.d.cts.map +1 -1
- package/dist/upload/options.d.ts +24 -0
- package/dist/upload/options.d.ts.map +1 -1
- package/dist/upload/options.js.map +1 -1
- package/dist/upload/resume.cjs +6 -4
- package/dist/upload/resume.cjs.map +1 -1
- package/dist/upload/resume.d.cts +9 -5
- package/dist/upload/resume.d.cts.map +1 -1
- package/dist/upload/resume.d.ts +9 -5
- package/dist/upload/resume.d.ts.map +1 -1
- package/dist/upload/resume.js +6 -4
- package/dist/upload/resume.js.map +1 -1
- package/dist/upload/retry.cjs +25 -13
- package/dist/upload/retry.cjs.map +1 -1
- package/dist/upload/retry.d.cts.map +1 -1
- package/dist/upload/retry.d.ts.map +1 -1
- package/dist/upload/retry.js +25 -13
- package/dist/upload/retry.js.map +1 -1
- package/dist/upload/single.cjs +35 -30
- package/dist/upload/single.cjs.map +1 -1
- package/dist/upload/single.d.cts +12 -0
- package/dist/upload/single.d.cts.map +1 -1
- package/dist/upload/single.d.ts +12 -0
- package/dist/upload/single.d.ts.map +1 -1
- package/dist/upload/single.js +35 -30
- package/dist/upload/single.js.map +1 -1
- package/dist/upload/stream.cjs +18 -5
- package/dist/upload/stream.cjs.map +1 -1
- package/dist/upload/stream.d.cts +12 -0
- package/dist/upload/stream.d.cts.map +1 -1
- package/dist/upload/stream.d.ts +12 -0
- package/dist/upload/stream.d.ts.map +1 -1
- package/dist/upload/stream.js +15 -2
- package/dist/upload/stream.js.map +1 -1
- package/dist/util/abort.cjs +114 -0
- package/dist/util/abort.cjs.map +1 -0
- package/dist/util/abort.d.cts +65 -0
- package/dist/util/abort.d.cts.map +1 -0
- package/dist/util/abort.d.ts +65 -0
- package/dist/util/abort.d.ts.map +1 -0
- package/dist/util/abort.js +109 -0
- package/dist/util/abort.js.map +1 -0
- package/dist/util/defaults.cjs +3 -0
- package/dist/util/defaults.cjs.map +1 -1
- package/dist/util/defaults.d.cts +2 -0
- package/dist/util/defaults.d.cts.map +1 -1
- package/dist/util/defaults.d.ts +2 -0
- package/dist/util/defaults.d.ts.map +1 -1
- package/dist/util/defaults.js +3 -1
- package/dist/util/defaults.js.map +1 -1
- package/dist/util/md5.cjs +211 -0
- package/dist/util/md5.cjs.map +1 -0
- package/dist/util/md5.d.cts +43 -0
- package/dist/util/md5.d.cts.map +1 -0
- package/dist/util/md5.d.ts +43 -0
- package/dist/util/md5.d.ts.map +1 -0
- package/dist/util/md5.js +209 -0
- package/dist/util/md5.js.map +1 -0
- package/dist/util/normalize.cjs +5 -5
- package/dist/util/normalize.cjs.map +1 -1
- package/dist/util/normalize.d.cts +7 -7
- package/dist/util/normalize.d.cts.map +1 -1
- package/dist/util/normalize.d.ts +7 -7
- package/dist/util/normalize.d.ts.map +1 -1
- package/dist/util/normalize.js +5 -5
- package/dist/util/normalize.js.map +1 -1
- package/dist/util/paginator.cjs +7 -1
- package/dist/util/paginator.cjs.map +1 -1
- package/dist/util/paginator.d.cts +3 -1
- package/dist/util/paginator.d.cts.map +1 -1
- package/dist/util/paginator.d.ts +3 -1
- package/dist/util/paginator.d.ts.map +1 -1
- package/dist/util/paginator.js +7 -1
- package/dist/util/paginator.js.map +1 -1
- package/dist/version.cjs +56 -4
- package/dist/version.cjs.map +1 -1
- package/dist/version.d.cts +53 -4
- package/dist/version.d.cts.map +1 -1
- package/dist/version.d.ts +53 -4
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +52 -5
- package/dist/version.js.map +1 -1
- package/package.json +47 -18
package/dist/sync/index.d.ts
CHANGED
|
@@ -22,5 +22,5 @@ export type { SyncSha1PendingState, SyncSha1State, SyncSha1UnavailableState, Syn
|
|
|
22
22
|
export { isUntrustedSha1, parseSyncContentSha1, selectB2ComparableSha1, syncSha1StateOf, untrustedSha1, untrustedSha1Prefix, } from './sha1-metadata.js';
|
|
23
23
|
export type { B2SyncFolder, LocalSyncFolder, SupportedSynchronizerConfig, SynchronizerB2Config, SynchronizerConfig, SynchronizerDownConfig, SynchronizerUpConfig, } from './synchronizer.js';
|
|
24
24
|
export { synchronize } from './synchronizer.js';
|
|
25
|
-
export type { B2SyncPath, CompareMode, KeepMode, LocalFileIdentity, LocalSyncPath, SyncActionEvent, SyncActionEventType, SyncCompareEvent, SyncDirection, SyncEncryptionProvider, SyncErrorEvent, SyncEvent, SyncEventType, SyncFilterOptions, SyncFilterPattern, SyncFolder, SyncOptions, SyncPath, SyncScanOptions, SyncSkipEvent, SyncSkipReason, } from './types.js';
|
|
25
|
+
export type { B2SyncPath, CompareMode, KeepMode, LocalFileIdentity, LocalSymlinkPolicy, LocalSyncPath, SyncActionEvent, SyncActionEventType, SyncCompareEvent, SyncDirection, SyncEncryptionProvider, SyncErrorEvent, SyncEvent, SyncEventType, SyncFilterOptions, SyncFilterPattern, SyncFolder, SyncOptions, SyncPath, SyncScanOptions, SyncSkipEvent, SyncSkipReason, } from './types.js';
|
|
26
26
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/sync/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sync/index.js"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,YAAY,EACV,UAAU,EACV,cAAc,GACf,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACL,UAAU,EACV,iBAAiB,EACjB,kBAAkB,EAClB,cAAc,EACd,UAAU,EACV,UAAU,EACV,YAAY,GACb,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACL,4BAA4B,EAC5B,eAAe,EACf,2BAA2B,EAC3B,qBAAqB,EACrB,6BAA6B,GAC9B,MAAM,cAAc,CAAA;AACrB,YAAY,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AACzC,OAAO,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAA;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AACzD,YAAY,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AACrD,OAAO,EACL,gBAAgB,EAChB,8BAA8B,EAC9B,uBAAuB,GACxB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACjD,YAAY,EACV,oBAAoB,EACpB,aAAa,EACb,wBAAwB,EACxB,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACL,eAAe,EACf,oBAAoB,EACpB,sBAAsB,EACtB,eAAe,EACf,aAAa,EACb,mBAAmB,GACpB,MAAM,oBAAoB,CAAA;AAC3B,YAAY,EACV,YAAY,EACZ,eAAe,EACf,2BAA2B,EAC3B,oBAAoB,EACpB,kBAAkB,EAClB,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,YAAY,EACV,UAAU,EACV,WAAW,EACX,QAAQ,EACR,iBAAiB,EACjB,aAAa,EACb,eAAe,EACf,mBAAmB,EACnB,gBAAgB,EAChB,aAAa,EACb,sBAAsB,EACtB,cAAc,EACd,SAAS,EACT,aAAa,EACb,iBAAiB,EACjB,iBAAiB,EACjB,UAAU,EACV,WAAW,EACX,QAAQ,EACR,eAAe,EACf,aAAa,EACb,cAAc,GACf,MAAM,YAAY,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sync/index.js"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,YAAY,EACV,UAAU,EACV,cAAc,GACf,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACL,UAAU,EACV,iBAAiB,EACjB,kBAAkB,EAClB,cAAc,EACd,UAAU,EACV,UAAU,EACV,YAAY,GACb,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACL,4BAA4B,EAC5B,eAAe,EACf,2BAA2B,EAC3B,qBAAqB,EACrB,6BAA6B,GAC9B,MAAM,cAAc,CAAA;AACrB,YAAY,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AACzC,OAAO,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAA;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AACzD,YAAY,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACxD,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AACrD,OAAO,EACL,gBAAgB,EAChB,8BAA8B,EAC9B,uBAAuB,GACxB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACjD,YAAY,EACV,oBAAoB,EACpB,aAAa,EACb,wBAAwB,EACxB,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EACL,eAAe,EACf,oBAAoB,EACpB,sBAAsB,EACtB,eAAe,EACf,aAAa,EACb,mBAAmB,GACpB,MAAM,oBAAoB,CAAA;AAC3B,YAAY,EACV,YAAY,EACZ,eAAe,EACf,2BAA2B,EAC3B,oBAAoB,EACpB,kBAAkB,EAClB,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,YAAY,EACV,UAAU,EACV,WAAW,EACX,QAAQ,EACR,iBAAiB,EACjB,kBAAkB,EAClB,aAAa,EACb,eAAe,EACf,mBAAmB,EACnB,gBAAgB,EAChB,aAAa,EACb,sBAAsB,EACtB,cAAc,EACd,SAAS,EACT,aAAa,EACb,iBAAiB,EACjB,iBAAiB,EACjB,UAAU,EACV,WAAW,EACX,QAAQ,EACR,eAAe,EACf,aAAa,EACb,cAAc,GACf,MAAM,YAAY,CAAA"}
|
|
@@ -25,7 +25,8 @@ async function openValidatedScannedLocalFile(path) {
|
|
|
25
25
|
throw new Error(`local file changed before upload: could not open scanned file: ${require_error_reason.sanitizeErrorReason(err)}`);
|
|
26
26
|
});
|
|
27
27
|
try {
|
|
28
|
-
|
|
28
|
+
const stats = await handle.stat();
|
|
29
|
+
require_local_file_identity.assertSameScannedRegularFile(stats, path, "upload", { platform: localFileIoTestHooks.platform });
|
|
29
30
|
return handle;
|
|
30
31
|
} catch (err) {
|
|
31
32
|
await handle.close().catch(() => {});
|
|
@@ -98,13 +99,17 @@ async function writeLocalStreamInsideRoot(root, relPath, body, options) {
|
|
|
98
99
|
throw err;
|
|
99
100
|
}
|
|
100
101
|
let stagingDirectory;
|
|
102
|
+
let managedStagingDirectory;
|
|
101
103
|
try {
|
|
102
|
-
|
|
104
|
+
const staging = await require_download_staging.createDownloadStagingDirectory(rootRealPath, path, randomUUID, statForDeviceCheck, localFileIoTestHooks.beforeStagingMarkerWrite, localFileIoTestHooks.downloadStagingActivityEntryLimit, localFileIoTestHooks.afterManagedStagingMarkerWrite);
|
|
105
|
+
stagingDirectory = staging.stagingDirectory;
|
|
106
|
+
managedStagingDirectory = staging.managedDirectory;
|
|
103
107
|
} catch (err) {
|
|
104
108
|
/* v8 ignore next -- best-effort close during setup failure */
|
|
105
109
|
await parentHandle?.close().catch(() => {});
|
|
106
110
|
throw err;
|
|
107
111
|
}
|
|
112
|
+
options.onDownloadStagingRoot?.(managedStagingDirectory);
|
|
108
113
|
const tmpPath = path.join(stagingDirectory, `.b2sdk-${randomUUID()}.partial`);
|
|
109
114
|
let handle;
|
|
110
115
|
try {
|
|
@@ -157,7 +162,8 @@ async function writeLocalStreamInsideRoot(root, relPath, body, options) {
|
|
|
157
162
|
await writeHandle.close();
|
|
158
163
|
handle = void 0;
|
|
159
164
|
const [parentRealPathBeforeRename, parentStatsBeforeRename] = await Promise.all([realpath(path.dirname(destPath)), stat(path.dirname(destPath))]);
|
|
160
|
-
|
|
165
|
+
const finalPathBeforeRename = path.join(parentRealPathBeforeRename, path.basename(destPath));
|
|
166
|
+
require_path_safety.assertPathInsideRoot(rootRealPath, finalPathBeforeRename, path);
|
|
161
167
|
await localFileIoTestHooks.beforeFinalRename?.(parentRealPathBeforeRename);
|
|
162
168
|
let publishPath = finalWritePath;
|
|
163
169
|
if (anchoredParentPath === void 0) {
|
|
@@ -234,7 +240,8 @@ async function publishDownload(lstat, link, path, randomUUID, rename, rm, tmpPat
|
|
|
234
240
|
if (require_path_safety.hasErrorCode(err, "ENOENT")) throw new Error("local file changed before download: file missing");
|
|
235
241
|
throw err;
|
|
236
242
|
}
|
|
237
|
-
|
|
243
|
+
const backupStats = await lstat(backupPath);
|
|
244
|
+
require_local_file_identity.assertSameScannedRegularFile(backupStats, {
|
|
238
245
|
...expectedDestination,
|
|
239
246
|
absolutePath: backupPath
|
|
240
247
|
}, "download", {
|
|
@@ -322,7 +329,8 @@ async function deleteLocalFileInsideRoot(root, scannedPath) {
|
|
|
322
329
|
/* v8 ignore stop */
|
|
323
330
|
try {
|
|
324
331
|
const unlinkPath = anchoredParentPath === void 0 ? finalPath : path.join(anchoredParentPath, path.basename(expectedPath));
|
|
325
|
-
|
|
332
|
+
const stats = await lstat(unlinkPath);
|
|
333
|
+
require_local_file_identity.assertSameScannedRegularFile(stats, {
|
|
326
334
|
...scannedPath,
|
|
327
335
|
absolutePath: unlinkPath
|
|
328
336
|
}, "delete", { platform: localFileIoTestHooks.platform });
|
|
@@ -332,7 +340,8 @@ async function deleteLocalFileInsideRoot(root, scannedPath) {
|
|
|
332
340
|
const [parentRealPathBeforeUnlink, parentStatsBeforeUnlink] = await Promise.all([realpath(path.dirname(expectedPath)), stat(path.dirname(expectedPath))]);
|
|
333
341
|
if (parentRealPathBeforeUnlink !== parentRealPath || !sameParentIdentity(parentStatsBeforeUnlink, parentStats)) throw new Error("unsafe local delete path: parent changed before unlink");
|
|
334
342
|
}
|
|
335
|
-
|
|
343
|
+
const finalStats = await lstat(unlinkPath);
|
|
344
|
+
require_local_file_identity.assertSameScannedRegularFile(finalStats, {
|
|
336
345
|
...scannedPath,
|
|
337
346
|
absolutePath: unlinkPath
|
|
338
347
|
}, "delete", { platform: localFileIoTestHooks.platform });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local-file-io.cjs","names":[],"sources":["../../src/sync/local-file-io.ts"],"sourcesContent":["import { sanitizeErrorReason } from '../util/error-reason.ts'\nimport { readStreamChunkWithTimeout } from './b2-sha1-reader.ts'\nimport {\n assertDownloadPathSameDevice,\n createDownloadStagingDirectory,\n DOWNLOAD_STAGING_DIRECTORY_NAME,\n isDownloadStagingDirectorySegment,\n} from './download-staging.ts'\nimport { assertSameScannedRegularFile } from './local-file-identity.ts'\nimport {\n assertPathInsideRoot,\n hasErrorCode,\n makeReservedSyncTempFileName,\n noFollowFlag,\n safeRelativePathSegments,\n} from './path-safety.ts'\nimport { DEFAULT_SHA1_IDLE_TIMEOUT_MILLIS } from './sha1-options.ts'\nimport type { LocalSyncPath } from './types.ts'\n\ntype DeviceStat = { readonly dev: number | bigint }\ntype DeviceStatFn = (path: string) => Promise<DeviceStat>\n\nexport { DOWNLOAD_STAGING_DIRECTORY_NAME }\n\n/** @internal */\nexport const localFileIoTestHooks: {\n afterParentDirectoryValidated?: (path: string) => Promise<void> | void\n afterDownloadBackupRename?: (path: string) => Promise<void> | void\n afterTempFileCreated?: (path: string, stagingDirectory: string) => Promise<void> | void\n beforeDownloadPublish?: (path: string) => Promise<void> | void\n beforeFinalRename?: (path: string) => Promise<void> | void\n beforeLocalDeleteOpenParent?: (path: string) => Promise<void> | void\n beforeLocalDeleteUnlink?: (path: string) => Promise<void> | void\n beforeStagingMarkerWrite?: (path: string) => Promise<void> | void\n disableProcFdAnchoring?: boolean\n platform?: string\n statForDeviceCheck?: DeviceStatFn\n} = {}\n\ninterface ScannedLocalFileHandle {\n stat(): Promise<{\n isFile(): boolean\n readonly dev: number\n readonly ino: number\n readonly mtimeMs: number\n readonly ctimeMs: number\n readonly size: number\n }>\n readFile(): Promise<Uint8Array>\n close(): Promise<void>\n}\n\n/**\n * Verifies that a previously scanned local file still points at the same regular file.\n *\n * @param path - Scanned local path and file identity.\n *\n * @internal\n */\nexport async function validateScannedLocalFile(path: LocalSyncPath): Promise<void> {\n const handle = await openValidatedScannedLocalFile(path)\n await handle.close()\n}\n\n/**\n * Reads a previously scanned local file while rejecting symlink swaps and metadata drift.\n *\n * @param path - Scanned local path and file identity.\n *\n * @returns The file bytes.\n *\n * @internal\n */\nexport async function readScannedLocalFile(path: LocalSyncPath): Promise<Uint8Array> {\n const handle = await openValidatedScannedLocalFile(path)\n try {\n const data = await handle.readFile()\n if (data.byteLength !== path.size) {\n throw new Error('local file changed before upload: size changed while reading')\n }\n return data\n } finally {\n await handle.close()\n }\n}\n\nasync function openValidatedScannedLocalFile(path: LocalSyncPath): Promise<ScannedLocalFileHandle> {\n const { constants } = await import('node:fs')\n const { open } = await import('node:fs/promises')\n const flags =\n constants.O_RDONLY |\n noFollowFlag(constants) |\n ((constants as { O_NONBLOCK?: number }).O_NONBLOCK ?? 0)\n const handle = await open(path.absolutePath, flags).catch((err: unknown) => {\n if (hasErrorCode(err, 'ELOOP')) {\n throw new Error('local file changed before upload: not a regular file')\n }\n throw new Error(\n `local file changed before upload: could not open scanned file: ${sanitizeErrorReason(err)}`,\n )\n })\n try {\n const stats = await handle.stat()\n assertSameScannedRegularFile(stats, path, 'upload', {\n platform: localFileIoTestHooks.platform,\n })\n return handle\n } catch (err) {\n await handle.close().catch(() => {})\n throw err\n }\n}\n\n/**\n * Writes bytes under a local sync root after path containment checks.\n *\n * @param root - Local sync root.\n * @param relPath - Destination path relative to the root.\n * @param data - Bytes to write.\n *\n * @internal\n */\nexport async function writeLocalFileInsideRoot(\n root: string,\n relPath: string,\n data: Uint8Array,\n): Promise<void> {\n await writeLocalStreamInsideRoot(\n root,\n relPath,\n new ReadableStream<Uint8Array>({\n start(controller) {\n controller.enqueue(data)\n controller.close()\n },\n }),\n {\n expectedBytes: data.byteLength,\n idleTimeoutMillis: DEFAULT_SHA1_IDLE_TIMEOUT_MILLIS,\n },\n )\n}\n\n/**\n * Streams a B2 download under a local sync root with path, timeout, and size checks.\n *\n * @param root - Local sync root.\n * @param relPath - Destination path relative to the root.\n * @param body - Download body stream.\n * @param options - Expected byte count, idle timeout, and optional abort signal.\n *\n * @internal\n */\nexport async function writeLocalStreamInsideRoot(\n root: string,\n relPath: string,\n body: ReadableStream<Uint8Array>,\n options: {\n readonly expectedBytes: number\n readonly expectedDestination?: LocalSyncPath | null\n readonly idleTimeoutMillis: number\n readonly signal?: AbortSignal\n },\n): Promise<void> {\n const { constants } = await import('node:fs')\n const { link, lstat, mkdir, open, realpath, rename, rm, stat } = await import('node:fs/promises')\n const path = await import('node:path')\n const { randomUUID } = await import('node:crypto')\n assertValidExpectedBytes(options.expectedBytes)\n const segments = safeRelativePathSegments(relPath)\n if (isDownloadStagingDirectorySegment(segments[0])) {\n throw new Error(\n `unsafe local destination path: ${DOWNLOAD_STAGING_DIRECTORY_NAME} is reserved for SDK download staging`,\n )\n }\n const rootRealPath = await realpath(root)\n\n let current = rootRealPath\n for (const segment of segments.slice(0, -1)) {\n current = path.join(current, segment)\n try {\n await mkdir(current)\n } catch (err) {\n if (!hasErrorCode(err, 'EEXIST')) throw err\n }\n const stats = await lstat(current)\n if (!stats.isDirectory()) {\n throw new Error('unsafe local destination path: parent is not a directory')\n }\n await localFileIoTestHooks.afterParentDirectoryValidated?.(current)\n }\n\n const destPath = path.join(rootRealPath, ...segments)\n assertPathInsideRoot(rootRealPath, destPath, path)\n\n const parentRealPath = await realpath(path.dirname(destPath))\n const finalPath = path.join(parentRealPath, path.basename(destPath))\n assertPathInsideRoot(rootRealPath, finalPath, path)\n try {\n const targetStats = await lstat(finalPath)\n if (targetStats.isSymbolicLink()) {\n throw new Error('unsafe local destination path: target is a symbolic link')\n }\n if (targetStats.isFile() && targetStats.nlink > 1) {\n throw new Error('unsafe local destination path: target has multiple hard links')\n }\n } catch (err) {\n if (!hasErrorCode(err, 'ENOENT')) throw err\n }\n const statForDeviceCheck = localFileIoTestHooks.statForDeviceCheck ?? stat\n await assertDownloadPathSameDevice(\n rootRealPath,\n parentRealPath,\n statForDeviceCheck,\n 'unsafe local destination path: cannot publish download across filesystems',\n )\n\n let parentHandle: Awaited<ReturnType<typeof open>> | undefined\n let anchoredParentPath: string | undefined\n const platform = (globalThis as { process?: { platform?: string } }).process?.platform\n /* v8 ignore start -- Linux-only fd-relative path support is covered by Linux CI */\n if (\n platform === 'linux' &&\n constants.O_DIRECTORY !== undefined &&\n localFileIoTestHooks.disableProcFdAnchoring !== true\n ) {\n try {\n parentHandle = await open(\n parentRealPath,\n constants.O_RDONLY | constants.O_DIRECTORY | noFollowFlag(constants),\n )\n anchoredParentPath = `/proc/self/fd/${parentHandle.fd}`\n } catch (err) {\n if (hasErrorCode(err, 'ELOOP') || hasErrorCode(err, 'ENOTDIR')) {\n throw new Error('unsafe local destination path: parent is not a directory')\n }\n throw err\n }\n }\n /* v8 ignore stop */\n const finalName = path.basename(destPath)\n const finalWritePath = path.join(anchoredParentPath ?? parentRealPath, finalName)\n let publishMode: number\n try {\n publishMode = await replacementFileMode(finalPath)\n } catch (err) {\n /* v8 ignore next -- best-effort close during setup failure */\n await parentHandle?.close().catch(() => {})\n throw err\n }\n let stagingDirectory: string\n try {\n stagingDirectory = await createDownloadStagingDirectory(\n rootRealPath,\n path,\n randomUUID,\n statForDeviceCheck,\n localFileIoTestHooks.beforeStagingMarkerWrite,\n )\n } catch (err) {\n /* v8 ignore next -- best-effort close during setup failure */\n await parentHandle?.close().catch(() => {})\n throw err\n }\n const tmpPath = path.join(stagingDirectory, `.b2sdk-${randomUUID()}.partial`)\n let handle: Awaited<ReturnType<typeof open>> | undefined\n try {\n handle = await open(\n tmpPath,\n constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | noFollowFlag(constants),\n PRIVATE_DOWNLOAD_FILE_MODE,\n )\n /* v8 ignore next -- best-effort chmod */\n await handle.chmod(PRIVATE_DOWNLOAD_FILE_MODE).catch(() => {})\n await localFileIoTestHooks.afterTempFileCreated?.(tmpPath, stagingDirectory)\n /* v8 ignore start -- defensive cleanup before the main write try/finally exists */\n } catch (err) {\n await parentHandle?.close().catch(() => {})\n await rm(stagingDirectory, { recursive: true, force: true }).catch(() => {})\n throw err\n }\n /* v8 ignore stop */\n try {\n const tmpRealPath = await realpath(tmpPath)\n assertPathInsideRoot(stagingDirectory, tmpRealPath, path)\n } catch (err) {\n /* v8 ignore next -- best-effort cleanup */\n await handle?.close().catch(() => {})\n /* v8 ignore next -- best-effort cleanup */\n await rm(tmpPath, { force: true }).catch(() => {})\n /* v8 ignore next -- best-effort cleanup */\n await rm(stagingDirectory, { recursive: true, force: true }).catch(() => {})\n /* v8 ignore next -- best-effort cleanup */\n await parentHandle?.close().catch(() => {})\n throw err\n }\n const writeHandle = handle\n const reader = body.getReader()\n let completed = false\n try {\n let bytesWritten = 0\n while (true) {\n const { done, value } = await readStreamChunkWithTimeout(\n reader,\n options.idleTimeoutMillis,\n `download read stalled for ${options.idleTimeoutMillis} ms`,\n options.signal,\n )\n if (done) break\n if (bytesWritten + value.byteLength > options.expectedBytes) {\n throw new Error(`download read exceeded ${options.expectedBytes} byte limit`)\n }\n await writeAll(writeHandle, value, bytesWritten)\n bytesWritten += value.byteLength\n }\n if (bytesWritten !== options.expectedBytes) {\n throw new Error(\n `download read ended after ${bytesWritten} bytes, expected ${options.expectedBytes}`,\n )\n }\n if (publishMode !== PRIVATE_DOWNLOAD_FILE_MODE) {\n /* v8 ignore next -- best-effort mode preservation */\n await writeHandle.chmod(publishMode).catch(() => {})\n }\n await writeHandle.close()\n handle = undefined\n\n const [parentRealPathBeforeRename, parentStatsBeforeRename] = await Promise.all([\n realpath(path.dirname(destPath)),\n stat(path.dirname(destPath)),\n ])\n const finalPathBeforeRename = path.join(parentRealPathBeforeRename, path.basename(destPath))\n assertPathInsideRoot(rootRealPath, finalPathBeforeRename, path)\n await localFileIoTestHooks.beforeFinalRename?.(parentRealPathBeforeRename)\n let publishPath = finalWritePath\n if (anchoredParentPath === undefined) {\n const [parentRealPathAfterHook, parentStatsAfterHook] = await Promise.all([\n realpath(path.dirname(destPath)),\n stat(path.dirname(destPath)),\n ])\n if (\n parentRealPathAfterHook !== parentRealPathBeforeRename ||\n !sameParentIdentity(parentStatsAfterHook, parentStatsBeforeRename)\n ) {\n throw new Error('unsafe local destination path: parent changed before final publish')\n }\n publishPath = path.join(parentRealPathAfterHook, path.basename(destPath))\n assertPathInsideRoot(rootRealPath, publishPath, path)\n }\n await publishDownload(\n lstat,\n link,\n path,\n randomUUID,\n rename,\n rm,\n tmpPath,\n publishPath,\n options.expectedDestination,\n )\n completed = true\n } catch (err) {\n /* v8 ignore next -- best-effort cleanup */\n void reader.cancel(err).catch(() => {})\n throw err\n } finally {\n reader.releaseLock()\n if (!completed) {\n /* v8 ignore next -- best-effort cleanup */\n await handle?.close().catch(() => {})\n /* v8 ignore next -- best-effort cleanup */\n await rm(tmpPath, { force: true }).catch(() => {})\n }\n /* v8 ignore next -- best-effort cleanup */\n await rm(stagingDirectory, { recursive: true, force: true }).catch(() => {})\n /* v8 ignore next -- best-effort cleanup */\n await parentHandle?.close().catch(() => {})\n }\n}\n\nfunction assertValidExpectedBytes(expectedBytes: number): void {\n if (!Number.isSafeInteger(expectedBytes) || expectedBytes < 0) {\n throw new Error('download expectedBytes must be a non-negative safe integer')\n }\n}\n\nasync function assertExpectedDownloadDestination(\n lstat: typeof import('node:fs/promises')['lstat'],\n finalPath: string,\n expectedDestination: LocalSyncPath | null | undefined,\n): Promise<void> {\n if (expectedDestination === undefined) return\n\n try {\n const stats = await lstat(finalPath)\n if (expectedDestination === null) {\n throw new Error('local destination changed before download: file was created')\n }\n assertSameScannedRegularFile(\n stats,\n { ...expectedDestination, absolutePath: finalPath },\n 'download',\n { platform: localFileIoTestHooks.platform },\n )\n } catch (err) {\n if (hasErrorCode(err, 'ENOENT')) {\n if (expectedDestination === null) return\n throw new Error('local file changed before download: file missing')\n }\n throw err\n }\n}\n\nasync function publishDownload(\n lstat: typeof import('node:fs/promises')['lstat'],\n link: typeof import('node:fs/promises')['link'],\n path: typeof import('node:path'),\n randomUUID: () => string,\n rename: typeof import('node:fs/promises')['rename'],\n rm: typeof import('node:fs/promises')['rm'],\n tmpPath: string,\n publishPath: string,\n expectedDestination: LocalSyncPath | null | undefined,\n): Promise<void> {\n await assertExpectedDownloadDestination(lstat, publishPath, expectedDestination)\n await localFileIoTestHooks.beforeDownloadPublish?.(publishPath)\n\n if (expectedDestination === undefined) {\n await rename(tmpPath, publishPath)\n return\n }\n\n if (expectedDestination === null) {\n await linkDownloadNoOverwrite(\n link,\n tmpPath,\n publishPath,\n 'local destination changed before download: file was created',\n )\n /* v8 ignore next -- staging cleanup is best-effort after a guarded publish succeeds. */\n await rm(tmpPath, { force: true }).catch(() => {})\n return\n }\n\n const backupPath = path.join(\n path.dirname(publishPath),\n makeReservedSyncTempFileName(path.basename(publishPath), randomUUID()),\n )\n let backupExists = false\n let removeBackup = false\n\n try {\n try {\n await rename(publishPath, backupPath)\n backupExists = true\n await localFileIoTestHooks.afterDownloadBackupRename?.(backupPath)\n } catch (err) {\n if (hasErrorCode(err, 'ENOENT')) {\n throw new Error('local file changed before download: file missing')\n }\n throw err\n }\n\n const backupStats = await lstat(backupPath)\n assertSameScannedRegularFile(\n backupStats,\n { ...expectedDestination, absolutePath: backupPath },\n 'download',\n { compareChangeTime: false, platform: localFileIoTestHooks.platform },\n )\n await linkDownloadNoOverwrite(\n link,\n tmpPath,\n publishPath,\n 'local destination changed before download: file was created',\n )\n removeBackup = true\n /* v8 ignore next -- staging cleanup is best-effort after a guarded publish succeeds. */\n await rm(tmpPath, { force: true }).catch(() => {})\n } catch (err) {\n if (backupExists && !removeBackup) {\n await restoreBackupWithoutOverwrite(link, rm, backupPath, publishPath).catch(() => {})\n }\n throw err\n } finally {\n if (removeBackup) {\n /* v8 ignore next -- old destination cleanup is best-effort after publish succeeds. */\n await rm(backupPath, { force: true }).catch(() => {})\n }\n }\n}\n\nasync function linkDownloadNoOverwrite(\n link: typeof import('node:fs/promises')['link'],\n sourcePath: string,\n destPath: string,\n message: string,\n): Promise<void> {\n try {\n await link(sourcePath, destPath)\n } catch (err) {\n if (hasErrorCode(err, 'EEXIST')) throw new Error(message)\n throw err\n }\n}\n\nasync function restoreBackupWithoutOverwrite(\n link: typeof import('node:fs/promises')['link'],\n rm: typeof import('node:fs/promises')['rm'],\n backupPath: string,\n publishPath: string,\n): Promise<void> {\n try {\n await link(backupPath, publishPath)\n await rm(backupPath, { force: true })\n } catch (err) {\n if (hasErrorCode(err, 'EEXIST')) return\n throw err\n }\n}\n\nconst PRIVATE_DOWNLOAD_FILE_MODE = 0o600\nasync function replacementFileMode(filePath: string): Promise<number> {\n const { lstat } = await import('node:fs/promises')\n try {\n const stats = await lstat(filePath)\n return stats.isFile() ? stats.mode & 0o777 : PRIVATE_DOWNLOAD_FILE_MODE\n } catch (err) {\n if (hasErrorCode(err, 'ENOENT')) return PRIVATE_DOWNLOAD_FILE_MODE\n throw err\n }\n}\n\n/**\n * Deletes a scanned local file under a sync root without re-resolving attacker-controlled parents.\n *\n * @param root - Local sync root.\n * @param scannedPath - Previously scanned local file metadata.\n *\n * @internal\n */\nexport async function deleteLocalFileInsideRoot(\n root: string,\n scannedPath: LocalSyncPath,\n): Promise<void> {\n if (root === '') {\n throw new Error('Local sync root required for filesystem mutation')\n }\n\n const { constants } = await import('node:fs')\n const { lstat, open, realpath, stat, unlink } = await import('node:fs/promises')\n const path = await import('node:path')\n const segments = safeRelativePathSegments(scannedPath.relativePath)\n const safeRoot = path.resolve(root)\n const rootStats = await lstat(safeRoot)\n if (rootStats.isSymbolicLink()) {\n throw new Error(`Refusing to access sync root through symlink: ${scannedPath.relativePath}`)\n }\n if (!rootStats.isDirectory()) {\n throw new Error(`Local sync root is not a directory: ${scannedPath.relativePath}`)\n }\n const rootRealPath = await realpath(safeRoot)\n const expectedPath = path.join(rootRealPath, ...segments)\n assertPathInsideRoot(rootRealPath, expectedPath, path)\n\n const scannerPath = path.resolve(scannedPath.absolutePath)\n if (scannerPath !== expectedPath) {\n throw new Error(`Refusing to delete outside sync root: ${scannedPath.relativePath}`)\n }\n\n const [parentRealPath, parentStats] = await Promise.all([\n realpath(path.dirname(expectedPath)),\n stat(path.dirname(expectedPath)),\n ])\n const finalPath = path.join(parentRealPath, path.basename(expectedPath))\n assertPathInsideRoot(rootRealPath, finalPath, path)\n\n const platform = (globalThis as { process?: { platform?: string } }).process?.platform\n let parentHandle: Awaited<ReturnType<typeof open>> | undefined\n let anchoredParentPath: string | undefined\n /* v8 ignore start -- Linux-only fd-relative path support is covered by Linux CI */\n if (\n platform === 'linux' &&\n constants.O_DIRECTORY !== undefined &&\n localFileIoTestHooks.disableProcFdAnchoring !== true\n ) {\n try {\n await localFileIoTestHooks.beforeLocalDeleteOpenParent?.(parentRealPath)\n parentHandle = await open(\n parentRealPath,\n constants.O_RDONLY | constants.O_DIRECTORY | noFollowFlag(constants),\n )\n anchoredParentPath = `/proc/self/fd/${parentHandle.fd}`\n } catch (err) {\n if (hasErrorCode(err, 'ELOOP') || hasErrorCode(err, 'ENOTDIR')) {\n throw new Error('unsafe local delete path: parent is not a directory')\n }\n throw err\n }\n }\n /* v8 ignore stop */\n\n try {\n const unlinkPath =\n anchoredParentPath === undefined\n ? finalPath\n : path.join(anchoredParentPath, path.basename(expectedPath))\n const stats = await lstat(unlinkPath)\n assertSameScannedRegularFile(stats, { ...scannedPath, absolutePath: unlinkPath }, 'delete', {\n platform: localFileIoTestHooks.platform,\n })\n await localFileIoTestHooks.beforeLocalDeleteUnlink?.(parentRealPath)\n if (\n anchoredParentPath === undefined &&\n localFileIoTestHooks.disableProcFdAnchoring === true &&\n parentRealPath !== rootRealPath\n ) {\n throw new Error('unsafe local delete path: stable parent handle unavailable for unlink')\n }\n\n if (anchoredParentPath === undefined) {\n const [parentRealPathBeforeUnlink, parentStatsBeforeUnlink] = await Promise.all([\n realpath(path.dirname(expectedPath)),\n stat(path.dirname(expectedPath)),\n ])\n if (\n parentRealPathBeforeUnlink !== parentRealPath ||\n !sameParentIdentity(parentStatsBeforeUnlink, parentStats)\n ) {\n throw new Error('unsafe local delete path: parent changed before unlink')\n }\n }\n const finalStats = await lstat(unlinkPath)\n assertSameScannedRegularFile(\n finalStats,\n { ...scannedPath, absolutePath: unlinkPath },\n 'delete',\n { platform: localFileIoTestHooks.platform },\n )\n await unlink(unlinkPath)\n } finally {\n /* v8 ignore next -- best-effort cleanup */\n await parentHandle?.close().catch(() => {})\n }\n}\n\nfunction sameParentIdentity(\n current: { readonly dev: number | bigint; readonly ino: number | bigint },\n expected: { readonly dev: number | bigint; readonly ino: number | bigint },\n): boolean {\n return current.dev === expected.dev && current.ino === expected.ino\n}\n\nasync function writeAll(\n handle: {\n write(\n buffer: Uint8Array,\n offset: number,\n length: number,\n position: number,\n ): Promise<{ readonly bytesWritten: number }>\n },\n data: Uint8Array,\n position: number,\n): Promise<void> {\n let offset = 0\n while (offset < data.byteLength) {\n const { bytesWritten } = await handle.write(\n data,\n offset,\n data.byteLength - offset,\n position + offset,\n )\n /* v8 ignore next -- defensive: FileHandle.write should progress for non-empty chunks. */\n if (bytesWritten <= 0) throw new Error('download write made no progress')\n offset += bytesWritten\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"local-file-io.cjs","names":[],"sources":["../../src/sync/local-file-io.ts"],"sourcesContent":["import { sanitizeErrorReason } from '../util/error-reason.ts'\nimport { readStreamChunkWithTimeout } from './b2-sha1-reader.ts'\nimport {\n assertDownloadPathSameDevice,\n createDownloadStagingDirectory,\n DOWNLOAD_STAGING_DIRECTORY_NAME,\n isDownloadStagingDirectorySegment,\n} from './download-staging.ts'\nimport { assertSameScannedRegularFile } from './local-file-identity.ts'\nimport {\n assertPathInsideRoot,\n hasErrorCode,\n makeReservedSyncTempFileName,\n noFollowFlag,\n safeRelativePathSegments,\n} from './path-safety.ts'\nimport { DEFAULT_SHA1_IDLE_TIMEOUT_MILLIS } from './sha1-options.ts'\nimport type { LocalSyncPath } from './types.ts'\n\ntype DeviceStat = { readonly dev: number | bigint }\ntype DeviceStatFn = (path: string) => Promise<DeviceStat>\n\nexport { DOWNLOAD_STAGING_DIRECTORY_NAME }\n\n/** @internal */\nexport const localFileIoTestHooks: {\n afterParentDirectoryValidated?: (path: string) => Promise<void> | void\n afterDownloadBackupRename?: (path: string) => Promise<void> | void\n afterTempFileCreated?: (path: string, stagingDirectory: string) => Promise<void> | void\n beforeDownloadPublish?: (path: string) => Promise<void> | void\n beforeFinalRename?: (path: string) => Promise<void> | void\n beforeLocalDeleteOpenParent?: (path: string) => Promise<void> | void\n beforeLocalDeleteUnlink?: (path: string) => Promise<void> | void\n afterManagedStagingMarkerWrite?: (path: string) => Promise<void> | void\n beforeStagingMarkerWrite?: (path: string) => Promise<void> | void\n disableProcFdAnchoring?: boolean\n downloadStagingActivityEntryLimit?: number\n platform?: string\n statForDeviceCheck?: DeviceStatFn\n} = {}\n\ninterface ScannedLocalFileHandle {\n stat(): Promise<{\n isFile(): boolean\n readonly dev: number\n readonly ino: number\n readonly mtimeMs: number\n readonly ctimeMs: number\n readonly size: number\n }>\n readFile(): Promise<Uint8Array>\n close(): Promise<void>\n}\n\n/**\n * Verifies that a previously scanned local file still points at the same regular file.\n *\n * @param path - Scanned local path and file identity.\n *\n * @internal\n */\nexport async function validateScannedLocalFile(path: LocalSyncPath): Promise<void> {\n const handle = await openValidatedScannedLocalFile(path)\n await handle.close()\n}\n\n/**\n * Reads a previously scanned local file while rejecting symlink swaps and metadata drift.\n *\n * @param path - Scanned local path and file identity.\n *\n * @returns The file bytes.\n *\n * @internal\n */\nexport async function readScannedLocalFile(path: LocalSyncPath): Promise<Uint8Array> {\n const handle = await openValidatedScannedLocalFile(path)\n try {\n const data = await handle.readFile()\n if (data.byteLength !== path.size) {\n throw new Error('local file changed before upload: size changed while reading')\n }\n return data\n } finally {\n await handle.close()\n }\n}\n\nasync function openValidatedScannedLocalFile(path: LocalSyncPath): Promise<ScannedLocalFileHandle> {\n const { constants } = await import('node:fs')\n const { open } = await import('node:fs/promises')\n const flags =\n constants.O_RDONLY |\n noFollowFlag(constants) |\n ((constants as { O_NONBLOCK?: number }).O_NONBLOCK ?? 0)\n const handle = await open(path.absolutePath, flags).catch((err: unknown) => {\n if (hasErrorCode(err, 'ELOOP')) {\n throw new Error('local file changed before upload: not a regular file')\n }\n throw new Error(\n `local file changed before upload: could not open scanned file: ${sanitizeErrorReason(err)}`,\n )\n })\n try {\n const stats = await handle.stat()\n assertSameScannedRegularFile(stats, path, 'upload', {\n platform: localFileIoTestHooks.platform,\n })\n return handle\n } catch (err) {\n await handle.close().catch(() => {})\n throw err\n }\n}\n\n/**\n * Writes bytes under a local sync root after path containment checks.\n *\n * @param root - Local sync root.\n * @param relPath - Destination path relative to the root.\n * @param data - Bytes to write.\n *\n * @internal\n */\nexport async function writeLocalFileInsideRoot(\n root: string,\n relPath: string,\n data: Uint8Array,\n): Promise<void> {\n await writeLocalStreamInsideRoot(\n root,\n relPath,\n new ReadableStream<Uint8Array>({\n start(controller) {\n controller.enqueue(data)\n controller.close()\n },\n }),\n {\n expectedBytes: data.byteLength,\n idleTimeoutMillis: DEFAULT_SHA1_IDLE_TIMEOUT_MILLIS,\n },\n )\n}\n\n/**\n * Streams a B2 download under a local sync root with path, timeout, and size checks.\n *\n * @param root - Local sync root.\n * @param relPath - Destination path relative to the root.\n * @param body - Download body stream.\n * @param options - Expected byte count, idle timeout, and optional abort signal.\n *\n * @internal\n */\nexport async function writeLocalStreamInsideRoot(\n root: string,\n relPath: string,\n body: ReadableStream<Uint8Array>,\n options: {\n readonly expectedBytes: number\n readonly expectedDestination?: LocalSyncPath | null\n readonly idleTimeoutMillis: number\n readonly onDownloadStagingRoot?: (managedDirectory: string) => void\n readonly signal?: AbortSignal\n },\n): Promise<void> {\n const { constants } = await import('node:fs')\n const { link, lstat, mkdir, open, realpath, rename, rm, stat } = await import('node:fs/promises')\n const path = await import('node:path')\n const { randomUUID } = await import('node:crypto')\n assertValidExpectedBytes(options.expectedBytes)\n const segments = safeRelativePathSegments(relPath)\n if (isDownloadStagingDirectorySegment(segments[0])) {\n throw new Error(\n `unsafe local destination path: ${DOWNLOAD_STAGING_DIRECTORY_NAME} is reserved for SDK download staging`,\n )\n }\n const rootRealPath = await realpath(root)\n\n let current = rootRealPath\n for (const segment of segments.slice(0, -1)) {\n current = path.join(current, segment)\n try {\n await mkdir(current)\n } catch (err) {\n if (!hasErrorCode(err, 'EEXIST')) throw err\n }\n const stats = await lstat(current)\n if (!stats.isDirectory()) {\n throw new Error('unsafe local destination path: parent is not a directory')\n }\n await localFileIoTestHooks.afterParentDirectoryValidated?.(current)\n }\n\n const destPath = path.join(rootRealPath, ...segments)\n assertPathInsideRoot(rootRealPath, destPath, path)\n\n const parentRealPath = await realpath(path.dirname(destPath))\n const finalPath = path.join(parentRealPath, path.basename(destPath))\n assertPathInsideRoot(rootRealPath, finalPath, path)\n try {\n const targetStats = await lstat(finalPath)\n if (targetStats.isSymbolicLink()) {\n throw new Error('unsafe local destination path: target is a symbolic link')\n }\n if (targetStats.isFile() && targetStats.nlink > 1) {\n throw new Error('unsafe local destination path: target has multiple hard links')\n }\n } catch (err) {\n if (!hasErrorCode(err, 'ENOENT')) throw err\n }\n const statForDeviceCheck = localFileIoTestHooks.statForDeviceCheck ?? stat\n await assertDownloadPathSameDevice(\n rootRealPath,\n parentRealPath,\n statForDeviceCheck,\n 'unsafe local destination path: cannot publish download across filesystems',\n )\n\n let parentHandle: Awaited<ReturnType<typeof open>> | undefined\n let anchoredParentPath: string | undefined\n const platform = (globalThis as { process?: { platform?: string } }).process?.platform\n /* v8 ignore start -- Linux-only fd-relative path support is covered by Linux CI */\n if (\n platform === 'linux' &&\n constants.O_DIRECTORY !== undefined &&\n localFileIoTestHooks.disableProcFdAnchoring !== true\n ) {\n try {\n parentHandle = await open(\n parentRealPath,\n constants.O_RDONLY | constants.O_DIRECTORY | noFollowFlag(constants),\n )\n anchoredParentPath = `/proc/self/fd/${parentHandle.fd}`\n } catch (err) {\n if (hasErrorCode(err, 'ELOOP') || hasErrorCode(err, 'ENOTDIR')) {\n throw new Error('unsafe local destination path: parent is not a directory')\n }\n throw err\n }\n }\n /* v8 ignore stop */\n const finalName = path.basename(destPath)\n const finalWritePath = path.join(anchoredParentPath ?? parentRealPath, finalName)\n let publishMode: number\n try {\n publishMode = await replacementFileMode(finalPath)\n } catch (err) {\n /* v8 ignore next -- best-effort close during setup failure */\n await parentHandle?.close().catch(() => {})\n throw err\n }\n let stagingDirectory: string\n let managedStagingDirectory: string\n try {\n const staging = await createDownloadStagingDirectory(\n rootRealPath,\n path,\n randomUUID,\n statForDeviceCheck,\n localFileIoTestHooks.beforeStagingMarkerWrite,\n localFileIoTestHooks.downloadStagingActivityEntryLimit,\n localFileIoTestHooks.afterManagedStagingMarkerWrite,\n )\n stagingDirectory = staging.stagingDirectory\n managedStagingDirectory = staging.managedDirectory\n } catch (err) {\n /* v8 ignore next -- best-effort close during setup failure */\n await parentHandle?.close().catch(() => {})\n throw err\n }\n options.onDownloadStagingRoot?.(managedStagingDirectory)\n const tmpPath = path.join(stagingDirectory, `.b2sdk-${randomUUID()}.partial`)\n let handle: Awaited<ReturnType<typeof open>> | undefined\n try {\n handle = await open(\n tmpPath,\n constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | noFollowFlag(constants),\n PRIVATE_DOWNLOAD_FILE_MODE,\n )\n /* v8 ignore next -- best-effort chmod */\n await handle.chmod(PRIVATE_DOWNLOAD_FILE_MODE).catch(() => {})\n await localFileIoTestHooks.afterTempFileCreated?.(tmpPath, stagingDirectory)\n /* v8 ignore start -- defensive cleanup before the main write try/finally exists */\n } catch (err) {\n await parentHandle?.close().catch(() => {})\n await rm(stagingDirectory, { recursive: true, force: true }).catch(() => {})\n throw err\n }\n /* v8 ignore stop */\n try {\n const tmpRealPath = await realpath(tmpPath)\n assertPathInsideRoot(stagingDirectory, tmpRealPath, path)\n } catch (err) {\n /* v8 ignore next -- best-effort cleanup */\n await handle?.close().catch(() => {})\n /* v8 ignore next -- best-effort cleanup */\n await rm(tmpPath, { force: true }).catch(() => {})\n /* v8 ignore next -- best-effort cleanup */\n await rm(stagingDirectory, { recursive: true, force: true }).catch(() => {})\n /* v8 ignore next -- best-effort cleanup */\n await parentHandle?.close().catch(() => {})\n throw err\n }\n const writeHandle = handle\n const reader = body.getReader()\n let completed = false\n try {\n let bytesWritten = 0\n while (true) {\n const { done, value } = await readStreamChunkWithTimeout(\n reader,\n options.idleTimeoutMillis,\n `download read stalled for ${options.idleTimeoutMillis} ms`,\n options.signal,\n )\n if (done) break\n if (bytesWritten + value.byteLength > options.expectedBytes) {\n throw new Error(`download read exceeded ${options.expectedBytes} byte limit`)\n }\n await writeAll(writeHandle, value, bytesWritten)\n bytesWritten += value.byteLength\n }\n if (bytesWritten !== options.expectedBytes) {\n throw new Error(\n `download read ended after ${bytesWritten} bytes, expected ${options.expectedBytes}`,\n )\n }\n if (publishMode !== PRIVATE_DOWNLOAD_FILE_MODE) {\n /* v8 ignore next -- best-effort mode preservation */\n await writeHandle.chmod(publishMode).catch(() => {})\n }\n await writeHandle.close()\n handle = undefined\n\n const [parentRealPathBeforeRename, parentStatsBeforeRename] = await Promise.all([\n realpath(path.dirname(destPath)),\n stat(path.dirname(destPath)),\n ])\n const finalPathBeforeRename = path.join(parentRealPathBeforeRename, path.basename(destPath))\n assertPathInsideRoot(rootRealPath, finalPathBeforeRename, path)\n await localFileIoTestHooks.beforeFinalRename?.(parentRealPathBeforeRename)\n let publishPath = finalWritePath\n if (anchoredParentPath === undefined) {\n const [parentRealPathAfterHook, parentStatsAfterHook] = await Promise.all([\n realpath(path.dirname(destPath)),\n stat(path.dirname(destPath)),\n ])\n if (\n parentRealPathAfterHook !== parentRealPathBeforeRename ||\n !sameParentIdentity(parentStatsAfterHook, parentStatsBeforeRename)\n ) {\n throw new Error('unsafe local destination path: parent changed before final publish')\n }\n publishPath = path.join(parentRealPathAfterHook, path.basename(destPath))\n assertPathInsideRoot(rootRealPath, publishPath, path)\n }\n await publishDownload(\n lstat,\n link,\n path,\n randomUUID,\n rename,\n rm,\n tmpPath,\n publishPath,\n options.expectedDestination,\n )\n completed = true\n } catch (err) {\n /* v8 ignore next -- best-effort cleanup */\n void reader.cancel(err).catch(() => {})\n throw err\n } finally {\n reader.releaseLock()\n if (!completed) {\n /* v8 ignore next -- best-effort cleanup */\n await handle?.close().catch(() => {})\n /* v8 ignore next -- best-effort cleanup */\n await rm(tmpPath, { force: true }).catch(() => {})\n }\n /* v8 ignore next -- best-effort cleanup */\n await rm(stagingDirectory, { recursive: true, force: true }).catch(() => {})\n /* v8 ignore next -- best-effort cleanup */\n await parentHandle?.close().catch(() => {})\n }\n}\n\nfunction assertValidExpectedBytes(expectedBytes: number): void {\n if (!Number.isSafeInteger(expectedBytes) || expectedBytes < 0) {\n throw new Error('download expectedBytes must be a non-negative safe integer')\n }\n}\n\nasync function assertExpectedDownloadDestination(\n lstat: typeof import('node:fs/promises')['lstat'],\n finalPath: string,\n expectedDestination: LocalSyncPath | null | undefined,\n): Promise<void> {\n if (expectedDestination === undefined) return\n\n try {\n const stats = await lstat(finalPath)\n if (expectedDestination === null) {\n throw new Error('local destination changed before download: file was created')\n }\n assertSameScannedRegularFile(\n stats,\n { ...expectedDestination, absolutePath: finalPath },\n 'download',\n { platform: localFileIoTestHooks.platform },\n )\n } catch (err) {\n if (hasErrorCode(err, 'ENOENT')) {\n if (expectedDestination === null) return\n throw new Error('local file changed before download: file missing')\n }\n throw err\n }\n}\n\nasync function publishDownload(\n lstat: typeof import('node:fs/promises')['lstat'],\n link: typeof import('node:fs/promises')['link'],\n path: typeof import('node:path'),\n randomUUID: () => string,\n rename: typeof import('node:fs/promises')['rename'],\n rm: typeof import('node:fs/promises')['rm'],\n tmpPath: string,\n publishPath: string,\n expectedDestination: LocalSyncPath | null | undefined,\n): Promise<void> {\n await assertExpectedDownloadDestination(lstat, publishPath, expectedDestination)\n await localFileIoTestHooks.beforeDownloadPublish?.(publishPath)\n\n if (expectedDestination === undefined) {\n await rename(tmpPath, publishPath)\n return\n }\n\n if (expectedDestination === null) {\n await linkDownloadNoOverwrite(\n link,\n tmpPath,\n publishPath,\n 'local destination changed before download: file was created',\n )\n /* v8 ignore next -- staging cleanup is best-effort after a guarded publish succeeds. */\n await rm(tmpPath, { force: true }).catch(() => {})\n return\n }\n\n const backupPath = path.join(\n path.dirname(publishPath),\n makeReservedSyncTempFileName(path.basename(publishPath), randomUUID()),\n )\n let backupExists = false\n let removeBackup = false\n\n try {\n try {\n await rename(publishPath, backupPath)\n backupExists = true\n await localFileIoTestHooks.afterDownloadBackupRename?.(backupPath)\n } catch (err) {\n if (hasErrorCode(err, 'ENOENT')) {\n throw new Error('local file changed before download: file missing')\n }\n throw err\n }\n\n const backupStats = await lstat(backupPath)\n assertSameScannedRegularFile(\n backupStats,\n { ...expectedDestination, absolutePath: backupPath },\n 'download',\n { compareChangeTime: false, platform: localFileIoTestHooks.platform },\n )\n await linkDownloadNoOverwrite(\n link,\n tmpPath,\n publishPath,\n 'local destination changed before download: file was created',\n )\n removeBackup = true\n /* v8 ignore next -- staging cleanup is best-effort after a guarded publish succeeds. */\n await rm(tmpPath, { force: true }).catch(() => {})\n } catch (err) {\n if (backupExists && !removeBackup) {\n await restoreBackupWithoutOverwrite(link, rm, backupPath, publishPath).catch(() => {})\n }\n throw err\n } finally {\n if (removeBackup) {\n /* v8 ignore next -- old destination cleanup is best-effort after publish succeeds. */\n await rm(backupPath, { force: true }).catch(() => {})\n }\n }\n}\n\nasync function linkDownloadNoOverwrite(\n link: typeof import('node:fs/promises')['link'],\n sourcePath: string,\n destPath: string,\n message: string,\n): Promise<void> {\n try {\n await link(sourcePath, destPath)\n } catch (err) {\n if (hasErrorCode(err, 'EEXIST')) throw new Error(message)\n throw err\n }\n}\n\nasync function restoreBackupWithoutOverwrite(\n link: typeof import('node:fs/promises')['link'],\n rm: typeof import('node:fs/promises')['rm'],\n backupPath: string,\n publishPath: string,\n): Promise<void> {\n try {\n await link(backupPath, publishPath)\n await rm(backupPath, { force: true })\n } catch (err) {\n if (hasErrorCode(err, 'EEXIST')) return\n throw err\n }\n}\n\nconst PRIVATE_DOWNLOAD_FILE_MODE = 0o600\nasync function replacementFileMode(filePath: string): Promise<number> {\n const { lstat } = await import('node:fs/promises')\n try {\n const stats = await lstat(filePath)\n return stats.isFile() ? stats.mode & 0o777 : PRIVATE_DOWNLOAD_FILE_MODE\n } catch (err) {\n if (hasErrorCode(err, 'ENOENT')) return PRIVATE_DOWNLOAD_FILE_MODE\n throw err\n }\n}\n\n/**\n * Deletes a scanned local file under a sync root without re-resolving attacker-controlled parents.\n *\n * @param root - Local sync root.\n * @param scannedPath - Previously scanned local file metadata.\n *\n * @internal\n */\nexport async function deleteLocalFileInsideRoot(\n root: string,\n scannedPath: LocalSyncPath,\n): Promise<void> {\n if (root === '') {\n throw new Error('Local sync root required for filesystem mutation')\n }\n\n const { constants } = await import('node:fs')\n const { lstat, open, realpath, stat, unlink } = await import('node:fs/promises')\n const path = await import('node:path')\n const segments = safeRelativePathSegments(scannedPath.relativePath)\n const safeRoot = path.resolve(root)\n const rootStats = await lstat(safeRoot)\n if (rootStats.isSymbolicLink()) {\n throw new Error(`Refusing to access sync root through symlink: ${scannedPath.relativePath}`)\n }\n if (!rootStats.isDirectory()) {\n throw new Error(`Local sync root is not a directory: ${scannedPath.relativePath}`)\n }\n const rootRealPath = await realpath(safeRoot)\n const expectedPath = path.join(rootRealPath, ...segments)\n assertPathInsideRoot(rootRealPath, expectedPath, path)\n\n const scannerPath = path.resolve(scannedPath.absolutePath)\n if (scannerPath !== expectedPath) {\n throw new Error(`Refusing to delete outside sync root: ${scannedPath.relativePath}`)\n }\n\n const [parentRealPath, parentStats] = await Promise.all([\n realpath(path.dirname(expectedPath)),\n stat(path.dirname(expectedPath)),\n ])\n const finalPath = path.join(parentRealPath, path.basename(expectedPath))\n assertPathInsideRoot(rootRealPath, finalPath, path)\n\n const platform = (globalThis as { process?: { platform?: string } }).process?.platform\n let parentHandle: Awaited<ReturnType<typeof open>> | undefined\n let anchoredParentPath: string | undefined\n /* v8 ignore start -- Linux-only fd-relative path support is covered by Linux CI */\n if (\n platform === 'linux' &&\n constants.O_DIRECTORY !== undefined &&\n localFileIoTestHooks.disableProcFdAnchoring !== true\n ) {\n try {\n await localFileIoTestHooks.beforeLocalDeleteOpenParent?.(parentRealPath)\n parentHandle = await open(\n parentRealPath,\n constants.O_RDONLY | constants.O_DIRECTORY | noFollowFlag(constants),\n )\n anchoredParentPath = `/proc/self/fd/${parentHandle.fd}`\n } catch (err) {\n if (hasErrorCode(err, 'ELOOP') || hasErrorCode(err, 'ENOTDIR')) {\n throw new Error('unsafe local delete path: parent is not a directory')\n }\n throw err\n }\n }\n /* v8 ignore stop */\n\n try {\n const unlinkPath =\n anchoredParentPath === undefined\n ? finalPath\n : path.join(anchoredParentPath, path.basename(expectedPath))\n const stats = await lstat(unlinkPath)\n assertSameScannedRegularFile(stats, { ...scannedPath, absolutePath: unlinkPath }, 'delete', {\n platform: localFileIoTestHooks.platform,\n })\n await localFileIoTestHooks.beforeLocalDeleteUnlink?.(parentRealPath)\n if (\n anchoredParentPath === undefined &&\n localFileIoTestHooks.disableProcFdAnchoring === true &&\n parentRealPath !== rootRealPath\n ) {\n throw new Error('unsafe local delete path: stable parent handle unavailable for unlink')\n }\n\n if (anchoredParentPath === undefined) {\n const [parentRealPathBeforeUnlink, parentStatsBeforeUnlink] = await Promise.all([\n realpath(path.dirname(expectedPath)),\n stat(path.dirname(expectedPath)),\n ])\n if (\n parentRealPathBeforeUnlink !== parentRealPath ||\n !sameParentIdentity(parentStatsBeforeUnlink, parentStats)\n ) {\n throw new Error('unsafe local delete path: parent changed before unlink')\n }\n }\n const finalStats = await lstat(unlinkPath)\n assertSameScannedRegularFile(\n finalStats,\n { ...scannedPath, absolutePath: unlinkPath },\n 'delete',\n { platform: localFileIoTestHooks.platform },\n )\n await unlink(unlinkPath)\n } finally {\n /* v8 ignore next -- best-effort cleanup */\n await parentHandle?.close().catch(() => {})\n }\n}\n\nfunction sameParentIdentity(\n current: { readonly dev: number | bigint; readonly ino: number | bigint },\n expected: { readonly dev: number | bigint; readonly ino: number | bigint },\n): boolean {\n return current.dev === expected.dev && current.ino === expected.ino\n}\n\nasync function writeAll(\n handle: {\n write(\n buffer: Uint8Array,\n offset: number,\n length: number,\n position: number,\n ): Promise<{ readonly bytesWritten: number }>\n },\n data: Uint8Array,\n position: number,\n): Promise<void> {\n let offset = 0\n while (offset < data.byteLength) {\n const { bytesWritten } = await handle.write(\n data,\n offset,\n data.byteLength - offset,\n position + offset,\n )\n /* v8 ignore next -- defensive: FileHandle.write should progress for non-empty chunks. */\n if (bytesWritten <= 0) throw new Error('download write made no progress')\n offset += bytesWritten\n }\n}\n"],"mappings":"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"}
|
|
@@ -14,8 +14,10 @@ export declare const localFileIoTestHooks: {
|
|
|
14
14
|
beforeFinalRename?: (path: string) => Promise<void> | void;
|
|
15
15
|
beforeLocalDeleteOpenParent?: (path: string) => Promise<void> | void;
|
|
16
16
|
beforeLocalDeleteUnlink?: (path: string) => Promise<void> | void;
|
|
17
|
+
afterManagedStagingMarkerWrite?: (path: string) => Promise<void> | void;
|
|
17
18
|
beforeStagingMarkerWrite?: (path: string) => Promise<void> | void;
|
|
18
19
|
disableProcFdAnchoring?: boolean;
|
|
20
|
+
downloadStagingActivityEntryLimit?: number;
|
|
19
21
|
platform?: string;
|
|
20
22
|
statForDeviceCheck?: DeviceStatFn;
|
|
21
23
|
};
|
|
@@ -61,6 +63,7 @@ export declare function writeLocalStreamInsideRoot(root: string, relPath: string
|
|
|
61
63
|
readonly expectedBytes: number;
|
|
62
64
|
readonly expectedDestination?: LocalSyncPath | null;
|
|
63
65
|
readonly idleTimeoutMillis: number;
|
|
66
|
+
readonly onDownloadStagingRoot?: (managedDirectory: string) => void;
|
|
64
67
|
readonly signal?: AbortSignal;
|
|
65
68
|
}): Promise<void>;
|
|
66
69
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local-file-io.d.ts","sourceRoot":"","sources":["../../src/sync/local-file-io.js"],"names":[],"mappings":"AAEA,OAAO,EAGL,+BAA+B,EAEhC,MAAM,uBAAuB,CAAA;AAU9B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE/C,KAAK,UAAU,GAAG;IAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,CAAA;AACnD,KAAK,YAAY,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,CAAA;AAEzD,OAAO,EAAE,+BAA+B,EAAE,CAAA;AAE1C,gBAAgB;AAChB,eAAO,MAAM,oBAAoB,EAAE;IACjC,6BAA6B,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IACtE,yBAAyB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAClE,oBAAoB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IACvF,qBAAqB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAC9D,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAC1D,2BAA2B,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IACpE,uBAAuB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAChE,wBAAwB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IACjE,sBAAsB,CAAC,EAAE,OAAO,CAAA;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,kBAAkB,CAAC,EAAE,YAAY,CAAA;CAC7B,CAAA;AAeN;;;;;;GAMG;AACH,wBAAsB,wBAAwB,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAGjF;AAED;;;;;;;;GAQG;AACH,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,UAAU,CAAC,CAWnF;AA6BD;;;;;;;;GAQG;AACH,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,UAAU,GACf,OAAO,CAAC,IAAI,CAAC,CAef;AAED;;;;;;;;;GASG;AACH,wBAAsB,0BAA0B,CAC9C,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,EAChC,OAAO,EAAE;IACP,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,mBAAmB,CAAC,EAAE,aAAa,GAAG,IAAI,CAAA;IACnD,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAA;IAClC,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAC9B,GACA,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"local-file-io.d.ts","sourceRoot":"","sources":["../../src/sync/local-file-io.js"],"names":[],"mappings":"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"}
|
|
@@ -14,8 +14,10 @@ export declare const localFileIoTestHooks: {
|
|
|
14
14
|
beforeFinalRename?: (path: string) => Promise<void> | void;
|
|
15
15
|
beforeLocalDeleteOpenParent?: (path: string) => Promise<void> | void;
|
|
16
16
|
beforeLocalDeleteUnlink?: (path: string) => Promise<void> | void;
|
|
17
|
+
afterManagedStagingMarkerWrite?: (path: string) => Promise<void> | void;
|
|
17
18
|
beforeStagingMarkerWrite?: (path: string) => Promise<void> | void;
|
|
18
19
|
disableProcFdAnchoring?: boolean;
|
|
20
|
+
downloadStagingActivityEntryLimit?: number;
|
|
19
21
|
platform?: string;
|
|
20
22
|
statForDeviceCheck?: DeviceStatFn;
|
|
21
23
|
};
|
|
@@ -61,6 +63,7 @@ export declare function writeLocalStreamInsideRoot(root: string, relPath: string
|
|
|
61
63
|
readonly expectedBytes: number;
|
|
62
64
|
readonly expectedDestination?: LocalSyncPath | null;
|
|
63
65
|
readonly idleTimeoutMillis: number;
|
|
66
|
+
readonly onDownloadStagingRoot?: (managedDirectory: string) => void;
|
|
64
67
|
readonly signal?: AbortSignal;
|
|
65
68
|
}): Promise<void>;
|
|
66
69
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local-file-io.d.ts","sourceRoot":"","sources":["../../src/sync/local-file-io.js"],"names":[],"mappings":"AAEA,OAAO,EAGL,+BAA+B,EAEhC,MAAM,uBAAuB,CAAA;AAU9B,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE/C,KAAK,UAAU,GAAG;IAAE,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;CAAE,CAAA;AACnD,KAAK,YAAY,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,CAAA;AAEzD,OAAO,EAAE,+BAA+B,EAAE,CAAA;AAE1C,gBAAgB;AAChB,eAAO,MAAM,oBAAoB,EAAE;IACjC,6BAA6B,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IACtE,yBAAyB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAClE,oBAAoB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IACvF,qBAAqB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAC9D,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAC1D,2BAA2B,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IACpE,uBAAuB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IAChE,wBAAwB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;IACjE,sBAAsB,CAAC,EAAE,OAAO,CAAA;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,kBAAkB,CAAC,EAAE,YAAY,CAAA;CAC7B,CAAA;AAeN;;;;;;GAMG;AACH,wBAAsB,wBAAwB,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAGjF;AAED;;;;;;;;GAQG;AACH,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,UAAU,CAAC,CAWnF;AA6BD;;;;;;;;GAQG;AACH,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,UAAU,GACf,OAAO,CAAC,IAAI,CAAC,CAef;AAED;;;;;;;;;GASG;AACH,wBAAsB,0BAA0B,CAC9C,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,cAAc,CAAC,UAAU,CAAC,EAChC,OAAO,EAAE;IACP,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;IAC9B,QAAQ,CAAC,mBAAmB,CAAC,EAAE,aAAa,GAAG,IAAI,CAAA;IACnD,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAA;IAClC,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAC9B,GACA,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"local-file-io.d.ts","sourceRoot":"","sources":["../../src/sync/local-file-io.js"],"names":[],"mappings":"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"}
|
|
@@ -25,7 +25,8 @@ async function openValidatedScannedLocalFile(path) {
|
|
|
25
25
|
throw new Error(`local file changed before upload: could not open scanned file: ${sanitizeErrorReason(err)}`);
|
|
26
26
|
});
|
|
27
27
|
try {
|
|
28
|
-
|
|
28
|
+
const stats = await handle.stat();
|
|
29
|
+
assertSameScannedRegularFile(stats, path, "upload", { platform: localFileIoTestHooks.platform });
|
|
29
30
|
return handle;
|
|
30
31
|
} catch (err) {
|
|
31
32
|
await handle.close().catch(() => {});
|
|
@@ -98,13 +99,17 @@ async function writeLocalStreamInsideRoot(root, relPath, body, options) {
|
|
|
98
99
|
throw err;
|
|
99
100
|
}
|
|
100
101
|
let stagingDirectory;
|
|
102
|
+
let managedStagingDirectory;
|
|
101
103
|
try {
|
|
102
|
-
|
|
104
|
+
const staging = await createDownloadStagingDirectory(rootRealPath, path, randomUUID, statForDeviceCheck, localFileIoTestHooks.beforeStagingMarkerWrite, localFileIoTestHooks.downloadStagingActivityEntryLimit, localFileIoTestHooks.afterManagedStagingMarkerWrite);
|
|
105
|
+
stagingDirectory = staging.stagingDirectory;
|
|
106
|
+
managedStagingDirectory = staging.managedDirectory;
|
|
103
107
|
} catch (err) {
|
|
104
108
|
/* v8 ignore next -- best-effort close during setup failure */
|
|
105
109
|
await parentHandle?.close().catch(() => {});
|
|
106
110
|
throw err;
|
|
107
111
|
}
|
|
112
|
+
options.onDownloadStagingRoot?.(managedStagingDirectory);
|
|
108
113
|
const tmpPath = path.join(stagingDirectory, `.b2sdk-${randomUUID()}.partial`);
|
|
109
114
|
let handle;
|
|
110
115
|
try {
|
|
@@ -157,7 +162,8 @@ async function writeLocalStreamInsideRoot(root, relPath, body, options) {
|
|
|
157
162
|
await writeHandle.close();
|
|
158
163
|
handle = void 0;
|
|
159
164
|
const [parentRealPathBeforeRename, parentStatsBeforeRename] = await Promise.all([realpath(path.dirname(destPath)), stat(path.dirname(destPath))]);
|
|
160
|
-
|
|
165
|
+
const finalPathBeforeRename = path.join(parentRealPathBeforeRename, path.basename(destPath));
|
|
166
|
+
assertPathInsideRoot(rootRealPath, finalPathBeforeRename, path);
|
|
161
167
|
await localFileIoTestHooks.beforeFinalRename?.(parentRealPathBeforeRename);
|
|
162
168
|
let publishPath = finalWritePath;
|
|
163
169
|
if (anchoredParentPath === void 0) {
|
|
@@ -234,7 +240,8 @@ async function publishDownload(lstat, link, path, randomUUID, rename, rm, tmpPat
|
|
|
234
240
|
if (hasErrorCode(err, "ENOENT")) throw new Error("local file changed before download: file missing");
|
|
235
241
|
throw err;
|
|
236
242
|
}
|
|
237
|
-
|
|
243
|
+
const backupStats = await lstat(backupPath);
|
|
244
|
+
assertSameScannedRegularFile(backupStats, {
|
|
238
245
|
...expectedDestination,
|
|
239
246
|
absolutePath: backupPath
|
|
240
247
|
}, "download", {
|
|
@@ -322,7 +329,8 @@ async function deleteLocalFileInsideRoot(root, scannedPath) {
|
|
|
322
329
|
/* v8 ignore stop */
|
|
323
330
|
try {
|
|
324
331
|
const unlinkPath = anchoredParentPath === void 0 ? finalPath : path.join(anchoredParentPath, path.basename(expectedPath));
|
|
325
|
-
|
|
332
|
+
const stats = await lstat(unlinkPath);
|
|
333
|
+
assertSameScannedRegularFile(stats, {
|
|
326
334
|
...scannedPath,
|
|
327
335
|
absolutePath: unlinkPath
|
|
328
336
|
}, "delete", { platform: localFileIoTestHooks.platform });
|
|
@@ -332,7 +340,8 @@ async function deleteLocalFileInsideRoot(root, scannedPath) {
|
|
|
332
340
|
const [parentRealPathBeforeUnlink, parentStatsBeforeUnlink] = await Promise.all([realpath(path.dirname(expectedPath)), stat(path.dirname(expectedPath))]);
|
|
333
341
|
if (parentRealPathBeforeUnlink !== parentRealPath || !sameParentIdentity(parentStatsBeforeUnlink, parentStats)) throw new Error("unsafe local delete path: parent changed before unlink");
|
|
334
342
|
}
|
|
335
|
-
|
|
343
|
+
const finalStats = await lstat(unlinkPath);
|
|
344
|
+
assertSameScannedRegularFile(finalStats, {
|
|
336
345
|
...scannedPath,
|
|
337
346
|
absolutePath: unlinkPath
|
|
338
347
|
}, "delete", { platform: localFileIoTestHooks.platform });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local-file-io.js","names":[],"sources":["../../src/sync/local-file-io.ts"],"sourcesContent":["import { sanitizeErrorReason } from '../util/error-reason.ts'\nimport { readStreamChunkWithTimeout } from './b2-sha1-reader.ts'\nimport {\n assertDownloadPathSameDevice,\n createDownloadStagingDirectory,\n DOWNLOAD_STAGING_DIRECTORY_NAME,\n isDownloadStagingDirectorySegment,\n} from './download-staging.ts'\nimport { assertSameScannedRegularFile } from './local-file-identity.ts'\nimport {\n assertPathInsideRoot,\n hasErrorCode,\n makeReservedSyncTempFileName,\n noFollowFlag,\n safeRelativePathSegments,\n} from './path-safety.ts'\nimport { DEFAULT_SHA1_IDLE_TIMEOUT_MILLIS } from './sha1-options.ts'\nimport type { LocalSyncPath } from './types.ts'\n\ntype DeviceStat = { readonly dev: number | bigint }\ntype DeviceStatFn = (path: string) => Promise<DeviceStat>\n\nexport { DOWNLOAD_STAGING_DIRECTORY_NAME }\n\n/** @internal */\nexport const localFileIoTestHooks: {\n afterParentDirectoryValidated?: (path: string) => Promise<void> | void\n afterDownloadBackupRename?: (path: string) => Promise<void> | void\n afterTempFileCreated?: (path: string, stagingDirectory: string) => Promise<void> | void\n beforeDownloadPublish?: (path: string) => Promise<void> | void\n beforeFinalRename?: (path: string) => Promise<void> | void\n beforeLocalDeleteOpenParent?: (path: string) => Promise<void> | void\n beforeLocalDeleteUnlink?: (path: string) => Promise<void> | void\n beforeStagingMarkerWrite?: (path: string) => Promise<void> | void\n disableProcFdAnchoring?: boolean\n platform?: string\n statForDeviceCheck?: DeviceStatFn\n} = {}\n\ninterface ScannedLocalFileHandle {\n stat(): Promise<{\n isFile(): boolean\n readonly dev: number\n readonly ino: number\n readonly mtimeMs: number\n readonly ctimeMs: number\n readonly size: number\n }>\n readFile(): Promise<Uint8Array>\n close(): Promise<void>\n}\n\n/**\n * Verifies that a previously scanned local file still points at the same regular file.\n *\n * @param path - Scanned local path and file identity.\n *\n * @internal\n */\nexport async function validateScannedLocalFile(path: LocalSyncPath): Promise<void> {\n const handle = await openValidatedScannedLocalFile(path)\n await handle.close()\n}\n\n/**\n * Reads a previously scanned local file while rejecting symlink swaps and metadata drift.\n *\n * @param path - Scanned local path and file identity.\n *\n * @returns The file bytes.\n *\n * @internal\n */\nexport async function readScannedLocalFile(path: LocalSyncPath): Promise<Uint8Array> {\n const handle = await openValidatedScannedLocalFile(path)\n try {\n const data = await handle.readFile()\n if (data.byteLength !== path.size) {\n throw new Error('local file changed before upload: size changed while reading')\n }\n return data\n } finally {\n await handle.close()\n }\n}\n\nasync function openValidatedScannedLocalFile(path: LocalSyncPath): Promise<ScannedLocalFileHandle> {\n const { constants } = await import('node:fs')\n const { open } = await import('node:fs/promises')\n const flags =\n constants.O_RDONLY |\n noFollowFlag(constants) |\n ((constants as { O_NONBLOCK?: number }).O_NONBLOCK ?? 0)\n const handle = await open(path.absolutePath, flags).catch((err: unknown) => {\n if (hasErrorCode(err, 'ELOOP')) {\n throw new Error('local file changed before upload: not a regular file')\n }\n throw new Error(\n `local file changed before upload: could not open scanned file: ${sanitizeErrorReason(err)}`,\n )\n })\n try {\n const stats = await handle.stat()\n assertSameScannedRegularFile(stats, path, 'upload', {\n platform: localFileIoTestHooks.platform,\n })\n return handle\n } catch (err) {\n await handle.close().catch(() => {})\n throw err\n }\n}\n\n/**\n * Writes bytes under a local sync root after path containment checks.\n *\n * @param root - Local sync root.\n * @param relPath - Destination path relative to the root.\n * @param data - Bytes to write.\n *\n * @internal\n */\nexport async function writeLocalFileInsideRoot(\n root: string,\n relPath: string,\n data: Uint8Array,\n): Promise<void> {\n await writeLocalStreamInsideRoot(\n root,\n relPath,\n new ReadableStream<Uint8Array>({\n start(controller) {\n controller.enqueue(data)\n controller.close()\n },\n }),\n {\n expectedBytes: data.byteLength,\n idleTimeoutMillis: DEFAULT_SHA1_IDLE_TIMEOUT_MILLIS,\n },\n )\n}\n\n/**\n * Streams a B2 download under a local sync root with path, timeout, and size checks.\n *\n * @param root - Local sync root.\n * @param relPath - Destination path relative to the root.\n * @param body - Download body stream.\n * @param options - Expected byte count, idle timeout, and optional abort signal.\n *\n * @internal\n */\nexport async function writeLocalStreamInsideRoot(\n root: string,\n relPath: string,\n body: ReadableStream<Uint8Array>,\n options: {\n readonly expectedBytes: number\n readonly expectedDestination?: LocalSyncPath | null\n readonly idleTimeoutMillis: number\n readonly signal?: AbortSignal\n },\n): Promise<void> {\n const { constants } = await import('node:fs')\n const { link, lstat, mkdir, open, realpath, rename, rm, stat } = await import('node:fs/promises')\n const path = await import('node:path')\n const { randomUUID } = await import('node:crypto')\n assertValidExpectedBytes(options.expectedBytes)\n const segments = safeRelativePathSegments(relPath)\n if (isDownloadStagingDirectorySegment(segments[0])) {\n throw new Error(\n `unsafe local destination path: ${DOWNLOAD_STAGING_DIRECTORY_NAME} is reserved for SDK download staging`,\n )\n }\n const rootRealPath = await realpath(root)\n\n let current = rootRealPath\n for (const segment of segments.slice(0, -1)) {\n current = path.join(current, segment)\n try {\n await mkdir(current)\n } catch (err) {\n if (!hasErrorCode(err, 'EEXIST')) throw err\n }\n const stats = await lstat(current)\n if (!stats.isDirectory()) {\n throw new Error('unsafe local destination path: parent is not a directory')\n }\n await localFileIoTestHooks.afterParentDirectoryValidated?.(current)\n }\n\n const destPath = path.join(rootRealPath, ...segments)\n assertPathInsideRoot(rootRealPath, destPath, path)\n\n const parentRealPath = await realpath(path.dirname(destPath))\n const finalPath = path.join(parentRealPath, path.basename(destPath))\n assertPathInsideRoot(rootRealPath, finalPath, path)\n try {\n const targetStats = await lstat(finalPath)\n if (targetStats.isSymbolicLink()) {\n throw new Error('unsafe local destination path: target is a symbolic link')\n }\n if (targetStats.isFile() && targetStats.nlink > 1) {\n throw new Error('unsafe local destination path: target has multiple hard links')\n }\n } catch (err) {\n if (!hasErrorCode(err, 'ENOENT')) throw err\n }\n const statForDeviceCheck = localFileIoTestHooks.statForDeviceCheck ?? stat\n await assertDownloadPathSameDevice(\n rootRealPath,\n parentRealPath,\n statForDeviceCheck,\n 'unsafe local destination path: cannot publish download across filesystems',\n )\n\n let parentHandle: Awaited<ReturnType<typeof open>> | undefined\n let anchoredParentPath: string | undefined\n const platform = (globalThis as { process?: { platform?: string } }).process?.platform\n /* v8 ignore start -- Linux-only fd-relative path support is covered by Linux CI */\n if (\n platform === 'linux' &&\n constants.O_DIRECTORY !== undefined &&\n localFileIoTestHooks.disableProcFdAnchoring !== true\n ) {\n try {\n parentHandle = await open(\n parentRealPath,\n constants.O_RDONLY | constants.O_DIRECTORY | noFollowFlag(constants),\n )\n anchoredParentPath = `/proc/self/fd/${parentHandle.fd}`\n } catch (err) {\n if (hasErrorCode(err, 'ELOOP') || hasErrorCode(err, 'ENOTDIR')) {\n throw new Error('unsafe local destination path: parent is not a directory')\n }\n throw err\n }\n }\n /* v8 ignore stop */\n const finalName = path.basename(destPath)\n const finalWritePath = path.join(anchoredParentPath ?? parentRealPath, finalName)\n let publishMode: number\n try {\n publishMode = await replacementFileMode(finalPath)\n } catch (err) {\n /* v8 ignore next -- best-effort close during setup failure */\n await parentHandle?.close().catch(() => {})\n throw err\n }\n let stagingDirectory: string\n try {\n stagingDirectory = await createDownloadStagingDirectory(\n rootRealPath,\n path,\n randomUUID,\n statForDeviceCheck,\n localFileIoTestHooks.beforeStagingMarkerWrite,\n )\n } catch (err) {\n /* v8 ignore next -- best-effort close during setup failure */\n await parentHandle?.close().catch(() => {})\n throw err\n }\n const tmpPath = path.join(stagingDirectory, `.b2sdk-${randomUUID()}.partial`)\n let handle: Awaited<ReturnType<typeof open>> | undefined\n try {\n handle = await open(\n tmpPath,\n constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | noFollowFlag(constants),\n PRIVATE_DOWNLOAD_FILE_MODE,\n )\n /* v8 ignore next -- best-effort chmod */\n await handle.chmod(PRIVATE_DOWNLOAD_FILE_MODE).catch(() => {})\n await localFileIoTestHooks.afterTempFileCreated?.(tmpPath, stagingDirectory)\n /* v8 ignore start -- defensive cleanup before the main write try/finally exists */\n } catch (err) {\n await parentHandle?.close().catch(() => {})\n await rm(stagingDirectory, { recursive: true, force: true }).catch(() => {})\n throw err\n }\n /* v8 ignore stop */\n try {\n const tmpRealPath = await realpath(tmpPath)\n assertPathInsideRoot(stagingDirectory, tmpRealPath, path)\n } catch (err) {\n /* v8 ignore next -- best-effort cleanup */\n await handle?.close().catch(() => {})\n /* v8 ignore next -- best-effort cleanup */\n await rm(tmpPath, { force: true }).catch(() => {})\n /* v8 ignore next -- best-effort cleanup */\n await rm(stagingDirectory, { recursive: true, force: true }).catch(() => {})\n /* v8 ignore next -- best-effort cleanup */\n await parentHandle?.close().catch(() => {})\n throw err\n }\n const writeHandle = handle\n const reader = body.getReader()\n let completed = false\n try {\n let bytesWritten = 0\n while (true) {\n const { done, value } = await readStreamChunkWithTimeout(\n reader,\n options.idleTimeoutMillis,\n `download read stalled for ${options.idleTimeoutMillis} ms`,\n options.signal,\n )\n if (done) break\n if (bytesWritten + value.byteLength > options.expectedBytes) {\n throw new Error(`download read exceeded ${options.expectedBytes} byte limit`)\n }\n await writeAll(writeHandle, value, bytesWritten)\n bytesWritten += value.byteLength\n }\n if (bytesWritten !== options.expectedBytes) {\n throw new Error(\n `download read ended after ${bytesWritten} bytes, expected ${options.expectedBytes}`,\n )\n }\n if (publishMode !== PRIVATE_DOWNLOAD_FILE_MODE) {\n /* v8 ignore next -- best-effort mode preservation */\n await writeHandle.chmod(publishMode).catch(() => {})\n }\n await writeHandle.close()\n handle = undefined\n\n const [parentRealPathBeforeRename, parentStatsBeforeRename] = await Promise.all([\n realpath(path.dirname(destPath)),\n stat(path.dirname(destPath)),\n ])\n const finalPathBeforeRename = path.join(parentRealPathBeforeRename, path.basename(destPath))\n assertPathInsideRoot(rootRealPath, finalPathBeforeRename, path)\n await localFileIoTestHooks.beforeFinalRename?.(parentRealPathBeforeRename)\n let publishPath = finalWritePath\n if (anchoredParentPath === undefined) {\n const [parentRealPathAfterHook, parentStatsAfterHook] = await Promise.all([\n realpath(path.dirname(destPath)),\n stat(path.dirname(destPath)),\n ])\n if (\n parentRealPathAfterHook !== parentRealPathBeforeRename ||\n !sameParentIdentity(parentStatsAfterHook, parentStatsBeforeRename)\n ) {\n throw new Error('unsafe local destination path: parent changed before final publish')\n }\n publishPath = path.join(parentRealPathAfterHook, path.basename(destPath))\n assertPathInsideRoot(rootRealPath, publishPath, path)\n }\n await publishDownload(\n lstat,\n link,\n path,\n randomUUID,\n rename,\n rm,\n tmpPath,\n publishPath,\n options.expectedDestination,\n )\n completed = true\n } catch (err) {\n /* v8 ignore next -- best-effort cleanup */\n void reader.cancel(err).catch(() => {})\n throw err\n } finally {\n reader.releaseLock()\n if (!completed) {\n /* v8 ignore next -- best-effort cleanup */\n await handle?.close().catch(() => {})\n /* v8 ignore next -- best-effort cleanup */\n await rm(tmpPath, { force: true }).catch(() => {})\n }\n /* v8 ignore next -- best-effort cleanup */\n await rm(stagingDirectory, { recursive: true, force: true }).catch(() => {})\n /* v8 ignore next -- best-effort cleanup */\n await parentHandle?.close().catch(() => {})\n }\n}\n\nfunction assertValidExpectedBytes(expectedBytes: number): void {\n if (!Number.isSafeInteger(expectedBytes) || expectedBytes < 0) {\n throw new Error('download expectedBytes must be a non-negative safe integer')\n }\n}\n\nasync function assertExpectedDownloadDestination(\n lstat: typeof import('node:fs/promises')['lstat'],\n finalPath: string,\n expectedDestination: LocalSyncPath | null | undefined,\n): Promise<void> {\n if (expectedDestination === undefined) return\n\n try {\n const stats = await lstat(finalPath)\n if (expectedDestination === null) {\n throw new Error('local destination changed before download: file was created')\n }\n assertSameScannedRegularFile(\n stats,\n { ...expectedDestination, absolutePath: finalPath },\n 'download',\n { platform: localFileIoTestHooks.platform },\n )\n } catch (err) {\n if (hasErrorCode(err, 'ENOENT')) {\n if (expectedDestination === null) return\n throw new Error('local file changed before download: file missing')\n }\n throw err\n }\n}\n\nasync function publishDownload(\n lstat: typeof import('node:fs/promises')['lstat'],\n link: typeof import('node:fs/promises')['link'],\n path: typeof import('node:path'),\n randomUUID: () => string,\n rename: typeof import('node:fs/promises')['rename'],\n rm: typeof import('node:fs/promises')['rm'],\n tmpPath: string,\n publishPath: string,\n expectedDestination: LocalSyncPath | null | undefined,\n): Promise<void> {\n await assertExpectedDownloadDestination(lstat, publishPath, expectedDestination)\n await localFileIoTestHooks.beforeDownloadPublish?.(publishPath)\n\n if (expectedDestination === undefined) {\n await rename(tmpPath, publishPath)\n return\n }\n\n if (expectedDestination === null) {\n await linkDownloadNoOverwrite(\n link,\n tmpPath,\n publishPath,\n 'local destination changed before download: file was created',\n )\n /* v8 ignore next -- staging cleanup is best-effort after a guarded publish succeeds. */\n await rm(tmpPath, { force: true }).catch(() => {})\n return\n }\n\n const backupPath = path.join(\n path.dirname(publishPath),\n makeReservedSyncTempFileName(path.basename(publishPath), randomUUID()),\n )\n let backupExists = false\n let removeBackup = false\n\n try {\n try {\n await rename(publishPath, backupPath)\n backupExists = true\n await localFileIoTestHooks.afterDownloadBackupRename?.(backupPath)\n } catch (err) {\n if (hasErrorCode(err, 'ENOENT')) {\n throw new Error('local file changed before download: file missing')\n }\n throw err\n }\n\n const backupStats = await lstat(backupPath)\n assertSameScannedRegularFile(\n backupStats,\n { ...expectedDestination, absolutePath: backupPath },\n 'download',\n { compareChangeTime: false, platform: localFileIoTestHooks.platform },\n )\n await linkDownloadNoOverwrite(\n link,\n tmpPath,\n publishPath,\n 'local destination changed before download: file was created',\n )\n removeBackup = true\n /* v8 ignore next -- staging cleanup is best-effort after a guarded publish succeeds. */\n await rm(tmpPath, { force: true }).catch(() => {})\n } catch (err) {\n if (backupExists && !removeBackup) {\n await restoreBackupWithoutOverwrite(link, rm, backupPath, publishPath).catch(() => {})\n }\n throw err\n } finally {\n if (removeBackup) {\n /* v8 ignore next -- old destination cleanup is best-effort after publish succeeds. */\n await rm(backupPath, { force: true }).catch(() => {})\n }\n }\n}\n\nasync function linkDownloadNoOverwrite(\n link: typeof import('node:fs/promises')['link'],\n sourcePath: string,\n destPath: string,\n message: string,\n): Promise<void> {\n try {\n await link(sourcePath, destPath)\n } catch (err) {\n if (hasErrorCode(err, 'EEXIST')) throw new Error(message)\n throw err\n }\n}\n\nasync function restoreBackupWithoutOverwrite(\n link: typeof import('node:fs/promises')['link'],\n rm: typeof import('node:fs/promises')['rm'],\n backupPath: string,\n publishPath: string,\n): Promise<void> {\n try {\n await link(backupPath, publishPath)\n await rm(backupPath, { force: true })\n } catch (err) {\n if (hasErrorCode(err, 'EEXIST')) return\n throw err\n }\n}\n\nconst PRIVATE_DOWNLOAD_FILE_MODE = 0o600\nasync function replacementFileMode(filePath: string): Promise<number> {\n const { lstat } = await import('node:fs/promises')\n try {\n const stats = await lstat(filePath)\n return stats.isFile() ? stats.mode & 0o777 : PRIVATE_DOWNLOAD_FILE_MODE\n } catch (err) {\n if (hasErrorCode(err, 'ENOENT')) return PRIVATE_DOWNLOAD_FILE_MODE\n throw err\n }\n}\n\n/**\n * Deletes a scanned local file under a sync root without re-resolving attacker-controlled parents.\n *\n * @param root - Local sync root.\n * @param scannedPath - Previously scanned local file metadata.\n *\n * @internal\n */\nexport async function deleteLocalFileInsideRoot(\n root: string,\n scannedPath: LocalSyncPath,\n): Promise<void> {\n if (root === '') {\n throw new Error('Local sync root required for filesystem mutation')\n }\n\n const { constants } = await import('node:fs')\n const { lstat, open, realpath, stat, unlink } = await import('node:fs/promises')\n const path = await import('node:path')\n const segments = safeRelativePathSegments(scannedPath.relativePath)\n const safeRoot = path.resolve(root)\n const rootStats = await lstat(safeRoot)\n if (rootStats.isSymbolicLink()) {\n throw new Error(`Refusing to access sync root through symlink: ${scannedPath.relativePath}`)\n }\n if (!rootStats.isDirectory()) {\n throw new Error(`Local sync root is not a directory: ${scannedPath.relativePath}`)\n }\n const rootRealPath = await realpath(safeRoot)\n const expectedPath = path.join(rootRealPath, ...segments)\n assertPathInsideRoot(rootRealPath, expectedPath, path)\n\n const scannerPath = path.resolve(scannedPath.absolutePath)\n if (scannerPath !== expectedPath) {\n throw new Error(`Refusing to delete outside sync root: ${scannedPath.relativePath}`)\n }\n\n const [parentRealPath, parentStats] = await Promise.all([\n realpath(path.dirname(expectedPath)),\n stat(path.dirname(expectedPath)),\n ])\n const finalPath = path.join(parentRealPath, path.basename(expectedPath))\n assertPathInsideRoot(rootRealPath, finalPath, path)\n\n const platform = (globalThis as { process?: { platform?: string } }).process?.platform\n let parentHandle: Awaited<ReturnType<typeof open>> | undefined\n let anchoredParentPath: string | undefined\n /* v8 ignore start -- Linux-only fd-relative path support is covered by Linux CI */\n if (\n platform === 'linux' &&\n constants.O_DIRECTORY !== undefined &&\n localFileIoTestHooks.disableProcFdAnchoring !== true\n ) {\n try {\n await localFileIoTestHooks.beforeLocalDeleteOpenParent?.(parentRealPath)\n parentHandle = await open(\n parentRealPath,\n constants.O_RDONLY | constants.O_DIRECTORY | noFollowFlag(constants),\n )\n anchoredParentPath = `/proc/self/fd/${parentHandle.fd}`\n } catch (err) {\n if (hasErrorCode(err, 'ELOOP') || hasErrorCode(err, 'ENOTDIR')) {\n throw new Error('unsafe local delete path: parent is not a directory')\n }\n throw err\n }\n }\n /* v8 ignore stop */\n\n try {\n const unlinkPath =\n anchoredParentPath === undefined\n ? finalPath\n : path.join(anchoredParentPath, path.basename(expectedPath))\n const stats = await lstat(unlinkPath)\n assertSameScannedRegularFile(stats, { ...scannedPath, absolutePath: unlinkPath }, 'delete', {\n platform: localFileIoTestHooks.platform,\n })\n await localFileIoTestHooks.beforeLocalDeleteUnlink?.(parentRealPath)\n if (\n anchoredParentPath === undefined &&\n localFileIoTestHooks.disableProcFdAnchoring === true &&\n parentRealPath !== rootRealPath\n ) {\n throw new Error('unsafe local delete path: stable parent handle unavailable for unlink')\n }\n\n if (anchoredParentPath === undefined) {\n const [parentRealPathBeforeUnlink, parentStatsBeforeUnlink] = await Promise.all([\n realpath(path.dirname(expectedPath)),\n stat(path.dirname(expectedPath)),\n ])\n if (\n parentRealPathBeforeUnlink !== parentRealPath ||\n !sameParentIdentity(parentStatsBeforeUnlink, parentStats)\n ) {\n throw new Error('unsafe local delete path: parent changed before unlink')\n }\n }\n const finalStats = await lstat(unlinkPath)\n assertSameScannedRegularFile(\n finalStats,\n { ...scannedPath, absolutePath: unlinkPath },\n 'delete',\n { platform: localFileIoTestHooks.platform },\n )\n await unlink(unlinkPath)\n } finally {\n /* v8 ignore next -- best-effort cleanup */\n await parentHandle?.close().catch(() => {})\n }\n}\n\nfunction sameParentIdentity(\n current: { readonly dev: number | bigint; readonly ino: number | bigint },\n expected: { readonly dev: number | bigint; readonly ino: number | bigint },\n): boolean {\n return current.dev === expected.dev && current.ino === expected.ino\n}\n\nasync function writeAll(\n handle: {\n write(\n buffer: Uint8Array,\n offset: number,\n length: number,\n position: number,\n ): Promise<{ readonly bytesWritten: number }>\n },\n data: Uint8Array,\n position: number,\n): Promise<void> {\n let offset = 0\n while (offset < data.byteLength) {\n const { bytesWritten } = await handle.write(\n data,\n offset,\n data.byteLength - offset,\n position + offset,\n )\n /* v8 ignore next -- defensive: FileHandle.write should progress for non-empty chunks. */\n if (bytesWritten <= 0) throw new Error('download write made no progress')\n offset += bytesWritten\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"local-file-io.js","names":[],"sources":["../../src/sync/local-file-io.ts"],"sourcesContent":["import { sanitizeErrorReason } from '../util/error-reason.ts'\nimport { readStreamChunkWithTimeout } from './b2-sha1-reader.ts'\nimport {\n assertDownloadPathSameDevice,\n createDownloadStagingDirectory,\n DOWNLOAD_STAGING_DIRECTORY_NAME,\n isDownloadStagingDirectorySegment,\n} from './download-staging.ts'\nimport { assertSameScannedRegularFile } from './local-file-identity.ts'\nimport {\n assertPathInsideRoot,\n hasErrorCode,\n makeReservedSyncTempFileName,\n noFollowFlag,\n safeRelativePathSegments,\n} from './path-safety.ts'\nimport { DEFAULT_SHA1_IDLE_TIMEOUT_MILLIS } from './sha1-options.ts'\nimport type { LocalSyncPath } from './types.ts'\n\ntype DeviceStat = { readonly dev: number | bigint }\ntype DeviceStatFn = (path: string) => Promise<DeviceStat>\n\nexport { DOWNLOAD_STAGING_DIRECTORY_NAME }\n\n/** @internal */\nexport const localFileIoTestHooks: {\n afterParentDirectoryValidated?: (path: string) => Promise<void> | void\n afterDownloadBackupRename?: (path: string) => Promise<void> | void\n afterTempFileCreated?: (path: string, stagingDirectory: string) => Promise<void> | void\n beforeDownloadPublish?: (path: string) => Promise<void> | void\n beforeFinalRename?: (path: string) => Promise<void> | void\n beforeLocalDeleteOpenParent?: (path: string) => Promise<void> | void\n beforeLocalDeleteUnlink?: (path: string) => Promise<void> | void\n afterManagedStagingMarkerWrite?: (path: string) => Promise<void> | void\n beforeStagingMarkerWrite?: (path: string) => Promise<void> | void\n disableProcFdAnchoring?: boolean\n downloadStagingActivityEntryLimit?: number\n platform?: string\n statForDeviceCheck?: DeviceStatFn\n} = {}\n\ninterface ScannedLocalFileHandle {\n stat(): Promise<{\n isFile(): boolean\n readonly dev: number\n readonly ino: number\n readonly mtimeMs: number\n readonly ctimeMs: number\n readonly size: number\n }>\n readFile(): Promise<Uint8Array>\n close(): Promise<void>\n}\n\n/**\n * Verifies that a previously scanned local file still points at the same regular file.\n *\n * @param path - Scanned local path and file identity.\n *\n * @internal\n */\nexport async function validateScannedLocalFile(path: LocalSyncPath): Promise<void> {\n const handle = await openValidatedScannedLocalFile(path)\n await handle.close()\n}\n\n/**\n * Reads a previously scanned local file while rejecting symlink swaps and metadata drift.\n *\n * @param path - Scanned local path and file identity.\n *\n * @returns The file bytes.\n *\n * @internal\n */\nexport async function readScannedLocalFile(path: LocalSyncPath): Promise<Uint8Array> {\n const handle = await openValidatedScannedLocalFile(path)\n try {\n const data = await handle.readFile()\n if (data.byteLength !== path.size) {\n throw new Error('local file changed before upload: size changed while reading')\n }\n return data\n } finally {\n await handle.close()\n }\n}\n\nasync function openValidatedScannedLocalFile(path: LocalSyncPath): Promise<ScannedLocalFileHandle> {\n const { constants } = await import('node:fs')\n const { open } = await import('node:fs/promises')\n const flags =\n constants.O_RDONLY |\n noFollowFlag(constants) |\n ((constants as { O_NONBLOCK?: number }).O_NONBLOCK ?? 0)\n const handle = await open(path.absolutePath, flags).catch((err: unknown) => {\n if (hasErrorCode(err, 'ELOOP')) {\n throw new Error('local file changed before upload: not a regular file')\n }\n throw new Error(\n `local file changed before upload: could not open scanned file: ${sanitizeErrorReason(err)}`,\n )\n })\n try {\n const stats = await handle.stat()\n assertSameScannedRegularFile(stats, path, 'upload', {\n platform: localFileIoTestHooks.platform,\n })\n return handle\n } catch (err) {\n await handle.close().catch(() => {})\n throw err\n }\n}\n\n/**\n * Writes bytes under a local sync root after path containment checks.\n *\n * @param root - Local sync root.\n * @param relPath - Destination path relative to the root.\n * @param data - Bytes to write.\n *\n * @internal\n */\nexport async function writeLocalFileInsideRoot(\n root: string,\n relPath: string,\n data: Uint8Array,\n): Promise<void> {\n await writeLocalStreamInsideRoot(\n root,\n relPath,\n new ReadableStream<Uint8Array>({\n start(controller) {\n controller.enqueue(data)\n controller.close()\n },\n }),\n {\n expectedBytes: data.byteLength,\n idleTimeoutMillis: DEFAULT_SHA1_IDLE_TIMEOUT_MILLIS,\n },\n )\n}\n\n/**\n * Streams a B2 download under a local sync root with path, timeout, and size checks.\n *\n * @param root - Local sync root.\n * @param relPath - Destination path relative to the root.\n * @param body - Download body stream.\n * @param options - Expected byte count, idle timeout, and optional abort signal.\n *\n * @internal\n */\nexport async function writeLocalStreamInsideRoot(\n root: string,\n relPath: string,\n body: ReadableStream<Uint8Array>,\n options: {\n readonly expectedBytes: number\n readonly expectedDestination?: LocalSyncPath | null\n readonly idleTimeoutMillis: number\n readonly onDownloadStagingRoot?: (managedDirectory: string) => void\n readonly signal?: AbortSignal\n },\n): Promise<void> {\n const { constants } = await import('node:fs')\n const { link, lstat, mkdir, open, realpath, rename, rm, stat } = await import('node:fs/promises')\n const path = await import('node:path')\n const { randomUUID } = await import('node:crypto')\n assertValidExpectedBytes(options.expectedBytes)\n const segments = safeRelativePathSegments(relPath)\n if (isDownloadStagingDirectorySegment(segments[0])) {\n throw new Error(\n `unsafe local destination path: ${DOWNLOAD_STAGING_DIRECTORY_NAME} is reserved for SDK download staging`,\n )\n }\n const rootRealPath = await realpath(root)\n\n let current = rootRealPath\n for (const segment of segments.slice(0, -1)) {\n current = path.join(current, segment)\n try {\n await mkdir(current)\n } catch (err) {\n if (!hasErrorCode(err, 'EEXIST')) throw err\n }\n const stats = await lstat(current)\n if (!stats.isDirectory()) {\n throw new Error('unsafe local destination path: parent is not a directory')\n }\n await localFileIoTestHooks.afterParentDirectoryValidated?.(current)\n }\n\n const destPath = path.join(rootRealPath, ...segments)\n assertPathInsideRoot(rootRealPath, destPath, path)\n\n const parentRealPath = await realpath(path.dirname(destPath))\n const finalPath = path.join(parentRealPath, path.basename(destPath))\n assertPathInsideRoot(rootRealPath, finalPath, path)\n try {\n const targetStats = await lstat(finalPath)\n if (targetStats.isSymbolicLink()) {\n throw new Error('unsafe local destination path: target is a symbolic link')\n }\n if (targetStats.isFile() && targetStats.nlink > 1) {\n throw new Error('unsafe local destination path: target has multiple hard links')\n }\n } catch (err) {\n if (!hasErrorCode(err, 'ENOENT')) throw err\n }\n const statForDeviceCheck = localFileIoTestHooks.statForDeviceCheck ?? stat\n await assertDownloadPathSameDevice(\n rootRealPath,\n parentRealPath,\n statForDeviceCheck,\n 'unsafe local destination path: cannot publish download across filesystems',\n )\n\n let parentHandle: Awaited<ReturnType<typeof open>> | undefined\n let anchoredParentPath: string | undefined\n const platform = (globalThis as { process?: { platform?: string } }).process?.platform\n /* v8 ignore start -- Linux-only fd-relative path support is covered by Linux CI */\n if (\n platform === 'linux' &&\n constants.O_DIRECTORY !== undefined &&\n localFileIoTestHooks.disableProcFdAnchoring !== true\n ) {\n try {\n parentHandle = await open(\n parentRealPath,\n constants.O_RDONLY | constants.O_DIRECTORY | noFollowFlag(constants),\n )\n anchoredParentPath = `/proc/self/fd/${parentHandle.fd}`\n } catch (err) {\n if (hasErrorCode(err, 'ELOOP') || hasErrorCode(err, 'ENOTDIR')) {\n throw new Error('unsafe local destination path: parent is not a directory')\n }\n throw err\n }\n }\n /* v8 ignore stop */\n const finalName = path.basename(destPath)\n const finalWritePath = path.join(anchoredParentPath ?? parentRealPath, finalName)\n let publishMode: number\n try {\n publishMode = await replacementFileMode(finalPath)\n } catch (err) {\n /* v8 ignore next -- best-effort close during setup failure */\n await parentHandle?.close().catch(() => {})\n throw err\n }\n let stagingDirectory: string\n let managedStagingDirectory: string\n try {\n const staging = await createDownloadStagingDirectory(\n rootRealPath,\n path,\n randomUUID,\n statForDeviceCheck,\n localFileIoTestHooks.beforeStagingMarkerWrite,\n localFileIoTestHooks.downloadStagingActivityEntryLimit,\n localFileIoTestHooks.afterManagedStagingMarkerWrite,\n )\n stagingDirectory = staging.stagingDirectory\n managedStagingDirectory = staging.managedDirectory\n } catch (err) {\n /* v8 ignore next -- best-effort close during setup failure */\n await parentHandle?.close().catch(() => {})\n throw err\n }\n options.onDownloadStagingRoot?.(managedStagingDirectory)\n const tmpPath = path.join(stagingDirectory, `.b2sdk-${randomUUID()}.partial`)\n let handle: Awaited<ReturnType<typeof open>> | undefined\n try {\n handle = await open(\n tmpPath,\n constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | noFollowFlag(constants),\n PRIVATE_DOWNLOAD_FILE_MODE,\n )\n /* v8 ignore next -- best-effort chmod */\n await handle.chmod(PRIVATE_DOWNLOAD_FILE_MODE).catch(() => {})\n await localFileIoTestHooks.afterTempFileCreated?.(tmpPath, stagingDirectory)\n /* v8 ignore start -- defensive cleanup before the main write try/finally exists */\n } catch (err) {\n await parentHandle?.close().catch(() => {})\n await rm(stagingDirectory, { recursive: true, force: true }).catch(() => {})\n throw err\n }\n /* v8 ignore stop */\n try {\n const tmpRealPath = await realpath(tmpPath)\n assertPathInsideRoot(stagingDirectory, tmpRealPath, path)\n } catch (err) {\n /* v8 ignore next -- best-effort cleanup */\n await handle?.close().catch(() => {})\n /* v8 ignore next -- best-effort cleanup */\n await rm(tmpPath, { force: true }).catch(() => {})\n /* v8 ignore next -- best-effort cleanup */\n await rm(stagingDirectory, { recursive: true, force: true }).catch(() => {})\n /* v8 ignore next -- best-effort cleanup */\n await parentHandle?.close().catch(() => {})\n throw err\n }\n const writeHandle = handle\n const reader = body.getReader()\n let completed = false\n try {\n let bytesWritten = 0\n while (true) {\n const { done, value } = await readStreamChunkWithTimeout(\n reader,\n options.idleTimeoutMillis,\n `download read stalled for ${options.idleTimeoutMillis} ms`,\n options.signal,\n )\n if (done) break\n if (bytesWritten + value.byteLength > options.expectedBytes) {\n throw new Error(`download read exceeded ${options.expectedBytes} byte limit`)\n }\n await writeAll(writeHandle, value, bytesWritten)\n bytesWritten += value.byteLength\n }\n if (bytesWritten !== options.expectedBytes) {\n throw new Error(\n `download read ended after ${bytesWritten} bytes, expected ${options.expectedBytes}`,\n )\n }\n if (publishMode !== PRIVATE_DOWNLOAD_FILE_MODE) {\n /* v8 ignore next -- best-effort mode preservation */\n await writeHandle.chmod(publishMode).catch(() => {})\n }\n await writeHandle.close()\n handle = undefined\n\n const [parentRealPathBeforeRename, parentStatsBeforeRename] = await Promise.all([\n realpath(path.dirname(destPath)),\n stat(path.dirname(destPath)),\n ])\n const finalPathBeforeRename = path.join(parentRealPathBeforeRename, path.basename(destPath))\n assertPathInsideRoot(rootRealPath, finalPathBeforeRename, path)\n await localFileIoTestHooks.beforeFinalRename?.(parentRealPathBeforeRename)\n let publishPath = finalWritePath\n if (anchoredParentPath === undefined) {\n const [parentRealPathAfterHook, parentStatsAfterHook] = await Promise.all([\n realpath(path.dirname(destPath)),\n stat(path.dirname(destPath)),\n ])\n if (\n parentRealPathAfterHook !== parentRealPathBeforeRename ||\n !sameParentIdentity(parentStatsAfterHook, parentStatsBeforeRename)\n ) {\n throw new Error('unsafe local destination path: parent changed before final publish')\n }\n publishPath = path.join(parentRealPathAfterHook, path.basename(destPath))\n assertPathInsideRoot(rootRealPath, publishPath, path)\n }\n await publishDownload(\n lstat,\n link,\n path,\n randomUUID,\n rename,\n rm,\n tmpPath,\n publishPath,\n options.expectedDestination,\n )\n completed = true\n } catch (err) {\n /* v8 ignore next -- best-effort cleanup */\n void reader.cancel(err).catch(() => {})\n throw err\n } finally {\n reader.releaseLock()\n if (!completed) {\n /* v8 ignore next -- best-effort cleanup */\n await handle?.close().catch(() => {})\n /* v8 ignore next -- best-effort cleanup */\n await rm(tmpPath, { force: true }).catch(() => {})\n }\n /* v8 ignore next -- best-effort cleanup */\n await rm(stagingDirectory, { recursive: true, force: true }).catch(() => {})\n /* v8 ignore next -- best-effort cleanup */\n await parentHandle?.close().catch(() => {})\n }\n}\n\nfunction assertValidExpectedBytes(expectedBytes: number): void {\n if (!Number.isSafeInteger(expectedBytes) || expectedBytes < 0) {\n throw new Error('download expectedBytes must be a non-negative safe integer')\n }\n}\n\nasync function assertExpectedDownloadDestination(\n lstat: typeof import('node:fs/promises')['lstat'],\n finalPath: string,\n expectedDestination: LocalSyncPath | null | undefined,\n): Promise<void> {\n if (expectedDestination === undefined) return\n\n try {\n const stats = await lstat(finalPath)\n if (expectedDestination === null) {\n throw new Error('local destination changed before download: file was created')\n }\n assertSameScannedRegularFile(\n stats,\n { ...expectedDestination, absolutePath: finalPath },\n 'download',\n { platform: localFileIoTestHooks.platform },\n )\n } catch (err) {\n if (hasErrorCode(err, 'ENOENT')) {\n if (expectedDestination === null) return\n throw new Error('local file changed before download: file missing')\n }\n throw err\n }\n}\n\nasync function publishDownload(\n lstat: typeof import('node:fs/promises')['lstat'],\n link: typeof import('node:fs/promises')['link'],\n path: typeof import('node:path'),\n randomUUID: () => string,\n rename: typeof import('node:fs/promises')['rename'],\n rm: typeof import('node:fs/promises')['rm'],\n tmpPath: string,\n publishPath: string,\n expectedDestination: LocalSyncPath | null | undefined,\n): Promise<void> {\n await assertExpectedDownloadDestination(lstat, publishPath, expectedDestination)\n await localFileIoTestHooks.beforeDownloadPublish?.(publishPath)\n\n if (expectedDestination === undefined) {\n await rename(tmpPath, publishPath)\n return\n }\n\n if (expectedDestination === null) {\n await linkDownloadNoOverwrite(\n link,\n tmpPath,\n publishPath,\n 'local destination changed before download: file was created',\n )\n /* v8 ignore next -- staging cleanup is best-effort after a guarded publish succeeds. */\n await rm(tmpPath, { force: true }).catch(() => {})\n return\n }\n\n const backupPath = path.join(\n path.dirname(publishPath),\n makeReservedSyncTempFileName(path.basename(publishPath), randomUUID()),\n )\n let backupExists = false\n let removeBackup = false\n\n try {\n try {\n await rename(publishPath, backupPath)\n backupExists = true\n await localFileIoTestHooks.afterDownloadBackupRename?.(backupPath)\n } catch (err) {\n if (hasErrorCode(err, 'ENOENT')) {\n throw new Error('local file changed before download: file missing')\n }\n throw err\n }\n\n const backupStats = await lstat(backupPath)\n assertSameScannedRegularFile(\n backupStats,\n { ...expectedDestination, absolutePath: backupPath },\n 'download',\n { compareChangeTime: false, platform: localFileIoTestHooks.platform },\n )\n await linkDownloadNoOverwrite(\n link,\n tmpPath,\n publishPath,\n 'local destination changed before download: file was created',\n )\n removeBackup = true\n /* v8 ignore next -- staging cleanup is best-effort after a guarded publish succeeds. */\n await rm(tmpPath, { force: true }).catch(() => {})\n } catch (err) {\n if (backupExists && !removeBackup) {\n await restoreBackupWithoutOverwrite(link, rm, backupPath, publishPath).catch(() => {})\n }\n throw err\n } finally {\n if (removeBackup) {\n /* v8 ignore next -- old destination cleanup is best-effort after publish succeeds. */\n await rm(backupPath, { force: true }).catch(() => {})\n }\n }\n}\n\nasync function linkDownloadNoOverwrite(\n link: typeof import('node:fs/promises')['link'],\n sourcePath: string,\n destPath: string,\n message: string,\n): Promise<void> {\n try {\n await link(sourcePath, destPath)\n } catch (err) {\n if (hasErrorCode(err, 'EEXIST')) throw new Error(message)\n throw err\n }\n}\n\nasync function restoreBackupWithoutOverwrite(\n link: typeof import('node:fs/promises')['link'],\n rm: typeof import('node:fs/promises')['rm'],\n backupPath: string,\n publishPath: string,\n): Promise<void> {\n try {\n await link(backupPath, publishPath)\n await rm(backupPath, { force: true })\n } catch (err) {\n if (hasErrorCode(err, 'EEXIST')) return\n throw err\n }\n}\n\nconst PRIVATE_DOWNLOAD_FILE_MODE = 0o600\nasync function replacementFileMode(filePath: string): Promise<number> {\n const { lstat } = await import('node:fs/promises')\n try {\n const stats = await lstat(filePath)\n return stats.isFile() ? stats.mode & 0o777 : PRIVATE_DOWNLOAD_FILE_MODE\n } catch (err) {\n if (hasErrorCode(err, 'ENOENT')) return PRIVATE_DOWNLOAD_FILE_MODE\n throw err\n }\n}\n\n/**\n * Deletes a scanned local file under a sync root without re-resolving attacker-controlled parents.\n *\n * @param root - Local sync root.\n * @param scannedPath - Previously scanned local file metadata.\n *\n * @internal\n */\nexport async function deleteLocalFileInsideRoot(\n root: string,\n scannedPath: LocalSyncPath,\n): Promise<void> {\n if (root === '') {\n throw new Error('Local sync root required for filesystem mutation')\n }\n\n const { constants } = await import('node:fs')\n const { lstat, open, realpath, stat, unlink } = await import('node:fs/promises')\n const path = await import('node:path')\n const segments = safeRelativePathSegments(scannedPath.relativePath)\n const safeRoot = path.resolve(root)\n const rootStats = await lstat(safeRoot)\n if (rootStats.isSymbolicLink()) {\n throw new Error(`Refusing to access sync root through symlink: ${scannedPath.relativePath}`)\n }\n if (!rootStats.isDirectory()) {\n throw new Error(`Local sync root is not a directory: ${scannedPath.relativePath}`)\n }\n const rootRealPath = await realpath(safeRoot)\n const expectedPath = path.join(rootRealPath, ...segments)\n assertPathInsideRoot(rootRealPath, expectedPath, path)\n\n const scannerPath = path.resolve(scannedPath.absolutePath)\n if (scannerPath !== expectedPath) {\n throw new Error(`Refusing to delete outside sync root: ${scannedPath.relativePath}`)\n }\n\n const [parentRealPath, parentStats] = await Promise.all([\n realpath(path.dirname(expectedPath)),\n stat(path.dirname(expectedPath)),\n ])\n const finalPath = path.join(parentRealPath, path.basename(expectedPath))\n assertPathInsideRoot(rootRealPath, finalPath, path)\n\n const platform = (globalThis as { process?: { platform?: string } }).process?.platform\n let parentHandle: Awaited<ReturnType<typeof open>> | undefined\n let anchoredParentPath: string | undefined\n /* v8 ignore start -- Linux-only fd-relative path support is covered by Linux CI */\n if (\n platform === 'linux' &&\n constants.O_DIRECTORY !== undefined &&\n localFileIoTestHooks.disableProcFdAnchoring !== true\n ) {\n try {\n await localFileIoTestHooks.beforeLocalDeleteOpenParent?.(parentRealPath)\n parentHandle = await open(\n parentRealPath,\n constants.O_RDONLY | constants.O_DIRECTORY | noFollowFlag(constants),\n )\n anchoredParentPath = `/proc/self/fd/${parentHandle.fd}`\n } catch (err) {\n if (hasErrorCode(err, 'ELOOP') || hasErrorCode(err, 'ENOTDIR')) {\n throw new Error('unsafe local delete path: parent is not a directory')\n }\n throw err\n }\n }\n /* v8 ignore stop */\n\n try {\n const unlinkPath =\n anchoredParentPath === undefined\n ? finalPath\n : path.join(anchoredParentPath, path.basename(expectedPath))\n const stats = await lstat(unlinkPath)\n assertSameScannedRegularFile(stats, { ...scannedPath, absolutePath: unlinkPath }, 'delete', {\n platform: localFileIoTestHooks.platform,\n })\n await localFileIoTestHooks.beforeLocalDeleteUnlink?.(parentRealPath)\n if (\n anchoredParentPath === undefined &&\n localFileIoTestHooks.disableProcFdAnchoring === true &&\n parentRealPath !== rootRealPath\n ) {\n throw new Error('unsafe local delete path: stable parent handle unavailable for unlink')\n }\n\n if (anchoredParentPath === undefined) {\n const [parentRealPathBeforeUnlink, parentStatsBeforeUnlink] = await Promise.all([\n realpath(path.dirname(expectedPath)),\n stat(path.dirname(expectedPath)),\n ])\n if (\n parentRealPathBeforeUnlink !== parentRealPath ||\n !sameParentIdentity(parentStatsBeforeUnlink, parentStats)\n ) {\n throw new Error('unsafe local delete path: parent changed before unlink')\n }\n }\n const finalStats = await lstat(unlinkPath)\n assertSameScannedRegularFile(\n finalStats,\n { ...scannedPath, absolutePath: unlinkPath },\n 'delete',\n { platform: localFileIoTestHooks.platform },\n )\n await unlink(unlinkPath)\n } finally {\n /* v8 ignore next -- best-effort cleanup */\n await parentHandle?.close().catch(() => {})\n }\n}\n\nfunction sameParentIdentity(\n current: { readonly dev: number | bigint; readonly ino: number | bigint },\n expected: { readonly dev: number | bigint; readonly ino: number | bigint },\n): boolean {\n return current.dev === expected.dev && current.ino === expected.ino\n}\n\nasync function writeAll(\n handle: {\n write(\n buffer: Uint8Array,\n offset: number,\n length: number,\n position: number,\n ): Promise<{ readonly bytesWritten: number }>\n },\n data: Uint8Array,\n position: number,\n): Promise<void> {\n let offset = 0\n while (offset < data.byteLength) {\n const { bytesWritten } = await handle.write(\n data,\n offset,\n data.byteLength - offset,\n position + offset,\n )\n /* v8 ignore next -- defensive: FileHandle.write should progress for non-empty chunks. */\n if (bytesWritten <= 0) throw new Error('download write made no progress')\n offset += bytesWritten\n }\n}\n"],"mappings":"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"}
|
package/dist/sync/local-sha1.cjs
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
const require_hash = require("../streams/hash.cjs");
|
|
2
|
-
const require_to_error = require("../util/to-error.cjs");
|
|
3
2
|
const require_error_reason = require("../util/error-reason.cjs");
|
|
4
3
|
const require_local_file_identity = require("./local-file-identity.cjs");
|
|
5
4
|
const require_sha1_options = require("./sha1-options.cjs");
|
|
@@ -15,16 +14,6 @@ function formatHashError(error) {
|
|
|
15
14
|
return require_error_reason.sanitizeErrorReason(error);
|
|
16
15
|
}
|
|
17
16
|
/**
|
|
18
|
-
* Returns whether an error represents an abort.
|
|
19
|
-
*
|
|
20
|
-
* @param err - Unknown thrown value.
|
|
21
|
-
*
|
|
22
|
-
* @returns True for AbortError values.
|
|
23
|
-
*/
|
|
24
|
-
function isAbortError(err) {
|
|
25
|
-
return require_to_error.toError(err).name === "AbortError";
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
17
|
* Reads a local file and computes its SHA-1 digest with non-regular-file rejection,
|
|
29
18
|
* scanned-size bounds, abort support, and an idle/no-progress timeout.
|
|
30
19
|
*
|
|
@@ -49,10 +38,13 @@ async function readLocalSha1File(path, signal, options = {}) {
|
|
|
49
38
|
}
|
|
50
39
|
try {
|
|
51
40
|
signal?.throwIfAborted();
|
|
52
|
-
|
|
41
|
+
const preOpenStat = await withTimeout(lstat(path.absolutePath), timeoutMillis, "sha1 file status");
|
|
42
|
+
require_local_file_identity.assertSameScannedRegularFile(preOpenStat, path, "sha1 comparison");
|
|
53
43
|
file = await openWithTimeout(open(path.absolutePath, flags), timeoutMillis);
|
|
54
|
-
|
|
55
|
-
require_local_file_identity.assertSameScannedRegularFile(
|
|
44
|
+
const postOpenStat = await withTimeout(lstat(path.absolutePath), timeoutMillis, "sha1 file status");
|
|
45
|
+
require_local_file_identity.assertSameScannedRegularFile(postOpenStat, path, "sha1 comparison");
|
|
46
|
+
const stat = await withTimeout(file.stat(), timeoutMillis, "sha1 file status");
|
|
47
|
+
require_local_file_identity.assertSameScannedRegularFile(stat, path, "sha1 comparison");
|
|
56
48
|
stream = file.createReadStream({
|
|
57
49
|
...path.size > 0 ? {
|
|
58
50
|
start: 0,
|
|
@@ -113,7 +105,6 @@ async function openWithTimeout(promise, timeoutMillis) {
|
|
|
113
105
|
/* v8 ignore stop */
|
|
114
106
|
//#endregion
|
|
115
107
|
exports.formatHashError = formatHashError;
|
|
116
|
-
exports.isAbortError = isAbortError;
|
|
117
108
|
exports.readLocalSha1File = readLocalSha1File;
|
|
118
109
|
|
|
119
110
|
//# sourceMappingURL=local-sha1.cjs.map
|