@backblaze-labs/b2-sdk 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (590) hide show
  1. package/CHANGELOG.md +86 -1
  2. package/README.md +38 -7
  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/auth/reauth-coalescer.cjs +65 -0
  7. package/dist/auth/reauth-coalescer.cjs.map +1 -0
  8. package/dist/auth/reauth-coalescer.d.cts +34 -0
  9. package/dist/auth/reauth-coalescer.d.cts.map +1 -0
  10. package/dist/auth/reauth-coalescer.d.ts +34 -0
  11. package/dist/auth/reauth-coalescer.d.ts.map +1 -0
  12. package/dist/auth/reauth-coalescer.js +65 -0
  13. package/dist/auth/reauth-coalescer.js.map +1 -0
  14. package/dist/backup/client.cjs +206 -0
  15. package/dist/backup/client.cjs.map +1 -0
  16. package/dist/backup/client.d.cts +238 -0
  17. package/dist/backup/client.d.cts.map +1 -0
  18. package/dist/backup/client.d.ts +238 -0
  19. package/dist/backup/client.d.ts.map +1 -0
  20. package/dist/backup/client.js +206 -0
  21. package/dist/backup/client.js.map +1 -0
  22. package/dist/backup/index.cjs +15 -0
  23. package/dist/backup/index.d.cts +22 -0
  24. package/dist/backup/index.d.cts.map +1 -0
  25. package/dist/backup/index.d.ts +22 -0
  26. package/dist/backup/index.d.ts.map +1 -0
  27. package/dist/backup/index.js +7 -0
  28. package/dist/backup/raw.cjs +168 -0
  29. package/dist/backup/raw.cjs.map +1 -0
  30. package/dist/backup/raw.d.cts +122 -0
  31. package/dist/backup/raw.d.cts.map +1 -0
  32. package/dist/backup/raw.d.ts +122 -0
  33. package/dist/backup/raw.d.ts.map +1 -0
  34. package/dist/backup/raw.js +168 -0
  35. package/dist/backup/raw.js.map +1 -0
  36. package/dist/bucket-validation.cjs +111 -0
  37. package/dist/bucket-validation.cjs.map +1 -0
  38. package/dist/bucket-validation.d.cts +27 -0
  39. package/dist/bucket-validation.d.cts.map +1 -0
  40. package/dist/bucket-validation.d.ts +27 -0
  41. package/dist/bucket-validation.d.ts.map +1 -0
  42. package/dist/bucket-validation.js +109 -0
  43. package/dist/bucket-validation.js.map +1 -0
  44. package/dist/bucket.cjs +125 -74
  45. package/dist/bucket.cjs.map +1 -1
  46. package/dist/bucket.d.cts +156 -61
  47. package/dist/bucket.d.cts.map +1 -1
  48. package/dist/bucket.d.ts +156 -61
  49. package/dist/bucket.d.ts.map +1 -1
  50. package/dist/bucket.js +125 -74
  51. package/dist/bucket.js.map +1 -1
  52. package/dist/client.cjs +29 -8
  53. package/dist/client.cjs.map +1 -1
  54. package/dist/client.d.cts +30 -9
  55. package/dist/client.d.cts.map +1 -1
  56. package/dist/client.d.ts +30 -9
  57. package/dist/client.d.ts.map +1 -1
  58. package/dist/client.js +29 -8
  59. package/dist/client.js.map +1 -1
  60. package/dist/copy/large.cjs +5 -3
  61. package/dist/copy/large.cjs.map +1 -1
  62. package/dist/copy/large.d.cts.map +1 -1
  63. package/dist/copy/large.d.ts.map +1 -1
  64. package/dist/copy/large.js +5 -3
  65. package/dist/copy/large.js.map +1 -1
  66. package/dist/download/checksum.cjs +6 -2
  67. package/dist/download/checksum.cjs.map +1 -1
  68. package/dist/download/checksum.js +6 -2
  69. package/dist/download/checksum.js.map +1 -1
  70. package/dist/download/parallel.cjs +52 -15
  71. package/dist/download/parallel.cjs.map +1 -1
  72. package/dist/download/parallel.d.cts +8 -1
  73. package/dist/download/parallel.d.cts.map +1 -1
  74. package/dist/download/parallel.d.ts +8 -1
  75. package/dist/download/parallel.d.ts.map +1 -1
  76. package/dist/download/parallel.js +52 -15
  77. package/dist/download/parallel.js.map +1 -1
  78. package/dist/download/single.cjs +179 -19
  79. package/dist/download/single.cjs.map +1 -1
  80. package/dist/download/single.d.cts +10 -4
  81. package/dist/download/single.d.cts.map +1 -1
  82. package/dist/download/single.d.ts +10 -4
  83. package/dist/download/single.d.ts.map +1 -1
  84. package/dist/download/single.js +179 -19
  85. package/dist/download/single.js.map +1 -1
  86. package/dist/errors/index.cjs +196 -3
  87. package/dist/errors/index.cjs.map +1 -1
  88. package/dist/errors/index.d.cts +134 -0
  89. package/dist/errors/index.d.cts.map +1 -1
  90. package/dist/errors/index.d.ts +134 -0
  91. package/dist/errors/index.d.ts.map +1 -1
  92. package/dist/errors/index.js +183 -4
  93. package/dist/errors/index.js.map +1 -1
  94. package/dist/http/non-idempotent-mutations.cjs +38 -0
  95. package/dist/http/non-idempotent-mutations.cjs.map +1 -0
  96. package/dist/http/non-idempotent-mutations.d.cts +25 -0
  97. package/dist/http/non-idempotent-mutations.d.cts.map +1 -0
  98. package/dist/http/non-idempotent-mutations.d.ts +25 -0
  99. package/dist/http/non-idempotent-mutations.d.ts.map +1 -0
  100. package/dist/http/non-idempotent-mutations.js +35 -0
  101. package/dist/http/non-idempotent-mutations.js.map +1 -0
  102. package/dist/http/retry.cjs +1 -1
  103. package/dist/http/retry.cjs.map +1 -1
  104. package/dist/http/retry.js +1 -1
  105. package/dist/http/retry.js.map +1 -1
  106. package/dist/http/transport.cjs +62 -23
  107. package/dist/http/transport.cjs.map +1 -1
  108. package/dist/http/transport.d.cts +30 -3
  109. package/dist/http/transport.d.cts.map +1 -1
  110. package/dist/http/transport.d.ts +30 -3
  111. package/dist/http/transport.d.ts.map +1 -1
  112. package/dist/http/transport.js +60 -22
  113. package/dist/http/transport.js.map +1 -1
  114. package/dist/http/user-agent.cjs +12 -1
  115. package/dist/http/user-agent.cjs.map +1 -1
  116. package/dist/http/user-agent.d.cts +8 -0
  117. package/dist/http/user-agent.d.cts.map +1 -1
  118. package/dist/http/user-agent.d.ts +8 -0
  119. package/dist/http/user-agent.d.ts.map +1 -1
  120. package/dist/http/user-agent.js +13 -3
  121. package/dist/http/user-agent.js.map +1 -1
  122. package/dist/index.cjs +50 -2
  123. package/dist/index.d.cts +15 -12
  124. package/dist/index.d.cts.map +1 -1
  125. package/dist/index.d.ts +15 -12
  126. package/dist/index.d.ts.map +1 -1
  127. package/dist/index.js +13 -10
  128. package/dist/notifications/index.cjs +3 -0
  129. package/dist/notifications/index.d.cts +2 -0
  130. package/dist/notifications/index.d.cts.map +1 -1
  131. package/dist/notifications/index.d.ts +2 -0
  132. package/dist/notifications/index.d.ts.map +1 -1
  133. package/dist/notifications/index.js +2 -1
  134. package/dist/object.cjs +13 -2
  135. package/dist/object.cjs.map +1 -1
  136. package/dist/object.d.cts +11 -3
  137. package/dist/object.d.cts.map +1 -1
  138. package/dist/object.d.ts +11 -3
  139. package/dist/object.d.ts.map +1 -1
  140. package/dist/object.js +13 -2
  141. package/dist/object.js.map +1 -1
  142. package/dist/partner/account-info.d.cts +43 -0
  143. package/dist/partner/account-info.d.cts.map +1 -0
  144. package/dist/partner/account-info.d.ts +43 -0
  145. package/dist/partner/account-info.d.ts.map +1 -0
  146. package/dist/partner/auth-clone.cjs +64 -0
  147. package/dist/partner/auth-clone.cjs.map +1 -0
  148. package/dist/partner/auth-clone.d.cts +27 -0
  149. package/dist/partner/auth-clone.d.cts.map +1 -0
  150. package/dist/partner/auth-clone.d.ts +27 -0
  151. package/dist/partner/auth-clone.d.ts.map +1 -0
  152. package/dist/partner/auth-clone.js +63 -0
  153. package/dist/partner/auth-clone.js.map +1 -0
  154. package/dist/partner/auth-core.cjs +156 -0
  155. package/dist/partner/auth-core.cjs.map +1 -0
  156. package/dist/partner/auth-core.d.cts +111 -0
  157. package/dist/partner/auth-core.d.cts.map +1 -0
  158. package/dist/partner/auth-core.d.ts +111 -0
  159. package/dist/partner/auth-core.d.ts.map +1 -0
  160. package/dist/partner/auth-core.js +156 -0
  161. package/dist/partner/auth-core.js.map +1 -0
  162. package/dist/partner/auth-shape.cjs +36 -0
  163. package/dist/partner/auth-shape.cjs.map +1 -0
  164. package/dist/partner/auth-shape.d.cts +12 -0
  165. package/dist/partner/auth-shape.d.cts.map +1 -0
  166. package/dist/partner/auth-shape.d.ts +12 -0
  167. package/dist/partner/auth-shape.d.ts.map +1 -0
  168. package/dist/partner/auth-shape.js +36 -0
  169. package/dist/partner/auth-shape.js.map +1 -0
  170. package/dist/partner/client.cjs +270 -0
  171. package/dist/partner/client.cjs.map +1 -0
  172. package/dist/partner/client.d.cts +357 -0
  173. package/dist/partner/client.d.cts.map +1 -0
  174. package/dist/partner/client.d.ts +357 -0
  175. package/dist/partner/client.d.ts.map +1 -0
  176. package/dist/partner/client.js +270 -0
  177. package/dist/partner/client.js.map +1 -0
  178. package/dist/partner/endpoint-url.cjs +109 -0
  179. package/dist/partner/endpoint-url.cjs.map +1 -0
  180. package/dist/partner/endpoint-url.d.cts +74 -0
  181. package/dist/partner/endpoint-url.d.cts.map +1 -0
  182. package/dist/partner/endpoint-url.d.ts +74 -0
  183. package/dist/partner/endpoint-url.d.ts.map +1 -0
  184. package/dist/partner/endpoint-url.js +106 -0
  185. package/dist/partner/endpoint-url.js.map +1 -0
  186. package/dist/partner/in-memory.cjs +135 -0
  187. package/dist/partner/in-memory.cjs.map +1 -0
  188. package/dist/partner/in-memory.d.cts +99 -0
  189. package/dist/partner/in-memory.d.cts.map +1 -0
  190. package/dist/partner/in-memory.d.ts +99 -0
  191. package/dist/partner/in-memory.d.ts.map +1 -0
  192. package/dist/partner/in-memory.js +135 -0
  193. package/dist/partner/in-memory.js.map +1 -0
  194. package/dist/partner/index.cjs +27 -0
  195. package/dist/partner/index.d.cts +32 -0
  196. package/dist/partner/index.d.cts.map +1 -0
  197. package/dist/partner/index.d.ts +32 -0
  198. package/dist/partner/index.d.ts.map +1 -0
  199. package/dist/partner/index.js +9 -0
  200. package/dist/partner/raw.cjs +400 -0
  201. package/dist/partner/raw.cjs.map +1 -0
  202. package/dist/partner/raw.d.cts +220 -0
  203. package/dist/partner/raw.d.cts.map +1 -0
  204. package/dist/partner/raw.d.ts +220 -0
  205. package/dist/partner/raw.d.ts.map +1 -0
  206. package/dist/partner/raw.js +398 -0
  207. package/dist/partner/raw.js.map +1 -0
  208. package/dist/partner/redaction-placeholders.cjs +10 -0
  209. package/dist/partner/redaction-placeholders.cjs.map +1 -0
  210. package/dist/partner/redaction-placeholders.d.cts +5 -0
  211. package/dist/partner/redaction-placeholders.d.cts.map +1 -0
  212. package/dist/partner/redaction-placeholders.d.ts +5 -0
  213. package/dist/partner/redaction-placeholders.d.ts.map +1 -0
  214. package/dist/partner/redaction-placeholders.js +9 -0
  215. package/dist/partner/redaction-placeholders.js.map +1 -0
  216. package/dist/partner/redaction.cjs +292 -0
  217. package/dist/partner/redaction.cjs.map +1 -0
  218. package/dist/partner/redaction.d.cts +158 -0
  219. package/dist/partner/redaction.d.cts.map +1 -0
  220. package/dist/partner/redaction.d.ts +158 -0
  221. package/dist/partner/redaction.d.ts.map +1 -0
  222. package/dist/partner/redaction.js +281 -0
  223. package/dist/partner/redaction.js.map +1 -0
  224. package/dist/raw/encoding.cjs +29 -10
  225. package/dist/raw/encoding.cjs.map +1 -1
  226. package/dist/raw/encoding.d.cts +3 -2
  227. package/dist/raw/encoding.d.cts.map +1 -1
  228. package/dist/raw/encoding.d.ts +3 -2
  229. package/dist/raw/encoding.d.ts.map +1 -1
  230. package/dist/raw/encoding.js +29 -10
  231. package/dist/raw/encoding.js.map +1 -1
  232. package/dist/raw/index.cjs +131 -44
  233. package/dist/raw/index.cjs.map +1 -1
  234. package/dist/raw/index.d.cts +21 -15
  235. package/dist/raw/index.d.cts.map +1 -1
  236. package/dist/raw/index.d.ts +21 -15
  237. package/dist/raw/index.d.ts.map +1 -1
  238. package/dist/raw/index.js +114 -45
  239. package/dist/raw/index.js.map +1 -1
  240. package/dist/raw/url.cjs +111 -0
  241. package/dist/raw/url.cjs.map +1 -0
  242. package/dist/raw/url.d.cts +41 -0
  243. package/dist/raw/url.d.cts.map +1 -0
  244. package/dist/raw/url.d.ts +41 -0
  245. package/dist/raw/url.d.ts.map +1 -0
  246. package/dist/raw/url.js +110 -0
  247. package/dist/raw/url.js.map +1 -0
  248. package/dist/s3/sigv4.cjs +15 -10
  249. package/dist/s3/sigv4.cjs.map +1 -1
  250. package/dist/s3/sigv4.js +15 -10
  251. package/dist/s3/sigv4.js.map +1 -1
  252. package/dist/simulator/index.cjs +1579 -257
  253. package/dist/simulator/index.cjs.map +1 -1
  254. package/dist/simulator/index.d.cts +124 -11
  255. package/dist/simulator/index.d.cts.map +1 -1
  256. package/dist/simulator/index.d.ts +124 -11
  257. package/dist/simulator/index.d.ts.map +1 -1
  258. package/dist/simulator/index.js +1568 -262
  259. package/dist/simulator/index.js.map +1 -1
  260. package/dist/simulator/partner-capabilities.cjs +77 -0
  261. package/dist/simulator/partner-capabilities.cjs.map +1 -0
  262. package/dist/simulator/partner-capabilities.d.cts +53 -0
  263. package/dist/simulator/partner-capabilities.d.cts.map +1 -0
  264. package/dist/simulator/partner-capabilities.d.ts +53 -0
  265. package/dist/simulator/partner-capabilities.d.ts.map +1 -0
  266. package/dist/simulator/partner-capabilities.js +73 -0
  267. package/dist/simulator/partner-capabilities.js.map +1 -0
  268. package/dist/simulator/partner.cjs +713 -0
  269. package/dist/simulator/partner.cjs.map +1 -0
  270. package/dist/simulator/partner.d.cts +186 -0
  271. package/dist/simulator/partner.d.cts.map +1 -0
  272. package/dist/simulator/partner.d.ts +186 -0
  273. package/dist/simulator/partner.d.ts.map +1 -0
  274. package/dist/simulator/partner.js +713 -0
  275. package/dist/simulator/partner.js.map +1 -0
  276. package/dist/simulator/validation.cjs +732 -25
  277. package/dist/simulator/validation.cjs.map +1 -1
  278. package/dist/simulator/validation.d.cts +145 -6
  279. package/dist/simulator/validation.d.cts.map +1 -1
  280. package/dist/simulator/validation.d.ts +145 -6
  281. package/dist/simulator/validation.d.ts.map +1 -1
  282. package/dist/simulator/validation.js +705 -26
  283. package/dist/simulator/validation.js.map +1 -1
  284. package/dist/streams/file-source.cjs +2 -2
  285. package/dist/streams/file-source.cjs.map +1 -1
  286. package/dist/streams/file-source.js +2 -2
  287. package/dist/streams/file-source.js.map +1 -1
  288. package/dist/streams/hash.cjs +246 -13
  289. package/dist/streams/hash.cjs.map +1 -1
  290. package/dist/streams/hash.d.cts +37 -12
  291. package/dist/streams/hash.d.cts.map +1 -1
  292. package/dist/streams/hash.d.ts +37 -12
  293. package/dist/streams/hash.d.ts.map +1 -1
  294. package/dist/streams/hash.js +246 -15
  295. package/dist/streams/hash.js.map +1 -1
  296. package/dist/streams/index.cjs +3 -1
  297. package/dist/streams/index.d.cts +1 -1
  298. package/dist/streams/index.d.cts.map +1 -1
  299. package/dist/streams/index.d.ts +1 -1
  300. package/dist/streams/index.d.ts.map +1 -1
  301. package/dist/streams/index.js +3 -3
  302. package/dist/streams/progress.cjs +63 -4
  303. package/dist/streams/progress.cjs.map +1 -1
  304. package/dist/streams/progress.d.cts +28 -2
  305. package/dist/streams/progress.d.cts.map +1 -1
  306. package/dist/streams/progress.d.ts +28 -2
  307. package/dist/streams/progress.d.ts.map +1 -1
  308. package/dist/streams/progress.js +63 -4
  309. package/dist/streams/progress.js.map +1 -1
  310. package/dist/streams/source.cjs +2 -1
  311. package/dist/streams/source.cjs.map +1 -1
  312. package/dist/streams/source.js +2 -1
  313. package/dist/streams/source.js.map +1 -1
  314. package/dist/sync/b2-sha1-reader.cjs +6 -2
  315. package/dist/sync/b2-sha1-reader.cjs.map +1 -1
  316. package/dist/sync/b2-sha1-reader.d.cts.map +1 -1
  317. package/dist/sync/b2-sha1-reader.d.ts.map +1 -1
  318. package/dist/sync/b2-sha1-reader.js +6 -2
  319. package/dist/sync/b2-sha1-reader.js.map +1 -1
  320. package/dist/sync/download-staging.cjs +156 -43
  321. package/dist/sync/download-staging.cjs.map +1 -1
  322. package/dist/sync/download-staging.d.cts +26 -2
  323. package/dist/sync/download-staging.d.cts.map +1 -1
  324. package/dist/sync/download-staging.d.ts +26 -2
  325. package/dist/sync/download-staging.d.ts.map +1 -1
  326. package/dist/sync/download-staging.js +155 -44
  327. package/dist/sync/download-staging.js.map +1 -1
  328. package/dist/sync/filters.cjs.map +1 -1
  329. package/dist/sync/filters.js.map +1 -1
  330. package/dist/sync/index.d.cts +1 -1
  331. package/dist/sync/index.d.cts.map +1 -1
  332. package/dist/sync/index.d.ts +1 -1
  333. package/dist/sync/index.d.ts.map +1 -1
  334. package/dist/sync/local-file-io.cjs +15 -6
  335. package/dist/sync/local-file-io.cjs.map +1 -1
  336. package/dist/sync/local-file-io.d.cts +3 -0
  337. package/dist/sync/local-file-io.d.cts.map +1 -1
  338. package/dist/sync/local-file-io.d.ts +3 -0
  339. package/dist/sync/local-file-io.d.ts.map +1 -1
  340. package/dist/sync/local-file-io.js +15 -6
  341. package/dist/sync/local-file-io.js.map +1 -1
  342. package/dist/sync/local-sha1.cjs +6 -15
  343. package/dist/sync/local-sha1.cjs.map +1 -1
  344. package/dist/sync/local-sha1.d.cts +0 -8
  345. package/dist/sync/local-sha1.d.cts.map +1 -1
  346. package/dist/sync/local-sha1.d.ts +0 -8
  347. package/dist/sync/local-sha1.d.ts.map +1 -1
  348. package/dist/sync/local-sha1.js +7 -15
  349. package/dist/sync/local-sha1.js.map +1 -1
  350. package/dist/sync/pairing.cjs +1 -0
  351. package/dist/sync/pairing.cjs.map +1 -1
  352. package/dist/sync/pairing.js +1 -0
  353. package/dist/sync/pairing.js.map +1 -1
  354. package/dist/sync/policies/compare.cjs +5 -3
  355. package/dist/sync/policies/compare.cjs.map +1 -1
  356. package/dist/sync/policies/compare.d.cts.map +1 -1
  357. package/dist/sync/policies/compare.d.ts.map +1 -1
  358. package/dist/sync/policies/compare.js +6 -4
  359. package/dist/sync/policies/compare.js.map +1 -1
  360. package/dist/sync/policies/index.cjs +4 -10
  361. package/dist/sync/policies/index.cjs.map +1 -1
  362. package/dist/sync/policies/index.js +4 -10
  363. package/dist/sync/policies/index.js.map +1 -1
  364. package/dist/sync/scanners/b2.cjs +4 -2
  365. package/dist/sync/scanners/b2.cjs.map +1 -1
  366. package/dist/sync/scanners/b2.d.cts.map +1 -1
  367. package/dist/sync/scanners/b2.d.ts.map +1 -1
  368. package/dist/sync/scanners/b2.js +4 -2
  369. package/dist/sync/scanners/b2.js.map +1 -1
  370. package/dist/sync/scanners/local.cjs +150 -33
  371. package/dist/sync/scanners/local.cjs.map +1 -1
  372. package/dist/sync/scanners/local.d.cts +6 -0
  373. package/dist/sync/scanners/local.d.cts.map +1 -1
  374. package/dist/sync/scanners/local.d.ts +6 -0
  375. package/dist/sync/scanners/local.d.ts.map +1 -1
  376. package/dist/sync/scanners/local.js +150 -33
  377. package/dist/sync/scanners/local.js.map +1 -1
  378. package/dist/sync/synchronizer.cjs +79 -31
  379. package/dist/sync/synchronizer.cjs.map +1 -1
  380. package/dist/sync/synchronizer.d.cts +1 -0
  381. package/dist/sync/synchronizer.d.cts.map +1 -1
  382. package/dist/sync/synchronizer.d.ts +1 -0
  383. package/dist/sync/synchronizer.d.ts.map +1 -1
  384. package/dist/sync/synchronizer.js +79 -31
  385. package/dist/sync/synchronizer.js.map +1 -1
  386. package/dist/sync/types.d.cts +19 -3
  387. package/dist/sync/types.d.cts.map +1 -1
  388. package/dist/sync/types.d.ts +19 -3
  389. package/dist/sync/types.d.ts.map +1 -1
  390. package/dist/types/auth.cjs +4 -0
  391. package/dist/types/auth.cjs.map +1 -1
  392. package/dist/types/auth.d.cts +4 -0
  393. package/dist/types/auth.d.cts.map +1 -1
  394. package/dist/types/auth.d.ts +4 -0
  395. package/dist/types/auth.d.ts.map +1 -1
  396. package/dist/types/auth.js +4 -0
  397. package/dist/types/auth.js.map +1 -1
  398. package/dist/types/backup.d.cts +85 -0
  399. package/dist/types/backup.d.cts.map +1 -0
  400. package/dist/types/backup.d.ts +85 -0
  401. package/dist/types/backup.d.ts.map +1 -0
  402. package/dist/types/bucket.cjs +81 -11
  403. package/dist/types/bucket.cjs.map +1 -1
  404. package/dist/types/bucket.d.cts +194 -41
  405. package/dist/types/bucket.d.cts.map +1 -1
  406. package/dist/types/bucket.d.ts +194 -41
  407. package/dist/types/bucket.d.ts.map +1 -1
  408. package/dist/types/bucket.js +67 -12
  409. package/dist/types/bucket.js.map +1 -1
  410. package/dist/types/download.cjs +62 -0
  411. package/dist/types/download.cjs.map +1 -0
  412. package/dist/types/download.d.cts +100 -16
  413. package/dist/types/download.d.cts.map +1 -1
  414. package/dist/types/download.d.ts +100 -16
  415. package/dist/types/download.d.ts.map +1 -1
  416. package/dist/types/download.js +61 -0
  417. package/dist/types/download.js.map +1 -0
  418. package/dist/types/encryption.cjs +76 -229
  419. package/dist/types/encryption.cjs.map +1 -1
  420. package/dist/types/encryption.d.cts +48 -6
  421. package/dist/types/encryption.d.cts.map +1 -1
  422. package/dist/types/encryption.d.ts +48 -6
  423. package/dist/types/encryption.d.ts.map +1 -1
  424. package/dist/types/encryption.js +74 -229
  425. package/dist/types/encryption.js.map +1 -1
  426. package/dist/types/errors.cjs +9 -0
  427. package/dist/types/errors.cjs.map +1 -1
  428. package/dist/types/errors.d.cts +1 -1
  429. package/dist/types/errors.d.cts.map +1 -1
  430. package/dist/types/errors.d.ts +1 -1
  431. package/dist/types/errors.d.ts.map +1 -1
  432. package/dist/types/errors.js +9 -0
  433. package/dist/types/errors.js.map +1 -1
  434. package/dist/types/file.cjs +3 -0
  435. package/dist/types/file.cjs.map +1 -1
  436. package/dist/types/file.d.cts +140 -18
  437. package/dist/types/file.d.cts.map +1 -1
  438. package/dist/types/file.d.ts +140 -18
  439. package/dist/types/file.d.ts.map +1 -1
  440. package/dist/types/file.js +3 -1
  441. package/dist/types/file.js.map +1 -1
  442. package/dist/types/ids.cjs +30 -0
  443. package/dist/types/ids.cjs.map +1 -1
  444. package/dist/types/ids.d.cts +32 -0
  445. package/dist/types/ids.d.cts.map +1 -1
  446. package/dist/types/ids.d.ts +32 -0
  447. package/dist/types/ids.d.ts.map +1 -1
  448. package/dist/types/ids.js +28 -1
  449. package/dist/types/ids.js.map +1 -1
  450. package/dist/types/index.d.cts +16 -12
  451. package/dist/types/index.d.cts.map +1 -1
  452. package/dist/types/index.d.ts +16 -12
  453. package/dist/types/index.d.ts.map +1 -1
  454. package/dist/types/key.d.cts +4 -3
  455. package/dist/types/key.d.cts.map +1 -1
  456. package/dist/types/key.d.ts +4 -3
  457. package/dist/types/key.d.ts.map +1 -1
  458. package/dist/types/lock.cjs.map +1 -1
  459. package/dist/types/lock.d.cts +14 -0
  460. package/dist/types/lock.d.cts.map +1 -1
  461. package/dist/types/lock.d.ts +14 -0
  462. package/dist/types/lock.d.ts.map +1 -1
  463. package/dist/types/lock.js.map +1 -1
  464. package/dist/types/notifications.cjs +31 -1
  465. package/dist/types/notifications.cjs.map +1 -1
  466. package/dist/types/notifications.d.cts +31 -4
  467. package/dist/types/notifications.d.cts.map +1 -1
  468. package/dist/types/notifications.d.ts +31 -4
  469. package/dist/types/notifications.d.ts.map +1 -1
  470. package/dist/types/notifications.js +30 -2
  471. package/dist/types/notifications.js.map +1 -1
  472. package/dist/types/partner.cjs +32 -0
  473. package/dist/types/partner.cjs.map +1 -0
  474. package/dist/types/partner.d.cts +386 -0
  475. package/dist/types/partner.d.cts.map +1 -0
  476. package/dist/types/partner.d.ts +386 -0
  477. package/dist/types/partner.d.ts.map +1 -0
  478. package/dist/types/partner.js +29 -0
  479. package/dist/types/partner.js.map +1 -0
  480. package/dist/types/upload.d.cts +35 -18
  481. package/dist/types/upload.d.cts.map +1 -1
  482. package/dist/types/upload.d.ts +35 -18
  483. package/dist/types/upload.d.ts.map +1 -1
  484. package/dist/upload/abort-scope.cjs +0 -45
  485. package/dist/upload/abort-scope.cjs.map +1 -1
  486. package/dist/upload/abort-scope.d.cts +0 -22
  487. package/dist/upload/abort-scope.d.cts.map +1 -1
  488. package/dist/upload/abort-scope.d.ts +0 -22
  489. package/dist/upload/abort-scope.d.ts.map +1 -1
  490. package/dist/upload/abort-scope.js +1 -44
  491. package/dist/upload/abort-scope.js.map +1 -1
  492. package/dist/upload/finish.cjs +3 -8
  493. package/dist/upload/finish.cjs.map +1 -1
  494. package/dist/upload/finish.d.cts.map +1 -1
  495. package/dist/upload/finish.d.ts.map +1 -1
  496. package/dist/upload/finish.js +1 -6
  497. package/dist/upload/finish.js.map +1 -1
  498. package/dist/upload/large.cjs +15 -8
  499. package/dist/upload/large.cjs.map +1 -1
  500. package/dist/upload/large.d.cts +31 -8
  501. package/dist/upload/large.d.cts.map +1 -1
  502. package/dist/upload/large.d.ts +31 -8
  503. package/dist/upload/large.d.ts.map +1 -1
  504. package/dist/upload/large.js +15 -8
  505. package/dist/upload/large.js.map +1 -1
  506. package/dist/upload/options.cjs.map +1 -1
  507. package/dist/upload/options.d.cts +24 -0
  508. package/dist/upload/options.d.cts.map +1 -1
  509. package/dist/upload/options.d.ts +24 -0
  510. package/dist/upload/options.d.ts.map +1 -1
  511. package/dist/upload/options.js.map +1 -1
  512. package/dist/upload/resume.cjs +6 -4
  513. package/dist/upload/resume.cjs.map +1 -1
  514. package/dist/upload/resume.d.cts +9 -5
  515. package/dist/upload/resume.d.cts.map +1 -1
  516. package/dist/upload/resume.d.ts +9 -5
  517. package/dist/upload/resume.d.ts.map +1 -1
  518. package/dist/upload/resume.js +6 -4
  519. package/dist/upload/resume.js.map +1 -1
  520. package/dist/upload/retry.cjs +25 -13
  521. package/dist/upload/retry.cjs.map +1 -1
  522. package/dist/upload/retry.d.cts.map +1 -1
  523. package/dist/upload/retry.d.ts.map +1 -1
  524. package/dist/upload/retry.js +25 -13
  525. package/dist/upload/retry.js.map +1 -1
  526. package/dist/upload/single.cjs +35 -30
  527. package/dist/upload/single.cjs.map +1 -1
  528. package/dist/upload/single.d.cts +12 -0
  529. package/dist/upload/single.d.cts.map +1 -1
  530. package/dist/upload/single.d.ts +12 -0
  531. package/dist/upload/single.d.ts.map +1 -1
  532. package/dist/upload/single.js +35 -30
  533. package/dist/upload/single.js.map +1 -1
  534. package/dist/upload/stream.cjs +18 -5
  535. package/dist/upload/stream.cjs.map +1 -1
  536. package/dist/upload/stream.d.cts +12 -0
  537. package/dist/upload/stream.d.cts.map +1 -1
  538. package/dist/upload/stream.d.ts +12 -0
  539. package/dist/upload/stream.d.ts.map +1 -1
  540. package/dist/upload/stream.js +15 -2
  541. package/dist/upload/stream.js.map +1 -1
  542. package/dist/util/abort.cjs +114 -0
  543. package/dist/util/abort.cjs.map +1 -0
  544. package/dist/util/abort.d.cts +65 -0
  545. package/dist/util/abort.d.cts.map +1 -0
  546. package/dist/util/abort.d.ts +65 -0
  547. package/dist/util/abort.d.ts.map +1 -0
  548. package/dist/util/abort.js +109 -0
  549. package/dist/util/abort.js.map +1 -0
  550. package/dist/util/defaults.cjs +3 -0
  551. package/dist/util/defaults.cjs.map +1 -1
  552. package/dist/util/defaults.d.cts +2 -0
  553. package/dist/util/defaults.d.cts.map +1 -1
  554. package/dist/util/defaults.d.ts +2 -0
  555. package/dist/util/defaults.d.ts.map +1 -1
  556. package/dist/util/defaults.js +3 -1
  557. package/dist/util/defaults.js.map +1 -1
  558. package/dist/util/md5.cjs +211 -0
  559. package/dist/util/md5.cjs.map +1 -0
  560. package/dist/util/md5.d.cts +43 -0
  561. package/dist/util/md5.d.cts.map +1 -0
  562. package/dist/util/md5.d.ts +43 -0
  563. package/dist/util/md5.d.ts.map +1 -0
  564. package/dist/util/md5.js +209 -0
  565. package/dist/util/md5.js.map +1 -0
  566. package/dist/util/normalize.cjs +5 -5
  567. package/dist/util/normalize.cjs.map +1 -1
  568. package/dist/util/normalize.d.cts +7 -7
  569. package/dist/util/normalize.d.cts.map +1 -1
  570. package/dist/util/normalize.d.ts +7 -7
  571. package/dist/util/normalize.d.ts.map +1 -1
  572. package/dist/util/normalize.js +5 -5
  573. package/dist/util/normalize.js.map +1 -1
  574. package/dist/util/paginator.cjs +7 -1
  575. package/dist/util/paginator.cjs.map +1 -1
  576. package/dist/util/paginator.d.cts +3 -1
  577. package/dist/util/paginator.d.cts.map +1 -1
  578. package/dist/util/paginator.d.ts +3 -1
  579. package/dist/util/paginator.d.ts.map +1 -1
  580. package/dist/util/paginator.js +7 -1
  581. package/dist/util/paginator.js.map +1 -1
  582. package/dist/version.cjs +56 -4
  583. package/dist/version.cjs.map +1 -1
  584. package/dist/version.d.cts +53 -4
  585. package/dist/version.d.cts.map +1 -1
  586. package/dist/version.d.ts +53 -4
  587. package/dist/version.d.ts.map +1 -1
  588. package/dist/version.js +52 -5
  589. package/dist/version.js.map +1 -1
  590. package/package.json +47 -18
