@backblaze-labs/b2-sdk 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +42 -1
- package/README.md +20 -2
- 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/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 +146 -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 +146 -0
- package/dist/backup/raw.js.map +1 -0
- package/dist/bucket.cjs +2 -1
- package/dist/bucket.cjs.map +1 -1
- package/dist/bucket.js +2 -1
- package/dist/bucket.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 +141 -6
- package/dist/download/single.cjs.map +1 -1
- package/dist/download/single.d.cts.map +1 -1
- package/dist/download/single.d.ts.map +1 -1
- package/dist/download/single.js +141 -6
- package/dist/download/single.js.map +1 -1
- package/dist/errors/index.cjs +173 -3
- package/dist/errors/index.cjs.map +1 -1
- package/dist/errors/index.d.cts +119 -0
- package/dist/errors/index.d.cts.map +1 -1
- package/dist/errors/index.d.ts +119 -0
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/index.js +161 -4
- package/dist/errors/index.js.map +1 -1
- 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 +61 -23
- package/dist/http/transport.cjs.map +1 -1
- package/dist/http/transport.d.cts +25 -3
- package/dist/http/transport.d.cts.map +1 -1
- package/dist/http/transport.d.ts +25 -3
- package/dist/http/transport.d.ts.map +1 -1
- package/dist/http/transport.js +59 -22
- package/dist/http/transport.js.map +1 -1
- package/dist/index.cjs +25 -2
- package/dist/index.d.cts +6 -4
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +6 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -6
- package/dist/object.cjs +1 -1
- package/dist/object.cjs.map +1 -1
- package/dist/object.d.cts +4 -2
- package/dist/object.d.cts.map +1 -1
- package/dist/object.d.ts +4 -2
- package/dist/object.d.ts.map +1 -1
- package/dist/object.js +1 -1
- 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 +175 -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 +175 -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 +255 -0
- package/dist/partner/client.cjs.map +1 -0
- package/dist/partner/client.d.cts +335 -0
- package/dist/partner/client.d.cts.map +1 -0
- package/dist/partner/client.d.ts +335 -0
- package/dist/partner/client.d.ts.map +1 -0
- package/dist/partner/client.js +255 -0
- package/dist/partner/client.js.map +1 -0
- package/dist/partner/endpoint-url.cjs +108 -0
- package/dist/partner/endpoint-url.cjs.map +1 -0
- package/dist/partner/endpoint-url.d.cts +72 -0
- package/dist/partner/endpoint-url.d.cts.map +1 -0
- package/dist/partner/endpoint-url.d.ts +72 -0
- package/dist/partner/endpoint-url.d.ts.map +1 -0
- package/dist/partner/endpoint-url.js +105 -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 +18 -0
- package/dist/partner/index.d.cts +23 -0
- package/dist/partner/index.d.cts.map +1 -0
- package/dist/partner/index.d.ts +23 -0
- package/dist/partner/index.d.ts.map +1 -0
- package/dist/partner/index.js +7 -0
- package/dist/partner/raw.cjs +392 -0
- package/dist/partner/raw.cjs.map +1 -0
- package/dist/partner/raw.d.cts +223 -0
- package/dist/partner/raw.d.cts.map +1 -0
- package/dist/partner/raw.d.ts +223 -0
- package/dist/partner/raw.d.ts.map +1 -0
- package/dist/partner/raw.js +390 -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 +267 -0
- package/dist/partner/redaction.cjs.map +1 -0
- package/dist/partner/redaction.d.cts +124 -0
- package/dist/partner/redaction.d.cts.map +1 -0
- package/dist/partner/redaction.d.ts +124 -0
- package/dist/partner/redaction.d.ts.map +1 -0
- package/dist/partner/redaction.js +257 -0
- package/dist/partner/redaction.js.map +1 -0
- package/dist/raw/index.cjs +81 -19
- package/dist/raw/index.cjs.map +1 -1
- package/dist/raw/index.d.cts +6 -2
- package/dist/raw/index.d.cts.map +1 -1
- package/dist/raw/index.d.ts +6 -2
- package/dist/raw/index.d.ts.map +1 -1
- package/dist/raw/index.js +81 -19
- 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 +1197 -206
- package/dist/simulator/index.cjs.map +1 -1
- package/dist/simulator/index.d.cts +109 -11
- package/dist/simulator/index.d.cts.map +1 -1
- package/dist/simulator/index.d.ts +109 -11
- package/dist/simulator/index.d.ts.map +1 -1
- package/dist/simulator/index.js +1196 -209
- package/dist/simulator/index.js.map +1 -1
- package/dist/simulator/partner.cjs +719 -0
- package/dist/simulator/partner.cjs.map +1 -0
- package/dist/simulator/partner.d.cts +183 -0
- package/dist/simulator/partner.d.cts.map +1 -0
- package/dist/simulator/partner.d.ts +183 -0
- package/dist/simulator/partner.d.ts.map +1 -0
- package/dist/simulator/partner.js +718 -0
- package/dist/simulator/partner.js.map +1 -0
- package/dist/simulator/validation.cjs +577 -0
- package/dist/simulator/validation.cjs.map +1 -1
- package/dist/simulator/validation.d.cts +117 -0
- package/dist/simulator/validation.d.cts.map +1 -1
- package/dist/simulator/validation.d.ts +117 -0
- package/dist/simulator/validation.d.ts.map +1 -1
- package/dist/simulator/validation.js +563 -1
- package/dist/simulator/validation.js.map +1 -1
- package/dist/streams/file-source.cjs +1 -1
- package/dist/streams/file-source.cjs.map +1 -1
- package/dist/streams/file-source.js +1 -1
- package/dist/streams/file-source.js.map +1 -1
- package/dist/streams/index.cjs +1 -1
- package/dist/streams/index.js +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 +11 -10
- package/dist/sync/download-staging.cjs.map +1 -1
- package/dist/sync/download-staging.d.cts +2 -1
- package/dist/sync/download-staging.d.cts.map +1 -1
- package/dist/sync/download-staging.d.ts +2 -1
- package/dist/sync/download-staging.d.ts.map +1 -1
- package/dist/sync/download-staging.js +11 -10
- 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/local-file-io.cjs +11 -6
- package/dist/sync/local-file-io.cjs.map +1 -1
- package/dist/sync/local-file-io.d.cts +1 -0
- package/dist/sync/local-file-io.d.cts.map +1 -1
- package/dist/sync/local-file-io.d.ts +1 -0
- package/dist/sync/local-file-io.d.ts.map +1 -1
- package/dist/sync/local-file-io.js +11 -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/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 +2 -2
- package/dist/sync/scanners/b2.cjs.map +1 -1
- package/dist/sync/scanners/b2.js +2 -2
- package/dist/sync/scanners/b2.js.map +1 -1
- package/dist/sync/scanners/local.cjs.map +1 -1
- package/dist/sync/scanners/local.js.map +1 -1
- package/dist/sync/synchronizer.cjs +3 -2
- package/dist/sync/synchronizer.cjs.map +1 -1
- package/dist/sync/synchronizer.js +3 -2
- package/dist/sync/synchronizer.js.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 +80 -0
- package/dist/types/backup.d.cts.map +1 -0
- package/dist/types/backup.d.ts +80 -0
- package/dist/types/backup.d.ts.map +1 -0
- package/dist/types/bucket.cjs.map +1 -1
- package/dist/types/bucket.d.cts +4 -2
- package/dist/types/bucket.d.cts.map +1 -1
- package/dist/types/bucket.d.ts +4 -2
- package/dist/types/bucket.d.ts.map +1 -1
- 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 +98 -0
- package/dist/types/download.d.cts.map +1 -1
- package/dist/types/download.d.ts +98 -0
- 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 +58 -229
- package/dist/types/encryption.cjs.map +1 -1
- package/dist/types/encryption.d.cts +32 -0
- package/dist/types/encryption.d.cts.map +1 -1
- package/dist/types/encryption.d.ts +32 -0
- package/dist/types/encryption.d.ts.map +1 -1
- package/dist/types/encryption.js +56 -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.map +1 -1
- package/dist/types/file.d.cts +15 -2
- package/dist/types/file.d.cts.map +1 -1
- package/dist/types/file.d.ts +15 -2
- package/dist/types/file.d.ts.map +1 -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 +10 -6
- package/dist/types/index.d.cts.map +1 -1
- package/dist/types/index.d.ts +10 -6
- package/dist/types/index.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 +1 -1
- package/dist/types/notifications.cjs.map +1 -1
- package/dist/types/notifications.d.cts +5 -3
- package/dist/types/notifications.d.cts.map +1 -1
- package/dist/types/notifications.d.ts +5 -3
- package/dist/types/notifications.d.ts.map +1 -1
- package/dist/types/notifications.js +1 -1
- 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 +394 -0
- package/dist/types/partner.d.cts.map +1 -0
- package/dist/types/partner.d.ts +394 -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 +31 -18
- package/dist/types/upload.d.cts.map +1 -1
- package/dist/types/upload.d.ts +31 -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 +5 -1
- package/dist/upload/large.cjs.map +1 -1
- package/dist/upload/large.d.cts +16 -2
- package/dist/upload/large.d.cts.map +1 -1
- package/dist/upload/large.d.ts +16 -2
- package/dist/upload/large.d.ts.map +1 -1
- package/dist/upload/large.js +5 -1
- package/dist/upload/large.js.map +1 -1
- package/dist/upload/options.cjs.map +1 -1
- package/dist/upload/options.d.cts +12 -0
- package/dist/upload/options.d.cts.map +1 -1
- package/dist/upload/options.d.ts +12 -0
- package/dist/upload/options.d.ts.map +1 -1
- package/dist/upload/options.js.map +1 -1
- package/dist/upload/resume.cjs +2 -1
- package/dist/upload/resume.cjs.map +1 -1
- package/dist/upload/resume.d.cts +3 -1
- package/dist/upload/resume.d.cts.map +1 -1
- package/dist/upload/resume.d.ts +3 -1
- package/dist/upload/resume.d.ts.map +1 -1
- package/dist/upload/resume.js +2 -1
- package/dist/upload/resume.js.map +1 -1
- package/dist/upload/retry.cjs +8 -3
- package/dist/upload/retry.cjs.map +1 -1
- package/dist/upload/retry.js +8 -3
- package/dist/upload/retry.js.map +1 -1
- package/dist/upload/single.cjs +2 -1
- package/dist/upload/single.cjs.map +1 -1
- package/dist/upload/single.d.cts +6 -0
- package/dist/upload/single.d.cts.map +1 -1
- package/dist/upload/single.d.ts +6 -0
- package/dist/upload/single.d.ts.map +1 -1
- package/dist/upload/single.js +2 -1
- package/dist/upload/single.js.map +1 -1
- package/dist/upload/stream.cjs +6 -4
- package/dist/upload/stream.cjs.map +1 -1
- package/dist/upload/stream.d.cts +6 -0
- package/dist/upload/stream.d.cts.map +1 -1
- package/dist/upload/stream.d.ts +6 -0
- package/dist/upload/stream.d.ts.map +1 -1
- package/dist/upload/stream.js +3 -1
- 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/package.json +38 -16
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"download-staging.js","names":[],"sources":["../../src/sync/download-staging.ts"],"sourcesContent":["import { assertPathInsideRoot, hasErrorCode, noFollowFlag } from './path-safety.ts'\n\ntype DeviceStat = { readonly dev: number | bigint }\ntype DeviceStatFn = (path: string) => Promise<DeviceStat>\n\n/** @internal */\nexport const DOWNLOAD_STAGING_DIRECTORY_NAME = '.b2sdk-download-staging'\n/** @internal */\nexport const DOWNLOAD_STAGING_MARKER_NAME = '.b2sdk-staging-marker.partial'\n\nconst CANONICAL_DOWNLOAD_STAGING_DIRECTORY_NAME = canonicalLocalFilesystemSegment(\n DOWNLOAD_STAGING_DIRECTORY_NAME,\n)\nconst DOWNLOAD_STAGING_ENTRY_SUFFIX = '.download'\nconst STALE_DOWNLOAD_STAGING_AGE_MS = 24 * 60 * 60 * 1000\nconst MAX_STAGING_CLEANUP_CONCURRENCY = 8\nconst MAX_CLEANUP_WARNING_ENTRIES = 3\n/** @internal */\nexport const DOWNLOAD_STAGING_ACTIVITY_ENTRY_LIMIT = 1024\nconst reapedManagedDirectories = new Map<string, Promise<void>>()\n\ntype StagingActivitySnapshot = {\n readonly newestActivityMs: number\n readonly signature: string\n}\n\ntype CleanupWarning = {\n readonly entryName: string\n readonly operation: 'inspect' | 'remove'\n}\n\n/**\n * Checks whether a single path segment matches the SDK-managed staging directory name.\n * @param segment - Candidate path segment.\n *\n * @returns True when the segment is the reserved staging directory name under\n * local filesystem canonicalization.\n *\n * @internal\n */\nexport function isDownloadStagingDirectorySegment(segment: string | undefined): boolean {\n return (\n segment !== undefined &&\n canonicalLocalFilesystemSegment(segment) === CANONICAL_DOWNLOAD_STAGING_DIRECTORY_NAME\n )\n}\n\n/**\n * Creates a private SDK-managed staging directory under a local sync root.\n * @param rootRealPath - Resolved local sync root path.\n * @param path - Node path module used for platform-specific path operations.\n * @param randomUUID - UUID provider used to create unique staging entries.\n * @param statForDeviceCheck - Stat function used to verify filesystem devices.\n * @param beforeStagingMarkerWrite - Test hook called before marker creation.\n *\n * @returns The resolved staging directory path.\n *\n * @internal\n */\nexport async function createDownloadStagingDirectory(\n rootRealPath: string,\n path: typeof import('node:path'),\n randomUUID: () => string,\n statForDeviceCheck: DeviceStatFn,\n beforeStagingMarkerWrite?: (directory: string) => Promise<void> | void,\n): Promise<string> {\n const { chmod, lstat, mkdir, readdir, realpath, rm } = await import('node:fs/promises')\n const managedDirectory = path.join(rootRealPath, DOWNLOAD_STAGING_DIRECTORY_NAME)\n try {\n await mkdir(managedDirectory, { mode: PRIVATE_DOWNLOAD_DIRECTORY_MODE })\n } catch (err) {\n if (!hasErrorCode(err, 'EEXIST')) throw err\n }\n const managedStats = await lstat(managedDirectory)\n if (!managedStats.isDirectory()) {\n throw new Error(\n `unsafe local destination path: ${DOWNLOAD_STAGING_DIRECTORY_NAME} is not a directory`,\n )\n }\n const realManagedDirectory = await realpath(managedDirectory)\n assertPathInsideRoot(rootRealPath, realManagedDirectory, path)\n await assertDownloadPathSameDevice(\n rootRealPath,\n realManagedDirectory,\n statForDeviceCheck,\n 'unsafe local destination path: cannot stage download across filesystems',\n )\n\n if (!(await isManagedDownloadStagingRoot(realManagedDirectory))) {\n const entries = await readdir(realManagedDirectory)\n if (entries.length > 0 && !(await isManagedDownloadStagingRoot(realManagedDirectory))) {\n throw new Error(\n `unsafe local destination path: ${DOWNLOAD_STAGING_DIRECTORY_NAME} is reserved for SDK download staging`,\n )\n }\n }\n await beforeStagingMarkerWrite?.(realManagedDirectory)\n await writeStagingMarker(realManagedDirectory, path)\n /* v8 ignore next -- best-effort chmod */\n await chmod(realManagedDirectory, PRIVATE_DOWNLOAD_DIRECTORY_MODE).catch(() => {})\n await reapStaleDownloadStagingDirectoriesOnce(realManagedDirectory, path, Date.now())\n\n const stagingDirectory = path.join(\n realManagedDirectory,\n `${Date.now()}-${randomUUID()}${DOWNLOAD_STAGING_ENTRY_SUFFIX}`,\n )\n await mkdir(stagingDirectory, { mode: PRIVATE_DOWNLOAD_DIRECTORY_MODE })\n /* v8 ignore next -- best-effort chmod */\n await chmod(stagingDirectory, PRIVATE_DOWNLOAD_DIRECTORY_MODE).catch(() => {})\n try {\n const realStagingDirectory = await realpath(stagingDirectory)\n assertPathInsideRoot(realManagedDirectory, realStagingDirectory, path)\n await assertDownloadPathSameDevice(\n rootRealPath,\n realStagingDirectory,\n statForDeviceCheck,\n 'unsafe local destination path: cannot stage download across filesystems',\n )\n await beforeStagingMarkerWrite?.(realStagingDirectory)\n await writeStagingMarker(realStagingDirectory, path)\n return realStagingDirectory\n } catch (err) {\n /* v8 ignore next -- best-effort cleanup */\n await rm(stagingDirectory, { recursive: true, force: true }).catch(() => {})\n throw err\n }\n}\n\n/**\n * Returns true when a scan-root entry is an SDK-managed download staging root.\n * @param directory - Candidate directory to inspect.\n *\n * @returns True when the directory contains SDK staging markers.\n *\n * @internal\n */\nexport async function isManagedDownloadStagingRoot(directory: string): Promise<boolean> {\n const { lstat, readdir } = await import('node:fs/promises')\n const path = await import('node:path')\n\n try {\n const markerStats = await lstat(path.join(directory, DOWNLOAD_STAGING_MARKER_NAME))\n if (markerStats.isFile()) return true\n } catch (err) {\n if (!hasErrorCode(err, 'ENOENT')) return false\n }\n\n let entries: import('node:fs').Dirent[]\n try {\n entries = await readdir(directory, { withFileTypes: true })\n } catch {\n return false\n }\n\n for (const entry of entries) {\n if (!entry.isDirectory() || !entry.name.endsWith(DOWNLOAD_STAGING_ENTRY_SUFFIX)) continue\n try {\n const markerStats = await lstat(\n path.join(directory, entry.name, DOWNLOAD_STAGING_MARKER_NAME),\n )\n if (markerStats.isFile()) return true\n } catch {\n // Keep checking siblings; one corrupt entry should not hide user content.\n }\n }\n return false\n}\n\nconst PRIVATE_DOWNLOAD_FILE_MODE = 0o600\nconst PRIVATE_DOWNLOAD_DIRECTORY_MODE = 0o700\n\nasync function writeStagingMarker(\n directory: string,\n path: typeof import('node:path'),\n): Promise<void> {\n const { constants } = await import('node:fs')\n const { lstat, open } = await import('node:fs/promises')\n const markerPath = path.join(directory, DOWNLOAD_STAGING_MARKER_NAME)\n let handle: Awaited<ReturnType<typeof open>> | undefined\n try {\n handle = await open(\n markerPath,\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 } catch (err) {\n if (hasErrorCode(err, 'EEXIST') || hasErrorCode(err, 'ELOOP')) {\n const markerStats = await lstat(markerPath).catch(() => undefined)\n if (markerStats?.isFile() === true) return\n throw new Error('unsafe local destination path: staging marker is not a regular file')\n }\n throw err\n } finally {\n /* v8 ignore next -- best-effort close */\n await handle?.close().catch(() => {})\n }\n}\n\n/**\n * Verifies that a candidate path is on the same filesystem device as the root.\n * @param rootRealPath - Resolved local sync root path.\n * @param candidateRealPath - Resolved candidate path to compare.\n * @param statForDeviceCheck - Stat function used to read device IDs.\n * @param message - Error message used when devices differ.\n *\n * @internal\n */\nexport async function assertDownloadPathSameDevice(\n rootRealPath: string,\n candidateRealPath: string,\n statForDeviceCheck: DeviceStatFn,\n message: string,\n): Promise<void> {\n const [rootStats, candidateStats] = await Promise.all([\n statForDeviceCheck(rootRealPath),\n statForDeviceCheck(candidateRealPath),\n ])\n if (rootStats.dev !== candidateStats.dev) throw new Error(message)\n}\n\nasync function reapStaleDownloadStagingDirectoriesOnce(\n managedDirectory: string,\n path: typeof import('node:path'),\n nowMillis: number,\n): Promise<void> {\n const previous = reapedManagedDirectories.get(managedDirectory)\n if (previous !== undefined) {\n await previous\n return\n }\n\n const next = reapStaleDownloadStagingDirectories(managedDirectory, path, nowMillis).finally(\n () => {\n if (reapedManagedDirectories.get(managedDirectory) === next) {\n reapedManagedDirectories.delete(managedDirectory)\n }\n },\n )\n reapedManagedDirectories.set(managedDirectory, next)\n await next\n}\n\nasync function reapStaleDownloadStagingDirectories(\n managedDirectory: string,\n path: typeof import('node:path'),\n nowMillis: number,\n): Promise<void> {\n const { readdir, realpath, rm } = await import('node:fs/promises')\n let entries: import('node:fs').Dirent[]\n try {\n entries = await readdir(managedDirectory, { withFileTypes: true })\n } catch (err) {\n if (hasErrorCode(err, 'ENOENT')) return\n emitCleanupWarning('failed to inspect B2 SDK download staging entries')\n return\n }\n\n const cleanupErrors: CleanupWarning[] = []\n await forEachWithConcurrency(entries, MAX_STAGING_CLEANUP_CONCURRENCY, async (entry) => {\n if (!entry.isDirectory() || !entry.name.endsWith(DOWNLOAD_STAGING_ENTRY_SUFFIX)) return\n const candidate = path.join(managedDirectory, entry.name)\n const activity = await readManagedStagingEntryActivity(candidate, path)\n if (activity === undefined || !stagingActivityIsStale(activity, nowMillis)) return\n const realCandidate = await realpath(candidate).catch(() => {\n cleanupErrors.push({ entryName: entry.name, operation: 'inspect' })\n return undefined\n })\n if (realCandidate === undefined) return\n try {\n assertPathInsideRoot(managedDirectory, realCandidate, path)\n const latestActivity = await readManagedStagingEntryActivity(candidate, path)\n if (\n latestActivity === undefined ||\n latestActivity.signature !== activity.signature ||\n !stagingActivityIsStale(latestActivity, nowMillis)\n ) {\n return\n }\n await rm(realCandidate, { recursive: true, force: true })\n } catch {\n cleanupErrors.push({ entryName: entry.name, operation: 'remove' })\n }\n })\n\n if (cleanupErrors.length > 0) {\n const noun = cleanupErrors.length === 1 ? 'entry' : 'entries'\n emitCleanupWarning(\n `failed to reap ${cleanupErrors.length} stale B2 SDK download staging ${noun}: ${cleanupErrors\n .slice(0, MAX_CLEANUP_WARNING_ENTRIES)\n .map(formatCleanupWarning)\n .join('; ')}`,\n )\n }\n}\n\nasync function readManagedStagingEntryActivity(\n candidate: string,\n path: typeof import('node:path'),\n): Promise<StagingActivitySnapshot | undefined> {\n const { lstat, readdir } = await import('node:fs/promises')\n\n try {\n const [directoryStats, markerStats] = await Promise.all([\n lstat(candidate),\n lstat(path.join(candidate, DOWNLOAD_STAGING_MARKER_NAME)),\n ])\n if (!directoryStats.isDirectory() || !markerStats.isFile()) return undefined\n\n const entries = await readdir(candidate, { withFileTypes: true })\n if (entries.length > DOWNLOAD_STAGING_ACTIVITY_ENTRY_LIMIT) return undefined\n\n const statsParts = [\n `.:${stagingStatsSignature(directoryStats)}`,\n `${DOWNLOAD_STAGING_MARKER_NAME}:${stagingStatsSignature(markerStats)}`,\n ]\n let newestActivityMs = Math.max(\n stagingStatsActivityMs(directoryStats),\n stagingStatsActivityMs(markerStats),\n )\n\n for (const entry of [...entries].sort((a, b) =>\n a.name < b.name ? -1 : a.name > b.name ? 1 : 0,\n )) {\n const stats = await lstat(path.join(candidate, entry.name))\n statsParts.push(`${entry.name}:${stagingStatsSignature(stats)}`)\n newestActivityMs = Math.max(newestActivityMs, stagingStatsActivityMs(stats))\n }\n\n return { newestActivityMs, signature: statsParts.join('|') }\n } catch {\n return undefined\n }\n}\n\nfunction stagingStatsActivityMs(stats: { readonly mtimeMs: number }) {\n return stats.mtimeMs\n}\n\nfunction stagingStatsSignature(stats: {\n readonly ctimeMs: number\n readonly dev: number | bigint\n readonly ino: number | bigint\n readonly mtimeMs: number\n readonly size: number\n}): string {\n return `${stats.dev}:${stats.ino}:${stats.size}:${stats.mtimeMs}:${stats.ctimeMs}`\n}\n\nfunction stagingActivityIsStale(activity: StagingActivitySnapshot, nowMillis: number): boolean {\n return nowMillis - activity.newestActivityMs >= STALE_DOWNLOAD_STAGING_AGE_MS\n}\n\nfunction formatCleanupWarning(error: CleanupWarning): string {\n return `${error.operation} ${safeWarningName(error.entryName)}`\n}\n\nfunction safeWarningName(name: string): string {\n const chars = Array.from(name)\n const bounded = chars.slice(0, 80).join('')\n const suffix = chars.length > 80 ? '...' : ''\n return JSON.stringify(`${bounded.replace(/[^A-Za-z0-9._-]/g, '?')}${suffix}`)\n}\n\nfunction canonicalLocalFilesystemSegment(segment: string): string {\n return segment.normalize('NFC').toLocaleLowerCase('en-US')\n}\n\nasync function forEachWithConcurrency<T>(\n items: readonly T[],\n concurrency: number,\n fn: (item: T) => Promise<void>,\n): Promise<void> {\n let index = 0\n const workers = Array.from({ length: Math.min(concurrency, items.length) }, async () => {\n while (index < items.length) {\n const item = items[index]\n index += 1\n if (item !== undefined) await fn(item)\n }\n })\n await Promise.all(workers)\n}\n\nfunction emitCleanupWarning(message: string): void {\n const processLike = (\n globalThis as {\n process?: {\n emitWarning?: (warning: string, options?: { readonly code?: string }) => void\n }\n }\n ).process\n processLike?.emitWarning?.(message, { code: 'B2SDK_DOWNLOAD_STAGING_CLEANUP_FAILED' })\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"download-staging.js","names":[],"sources":["../../src/sync/download-staging.ts"],"sourcesContent":["import { assertPathInsideRoot, hasErrorCode, noFollowFlag } from './path-safety.ts'\n\ntype DeviceStat = { readonly dev: number | bigint }\ntype DeviceStatFn = (path: string) => Promise<DeviceStat>\n\n/** @internal */\nexport const DOWNLOAD_STAGING_DIRECTORY_NAME = '.b2sdk-download-staging'\n/** @internal */\nexport const DOWNLOAD_STAGING_MARKER_NAME = '.b2sdk-staging-marker.partial'\n\nconst CANONICAL_DOWNLOAD_STAGING_DIRECTORY_NAME = canonicalLocalFilesystemSegment(\n DOWNLOAD_STAGING_DIRECTORY_NAME,\n)\nconst DOWNLOAD_STAGING_ENTRY_SUFFIX = '.download'\nconst STALE_DOWNLOAD_STAGING_AGE_MS = 24 * 60 * 60 * 1000\nconst MAX_STAGING_CLEANUP_CONCURRENCY = 8\nconst MAX_CLEANUP_WARNING_ENTRIES = 3\n/** @internal */\nexport const DOWNLOAD_STAGING_ACTIVITY_ENTRY_LIMIT = 1024\nconst reapedManagedDirectories = new Map<string, Promise<void>>()\n\ntype StagingActivitySnapshot = {\n readonly newestActivityMs: number\n readonly signature: string\n}\n\ntype CleanupWarning = {\n readonly entryName: string\n readonly operation: 'inspect' | 'remove'\n}\n\n/**\n * Checks whether a single path segment matches the SDK-managed staging directory name.\n * @param segment - Candidate path segment.\n *\n * @returns True when the segment is the reserved staging directory name under\n * local filesystem canonicalization.\n *\n * @internal\n */\nexport function isDownloadStagingDirectorySegment(segment: string | undefined): boolean {\n return (\n segment !== undefined &&\n canonicalLocalFilesystemSegment(segment) === CANONICAL_DOWNLOAD_STAGING_DIRECTORY_NAME\n )\n}\n\n/**\n * Creates a private SDK-managed staging directory under a local sync root.\n * @param rootRealPath - Resolved local sync root path.\n * @param path - Node path module used for platform-specific path operations.\n * @param randomUUID - UUID provider used to create unique staging entries.\n * @param statForDeviceCheck - Stat function used to verify filesystem devices.\n * @param beforeStagingMarkerWrite - Test hook called before marker creation.\n * @param activityEntryLimit - Maximum entry count inspected for stale staging activity.\n *\n * @returns The resolved staging directory path.\n *\n * @internal\n */\nexport async function createDownloadStagingDirectory(\n rootRealPath: string,\n path: typeof import('node:path'),\n randomUUID: () => string,\n statForDeviceCheck: DeviceStatFn,\n beforeStagingMarkerWrite?: (directory: string) => Promise<void> | void,\n activityEntryLimit = DOWNLOAD_STAGING_ACTIVITY_ENTRY_LIMIT,\n): Promise<string> {\n const { chmod, lstat, mkdir, readdir, realpath, rm } = await import('node:fs/promises')\n const managedDirectory = path.join(rootRealPath, DOWNLOAD_STAGING_DIRECTORY_NAME)\n try {\n await mkdir(managedDirectory, { mode: PRIVATE_DOWNLOAD_DIRECTORY_MODE })\n } catch (err) {\n if (!hasErrorCode(err, 'EEXIST')) throw err\n }\n const managedStats = await lstat(managedDirectory)\n if (!managedStats.isDirectory()) {\n throw new Error(\n `unsafe local destination path: ${DOWNLOAD_STAGING_DIRECTORY_NAME} is not a directory`,\n )\n }\n const realManagedDirectory = await realpath(managedDirectory)\n assertPathInsideRoot(rootRealPath, realManagedDirectory, path)\n await assertDownloadPathSameDevice(\n rootRealPath,\n realManagedDirectory,\n statForDeviceCheck,\n 'unsafe local destination path: cannot stage download across filesystems',\n )\n\n if (!(await isManagedDownloadStagingRoot(realManagedDirectory))) {\n const entries = await readdir(realManagedDirectory)\n if (entries.length > 0 && !(await isManagedDownloadStagingRoot(realManagedDirectory))) {\n throw new Error(\n `unsafe local destination path: ${DOWNLOAD_STAGING_DIRECTORY_NAME} is reserved for SDK download staging`,\n )\n }\n }\n await beforeStagingMarkerWrite?.(realManagedDirectory)\n await writeStagingMarker(realManagedDirectory, path)\n /* v8 ignore next -- best-effort chmod */\n await chmod(realManagedDirectory, PRIVATE_DOWNLOAD_DIRECTORY_MODE).catch(() => {})\n await reapStaleDownloadStagingDirectoriesOnce(\n realManagedDirectory,\n path,\n Date.now(),\n activityEntryLimit,\n )\n\n const stagingDirectory = path.join(\n realManagedDirectory,\n `${Date.now()}-${randomUUID()}${DOWNLOAD_STAGING_ENTRY_SUFFIX}`,\n )\n await mkdir(stagingDirectory, { mode: PRIVATE_DOWNLOAD_DIRECTORY_MODE })\n /* v8 ignore next -- best-effort chmod */\n await chmod(stagingDirectory, PRIVATE_DOWNLOAD_DIRECTORY_MODE).catch(() => {})\n try {\n const realStagingDirectory = await realpath(stagingDirectory)\n assertPathInsideRoot(realManagedDirectory, realStagingDirectory, path)\n await assertDownloadPathSameDevice(\n rootRealPath,\n realStagingDirectory,\n statForDeviceCheck,\n 'unsafe local destination path: cannot stage download across filesystems',\n )\n await beforeStagingMarkerWrite?.(realStagingDirectory)\n await writeStagingMarker(realStagingDirectory, path)\n return realStagingDirectory\n } catch (err) {\n /* v8 ignore next -- best-effort cleanup */\n await rm(stagingDirectory, { recursive: true, force: true }).catch(() => {})\n throw err\n }\n}\n\n/**\n * Returns true when a scan-root entry is an SDK-managed download staging root.\n * @param directory - Candidate directory to inspect.\n *\n * @returns True when the directory contains SDK staging markers.\n *\n * @internal\n */\nexport async function isManagedDownloadStagingRoot(directory: string): Promise<boolean> {\n const { lstat, readdir } = await import('node:fs/promises')\n const path = await import('node:path')\n\n try {\n const markerStats = await lstat(path.join(directory, DOWNLOAD_STAGING_MARKER_NAME))\n if (markerStats.isFile()) return true\n } catch (err) {\n if (!hasErrorCode(err, 'ENOENT')) return false\n }\n\n let entries: import('node:fs').Dirent[]\n try {\n entries = await readdir(directory, { withFileTypes: true })\n } catch {\n return false\n }\n\n for (const entry of entries) {\n if (!entry.isDirectory() || !entry.name.endsWith(DOWNLOAD_STAGING_ENTRY_SUFFIX)) continue\n try {\n const markerStats = await lstat(\n path.join(directory, entry.name, DOWNLOAD_STAGING_MARKER_NAME),\n )\n if (markerStats.isFile()) return true\n } catch {\n // Keep checking siblings; one corrupt entry should not hide user content.\n }\n }\n return false\n}\n\nconst PRIVATE_DOWNLOAD_FILE_MODE = 0o600\nconst PRIVATE_DOWNLOAD_DIRECTORY_MODE = 0o700\n\nasync function writeStagingMarker(\n directory: string,\n path: typeof import('node:path'),\n): Promise<void> {\n const { constants } = await import('node:fs')\n const { lstat, open } = await import('node:fs/promises')\n const markerPath = path.join(directory, DOWNLOAD_STAGING_MARKER_NAME)\n let handle: Awaited<ReturnType<typeof open>> | undefined\n try {\n handle = await open(\n markerPath,\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 } catch (err) {\n if (hasErrorCode(err, 'EEXIST') || hasErrorCode(err, 'ELOOP')) {\n const markerStats = await lstat(markerPath).catch(() => undefined)\n if (markerStats?.isFile() === true) return\n throw new Error('unsafe local destination path: staging marker is not a regular file')\n }\n throw err\n } finally {\n /* v8 ignore next -- best-effort close */\n await handle?.close().catch(() => {})\n }\n}\n\n/**\n * Verifies that a candidate path is on the same filesystem device as the root.\n * @param rootRealPath - Resolved local sync root path.\n * @param candidateRealPath - Resolved candidate path to compare.\n * @param statForDeviceCheck - Stat function used to read device IDs.\n * @param message - Error message used when devices differ.\n *\n * @internal\n */\nexport async function assertDownloadPathSameDevice(\n rootRealPath: string,\n candidateRealPath: string,\n statForDeviceCheck: DeviceStatFn,\n message: string,\n): Promise<void> {\n const [rootStats, candidateStats] = await Promise.all([\n statForDeviceCheck(rootRealPath),\n statForDeviceCheck(candidateRealPath),\n ])\n if (rootStats.dev !== candidateStats.dev) throw new Error(message)\n}\n\nasync function reapStaleDownloadStagingDirectoriesOnce(\n managedDirectory: string,\n path: typeof import('node:path'),\n nowMillis: number,\n activityEntryLimit: number,\n): Promise<void> {\n const previous = reapedManagedDirectories.get(managedDirectory)\n if (previous !== undefined) {\n await previous\n return\n }\n\n const next = reapStaleDownloadStagingDirectories(\n managedDirectory,\n path,\n nowMillis,\n activityEntryLimit,\n ).finally(() => {\n if (reapedManagedDirectories.get(managedDirectory) === next) {\n reapedManagedDirectories.delete(managedDirectory)\n }\n })\n reapedManagedDirectories.set(managedDirectory, next)\n await next\n}\n\nasync function reapStaleDownloadStagingDirectories(\n managedDirectory: string,\n path: typeof import('node:path'),\n nowMillis: number,\n activityEntryLimit: number,\n): Promise<void> {\n const { readdir, realpath, rm } = await import('node:fs/promises')\n let entries: import('node:fs').Dirent[]\n try {\n entries = await readdir(managedDirectory, { withFileTypes: true })\n } catch (err) {\n if (hasErrorCode(err, 'ENOENT')) return\n emitCleanupWarning('failed to inspect B2 SDK download staging entries')\n return\n }\n\n const cleanupErrors: CleanupWarning[] = []\n await forEachWithConcurrency(entries, MAX_STAGING_CLEANUP_CONCURRENCY, async (entry) => {\n if (!entry.isDirectory() || !entry.name.endsWith(DOWNLOAD_STAGING_ENTRY_SUFFIX)) return\n const candidate = path.join(managedDirectory, entry.name)\n const activity = await readManagedStagingEntryActivity(candidate, path, activityEntryLimit)\n if (activity === undefined || !stagingActivityIsStale(activity, nowMillis)) return\n const realCandidate = await realpath(candidate).catch(() => {\n cleanupErrors.push({ entryName: entry.name, operation: 'inspect' })\n return undefined\n })\n if (realCandidate === undefined) return\n try {\n assertPathInsideRoot(managedDirectory, realCandidate, path)\n const latestActivity = await readManagedStagingEntryActivity(\n candidate,\n path,\n activityEntryLimit,\n )\n if (\n latestActivity === undefined ||\n latestActivity.signature !== activity.signature ||\n !stagingActivityIsStale(latestActivity, nowMillis)\n ) {\n return\n }\n await rm(realCandidate, { recursive: true, force: true })\n } catch {\n cleanupErrors.push({ entryName: entry.name, operation: 'remove' })\n }\n })\n\n if (cleanupErrors.length > 0) {\n const noun = cleanupErrors.length === 1 ? 'entry' : 'entries'\n emitCleanupWarning(\n `failed to reap ${cleanupErrors.length} stale B2 SDK download staging ${noun}: ${cleanupErrors\n .slice(0, MAX_CLEANUP_WARNING_ENTRIES)\n .map(formatCleanupWarning)\n .join('; ')}`,\n )\n }\n}\n\nasync function readManagedStagingEntryActivity(\n candidate: string,\n path: typeof import('node:path'),\n activityEntryLimit: number,\n): Promise<StagingActivitySnapshot | undefined> {\n const { lstat, readdir } = await import('node:fs/promises')\n\n try {\n const [directoryStats, markerStats] = await Promise.all([\n lstat(candidate),\n lstat(path.join(candidate, DOWNLOAD_STAGING_MARKER_NAME)),\n ])\n if (!directoryStats.isDirectory() || !markerStats.isFile()) return undefined\n\n const entries = await readdir(candidate, { withFileTypes: true })\n if (entries.length > activityEntryLimit) return undefined\n\n const statsParts = [\n `.:${stagingStatsSignature(directoryStats)}`,\n `${DOWNLOAD_STAGING_MARKER_NAME}:${stagingStatsSignature(markerStats)}`,\n ]\n let newestActivityMs = Math.max(\n stagingStatsActivityMs(directoryStats),\n stagingStatsActivityMs(markerStats),\n )\n\n for (const entry of [...entries].sort((a, b) =>\n a.name < b.name ? -1 : a.name > b.name ? 1 : 0,\n )) {\n const stats = await lstat(path.join(candidate, entry.name))\n statsParts.push(`${entry.name}:${stagingStatsSignature(stats)}`)\n newestActivityMs = Math.max(newestActivityMs, stagingStatsActivityMs(stats))\n }\n\n return { newestActivityMs, signature: statsParts.join('|') }\n } catch {\n return undefined\n }\n}\n\nfunction stagingStatsActivityMs(stats: { readonly mtimeMs: number }) {\n return stats.mtimeMs\n}\n\nfunction stagingStatsSignature(stats: {\n readonly ctimeMs: number\n readonly dev: number | bigint\n readonly ino: number | bigint\n readonly mtimeMs: number\n readonly size: number\n}): string {\n return `${stats.dev}:${stats.ino}:${stats.size}:${stats.mtimeMs}:${stats.ctimeMs}`\n}\n\nfunction stagingActivityIsStale(activity: StagingActivitySnapshot, nowMillis: number): boolean {\n return nowMillis - activity.newestActivityMs >= STALE_DOWNLOAD_STAGING_AGE_MS\n}\n\nfunction formatCleanupWarning(error: CleanupWarning): string {\n return `${error.operation} ${safeWarningName(error.entryName)}`\n}\n\nfunction safeWarningName(name: string): string {\n const chars = Array.from(name)\n const bounded = chars.slice(0, 80).join('')\n const suffix = chars.length > 80 ? '...' : ''\n return JSON.stringify(`${bounded.replace(/[^A-Za-z0-9._-]/g, '?')}${suffix}`)\n}\n\nfunction canonicalLocalFilesystemSegment(segment: string): string {\n return segment.normalize('NFC').toLocaleLowerCase('en-US')\n}\n\nasync function forEachWithConcurrency<T>(\n items: readonly T[],\n concurrency: number,\n fn: (item: T) => Promise<void>,\n): Promise<void> {\n let index = 0\n const workers = Array.from({ length: Math.min(concurrency, items.length) }, async () => {\n while (index < items.length) {\n const item = items[index]\n index += 1\n if (item !== undefined) await fn(item)\n }\n })\n await Promise.all(workers)\n}\n\nfunction emitCleanupWarning(message: string): void {\n const processLike = (\n globalThis as {\n process?: {\n emitWarning?: (warning: string, options?: { readonly code?: string }) => void\n }\n }\n ).process\n processLike?.emitWarning?.(message, { code: 'B2SDK_DOWNLOAD_STAGING_CLEANUP_FAILED' })\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filters.cjs","names":[],"sources":["../../src/sync/filters.ts"],"sourcesContent":["import {\n pathExceedsSafeRegExpInput,\n regexpMatchesSyncPath,\n validateSyncFilters,\n} from './regexp-safety.ts'\nimport { emitScannerSkip, regexpInputTooLongSkip } from './scan-events.ts'\nimport type { SyncFilterOptions, SyncFilterPattern, SyncPath, SyncScanOptions } from './types.ts'\n\n/**\n * Tests whether a relative sync path is included by the configured include/exclude filters.\n * Exclude filters win over include filters when both match the same path.\n *\n * @param relativePath - Folder-relative path using forward slashes.\n * @param filters - Optional include and exclude filters.\n *\n * @returns True when the path should remain in the sync scan.\n */\nexport function pathPassesSyncFilters(\n relativePath: string,\n filters: SyncFilterOptions | undefined,\n): boolean {\n validateSyncFilters(filters)\n const path = normalizePath(relativePath)\n\n if (normalizedPathSkippedByRegExpInputLimit(path, filters)) return false\n\n const include = filters?.include ?? []\n const exclude = filters?.exclude ?? []\n\n if (include.length > 0) {\n if (!include.some((pattern) => matchesPattern(path, pattern))) return false\n }\n\n return !exclude.some((pattern) => matchesPattern(path, pattern))\n}\n\n/**\n * Tests whether a directory may contain paths admitted by the configured filters.\n *\n * @param relativePath - Folder-relative directory path using forward slashes.\n * @param filters - Optional include and exclude filters.\n *\n * @returns True when the scanner should descend into the directory.\n */\nexport function directoryMayContainSyncPaths(\n relativePath: string,\n filters: SyncFilterOptions | undefined,\n): boolean {\n validateSyncFilters(filters)\n const path = normalizePath(relativePath)\n if (path === '') return true\n\n const exclude = filters?.exclude ?? []\n if (exclude.some((pattern) => stringPatternExcludesAllDescendants(path, pattern))) {\n return false\n }\n\n const include = filters?.include ?? []\n return include.length === 0 || include.some((pattern) => patternMayMatchDescendant(path, pattern))\n}\n\n/**\n * Returns the safe literal prefix that B2 listing can use for include filters.\n * Exclude filters are not considered because they cannot narrow a B2 prefix.\n *\n * @param filters - Optional include and exclude filters.\n *\n * @returns A folder-relative literal prefix, or an empty string when no safe narrowing exists.\n */\nexport function literalPrefixForSyncFilters(filters: SyncFilterOptions | undefined): string {\n validateSyncFilters(filters)\n const include = filters?.include ?? []\n let commonPrefix: string | undefined\n\n for (const pattern of include) {\n if (patternIsRegExp(pattern)) return ''\n\n const glob = normalizePath(pattern)\n if (!glob.includes('/')) return ''\n\n const prefix = literalPrefixForGlob(glob)\n if (prefix === '') return ''\n commonPrefix = commonPrefix === undefined ? prefix : commonLiteralPrefix(commonPrefix, prefix)\n }\n\n return commonPrefix ?? ''\n}\n\n/**\n * Filters an async iterable of sync paths while preserving the original item type.\n *\n * @typeParam T - Concrete sync path shape yielded by the source folder.\n *\n * @param paths - Async iterable of folder scan results.\n * @param filters - Optional include and exclude filters.\n *\n * @returns A filtered async generator of sync paths.\n */\nexport async function* filterSyncPaths<T extends SyncPath>(\n paths: AsyncIterable<T>,\n filters: SyncScanOptions | undefined,\n): AsyncGenerator<T> {\n for await (const path of paths) {\n if (pathPassesSyncFilters(path.relativePath, filters)) {\n yield path\n } else if (pathSkippedByRegExpInputLimit(path.relativePath, filters)) {\n emitScannerSkip(filters, regexpInputTooLongSkip(normalizePath(path.relativePath)))\n }\n }\n}\n\n/**\n * Tests whether a path is skipped because RegExp filters are configured and the normalized path\n * exceeds the SDK RegExp input guard.\n *\n * @param relativePath - Folder-relative path using forward slashes.\n * @param filters - Optional include and exclude filters.\n *\n * @returns True when any RegExp filter is present and the path is too long to evaluate.\n */\nexport function pathSkippedByRegExpInputLimit(\n relativePath: string,\n filters: SyncFilterOptions | undefined,\n): boolean {\n validateSyncFilters(filters)\n return normalizedPathSkippedByRegExpInputLimit(normalizePath(relativePath), filters)\n}\n\nfunction normalizedPathSkippedByRegExpInputLimit(\n normalizedPath: string,\n filters: SyncFilterOptions | undefined,\n): boolean {\n if (!pathExceedsSafeRegExpInput(normalizedPath) || !filtersContainRegExp(filters)) {\n return false\n }\n return true\n}\n\nfunction matchesPattern(relativePath: string, pattern: SyncFilterPattern): boolean {\n if (patternIsRegExp(pattern)) {\n return regexpMatchesSyncPath(relativePath, pattern)\n }\n\n const glob = normalizePath(pattern)\n if (glob === '') return relativePath === ''\n\n const segments = splitPath(relativePath)\n if (!glob.includes('/')) {\n return segments.some((segment) => matchSegmentGlob(segment, glob))\n }\n\n return matchPathGlob(segments, splitPath(glob))\n}\n\nfunction stringPatternExcludesAllDescendants(\n relativePath: string,\n pattern: SyncFilterPattern,\n): boolean {\n if (patternIsRegExp(pattern)) return false\n\n const glob = normalizePath(pattern)\n if (glob === '') return false\n if (!glob.includes('/')) return matchesPattern(relativePath, pattern)\n\n const globSegments = splitPath(glob)\n return globSegments.at(-1) === '**' && matchPathGlob(splitPath(relativePath), globSegments)\n}\n\nfunction filtersContainRegExp(filters: SyncFilterOptions | undefined): boolean {\n return (\n filters?.include?.some(patternIsRegExp) === true ||\n filters?.exclude?.some(patternIsRegExp) === true\n )\n}\n\nfunction patternMayMatchDescendant(relativePath: string, pattern: SyncFilterPattern): boolean {\n if (patternIsRegExp(pattern)) return true\n\n const glob = normalizePath(pattern)\n if (glob === '' || !glob.includes('/')) return true\n\n const pathSegments = splitPath(relativePath)\n const globSegments = splitPath(glob)\n const length = Math.min(pathSegments.length, globSegments.length)\n\n for (let i = 0; i < length; i++) {\n const globSegment = globSegments[i]\n if (globSegment === '**' || globSegment === undefined || hasGlobWildcard(globSegment)) {\n return true\n }\n if (globSegment !== pathSegments[i]) {\n return false\n }\n }\n\n return true\n}\n\nfunction matchPathGlob(pathSegments: readonly string[], globSegments: readonly string[]): boolean {\n let reachable = new Array<boolean>(pathSegments.length + 1).fill(false)\n reachable[0] = true\n\n for (const globSegment of globSegments) {\n const next = new Array<boolean>(pathSegments.length + 1).fill(false)\n\n if (globSegment === '**') {\n // A whole-segment `**` consumes zero or more complete path segments.\n let canReach = false\n for (let i = 0; i <= pathSegments.length; i++) {\n canReach = canReach || reachable[i] === true\n next[i] = canReach\n }\n } else {\n for (let i = 0; i < pathSegments.length; i++) {\n if (reachable[i] === true && matchSegmentGlob(pathSegments[i] ?? '', globSegment)) {\n next[i + 1] = true\n }\n }\n }\n\n reachable = next\n }\n\n return reachable[pathSegments.length] === true\n}\n\nfunction matchSegmentGlob(segment: string, glob: string): boolean {\n let segmentIndex = 0\n let globIndex = 0\n let starIndex = -1\n let starMatchIndex = 0\n\n while (segmentIndex < segment.length) {\n const globChar = glob[globIndex]\n if (globChar === '?' || globChar === segment[segmentIndex]) {\n globIndex++\n segmentIndex++\n } else if (globChar === '*') {\n // Repeated `*` in one segment is still one segment-local wildcard.\n while (glob[globIndex + 1] === '*') globIndex++\n starIndex = globIndex\n starMatchIndex = segmentIndex\n globIndex++\n } else if (starIndex !== -1) {\n globIndex = starIndex + 1\n starMatchIndex++\n segmentIndex = starMatchIndex\n } else {\n return false\n }\n }\n\n while (glob[globIndex] === '*') globIndex++\n return globIndex === glob.length\n}\n\nfunction literalPrefixForGlob(glob: string): string {\n const segments = splitPath(glob)\n const literalSegments: string[] = []\n let firstWildcardIndex = segments.length\n\n for (const [index, segment] of segments.entries()) {\n if (segment === '**' || hasGlobWildcard(segment)) {\n firstWildcardIndex = index\n break\n }\n literalSegments.push(segment)\n }\n\n if (literalSegments.length === 0) return ''\n const prefix = literalSegments.join('/')\n const wildcardTail = segments.slice(firstWildcardIndex)\n const tailMayMatchBarePrefix =\n wildcardTail.length > 0 && wildcardTail.every((segment) => segment === '**')\n if (tailMayMatchBarePrefix) return prefix\n return literalSegments.length < segments.length ? `${prefix}/` : prefix\n}\n\nfunction commonLiteralPrefix(a: string, b: string): string {\n let end = 0\n const max = Math.min(a.length, b.length)\n while (end < max && a[end] === b[end]) end++\n return trimTrailingHighSurrogate(a.slice(0, end))\n}\n\nfunction trimTrailingHighSurrogate(value: string): string {\n const lastCodeUnit = value.charCodeAt(value.length - 1)\n return lastCodeUnit >= 0xd800 && lastCodeUnit <= 0xdbff ? value.slice(0, -1) : value\n}\n\nfunction hasGlobWildcard(glob: string): boolean {\n return glob.includes('*') || glob.includes('?')\n}\n\nfunction patternIsRegExp(pattern: SyncFilterPattern): pattern is RegExp {\n return typeof pattern !== 'string'\n}\n\nfunction splitPath(path: string): string[] {\n if (path === '') return []\n return path.split('/').filter((segment) => segment !== '')\n}\n\nfunction normalizePath(path: string): string {\n let normalized = path.split('\\\\').join('/')\n while (normalized.startsWith('./')) normalized = normalized.slice(2)\n while (normalized.startsWith('/')) normalized = normalized.slice(1)\n while (normalized.endsWith('/') && normalized.length > 1) {\n normalized = normalized.slice(0, -1)\n }\n return normalized\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"filters.cjs","names":[],"sources":["../../src/sync/filters.ts"],"sourcesContent":["import {\n pathExceedsSafeRegExpInput,\n regexpMatchesSyncPath,\n validateSyncFilters,\n} from './regexp-safety.ts'\nimport { emitScannerSkip, regexpInputTooLongSkip } from './scan-events.ts'\nimport type { SyncFilterOptions, SyncFilterPattern, SyncPath, SyncScanOptions } from './types.ts'\n\n/**\n * Tests whether a relative sync path is included by the configured include/exclude filters.\n * Exclude filters win over include filters when both match the same path.\n *\n * @param relativePath - Folder-relative path using forward slashes.\n * @param filters - Optional include and exclude filters.\n *\n * @returns True when the path should remain in the sync scan.\n */\nexport function pathPassesSyncFilters(\n relativePath: string,\n filters: SyncFilterOptions | undefined,\n): boolean {\n validateSyncFilters(filters)\n const path = normalizePath(relativePath)\n\n if (normalizedPathSkippedByRegExpInputLimit(path, filters)) return false\n\n const include = filters?.include ?? []\n const exclude = filters?.exclude ?? []\n\n if (include.length > 0) {\n if (!include.some((pattern) => matchesPattern(path, pattern))) return false\n }\n\n return !exclude.some((pattern) => matchesPattern(path, pattern))\n}\n\n/**\n * Tests whether a directory may contain paths admitted by the configured filters.\n *\n * @param relativePath - Folder-relative directory path using forward slashes.\n * @param filters - Optional include and exclude filters.\n *\n * @returns True when the scanner should descend into the directory.\n */\nexport function directoryMayContainSyncPaths(\n relativePath: string,\n filters: SyncFilterOptions | undefined,\n): boolean {\n validateSyncFilters(filters)\n const path = normalizePath(relativePath)\n if (path === '') return true\n\n const exclude = filters?.exclude ?? []\n if (exclude.some((pattern) => stringPatternExcludesAllDescendants(path, pattern))) {\n return false\n }\n\n const include = filters?.include ?? []\n return include.length === 0 || include.some((pattern) => patternMayMatchDescendant(path, pattern))\n}\n\n/**\n * Returns the safe literal prefix that B2 listing can use for include filters.\n * Exclude filters are not considered because they cannot narrow a B2 prefix.\n *\n * @param filters - Optional include and exclude filters.\n *\n * @returns A folder-relative literal prefix, or an empty string when no safe narrowing exists.\n */\nexport function literalPrefixForSyncFilters(filters: SyncFilterOptions | undefined): string {\n validateSyncFilters(filters)\n const include = filters?.include ?? []\n let commonPrefix: string | undefined\n\n for (const pattern of include) {\n if (patternIsRegExp(pattern)) return ''\n\n const glob = normalizePath(pattern)\n if (!glob.includes('/')) return ''\n\n const prefix = literalPrefixForGlob(glob)\n if (prefix === '') return ''\n commonPrefix = commonPrefix === undefined ? prefix : commonLiteralPrefix(commonPrefix, prefix)\n }\n\n return commonPrefix ?? ''\n}\n\n/**\n * Filters an async iterable of sync paths while preserving the original item type.\n *\n * @typeParam T - Concrete sync path shape yielded by the source folder.\n *\n * @param paths - Async iterable of folder scan results.\n * @param filters - Optional include and exclude filters.\n *\n * @returns A filtered async generator of sync paths.\n */\nexport async function* filterSyncPaths<T extends SyncPath>(\n paths: AsyncIterable<T>,\n filters: SyncScanOptions | undefined,\n): AsyncGenerator<T> {\n for await (const path of paths) {\n if (pathPassesSyncFilters(path.relativePath, filters)) {\n yield path\n } else if (pathSkippedByRegExpInputLimit(path.relativePath, filters)) {\n emitScannerSkip(filters, regexpInputTooLongSkip(normalizePath(path.relativePath)))\n }\n }\n}\n\n/**\n * Tests whether a path is skipped because RegExp filters are configured and the normalized path\n * exceeds the SDK RegExp input guard.\n *\n * @param relativePath - Folder-relative path using forward slashes.\n * @param filters - Optional include and exclude filters.\n *\n * @returns True when any RegExp filter is present and the path is too long to evaluate.\n */\nexport function pathSkippedByRegExpInputLimit(\n relativePath: string,\n filters: SyncFilterOptions | undefined,\n): boolean {\n validateSyncFilters(filters)\n return normalizedPathSkippedByRegExpInputLimit(normalizePath(relativePath), filters)\n}\n\nfunction normalizedPathSkippedByRegExpInputLimit(\n normalizedPath: string,\n filters: SyncFilterOptions | undefined,\n): boolean {\n if (!pathExceedsSafeRegExpInput(normalizedPath) || !filtersContainRegExp(filters)) {\n return false\n }\n return true\n}\n\nfunction matchesPattern(relativePath: string, pattern: SyncFilterPattern): boolean {\n if (patternIsRegExp(pattern)) {\n return regexpMatchesSyncPath(relativePath, pattern)\n }\n\n const glob = normalizePath(pattern)\n if (glob === '') return relativePath === ''\n\n const segments = splitPath(relativePath)\n if (!glob.includes('/')) {\n return segments.some((segment) => matchSegmentGlob(segment, glob))\n }\n\n return matchPathGlob(segments, splitPath(glob))\n}\n\nfunction stringPatternExcludesAllDescendants(\n relativePath: string,\n pattern: SyncFilterPattern,\n): boolean {\n if (patternIsRegExp(pattern)) return false\n\n const glob = normalizePath(pattern)\n if (glob === '') return false\n if (!glob.includes('/')) return matchesPattern(relativePath, pattern)\n\n const globSegments = splitPath(glob)\n return globSegments.at(-1) === '**' && matchPathGlob(splitPath(relativePath), globSegments)\n}\n\nfunction filtersContainRegExp(filters: SyncFilterOptions | undefined): boolean {\n return (\n filters?.include?.some(patternIsRegExp) === true ||\n filters?.exclude?.some(patternIsRegExp) === true\n )\n}\n\nfunction patternMayMatchDescendant(relativePath: string, pattern: SyncFilterPattern): boolean {\n if (patternIsRegExp(pattern)) return true\n\n const glob = normalizePath(pattern)\n if (glob === '' || !glob.includes('/')) return true\n\n const pathSegments = splitPath(relativePath)\n const globSegments = splitPath(glob)\n const length = Math.min(pathSegments.length, globSegments.length)\n\n for (let i = 0; i < length; i++) {\n const globSegment = globSegments[i]\n if (globSegment === '**' || globSegment === undefined || hasGlobWildcard(globSegment)) {\n return true\n }\n if (globSegment !== pathSegments[i]) {\n return false\n }\n }\n\n return true\n}\n\nfunction matchPathGlob(pathSegments: readonly string[], globSegments: readonly string[]): boolean {\n let reachable = new Array<boolean>(pathSegments.length + 1).fill(false)\n reachable[0] = true\n\n for (const globSegment of globSegments) {\n const next = new Array<boolean>(pathSegments.length + 1).fill(false)\n\n if (globSegment === '**') {\n // A whole-segment `**` consumes zero or more complete path segments.\n let canReach = false\n for (let i = 0; i <= pathSegments.length; i++) {\n canReach = canReach || reachable[i] === true\n next[i] = canReach\n }\n } else {\n for (let i = 0; i < pathSegments.length; i++) {\n if (reachable[i] === true && matchSegmentGlob(pathSegments[i] ?? '', globSegment)) {\n next[i + 1] = true\n }\n }\n }\n\n reachable = next\n }\n\n return reachable[pathSegments.length] === true\n}\n\nfunction matchSegmentGlob(segment: string, glob: string): boolean {\n let segmentIndex = 0\n let globIndex = 0\n let starIndex = -1\n let starMatchIndex = 0\n\n while (segmentIndex < segment.length) {\n const globChar = glob[globIndex]\n if (globChar === '?' || globChar === segment[segmentIndex]) {\n globIndex++\n segmentIndex++\n } else if (globChar === '*') {\n // Repeated `*` in one segment is still one segment-local wildcard.\n while (glob[globIndex + 1] === '*') globIndex++\n starIndex = globIndex\n starMatchIndex = segmentIndex\n globIndex++\n } else if (starIndex !== -1) {\n globIndex = starIndex + 1\n starMatchIndex++\n segmentIndex = starMatchIndex\n } else {\n return false\n }\n }\n\n while (glob[globIndex] === '*') globIndex++\n return globIndex === glob.length\n}\n\nfunction literalPrefixForGlob(glob: string): string {\n const segments = splitPath(glob)\n const literalSegments: string[] = []\n let firstWildcardIndex = segments.length\n\n for (const [index, segment] of segments.entries()) {\n if (segment === '**' || hasGlobWildcard(segment)) {\n firstWildcardIndex = index\n break\n }\n literalSegments.push(segment)\n }\n\n if (literalSegments.length === 0) return ''\n const prefix = literalSegments.join('/')\n const wildcardTail = segments.slice(firstWildcardIndex)\n const tailMayMatchBarePrefix =\n wildcardTail.length > 0 && wildcardTail.every((segment) => segment === '**')\n if (tailMayMatchBarePrefix) return prefix\n return literalSegments.length < segments.length ? `${prefix}/` : prefix\n}\n\nfunction commonLiteralPrefix(a: string, b: string): string {\n let end = 0\n const max = Math.min(a.length, b.length)\n while (end < max && a[end] === b[end]) end++\n return trimTrailingHighSurrogate(a.slice(0, end))\n}\n\nfunction trimTrailingHighSurrogate(value: string): string {\n const lastCodeUnit = value.charCodeAt(value.length - 1)\n return lastCodeUnit >= 0xd800 && lastCodeUnit <= 0xdbff ? value.slice(0, -1) : value\n}\n\nfunction hasGlobWildcard(glob: string): boolean {\n return glob.includes('*') || glob.includes('?')\n}\n\nfunction patternIsRegExp(pattern: SyncFilterPattern): pattern is RegExp {\n return typeof pattern !== 'string'\n}\n\nfunction splitPath(path: string): string[] {\n if (path === '') return []\n return path.split('/').filter((segment) => segment !== '')\n}\n\nfunction normalizePath(path: string): string {\n let normalized = path.split('\\\\').join('/')\n while (normalized.startsWith('./')) normalized = normalized.slice(2)\n while (normalized.startsWith('/')) normalized = normalized.slice(1)\n while (normalized.endsWith('/') && normalized.length > 1) {\n normalized = normalized.slice(0, -1)\n }\n return normalized\n}\n"],"mappings":"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"}
|
package/dist/sync/filters.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filters.js","names":[],"sources":["../../src/sync/filters.ts"],"sourcesContent":["import {\n pathExceedsSafeRegExpInput,\n regexpMatchesSyncPath,\n validateSyncFilters,\n} from './regexp-safety.ts'\nimport { emitScannerSkip, regexpInputTooLongSkip } from './scan-events.ts'\nimport type { SyncFilterOptions, SyncFilterPattern, SyncPath, SyncScanOptions } from './types.ts'\n\n/**\n * Tests whether a relative sync path is included by the configured include/exclude filters.\n * Exclude filters win over include filters when both match the same path.\n *\n * @param relativePath - Folder-relative path using forward slashes.\n * @param filters - Optional include and exclude filters.\n *\n * @returns True when the path should remain in the sync scan.\n */\nexport function pathPassesSyncFilters(\n relativePath: string,\n filters: SyncFilterOptions | undefined,\n): boolean {\n validateSyncFilters(filters)\n const path = normalizePath(relativePath)\n\n if (normalizedPathSkippedByRegExpInputLimit(path, filters)) return false\n\n const include = filters?.include ?? []\n const exclude = filters?.exclude ?? []\n\n if (include.length > 0) {\n if (!include.some((pattern) => matchesPattern(path, pattern))) return false\n }\n\n return !exclude.some((pattern) => matchesPattern(path, pattern))\n}\n\n/**\n * Tests whether a directory may contain paths admitted by the configured filters.\n *\n * @param relativePath - Folder-relative directory path using forward slashes.\n * @param filters - Optional include and exclude filters.\n *\n * @returns True when the scanner should descend into the directory.\n */\nexport function directoryMayContainSyncPaths(\n relativePath: string,\n filters: SyncFilterOptions | undefined,\n): boolean {\n validateSyncFilters(filters)\n const path = normalizePath(relativePath)\n if (path === '') return true\n\n const exclude = filters?.exclude ?? []\n if (exclude.some((pattern) => stringPatternExcludesAllDescendants(path, pattern))) {\n return false\n }\n\n const include = filters?.include ?? []\n return include.length === 0 || include.some((pattern) => patternMayMatchDescendant(path, pattern))\n}\n\n/**\n * Returns the safe literal prefix that B2 listing can use for include filters.\n * Exclude filters are not considered because they cannot narrow a B2 prefix.\n *\n * @param filters - Optional include and exclude filters.\n *\n * @returns A folder-relative literal prefix, or an empty string when no safe narrowing exists.\n */\nexport function literalPrefixForSyncFilters(filters: SyncFilterOptions | undefined): string {\n validateSyncFilters(filters)\n const include = filters?.include ?? []\n let commonPrefix: string | undefined\n\n for (const pattern of include) {\n if (patternIsRegExp(pattern)) return ''\n\n const glob = normalizePath(pattern)\n if (!glob.includes('/')) return ''\n\n const prefix = literalPrefixForGlob(glob)\n if (prefix === '') return ''\n commonPrefix = commonPrefix === undefined ? prefix : commonLiteralPrefix(commonPrefix, prefix)\n }\n\n return commonPrefix ?? ''\n}\n\n/**\n * Filters an async iterable of sync paths while preserving the original item type.\n *\n * @typeParam T - Concrete sync path shape yielded by the source folder.\n *\n * @param paths - Async iterable of folder scan results.\n * @param filters - Optional include and exclude filters.\n *\n * @returns A filtered async generator of sync paths.\n */\nexport async function* filterSyncPaths<T extends SyncPath>(\n paths: AsyncIterable<T>,\n filters: SyncScanOptions | undefined,\n): AsyncGenerator<T> {\n for await (const path of paths) {\n if (pathPassesSyncFilters(path.relativePath, filters)) {\n yield path\n } else if (pathSkippedByRegExpInputLimit(path.relativePath, filters)) {\n emitScannerSkip(filters, regexpInputTooLongSkip(normalizePath(path.relativePath)))\n }\n }\n}\n\n/**\n * Tests whether a path is skipped because RegExp filters are configured and the normalized path\n * exceeds the SDK RegExp input guard.\n *\n * @param relativePath - Folder-relative path using forward slashes.\n * @param filters - Optional include and exclude filters.\n *\n * @returns True when any RegExp filter is present and the path is too long to evaluate.\n */\nexport function pathSkippedByRegExpInputLimit(\n relativePath: string,\n filters: SyncFilterOptions | undefined,\n): boolean {\n validateSyncFilters(filters)\n return normalizedPathSkippedByRegExpInputLimit(normalizePath(relativePath), filters)\n}\n\nfunction normalizedPathSkippedByRegExpInputLimit(\n normalizedPath: string,\n filters: SyncFilterOptions | undefined,\n): boolean {\n if (!pathExceedsSafeRegExpInput(normalizedPath) || !filtersContainRegExp(filters)) {\n return false\n }\n return true\n}\n\nfunction matchesPattern(relativePath: string, pattern: SyncFilterPattern): boolean {\n if (patternIsRegExp(pattern)) {\n return regexpMatchesSyncPath(relativePath, pattern)\n }\n\n const glob = normalizePath(pattern)\n if (glob === '') return relativePath === ''\n\n const segments = splitPath(relativePath)\n if (!glob.includes('/')) {\n return segments.some((segment) => matchSegmentGlob(segment, glob))\n }\n\n return matchPathGlob(segments, splitPath(glob))\n}\n\nfunction stringPatternExcludesAllDescendants(\n relativePath: string,\n pattern: SyncFilterPattern,\n): boolean {\n if (patternIsRegExp(pattern)) return false\n\n const glob = normalizePath(pattern)\n if (glob === '') return false\n if (!glob.includes('/')) return matchesPattern(relativePath, pattern)\n\n const globSegments = splitPath(glob)\n return globSegments.at(-1) === '**' && matchPathGlob(splitPath(relativePath), globSegments)\n}\n\nfunction filtersContainRegExp(filters: SyncFilterOptions | undefined): boolean {\n return (\n filters?.include?.some(patternIsRegExp) === true ||\n filters?.exclude?.some(patternIsRegExp) === true\n )\n}\n\nfunction patternMayMatchDescendant(relativePath: string, pattern: SyncFilterPattern): boolean {\n if (patternIsRegExp(pattern)) return true\n\n const glob = normalizePath(pattern)\n if (glob === '' || !glob.includes('/')) return true\n\n const pathSegments = splitPath(relativePath)\n const globSegments = splitPath(glob)\n const length = Math.min(pathSegments.length, globSegments.length)\n\n for (let i = 0; i < length; i++) {\n const globSegment = globSegments[i]\n if (globSegment === '**' || globSegment === undefined || hasGlobWildcard(globSegment)) {\n return true\n }\n if (globSegment !== pathSegments[i]) {\n return false\n }\n }\n\n return true\n}\n\nfunction matchPathGlob(pathSegments: readonly string[], globSegments: readonly string[]): boolean {\n let reachable = new Array<boolean>(pathSegments.length + 1).fill(false)\n reachable[0] = true\n\n for (const globSegment of globSegments) {\n const next = new Array<boolean>(pathSegments.length + 1).fill(false)\n\n if (globSegment === '**') {\n // A whole-segment `**` consumes zero or more complete path segments.\n let canReach = false\n for (let i = 0; i <= pathSegments.length; i++) {\n canReach = canReach || reachable[i] === true\n next[i] = canReach\n }\n } else {\n for (let i = 0; i < pathSegments.length; i++) {\n if (reachable[i] === true && matchSegmentGlob(pathSegments[i] ?? '', globSegment)) {\n next[i + 1] = true\n }\n }\n }\n\n reachable = next\n }\n\n return reachable[pathSegments.length] === true\n}\n\nfunction matchSegmentGlob(segment: string, glob: string): boolean {\n let segmentIndex = 0\n let globIndex = 0\n let starIndex = -1\n let starMatchIndex = 0\n\n while (segmentIndex < segment.length) {\n const globChar = glob[globIndex]\n if (globChar === '?' || globChar === segment[segmentIndex]) {\n globIndex++\n segmentIndex++\n } else if (globChar === '*') {\n // Repeated `*` in one segment is still one segment-local wildcard.\n while (glob[globIndex + 1] === '*') globIndex++\n starIndex = globIndex\n starMatchIndex = segmentIndex\n globIndex++\n } else if (starIndex !== -1) {\n globIndex = starIndex + 1\n starMatchIndex++\n segmentIndex = starMatchIndex\n } else {\n return false\n }\n }\n\n while (glob[globIndex] === '*') globIndex++\n return globIndex === glob.length\n}\n\nfunction literalPrefixForGlob(glob: string): string {\n const segments = splitPath(glob)\n const literalSegments: string[] = []\n let firstWildcardIndex = segments.length\n\n for (const [index, segment] of segments.entries()) {\n if (segment === '**' || hasGlobWildcard(segment)) {\n firstWildcardIndex = index\n break\n }\n literalSegments.push(segment)\n }\n\n if (literalSegments.length === 0) return ''\n const prefix = literalSegments.join('/')\n const wildcardTail = segments.slice(firstWildcardIndex)\n const tailMayMatchBarePrefix =\n wildcardTail.length > 0 && wildcardTail.every((segment) => segment === '**')\n if (tailMayMatchBarePrefix) return prefix\n return literalSegments.length < segments.length ? `${prefix}/` : prefix\n}\n\nfunction commonLiteralPrefix(a: string, b: string): string {\n let end = 0\n const max = Math.min(a.length, b.length)\n while (end < max && a[end] === b[end]) end++\n return trimTrailingHighSurrogate(a.slice(0, end))\n}\n\nfunction trimTrailingHighSurrogate(value: string): string {\n const lastCodeUnit = value.charCodeAt(value.length - 1)\n return lastCodeUnit >= 0xd800 && lastCodeUnit <= 0xdbff ? value.slice(0, -1) : value\n}\n\nfunction hasGlobWildcard(glob: string): boolean {\n return glob.includes('*') || glob.includes('?')\n}\n\nfunction patternIsRegExp(pattern: SyncFilterPattern): pattern is RegExp {\n return typeof pattern !== 'string'\n}\n\nfunction splitPath(path: string): string[] {\n if (path === '') return []\n return path.split('/').filter((segment) => segment !== '')\n}\n\nfunction normalizePath(path: string): string {\n let normalized = path.split('\\\\').join('/')\n while (normalized.startsWith('./')) normalized = normalized.slice(2)\n while (normalized.startsWith('/')) normalized = normalized.slice(1)\n while (normalized.endsWith('/') && normalized.length > 1) {\n normalized = normalized.slice(0, -1)\n }\n return normalized\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"filters.js","names":[],"sources":["../../src/sync/filters.ts"],"sourcesContent":["import {\n pathExceedsSafeRegExpInput,\n regexpMatchesSyncPath,\n validateSyncFilters,\n} from './regexp-safety.ts'\nimport { emitScannerSkip, regexpInputTooLongSkip } from './scan-events.ts'\nimport type { SyncFilterOptions, SyncFilterPattern, SyncPath, SyncScanOptions } from './types.ts'\n\n/**\n * Tests whether a relative sync path is included by the configured include/exclude filters.\n * Exclude filters win over include filters when both match the same path.\n *\n * @param relativePath - Folder-relative path using forward slashes.\n * @param filters - Optional include and exclude filters.\n *\n * @returns True when the path should remain in the sync scan.\n */\nexport function pathPassesSyncFilters(\n relativePath: string,\n filters: SyncFilterOptions | undefined,\n): boolean {\n validateSyncFilters(filters)\n const path = normalizePath(relativePath)\n\n if (normalizedPathSkippedByRegExpInputLimit(path, filters)) return false\n\n const include = filters?.include ?? []\n const exclude = filters?.exclude ?? []\n\n if (include.length > 0) {\n if (!include.some((pattern) => matchesPattern(path, pattern))) return false\n }\n\n return !exclude.some((pattern) => matchesPattern(path, pattern))\n}\n\n/**\n * Tests whether a directory may contain paths admitted by the configured filters.\n *\n * @param relativePath - Folder-relative directory path using forward slashes.\n * @param filters - Optional include and exclude filters.\n *\n * @returns True when the scanner should descend into the directory.\n */\nexport function directoryMayContainSyncPaths(\n relativePath: string,\n filters: SyncFilterOptions | undefined,\n): boolean {\n validateSyncFilters(filters)\n const path = normalizePath(relativePath)\n if (path === '') return true\n\n const exclude = filters?.exclude ?? []\n if (exclude.some((pattern) => stringPatternExcludesAllDescendants(path, pattern))) {\n return false\n }\n\n const include = filters?.include ?? []\n return include.length === 0 || include.some((pattern) => patternMayMatchDescendant(path, pattern))\n}\n\n/**\n * Returns the safe literal prefix that B2 listing can use for include filters.\n * Exclude filters are not considered because they cannot narrow a B2 prefix.\n *\n * @param filters - Optional include and exclude filters.\n *\n * @returns A folder-relative literal prefix, or an empty string when no safe narrowing exists.\n */\nexport function literalPrefixForSyncFilters(filters: SyncFilterOptions | undefined): string {\n validateSyncFilters(filters)\n const include = filters?.include ?? []\n let commonPrefix: string | undefined\n\n for (const pattern of include) {\n if (patternIsRegExp(pattern)) return ''\n\n const glob = normalizePath(pattern)\n if (!glob.includes('/')) return ''\n\n const prefix = literalPrefixForGlob(glob)\n if (prefix === '') return ''\n commonPrefix = commonPrefix === undefined ? prefix : commonLiteralPrefix(commonPrefix, prefix)\n }\n\n return commonPrefix ?? ''\n}\n\n/**\n * Filters an async iterable of sync paths while preserving the original item type.\n *\n * @typeParam T - Concrete sync path shape yielded by the source folder.\n *\n * @param paths - Async iterable of folder scan results.\n * @param filters - Optional include and exclude filters.\n *\n * @returns A filtered async generator of sync paths.\n */\nexport async function* filterSyncPaths<T extends SyncPath>(\n paths: AsyncIterable<T>,\n filters: SyncScanOptions | undefined,\n): AsyncGenerator<T> {\n for await (const path of paths) {\n if (pathPassesSyncFilters(path.relativePath, filters)) {\n yield path\n } else if (pathSkippedByRegExpInputLimit(path.relativePath, filters)) {\n emitScannerSkip(filters, regexpInputTooLongSkip(normalizePath(path.relativePath)))\n }\n }\n}\n\n/**\n * Tests whether a path is skipped because RegExp filters are configured and the normalized path\n * exceeds the SDK RegExp input guard.\n *\n * @param relativePath - Folder-relative path using forward slashes.\n * @param filters - Optional include and exclude filters.\n *\n * @returns True when any RegExp filter is present and the path is too long to evaluate.\n */\nexport function pathSkippedByRegExpInputLimit(\n relativePath: string,\n filters: SyncFilterOptions | undefined,\n): boolean {\n validateSyncFilters(filters)\n return normalizedPathSkippedByRegExpInputLimit(normalizePath(relativePath), filters)\n}\n\nfunction normalizedPathSkippedByRegExpInputLimit(\n normalizedPath: string,\n filters: SyncFilterOptions | undefined,\n): boolean {\n if (!pathExceedsSafeRegExpInput(normalizedPath) || !filtersContainRegExp(filters)) {\n return false\n }\n return true\n}\n\nfunction matchesPattern(relativePath: string, pattern: SyncFilterPattern): boolean {\n if (patternIsRegExp(pattern)) {\n return regexpMatchesSyncPath(relativePath, pattern)\n }\n\n const glob = normalizePath(pattern)\n if (glob === '') return relativePath === ''\n\n const segments = splitPath(relativePath)\n if (!glob.includes('/')) {\n return segments.some((segment) => matchSegmentGlob(segment, glob))\n }\n\n return matchPathGlob(segments, splitPath(glob))\n}\n\nfunction stringPatternExcludesAllDescendants(\n relativePath: string,\n pattern: SyncFilterPattern,\n): boolean {\n if (patternIsRegExp(pattern)) return false\n\n const glob = normalizePath(pattern)\n if (glob === '') return false\n if (!glob.includes('/')) return matchesPattern(relativePath, pattern)\n\n const globSegments = splitPath(glob)\n return globSegments.at(-1) === '**' && matchPathGlob(splitPath(relativePath), globSegments)\n}\n\nfunction filtersContainRegExp(filters: SyncFilterOptions | undefined): boolean {\n return (\n filters?.include?.some(patternIsRegExp) === true ||\n filters?.exclude?.some(patternIsRegExp) === true\n )\n}\n\nfunction patternMayMatchDescendant(relativePath: string, pattern: SyncFilterPattern): boolean {\n if (patternIsRegExp(pattern)) return true\n\n const glob = normalizePath(pattern)\n if (glob === '' || !glob.includes('/')) return true\n\n const pathSegments = splitPath(relativePath)\n const globSegments = splitPath(glob)\n const length = Math.min(pathSegments.length, globSegments.length)\n\n for (let i = 0; i < length; i++) {\n const globSegment = globSegments[i]\n if (globSegment === '**' || globSegment === undefined || hasGlobWildcard(globSegment)) {\n return true\n }\n if (globSegment !== pathSegments[i]) {\n return false\n }\n }\n\n return true\n}\n\nfunction matchPathGlob(pathSegments: readonly string[], globSegments: readonly string[]): boolean {\n let reachable = new Array<boolean>(pathSegments.length + 1).fill(false)\n reachable[0] = true\n\n for (const globSegment of globSegments) {\n const next = new Array<boolean>(pathSegments.length + 1).fill(false)\n\n if (globSegment === '**') {\n // A whole-segment `**` consumes zero or more complete path segments.\n let canReach = false\n for (let i = 0; i <= pathSegments.length; i++) {\n canReach = canReach || reachable[i] === true\n next[i] = canReach\n }\n } else {\n for (let i = 0; i < pathSegments.length; i++) {\n if (reachable[i] === true && matchSegmentGlob(pathSegments[i] ?? '', globSegment)) {\n next[i + 1] = true\n }\n }\n }\n\n reachable = next\n }\n\n return reachable[pathSegments.length] === true\n}\n\nfunction matchSegmentGlob(segment: string, glob: string): boolean {\n let segmentIndex = 0\n let globIndex = 0\n let starIndex = -1\n let starMatchIndex = 0\n\n while (segmentIndex < segment.length) {\n const globChar = glob[globIndex]\n if (globChar === '?' || globChar === segment[segmentIndex]) {\n globIndex++\n segmentIndex++\n } else if (globChar === '*') {\n // Repeated `*` in one segment is still one segment-local wildcard.\n while (glob[globIndex + 1] === '*') globIndex++\n starIndex = globIndex\n starMatchIndex = segmentIndex\n globIndex++\n } else if (starIndex !== -1) {\n globIndex = starIndex + 1\n starMatchIndex++\n segmentIndex = starMatchIndex\n } else {\n return false\n }\n }\n\n while (glob[globIndex] === '*') globIndex++\n return globIndex === glob.length\n}\n\nfunction literalPrefixForGlob(glob: string): string {\n const segments = splitPath(glob)\n const literalSegments: string[] = []\n let firstWildcardIndex = segments.length\n\n for (const [index, segment] of segments.entries()) {\n if (segment === '**' || hasGlobWildcard(segment)) {\n firstWildcardIndex = index\n break\n }\n literalSegments.push(segment)\n }\n\n if (literalSegments.length === 0) return ''\n const prefix = literalSegments.join('/')\n const wildcardTail = segments.slice(firstWildcardIndex)\n const tailMayMatchBarePrefix =\n wildcardTail.length > 0 && wildcardTail.every((segment) => segment === '**')\n if (tailMayMatchBarePrefix) return prefix\n return literalSegments.length < segments.length ? `${prefix}/` : prefix\n}\n\nfunction commonLiteralPrefix(a: string, b: string): string {\n let end = 0\n const max = Math.min(a.length, b.length)\n while (end < max && a[end] === b[end]) end++\n return trimTrailingHighSurrogate(a.slice(0, end))\n}\n\nfunction trimTrailingHighSurrogate(value: string): string {\n const lastCodeUnit = value.charCodeAt(value.length - 1)\n return lastCodeUnit >= 0xd800 && lastCodeUnit <= 0xdbff ? value.slice(0, -1) : value\n}\n\nfunction hasGlobWildcard(glob: string): boolean {\n return glob.includes('*') || glob.includes('?')\n}\n\nfunction patternIsRegExp(pattern: SyncFilterPattern): pattern is RegExp {\n return typeof pattern !== 'string'\n}\n\nfunction splitPath(path: string): string[] {\n if (path === '') return []\n return path.split('/').filter((segment) => segment !== '')\n}\n\nfunction normalizePath(path: string): string {\n let normalized = path.split('\\\\').join('/')\n while (normalized.startsWith('./')) normalized = normalized.slice(2)\n while (normalized.startsWith('/')) normalized = normalized.slice(1)\n while (normalized.endsWith('/') && normalized.length > 1) {\n normalized = normalized.slice(0, -1)\n }\n return normalized\n}\n"],"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: ${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(() => {});
|
|
@@ -99,7 +100,7 @@ async function writeLocalStreamInsideRoot(root, relPath, body, options) {
|
|
|
99
100
|
}
|
|
100
101
|
let stagingDirectory;
|
|
101
102
|
try {
|
|
102
|
-
stagingDirectory = await require_download_staging.createDownloadStagingDirectory(rootRealPath, path, randomUUID, statForDeviceCheck, localFileIoTestHooks.beforeStagingMarkerWrite);
|
|
103
|
+
stagingDirectory = await require_download_staging.createDownloadStagingDirectory(rootRealPath, path, randomUUID, statForDeviceCheck, localFileIoTestHooks.beforeStagingMarkerWrite, localFileIoTestHooks.downloadStagingActivityEntryLimit);
|
|
103
104
|
} catch (err) {
|
|
104
105
|
/* v8 ignore next -- best-effort close during setup failure */
|
|
105
106
|
await parentHandle?.close().catch(() => {});
|
|
@@ -157,7 +158,8 @@ async function writeLocalStreamInsideRoot(root, relPath, body, options) {
|
|
|
157
158
|
await writeHandle.close();
|
|
158
159
|
handle = void 0;
|
|
159
160
|
const [parentRealPathBeforeRename, parentStatsBeforeRename] = await Promise.all([realpath(path.dirname(destPath)), stat(path.dirname(destPath))]);
|
|
160
|
-
|
|
161
|
+
const finalPathBeforeRename = path.join(parentRealPathBeforeRename, path.basename(destPath));
|
|
162
|
+
require_path_safety.assertPathInsideRoot(rootRealPath, finalPathBeforeRename, path);
|
|
161
163
|
await localFileIoTestHooks.beforeFinalRename?.(parentRealPathBeforeRename);
|
|
162
164
|
let publishPath = finalWritePath;
|
|
163
165
|
if (anchoredParentPath === void 0) {
|
|
@@ -234,7 +236,8 @@ async function publishDownload(lstat, link, path, randomUUID, rename, rm, tmpPat
|
|
|
234
236
|
if (require_path_safety.hasErrorCode(err, "ENOENT")) throw new Error("local file changed before download: file missing");
|
|
235
237
|
throw err;
|
|
236
238
|
}
|
|
237
|
-
|
|
239
|
+
const backupStats = await lstat(backupPath);
|
|
240
|
+
require_local_file_identity.assertSameScannedRegularFile(backupStats, {
|
|
238
241
|
...expectedDestination,
|
|
239
242
|
absolutePath: backupPath
|
|
240
243
|
}, "download", {
|
|
@@ -322,7 +325,8 @@ async function deleteLocalFileInsideRoot(root, scannedPath) {
|
|
|
322
325
|
/* v8 ignore stop */
|
|
323
326
|
try {
|
|
324
327
|
const unlinkPath = anchoredParentPath === void 0 ? finalPath : path.join(anchoredParentPath, path.basename(expectedPath));
|
|
325
|
-
|
|
328
|
+
const stats = await lstat(unlinkPath);
|
|
329
|
+
require_local_file_identity.assertSameScannedRegularFile(stats, {
|
|
326
330
|
...scannedPath,
|
|
327
331
|
absolutePath: unlinkPath
|
|
328
332
|
}, "delete", { platform: localFileIoTestHooks.platform });
|
|
@@ -332,7 +336,8 @@ async function deleteLocalFileInsideRoot(root, scannedPath) {
|
|
|
332
336
|
const [parentRealPathBeforeUnlink, parentStatsBeforeUnlink] = await Promise.all([realpath(path.dirname(expectedPath)), stat(path.dirname(expectedPath))]);
|
|
333
337
|
if (parentRealPathBeforeUnlink !== parentRealPath || !sameParentIdentity(parentStatsBeforeUnlink, parentStats)) throw new Error("unsafe local delete path: parent changed before unlink");
|
|
334
338
|
}
|
|
335
|
-
|
|
339
|
+
const finalStats = await lstat(unlinkPath);
|
|
340
|
+
require_local_file_identity.assertSameScannedRegularFile(finalStats, {
|
|
336
341
|
...scannedPath,
|
|
337
342
|
absolutePath: unlinkPath
|
|
338
343
|
}, "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 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 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 localFileIoTestHooks.downloadStagingActivityEntryLimit,\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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"}
|
|
@@ -16,6 +16,7 @@ export declare const localFileIoTestHooks: {
|
|
|
16
16
|
beforeLocalDeleteUnlink?: (path: string) => Promise<void> | void;
|
|
17
17
|
beforeStagingMarkerWrite?: (path: string) => Promise<void> | void;
|
|
18
18
|
disableProcFdAnchoring?: boolean;
|
|
19
|
+
downloadStagingActivityEntryLimit?: number;
|
|
19
20
|
platform?: string;
|
|
20
21
|
statForDeviceCheck?: DeviceStatFn;
|
|
21
22
|
};
|
|
@@ -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":"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,iCAAiC,CAAC,EAAE,MAAM,CAAA;IAC1C,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,CAwNf;AA2JD;;;;;;;GAOG;AACH,wBAAsB,yBAAyB,CAC7C,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,aAAa,GACzB,OAAO,CAAC,IAAI,CAAC,CAoGf"}
|
|
@@ -16,6 +16,7 @@ export declare const localFileIoTestHooks: {
|
|
|
16
16
|
beforeLocalDeleteUnlink?: (path: string) => Promise<void> | void;
|
|
17
17
|
beforeStagingMarkerWrite?: (path: string) => Promise<void> | void;
|
|
18
18
|
disableProcFdAnchoring?: boolean;
|
|
19
|
+
downloadStagingActivityEntryLimit?: number;
|
|
19
20
|
platform?: string;
|
|
20
21
|
statForDeviceCheck?: DeviceStatFn;
|
|
21
22
|
};
|
|
@@ -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":"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,iCAAiC,CAAC,EAAE,MAAM,CAAA;IAC1C,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,CAwNf;AA2JD;;;;;;;GAOG;AACH,wBAAsB,yBAAyB,CAC7C,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,aAAa,GACzB,OAAO,CAAC,IAAI,CAAC,CAoGf"}
|
|
@@ -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(() => {});
|
|
@@ -99,7 +100,7 @@ async function writeLocalStreamInsideRoot(root, relPath, body, options) {
|
|
|
99
100
|
}
|
|
100
101
|
let stagingDirectory;
|
|
101
102
|
try {
|
|
102
|
-
stagingDirectory = await createDownloadStagingDirectory(rootRealPath, path, randomUUID, statForDeviceCheck, localFileIoTestHooks.beforeStagingMarkerWrite);
|
|
103
|
+
stagingDirectory = await createDownloadStagingDirectory(rootRealPath, path, randomUUID, statForDeviceCheck, localFileIoTestHooks.beforeStagingMarkerWrite, localFileIoTestHooks.downloadStagingActivityEntryLimit);
|
|
103
104
|
} catch (err) {
|
|
104
105
|
/* v8 ignore next -- best-effort close during setup failure */
|
|
105
106
|
await parentHandle?.close().catch(() => {});
|
|
@@ -157,7 +158,8 @@ async function writeLocalStreamInsideRoot(root, relPath, body, options) {
|
|
|
157
158
|
await writeHandle.close();
|
|
158
159
|
handle = void 0;
|
|
159
160
|
const [parentRealPathBeforeRename, parentStatsBeforeRename] = await Promise.all([realpath(path.dirname(destPath)), stat(path.dirname(destPath))]);
|
|
160
|
-
|
|
161
|
+
const finalPathBeforeRename = path.join(parentRealPathBeforeRename, path.basename(destPath));
|
|
162
|
+
assertPathInsideRoot(rootRealPath, finalPathBeforeRename, path);
|
|
161
163
|
await localFileIoTestHooks.beforeFinalRename?.(parentRealPathBeforeRename);
|
|
162
164
|
let publishPath = finalWritePath;
|
|
163
165
|
if (anchoredParentPath === void 0) {
|
|
@@ -234,7 +236,8 @@ async function publishDownload(lstat, link, path, randomUUID, rename, rm, tmpPat
|
|
|
234
236
|
if (hasErrorCode(err, "ENOENT")) throw new Error("local file changed before download: file missing");
|
|
235
237
|
throw err;
|
|
236
238
|
}
|
|
237
|
-
|
|
239
|
+
const backupStats = await lstat(backupPath);
|
|
240
|
+
assertSameScannedRegularFile(backupStats, {
|
|
238
241
|
...expectedDestination,
|
|
239
242
|
absolutePath: backupPath
|
|
240
243
|
}, "download", {
|
|
@@ -322,7 +325,8 @@ async function deleteLocalFileInsideRoot(root, scannedPath) {
|
|
|
322
325
|
/* v8 ignore stop */
|
|
323
326
|
try {
|
|
324
327
|
const unlinkPath = anchoredParentPath === void 0 ? finalPath : path.join(anchoredParentPath, path.basename(expectedPath));
|
|
325
|
-
|
|
328
|
+
const stats = await lstat(unlinkPath);
|
|
329
|
+
assertSameScannedRegularFile(stats, {
|
|
326
330
|
...scannedPath,
|
|
327
331
|
absolutePath: unlinkPath
|
|
328
332
|
}, "delete", { platform: localFileIoTestHooks.platform });
|
|
@@ -332,7 +336,8 @@ async function deleteLocalFileInsideRoot(root, scannedPath) {
|
|
|
332
336
|
const [parentRealPathBeforeUnlink, parentStatsBeforeUnlink] = await Promise.all([realpath(path.dirname(expectedPath)), stat(path.dirname(expectedPath))]);
|
|
333
337
|
if (parentRealPathBeforeUnlink !== parentRealPath || !sameParentIdentity(parentStatsBeforeUnlink, parentStats)) throw new Error("unsafe local delete path: parent changed before unlink");
|
|
334
338
|
}
|
|
335
|
-
|
|
339
|
+
const finalStats = await lstat(unlinkPath);
|
|
340
|
+
assertSameScannedRegularFile(finalStats, {
|
|
336
341
|
...scannedPath,
|
|
337
342
|
absolutePath: unlinkPath
|
|
338
343
|
}, "delete", { platform: localFileIoTestHooks.platform });
|