payload 3.89.0 → 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 (439) 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 +118 -18
  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 +102 -24
  255. package/dist/index.d.ts +3 -1
  256. package/dist/index.d.ts.map +1 -1
  257. package/dist/index.js +2 -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/types/index.d.ts +2 -1
  266. package/dist/queues/config/types/index.d.ts.map +1 -1
  267. package/dist/queues/config/types/index.js.map +1 -1
  268. package/dist/uploads/assertClientUploadAccess.d.ts +7 -0
  269. package/dist/uploads/assertClientUploadAccess.d.ts.map +1 -0
  270. package/dist/uploads/assertClientUploadAccess.js +27 -0
  271. package/dist/uploads/assertClientUploadAccess.js.map +1 -0
  272. package/dist/uploads/assertClientUploadAllowed.d.ts +11 -0
  273. package/dist/uploads/assertClientUploadAllowed.d.ts.map +1 -0
  274. package/dist/uploads/assertClientUploadAllowed.js +46 -0
  275. package/dist/uploads/assertClientUploadAllowed.js.map +1 -0
  276. package/dist/uploads/assertClientUploadAllowed.spec.js +91 -0
  277. package/dist/uploads/assertClientUploadAllowed.spec.js.map +1 -0
  278. package/dist/uploads/checkFileRestrictions.d.ts +4 -1
  279. package/dist/uploads/checkFileRestrictions.d.ts.map +1 -1
  280. package/dist/uploads/checkFileRestrictions.js +66 -26
  281. package/dist/uploads/checkFileRestrictions.js.map +1 -1
  282. package/dist/uploads/clientUploadReceipt.d.ts +23 -0
  283. package/dist/uploads/clientUploadReceipt.d.ts.map +1 -0
  284. package/dist/uploads/clientUploadReceipt.js +55 -0
  285. package/dist/uploads/clientUploadReceipt.js.map +1 -0
  286. package/dist/uploads/deleteAssociatedFiles.d.ts.map +1 -1
  287. package/dist/uploads/deleteAssociatedFiles.js +33 -8
  288. package/dist/uploads/deleteAssociatedFiles.js.map +1 -1
  289. package/dist/uploads/deleteAssociatedFiles.spec.js +124 -0
  290. package/dist/uploads/deleteAssociatedFiles.spec.js.map +1 -0
  291. package/dist/uploads/detectSvgFromXml.d.ts +1 -3
  292. package/dist/uploads/detectSvgFromXml.d.ts.map +1 -1
  293. package/dist/uploads/detectSvgFromXml.js +3 -40
  294. package/dist/uploads/detectSvgFromXml.js.map +1 -1
  295. package/dist/uploads/endpoints/getFile.d.ts.map +1 -1
  296. package/dist/uploads/endpoints/getFile.js +5 -3
  297. package/dist/uploads/endpoints/getFile.js.map +1 -1
  298. package/dist/uploads/fetchAPI-multipart/handlers.d.ts +2 -2
  299. package/dist/uploads/fetchAPI-multipart/handlers.d.ts.map +1 -1
  300. package/dist/uploads/fetchAPI-multipart/handlers.js +18 -5
  301. package/dist/uploads/fetchAPI-multipart/handlers.js.map +1 -1
  302. package/dist/uploads/fetchAPI-multipart/index.d.ts.map +1 -1
  303. package/dist/uploads/fetchAPI-multipart/index.js +24 -2
  304. package/dist/uploads/fetchAPI-multipart/index.js.map +1 -1
  305. package/dist/uploads/fetchAPI-multipart/processMultipart.d.ts +0 -6
  306. package/dist/uploads/fetchAPI-multipart/processMultipart.d.ts.map +1 -1
  307. package/dist/uploads/fetchAPI-multipart/processMultipart.js +146 -126
  308. package/dist/uploads/fetchAPI-multipart/processMultipart.js.map +1 -1
  309. package/dist/uploads/fetchAPI-multipart/processMultipart.spec.js +663 -0
  310. package/dist/uploads/fetchAPI-multipart/processMultipart.spec.js.map +1 -0
  311. package/dist/uploads/generateFileData.d.ts +3 -1
  312. package/dist/uploads/generateFileData.d.ts.map +1 -1
  313. package/dist/uploads/generateFileData.js +29 -16
  314. package/dist/uploads/generateFileData.js.map +1 -1
  315. package/dist/uploads/getBaseFields.d.ts.map +1 -1
  316. package/dist/uploads/getBaseFields.js +3 -1
  317. package/dist/uploads/getBaseFields.js.map +1 -1
  318. package/dist/uploads/getExternalFile.d.ts +1 -1
  319. package/dist/uploads/getExternalFile.d.ts.map +1 -1
  320. package/dist/uploads/getExternalFile.js +60 -15
  321. package/dist/uploads/getExternalFile.js.map +1 -1
  322. package/dist/uploads/getFileTypeFallback.d.ts.map +1 -1
  323. package/dist/uploads/getFileTypeFallback.js +3 -1
  324. package/dist/uploads/getFileTypeFallback.js.map +1 -1
  325. package/dist/uploads/getFileTypeIdentity.d.ts +19 -0
  326. package/dist/uploads/getFileTypeIdentity.d.ts.map +1 -0
  327. package/dist/uploads/getFileTypeIdentity.js +34 -0
  328. package/dist/uploads/getFileTypeIdentity.js.map +1 -0
  329. package/dist/uploads/getFileTypeIdentity.spec.js +43 -0
  330. package/dist/uploads/getFileTypeIdentity.spec.js.map +1 -0
  331. package/dist/uploads/isProcessableImage.d.ts +2 -0
  332. package/dist/uploads/isProcessableImage.d.ts.map +1 -0
  333. package/dist/uploads/isProcessableImage.js +7 -0
  334. package/dist/uploads/isProcessableImage.js.map +1 -0
  335. package/dist/uploads/safeFetch.d.ts.map +1 -1
  336. package/dist/uploads/safeFetch.js +3 -2
  337. package/dist/uploads/safeFetch.js.map +1 -1
  338. package/dist/uploads/sanitizeUploadData.d.ts +26 -0
  339. package/dist/uploads/sanitizeUploadData.d.ts.map +1 -0
  340. package/dist/uploads/sanitizeUploadData.js +159 -0
  341. package/dist/uploads/sanitizeUploadData.js.map +1 -0
  342. package/dist/uploads/sanitizeUploadData.spec.js +255 -0
  343. package/dist/uploads/sanitizeUploadData.spec.js.map +1 -0
  344. package/dist/uploads/types.d.ts +11 -3
  345. package/dist/uploads/types.d.ts.map +1 -1
  346. package/dist/uploads/types.js.map +1 -1
  347. package/dist/uploads/uploadContentSecurityPolicy.d.ts +2 -0
  348. package/dist/uploads/uploadContentSecurityPolicy.d.ts.map +1 -0
  349. package/dist/uploads/uploadContentSecurityPolicy.js +3 -0
  350. package/dist/uploads/uploadContentSecurityPolicy.js.map +1 -0
  351. package/dist/uploads/validateISOBaseMediaFile.d.ts +4 -0
  352. package/dist/uploads/validateISOBaseMediaFile.d.ts.map +1 -0
  353. package/dist/uploads/validateISOBaseMediaFile.js +91 -0
  354. package/dist/uploads/validateISOBaseMediaFile.js.map +1 -0
  355. package/dist/uploads/validateISOBaseMediaFile.spec.js +130 -0
  356. package/dist/uploads/validateISOBaseMediaFile.spec.js.map +1 -0
  357. package/dist/uploads/validateSvg.d.ts +10 -4
  358. package/dist/uploads/validateSvg.d.ts.map +1 -1
  359. package/dist/uploads/validateSvg.js +280 -40
  360. package/dist/uploads/validateSvg.js.map +1 -1
  361. package/dist/uploads/validateSvg.spec.js +123 -0
  362. package/dist/uploads/validateSvg.spec.js.map +1 -0
  363. package/dist/uploads/validateUploadFilename.d.ts +3 -0
  364. package/dist/uploads/validateUploadFilename.d.ts.map +1 -0
  365. package/dist/uploads/validateUploadFilename.js +13 -0
  366. package/dist/uploads/validateUploadFilename.js.map +1 -0
  367. package/dist/uploads/validateUploadFilename.spec.js +24 -0
  368. package/dist/uploads/validateUploadFilename.spec.js.map +1 -0
  369. package/dist/utilities/addDataAndFileToRequest.d.ts.map +1 -1
  370. package/dist/utilities/addDataAndFileToRequest.js +28 -1
  371. package/dist/utilities/addDataAndFileToRequest.js.map +1 -1
  372. package/dist/utilities/addDataAndFileToRequest.spec.js +158 -0
  373. package/dist/utilities/addDataAndFileToRequest.spec.js.map +1 -1
  374. package/dist/utilities/createPayloadRequest.d.ts.map +1 -1
  375. package/dist/utilities/createPayloadRequest.js +2 -1
  376. package/dist/utilities/createPayloadRequest.js.map +1 -1
  377. package/dist/utilities/deepCopyObject.d.ts.map +1 -1
  378. package/dist/utilities/deepCopyObject.js +110 -19
  379. package/dist/utilities/deepCopyObject.js.map +1 -1
  380. package/dist/utilities/deepCopyObject.spec.js +216 -0
  381. package/dist/utilities/deepCopyObject.spec.js.map +1 -0
  382. package/dist/utilities/fieldPath.d.ts +13 -0
  383. package/dist/utilities/fieldPath.d.ts.map +1 -0
  384. package/dist/utilities/fieldPath.js +51 -0
  385. package/dist/utilities/fieldPath.js.map +1 -0
  386. package/dist/utilities/fieldPath.spec.js +155 -0
  387. package/dist/utilities/fieldPath.spec.js.map +1 -0
  388. package/dist/utilities/getEntityPermissions/getEntityPermissions.d.ts.map +1 -1
  389. package/dist/utilities/getEntityPermissions/getEntityPermissions.js +2 -0
  390. package/dist/utilities/getEntityPermissions/getEntityPermissions.js.map +1 -1
  391. package/dist/utilities/getObjectDotNotation.d.ts.map +1 -1
  392. package/dist/utilities/getObjectDotNotation.js +7 -1
  393. package/dist/utilities/getObjectDotNotation.js.map +1 -1
  394. package/dist/utilities/getObjectDotNotation.spec.js +33 -0
  395. package/dist/utilities/getObjectDotNotation.spec.js.map +1 -0
  396. package/dist/utilities/getSafeRedirect.spec.js +14 -0
  397. package/dist/utilities/getSafeRedirect.spec.js.map +1 -1
  398. package/dist/utilities/getTopLevelFieldNames.d.ts +2 -0
  399. package/dist/utilities/getTopLevelFieldNames.d.ts.map +1 -0
  400. package/dist/utilities/getTopLevelFieldNames.js +3 -0
  401. package/dist/utilities/getTopLevelFieldNames.js.map +1 -0
  402. package/dist/utilities/getTopLevelFieldNames.spec.js +18 -0
  403. package/dist/utilities/getTopLevelFieldNames.spec.js.map +1 -0
  404. package/dist/utilities/handleEndpoints.js +1 -1
  405. package/dist/utilities/handleEndpoints.js.map +1 -1
  406. package/dist/utilities/parseParams/index.d.ts.map +1 -1
  407. package/dist/utilities/parseParams/index.js +3 -1
  408. package/dist/utilities/parseParams/index.js.map +1 -1
  409. package/dist/utilities/parseParams/index.spec.js +8 -0
  410. package/dist/utilities/parseParams/index.spec.js.map +1 -1
  411. package/dist/utilities/traverseFields.d.ts.map +1 -1
  412. package/dist/utilities/traverseFields.js +5 -14
  413. package/dist/utilities/traverseFields.js.map +1 -1
  414. package/dist/utilities/unflatten.d.ts.map +1 -1
  415. package/dist/utilities/unflatten.js +21 -9
  416. package/dist/utilities/unflatten.js.map +1 -1
  417. package/dist/utilities/unflatten.spec.js +38 -0
  418. package/dist/utilities/unflatten.spec.js.map +1 -0
  419. package/dist/versions/allLocalesPublicationStatus.d.ts +38 -0
  420. package/dist/versions/allLocalesPublicationStatus.d.ts.map +1 -0
  421. package/dist/versions/allLocalesPublicationStatus.js +57 -0
  422. package/dist/versions/allLocalesPublicationStatus.js.map +1 -0
  423. package/dist/versions/getLatestGlobalVersion.d.ts.map +1 -1
  424. package/dist/versions/getLatestGlobalVersion.js +10 -2
  425. package/dist/versions/getLatestGlobalVersion.js.map +1 -1
  426. package/dist/versions/getRestoredStatusesToAuthorize.d.ts +9 -0
  427. package/dist/versions/getRestoredStatusesToAuthorize.d.ts.map +1 -0
  428. package/dist/versions/getRestoredStatusesToAuthorize.js +22 -0
  429. package/dist/versions/getRestoredStatusesToAuthorize.js.map +1 -0
  430. package/dist/versions/getRestoredStatusesToAuthorize.spec.js +50 -0
  431. package/dist/versions/getRestoredStatusesToAuthorize.spec.js.map +1 -0
  432. package/dist/versions/schedule/job.d.ts +2 -2
  433. package/dist/versions/schedule/job.d.ts.map +1 -1
  434. package/dist/versions/schedule/job.js +11 -7
  435. package/dist/versions/schedule/job.js.map +1 -1
  436. package/dist/versions/schedule/types.d.ts +5 -1
  437. package/dist/versions/schedule/types.d.ts.map +1 -1
  438. package/dist/versions/schedule/types.js.map +1 -1
  439. 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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"}
