payload 3.89.0-internal.95c9cba → 3.90.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 (468) hide show
  1. package/dist/admin/forms/Field.d.ts +2 -0
  2. package/dist/admin/forms/Field.d.ts.map +1 -1
  3. package/dist/admin/forms/Field.js.map +1 -1
  4. package/dist/admin/forms/Form.d.ts +2 -0
  5. package/dist/admin/forms/Form.d.ts.map +1 -1
  6. package/dist/admin/forms/Form.js.map +1 -1
  7. package/dist/admin/functions/index.d.ts +3 -1
  8. package/dist/admin/functions/index.d.ts.map +1 -1
  9. package/dist/admin/functions/index.js.map +1 -1
  10. package/dist/admin/views/dashboard.d.ts +1 -1
  11. package/dist/admin/views/dashboard.d.ts.map +1 -1
  12. package/dist/admin/views/dashboard.js.map +1 -1
  13. package/dist/auth/applyUserReadAccess.d.ts +15 -0
  14. package/dist/auth/applyUserReadAccess.d.ts.map +1 -0
  15. package/dist/auth/applyUserReadAccess.js +41 -0
  16. package/dist/auth/applyUserReadAccess.js.map +1 -0
  17. package/dist/auth/baseFields/apiKey/canCreateOrUpdateAPIKey.d.ts +4 -0
  18. package/dist/auth/baseFields/apiKey/canCreateOrUpdateAPIKey.d.ts.map +1 -0
  19. package/dist/auth/baseFields/apiKey/canCreateOrUpdateAPIKey.js +20 -0
  20. package/dist/auth/baseFields/apiKey/canCreateOrUpdateAPIKey.js.map +1 -0
  21. package/dist/auth/baseFields/apiKey/encryptAPIKey.d.ts +4 -0
  22. package/dist/auth/baseFields/apiKey/encryptAPIKey.d.ts.map +1 -0
  23. package/dist/auth/baseFields/apiKey/encryptAPIKey.js +16 -0
  24. package/dist/auth/baseFields/apiKey/encryptAPIKey.js.map +1 -0
  25. package/dist/auth/baseFields/apiKey/index.d.ts +6 -0
  26. package/dist/auth/baseFields/apiKey/index.d.ts.map +1 -0
  27. package/dist/auth/baseFields/apiKey/index.js +112 -0
  28. package/dist/auth/baseFields/apiKey/index.js.map +1 -0
  29. package/dist/auth/baseFields/apiKey/omitAPIKey.d.ts +4 -0
  30. package/dist/auth/baseFields/apiKey/omitAPIKey.d.ts.map +1 -0
  31. package/dist/auth/baseFields/apiKey/omitAPIKey.js +8 -0
  32. package/dist/auth/baseFields/apiKey/omitAPIKey.js.map +1 -0
  33. package/dist/auth/baseFields/apiKey/omitAPIKeyIndex.d.ts +4 -0
  34. package/dist/auth/baseFields/apiKey/omitAPIKeyIndex.d.ts.map +1 -0
  35. package/dist/auth/baseFields/apiKey/omitAPIKeyIndex.js +5 -0
  36. package/dist/auth/baseFields/apiKey/omitAPIKeyIndex.js.map +1 -0
  37. package/dist/auth/baseFields/apiKey/omitEncryptedAPIKey.d.ts +4 -0
  38. package/dist/auth/baseFields/apiKey/omitEncryptedAPIKey.d.ts.map +1 -0
  39. package/dist/auth/baseFields/apiKey/omitEncryptedAPIKey.js +5 -0
  40. package/dist/auth/baseFields/apiKey/omitEncryptedAPIKey.js.map +1 -0
  41. package/dist/auth/baseFields/apiKey/setAPIKeyIndex.d.ts +4 -0
  42. package/dist/auth/baseFields/apiKey/setAPIKeyIndex.d.ts.map +1 -0
  43. package/dist/auth/baseFields/apiKey/setAPIKeyIndex.js +17 -0
  44. package/dist/auth/baseFields/apiKey/setAPIKeyIndex.js.map +1 -0
  45. package/dist/auth/baseFields/auth.d.ts +1 -0
  46. package/dist/auth/baseFields/auth.d.ts.map +1 -1
  47. package/dist/auth/baseFields/auth.js +9 -0
  48. package/dist/auth/baseFields/auth.js.map +1 -1
  49. package/dist/auth/defaultUnlockAccess.d.ts +3 -0
  50. package/dist/auth/defaultUnlockAccess.d.ts.map +1 -0
  51. package/dist/auth/defaultUnlockAccess.js +8 -0
  52. package/dist/auth/defaultUnlockAccess.js.map +1 -0
  53. package/dist/auth/endpoints/index.d.ts +1 -0
  54. package/dist/auth/endpoints/index.d.ts.map +1 -1
  55. package/dist/auth/endpoints/index.js +8 -0
  56. package/dist/auth/endpoints/index.js.map +1 -1
  57. package/dist/auth/endpoints/revealAPIKey.d.ts +7 -0
  58. package/dist/auth/endpoints/revealAPIKey.d.ts.map +1 -0
  59. package/dist/auth/endpoints/revealAPIKey.js +120 -0
  60. package/dist/auth/endpoints/revealAPIKey.js.map +1 -0
  61. package/dist/auth/executeAccess.d.ts +3 -1
  62. package/dist/auth/executeAccess.d.ts.map +1 -1
  63. package/dist/auth/executeAccess.js +2 -1
  64. package/dist/auth/executeAccess.js.map +1 -1
  65. package/dist/auth/getAuthFields.d.ts +1 -1
  66. package/dist/auth/getAuthFields.d.ts.map +1 -1
  67. package/dist/auth/getAuthFields.js +7 -4
  68. package/dist/auth/getAuthFields.js.map +1 -1
  69. package/dist/auth/getFieldsToSign.d.ts.map +1 -1
  70. package/dist/auth/getFieldsToSign.js +17 -10
  71. package/dist/auth/getFieldsToSign.js.map +1 -1
  72. package/dist/auth/getFieldsToSign.spec.js +114 -0
  73. package/dist/auth/getFieldsToSign.spec.js.map +1 -0
  74. package/dist/auth/jwt.d.ts.map +1 -1
  75. package/dist/auth/jwt.js +2 -0
  76. package/dist/auth/jwt.js.map +1 -1
  77. package/dist/auth/jwt.spec.js +24 -0
  78. package/dist/auth/jwt.spec.js.map +1 -0
  79. package/dist/auth/jwtAuth.d.ts +9 -0
  80. package/dist/auth/jwtAuth.d.ts.map +1 -0
  81. package/dist/auth/jwtAuth.js +8 -0
  82. package/dist/auth/jwtAuth.js.map +1 -0
  83. package/dist/auth/operations/auth.d.ts.map +1 -1
  84. package/dist/auth/operations/auth.js +2 -1
  85. package/dist/auth/operations/auth.js.map +1 -1
  86. package/dist/auth/operations/forgotPassword.d.ts.map +1 -1
  87. package/dist/auth/operations/forgotPassword.js +95 -7
  88. package/dist/auth/operations/forgotPassword.js.map +1 -1
  89. package/dist/auth/operations/local/auth.js +3 -1
  90. package/dist/auth/operations/local/auth.js.map +1 -1
  91. package/dist/auth/operations/login.d.ts.map +1 -1
  92. package/dist/auth/operations/login.js +48 -26
  93. package/dist/auth/operations/login.js.map +1 -1
  94. package/dist/auth/operations/refresh.d.ts.map +1 -1
  95. package/dist/auth/operations/refresh.js +32 -9
  96. package/dist/auth/operations/refresh.js.map +1 -1
  97. package/dist/auth/operations/registerFirstUser.d.ts.map +1 -1
  98. package/dist/auth/operations/registerFirstUser.js +3 -4
  99. package/dist/auth/operations/registerFirstUser.js.map +1 -1
  100. package/dist/auth/operations/resetPassword.d.ts.map +1 -1
  101. package/dist/auth/operations/resetPassword.js +31 -22
  102. package/dist/auth/operations/resetPassword.js.map +1 -1
  103. package/dist/auth/operations/unlock.d.ts.map +1 -1
  104. package/dist/auth/operations/unlock.js +1 -0
  105. package/dist/auth/operations/unlock.js.map +1 -1
  106. package/dist/auth/strategies/apiKey.d.ts.map +1 -1
  107. package/dist/auth/strategies/apiKey.js +15 -1
  108. package/dist/auth/strategies/apiKey.js.map +1 -1
  109. package/dist/auth/strategies/jwt.d.ts.map +1 -1
  110. package/dist/auth/strategies/jwt.js +61 -10
  111. package/dist/auth/strategies/jwt.js.map +1 -1
  112. package/dist/auth/strategies/jwt.spec.js +281 -0
  113. package/dist/auth/strategies/jwt.spec.js.map +1 -0
  114. package/dist/auth/strategies/local/authenticate.d.ts +5 -1
  115. package/dist/auth/strategies/local/authenticate.d.ts.map +1 -1
  116. package/dist/auth/strategies/local/authenticate.js +8 -3
  117. package/dist/auth/strategies/local/authenticate.js.map +1 -1
  118. package/dist/auth/strategies/local/authenticate.spec.js +37 -10
  119. package/dist/auth/strategies/local/authenticate.spec.js.map +1 -1
  120. package/dist/auth/strategies/local/generatePasswordSaltHash.d.ts +9 -1
  121. package/dist/auth/strategies/local/generatePasswordSaltHash.d.ts.map +1 -1
  122. package/dist/auth/strategies/local/generatePasswordSaltHash.js +61 -34
  123. package/dist/auth/strategies/local/generatePasswordSaltHash.js.map +1 -1
  124. package/dist/auth/types.d.ts +16 -1
  125. package/dist/auth/types.d.ts.map +1 -1
  126. package/dist/auth/types.js.map +1 -1
  127. package/dist/collections/config/client.d.ts +1 -0
  128. package/dist/collections/config/client.d.ts.map +1 -1
  129. package/dist/collections/config/client.js +3 -0
  130. package/dist/collections/config/client.js.map +1 -1
  131. package/dist/collections/config/defaults.d.ts.map +1 -1
  132. package/dist/collections/config/defaults.js +4 -2
  133. package/dist/collections/config/defaults.js.map +1 -1
  134. package/dist/collections/config/sanitize.d.ts.map +1 -1
  135. package/dist/collections/config/sanitize.js +17 -6
  136. package/dist/collections/config/sanitize.js.map +1 -1
  137. package/dist/collections/endpoints/index.d.ts +1 -0
  138. package/dist/collections/endpoints/index.d.ts.map +1 -1
  139. package/dist/collections/endpoints/index.js +6 -5
  140. package/dist/collections/endpoints/index.js.map +1 -1
  141. package/dist/collections/operations/create.d.ts.map +1 -1
  142. package/dist/collections/operations/create.js +105 -8
  143. package/dist/collections/operations/create.js.map +1 -1
  144. package/dist/collections/operations/deleteByID.js +13 -13
  145. package/dist/collections/operations/deleteByID.js.map +1 -1
  146. package/dist/collections/operations/findDistinct.d.ts.map +1 -1
  147. package/dist/collections/operations/findDistinct.js +96 -18
  148. package/dist/collections/operations/findDistinct.js.map +1 -1
  149. package/dist/collections/operations/restoreVersion.d.ts.map +1 -1
  150. package/dist/collections/operations/restoreVersion.js +47 -13
  151. package/dist/collections/operations/restoreVersion.js.map +1 -1
  152. package/dist/collections/operations/update.d.ts.map +1 -1
  153. package/dist/collections/operations/update.js +123 -23
  154. package/dist/collections/operations/update.js.map +1 -1
  155. package/dist/collections/operations/updateByID.d.ts.map +1 -1
  156. package/dist/collections/operations/updateByID.js +62 -2
  157. package/dist/collections/operations/updateByID.js.map +1 -1
  158. package/dist/collections/operations/utilities/update.d.ts.map +1 -1
  159. package/dist/collections/operations/utilities/update.js +71 -15
  160. package/dist/collections/operations/utilities/update.js.map +1 -1
  161. package/dist/config/orderable/index.d.ts.map +1 -1
  162. package/dist/config/orderable/index.js +67 -14
  163. package/dist/config/orderable/index.js.map +1 -1
  164. package/dist/config/sanitize.d.ts.map +1 -1
  165. package/dist/config/sanitize.js +1 -1
  166. package/dist/config/sanitize.js.map +1 -1
  167. package/dist/config/types.d.ts +13 -1
  168. package/dist/config/types.d.ts.map +1 -1
  169. package/dist/config/types.js.map +1 -1
  170. package/dist/database/getLocalizedPaths.d.ts +2 -1
  171. package/dist/database/getLocalizedPaths.d.ts.map +1 -1
  172. package/dist/database/getLocalizedPaths.js +12 -3
  173. package/dist/database/getLocalizedPaths.js.map +1 -1
  174. package/dist/database/isNestedRelationshipQuery.d.ts +9 -0
  175. package/dist/database/isNestedRelationshipQuery.d.ts.map +1 -0
  176. package/dist/database/isNestedRelationshipQuery.js +14 -0
  177. package/dist/database/isNestedRelationshipQuery.js.map +1 -0
  178. package/dist/database/prefixWherePaths.d.ts +6 -0
  179. package/dist/database/prefixWherePaths.d.ts.map +1 -0
  180. package/dist/database/prefixWherePaths.js +19 -0
  181. package/dist/database/prefixWherePaths.js.map +1 -0
  182. package/dist/database/prefixWherePaths.spec.js +46 -0
  183. package/dist/database/prefixWherePaths.spec.js.map +1 -0
  184. package/dist/database/queryValidation/validateQueryPaths.d.ts +2 -1
  185. package/dist/database/queryValidation/validateQueryPaths.d.ts.map +1 -1
  186. package/dist/database/queryValidation/validateQueryPaths.js +4 -1
  187. package/dist/database/queryValidation/validateQueryPaths.js.map +1 -1
  188. package/dist/database/queryValidation/validateSearchParams.d.ts +2 -1
  189. package/dist/database/queryValidation/validateSearchParams.d.ts.map +1 -1
  190. package/dist/database/queryValidation/validateSearchParams.js +82 -4
  191. package/dist/database/queryValidation/validateSearchParams.js.map +1 -1
  192. package/dist/database/types.d.ts +5 -0
  193. package/dist/database/types.d.ts.map +1 -1
  194. package/dist/database/types.js.map +1 -1
  195. package/dist/email/normalizeSendEmailOptions.js.map +1 -1
  196. package/dist/email/types.d.ts +4 -1
  197. package/dist/email/types.d.ts.map +1 -1
  198. package/dist/email/types.js.map +1 -1
  199. package/dist/exports/internal.d.ts +7 -0
  200. package/dist/exports/internal.d.ts.map +1 -1
  201. package/dist/exports/internal.js +7 -1
  202. package/dist/exports/internal.js.map +1 -1
  203. package/dist/exports/shared.d.ts +3 -0
  204. package/dist/exports/shared.d.ts.map +1 -1
  205. package/dist/exports/shared.js +3 -0
  206. package/dist/exports/shared.js.map +1 -1
  207. package/dist/fields/config/types.d.ts +7 -5
  208. package/dist/fields/config/types.d.ts.map +1 -1
  209. package/dist/fields/config/types.js.map +1 -1
  210. package/dist/fields/hooks/beforeChange/index.d.ts +7 -1
  211. package/dist/fields/hooks/beforeChange/index.d.ts.map +1 -1
  212. package/dist/fields/hooks/beforeChange/index.js +4 -2
  213. package/dist/fields/hooks/beforeChange/index.js.map +1 -1
  214. package/dist/fields/hooks/beforeChange/index.spec.js +206 -0
  215. package/dist/fields/hooks/beforeChange/index.spec.js.map +1 -0
  216. package/dist/fields/hooks/beforeChange/promise.d.ts +2 -1
  217. package/dist/fields/hooks/beforeChange/promise.d.ts.map +1 -1
  218. package/dist/fields/hooks/beforeChange/promise.js +51 -9
  219. package/dist/fields/hooks/beforeChange/promise.js.map +1 -1
  220. package/dist/fields/hooks/beforeChange/traverseFields.d.ts +2 -1
  221. package/dist/fields/hooks/beforeChange/traverseFields.d.ts.map +1 -1
  222. package/dist/fields/hooks/beforeChange/traverseFields.js +3 -2
  223. package/dist/fields/hooks/beforeChange/traverseFields.js.map +1 -1
  224. package/dist/fields/hooks/beforeDuplicate/promise.d.ts.map +1 -1
  225. package/dist/fields/hooks/beforeDuplicate/promise.js +4 -0
  226. package/dist/fields/hooks/beforeDuplicate/promise.js.map +1 -1
  227. package/dist/fields/hooks/beforeValidate/getFallbackValue.d.ts +2 -1
  228. package/dist/fields/hooks/beforeValidate/getFallbackValue.d.ts.map +1 -1
  229. package/dist/fields/hooks/beforeValidate/getFallbackValue.js +2 -2
  230. package/dist/fields/hooks/beforeValidate/getFallbackValue.js.map +1 -1
  231. package/dist/fields/hooks/beforeValidate/index.d.ts +6 -1
  232. package/dist/fields/hooks/beforeValidate/index.d.ts.map +1 -1
  233. package/dist/fields/hooks/beforeValidate/index.js +3 -1
  234. package/dist/fields/hooks/beforeValidate/index.js.map +1 -1
  235. package/dist/fields/hooks/beforeValidate/index.spec.js +90 -0
  236. package/dist/fields/hooks/beforeValidate/index.spec.js.map +1 -0
  237. package/dist/fields/hooks/beforeValidate/promise.d.ts +6 -1
  238. package/dist/fields/hooks/beforeValidate/promise.d.ts.map +1 -1
  239. package/dist/fields/hooks/beforeValidate/promise.js +23 -8
  240. package/dist/fields/hooks/beforeValidate/promise.js.map +1 -1
  241. package/dist/fields/hooks/beforeValidate/traverseFields.d.ts +6 -1
  242. package/dist/fields/hooks/beforeValidate/traverseFields.d.ts.map +1 -1
  243. package/dist/fields/hooks/beforeValidate/traverseFields.js +3 -1
  244. package/dist/fields/hooks/beforeValidate/traverseFields.js.map +1 -1
  245. package/dist/fields/validations.d.ts.map +1 -1
  246. package/dist/fields/validations.js +3 -1
  247. package/dist/fields/validations.js.map +1 -1
  248. package/dist/globals/operations/restoreVersion.d.ts.map +1 -1
  249. package/dist/globals/operations/restoreVersion.js +45 -11
  250. package/dist/globals/operations/restoreVersion.js.map +1 -1
  251. package/dist/globals/operations/update.d.ts.map +1 -1
  252. package/dist/globals/operations/update.js +95 -16
  253. package/dist/globals/operations/update.js.map +1 -1
  254. package/dist/index.bundled.d.ts +146 -48
  255. package/dist/index.d.ts +4 -1
  256. package/dist/index.d.ts.map +1 -1
  257. package/dist/index.js +3 -2
  258. package/dist/index.js.map +1 -1
  259. package/dist/preferences/config.d.ts.map +1 -1
  260. package/dist/preferences/config.js +2 -1
  261. package/dist/preferences/config.js.map +1 -1
  262. package/dist/query-presets/access.d.ts.map +1 -1
  263. package/dist/query-presets/access.js +2 -1
  264. package/dist/query-presets/access.js.map +1 -1
  265. package/dist/queues/config/collection.d.ts.map +1 -1
  266. package/dist/queues/config/collection.js +6 -0
  267. package/dist/queues/config/collection.js.map +1 -1
  268. package/dist/queues/config/types/index.d.ts +4 -1
  269. package/dist/queues/config/types/index.d.ts.map +1 -1
  270. package/dist/queues/config/types/index.js.map +1 -1
  271. package/dist/queues/endpoints/handleSchedules.d.ts.map +1 -1
  272. package/dist/queues/endpoints/handleSchedules.js +2 -1
  273. package/dist/queues/endpoints/handleSchedules.js.map +1 -1
  274. package/dist/queues/endpoints/run.d.ts.map +1 -1
  275. package/dist/queues/endpoints/run.js +2 -1
  276. package/dist/queues/endpoints/run.js.map +1 -1
  277. package/dist/uploads/assertClientUploadAccess.d.ts +7 -0
  278. package/dist/uploads/assertClientUploadAccess.d.ts.map +1 -0
  279. package/dist/uploads/assertClientUploadAccess.js +27 -0
  280. package/dist/uploads/assertClientUploadAccess.js.map +1 -0
  281. package/dist/uploads/assertClientUploadAllowed.d.ts +11 -0
  282. package/dist/uploads/assertClientUploadAllowed.d.ts.map +1 -0
  283. package/dist/uploads/assertClientUploadAllowed.js +46 -0
  284. package/dist/uploads/assertClientUploadAllowed.js.map +1 -0
  285. package/dist/uploads/assertClientUploadAllowed.spec.js +91 -0
  286. package/dist/uploads/assertClientUploadAllowed.spec.js.map +1 -0
  287. package/dist/uploads/checkFileRestrictions.d.ts +4 -1
  288. package/dist/uploads/checkFileRestrictions.d.ts.map +1 -1
  289. package/dist/uploads/checkFileRestrictions.js +66 -26
  290. package/dist/uploads/checkFileRestrictions.js.map +1 -1
  291. package/dist/uploads/clientUploadReceipt.d.ts +23 -0
  292. package/dist/uploads/clientUploadReceipt.d.ts.map +1 -0
  293. package/dist/uploads/clientUploadReceipt.js +55 -0
  294. package/dist/uploads/clientUploadReceipt.js.map +1 -0
  295. package/dist/uploads/deleteAssociatedFiles.d.ts.map +1 -1
  296. package/dist/uploads/deleteAssociatedFiles.js +33 -8
  297. package/dist/uploads/deleteAssociatedFiles.js.map +1 -1
  298. package/dist/uploads/deleteAssociatedFiles.spec.js +124 -0
  299. package/dist/uploads/deleteAssociatedFiles.spec.js.map +1 -0
  300. package/dist/uploads/detectSvgFromXml.d.ts +1 -3
  301. package/dist/uploads/detectSvgFromXml.d.ts.map +1 -1
  302. package/dist/uploads/detectSvgFromXml.js +3 -40
  303. package/dist/uploads/detectSvgFromXml.js.map +1 -1
  304. package/dist/uploads/endpoints/getFile.d.ts.map +1 -1
  305. package/dist/uploads/endpoints/getFile.js +5 -3
  306. package/dist/uploads/endpoints/getFile.js.map +1 -1
  307. package/dist/uploads/fetchAPI-multipart/handlers.d.ts +2 -2
  308. package/dist/uploads/fetchAPI-multipart/handlers.d.ts.map +1 -1
  309. package/dist/uploads/fetchAPI-multipart/handlers.js +18 -5
  310. package/dist/uploads/fetchAPI-multipart/handlers.js.map +1 -1
  311. package/dist/uploads/fetchAPI-multipart/index.d.ts.map +1 -1
  312. package/dist/uploads/fetchAPI-multipart/index.js +24 -2
  313. package/dist/uploads/fetchAPI-multipart/index.js.map +1 -1
  314. package/dist/uploads/fetchAPI-multipart/processMultipart.d.ts +0 -6
  315. package/dist/uploads/fetchAPI-multipart/processMultipart.d.ts.map +1 -1
  316. package/dist/uploads/fetchAPI-multipart/processMultipart.js +146 -126
  317. package/dist/uploads/fetchAPI-multipart/processMultipart.js.map +1 -1
  318. package/dist/uploads/fetchAPI-multipart/processMultipart.spec.js +663 -0
  319. package/dist/uploads/fetchAPI-multipart/processMultipart.spec.js.map +1 -0
  320. package/dist/uploads/generateFileData.d.ts +3 -1
  321. package/dist/uploads/generateFileData.d.ts.map +1 -1
  322. package/dist/uploads/generateFileData.js +29 -16
  323. package/dist/uploads/generateFileData.js.map +1 -1
  324. package/dist/uploads/getBaseFields.d.ts.map +1 -1
  325. package/dist/uploads/getBaseFields.js +3 -1
  326. package/dist/uploads/getBaseFields.js.map +1 -1
  327. package/dist/uploads/getExternalFile.d.ts +1 -1
  328. package/dist/uploads/getExternalFile.d.ts.map +1 -1
  329. package/dist/uploads/getExternalFile.js +60 -15
  330. package/dist/uploads/getExternalFile.js.map +1 -1
  331. package/dist/uploads/getFileFromClientUpload.d.ts +2 -2
  332. package/dist/uploads/getFileFromClientUpload.d.ts.map +1 -1
  333. package/dist/uploads/getFileFromClientUpload.js +4 -4
  334. package/dist/uploads/getFileFromClientUpload.js.map +1 -1
  335. package/dist/uploads/getFileFromClientUpload.spec.js +62 -0
  336. package/dist/uploads/getFileFromClientUpload.spec.js.map +1 -1
  337. package/dist/uploads/getFileTypeFallback.d.ts.map +1 -1
  338. package/dist/uploads/getFileTypeFallback.js +3 -1
  339. package/dist/uploads/getFileTypeFallback.js.map +1 -1
  340. package/dist/uploads/getFileTypeIdentity.d.ts +19 -0
  341. package/dist/uploads/getFileTypeIdentity.d.ts.map +1 -0
  342. package/dist/uploads/getFileTypeIdentity.js +34 -0
  343. package/dist/uploads/getFileTypeIdentity.js.map +1 -0
  344. package/dist/uploads/getFileTypeIdentity.spec.js +43 -0
  345. package/dist/uploads/getFileTypeIdentity.spec.js.map +1 -0
  346. package/dist/uploads/isProcessableImage.d.ts +2 -0
  347. package/dist/uploads/isProcessableImage.d.ts.map +1 -0
  348. package/dist/uploads/isProcessableImage.js +7 -0
  349. package/dist/uploads/isProcessableImage.js.map +1 -0
  350. package/dist/uploads/safeFetch.d.ts.map +1 -1
  351. package/dist/uploads/safeFetch.js +3 -2
  352. package/dist/uploads/safeFetch.js.map +1 -1
  353. package/dist/uploads/sanitizeUploadData.d.ts +26 -0
  354. package/dist/uploads/sanitizeUploadData.d.ts.map +1 -0
  355. package/dist/uploads/sanitizeUploadData.js +159 -0
  356. package/dist/uploads/sanitizeUploadData.js.map +1 -0
  357. package/dist/uploads/sanitizeUploadData.spec.js +255 -0
  358. package/dist/uploads/sanitizeUploadData.spec.js.map +1 -0
  359. package/dist/uploads/types.d.ts +11 -3
  360. package/dist/uploads/types.d.ts.map +1 -1
  361. package/dist/uploads/types.js.map +1 -1
  362. package/dist/uploads/unlinkClientUploadTempFile.d.ts +20 -0
  363. package/dist/uploads/unlinkClientUploadTempFile.d.ts.map +1 -0
  364. package/dist/uploads/unlinkClientUploadTempFile.js +30 -0
  365. package/dist/uploads/unlinkClientUploadTempFile.js.map +1 -0
  366. package/dist/uploads/unlinkTempFiles.d.ts.map +1 -1
  367. package/dist/uploads/unlinkTempFiles.js +7 -9
  368. package/dist/uploads/unlinkTempFiles.js.map +1 -1
  369. package/dist/uploads/unlinkTempFiles.spec.js +61 -1
  370. package/dist/uploads/unlinkTempFiles.spec.js.map +1 -1
  371. package/dist/uploads/uploadContentSecurityPolicy.d.ts +2 -0
  372. package/dist/uploads/uploadContentSecurityPolicy.d.ts.map +1 -0
  373. package/dist/uploads/uploadContentSecurityPolicy.js +3 -0
  374. package/dist/uploads/uploadContentSecurityPolicy.js.map +1 -0
  375. package/dist/uploads/validateISOBaseMediaFile.d.ts +4 -0
  376. package/dist/uploads/validateISOBaseMediaFile.d.ts.map +1 -0
  377. package/dist/uploads/validateISOBaseMediaFile.js +91 -0
  378. package/dist/uploads/validateISOBaseMediaFile.js.map +1 -0
  379. package/dist/uploads/validateISOBaseMediaFile.spec.js +130 -0
  380. package/dist/uploads/validateISOBaseMediaFile.spec.js.map +1 -0
  381. package/dist/uploads/validateSvg.d.ts +10 -4
  382. package/dist/uploads/validateSvg.d.ts.map +1 -1
  383. package/dist/uploads/validateSvg.js +280 -40
  384. package/dist/uploads/validateSvg.js.map +1 -1
  385. package/dist/uploads/validateSvg.spec.js +123 -0
  386. package/dist/uploads/validateSvg.spec.js.map +1 -0
  387. package/dist/uploads/validateUploadFilename.d.ts +3 -0
  388. package/dist/uploads/validateUploadFilename.d.ts.map +1 -0
  389. package/dist/uploads/validateUploadFilename.js +13 -0
  390. package/dist/uploads/validateUploadFilename.js.map +1 -0
  391. package/dist/uploads/validateUploadFilename.spec.js +24 -0
  392. package/dist/uploads/validateUploadFilename.spec.js.map +1 -0
  393. package/dist/utilities/addDataAndFileToRequest.d.ts.map +1 -1
  394. package/dist/utilities/addDataAndFileToRequest.js +28 -1
  395. package/dist/utilities/addDataAndFileToRequest.js.map +1 -1
  396. package/dist/utilities/addDataAndFileToRequest.spec.js +158 -0
  397. package/dist/utilities/addDataAndFileToRequest.spec.js.map +1 -1
  398. package/dist/utilities/createPayloadRequest.d.ts.map +1 -1
  399. package/dist/utilities/createPayloadRequest.js +2 -1
  400. package/dist/utilities/createPayloadRequest.js.map +1 -1
  401. package/dist/utilities/deepCopyObject.d.ts.map +1 -1
  402. package/dist/utilities/deepCopyObject.js +110 -19
  403. package/dist/utilities/deepCopyObject.js.map +1 -1
  404. package/dist/utilities/deepCopyObject.spec.js +216 -0
  405. package/dist/utilities/deepCopyObject.spec.js.map +1 -0
  406. package/dist/utilities/fieldPath.d.ts +13 -0
  407. package/dist/utilities/fieldPath.d.ts.map +1 -0
  408. package/dist/utilities/fieldPath.js +51 -0
  409. package/dist/utilities/fieldPath.js.map +1 -0
  410. package/dist/utilities/fieldPath.spec.js +155 -0
  411. package/dist/utilities/fieldPath.spec.js.map +1 -0
  412. package/dist/utilities/getEntityPermissions/getEntityPermissions.d.ts.map +1 -1
  413. package/dist/utilities/getEntityPermissions/getEntityPermissions.js +2 -0
  414. package/dist/utilities/getEntityPermissions/getEntityPermissions.js.map +1 -1
  415. package/dist/utilities/getObjectDotNotation.d.ts.map +1 -1
  416. package/dist/utilities/getObjectDotNotation.js +7 -1
  417. package/dist/utilities/getObjectDotNotation.js.map +1 -1
  418. package/dist/utilities/getObjectDotNotation.spec.js +33 -0
  419. package/dist/utilities/getObjectDotNotation.spec.js.map +1 -0
  420. package/dist/utilities/getSafeRedirect.spec.js +14 -0
  421. package/dist/utilities/getSafeRedirect.spec.js.map +1 -1
  422. package/dist/utilities/getTopLevelFieldNames.d.ts +2 -0
  423. package/dist/utilities/getTopLevelFieldNames.d.ts.map +1 -0
  424. package/dist/utilities/getTopLevelFieldNames.js +3 -0
  425. package/dist/utilities/getTopLevelFieldNames.js.map +1 -0
  426. package/dist/utilities/getTopLevelFieldNames.spec.js +18 -0
  427. package/dist/utilities/getTopLevelFieldNames.spec.js.map +1 -0
  428. package/dist/utilities/handleEndpoints.js +1 -1
  429. package/dist/utilities/handleEndpoints.js.map +1 -1
  430. package/dist/utilities/parseParams/index.d.ts.map +1 -1
  431. package/dist/utilities/parseParams/index.js +3 -1
  432. package/dist/utilities/parseParams/index.js.map +1 -1
  433. package/dist/utilities/parseParams/index.spec.js +8 -0
  434. package/dist/utilities/parseParams/index.spec.js.map +1 -1
  435. package/dist/utilities/traverseFields.d.ts.map +1 -1
  436. package/dist/utilities/traverseFields.js +5 -14
  437. package/dist/utilities/traverseFields.js.map +1 -1
  438. package/dist/utilities/unflatten.d.ts.map +1 -1
  439. package/dist/utilities/unflatten.js +21 -9
  440. package/dist/utilities/unflatten.js.map +1 -1
  441. package/dist/utilities/unflatten.spec.js +38 -0
  442. package/dist/utilities/unflatten.spec.js.map +1 -0
  443. package/dist/utilities/wrapInternalEndpoints.d.ts.map +1 -1
  444. package/dist/utilities/wrapInternalEndpoints.js +10 -3
  445. package/dist/utilities/wrapInternalEndpoints.js.map +1 -1
  446. package/dist/utilities/wrapInternalEndpoints.spec.js +124 -0
  447. package/dist/utilities/wrapInternalEndpoints.spec.js.map +1 -0
  448. package/dist/versions/allLocalesPublicationStatus.d.ts +38 -0
  449. package/dist/versions/allLocalesPublicationStatus.d.ts.map +1 -0
  450. package/dist/versions/allLocalesPublicationStatus.js +57 -0
  451. package/dist/versions/allLocalesPublicationStatus.js.map +1 -0
  452. package/dist/versions/getLatestGlobalVersion.d.ts.map +1 -1
  453. package/dist/versions/getLatestGlobalVersion.js +10 -2
  454. package/dist/versions/getLatestGlobalVersion.js.map +1 -1
  455. package/dist/versions/getRestoredStatusesToAuthorize.d.ts +9 -0
  456. package/dist/versions/getRestoredStatusesToAuthorize.d.ts.map +1 -0
  457. package/dist/versions/getRestoredStatusesToAuthorize.js +22 -0
  458. package/dist/versions/getRestoredStatusesToAuthorize.js.map +1 -0
  459. package/dist/versions/getRestoredStatusesToAuthorize.spec.js +50 -0
  460. package/dist/versions/getRestoredStatusesToAuthorize.spec.js.map +1 -0
  461. package/dist/versions/schedule/job.d.ts +2 -2
  462. package/dist/versions/schedule/job.d.ts.map +1 -1
  463. package/dist/versions/schedule/job.js +11 -7
  464. package/dist/versions/schedule/job.js.map +1 -1
  465. package/dist/versions/schedule/types.d.ts +5 -1
  466. package/dist/versions/schedule/types.d.ts.map +1 -1
  467. package/dist/versions/schedule/types.js.map +1 -1
  468. package/package.json +4 -2
