@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.
Files changed (460) hide show
  1. package/CHANGELOG.md +42 -1
  2. package/README.md +20 -2
  3. package/dist/_virtual/_b2-sdk-version-json.cjs +1 -1
  4. package/dist/_virtual/_b2-sdk-version-json.js +1 -1
  5. package/dist/auth/file.cjs.map +1 -1
  6. package/dist/backup/client.cjs +206 -0
  7. package/dist/backup/client.cjs.map +1 -0
  8. package/dist/backup/client.d.cts +238 -0
  9. package/dist/backup/client.d.cts.map +1 -0
  10. package/dist/backup/client.d.ts +238 -0
  11. package/dist/backup/client.d.ts.map +1 -0
  12. package/dist/backup/client.js +206 -0
  13. package/dist/backup/client.js.map +1 -0
  14. package/dist/backup/index.cjs +15 -0
  15. package/dist/backup/index.d.cts +22 -0
  16. package/dist/backup/index.d.cts.map +1 -0
  17. package/dist/backup/index.d.ts +22 -0
  18. package/dist/backup/index.d.ts.map +1 -0
  19. package/dist/backup/index.js +7 -0
  20. package/dist/backup/raw.cjs +146 -0
  21. package/dist/backup/raw.cjs.map +1 -0
  22. package/dist/backup/raw.d.cts +122 -0
  23. package/dist/backup/raw.d.cts.map +1 -0
  24. package/dist/backup/raw.d.ts +122 -0
  25. package/dist/backup/raw.d.ts.map +1 -0
  26. package/dist/backup/raw.js +146 -0
  27. package/dist/backup/raw.js.map +1 -0
  28. package/dist/bucket.cjs +2 -1
  29. package/dist/bucket.cjs.map +1 -1
  30. package/dist/bucket.js +2 -1
  31. package/dist/bucket.js.map +1 -1
  32. package/dist/copy/large.cjs +5 -3
  33. package/dist/copy/large.cjs.map +1 -1
  34. package/dist/copy/large.d.cts.map +1 -1
  35. package/dist/copy/large.d.ts.map +1 -1
  36. package/dist/copy/large.js +5 -3
  37. package/dist/copy/large.js.map +1 -1
  38. package/dist/download/checksum.cjs +6 -2
  39. package/dist/download/checksum.cjs.map +1 -1
  40. package/dist/download/checksum.js +6 -2
  41. package/dist/download/checksum.js.map +1 -1
  42. package/dist/download/parallel.cjs +52 -15
  43. package/dist/download/parallel.cjs.map +1 -1
  44. package/dist/download/parallel.d.cts +8 -1
  45. package/dist/download/parallel.d.cts.map +1 -1
  46. package/dist/download/parallel.d.ts +8 -1
  47. package/dist/download/parallel.d.ts.map +1 -1
  48. package/dist/download/parallel.js +52 -15
  49. package/dist/download/parallel.js.map +1 -1
  50. package/dist/download/single.cjs +141 -6
  51. package/dist/download/single.cjs.map +1 -1
  52. package/dist/download/single.d.cts.map +1 -1
  53. package/dist/download/single.d.ts.map +1 -1
  54. package/dist/download/single.js +141 -6
  55. package/dist/download/single.js.map +1 -1
  56. package/dist/errors/index.cjs +173 -3
  57. package/dist/errors/index.cjs.map +1 -1
  58. package/dist/errors/index.d.cts +119 -0
  59. package/dist/errors/index.d.cts.map +1 -1
  60. package/dist/errors/index.d.ts +119 -0
  61. package/dist/errors/index.d.ts.map +1 -1
  62. package/dist/errors/index.js +161 -4
  63. package/dist/errors/index.js.map +1 -1
  64. package/dist/http/retry.cjs +1 -1
  65. package/dist/http/retry.cjs.map +1 -1
  66. package/dist/http/retry.js +1 -1
  67. package/dist/http/retry.js.map +1 -1
  68. package/dist/http/transport.cjs +61 -23
  69. package/dist/http/transport.cjs.map +1 -1
  70. package/dist/http/transport.d.cts +25 -3
  71. package/dist/http/transport.d.cts.map +1 -1
  72. package/dist/http/transport.d.ts +25 -3
  73. package/dist/http/transport.d.ts.map +1 -1
  74. package/dist/http/transport.js +59 -22
  75. package/dist/http/transport.js.map +1 -1
  76. package/dist/index.cjs +25 -2
  77. package/dist/index.d.cts +6 -4
  78. package/dist/index.d.cts.map +1 -1
  79. package/dist/index.d.ts +6 -4
  80. package/dist/index.d.ts.map +1 -1
  81. package/dist/index.js +8 -6
  82. package/dist/object.cjs +1 -1
  83. package/dist/object.cjs.map +1 -1
  84. package/dist/object.d.cts +4 -2
  85. package/dist/object.d.cts.map +1 -1
  86. package/dist/object.d.ts +4 -2
  87. package/dist/object.d.ts.map +1 -1
  88. package/dist/object.js +1 -1
  89. package/dist/object.js.map +1 -1
  90. package/dist/partner/account-info.d.cts +43 -0
  91. package/dist/partner/account-info.d.cts.map +1 -0
  92. package/dist/partner/account-info.d.ts +43 -0
  93. package/dist/partner/account-info.d.ts.map +1 -0
  94. package/dist/partner/auth-clone.cjs +64 -0
  95. package/dist/partner/auth-clone.cjs.map +1 -0
  96. package/dist/partner/auth-clone.d.cts +27 -0
  97. package/dist/partner/auth-clone.d.cts.map +1 -0
  98. package/dist/partner/auth-clone.d.ts +27 -0
  99. package/dist/partner/auth-clone.d.ts.map +1 -0
  100. package/dist/partner/auth-clone.js +63 -0
  101. package/dist/partner/auth-clone.js.map +1 -0
  102. package/dist/partner/auth-core.cjs +175 -0
  103. package/dist/partner/auth-core.cjs.map +1 -0
  104. package/dist/partner/auth-core.d.cts +111 -0
  105. package/dist/partner/auth-core.d.cts.map +1 -0
  106. package/dist/partner/auth-core.d.ts +111 -0
  107. package/dist/partner/auth-core.d.ts.map +1 -0
  108. package/dist/partner/auth-core.js +175 -0
  109. package/dist/partner/auth-core.js.map +1 -0
  110. package/dist/partner/auth-shape.cjs +36 -0
  111. package/dist/partner/auth-shape.cjs.map +1 -0
  112. package/dist/partner/auth-shape.d.cts +12 -0
  113. package/dist/partner/auth-shape.d.cts.map +1 -0
  114. package/dist/partner/auth-shape.d.ts +12 -0
  115. package/dist/partner/auth-shape.d.ts.map +1 -0
  116. package/dist/partner/auth-shape.js +36 -0
  117. package/dist/partner/auth-shape.js.map +1 -0
  118. package/dist/partner/client.cjs +255 -0
  119. package/dist/partner/client.cjs.map +1 -0
  120. package/dist/partner/client.d.cts +335 -0
  121. package/dist/partner/client.d.cts.map +1 -0
  122. package/dist/partner/client.d.ts +335 -0
  123. package/dist/partner/client.d.ts.map +1 -0
  124. package/dist/partner/client.js +255 -0
  125. package/dist/partner/client.js.map +1 -0
  126. package/dist/partner/endpoint-url.cjs +108 -0
  127. package/dist/partner/endpoint-url.cjs.map +1 -0
  128. package/dist/partner/endpoint-url.d.cts +72 -0
  129. package/dist/partner/endpoint-url.d.cts.map +1 -0
  130. package/dist/partner/endpoint-url.d.ts +72 -0
  131. package/dist/partner/endpoint-url.d.ts.map +1 -0
  132. package/dist/partner/endpoint-url.js +105 -0
  133. package/dist/partner/endpoint-url.js.map +1 -0
  134. package/dist/partner/in-memory.cjs +135 -0
  135. package/dist/partner/in-memory.cjs.map +1 -0
  136. package/dist/partner/in-memory.d.cts +99 -0
  137. package/dist/partner/in-memory.d.cts.map +1 -0
  138. package/dist/partner/in-memory.d.ts +99 -0
  139. package/dist/partner/in-memory.d.ts.map +1 -0
  140. package/dist/partner/in-memory.js +135 -0
  141. package/dist/partner/in-memory.js.map +1 -0
  142. package/dist/partner/index.cjs +18 -0
  143. package/dist/partner/index.d.cts +23 -0
  144. package/dist/partner/index.d.cts.map +1 -0
  145. package/dist/partner/index.d.ts +23 -0
  146. package/dist/partner/index.d.ts.map +1 -0
  147. package/dist/partner/index.js +7 -0
  148. package/dist/partner/raw.cjs +392 -0
  149. package/dist/partner/raw.cjs.map +1 -0
  150. package/dist/partner/raw.d.cts +223 -0
  151. package/dist/partner/raw.d.cts.map +1 -0
  152. package/dist/partner/raw.d.ts +223 -0
  153. package/dist/partner/raw.d.ts.map +1 -0
  154. package/dist/partner/raw.js +390 -0
  155. package/dist/partner/raw.js.map +1 -0
  156. package/dist/partner/redaction-placeholders.cjs +10 -0
  157. package/dist/partner/redaction-placeholders.cjs.map +1 -0
  158. package/dist/partner/redaction-placeholders.d.cts +5 -0
  159. package/dist/partner/redaction-placeholders.d.cts.map +1 -0
  160. package/dist/partner/redaction-placeholders.d.ts +5 -0
  161. package/dist/partner/redaction-placeholders.d.ts.map +1 -0
  162. package/dist/partner/redaction-placeholders.js +9 -0
  163. package/dist/partner/redaction-placeholders.js.map +1 -0
  164. package/dist/partner/redaction.cjs +267 -0
  165. package/dist/partner/redaction.cjs.map +1 -0
  166. package/dist/partner/redaction.d.cts +124 -0
  167. package/dist/partner/redaction.d.cts.map +1 -0
  168. package/dist/partner/redaction.d.ts +124 -0
  169. package/dist/partner/redaction.d.ts.map +1 -0
  170. package/dist/partner/redaction.js +257 -0
  171. package/dist/partner/redaction.js.map +1 -0
  172. package/dist/raw/index.cjs +81 -19
  173. package/dist/raw/index.cjs.map +1 -1
  174. package/dist/raw/index.d.cts +6 -2
  175. package/dist/raw/index.d.cts.map +1 -1
  176. package/dist/raw/index.d.ts +6 -2
  177. package/dist/raw/index.d.ts.map +1 -1
  178. package/dist/raw/index.js +81 -19
  179. package/dist/raw/index.js.map +1 -1
  180. package/dist/raw/url.cjs +111 -0
  181. package/dist/raw/url.cjs.map +1 -0
  182. package/dist/raw/url.d.cts +41 -0
  183. package/dist/raw/url.d.cts.map +1 -0
  184. package/dist/raw/url.d.ts +41 -0
  185. package/dist/raw/url.d.ts.map +1 -0
  186. package/dist/raw/url.js +110 -0
  187. package/dist/raw/url.js.map +1 -0
  188. package/dist/s3/sigv4.cjs +15 -10
  189. package/dist/s3/sigv4.cjs.map +1 -1
  190. package/dist/s3/sigv4.js +15 -10
  191. package/dist/s3/sigv4.js.map +1 -1
  192. package/dist/simulator/index.cjs +1197 -206
  193. package/dist/simulator/index.cjs.map +1 -1
  194. package/dist/simulator/index.d.cts +109 -11
  195. package/dist/simulator/index.d.cts.map +1 -1
  196. package/dist/simulator/index.d.ts +109 -11
  197. package/dist/simulator/index.d.ts.map +1 -1
  198. package/dist/simulator/index.js +1196 -209
  199. package/dist/simulator/index.js.map +1 -1
  200. package/dist/simulator/partner.cjs +719 -0
  201. package/dist/simulator/partner.cjs.map +1 -0
  202. package/dist/simulator/partner.d.cts +183 -0
  203. package/dist/simulator/partner.d.cts.map +1 -0
  204. package/dist/simulator/partner.d.ts +183 -0
  205. package/dist/simulator/partner.d.ts.map +1 -0
  206. package/dist/simulator/partner.js +718 -0
  207. package/dist/simulator/partner.js.map +1 -0
  208. package/dist/simulator/validation.cjs +577 -0
  209. package/dist/simulator/validation.cjs.map +1 -1
  210. package/dist/simulator/validation.d.cts +117 -0
  211. package/dist/simulator/validation.d.cts.map +1 -1
  212. package/dist/simulator/validation.d.ts +117 -0
  213. package/dist/simulator/validation.d.ts.map +1 -1
  214. package/dist/simulator/validation.js +563 -1
  215. package/dist/simulator/validation.js.map +1 -1
  216. package/dist/streams/file-source.cjs +1 -1
  217. package/dist/streams/file-source.cjs.map +1 -1
  218. package/dist/streams/file-source.js +1 -1
  219. package/dist/streams/file-source.js.map +1 -1
  220. package/dist/streams/index.cjs +1 -1
  221. package/dist/streams/index.js +1 -1
  222. package/dist/streams/source.cjs +2 -1
  223. package/dist/streams/source.cjs.map +1 -1
  224. package/dist/streams/source.js +2 -1
  225. package/dist/streams/source.js.map +1 -1
  226. package/dist/sync/b2-sha1-reader.cjs +6 -2
  227. package/dist/sync/b2-sha1-reader.cjs.map +1 -1
  228. package/dist/sync/b2-sha1-reader.d.cts.map +1 -1
  229. package/dist/sync/b2-sha1-reader.d.ts.map +1 -1
  230. package/dist/sync/b2-sha1-reader.js +6 -2
  231. package/dist/sync/b2-sha1-reader.js.map +1 -1
  232. package/dist/sync/download-staging.cjs +11 -10
  233. package/dist/sync/download-staging.cjs.map +1 -1
  234. package/dist/sync/download-staging.d.cts +2 -1
  235. package/dist/sync/download-staging.d.cts.map +1 -1
  236. package/dist/sync/download-staging.d.ts +2 -1
  237. package/dist/sync/download-staging.d.ts.map +1 -1
  238. package/dist/sync/download-staging.js +11 -10
  239. package/dist/sync/download-staging.js.map +1 -1
  240. package/dist/sync/filters.cjs.map +1 -1
  241. package/dist/sync/filters.js.map +1 -1
  242. package/dist/sync/local-file-io.cjs +11 -6
  243. package/dist/sync/local-file-io.cjs.map +1 -1
  244. package/dist/sync/local-file-io.d.cts +1 -0
  245. package/dist/sync/local-file-io.d.cts.map +1 -1
  246. package/dist/sync/local-file-io.d.ts +1 -0
  247. package/dist/sync/local-file-io.d.ts.map +1 -1
  248. package/dist/sync/local-file-io.js +11 -6
  249. package/dist/sync/local-file-io.js.map +1 -1
  250. package/dist/sync/local-sha1.cjs +6 -15
  251. package/dist/sync/local-sha1.cjs.map +1 -1
  252. package/dist/sync/local-sha1.d.cts +0 -8
  253. package/dist/sync/local-sha1.d.cts.map +1 -1
  254. package/dist/sync/local-sha1.d.ts +0 -8
  255. package/dist/sync/local-sha1.d.ts.map +1 -1
  256. package/dist/sync/local-sha1.js +7 -15
  257. package/dist/sync/local-sha1.js.map +1 -1
  258. package/dist/sync/policies/compare.cjs +5 -3
  259. package/dist/sync/policies/compare.cjs.map +1 -1
  260. package/dist/sync/policies/compare.d.cts.map +1 -1
  261. package/dist/sync/policies/compare.d.ts.map +1 -1
  262. package/dist/sync/policies/compare.js +6 -4
  263. package/dist/sync/policies/compare.js.map +1 -1
  264. package/dist/sync/policies/index.cjs +4 -10
  265. package/dist/sync/policies/index.cjs.map +1 -1
  266. package/dist/sync/policies/index.js +4 -10
  267. package/dist/sync/policies/index.js.map +1 -1
  268. package/dist/sync/scanners/b2.cjs +2 -2
  269. package/dist/sync/scanners/b2.cjs.map +1 -1
  270. package/dist/sync/scanners/b2.js +2 -2
  271. package/dist/sync/scanners/b2.js.map +1 -1
  272. package/dist/sync/scanners/local.cjs.map +1 -1
  273. package/dist/sync/scanners/local.js.map +1 -1
  274. package/dist/sync/synchronizer.cjs +3 -2
  275. package/dist/sync/synchronizer.cjs.map +1 -1
  276. package/dist/sync/synchronizer.js +3 -2
  277. package/dist/sync/synchronizer.js.map +1 -1
  278. package/dist/types/auth.cjs +4 -0
  279. package/dist/types/auth.cjs.map +1 -1
  280. package/dist/types/auth.d.cts +4 -0
  281. package/dist/types/auth.d.cts.map +1 -1
  282. package/dist/types/auth.d.ts +4 -0
  283. package/dist/types/auth.d.ts.map +1 -1
  284. package/dist/types/auth.js +4 -0
  285. package/dist/types/auth.js.map +1 -1
  286. package/dist/types/backup.d.cts +80 -0
  287. package/dist/types/backup.d.cts.map +1 -0
  288. package/dist/types/backup.d.ts +80 -0
  289. package/dist/types/backup.d.ts.map +1 -0
  290. package/dist/types/bucket.cjs.map +1 -1
  291. package/dist/types/bucket.d.cts +4 -2
  292. package/dist/types/bucket.d.cts.map +1 -1
  293. package/dist/types/bucket.d.ts +4 -2
  294. package/dist/types/bucket.d.ts.map +1 -1
  295. package/dist/types/bucket.js.map +1 -1
  296. package/dist/types/download.cjs +62 -0
  297. package/dist/types/download.cjs.map +1 -0
  298. package/dist/types/download.d.cts +98 -0
  299. package/dist/types/download.d.cts.map +1 -1
  300. package/dist/types/download.d.ts +98 -0
  301. package/dist/types/download.d.ts.map +1 -1
  302. package/dist/types/download.js +61 -0
  303. package/dist/types/download.js.map +1 -0
  304. package/dist/types/encryption.cjs +58 -229
  305. package/dist/types/encryption.cjs.map +1 -1
  306. package/dist/types/encryption.d.cts +32 -0
  307. package/dist/types/encryption.d.cts.map +1 -1
  308. package/dist/types/encryption.d.ts +32 -0
  309. package/dist/types/encryption.d.ts.map +1 -1
  310. package/dist/types/encryption.js +56 -229
  311. package/dist/types/encryption.js.map +1 -1
  312. package/dist/types/errors.cjs +9 -0
  313. package/dist/types/errors.cjs.map +1 -1
  314. package/dist/types/errors.d.cts +1 -1
  315. package/dist/types/errors.d.cts.map +1 -1
  316. package/dist/types/errors.d.ts +1 -1
  317. package/dist/types/errors.d.ts.map +1 -1
  318. package/dist/types/errors.js +9 -0
  319. package/dist/types/errors.js.map +1 -1
  320. package/dist/types/file.cjs.map +1 -1
  321. package/dist/types/file.d.cts +15 -2
  322. package/dist/types/file.d.cts.map +1 -1
  323. package/dist/types/file.d.ts +15 -2
  324. package/dist/types/file.d.ts.map +1 -1
  325. package/dist/types/file.js.map +1 -1
  326. package/dist/types/ids.cjs +30 -0
  327. package/dist/types/ids.cjs.map +1 -1
  328. package/dist/types/ids.d.cts +32 -0
  329. package/dist/types/ids.d.cts.map +1 -1
  330. package/dist/types/ids.d.ts +32 -0
  331. package/dist/types/ids.d.ts.map +1 -1
  332. package/dist/types/ids.js +28 -1
  333. package/dist/types/ids.js.map +1 -1
  334. package/dist/types/index.d.cts +10 -6
  335. package/dist/types/index.d.cts.map +1 -1
  336. package/dist/types/index.d.ts +10 -6
  337. package/dist/types/index.d.ts.map +1 -1
  338. package/dist/types/lock.cjs.map +1 -1
  339. package/dist/types/lock.d.cts +14 -0
  340. package/dist/types/lock.d.cts.map +1 -1
  341. package/dist/types/lock.d.ts +14 -0
  342. package/dist/types/lock.d.ts.map +1 -1
  343. package/dist/types/lock.js.map +1 -1
  344. package/dist/types/notifications.cjs +1 -1
  345. package/dist/types/notifications.cjs.map +1 -1
  346. package/dist/types/notifications.d.cts +5 -3
  347. package/dist/types/notifications.d.cts.map +1 -1
  348. package/dist/types/notifications.d.ts +5 -3
  349. package/dist/types/notifications.d.ts.map +1 -1
  350. package/dist/types/notifications.js +1 -1
  351. package/dist/types/notifications.js.map +1 -1
  352. package/dist/types/partner.cjs +32 -0
  353. package/dist/types/partner.cjs.map +1 -0
  354. package/dist/types/partner.d.cts +394 -0
  355. package/dist/types/partner.d.cts.map +1 -0
  356. package/dist/types/partner.d.ts +394 -0
  357. package/dist/types/partner.d.ts.map +1 -0
  358. package/dist/types/partner.js +29 -0
  359. package/dist/types/partner.js.map +1 -0
  360. package/dist/types/upload.d.cts +31 -18
  361. package/dist/types/upload.d.cts.map +1 -1
  362. package/dist/types/upload.d.ts +31 -18
  363. package/dist/types/upload.d.ts.map +1 -1
  364. package/dist/upload/abort-scope.cjs +0 -45
  365. package/dist/upload/abort-scope.cjs.map +1 -1
  366. package/dist/upload/abort-scope.d.cts +0 -22
  367. package/dist/upload/abort-scope.d.cts.map +1 -1
  368. package/dist/upload/abort-scope.d.ts +0 -22
  369. package/dist/upload/abort-scope.d.ts.map +1 -1
  370. package/dist/upload/abort-scope.js +1 -44
  371. package/dist/upload/abort-scope.js.map +1 -1
  372. package/dist/upload/finish.cjs +3 -8
  373. package/dist/upload/finish.cjs.map +1 -1
  374. package/dist/upload/finish.d.cts.map +1 -1
  375. package/dist/upload/finish.d.ts.map +1 -1
  376. package/dist/upload/finish.js +1 -6
  377. package/dist/upload/finish.js.map +1 -1
  378. package/dist/upload/large.cjs +5 -1
  379. package/dist/upload/large.cjs.map +1 -1
  380. package/dist/upload/large.d.cts +16 -2
  381. package/dist/upload/large.d.cts.map +1 -1
  382. package/dist/upload/large.d.ts +16 -2
  383. package/dist/upload/large.d.ts.map +1 -1
  384. package/dist/upload/large.js +5 -1
  385. package/dist/upload/large.js.map +1 -1
  386. package/dist/upload/options.cjs.map +1 -1
  387. package/dist/upload/options.d.cts +12 -0
  388. package/dist/upload/options.d.cts.map +1 -1
  389. package/dist/upload/options.d.ts +12 -0
  390. package/dist/upload/options.d.ts.map +1 -1
  391. package/dist/upload/options.js.map +1 -1
  392. package/dist/upload/resume.cjs +2 -1
  393. package/dist/upload/resume.cjs.map +1 -1
  394. package/dist/upload/resume.d.cts +3 -1
  395. package/dist/upload/resume.d.cts.map +1 -1
  396. package/dist/upload/resume.d.ts +3 -1
  397. package/dist/upload/resume.d.ts.map +1 -1
  398. package/dist/upload/resume.js +2 -1
  399. package/dist/upload/resume.js.map +1 -1
  400. package/dist/upload/retry.cjs +8 -3
  401. package/dist/upload/retry.cjs.map +1 -1
  402. package/dist/upload/retry.js +8 -3
  403. package/dist/upload/retry.js.map +1 -1
  404. package/dist/upload/single.cjs +2 -1
  405. package/dist/upload/single.cjs.map +1 -1
  406. package/dist/upload/single.d.cts +6 -0
  407. package/dist/upload/single.d.cts.map +1 -1
  408. package/dist/upload/single.d.ts +6 -0
  409. package/dist/upload/single.d.ts.map +1 -1
  410. package/dist/upload/single.js +2 -1
  411. package/dist/upload/single.js.map +1 -1
  412. package/dist/upload/stream.cjs +6 -4
  413. package/dist/upload/stream.cjs.map +1 -1
  414. package/dist/upload/stream.d.cts +6 -0
  415. package/dist/upload/stream.d.cts.map +1 -1
  416. package/dist/upload/stream.d.ts +6 -0
  417. package/dist/upload/stream.d.ts.map +1 -1
  418. package/dist/upload/stream.js +3 -1
  419. package/dist/upload/stream.js.map +1 -1
  420. package/dist/util/abort.cjs +114 -0
  421. package/dist/util/abort.cjs.map +1 -0
  422. package/dist/util/abort.d.cts +65 -0
  423. package/dist/util/abort.d.cts.map +1 -0
  424. package/dist/util/abort.d.ts +65 -0
  425. package/dist/util/abort.d.ts.map +1 -0
  426. package/dist/util/abort.js +109 -0
  427. package/dist/util/abort.js.map +1 -0
  428. package/dist/util/defaults.cjs +3 -0
  429. package/dist/util/defaults.cjs.map +1 -1
  430. package/dist/util/defaults.d.cts +2 -0
  431. package/dist/util/defaults.d.cts.map +1 -1
  432. package/dist/util/defaults.d.ts +2 -0
  433. package/dist/util/defaults.d.ts.map +1 -1
  434. package/dist/util/defaults.js +3 -1
  435. package/dist/util/defaults.js.map +1 -1
  436. package/dist/util/md5.cjs +211 -0
  437. package/dist/util/md5.cjs.map +1 -0
  438. package/dist/util/md5.d.cts +43 -0
  439. package/dist/util/md5.d.cts.map +1 -0
  440. package/dist/util/md5.d.ts +43 -0
  441. package/dist/util/md5.d.ts.map +1 -0
  442. package/dist/util/md5.js +209 -0
  443. package/dist/util/md5.js.map +1 -0
  444. package/dist/util/normalize.cjs +5 -5
  445. package/dist/util/normalize.cjs.map +1 -1
  446. package/dist/util/normalize.d.cts +7 -7
  447. package/dist/util/normalize.d.cts.map +1 -1
  448. package/dist/util/normalize.d.ts +7 -7
  449. package/dist/util/normalize.d.ts.map +1 -1
  450. package/dist/util/normalize.js +5 -5
  451. package/dist/util/normalize.js.map +1 -1
  452. package/dist/util/paginator.cjs +7 -1
  453. package/dist/util/paginator.cjs.map +1 -1
  454. package/dist/util/paginator.d.cts +3 -1
  455. package/dist/util/paginator.d.cts.map +1 -1
  456. package/dist/util/paginator.d.ts +3 -1
  457. package/dist/util/paginator.d.ts.map +1 -1
  458. package/dist/util/paginator.js +7 -1
  459. package/dist/util/paginator.js.map +1 -1
  460. package/package.json +38 -16