@@ -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"}
@@ -0,0 +1,43 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { getSanitizedUploadFilename, isXmlMimeType } from './getFileTypeIdentity.js';
3
+ describe('getSanitizedUploadFilename', ()=>{
4
+ it('should use the output extension while preserving the sanitized source basename', ()=>{
5
+ expect(getSanitizedUploadFilename('incoming/image.jpg', 'png')).toBe('image.png');
6
+ });
7
+ });
8
+ describe('isXmlMimeType', ()=>{
9
+ it.each([
10
+ [
11
+ 'application/xml',
12
+ true
13
+ ],
14
+ [
15
+ 'text/xml',
16
+ true
17
+ ],
18
+ [
19
+ 'application/atom+xml',
20
+ true
21
+ ],
22
+ [
23
+ 'image/svg+xml',
24
+ true
25
+ ],
26
+ [
27
+ 'APPLICATION/XML',
28
+ true
29
+ ],
30
+ [
31
+ ' Application/Atom+Xml; Charset=UTF-8 ',
32
+ true
33
+ ],
34
+ [
35
+ 'text/plain',
36
+ false
37
+ ]
38
+ ])('should classify %s as %s', (mimeType, expected)=>{
39
+ expect(isXmlMimeType(mimeType)).toBe(expected);
40
+ });
41
+ });
42
+
43
+ //# sourceMappingURL=getFileTypeIdentity.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/uploads/getFileTypeIdentity.spec.ts"],"sourcesContent":["import { describe, expect, it } from 'vitest'\n\nimport { getSanitizedUploadFilename, isXmlMimeType } from './getFileTypeIdentity.js'\n\ndescribe('getSanitizedUploadFilename', () => {\n it('should use the output extension while preserving the sanitized source basename', () => {\n expect(getSanitizedUploadFilename('incoming/image.jpg', 'png')).toBe('image.png')\n })\n})\n\ndescribe('isXmlMimeType', () => {\n it.each([\n ['application/xml', true],\n ['text/xml', true],\n ['application/atom+xml', true],\n ['image/svg+xml', true],\n ['APPLICATION/XML', true],\n [' Application/Atom+Xml; Charset=UTF-8 ', true],\n ['text/plain', false],\n ])('should classify %s as %s', (mimeType, expected) => {\n expect(isXmlMimeType(mimeType)).toBe(expected)\n })\n})\n"],"names":["describe","expect","it","getSanitizedUploadFilename","isXmlMimeType","toBe","each","mimeType","expected"],"mappings":"AAAA,SAASA,QAAQ,EAAEC,MAAM,EAAEC,EAAE,QAAQ,SAAQ;AAE7C,SAASC,0BAA0B,EAAEC,aAAa,QAAQ,2BAA0B;AAEpFJ,SAAS,8BAA8B;IACrCE,GAAG,kFAAkF;QACnFD,OAAOE,2BAA2B,sBAAsB,QAAQE,IAAI,CAAC;IACvE;AACF;AAEAL,SAAS,iBAAiB;IACxBE,GAAGI,IAAI,CAAC;QACN;YAAC;YAAmB;SAAK;QACzB;YAAC;YAAY;SAAK;QAClB;YAAC;YAAwB;SAAK;QAC9B;YAAC;YAAiB;SAAK;QACvB;YAAC;YAAmB;SAAK;QACzB;YAAC;YAAyC;SAAK;QAC/C;YAAC;YAAc;SAAM;KACtB,EAAE,4BAA4B,CAACC,UAAUC;QACxCP,OAAOG,cAAcG,WAAWF,IAAI,CAACG;IACvC;AACF"}
@@ -0,0 +1,2 @@
1
+ export declare function isProcessableImage(mimeType: string): boolean;
2
+ //# sourceMappingURL=isProcessableImage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"isProcessableImage.d.ts","sourceRoot":"","sources":["../../src/uploads/isProcessableImage.ts"],"names":[],"mappings":"AAGA,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAE5D"}
@@ -0,0 +1,7 @@
1
+ import { canResizeImage } from './canResizeImage.js';
2
+ import { isImage } from './isImage.js';
3
+ export function isProcessableImage(mimeType) {
4
+ return isImage(mimeType) || canResizeImage(mimeType);
5
+ }
6
+
7
+ //# sourceMappingURL=isProcessableImage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/uploads/isProcessableImage.ts"],"sourcesContent":["import { canResizeImage } from './canResizeImage.js'\nimport { isImage } from './isImage.js'\n\nexport function isProcessableImage(mimeType: string): boolean {\n return isImage(mimeType) || canResizeImage(mimeType)\n}\n"],"names":["canResizeImage","isImage","isProcessableImage","mimeType"],"mappings":"AAAA,SAASA,cAAc,QAAQ,sBAAqB;AACpD,SAASC,OAAO,QAAQ,eAAc;AAEtC,OAAO,SAASC,mBAAmBC,QAAgB;IACjD,OAAOF,QAAQE,aAAaH,eAAeG;AAC7C"}
@@ -1 +1 @@
1
- {"version":3,"file":"safeFetch.d.ts","sourceRoot":"","sources":["../../src/uploads/safeFetch.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,KAAK,CAAA;AAE5B,OAAO,EAAS,KAAK,IAAI,WAAW,EAAE,MAAM,QAAQ,CAAA;AAEpD;;GAEG;AACH,eAAO,MAAM,yBAAyB;;CAErC,CAAA;AAgDD;;;;;;GAMG;AACH,eAAO,MAAM,SAAS,4FAAoD,OAAO,CAAC,QAAQ,CA4CzF,CAAA"}
1
+ {"version":3,"file":"safeFetch.d.ts","sourceRoot":"","sources":["../../src/uploads/safeFetch.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,KAAK,CAAA;AAE5B,OAAO,EAAS,KAAK,IAAI,WAAW,EAAE,MAAM,QAAQ,CAAA;AAEpD;;GAEG;AACH,eAAO,MAAM,yBAAyB;;CAErC,CAAA;AAuDD;;;;;;GAMG;AACH,eAAO,MAAM,SAAS,4FAAoD,OAAO,CAAC,QAAQ,CA4CzF,CAAA"}
@@ -6,6 +6,7 @@ import { Agent, fetch as undiciFetch } from 'undici';
6
6
  */ export const _internal_safeFetchGlobal = {
7
7
  lookup
8
8
  };
9
+ const buildBlockedFetchError = (hostname)=>new Error(`Blocked unsafe attempt to ${hostname}. This host resolves to a private or internal address. ` + `If this is a trusted destination, allowlist it via the upload collection's "skipSafeFetch" option ` + `(https://payloadcms.com/docs/upload/overview#skip-safe-fetch).`);
9
10
  const isSafeIp = (ip)=>{
10
11
  try {
11
12
  if (!ip) {
@@ -39,7 +40,7 @@ const ssrfFilterInterceptor = (hostname, options, callback)=>{
39
40
  ];
40
41
  }
41
42
  if (ips.some((ip)=>!isSafeIp(ip))) {
42
- callback(new Error(`Blocked unsafe attempt to ${hostname}`), address, family);
43
+ callback(buildBlockedFetchError(hostname), address, family);
43
44
  return;
44
45
  }
45
46
  callback(null, address, family);
@@ -68,7 +69,7 @@ const safeDispatcher = new Agent({
68
69
  }
69
70
  if (ipaddr.isValid(hostname)) {
70
71
  if (!isSafeIp(hostname)) {
71
- throw new Error(`Blocked unsafe attempt to ${hostname}`);
72
+ throw buildBlockedFetchError(hostname);
72
73
  }
73
74
  }
74
75
  return await undiciFetch(url, {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/uploads/safeFetch.ts"],"sourcesContent":["import type { LookupFunction } from 'net'\n\nimport { lookup } from 'dns'\nimport ipaddr from 'ipaddr.js'\nimport { Agent, fetch as undiciFetch } from 'undici'\n\n/**\n * @internal this is used to mock the IP `lookup` function in integration tests\n */\nexport const _internal_safeFetchGlobal = {\n lookup,\n}\n\nconst isSafeIp = (ip: string) => {\n try {\n if (!ip) {\n return false\n }\n\n if (!ipaddr.isValid(ip)) {\n return false\n }\n\n const parsedIpAddress = ipaddr.parse(ip)\n const range = parsedIpAddress.range()\n if (range !== 'unicast') {\n return false // Private IP Range\n }\n } catch (ignore) {\n return false\n }\n return true\n}\n\nconst ssrfFilterInterceptor: LookupFunction = (hostname, options, callback) => {\n _internal_safeFetchGlobal.lookup(hostname, options, (err, address, family) => {\n if (err) {\n callback(err, address, family)\n } else {\n let ips = [] as string[]\n if (Array.isArray(address)) {\n ips = address.map((a) => a.address)\n } else {\n ips = [address]\n }\n\n if (ips.some((ip) => !isSafeIp(ip))) {\n callback(new Error(`Blocked unsafe attempt to ${hostname}`), address, family)\n return\n }\n\n callback(null, address, family)\n }\n })\n}\n\nconst safeDispatcher = new Agent({\n connect: { lookup: ssrfFilterInterceptor },\n})\n/**\n * A \"safe\" version of undici's fetch that prevents SSRF attacks.\n *\n * - Utilizes a custom dispatcher that filters out requests to unsafe IP addresses.\n * - Validates domain names by resolving them to IP addresses and checking if they're safe.\n * - Undici was used because it supported interceptors as well as \"credentials: include\". Native fetch\n */\nexport const safeFetch = async (...args: Parameters<typeof undiciFetch>): Promise<Response> => {\n const [unverifiedUrl, options] = args\n\n try {\n const url = new URL(unverifiedUrl)\n\n let hostname = url.hostname\n\n // Strip brackets from IPv6 addresses (e.g., \"[::1]\" => \"::1\")\n if (hostname.startsWith('[') && hostname.endsWith(']')) {\n hostname = hostname.slice(1, -1)\n }\n\n if (ipaddr.isValid(hostname)) {\n if (!isSafeIp(hostname)) {\n throw new Error(`Blocked unsafe attempt to ${hostname}`)\n }\n }\n return (await undiciFetch(url, {\n ...options,\n dispatcher: safeDispatcher,\n redirect: 'manual', // Prevent automatic redirects\n })) as unknown as Response\n } catch (error) {\n if (error instanceof Error) {\n if (error.cause instanceof Error && error.cause.message.includes('unsafe')) {\n // Errors thrown from within interceptors always have 'fetch error' as the message\n // The desired message we want to bubble up is in the cause\n throw new Error(error.cause.message)\n } else {\n let stringifiedUrl: string | undefined = undefined\n if (typeof unverifiedUrl === 'string') {\n stringifiedUrl = unverifiedUrl\n } else if (unverifiedUrl instanceof URL) {\n stringifiedUrl = unverifiedUrl.toString()\n } else if (unverifiedUrl instanceof Request) {\n stringifiedUrl = unverifiedUrl.url\n }\n\n throw new Error(`Failed to fetch from ${stringifiedUrl}, ${error.message}`)\n }\n }\n throw error\n }\n}\n"],"names":["lookup","ipaddr","Agent","fetch","undiciFetch","_internal_safeFetchGlobal","isSafeIp","ip","isValid","parsedIpAddress","parse","range","ignore","ssrfFilterInterceptor","hostname","options","callback","err","address","family","ips","Array","isArray","map","a","some","Error","safeDispatcher","connect","safeFetch","args","unverifiedUrl","url","URL","startsWith","endsWith","slice","dispatcher","redirect","error","cause","message","includes","stringifiedUrl","undefined","toString","Request"],"mappings":"AAEA,SAASA,MAAM,QAAQ,MAAK;AAC5B,OAAOC,YAAY,YAAW;AAC9B,SAASC,KAAK,EAAEC,SAASC,WAAW,QAAQ,SAAQ;AAEpD;;CAEC,GACD,OAAO,MAAMC,4BAA4B;IACvCL;AACF,EAAC;AAED,MAAMM,WAAW,CAACC;IAChB,IAAI;QACF,IAAI,CAACA,IAAI;YACP,OAAO;QACT;QAEA,IAAI,CAACN,OAAOO,OAAO,CAACD,KAAK;YACvB,OAAO;QACT;QAEA,MAAME,kBAAkBR,OAAOS,KAAK,CAACH;QACrC,MAAMI,QAAQF,gBAAgBE,KAAK;QACnC,IAAIA,UAAU,WAAW;YACvB,OAAO,MAAM,mBAAmB;;QAClC;IACF,EAAE,OAAOC,QAAQ;QACf,OAAO;IACT;IACA,OAAO;AACT;AAEA,MAAMC,wBAAwC,CAACC,UAAUC,SAASC;IAChEX,0BAA0BL,MAAM,CAACc,UAAUC,SAAS,CAACE,KAAKC,SAASC;QACjE,IAAIF,KAAK;YACPD,SAASC,KAAKC,SAASC;QACzB,OAAO;YACL,IAAIC,MAAM,EAAE;YACZ,IAAIC,MAAMC,OAAO,CAACJ,UAAU;gBAC1BE,MAAMF,QAAQK,GAAG,CAAC,CAACC,IAAMA,EAAEN,OAAO;YACpC,OAAO;gBACLE,MAAM;oBAACF;iBAAQ;YACjB;YAEA,IAAIE,IAAIK,IAAI,CAAC,CAAClB,KAAO,CAACD,SAASC,MAAM;gBACnCS,SAAS,IAAIU,MAAM,CAAC,0BAA0B,EAAEZ,UAAU,GAAGI,SAASC;gBACtE;YACF;YAEAH,SAAS,MAAME,SAASC;QAC1B;IACF;AACF;AAEA,MAAMQ,iBAAiB,IAAIzB,MAAM;IAC/B0B,SAAS;QAAE5B,QAAQa;IAAsB;AAC3C;AACA;;;;;;CAMC,GACD,OAAO,MAAMgB,YAAY,OAAO,GAAGC;IACjC,MAAM,CAACC,eAAehB,QAAQ,GAAGe;IAEjC,IAAI;QACF,MAAME,MAAM,IAAIC,IAAIF;QAEpB,IAAIjB,WAAWkB,IAAIlB,QAAQ;QAE3B,8DAA8D;QAC9D,IAAIA,SAASoB,UAAU,CAAC,QAAQpB,SAASqB,QAAQ,CAAC,MAAM;YACtDrB,WAAWA,SAASsB,KAAK,CAAC,GAAG,CAAC;QAChC;QAEA,IAAInC,OAAOO,OAAO,CAACM,WAAW;YAC5B,IAAI,CAACR,SAASQ,WAAW;gBACvB,MAAM,IAAIY,MAAM,CAAC,0BAA0B,EAAEZ,UAAU;YACzD;QACF;QACA,OAAQ,MAAMV,YAAY4B,KAAK;YAC7B,GAAGjB,OAAO;YACVsB,YAAYV;YACZW,UAAU;QACZ;IACF,EAAE,OAAOC,OAAO;QACd,IAAIA,iBAAiBb,OAAO;YAC1B,IAAIa,MAAMC,KAAK,YAAYd,SAASa,MAAMC,KAAK,CAACC,OAAO,CAACC,QAAQ,CAAC,WAAW;gBAC1E,kFAAkF;gBAClF,2DAA2D;gBAC3D,MAAM,IAAIhB,MAAMa,MAAMC,KAAK,CAACC,OAAO;YACrC,OAAO;gBACL,IAAIE,iBAAqCC;gBACzC,IAAI,OAAOb,kBAAkB,UAAU;oBACrCY,iBAAiBZ;gBACnB,OAAO,IAAIA,yBAAyBE,KAAK;oBACvCU,iBAAiBZ,cAAcc,QAAQ;gBACzC,OAAO,IAAId,yBAAyBe,SAAS;oBAC3CH,iBAAiBZ,cAAcC,GAAG;gBACpC;gBAEA,MAAM,IAAIN,MAAM,CAAC,qBAAqB,EAAEiB,eAAe,EAAE,EAAEJ,MAAME,OAAO,EAAE;YAC5E;QACF;QACA,MAAMF;IACR;AACF,EAAC"}
1
+ {"version":3,"sources":["../../src/uploads/safeFetch.ts"],"sourcesContent":["import type { LookupFunction } from 'net'\n\nimport { lookup } from 'dns'\nimport ipaddr from 'ipaddr.js'\nimport { Agent, fetch as undiciFetch } from 'undici'\n\n/**\n * @internal this is used to mock the IP `lookup` function in integration tests\n */\nexport const _internal_safeFetchGlobal = {\n lookup,\n}\n\nconst buildBlockedFetchError = (hostname: string): Error =>\n new Error(\n `Blocked unsafe attempt to ${hostname}. This host resolves to a private or internal address. ` +\n `If this is a trusted destination, allowlist it via the upload collection's \"skipSafeFetch\" option ` +\n `(https://payloadcms.com/docs/upload/overview#skip-safe-fetch).`,\n )\n\nconst isSafeIp = (ip: string) => {\n try {\n if (!ip) {\n return false\n }\n\n if (!ipaddr.isValid(ip)) {\n return false\n }\n\n const parsedIpAddress = ipaddr.parse(ip)\n const range = parsedIpAddress.range()\n if (range !== 'unicast') {\n return false // Private IP Range\n }\n } catch (ignore) {\n return false\n }\n return true\n}\n\nconst ssrfFilterInterceptor: LookupFunction = (hostname, options, callback) => {\n _internal_safeFetchGlobal.lookup(hostname, options, (err, address, family) => {\n if (err) {\n callback(err, address, family)\n } else {\n let ips = [] as string[]\n if (Array.isArray(address)) {\n ips = address.map((a) => a.address)\n } else {\n ips = [address]\n }\n\n if (ips.some((ip) => !isSafeIp(ip))) {\n callback(buildBlockedFetchError(hostname), address, family)\n return\n }\n\n callback(null, address, family)\n }\n })\n}\n\nconst safeDispatcher = new Agent({\n connect: { lookup: ssrfFilterInterceptor },\n})\n/**\n * A \"safe\" version of undici's fetch that prevents SSRF attacks.\n *\n * - Utilizes a custom dispatcher that filters out requests to unsafe IP addresses.\n * - Validates domain names by resolving them to IP addresses and checking if they're safe.\n * - Undici was used because it supported interceptors as well as \"credentials: include\". Native fetch\n */\nexport const safeFetch = async (...args: Parameters<typeof undiciFetch>): Promise<Response> => {\n const [unverifiedUrl, options] = args\n\n try {\n const url = new URL(unverifiedUrl)\n\n let hostname = url.hostname\n\n // Strip brackets from IPv6 addresses (e.g., \"[::1]\" => \"::1\")\n if (hostname.startsWith('[') && hostname.endsWith(']')) {\n hostname = hostname.slice(1, -1)\n }\n\n if (ipaddr.isValid(hostname)) {\n if (!isSafeIp(hostname)) {\n throw buildBlockedFetchError(hostname)\n }\n }\n return (await undiciFetch(url, {\n ...options,\n dispatcher: safeDispatcher,\n redirect: 'manual', // Prevent automatic redirects\n })) as unknown as Response\n } catch (error) {\n if (error instanceof Error) {\n if (error.cause instanceof Error && error.cause.message.includes('unsafe')) {\n // Errors thrown from within interceptors always have 'fetch error' as the message\n // The desired message we want to bubble up is in the cause\n throw new Error(error.cause.message)\n } else {\n let stringifiedUrl: string | undefined = undefined\n if (typeof unverifiedUrl === 'string') {\n stringifiedUrl = unverifiedUrl\n } else if (unverifiedUrl instanceof URL) {\n stringifiedUrl = unverifiedUrl.toString()\n } else if (unverifiedUrl instanceof Request) {\n stringifiedUrl = unverifiedUrl.url\n }\n\n throw new Error(`Failed to fetch from ${stringifiedUrl}, ${error.message}`)\n }\n }\n throw error\n }\n}\n"],"names":["lookup","ipaddr","Agent","fetch","undiciFetch","_internal_safeFetchGlobal","buildBlockedFetchError","hostname","Error","isSafeIp","ip","isValid","parsedIpAddress","parse","range","ignore","ssrfFilterInterceptor","options","callback","err","address","family","ips","Array","isArray","map","a","some","safeDispatcher","connect","safeFetch","args","unverifiedUrl","url","URL","startsWith","endsWith","slice","dispatcher","redirect","error","cause","message","includes","stringifiedUrl","undefined","toString","Request"],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import type { FlattenedField } from '../fields/config/types.js';
2
+ type Operation = 'create' | 'update';
3
+ export type ExternalUploadSource = {
4
+ filename: string;
5
+ url: string;
6
+ };
7
+ type UploadDataOptions = {
8
+ locale?: string;
9
+ localizedProperties?: Set<string>;
10
+ };
11
+ export declare const getExternalUploadSource: (data: unknown) => ExternalUploadSource | undefined;
12
+ export declare const getUploadDestination: ({ data, file, }: {
13
+ data: unknown;
14
+ file: {
15
+ clientUploadContext?: unknown;
16
+ } | null | undefined;
17
+ }) => {
18
+ objectKey?: string;
19
+ prefix?: string;
20
+ };
21
+ export declare const getLocalizedUploadProperties: (fields: FlattenedField[]) => Set<string>;
22
+ export declare const sanitizeUploadData: <T>(data: T, operation: Operation) => T;
23
+ export declare const mergeUploadDataWithDocument: <T>(data: T, document: unknown, options?: UploadDataOptions) => T;
24
+ export declare const restoreUploadDataFromDocument: <T>(data: T, document: unknown, options?: UploadDataOptions) => T;
25
+ export {};
26
+ //# sourceMappingURL=sanitizeUploadData.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitizeUploadData.d.ts","sourceRoot":"","sources":["../../src/uploads/sanitizeUploadData.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAA;AAI/D,KAAK,SAAS,GAAG,QAAQ,GAAG,QAAQ,CAAA;AAEpC,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,EAAE,MAAM,CAAA;IAChB,GAAG,EAAE,MAAM,CAAA;CACZ,CAAA;AAED,KAAK,iBAAiB,GAAG;IACvB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,mBAAmB,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CAClC,CAAA;AAQD,eAAO,MAAM,uBAAuB,SAAU,OAAO,KAAG,oBAAoB,GAAG,SAS9E,CAAA;AAKD,eAAO,MAAM,oBAAoB,oBAG9B;IACD,IAAI,EAAE,OAAO,CAAA;IACb,IAAI,EAAE;QAAE,mBAAmB,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,GAAG,SAAS,CAAA;CAC3D,KAAG;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAoBxC,CAAA;AAcD,eAAO,MAAM,4BAA4B,WAAY,cAAc,EAAE,KAAG,GAAG,CAAC,MAAM,CAUjF,CAAA;AAqBD,eAAO,MAAM,kBAAkB,GAAI,CAAC,QAAQ,CAAC,aAAa,SAAS,KAAG,CA4BrE,CAAA;AAQD,eAAO,MAAM,2BAA2B,GAAI,CAAC,QACrC,CAAC,YACG,OAAO,YACR,iBAAiB,KACzB,CA8BF,CAAA;AAiBD,eAAO,MAAM,6BAA6B,GAAI,CAAC,QACvC,CAAC,YACG,OAAO,YACR,iBAAiB,KACzB,CAeF,CAAA"}
@@ -0,0 +1,159 @@
1
+ import path from 'path';
2
+ import { APIError } from '../errors/APIError.js';
3
+ const hasOwnProperty = (value, key)=>Object.prototype.hasOwnProperty.call(value, key);
4
+ const isRecord = (value)=>typeof value === 'object' && value !== null && !Array.isArray(value);
5
+ export const getExternalUploadSource = (data)=>{
6
+ if (!isRecord(data) || typeof data.filename !== 'string' || typeof data.url !== 'string') {
7
+ return undefined;
8
+ }
9
+ return {
10
+ filename: data.filename,
11
+ url: data.url
12
+ };
13
+ };
14
+ // Resolves the server-owned upload destination for a document write. For client uploads the prefix
15
+ // and key segment come from the verified upload context; for server uploads the prefix falls back
16
+ // to the submitted document data and there is no key segment.
17
+ export const getUploadDestination = ({ data, file })=>{
18
+ if (!file) {
19
+ return {};
20
+ }
21
+ const submittedPrefix = isRecord(data) && typeof data.prefix === 'string' ? data.prefix : undefined;
22
+ const { clientUploadContext } = file;
23
+ if (!isRecord(clientUploadContext)) {
24
+ return {
25
+ prefix: submittedPrefix
26
+ };
27
+ }
28
+ return {
29
+ objectKey: typeof clientUploadContext._objectKey === 'string' ? clientUploadContext._objectKey : undefined,
30
+ prefix: typeof clientUploadContext.prefix === 'string' ? clientUploadContext.prefix : undefined
31
+ };
32
+ };
33
+ const getDocumentProperty = (document, property, { locale, localizedProperties })=>{
34
+ const value = document[property];
35
+ return locale && localizedProperties?.has(property) && isRecord(value) ? value[locale] : value;
36
+ };
37
+ // Accepts flattened fields so localized identity fields declared inside presentational
38
+ // containers (row/collapsible/unnamed tabs) are still detected — those live at the document
39
+ // root data level but are nested in the config tree, so walking `fields` alone would miss them.
40
+ export const getLocalizedUploadProperties = (fields)=>{
41
+ const localizedProperties = new Set();
42
+ for (const field of fields){
43
+ if ('name' in field && field.localized) {
44
+ localizedProperties.add(field.name);
45
+ }
46
+ }
47
+ return localizedProperties;
48
+ };
49
+ // The submitted filename is stripped by sanitizeUploadData regardless, so this only exists to
50
+ // reject path-traversal attempts loudly (400) instead of silently dropping them. Non-string
51
+ // values are ignored here because they are removed by the caller anyway.
52
+ const validateFilename = (filename)=>{
53
+ if (typeof filename !== 'string') {
54
+ return;
55
+ }
56
+ const pathSegments = filename.replace(/\\/g, '/').split('/');
57
+ if (path.posix.isAbsolute(filename) || path.win32.isAbsolute(filename) || pathSegments.includes('..')) {
58
+ throw new APIError('Invalid filename.', 400);
59
+ }
60
+ };
61
+ export const sanitizeUploadData = (data, operation)=>{
62
+ if (!isRecord(data)) {
63
+ return data;
64
+ }
65
+ validateFilename(data.filename);
66
+ if (isRecord(data.sizes)) {
67
+ for (const sizeData of Object.values(data.sizes)){
68
+ if (isRecord(sizeData)) {
69
+ validateFilename(sizeData.filename);
70
+ }
71
+ }
72
+ }
73
+ const sanitizedData = {
74
+ ...data
75
+ };
76
+ delete sanitizedData.filename;
77
+ delete sanitizedData.sizes;
78
+ delete sanitizedData.url;
79
+ // Server-owned; never accepted from the caller.
80
+ delete sanitizedData._objectKey;
81
+ // On update, `prefix` is restored from the stored document (file identity); kept on create.
82
+ if (operation === 'update') {
83
+ delete sanitizedData.prefix;
84
+ }
85
+ return sanitizedData;
86
+ };
87
+ // Restores only the identity fields that select/serve a stored object (`filename`, `prefix`,
88
+ // `url`, `sizes`). Other generated-but-derived fields (`thumbnailURL`, `filesize`, `height`,
89
+ // `width`, `mimeType`, `focalX`/`focalY`) are intentionally left as submitted: `focalX`/`focalY`
90
+ // are user-editable crop input, and the rest are regenerated by generateFileData when a file is
91
+ // written. Compare with restoreUploadDataFromDocument (version restore), which re-binds the full
92
+ // tuple below.
93
+ export const mergeUploadDataWithDocument = (data, document, options = {})=>{
94
+ if (!isRecord(data) || !isRecord(document)) {
95
+ return data;
96
+ }
97
+ const mergedData = {
98
+ ...data
99
+ };
100
+ for (const property of [
101
+ '_objectKey',
102
+ 'filename',
103
+ 'prefix',
104
+ 'url'
105
+ ]){
106
+ if (!hasOwnProperty(data, property) && hasOwnProperty(document, property)) {
107
+ mergedData[property] = getDocumentProperty(document, property, options);
108
+ }
109
+ }
110
+ const documentSizes = getDocumentProperty(document, 'sizes', options);
111
+ if (!hasOwnProperty(data, 'sizes') && hasOwnProperty(document, 'sizes')) {
112
+ mergedData.sizes = documentSizes;
113
+ } else if (isRecord(data.sizes) && isRecord(documentSizes)) {
114
+ const mergedSizes = {
115
+ ...documentSizes
116
+ };
117
+ for (const [sizeName, sizeData] of Object.entries(data.sizes)){
118
+ const documentSize = documentSizes[sizeName];
119
+ mergedSizes[sizeName] = isRecord(sizeData) && isRecord(documentSize) ? {
120
+ ...documentSize,
121
+ ...sizeData
122
+ } : sizeData;
123
+ }
124
+ mergedData.sizes = mergedSizes;
125
+ }
126
+ return mergedData;
127
+ };
128
+ const uploadDerivedProperties = [
129
+ '_objectKey',
130
+ 'filename',
131
+ 'filesize',
132
+ 'focalX',
133
+ 'focalY',
134
+ 'height',
135
+ 'mimeType',
136
+ 'prefix',
137
+ 'sizes',
138
+ 'thumbnailURL',
139
+ 'url',
140
+ 'width'
141
+ ];
142
+ export const restoreUploadDataFromDocument = (data, document, options = {})=>{
143
+ if (!isRecord(data) || !isRecord(document)) {
144
+ return data;
145
+ }
146
+ const restoredData = {
147
+ ...data
148
+ };
149
+ for (const property of uploadDerivedProperties){
150
+ if (hasOwnProperty(document, property)) {
151
+ restoredData[property] = getDocumentProperty(document, property, options);
152
+ } else {
153
+ delete restoredData[property];
154
+ }
155
+ }
156
+ return restoredData;
157
+ };
158
+
159
+ //# sourceMappingURL=sanitizeUploadData.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/uploads/sanitizeUploadData.ts"],"sourcesContent":["import path from 'path'\n\nimport type { FlattenedField } from '../fields/config/types.js'\n\nimport { APIError } from '../errors/APIError.js'\n\ntype Operation = 'create' | 'update'\n\nexport type ExternalUploadSource = {\n filename: string\n url: string\n}\n\ntype UploadDataOptions = {\n locale?: string\n localizedProperties?: Set<string>\n}\n\nconst hasOwnProperty = (value: Record<string, unknown>, key: string): boolean =>\n Object.prototype.hasOwnProperty.call(value, key)\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value)\n\nexport const getExternalUploadSource = (data: unknown): ExternalUploadSource | undefined => {\n if (!isRecord(data) || typeof data.filename !== 'string' || typeof data.url !== 'string') {\n return undefined\n }\n\n return {\n filename: data.filename,\n url: data.url,\n }\n}\n\n// Resolves the server-owned upload destination for a document write. For client uploads the prefix\n// and key segment come from the verified upload context; for server uploads the prefix falls back\n// to the submitted document data and there is no key segment.\nexport const getUploadDestination = ({\n data,\n file,\n}: {\n data: unknown\n file: { clientUploadContext?: unknown } | null | undefined\n}): { objectKey?: string; prefix?: string } => {\n if (!file) {\n return {}\n }\n\n const submittedPrefix =\n isRecord(data) && typeof data.prefix === 'string' ? data.prefix : undefined\n const { clientUploadContext } = file\n\n if (!isRecord(clientUploadContext)) {\n return { prefix: submittedPrefix }\n }\n\n return {\n objectKey:\n typeof clientUploadContext._objectKey === 'string'\n ? clientUploadContext._objectKey\n : undefined,\n prefix: typeof clientUploadContext.prefix === 'string' ? clientUploadContext.prefix : undefined,\n }\n}\n\nconst getDocumentProperty = (\n document: Record<string, unknown>,\n property: string,\n { locale, localizedProperties }: UploadDataOptions,\n): unknown => {\n const value = document[property]\n return locale && localizedProperties?.has(property) && isRecord(value) ? value[locale] : value\n}\n\n// Accepts flattened fields so localized identity fields declared inside presentational\n// containers (row/collapsible/unnamed tabs) are still detected — those live at the document\n// root data level but are nested in the config tree, so walking `fields` alone would miss them.\nexport const getLocalizedUploadProperties = (fields: FlattenedField[]): Set<string> => {\n const localizedProperties = new Set<string>()\n\n for (const field of fields) {\n if ('name' in field && (field as { localized?: boolean }).localized) {\n localizedProperties.add(field.name)\n }\n }\n\n return localizedProperties\n}\n\n// The submitted filename is stripped by sanitizeUploadData regardless, so this only exists to\n// reject path-traversal attempts loudly (400) instead of silently dropping them. Non-string\n// values are ignored here because they are removed by the caller anyway.\nconst validateFilename = (filename: unknown): void => {\n if (typeof filename !== 'string') {\n return\n }\n\n const pathSegments = filename.replace(/\\\\/g, '/').split('/')\n\n if (\n path.posix.isAbsolute(filename) ||\n path.win32.isAbsolute(filename) ||\n pathSegments.includes('..')\n ) {\n throw new APIError('Invalid filename.', 400)\n }\n}\n\nexport const sanitizeUploadData = <T>(data: T, operation: Operation): T => {\n if (!isRecord(data)) {\n return data\n }\n\n validateFilename(data.filename)\n\n if (isRecord(data.sizes)) {\n for (const sizeData of Object.values(data.sizes)) {\n if (isRecord(sizeData)) {\n validateFilename(sizeData.filename)\n }\n }\n }\n\n const sanitizedData: Record<string, unknown> = { ...data }\n delete sanitizedData.filename\n delete sanitizedData.sizes\n delete sanitizedData.url\n // Server-owned; never accepted from the caller.\n delete sanitizedData._objectKey\n\n // On update, `prefix` is restored from the stored document (file identity); kept on create.\n if (operation === 'update') {\n delete sanitizedData.prefix\n }\n\n return sanitizedData as T\n}\n\n// Restores only the identity fields that select/serve a stored object (`filename`, `prefix`,\n// `url`, `sizes`). Other generated-but-derived fields (`thumbnailURL`, `filesize`, `height`,\n// `width`, `mimeType`, `focalX`/`focalY`) are intentionally left as submitted: `focalX`/`focalY`\n// are user-editable crop input, and the rest are regenerated by generateFileData when a file is\n// written. Compare with restoreUploadDataFromDocument (version restore), which re-binds the full\n// tuple below.\nexport const mergeUploadDataWithDocument = <T>(\n data: T,\n document: unknown,\n options: UploadDataOptions = {},\n): T => {\n if (!isRecord(data) || !isRecord(document)) {\n return data\n }\n\n const mergedData: Record<string, unknown> = { ...data }\n\n for (const property of ['_objectKey', 'filename', 'prefix', 'url']) {\n if (!hasOwnProperty(data, property) && hasOwnProperty(document, property)) {\n mergedData[property] = getDocumentProperty(document, property, options)\n }\n }\n\n const documentSizes = getDocumentProperty(document, 'sizes', options)\n if (!hasOwnProperty(data, 'sizes') && hasOwnProperty(document, 'sizes')) {\n mergedData.sizes = documentSizes\n } else if (isRecord(data.sizes) && isRecord(documentSizes)) {\n const mergedSizes: Record<string, unknown> = { ...documentSizes }\n\n for (const [sizeName, sizeData] of Object.entries(data.sizes)) {\n const documentSize = documentSizes[sizeName]\n\n mergedSizes[sizeName] =\n isRecord(sizeData) && isRecord(documentSize) ? { ...documentSize, ...sizeData } : sizeData\n }\n\n mergedData.sizes = mergedSizes\n }\n\n return mergedData as T\n}\n\nconst uploadDerivedProperties = [\n '_objectKey',\n 'filename',\n 'filesize',\n 'focalX',\n 'focalY',\n 'height',\n 'mimeType',\n 'prefix',\n 'sizes',\n 'thumbnailURL',\n 'url',\n 'width',\n] as const\n\nexport const restoreUploadDataFromDocument = <T>(\n data: T,\n document: unknown,\n options: UploadDataOptions = {},\n): T => {\n if (!isRecord(data) || !isRecord(document)) {\n return data\n }\n\n const restoredData: Record<string, unknown> = { ...data }\n for (const property of uploadDerivedProperties) {\n if (hasOwnProperty(document, property)) {\n restoredData[property] = getDocumentProperty(document, property, options)\n } else {\n delete restoredData[property]\n }\n }\n\n return restoredData as T\n}\n"],"names":["path","APIError","hasOwnProperty","value","key","Object","prototype","call","isRecord","Array","isArray","getExternalUploadSource","data","filename","url","undefined","getUploadDestination","file","submittedPrefix","prefix","clientUploadContext","objectKey","_objectKey","getDocumentProperty","document","property","locale","localizedProperties","has","getLocalizedUploadProperties","fields","Set","field","localized","add","name","validateFilename","pathSegments","replace","split","posix","isAbsolute","win32","includes","sanitizeUploadData","operation","sizes","sizeData","values","sanitizedData","mergeUploadDataWithDocument","options","mergedData","documentSizes","mergedSizes","sizeName","entries","documentSize","uploadDerivedProperties","restoreUploadDataFromDocument","restoredData"],"mappings":"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"}