@@ -1,28 +1,73 @@
1
1
  import { APIError } from '../errors/index.js';
2
+ import { getRequestOrigin } from '../utilities/getRequestOrigin.js';
2
3
  import { isURLAllowed } from '../utilities/isURLAllowed.js';
3
4
  import { safeFetch } from './safeFetch.js';
5
+ const hasProtocol = (url)=>/^[a-z][a-z\d+.-]*:/i.test(url);
6
+ const validateFileURL = (url)=>{
7
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') {
8
+ throw new APIError('External file URLs must use HTTP or HTTPS.', 400);
9
+ }
10
+ return url;
11
+ };
12
+ const parseFileURL = (url, base)=>{
13
+ try {
14
+ return validateFileURL(new URL(url, base));
15
+ } catch (error) {
16
+ if (error instanceof APIError) {
17
+ throw error;
18
+ }
19
+ throw new APIError('Invalid external file URL.', 400);
20
+ }
21
+ };
22
+ const getRequestBaseURL = (req)=>{
23
+ try {
24
+ const requestURL = validateFileURL(new URL(req.url));
25
+ const host = req.headers.get('host') || requestURL.host;
26
+ return validateFileURL(new URL(`${requestURL.protocol}//${host}`));
27
+ } catch {
28
+ return undefined;
29
+ }
30
+ };
4
31
  export const getExternalFile = async ({ data, req, uploadConfig })=>{
5
32
  const { filename, url } = data;
6
- let trimAuthCookies = true;
7
33
  if (typeof url === 'string') {
8
- let fileURL = url;
9
- if (!url.startsWith('http')) {
10
- // URL points to the same server - we can send any cookies safely to our server.
11
- trimAuthCookies = false;
12
- const baseUrl = req.headers.get('origin') || `${req.protocol}://${req.headers.get('host')}`;
13
- fileURL = `${baseUrl}${url}`;
14
- }
15
- let cookies = (req.headers.get('cookie') ?? '').split(';');
16
- if (trimAuthCookies) {
17
- cookies = cookies.filter((cookie)=>!cookie.trim().startsWith(req.payload.config.cookiePrefix));
34
+ let baseOrigin;
35
+ let parsedFileURL;
36
+ if (hasProtocol(url)) {
37
+ parsedFileURL = parseFileURL(url);
38
+ } else {
39
+ const requestOrigin = getRequestOrigin({
40
+ config: req.payload.config,
41
+ req
42
+ });
43
+ let requestBaseURL;
44
+ if (requestOrigin) {
45
+ requestBaseURL = parseFileURL(requestOrigin);
46
+ baseOrigin = requestBaseURL.origin;
47
+ } else {
48
+ requestBaseURL = getRequestBaseURL(req);
49
+ }
50
+ if (!requestBaseURL) {
51
+ throw new APIError('Unable to determine an HTTP(S) base URL for the external file.', 400);
52
+ }
53
+ parsedFileURL = parseFileURL(url, requestBaseURL);
18
54
  }
19
- const headers = uploadConfig.externalFileHeaderFilter ? uploadConfig.externalFileHeaderFilter(Object.fromEntries(new Headers(req.headers))) : {
20
- cookie: cookies.join(';')
21
- };
55
+ let fileURL = parsedFileURL.toString();
56
+ const requestHeaders = Object.fromEntries(new Headers(req.headers));
57
+ const cookies = (req.headers.get('cookie') ?? '').split(';');
22
58
  let res;
23
59
  let redirectCount = 0;
24
60
  const maxRedirects = 3;
25
61
  while(redirectCount <= maxRedirects){
62
+ const isSameOrigin = Boolean(baseOrigin && new URL(fileURL).origin === baseOrigin);
63
+ const headers = uploadConfig.externalFileHeaderFilter ? uploadConfig.externalFileHeaderFilter({
64
+ ...requestHeaders
65
+ }, {
66
+ isSameOrigin,
67
+ url: fileURL
68
+ }) : {
69
+ cookie: cookies.filter((cookie)=>isSameOrigin || !cookie.trim().startsWith(req.payload.config.cookiePrefix)).join(';')
70
+ };
26
71
  const skipSafeFetch = uploadConfig.skipSafeFetch === true ? uploadConfig.skipSafeFetch : Array.isArray(uploadConfig.skipSafeFetch) && isURLAllowed(fileURL, uploadConfig.skipSafeFetch);
27
72
  const isAllowedPasteUrl = uploadConfig.pasteURL && uploadConfig.pasteURL.allowList && isURLAllowed(fileURL, uploadConfig.pasteURL.allowList);
28
73
  if (skipSafeFetch || isAllowedPasteUrl) {
@@ -47,7 +92,7 @@ export const getExternalFile = async ({ data, req, uploadConfig })=>{
47
92
  }
48
93
  const location = res.headers.get('location');
49
94
  if (location) {
50
- fileURL = new URL(location, fileURL).toString();
95
+ fileURL = parseFileURL(location, new URL(fileURL)).toString();
51
96
  if (uploadConfig.pasteURL && uploadConfig.pasteURL.allowList && !isURLAllowed(fileURL, uploadConfig.pasteURL.allowList)) {
52
97
  throw new APIError('Redirect target is not allowed.', 400);
53
98
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/uploads/getExternalFile.ts"],"sourcesContent":["import type { PayloadRequest } from '../types/index.js'\nimport type { File, FileData, UploadConfig } from './types.js'\n\nimport { APIError } from '../errors/index.js'\nimport { isURLAllowed } from '../utilities/isURLAllowed.js'\nimport { safeFetch } from './safeFetch.js'\n\ntype Args = {\n data: FileData\n req: PayloadRequest\n uploadConfig: UploadConfig\n}\nexport const getExternalFile = async ({ data, req, uploadConfig }: Args): Promise<File> => {\n const { filename, url } = data\n\n let trimAuthCookies = true\n if (typeof url === 'string') {\n let fileURL = url\n if (!url.startsWith('http')) {\n // URL points to the same server - we can send any cookies safely to our server.\n trimAuthCookies = false\n const baseUrl = req.headers.get('origin') || `${req.protocol}://${req.headers.get('host')}`\n fileURL = `${baseUrl}${url}`\n }\n\n let cookies = (req.headers.get('cookie') ?? '').split(';')\n\n if (trimAuthCookies) {\n cookies = cookies.filter(\n (cookie) => !cookie.trim().startsWith(req.payload.config.cookiePrefix),\n )\n }\n\n const headers = uploadConfig.externalFileHeaderFilter\n ? uploadConfig.externalFileHeaderFilter(Object.fromEntries(new Headers(req.headers)))\n : {\n cookie: cookies.join(';'),\n }\n\n let res\n let redirectCount = 0\n const maxRedirects = 3\n\n while (redirectCount <= maxRedirects) {\n const skipSafeFetch: boolean =\n uploadConfig.skipSafeFetch === true\n ? uploadConfig.skipSafeFetch\n : Array.isArray(uploadConfig.skipSafeFetch) &&\n isURLAllowed(fileURL, uploadConfig.skipSafeFetch)\n\n const isAllowedPasteUrl: boolean | undefined =\n uploadConfig.pasteURL &&\n uploadConfig.pasteURL.allowList &&\n isURLAllowed(fileURL, uploadConfig.pasteURL.allowList)\n\n if (skipSafeFetch || isAllowedPasteUrl) {\n res = await fetch(fileURL, {\n credentials: 'include',\n headers,\n method: 'GET',\n redirect: 'manual',\n })\n } else {\n // Default\n res = await safeFetch(fileURL, {\n credentials: 'include',\n headers,\n method: 'GET',\n })\n }\n\n if (res.status >= 300 && res.status < 400) {\n redirectCount++\n if (redirectCount > maxRedirects) {\n throw new APIError(`Too many redirects (max ${maxRedirects})`, 403)\n }\n const location = res.headers.get('location')\n if (location) {\n fileURL = new URL(location, fileURL).toString()\n if (\n uploadConfig.pasteURL &&\n uploadConfig.pasteURL.allowList &&\n !isURLAllowed(fileURL, uploadConfig.pasteURL.allowList)\n ) {\n throw new APIError('Redirect target is not allowed.', 400)\n }\n continue\n }\n }\n\n break\n }\n\n if (!res || !res.ok) {\n throw new APIError(`Failed to fetch file from ${fileURL}`, res?.status)\n }\n\n const data = await res.arrayBuffer()\n\n return {\n name: filename,\n data: Buffer.from(data),\n mimetype: res.headers.get('content-type') || undefined!,\n size: Number(res.headers.get('content-length')) || 0,\n }\n }\n\n throw new APIError('Invalid file url', 400)\n}\n"],"names":["APIError","isURLAllowed","safeFetch","getExternalFile","data","req","uploadConfig","filename","url","trimAuthCookies","fileURL","startsWith","baseUrl","headers","get","protocol","cookies","split","filter","cookie","trim","payload","config","cookiePrefix","externalFileHeaderFilter","Object","fromEntries","Headers","join","res","redirectCount","maxRedirects","skipSafeFetch","Array","isArray","isAllowedPasteUrl","pasteURL","allowList","fetch","credentials","method","redirect","status","location","URL","toString","ok","arrayBuffer","name","Buffer","from","mimetype","undefined","size","Number"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/uploads/getExternalFile.ts"],"sourcesContent":["import type { PayloadRequest } from '../types/index.js'\nimport type { File, FileData, UploadConfig } from './types.js'\n\nimport { APIError } from '../errors/index.js'\nimport { getRequestOrigin } from '../utilities/getRequestOrigin.js'\nimport { isURLAllowed } from '../utilities/isURLAllowed.js'\nimport { safeFetch } from './safeFetch.js'\n\ntype Args = {\n data: Pick<FileData, 'filename' | 'url'>\n req: PayloadRequest\n uploadConfig: UploadConfig\n}\n\nconst hasProtocol = (url: string): boolean => /^[a-z][a-z\\d+.-]*:/i.test(url)\n\nconst validateFileURL = (url: URL): URL => {\n if (url.protocol !== 'http:' && url.protocol !== 'https:') {\n throw new APIError('External file URLs must use HTTP or HTTPS.', 400)\n }\n\n return url\n}\n\nconst parseFileURL = (url: string, base?: URL): URL => {\n try {\n return validateFileURL(new URL(url, base))\n } catch (error) {\n if (error instanceof APIError) {\n throw error\n }\n\n throw new APIError('Invalid external file URL.', 400)\n }\n}\n\nconst getRequestBaseURL = (req: PayloadRequest): undefined | URL => {\n try {\n const requestURL = validateFileURL(new URL(req.url!))\n const host = req.headers.get('host') || requestURL.host\n\n return validateFileURL(new URL(`${requestURL.protocol}//${host}`))\n } catch {\n return undefined\n }\n}\n\nexport const getExternalFile = async ({ data, req, uploadConfig }: Args): Promise<File> => {\n const { filename, url } = data\n\n if (typeof url === 'string') {\n let baseOrigin: string | undefined\n let parsedFileURL: URL\n\n if (hasProtocol(url)) {\n parsedFileURL = parseFileURL(url)\n } else {\n const requestOrigin = getRequestOrigin({ config: req.payload.config, req })\n let requestBaseURL: undefined | URL\n\n if (requestOrigin) {\n requestBaseURL = parseFileURL(requestOrigin)\n baseOrigin = requestBaseURL.origin\n } else {\n requestBaseURL = getRequestBaseURL(req)\n }\n\n if (!requestBaseURL) {\n throw new APIError('Unable to determine an HTTP(S) base URL for the external file.', 400)\n }\n\n parsedFileURL = parseFileURL(url, requestBaseURL)\n }\n\n let fileURL = parsedFileURL.toString()\n const requestHeaders = Object.fromEntries(new Headers(req.headers))\n const cookies = (req.headers.get('cookie') ?? '').split(';')\n\n let res\n let redirectCount = 0\n const maxRedirects = 3\n\n while (redirectCount <= maxRedirects) {\n const isSameOrigin = Boolean(baseOrigin && new URL(fileURL).origin === baseOrigin)\n const headers = uploadConfig.externalFileHeaderFilter\n ? uploadConfig.externalFileHeaderFilter(\n { ...requestHeaders },\n { isSameOrigin, url: fileURL },\n )\n : {\n cookie: cookies\n .filter(\n (cookie) =>\n isSameOrigin || !cookie.trim().startsWith(req.payload.config.cookiePrefix),\n )\n .join(';'),\n }\n\n const skipSafeFetch: boolean =\n uploadConfig.skipSafeFetch === true\n ? uploadConfig.skipSafeFetch\n : Array.isArray(uploadConfig.skipSafeFetch) &&\n isURLAllowed(fileURL, uploadConfig.skipSafeFetch)\n\n const isAllowedPasteUrl: boolean | undefined =\n uploadConfig.pasteURL &&\n uploadConfig.pasteURL.allowList &&\n isURLAllowed(fileURL, uploadConfig.pasteURL.allowList)\n\n if (skipSafeFetch || isAllowedPasteUrl) {\n res = await fetch(fileURL, {\n credentials: 'include',\n headers,\n method: 'GET',\n redirect: 'manual',\n })\n } else {\n // Default\n res = await safeFetch(fileURL, {\n credentials: 'include',\n headers,\n method: 'GET',\n })\n }\n\n if (res.status >= 300 && res.status < 400) {\n redirectCount++\n if (redirectCount > maxRedirects) {\n throw new APIError(`Too many redirects (max ${maxRedirects})`, 403)\n }\n const location = res.headers.get('location')\n if (location) {\n fileURL = parseFileURL(location, new URL(fileURL)).toString()\n if (\n uploadConfig.pasteURL &&\n uploadConfig.pasteURL.allowList &&\n !isURLAllowed(fileURL, uploadConfig.pasteURL.allowList)\n ) {\n throw new APIError('Redirect target is not allowed.', 400)\n }\n continue\n }\n }\n\n break\n }\n\n if (!res || !res.ok) {\n throw new APIError(`Failed to fetch file from ${fileURL}`, res?.status)\n }\n\n const data = await res.arrayBuffer()\n\n return {\n name: filename,\n data: Buffer.from(data),\n mimetype: res.headers.get('content-type') || undefined!,\n size: Number(res.headers.get('content-length')) || 0,\n }\n }\n\n throw new APIError('Invalid file url', 400)\n}\n"],"names":["APIError","getRequestOrigin","isURLAllowed","safeFetch","hasProtocol","url","test","validateFileURL","protocol","parseFileURL","base","URL","error","getRequestBaseURL","req","requestURL","host","headers","get","undefined","getExternalFile","data","uploadConfig","filename","baseOrigin","parsedFileURL","requestOrigin","config","payload","requestBaseURL","origin","fileURL","toString","requestHeaders","Object","fromEntries","Headers","cookies","split","res","redirectCount","maxRedirects","isSameOrigin","Boolean","externalFileHeaderFilter","cookie","filter","trim","startsWith","cookiePrefix","join","skipSafeFetch","Array","isArray","isAllowedPasteUrl","pasteURL","allowList","fetch","credentials","method","redirect","status","location","ok","arrayBuffer","name","Buffer","from","mimetype","size","Number"],"mappings":"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"}
@@ -7,8 +7,8 @@ export type ClientUploadData = {
7
7
  size: number;
8
8
  };
9
9
  /**
10
- * `req.context` key `unlinkTempFiles` reads to find a materializer-created temp file once
11
- * `req.file` is gone - plugin-cloud-storage's afterChange hook clears `req.file` after
10
+ * `req.context` key `unlinkClientUploadTempFile` reads to find a materializer-created temp file
11
+ * once `req.file` is gone - plugin-cloud-storage's afterChange hook clears `req.file` after
12
12
  * uploading generated image sizes, before cleanup runs, so tracking it only on `req.file`
13
13
  * would leak the temp file.
14
14
  */
@@ -1 +1 @@
1
- {"version":3,"file":"getFileFromClientUpload.d.ts","sourceRoot":"","sources":["../../src/uploads/getFileFromClientUpload.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAQvD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAC7B,cAAc,EAAE,MAAM,CAAA;IACtB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,wCAAwC,oCAAoC,CAAA;AAEzF,wBAAsB,uBAAuB,CAAC,EAC5C,IAAI,EACJ,GAAG,GACJ,EAAE;IACD,IAAI,EAAE,gBAAgB,CAAA;IACtB,GAAG,EAAE,cAAc,CAAA;CACpB,GAAG,OAAO,CAAC,WAAW,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,CA8C/C"}
1
+ {"version":3,"file":"getFileFromClientUpload.d.ts","sourceRoot":"","sources":["../../src/uploads/getFileFromClientUpload.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAQvD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAC7B,cAAc,EAAE,MAAM,CAAA;IACtB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,wCAAwC,oCAAoC,CAAA;AAEzF,wBAAsB,uBAAuB,CAAC,EAC5C,IAAI,EACJ,GAAG,GACJ,EAAE;IACD,IAAI,EAAE,gBAAgB,CAAA;IACtB,GAAG,EAAE,cAAc,CAAA;CACpB,GAAG,OAAO,CAAC,WAAW,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,CAgD/C"}
@@ -10,8 +10,8 @@ import { isolateObjectProperty } from '../utilities/isolateObjectProperty.js';
10
10
  import { getFileContentRequirement, HEADER_PROBE_BYTE_LENGTH } from './getFileContentRequirement.js';
11
11
  import { getImageSize } from './getImageSize.js';
12
12
  /**
13
- * `req.context` key `unlinkTempFiles` reads to find a materializer-created temp file once
14
- * `req.file` is gone - plugin-cloud-storage's afterChange hook clears `req.file` after
13
+ * `req.context` key `unlinkClientUploadTempFile` reads to find a materializer-created temp file
14
+ * once `req.file` is gone - plugin-cloud-storage's afterChange hook clears `req.file` after
15
15
  * uploading generated image sizes, before cleanup runs, so tracking it only on `req.file`
16
16
  * would leak the temp file.
17
17
  */ export const CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY = 'payloadClientUploadTempFilePath';
@@ -20,11 +20,11 @@ export async function getFileFromClientUpload({ file, req }) {
20
20
  if (!uploadConfig || !uploadConfig.handlers) {
21
21
  throw new APIError(`uploadConfig.handlers is not present for ${file.collectionSlug}`);
22
22
  }
23
- const contentRequirement = getFileContentRequirement({
23
+ const contentRequirement = file.clientUploadContext ? getFileContentRequirement({
24
24
  hasSizeEdits: requestHasSizeEdits(req),
25
25
  mimeType: file.mimeType,
26
26
  uploadConfig
27
- });
27
+ }) : 'full';
28
28
  if (contentRequirement === 'none') {
29
29
  return {
30
30
  name: file.filename,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/uploads/getFileFromClientUpload.ts"],"sourcesContent":["import { randomUUID } from 'crypto'\nimport fs from 'fs'\nimport { mkdir, rm, writeFile } from 'fs/promises'\nimport os from 'os'\nimport path from 'path'\nimport { Readable } from 'stream'\nimport { pipeline } from 'stream/promises'\n\nimport type { PayloadRequest } from '../types/index.js'\nimport type { SanitizedUploadConfig, UploadEdits } from './types.js'\n\nimport { APIError } from '../errors/APIError.js'\nimport { isolateObjectProperty } from '../utilities/isolateObjectProperty.js'\nimport { getFileContentRequirement, HEADER_PROBE_BYTE_LENGTH } from './getFileContentRequirement.js'\nimport { getImageSize } from './getImageSize.js'\n\nexport type ClientUploadData = {\n clientUploadContext?: unknown\n collectionSlug: string\n filename: string\n mimeType: string\n size: number\n}\n\n/**\n * `req.context` key `unlinkTempFiles` reads to find a materializer-created temp file once\n * `req.file` is gone - plugin-cloud-storage's afterChange hook clears `req.file` after\n * uploading generated image sizes, before cleanup runs, so tracking it only on `req.file`\n * would leak the temp file.\n */\nexport const CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY = 'payloadClientUploadTempFilePath'\n\nexport async function getFileFromClientUpload({\n file,\n req,\n}: {\n file: ClientUploadData\n req: PayloadRequest\n}): Promise<NonNullable<PayloadRequest['file']>> {\n const uploadConfig = req.payload.collections[file.collectionSlug]?.config.upload\n\n if (!uploadConfig || !uploadConfig.handlers) {\n throw new APIError(`uploadConfig.handlers is not present for ${file.collectionSlug}`)\n }\n\n const contentRequirement = getFileContentRequirement({\n hasSizeEdits: requestHasSizeEdits(req),\n mimeType: file.mimeType,\n uploadConfig,\n })\n\n if (contentRequirement === 'none') {\n return {\n name: file.filename,\n clientUploadContext: file.clientUploadContext,\n data: Buffer.alloc(0),\n mimetype: file.mimeType,\n size: file.size,\n }\n }\n\n if (contentRequirement === 'header') {\n const headerFile = await fetchHeaderOnly({ file, req, uploadConfig })\n if (headerFile) {\n return headerFile\n }\n }\n\n const response = await fetchUploadResponse({ file, req, uploadConfig })\n const tempFilePath = await streamResponseToTempFile({ req, response })\n\n if (!req.context) {\n req.context = {}\n }\n req.context[CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY] = tempFilePath\n\n return {\n name: file.filename,\n clientUploadContext: file.clientUploadContext,\n data: Buffer.alloc(0),\n mimetype: response.headers.get('Content-Type') || file.mimeType,\n size: file.size,\n tempFilePath,\n }\n}\n\n/**\n * Only crop and explicit pixel edits require the original bytes - a focal-point-only\n * change re-derives from data already on the document, not the incoming upload.\n */\nfunction requestHasSizeEdits(req: PayloadRequest): boolean {\n const uploadEdits =\n req.query?.uploadEdits && typeof req.query.uploadEdits === 'object'\n ? (req.query.uploadEdits as UploadEdits)\n : undefined\n\n return Boolean(uploadEdits?.crop || uploadEdits?.heightInPixels || uploadEdits?.widthInPixels)\n}\n\n/**\n * Runs every configured handler and returns the last one that responds, following a single\n * redirect if that response is one - matching the pre-existing v3 handler contract exactly\n * (v3 always ran every handler; only v4 stops at the first response, via an unrelated PR).\n */\nasync function fetchUploadResponse({\n file,\n req,\n uploadConfig,\n}: {\n file: ClientUploadData\n req: PayloadRequest\n uploadConfig: SanitizedUploadConfig\n}): Promise<Response> {\n let response: null | Response = null\n let error: unknown\n\n for (const handler of uploadConfig.handlers!) {\n try {\n const result = await handler(req, {\n doc: null!,\n params: {\n clientUploadContext: file.clientUploadContext,\n collection: file.collectionSlug,\n filename: file.filename,\n },\n })\n\n if (result) {\n response = result\n }\n // If we couldn't get the file from that handler, save the error and try other.\n } catch (err) {\n error = err\n }\n }\n\n if (!response) {\n if (error) {\n req.payload.logger.error(error)\n }\n\n throw new APIError('Expected response from the upload handler.')\n }\n\n if (response.status >= 300 && response.status < 400) {\n const redirectUrl = response.headers.get('Location')\n if (redirectUrl) {\n // Forward the Range header (if any) so a redirect from the header-probe path still\n // fetches only the bounded slice, instead of the whole object, from the redirect target.\n const rangeHeader = req.headers.get('Range')\n response = rangeHeader\n ? await fetch(redirectUrl, { headers: { Range: rangeHeader } })\n : await fetch(redirectUrl)\n }\n }\n\n return response\n}\n\n/**\n * Requests a bounded byte range from the handlers and probes it for image dimensions, without\n * ever reading past `HEADER_PROBE_BYTE_LENGTH`. Returns `null` when the bounded bytes cannot be\n * probed, so the caller falls back to a full streamed fetch.\n */\nasync function fetchHeaderOnly({\n file,\n req,\n uploadConfig,\n}: {\n file: ClientUploadData\n req: PayloadRequest\n uploadConfig: SanitizedUploadConfig\n}): Promise<NonNullable<PayloadRequest['file']> | null> {\n const rangedHeaders = new Headers(req.headers)\n rangedHeaders.set('Range', `bytes=0-${HEADER_PROBE_BYTE_LENGTH - 1}`)\n\n const scopedReq = isolateObjectProperty(req, 'headers')\n scopedReq.headers = rangedHeaders\n\n const response = await fetchUploadResponse({ file, req: scopedReq, uploadConfig })\n\n if (!response.body) {\n return null\n }\n\n const reader = response.body.getReader()\n const chunks: Uint8Array[] = []\n let bytesRead = 0\n\n try {\n while (bytesRead < HEADER_PROBE_BYTE_LENGTH) {\n const { done, value } = await reader.read()\n if (done || !value) {\n break\n }\n\n const remaining = HEADER_PROBE_BYTE_LENGTH - bytesRead\n const chunk = value.length > remaining ? value.slice(0, remaining) : value\n\n chunks.push(chunk)\n bytesRead += chunk.length\n }\n } finally {\n await reader.cancel().catch(() => undefined)\n }\n\n const probedFile = {\n name: file.filename,\n clientUploadContext: file.clientUploadContext,\n data: Buffer.concat(chunks),\n mimetype: response.headers.get('Content-Type') || file.mimeType,\n size: file.size,\n }\n\n try {\n await getImageSize({ file: probedFile, sharp: req.payload.config.sharp })\n } catch {\n return null\n }\n\n return probedFile\n}\n\n/**\n * Streams the full response body straight to disk so a cloud object of unbounded size never\n * becomes a single in-memory Buffer.\n */\nasync function streamResponseToTempFile({\n req,\n response,\n}: {\n req: PayloadRequest\n response: Response\n}): Promise<string> {\n const tempFileDir = req.payload.config.upload?.tempFileDir || os.tmpdir()\n await mkdir(tempFileDir, { recursive: true })\n const tempFilePath = path.join(tempFileDir, `payload-client-upload-${randomUUID()}`)\n\n // A null body is a legitimate zero-byte file (the Fetch API allows a Response to omit a\n // body entirely), not a failure - `response.arrayBuffer()` tolerated this the same way.\n if (!response.body) {\n await writeFile(tempFilePath, Buffer.alloc(0))\n return tempFilePath\n }\n\n try {\n await pipeline(\n Readable.fromWeb(response.body as ReadableStream<Uint8Array>),\n fs.createWriteStream(tempFilePath),\n )\n } catch (error) {\n await rm(tempFilePath, { force: true })\n throw error\n }\n\n return tempFilePath\n}\n"],"names":["randomUUID","fs","mkdir","rm","writeFile","os","path","Readable","pipeline","APIError","isolateObjectProperty","getFileContentRequirement","HEADER_PROBE_BYTE_LENGTH","getImageSize","CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY","getFileFromClientUpload","file","req","uploadConfig","payload","collections","collectionSlug","config","upload","handlers","contentRequirement","hasSizeEdits","requestHasSizeEdits","mimeType","name","filename","clientUploadContext","data","Buffer","alloc","mimetype","size","headerFile","fetchHeaderOnly","response","fetchUploadResponse","tempFilePath","streamResponseToTempFile","context","headers","get","uploadEdits","query","undefined","Boolean","crop","heightInPixels","widthInPixels","error","handler","result","doc","params","collection","err","logger","status","redirectUrl","rangeHeader","fetch","Range","rangedHeaders","Headers","set","scopedReq","body","reader","getReader","chunks","bytesRead","done","value","read","remaining","chunk","length","slice","push","cancel","catch","probedFile","concat","sharp","tempFileDir","tmpdir","recursive","join","fromWeb","createWriteStream","force"],"mappings":"AAAA,SAASA,UAAU,QAAQ,SAAQ;AACnC,OAAOC,QAAQ,KAAI;AACnB,SAASC,KAAK,EAAEC,EAAE,EAAEC,SAAS,QAAQ,cAAa;AAClD,OAAOC,QAAQ,KAAI;AACnB,OAAOC,UAAU,OAAM;AACvB,SAASC,QAAQ,QAAQ,SAAQ;AACjC,SAASC,QAAQ,QAAQ,kBAAiB;AAK1C,SAASC,QAAQ,QAAQ,wBAAuB;AAChD,SAASC,qBAAqB,QAAQ,wCAAuC;AAC7E,SAASC,yBAAyB,EAAEC,wBAAwB,QAAQ,iCAAgC;AACpG,SAASC,YAAY,QAAQ,oBAAmB;AAUhD;;;;;CAKC,GACD,OAAO,MAAMC,2CAA2C,kCAAiC;AAEzF,OAAO,eAAeC,wBAAwB,EAC5CC,IAAI,EACJC,GAAG,EAIJ;IACC,MAAMC,eAAeD,IAAIE,OAAO,CAACC,WAAW,CAACJ,KAAKK,cAAc,CAAC,EAAEC,OAAOC;IAE1E,IAAI,CAACL,gBAAgB,CAACA,aAAaM,QAAQ,EAAE;QAC3C,MAAM,IAAIf,SAAS,CAAC,yCAAyC,EAAEO,KAAKK,cAAc,EAAE;IACtF;IAEA,MAAMI,qBAAqBd,0BAA0B;QACnDe,cAAcC,oBAAoBV;QAClCW,UAAUZ,KAAKY,QAAQ;QACvBV;IACF;IAEA,IAAIO,uBAAuB,QAAQ;QACjC,OAAO;YACLI,MAAMb,KAAKc,QAAQ;YACnBC,qBAAqBf,KAAKe,mBAAmB;YAC7CC,MAAMC,OAAOC,KAAK,CAAC;YACnBC,UAAUnB,KAAKY,QAAQ;YACvBQ,MAAMpB,KAAKoB,IAAI;QACjB;IACF;IAEA,IAAIX,uBAAuB,UAAU;QACnC,MAAMY,aAAa,MAAMC,gBAAgB;YAAEtB;YAAMC;YAAKC;QAAa;QACnE,IAAImB,YAAY;YACd,OAAOA;QACT;IACF;IAEA,MAAME,WAAW,MAAMC,oBAAoB;QAAExB;QAAMC;QAAKC;IAAa;IACrE,MAAMuB,eAAe,MAAMC,yBAAyB;QAAEzB;QAAKsB;IAAS;IAEpE,IAAI,CAACtB,IAAI0B,OAAO,EAAE;QAChB1B,IAAI0B,OAAO,GAAG,CAAC;IACjB;IACA1B,IAAI0B,OAAO,CAAC7B,yCAAyC,GAAG2B;IAExD,OAAO;QACLZ,MAAMb,KAAKc,QAAQ;QACnBC,qBAAqBf,KAAKe,mBAAmB;QAC7CC,MAAMC,OAAOC,KAAK,CAAC;QACnBC,UAAUI,SAASK,OAAO,CAACC,GAAG,CAAC,mBAAmB7B,KAAKY,QAAQ;QAC/DQ,MAAMpB,KAAKoB,IAAI;QACfK;IACF;AACF;AAEA;;;CAGC,GACD,SAASd,oBAAoBV,GAAmB;IAC9C,MAAM6B,cACJ7B,IAAI8B,KAAK,EAAED,eAAe,OAAO7B,IAAI8B,KAAK,CAACD,WAAW,KAAK,WACtD7B,IAAI8B,KAAK,CAACD,WAAW,GACtBE;IAEN,OAAOC,QAAQH,aAAaI,QAAQJ,aAAaK,kBAAkBL,aAAaM;AAClF;AAEA;;;;CAIC,GACD,eAAeZ,oBAAoB,EACjCxB,IAAI,EACJC,GAAG,EACHC,YAAY,EAKb;IACC,IAAIqB,WAA4B;IAChC,IAAIc;IAEJ,KAAK,MAAMC,WAAWpC,aAAaM,QAAQ,CAAG;QAC5C,IAAI;YACF,MAAM+B,SAAS,MAAMD,QAAQrC,KAAK;gBAChCuC,KAAK;gBACLC,QAAQ;oBACN1B,qBAAqBf,KAAKe,mBAAmB;oBAC7C2B,YAAY1C,KAAKK,cAAc;oBAC/BS,UAAUd,KAAKc,QAAQ;gBACzB;YACF;YAEA,IAAIyB,QAAQ;gBACVhB,WAAWgB;YACb;QACA,+EAA+E;QACjF,EAAE,OAAOI,KAAK;YACZN,QAAQM;QACV;IACF;IAEA,IAAI,CAACpB,UAAU;QACb,IAAIc,OAAO;YACTpC,IAAIE,OAAO,CAACyC,MAAM,CAACP,KAAK,CAACA;QAC3B;QAEA,MAAM,IAAI5C,SAAS;IACrB;IAEA,IAAI8B,SAASsB,MAAM,IAAI,OAAOtB,SAASsB,MAAM,GAAG,KAAK;QACnD,MAAMC,cAAcvB,SAASK,OAAO,CAACC,GAAG,CAAC;QACzC,IAAIiB,aAAa;YACf,mFAAmF;YACnF,yFAAyF;YACzF,MAAMC,cAAc9C,IAAI2B,OAAO,CAACC,GAAG,CAAC;YACpCN,WAAWwB,cACP,MAAMC,MAAMF,aAAa;gBAAElB,SAAS;oBAAEqB,OAAOF;gBAAY;YAAE,KAC3D,MAAMC,MAAMF;QAClB;IACF;IAEA,OAAOvB;AACT;AAEA;;;;CAIC,GACD,eAAeD,gBAAgB,EAC7BtB,IAAI,EACJC,GAAG,EACHC,YAAY,EAKb;IACC,MAAMgD,gBAAgB,IAAIC,QAAQlD,IAAI2B,OAAO;IAC7CsB,cAAcE,GAAG,CAAC,SAAS,CAAC,QAAQ,EAAExD,2BAA2B,GAAG;IAEpE,MAAMyD,YAAY3D,sBAAsBO,KAAK;IAC7CoD,UAAUzB,OAAO,GAAGsB;IAEpB,MAAM3B,WAAW,MAAMC,oBAAoB;QAAExB;QAAMC,KAAKoD;QAAWnD;IAAa;IAEhF,IAAI,CAACqB,SAAS+B,IAAI,EAAE;QAClB,OAAO;IACT;IAEA,MAAMC,SAAShC,SAAS+B,IAAI,CAACE,SAAS;IACtC,MAAMC,SAAuB,EAAE;IAC/B,IAAIC,YAAY;IAEhB,IAAI;QACF,MAAOA,YAAY9D,yBAA0B;YAC3C,MAAM,EAAE+D,IAAI,EAAEC,KAAK,EAAE,GAAG,MAAML,OAAOM,IAAI;YACzC,IAAIF,QAAQ,CAACC,OAAO;gBAClB;YACF;YAEA,MAAME,YAAYlE,2BAA2B8D;YAC7C,MAAMK,QAAQH,MAAMI,MAAM,GAAGF,YAAYF,MAAMK,KAAK,CAAC,GAAGH,aAAaF;YAErEH,OAAOS,IAAI,CAACH;YACZL,aAAaK,MAAMC,MAAM;QAC3B;IACF,SAAU;QACR,MAAMT,OAAOY,MAAM,GAAGC,KAAK,CAAC,IAAMpC;IACpC;IAEA,MAAMqC,aAAa;QACjBxD,MAAMb,KAAKc,QAAQ;QACnBC,qBAAqBf,KAAKe,mBAAmB;QAC7CC,MAAMC,OAAOqD,MAAM,CAACb;QACpBtC,UAAUI,SAASK,OAAO,CAACC,GAAG,CAAC,mBAAmB7B,KAAKY,QAAQ;QAC/DQ,MAAMpB,KAAKoB,IAAI;IACjB;IAEA,IAAI;QACF,MAAMvB,aAAa;YAAEG,MAAMqE;YAAYE,OAAOtE,IAAIE,OAAO,CAACG,MAAM,CAACiE,KAAK;QAAC;IACzE,EAAE,OAAM;QACN,OAAO;IACT;IAEA,OAAOF;AACT;AAEA;;;CAGC,GACD,eAAe3C,yBAAyB,EACtCzB,GAAG,EACHsB,QAAQ,EAIT;IACC,MAAMiD,cAAcvE,IAAIE,OAAO,CAACG,MAAM,CAACC,MAAM,EAAEiE,eAAenF,GAAGoF,MAAM;IACvE,MAAMvF,MAAMsF,aAAa;QAAEE,WAAW;IAAK;IAC3C,MAAMjD,eAAenC,KAAKqF,IAAI,CAACH,aAAa,CAAC,sBAAsB,EAAExF,cAAc;IAEnF,wFAAwF;IACxF,wFAAwF;IACxF,IAAI,CAACuC,SAAS+B,IAAI,EAAE;QAClB,MAAMlE,UAAUqC,cAAcR,OAAOC,KAAK,CAAC;QAC3C,OAAOO;IACT;IAEA,IAAI;QACF,MAAMjC,SACJD,SAASqF,OAAO,CAACrD,SAAS+B,IAAI,GAC9BrE,GAAG4F,iBAAiB,CAACpD;IAEzB,EAAE,OAAOY,OAAO;QACd,MAAMlD,GAAGsC,cAAc;YAAEqD,OAAO;QAAK;QACrC,MAAMzC;IACR;IAEA,OAAOZ;AACT"}
1
+ {"version":3,"sources":["../../src/uploads/getFileFromClientUpload.ts"],"sourcesContent":["import { randomUUID } from 'crypto'\nimport fs from 'fs'\nimport { mkdir, rm, writeFile } from 'fs/promises'\nimport os from 'os'\nimport path from 'path'\nimport { Readable } from 'stream'\nimport { pipeline } from 'stream/promises'\n\nimport type { PayloadRequest } from '../types/index.js'\nimport type { SanitizedUploadConfig, UploadEdits } from './types.js'\n\nimport { APIError } from '../errors/APIError.js'\nimport { isolateObjectProperty } from '../utilities/isolateObjectProperty.js'\nimport { getFileContentRequirement, HEADER_PROBE_BYTE_LENGTH } from './getFileContentRequirement.js'\nimport { getImageSize } from './getImageSize.js'\n\nexport type ClientUploadData = {\n clientUploadContext?: unknown\n collectionSlug: string\n filename: string\n mimeType: string\n size: number\n}\n\n/**\n * `req.context` key `unlinkClientUploadTempFile` reads to find a materializer-created temp file\n * once `req.file` is gone - plugin-cloud-storage's afterChange hook clears `req.file` after\n * uploading generated image sizes, before cleanup runs, so tracking it only on `req.file`\n * would leak the temp file.\n */\nexport const CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY = 'payloadClientUploadTempFilePath'\n\nexport async function getFileFromClientUpload({\n file,\n req,\n}: {\n file: ClientUploadData\n req: PayloadRequest\n}): Promise<NonNullable<PayloadRequest['file']>> {\n const uploadConfig = req.payload.collections[file.collectionSlug]?.config.upload\n\n if (!uploadConfig || !uploadConfig.handlers) {\n throw new APIError(`uploadConfig.handlers is not present for ${file.collectionSlug}`)\n }\n\n const contentRequirement = file.clientUploadContext\n ? getFileContentRequirement({\n hasSizeEdits: requestHasSizeEdits(req),\n mimeType: file.mimeType,\n uploadConfig,\n })\n : 'full'\n\n if (contentRequirement === 'none') {\n return {\n name: file.filename,\n clientUploadContext: file.clientUploadContext,\n data: Buffer.alloc(0),\n mimetype: file.mimeType,\n size: file.size,\n }\n }\n\n if (contentRequirement === 'header') {\n const headerFile = await fetchHeaderOnly({ file, req, uploadConfig })\n if (headerFile) {\n return headerFile\n }\n }\n\n const response = await fetchUploadResponse({ file, req, uploadConfig })\n const tempFilePath = await streamResponseToTempFile({ req, response })\n\n if (!req.context) {\n req.context = {}\n }\n req.context[CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY] = tempFilePath\n\n return {\n name: file.filename,\n clientUploadContext: file.clientUploadContext,\n data: Buffer.alloc(0),\n mimetype: response.headers.get('Content-Type') || file.mimeType,\n size: file.size,\n tempFilePath,\n }\n}\n\n/**\n * Only crop and explicit pixel edits require the original bytes - a focal-point-only\n * change re-derives from data already on the document, not the incoming upload.\n */\nfunction requestHasSizeEdits(req: PayloadRequest): boolean {\n const uploadEdits =\n req.query?.uploadEdits && typeof req.query.uploadEdits === 'object'\n ? (req.query.uploadEdits as UploadEdits)\n : undefined\n\n return Boolean(uploadEdits?.crop || uploadEdits?.heightInPixels || uploadEdits?.widthInPixels)\n}\n\n/**\n * Runs every configured handler and returns the last one that responds, following a single\n * redirect if that response is one - matching the pre-existing v3 handler contract exactly\n * (v3 always ran every handler; only v4 stops at the first response, via an unrelated PR).\n */\nasync function fetchUploadResponse({\n file,\n req,\n uploadConfig,\n}: {\n file: ClientUploadData\n req: PayloadRequest\n uploadConfig: SanitizedUploadConfig\n}): Promise<Response> {\n let response: null | Response = null\n let error: unknown\n\n for (const handler of uploadConfig.handlers!) {\n try {\n const result = await handler(req, {\n doc: null!,\n params: {\n clientUploadContext: file.clientUploadContext,\n collection: file.collectionSlug,\n filename: file.filename,\n },\n })\n\n if (result) {\n response = result\n }\n // If we couldn't get the file from that handler, save the error and try other.\n } catch (err) {\n error = err\n }\n }\n\n if (!response) {\n if (error) {\n req.payload.logger.error(error)\n }\n\n throw new APIError('Expected response from the upload handler.')\n }\n\n if (response.status >= 300 && response.status < 400) {\n const redirectUrl = response.headers.get('Location')\n if (redirectUrl) {\n // Forward the Range header (if any) so a redirect from the header-probe path still\n // fetches only the bounded slice, instead of the whole object, from the redirect target.\n const rangeHeader = req.headers.get('Range')\n response = rangeHeader\n ? await fetch(redirectUrl, { headers: { Range: rangeHeader } })\n : await fetch(redirectUrl)\n }\n }\n\n return response\n}\n\n/**\n * Requests a bounded byte range from the handlers and probes it for image dimensions, without\n * ever reading past `HEADER_PROBE_BYTE_LENGTH`. Returns `null` when the bounded bytes cannot be\n * probed, so the caller falls back to a full streamed fetch.\n */\nasync function fetchHeaderOnly({\n file,\n req,\n uploadConfig,\n}: {\n file: ClientUploadData\n req: PayloadRequest\n uploadConfig: SanitizedUploadConfig\n}): Promise<NonNullable<PayloadRequest['file']> | null> {\n const rangedHeaders = new Headers(req.headers)\n rangedHeaders.set('Range', `bytes=0-${HEADER_PROBE_BYTE_LENGTH - 1}`)\n\n const scopedReq = isolateObjectProperty(req, 'headers')\n scopedReq.headers = rangedHeaders\n\n const response = await fetchUploadResponse({ file, req: scopedReq, uploadConfig })\n\n if (!response.body) {\n return null\n }\n\n const reader = response.body.getReader()\n const chunks: Uint8Array[] = []\n let bytesRead = 0\n\n try {\n while (bytesRead < HEADER_PROBE_BYTE_LENGTH) {\n const { done, value } = await reader.read()\n if (done || !value) {\n break\n }\n\n const remaining = HEADER_PROBE_BYTE_LENGTH - bytesRead\n const chunk = value.length > remaining ? value.slice(0, remaining) : value\n\n chunks.push(chunk)\n bytesRead += chunk.length\n }\n } finally {\n await reader.cancel().catch(() => undefined)\n }\n\n const probedFile = {\n name: file.filename,\n clientUploadContext: file.clientUploadContext,\n data: Buffer.concat(chunks),\n mimetype: response.headers.get('Content-Type') || file.mimeType,\n size: file.size,\n }\n\n try {\n await getImageSize({ file: probedFile, sharp: req.payload.config.sharp })\n } catch {\n return null\n }\n\n return probedFile\n}\n\n/**\n * Streams the full response body straight to disk so a cloud object of unbounded size never\n * becomes a single in-memory Buffer.\n */\nasync function streamResponseToTempFile({\n req,\n response,\n}: {\n req: PayloadRequest\n response: Response\n}): Promise<string> {\n const tempFileDir = req.payload.config.upload?.tempFileDir || os.tmpdir()\n await mkdir(tempFileDir, { recursive: true })\n const tempFilePath = path.join(tempFileDir, `payload-client-upload-${randomUUID()}`)\n\n // A null body is a legitimate zero-byte file (the Fetch API allows a Response to omit a\n // body entirely), not a failure - `response.arrayBuffer()` tolerated this the same way.\n if (!response.body) {\n await writeFile(tempFilePath, Buffer.alloc(0))\n return tempFilePath\n }\n\n try {\n await pipeline(\n Readable.fromWeb(response.body as ReadableStream<Uint8Array>),\n fs.createWriteStream(tempFilePath),\n )\n } catch (error) {\n await rm(tempFilePath, { force: true })\n throw error\n }\n\n return tempFilePath\n}\n"],"names":["randomUUID","fs","mkdir","rm","writeFile","os","path","Readable","pipeline","APIError","isolateObjectProperty","getFileContentRequirement","HEADER_PROBE_BYTE_LENGTH","getImageSize","CLIENT_UPLOAD_TEMP_FILE_PATH_CONTEXT_KEY","getFileFromClientUpload","file","req","uploadConfig","payload","collections","collectionSlug","config","upload","handlers","contentRequirement","clientUploadContext","hasSizeEdits","requestHasSizeEdits","mimeType","name","filename","data","Buffer","alloc","mimetype","size","headerFile","fetchHeaderOnly","response","fetchUploadResponse","tempFilePath","streamResponseToTempFile","context","headers","get","uploadEdits","query","undefined","Boolean","crop","heightInPixels","widthInPixels","error","handler","result","doc","params","collection","err","logger","status","redirectUrl","rangeHeader","fetch","Range","rangedHeaders","Headers","set","scopedReq","body","reader","getReader","chunks","bytesRead","done","value","read","remaining","chunk","length","slice","push","cancel","catch","probedFile","concat","sharp","tempFileDir","tmpdir","recursive","join","fromWeb","createWriteStream","force"],"mappings":"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"}
@@ -82,6 +82,9 @@ const videoFile = (overrides = {})=>({
82
82
  ...overrides
83
83
  });
84
84
  const imageFile = (overrides = {})=>({
85
+ clientUploadContext: {
86
+ prefix: 'abc'
87
+ },
85
88
  collectionSlug: 'media',
86
89
  filename: 'photo.png',
87
90
  mimeType: 'image/png',
@@ -523,6 +526,65 @@ describe('getFileFromClientUpload', ()=>{
523
526
  const written = await fs.readFile(result.tempFilePath);
524
527
  expect(written.length).toBe(0);
525
528
  });
529
+ it('streams the full file to disk when a non-image client upload carries no context', async ()=>{
530
+ const handler = vi.fn(async ()=>new Response(Buffer.from('full-video-bytes'), {
531
+ headers: {
532
+ 'Content-Type': 'video/mp4'
533
+ },
534
+ status: 200
535
+ }));
536
+ const req = createReq({
537
+ handlers: [
538
+ handler
539
+ ],
540
+ upload: {
541
+ disableLocalStorage: true
542
+ }
543
+ });
544
+ const file = videoFile({
545
+ clientUploadContext: undefined
546
+ });
547
+ const result = await getFileFromClientUpload({
548
+ file,
549
+ req
550
+ });
551
+ expect(handler).toHaveBeenCalledTimes(1);
552
+ expect(result.tempFilePath).toBeDefined();
553
+ tempFilesToRemove.push(result.tempFilePath);
554
+ const written = await fs.readFile(result.tempFilePath);
555
+ expect(written.toString()).toBe('full-video-bytes');
556
+ });
557
+ it('streams the full file to disk when an image client upload carries no context', async ()=>{
558
+ const handler = vi.fn(async (handlerReq)=>{
559
+ expect(handlerReq.headers.get('Range')).toBeNull();
560
+ return new Response(MINIMAL_PNG, {
561
+ headers: {
562
+ 'Content-Type': 'image/png'
563
+ },
564
+ status: 200
565
+ });
566
+ });
567
+ const req = createReq({
568
+ handlers: [
569
+ handler
570
+ ],
571
+ upload: {
572
+ disableLocalStorage: true
573
+ }
574
+ });
575
+ const file = imageFile({
576
+ clientUploadContext: undefined
577
+ });
578
+ const result = await getFileFromClientUpload({
579
+ file,
580
+ req
581
+ });
582
+ expect(handler).toHaveBeenCalledTimes(1);
583
+ expect(result.tempFilePath).toBeDefined();
584
+ tempFilesToRemove.push(result.tempFilePath);
585
+ const written = await fs.readFile(result.tempFilePath);
586
+ expect(written).toEqual(MINIMAL_PNG);
587
+ });
526
588
  });
527
589
 
528
590
  //# sourceMappingURL=getFileFromClientUpload.spec.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/uploads/getFileFromClientUpload.spec.ts"],"sourcesContent":["import type { PayloadRequest } from '../types/index.js'\nimport type { SanitizedUploadConfig } from './types.js'\n\nimport fs from 'fs/promises'\nimport os from 'os'\nimport { afterEach, describe, expect, it, vi } from 'vitest'\n\nimport { HEADER_PROBE_BYTE_LENGTH } from './getFileContentRequirement.js'\nimport { type ClientUploadData, getFileFromClientUpload } from './getFileFromClientUpload.js'\nimport { getImageSize } from './getImageSize.js'\n\nconst MINIMAL_PNG = (() => {\n const signature = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])\n const ihdrData = Buffer.alloc(13)\n ihdrData.writeUInt32BE(400, 0) // width\n ihdrData.writeUInt32BE(300, 4) // height\n ihdrData[8] = 8 // bit depth\n ihdrData[9] = 2 // color type: RGB\n const ihdrChunk = Buffer.concat([\n Buffer.from([0, 0, 0, 13]),\n Buffer.from('IHDR'),\n ihdrData,\n Buffer.alloc(4), // crc (unchecked by dimension probing)\n ])\n return Buffer.concat([signature, ihdrChunk])\n})()\n\nconst tempFilesToRemove: string[] = []\n\nafterEach(async () => {\n vi.unstubAllGlobals()\n for (const tempFilePath of tempFilesToRemove) {\n await fs.rm(tempFilePath, { force: true })\n }\n tempFilesToRemove.length = 0\n})\n\nconst createReq = ({\n handlers,\n upload,\n}: {\n handlers: NonNullable<SanitizedUploadConfig['handlers']>\n upload?: Partial<SanitizedUploadConfig>\n}): PayloadRequest => {\n const request = new Request('http://localhost/api/media')\n const req = request as unknown as PayloadRequest\n\n req.query = {}\n req.payload = {\n collections: {\n media: {\n config: {\n upload: {\n disableLocalStorage: true,\n handlers,\n ...upload,\n },\n },\n },\n },\n config: {\n upload: {},\n },\n logger: { error: vi.fn() },\n } as unknown as PayloadRequest['payload']\n\n return req\n}\n\nconst videoFile = (overrides: Partial<ClientUploadData> = {}): ClientUploadData => ({\n clientUploadContext: { prefix: 'abc' },\n collectionSlug: 'media',\n filename: 'clip.mp4',\n mimeType: 'video/mp4',\n size: 10,\n ...overrides,\n})\n\nconst imageFile = (overrides: Partial<ClientUploadData> = {}): ClientUploadData => ({\n collectionSlug: 'media',\n filename: 'photo.png',\n mimeType: 'image/png',\n size: MINIMAL_PNG.length,\n ...overrides,\n})\n\ndescribe('getFileFromClientUpload', () => {\n it('does not call a handler and returns empty data with an own clientUploadContext property when no content is required', async () => {\n const handler = vi.fn()\n const req = createReq({ handlers: [handler], upload: { disableLocalStorage: true } })\n const file = videoFile()\n\n const result = await getFileFromClientUpload({ file, req })\n\n expect(handler).not.toHaveBeenCalled()\n expect(Object.prototype.hasOwnProperty.call(result, 'clientUploadContext')).toBe(true)\n expect(result.clientUploadContext).toEqual({ prefix: 'abc' })\n expect(result.data.length).toBe(0)\n expect(result.tempFilePath).toBeUndefined()\n expect(result.name).toBe('clip.mp4')\n expect(result.size).toBe(10)\n })\n\n it('requests a bounded byte range and returns dimensions for a header-only image', async () => {\n const handler = vi.fn(async (handlerReq: PayloadRequest) => {\n expect(handlerReq.headers.get('Range')).toBe(`bytes=0-${HEADER_PROBE_BYTE_LENGTH - 1}`)\n return new Response(MINIMAL_PNG, {\n headers: { 'Content-Type': 'image/png' },\n status: 206,\n })\n })\n const req = createReq({ handlers: [handler], upload: { disableLocalStorage: true } })\n const file = imageFile()\n\n const result = await getFileFromClientUpload({ file, req })\n\n expect(result.data.length).toBeLessThanOrEqual(HEADER_PROBE_BYTE_LENGTH)\n expect(result.tempFilePath).toBeUndefined()\n await expect(getImageSize({ file: result })).resolves.toEqual({ height: 300, width: 400 })\n })\n\n it('caps the header read at the boundary and cancels the stream without pulling past it', async () => {\n let hasCancelled = false\n let hasReadPastBoundary = false\n const firstChunk = Buffer.concat([\n MINIMAL_PNG,\n Buffer.alloc(HEADER_PROBE_BYTE_LENGTH - MINIMAL_PNG.length),\n ])\n const stream = new ReadableStream(\n {\n cancel() {\n hasCancelled = true\n },\n pull(controller) {\n hasReadPastBoundary = true\n controller.error(new Error('Read past header boundary'))\n },\n start(controller) {\n controller.enqueue(firstChunk)\n },\n },\n { highWaterMark: 0 },\n )\n\n const handler = vi.fn(\n async () => new Response(stream, { headers: { 'Content-Type': 'image/png' }, status: 206 }),\n )\n const req = createReq({ handlers: [handler], upload: { disableLocalStorage: true } })\n const file = imageFile({ size: HEADER_PROBE_BYTE_LENGTH })\n\n const result = await getFileFromClientUpload({ file, req })\n\n expect(result.data.length).toBe(HEADER_PROBE_BYTE_LENGTH)\n expect(hasCancelled).toBe(true)\n expect(hasReadPastBoundary).toBe(false)\n })\n\n it('lets a handler read native Request properties like signal through the Range-scoped proxy', async () => {\n const handler = vi.fn(async (handlerReq: PayloadRequest) => {\n expect(() => handlerReq.signal).not.toThrow()\n expect(handlerReq.signal).toBeInstanceOf(AbortSignal)\n return new Response(MINIMAL_PNG, {\n headers: { 'Content-Type': 'image/png' },\n status: 206,\n })\n })\n const req = createReq({ handlers: [handler], upload: { disableLocalStorage: true } })\n const file = imageFile()\n\n await getFileFromClientUpload({ file, req })\n\n expect(handler).toHaveBeenCalledTimes(1)\n })\n\n it('falls back to a full fetch when the bounded header cannot be probed for dimensions', async () => {\n const handler = vi\n .fn()\n .mockResolvedValueOnce(\n new Response(Buffer.from('not an image'), {\n headers: { 'Content-Type': 'image/png' },\n status: 206,\n }),\n )\n .mockResolvedValueOnce(\n new Response(MINIMAL_PNG, { headers: { 'Content-Type': 'image/png' }, status: 200 }),\n )\n const req = createReq({ handlers: [handler], upload: { disableLocalStorage: true } })\n const file = imageFile()\n\n const result = await getFileFromClientUpload({ file, req })\n tempFilesToRemove.push(result.tempFilePath!)\n\n expect(handler).toHaveBeenCalledTimes(2)\n expect(result.tempFilePath).toBeDefined()\n expect(result.data.length).toBe(0)\n })\n\n it('streams a full-content response to a temp file without buffering the whole body', async () => {\n const chunks = [\n Buffer.from('chunk-one-'),\n Buffer.from('chunk-two-'),\n Buffer.from('chunk-three'),\n ]\n const stream = new ReadableStream({\n start(controller) {\n for (const chunk of chunks) {\n controller.enqueue(chunk)\n }\n controller.close()\n },\n })\n\n const response = new Response(stream, {\n headers: { 'Content-Type': 'video/mp4' },\n status: 200,\n })\n const arrayBufferTripwire = vi.fn(async () => {\n throw new Error('Unexpected whole-body buffering')\n })\n Object.defineProperty(response, 'arrayBuffer', { value: arrayBufferTripwire })\n\n const handler = vi.fn(async () => response)\n const req = createReq({\n handlers: [handler],\n upload: { disableLocalStorage: true, mimeTypes: ['video/*'] },\n })\n const file = videoFile({ size: 30 })\n\n const result = await getFileFromClientUpload({ file, req })\n tempFilesToRemove.push(result.tempFilePath!)\n\n expect(result.data).toEqual(Buffer.alloc(0))\n expect(arrayBufferTripwire).not.toHaveBeenCalled()\n const written = await fs.readFile(result.tempFilePath!)\n expect(written.toString()).toBe(chunks.map((chunk) => chunk.toString()).join(''))\n })\n\n it('runs every handler and uses the last one that returns a response, matching v3', async () => {\n const firstHandler = vi.fn(async () => new Response(Buffer.from('first-response')))\n const secondHandler = vi.fn(async () => new Response(Buffer.from('second-response')))\n const req = createReq({\n handlers: [firstHandler, secondHandler],\n upload: { disableLocalStorage: true, mimeTypes: ['video/*'] },\n })\n const file = videoFile()\n\n const result = await getFileFromClientUpload({ file, req })\n tempFilesToRemove.push(result.tempFilePath!)\n\n expect(firstHandler).toHaveBeenCalledTimes(1)\n expect(secondHandler).toHaveBeenCalledTimes(1)\n const written = await fs.readFile(result.tempFilePath!)\n expect(written.toString()).toBe('second-response')\n })\n\n it('falls through to the next handler when the first returns nothing', async () => {\n const firstHandler = vi.fn(async () => undefined)\n const secondHandler = vi.fn(async () => new Response(Buffer.alloc(10), { status: 200 }))\n const req = createReq({\n handlers: [firstHandler, secondHandler],\n upload: { disableLocalStorage: true, mimeTypes: ['video/*'] },\n })\n const file = videoFile()\n\n const result = await getFileFromClientUpload({ file, req })\n tempFilesToRemove.push(result.tempFilePath!)\n\n expect(firstHandler).toHaveBeenCalledTimes(1)\n expect(secondHandler).toHaveBeenCalledTimes(1)\n })\n\n it('follows exactly one redirect from a handler response', async () => {\n const redirectTarget = 'http://storage.example.com/file.mp4'\n const handler = vi.fn(\n async () => new Response(null, { headers: { Location: redirectTarget }, status: 302 }),\n )\n const fetchMock = vi.fn(\n async () =>\n new Response(Buffer.alloc(10), { headers: { 'Content-Type': 'video/mp4' }, status: 200 }),\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const req = createReq({\n handlers: [handler],\n upload: { disableLocalStorage: true, mimeTypes: ['video/*'] },\n })\n const file = videoFile()\n\n const result = await getFileFromClientUpload({ file, req })\n tempFilesToRemove.push(result.tempFilePath!)\n\n expect(fetchMock).toHaveBeenCalledTimes(1)\n expect(fetchMock).toHaveBeenCalledWith(redirectTarget)\n })\n\n it('forwards the Range header through a redirect during the bounded header probe', async () => {\n const redirectTarget = 'http://storage.example.com/photo.png'\n const handler = vi.fn(\n async () => new Response(null, { headers: { Location: redirectTarget }, status: 302 }),\n )\n const fetchMock = vi.fn(\n async () =>\n new Response(MINIMAL_PNG, { headers: { 'Content-Type': 'image/png' }, status: 206 }),\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const req = createReq({ handlers: [handler], upload: { disableLocalStorage: true } })\n const file = imageFile()\n\n const result = await getFileFromClientUpload({ file, req })\n\n expect(result.tempFilePath).toBeUndefined()\n expect(fetchMock).toHaveBeenCalledTimes(1)\n expect(fetchMock).toHaveBeenCalledWith(redirectTarget, {\n headers: { Range: `bytes=0-${HEADER_PROBE_BYTE_LENGTH - 1}` },\n })\n })\n\n it('removes the partial temp file when the stream fails before completion', async () => {\n const stream = new ReadableStream({\n pull(controller) {\n controller.error(new Error('stream broke'))\n },\n start(controller) {\n controller.enqueue(Buffer.from('partial-chunk'))\n },\n })\n const handler = vi.fn(\n async () => new Response(stream, { headers: { 'Content-Type': 'video/mp4' }, status: 200 }),\n )\n const req = createReq({\n handlers: [handler],\n upload: { disableLocalStorage: true, mimeTypes: ['video/*'] },\n })\n const file = videoFile()\n\n const tempFileDir = os.tmpdir()\n const filesBefore = new Set(await fs.readdir(tempFileDir))\n\n await expect(getFileFromClientUpload({ file, req })).rejects.toThrow('stream broke')\n\n const filesAfter = await fs.readdir(tempFileDir)\n const leftoverTempFiles = filesAfter.filter(\n (entry) => !filesBefore.has(entry) && entry.startsWith('payload-client-upload-'),\n )\n expect(leftoverTempFiles).toEqual([])\n })\n\n it('requires full content and skips the Range header when the request carries crop or size edits', async () => {\n const handler = vi.fn(async (handlerReq: PayloadRequest) => {\n expect(handlerReq.headers.get('Range')).toBeNull()\n return new Response(MINIMAL_PNG, { headers: { 'Content-Type': 'image/png' }, status: 200 })\n })\n const req = createReq({ handlers: [handler], upload: { disableLocalStorage: true } })\n req.query = { uploadEdits: { heightInPixels: 100, widthInPixels: 100 } }\n const file = imageFile()\n\n const result = await getFileFromClientUpload({ file, req })\n tempFilesToRemove.push(result.tempFilePath!)\n\n expect(handler).toHaveBeenCalledTimes(1)\n expect(result.tempFilePath).toBeDefined()\n expect(result.data.length).toBe(0)\n })\n\n it('treats a null response body as a legitimate zero-byte file', async () => {\n const handler = vi.fn(\n async () => new Response(null, { headers: { 'Content-Type': 'video/mp4' }, status: 200 }),\n )\n const req = createReq({\n handlers: [handler],\n upload: { disableLocalStorage: true, mimeTypes: ['video/*'] },\n })\n const file = videoFile({ size: 0 })\n\n const result = await getFileFromClientUpload({ file, req })\n tempFilesToRemove.push(result.tempFilePath!)\n\n expect(result.tempFilePath).toBeDefined()\n const written = await fs.readFile(result.tempFilePath!)\n expect(written.length).toBe(0)\n })\n})\n"],"names":["fs","os","afterEach","describe","expect","it","vi","HEADER_PROBE_BYTE_LENGTH","getFileFromClientUpload","getImageSize","MINIMAL_PNG","signature","Buffer","from","ihdrData","alloc","writeUInt32BE","ihdrChunk","concat","tempFilesToRemove","unstubAllGlobals","tempFilePath","rm","force","length","createReq","handlers","upload","request","Request","req","query","payload","collections","media","config","disableLocalStorage","logger","error","fn","videoFile","overrides","clientUploadContext","prefix","collectionSlug","filename","mimeType","size","imageFile","handler","file","result","not","toHaveBeenCalled","Object","prototype","hasOwnProperty","call","toBe","toEqual","data","toBeUndefined","name","handlerReq","headers","get","Response","status","toBeLessThanOrEqual","resolves","height","width","hasCancelled","hasReadPastBoundary","firstChunk","stream","ReadableStream","cancel","pull","controller","Error","start","enqueue","highWaterMark","signal","toThrow","toBeInstanceOf","AbortSignal","toHaveBeenCalledTimes","mockResolvedValueOnce","push","toBeDefined","chunks","chunk","close","response","arrayBufferTripwire","defineProperty","value","mimeTypes","written","readFile","toString","map","join","firstHandler","secondHandler","undefined","redirectTarget","Location","fetchMock","stubGlobal","toHaveBeenCalledWith","Range","tempFileDir","tmpdir","filesBefore","Set","readdir","rejects","filesAfter","leftoverTempFiles","filter","entry","has","startsWith","toBeNull","uploadEdits","heightInPixels","widthInPixels"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/uploads/getFileFromClientUpload.spec.ts"],"sourcesContent":["import type { PayloadRequest } from '../types/index.js'\nimport type { SanitizedUploadConfig } from './types.js'\n\nimport fs from 'fs/promises'\nimport os from 'os'\nimport { afterEach, describe, expect, it, vi } from 'vitest'\n\nimport { HEADER_PROBE_BYTE_LENGTH } from './getFileContentRequirement.js'\nimport { type ClientUploadData, getFileFromClientUpload } from './getFileFromClientUpload.js'\nimport { getImageSize } from './getImageSize.js'\n\nconst MINIMAL_PNG = (() => {\n const signature = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])\n const ihdrData = Buffer.alloc(13)\n ihdrData.writeUInt32BE(400, 0) // width\n ihdrData.writeUInt32BE(300, 4) // height\n ihdrData[8] = 8 // bit depth\n ihdrData[9] = 2 // color type: RGB\n const ihdrChunk = Buffer.concat([\n Buffer.from([0, 0, 0, 13]),\n Buffer.from('IHDR'),\n ihdrData,\n Buffer.alloc(4), // crc (unchecked by dimension probing)\n ])\n return Buffer.concat([signature, ihdrChunk])\n})()\n\nconst tempFilesToRemove: string[] = []\n\nafterEach(async () => {\n vi.unstubAllGlobals()\n for (const tempFilePath of tempFilesToRemove) {\n await fs.rm(tempFilePath, { force: true })\n }\n tempFilesToRemove.length = 0\n})\n\nconst createReq = ({\n handlers,\n upload,\n}: {\n handlers: NonNullable<SanitizedUploadConfig['handlers']>\n upload?: Partial<SanitizedUploadConfig>\n}): PayloadRequest => {\n const request = new Request('http://localhost/api/media')\n const req = request as unknown as PayloadRequest\n\n req.query = {}\n req.payload = {\n collections: {\n media: {\n config: {\n upload: {\n disableLocalStorage: true,\n handlers,\n ...upload,\n },\n },\n },\n },\n config: {\n upload: {},\n },\n logger: { error: vi.fn() },\n } as unknown as PayloadRequest['payload']\n\n return req\n}\n\nconst videoFile = (overrides: Partial<ClientUploadData> = {}): ClientUploadData => ({\n clientUploadContext: { prefix: 'abc' },\n collectionSlug: 'media',\n filename: 'clip.mp4',\n mimeType: 'video/mp4',\n size: 10,\n ...overrides,\n})\n\nconst imageFile = (overrides: Partial<ClientUploadData> = {}): ClientUploadData => ({\n clientUploadContext: { prefix: 'abc' },\n collectionSlug: 'media',\n filename: 'photo.png',\n mimeType: 'image/png',\n size: MINIMAL_PNG.length,\n ...overrides,\n})\n\ndescribe('getFileFromClientUpload', () => {\n it('does not call a handler and returns empty data with an own clientUploadContext property when no content is required', async () => {\n const handler = vi.fn()\n const req = createReq({ handlers: [handler], upload: { disableLocalStorage: true } })\n const file = videoFile()\n\n const result = await getFileFromClientUpload({ file, req })\n\n expect(handler).not.toHaveBeenCalled()\n expect(Object.prototype.hasOwnProperty.call(result, 'clientUploadContext')).toBe(true)\n expect(result.clientUploadContext).toEqual({ prefix: 'abc' })\n expect(result.data.length).toBe(0)\n expect(result.tempFilePath).toBeUndefined()\n expect(result.name).toBe('clip.mp4')\n expect(result.size).toBe(10)\n })\n\n it('requests a bounded byte range and returns dimensions for a header-only image', async () => {\n const handler = vi.fn(async (handlerReq: PayloadRequest) => {\n expect(handlerReq.headers.get('Range')).toBe(`bytes=0-${HEADER_PROBE_BYTE_LENGTH - 1}`)\n return new Response(MINIMAL_PNG, {\n headers: { 'Content-Type': 'image/png' },\n status: 206,\n })\n })\n const req = createReq({ handlers: [handler], upload: { disableLocalStorage: true } })\n const file = imageFile()\n\n const result = await getFileFromClientUpload({ file, req })\n\n expect(result.data.length).toBeLessThanOrEqual(HEADER_PROBE_BYTE_LENGTH)\n expect(result.tempFilePath).toBeUndefined()\n await expect(getImageSize({ file: result })).resolves.toEqual({ height: 300, width: 400 })\n })\n\n it('caps the header read at the boundary and cancels the stream without pulling past it', async () => {\n let hasCancelled = false\n let hasReadPastBoundary = false\n const firstChunk = Buffer.concat([\n MINIMAL_PNG,\n Buffer.alloc(HEADER_PROBE_BYTE_LENGTH - MINIMAL_PNG.length),\n ])\n const stream = new ReadableStream(\n {\n cancel() {\n hasCancelled = true\n },\n pull(controller) {\n hasReadPastBoundary = true\n controller.error(new Error('Read past header boundary'))\n },\n start(controller) {\n controller.enqueue(firstChunk)\n },\n },\n { highWaterMark: 0 },\n )\n\n const handler = vi.fn(\n async () => new Response(stream, { headers: { 'Content-Type': 'image/png' }, status: 206 }),\n )\n const req = createReq({ handlers: [handler], upload: { disableLocalStorage: true } })\n const file = imageFile({ size: HEADER_PROBE_BYTE_LENGTH })\n\n const result = await getFileFromClientUpload({ file, req })\n\n expect(result.data.length).toBe(HEADER_PROBE_BYTE_LENGTH)\n expect(hasCancelled).toBe(true)\n expect(hasReadPastBoundary).toBe(false)\n })\n\n it('lets a handler read native Request properties like signal through the Range-scoped proxy', async () => {\n const handler = vi.fn(async (handlerReq: PayloadRequest) => {\n expect(() => handlerReq.signal).not.toThrow()\n expect(handlerReq.signal).toBeInstanceOf(AbortSignal)\n return new Response(MINIMAL_PNG, {\n headers: { 'Content-Type': 'image/png' },\n status: 206,\n })\n })\n const req = createReq({ handlers: [handler], upload: { disableLocalStorage: true } })\n const file = imageFile()\n\n await getFileFromClientUpload({ file, req })\n\n expect(handler).toHaveBeenCalledTimes(1)\n })\n\n it('falls back to a full fetch when the bounded header cannot be probed for dimensions', async () => {\n const handler = vi\n .fn()\n .mockResolvedValueOnce(\n new Response(Buffer.from('not an image'), {\n headers: { 'Content-Type': 'image/png' },\n status: 206,\n }),\n )\n .mockResolvedValueOnce(\n new Response(MINIMAL_PNG, { headers: { 'Content-Type': 'image/png' }, status: 200 }),\n )\n const req = createReq({ handlers: [handler], upload: { disableLocalStorage: true } })\n const file = imageFile()\n\n const result = await getFileFromClientUpload({ file, req })\n tempFilesToRemove.push(result.tempFilePath!)\n\n expect(handler).toHaveBeenCalledTimes(2)\n expect(result.tempFilePath).toBeDefined()\n expect(result.data.length).toBe(0)\n })\n\n it('streams a full-content response to a temp file without buffering the whole body', async () => {\n const chunks = [\n Buffer.from('chunk-one-'),\n Buffer.from('chunk-two-'),\n Buffer.from('chunk-three'),\n ]\n const stream = new ReadableStream({\n start(controller) {\n for (const chunk of chunks) {\n controller.enqueue(chunk)\n }\n controller.close()\n },\n })\n\n const response = new Response(stream, {\n headers: { 'Content-Type': 'video/mp4' },\n status: 200,\n })\n const arrayBufferTripwire = vi.fn(async () => {\n throw new Error('Unexpected whole-body buffering')\n })\n Object.defineProperty(response, 'arrayBuffer', { value: arrayBufferTripwire })\n\n const handler = vi.fn(async () => response)\n const req = createReq({\n handlers: [handler],\n upload: { disableLocalStorage: true, mimeTypes: ['video/*'] },\n })\n const file = videoFile({ size: 30 })\n\n const result = await getFileFromClientUpload({ file, req })\n tempFilesToRemove.push(result.tempFilePath!)\n\n expect(result.data).toEqual(Buffer.alloc(0))\n expect(arrayBufferTripwire).not.toHaveBeenCalled()\n const written = await fs.readFile(result.tempFilePath!)\n expect(written.toString()).toBe(chunks.map((chunk) => chunk.toString()).join(''))\n })\n\n it('runs every handler and uses the last one that returns a response, matching v3', async () => {\n const firstHandler = vi.fn(async () => new Response(Buffer.from('first-response')))\n const secondHandler = vi.fn(async () => new Response(Buffer.from('second-response')))\n const req = createReq({\n handlers: [firstHandler, secondHandler],\n upload: { disableLocalStorage: true, mimeTypes: ['video/*'] },\n })\n const file = videoFile()\n\n const result = await getFileFromClientUpload({ file, req })\n tempFilesToRemove.push(result.tempFilePath!)\n\n expect(firstHandler).toHaveBeenCalledTimes(1)\n expect(secondHandler).toHaveBeenCalledTimes(1)\n const written = await fs.readFile(result.tempFilePath!)\n expect(written.toString()).toBe('second-response')\n })\n\n it('falls through to the next handler when the first returns nothing', async () => {\n const firstHandler = vi.fn(async () => undefined)\n const secondHandler = vi.fn(async () => new Response(Buffer.alloc(10), { status: 200 }))\n const req = createReq({\n handlers: [firstHandler, secondHandler],\n upload: { disableLocalStorage: true, mimeTypes: ['video/*'] },\n })\n const file = videoFile()\n\n const result = await getFileFromClientUpload({ file, req })\n tempFilesToRemove.push(result.tempFilePath!)\n\n expect(firstHandler).toHaveBeenCalledTimes(1)\n expect(secondHandler).toHaveBeenCalledTimes(1)\n })\n\n it('follows exactly one redirect from a handler response', async () => {\n const redirectTarget = 'http://storage.example.com/file.mp4'\n const handler = vi.fn(\n async () => new Response(null, { headers: { Location: redirectTarget }, status: 302 }),\n )\n const fetchMock = vi.fn(\n async () =>\n new Response(Buffer.alloc(10), { headers: { 'Content-Type': 'video/mp4' }, status: 200 }),\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const req = createReq({\n handlers: [handler],\n upload: { disableLocalStorage: true, mimeTypes: ['video/*'] },\n })\n const file = videoFile()\n\n const result = await getFileFromClientUpload({ file, req })\n tempFilesToRemove.push(result.tempFilePath!)\n\n expect(fetchMock).toHaveBeenCalledTimes(1)\n expect(fetchMock).toHaveBeenCalledWith(redirectTarget)\n })\n\n it('forwards the Range header through a redirect during the bounded header probe', async () => {\n const redirectTarget = 'http://storage.example.com/photo.png'\n const handler = vi.fn(\n async () => new Response(null, { headers: { Location: redirectTarget }, status: 302 }),\n )\n const fetchMock = vi.fn(\n async () =>\n new Response(MINIMAL_PNG, { headers: { 'Content-Type': 'image/png' }, status: 206 }),\n )\n vi.stubGlobal('fetch', fetchMock)\n\n const req = createReq({ handlers: [handler], upload: { disableLocalStorage: true } })\n const file = imageFile()\n\n const result = await getFileFromClientUpload({ file, req })\n\n expect(result.tempFilePath).toBeUndefined()\n expect(fetchMock).toHaveBeenCalledTimes(1)\n expect(fetchMock).toHaveBeenCalledWith(redirectTarget, {\n headers: { Range: `bytes=0-${HEADER_PROBE_BYTE_LENGTH - 1}` },\n })\n })\n\n it('removes the partial temp file when the stream fails before completion', async () => {\n const stream = new ReadableStream({\n pull(controller) {\n controller.error(new Error('stream broke'))\n },\n start(controller) {\n controller.enqueue(Buffer.from('partial-chunk'))\n },\n })\n const handler = vi.fn(\n async () => new Response(stream, { headers: { 'Content-Type': 'video/mp4' }, status: 200 }),\n )\n const req = createReq({\n handlers: [handler],\n upload: { disableLocalStorage: true, mimeTypes: ['video/*'] },\n })\n const file = videoFile()\n\n const tempFileDir = os.tmpdir()\n const filesBefore = new Set(await fs.readdir(tempFileDir))\n\n await expect(getFileFromClientUpload({ file, req })).rejects.toThrow('stream broke')\n\n const filesAfter = await fs.readdir(tempFileDir)\n const leftoverTempFiles = filesAfter.filter(\n (entry) => !filesBefore.has(entry) && entry.startsWith('payload-client-upload-'),\n )\n expect(leftoverTempFiles).toEqual([])\n })\n\n it('requires full content and skips the Range header when the request carries crop or size edits', async () => {\n const handler = vi.fn(async (handlerReq: PayloadRequest) => {\n expect(handlerReq.headers.get('Range')).toBeNull()\n return new Response(MINIMAL_PNG, { headers: { 'Content-Type': 'image/png' }, status: 200 })\n })\n const req = createReq({ handlers: [handler], upload: { disableLocalStorage: true } })\n req.query = { uploadEdits: { heightInPixels: 100, widthInPixels: 100 } }\n const file = imageFile()\n\n const result = await getFileFromClientUpload({ file, req })\n tempFilesToRemove.push(result.tempFilePath!)\n\n expect(handler).toHaveBeenCalledTimes(1)\n expect(result.tempFilePath).toBeDefined()\n expect(result.data.length).toBe(0)\n })\n\n it('treats a null response body as a legitimate zero-byte file', async () => {\n const handler = vi.fn(\n async () => new Response(null, { headers: { 'Content-Type': 'video/mp4' }, status: 200 }),\n )\n const req = createReq({\n handlers: [handler],\n upload: { disableLocalStorage: true, mimeTypes: ['video/*'] },\n })\n const file = videoFile({ size: 0 })\n\n const result = await getFileFromClientUpload({ file, req })\n tempFilesToRemove.push(result.tempFilePath!)\n\n expect(result.tempFilePath).toBeDefined()\n const written = await fs.readFile(result.tempFilePath!)\n expect(written.length).toBe(0)\n })\n\n it('streams the full file to disk when a non-image client upload carries no context', async () => {\n const handler = vi.fn(\n async () =>\n new Response(Buffer.from('full-video-bytes'), {\n headers: { 'Content-Type': 'video/mp4' },\n status: 200,\n }),\n )\n const req = createReq({ handlers: [handler], upload: { disableLocalStorage: true } })\n const file = videoFile({ clientUploadContext: undefined })\n\n const result = await getFileFromClientUpload({ file, req })\n\n expect(handler).toHaveBeenCalledTimes(1)\n expect(result.tempFilePath).toBeDefined()\n tempFilesToRemove.push(result.tempFilePath!)\n\n const written = await fs.readFile(result.tempFilePath!)\n expect(written.toString()).toBe('full-video-bytes')\n })\n\n it('streams the full file to disk when an image client upload carries no context', async () => {\n const handler = vi.fn(async (handlerReq: PayloadRequest) => {\n expect(handlerReq.headers.get('Range')).toBeNull()\n return new Response(MINIMAL_PNG, { headers: { 'Content-Type': 'image/png' }, status: 200 })\n })\n const req = createReq({ handlers: [handler], upload: { disableLocalStorage: true } })\n const file = imageFile({ clientUploadContext: undefined })\n\n const result = await getFileFromClientUpload({ file, req })\n\n expect(handler).toHaveBeenCalledTimes(1)\n expect(result.tempFilePath).toBeDefined()\n tempFilesToRemove.push(result.tempFilePath!)\n\n const written = await fs.readFile(result.tempFilePath!)\n expect(written).toEqual(MINIMAL_PNG)\n })\n})\n"],"names":["fs","os","afterEach","describe","expect","it","vi","HEADER_PROBE_BYTE_LENGTH","getFileFromClientUpload","getImageSize","MINIMAL_PNG","signature","Buffer","from","ihdrData","alloc","writeUInt32BE","ihdrChunk","concat","tempFilesToRemove","unstubAllGlobals","tempFilePath","rm","force","length","createReq","handlers","upload","request","Request","req","query","payload","collections","media","config","disableLocalStorage","logger","error","fn","videoFile","overrides","clientUploadContext","prefix","collectionSlug","filename","mimeType","size","imageFile","handler","file","result","not","toHaveBeenCalled","Object","prototype","hasOwnProperty","call","toBe","toEqual","data","toBeUndefined","name","handlerReq","headers","get","Response","status","toBeLessThanOrEqual","resolves","height","width","hasCancelled","hasReadPastBoundary","firstChunk","stream","ReadableStream","cancel","pull","controller","Error","start","enqueue","highWaterMark","signal","toThrow","toBeInstanceOf","AbortSignal","toHaveBeenCalledTimes","mockResolvedValueOnce","push","toBeDefined","chunks","chunk","close","response","arrayBufferTripwire","defineProperty","value","mimeTypes","written","readFile","toString","map","join","firstHandler","secondHandler","undefined","redirectTarget","Location","fetchMock","stubGlobal","toHaveBeenCalledWith","Range","tempFileDir","tmpdir","filesBefore","Set","readdir","rejects","filesAfter","leftoverTempFiles","filter","entry","has","startsWith","toBeNull","uploadEdits","heightInPixels","widthInPixels"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"getFileTypeFallback.d.ts","sourceRoot":"","sources":["../../src/uploads/getFileTypeFallback.ts"],"names":[],"mappings":"AAAA,KAAK,UAAU,GAAG;IAChB,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAiBD,eAAO,MAAM,mBAAmB,SAAU,MAAM,KAAG,UAOlD,CAAA"}
1
+ {"version":3,"file":"getFileTypeFallback.d.ts","sourceRoot":"","sources":["../../src/uploads/getFileTypeFallback.ts"],"names":[],"mappings":"AAEA,KAAK,UAAU,GAAG;IAChB,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,MAAM,CAAA;CACb,CAAA;AAkBD,eAAO,MAAM,mBAAmB,SAAU,MAAM,KAAG,UAOlD,CAAA"}
@@ -1,3 +1,4 @@
1
+ import { getFileExtension, getSanitizedUploadFilename } from './getFileTypeIdentity.js';
1
2
  const extensionMap = {
2
3
  css: 'text/css',
3
4
  csv: 'text/csv',
@@ -7,11 +8,12 @@ const extensionMap = {
7
8
  json: 'application/json',
8
9
  md: 'text/markdown',
9
10
  svg: 'image/svg+xml',
11
+ xhtml: 'application/xhtml+xml',
10
12
  xml: 'application/xml',
11
13
  yml: 'application/x-yaml'
12
14
  };
13
15
  export const getFileTypeFallback = (path)=>{
14
- const ext = path.split('.').pop() || 'txt';
16
+ const ext = getFileExtension(getSanitizedUploadFilename(path)).toLowerCase() || 'txt';
15
17
  return {
16
18
  ext,
17
19
  mime: extensionMap[ext] || 'text/plain'
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/uploads/getFileTypeFallback.ts"],"sourcesContent":["type ReturnType = {\n ext: string\n mime: string\n}\n\nconst extensionMap: {\n [ext: string]: string\n} = {\n css: 'text/css',\n csv: 'text/csv',\n htm: 'text/html',\n html: 'text/html',\n js: 'application/javascript',\n json: 'application/json',\n md: 'text/markdown',\n svg: 'image/svg+xml',\n xml: 'application/xml',\n yml: 'application/x-yaml',\n}\n\nexport const getFileTypeFallback = (path: string): ReturnType => {\n const ext = path.split('.').pop() || 'txt'\n\n return {\n ext,\n mime: extensionMap[ext] || 'text/plain',\n }\n}\n"],"names":["extensionMap","css","csv","htm","html","js","json","md","svg","xml","yml","getFileTypeFallback","path","ext","split","pop","mime"],"mappings":"AAKA,MAAMA,eAEF;IACFC,KAAK;IACLC,KAAK;IACLC,KAAK;IACLC,MAAM;IACNC,IAAI;IACJC,MAAM;IACNC,IAAI;IACJC,KAAK;IACLC,KAAK;IACLC,KAAK;AACP;AAEA,OAAO,MAAMC,sBAAsB,CAACC;IAClC,MAAMC,MAAMD,KAAKE,KAAK,CAAC,KAAKC,GAAG,MAAM;IAErC,OAAO;QACLF;QACAG,MAAMhB,YAAY,CAACa,IAAI,IAAI;IAC7B;AACF,EAAC"}
1
+ {"version":3,"sources":["../../src/uploads/getFileTypeFallback.ts"],"sourcesContent":["import { getFileExtension, getSanitizedUploadFilename } from './getFileTypeIdentity.js'\n\ntype ReturnType = {\n ext: string\n mime: string\n}\n\nconst extensionMap: {\n [ext: string]: string\n} = {\n css: 'text/css',\n csv: 'text/csv',\n htm: 'text/html',\n html: 'text/html',\n js: 'application/javascript',\n json: 'application/json',\n md: 'text/markdown',\n svg: 'image/svg+xml',\n xhtml: 'application/xhtml+xml',\n xml: 'application/xml',\n yml: 'application/x-yaml',\n}\n\nexport const getFileTypeFallback = (path: string): ReturnType => {\n const ext = getFileExtension(getSanitizedUploadFilename(path)).toLowerCase() || 'txt'\n\n return {\n ext,\n mime: extensionMap[ext] || 'text/plain',\n }\n}\n"],"names":["getFileExtension","getSanitizedUploadFilename","extensionMap","css","csv","htm","html","js","json","md","svg","xhtml","xml","yml","getFileTypeFallback","path","ext","toLowerCase","mime"],"mappings":"AAAA,SAASA,gBAAgB,EAAEC,0BAA0B,QAAQ,2BAA0B;AAOvF,MAAMC,eAEF;IACFC,KAAK;IACLC,KAAK;IACLC,KAAK;IACLC,MAAM;IACNC,IAAI;IACJC,MAAM;IACNC,IAAI;IACJC,KAAK;IACLC,OAAO;IACPC,KAAK;IACLC,KAAK;AACP;AAEA,OAAO,MAAMC,sBAAsB,CAACC;IAClC,MAAMC,MAAMhB,iBAAiBC,2BAA2Bc,OAAOE,WAAW,MAAM;IAEhF,OAAO;QACLD;QACAE,MAAMhB,YAAY,CAACc,IAAI,IAAI;IAC7B;AACF,EAAC"}
@@ -0,0 +1,19 @@
1
+ export declare const getFileExtension: (filename: string) => string;
2
+ export declare const getSanitizedUploadFilename: (filename: string, outputExtension?: string) => string;
3
+ export declare const getMimeTypeEssence: (mimeType?: null | string) => string;
4
+ export declare const isSvgMimeType: (mimeType?: null | string) => boolean;
5
+ export declare const isXmlMimeType: (mimeType?: null | string) => boolean;
6
+ export declare const isSvgUpload: ({ filename, mimeType, }: {
7
+ filename: string;
8
+ mimeType?: null | string;
9
+ }) => boolean;
10
+ export declare const isXmlUpload: ({ filename, mimeType, }: {
11
+ filename: string;
12
+ mimeType?: null | string;
13
+ }) => boolean;
14
+ export declare const uploadRequiresServerValidation: (upload: {
15
+ allowRestrictedFileTypes?: boolean;
16
+ filename: string;
17
+ mimeType?: null | string;
18
+ }) => boolean;
19
+ //# sourceMappingURL=getFileTypeIdentity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"getFileTypeIdentity.d.ts","sourceRoot":"","sources":["../../src/uploads/getFileTypeIdentity.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,gBAAgB,aAAc,MAAM,KAAG,MASnD,CAAA;AAED,eAAO,MAAM,0BAA0B,aAAc,MAAM,oBAAoB,MAAM,KAAG,MAOvF,CAAA;AAED,eAAO,MAAM,kBAAkB,cAAe,IAAI,GAAG,MAAM,KAAG,MAQ7D,CAAA;AAED,eAAO,MAAM,aAAa,cAAe,IAAI,GAAG,MAAM,KAAG,OACP,CAAA;AAElD,eAAO,MAAM,aAAa,cAAe,IAAI,GAAG,MAAM,KAAG,OAQxD,CAAA;AAED,eAAO,MAAM,WAAW,4BAGrB;IACD,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,IAAI,GAAG,MAAM,CAAA;CACzB,KAAG,OAEqB,CAAA;AAEzB,eAAO,MAAM,WAAW,4BAGrB;IACD,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,IAAI,GAAG,MAAM,CAAA;CACzB,KAAG,OAEqB,CAAA;AAEzB,eAAO,MAAM,8BAA8B,WAAY;IACrD,wBAAwB,CAAC,EAAE,OAAO,CAAA;IAClC,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,IAAI,GAAG,MAAM,CAAA;CACzB,KAAG,OAA2F,CAAA"}
@@ -0,0 +1,34 @@
1
+ import sanitize from 'sanitize-filename';
2
+ import { sanitizeFilename } from '../utilities/sanitizeFilename.js';
3
+ export const getFileExtension = (filename)=>{
4
+ if (!filename.includes('.')) {
5
+ return '';
6
+ }
7
+ const extension = filename.split('.').pop() || '';
8
+ const [extensionWithoutSuffix = ''] = extension.split(/[?#]/, 1);
9
+ return extensionWithoutSuffix;
10
+ };
11
+ export const getSanitizedUploadFilename = (filename, outputExtension)=>{
12
+ const basename = sanitizeFilename(filename);
13
+ const sourceExtension = getFileExtension(basename);
14
+ const baseFilename = sanitize(basename.substring(0, basename.lastIndexOf('.')) || basename);
15
+ const extension = outputExtension ?? sourceExtension;
16
+ return `${baseFilename}${extension ? `.${extension}` : ''}`;
17
+ };
18
+ export const getMimeTypeEssence = (mimeType)=>{
19
+ if (!mimeType) {
20
+ return '';
21
+ }
22
+ const [essence = ''] = mimeType.split(';', 1);
23
+ return essence.trim().toLowerCase();
24
+ };
25
+ export const isSvgMimeType = (mimeType)=>getMimeTypeEssence(mimeType) === 'image/svg+xml';
26
+ export const isXmlMimeType = (mimeType)=>{
27
+ const mimeTypeEssence = getMimeTypeEssence(mimeType);
28
+ return mimeTypeEssence === 'application/xml' || mimeTypeEssence === 'text/xml' || mimeTypeEssence.endsWith('+xml');
29
+ };
30
+ export const isSvgUpload = ({ filename, mimeType })=>getFileExtension(getSanitizedUploadFilename(filename)).toLowerCase() === 'svg' || isSvgMimeType(mimeType);
31
+ export const isXmlUpload = ({ filename, mimeType })=>getFileExtension(getSanitizedUploadFilename(filename)).toLowerCase() === 'xml' || isXmlMimeType(mimeType);
32
+ export const uploadRequiresServerValidation = (upload)=>!upload.allowRestrictedFileTypes && (isSvgUpload(upload) || isXmlUpload(upload));
33
+
34
+ //# sourceMappingURL=getFileTypeIdentity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/uploads/getFileTypeIdentity.ts"],"sourcesContent":["import sanitize from 'sanitize-filename'\n\nimport { sanitizeFilename } from '../utilities/sanitizeFilename.js'\n\nexport const getFileExtension = (filename: string): string => {\n if (!filename.includes('.')) {\n return ''\n }\n\n const extension = filename.split('.').pop() || ''\n const [extensionWithoutSuffix = ''] = extension.split(/[?#]/, 1)\n\n return extensionWithoutSuffix\n}\n\nexport const getSanitizedUploadFilename = (filename: string, outputExtension?: string): string => {\n const basename = sanitizeFilename(filename)\n const sourceExtension = getFileExtension(basename)\n const baseFilename = sanitize(basename.substring(0, basename.lastIndexOf('.')) || basename)\n const extension = outputExtension ?? sourceExtension\n\n return `${baseFilename}${extension ? `.${extension}` : ''}`\n}\n\nexport const getMimeTypeEssence = (mimeType?: null | string): string => {\n if (!mimeType) {\n return ''\n }\n\n const [essence = ''] = mimeType.split(';', 1)\n\n return essence.trim().toLowerCase()\n}\n\nexport const isSvgMimeType = (mimeType?: null | string): boolean =>\n getMimeTypeEssence(mimeType) === 'image/svg+xml'\n\nexport const isXmlMimeType = (mimeType?: null | string): boolean => {\n const mimeTypeEssence = getMimeTypeEssence(mimeType)\n\n return (\n mimeTypeEssence === 'application/xml' ||\n mimeTypeEssence === 'text/xml' ||\n mimeTypeEssence.endsWith('+xml')\n )\n}\n\nexport const isSvgUpload = ({\n filename,\n mimeType,\n}: {\n filename: string\n mimeType?: null | string\n}): boolean =>\n getFileExtension(getSanitizedUploadFilename(filename)).toLowerCase() === 'svg' ||\n isSvgMimeType(mimeType)\n\nexport const isXmlUpload = ({\n filename,\n mimeType,\n}: {\n filename: string\n mimeType?: null | string\n}): boolean =>\n getFileExtension(getSanitizedUploadFilename(filename)).toLowerCase() === 'xml' ||\n isXmlMimeType(mimeType)\n\nexport const uploadRequiresServerValidation = (upload: {\n allowRestrictedFileTypes?: boolean\n filename: string\n mimeType?: null | string\n}): boolean => !upload.allowRestrictedFileTypes && (isSvgUpload(upload) || isXmlUpload(upload))\n"],"names":["sanitize","sanitizeFilename","getFileExtension","filename","includes","extension","split","pop","extensionWithoutSuffix","getSanitizedUploadFilename","outputExtension","basename","sourceExtension","baseFilename","substring","lastIndexOf","getMimeTypeEssence","mimeType","essence","trim","toLowerCase","isSvgMimeType","isXmlMimeType","mimeTypeEssence","endsWith","isSvgUpload","isXmlUpload","uploadRequiresServerValidation","upload","allowRestrictedFileTypes"],"mappings":"AAAA,OAAOA,cAAc,oBAAmB;AAExC,SAASC,gBAAgB,QAAQ,mCAAkC;AAEnE,OAAO,MAAMC,mBAAmB,CAACC;IAC/B,IAAI,CAACA,SAASC,QAAQ,CAAC,MAAM;QAC3B,OAAO;IACT;IAEA,MAAMC,YAAYF,SAASG,KAAK,CAAC,KAAKC,GAAG,MAAM;IAC/C,MAAM,CAACC,yBAAyB,EAAE,CAAC,GAAGH,UAAUC,KAAK,CAAC,QAAQ;IAE9D,OAAOE;AACT,EAAC;AAED,OAAO,MAAMC,6BAA6B,CAACN,UAAkBO;IAC3D,MAAMC,WAAWV,iBAAiBE;IAClC,MAAMS,kBAAkBV,iBAAiBS;IACzC,MAAME,eAAeb,SAASW,SAASG,SAAS,CAAC,GAAGH,SAASI,WAAW,CAAC,SAASJ;IAClF,MAAMN,YAAYK,mBAAmBE;IAErC,OAAO,GAAGC,eAAeR,YAAY,CAAC,CAAC,EAAEA,WAAW,GAAG,IAAI;AAC7D,EAAC;AAED,OAAO,MAAMW,qBAAqB,CAACC;IACjC,IAAI,CAACA,UAAU;QACb,OAAO;IACT;IAEA,MAAM,CAACC,UAAU,EAAE,CAAC,GAAGD,SAASX,KAAK,CAAC,KAAK;IAE3C,OAAOY,QAAQC,IAAI,GAAGC,WAAW;AACnC,EAAC;AAED,OAAO,MAAMC,gBAAgB,CAACJ,WAC5BD,mBAAmBC,cAAc,gBAAe;AAElD,OAAO,MAAMK,gBAAgB,CAACL;IAC5B,MAAMM,kBAAkBP,mBAAmBC;IAE3C,OACEM,oBAAoB,qBACpBA,oBAAoB,cACpBA,gBAAgBC,QAAQ,CAAC;AAE7B,EAAC;AAED,OAAO,MAAMC,cAAc,CAAC,EAC1BtB,QAAQ,EACRc,QAAQ,EAIT,GACCf,iBAAiBO,2BAA2BN,WAAWiB,WAAW,OAAO,SACzEC,cAAcJ,UAAS;AAEzB,OAAO,MAAMS,cAAc,CAAC,EAC1BvB,QAAQ,EACRc,QAAQ,EAIT,GACCf,iBAAiBO,2BAA2BN,WAAWiB,WAAW,OAAO,SACzEE,cAAcL,UAAS;AAEzB,OAAO,MAAMU,iCAAiC,CAACC,SAIhC,CAACA,OAAOC,wBAAwB,IAAKJ,CAAAA,YAAYG,WAAWF,YAAYE,OAAM,EAAE"}