@@ -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 = "b2-sdk-typescript-simulator-upload-token-v1";
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 uploadServerSideEncryption(headers, fallback) {
172
- if (headers["x-bz-server-side-encryption-customer-algorithm"] === require_encryption.EncryptionAlgorithm.Aes256) return {
173
- mode: require_encryption.EncryptionMode.SseC,
174
- algorithm: require_encryption.EncryptionAlgorithm.Aes256,
175
- customerKey: headers["x-bz-server-side-encryption-customer-key"] ?? "",
176
- customerKeyMd5: headers["x-bz-server-side-encryption-customer-key-md5"] ?? ""
177
- };
178
- if (headers["x-bz-server-side-encryption"] === require_encryption.EncryptionAlgorithm.Aes256) return {
179
- mode: require_encryption.EncryptionMode.SseB2,
180
- algorithm: require_encryption.EncryptionAlgorithm.Aes256
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
- return fallback;
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 days = policy.period.unit === "days" ? policy.period.duration : policy.period.duration * 365;
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: uploadTimestamp + days * 24 * 60 * 60 * 1e3
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.authTokenTtlMs = options.authTokenTtlMs ?? 1440 * 60 * 1e3;
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
- if (token.bucketIds !== null) {
465
- if ((scope?.bucketIds.length ?? 0) === 0 && scope?.requiresBucketScope === true) return this.error(403, "unauthorized", `application key is scoped to buckets ${token.bucketIds.join(", ")}; bucket scope is required`);
466
- for (const bucketId of scope?.bucketIds ?? []) if (!token.bucketIds.includes(bucketId)) return this.error(403, "unauthorized", `application key is scoped to buckets ${token.bucketIds.join(", ")}; cannot access ${bucketId}`);
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
- if (token.namePrefix !== null) for (const fileName of scope?.fileNames ?? []) {
469
- if (fileName.startsWith(token.namePrefix)) continue;
470
- return this.error(403, "unauthorized", `application key is scoped to prefix "${token.namePrefix}"; "${fileName}" is outside scope`);
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(`http://localhost:0/b2api/v3/${endpoint}`);
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
- * Look up the application key matching the `Authorization` header on
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
- * Returns `null` for the implicit master credential (anything that
695
- * does not match a key minted via `b2_create_key`); in that case
696
- * `authorize` grants the full master capability set.
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 matching key's grant scope, or `null` for the master.
1341
+ * @returns The authorizing key's grant scope or the rejection reason.
701
1342
  */
702
- findKeyForAuthHeader(authzHeader) {
703
- if (!authzHeader?.startsWith("Basic ")) return null;
704
- const decoded = (() => {
705
- try {
706
- return atob(authzHeader.slice(6));
707
- } catch {
708
- return null;
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 _method - The HTTP method (unused).
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(_method, origin, path, headers, body) {
825
- const endpoint = path.split("/").pop() ?? "";
826
- const apiVersion = path.match(/\/b2api\/(v\d+)\//)?.[1] ?? "v3";
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": return this.authorize(headers["authorization"], origin);
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, apiVersion);
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 url = new URL(`http://localhost${path}`);
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["range"]), url, method);
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.authorizeRequest(headers["authorization"], "b2_download_file_by_name", {
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
- const url = new URL(`http://localhost${path}`);
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(overrideMap)) {
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: uploadServerSideEncryption(headers, bucket.info.defaultServerSideEncryption)
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 = Number.parseInt(headers["x-bz-part-number"] ?? "0", 10);
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: Date.now()
1783
+ uploadTimestamp
1081
1784
  }
1082
1785
  };
1083
1786
  }
1084
- downloadById(fileId, range) {
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
- serveFile(stored, range) {
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 capabilities = [
1152
- require_auth.Capability.ListBuckets,
1153
- require_auth.Capability.ReadBuckets,
1154
- require_auth.Capability.WriteBuckets,
1155
- require_auth.Capability.DeleteBuckets,
1156
- require_auth.Capability.ListFiles,
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
- capabilities: keyForAuth?.capabilities ?? capabilities,
1174
- bucketIds: keyForAuth?.bucketIds ?? null,
1175
- namePrefix: keyForAuth?.namePrefix ?? null,
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: keyForAuth?.applicationKeyId ?? null
1947
+ applicationKeyId: grant.applicationKeyId
1178
1948
  });
1179
1949
  return {
1180
1950
  status: 200,
1181
1951
  body: {
1182
- accountId: require_ids.accountId(this.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: keyForAuth?.namePrefix ?? null,
1961
+ namePrefix: grant.namePrefix,
1192
1962
  recommendedPartSize: this.recommendedPartSize,
1193
1963
  s3ApiUrl: origin,
1194
1964
  allowed: {
1195
- capabilities: keyForAuth?.capabilities ?? 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: keyForAuth?.namePrefix ?? null
1969
+ namePrefix: grant.namePrefix
1200
1970
  }
1201
1971
  } },
1202
- applicationKeyExpirationTimestamp: null
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
- if (req.bucketInfo !== void 0) {
1210
- const infoError = require_validation.validateBucketInfo(req.bucketInfo);
1211
- if (infoError) return this.error(400, infoError.code, infoError.message);
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: req.fileLockEnabled ?? false,
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
- if (req["bucketInfo"] !== void 0) {
1271
- const infoError = require_validation.validateBucketInfo(req["bucketInfo"]);
1272
- if (infoError) return this.error(400, infoError.code, infoError.message);
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: bucket.info.fileLockConfiguration.value?.isFileLockEnabled ?? false,
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 === "on") return this.error(400, "file_lock_legal_hold_protected", "File is on legal hold and cannot be deleted");
1414
- if (retention?.mode === "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()}`);
1415
- if (retention?.mode === "governance" && retention.retainUntilTimestamp !== null && retention.retainUntilTimestamp > now && req.bypassGovernance !== true) return this.error(400, "file_lock_governance_protected", "File is under governance-mode retention; pass bypassGovernance: true to delete");
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
- serverSideEncryption: req.serverSideEncryption ?? bucket.info.defaultServerSideEncryption,
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 > 1e4) return this.error(400, "bad_request", `multipart upload has ${sortedParts.length} parts; B2 caps at 10000`);
1557
- for (const [partNumber] of sortedParts) if (!Number.isInteger(partNumber) || partNumber < 1 || partNumber > 1e4) return this.error(400, "bad_request", `partNumber ${partNumber} is outside the [1, 10000] range B2 accepts`);
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 (let i = 0; i < sortedParts.length; i++) {
1560
- const [partNumber, part] = sortedParts[i];
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
- uploadTimestamp: Date.now()
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 match = req.range.match(/bytes=(\d+)-(\d+)?/);
1686
- if (match) {
1687
- const rs = Number.parseInt(match[1] ?? "0", 10);
1688
- const re = match[2] !== void 0 ? Number.parseInt(match[2], 10) : sourceStored.data.byteLength - 1;
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: `sim_dl_auth_${this.genId("tok")}`
2562
+ authorizationToken
1715
2563
  }
1716
2564
  };
1717
2565
  }
1718
- createKey(req, apiVersion) {
1719
- if (apiVersion === "v4" && hasOwnField(req, "bucketId")) return this.error(400, "bad_request", "bucketId is not accepted by v4 b2_create_key; use bucketIds");
1720
- if (apiVersion !== "v4" && req.bucketId !== void 0 && req.bucketIds !== void 0) return this.error(400, "bad_request", "b2_create_key accepts either bucketIds or bucketId");
1721
- const bucketIds = apiVersion !== "v4" && req.bucketId !== void 0 ? Object.freeze([req.bucketId]) : normalizeKeyBucketIds(req);
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
- if (hasKeyManagementCapability(req.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");
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: req.keyName,
1730
- capabilities: req.capabilities,
1731
- accountId: req.accountId,
1732
- applicationKey: appKey,
2595
+ keyName,
2596
+ capabilities,
2597
+ accountId: creatorAccountId,
2598
+ applicationKey: this.genId("sim_secret"),
1733
2599
  bucketIds,
1734
2600
  namePrefix,
1735
- expirationTimestamp: expiration
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: req.fileRetention
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: req.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: null,
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
- const bucket = this.buckets.get(bucketId);
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 sourceRules = bucket.info.replicationConfiguration.asReplicationSource?.replicationRules ?? [];
1968
- for (const rule of sourceRules) {
1969
- if (!rule.isEnabled) continue;
1970
- if (rule.fileNamePrefix && !fileVersion.fileName.startsWith(rule.fileNamePrefix)) continue;
1971
- const hook = this.onReplicate;
1972
- this.dispatchHook("replication", () => hook({
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: () => Promise.reject(/* @__PURE__ */ new Error("Download response is not JSON")),
2090
- text: () => Promise.resolve(require_text_codec.utf8Decoder.decode(data)),
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 && request.body) {
2096
- const data = new Uint8Array(request.body instanceof ArrayBuffer ? request.body : request.body instanceof Uint8Array ? request.body.buffer.slice(request.body.byteOffset, request.body.byteOffset + request.body.byteLength) : await new Response(request.body).arrayBuffer());
2097
- result = await this.sim.handleUpload(url, headers, data);
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