@@ -3,17 +3,97 @@ 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_capabilities = require("./partner-capabilities.cjs");
17
+ const require_partner = require("./partner.cjs");
12
18
  const require_capabilities = require("./capabilities.cjs");
13
19
  const require_b2_naming = require("../internal/b2-naming.cjs");
14
20
  const require_validation = require("./validation.cjs");
15
21
  //#region src/simulator/index.ts
16
- var UPLOAD_TOKEN_SIGNING_KEY = "b2-sdk-typescript-simulator-upload-token-v1";
22
+ var UPLOAD_TOKEN_SIGNING_KEY = [
23
+ "b2 sdk typescript",
24
+ "simulator upload authorization",
25
+ "v1"
26
+ ].join(":");
27
+ var SIMULATOR_MASTER_APPLICATION_KEY_ID = "master-key-id";
28
+ var SIMULATOR_MASTER_APPLICATION_KEY = "master-key";
29
+ var SIMULATOR_TEST_APPLICATION_KEY_ID = "test-key-id";
30
+ var SIMULATOR_TEST_APPLICATION_KEY = "test-key";
31
+ var SIMULATOR_APPLICATION_KEY_ID_PREFIX = "sim_key_";
32
+ var SIMULATOR_MASTER_CAPABILITIES = [
33
+ require_auth.Capability.ListBuckets,
34
+ require_auth.Capability.ListAllBucketNames,
35
+ require_auth.Capability.ReadBuckets,
36
+ require_auth.Capability.WriteBuckets,
37
+ require_auth.Capability.DeleteBuckets,
38
+ require_auth.Capability.ReadBucketEncryption,
39
+ require_auth.Capability.WriteBucketEncryption,
40
+ require_auth.Capability.ReadBucketReplications,
41
+ require_auth.Capability.WriteBucketReplications,
42
+ require_auth.Capability.ReadBucketNotifications,
43
+ require_auth.Capability.WriteBucketNotifications,
44
+ require_auth.Capability.ReadBucketLogging,
45
+ require_auth.Capability.WriteBucketLogging,
46
+ require_auth.Capability.ReadBucketLifecycleRules,
47
+ require_auth.Capability.WriteBucketLifecycleRules,
48
+ require_auth.Capability.ListFiles,
49
+ require_auth.Capability.ReadFiles,
50
+ require_auth.Capability.WriteFiles,
51
+ require_auth.Capability.DeleteFiles,
52
+ require_auth.Capability.ListKeys,
53
+ require_auth.Capability.WriteKeys,
54
+ require_auth.Capability.DeleteKeys,
55
+ require_auth.Capability.ShareFiles
56
+ ];
57
+ function apiPathParts(path) {
58
+ const segments = path.split("/").filter((segment) => segment.length > 0);
59
+ const candidate = segments.at(-2);
60
+ const endpoint = segments.at(-1) ?? "";
61
+ const isB2ApiPath = segments.at(-3) === "b2api";
62
+ if (candidate !== void 0 && require_url.isB2ApiVersion(candidate)) {
63
+ if (candidate === "v3" || candidate === "v4") return {
64
+ endpoint,
65
+ version: candidate
66
+ };
67
+ if (isB2ApiPath) return {
68
+ endpoint,
69
+ version: "v3",
70
+ unsupportedVersion: candidate
71
+ };
72
+ }
73
+ return {
74
+ endpoint,
75
+ version: "v3"
76
+ };
77
+ }
78
+ function parseBasicAuthorizationHeader(authzHeader) {
79
+ if (!authzHeader?.startsWith("Basic ")) return null;
80
+ const decoded = (() => {
81
+ try {
82
+ return atob(authzHeader.slice(6));
83
+ } catch {
84
+ return null;
85
+ }
86
+ })();
87
+ if (decoded === null) return null;
88
+ const idx = decoded.indexOf(":");
89
+ if (idx <= 0 || idx === decoded.length - 1) return null;
90
+ return {
91
+ applicationKeyId: decoded.slice(0, idx),
92
+ applicationKey: decoded.slice(idx + 1)
93
+ };
94
+ }
95
+ var B2_MIN_PART_NUMBER = 1;
96
+ var B2_MAX_PART_NUMBER = 1e4;
17
97
  function base64UrlEncode(bytes) {
18
98
  let binary = "";
19
99
  for (const byte of bytes) binary += String.fromCharCode(byte);
@@ -35,6 +115,11 @@ function timingSafeStringEqual(left, right) {
35
115
  for (let i = 0; i < max; i++) mismatch |= (left.charCodeAt(i) || 0) ^ (right.charCodeAt(i) || 0);
36
116
  return mismatch === 0;
37
117
  }
118
+ var MS_PER_DAY = 864e5;
119
+ var MAX_JS_DATE_MS = 864e13;
120
+ function isValidRetentionTimestamp(timestamp, now = Date.now()) {
121
+ return Number.isFinite(timestamp) && timestamp <= MAX_JS_DATE_MS && timestamp > now;
122
+ }
38
123
  /**
39
124
  * Parse an RFC 7233 `Range` header value into inclusive start/end byte
40
125
  * offsets clamped to the file size. Supports closed (`bytes=0-999`),
@@ -120,6 +205,56 @@ function compareB2FileNames(a, b) {
120
205
  if (a > b) return 1;
121
206
  return 0;
122
207
  }
208
+ function compareFileVersionListEntries(a, b) {
209
+ const nameCmp = compareB2FileNames(a.fileName, b.fileName);
210
+ if (nameCmp !== 0) return nameCmp;
211
+ return b.uploadTimestamp - a.uploadTimestamp;
212
+ }
213
+ var DOWNLOAD_AUTH_TOKEN_BYTES = 32;
214
+ var DOWNLOAD_AUTH_TOKEN_PREFIX = "sim_dl_auth_";
215
+ var DOWNLOAD_AUTH_PURGE_BATCH_SIZE = 128;
216
+ var DOWNLOAD_RESPONSE_OVERRIDE_PARAMS = [
217
+ "b2ContentDisposition",
218
+ "b2ContentLanguage",
219
+ "b2ContentEncoding",
220
+ "b2ContentType",
221
+ "b2CacheControl",
222
+ "b2Expires"
223
+ ];
224
+ var DOWNLOAD_RESPONSE_OVERRIDE_HEADERS = {
225
+ b2ContentDisposition: require_download.DownloadHeaderName.ContentDisposition,
226
+ b2ContentLanguage: require_download.DownloadHeaderName.ContentLanguage,
227
+ b2ContentEncoding: require_download.DownloadHeaderName.ContentEncoding,
228
+ b2ContentType: require_download.DownloadHeaderName.ContentType,
229
+ b2CacheControl: require_download.DownloadHeaderName.CacheControl,
230
+ b2Expires: require_download.DownloadHeaderName.Expires
231
+ };
232
+ function randomDownloadAuthorizationToken() {
233
+ const crypto = globalThis.crypto;
234
+ if (typeof crypto?.getRandomValues !== "function") throw new Error("Download authorization tokens require globalThis.crypto.getRandomValues");
235
+ const bytes = new Uint8Array(DOWNLOAD_AUTH_TOKEN_BYTES);
236
+ crypto.getRandomValues(bytes);
237
+ return `${DOWNLOAD_AUTH_TOKEN_PREFIX}${require_crypto.hexEncode(bytes)}`;
238
+ }
239
+ function pickDownloadResponseOverrides(req) {
240
+ const overrides = {};
241
+ for (const param of DOWNLOAD_RESPONSE_OVERRIDE_PARAMS) {
242
+ const value = req[param];
243
+ if (typeof value === "string") overrides[param] = value;
244
+ }
245
+ return overrides;
246
+ }
247
+ function validateDownloadResponseOverrides(req) {
248
+ for (const param of DOWNLOAD_RESPONSE_OVERRIDE_PARAMS) {
249
+ const value = req[param];
250
+ if (value === void 0 || typeof value === "string") continue;
251
+ return {
252
+ code: "bad_request",
253
+ message: `${param} must be a string`
254
+ };
255
+ }
256
+ return null;
257
+ }
123
258
  function normalizeKeyBucketIds(req) {
124
259
  return req.bucketIds === void 0 || req.bucketIds === null ? null : Object.freeze([...req.bucketIds]);
125
260
  }
@@ -129,6 +264,18 @@ function singleBucketId(bucketIds) {
129
264
  function cloneBucketIds(bucketIds) {
130
265
  return bucketIds === null ? null : [...bucketIds];
131
266
  }
267
+ function cloneCapabilities(capabilities) {
268
+ return [...capabilities];
269
+ }
270
+ function isImplicitStorageMasterCredential(credentials) {
271
+ 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;
272
+ }
273
+ function isImplicitPartnerMasterCredential(credentials) {
274
+ return credentials.applicationKeyId === SIMULATOR_MASTER_APPLICATION_KEY_ID && credentials.applicationKey === SIMULATOR_MASTER_APPLICATION_KEY;
275
+ }
276
+ function isSimulatorApplicationKeyId(applicationKeyId) {
277
+ return applicationKeyId.startsWith(SIMULATOR_APPLICATION_KEY_ID_PREFIX);
278
+ }
132
279
  function hasOwnField(body, field) {
133
280
  return typeof body === "object" && body !== null && Object.hasOwn(body, field);
134
281
  }
@@ -137,10 +284,347 @@ function requestStringField(body, field) {
137
284
  const value = body[field];
138
285
  return typeof value === "string" ? value : void 0;
139
286
  }
287
+ function requestRecord(body) {
288
+ return typeof body === "object" && body !== null && !Array.isArray(body) ? body : null;
289
+ }
290
+ function replicationDestinationBucketIds(body) {
291
+ const rules = requestRecord(requestRecord(requestRecord(body)?.["replicationConfiguration"])?.["asReplicationSource"])?.["replicationRules"];
292
+ if (!Array.isArray(rules)) return [];
293
+ const bucketIds = [];
294
+ for (const rule of rules) {
295
+ const destinationBucketId = requestRecord(rule)?.["destinationBucketId"];
296
+ if (typeof destinationBucketId === "string") bucketIds.push(destinationBucketId);
297
+ }
298
+ return [...new Set(bucketIds)];
299
+ }
300
+ function normalizeReplicationConfiguration(config) {
301
+ return {
302
+ asReplicationSource: config.asReplicationSource ?? null,
303
+ asReplicationDestination: config.asReplicationDestination ?? null
304
+ };
305
+ }
306
+ function wrapReplicationConfiguration(config) {
307
+ if (config === void 0) return {
308
+ isClientAuthorizedToRead: true,
309
+ value: null
310
+ };
311
+ const normalized = normalizeReplicationConfiguration(config);
312
+ return {
313
+ isClientAuthorizedToRead: true,
314
+ value: normalized.asReplicationSource === null && normalized.asReplicationDestination === null ? null : normalized
315
+ };
316
+ }
317
+ function filterReadableReplicationConfiguration(config, canRead) {
318
+ return canRead ? {
319
+ isClientAuthorizedToRead: true,
320
+ value: config.value
321
+ } : {
322
+ isClientAuthorizedToRead: false,
323
+ value: null
324
+ };
325
+ }
326
+ function readableFileRetention(value, canRead) {
327
+ return canRead ? {
328
+ isClientAuthorizedToRead: true,
329
+ value
330
+ } : {
331
+ isClientAuthorizedToRead: false,
332
+ value: null
333
+ };
334
+ }
335
+ function readableLegalHold(value, canRead) {
336
+ return canRead ? {
337
+ isClientAuthorizedToRead: true,
338
+ value
339
+ } : {
340
+ isClientAuthorizedToRead: false,
341
+ value: null
342
+ };
343
+ }
344
+ function validateBucketConfigurationFields(fields, options) {
345
+ if (fields.bucketInfo !== void 0) {
346
+ const infoError = require_validation.validateBucketInfo(fields.bucketInfo);
347
+ if (infoError) return infoError;
348
+ }
349
+ if (fields.corsRules !== void 0) {
350
+ const corsError = require_validation.validateCorsRules(fields.corsRules);
351
+ if (corsError) return corsError;
352
+ }
353
+ if (fields.lifecycleRules !== void 0) {
354
+ const lifecycleError = require_validation.validateLifecycleRules(fields.lifecycleRules);
355
+ if (lifecycleError) return lifecycleError;
356
+ }
357
+ if (fields.defaultServerSideEncryption !== void 0) {
358
+ const encryptionError = validateBucketDefaultServerSideEncryption(fields.defaultServerSideEncryption);
359
+ if (encryptionError) return encryptionError;
360
+ }
361
+ if (fields.replicationConfiguration !== void 0) {
362
+ const replicationError = require_validation.validateReplicationConfiguration(fields.replicationConfiguration);
363
+ if (replicationError) return replicationError;
364
+ }
365
+ if (fields.defaultRetention !== void 0) {
366
+ const retentionError = require_validation.validateDefaultRetention(fields.defaultRetention);
367
+ if (retentionError) return retentionError;
368
+ if (requestRecord(fields.defaultRetention)?.["mode"] !== require_bucket.BucketRetentionMode.None && !options.objectLockEnabled) return {
369
+ code: "file_lock_not_enabled",
370
+ message: "Bucket must have Object Lock enabled to set defaultRetention"
371
+ };
372
+ }
373
+ return null;
374
+ }
375
+ function validateBucketDefaultServerSideEncryption(encryption) {
376
+ const runtimeEncryption = requestRecord(encryption);
377
+ if (runtimeEncryption === null) return {
378
+ code: "bad_request",
379
+ message: "defaultServerSideEncryption must be an object"
380
+ };
381
+ if (hasCustomerEncryptionFields(runtimeEncryption)) return {
382
+ code: "bad_request",
383
+ message: "Bucket default server-side encryption cannot use SSE-C"
384
+ };
385
+ const mode = runtimeEncryption["mode"];
386
+ if (mode === require_encryption.EncryptionMode.None) return null;
387
+ if (mode === require_encryption.EncryptionMode.SseB2) {
388
+ if (runtimeEncryption["algorithm"] !== require_encryption.EncryptionAlgorithm.Aes256) return {
389
+ code: "bad_request",
390
+ message: "SSE-B2 algorithm must be AES256"
391
+ };
392
+ return null;
393
+ }
394
+ if (mode === require_encryption.EncryptionMode.SseC) return {
395
+ code: "bad_request",
396
+ message: "Bucket default server-side encryption cannot use SSE-C"
397
+ };
398
+ return {
399
+ code: "bad_request",
400
+ message: `Unsupported bucket default server-side encryption mode: ${String(mode)}`
401
+ };
402
+ }
403
+ function nextObjectLockEnabled(current, requested) {
404
+ if (requested === void 0) return current;
405
+ if (typeof requested !== "boolean") return {
406
+ code: "bad_request",
407
+ message: "fileLockEnabled must be a boolean"
408
+ };
409
+ if (current && requested === false) return {
410
+ code: "file_lock_conflict",
411
+ message: "Object Lock cannot be disabled"
412
+ };
413
+ return current || requested;
414
+ }
415
+ function requestHeaderValue(headers, name) {
416
+ const exact = headers[name];
417
+ if (exact !== void 0) return exact;
418
+ const lowerName = name.toLowerCase();
419
+ for (const [key, value] of Object.entries(headers)) if (key.toLowerCase() === lowerName) return value;
420
+ }
421
+ function parseContentLengthHeader(headers) {
422
+ const header = requestHeaderValue(headers, "content-length");
423
+ if (header === void 0) return { kind: "absent" };
424
+ if (!/^\d+$/.test(header)) return {
425
+ kind: "error",
426
+ message: `Content-Length must be a byte count: ${header}`
427
+ };
428
+ const expectedLength = Number(header);
429
+ if (!Number.isSafeInteger(expectedLength)) return {
430
+ kind: "error",
431
+ message: `Content-Length is too large: ${header}`
432
+ };
433
+ return {
434
+ kind: "ok",
435
+ expectedLength
436
+ };
437
+ }
438
+ function contentLengthMismatchMessage(expectedLength, actualLength) {
439
+ return `Content-Length ${expectedLength} does not match request body length ${actualLength}`;
440
+ }
140
441
  function fileNames(...names) {
141
442
  const present = names.filter((name) => name !== void 0);
142
443
  return present.length > 0 ? present : void 0;
143
444
  }
445
+ function queryParamsBody(params, endpoint) {
446
+ const metadata = jsonEndpointMetadata(endpoint);
447
+ const queryFields = new Set(metadata?.queryFields ?? []);
448
+ const numericFields = new Set(metadata?.numericQueryFields ?? []);
449
+ const body = Object.create(null);
450
+ for (const [key, value] of params) {
451
+ if (!queryFields.has(key)) continue;
452
+ if (body[key] !== void 0) continue;
453
+ body[key] = queryParamValue(value, numericFields.has(key));
454
+ }
455
+ return body;
456
+ }
457
+ function queryParamValue(value, numeric) {
458
+ if (!numeric || !/^-?\d+$/.test(value)) return value;
459
+ const numericValue = Number(value);
460
+ return Number.isSafeInteger(numericValue) ? numericValue : value;
461
+ }
462
+ var JSON_ENDPOINTS = {
463
+ b2_authorize_account: { methods: ["GET"] },
464
+ [require_partner_capabilities.PartnerEndpoint.ListGroups]: {
465
+ methods: ["GET"],
466
+ queryFields: [
467
+ "adminAccountId",
468
+ "groupName",
469
+ "startGroupId",
470
+ "maxGroupCount"
471
+ ],
472
+ numericQueryFields: ["maxGroupCount"]
473
+ },
474
+ [require_partner_capabilities.PartnerEndpoint.ListGroupMembers]: {
475
+ methods: ["GET"],
476
+ queryFields: [
477
+ "adminAccountId",
478
+ "groupId",
479
+ "startEmail",
480
+ "maxMemberCount"
481
+ ],
482
+ numericQueryFields: ["maxMemberCount"]
483
+ },
484
+ [require_partner_capabilities.PartnerEndpoint.ListComputers]: {
485
+ methods: ["GET"],
486
+ queryFields: [
487
+ "accountId",
488
+ "startComputerId",
489
+ "maxComputerCount"
490
+ ],
491
+ numericQueryFields: ["maxComputerCount"]
492
+ },
493
+ b2_get_upload_url: {
494
+ methods: ["GET", "POST"],
495
+ queryFields: ["bucketId"]
496
+ },
497
+ b2_list_file_names: {
498
+ methods: ["GET", "POST"],
499
+ queryFields: [
500
+ "bucketId",
501
+ "delimiter",
502
+ "maxFileCount",
503
+ "prefix",
504
+ "startFileName"
505
+ ],
506
+ numericQueryFields: ["maxFileCount"]
507
+ },
508
+ b2_list_file_versions: {
509
+ methods: ["GET", "POST"],
510
+ queryFields: [
511
+ "bucketId",
512
+ "delimiter",
513
+ "maxFileCount",
514
+ "prefix",
515
+ "startFileName",
516
+ "startFileId"
517
+ ],
518
+ numericQueryFields: ["maxFileCount"]
519
+ },
520
+ b2_get_file_info: {
521
+ methods: ["GET", "POST"],
522
+ queryFields: ["fileId"]
523
+ },
524
+ b2_copy_file: {
525
+ methods: ["GET", "POST"],
526
+ queryFields: [
527
+ "sourceFileId",
528
+ "fileName",
529
+ "destinationBucketId",
530
+ "range",
531
+ "metadataDirective",
532
+ "contentType"
533
+ ]
534
+ },
535
+ b2_get_upload_part_url: {
536
+ methods: ["GET", "POST"],
537
+ queryFields: ["fileId"]
538
+ },
539
+ b2_list_unfinished_large_files: {
540
+ methods: ["GET", "POST"],
541
+ queryFields: [
542
+ "bucketId",
543
+ "namePrefix",
544
+ "startFileId",
545
+ "maxFileCount"
546
+ ],
547
+ numericQueryFields: ["maxFileCount"]
548
+ },
549
+ b2_list_parts: {
550
+ methods: ["GET", "POST"],
551
+ queryFields: [
552
+ "fileId",
553
+ "startPartNumber",
554
+ "maxPartCount"
555
+ ],
556
+ numericQueryFields: ["startPartNumber", "maxPartCount"]
557
+ },
558
+ b2_copy_part: {
559
+ methods: ["GET", "POST"],
560
+ queryFields: [
561
+ "sourceFileId",
562
+ "largeFileId",
563
+ "partNumber",
564
+ "range"
565
+ ],
566
+ numericQueryFields: ["partNumber"]
567
+ },
568
+ b2_get_download_authorization: {
569
+ methods: ["GET", "POST"],
570
+ queryFields: [
571
+ "bucketId",
572
+ "fileNamePrefix",
573
+ "validDurationInSeconds",
574
+ "b2ContentDisposition",
575
+ "b2ContentLanguage",
576
+ "b2ContentEncoding",
577
+ "b2ContentType",
578
+ "b2CacheControl",
579
+ "b2Expires"
580
+ ],
581
+ numericQueryFields: ["validDurationInSeconds"]
582
+ },
583
+ b2_list_keys: {
584
+ methods: ["GET", "POST"],
585
+ queryFields: [
586
+ "accountId",
587
+ "maxKeyCount",
588
+ "startApplicationKeyId"
589
+ ],
590
+ numericQueryFields: ["maxKeyCount"]
591
+ },
592
+ b2_get_bucket_notification_rules: {
593
+ methods: ["GET", "POST"],
594
+ queryFields: ["bucketId"]
595
+ },
596
+ [require_partner_capabilities.PartnerEndpoint.CreateGroupMember]: { methods: ["POST"] },
597
+ [require_partner_capabilities.PartnerEndpoint.EjectGroupMember]: { methods: ["POST"] },
598
+ [require_partner_capabilities.PartnerEndpoint.ReserveTrialCreateAccount]: { methods: ["POST"] },
599
+ [require_partner_capabilities.PartnerEndpoint.DeleteComputer]: { methods: ["POST"] },
600
+ b2_create_bucket: { methods: ["POST"] },
601
+ b2_list_buckets: { methods: ["POST"] },
602
+ b2_delete_bucket: { methods: ["POST"] },
603
+ b2_update_bucket: { methods: ["POST"] },
604
+ b2_hide_file: { methods: ["POST"] },
605
+ b2_delete_file_version: { methods: ["POST"] },
606
+ b2_start_large_file: { methods: ["POST"] },
607
+ b2_finish_large_file: { methods: ["POST"] },
608
+ b2_cancel_large_file: { methods: ["POST"] },
609
+ b2_create_key: { methods: ["POST"] },
610
+ b2_delete_key: { methods: ["POST"] },
611
+ b2_update_file_retention: { methods: ["POST"] },
612
+ b2_update_file_legal_hold: { methods: ["POST"] },
613
+ b2_set_bucket_notification_rules: { methods: ["POST"] }
614
+ };
615
+ function jsonEndpointMetadata(endpoint) {
616
+ return JSON_ENDPOINTS[endpoint];
617
+ }
618
+ function jsonEndpointAllowsMethod(method, endpoint) {
619
+ const metadata = jsonEndpointMetadata(endpoint);
620
+ if (metadata === void 0) return true;
621
+ const normalizedMethod = method.toUpperCase();
622
+ if (normalizedMethod !== "GET" && normalizedMethod !== "POST") return false;
623
+ return metadata.methods.includes(normalizedMethod);
624
+ }
625
+ function isJsonQueryEndpoint(endpoint) {
626
+ return jsonEndpointMetadata(endpoint)?.queryFields !== void 0;
627
+ }
144
628
  function notificationRulePrefixes(body) {
145
629
  if (typeof body !== "object" || body === null) return void 0;
146
630
  const rules = body["eventNotificationRules"];
@@ -157,6 +641,13 @@ function storedNotificationRulePrefixes(rules) {
157
641
  function hasKeyManagementCapability(capabilities) {
158
642
  return capabilities.some((capability) => capability === require_auth.Capability.ListKeys || capability === require_auth.Capability.WriteKeys || capability === require_auth.Capability.DeleteKeys);
159
643
  }
644
+ function missingStoredKeyCapabilities(key, required) {
645
+ const capabilities = new Set(key.capabilities);
646
+ return required.filter((capability) => !capabilities.has(capability));
647
+ }
648
+ function storedKeyAllowsBucket(key, bucketId) {
649
+ return key.bucketIds === null || key.bucketIds.includes(bucketId);
650
+ }
160
651
  function publicServerSideEncryption(encryption) {
161
652
  if (encryption.mode === require_encryption.EncryptionMode.SseC) return {
162
653
  mode: encryption.mode,
@@ -168,27 +659,212 @@ function publicServerSideEncryption(encryption) {
168
659
  };
169
660
  return encryption;
170
661
  }
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"] ?? ""
662
+ function publicBucketDefaultServerSideEncryption(encryption) {
663
+ return {
664
+ isClientAuthorizedToRead: true,
665
+ value: publicBucketDefaultServerSideEncryptionValue(encryption)
666
+ };
667
+ }
668
+ function publicBucketDefaultServerSideEncryptionValue(encryption) {
669
+ if (encryption?.mode === require_encryption.EncryptionMode.SseB2) return encryption;
670
+ return {
671
+ mode: null,
672
+ algorithm: null
673
+ };
674
+ }
675
+ function bucketDefaultServerSideEncryptionForStorage(info) {
676
+ const encryption = info.defaultServerSideEncryption;
677
+ if (!encryption.isClientAuthorizedToRead) return {
678
+ mode: null,
679
+ algorithm: null
680
+ };
681
+ return encryption.value ?? {
682
+ mode: null,
683
+ algorithm: null
684
+ };
685
+ }
686
+ function customerKeyDigest(customerKey) {
687
+ let first = 2166136261;
688
+ let second = 668265261;
689
+ for (const byte of customerKey) {
690
+ first = Math.imul(first ^ byte, 16777619) >>> 0;
691
+ second = Math.imul(second ^ byte, 2246822507) >>> 0;
692
+ }
693
+ return `${first.toString(16).padStart(8, "0")}${second.toString(16).padStart(8, "0")}`;
694
+ }
695
+ var BASE64_ALPHABET = /^[A-Za-z0-9+/]+$/;
696
+ function normalizeSizedBase64(value, decodedByteLength) {
697
+ const paddedLength = Math.ceil(decodedByteLength / 3) * 4;
698
+ const noPaddingLength = Math.ceil(decodedByteLength * 8 / 6);
699
+ const padding = "=".repeat(paddedLength - noPaddingLength);
700
+ if (value.length === noPaddingLength && BASE64_ALPHABET.test(value)) return value.padEnd(paddedLength, "=");
701
+ if (value.length === paddedLength && value.endsWith(padding) && BASE64_ALPHABET.test(value.slice(0, noPaddingLength))) return value;
702
+ return null;
703
+ }
704
+ function base64ToBytes(value, decodedByteLength) {
705
+ const normalized = normalizeSizedBase64(value, decodedByteLength);
706
+ if (normalized === null) return null;
707
+ try {
708
+ const decoded = atob(normalized);
709
+ if (decoded.length !== decodedByteLength) return null;
710
+ const bytes = new Uint8Array(decoded.length);
711
+ for (let i = 0; i < decoded.length; i++) bytes[i] = decoded.charCodeAt(i);
712
+ return bytes;
713
+ } catch {
714
+ return null;
715
+ }
716
+ }
717
+ function encryptionValidationError(message) {
718
+ return {
719
+ status: 400,
720
+ body: {
721
+ status: 400,
722
+ code: "bad_request",
723
+ message
724
+ }
177
725
  };
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
726
+ }
727
+ function sameStoredServerSideEncryption(a, b) {
728
+ if (a.mode !== b.mode) return false;
729
+ if (a.mode === require_encryption.EncryptionMode.None) return true;
730
+ if (a.mode === require_encryption.EncryptionMode.SseB2 && b.mode === require_encryption.EncryptionMode.SseB2) return a.algorithm === b.algorithm;
731
+ 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;
732
+ return false;
733
+ }
734
+ function hasCustomerEncryptionFields(encryption) {
735
+ return encryption["customerKey"] !== void 0 || encryption["customerKeyMd5"] !== void 0 || encryption["customerAlgorithm"] !== void 0;
736
+ }
737
+ function hasCustomerEncryptionHeaders(headers) {
738
+ 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;
739
+ }
740
+ function isExactNoEncryptionWireSetting(encryption) {
741
+ return encryption["mode"] === null && encryption["algorithm"] === null && Object.keys(encryption).length === 2;
742
+ }
743
+ async function storedServerSideEncryption(encryption) {
744
+ const runtimeEncryption = encryption;
745
+ if (encryption.mode === null) {
746
+ if (isExactNoEncryptionWireSetting(runtimeEncryption)) return { mode: require_encryption.EncryptionMode.None };
747
+ if (hasCustomerEncryptionFields(runtimeEncryption)) return encryptionValidationError("No-encryption settings must not include SSE-C customer keys");
748
+ return encryptionValidationError("No-encryption wire settings must be exactly { mode: null, algorithm: null }");
749
+ }
750
+ if (encryption.mode === require_encryption.EncryptionMode.SseC) {
751
+ if (runtimeEncryption["algorithm"] === void 0) return encryptionValidationError("SSE-C customer algorithm is required");
752
+ if (encryption.algorithm !== require_encryption.EncryptionAlgorithm.Aes256) return encryptionValidationError("SSE-C customer algorithm must be AES256");
753
+ const customerKey = "customerKey" in encryption ? encryption.customerKey : void 0;
754
+ const customerKeyMd5 = "customerKeyMd5" in encryption ? encryption.customerKeyMd5 : void 0;
755
+ if (typeof customerKey !== "string" || customerKey === "") return encryptionValidationError("SSE-C customer key is required");
756
+ if (typeof customerKeyMd5 !== "string" || customerKeyMd5 === "") return encryptionValidationError("SSE-C customer key MD5 is required");
757
+ const customerKeyBytes = base64ToBytes(customerKey, 32);
758
+ if (customerKeyBytes === null) return encryptionValidationError("SSE-C customer key must be a base64-encoded 256-bit key");
759
+ const normalizedCustomerKeyMd5 = normalizeSizedBase64(customerKeyMd5, 16);
760
+ if (normalizedCustomerKeyMd5 === null) return encryptionValidationError("SSE-C customer key MD5 must be a base64-encoded 128-bit digest");
761
+ if (await require_md5.md5Base64(customerKeyBytes) !== normalizedCustomerKeyMd5) return encryptionValidationError("SSE-C customer key MD5 does not match the key");
762
+ return {
763
+ mode: encryption.mode,
764
+ algorithm: encryption.algorithm,
765
+ customerKeyDigest: customerKeyDigest(customerKeyBytes),
766
+ customerKeyMd5: normalizedCustomerKeyMd5
767
+ };
768
+ }
769
+ if (encryption.mode === require_encryption.EncryptionMode.SseB2) {
770
+ if (hasCustomerEncryptionFields(runtimeEncryption)) return encryptionValidationError("SSE-B2 settings must not include SSE-C customer keys");
771
+ if (encryption.algorithm !== require_encryption.EncryptionAlgorithm.Aes256) return encryptionValidationError("SSE-B2 algorithm must be AES256");
772
+ return {
773
+ mode: encryption.mode,
774
+ algorithm: encryption.algorithm
775
+ };
776
+ }
777
+ if (encryption.mode === require_encryption.EncryptionMode.None) {
778
+ if (hasCustomerEncryptionFields(runtimeEncryption)) return encryptionValidationError("No-encryption settings must not include SSE-C customer keys");
779
+ return { mode: require_encryption.EncryptionMode.None };
780
+ }
781
+ return encryptionValidationError(`Unsupported server-side encryption mode: ${String(runtimeEncryption["mode"])}`);
782
+ }
783
+ async function uploadServerSideEncryption(headers, fallback) {
784
+ const customerAlgorithm = headers["x-bz-server-side-encryption-customer-algorithm"];
785
+ const customerKey = headers["x-bz-server-side-encryption-customer-key"];
786
+ const customerKeyMd5 = headers["x-bz-server-side-encryption-customer-key-md5"];
787
+ const managedEncryption = headers["x-bz-server-side-encryption"];
788
+ if (hasCustomerEncryptionHeaders(headers)) {
789
+ if (managedEncryption !== void 0) return encryptionValidationError("SSE-B2 settings must not include SSE-C customer keys");
790
+ if (customerAlgorithm === void 0) return encryptionValidationError("SSE-C customer algorithm is required");
791
+ if (customerAlgorithm !== require_encryption.EncryptionAlgorithm.Aes256) return encryptionValidationError("SSE-C customer algorithm must be AES256");
792
+ if (!customerKey || !customerKeyMd5) return encryptionValidationError("SSE-C customer key and MD5 are required");
793
+ return await storedServerSideEncryption({
794
+ mode: require_encryption.EncryptionMode.SseC,
795
+ algorithm: require_encryption.EncryptionAlgorithm.Aes256,
796
+ customerKey,
797
+ customerKeyMd5
798
+ });
799
+ }
800
+ if (managedEncryption !== void 0) {
801
+ if (managedEncryption !== require_encryption.EncryptionAlgorithm.Aes256) return encryptionValidationError("SSE-B2 algorithm must be AES256");
802
+ return {
803
+ mode: require_encryption.EncryptionMode.SseB2,
804
+ algorithm: require_encryption.EncryptionAlgorithm.Aes256
805
+ };
806
+ }
807
+ return await storedServerSideEncryption(fallback);
808
+ }
809
+ function noBucketDefaultRetention() {
810
+ return {
811
+ mode: null,
812
+ period: null
181
813
  };
182
- return fallback;
814
+ }
815
+ function bucketDefaultRetentionForResponse(policy) {
816
+ if (policy === void 0 || policy.mode === require_bucket.BucketRetentionMode.None) return noBucketDefaultRetention();
817
+ return policy;
818
+ }
819
+ function bucketDefaultRetention(info) {
820
+ return info.fileLockConfiguration.value?.defaultRetention;
183
821
  }
184
822
  function defaultFileRetention(policy, uploadTimestamp) {
823
+ if (policy === void 0) return null;
824
+ if (policy.mode === null) return null;
185
825
  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;
826
+ const retainUntilTimestamp = uploadTimestamp + (policy.period.unit === "days" ? policy.period.duration : policy.period.duration * 365) * MS_PER_DAY;
827
+ if (!isValidRetentionTimestamp(retainUntilTimestamp, uploadTimestamp)) return null;
187
828
  return {
188
829
  mode: policy.mode,
189
- retainUntilTimestamp: uploadTimestamp + days * 24 * 60 * 60 * 1e3
830
+ retainUntilTimestamp
190
831
  };
191
832
  }
833
+ function isRetentionMode(value) {
834
+ return value === require_lock.RetentionMode.Compliance || value === require_lock.RetentionMode.Governance;
835
+ }
836
+ function parseFileRetentionValue(value, now) {
837
+ if (typeof value !== "object" || value === null) return null;
838
+ const candidate = value;
839
+ const mode = candidate["mode"];
840
+ const retainUntilTimestamp = candidate["retainUntilTimestamp"];
841
+ if (mode === null && retainUntilTimestamp === null) return {
842
+ mode,
843
+ retainUntilTimestamp
844
+ };
845
+ if (isRetentionMode(mode) && typeof retainUntilTimestamp === "number") {
846
+ if (!isValidRetentionTimestamp(retainUntilTimestamp, now)) return null;
847
+ return {
848
+ mode,
849
+ retainUntilTimestamp
850
+ };
851
+ }
852
+ return null;
853
+ }
854
+ function isRetentionWeakened(current, next) {
855
+ if (current === null || current === void 0) return false;
856
+ if (current.mode === null) return false;
857
+ if (next.mode === null) return true;
858
+ if (current.mode === require_lock.RetentionMode.Compliance && next.mode !== require_lock.RetentionMode.Compliance) return true;
859
+ if (current.retainUntilTimestamp === null) return next.mode !== current.mode;
860
+ if (next.retainUntilTimestamp === null) return true;
861
+ return next.retainUntilTimestamp < current.retainUntilTimestamp;
862
+ }
863
+ function requiresGovernanceBypass(current, next) {
864
+ if (current?.mode !== require_lock.RetentionMode.Governance) return false;
865
+ if (next.mode === require_lock.RetentionMode.Compliance) return true;
866
+ return isRetentionWeakened(current, next);
867
+ }
192
868
  /**
193
869
  * In-memory B2 simulator for testing. Implements the B2 native API at the
194
870
  * request/response level without any network I/O. Supports 25+ operations
@@ -204,6 +880,15 @@ function defaultFileRetention(policy, uploadTimestamp) {
204
880
  * })
205
881
  * await client.authorize()
206
882
  * ```
883
+ *
884
+ * `test-key-id:test-key` is the documented implicit full-access
885
+ * storage credential. `master-key-id:master-key` is also accepted for
886
+ * tests that share storage and Partner simulator credentials. In the
887
+ * default permissive mode, other well-formed Basic credentials whose
888
+ * applicationKeyId was not minted by `b2_create_key` receive the same
889
+ * implicit master grant for backward compatibility. A simulator-created
890
+ * key always authorizes with its own capabilities, bucket restrictions,
891
+ * name prefix, and expiration.
207
892
  */
208
893
  var B2Simulator = class {
209
894
  buckets = /* @__PURE__ */ new Map();
@@ -220,6 +905,8 @@ var B2Simulator = class {
220
905
  onHookError;
221
906
  strictAuth;
222
907
  authTokenTtlMs;
908
+ customUploadTimestampsEnabled;
909
+ partner;
223
910
  /**
224
911
  * Issued auth tokens with their associated grant scope + expiry. In
225
912
  * permissive mode (`strictAuth: false`) this is still populated by
@@ -235,6 +922,8 @@ var B2Simulator = class {
235
922
  * state for tests.
236
923
  */
237
924
  uploadTokens = /* @__PURE__ */ new Map();
925
+ downloadAuthorizationTokens = /* @__PURE__ */ new Map();
926
+ downloadAuthorizationExpiryHeap = [];
238
927
  /**
239
928
  * Virtual-clock offset applied to `Date.now()` for expiry checks.
240
929
  * Defaults to 0. Tests advance via {@link advanceTime} to fast-forward
@@ -278,7 +967,23 @@ var B2Simulator = class {
278
967
  if (options.onReplicate !== void 0) this.onReplicate = options.onReplicate;
279
968
  if (options.onHookError !== void 0) this.onHookError = options.onHookError;
280
969
  this.strictAuth = options.strictAuth ?? false;
281
- this.authTokenTtlMs = options.authTokenTtlMs ?? 1440 * 60 * 1e3;
970
+ this.customUploadTimestampsEnabled = options.customUploadTimestampsEnabled ?? false;
971
+ this.authTokenTtlMs = options.authTokenTtlMs ?? 864e5;
972
+ this.partner = new require_partner.PartnerSimulator({
973
+ accountId: this.accountId,
974
+ authTokenTtlMs: this.authTokenTtlMs,
975
+ minimumPartSize: this.minimumPartSize,
976
+ recommendedPartSize: this.recommendedPartSize,
977
+ canAuthorize: (authzHeader) => this.canAuthorizePartner(authzHeader),
978
+ createBucket: (req) => this.createBucket(req),
979
+ error: (status, code, message) => this.error(status, code, message),
980
+ genId: (prefix) => this.genId(prefix),
981
+ monotonicTimestamp: () => this.monotonicTimestamp(),
982
+ now: () => this.now(),
983
+ storeKey: (key) => {
984
+ this.keys.set(key.applicationKeyId, key);
985
+ }
986
+ }, options);
282
987
  }
283
988
  /**
284
989
  * Advance the simulator's virtual clock by `ms` milliseconds. Used in
@@ -336,6 +1041,9 @@ var B2Simulator = class {
336
1041
  now() {
337
1042
  return Date.now() + this.clockOffsetMs;
338
1043
  }
1044
+ isStoredKeyExpired(key) {
1045
+ return key.expirationTimestamp !== null && key.expirationTimestamp <= this.now();
1046
+ }
339
1047
  pruneExpiredUploadTokens(now = this.now()) {
340
1048
  for (const [authorizationToken, token] of this.uploadTokens.entries()) if (now >= token.cleanupAt) this.uploadTokens.delete(authorizationToken);
341
1049
  }
@@ -459,15 +1167,205 @@ var B2Simulator = class {
459
1167
  const token = this.issuedTokens.get(authToken);
460
1168
  if (!token) return this.error(401, "bad_auth_token", "unknown auth token");
461
1169
  if (this.now() > token.expiresAt) return this.error(401, "expired_auth_token", "auth token has expired; reauthorize");
1170
+ if (token.applicationKeyId !== null) {
1171
+ const key = this.keys.get(token.applicationKeyId);
1172
+ if (key === void 0) return this.error(401, "bad_auth_token", "application key has been deleted; reauthorize");
1173
+ if (this.isStoredKeyExpired(key)) return this.error(401, "expired_auth_token", "application key has expired; reauthorize");
1174
+ }
462
1175
  const missing = require_capabilities.missingCapabilitiesFor(endpoint, token.capabilities);
463
1176
  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}`);
1177
+ return this.authorizeScopeGrant(scope, {
1178
+ bucketIds: token.bucketIds,
1179
+ namePrefix: token.namePrefix,
1180
+ bucketScopeRequiredMessage: () => `application key is scoped to buckets ${token.bucketIds?.join(", ")}; bucket scope is required`,
1181
+ bucketMismatchMessage: (bucketId) => `application key is scoped to buckets ${token.bucketIds?.join(", ")}; cannot access ${bucketId}`,
1182
+ prefixMismatchMessage: (fileName) => `application key is scoped to prefix "${token.namePrefix}"; "${fileName}" is outside scope`
1183
+ });
1184
+ }
1185
+ authorizeScopeGrant(scope, grant) {
1186
+ if (grant.bucketIds !== null) {
1187
+ if (scope?.requiresAccountLevelBucketAccess === true) return this.error(403, "unauthorized", grant.bucketScopeRequiredMessage());
1188
+ if ((scope?.bucketIds.length ?? 0) === 0 && scope?.requiresBucketScope === true) return this.error(403, "unauthorized", grant.bucketScopeRequiredMessage());
1189
+ for (const bucketId of scope?.bucketIds ?? []) {
1190
+ if (grant.bucketIds.includes(bucketId)) continue;
1191
+ return this.error(403, "unauthorized", grant.bucketMismatchMessage(bucketId));
1192
+ }
1193
+ }
1194
+ if (grant.namePrefix !== null) for (const fileName of scope?.fileNames ?? []) {
1195
+ if (fileName.startsWith(grant.namePrefix)) continue;
1196
+ return this.error(403, "unauthorized", grant.prefixMismatchMessage(fileName));
1197
+ }
1198
+ return null;
1199
+ }
1200
+ authorizeDownloadRequest(authToken, endpoint, scope, url) {
1201
+ if (authToken === void 0 || authToken === "") return this.error(401, "bad_auth_token", "missing authorization token");
1202
+ const downloadAuth = this.downloadAuthorizationTokens.get(authToken);
1203
+ if (downloadAuth === void 0) {
1204
+ this.purgeExpiredDownloadAuthorizationTokens();
1205
+ return this.authorizeRequest(authToken, endpoint, scope);
1206
+ }
1207
+ if (this.isExpiredDownloadAuthorizationToken(downloadAuth)) {
1208
+ this.downloadAuthorizationTokens.delete(authToken);
1209
+ this.purgeExpiredDownloadAuthorizationTokens();
1210
+ return this.error(401, "expired_auth_token", "download authorization token has expired");
1211
+ }
1212
+ if (endpoint === "b2_download_file_by_id") return this.error(403, "unauthorized", "download authorization tokens cannot be used with b2_download_file_by_id");
1213
+ const overrideError = this.authorizeDownloadResponseOverrides(downloadAuth, url);
1214
+ if (overrideError !== null) return overrideError;
1215
+ return this.authorizeScopeGrant(scope, {
1216
+ bucketIds: [downloadAuth.bucketId],
1217
+ namePrefix: downloadAuth.fileNamePrefix,
1218
+ bucketScopeRequiredMessage: () => `download authorization is scoped to bucket ${downloadAuth.bucketId}; bucket scope is required`,
1219
+ bucketMismatchMessage: (bucketId) => `download authorization is scoped to bucket ${downloadAuth.bucketId}; cannot access ${bucketId}`,
1220
+ prefixMismatchMessage: (fileName) => `download authorization is scoped to prefix "${downloadAuth.fileNamePrefix}"; "${fileName}" is outside scope`
1221
+ });
1222
+ }
1223
+ isExpiredDownloadAuthorizationToken(token, now = this.now()) {
1224
+ return now >= token.expiresAt;
1225
+ }
1226
+ purgeExpiredDownloadAuthorizationTokens() {
1227
+ const now = this.now();
1228
+ for (let i = 0; i < DOWNLOAD_AUTH_PURGE_BATCH_SIZE; i++) {
1229
+ const next = this.downloadAuthorizationExpiryHeap[0];
1230
+ if (next === void 0 || next.expiresAt > now) return;
1231
+ this.popDownloadAuthorizationExpiry();
1232
+ if (this.downloadAuthorizationTokens.get(next.token)?.expiresAt === next.expiresAt) this.downloadAuthorizationTokens.delete(next.token);
1233
+ }
1234
+ }
1235
+ pushDownloadAuthorizationExpiry(entry) {
1236
+ const heap = this.downloadAuthorizationExpiryHeap;
1237
+ heap.push(entry);
1238
+ let idx = heap.length - 1;
1239
+ while (idx > 0) {
1240
+ const parentIdx = Math.floor((idx - 1) / 2);
1241
+ const parent = heap[parentIdx];
1242
+ if (parent.expiresAt <= entry.expiresAt) break;
1243
+ heap[idx] = parent;
1244
+ idx = parentIdx;
1245
+ }
1246
+ heap[idx] = entry;
1247
+ }
1248
+ popDownloadAuthorizationExpiry() {
1249
+ const heap = this.downloadAuthorizationExpiryHeap;
1250
+ const root = heap[0];
1251
+ const last = heap.pop();
1252
+ if (root === void 0 || last === void 0 || heap.length === 0) return root;
1253
+ let idx = 0;
1254
+ while (true) {
1255
+ const leftIdx = idx * 2 + 1;
1256
+ const rightIdx = leftIdx + 1;
1257
+ const left = heap[leftIdx];
1258
+ const right = heap[rightIdx];
1259
+ if (left === void 0) break;
1260
+ const childIdx = right !== void 0 && right.expiresAt < left.expiresAt ? rightIdx : leftIdx;
1261
+ const child = heap[childIdx];
1262
+ if (child.expiresAt >= last.expiresAt) break;
1263
+ heap[idx] = child;
1264
+ idx = childIdx;
467
1265
  }
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`);
1266
+ heap[idx] = last;
1267
+ return root;
1268
+ }
1269
+ authorizeDownloadResponseOverrides(downloadAuth, url) {
1270
+ for (const param of DOWNLOAD_RESPONSE_OVERRIDE_PARAMS) {
1271
+ const expected = downloadAuth.responseHeaderOverrides[param];
1272
+ if (expected === void 0) continue;
1273
+ const actual = url.searchParams.get(param);
1274
+ if (actual === expected) continue;
1275
+ const expectedText = JSON.stringify(expected);
1276
+ const actualText = actual === null ? "absent" : JSON.stringify(actual);
1277
+ return this.error(403, "unauthorized", `download authorization requires ${param}=${expectedText}; got ${actualText}`);
1278
+ }
1279
+ return null;
1280
+ }
1281
+ downloadAuthorizationFromRequest(headers, url, options = {}) {
1282
+ const headerToken = requestHeaderValue(headers, "authorization");
1283
+ if (headerToken !== void 0) return headerToken;
1284
+ if (options.allowQueryAuthorization !== true) return void 0;
1285
+ const queryToken = url.searchParams.get("Authorization") ?? url.searchParams.get("authorization");
1286
+ return queryToken?.startsWith(DOWNLOAD_AUTH_TOKEN_PREFIX) ? queryToken : void 0;
1287
+ }
1288
+ requestHasCapability(authToken, capability) {
1289
+ if (!this.strictAuth) return true;
1290
+ if (authToken === void 0) return false;
1291
+ return this.issuedTokens.get(authToken)?.capabilities.includes(capability) === true;
1292
+ }
1293
+ bucketInfoForResponse(info, authToken) {
1294
+ return {
1295
+ ...info,
1296
+ defaultServerSideEncryption: this.requestHasCapability(authToken, require_auth.Capability.ReadBucketEncryption) ? info.defaultServerSideEncryption : {
1297
+ isClientAuthorizedToRead: false,
1298
+ value: null
1299
+ },
1300
+ replicationConfiguration: filterReadableReplicationConfiguration(info.replicationConfiguration, this.requestHasCapability(authToken, require_auth.Capability.ReadBucketReplications))
1301
+ };
1302
+ }
1303
+ requireFileLockEnabled(bucket) {
1304
+ if (bucket.info.fileLockConfiguration.value?.isFileLockEnabled === true) return null;
1305
+ return this.error(400, "file_lock_not_enabled", "Bucket does not have file lock enabled");
1306
+ }
1307
+ requireGovernanceBypass(req, authToken, message) {
1308
+ if (req.bypassGovernance !== true) return this.error(400, "file_lock_governance_protected", message);
1309
+ if (!this.requestHasCapability(authToken, require_auth.Capability.BypassGovernance)) return this.error(403, "unauthorized", `application key lacks required capabilities: ${require_auth.Capability.BypassGovernance}`);
1310
+ return null;
1311
+ }
1312
+ validateReplicationApplicationKey(applicationKeyId, requiredCapabilities, options) {
1313
+ const key = this.keys.get(applicationKeyId);
1314
+ if (key === void 0) return this.invalidReplicationApplicationKey(options.role);
1315
+ if (missingStoredKeyCapabilities(key, requiredCapabilities).length > 0) return this.invalidReplicationApplicationKey(options.role);
1316
+ if (options.bucketId !== void 0 && !storedKeyAllowsBucket(key, options.bucketId)) return this.invalidReplicationApplicationKey(options.role);
1317
+ return null;
1318
+ }
1319
+ invalidReplicationApplicationKey(role) {
1320
+ return this.error(400, "bad_request", `${role} application key is invalid or not authorized for this replication configuration`);
1321
+ }
1322
+ invalidReplicationDestinationBucket() {
1323
+ return this.error(400, "bad_request", "replication destination bucket is invalid or not configured for this source application key");
1324
+ }
1325
+ validateReplicationDestinationBuckets(sourceApplicationKeyId, rules) {
1326
+ for (const rule of rules) {
1327
+ const destinationBucketId = requestRecord(rule)?.["destinationBucketId"];
1328
+ if (typeof destinationBucketId !== "string") continue;
1329
+ const destinationApplicationKeyId = this.buckets.get(destinationBucketId)?.info.replicationConfiguration.value?.asReplicationDestination?.sourceToDestinationKeyMapping[sourceApplicationKeyId];
1330
+ if (destinationApplicationKeyId === void 0) return this.invalidReplicationDestinationBucket();
1331
+ const destinationKeyError = this.validateReplicationApplicationKey(destinationApplicationKeyId, [require_auth.Capability.WriteFiles], {
1332
+ bucketId: destinationBucketId,
1333
+ role: "replication destination"
1334
+ });
1335
+ if (destinationKeyError !== null) return destinationKeyError;
1336
+ }
1337
+ return null;
1338
+ }
1339
+ validateReplicationApplicationKeys(config, bucketId) {
1340
+ if (!this.strictAuth || config === void 0) return null;
1341
+ const replicationConfiguration = requestRecord(config);
1342
+ const source = requestRecord(replicationConfiguration?.["asReplicationSource"]);
1343
+ if (source !== null) {
1344
+ const sourceApplicationKeyId = source["sourceApplicationKeyId"];
1345
+ if (typeof sourceApplicationKeyId === "string") {
1346
+ const sourceKeyError = this.validateReplicationApplicationKey(sourceApplicationKeyId, [require_auth.Capability.ReadFiles, require_auth.Capability.ListFiles], {
1347
+ bucketId,
1348
+ role: "replication source"
1349
+ });
1350
+ if (sourceKeyError !== null) return sourceKeyError;
1351
+ }
1352
+ const rules = source["replicationRules"];
1353
+ if (typeof sourceApplicationKeyId === "string" && Array.isArray(rules)) {
1354
+ const destinationError = this.validateReplicationDestinationBuckets(sourceApplicationKeyId, rules);
1355
+ if (destinationError !== null) return destinationError;
1356
+ }
1357
+ }
1358
+ const mapping = requestRecord(requestRecord(replicationConfiguration?.["asReplicationDestination"])?.["sourceToDestinationKeyMapping"]);
1359
+ if (mapping !== null) for (const [sourceApplicationKeyId, destinationApplicationKeyId] of Object.entries(mapping)) {
1360
+ const sourceKeyError = this.validateReplicationApplicationKey(sourceApplicationKeyId, [require_auth.Capability.ReadFiles, require_auth.Capability.ListFiles], { role: "replication source" });
1361
+ if (sourceKeyError !== null) return sourceKeyError;
1362
+ if (typeof destinationApplicationKeyId === "string") {
1363
+ const destinationKeyError = this.validateReplicationApplicationKey(destinationApplicationKeyId, [require_auth.Capability.WriteFiles], {
1364
+ bucketId,
1365
+ role: "replication destination"
1366
+ });
1367
+ if (destinationKeyError !== null) return destinationKeyError;
1368
+ }
471
1369
  }
472
1370
  return null;
473
1371
  }
@@ -475,10 +1373,7 @@ var B2Simulator = class {
475
1373
  const directBucketId = requestStringField(body, "bucketId");
476
1374
  const directFileName = requestStringField(body, "fileName");
477
1375
  switch (endpoint) {
478
- case "b2_create_bucket": return {
479
- bucketIds: [],
480
- requiresBucketScope: true
481
- };
1376
+ case "b2_create_bucket": return this.createBucketScope(body);
482
1377
  case "b2_list_buckets": return this.listBucketsScope(body, directBucketId);
483
1378
  case "b2_list_file_names":
484
1379
  case "b2_list_file_versions": return {
@@ -514,9 +1409,23 @@ var B2Simulator = class {
514
1409
  };
515
1410
  case "b2_get_bucket_notification_rules": return this.notificationRulesScope(body, directBucketId, false);
516
1411
  case "b2_set_bucket_notification_rules": return this.notificationRulesScope(body, directBucketId, true);
1412
+ case "b2_update_bucket": return this.updateBucketScope(body, directBucketId);
517
1413
  default: return this.defaultRequestScope(directBucketId, directFileName);
518
1414
  }
519
1415
  }
1416
+ createBucketScope(body) {
1417
+ return {
1418
+ bucketIds: replicationDestinationBucketIds(body),
1419
+ requiresBucketScope: true,
1420
+ requiresAccountLevelBucketAccess: true
1421
+ };
1422
+ }
1423
+ updateBucketScope(body, directBucketId) {
1424
+ return {
1425
+ bucketIds: [...directBucketId === void 0 ? [] : [directBucketId], ...replicationDestinationBucketIds(body)],
1426
+ requiresBucketScope: true
1427
+ };
1428
+ }
520
1429
  listBucketsScope(body, directBucketId) {
521
1430
  if (directBucketId !== void 0) return {
522
1431
  bucketIds: [directBucketId],
@@ -614,7 +1523,10 @@ var B2Simulator = class {
614
1523
  const idParam = options.kind === "file" ? "bucketId" : "fileId";
615
1524
  const scopedId = options.kind === "file" ? options.bucketId : options.fileId;
616
1525
  const uploadId = this.genId(options.kind === "file" ? "upload_file" : "upload_part");
617
- const uploadUrl = new URL(`http://localhost:0/b2api/v3/${endpoint}`);
1526
+ const uploadUrl = new URL(require_url.b2Url("http://localhost:0", {
1527
+ version: "v3",
1528
+ endpoint
1529
+ }));
618
1530
  uploadUrl.searchParams.set(idParam, scopedId);
619
1531
  uploadUrl.searchParams.set("uploadId", uploadId);
620
1532
  const sourceToken = options.sourceAuthToken === void 0 ? void 0 : this.issuedTokens.get(options.sourceAuthToken);
@@ -687,40 +1599,61 @@ var B2Simulator = class {
687
1599
  }));
688
1600
  }
689
1601
  /**
690
- * Look up the application key matching the `Authorization` header on
1602
+ * Resolve the application key matching the `Authorization` header on
691
1603
  * an `authorize_account` request. The header is in the form
692
1604
  * `Basic base64(applicationKeyId:applicationKey)`.
693
1605
  *
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.
1606
+ * The simulator has two implicit full-access storage credentials:
1607
+ * the documented `test-key-id:test-key` pair used by SDK tests and
1608
+ * `master-key-id:master-key`. In default permissive mode, any other
1609
+ * well-formed Basic credential whose applicationKeyId is not a stored
1610
+ * key also receives that implicit master grant for backward
1611
+ * compatibility. A credential whose applicationKeyId matches a stored
1612
+ * key must use the stored secret and must not be expired.
697
1613
  *
698
1614
  * @param authzHeader - Raw HTTP `Authorization` header value.
699
1615
  *
700
- * @returns The matching key's grant scope, or `null` for the master.
1616
+ * @returns The authorizing key's grant scope or the rejection reason.
701
1617
  */
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;
1618
+ findAuthorizationGrant(authzHeader) {
1619
+ const credentials = parseBasicAuthorizationHeader(authzHeader);
1620
+ if (credentials === null) return { kind: "invalid" };
1621
+ const stored = this.keys.get(credentials.applicationKeyId);
1622
+ if (stored !== void 0) {
1623
+ if (!timingSafeStringEqual(stored.applicationKey, credentials.applicationKey)) return { kind: "invalid" };
1624
+ if (this.isStoredKeyExpired(stored)) return { kind: "expired" };
1625
+ return {
1626
+ kind: "ok",
1627
+ grant: {
1628
+ capabilities: stored.capabilities,
1629
+ bucketIds: stored.bucketIds,
1630
+ namePrefix: stored.namePrefix,
1631
+ applicationKeyId: credentials.applicationKeyId,
1632
+ accountId: stored.accountId,
1633
+ expirationTimestamp: stored.expirationTimestamp
1634
+ }
1635
+ };
1636
+ }
1637
+ if (isSimulatorApplicationKeyId(credentials.applicationKeyId)) return { kind: "invalid" };
1638
+ if (isImplicitStorageMasterCredential(credentials) || !this.strictAuth) return {
1639
+ kind: "ok",
1640
+ grant: {
1641
+ capabilities: SIMULATOR_MASTER_CAPABILITIES,
1642
+ bucketIds: null,
1643
+ namePrefix: null,
1644
+ applicationKeyId: null,
1645
+ accountId: this.accountId,
1646
+ expirationTimestamp: null
709
1647
  }
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
1648
  };
1649
+ return { kind: "invalid" };
1650
+ }
1651
+ canAuthorizePartner(authzHeader) {
1652
+ const credentials = parseBasicAuthorizationHeader(authzHeader);
1653
+ if (credentials === null) return false;
1654
+ if (isImplicitPartnerMasterCredential(credentials)) return true;
1655
+ const stored = this.keys.get(credentials.applicationKeyId);
1656
+ return stored !== void 0 && stored.accountId === this.accountId && !this.isStoredKeyExpired(stored) && timingSafeStringEqual(stored.applicationKey, credentials.applicationKey);
724
1657
  }
725
1658
  /**
726
1659
  * Creates an {@link HttpTransport} that routes requests to this simulator.
@@ -812,7 +1745,7 @@ var B2Simulator = class {
812
1745
  }
813
1746
  /**
814
1747
  * Dispatches a JSON API request to the appropriate handler.
815
- * @param _method - The HTTP method (unused).
1748
+ * @param method - The HTTP method.
816
1749
  * @param origin - The request URL origin used for simulator-issued endpoints.
817
1750
  * @param path - The request URL path containing the B2 endpoint name.
818
1751
  * @param headers - The HTTP request headers; consulted by the
@@ -821,38 +1754,48 @@ var B2Simulator = class {
821
1754
  *
822
1755
  * @returns An object with HTTP status and JSON response body.
823
1756
  */
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) {
1757
+ async handleRequest(method, origin, path, headers, body) {
1758
+ const { endpoint, version, unsupportedVersion } = apiPathParts(path);
1759
+ if (unsupportedVersion !== void 0) return this.error(400, "unsupported_api_version", `B2 API version ${unsupportedVersion} is not supported by the simulator; use v4 or v3`);
1760
+ if (!jsonEndpointAllowsMethod(method, endpoint)) return this.error(405, "method_not_allowed", `${endpoint} does not support ${method.toUpperCase()}`);
1761
+ if (this.strictAuth && !this.partner.isEndpoint(endpoint)) {
828
1762
  const authError = this.authorizeRequest(headers["authorization"], endpoint, this.requestScope(endpoint, body));
829
1763
  if (authError !== null) return authError;
830
1764
  }
831
1765
  switch (endpoint) {
832
- case "b2_authorize_account": return this.authorize(headers["authorization"], origin);
833
- case "b2_create_bucket": return this.createBucket(body);
834
- case "b2_list_buckets": return this.listBuckets(body);
835
- case "b2_delete_bucket": return this.deleteBucket(body);
836
- case "b2_update_bucket": return this.updateBucket(body);
1766
+ case "b2_authorize_account":
1767
+ if (version === "v3" && this.partner.isAuthorizeEnabled()) return this.partner.authorize(headers["authorization"], origin);
1768
+ return this.authorize(headers["authorization"], origin);
1769
+ case require_partner_capabilities.PartnerEndpoint.CreateGroupMember: return this.partner.createGroupMember(body, headers["authorization"]);
1770
+ case require_partner_capabilities.PartnerEndpoint.EjectGroupMember: return this.partner.ejectGroupMember(body, headers["authorization"]);
1771
+ case require_partner_capabilities.PartnerEndpoint.ListGroups: return this.partner.listGroups(body, headers["authorization"]);
1772
+ case require_partner_capabilities.PartnerEndpoint.ListGroupMembers: return this.partner.listGroupMembers(body, headers["authorization"]);
1773
+ case require_partner_capabilities.PartnerEndpoint.ReserveTrialCreateAccount: return this.partner.reserveTrialCreateAccount(body, headers["authorization"]);
1774
+ case require_partner_capabilities.PartnerEndpoint.ListComputers: return this.partner.listComputers(body, headers["authorization"]);
1775
+ case require_partner_capabilities.PartnerEndpoint.DeleteComputer: return this.partner.deleteComputer(body, headers["authorization"]);
1776
+ case "b2_create_bucket": return this.createBucket(body, headers["authorization"]);
1777
+ case "b2_list_buckets": return this.listBuckets(body, headers["authorization"]);
1778
+ case "b2_delete_bucket": return this.deleteBucket(body, headers["authorization"]);
1779
+ case "b2_update_bucket": return this.updateBucket(body, headers["authorization"]);
837
1780
  case "b2_get_upload_url": return await this.getUploadUrl(body, headers["authorization"]);
838
1781
  case "b2_list_file_names": return this.listFileNames(body);
839
1782
  case "b2_list_file_versions": return this.listFileVersions(body);
840
1783
  case "b2_get_file_info": return this.getFileInfo(body);
841
1784
  case "b2_hide_file": return this.hideFile(body);
842
- case "b2_delete_file_version": return this.deleteFileVersion(body);
1785
+ case "b2_delete_file_version": return this.deleteFileVersion(body, headers["authorization"]);
843
1786
  case "b2_copy_file": return await this.copyFile(body);
844
- case "b2_start_large_file": return this.startLargeFile(body);
1787
+ case "b2_start_large_file": return await this.startLargeFile(body, headers["authorization"]);
845
1788
  case "b2_get_upload_part_url": return await this.getUploadPartUrl(body, headers["authorization"]);
846
1789
  case "b2_finish_large_file": return this.finishLargeFile(body);
847
1790
  case "b2_cancel_large_file": return this.cancelLargeFile(body);
848
- case "b2_list_unfinished_large_files": return this.listUnfinishedLargeFiles(body);
1791
+ case "b2_list_unfinished_large_files": return this.listUnfinishedLargeFiles(body, headers["authorization"]);
849
1792
  case "b2_list_parts": return this.listParts(body);
850
1793
  case "b2_copy_part": return await this.copyPart(body);
851
1794
  case "b2_get_download_authorization": return this.getDownloadAuthorization(body);
852
- case "b2_create_key": return this.createKey(body, apiVersion);
1795
+ case "b2_create_key": return this.createKey(body, version, headers["authorization"]);
853
1796
  case "b2_list_keys": return this.listKeys(body);
854
1797
  case "b2_delete_key": return this.deleteKey(body);
855
- case "b2_update_file_retention": return this.updateFileRetention(body);
1798
+ case "b2_update_file_retention": return this.updateFileRetention(body, headers["authorization"]);
856
1799
  case "b2_update_file_legal_hold": return this.updateFileLegalHold(body);
857
1800
  case "b2_get_bucket_notification_rules": return this.getBucketNotificationRules(body);
858
1801
  case "b2_set_bucket_notification_rules": return this.setBucketNotificationRules(body);
@@ -882,9 +1825,25 @@ var B2Simulator = class {
882
1825
  const fileName = kind === "part" || headers["x-bz-file-name"] === void 0 ? void 0 : decodeHeaderValue(headers["x-bz-file-name"]);
883
1826
  const authError = await this.validateUploadAuthorization(kind, url, headers["authorization"], fileName);
884
1827
  if (authError !== null) return authError;
1828
+ const contentLengthError = this.validateContentLength(headers, data.byteLength);
1829
+ if (contentLengthError !== null) return contentLengthError;
885
1830
  if (kind === "part") return await this.handleUploadPart(url, headers, data);
886
1831
  return await this.handleUploadFile(url, headers, data);
887
1832
  }
1833
+ validateContentLength(headers, actualLength) {
1834
+ const parsed = parseContentLengthHeader(headers);
1835
+ if (parsed.kind === "absent") return null;
1836
+ if (parsed.kind === "error") return this.error(400, "bad_request", parsed.message);
1837
+ if (parsed.expectedLength !== actualLength) return this.error(400, "bad_request", contentLengthMismatchMessage(parsed.expectedLength, actualLength));
1838
+ return null;
1839
+ }
1840
+ parseCustomUploadTimestamp(value) {
1841
+ if (value === void 0 || value === null) return { timestamp: null };
1842
+ if (!this.customUploadTimestampsEnabled) return this.error(400, "custom_timestamp_not_allowed", "Custom upload timestamps are not enabled for this account.");
1843
+ const timestamp = typeof value === "string" && /^\d+$/.test(value) ? Number(value) : NaN;
1844
+ if (!Number.isSafeInteger(timestamp) || timestamp < 0 || timestamp > this.now()) return this.error(400, "custom_timestamp_invalid", "The request has an invalid custom upload timestamp.");
1845
+ return { timestamp };
1846
+ }
888
1847
  /**
889
1848
  * Handles file download requests (`b2_download_file_by_id`, `/file/` by name).
890
1849
  * Returns the file data along with B2 response headers.
@@ -896,33 +1855,32 @@ var B2Simulator = class {
896
1855
  */
897
1856
  handleDownload(path, headers, method = "GET") {
898
1857
  if (path.includes("b2_download_file_by_id")) {
1858
+ const url = new URL(`http://localhost${path}`);
899
1859
  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) ?? {
1860
+ const authError = this.authorizeDownloadRequest(this.downloadAuthorizationFromRequest(headers, url, { allowQueryAuthorization: false }), "b2_download_file_by_id", this.fileIdScope(url.searchParams.get("fileId") ?? void 0) ?? {
902
1861
  bucketIds: [],
903
1862
  requiresBucketScope: true
904
- });
1863
+ }, url);
905
1864
  if (authError !== null) return this.errorAsDownload(authError);
906
1865
  }
907
- const url = new URL(`http://localhost${path}`);
908
1866
  const fileId = url.searchParams.get("fileId") ?? "";
909
- return this.finalizeDownload(this.downloadById(fileId, headers["range"]), url, method);
1867
+ return this.finalizeDownload(this.downloadById(fileId, headers, requestHeaderValue(headers, "range")), url, method);
910
1868
  }
911
1869
  const fileMatch = path.match(/^([^?]+)/)?.[1]?.match(/\/file\/([^/]+)\/(.+)/);
912
1870
  if (fileMatch) {
913
1871
  const bucketName = decodeURIComponent(fileMatch[1] ?? "");
914
1872
  const fileName = decodeURIComponent(fileMatch[2] ?? "");
1873
+ const url = new URL(`http://localhost${path}`);
915
1874
  if (this.strictAuth) {
916
1875
  const bucket = [...this.buckets.values()].find((b) => b.info.bucketName === bucketName);
917
- const authError = this.authorizeRequest(headers["authorization"], "b2_download_file_by_name", {
1876
+ const authError = this.authorizeDownloadRequest(this.downloadAuthorizationFromRequest(headers, url, { allowQueryAuthorization: true }), "b2_download_file_by_name", {
918
1877
  bucketIds: bucket === void 0 ? [] : [bucket.info.bucketId],
919
1878
  fileNames: [fileName],
920
1879
  requiresBucketScope: true
921
- });
1880
+ }, url);
922
1881
  if (authError !== null) return this.errorAsDownload(authError);
923
1882
  }
924
- const url = new URL(`http://localhost${path}`);
925
- return this.finalizeDownload(this.downloadByName(bucketName, fileName, headers["range"]), url, method);
1883
+ return this.finalizeDownload(this.downloadByName(bucketName, fileName, headers, requestHeaderValue(headers, "range")), url, method);
926
1884
  }
927
1885
  return {
928
1886
  status: 404,
@@ -959,16 +1917,8 @@ var B2Simulator = class {
959
1917
  * @returns The response with overrides applied.
960
1918
  */
961
1919
  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
1920
  const newHeaders = { ...response.headers };
971
- for (const [param, header] of Object.entries(overrideMap)) {
1921
+ for (const [param, header] of Object.entries(DOWNLOAD_RESPONSE_OVERRIDE_HEADERS)) {
972
1922
  const value = url.searchParams.get(param);
973
1923
  if (value !== null) newHeaders[header] = value;
974
1924
  }
@@ -994,6 +1944,10 @@ var B2Simulator = class {
994
1944
  const resolved = await this.resolveUploadSha1(headers["x-bz-content-sha1"], data);
995
1945
  if ("status" in resolved) return resolved;
996
1946
  const { sha1: contentSha1, data: storedData } = resolved;
1947
+ const serverSideEncryption = await uploadServerSideEncryption(headers, bucketDefaultServerSideEncryptionForStorage(bucket.info));
1948
+ if ("status" in serverSideEncryption) return serverSideEncryption;
1949
+ const customUploadTimestamp = this.parseCustomUploadTimestamp(requestHeaderValue(headers, "x-bz-custom-upload-timestamp"));
1950
+ if ("status" in customUploadTimestamp) return customUploadTimestamp;
997
1951
  const fileVersion = this.makeFileVersion({
998
1952
  bucketId,
999
1953
  fileName,
@@ -1002,11 +1956,14 @@ var B2Simulator = class {
1002
1956
  contentSha1,
1003
1957
  fileInfo,
1004
1958
  action: require_file.FileAction.Upload,
1005
- serverSideEncryption: uploadServerSideEncryption(headers, bucket.info.defaultServerSideEncryption)
1959
+ serverSideEncryption,
1960
+ ...this.newReplicationStatusField(bucketId, fileName),
1961
+ ...customUploadTimestamp.timestamp !== null ? { uploadTimestamp: customUploadTimestamp.timestamp } : {}
1006
1962
  });
1007
1963
  const stored = {
1008
1964
  fileVersion,
1009
- data: storedData
1965
+ data: storedData,
1966
+ serverSideEncryption
1010
1967
  };
1011
1968
  const existing = bucket.files.get(fileName);
1012
1969
  if (existing) existing.push(stored);
@@ -1056,18 +2013,39 @@ var B2Simulator = class {
1056
2013
  data
1057
2014
  };
1058
2015
  }
2016
+ async validateUploadPartEncryption(large, headers) {
2017
+ if (large.serverSideEncryption.mode !== require_encryption.EncryptionMode.SseC) {
2018
+ if (!hasCustomerEncryptionHeaders(headers)) return null;
2019
+ return this.error(400, "bad_request", "SSE-C upload part headers require an SSE-C large file");
2020
+ }
2021
+ const supplied = await storedServerSideEncryption({
2022
+ mode: require_encryption.EncryptionMode.SseC,
2023
+ algorithm: headers["x-bz-server-side-encryption-customer-algorithm"],
2024
+ customerKey: headers["x-bz-server-side-encryption-customer-key"],
2025
+ customerKeyMd5: headers["x-bz-server-side-encryption-customer-key-md5"]
2026
+ });
2027
+ if ("status" in supplied) return supplied;
2028
+ if (!sameStoredServerSideEncryption(large.serverSideEncryption, supplied)) return this.error(400, "bad_request", "SSE-C upload parts require matching customer encryption headers");
2029
+ return null;
2030
+ }
1059
2031
  async handleUploadPart(url, headers, data) {
1060
2032
  const fileId = new URL(url).searchParams.get("fileId");
1061
2033
  if (!fileId) return this.error(400, "bad_request", "Missing fileId");
1062
2034
  const large = this.largeFiles.get(fileId);
1063
2035
  if (!large) return this.error(400, "bad_request", "Large file not found");
1064
- const partNumber = Number.parseInt(headers["x-bz-part-number"] ?? "0", 10);
2036
+ const partNumber = this.parsePartNumberHeader(requestHeaderValue(headers, "x-bz-part-number"));
2037
+ if (typeof partNumber !== "number") return partNumber;
2038
+ const encryptionError = await this.validateUploadPartEncryption(large, headers);
2039
+ if (encryptionError !== null) return encryptionError;
1065
2040
  const resolved = await this.resolveUploadSha1(headers["x-bz-content-sha1"], data);
1066
2041
  if ("status" in resolved) return resolved;
1067
2042
  const { sha1, data: partData } = resolved;
2043
+ const uploadTimestamp = this.monotonicTimestamp();
1068
2044
  large.parts.set(partNumber, {
1069
2045
  data: partData,
1070
- sha1
2046
+ sha1,
2047
+ contentMd5: null,
2048
+ uploadTimestamp
1071
2049
  });
1072
2050
  return {
1073
2051
  status: 200,
@@ -1076,21 +2054,32 @@ var B2Simulator = class {
1076
2054
  partNumber,
1077
2055
  contentLength: partData.byteLength,
1078
2056
  contentSha1: sha1,
2057
+ contentMd5: null,
1079
2058
  serverSideEncryption: publicServerSideEncryption(large.serverSideEncryption),
1080
- uploadTimestamp: Date.now()
2059
+ uploadTimestamp
1081
2060
  }
1082
2061
  };
1083
2062
  }
1084
- downloadById(fileId, range) {
2063
+ parsePartNumberHeader(header) {
2064
+ if (header === void 0) return this.error(400, "bad_request", "X-Bz-Part-Number header is required");
2065
+ 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}`);
2066
+ const partNumber = Number(header);
2067
+ return this.validatePartNumber(partNumber) ?? partNumber;
2068
+ }
2069
+ validatePartNumber(partNumber) {
2070
+ if (Number.isInteger(partNumber) && partNumber >= B2_MIN_PART_NUMBER && partNumber <= B2_MAX_PART_NUMBER) return null;
2071
+ return this.error(400, "bad_request", `partNumber must be an integer between ${B2_MIN_PART_NUMBER} and ${B2_MAX_PART_NUMBER}; received ${String(partNumber)}`);
2072
+ }
2073
+ downloadById(fileId, headers, range) {
1085
2074
  const found = this.findFile(fileId);
1086
2075
  if (found === null) return {
1087
2076
  status: 404,
1088
2077
  headers: {},
1089
2078
  data: null
1090
2079
  };
1091
- return this.serveFile(found.stored, range);
2080
+ return this.serveFile(found.stored, headers, range);
1092
2081
  }
1093
- downloadByName(bucketName, fileName, range) {
2082
+ downloadByName(bucketName, fileName, headers, range) {
1094
2083
  for (const bucket of this.buckets.values()) {
1095
2084
  if (bucket.info.bucketName !== bucketName) continue;
1096
2085
  const versions = bucket.files.get(fileName);
@@ -1101,7 +2090,7 @@ var B2Simulator = class {
1101
2090
  headers: {},
1102
2091
  data: null
1103
2092
  };
1104
- return this.serveFile(latest, range);
2093
+ return this.serveFile(latest, headers, range);
1105
2094
  }
1106
2095
  return {
1107
2096
  status: 404,
@@ -1109,7 +2098,41 @@ var B2Simulator = class {
1109
2098
  data: null
1110
2099
  };
1111
2100
  }
1112
- serveFile(stored, range) {
2101
+ validateDownloadEncryption(stored, headers) {
2102
+ const encryption = stored.serverSideEncryption;
2103
+ if (encryption.mode !== require_encryption.EncryptionMode.SseC) return null;
2104
+ const algorithm = headers["x-bz-server-side-encryption-customer-algorithm"];
2105
+ const customerKey = headers["x-bz-server-side-encryption-customer-key"];
2106
+ const customerKeyMd5 = headers["x-bz-server-side-encryption-customer-key-md5"];
2107
+ const customerKeyBytes = customerKey === void 0 ? null : base64ToBytes(customerKey, 32);
2108
+ const normalizedCustomerKeyMd5 = customerKeyMd5 === void 0 ? null : normalizeSizedBase64(customerKeyMd5, 16);
2109
+ 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"));
2110
+ 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"));
2111
+ return null;
2112
+ }
2113
+ async validateSourceServerSideEncryption(stored, encryption) {
2114
+ const expected = stored.serverSideEncryption;
2115
+ if (expected.mode !== require_encryption.EncryptionMode.SseC) return null;
2116
+ if (encryption?.mode !== require_encryption.EncryptionMode.SseC) return this.error(400, "bad_request", "SSE-C source copies require customer encryption");
2117
+ const supplied = await storedServerSideEncryption(encryption);
2118
+ if ("status" in supplied) return supplied;
2119
+ if (!sameStoredServerSideEncryption(expected, supplied)) return this.error(400, "bad_request", "SSE-C source customer encryption does not match");
2120
+ return null;
2121
+ }
2122
+ async resolveCopyDestinationEncryption(destinationBucket, requested) {
2123
+ if (requested !== void 0) return await storedServerSideEncryption(requested);
2124
+ return await storedServerSideEncryption(bucketDefaultServerSideEncryptionForStorage(destinationBucket.info));
2125
+ }
2126
+ async validateCopyPartDestinationEncryption(large, encryption) {
2127
+ 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;
2128
+ const supplied = await storedServerSideEncryption(encryption);
2129
+ if ("status" in supplied) return supplied;
2130
+ if (!sameStoredServerSideEncryption(large.serverSideEncryption, supplied)) return this.error(400, "bad_request", "Destination server-side encryption does not match");
2131
+ return null;
2132
+ }
2133
+ serveFile(stored, requestHeaders, range) {
2134
+ const encryptionError = this.validateDownloadEncryption(stored, requestHeaders);
2135
+ if (encryptionError !== null) return encryptionError;
1113
2136
  const fullData = stored.data;
1114
2137
  let data = fullData;
1115
2138
  let status = 200;
@@ -1140,6 +2163,10 @@ var B2Simulator = class {
1140
2163
  };
1141
2164
  for (const [key, value] of Object.entries(fv.fileInfo)) headers[`X-Bz-Info-${require_encoding.encodeFileName(key)}`] = require_encoding.encodeFileName(value);
1142
2165
  if (!("src_last_modified_millis" in fv.fileInfo)) headers["X-Bz-Info-src_last_modified_millis"] = String(fv.uploadTimestamp);
2166
+ const unauthorizedToRead = [];
2167
+ this.addDownloadEncryptionHeaders(headers, stored.serverSideEncryption, requestHeaders, unauthorizedToRead);
2168
+ this.addDownloadObjectLockHeaders(headers, fv, requestHeaders, unauthorizedToRead);
2169
+ if (unauthorizedToRead.length > 0) headers[require_download.DownloadHeaderName.ClientUnauthorizedToRead] = unauthorizedToRead.join(",");
1143
2170
  if (contentRange !== null) headers["Content-Range"] = contentRange;
1144
2171
  return {
1145
2172
  status,
@@ -1147,39 +2174,58 @@ var B2Simulator = class {
1147
2174
  data
1148
2175
  };
1149
2176
  }
2177
+ addDownloadEncryptionHeaders(headers, encryption, requestHeaders, unauthorizedToRead) {
2178
+ if (encryption.mode === require_encryption.EncryptionMode.None) return;
2179
+ if (!this.requestHasCapability(requestHeaderValue(requestHeaders, "authorization"), require_auth.Capability.ReadBucketEncryption)) {
2180
+ if (encryption.mode === require_encryption.EncryptionMode.SseB2) unauthorizedToRead.push(require_download.DownloadClientUnauthorizedToReadMarker.ServerSideEncryption);
2181
+ else unauthorizedToRead.push(require_download.DownloadClientUnauthorizedToReadMarker.ServerSideEncryptionCustomerAlgorithm, require_download.DownloadClientUnauthorizedToReadMarker.ServerSideEncryptionCustomerKeyMd5);
2182
+ return;
2183
+ }
2184
+ if (encryption.mode === require_encryption.EncryptionMode.SseB2) {
2185
+ headers[require_download.DownloadHeaderName.ServerSideEncryption] = encryption.algorithm;
2186
+ return;
2187
+ }
2188
+ headers[require_download.DownloadHeaderName.ServerSideEncryptionCustomerAlgorithm] = encryption.algorithm;
2189
+ headers[require_download.DownloadHeaderName.ServerSideEncryptionCustomerKeyMd5] = encryption.customerKeyMd5;
2190
+ }
2191
+ addDownloadObjectLockHeaders(headers, fileVersion, requestHeaders, unauthorizedToRead) {
2192
+ const authToken = requestHeaderValue(requestHeaders, "authorization");
2193
+ const retention = fileVersion.fileRetention.value;
2194
+ if (retention !== null && retention.mode !== null && retention.retainUntilTimestamp !== null) {
2195
+ if (this.requestHasCapability(authToken, require_auth.Capability.ReadFileRetentions)) {
2196
+ headers[require_download.DownloadHeaderName.FileRetentionMode] = retention.mode;
2197
+ headers[require_download.DownloadHeaderName.FileRetentionRetainUntilTimestamp] = String(retention.retainUntilTimestamp);
2198
+ } else unauthorizedToRead.push(require_download.DownloadClientUnauthorizedToReadMarker.FileRetentionMode, require_download.DownloadClientUnauthorizedToReadMarker.FileRetentionRetainUntilTimestamp);
2199
+ }
2200
+ const legalHold = fileVersion.legalHold.value;
2201
+ if (legalHold === null) return;
2202
+ if (this.requestHasCapability(authToken, require_auth.Capability.ReadFileLegalHolds)) {
2203
+ headers[require_download.DownloadHeaderName.FileLegalHold] = legalHold;
2204
+ return;
2205
+ }
2206
+ unauthorizedToRead.push(require_download.DownloadClientUnauthorizedToReadMarker.FileLegalHold);
2207
+ }
1150
2208
  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);
2209
+ const resolution = this.findAuthorizationGrant(authzHeader);
2210
+ if (resolution.kind === "invalid") return this.error(401, "unauthorized", "application key is not valid");
2211
+ if (resolution.kind === "expired") return this.error(401, "unauthorized", "application key is expired");
2212
+ const { grant } = resolution;
2213
+ const allowedBuckets = this.allowedBuckets(grant.bucketIds);
2214
+ const legacyBucketId = singleBucketId(grant.bucketIds);
1170
2215
  const legacyBucketName = legacyBucketId === null ? null : this.buckets.get(legacyBucketId)?.info.bucketName ?? null;
1171
2216
  const tokenStr = `sim_auth_token_${this.nextId++}`;
1172
2217
  this.issuedTokens.set(tokenStr, {
1173
- capabilities: keyForAuth?.capabilities ?? capabilities,
1174
- bucketIds: keyForAuth?.bucketIds ?? null,
1175
- namePrefix: keyForAuth?.namePrefix ?? null,
2218
+ accountId: grant.accountId,
2219
+ capabilities: grant.capabilities,
2220
+ bucketIds: grant.bucketIds,
2221
+ namePrefix: grant.namePrefix,
1176
2222
  expiresAt: this.now() + this.authTokenTtlMs,
1177
- applicationKeyId: keyForAuth?.applicationKeyId ?? null
2223
+ applicationKeyId: grant.applicationKeyId
1178
2224
  });
1179
2225
  return {
1180
2226
  status: 200,
1181
2227
  body: {
1182
- accountId: require_ids.accountId(this.accountId),
2228
+ accountId: require_ids.accountId(grant.accountId),
1183
2229
  authorizationToken: tokenStr,
1184
2230
  apiInfo: { storageApi: {
1185
2231
  absoluteMinimumPartSize: this.minimumPartSize,
@@ -1188,34 +2234,35 @@ var B2Simulator = class {
1188
2234
  bucketName: legacyBucketName,
1189
2235
  downloadUrl: origin,
1190
2236
  infoType: "storageApi",
1191
- namePrefix: keyForAuth?.namePrefix ?? null,
2237
+ namePrefix: grant.namePrefix,
1192
2238
  recommendedPartSize: this.recommendedPartSize,
1193
2239
  s3ApiUrl: origin,
1194
2240
  allowed: {
1195
- capabilities: keyForAuth?.capabilities ?? capabilities,
2241
+ capabilities: cloneCapabilities(grant.capabilities),
1196
2242
  buckets: allowedBuckets,
1197
2243
  bucketId: legacyBucketId === null ? null : require_ids.bucketId(legacyBucketId),
1198
2244
  bucketName: legacyBucketName,
1199
- namePrefix: keyForAuth?.namePrefix ?? null
2245
+ namePrefix: grant.namePrefix
1200
2246
  }
1201
2247
  } },
1202
- applicationKeyExpirationTimestamp: null
2248
+ applicationKeyExpirationTimestamp: grant.expirationTimestamp
1203
2249
  }
1204
2250
  };
1205
2251
  }
1206
- createBucket(req) {
2252
+ createBucket(req, authToken) {
1207
2253
  const nameError = require_validation.validateBucketName(req.bucketName);
1208
2254
  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
- }
2255
+ const bucketTypeError = require_validation.validateBucketType(req.bucketType);
2256
+ if (bucketTypeError) return this.error(400, bucketTypeError.code, bucketTypeError.message);
2257
+ const objectLockEnabled = nextObjectLockEnabled(false, req.fileLockEnabled);
2258
+ if (typeof objectLockEnabled !== "boolean") return this.error(400, objectLockEnabled.code, objectLockEnabled.message);
2259
+ const configError = validateBucketConfigurationFields(req, { objectLockEnabled });
2260
+ if (configError) return this.error(400, configError.code, configError.message);
2261
+ const replicationKeyError = this.validateReplicationApplicationKeys(req.replicationConfiguration, void 0);
2262
+ if (replicationKeyError !== null) return replicationKeyError;
1213
2263
  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
2264
  const bid = require_ids.bucketId(this.genId("b2_bucket"));
1215
- const defaultRetention = req.defaultRetention ?? {
1216
- mode: require_bucket.BucketRetentionMode.None,
1217
- period: null
1218
- };
2265
+ const defaultRetention = bucketDefaultRetentionForResponse(req.defaultRetention);
1219
2266
  const info = {
1220
2267
  accountId: require_ids.accountId(req.accountId),
1221
2268
  bucketId: bid,
@@ -1223,22 +2270,18 @@ var B2Simulator = class {
1223
2270
  bucketType: req.bucketType,
1224
2271
  bucketInfo: req.bucketInfo ?? {},
1225
2272
  corsRules: req.corsRules ?? [],
1226
- defaultServerSideEncryption: req.defaultServerSideEncryption ?? { mode: require_encryption.EncryptionMode.None },
2273
+ defaultServerSideEncryption: publicBucketDefaultServerSideEncryption(req.defaultServerSideEncryption),
1227
2274
  fileLockConfiguration: {
1228
2275
  isClientAuthorizedToRead: true,
1229
2276
  value: {
1230
- isFileLockEnabled: req.fileLockEnabled ?? false,
2277
+ isFileLockEnabled: objectLockEnabled,
1231
2278
  defaultRetention
1232
2279
  }
1233
2280
  },
1234
2281
  lifecycleRules: req.lifecycleRules ?? [],
1235
2282
  options: [],
1236
2283
  revision: 1,
1237
- defaultRetention,
1238
- replicationConfiguration: req.replicationConfiguration ?? {
1239
- asReplicationSource: null,
1240
- asReplicationDestination: null
1241
- }
2284
+ replicationConfiguration: wrapReplicationConfiguration(req.replicationConfiguration)
1242
2285
  };
1243
2286
  this.buckets.set(bid, {
1244
2287
  info,
@@ -1246,49 +2289,79 @@ var B2Simulator = class {
1246
2289
  });
1247
2290
  return {
1248
2291
  status: 200,
1249
- body: info
2292
+ body: this.bucketInfoForResponse(info, authToken)
1250
2293
  };
1251
2294
  }
1252
- listBuckets(req) {
2295
+ listBuckets(req, authToken) {
2296
+ const bucketTypesError = require_validation.validateBucketTypes(req.bucketTypes);
2297
+ if (bucketTypesError) return this.error(400, bucketTypesError.code, bucketTypesError.message);
1253
2298
  return {
1254
2299
  status: 200,
1255
- 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)) }
2300
+ 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[0] === "all" || req.bucketTypes.some((bucketType) => bucketType === bucket.bucketType)).map((bucket) => this.bucketInfoForResponse(bucket, authToken)) }
1256
2301
  };
1257
2302
  }
1258
- deleteBucket(req) {
2303
+ deleteBucket(req, authToken) {
1259
2304
  const bucket = this.buckets.get(req.bucketId);
1260
2305
  if (!bucket) return this.error(400, "bad_bucket_id", "Bucket not found");
2306
+ if (this.bucketHasContents(bucket)) return this.error(400, "cannot_delete_non_empty_bucket", "Bucket is not empty and cannot be deleted");
1261
2307
  this.buckets.delete(req.bucketId);
1262
2308
  return {
1263
2309
  status: 200,
1264
- body: bucket.info
2310
+ body: this.bucketInfoForResponse(bucket.info, authToken)
1265
2311
  };
1266
2312
  }
1267
- updateBucket(req) {
2313
+ bucketHasContents(bucket) {
2314
+ for (const versions of bucket.files.values()) if (versions.length > 0) return true;
2315
+ for (const large of this.largeFiles.values()) if (large.bucketId === bucket.info.bucketId) return true;
2316
+ return false;
2317
+ }
2318
+ updateBucket(req, authToken) {
1268
2319
  const bucket = this.buckets.get(req["bucketId"]);
1269
2320
  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);
2321
+ const revisionGuard = req["ifRevisionIs"];
2322
+ if (revisionGuard !== void 0) {
2323
+ if (typeof revisionGuard !== "number" || !Number.isInteger(revisionGuard)) return this.error(400, "bad_request", "ifRevisionIs must be an integer");
2324
+ if (revisionGuard !== bucket.info.revision) return this.error(409, "conflict", "ifRevisionIs test failed");
2325
+ }
2326
+ const objectLockEnabled = nextObjectLockEnabled(bucket.info.fileLockConfiguration.value?.isFileLockEnabled ?? false, req["fileLockEnabled"]);
2327
+ if (typeof objectLockEnabled !== "boolean") return this.error(400, objectLockEnabled.code, objectLockEnabled.message);
2328
+ if (req["bucketType"] !== void 0) {
2329
+ const bucketTypeError = require_validation.validateBucketType(req["bucketType"]);
2330
+ if (bucketTypeError) return this.error(400, bucketTypeError.code, bucketTypeError.message);
1273
2331
  }
2332
+ const configError = validateBucketConfigurationFields({
2333
+ bucketInfo: req["bucketInfo"],
2334
+ corsRules: req["corsRules"],
2335
+ defaultServerSideEncryption: req["defaultServerSideEncryption"],
2336
+ defaultRetention: req["defaultRetention"],
2337
+ lifecycleRules: req["lifecycleRules"],
2338
+ replicationConfiguration: req["replicationConfiguration"]
2339
+ }, { objectLockEnabled });
2340
+ if (configError) return this.error(400, configError.code, configError.message);
2341
+ const replicationKeyError = this.validateReplicationApplicationKeys(req["replicationConfiguration"], req["bucketId"]);
2342
+ if (replicationKeyError !== null) return replicationKeyError;
1274
2343
  const updated = {
1275
2344
  ...bucket.info,
1276
2345
  ...req["bucketType"] !== void 0 ? { bucketType: req["bucketType"] } : {},
1277
2346
  ...req["bucketInfo"] !== void 0 ? { bucketInfo: req["bucketInfo"] } : {},
1278
2347
  ...req["lifecycleRules"] !== void 0 ? { lifecycleRules: req["lifecycleRules"] } : {},
1279
2348
  ...req["corsRules"] !== void 0 ? { corsRules: req["corsRules"] } : {},
1280
- ...req["replicationConfiguration"] !== void 0 ? { replicationConfiguration: req["replicationConfiguration"] } : {},
1281
- ...req["defaultRetention"] !== void 0 ? {
1282
- defaultRetention: req["defaultRetention"],
1283
- fileLockConfiguration: {
1284
- isClientAuthorizedToRead: true,
1285
- value: {
1286
- isFileLockEnabled: bucket.info.fileLockConfiguration.value?.isFileLockEnabled ?? false,
1287
- defaultRetention: req["defaultRetention"]
1288
- }
2349
+ ...req["replicationConfiguration"] !== void 0 ? { replicationConfiguration: wrapReplicationConfiguration(req["replicationConfiguration"]) } : {},
2350
+ ...req["defaultRetention"] !== void 0 ? { fileLockConfiguration: {
2351
+ isClientAuthorizedToRead: true,
2352
+ value: {
2353
+ isFileLockEnabled: objectLockEnabled,
2354
+ defaultRetention: bucketDefaultRetentionForResponse(req["defaultRetention"])
1289
2355
  }
1290
- } : {},
1291
- ...req["defaultServerSideEncryption"] !== void 0 ? { defaultServerSideEncryption: req["defaultServerSideEncryption"] } : {},
2356
+ } } : {},
2357
+ ...req["fileLockEnabled"] !== void 0 && req["defaultRetention"] === void 0 ? { fileLockConfiguration: {
2358
+ isClientAuthorizedToRead: true,
2359
+ value: {
2360
+ isFileLockEnabled: objectLockEnabled,
2361
+ defaultRetention: bucketDefaultRetention(bucket.info) ?? noBucketDefaultRetention()
2362
+ }
2363
+ } } : {},
2364
+ ...req["defaultServerSideEncryption"] !== void 0 ? { defaultServerSideEncryption: publicBucketDefaultServerSideEncryption(req["defaultServerSideEncryption"]) } : {},
1292
2365
  revision: bucket.info.revision + 1
1293
2366
  };
1294
2367
  this.buckets.set(req["bucketId"], {
@@ -1297,7 +2370,7 @@ var B2Simulator = class {
1297
2370
  });
1298
2371
  return {
1299
2372
  status: 200,
1300
- body: updated
2373
+ body: this.bucketInfoForResponse(updated, authToken)
1301
2374
  };
1302
2375
  }
1303
2376
  async getUploadUrl(req, authToken) {
@@ -1323,7 +2396,13 @@ var B2Simulator = class {
1323
2396
  if (countError) return this.error(400, countError.code, countError.message);
1324
2397
  const max = req.maxFileCount ?? 1e3;
1325
2398
  const prefix = req.prefix ?? "";
1326
- let allFiles = [...bucket.files.entries()].filter(([name]) => name.startsWith(prefix)).map(([_, versions]) => versions[versions.length - 1]).filter((v) => v !== void 0).map((v) => v.fileVersion).sort((a, b) => compareB2FileNames(a.fileName, b.fileName));
2399
+ const currentVersions = [...bucket.files.entries()].filter(([name]) => name.startsWith(prefix)).map(([_, versions]) => versions[versions.length - 1]).filter((v) => v !== void 0).map((v) => v.fileVersion).filter((v) => v.action !== require_file.FileAction.Hide).sort((a, b) => compareB2FileNames(a.fileName, b.fileName));
2400
+ let allFiles = this.applyListDelimiter({
2401
+ bucketId: req.bucketId,
2402
+ delimiter: req.delimiter,
2403
+ entries: currentVersions,
2404
+ prefix
2405
+ });
1327
2406
  if (req.startFileName) {
1328
2407
  const start = req.startFileName;
1329
2408
  allFiles = allFiles.filter((f) => f.fileName >= start);
@@ -1343,11 +2422,17 @@ var B2Simulator = class {
1343
2422
  if (countError) return this.error(400, countError.code, countError.message);
1344
2423
  const max = req.maxFileCount ?? 1e3;
1345
2424
  const prefix = req.prefix ?? "";
1346
- const allVersions = [...bucket.files.entries()].filter(([name]) => name.startsWith(prefix)).flatMap(([_, versions]) => versions.map((v) => v.fileVersion)).sort((a, b) => {
2425
+ const versions = [...bucket.files.entries()].filter(([name]) => name.startsWith(prefix)).flatMap(([_, versions]) => versions.map((v) => v.fileVersion)).sort((a, b) => {
1347
2426
  const nameCmp = compareB2FileNames(a.fileName, b.fileName);
1348
2427
  if (nameCmp !== 0) return nameCmp;
1349
2428
  return b.uploadTimestamp - a.uploadTimestamp;
1350
2429
  });
2430
+ const allVersions = this.applyListDelimiter({
2431
+ bucketId: req.bucketId,
2432
+ delimiter: req.delimiter,
2433
+ entries: versions,
2434
+ prefix
2435
+ });
1351
2436
  let startIdx = 0;
1352
2437
  if (req.startFileName !== void 0) {
1353
2438
  const startName = req.startFileName;
@@ -1370,6 +2455,48 @@ var B2Simulator = class {
1370
2455
  }
1371
2456
  };
1372
2457
  }
2458
+ applyListDelimiter(params) {
2459
+ if (params.delimiter === void 0 || params.delimiter === "") return params.entries.map((entry) => this.toListEntry(entry));
2460
+ const grouped = [];
2461
+ const seenFolders = /* @__PURE__ */ new Set();
2462
+ for (const entry of params.entries) {
2463
+ const delimiterIndex = entry.fileName.indexOf(params.delimiter, params.prefix.length);
2464
+ if (delimiterIndex === -1) {
2465
+ grouped.push(this.toListEntry(entry));
2466
+ continue;
2467
+ }
2468
+ const folderName = entry.fileName.slice(0, delimiterIndex + params.delimiter.length);
2469
+ if (!seenFolders.has(folderName)) {
2470
+ seenFolders.add(folderName);
2471
+ grouped.push(this.makeFolderListEntry(params.bucketId, folderName));
2472
+ }
2473
+ }
2474
+ return grouped.sort(compareFileVersionListEntries);
2475
+ }
2476
+ toListEntry(entry) {
2477
+ if (entry.action !== require_file.FileAction.Hide) return entry;
2478
+ const { contentType: _contentType, fileRetention, legalHold, serverSideEncryption, ...listed } = entry;
2479
+ return {
2480
+ ...listed,
2481
+ action: require_file.FileAction.Hide,
2482
+ contentType: require_file.HIDE_MARKER_CONTENT_TYPE
2483
+ };
2484
+ }
2485
+ makeFolderListEntry(bucketId$1, fileName) {
2486
+ return {
2487
+ accountId: require_ids.accountId(this.accountId),
2488
+ action: require_file.FileAction.Folder,
2489
+ bucketId: require_ids.bucketId(bucketId$1),
2490
+ contentLength: 0,
2491
+ contentMd5: null,
2492
+ contentSha1: null,
2493
+ contentType: null,
2494
+ fileId: null,
2495
+ fileInfo: {},
2496
+ fileName,
2497
+ uploadTimestamp: 0
2498
+ };
2499
+ }
1373
2500
  getFileInfo(req) {
1374
2501
  const found = this.findFile(req.fileId);
1375
2502
  if (found === null) return this.error(404, "file_not_present", "File not found");
@@ -1386,7 +2513,7 @@ var B2Simulator = class {
1386
2513
  const fileVersion = this.makeFileVersion({
1387
2514
  bucketId: req.bucketId,
1388
2515
  fileName: req.fileName,
1389
- contentType: "application/octet-stream",
2516
+ contentType: require_file.HIDE_MARKER_CONTENT_TYPE,
1390
2517
  contentLength: 0,
1391
2518
  contentSha1: "none",
1392
2519
  action: require_file.FileAction.Hide
@@ -1394,7 +2521,8 @@ var B2Simulator = class {
1394
2521
  const existing = bucket.files.get(req.fileName);
1395
2522
  const stored = {
1396
2523
  fileVersion,
1397
- data: /* @__PURE__ */ new Uint8Array(0)
2524
+ data: /* @__PURE__ */ new Uint8Array(0),
2525
+ serverSideEncryption: { mode: require_encryption.EncryptionMode.None }
1398
2526
  };
1399
2527
  if (existing) existing.push(stored);
1400
2528
  else bucket.files.set(req.fileName, [stored]);
@@ -1403,16 +2531,19 @@ var B2Simulator = class {
1403
2531
  body: fileVersion
1404
2532
  };
1405
2533
  }
1406
- deleteFileVersion(req) {
2534
+ deleteFileVersion(req, authToken) {
1407
2535
  const found = this.findFile(req.fileId);
1408
2536
  if (found === null || found.stored.fileVersion.fileName !== req.fileName) return this.error(400, "file_not_present", "File version not found");
1409
2537
  const fv = found.stored.fileVersion;
1410
2538
  const retention = fv.fileRetention?.value;
1411
2539
  const legalHold = fv.legalHold?.value;
1412
2540
  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");
2541
+ if (legalHold === require_lock.LegalHoldValue.On) return this.error(400, "file_lock_legal_hold_protected", "File is on legal hold and cannot be deleted");
2542
+ 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()}`);
2543
+ if (retention?.mode === require_lock.RetentionMode.Governance && retention.retainUntilTimestamp !== null && retention.retainUntilTimestamp > now) {
2544
+ const bypassError = this.requireGovernanceBypass(req, authToken, "File is under governance-mode retention; pass bypassGovernance: true to delete");
2545
+ if (bypassError !== null) return bypassError;
2546
+ }
1416
2547
  found.versions.splice(found.index, 1);
1417
2548
  if (found.versions.length === 0) found.bucket.files.delete(req.fileName);
1418
2549
  return {
@@ -1432,6 +2563,10 @@ var B2Simulator = class {
1432
2563
  const destBucketId = req.destinationBucketId ?? found.bucketId;
1433
2564
  const destBucket = this.buckets.get(destBucketId);
1434
2565
  if (!destBucket) return this.error(400, "bad_bucket_id", "Destination bucket not found");
2566
+ const sourceEncryptionError = await this.validateSourceServerSideEncryption(sourceStored, req.sourceServerSideEncryption);
2567
+ if (sourceEncryptionError !== null) return sourceEncryptionError;
2568
+ const destinationEncryption = await this.resolveCopyDestinationEncryption(destBucket, req.destinationServerSideEncryption);
2569
+ if ("status" in destinationEncryption) return destinationEncryption;
1435
2570
  let data = sourceStored.data;
1436
2571
  let contentSha1 = sourceStored.fileVersion.contentSha1 ?? "none";
1437
2572
  if (req.range !== void 0) {
@@ -1464,11 +2599,14 @@ var B2Simulator = class {
1464
2599
  contentLength: data.byteLength,
1465
2600
  contentSha1,
1466
2601
  fileInfo,
1467
- action: require_file.FileAction.Copy
2602
+ action: require_file.FileAction.Copy,
2603
+ serverSideEncryption: destinationEncryption,
2604
+ ...this.newReplicationStatusField(destBucketId, req.fileName)
1468
2605
  });
1469
2606
  const copied = {
1470
2607
  fileVersion,
1471
- data: new Uint8Array(data)
2608
+ data: new Uint8Array(data),
2609
+ serverSideEncryption: destinationEncryption
1472
2610
  };
1473
2611
  const existing = destBucket.files.get(req.fileName);
1474
2612
  if (existing) existing.push(copied);
@@ -1479,7 +2617,7 @@ var B2Simulator = class {
1479
2617
  body: fileVersion
1480
2618
  };
1481
2619
  }
1482
- startLargeFile(req) {
2620
+ async startLargeFile(req, authToken) {
1483
2621
  const bucket = this.buckets.get(req.bucketId);
1484
2622
  if (!bucket) return this.error(400, "bad_bucket_id", "Bucket not found");
1485
2623
  const nameError = require_validation.validateFileName(req.fileName);
@@ -1488,17 +2626,23 @@ var B2Simulator = class {
1488
2626
  const infoError = require_validation.validateFileInfo(req.fileInfo);
1489
2627
  if (infoError) return this.error(400, infoError.code, infoError.message);
1490
2628
  }
2629
+ const customUploadTimestamp = this.parseCustomUploadTimestamp(req.customUploadTimestamp);
2630
+ if ("status" in customUploadTimestamp) return customUploadTimestamp;
1491
2631
  const fid = this.genId("4_z");
1492
- const uploadTimestamp = this.monotonicTimestamp();
2632
+ const uploadTimestamp = customUploadTimestamp.timestamp ?? this.monotonicTimestamp();
2633
+ const serverSideEncryption = await storedServerSideEncryption(req.serverSideEncryption ?? bucketDefaultServerSideEncryptionForStorage(bucket.info));
2634
+ if ("status" in serverSideEncryption) return serverSideEncryption;
2635
+ const replicationStatus = this.replicationStatusForNewFile(req.bucketId, req.fileName);
1493
2636
  const large = {
1494
2637
  fileId: fid,
1495
2638
  bucketId: req.bucketId,
1496
2639
  fileName: req.fileName,
1497
2640
  contentType: req.contentType,
1498
2641
  fileInfo: req.fileInfo ?? {},
1499
- fileRetention: req.fileRetention ?? defaultFileRetention(bucket.info.defaultRetention, uploadTimestamp),
2642
+ fileRetention: req.fileRetention ?? defaultFileRetention(bucketDefaultRetention(bucket.info), uploadTimestamp),
1500
2643
  legalHold: req.legalHold ?? null,
1501
- serverSideEncryption: req.serverSideEncryption ?? bucket.info.defaultServerSideEncryption,
2644
+ ...this.replicationStatusMetadataFor(replicationStatus),
2645
+ serverSideEncryption,
1502
2646
  uploadTimestamp,
1503
2647
  parts: /* @__PURE__ */ new Map()
1504
2648
  };
@@ -1516,14 +2660,9 @@ var B2Simulator = class {
1516
2660
  contentLength: 0,
1517
2661
  contentSha1: "none",
1518
2662
  contentMd5: null,
1519
- fileRetention: {
1520
- isClientAuthorizedToRead: true,
1521
- value: large.fileRetention
1522
- },
1523
- legalHold: {
1524
- isClientAuthorizedToRead: true,
1525
- value: large.legalHold
1526
- },
2663
+ fileRetention: readableFileRetention(large.fileRetention, this.requestHasCapability(authToken, require_auth.Capability.ReadFileRetentions)),
2664
+ legalHold: readableLegalHold(large.legalHold, this.requestHasCapability(authToken, require_auth.Capability.ReadFileLegalHolds)),
2665
+ ...this.replicationStatusMetadataFor(large.replicationStatus),
1527
2666
  serverSideEncryption: publicServerSideEncryption(large.serverSideEncryption),
1528
2667
  uploadTimestamp: large.uploadTimestamp
1529
2668
  }
@@ -1553,17 +2692,11 @@ var B2Simulator = class {
1553
2692
  const bucket = this.buckets.get(large.bucketId);
1554
2693
  if (!bucket) return this.error(400, "bad_bucket_id", "Bucket not found");
1555
2694
  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`);
2695
+ 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}`);
2696
+ 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
2697
  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
- }
2698
+ 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`);
2699
+ 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
2700
  let totalSize = 0;
1568
2701
  for (const [_, part] of sortedParts) totalSize += part.data.byteLength;
1569
2702
  const combined = new Uint8Array(totalSize);
@@ -1582,11 +2715,14 @@ var B2Simulator = class {
1582
2715
  action: require_file.FileAction.Upload,
1583
2716
  fileRetention: large.fileRetention,
1584
2717
  legalHold: large.legalHold,
1585
- serverSideEncryption: large.serverSideEncryption
2718
+ serverSideEncryption: large.serverSideEncryption,
2719
+ uploadTimestamp: large.uploadTimestamp,
2720
+ ...this.replicationStatusMetadataFor(large.replicationStatus)
1586
2721
  });
1587
2722
  const stored = {
1588
2723
  fileVersion,
1589
- data: combined
2724
+ data: combined,
2725
+ serverSideEncryption: large.serverSideEncryption
1590
2726
  };
1591
2727
  const existing = bucket.files.get(large.fileName);
1592
2728
  if (existing) existing.push(stored);
@@ -1612,7 +2748,7 @@ var B2Simulator = class {
1612
2748
  }
1613
2749
  };
1614
2750
  }
1615
- listUnfinishedLargeFiles(req) {
2751
+ listUnfinishedLargeFiles(req, authToken) {
1616
2752
  const countError = require_validation.validateMaxCount(req.maxFileCount, "b2_list_unfinished_large_files");
1617
2753
  if (countError) return this.error(400, countError.code, countError.message);
1618
2754
  const prefix = req.namePrefix ?? "";
@@ -1623,10 +2759,13 @@ var B2Simulator = class {
1623
2759
  const found = candidates.findIndex((f) => f.fileId === req.startFileId);
1624
2760
  startIndex = found >= 0 ? found : 0;
1625
2761
  }
2762
+ const slice = candidates.slice(startIndex, startIndex + max);
2763
+ const canReadFileRetentions = this.requestHasCapability(authToken, require_auth.Capability.ReadFileRetentions);
2764
+ const canReadFileLegalHolds = this.requestHasCapability(authToken, require_auth.Capability.ReadFileLegalHolds);
1626
2765
  return {
1627
2766
  status: 200,
1628
2767
  body: {
1629
- files: candidates.slice(startIndex, startIndex + max).map((f) => ({
2768
+ files: slice.map((f) => ({
1630
2769
  fileId: f.fileId,
1631
2770
  fileName: f.fileName,
1632
2771
  accountId: this.accountId,
@@ -1637,14 +2776,9 @@ var B2Simulator = class {
1637
2776
  contentSha1: "none",
1638
2777
  contentMd5: null,
1639
2778
  fileInfo: f.fileInfo,
1640
- fileRetention: {
1641
- isClientAuthorizedToRead: true,
1642
- value: f.fileRetention
1643
- },
1644
- legalHold: {
1645
- isClientAuthorizedToRead: true,
1646
- value: f.legalHold
1647
- },
2779
+ fileRetention: readableFileRetention(f.fileRetention, canReadFileRetentions),
2780
+ legalHold: readableLegalHold(f.legalHold, canReadFileLegalHolds),
2781
+ ...this.replicationStatusMetadataFor(f.replicationStatus),
1648
2782
  serverSideEncryption: publicServerSideEncryption(f.serverSideEncryption),
1649
2783
  uploadTimestamp: f.uploadTimestamp
1650
2784
  })),
@@ -1664,7 +2798,9 @@ var B2Simulator = class {
1664
2798
  partNumber,
1665
2799
  contentLength: part.data.byteLength,
1666
2800
  contentSha1: part.sha1,
1667
- uploadTimestamp: Date.now()
2801
+ contentMd5: part.contentMd5,
2802
+ serverSideEncryption: publicServerSideEncryption(large.serverSideEncryption),
2803
+ uploadTimestamp: part.uploadTimestamp
1668
2804
  }));
1669
2805
  return {
1670
2806
  status: 200,
@@ -1677,22 +2813,29 @@ var B2Simulator = class {
1677
2813
  async copyPart(req) {
1678
2814
  const large = this.largeFiles.get(req.largeFileId);
1679
2815
  if (!large) return this.error(400, "bad_request", "Large file not found");
2816
+ const partNumberError = this.validatePartNumber(req.partNumber);
2817
+ if (partNumberError !== null) return partNumberError;
1680
2818
  const found = this.findFile(req.sourceFileId);
1681
2819
  if (found === null) return this.error(404, "file_not_present", "Source file not found");
1682
2820
  const sourceStored = found.stored;
2821
+ const sourceEncryptionError = await this.validateSourceServerSideEncryption(sourceStored, req.sourceServerSideEncryption);
2822
+ if (sourceEncryptionError !== null) return sourceEncryptionError;
2823
+ const destinationEncryptionError = await this.validateCopyPartDestinationEncryption(large, req.destinationServerSideEncryption);
2824
+ if (destinationEncryptionError !== null) return destinationEncryptionError;
1683
2825
  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
- }
2826
+ if (req.range !== void 0) {
2827
+ const parsed = parseRangeHeader(req.range, sourceStored.data.byteLength);
2828
+ if (parsed.kind === "malformed") return this.error(400, "bad_request", `Malformed copy range: ${req.range}`);
2829
+ if (parsed.kind === "unsatisfiable") return this.error(416, "range_not_satisfiable", `Unsatisfiable copy range: ${req.range}`);
2830
+ partData = sourceStored.data.subarray(parsed.start, parsed.end + 1);
1691
2831
  }
1692
2832
  const sha1 = await require_hash.sha1Hex(partData);
2833
+ const uploadTimestamp = this.monotonicTimestamp();
1693
2834
  large.parts.set(req.partNumber, {
1694
2835
  data: new Uint8Array(partData),
1695
- sha1
2836
+ sha1,
2837
+ contentMd5: null,
2838
+ uploadTimestamp
1696
2839
  });
1697
2840
  return {
1698
2841
  status: 200,
@@ -1700,39 +2843,80 @@ var B2Simulator = class {
1700
2843
  fileId: req.largeFileId,
1701
2844
  partNumber: req.partNumber,
1702
2845
  contentLength: partData.byteLength,
1703
- contentSha1: sha1
2846
+ contentSha1: sha1,
2847
+ contentMd5: null,
2848
+ serverSideEncryption: publicServerSideEncryption(large.serverSideEncryption),
2849
+ uploadTimestamp
1704
2850
  }
1705
2851
  };
1706
2852
  }
1707
2853
  getDownloadAuthorization(req) {
2854
+ this.purgeExpiredDownloadAuthorizationTokens();
1708
2855
  if (!this.buckets.has(req.bucketId)) return this.error(400, "bad_bucket_id", "Bucket not found");
2856
+ const prefixError = require_validation.validateDownloadAuthorizationPrefix(req.fileNamePrefix);
2857
+ if (prefixError) return this.error(400, prefixError.code, prefixError.message);
2858
+ const durationError = require_validation.validateDownloadAuthorizationDuration(req.validDurationInSeconds);
2859
+ if (durationError) return this.error(400, durationError.code, durationError.message);
2860
+ const overrideError = validateDownloadResponseOverrides(req);
2861
+ if (overrideError) return this.error(400, overrideError.code, overrideError.message);
2862
+ let authorizationToken = randomDownloadAuthorizationToken();
2863
+ while (this.downloadAuthorizationTokens.has(authorizationToken)) authorizationToken = randomDownloadAuthorizationToken();
2864
+ const expiresAt = this.now() + req.validDurationInSeconds * 1e3;
2865
+ this.downloadAuthorizationTokens.set(authorizationToken, {
2866
+ bucketId: req.bucketId,
2867
+ fileNamePrefix: req.fileNamePrefix,
2868
+ expiresAt,
2869
+ responseHeaderOverrides: pickDownloadResponseOverrides(req)
2870
+ });
2871
+ this.pushDownloadAuthorizationExpiry({
2872
+ token: authorizationToken,
2873
+ expiresAt
2874
+ });
1709
2875
  return {
1710
2876
  status: 200,
1711
2877
  body: {
1712
2878
  bucketId: req.bucketId,
1713
2879
  fileNamePrefix: req.fileNamePrefix,
1714
- authorizationToken: `sim_dl_auth_${this.genId("tok")}`
2880
+ authorizationToken
1715
2881
  }
1716
2882
  };
1717
2883
  }
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);
2884
+ createKeyCapabilitiesOutsideGrant(authToken, requested) {
2885
+ const creator = authToken === void 0 ? void 0 : this.issuedTokens.get(authToken);
2886
+ if (creator === void 0) return this.strictAuth ? requested : [];
2887
+ if (creator.applicationKeyId === null) return [];
2888
+ const granted = new Set(creator.capabilities);
2889
+ return requested.filter((capability) => !granted.has(capability));
2890
+ }
2891
+ createKeyCreatorAccountId(authToken) {
2892
+ return (authToken === void 0 ? void 0 : this.issuedTokens.get(authToken))?.accountId ?? this.accountId;
2893
+ }
2894
+ createKey(req, version, authToken) {
2895
+ if (version === "v4" && hasOwnField(req, "bucketId")) return this.error(400, "bad_request", "bucketId is not accepted by v4 b2_create_key; use bucketIds");
2896
+ 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");
2897
+ const bucketIds = version !== "v4" && req.bucketId !== void 0 ? Object.freeze([req.bucketId]) : normalizeKeyBucketIds(req);
1722
2898
  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");
2899
+ const creatorAccountId = this.createKeyCreatorAccountId(authToken);
2900
+ if (req.accountId !== creatorAccountId) return this.error(400, "bad_request", "accountId must match authorized account");
2901
+ const keyNameError = require_validation.validateCreateKeyName(req.keyName);
2902
+ if (keyNameError) return this.error(400, keyNameError.code, keyNameError.message);
2903
+ const keyName = req.keyName;
2904
+ const capabilitiesError = require_validation.validateCreateKeyCapabilities(req.capabilities);
2905
+ if (capabilitiesError) return this.error(400, capabilitiesError.code, capabilitiesError.message);
2906
+ const capabilities = require_validation.normalizeCreateKeyCapabilities(req.capabilities);
2907
+ const unauthorizedCapabilities = this.createKeyCapabilitiesOutsideGrant(authToken, capabilities);
2908
+ if (unauthorizedCapabilities.length > 0) return this.error(400, "bad_request", `requested capabilities exceed creator grant: ${unauthorizedCapabilities.join(", ")}`);
2909
+ 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
2910
  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
2911
  const stored = {
1728
2912
  applicationKeyId: kid,
1729
- keyName: req.keyName,
1730
- capabilities: req.capabilities,
1731
- accountId: req.accountId,
1732
- applicationKey: appKey,
2913
+ keyName,
2914
+ capabilities,
2915
+ accountId: creatorAccountId,
2916
+ applicationKey: this.genId("sim_secret"),
1733
2917
  bucketIds,
1734
2918
  namePrefix,
1735
- expirationTimestamp: expiration
2919
+ expirationTimestamp: req.validDurationInSeconds !== void 0 ? this.now() + req.validDurationInSeconds * 1e3 : null
1736
2920
  };
1737
2921
  this.keys.set(kid, stored);
1738
2922
  return {
@@ -1741,7 +2925,7 @@ var B2Simulator = class {
1741
2925
  keyName: stored.keyName,
1742
2926
  applicationKeyId: stored.applicationKeyId,
1743
2927
  applicationKey: stored.applicationKey,
1744
- capabilities: stored.capabilities,
2928
+ capabilities: cloneCapabilities(stored.capabilities),
1745
2929
  accountId: stored.accountId,
1746
2930
  expirationTimestamp: stored.expirationTimestamp,
1747
2931
  bucketIds: cloneBucketIds(stored.bucketIds),
@@ -1766,7 +2950,7 @@ var B2Simulator = class {
1766
2950
  keys: allKeys.slice(0, max).map((k) => ({
1767
2951
  keyName: k.keyName,
1768
2952
  applicationKeyId: k.applicationKeyId,
1769
- capabilities: k.capabilities,
2953
+ capabilities: cloneCapabilities(k.capabilities),
1770
2954
  accountId: k.accountId,
1771
2955
  expirationTimestamp: k.expirationTimestamp,
1772
2956
  bucketIds: cloneBucketIds(k.bucketIds),
@@ -1792,7 +2976,7 @@ var B2Simulator = class {
1792
2976
  body: {
1793
2977
  keyName: key.keyName,
1794
2978
  applicationKeyId: key.applicationKeyId,
1795
- capabilities: key.capabilities,
2979
+ capabilities: cloneCapabilities(key.capabilities),
1796
2980
  accountId: key.accountId,
1797
2981
  expirationTimestamp: key.expirationTimestamp,
1798
2982
  bucketIds: cloneBucketIds(key.bucketIds),
@@ -1802,31 +2986,45 @@ var B2Simulator = class {
1802
2986
  }
1803
2987
  };
1804
2988
  }
1805
- updateFileRetention(req) {
2989
+ updateFileRetention(req, authToken) {
1806
2990
  const found = this.findFile(req.fileId);
1807
2991
  if (found === null || found.stored.fileVersion.fileName !== req.fileName) return this.error(404, "file_not_present", "File not found");
2992
+ const fileLockError = this.requireFileLockEnabled(found.bucket);
2993
+ if (fileLockError !== null) return fileLockError;
2994
+ const fileRetention = parseFileRetentionValue(req.fileRetention, Date.now());
2995
+ if (fileRetention === null) return this.error(400, "bad_request", "fileRetention must use a valid mode and retainUntilTimestamp pair");
2996
+ const current = found.stored.fileVersion.fileRetention?.value;
2997
+ 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");
2998
+ if (requiresGovernanceBypass(current, fileRetention)) {
2999
+ const bypassError = this.requireGovernanceBypass(req, authToken, "Governance-mode retention cannot be shortened, removed, or converted without bypassGovernance");
3000
+ if (bypassError !== null) return bypassError;
3001
+ }
1808
3002
  found.versions[found.index] = {
1809
3003
  fileVersion: {
1810
3004
  ...found.stored.fileVersion,
1811
3005
  fileRetention: {
1812
3006
  isClientAuthorizedToRead: true,
1813
- value: req.fileRetention
3007
+ value: fileRetention
1814
3008
  }
1815
3009
  },
1816
- data: found.stored.data
3010
+ data: found.stored.data,
3011
+ serverSideEncryption: found.stored.serverSideEncryption
1817
3012
  };
1818
3013
  return {
1819
3014
  status: 200,
1820
3015
  body: {
1821
3016
  fileName: req.fileName,
1822
3017
  fileId: req.fileId,
1823
- fileRetention: req.fileRetention
3018
+ fileRetention
1824
3019
  }
1825
3020
  };
1826
3021
  }
1827
3022
  updateFileLegalHold(req) {
1828
3023
  const found = this.findFile(req.fileId);
1829
3024
  if (found === null || found.stored.fileVersion.fileName !== req.fileName) return this.error(404, "file_not_present", "File not found");
3025
+ const fileLockError = this.requireFileLockEnabled(found.bucket);
3026
+ if (fileLockError !== null) return fileLockError;
3027
+ 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
3028
  found.versions[found.index] = {
1831
3029
  fileVersion: {
1832
3030
  ...found.stored.fileVersion,
@@ -1835,7 +3033,8 @@ var B2Simulator = class {
1835
3033
  value: req.legalHold
1836
3034
  }
1837
3035
  },
1838
- data: found.stored.data
3036
+ data: found.stored.data,
3037
+ serverSideEncryption: found.stored.serverSideEncryption
1839
3038
  };
1840
3039
  return {
1841
3040
  status: 200,
@@ -1859,6 +3058,8 @@ var B2Simulator = class {
1859
3058
  }
1860
3059
  setBucketNotificationRules(req) {
1861
3060
  if (!this.buckets.has(req.bucketId)) return this.error(400, "bad_bucket_id", "Bucket not found");
3061
+ const rulesError = require_validation.validateNotificationRules(req.eventNotificationRules);
3062
+ if (rulesError) return this.error(400, rulesError.code, rulesError.message);
1862
3063
  this.notificationRules.set(req.bucketId, req.eventNotificationRules);
1863
3064
  return {
1864
3065
  status: 200,
@@ -1916,11 +3117,23 @@ var B2Simulator = class {
1916
3117
  isClientAuthorizedToRead: true,
1917
3118
  value: params.legalHold ?? null
1918
3119
  },
1919
- replicationStatus: null,
3120
+ ...this.replicationStatusMetadataFor(params.replicationStatus),
1920
3121
  serverSideEncryption: publicServerSideEncryption(params.serverSideEncryption ?? { mode: require_encryption.EncryptionMode.None }),
1921
- uploadTimestamp: this.monotonicTimestamp()
3122
+ uploadTimestamp: params.uploadTimestamp ?? this.monotonicTimestamp()
1922
3123
  };
1923
3124
  }
3125
+ newReplicationStatusField(bucketId, fileName) {
3126
+ return this.replicationStatusMetadataFor(this.replicationStatusForNewFile(bucketId, fileName));
3127
+ }
3128
+ replicationStatusMetadataFor(replicationStatus) {
3129
+ return replicationStatus === void 0 ? {} : { replicationStatus };
3130
+ }
3131
+ replicationStatusForNewFile(bucketId, fileName) {
3132
+ return this.matchingEnabledReplicationRules(bucketId, fileName).length > 0 ? "PENDING" : void 0;
3133
+ }
3134
+ matchingEnabledReplicationRules(bucketId, fileName) {
3135
+ return (this.buckets.get(bucketId)?.info.replicationConfiguration.value?.asReplicationSource?.replicationRules)?.filter((rule) => rule.isEnabled && fileName.startsWith(rule.fileNamePrefix)) ?? [];
3136
+ }
1924
3137
  error(status, code, message) {
1925
3138
  return {
1926
3139
  status,
@@ -1948,8 +3161,7 @@ var B2Simulator = class {
1948
3161
  * used to match against `EventNotificationRule.eventTypes` globs.
1949
3162
  */
1950
3163
  firePostUploadHooks(fileVersion, bucketId, eventType) {
1951
- const bucket = this.buckets.get(bucketId);
1952
- if (!bucket) return;
3164
+ if (!this.buckets.get(bucketId)) return;
1953
3165
  if (this.onWebhookDeliver !== void 0) {
1954
3166
  const rules = this.notificationRules.get(bucketId) ?? [];
1955
3167
  for (const rule of rules) {
@@ -1963,18 +3175,13 @@ var B2Simulator = class {
1963
3175
  }));
1964
3176
  }
1965
3177
  }
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
- }
3178
+ if (this.onReplicate !== void 0) for (const rule of this.matchingEnabledReplicationRules(bucketId, fileVersion.fileName)) {
3179
+ const hook = this.onReplicate;
3180
+ this.dispatchHook("replication", () => hook({
3181
+ sourceFileVersion: fileVersion,
3182
+ sourceBucketId: bucketId,
3183
+ destinationBucketId: rule.destinationBucketId
3184
+ }));
1978
3185
  }
1979
3186
  }
1980
3187
  /**
@@ -2058,6 +3265,83 @@ function buildFaultResponse(fault) {
2058
3265
  arrayBuffer: () => Promise.resolve(require_text_codec.utf8Encoder.encode(body).buffer)
2059
3266
  };
2060
3267
  }
3268
+ function rawUrlPathContainsLiteralBackslash(rawUrl) {
3269
+ const schemeEnd = rawUrl.indexOf("://");
3270
+ const authorityStart = schemeEnd === -1 ? 0 : schemeEnd + 3;
3271
+ const urlEndCandidates = [rawUrl.indexOf("?", authorityStart), rawUrl.indexOf("#", authorityStart)].filter((index) => index !== -1);
3272
+ const urlEnd = urlEndCandidates.length === 0 ? rawUrl.length : Math.min(...urlEndCandidates);
3273
+ const pathStartCandidates = [rawUrl.indexOf("/", authorityStart), rawUrl.indexOf("\\", authorityStart)].filter((index) => index !== -1 && index < urlEnd);
3274
+ if (pathStartCandidates.length === 0) return false;
3275
+ const pathStart = Math.min(...pathStartCandidates);
3276
+ const pathEndCandidates = [rawUrl.indexOf("?", pathStart), rawUrl.indexOf("#", pathStart)].filter((index) => index !== -1);
3277
+ const pathEnd = pathEndCandidates.length === 0 ? rawUrl.length : Math.min(...pathEndCandidates);
3278
+ return rawUrl.slice(pathStart, pathEnd).includes("\\");
3279
+ }
3280
+ function badRequestJson(message) {
3281
+ return {
3282
+ status: 400,
3283
+ body: {
3284
+ status: 400,
3285
+ code: "bad_request",
3286
+ message
3287
+ }
3288
+ };
3289
+ }
3290
+ function checkedUploadBody(data, expectedLength) {
3291
+ if (expectedLength !== null && data.byteLength !== expectedLength) return { error: badRequestJson(contentLengthMismatchMessage(expectedLength, data.byteLength)) };
3292
+ return { data };
3293
+ }
3294
+ function copyBytes(bytes) {
3295
+ const copy = new Uint8Array(bytes.byteLength);
3296
+ copy.set(bytes);
3297
+ return copy;
3298
+ }
3299
+ function concatenateChunks(chunks, totalLength) {
3300
+ if (chunks.length === 0) return /* @__PURE__ */ new Uint8Array(0);
3301
+ if (chunks.length === 1) return copyBytes(chunks[0]);
3302
+ const data = new Uint8Array(totalLength);
3303
+ let offset = 0;
3304
+ for (const chunk of chunks) {
3305
+ data.set(chunk, offset);
3306
+ offset += chunk.byteLength;
3307
+ }
3308
+ return data;
3309
+ }
3310
+ async function readStreamingUploadBody(stream, expectedLength) {
3311
+ const reader = stream.getReader();
3312
+ const chunks = [];
3313
+ let totalLength = 0;
3314
+ try {
3315
+ while (true) {
3316
+ const { done, value } = await reader.read();
3317
+ if (done) break;
3318
+ if (value === void 0) continue;
3319
+ const chunk = value instanceof Uint8Array ? value : new Uint8Array(value);
3320
+ totalLength += chunk.byteLength;
3321
+ if (expectedLength !== null && totalLength > expectedLength) {
3322
+ await reader.cancel().catch(() => void 0);
3323
+ return { error: badRequestJson(contentLengthMismatchMessage(expectedLength, totalLength)) };
3324
+ }
3325
+ chunks.push(chunk);
3326
+ }
3327
+ } finally {
3328
+ reader.releaseLock();
3329
+ }
3330
+ if (expectedLength !== null && totalLength !== expectedLength) return { error: badRequestJson(contentLengthMismatchMessage(expectedLength, totalLength)) };
3331
+ return { data: concatenateChunks(chunks, totalLength) };
3332
+ }
3333
+ async function readUploadBody(body, expectedLength) {
3334
+ if (body instanceof ReadableStream) return readStreamingUploadBody(body, expectedLength);
3335
+ if (body instanceof ArrayBuffer) return checkedUploadBody(new Uint8Array(body), expectedLength);
3336
+ if (ArrayBuffer.isView(body)) {
3337
+ const view = body;
3338
+ return checkedUploadBody(copyBytes(new Uint8Array(view.buffer, view.byteOffset, view.byteLength)), expectedLength);
3339
+ }
3340
+ if (typeof Blob !== "undefined" && body instanceof Blob) {
3341
+ if (expectedLength !== null && body.size !== expectedLength) return { error: badRequestJson(contentLengthMismatchMessage(expectedLength, body.size)) };
3342
+ }
3343
+ return checkedUploadBody(new Uint8Array(await new Response(body).arrayBuffer()), expectedLength);
3344
+ }
2061
3345
  var SimulatorTransport = class {
2062
3346
  sim;
2063
3347
  constructor(sim) {
@@ -2067,6 +3351,12 @@ var SimulatorTransport = class {
2067
3351
  const url = request.url;
2068
3352
  const fault = this.sim.consumeMatchingFault(url);
2069
3353
  if (fault !== null) return buildFaultResponse(fault);
3354
+ if (rawUrlPathContainsLiteralBackslash(url)) return buildFaultResponse({
3355
+ on: "\\",
3356
+ status: 400,
3357
+ code: "bad_request",
3358
+ message: "URL path must not contain literal backslashes"
3359
+ });
2070
3360
  const headers = {};
2071
3361
  if (request.headers) for (const [k, v] of Object.entries(request.headers)) headers[k.toLowerCase()] = v;
2072
3362
  const parsedUrl = new URL(url);
@@ -2074,10 +3364,15 @@ var SimulatorTransport = class {
2074
3364
  const isUpload = endpoint === "b2_upload_file" || endpoint === "b2_upload_part";
2075
3365
  if (parsedUrl.pathname.includes("b2_download_file_by_id") || parsedUrl.pathname.includes("/file/")) {
2076
3366
  const method = request.method === "HEAD" ? "HEAD" : "GET";
2077
- const result = this.sim.handleDownload(parsedUrl.pathname + parsedUrl.search, headers, method);
3367
+ const result = await this.sim.handleDownload(parsedUrl.pathname + parsedUrl.search, headers, method);
2078
3368
  const data = result.data ?? /* @__PURE__ */ new Uint8Array(0);
2079
3369
  const responseHeaders = new Headers(result.headers);
2080
3370
  responseHeaders.set("Content-Type", result.headers["Content-Type"] ?? "application/octet-stream");
3371
+ let decodedText = null;
3372
+ const text = () => {
3373
+ decodedText ??= require_text_codec.utf8Decoder.decode(data);
3374
+ return decodedText;
3375
+ };
2081
3376
  const body = method === "HEAD" || result.data === null ? null : new ReadableStream({ start(controller) {
2082
3377
  controller.enqueue(data);
2083
3378
  controller.close();
@@ -2086,16 +3381,27 @@ var SimulatorTransport = class {
2086
3381
  status: result.status,
2087
3382
  headers: responseHeaders,
2088
3383
  body,
2089
- json: () => Promise.reject(/* @__PURE__ */ new Error("Download response is not JSON")),
2090
- text: () => Promise.resolve(require_text_codec.utf8Decoder.decode(data)),
3384
+ json: () => {
3385
+ try {
3386
+ return Promise.resolve(JSON.parse(text()));
3387
+ } catch {
3388
+ return Promise.reject(/* @__PURE__ */ new Error("Download response is not JSON"));
3389
+ }
3390
+ },
3391
+ text: () => Promise.resolve(text()),
2091
3392
  arrayBuffer: () => Promise.resolve(data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength))
2092
3393
  };
2093
3394
  }
2094
3395
  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);
3396
+ if (isUpload) {
3397
+ const parsedContentLength = parseContentLengthHeader(headers);
3398
+ if (parsedContentLength.kind === "error") result = badRequestJson(parsedContentLength.message);
3399
+ else {
3400
+ const uploadBody = await readUploadBody(request.body ?? /* @__PURE__ */ new Uint8Array(0), parsedContentLength.kind === "ok" ? parsedContentLength.expectedLength : null);
3401
+ result = "error" in uploadBody ? uploadBody.error : await this.sim.handleUpload(url, headers, uploadBody.data);
3402
+ }
2098
3403
  } else {
3404
+ const requestPath = apiPathParts(parsedUrl.pathname);
2099
3405
  let body = null;
2100
3406
  if (request.body) {
2101
3407
  const text = typeof request.body === "string" ? request.body : await new Response(request.body).text();
@@ -2104,7 +3410,7 @@ var SimulatorTransport = class {
2104
3410
  } catch {
2105
3411
  body = text;
2106
3412
  }
2107
- }
3413
+ } else if (request.method.toUpperCase() === "GET" && isJsonQueryEndpoint(requestPath.endpoint)) body = queryParamsBody(parsedUrl.searchParams, requestPath.endpoint);
2108
3414
  result = await this.sim.handleRequest(request.method, parsedUrl.origin, parsedUrl.pathname, headers, body);
2109
3415
  }
2110
3416
  const responseBody = JSON.stringify(result.body);
@@ -2123,13 +3429,29 @@ var SimulatorTransport = class {
2123
3429
  };
2124
3430
  //#endregion
2125
3431
  exports.B2Simulator = B2Simulator;
3432
+ exports.BUCKET_INFO_KEY_MAX_BYTES = require_bucket.BUCKET_INFO_KEY_MAX_BYTES;
3433
+ exports.BUCKET_INFO_KEY_MIN_BYTES = require_bucket.BUCKET_INFO_KEY_MIN_BYTES;
3434
+ exports.BUCKET_INFO_KEY_PATTERN = require_bucket.BUCKET_INFO_KEY_PATTERN;
2126
3435
  exports.BUCKET_INFO_MAX_KEYS = require_validation.BUCKET_INFO_MAX_KEYS;
3436
+ exports.BUCKET_INFO_RESERVED_PREFIX = require_bucket.BUCKET_INFO_RESERVED_PREFIX;
3437
+ exports.BUCKET_INFO_VALUES_MAX_BYTES = require_bucket.BUCKET_INFO_VALUES_MAX_BYTES;
2127
3438
  exports.BUCKET_INFO_VALUE_MAX = require_validation.BUCKET_INFO_VALUE_MAX;
2128
3439
  exports.BUCKET_NAME_MAX = require_b2_naming.BUCKET_NAME_MAX;
2129
3440
  exports.BUCKET_NAME_MIN = require_b2_naming.BUCKET_NAME_MIN;
3441
+ exports.CORS_MAX_AGE_SECONDS_MAX = require_bucket.CORS_MAX_AGE_SECONDS_MAX;
3442
+ exports.CORS_RULES_MAX_COUNT = require_bucket.CORS_RULES_MAX_COUNT;
3443
+ exports.CORS_RULE_MAX_BYTES = require_bucket.CORS_RULE_MAX_BYTES;
3444
+ exports.CORS_RULE_NAME_MAX_LENGTH = require_bucket.CORS_RULE_NAME_MAX_LENGTH;
3445
+ exports.CORS_RULE_NAME_MIN_LENGTH = require_bucket.CORS_RULE_NAME_MIN_LENGTH;
3446
+ exports.CORS_RULE_NAME_PATTERN = require_bucket.CORS_RULE_NAME_PATTERN;
3447
+ exports.CORS_RULE_NAME_RESERVED_PREFIX = require_bucket.CORS_RULE_NAME_RESERVED_PREFIX;
3448
+ exports.DOWNLOAD_AUTH_DURATION_MAX_SECONDS = require_validation.DOWNLOAD_AUTH_DURATION_MAX_SECONDS;
3449
+ exports.DOWNLOAD_AUTH_DURATION_MIN_SECONDS = require_validation.DOWNLOAD_AUTH_DURATION_MIN_SECONDS;
2130
3450
  exports.FILE_INFO_TOTAL_MAX = require_validation.FILE_INFO_TOTAL_MAX;
2131
3451
  exports.FILE_INFO_VALUE_MAX = require_validation.FILE_INFO_VALUE_MAX;
2132
3452
  exports.FILE_NAME_MAX_BYTES = require_b2_naming.FILE_NAME_MAX_BYTES;
3453
+ exports.KEY_NAME_MAX = require_validation.KEY_NAME_MAX;
3454
+ exports.KEY_NAME_MIN = require_validation.KEY_NAME_MIN;
2133
3455
  exports.LIST_ENDPOINT_CAPS = require_validation.LIST_ENDPOINT_CAPS;
2134
3456
 
2135
3457
  //# sourceMappingURL=index.cjs.map