@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
package/dist/simulator/index.cjs
CHANGED
|
@@ -3,17 +3,83 @@ const require_file = require("../types/file.cjs");
|
|
|
3
3
|
const require_ids = require("../types/ids.cjs");
|
|
4
4
|
const require_text_codec = require("../util/text-codec.cjs");
|
|
5
5
|
const require_encoding = require("../raw/encoding.cjs");
|
|
6
|
+
const require_download = require("../types/download.cjs");
|
|
7
|
+
const require_md5 = require("../util/md5.cjs");
|
|
8
|
+
const require_encryption = require("../types/encryption.cjs");
|
|
9
|
+
const require_lock = require("../types/lock.cjs");
|
|
6
10
|
const require_crypto = require("../util/crypto.cjs");
|
|
7
11
|
const require_hash = require("../streams/hash.cjs");
|
|
8
12
|
const require_bucket = require("../types/bucket.cjs");
|
|
9
|
-
const require_encryption = require("../types/encryption.cjs");
|
|
10
13
|
const require_to_error = require("../util/to-error.cjs");
|
|
14
|
+
const require_url = require("../raw/url.cjs");
|
|
11
15
|
const require_auth = require("../types/auth.cjs");
|
|
16
|
+
const require_partner = require("./partner.cjs");
|
|
12
17
|
const require_capabilities = require("./capabilities.cjs");
|
|
13
18
|
const require_b2_naming = require("../internal/b2-naming.cjs");
|
|
14
19
|
const require_validation = require("./validation.cjs");
|
|
15
20
|
//#region src/simulator/index.ts
|
|
16
|
-
var UPLOAD_TOKEN_SIGNING_KEY =
|
|
21
|
+
var UPLOAD_TOKEN_SIGNING_KEY = [
|
|
22
|
+
"b2 sdk typescript",
|
|
23
|
+
"simulator upload authorization",
|
|
24
|
+
"v1"
|
|
25
|
+
].join(":");
|
|
26
|
+
var SIMULATOR_MASTER_APPLICATION_KEY_ID = "master-key-id";
|
|
27
|
+
var SIMULATOR_MASTER_APPLICATION_KEY = "master-key";
|
|
28
|
+
var SIMULATOR_TEST_APPLICATION_KEY_ID = "test-key-id";
|
|
29
|
+
var SIMULATOR_TEST_APPLICATION_KEY = "test-key";
|
|
30
|
+
var SIMULATOR_APPLICATION_KEY_ID_PREFIX = "sim_key_";
|
|
31
|
+
var SIMULATOR_MASTER_CAPABILITIES = [
|
|
32
|
+
require_auth.Capability.ListBuckets,
|
|
33
|
+
require_auth.Capability.ListAllBucketNames,
|
|
34
|
+
require_auth.Capability.ReadBuckets,
|
|
35
|
+
require_auth.Capability.WriteBuckets,
|
|
36
|
+
require_auth.Capability.DeleteBuckets,
|
|
37
|
+
require_auth.Capability.ReadBucketEncryption,
|
|
38
|
+
require_auth.Capability.WriteBucketEncryption,
|
|
39
|
+
require_auth.Capability.ReadBucketReplications,
|
|
40
|
+
require_auth.Capability.WriteBucketReplications,
|
|
41
|
+
require_auth.Capability.ReadBucketNotifications,
|
|
42
|
+
require_auth.Capability.WriteBucketNotifications,
|
|
43
|
+
require_auth.Capability.ReadBucketLogging,
|
|
44
|
+
require_auth.Capability.WriteBucketLogging,
|
|
45
|
+
require_auth.Capability.ReadBucketLifecycleRules,
|
|
46
|
+
require_auth.Capability.WriteBucketLifecycleRules,
|
|
47
|
+
require_auth.Capability.ListFiles,
|
|
48
|
+
require_auth.Capability.ReadFiles,
|
|
49
|
+
require_auth.Capability.WriteFiles,
|
|
50
|
+
require_auth.Capability.DeleteFiles,
|
|
51
|
+
require_auth.Capability.ListKeys,
|
|
52
|
+
require_auth.Capability.WriteKeys,
|
|
53
|
+
require_auth.Capability.DeleteKeys,
|
|
54
|
+
require_auth.Capability.ShareFiles
|
|
55
|
+
];
|
|
56
|
+
function apiPathParts(path) {
|
|
57
|
+
const segments = path.split("/").filter((segment) => segment.length > 0);
|
|
58
|
+
const candidate = segments.at(-2);
|
|
59
|
+
return {
|
|
60
|
+
endpoint: segments.at(-1) ?? "",
|
|
61
|
+
version: candidate !== void 0 && require_url.isB2ApiVersion(candidate) ? candidate : "v3"
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
function parseBasicAuthorizationHeader(authzHeader) {
|
|
65
|
+
if (!authzHeader?.startsWith("Basic ")) return null;
|
|
66
|
+
const decoded = (() => {
|
|
67
|
+
try {
|
|
68
|
+
return atob(authzHeader.slice(6));
|
|
69
|
+
} catch {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
})();
|
|
73
|
+
if (decoded === null) return null;
|
|
74
|
+
const idx = decoded.indexOf(":");
|
|
75
|
+
if (idx <= 0 || idx === decoded.length - 1) return null;
|
|
76
|
+
return {
|
|
77
|
+
applicationKeyId: decoded.slice(0, idx),
|
|
78
|
+
applicationKey: decoded.slice(idx + 1)
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
var B2_MIN_PART_NUMBER = 1;
|
|
82
|
+
var B2_MAX_PART_NUMBER = 1e4;
|
|
17
83
|
function base64UrlEncode(bytes) {
|
|
18
84
|
let binary = "";
|
|
19
85
|
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
@@ -35,6 +101,11 @@ function timingSafeStringEqual(left, right) {
|
|
|
35
101
|
for (let i = 0; i < max; i++) mismatch |= (left.charCodeAt(i) || 0) ^ (right.charCodeAt(i) || 0);
|
|
36
102
|
return mismatch === 0;
|
|
37
103
|
}
|
|
104
|
+
var MS_PER_DAY = 864e5;
|
|
105
|
+
var MAX_JS_DATE_MS = 864e13;
|
|
106
|
+
function isValidRetentionTimestamp(timestamp, now = Date.now()) {
|
|
107
|
+
return Number.isFinite(timestamp) && timestamp <= MAX_JS_DATE_MS && timestamp > now;
|
|
108
|
+
}
|
|
38
109
|
/**
|
|
39
110
|
* Parse an RFC 7233 `Range` header value into inclusive start/end byte
|
|
40
111
|
* offsets clamped to the file size. Supports closed (`bytes=0-999`),
|
|
@@ -120,6 +191,51 @@ function compareB2FileNames(a, b) {
|
|
|
120
191
|
if (a > b) return 1;
|
|
121
192
|
return 0;
|
|
122
193
|
}
|
|
194
|
+
var DOWNLOAD_AUTH_TOKEN_BYTES = 32;
|
|
195
|
+
var DOWNLOAD_AUTH_TOKEN_PREFIX = "sim_dl_auth_";
|
|
196
|
+
var DOWNLOAD_AUTH_PURGE_BATCH_SIZE = 128;
|
|
197
|
+
var DOWNLOAD_RESPONSE_OVERRIDE_PARAMS = [
|
|
198
|
+
"b2ContentDisposition",
|
|
199
|
+
"b2ContentLanguage",
|
|
200
|
+
"b2ContentEncoding",
|
|
201
|
+
"b2ContentType",
|
|
202
|
+
"b2CacheControl",
|
|
203
|
+
"b2Expires"
|
|
204
|
+
];
|
|
205
|
+
var DOWNLOAD_RESPONSE_OVERRIDE_HEADERS = {
|
|
206
|
+
b2ContentDisposition: require_download.DownloadHeaderName.ContentDisposition,
|
|
207
|
+
b2ContentLanguage: require_download.DownloadHeaderName.ContentLanguage,
|
|
208
|
+
b2ContentEncoding: require_download.DownloadHeaderName.ContentEncoding,
|
|
209
|
+
b2ContentType: require_download.DownloadHeaderName.ContentType,
|
|
210
|
+
b2CacheControl: require_download.DownloadHeaderName.CacheControl,
|
|
211
|
+
b2Expires: require_download.DownloadHeaderName.Expires
|
|
212
|
+
};
|
|
213
|
+
function randomDownloadAuthorizationToken() {
|
|
214
|
+
const crypto = globalThis.crypto;
|
|
215
|
+
if (typeof crypto?.getRandomValues !== "function") throw new Error("Download authorization tokens require globalThis.crypto.getRandomValues");
|
|
216
|
+
const bytes = new Uint8Array(DOWNLOAD_AUTH_TOKEN_BYTES);
|
|
217
|
+
crypto.getRandomValues(bytes);
|
|
218
|
+
return `${DOWNLOAD_AUTH_TOKEN_PREFIX}${require_crypto.hexEncode(bytes)}`;
|
|
219
|
+
}
|
|
220
|
+
function pickDownloadResponseOverrides(req) {
|
|
221
|
+
const overrides = {};
|
|
222
|
+
for (const param of DOWNLOAD_RESPONSE_OVERRIDE_PARAMS) {
|
|
223
|
+
const value = req[param];
|
|
224
|
+
if (typeof value === "string") overrides[param] = value;
|
|
225
|
+
}
|
|
226
|
+
return overrides;
|
|
227
|
+
}
|
|
228
|
+
function validateDownloadResponseOverrides(req) {
|
|
229
|
+
for (const param of DOWNLOAD_RESPONSE_OVERRIDE_PARAMS) {
|
|
230
|
+
const value = req[param];
|
|
231
|
+
if (value === void 0 || typeof value === "string") continue;
|
|
232
|
+
return {
|
|
233
|
+
code: "bad_request",
|
|
234
|
+
message: `${param} must be a string`
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
123
239
|
function normalizeKeyBucketIds(req) {
|
|
124
240
|
return req.bucketIds === void 0 || req.bucketIds === null ? null : Object.freeze([...req.bucketIds]);
|
|
125
241
|
}
|
|
@@ -129,6 +245,18 @@ function singleBucketId(bucketIds) {
|
|
|
129
245
|
function cloneBucketIds(bucketIds) {
|
|
130
246
|
return bucketIds === null ? null : [...bucketIds];
|
|
131
247
|
}
|
|
248
|
+
function cloneCapabilities(capabilities) {
|
|
249
|
+
return [...capabilities];
|
|
250
|
+
}
|
|
251
|
+
function isImplicitStorageMasterCredential(credentials) {
|
|
252
|
+
return credentials.applicationKeyId === SIMULATOR_MASTER_APPLICATION_KEY_ID && credentials.applicationKey === SIMULATOR_MASTER_APPLICATION_KEY || credentials.applicationKeyId === SIMULATOR_TEST_APPLICATION_KEY_ID && credentials.applicationKey === SIMULATOR_TEST_APPLICATION_KEY;
|
|
253
|
+
}
|
|
254
|
+
function isImplicitPartnerMasterCredential(credentials) {
|
|
255
|
+
return credentials.applicationKeyId === SIMULATOR_MASTER_APPLICATION_KEY_ID && credentials.applicationKey === SIMULATOR_MASTER_APPLICATION_KEY;
|
|
256
|
+
}
|
|
257
|
+
function isSimulatorApplicationKeyId(applicationKeyId) {
|
|
258
|
+
return applicationKeyId.startsWith(SIMULATOR_APPLICATION_KEY_ID_PREFIX);
|
|
259
|
+
}
|
|
132
260
|
function hasOwnField(body, field) {
|
|
133
261
|
return typeof body === "object" && body !== null && Object.hasOwn(body, field);
|
|
134
262
|
}
|
|
@@ -137,10 +265,148 @@ function requestStringField(body, field) {
|
|
|
137
265
|
const value = body[field];
|
|
138
266
|
return typeof value === "string" ? value : void 0;
|
|
139
267
|
}
|
|
268
|
+
function requestRecord(body) {
|
|
269
|
+
return typeof body === "object" && body !== null && !Array.isArray(body) ? body : null;
|
|
270
|
+
}
|
|
271
|
+
function replicationDestinationBucketIds(body) {
|
|
272
|
+
const rules = requestRecord(requestRecord(requestRecord(body)?.["replicationConfiguration"])?.["asReplicationSource"])?.["replicationRules"];
|
|
273
|
+
if (!Array.isArray(rules)) return [];
|
|
274
|
+
const bucketIds = [];
|
|
275
|
+
for (const rule of rules) {
|
|
276
|
+
const destinationBucketId = requestRecord(rule)?.["destinationBucketId"];
|
|
277
|
+
if (typeof destinationBucketId === "string") bucketIds.push(destinationBucketId);
|
|
278
|
+
}
|
|
279
|
+
return [...new Set(bucketIds)];
|
|
280
|
+
}
|
|
281
|
+
function normalizeReplicationConfiguration(config) {
|
|
282
|
+
return {
|
|
283
|
+
asReplicationSource: config.asReplicationSource ?? null,
|
|
284
|
+
asReplicationDestination: config.asReplicationDestination ?? null
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
function validateBucketConfigurationFields(fields, options) {
|
|
288
|
+
if (fields.bucketInfo !== void 0) {
|
|
289
|
+
const infoError = require_validation.validateBucketInfo(fields.bucketInfo);
|
|
290
|
+
if (infoError) return infoError;
|
|
291
|
+
}
|
|
292
|
+
if (fields.corsRules !== void 0) {
|
|
293
|
+
const corsError = require_validation.validateCorsRules(fields.corsRules);
|
|
294
|
+
if (corsError) return corsError;
|
|
295
|
+
}
|
|
296
|
+
if (fields.lifecycleRules !== void 0) {
|
|
297
|
+
const lifecycleError = require_validation.validateLifecycleRules(fields.lifecycleRules);
|
|
298
|
+
if (lifecycleError) return lifecycleError;
|
|
299
|
+
}
|
|
300
|
+
if (fields.replicationConfiguration !== void 0) {
|
|
301
|
+
const replicationError = require_validation.validateReplicationConfiguration(fields.replicationConfiguration);
|
|
302
|
+
if (replicationError) return replicationError;
|
|
303
|
+
}
|
|
304
|
+
if (fields.defaultRetention !== void 0) {
|
|
305
|
+
const retentionError = require_validation.validateDefaultRetention(fields.defaultRetention);
|
|
306
|
+
if (retentionError) return retentionError;
|
|
307
|
+
if (requestRecord(fields.defaultRetention)?.["mode"] !== require_bucket.BucketRetentionMode.None && !options.objectLockEnabled) return {
|
|
308
|
+
code: "file_lock_not_enabled",
|
|
309
|
+
message: "Bucket must have Object Lock enabled to set defaultRetention"
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
function nextObjectLockEnabled(current, requested) {
|
|
315
|
+
if (requested === void 0) return current;
|
|
316
|
+
if (typeof requested !== "boolean") return {
|
|
317
|
+
code: "bad_request",
|
|
318
|
+
message: "fileLockEnabled must be a boolean"
|
|
319
|
+
};
|
|
320
|
+
if (current && requested === false) return {
|
|
321
|
+
code: "file_lock_conflict",
|
|
322
|
+
message: "Object Lock cannot be disabled"
|
|
323
|
+
};
|
|
324
|
+
return current || requested;
|
|
325
|
+
}
|
|
326
|
+
function requestHeaderValue(headers, name) {
|
|
327
|
+
const exact = headers[name];
|
|
328
|
+
if (exact !== void 0) return exact;
|
|
329
|
+
const lowerName = name.toLowerCase();
|
|
330
|
+
for (const [key, value] of Object.entries(headers)) if (key.toLowerCase() === lowerName) return value;
|
|
331
|
+
}
|
|
332
|
+
function parseContentLengthHeader(headers) {
|
|
333
|
+
const header = requestHeaderValue(headers, "content-length");
|
|
334
|
+
if (header === void 0) return { kind: "absent" };
|
|
335
|
+
if (!/^\d+$/.test(header)) return {
|
|
336
|
+
kind: "error",
|
|
337
|
+
message: `Content-Length must be a byte count: ${header}`
|
|
338
|
+
};
|
|
339
|
+
const expectedLength = Number(header);
|
|
340
|
+
if (!Number.isSafeInteger(expectedLength)) return {
|
|
341
|
+
kind: "error",
|
|
342
|
+
message: `Content-Length is too large: ${header}`
|
|
343
|
+
};
|
|
344
|
+
return {
|
|
345
|
+
kind: "ok",
|
|
346
|
+
expectedLength
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
function contentLengthMismatchMessage(expectedLength, actualLength) {
|
|
350
|
+
return `Content-Length ${expectedLength} does not match request body length ${actualLength}`;
|
|
351
|
+
}
|
|
140
352
|
function fileNames(...names) {
|
|
141
353
|
const present = names.filter((name) => name !== void 0);
|
|
142
354
|
return present.length > 0 ? present : void 0;
|
|
143
355
|
}
|
|
356
|
+
function queryParamsBody(params) {
|
|
357
|
+
const body = Object.create(null);
|
|
358
|
+
let hasParams = false;
|
|
359
|
+
for (const [key, value] of params) {
|
|
360
|
+
if (body[key] !== void 0) continue;
|
|
361
|
+
body[key] = value;
|
|
362
|
+
hasParams = true;
|
|
363
|
+
}
|
|
364
|
+
return hasParams ? body : null;
|
|
365
|
+
}
|
|
366
|
+
var JSON_GET_ENDPOINTS = /* @__PURE__ */ new Set([
|
|
367
|
+
"b2_authorize_account",
|
|
368
|
+
"b2_list_groups",
|
|
369
|
+
"b2_list_group_members",
|
|
370
|
+
"bz_list_computers"
|
|
371
|
+
]);
|
|
372
|
+
var JSON_POST_ENDPOINTS = /* @__PURE__ */ new Set([
|
|
373
|
+
"b2_create_group_member",
|
|
374
|
+
"b2_eject_group_member",
|
|
375
|
+
"b2_reserve_trial_create_account",
|
|
376
|
+
"bz_delete_computer",
|
|
377
|
+
"b2_create_bucket",
|
|
378
|
+
"b2_list_buckets",
|
|
379
|
+
"b2_delete_bucket",
|
|
380
|
+
"b2_update_bucket",
|
|
381
|
+
"b2_get_upload_url",
|
|
382
|
+
"b2_list_file_names",
|
|
383
|
+
"b2_list_file_versions",
|
|
384
|
+
"b2_get_file_info",
|
|
385
|
+
"b2_hide_file",
|
|
386
|
+
"b2_delete_file_version",
|
|
387
|
+
"b2_copy_file",
|
|
388
|
+
"b2_start_large_file",
|
|
389
|
+
"b2_get_upload_part_url",
|
|
390
|
+
"b2_finish_large_file",
|
|
391
|
+
"b2_cancel_large_file",
|
|
392
|
+
"b2_list_unfinished_large_files",
|
|
393
|
+
"b2_list_parts",
|
|
394
|
+
"b2_copy_part",
|
|
395
|
+
"b2_get_download_authorization",
|
|
396
|
+
"b2_create_key",
|
|
397
|
+
"b2_list_keys",
|
|
398
|
+
"b2_delete_key",
|
|
399
|
+
"b2_update_file_retention",
|
|
400
|
+
"b2_update_file_legal_hold",
|
|
401
|
+
"b2_get_bucket_notification_rules",
|
|
402
|
+
"b2_set_bucket_notification_rules"
|
|
403
|
+
]);
|
|
404
|
+
function jsonEndpointAllowsMethod(method, endpoint) {
|
|
405
|
+
const normalizedMethod = method.toUpperCase();
|
|
406
|
+
if (JSON_GET_ENDPOINTS.has(endpoint)) return normalizedMethod === "GET";
|
|
407
|
+
if (JSON_POST_ENDPOINTS.has(endpoint)) return normalizedMethod === "POST";
|
|
408
|
+
return true;
|
|
409
|
+
}
|
|
144
410
|
function notificationRulePrefixes(body) {
|
|
145
411
|
if (typeof body !== "object" || body === null) return void 0;
|
|
146
412
|
const rules = body["eventNotificationRules"];
|
|
@@ -157,6 +423,13 @@ function storedNotificationRulePrefixes(rules) {
|
|
|
157
423
|
function hasKeyManagementCapability(capabilities) {
|
|
158
424
|
return capabilities.some((capability) => capability === require_auth.Capability.ListKeys || capability === require_auth.Capability.WriteKeys || capability === require_auth.Capability.DeleteKeys);
|
|
159
425
|
}
|
|
426
|
+
function missingStoredKeyCapabilities(key, required) {
|
|
427
|
+
const capabilities = new Set(key.capabilities);
|
|
428
|
+
return required.filter((capability) => !capabilities.has(capability));
|
|
429
|
+
}
|
|
430
|
+
function storedKeyAllowsBucket(key, bucketId) {
|
|
431
|
+
return key.bucketIds === null || key.bucketIds.includes(bucketId);
|
|
432
|
+
}
|
|
160
433
|
function publicServerSideEncryption(encryption) {
|
|
161
434
|
if (encryption.mode === require_encryption.EncryptionMode.SseC) return {
|
|
162
435
|
mode: encryption.mode,
|
|
@@ -168,26 +441,164 @@ function publicServerSideEncryption(encryption) {
|
|
|
168
441
|
};
|
|
169
442
|
return encryption;
|
|
170
443
|
}
|
|
171
|
-
function
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
444
|
+
function customerKeyDigest(customerKey) {
|
|
445
|
+
let first = 2166136261;
|
|
446
|
+
let second = 668265261;
|
|
447
|
+
for (const byte of customerKey) {
|
|
448
|
+
first = Math.imul(first ^ byte, 16777619) >>> 0;
|
|
449
|
+
second = Math.imul(second ^ byte, 2246822507) >>> 0;
|
|
450
|
+
}
|
|
451
|
+
return `${first.toString(16).padStart(8, "0")}${second.toString(16).padStart(8, "0")}`;
|
|
452
|
+
}
|
|
453
|
+
var BASE64_ALPHABET = /^[A-Za-z0-9+/]+$/;
|
|
454
|
+
function normalizeSizedBase64(value, decodedByteLength) {
|
|
455
|
+
const paddedLength = Math.ceil(decodedByteLength / 3) * 4;
|
|
456
|
+
const noPaddingLength = Math.ceil(decodedByteLength * 8 / 6);
|
|
457
|
+
const padding = "=".repeat(paddedLength - noPaddingLength);
|
|
458
|
+
if (value.length === noPaddingLength && BASE64_ALPHABET.test(value)) return value.padEnd(paddedLength, "=");
|
|
459
|
+
if (value.length === paddedLength && value.endsWith(padding) && BASE64_ALPHABET.test(value.slice(0, noPaddingLength))) return value;
|
|
460
|
+
return null;
|
|
461
|
+
}
|
|
462
|
+
function base64ToBytes(value, decodedByteLength) {
|
|
463
|
+
const normalized = normalizeSizedBase64(value, decodedByteLength);
|
|
464
|
+
if (normalized === null) return null;
|
|
465
|
+
try {
|
|
466
|
+
const decoded = atob(normalized);
|
|
467
|
+
if (decoded.length !== decodedByteLength) return null;
|
|
468
|
+
const bytes = new Uint8Array(decoded.length);
|
|
469
|
+
for (let i = 0; i < decoded.length; i++) bytes[i] = decoded.charCodeAt(i);
|
|
470
|
+
return bytes;
|
|
471
|
+
} catch {
|
|
472
|
+
return null;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
function encryptionValidationError(message) {
|
|
476
|
+
return {
|
|
477
|
+
status: 400,
|
|
478
|
+
body: {
|
|
479
|
+
status: 400,
|
|
480
|
+
code: "bad_request",
|
|
481
|
+
message
|
|
482
|
+
}
|
|
181
483
|
};
|
|
182
|
-
|
|
484
|
+
}
|
|
485
|
+
function sameStoredServerSideEncryption(a, b) {
|
|
486
|
+
if (a.mode !== b.mode) return false;
|
|
487
|
+
if (a.mode === require_encryption.EncryptionMode.None) return true;
|
|
488
|
+
if (a.mode === require_encryption.EncryptionMode.SseB2 && b.mode === require_encryption.EncryptionMode.SseB2) return a.algorithm === b.algorithm;
|
|
489
|
+
if (a.mode === require_encryption.EncryptionMode.SseC && b.mode === require_encryption.EncryptionMode.SseC) return a.algorithm === b.algorithm && a.customerKeyDigest === b.customerKeyDigest && a.customerKeyMd5 === b.customerKeyMd5;
|
|
490
|
+
return false;
|
|
491
|
+
}
|
|
492
|
+
function hasCustomerEncryptionFields(encryption) {
|
|
493
|
+
return encryption["customerKey"] !== void 0 || encryption["customerKeyMd5"] !== void 0 || encryption["customerAlgorithm"] !== void 0;
|
|
494
|
+
}
|
|
495
|
+
function hasCustomerEncryptionHeaders(headers) {
|
|
496
|
+
return headers["x-bz-server-side-encryption-customer-algorithm"] !== void 0 || headers["x-bz-server-side-encryption-customer-key"] !== void 0 || headers["x-bz-server-side-encryption-customer-key-md5"] !== void 0;
|
|
497
|
+
}
|
|
498
|
+
async function storedServerSideEncryption(encryption) {
|
|
499
|
+
const runtimeEncryption = encryption;
|
|
500
|
+
if (encryption.mode === require_encryption.EncryptionMode.SseC) {
|
|
501
|
+
if (runtimeEncryption["algorithm"] === void 0) return encryptionValidationError("SSE-C customer algorithm is required");
|
|
502
|
+
if (encryption.algorithm !== require_encryption.EncryptionAlgorithm.Aes256) return encryptionValidationError("SSE-C customer algorithm must be AES256");
|
|
503
|
+
const customerKey = encryption.customerKey;
|
|
504
|
+
const customerKeyMd5 = encryption.customerKeyMd5;
|
|
505
|
+
if (typeof customerKey !== "string" || customerKey === "") return encryptionValidationError("SSE-C customer key is required");
|
|
506
|
+
if (typeof customerKeyMd5 !== "string" || customerKeyMd5 === "") return encryptionValidationError("SSE-C customer key MD5 is required");
|
|
507
|
+
const customerKeyBytes = base64ToBytes(customerKey, 32);
|
|
508
|
+
if (customerKeyBytes === null) return encryptionValidationError("SSE-C customer key must be a base64-encoded 256-bit key");
|
|
509
|
+
const normalizedCustomerKeyMd5 = normalizeSizedBase64(customerKeyMd5, 16);
|
|
510
|
+
if (normalizedCustomerKeyMd5 === null) return encryptionValidationError("SSE-C customer key MD5 must be a base64-encoded 128-bit digest");
|
|
511
|
+
if (await require_md5.md5Base64(customerKeyBytes) !== normalizedCustomerKeyMd5) return encryptionValidationError("SSE-C customer key MD5 does not match the key");
|
|
512
|
+
return {
|
|
513
|
+
mode: encryption.mode,
|
|
514
|
+
algorithm: encryption.algorithm,
|
|
515
|
+
customerKeyDigest: customerKeyDigest(customerKeyBytes),
|
|
516
|
+
customerKeyMd5: normalizedCustomerKeyMd5
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
if (encryption.mode === require_encryption.EncryptionMode.SseB2) {
|
|
520
|
+
if (hasCustomerEncryptionFields(runtimeEncryption)) return encryptionValidationError("SSE-B2 settings must not include SSE-C customer keys");
|
|
521
|
+
if (encryption.algorithm !== require_encryption.EncryptionAlgorithm.Aes256) return encryptionValidationError("SSE-B2 algorithm must be AES256");
|
|
522
|
+
return {
|
|
523
|
+
mode: encryption.mode,
|
|
524
|
+
algorithm: encryption.algorithm
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
if (encryption.mode === require_encryption.EncryptionMode.None) {
|
|
528
|
+
if (hasCustomerEncryptionFields(runtimeEncryption)) return encryptionValidationError("No-encryption settings must not include SSE-C customer keys");
|
|
529
|
+
return { mode: require_encryption.EncryptionMode.None };
|
|
530
|
+
}
|
|
531
|
+
return encryptionValidationError(`Unsupported server-side encryption mode: ${String(runtimeEncryption["mode"])}`);
|
|
532
|
+
}
|
|
533
|
+
async function uploadServerSideEncryption(headers, fallback) {
|
|
534
|
+
const customerAlgorithm = headers["x-bz-server-side-encryption-customer-algorithm"];
|
|
535
|
+
const customerKey = headers["x-bz-server-side-encryption-customer-key"];
|
|
536
|
+
const customerKeyMd5 = headers["x-bz-server-side-encryption-customer-key-md5"];
|
|
537
|
+
const managedEncryption = headers["x-bz-server-side-encryption"];
|
|
538
|
+
if (hasCustomerEncryptionHeaders(headers)) {
|
|
539
|
+
if (managedEncryption !== void 0) return encryptionValidationError("SSE-B2 settings must not include SSE-C customer keys");
|
|
540
|
+
if (customerAlgorithm === void 0) return encryptionValidationError("SSE-C customer algorithm is required");
|
|
541
|
+
if (customerAlgorithm !== require_encryption.EncryptionAlgorithm.Aes256) return encryptionValidationError("SSE-C customer algorithm must be AES256");
|
|
542
|
+
if (!customerKey || !customerKeyMd5) return encryptionValidationError("SSE-C customer key and MD5 are required");
|
|
543
|
+
return await storedServerSideEncryption({
|
|
544
|
+
mode: require_encryption.EncryptionMode.SseC,
|
|
545
|
+
algorithm: require_encryption.EncryptionAlgorithm.Aes256,
|
|
546
|
+
customerKey,
|
|
547
|
+
customerKeyMd5
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
if (managedEncryption !== void 0) {
|
|
551
|
+
if (managedEncryption !== require_encryption.EncryptionAlgorithm.Aes256) return encryptionValidationError("SSE-B2 algorithm must be AES256");
|
|
552
|
+
return {
|
|
553
|
+
mode: require_encryption.EncryptionMode.SseB2,
|
|
554
|
+
algorithm: require_encryption.EncryptionAlgorithm.Aes256
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
return await storedServerSideEncryption(fallback);
|
|
183
558
|
}
|
|
184
559
|
function defaultFileRetention(policy, uploadTimestamp) {
|
|
185
560
|
if (policy.mode === require_bucket.BucketRetentionMode.None || policy.period === null) return null;
|
|
186
|
-
const
|
|
561
|
+
const retainUntilTimestamp = uploadTimestamp + (policy.period.unit === "days" ? policy.period.duration : policy.period.duration * 365) * MS_PER_DAY;
|
|
562
|
+
if (!isValidRetentionTimestamp(retainUntilTimestamp, uploadTimestamp)) return null;
|
|
187
563
|
return {
|
|
188
564
|
mode: policy.mode,
|
|
189
|
-
retainUntilTimestamp
|
|
565
|
+
retainUntilTimestamp
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
function isRetentionMode(value) {
|
|
569
|
+
return value === require_lock.RetentionMode.Compliance || value === require_lock.RetentionMode.Governance;
|
|
570
|
+
}
|
|
571
|
+
function parseFileRetentionValue(value, now) {
|
|
572
|
+
if (typeof value !== "object" || value === null) return null;
|
|
573
|
+
const candidate = value;
|
|
574
|
+
const mode = candidate["mode"];
|
|
575
|
+
const retainUntilTimestamp = candidate["retainUntilTimestamp"];
|
|
576
|
+
if (mode === null && retainUntilTimestamp === null) return {
|
|
577
|
+
mode,
|
|
578
|
+
retainUntilTimestamp
|
|
190
579
|
};
|
|
580
|
+
if (isRetentionMode(mode) && typeof retainUntilTimestamp === "number") {
|
|
581
|
+
if (!isValidRetentionTimestamp(retainUntilTimestamp, now)) return null;
|
|
582
|
+
return {
|
|
583
|
+
mode,
|
|
584
|
+
retainUntilTimestamp
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
return null;
|
|
588
|
+
}
|
|
589
|
+
function isRetentionWeakened(current, next) {
|
|
590
|
+
if (current === null || current === void 0) return false;
|
|
591
|
+
if (current.mode === null) return false;
|
|
592
|
+
if (next.mode === null) return true;
|
|
593
|
+
if (current.mode === require_lock.RetentionMode.Compliance && next.mode !== require_lock.RetentionMode.Compliance) return true;
|
|
594
|
+
if (current.retainUntilTimestamp === null) return next.mode !== current.mode;
|
|
595
|
+
if (next.retainUntilTimestamp === null) return true;
|
|
596
|
+
return next.retainUntilTimestamp < current.retainUntilTimestamp;
|
|
597
|
+
}
|
|
598
|
+
function requiresGovernanceBypass(current, next) {
|
|
599
|
+
if (current?.mode !== require_lock.RetentionMode.Governance) return false;
|
|
600
|
+
if (next.mode === require_lock.RetentionMode.Compliance) return true;
|
|
601
|
+
return isRetentionWeakened(current, next);
|
|
191
602
|
}
|
|
192
603
|
/**
|
|
193
604
|
* In-memory B2 simulator for testing. Implements the B2 native API at the
|
|
@@ -204,6 +615,15 @@ function defaultFileRetention(policy, uploadTimestamp) {
|
|
|
204
615
|
* })
|
|
205
616
|
* await client.authorize()
|
|
206
617
|
* ```
|
|
618
|
+
*
|
|
619
|
+
* `test-key-id:test-key` is the documented implicit full-access
|
|
620
|
+
* storage credential. `master-key-id:master-key` is also accepted for
|
|
621
|
+
* tests that share storage and Partner simulator credentials. In the
|
|
622
|
+
* default permissive mode, other well-formed Basic credentials whose
|
|
623
|
+
* applicationKeyId was not minted by `b2_create_key` receive the same
|
|
624
|
+
* implicit master grant for backward compatibility. A simulator-created
|
|
625
|
+
* key always authorizes with its own capabilities, bucket restrictions,
|
|
626
|
+
* name prefix, and expiration.
|
|
207
627
|
*/
|
|
208
628
|
var B2Simulator = class {
|
|
209
629
|
buckets = /* @__PURE__ */ new Map();
|
|
@@ -220,6 +640,8 @@ var B2Simulator = class {
|
|
|
220
640
|
onHookError;
|
|
221
641
|
strictAuth;
|
|
222
642
|
authTokenTtlMs;
|
|
643
|
+
customUploadTimestampsEnabled;
|
|
644
|
+
partner;
|
|
223
645
|
/**
|
|
224
646
|
* Issued auth tokens with their associated grant scope + expiry. In
|
|
225
647
|
* permissive mode (`strictAuth: false`) this is still populated by
|
|
@@ -235,6 +657,8 @@ var B2Simulator = class {
|
|
|
235
657
|
* state for tests.
|
|
236
658
|
*/
|
|
237
659
|
uploadTokens = /* @__PURE__ */ new Map();
|
|
660
|
+
downloadAuthorizationTokens = /* @__PURE__ */ new Map();
|
|
661
|
+
downloadAuthorizationExpiryHeap = [];
|
|
238
662
|
/**
|
|
239
663
|
* Virtual-clock offset applied to `Date.now()` for expiry checks.
|
|
240
664
|
* Defaults to 0. Tests advance via {@link advanceTime} to fast-forward
|
|
@@ -278,7 +702,23 @@ var B2Simulator = class {
|
|
|
278
702
|
if (options.onReplicate !== void 0) this.onReplicate = options.onReplicate;
|
|
279
703
|
if (options.onHookError !== void 0) this.onHookError = options.onHookError;
|
|
280
704
|
this.strictAuth = options.strictAuth ?? false;
|
|
281
|
-
this.
|
|
705
|
+
this.customUploadTimestampsEnabled = options.customUploadTimestampsEnabled ?? false;
|
|
706
|
+
this.authTokenTtlMs = options.authTokenTtlMs ?? 864e5;
|
|
707
|
+
this.partner = new require_partner.PartnerSimulator({
|
|
708
|
+
accountId: this.accountId,
|
|
709
|
+
authTokenTtlMs: this.authTokenTtlMs,
|
|
710
|
+
minimumPartSize: this.minimumPartSize,
|
|
711
|
+
recommendedPartSize: this.recommendedPartSize,
|
|
712
|
+
canAuthorize: (authzHeader) => this.canAuthorizePartner(authzHeader),
|
|
713
|
+
createBucket: (req) => this.createBucket(req),
|
|
714
|
+
error: (status, code, message) => this.error(status, code, message),
|
|
715
|
+
genId: (prefix) => this.genId(prefix),
|
|
716
|
+
monotonicTimestamp: () => this.monotonicTimestamp(),
|
|
717
|
+
now: () => this.now(),
|
|
718
|
+
storeKey: (key) => {
|
|
719
|
+
this.keys.set(key.applicationKeyId, key);
|
|
720
|
+
}
|
|
721
|
+
}, options);
|
|
282
722
|
}
|
|
283
723
|
/**
|
|
284
724
|
* Advance the simulator's virtual clock by `ms` milliseconds. Used in
|
|
@@ -336,6 +776,9 @@ var B2Simulator = class {
|
|
|
336
776
|
now() {
|
|
337
777
|
return Date.now() + this.clockOffsetMs;
|
|
338
778
|
}
|
|
779
|
+
isStoredKeyExpired(key) {
|
|
780
|
+
return key.expirationTimestamp !== null && key.expirationTimestamp <= this.now();
|
|
781
|
+
}
|
|
339
782
|
pruneExpiredUploadTokens(now = this.now()) {
|
|
340
783
|
for (const [authorizationToken, token] of this.uploadTokens.entries()) if (now >= token.cleanupAt) this.uploadTokens.delete(authorizationToken);
|
|
341
784
|
}
|
|
@@ -459,15 +902,195 @@ var B2Simulator = class {
|
|
|
459
902
|
const token = this.issuedTokens.get(authToken);
|
|
460
903
|
if (!token) return this.error(401, "bad_auth_token", "unknown auth token");
|
|
461
904
|
if (this.now() > token.expiresAt) return this.error(401, "expired_auth_token", "auth token has expired; reauthorize");
|
|
905
|
+
if (token.applicationKeyId !== null) {
|
|
906
|
+
const key = this.keys.get(token.applicationKeyId);
|
|
907
|
+
if (key === void 0) return this.error(401, "bad_auth_token", "application key has been deleted; reauthorize");
|
|
908
|
+
if (this.isStoredKeyExpired(key)) return this.error(401, "expired_auth_token", "application key has expired; reauthorize");
|
|
909
|
+
}
|
|
462
910
|
const missing = require_capabilities.missingCapabilitiesFor(endpoint, token.capabilities);
|
|
463
911
|
if (missing.length > 0) return this.error(403, "unauthorized", `application key lacks required capabilities: ${missing.join(", ")}`);
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
912
|
+
return this.authorizeScopeGrant(scope, {
|
|
913
|
+
bucketIds: token.bucketIds,
|
|
914
|
+
namePrefix: token.namePrefix,
|
|
915
|
+
bucketScopeRequiredMessage: () => `application key is scoped to buckets ${token.bucketIds?.join(", ")}; bucket scope is required`,
|
|
916
|
+
bucketMismatchMessage: (bucketId) => `application key is scoped to buckets ${token.bucketIds?.join(", ")}; cannot access ${bucketId}`,
|
|
917
|
+
prefixMismatchMessage: (fileName) => `application key is scoped to prefix "${token.namePrefix}"; "${fileName}" is outside scope`
|
|
918
|
+
});
|
|
919
|
+
}
|
|
920
|
+
authorizeScopeGrant(scope, grant) {
|
|
921
|
+
if (grant.bucketIds !== null) {
|
|
922
|
+
if (scope?.requiresAccountLevelBucketAccess === true) return this.error(403, "unauthorized", grant.bucketScopeRequiredMessage());
|
|
923
|
+
if ((scope?.bucketIds.length ?? 0) === 0 && scope?.requiresBucketScope === true) return this.error(403, "unauthorized", grant.bucketScopeRequiredMessage());
|
|
924
|
+
for (const bucketId of scope?.bucketIds ?? []) {
|
|
925
|
+
if (grant.bucketIds.includes(bucketId)) continue;
|
|
926
|
+
return this.error(403, "unauthorized", grant.bucketMismatchMessage(bucketId));
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
if (grant.namePrefix !== null) for (const fileName of scope?.fileNames ?? []) {
|
|
930
|
+
if (fileName.startsWith(grant.namePrefix)) continue;
|
|
931
|
+
return this.error(403, "unauthorized", grant.prefixMismatchMessage(fileName));
|
|
932
|
+
}
|
|
933
|
+
return null;
|
|
934
|
+
}
|
|
935
|
+
authorizeDownloadRequest(authToken, endpoint, scope, url) {
|
|
936
|
+
if (authToken === void 0 || authToken === "") return this.error(401, "bad_auth_token", "missing authorization token");
|
|
937
|
+
const downloadAuth = this.downloadAuthorizationTokens.get(authToken);
|
|
938
|
+
if (downloadAuth === void 0) {
|
|
939
|
+
this.purgeExpiredDownloadAuthorizationTokens();
|
|
940
|
+
return this.authorizeRequest(authToken, endpoint, scope);
|
|
941
|
+
}
|
|
942
|
+
if (this.isExpiredDownloadAuthorizationToken(downloadAuth)) {
|
|
943
|
+
this.downloadAuthorizationTokens.delete(authToken);
|
|
944
|
+
this.purgeExpiredDownloadAuthorizationTokens();
|
|
945
|
+
return this.error(401, "expired_auth_token", "download authorization token has expired");
|
|
946
|
+
}
|
|
947
|
+
if (endpoint === "b2_download_file_by_id") return this.error(403, "unauthorized", "download authorization tokens cannot be used with b2_download_file_by_id");
|
|
948
|
+
const overrideError = this.authorizeDownloadResponseOverrides(downloadAuth, url);
|
|
949
|
+
if (overrideError !== null) return overrideError;
|
|
950
|
+
return this.authorizeScopeGrant(scope, {
|
|
951
|
+
bucketIds: [downloadAuth.bucketId],
|
|
952
|
+
namePrefix: downloadAuth.fileNamePrefix,
|
|
953
|
+
bucketScopeRequiredMessage: () => `download authorization is scoped to bucket ${downloadAuth.bucketId}; bucket scope is required`,
|
|
954
|
+
bucketMismatchMessage: (bucketId) => `download authorization is scoped to bucket ${downloadAuth.bucketId}; cannot access ${bucketId}`,
|
|
955
|
+
prefixMismatchMessage: (fileName) => `download authorization is scoped to prefix "${downloadAuth.fileNamePrefix}"; "${fileName}" is outside scope`
|
|
956
|
+
});
|
|
957
|
+
}
|
|
958
|
+
isExpiredDownloadAuthorizationToken(token, now = this.now()) {
|
|
959
|
+
return now >= token.expiresAt;
|
|
960
|
+
}
|
|
961
|
+
purgeExpiredDownloadAuthorizationTokens() {
|
|
962
|
+
const now = this.now();
|
|
963
|
+
for (let i = 0; i < DOWNLOAD_AUTH_PURGE_BATCH_SIZE; i++) {
|
|
964
|
+
const next = this.downloadAuthorizationExpiryHeap[0];
|
|
965
|
+
if (next === void 0 || next.expiresAt > now) return;
|
|
966
|
+
this.popDownloadAuthorizationExpiry();
|
|
967
|
+
if (this.downloadAuthorizationTokens.get(next.token)?.expiresAt === next.expiresAt) this.downloadAuthorizationTokens.delete(next.token);
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
pushDownloadAuthorizationExpiry(entry) {
|
|
971
|
+
const heap = this.downloadAuthorizationExpiryHeap;
|
|
972
|
+
heap.push(entry);
|
|
973
|
+
let idx = heap.length - 1;
|
|
974
|
+
while (idx > 0) {
|
|
975
|
+
const parentIdx = Math.floor((idx - 1) / 2);
|
|
976
|
+
const parent = heap[parentIdx];
|
|
977
|
+
if (parent.expiresAt <= entry.expiresAt) break;
|
|
978
|
+
heap[idx] = parent;
|
|
979
|
+
idx = parentIdx;
|
|
980
|
+
}
|
|
981
|
+
heap[idx] = entry;
|
|
982
|
+
}
|
|
983
|
+
popDownloadAuthorizationExpiry() {
|
|
984
|
+
const heap = this.downloadAuthorizationExpiryHeap;
|
|
985
|
+
const root = heap[0];
|
|
986
|
+
const last = heap.pop();
|
|
987
|
+
if (root === void 0 || last === void 0 || heap.length === 0) return root;
|
|
988
|
+
let idx = 0;
|
|
989
|
+
while (true) {
|
|
990
|
+
const leftIdx = idx * 2 + 1;
|
|
991
|
+
const rightIdx = leftIdx + 1;
|
|
992
|
+
const left = heap[leftIdx];
|
|
993
|
+
const right = heap[rightIdx];
|
|
994
|
+
if (left === void 0) break;
|
|
995
|
+
const childIdx = right !== void 0 && right.expiresAt < left.expiresAt ? rightIdx : leftIdx;
|
|
996
|
+
const child = heap[childIdx];
|
|
997
|
+
if (child.expiresAt >= last.expiresAt) break;
|
|
998
|
+
heap[idx] = child;
|
|
999
|
+
idx = childIdx;
|
|
1000
|
+
}
|
|
1001
|
+
heap[idx] = last;
|
|
1002
|
+
return root;
|
|
1003
|
+
}
|
|
1004
|
+
authorizeDownloadResponseOverrides(downloadAuth, url) {
|
|
1005
|
+
for (const param of DOWNLOAD_RESPONSE_OVERRIDE_PARAMS) {
|
|
1006
|
+
const expected = downloadAuth.responseHeaderOverrides[param];
|
|
1007
|
+
if (expected === void 0) continue;
|
|
1008
|
+
const actual = url.searchParams.get(param);
|
|
1009
|
+
if (actual === expected) continue;
|
|
1010
|
+
const expectedText = JSON.stringify(expected);
|
|
1011
|
+
const actualText = actual === null ? "absent" : JSON.stringify(actual);
|
|
1012
|
+
return this.error(403, "unauthorized", `download authorization requires ${param}=${expectedText}; got ${actualText}`);
|
|
1013
|
+
}
|
|
1014
|
+
return null;
|
|
1015
|
+
}
|
|
1016
|
+
downloadAuthorizationFromRequest(headers, url, options = {}) {
|
|
1017
|
+
const headerToken = requestHeaderValue(headers, "authorization");
|
|
1018
|
+
if (headerToken !== void 0) return headerToken;
|
|
1019
|
+
if (options.allowQueryAuthorization !== true) return void 0;
|
|
1020
|
+
const queryToken = url.searchParams.get("Authorization") ?? url.searchParams.get("authorization");
|
|
1021
|
+
return queryToken?.startsWith(DOWNLOAD_AUTH_TOKEN_PREFIX) ? queryToken : void 0;
|
|
1022
|
+
}
|
|
1023
|
+
requestHasCapability(authToken, capability) {
|
|
1024
|
+
if (!this.strictAuth) return true;
|
|
1025
|
+
if (authToken === void 0) return false;
|
|
1026
|
+
return this.issuedTokens.get(authToken)?.capabilities.includes(capability) === true;
|
|
1027
|
+
}
|
|
1028
|
+
requireFileLockEnabled(bucket) {
|
|
1029
|
+
if (bucket.info.fileLockConfiguration.value?.isFileLockEnabled === true) return null;
|
|
1030
|
+
return this.error(400, "file_lock_not_enabled", "Bucket does not have file lock enabled");
|
|
1031
|
+
}
|
|
1032
|
+
requireGovernanceBypass(req, authToken, message) {
|
|
1033
|
+
if (req.bypassGovernance !== true) return this.error(400, "file_lock_governance_protected", message);
|
|
1034
|
+
if (!this.requestHasCapability(authToken, require_auth.Capability.BypassGovernance)) return this.error(403, "unauthorized", `application key lacks required capabilities: ${require_auth.Capability.BypassGovernance}`);
|
|
1035
|
+
return null;
|
|
1036
|
+
}
|
|
1037
|
+
validateReplicationApplicationKey(applicationKeyId, requiredCapabilities, options) {
|
|
1038
|
+
const key = this.keys.get(applicationKeyId);
|
|
1039
|
+
if (key === void 0) return this.invalidReplicationApplicationKey(options.role);
|
|
1040
|
+
if (missingStoredKeyCapabilities(key, requiredCapabilities).length > 0) return this.invalidReplicationApplicationKey(options.role);
|
|
1041
|
+
if (options.bucketId !== void 0 && !storedKeyAllowsBucket(key, options.bucketId)) return this.invalidReplicationApplicationKey(options.role);
|
|
1042
|
+
return null;
|
|
1043
|
+
}
|
|
1044
|
+
invalidReplicationApplicationKey(role) {
|
|
1045
|
+
return this.error(400, "bad_request", `${role} application key is invalid or not authorized for this replication configuration`);
|
|
1046
|
+
}
|
|
1047
|
+
invalidReplicationDestinationBucket() {
|
|
1048
|
+
return this.error(400, "bad_request", "replication destination bucket is invalid or not configured for this source application key");
|
|
1049
|
+
}
|
|
1050
|
+
validateReplicationDestinationBuckets(sourceApplicationKeyId, rules) {
|
|
1051
|
+
for (const rule of rules) {
|
|
1052
|
+
const destinationBucketId = requestRecord(rule)?.["destinationBucketId"];
|
|
1053
|
+
if (typeof destinationBucketId !== "string") continue;
|
|
1054
|
+
const destinationApplicationKeyId = this.buckets.get(destinationBucketId)?.info.replicationConfiguration.asReplicationDestination?.sourceToDestinationKeyMapping[sourceApplicationKeyId];
|
|
1055
|
+
if (destinationApplicationKeyId === void 0) return this.invalidReplicationDestinationBucket();
|
|
1056
|
+
const destinationKeyError = this.validateReplicationApplicationKey(destinationApplicationKeyId, [require_auth.Capability.WriteFiles], {
|
|
1057
|
+
bucketId: destinationBucketId,
|
|
1058
|
+
role: "replication destination"
|
|
1059
|
+
});
|
|
1060
|
+
if (destinationKeyError !== null) return destinationKeyError;
|
|
467
1061
|
}
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
1062
|
+
return null;
|
|
1063
|
+
}
|
|
1064
|
+
validateReplicationApplicationKeys(config, bucketId) {
|
|
1065
|
+
if (!this.strictAuth || config === void 0) return null;
|
|
1066
|
+
const replicationConfiguration = requestRecord(config);
|
|
1067
|
+
const source = requestRecord(replicationConfiguration?.["asReplicationSource"]);
|
|
1068
|
+
if (source !== null) {
|
|
1069
|
+
const sourceApplicationKeyId = source["sourceApplicationKeyId"];
|
|
1070
|
+
if (typeof sourceApplicationKeyId === "string") {
|
|
1071
|
+
const sourceKeyError = this.validateReplicationApplicationKey(sourceApplicationKeyId, [require_auth.Capability.ReadFiles, require_auth.Capability.ListFiles], {
|
|
1072
|
+
bucketId,
|
|
1073
|
+
role: "replication source"
|
|
1074
|
+
});
|
|
1075
|
+
if (sourceKeyError !== null) return sourceKeyError;
|
|
1076
|
+
}
|
|
1077
|
+
const rules = source["replicationRules"];
|
|
1078
|
+
if (typeof sourceApplicationKeyId === "string" && Array.isArray(rules)) {
|
|
1079
|
+
const destinationError = this.validateReplicationDestinationBuckets(sourceApplicationKeyId, rules);
|
|
1080
|
+
if (destinationError !== null) return destinationError;
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
const mapping = requestRecord(requestRecord(replicationConfiguration?.["asReplicationDestination"])?.["sourceToDestinationKeyMapping"]);
|
|
1084
|
+
if (mapping !== null) for (const [sourceApplicationKeyId, destinationApplicationKeyId] of Object.entries(mapping)) {
|
|
1085
|
+
const sourceKeyError = this.validateReplicationApplicationKey(sourceApplicationKeyId, [require_auth.Capability.ReadFiles, require_auth.Capability.ListFiles], { role: "replication source" });
|
|
1086
|
+
if (sourceKeyError !== null) return sourceKeyError;
|
|
1087
|
+
if (typeof destinationApplicationKeyId === "string") {
|
|
1088
|
+
const destinationKeyError = this.validateReplicationApplicationKey(destinationApplicationKeyId, [require_auth.Capability.WriteFiles], {
|
|
1089
|
+
bucketId,
|
|
1090
|
+
role: "replication destination"
|
|
1091
|
+
});
|
|
1092
|
+
if (destinationKeyError !== null) return destinationKeyError;
|
|
1093
|
+
}
|
|
471
1094
|
}
|
|
472
1095
|
return null;
|
|
473
1096
|
}
|
|
@@ -475,10 +1098,7 @@ var B2Simulator = class {
|
|
|
475
1098
|
const directBucketId = requestStringField(body, "bucketId");
|
|
476
1099
|
const directFileName = requestStringField(body, "fileName");
|
|
477
1100
|
switch (endpoint) {
|
|
478
|
-
case "b2_create_bucket": return
|
|
479
|
-
bucketIds: [],
|
|
480
|
-
requiresBucketScope: true
|
|
481
|
-
};
|
|
1101
|
+
case "b2_create_bucket": return this.createBucketScope(body);
|
|
482
1102
|
case "b2_list_buckets": return this.listBucketsScope(body, directBucketId);
|
|
483
1103
|
case "b2_list_file_names":
|
|
484
1104
|
case "b2_list_file_versions": return {
|
|
@@ -514,9 +1134,23 @@ var B2Simulator = class {
|
|
|
514
1134
|
};
|
|
515
1135
|
case "b2_get_bucket_notification_rules": return this.notificationRulesScope(body, directBucketId, false);
|
|
516
1136
|
case "b2_set_bucket_notification_rules": return this.notificationRulesScope(body, directBucketId, true);
|
|
1137
|
+
case "b2_update_bucket": return this.updateBucketScope(body, directBucketId);
|
|
517
1138
|
default: return this.defaultRequestScope(directBucketId, directFileName);
|
|
518
1139
|
}
|
|
519
1140
|
}
|
|
1141
|
+
createBucketScope(body) {
|
|
1142
|
+
return {
|
|
1143
|
+
bucketIds: replicationDestinationBucketIds(body),
|
|
1144
|
+
requiresBucketScope: true,
|
|
1145
|
+
requiresAccountLevelBucketAccess: true
|
|
1146
|
+
};
|
|
1147
|
+
}
|
|
1148
|
+
updateBucketScope(body, directBucketId) {
|
|
1149
|
+
return {
|
|
1150
|
+
bucketIds: [...directBucketId === void 0 ? [] : [directBucketId], ...replicationDestinationBucketIds(body)],
|
|
1151
|
+
requiresBucketScope: true
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
520
1154
|
listBucketsScope(body, directBucketId) {
|
|
521
1155
|
if (directBucketId !== void 0) return {
|
|
522
1156
|
bucketIds: [directBucketId],
|
|
@@ -614,7 +1248,10 @@ var B2Simulator = class {
|
|
|
614
1248
|
const idParam = options.kind === "file" ? "bucketId" : "fileId";
|
|
615
1249
|
const scopedId = options.kind === "file" ? options.bucketId : options.fileId;
|
|
616
1250
|
const uploadId = this.genId(options.kind === "file" ? "upload_file" : "upload_part");
|
|
617
|
-
const uploadUrl = new URL(
|
|
1251
|
+
const uploadUrl = new URL(require_url.b2Url("http://localhost:0", {
|
|
1252
|
+
version: "v3",
|
|
1253
|
+
endpoint
|
|
1254
|
+
}));
|
|
618
1255
|
uploadUrl.searchParams.set(idParam, scopedId);
|
|
619
1256
|
uploadUrl.searchParams.set("uploadId", uploadId);
|
|
620
1257
|
const sourceToken = options.sourceAuthToken === void 0 ? void 0 : this.issuedTokens.get(options.sourceAuthToken);
|
|
@@ -687,40 +1324,61 @@ var B2Simulator = class {
|
|
|
687
1324
|
}));
|
|
688
1325
|
}
|
|
689
1326
|
/**
|
|
690
|
-
*
|
|
1327
|
+
* Resolve the application key matching the `Authorization` header on
|
|
691
1328
|
* an `authorize_account` request. The header is in the form
|
|
692
1329
|
* `Basic base64(applicationKeyId:applicationKey)`.
|
|
693
1330
|
*
|
|
694
|
-
*
|
|
695
|
-
*
|
|
696
|
-
* `
|
|
1331
|
+
* The simulator has two implicit full-access storage credentials:
|
|
1332
|
+
* the documented `test-key-id:test-key` pair used by SDK tests and
|
|
1333
|
+
* `master-key-id:master-key`. In default permissive mode, any other
|
|
1334
|
+
* well-formed Basic credential whose applicationKeyId is not a stored
|
|
1335
|
+
* key also receives that implicit master grant for backward
|
|
1336
|
+
* compatibility. A credential whose applicationKeyId matches a stored
|
|
1337
|
+
* key must use the stored secret and must not be expired.
|
|
697
1338
|
*
|
|
698
1339
|
* @param authzHeader - Raw HTTP `Authorization` header value.
|
|
699
1340
|
*
|
|
700
|
-
* @returns The
|
|
1341
|
+
* @returns The authorizing key's grant scope or the rejection reason.
|
|
701
1342
|
*/
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
1343
|
+
findAuthorizationGrant(authzHeader) {
|
|
1344
|
+
const credentials = parseBasicAuthorizationHeader(authzHeader);
|
|
1345
|
+
if (credentials === null) return { kind: "invalid" };
|
|
1346
|
+
const stored = this.keys.get(credentials.applicationKeyId);
|
|
1347
|
+
if (stored !== void 0) {
|
|
1348
|
+
if (!timingSafeStringEqual(stored.applicationKey, credentials.applicationKey)) return { kind: "invalid" };
|
|
1349
|
+
if (this.isStoredKeyExpired(stored)) return { kind: "expired" };
|
|
1350
|
+
return {
|
|
1351
|
+
kind: "ok",
|
|
1352
|
+
grant: {
|
|
1353
|
+
capabilities: stored.capabilities,
|
|
1354
|
+
bucketIds: stored.bucketIds,
|
|
1355
|
+
namePrefix: stored.namePrefix,
|
|
1356
|
+
applicationKeyId: credentials.applicationKeyId,
|
|
1357
|
+
accountId: stored.accountId,
|
|
1358
|
+
expirationTimestamp: stored.expirationTimestamp
|
|
1359
|
+
}
|
|
1360
|
+
};
|
|
1361
|
+
}
|
|
1362
|
+
if (isSimulatorApplicationKeyId(credentials.applicationKeyId)) return { kind: "invalid" };
|
|
1363
|
+
if (isImplicitStorageMasterCredential(credentials) || !this.strictAuth) return {
|
|
1364
|
+
kind: "ok",
|
|
1365
|
+
grant: {
|
|
1366
|
+
capabilities: SIMULATOR_MASTER_CAPABILITIES,
|
|
1367
|
+
bucketIds: null,
|
|
1368
|
+
namePrefix: null,
|
|
1369
|
+
applicationKeyId: null,
|
|
1370
|
+
accountId: this.accountId,
|
|
1371
|
+
expirationTimestamp: null
|
|
709
1372
|
}
|
|
710
|
-
})();
|
|
711
|
-
if (decoded === null) return null;
|
|
712
|
-
const idx = decoded.indexOf(":");
|
|
713
|
-
if (idx === -1) return null;
|
|
714
|
-
const applicationKeyId = decoded.slice(0, idx);
|
|
715
|
-
const applicationKey = decoded.slice(idx + 1);
|
|
716
|
-
const stored = this.keys.get(applicationKeyId);
|
|
717
|
-
if (!stored || stored.applicationKey !== applicationKey) return null;
|
|
718
|
-
return {
|
|
719
|
-
capabilities: stored.capabilities,
|
|
720
|
-
bucketIds: stored.bucketIds,
|
|
721
|
-
namePrefix: stored.namePrefix,
|
|
722
|
-
applicationKeyId
|
|
723
1373
|
};
|
|
1374
|
+
return { kind: "invalid" };
|
|
1375
|
+
}
|
|
1376
|
+
canAuthorizePartner(authzHeader) {
|
|
1377
|
+
const credentials = parseBasicAuthorizationHeader(authzHeader);
|
|
1378
|
+
if (credentials === null) return false;
|
|
1379
|
+
if (isImplicitPartnerMasterCredential(credentials)) return true;
|
|
1380
|
+
const stored = this.keys.get(credentials.applicationKeyId);
|
|
1381
|
+
return stored !== void 0 && stored.accountId === this.accountId && !this.isStoredKeyExpired(stored) && timingSafeStringEqual(stored.applicationKey, credentials.applicationKey);
|
|
724
1382
|
}
|
|
725
1383
|
/**
|
|
726
1384
|
* Creates an {@link HttpTransport} that routes requests to this simulator.
|
|
@@ -812,7 +1470,7 @@ var B2Simulator = class {
|
|
|
812
1470
|
}
|
|
813
1471
|
/**
|
|
814
1472
|
* Dispatches a JSON API request to the appropriate handler.
|
|
815
|
-
* @param
|
|
1473
|
+
* @param method - The HTTP method.
|
|
816
1474
|
* @param origin - The request URL origin used for simulator-issued endpoints.
|
|
817
1475
|
* @param path - The request URL path containing the B2 endpoint name.
|
|
818
1476
|
* @param headers - The HTTP request headers; consulted by the
|
|
@@ -821,15 +1479,24 @@ var B2Simulator = class {
|
|
|
821
1479
|
*
|
|
822
1480
|
* @returns An object with HTTP status and JSON response body.
|
|
823
1481
|
*/
|
|
824
|
-
async handleRequest(
|
|
825
|
-
const endpoint = path
|
|
826
|
-
|
|
827
|
-
if (this.strictAuth) {
|
|
1482
|
+
async handleRequest(method, origin, path, headers, body) {
|
|
1483
|
+
const { endpoint, version } = apiPathParts(path);
|
|
1484
|
+
if (!jsonEndpointAllowsMethod(method, endpoint)) return this.error(405, "method_not_allowed", `${endpoint} does not support ${method.toUpperCase()}`);
|
|
1485
|
+
if (this.strictAuth && !this.partner.isEndpoint(endpoint)) {
|
|
828
1486
|
const authError = this.authorizeRequest(headers["authorization"], endpoint, this.requestScope(endpoint, body));
|
|
829
1487
|
if (authError !== null) return authError;
|
|
830
1488
|
}
|
|
831
1489
|
switch (endpoint) {
|
|
832
|
-
case "b2_authorize_account":
|
|
1490
|
+
case "b2_authorize_account":
|
|
1491
|
+
if (version === "v3" && this.partner.isAuthorizeEnabled()) return this.partner.authorize(headers["authorization"], origin);
|
|
1492
|
+
return this.authorize(headers["authorization"], origin);
|
|
1493
|
+
case "b2_create_group_member": return this.partner.createGroupMember(body, headers["authorization"]);
|
|
1494
|
+
case "b2_eject_group_member": return this.partner.ejectGroupMember(body, headers["authorization"]);
|
|
1495
|
+
case "b2_list_groups": return this.partner.listGroups(body, headers["authorization"]);
|
|
1496
|
+
case "b2_list_group_members": return this.partner.listGroupMembers(body, headers["authorization"]);
|
|
1497
|
+
case "b2_reserve_trial_create_account": return this.partner.reserveTrialCreateAccount(body, headers["authorization"]);
|
|
1498
|
+
case "bz_list_computers": return this.partner.listComputers(body, headers["authorization"]);
|
|
1499
|
+
case "bz_delete_computer": return this.partner.deleteComputer(body, headers["authorization"]);
|
|
833
1500
|
case "b2_create_bucket": return this.createBucket(body);
|
|
834
1501
|
case "b2_list_buckets": return this.listBuckets(body);
|
|
835
1502
|
case "b2_delete_bucket": return this.deleteBucket(body);
|
|
@@ -839,9 +1506,9 @@ var B2Simulator = class {
|
|
|
839
1506
|
case "b2_list_file_versions": return this.listFileVersions(body);
|
|
840
1507
|
case "b2_get_file_info": return this.getFileInfo(body);
|
|
841
1508
|
case "b2_hide_file": return this.hideFile(body);
|
|
842
|
-
case "b2_delete_file_version": return this.deleteFileVersion(body);
|
|
1509
|
+
case "b2_delete_file_version": return this.deleteFileVersion(body, headers["authorization"]);
|
|
843
1510
|
case "b2_copy_file": return await this.copyFile(body);
|
|
844
|
-
case "b2_start_large_file": return this.startLargeFile(body);
|
|
1511
|
+
case "b2_start_large_file": return await this.startLargeFile(body);
|
|
845
1512
|
case "b2_get_upload_part_url": return await this.getUploadPartUrl(body, headers["authorization"]);
|
|
846
1513
|
case "b2_finish_large_file": return this.finishLargeFile(body);
|
|
847
1514
|
case "b2_cancel_large_file": return this.cancelLargeFile(body);
|
|
@@ -849,10 +1516,10 @@ var B2Simulator = class {
|
|
|
849
1516
|
case "b2_list_parts": return this.listParts(body);
|
|
850
1517
|
case "b2_copy_part": return await this.copyPart(body);
|
|
851
1518
|
case "b2_get_download_authorization": return this.getDownloadAuthorization(body);
|
|
852
|
-
case "b2_create_key": return this.createKey(body,
|
|
1519
|
+
case "b2_create_key": return this.createKey(body, version, headers["authorization"]);
|
|
853
1520
|
case "b2_list_keys": return this.listKeys(body);
|
|
854
1521
|
case "b2_delete_key": return this.deleteKey(body);
|
|
855
|
-
case "b2_update_file_retention": return this.updateFileRetention(body);
|
|
1522
|
+
case "b2_update_file_retention": return this.updateFileRetention(body, headers["authorization"]);
|
|
856
1523
|
case "b2_update_file_legal_hold": return this.updateFileLegalHold(body);
|
|
857
1524
|
case "b2_get_bucket_notification_rules": return this.getBucketNotificationRules(body);
|
|
858
1525
|
case "b2_set_bucket_notification_rules": return this.setBucketNotificationRules(body);
|
|
@@ -882,9 +1549,25 @@ var B2Simulator = class {
|
|
|
882
1549
|
const fileName = kind === "part" || headers["x-bz-file-name"] === void 0 ? void 0 : decodeHeaderValue(headers["x-bz-file-name"]);
|
|
883
1550
|
const authError = await this.validateUploadAuthorization(kind, url, headers["authorization"], fileName);
|
|
884
1551
|
if (authError !== null) return authError;
|
|
1552
|
+
const contentLengthError = this.validateContentLength(headers, data.byteLength);
|
|
1553
|
+
if (contentLengthError !== null) return contentLengthError;
|
|
885
1554
|
if (kind === "part") return await this.handleUploadPart(url, headers, data);
|
|
886
1555
|
return await this.handleUploadFile(url, headers, data);
|
|
887
1556
|
}
|
|
1557
|
+
validateContentLength(headers, actualLength) {
|
|
1558
|
+
const parsed = parseContentLengthHeader(headers);
|
|
1559
|
+
if (parsed.kind === "absent") return null;
|
|
1560
|
+
if (parsed.kind === "error") return this.error(400, "bad_request", parsed.message);
|
|
1561
|
+
if (parsed.expectedLength !== actualLength) return this.error(400, "bad_request", contentLengthMismatchMessage(parsed.expectedLength, actualLength));
|
|
1562
|
+
return null;
|
|
1563
|
+
}
|
|
1564
|
+
parseCustomUploadTimestamp(value) {
|
|
1565
|
+
if (value === void 0 || value === null) return { timestamp: null };
|
|
1566
|
+
if (!this.customUploadTimestampsEnabled) return this.error(400, "custom_timestamp_not_allowed", "Custom upload timestamps are not enabled for this account.");
|
|
1567
|
+
const timestamp = typeof value === "string" && /^\d+$/.test(value) ? Number(value) : NaN;
|
|
1568
|
+
if (!Number.isSafeInteger(timestamp) || timestamp < 0 || timestamp > this.now()) return this.error(400, "custom_timestamp_invalid", "The request has an invalid custom upload timestamp.");
|
|
1569
|
+
return { timestamp };
|
|
1570
|
+
}
|
|
888
1571
|
/**
|
|
889
1572
|
* Handles file download requests (`b2_download_file_by_id`, `/file/` by name).
|
|
890
1573
|
* Returns the file data along with B2 response headers.
|
|
@@ -896,33 +1579,32 @@ var B2Simulator = class {
|
|
|
896
1579
|
*/
|
|
897
1580
|
handleDownload(path, headers, method = "GET") {
|
|
898
1581
|
if (path.includes("b2_download_file_by_id")) {
|
|
1582
|
+
const url = new URL(`http://localhost${path}`);
|
|
899
1583
|
if (this.strictAuth) {
|
|
900
|
-
const
|
|
901
|
-
const authError = this.authorizeRequest(headers["authorization"], "b2_download_file_by_id", this.fileIdScope(url.searchParams.get("fileId") ?? void 0) ?? {
|
|
1584
|
+
const authError = this.authorizeDownloadRequest(this.downloadAuthorizationFromRequest(headers, url, { allowQueryAuthorization: false }), "b2_download_file_by_id", this.fileIdScope(url.searchParams.get("fileId") ?? void 0) ?? {
|
|
902
1585
|
bucketIds: [],
|
|
903
1586
|
requiresBucketScope: true
|
|
904
|
-
});
|
|
1587
|
+
}, url);
|
|
905
1588
|
if (authError !== null) return this.errorAsDownload(authError);
|
|
906
1589
|
}
|
|
907
|
-
const url = new URL(`http://localhost${path}`);
|
|
908
1590
|
const fileId = url.searchParams.get("fileId") ?? "";
|
|
909
|
-
return this.finalizeDownload(this.downloadById(fileId, headers
|
|
1591
|
+
return this.finalizeDownload(this.downloadById(fileId, headers, requestHeaderValue(headers, "range")), url, method);
|
|
910
1592
|
}
|
|
911
1593
|
const fileMatch = path.match(/^([^?]+)/)?.[1]?.match(/\/file\/([^/]+)\/(.+)/);
|
|
912
1594
|
if (fileMatch) {
|
|
913
1595
|
const bucketName = decodeURIComponent(fileMatch[1] ?? "");
|
|
914
1596
|
const fileName = decodeURIComponent(fileMatch[2] ?? "");
|
|
1597
|
+
const url = new URL(`http://localhost${path}`);
|
|
915
1598
|
if (this.strictAuth) {
|
|
916
1599
|
const bucket = [...this.buckets.values()].find((b) => b.info.bucketName === bucketName);
|
|
917
|
-
const authError = this.
|
|
1600
|
+
const authError = this.authorizeDownloadRequest(this.downloadAuthorizationFromRequest(headers, url, { allowQueryAuthorization: true }), "b2_download_file_by_name", {
|
|
918
1601
|
bucketIds: bucket === void 0 ? [] : [bucket.info.bucketId],
|
|
919
1602
|
fileNames: [fileName],
|
|
920
1603
|
requiresBucketScope: true
|
|
921
|
-
});
|
|
1604
|
+
}, url);
|
|
922
1605
|
if (authError !== null) return this.errorAsDownload(authError);
|
|
923
1606
|
}
|
|
924
|
-
|
|
925
|
-
return this.finalizeDownload(this.downloadByName(bucketName, fileName, headers["range"]), url, method);
|
|
1607
|
+
return this.finalizeDownload(this.downloadByName(bucketName, fileName, headers, requestHeaderValue(headers, "range")), url, method);
|
|
926
1608
|
}
|
|
927
1609
|
return {
|
|
928
1610
|
status: 404,
|
|
@@ -959,16 +1641,8 @@ var B2Simulator = class {
|
|
|
959
1641
|
* @returns The response with overrides applied.
|
|
960
1642
|
*/
|
|
961
1643
|
finalizeDownload(response, url, method) {
|
|
962
|
-
const overrideMap = {
|
|
963
|
-
b2ContentDisposition: "Content-Disposition",
|
|
964
|
-
b2ContentLanguage: "Content-Language",
|
|
965
|
-
b2ContentEncoding: "Content-Encoding",
|
|
966
|
-
b2ContentType: "Content-Type",
|
|
967
|
-
b2CacheControl: "Cache-Control",
|
|
968
|
-
b2Expires: "Expires"
|
|
969
|
-
};
|
|
970
1644
|
const newHeaders = { ...response.headers };
|
|
971
|
-
for (const [param, header] of Object.entries(
|
|
1645
|
+
for (const [param, header] of Object.entries(DOWNLOAD_RESPONSE_OVERRIDE_HEADERS)) {
|
|
972
1646
|
const value = url.searchParams.get(param);
|
|
973
1647
|
if (value !== null) newHeaders[header] = value;
|
|
974
1648
|
}
|
|
@@ -994,6 +1668,10 @@ var B2Simulator = class {
|
|
|
994
1668
|
const resolved = await this.resolveUploadSha1(headers["x-bz-content-sha1"], data);
|
|
995
1669
|
if ("status" in resolved) return resolved;
|
|
996
1670
|
const { sha1: contentSha1, data: storedData } = resolved;
|
|
1671
|
+
const serverSideEncryption = await uploadServerSideEncryption(headers, bucket.info.defaultServerSideEncryption);
|
|
1672
|
+
if ("status" in serverSideEncryption) return serverSideEncryption;
|
|
1673
|
+
const customUploadTimestamp = this.parseCustomUploadTimestamp(requestHeaderValue(headers, "x-bz-custom-upload-timestamp"));
|
|
1674
|
+
if ("status" in customUploadTimestamp) return customUploadTimestamp;
|
|
997
1675
|
const fileVersion = this.makeFileVersion({
|
|
998
1676
|
bucketId,
|
|
999
1677
|
fileName,
|
|
@@ -1002,11 +1680,14 @@ var B2Simulator = class {
|
|
|
1002
1680
|
contentSha1,
|
|
1003
1681
|
fileInfo,
|
|
1004
1682
|
action: require_file.FileAction.Upload,
|
|
1005
|
-
serverSideEncryption
|
|
1683
|
+
serverSideEncryption,
|
|
1684
|
+
...this.newReplicationStatusField(bucketId, fileName),
|
|
1685
|
+
...customUploadTimestamp.timestamp !== null ? { uploadTimestamp: customUploadTimestamp.timestamp } : {}
|
|
1006
1686
|
});
|
|
1007
1687
|
const stored = {
|
|
1008
1688
|
fileVersion,
|
|
1009
|
-
data: storedData
|
|
1689
|
+
data: storedData,
|
|
1690
|
+
serverSideEncryption
|
|
1010
1691
|
};
|
|
1011
1692
|
const existing = bucket.files.get(fileName);
|
|
1012
1693
|
if (existing) existing.push(stored);
|
|
@@ -1056,18 +1737,39 @@ var B2Simulator = class {
|
|
|
1056
1737
|
data
|
|
1057
1738
|
};
|
|
1058
1739
|
}
|
|
1740
|
+
async validateUploadPartEncryption(large, headers) {
|
|
1741
|
+
if (large.serverSideEncryption.mode !== require_encryption.EncryptionMode.SseC) {
|
|
1742
|
+
if (!hasCustomerEncryptionHeaders(headers)) return null;
|
|
1743
|
+
return this.error(400, "bad_request", "SSE-C upload part headers require an SSE-C large file");
|
|
1744
|
+
}
|
|
1745
|
+
const supplied = await storedServerSideEncryption({
|
|
1746
|
+
mode: require_encryption.EncryptionMode.SseC,
|
|
1747
|
+
algorithm: headers["x-bz-server-side-encryption-customer-algorithm"],
|
|
1748
|
+
customerKey: headers["x-bz-server-side-encryption-customer-key"],
|
|
1749
|
+
customerKeyMd5: headers["x-bz-server-side-encryption-customer-key-md5"]
|
|
1750
|
+
});
|
|
1751
|
+
if ("status" in supplied) return supplied;
|
|
1752
|
+
if (!sameStoredServerSideEncryption(large.serverSideEncryption, supplied)) return this.error(400, "bad_request", "SSE-C upload parts require matching customer encryption headers");
|
|
1753
|
+
return null;
|
|
1754
|
+
}
|
|
1059
1755
|
async handleUploadPart(url, headers, data) {
|
|
1060
1756
|
const fileId = new URL(url).searchParams.get("fileId");
|
|
1061
1757
|
if (!fileId) return this.error(400, "bad_request", "Missing fileId");
|
|
1062
1758
|
const large = this.largeFiles.get(fileId);
|
|
1063
1759
|
if (!large) return this.error(400, "bad_request", "Large file not found");
|
|
1064
|
-
const partNumber =
|
|
1760
|
+
const partNumber = this.parsePartNumberHeader(requestHeaderValue(headers, "x-bz-part-number"));
|
|
1761
|
+
if (typeof partNumber !== "number") return partNumber;
|
|
1762
|
+
const encryptionError = await this.validateUploadPartEncryption(large, headers);
|
|
1763
|
+
if (encryptionError !== null) return encryptionError;
|
|
1065
1764
|
const resolved = await this.resolveUploadSha1(headers["x-bz-content-sha1"], data);
|
|
1066
1765
|
if ("status" in resolved) return resolved;
|
|
1067
1766
|
const { sha1, data: partData } = resolved;
|
|
1767
|
+
const uploadTimestamp = this.monotonicTimestamp();
|
|
1068
1768
|
large.parts.set(partNumber, {
|
|
1069
1769
|
data: partData,
|
|
1070
|
-
sha1
|
|
1770
|
+
sha1,
|
|
1771
|
+
contentMd5: null,
|
|
1772
|
+
uploadTimestamp
|
|
1071
1773
|
});
|
|
1072
1774
|
return {
|
|
1073
1775
|
status: 200,
|
|
@@ -1076,21 +1778,32 @@ var B2Simulator = class {
|
|
|
1076
1778
|
partNumber,
|
|
1077
1779
|
contentLength: partData.byteLength,
|
|
1078
1780
|
contentSha1: sha1,
|
|
1781
|
+
contentMd5: null,
|
|
1079
1782
|
serverSideEncryption: publicServerSideEncryption(large.serverSideEncryption),
|
|
1080
|
-
uploadTimestamp
|
|
1783
|
+
uploadTimestamp
|
|
1081
1784
|
}
|
|
1082
1785
|
};
|
|
1083
1786
|
}
|
|
1084
|
-
|
|
1787
|
+
parsePartNumberHeader(header) {
|
|
1788
|
+
if (header === void 0) return this.error(400, "bad_request", "X-Bz-Part-Number header is required");
|
|
1789
|
+
if (!/^\d+$/.test(header)) return this.error(400, "bad_request", `X-Bz-Part-Number must be an integer between ${B2_MIN_PART_NUMBER} and ${B2_MAX_PART_NUMBER}; received ${header}`);
|
|
1790
|
+
const partNumber = Number(header);
|
|
1791
|
+
return this.validatePartNumber(partNumber) ?? partNumber;
|
|
1792
|
+
}
|
|
1793
|
+
validatePartNumber(partNumber) {
|
|
1794
|
+
if (Number.isInteger(partNumber) && partNumber >= B2_MIN_PART_NUMBER && partNumber <= B2_MAX_PART_NUMBER) return null;
|
|
1795
|
+
return this.error(400, "bad_request", `partNumber must be an integer between ${B2_MIN_PART_NUMBER} and ${B2_MAX_PART_NUMBER}; received ${String(partNumber)}`);
|
|
1796
|
+
}
|
|
1797
|
+
downloadById(fileId, headers, range) {
|
|
1085
1798
|
const found = this.findFile(fileId);
|
|
1086
1799
|
if (found === null) return {
|
|
1087
1800
|
status: 404,
|
|
1088
1801
|
headers: {},
|
|
1089
1802
|
data: null
|
|
1090
1803
|
};
|
|
1091
|
-
return this.serveFile(found.stored, range);
|
|
1804
|
+
return this.serveFile(found.stored, headers, range);
|
|
1092
1805
|
}
|
|
1093
|
-
downloadByName(bucketName, fileName, range) {
|
|
1806
|
+
downloadByName(bucketName, fileName, headers, range) {
|
|
1094
1807
|
for (const bucket of this.buckets.values()) {
|
|
1095
1808
|
if (bucket.info.bucketName !== bucketName) continue;
|
|
1096
1809
|
const versions = bucket.files.get(fileName);
|
|
@@ -1101,7 +1814,7 @@ var B2Simulator = class {
|
|
|
1101
1814
|
headers: {},
|
|
1102
1815
|
data: null
|
|
1103
1816
|
};
|
|
1104
|
-
return this.serveFile(latest, range);
|
|
1817
|
+
return this.serveFile(latest, headers, range);
|
|
1105
1818
|
}
|
|
1106
1819
|
return {
|
|
1107
1820
|
status: 404,
|
|
@@ -1109,7 +1822,41 @@ var B2Simulator = class {
|
|
|
1109
1822
|
data: null
|
|
1110
1823
|
};
|
|
1111
1824
|
}
|
|
1112
|
-
|
|
1825
|
+
validateDownloadEncryption(stored, headers) {
|
|
1826
|
+
const encryption = stored.serverSideEncryption;
|
|
1827
|
+
if (encryption.mode !== require_encryption.EncryptionMode.SseC) return null;
|
|
1828
|
+
const algorithm = headers["x-bz-server-side-encryption-customer-algorithm"];
|
|
1829
|
+
const customerKey = headers["x-bz-server-side-encryption-customer-key"];
|
|
1830
|
+
const customerKeyMd5 = headers["x-bz-server-side-encryption-customer-key-md5"];
|
|
1831
|
+
const customerKeyBytes = customerKey === void 0 ? null : base64ToBytes(customerKey, 32);
|
|
1832
|
+
const normalizedCustomerKeyMd5 = customerKeyMd5 === void 0 ? null : normalizeSizedBase64(customerKeyMd5, 16);
|
|
1833
|
+
if (algorithm !== encryption.algorithm || customerKeyBytes === null || normalizedCustomerKeyMd5 === null || require_md5.md5Base64Sync(customerKeyBytes) !== normalizedCustomerKeyMd5) return this.errorAsDownload(this.error(400, "bad_request", "SSE-C downloads require matching customer encryption headers"));
|
|
1834
|
+
if (normalizedCustomerKeyMd5 !== encryption.customerKeyMd5 || customerKeyDigest(customerKeyBytes) !== encryption.customerKeyDigest) return this.errorAsDownload(this.error(403, "access_denied", "SSE-C customer encryption key does not match the stored file"));
|
|
1835
|
+
return null;
|
|
1836
|
+
}
|
|
1837
|
+
async validateSourceServerSideEncryption(stored, encryption) {
|
|
1838
|
+
const expected = stored.serverSideEncryption;
|
|
1839
|
+
if (expected.mode !== require_encryption.EncryptionMode.SseC) return null;
|
|
1840
|
+
if (encryption?.mode !== require_encryption.EncryptionMode.SseC) return this.error(400, "bad_request", "SSE-C source copies require customer encryption");
|
|
1841
|
+
const supplied = await storedServerSideEncryption(encryption);
|
|
1842
|
+
if ("status" in supplied) return supplied;
|
|
1843
|
+
if (!sameStoredServerSideEncryption(expected, supplied)) return this.error(400, "bad_request", "SSE-C source customer encryption does not match");
|
|
1844
|
+
return null;
|
|
1845
|
+
}
|
|
1846
|
+
async resolveCopyDestinationEncryption(destinationBucket, requested) {
|
|
1847
|
+
if (requested !== void 0) return await storedServerSideEncryption(requested);
|
|
1848
|
+
return await storedServerSideEncryption(destinationBucket.info.defaultServerSideEncryption);
|
|
1849
|
+
}
|
|
1850
|
+
async validateCopyPartDestinationEncryption(large, encryption) {
|
|
1851
|
+
if (encryption === void 0) return large.serverSideEncryption.mode === require_encryption.EncryptionMode.SseC ? this.error(400, "bad_request", "SSE-C destination copies require customer encryption") : null;
|
|
1852
|
+
const supplied = await storedServerSideEncryption(encryption);
|
|
1853
|
+
if ("status" in supplied) return supplied;
|
|
1854
|
+
if (!sameStoredServerSideEncryption(large.serverSideEncryption, supplied)) return this.error(400, "bad_request", "Destination server-side encryption does not match");
|
|
1855
|
+
return null;
|
|
1856
|
+
}
|
|
1857
|
+
serveFile(stored, requestHeaders, range) {
|
|
1858
|
+
const encryptionError = this.validateDownloadEncryption(stored, requestHeaders);
|
|
1859
|
+
if (encryptionError !== null) return encryptionError;
|
|
1113
1860
|
const fullData = stored.data;
|
|
1114
1861
|
let data = fullData;
|
|
1115
1862
|
let status = 200;
|
|
@@ -1140,6 +1887,10 @@ var B2Simulator = class {
|
|
|
1140
1887
|
};
|
|
1141
1888
|
for (const [key, value] of Object.entries(fv.fileInfo)) headers[`X-Bz-Info-${require_encoding.encodeFileName(key)}`] = require_encoding.encodeFileName(value);
|
|
1142
1889
|
if (!("src_last_modified_millis" in fv.fileInfo)) headers["X-Bz-Info-src_last_modified_millis"] = String(fv.uploadTimestamp);
|
|
1890
|
+
const unauthorizedToRead = [];
|
|
1891
|
+
this.addDownloadEncryptionHeaders(headers, stored.serverSideEncryption, requestHeaders, unauthorizedToRead);
|
|
1892
|
+
this.addDownloadObjectLockHeaders(headers, fv, requestHeaders, unauthorizedToRead);
|
|
1893
|
+
if (unauthorizedToRead.length > 0) headers[require_download.DownloadHeaderName.ClientUnauthorizedToRead] = unauthorizedToRead.join(",");
|
|
1143
1894
|
if (contentRange !== null) headers["Content-Range"] = contentRange;
|
|
1144
1895
|
return {
|
|
1145
1896
|
status,
|
|
@@ -1147,39 +1898,58 @@ var B2Simulator = class {
|
|
|
1147
1898
|
data
|
|
1148
1899
|
};
|
|
1149
1900
|
}
|
|
1901
|
+
addDownloadEncryptionHeaders(headers, encryption, requestHeaders, unauthorizedToRead) {
|
|
1902
|
+
if (encryption.mode === require_encryption.EncryptionMode.None) return;
|
|
1903
|
+
if (!this.requestHasCapability(requestHeaderValue(requestHeaders, "authorization"), require_auth.Capability.ReadBucketEncryption)) {
|
|
1904
|
+
if (encryption.mode === require_encryption.EncryptionMode.SseB2) unauthorizedToRead.push(require_download.DownloadClientUnauthorizedToReadMarker.ServerSideEncryption);
|
|
1905
|
+
else unauthorizedToRead.push(require_download.DownloadClientUnauthorizedToReadMarker.ServerSideEncryptionCustomerAlgorithm, require_download.DownloadClientUnauthorizedToReadMarker.ServerSideEncryptionCustomerKeyMd5);
|
|
1906
|
+
return;
|
|
1907
|
+
}
|
|
1908
|
+
if (encryption.mode === require_encryption.EncryptionMode.SseB2) {
|
|
1909
|
+
headers[require_download.DownloadHeaderName.ServerSideEncryption] = encryption.algorithm;
|
|
1910
|
+
return;
|
|
1911
|
+
}
|
|
1912
|
+
headers[require_download.DownloadHeaderName.ServerSideEncryptionCustomerAlgorithm] = encryption.algorithm;
|
|
1913
|
+
headers[require_download.DownloadHeaderName.ServerSideEncryptionCustomerKeyMd5] = encryption.customerKeyMd5;
|
|
1914
|
+
}
|
|
1915
|
+
addDownloadObjectLockHeaders(headers, fileVersion, requestHeaders, unauthorizedToRead) {
|
|
1916
|
+
const authToken = requestHeaderValue(requestHeaders, "authorization");
|
|
1917
|
+
const retention = fileVersion.fileRetention.value;
|
|
1918
|
+
if (retention !== null && retention.mode !== null && retention.retainUntilTimestamp !== null) {
|
|
1919
|
+
if (this.requestHasCapability(authToken, require_auth.Capability.ReadFileRetentions)) {
|
|
1920
|
+
headers[require_download.DownloadHeaderName.FileRetentionMode] = retention.mode;
|
|
1921
|
+
headers[require_download.DownloadHeaderName.FileRetentionRetainUntilTimestamp] = String(retention.retainUntilTimestamp);
|
|
1922
|
+
} else unauthorizedToRead.push(require_download.DownloadClientUnauthorizedToReadMarker.FileRetentionMode, require_download.DownloadClientUnauthorizedToReadMarker.FileRetentionRetainUntilTimestamp);
|
|
1923
|
+
}
|
|
1924
|
+
const legalHold = fileVersion.legalHold.value;
|
|
1925
|
+
if (legalHold === null) return;
|
|
1926
|
+
if (this.requestHasCapability(authToken, require_auth.Capability.ReadFileLegalHolds)) {
|
|
1927
|
+
headers[require_download.DownloadHeaderName.FileLegalHold] = legalHold;
|
|
1928
|
+
return;
|
|
1929
|
+
}
|
|
1930
|
+
unauthorizedToRead.push(require_download.DownloadClientUnauthorizedToReadMarker.FileLegalHold);
|
|
1931
|
+
}
|
|
1150
1932
|
authorize(authzHeader, origin = "http://localhost:0") {
|
|
1151
|
-
const
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
require_auth.Capability.ReadFiles,
|
|
1158
|
-
require_auth.Capability.WriteFiles,
|
|
1159
|
-
require_auth.Capability.DeleteFiles,
|
|
1160
|
-
require_auth.Capability.ListKeys,
|
|
1161
|
-
require_auth.Capability.WriteKeys,
|
|
1162
|
-
require_auth.Capability.DeleteKeys,
|
|
1163
|
-
require_auth.Capability.ShareFiles,
|
|
1164
|
-
require_auth.Capability.ReadBucketNotifications,
|
|
1165
|
-
require_auth.Capability.WriteBucketNotifications
|
|
1166
|
-
];
|
|
1167
|
-
const keyForAuth = this.findKeyForAuthHeader(authzHeader);
|
|
1168
|
-
const allowedBuckets = this.allowedBuckets(keyForAuth?.bucketIds);
|
|
1169
|
-
const legacyBucketId = singleBucketId(keyForAuth?.bucketIds);
|
|
1933
|
+
const resolution = this.findAuthorizationGrant(authzHeader);
|
|
1934
|
+
if (resolution.kind === "invalid") return this.error(401, "unauthorized", "application key is not valid");
|
|
1935
|
+
if (resolution.kind === "expired") return this.error(401, "unauthorized", "application key is expired");
|
|
1936
|
+
const { grant } = resolution;
|
|
1937
|
+
const allowedBuckets = this.allowedBuckets(grant.bucketIds);
|
|
1938
|
+
const legacyBucketId = singleBucketId(grant.bucketIds);
|
|
1170
1939
|
const legacyBucketName = legacyBucketId === null ? null : this.buckets.get(legacyBucketId)?.info.bucketName ?? null;
|
|
1171
1940
|
const tokenStr = `sim_auth_token_${this.nextId++}`;
|
|
1172
1941
|
this.issuedTokens.set(tokenStr, {
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1942
|
+
accountId: grant.accountId,
|
|
1943
|
+
capabilities: grant.capabilities,
|
|
1944
|
+
bucketIds: grant.bucketIds,
|
|
1945
|
+
namePrefix: grant.namePrefix,
|
|
1176
1946
|
expiresAt: this.now() + this.authTokenTtlMs,
|
|
1177
|
-
applicationKeyId:
|
|
1947
|
+
applicationKeyId: grant.applicationKeyId
|
|
1178
1948
|
});
|
|
1179
1949
|
return {
|
|
1180
1950
|
status: 200,
|
|
1181
1951
|
body: {
|
|
1182
|
-
accountId: require_ids.accountId(
|
|
1952
|
+
accountId: require_ids.accountId(grant.accountId),
|
|
1183
1953
|
authorizationToken: tokenStr,
|
|
1184
1954
|
apiInfo: { storageApi: {
|
|
1185
1955
|
absoluteMinimumPartSize: this.minimumPartSize,
|
|
@@ -1188,28 +1958,30 @@ var B2Simulator = class {
|
|
|
1188
1958
|
bucketName: legacyBucketName,
|
|
1189
1959
|
downloadUrl: origin,
|
|
1190
1960
|
infoType: "storageApi",
|
|
1191
|
-
namePrefix:
|
|
1961
|
+
namePrefix: grant.namePrefix,
|
|
1192
1962
|
recommendedPartSize: this.recommendedPartSize,
|
|
1193
1963
|
s3ApiUrl: origin,
|
|
1194
1964
|
allowed: {
|
|
1195
|
-
capabilities:
|
|
1965
|
+
capabilities: cloneCapabilities(grant.capabilities),
|
|
1196
1966
|
buckets: allowedBuckets,
|
|
1197
1967
|
bucketId: legacyBucketId === null ? null : require_ids.bucketId(legacyBucketId),
|
|
1198
1968
|
bucketName: legacyBucketName,
|
|
1199
|
-
namePrefix:
|
|
1969
|
+
namePrefix: grant.namePrefix
|
|
1200
1970
|
}
|
|
1201
1971
|
} },
|
|
1202
|
-
applicationKeyExpirationTimestamp:
|
|
1972
|
+
applicationKeyExpirationTimestamp: grant.expirationTimestamp
|
|
1203
1973
|
}
|
|
1204
1974
|
};
|
|
1205
1975
|
}
|
|
1206
1976
|
createBucket(req) {
|
|
1207
1977
|
const nameError = require_validation.validateBucketName(req.bucketName);
|
|
1208
1978
|
if (nameError) return this.error(400, nameError.code, nameError.message);
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1979
|
+
const objectLockEnabled = nextObjectLockEnabled(false, req.fileLockEnabled);
|
|
1980
|
+
if (typeof objectLockEnabled !== "boolean") return this.error(400, objectLockEnabled.code, objectLockEnabled.message);
|
|
1981
|
+
const configError = validateBucketConfigurationFields(req, { objectLockEnabled });
|
|
1982
|
+
if (configError) return this.error(400, configError.code, configError.message);
|
|
1983
|
+
const replicationKeyError = this.validateReplicationApplicationKeys(req.replicationConfiguration, void 0);
|
|
1984
|
+
if (replicationKeyError !== null) return replicationKeyError;
|
|
1213
1985
|
for (const b of this.buckets.values()) if (b.info.bucketName === req.bucketName) return this.error(400, "duplicate_bucket_name", "Bucket name already in use");
|
|
1214
1986
|
const bid = require_ids.bucketId(this.genId("b2_bucket"));
|
|
1215
1987
|
const defaultRetention = req.defaultRetention ?? {
|
|
@@ -1227,7 +1999,7 @@ var B2Simulator = class {
|
|
|
1227
1999
|
fileLockConfiguration: {
|
|
1228
2000
|
isClientAuthorizedToRead: true,
|
|
1229
2001
|
value: {
|
|
1230
|
-
isFileLockEnabled:
|
|
2002
|
+
isFileLockEnabled: objectLockEnabled,
|
|
1231
2003
|
defaultRetention
|
|
1232
2004
|
}
|
|
1233
2005
|
},
|
|
@@ -1235,10 +2007,10 @@ var B2Simulator = class {
|
|
|
1235
2007
|
options: [],
|
|
1236
2008
|
revision: 1,
|
|
1237
2009
|
defaultRetention,
|
|
1238
|
-
replicationConfiguration: req.replicationConfiguration
|
|
2010
|
+
replicationConfiguration: req.replicationConfiguration === void 0 ? {
|
|
1239
2011
|
asReplicationSource: null,
|
|
1240
2012
|
asReplicationDestination: null
|
|
1241
|
-
}
|
|
2013
|
+
} : normalizeReplicationConfiguration(req.replicationConfiguration)
|
|
1242
2014
|
};
|
|
1243
2015
|
this.buckets.set(bid, {
|
|
1244
2016
|
info,
|
|
@@ -1250,6 +2022,8 @@ var B2Simulator = class {
|
|
|
1250
2022
|
};
|
|
1251
2023
|
}
|
|
1252
2024
|
listBuckets(req) {
|
|
2025
|
+
const bucketTypesError = require_validation.validateBucketTypes(req.bucketTypes);
|
|
2026
|
+
if (bucketTypesError) return this.error(400, bucketTypesError.code, bucketTypesError.message);
|
|
1253
2027
|
return {
|
|
1254
2028
|
status: 200,
|
|
1255
2029
|
body: { buckets: [...this.buckets.values()].map((b) => b.info).filter((bucket) => req.bucketId === void 0 || bucket.bucketId === req.bucketId).filter((bucket) => req.bucketName === void 0 || bucket.bucketName === req.bucketName).filter((bucket) => req.bucketTypes === void 0 || req.bucketTypes.includes(bucket.bucketType)) }
|
|
@@ -1258,36 +2032,62 @@ var B2Simulator = class {
|
|
|
1258
2032
|
deleteBucket(req) {
|
|
1259
2033
|
const bucket = this.buckets.get(req.bucketId);
|
|
1260
2034
|
if (!bucket) return this.error(400, "bad_bucket_id", "Bucket not found");
|
|
2035
|
+
if (this.bucketHasContents(bucket)) return this.error(400, "cannot_delete_non_empty_bucket", "Bucket is not empty and cannot be deleted");
|
|
1261
2036
|
this.buckets.delete(req.bucketId);
|
|
1262
2037
|
return {
|
|
1263
2038
|
status: 200,
|
|
1264
2039
|
body: bucket.info
|
|
1265
2040
|
};
|
|
1266
2041
|
}
|
|
2042
|
+
bucketHasContents(bucket) {
|
|
2043
|
+
for (const versions of bucket.files.values()) if (versions.length > 0) return true;
|
|
2044
|
+
for (const large of this.largeFiles.values()) if (large.bucketId === bucket.info.bucketId) return true;
|
|
2045
|
+
return false;
|
|
2046
|
+
}
|
|
1267
2047
|
updateBucket(req) {
|
|
1268
2048
|
const bucket = this.buckets.get(req["bucketId"]);
|
|
1269
2049
|
if (!bucket) return this.error(400, "bad_bucket_id", "Bucket not found");
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
if (
|
|
2050
|
+
const revisionGuard = req["ifRevisionIs"];
|
|
2051
|
+
if (revisionGuard !== void 0) {
|
|
2052
|
+
if (typeof revisionGuard !== "number" || !Number.isInteger(revisionGuard)) return this.error(400, "bad_request", "ifRevisionIs must be an integer");
|
|
2053
|
+
if (revisionGuard !== bucket.info.revision) return this.error(409, "conflict", "ifRevisionIs test failed");
|
|
1273
2054
|
}
|
|
2055
|
+
const objectLockEnabled = nextObjectLockEnabled(bucket.info.fileLockConfiguration.value?.isFileLockEnabled ?? false, req["fileLockEnabled"]);
|
|
2056
|
+
if (typeof objectLockEnabled !== "boolean") return this.error(400, objectLockEnabled.code, objectLockEnabled.message);
|
|
2057
|
+
const configError = validateBucketConfigurationFields({
|
|
2058
|
+
bucketInfo: req["bucketInfo"],
|
|
2059
|
+
corsRules: req["corsRules"],
|
|
2060
|
+
defaultRetention: req["defaultRetention"],
|
|
2061
|
+
lifecycleRules: req["lifecycleRules"],
|
|
2062
|
+
replicationConfiguration: req["replicationConfiguration"]
|
|
2063
|
+
}, { objectLockEnabled });
|
|
2064
|
+
if (configError) return this.error(400, configError.code, configError.message);
|
|
2065
|
+
const replicationKeyError = this.validateReplicationApplicationKeys(req["replicationConfiguration"], req["bucketId"]);
|
|
2066
|
+
if (replicationKeyError !== null) return replicationKeyError;
|
|
1274
2067
|
const updated = {
|
|
1275
2068
|
...bucket.info,
|
|
1276
2069
|
...req["bucketType"] !== void 0 ? { bucketType: req["bucketType"] } : {},
|
|
1277
2070
|
...req["bucketInfo"] !== void 0 ? { bucketInfo: req["bucketInfo"] } : {},
|
|
1278
2071
|
...req["lifecycleRules"] !== void 0 ? { lifecycleRules: req["lifecycleRules"] } : {},
|
|
1279
2072
|
...req["corsRules"] !== void 0 ? { corsRules: req["corsRules"] } : {},
|
|
1280
|
-
...req["replicationConfiguration"] !== void 0 ? { replicationConfiguration: req["replicationConfiguration"] } : {},
|
|
2073
|
+
...req["replicationConfiguration"] !== void 0 ? { replicationConfiguration: normalizeReplicationConfiguration(req["replicationConfiguration"]) } : {},
|
|
1281
2074
|
...req["defaultRetention"] !== void 0 ? {
|
|
1282
2075
|
defaultRetention: req["defaultRetention"],
|
|
1283
2076
|
fileLockConfiguration: {
|
|
1284
2077
|
isClientAuthorizedToRead: true,
|
|
1285
2078
|
value: {
|
|
1286
|
-
isFileLockEnabled:
|
|
2079
|
+
isFileLockEnabled: objectLockEnabled,
|
|
1287
2080
|
defaultRetention: req["defaultRetention"]
|
|
1288
2081
|
}
|
|
1289
2082
|
}
|
|
1290
2083
|
} : {},
|
|
2084
|
+
...req["fileLockEnabled"] !== void 0 && req["defaultRetention"] === void 0 ? { fileLockConfiguration: {
|
|
2085
|
+
isClientAuthorizedToRead: true,
|
|
2086
|
+
value: {
|
|
2087
|
+
isFileLockEnabled: objectLockEnabled,
|
|
2088
|
+
defaultRetention: bucket.info.defaultRetention
|
|
2089
|
+
}
|
|
2090
|
+
} } : {},
|
|
1291
2091
|
...req["defaultServerSideEncryption"] !== void 0 ? { defaultServerSideEncryption: req["defaultServerSideEncryption"] } : {},
|
|
1292
2092
|
revision: bucket.info.revision + 1
|
|
1293
2093
|
};
|
|
@@ -1394,7 +2194,8 @@ var B2Simulator = class {
|
|
|
1394
2194
|
const existing = bucket.files.get(req.fileName);
|
|
1395
2195
|
const stored = {
|
|
1396
2196
|
fileVersion,
|
|
1397
|
-
data: /* @__PURE__ */ new Uint8Array(0)
|
|
2197
|
+
data: /* @__PURE__ */ new Uint8Array(0),
|
|
2198
|
+
serverSideEncryption: { mode: require_encryption.EncryptionMode.None }
|
|
1398
2199
|
};
|
|
1399
2200
|
if (existing) existing.push(stored);
|
|
1400
2201
|
else bucket.files.set(req.fileName, [stored]);
|
|
@@ -1403,16 +2204,19 @@ var B2Simulator = class {
|
|
|
1403
2204
|
body: fileVersion
|
|
1404
2205
|
};
|
|
1405
2206
|
}
|
|
1406
|
-
deleteFileVersion(req) {
|
|
2207
|
+
deleteFileVersion(req, authToken) {
|
|
1407
2208
|
const found = this.findFile(req.fileId);
|
|
1408
2209
|
if (found === null || found.stored.fileVersion.fileName !== req.fileName) return this.error(400, "file_not_present", "File version not found");
|
|
1409
2210
|
const fv = found.stored.fileVersion;
|
|
1410
2211
|
const retention = fv.fileRetention?.value;
|
|
1411
2212
|
const legalHold = fv.legalHold?.value;
|
|
1412
2213
|
const now = Date.now();
|
|
1413
|
-
if (legalHold ===
|
|
1414
|
-
if (retention?.mode ===
|
|
1415
|
-
if (retention?.mode ===
|
|
2214
|
+
if (legalHold === require_lock.LegalHoldValue.On) return this.error(400, "file_lock_legal_hold_protected", "File is on legal hold and cannot be deleted");
|
|
2215
|
+
if (retention?.mode === require_lock.RetentionMode.Compliance && retention.retainUntilTimestamp !== null && retention.retainUntilTimestamp > now) return this.error(400, "file_lock_compliance_protected", `File is under compliance-mode retention and cannot be deleted until ${new Date(retention.retainUntilTimestamp).toISOString()}`);
|
|
2216
|
+
if (retention?.mode === require_lock.RetentionMode.Governance && retention.retainUntilTimestamp !== null && retention.retainUntilTimestamp > now) {
|
|
2217
|
+
const bypassError = this.requireGovernanceBypass(req, authToken, "File is under governance-mode retention; pass bypassGovernance: true to delete");
|
|
2218
|
+
if (bypassError !== null) return bypassError;
|
|
2219
|
+
}
|
|
1416
2220
|
found.versions.splice(found.index, 1);
|
|
1417
2221
|
if (found.versions.length === 0) found.bucket.files.delete(req.fileName);
|
|
1418
2222
|
return {
|
|
@@ -1432,6 +2236,10 @@ var B2Simulator = class {
|
|
|
1432
2236
|
const destBucketId = req.destinationBucketId ?? found.bucketId;
|
|
1433
2237
|
const destBucket = this.buckets.get(destBucketId);
|
|
1434
2238
|
if (!destBucket) return this.error(400, "bad_bucket_id", "Destination bucket not found");
|
|
2239
|
+
const sourceEncryptionError = await this.validateSourceServerSideEncryption(sourceStored, req.sourceServerSideEncryption);
|
|
2240
|
+
if (sourceEncryptionError !== null) return sourceEncryptionError;
|
|
2241
|
+
const destinationEncryption = await this.resolveCopyDestinationEncryption(destBucket, req.destinationServerSideEncryption);
|
|
2242
|
+
if ("status" in destinationEncryption) return destinationEncryption;
|
|
1435
2243
|
let data = sourceStored.data;
|
|
1436
2244
|
let contentSha1 = sourceStored.fileVersion.contentSha1 ?? "none";
|
|
1437
2245
|
if (req.range !== void 0) {
|
|
@@ -1464,11 +2272,14 @@ var B2Simulator = class {
|
|
|
1464
2272
|
contentLength: data.byteLength,
|
|
1465
2273
|
contentSha1,
|
|
1466
2274
|
fileInfo,
|
|
1467
|
-
action: require_file.FileAction.Copy
|
|
2275
|
+
action: require_file.FileAction.Copy,
|
|
2276
|
+
serverSideEncryption: destinationEncryption,
|
|
2277
|
+
...this.newReplicationStatusField(destBucketId, req.fileName)
|
|
1468
2278
|
});
|
|
1469
2279
|
const copied = {
|
|
1470
2280
|
fileVersion,
|
|
1471
|
-
data: new Uint8Array(data)
|
|
2281
|
+
data: new Uint8Array(data),
|
|
2282
|
+
serverSideEncryption: destinationEncryption
|
|
1472
2283
|
};
|
|
1473
2284
|
const existing = destBucket.files.get(req.fileName);
|
|
1474
2285
|
if (existing) existing.push(copied);
|
|
@@ -1479,7 +2290,7 @@ var B2Simulator = class {
|
|
|
1479
2290
|
body: fileVersion
|
|
1480
2291
|
};
|
|
1481
2292
|
}
|
|
1482
|
-
startLargeFile(req) {
|
|
2293
|
+
async startLargeFile(req) {
|
|
1483
2294
|
const bucket = this.buckets.get(req.bucketId);
|
|
1484
2295
|
if (!bucket) return this.error(400, "bad_bucket_id", "Bucket not found");
|
|
1485
2296
|
const nameError = require_validation.validateFileName(req.fileName);
|
|
@@ -1488,8 +2299,13 @@ var B2Simulator = class {
|
|
|
1488
2299
|
const infoError = require_validation.validateFileInfo(req.fileInfo);
|
|
1489
2300
|
if (infoError) return this.error(400, infoError.code, infoError.message);
|
|
1490
2301
|
}
|
|
2302
|
+
const customUploadTimestamp = this.parseCustomUploadTimestamp(req.customUploadTimestamp);
|
|
2303
|
+
if ("status" in customUploadTimestamp) return customUploadTimestamp;
|
|
1491
2304
|
const fid = this.genId("4_z");
|
|
1492
|
-
const uploadTimestamp = this.monotonicTimestamp();
|
|
2305
|
+
const uploadTimestamp = customUploadTimestamp.timestamp ?? this.monotonicTimestamp();
|
|
2306
|
+
const serverSideEncryption = await storedServerSideEncryption(req.serverSideEncryption ?? bucket.info.defaultServerSideEncryption);
|
|
2307
|
+
if ("status" in serverSideEncryption) return serverSideEncryption;
|
|
2308
|
+
const replicationStatus = this.replicationStatusForNewFile(req.bucketId, req.fileName);
|
|
1493
2309
|
const large = {
|
|
1494
2310
|
fileId: fid,
|
|
1495
2311
|
bucketId: req.bucketId,
|
|
@@ -1498,7 +2314,8 @@ var B2Simulator = class {
|
|
|
1498
2314
|
fileInfo: req.fileInfo ?? {},
|
|
1499
2315
|
fileRetention: req.fileRetention ?? defaultFileRetention(bucket.info.defaultRetention, uploadTimestamp),
|
|
1500
2316
|
legalHold: req.legalHold ?? null,
|
|
1501
|
-
|
|
2317
|
+
...this.replicationStatusMetadataFor(replicationStatus),
|
|
2318
|
+
serverSideEncryption,
|
|
1502
2319
|
uploadTimestamp,
|
|
1503
2320
|
parts: /* @__PURE__ */ new Map()
|
|
1504
2321
|
};
|
|
@@ -1524,6 +2341,7 @@ var B2Simulator = class {
|
|
|
1524
2341
|
isClientAuthorizedToRead: true,
|
|
1525
2342
|
value: large.legalHold
|
|
1526
2343
|
},
|
|
2344
|
+
...this.replicationStatusMetadataFor(large.replicationStatus),
|
|
1527
2345
|
serverSideEncryption: publicServerSideEncryption(large.serverSideEncryption),
|
|
1528
2346
|
uploadTimestamp: large.uploadTimestamp
|
|
1529
2347
|
}
|
|
@@ -1553,17 +2371,11 @@ var B2Simulator = class {
|
|
|
1553
2371
|
const bucket = this.buckets.get(large.bucketId);
|
|
1554
2372
|
if (!bucket) return this.error(400, "bad_bucket_id", "Bucket not found");
|
|
1555
2373
|
const sortedParts = [...large.parts.entries()].sort((a, b) => a[0] - b[0]);
|
|
1556
|
-
if (sortedParts.length >
|
|
1557
|
-
for (const [partNumber] of sortedParts) if (!Number.isInteger(partNumber) || partNumber <
|
|
2374
|
+
if (sortedParts.length > B2_MAX_PART_NUMBER) return this.error(400, "bad_request", `multipart upload has ${sortedParts.length} parts; B2 caps at ${B2_MAX_PART_NUMBER}`);
|
|
2375
|
+
for (const [partNumber] of sortedParts) if (!Number.isInteger(partNumber) || partNumber < B2_MIN_PART_NUMBER || partNumber > B2_MAX_PART_NUMBER) return this.error(400, "bad_request", `partNumber ${partNumber} is outside the [${B2_MIN_PART_NUMBER}, ${B2_MAX_PART_NUMBER}] range B2 accepts`);
|
|
1558
2376
|
if (req.partSha1Array.length !== sortedParts.length) return this.error(400, "bad_request", `partSha1Array has ${req.partSha1Array.length} entries but ${sortedParts.length} parts were uploaded`);
|
|
1559
|
-
for (
|
|
1560
|
-
|
|
1561
|
-
if (req.partSha1Array[i]?.toLowerCase() !== part.sha1) return this.error(400, "bad_request", `part ${partNumber} SHA-1 does not match the uploaded part`);
|
|
1562
|
-
}
|
|
1563
|
-
for (let i = 0; i < sortedParts.length - 1; i++) {
|
|
1564
|
-
const [partNumber, part] = sortedParts[i];
|
|
1565
|
-
if (part.data.byteLength < this.minimumPartSize) return this.error(400, "bad_request", `part ${partNumber} (${part.data.byteLength} bytes) is smaller than the minimum part size of ${this.minimumPartSize}`);
|
|
1566
|
-
}
|
|
2377
|
+
for (const [i, [partNumber, part]] of sortedParts.entries()) if (req.partSha1Array[i]?.toLowerCase() !== part.sha1) return this.error(400, "bad_request", `part ${partNumber} SHA-1 does not match the uploaded part`);
|
|
2378
|
+
for (const [partNumber, part] of sortedParts.slice(0, -1)) if (part.data.byteLength < this.minimumPartSize) return this.error(400, "bad_request", `part ${partNumber} (${part.data.byteLength} bytes) is smaller than the minimum part size of ${this.minimumPartSize}`);
|
|
1567
2379
|
let totalSize = 0;
|
|
1568
2380
|
for (const [_, part] of sortedParts) totalSize += part.data.byteLength;
|
|
1569
2381
|
const combined = new Uint8Array(totalSize);
|
|
@@ -1582,11 +2394,14 @@ var B2Simulator = class {
|
|
|
1582
2394
|
action: require_file.FileAction.Upload,
|
|
1583
2395
|
fileRetention: large.fileRetention,
|
|
1584
2396
|
legalHold: large.legalHold,
|
|
1585
|
-
serverSideEncryption: large.serverSideEncryption
|
|
2397
|
+
serverSideEncryption: large.serverSideEncryption,
|
|
2398
|
+
uploadTimestamp: large.uploadTimestamp,
|
|
2399
|
+
...this.replicationStatusMetadataFor(large.replicationStatus)
|
|
1586
2400
|
});
|
|
1587
2401
|
const stored = {
|
|
1588
2402
|
fileVersion,
|
|
1589
|
-
data: combined
|
|
2403
|
+
data: combined,
|
|
2404
|
+
serverSideEncryption: large.serverSideEncryption
|
|
1590
2405
|
};
|
|
1591
2406
|
const existing = bucket.files.get(large.fileName);
|
|
1592
2407
|
if (existing) existing.push(stored);
|
|
@@ -1645,6 +2460,7 @@ var B2Simulator = class {
|
|
|
1645
2460
|
isClientAuthorizedToRead: true,
|
|
1646
2461
|
value: f.legalHold
|
|
1647
2462
|
},
|
|
2463
|
+
...this.replicationStatusMetadataFor(f.replicationStatus),
|
|
1648
2464
|
serverSideEncryption: publicServerSideEncryption(f.serverSideEncryption),
|
|
1649
2465
|
uploadTimestamp: f.uploadTimestamp
|
|
1650
2466
|
})),
|
|
@@ -1664,7 +2480,9 @@ var B2Simulator = class {
|
|
|
1664
2480
|
partNumber,
|
|
1665
2481
|
contentLength: part.data.byteLength,
|
|
1666
2482
|
contentSha1: part.sha1,
|
|
1667
|
-
|
|
2483
|
+
contentMd5: part.contentMd5,
|
|
2484
|
+
serverSideEncryption: publicServerSideEncryption(large.serverSideEncryption),
|
|
2485
|
+
uploadTimestamp: part.uploadTimestamp
|
|
1668
2486
|
}));
|
|
1669
2487
|
return {
|
|
1670
2488
|
status: 200,
|
|
@@ -1677,22 +2495,29 @@ var B2Simulator = class {
|
|
|
1677
2495
|
async copyPart(req) {
|
|
1678
2496
|
const large = this.largeFiles.get(req.largeFileId);
|
|
1679
2497
|
if (!large) return this.error(400, "bad_request", "Large file not found");
|
|
2498
|
+
const partNumberError = this.validatePartNumber(req.partNumber);
|
|
2499
|
+
if (partNumberError !== null) return partNumberError;
|
|
1680
2500
|
const found = this.findFile(req.sourceFileId);
|
|
1681
2501
|
if (found === null) return this.error(404, "file_not_present", "Source file not found");
|
|
1682
2502
|
const sourceStored = found.stored;
|
|
2503
|
+
const sourceEncryptionError = await this.validateSourceServerSideEncryption(sourceStored, req.sourceServerSideEncryption);
|
|
2504
|
+
if (sourceEncryptionError !== null) return sourceEncryptionError;
|
|
2505
|
+
const destinationEncryptionError = await this.validateCopyPartDestinationEncryption(large, req.destinationServerSideEncryption);
|
|
2506
|
+
if (destinationEncryptionError !== null) return destinationEncryptionError;
|
|
1683
2507
|
let partData = sourceStored.data;
|
|
1684
|
-
if (req.range) {
|
|
1685
|
-
const
|
|
1686
|
-
if (
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
partData = sourceStored.data.slice(rs, re + 1);
|
|
1690
|
-
}
|
|
2508
|
+
if (req.range !== void 0) {
|
|
2509
|
+
const parsed = parseRangeHeader(req.range, sourceStored.data.byteLength);
|
|
2510
|
+
if (parsed.kind === "malformed") return this.error(400, "bad_request", `Malformed copy range: ${req.range}`);
|
|
2511
|
+
if (parsed.kind === "unsatisfiable") return this.error(416, "range_not_satisfiable", `Unsatisfiable copy range: ${req.range}`);
|
|
2512
|
+
partData = sourceStored.data.subarray(parsed.start, parsed.end + 1);
|
|
1691
2513
|
}
|
|
1692
2514
|
const sha1 = await require_hash.sha1Hex(partData);
|
|
2515
|
+
const uploadTimestamp = this.monotonicTimestamp();
|
|
1693
2516
|
large.parts.set(req.partNumber, {
|
|
1694
2517
|
data: new Uint8Array(partData),
|
|
1695
|
-
sha1
|
|
2518
|
+
sha1,
|
|
2519
|
+
contentMd5: null,
|
|
2520
|
+
uploadTimestamp
|
|
1696
2521
|
});
|
|
1697
2522
|
return {
|
|
1698
2523
|
status: 200,
|
|
@@ -1700,39 +2525,80 @@ var B2Simulator = class {
|
|
|
1700
2525
|
fileId: req.largeFileId,
|
|
1701
2526
|
partNumber: req.partNumber,
|
|
1702
2527
|
contentLength: partData.byteLength,
|
|
1703
|
-
contentSha1: sha1
|
|
2528
|
+
contentSha1: sha1,
|
|
2529
|
+
contentMd5: null,
|
|
2530
|
+
serverSideEncryption: publicServerSideEncryption(large.serverSideEncryption),
|
|
2531
|
+
uploadTimestamp
|
|
1704
2532
|
}
|
|
1705
2533
|
};
|
|
1706
2534
|
}
|
|
1707
2535
|
getDownloadAuthorization(req) {
|
|
2536
|
+
this.purgeExpiredDownloadAuthorizationTokens();
|
|
1708
2537
|
if (!this.buckets.has(req.bucketId)) return this.error(400, "bad_bucket_id", "Bucket not found");
|
|
2538
|
+
const prefixError = require_validation.validateDownloadAuthorizationPrefix(req.fileNamePrefix);
|
|
2539
|
+
if (prefixError) return this.error(400, prefixError.code, prefixError.message);
|
|
2540
|
+
const durationError = require_validation.validateDownloadAuthorizationDuration(req.validDurationInSeconds);
|
|
2541
|
+
if (durationError) return this.error(400, durationError.code, durationError.message);
|
|
2542
|
+
const overrideError = validateDownloadResponseOverrides(req);
|
|
2543
|
+
if (overrideError) return this.error(400, overrideError.code, overrideError.message);
|
|
2544
|
+
let authorizationToken = randomDownloadAuthorizationToken();
|
|
2545
|
+
while (this.downloadAuthorizationTokens.has(authorizationToken)) authorizationToken = randomDownloadAuthorizationToken();
|
|
2546
|
+
const expiresAt = this.now() + req.validDurationInSeconds * 1e3;
|
|
2547
|
+
this.downloadAuthorizationTokens.set(authorizationToken, {
|
|
2548
|
+
bucketId: req.bucketId,
|
|
2549
|
+
fileNamePrefix: req.fileNamePrefix,
|
|
2550
|
+
expiresAt,
|
|
2551
|
+
responseHeaderOverrides: pickDownloadResponseOverrides(req)
|
|
2552
|
+
});
|
|
2553
|
+
this.pushDownloadAuthorizationExpiry({
|
|
2554
|
+
token: authorizationToken,
|
|
2555
|
+
expiresAt
|
|
2556
|
+
});
|
|
1709
2557
|
return {
|
|
1710
2558
|
status: 200,
|
|
1711
2559
|
body: {
|
|
1712
2560
|
bucketId: req.bucketId,
|
|
1713
2561
|
fileNamePrefix: req.fileNamePrefix,
|
|
1714
|
-
authorizationToken
|
|
2562
|
+
authorizationToken
|
|
1715
2563
|
}
|
|
1716
2564
|
};
|
|
1717
2565
|
}
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
if (
|
|
1721
|
-
|
|
2566
|
+
createKeyCapabilitiesOutsideGrant(authToken, requested) {
|
|
2567
|
+
const creator = authToken === void 0 ? void 0 : this.issuedTokens.get(authToken);
|
|
2568
|
+
if (creator === void 0) return this.strictAuth ? requested : [];
|
|
2569
|
+
if (creator.applicationKeyId === null) return [];
|
|
2570
|
+
const granted = new Set(creator.capabilities);
|
|
2571
|
+
return requested.filter((capability) => !granted.has(capability));
|
|
2572
|
+
}
|
|
2573
|
+
createKeyCreatorAccountId(authToken) {
|
|
2574
|
+
return (authToken === void 0 ? void 0 : this.issuedTokens.get(authToken))?.accountId ?? this.accountId;
|
|
2575
|
+
}
|
|
2576
|
+
createKey(req, version, authToken) {
|
|
2577
|
+
if (version === "v4" && hasOwnField(req, "bucketId")) return this.error(400, "bad_request", "bucketId is not accepted by v4 b2_create_key; use bucketIds");
|
|
2578
|
+
if (version !== "v4" && req.bucketId !== void 0 && req.bucketIds !== void 0) return this.error(400, "bad_request", "b2_create_key accepts either bucketIds or bucketId");
|
|
2579
|
+
const bucketIds = version !== "v4" && req.bucketId !== void 0 ? Object.freeze([req.bucketId]) : normalizeKeyBucketIds(req);
|
|
1722
2580
|
const namePrefix = req.namePrefix === void 0 || req.namePrefix === "" ? null : req.namePrefix;
|
|
1723
|
-
|
|
2581
|
+
const creatorAccountId = this.createKeyCreatorAccountId(authToken);
|
|
2582
|
+
if (req.accountId !== creatorAccountId) return this.error(400, "bad_request", "accountId must match authorized account");
|
|
2583
|
+
const keyNameError = require_validation.validateCreateKeyName(req.keyName);
|
|
2584
|
+
if (keyNameError) return this.error(400, keyNameError.code, keyNameError.message);
|
|
2585
|
+
const keyName = req.keyName;
|
|
2586
|
+
const capabilitiesError = require_validation.validateCreateKeyCapabilities(req.capabilities);
|
|
2587
|
+
if (capabilitiesError) return this.error(400, capabilitiesError.code, capabilitiesError.message);
|
|
2588
|
+
const capabilities = require_validation.normalizeCreateKeyCapabilities(req.capabilities);
|
|
2589
|
+
const unauthorizedCapabilities = this.createKeyCapabilitiesOutsideGrant(authToken, capabilities);
|
|
2590
|
+
if (unauthorizedCapabilities.length > 0) return this.error(400, "bad_request", `requested capabilities exceed creator grant: ${unauthorizedCapabilities.join(", ")}`);
|
|
2591
|
+
if (hasKeyManagementCapability(capabilities) && (bucketIds !== null || namePrefix !== null)) return this.error(400, "bad_request", "key-management capabilities are account-level and cannot be bucket or name-prefix scoped");
|
|
1724
2592
|
const kid = this.genId("sim_key");
|
|
1725
|
-
const appKey = this.genId("sim_secret");
|
|
1726
|
-
const expiration = req.validDurationInSeconds !== void 0 ? Date.now() + req.validDurationInSeconds * 1e3 : null;
|
|
1727
2593
|
const stored = {
|
|
1728
2594
|
applicationKeyId: kid,
|
|
1729
|
-
keyName
|
|
1730
|
-
capabilities
|
|
1731
|
-
accountId:
|
|
1732
|
-
applicationKey:
|
|
2595
|
+
keyName,
|
|
2596
|
+
capabilities,
|
|
2597
|
+
accountId: creatorAccountId,
|
|
2598
|
+
applicationKey: this.genId("sim_secret"),
|
|
1733
2599
|
bucketIds,
|
|
1734
2600
|
namePrefix,
|
|
1735
|
-
expirationTimestamp:
|
|
2601
|
+
expirationTimestamp: req.validDurationInSeconds !== void 0 ? this.now() + req.validDurationInSeconds * 1e3 : null
|
|
1736
2602
|
};
|
|
1737
2603
|
this.keys.set(kid, stored);
|
|
1738
2604
|
return {
|
|
@@ -1741,7 +2607,7 @@ var B2Simulator = class {
|
|
|
1741
2607
|
keyName: stored.keyName,
|
|
1742
2608
|
applicationKeyId: stored.applicationKeyId,
|
|
1743
2609
|
applicationKey: stored.applicationKey,
|
|
1744
|
-
capabilities: stored.capabilities,
|
|
2610
|
+
capabilities: cloneCapabilities(stored.capabilities),
|
|
1745
2611
|
accountId: stored.accountId,
|
|
1746
2612
|
expirationTimestamp: stored.expirationTimestamp,
|
|
1747
2613
|
bucketIds: cloneBucketIds(stored.bucketIds),
|
|
@@ -1766,7 +2632,7 @@ var B2Simulator = class {
|
|
|
1766
2632
|
keys: allKeys.slice(0, max).map((k) => ({
|
|
1767
2633
|
keyName: k.keyName,
|
|
1768
2634
|
applicationKeyId: k.applicationKeyId,
|
|
1769
|
-
capabilities: k.capabilities,
|
|
2635
|
+
capabilities: cloneCapabilities(k.capabilities),
|
|
1770
2636
|
accountId: k.accountId,
|
|
1771
2637
|
expirationTimestamp: k.expirationTimestamp,
|
|
1772
2638
|
bucketIds: cloneBucketIds(k.bucketIds),
|
|
@@ -1792,7 +2658,7 @@ var B2Simulator = class {
|
|
|
1792
2658
|
body: {
|
|
1793
2659
|
keyName: key.keyName,
|
|
1794
2660
|
applicationKeyId: key.applicationKeyId,
|
|
1795
|
-
capabilities: key.capabilities,
|
|
2661
|
+
capabilities: cloneCapabilities(key.capabilities),
|
|
1796
2662
|
accountId: key.accountId,
|
|
1797
2663
|
expirationTimestamp: key.expirationTimestamp,
|
|
1798
2664
|
bucketIds: cloneBucketIds(key.bucketIds),
|
|
@@ -1802,31 +2668,45 @@ var B2Simulator = class {
|
|
|
1802
2668
|
}
|
|
1803
2669
|
};
|
|
1804
2670
|
}
|
|
1805
|
-
updateFileRetention(req) {
|
|
2671
|
+
updateFileRetention(req, authToken) {
|
|
1806
2672
|
const found = this.findFile(req.fileId);
|
|
1807
2673
|
if (found === null || found.stored.fileVersion.fileName !== req.fileName) return this.error(404, "file_not_present", "File not found");
|
|
2674
|
+
const fileLockError = this.requireFileLockEnabled(found.bucket);
|
|
2675
|
+
if (fileLockError !== null) return fileLockError;
|
|
2676
|
+
const fileRetention = parseFileRetentionValue(req.fileRetention, Date.now());
|
|
2677
|
+
if (fileRetention === null) return this.error(400, "bad_request", "fileRetention must use a valid mode and retainUntilTimestamp pair");
|
|
2678
|
+
const current = found.stored.fileVersion.fileRetention?.value;
|
|
2679
|
+
if (current?.mode === require_lock.RetentionMode.Compliance && (fileRetention.mode !== require_lock.RetentionMode.Compliance || isRetentionWeakened(current, fileRetention))) return this.error(400, "file_lock_compliance_protected", "Compliance-mode retention cannot be shortened or removed");
|
|
2680
|
+
if (requiresGovernanceBypass(current, fileRetention)) {
|
|
2681
|
+
const bypassError = this.requireGovernanceBypass(req, authToken, "Governance-mode retention cannot be shortened, removed, or converted without bypassGovernance");
|
|
2682
|
+
if (bypassError !== null) return bypassError;
|
|
2683
|
+
}
|
|
1808
2684
|
found.versions[found.index] = {
|
|
1809
2685
|
fileVersion: {
|
|
1810
2686
|
...found.stored.fileVersion,
|
|
1811
2687
|
fileRetention: {
|
|
1812
2688
|
isClientAuthorizedToRead: true,
|
|
1813
|
-
value:
|
|
2689
|
+
value: fileRetention
|
|
1814
2690
|
}
|
|
1815
2691
|
},
|
|
1816
|
-
data: found.stored.data
|
|
2692
|
+
data: found.stored.data,
|
|
2693
|
+
serverSideEncryption: found.stored.serverSideEncryption
|
|
1817
2694
|
};
|
|
1818
2695
|
return {
|
|
1819
2696
|
status: 200,
|
|
1820
2697
|
body: {
|
|
1821
2698
|
fileName: req.fileName,
|
|
1822
2699
|
fileId: req.fileId,
|
|
1823
|
-
fileRetention
|
|
2700
|
+
fileRetention
|
|
1824
2701
|
}
|
|
1825
2702
|
};
|
|
1826
2703
|
}
|
|
1827
2704
|
updateFileLegalHold(req) {
|
|
1828
2705
|
const found = this.findFile(req.fileId);
|
|
1829
2706
|
if (found === null || found.stored.fileVersion.fileName !== req.fileName) return this.error(404, "file_not_present", "File not found");
|
|
2707
|
+
const fileLockError = this.requireFileLockEnabled(found.bucket);
|
|
2708
|
+
if (fileLockError !== null) return fileLockError;
|
|
2709
|
+
if (req.legalHold !== require_lock.LegalHoldValue.On && req.legalHold !== require_lock.LegalHoldValue.Off) return this.error(400, "bad_request", "legalHold must be \"on\" or \"off\"");
|
|
1830
2710
|
found.versions[found.index] = {
|
|
1831
2711
|
fileVersion: {
|
|
1832
2712
|
...found.stored.fileVersion,
|
|
@@ -1835,7 +2715,8 @@ var B2Simulator = class {
|
|
|
1835
2715
|
value: req.legalHold
|
|
1836
2716
|
}
|
|
1837
2717
|
},
|
|
1838
|
-
data: found.stored.data
|
|
2718
|
+
data: found.stored.data,
|
|
2719
|
+
serverSideEncryption: found.stored.serverSideEncryption
|
|
1839
2720
|
};
|
|
1840
2721
|
return {
|
|
1841
2722
|
status: 200,
|
|
@@ -1859,6 +2740,8 @@ var B2Simulator = class {
|
|
|
1859
2740
|
}
|
|
1860
2741
|
setBucketNotificationRules(req) {
|
|
1861
2742
|
if (!this.buckets.has(req.bucketId)) return this.error(400, "bad_bucket_id", "Bucket not found");
|
|
2743
|
+
const rulesError = require_validation.validateNotificationRules(req.eventNotificationRules);
|
|
2744
|
+
if (rulesError) return this.error(400, rulesError.code, rulesError.message);
|
|
1862
2745
|
this.notificationRules.set(req.bucketId, req.eventNotificationRules);
|
|
1863
2746
|
return {
|
|
1864
2747
|
status: 200,
|
|
@@ -1916,11 +2799,23 @@ var B2Simulator = class {
|
|
|
1916
2799
|
isClientAuthorizedToRead: true,
|
|
1917
2800
|
value: params.legalHold ?? null
|
|
1918
2801
|
},
|
|
1919
|
-
replicationStatus
|
|
2802
|
+
...this.replicationStatusMetadataFor(params.replicationStatus),
|
|
1920
2803
|
serverSideEncryption: publicServerSideEncryption(params.serverSideEncryption ?? { mode: require_encryption.EncryptionMode.None }),
|
|
1921
|
-
uploadTimestamp: this.monotonicTimestamp()
|
|
2804
|
+
uploadTimestamp: params.uploadTimestamp ?? this.monotonicTimestamp()
|
|
1922
2805
|
};
|
|
1923
2806
|
}
|
|
2807
|
+
newReplicationStatusField(bucketId, fileName) {
|
|
2808
|
+
return this.replicationStatusMetadataFor(this.replicationStatusForNewFile(bucketId, fileName));
|
|
2809
|
+
}
|
|
2810
|
+
replicationStatusMetadataFor(replicationStatus) {
|
|
2811
|
+
return replicationStatus === void 0 ? {} : { replicationStatus };
|
|
2812
|
+
}
|
|
2813
|
+
replicationStatusForNewFile(bucketId, fileName) {
|
|
2814
|
+
return this.matchingEnabledReplicationRules(bucketId, fileName).length > 0 ? "PENDING" : void 0;
|
|
2815
|
+
}
|
|
2816
|
+
matchingEnabledReplicationRules(bucketId, fileName) {
|
|
2817
|
+
return (this.buckets.get(bucketId)?.info.replicationConfiguration.asReplicationSource?.replicationRules)?.filter((rule) => rule.isEnabled && fileName.startsWith(rule.fileNamePrefix)) ?? [];
|
|
2818
|
+
}
|
|
1924
2819
|
error(status, code, message) {
|
|
1925
2820
|
return {
|
|
1926
2821
|
status,
|
|
@@ -1948,8 +2843,7 @@ var B2Simulator = class {
|
|
|
1948
2843
|
* used to match against `EventNotificationRule.eventTypes` globs.
|
|
1949
2844
|
*/
|
|
1950
2845
|
firePostUploadHooks(fileVersion, bucketId, eventType) {
|
|
1951
|
-
|
|
1952
|
-
if (!bucket) return;
|
|
2846
|
+
if (!this.buckets.get(bucketId)) return;
|
|
1953
2847
|
if (this.onWebhookDeliver !== void 0) {
|
|
1954
2848
|
const rules = this.notificationRules.get(bucketId) ?? [];
|
|
1955
2849
|
for (const rule of rules) {
|
|
@@ -1963,18 +2857,13 @@ var B2Simulator = class {
|
|
|
1963
2857
|
}));
|
|
1964
2858
|
}
|
|
1965
2859
|
}
|
|
1966
|
-
if (this.onReplicate !== void 0) {
|
|
1967
|
-
const
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
sourceFileVersion: fileVersion,
|
|
1974
|
-
sourceBucketId: bucketId,
|
|
1975
|
-
destinationBucketId: rule.destinationBucketId
|
|
1976
|
-
}));
|
|
1977
|
-
}
|
|
2860
|
+
if (this.onReplicate !== void 0) for (const rule of this.matchingEnabledReplicationRules(bucketId, fileVersion.fileName)) {
|
|
2861
|
+
const hook = this.onReplicate;
|
|
2862
|
+
this.dispatchHook("replication", () => hook({
|
|
2863
|
+
sourceFileVersion: fileVersion,
|
|
2864
|
+
sourceBucketId: bucketId,
|
|
2865
|
+
destinationBucketId: rule.destinationBucketId
|
|
2866
|
+
}));
|
|
1978
2867
|
}
|
|
1979
2868
|
}
|
|
1980
2869
|
/**
|
|
@@ -2058,6 +2947,83 @@ function buildFaultResponse(fault) {
|
|
|
2058
2947
|
arrayBuffer: () => Promise.resolve(require_text_codec.utf8Encoder.encode(body).buffer)
|
|
2059
2948
|
};
|
|
2060
2949
|
}
|
|
2950
|
+
function rawUrlPathContainsLiteralBackslash(rawUrl) {
|
|
2951
|
+
const schemeEnd = rawUrl.indexOf("://");
|
|
2952
|
+
const authorityStart = schemeEnd === -1 ? 0 : schemeEnd + 3;
|
|
2953
|
+
const urlEndCandidates = [rawUrl.indexOf("?", authorityStart), rawUrl.indexOf("#", authorityStart)].filter((index) => index !== -1);
|
|
2954
|
+
const urlEnd = urlEndCandidates.length === 0 ? rawUrl.length : Math.min(...urlEndCandidates);
|
|
2955
|
+
const pathStartCandidates = [rawUrl.indexOf("/", authorityStart), rawUrl.indexOf("\\", authorityStart)].filter((index) => index !== -1 && index < urlEnd);
|
|
2956
|
+
if (pathStartCandidates.length === 0) return false;
|
|
2957
|
+
const pathStart = Math.min(...pathStartCandidates);
|
|
2958
|
+
const pathEndCandidates = [rawUrl.indexOf("?", pathStart), rawUrl.indexOf("#", pathStart)].filter((index) => index !== -1);
|
|
2959
|
+
const pathEnd = pathEndCandidates.length === 0 ? rawUrl.length : Math.min(...pathEndCandidates);
|
|
2960
|
+
return rawUrl.slice(pathStart, pathEnd).includes("\\");
|
|
2961
|
+
}
|
|
2962
|
+
function badRequestJson(message) {
|
|
2963
|
+
return {
|
|
2964
|
+
status: 400,
|
|
2965
|
+
body: {
|
|
2966
|
+
status: 400,
|
|
2967
|
+
code: "bad_request",
|
|
2968
|
+
message
|
|
2969
|
+
}
|
|
2970
|
+
};
|
|
2971
|
+
}
|
|
2972
|
+
function checkedUploadBody(data, expectedLength) {
|
|
2973
|
+
if (expectedLength !== null && data.byteLength !== expectedLength) return { error: badRequestJson(contentLengthMismatchMessage(expectedLength, data.byteLength)) };
|
|
2974
|
+
return { data };
|
|
2975
|
+
}
|
|
2976
|
+
function copyBytes(bytes) {
|
|
2977
|
+
const copy = new Uint8Array(bytes.byteLength);
|
|
2978
|
+
copy.set(bytes);
|
|
2979
|
+
return copy;
|
|
2980
|
+
}
|
|
2981
|
+
function concatenateChunks(chunks, totalLength) {
|
|
2982
|
+
if (chunks.length === 0) return /* @__PURE__ */ new Uint8Array(0);
|
|
2983
|
+
if (chunks.length === 1) return copyBytes(chunks[0]);
|
|
2984
|
+
const data = new Uint8Array(totalLength);
|
|
2985
|
+
let offset = 0;
|
|
2986
|
+
for (const chunk of chunks) {
|
|
2987
|
+
data.set(chunk, offset);
|
|
2988
|
+
offset += chunk.byteLength;
|
|
2989
|
+
}
|
|
2990
|
+
return data;
|
|
2991
|
+
}
|
|
2992
|
+
async function readStreamingUploadBody(stream, expectedLength) {
|
|
2993
|
+
const reader = stream.getReader();
|
|
2994
|
+
const chunks = [];
|
|
2995
|
+
let totalLength = 0;
|
|
2996
|
+
try {
|
|
2997
|
+
while (true) {
|
|
2998
|
+
const { done, value } = await reader.read();
|
|
2999
|
+
if (done) break;
|
|
3000
|
+
if (value === void 0) continue;
|
|
3001
|
+
const chunk = value instanceof Uint8Array ? value : new Uint8Array(value);
|
|
3002
|
+
totalLength += chunk.byteLength;
|
|
3003
|
+
if (expectedLength !== null && totalLength > expectedLength) {
|
|
3004
|
+
await reader.cancel().catch(() => void 0);
|
|
3005
|
+
return { error: badRequestJson(contentLengthMismatchMessage(expectedLength, totalLength)) };
|
|
3006
|
+
}
|
|
3007
|
+
chunks.push(chunk);
|
|
3008
|
+
}
|
|
3009
|
+
} finally {
|
|
3010
|
+
reader.releaseLock();
|
|
3011
|
+
}
|
|
3012
|
+
if (expectedLength !== null && totalLength !== expectedLength) return { error: badRequestJson(contentLengthMismatchMessage(expectedLength, totalLength)) };
|
|
3013
|
+
return { data: concatenateChunks(chunks, totalLength) };
|
|
3014
|
+
}
|
|
3015
|
+
async function readUploadBody(body, expectedLength) {
|
|
3016
|
+
if (body instanceof ReadableStream) return readStreamingUploadBody(body, expectedLength);
|
|
3017
|
+
if (body instanceof ArrayBuffer) return checkedUploadBody(new Uint8Array(body), expectedLength);
|
|
3018
|
+
if (ArrayBuffer.isView(body)) {
|
|
3019
|
+
const view = body;
|
|
3020
|
+
return checkedUploadBody(copyBytes(new Uint8Array(view.buffer, view.byteOffset, view.byteLength)), expectedLength);
|
|
3021
|
+
}
|
|
3022
|
+
if (typeof Blob !== "undefined" && body instanceof Blob) {
|
|
3023
|
+
if (expectedLength !== null && body.size !== expectedLength) return { error: badRequestJson(contentLengthMismatchMessage(expectedLength, body.size)) };
|
|
3024
|
+
}
|
|
3025
|
+
return checkedUploadBody(new Uint8Array(await new Response(body).arrayBuffer()), expectedLength);
|
|
3026
|
+
}
|
|
2061
3027
|
var SimulatorTransport = class {
|
|
2062
3028
|
sim;
|
|
2063
3029
|
constructor(sim) {
|
|
@@ -2067,6 +3033,12 @@ var SimulatorTransport = class {
|
|
|
2067
3033
|
const url = request.url;
|
|
2068
3034
|
const fault = this.sim.consumeMatchingFault(url);
|
|
2069
3035
|
if (fault !== null) return buildFaultResponse(fault);
|
|
3036
|
+
if (rawUrlPathContainsLiteralBackslash(url)) return buildFaultResponse({
|
|
3037
|
+
on: "\\",
|
|
3038
|
+
status: 400,
|
|
3039
|
+
code: "bad_request",
|
|
3040
|
+
message: "URL path must not contain literal backslashes"
|
|
3041
|
+
});
|
|
2070
3042
|
const headers = {};
|
|
2071
3043
|
if (request.headers) for (const [k, v] of Object.entries(request.headers)) headers[k.toLowerCase()] = v;
|
|
2072
3044
|
const parsedUrl = new URL(url);
|
|
@@ -2074,10 +3046,15 @@ var SimulatorTransport = class {
|
|
|
2074
3046
|
const isUpload = endpoint === "b2_upload_file" || endpoint === "b2_upload_part";
|
|
2075
3047
|
if (parsedUrl.pathname.includes("b2_download_file_by_id") || parsedUrl.pathname.includes("/file/")) {
|
|
2076
3048
|
const method = request.method === "HEAD" ? "HEAD" : "GET";
|
|
2077
|
-
const result = this.sim.handleDownload(parsedUrl.pathname + parsedUrl.search, headers, method);
|
|
3049
|
+
const result = await this.sim.handleDownload(parsedUrl.pathname + parsedUrl.search, headers, method);
|
|
2078
3050
|
const data = result.data ?? /* @__PURE__ */ new Uint8Array(0);
|
|
2079
3051
|
const responseHeaders = new Headers(result.headers);
|
|
2080
3052
|
responseHeaders.set("Content-Type", result.headers["Content-Type"] ?? "application/octet-stream");
|
|
3053
|
+
let decodedText = null;
|
|
3054
|
+
const text = () => {
|
|
3055
|
+
decodedText ??= require_text_codec.utf8Decoder.decode(data);
|
|
3056
|
+
return decodedText;
|
|
3057
|
+
};
|
|
2081
3058
|
const body = method === "HEAD" || result.data === null ? null : new ReadableStream({ start(controller) {
|
|
2082
3059
|
controller.enqueue(data);
|
|
2083
3060
|
controller.close();
|
|
@@ -2086,15 +3063,25 @@ var SimulatorTransport = class {
|
|
|
2086
3063
|
status: result.status,
|
|
2087
3064
|
headers: responseHeaders,
|
|
2088
3065
|
body,
|
|
2089
|
-
json: () =>
|
|
2090
|
-
|
|
3066
|
+
json: () => {
|
|
3067
|
+
try {
|
|
3068
|
+
return Promise.resolve(JSON.parse(text()));
|
|
3069
|
+
} catch {
|
|
3070
|
+
return Promise.reject(/* @__PURE__ */ new Error("Download response is not JSON"));
|
|
3071
|
+
}
|
|
3072
|
+
},
|
|
3073
|
+
text: () => Promise.resolve(text()),
|
|
2091
3074
|
arrayBuffer: () => Promise.resolve(data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength))
|
|
2092
3075
|
};
|
|
2093
3076
|
}
|
|
2094
3077
|
let result;
|
|
2095
|
-
if (isUpload
|
|
2096
|
-
const
|
|
2097
|
-
result =
|
|
3078
|
+
if (isUpload) {
|
|
3079
|
+
const parsedContentLength = parseContentLengthHeader(headers);
|
|
3080
|
+
if (parsedContentLength.kind === "error") result = badRequestJson(parsedContentLength.message);
|
|
3081
|
+
else {
|
|
3082
|
+
const uploadBody = await readUploadBody(request.body ?? /* @__PURE__ */ new Uint8Array(0), parsedContentLength.kind === "ok" ? parsedContentLength.expectedLength : null);
|
|
3083
|
+
result = "error" in uploadBody ? uploadBody.error : await this.sim.handleUpload(url, headers, uploadBody.data);
|
|
3084
|
+
}
|
|
2098
3085
|
} else {
|
|
2099
3086
|
let body = null;
|
|
2100
3087
|
if (request.body) {
|
|
@@ -2104,7 +3091,7 @@ var SimulatorTransport = class {
|
|
|
2104
3091
|
} catch {
|
|
2105
3092
|
body = text;
|
|
2106
3093
|
}
|
|
2107
|
-
}
|
|
3094
|
+
} else if (request.method.toUpperCase() === "GET" && require_partner.isPartnerQueryEndpoint(apiPathParts(parsedUrl.pathname).endpoint)) body = queryParamsBody(parsedUrl.searchParams);
|
|
2108
3095
|
result = await this.sim.handleRequest(request.method, parsedUrl.origin, parsedUrl.pathname, headers, body);
|
|
2109
3096
|
}
|
|
2110
3097
|
const responseBody = JSON.stringify(result.body);
|
|
@@ -2127,9 +3114,13 @@ exports.BUCKET_INFO_MAX_KEYS = require_validation.BUCKET_INFO_MAX_KEYS;
|
|
|
2127
3114
|
exports.BUCKET_INFO_VALUE_MAX = require_validation.BUCKET_INFO_VALUE_MAX;
|
|
2128
3115
|
exports.BUCKET_NAME_MAX = require_b2_naming.BUCKET_NAME_MAX;
|
|
2129
3116
|
exports.BUCKET_NAME_MIN = require_b2_naming.BUCKET_NAME_MIN;
|
|
3117
|
+
exports.DOWNLOAD_AUTH_DURATION_MAX_SECONDS = require_validation.DOWNLOAD_AUTH_DURATION_MAX_SECONDS;
|
|
3118
|
+
exports.DOWNLOAD_AUTH_DURATION_MIN_SECONDS = require_validation.DOWNLOAD_AUTH_DURATION_MIN_SECONDS;
|
|
2130
3119
|
exports.FILE_INFO_TOTAL_MAX = require_validation.FILE_INFO_TOTAL_MAX;
|
|
2131
3120
|
exports.FILE_INFO_VALUE_MAX = require_validation.FILE_INFO_VALUE_MAX;
|
|
2132
3121
|
exports.FILE_NAME_MAX_BYTES = require_b2_naming.FILE_NAME_MAX_BYTES;
|
|
3122
|
+
exports.KEY_NAME_MAX = require_validation.KEY_NAME_MAX;
|
|
3123
|
+
exports.KEY_NAME_MIN = require_validation.KEY_NAME_MIN;
|
|
2133
3124
|
exports.LIST_ENDPOINT_CAPS = require_validation.LIST_ENDPOINT_CAPS;
|
|
2134
3125
|
|
|
2135
3126
|
//# sourceMappingURL=index.cjs.map
|