@strapi/upload 5.37.1 → 5.38.1

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 (536) hide show
  1. package/dist/admin/ai/components/AIAssetCard.js.map +1 -1
  2. package/dist/admin/ai/components/AIAssetCard.mjs +1 -1
  3. package/dist/admin/ai/components/AIAssetCard.mjs.map +1 -1
  4. package/dist/admin/ai/components/AIUploadModal.js.map +1 -1
  5. package/dist/admin/ai/components/AIUploadModal.mjs +1 -1
  6. package/dist/admin/ai/components/AIUploadModal.mjs.map +1 -1
  7. package/dist/admin/components/AssetCard/AssetCard.js.map +1 -1
  8. package/dist/admin/components/AssetCard/AssetCard.mjs.map +1 -1
  9. package/dist/admin/components/AssetCard/AssetCardBase.js.map +1 -1
  10. package/dist/admin/components/AssetCard/AssetCardBase.mjs.map +1 -1
  11. package/dist/admin/components/AssetCard/AudioAssetCard.js.map +1 -1
  12. package/dist/admin/components/AssetCard/AudioAssetCard.mjs.map +1 -1
  13. package/dist/admin/components/AssetCard/AudioPreview.js.map +1 -1
  14. package/dist/admin/components/AssetCard/AudioPreview.mjs.map +1 -1
  15. package/dist/admin/components/AssetCard/DocAssetCard.js.map +1 -1
  16. package/dist/admin/components/AssetCard/DocAssetCard.mjs.map +1 -1
  17. package/dist/admin/components/AssetCard/ImageAssetCard.js.map +1 -1
  18. package/dist/admin/components/AssetCard/ImageAssetCard.mjs.map +1 -1
  19. package/dist/admin/components/AssetCard/UploadingAssetCard.js.map +1 -1
  20. package/dist/admin/components/AssetCard/UploadingAssetCard.mjs.map +1 -1
  21. package/dist/admin/components/AssetCard/VideoAssetCard.js.map +1 -1
  22. package/dist/admin/components/AssetCard/VideoAssetCard.mjs.map +1 -1
  23. package/dist/admin/components/AssetCard/VideoPreview.js.map +1 -1
  24. package/dist/admin/components/AssetCard/VideoPreview.mjs.map +1 -1
  25. package/dist/admin/components/AssetDialog/AssetDialog.js.map +1 -1
  26. package/dist/admin/components/AssetDialog/AssetDialog.mjs.map +1 -1
  27. package/dist/admin/components/AssetDialog/BrowseStep/BrowseStep.js +1 -1
  28. package/dist/admin/components/AssetDialog/BrowseStep/BrowseStep.js.map +1 -1
  29. package/dist/admin/components/AssetDialog/BrowseStep/BrowseStep.mjs +1 -1
  30. package/dist/admin/components/AssetDialog/BrowseStep/BrowseStep.mjs.map +1 -1
  31. package/dist/admin/components/AssetDialog/BrowseStep/Filters.js.map +1 -1
  32. package/dist/admin/components/AssetDialog/BrowseStep/Filters.mjs.map +1 -1
  33. package/dist/admin/components/AssetDialog/BrowseStep/PageSize.js.map +1 -1
  34. package/dist/admin/components/AssetDialog/BrowseStep/PageSize.mjs.map +1 -1
  35. package/dist/admin/components/AssetDialog/BrowseStep/PaginationFooter/Pagination.js.map +1 -1
  36. package/dist/admin/components/AssetDialog/BrowseStep/PaginationFooter/Pagination.mjs.map +1 -1
  37. package/dist/admin/components/AssetDialog/BrowseStep/PaginationFooter/PaginationFooter.js.map +1 -1
  38. package/dist/admin/components/AssetDialog/BrowseStep/PaginationFooter/PaginationFooter.mjs.map +1 -1
  39. package/dist/admin/components/AssetDialog/BrowseStep/SearchAsset/SearchAsset.js.map +1 -1
  40. package/dist/admin/components/AssetDialog/BrowseStep/SearchAsset/SearchAsset.mjs.map +1 -1
  41. package/dist/admin/components/AssetDialog/BrowseStep/utils/isSelectable.js.map +1 -1
  42. package/dist/admin/components/AssetDialog/BrowseStep/utils/isSelectable.mjs.map +1 -1
  43. package/dist/admin/components/AssetDialog/DialogFooter.js.map +1 -1
  44. package/dist/admin/components/AssetDialog/DialogFooter.mjs.map +1 -1
  45. package/dist/admin/components/AssetDialog/SelectedStep/SelectedStep.js.map +1 -1
  46. package/dist/admin/components/AssetDialog/SelectedStep/SelectedStep.mjs.map +1 -1
  47. package/dist/admin/components/AssetGridList/AssetGridList.js.map +1 -1
  48. package/dist/admin/components/AssetGridList/AssetGridList.mjs.map +1 -1
  49. package/dist/admin/components/AssetGridList/Draggable.js.map +1 -1
  50. package/dist/admin/components/AssetGridList/Draggable.mjs.map +1 -1
  51. package/dist/admin/components/Breadcrumbs/Breadcrumbs.js.map +1 -1
  52. package/dist/admin/components/Breadcrumbs/Breadcrumbs.mjs.map +1 -1
  53. package/dist/admin/components/Breadcrumbs/CrumbSimpleMenuAsync.js.map +1 -1
  54. package/dist/admin/components/Breadcrumbs/CrumbSimpleMenuAsync.mjs.map +1 -1
  55. package/dist/admin/components/BulkMoveDialog/BulkMoveDialog.js.map +1 -1
  56. package/dist/admin/components/BulkMoveDialog/BulkMoveDialog.mjs.map +1 -1
  57. package/dist/admin/components/ContextInfo/ContextInfo.js.map +1 -1
  58. package/dist/admin/components/ContextInfo/ContextInfo.mjs.map +1 -1
  59. package/dist/admin/components/CopyLinkButton/CopyLinkButton.js.map +1 -1
  60. package/dist/admin/components/CopyLinkButton/CopyLinkButton.mjs.map +1 -1
  61. package/dist/admin/components/EditAssetDialog/DialogHeader.js.map +1 -1
  62. package/dist/admin/components/EditAssetDialog/DialogHeader.mjs.map +1 -1
  63. package/dist/admin/components/EditAssetDialog/EditAssetContent.js.map +1 -1
  64. package/dist/admin/components/EditAssetDialog/EditAssetContent.mjs.map +1 -1
  65. package/dist/admin/components/EditAssetDialog/PreviewBox/AssetPreview.js +1 -0
  66. package/dist/admin/components/EditAssetDialog/PreviewBox/AssetPreview.js.map +1 -1
  67. package/dist/admin/components/EditAssetDialog/PreviewBox/AssetPreview.mjs +1 -0
  68. package/dist/admin/components/EditAssetDialog/PreviewBox/AssetPreview.mjs.map +1 -1
  69. package/dist/admin/components/EditAssetDialog/PreviewBox/CroppingActions.js.map +1 -1
  70. package/dist/admin/components/EditAssetDialog/PreviewBox/CroppingActions.mjs.map +1 -1
  71. package/dist/admin/components/EditAssetDialog/PreviewBox/FocalPointActions.js.map +1 -1
  72. package/dist/admin/components/EditAssetDialog/PreviewBox/FocalPointActions.mjs.map +1 -1
  73. package/dist/admin/components/EditAssetDialog/PreviewBox/PreviewBox.js +1 -4
  74. package/dist/admin/components/EditAssetDialog/PreviewBox/PreviewBox.js.map +1 -1
  75. package/dist/admin/components/EditAssetDialog/PreviewBox/PreviewBox.mjs +1 -4
  76. package/dist/admin/components/EditAssetDialog/PreviewBox/PreviewBox.mjs.map +1 -1
  77. package/dist/admin/components/EditAssetDialog/PreviewBox/PreviewComponents.js.map +1 -1
  78. package/dist/admin/components/EditAssetDialog/PreviewBox/PreviewComponents.mjs.map +1 -1
  79. package/dist/admin/components/EditAssetDialog/RemoveAssetDialog.js.map +1 -1
  80. package/dist/admin/components/EditAssetDialog/RemoveAssetDialog.mjs.map +1 -1
  81. package/dist/admin/components/EditAssetDialog/ReplaceMediaButton.js.map +1 -1
  82. package/dist/admin/components/EditAssetDialog/ReplaceMediaButton.mjs.map +1 -1
  83. package/dist/admin/components/EditFolderDialog/EditFolderDialog.js.map +1 -1
  84. package/dist/admin/components/EditFolderDialog/EditFolderDialog.mjs.map +1 -1
  85. package/dist/admin/components/EditFolderDialog/ModalHeader/ModalHeader.js.map +1 -1
  86. package/dist/admin/components/EditFolderDialog/ModalHeader/ModalHeader.mjs.map +1 -1
  87. package/dist/admin/components/EditFolderDialog/RemoveFolderDialog.js.map +1 -1
  88. package/dist/admin/components/EditFolderDialog/RemoveFolderDialog.mjs.map +1 -1
  89. package/dist/admin/components/EmptyAssets/EmptyAssetGrid.js.map +1 -1
  90. package/dist/admin/components/EmptyAssets/EmptyAssetGrid.mjs.map +1 -1
  91. package/dist/admin/components/EmptyAssets/EmptyAssets.js.map +1 -1
  92. package/dist/admin/components/EmptyAssets/EmptyAssets.mjs.map +1 -1
  93. package/dist/admin/components/FilterList/FilterList.js.map +1 -1
  94. package/dist/admin/components/FilterList/FilterList.mjs.map +1 -1
  95. package/dist/admin/components/FilterList/FilterTag.js.map +1 -1
  96. package/dist/admin/components/FilterList/FilterTag.mjs.map +1 -1
  97. package/dist/admin/components/FilterPopover/FilterPopover.js.map +1 -1
  98. package/dist/admin/components/FilterPopover/FilterPopover.mjs.map +1 -1
  99. package/dist/admin/components/FilterPopover/FilterValueInput.js.map +1 -1
  100. package/dist/admin/components/FilterPopover/FilterValueInput.mjs.map +1 -1
  101. package/dist/admin/components/FilterPopover/utils/getFilterList.js.map +1 -1
  102. package/dist/admin/components/FilterPopover/utils/getFilterList.mjs.map +1 -1
  103. package/dist/admin/components/FolderCard/FolderCard/FolderCard.js.map +1 -1
  104. package/dist/admin/components/FolderCard/FolderCard/FolderCard.mjs.map +1 -1
  105. package/dist/admin/components/FolderCard/FolderCardBody/FolderCardBody.js.map +1 -1
  106. package/dist/admin/components/FolderCard/FolderCardBody/FolderCardBody.mjs.map +1 -1
  107. package/dist/admin/components/FolderCard/FolderCardBodyAction/FolderCardBodyAction.js.map +1 -1
  108. package/dist/admin/components/FolderCard/FolderCardBodyAction/FolderCardBodyAction.mjs.map +1 -1
  109. package/dist/admin/components/FolderCard/FolderCardCheckbox/FolderCardCheckbox.js.map +1 -1
  110. package/dist/admin/components/FolderCard/FolderCardCheckbox/FolderCardCheckbox.mjs.map +1 -1
  111. package/dist/admin/components/FolderCard/contexts/FolderCard.js.map +1 -1
  112. package/dist/admin/components/FolderCard/contexts/FolderCard.mjs.map +1 -1
  113. package/dist/admin/components/FolderGridList/FolderGridList.js.map +1 -1
  114. package/dist/admin/components/FolderGridList/FolderGridList.mjs.map +1 -1
  115. package/dist/admin/components/MediaLibraryDialog/MediaLibraryDialog.js.map +1 -1
  116. package/dist/admin/components/MediaLibraryDialog/MediaLibraryDialog.mjs.map +1 -1
  117. package/dist/admin/components/MediaLibraryInput/Carousel/CarouselAsset.js.map +1 -1
  118. package/dist/admin/components/MediaLibraryInput/Carousel/CarouselAsset.mjs.map +1 -1
  119. package/dist/admin/components/MediaLibraryInput/Carousel/CarouselAssetActions.js.map +1 -1
  120. package/dist/admin/components/MediaLibraryInput/Carousel/CarouselAssetActions.mjs.map +1 -1
  121. package/dist/admin/components/MediaLibraryInput/Carousel/CarouselAssets.js.map +1 -1
  122. package/dist/admin/components/MediaLibraryInput/Carousel/CarouselAssets.mjs.map +1 -1
  123. package/dist/admin/components/MediaLibraryInput/Carousel/EmptyStateAsset.js.map +1 -1
  124. package/dist/admin/components/MediaLibraryInput/Carousel/EmptyStateAsset.mjs.map +1 -1
  125. package/dist/admin/components/MediaLibraryInput/MediaLibraryInput.js.map +1 -1
  126. package/dist/admin/components/MediaLibraryInput/MediaLibraryInput.mjs.map +1 -1
  127. package/dist/admin/components/SelectTree/Option.js.map +1 -1
  128. package/dist/admin/components/SelectTree/Option.mjs.map +1 -1
  129. package/dist/admin/components/SelectTree/SelectTree.js.map +1 -1
  130. package/dist/admin/components/SelectTree/SelectTree.mjs +1 -1
  131. package/dist/admin/components/SelectTree/SelectTree.mjs.map +1 -1
  132. package/dist/admin/components/SelectTree/utils/flattenTree.js.map +1 -1
  133. package/dist/admin/components/SelectTree/utils/flattenTree.mjs.map +1 -1
  134. package/dist/admin/components/SelectTree/utils/getOpenValues.js.map +1 -1
  135. package/dist/admin/components/SelectTree/utils/getOpenValues.mjs.map +1 -1
  136. package/dist/admin/components/SelectTree/utils/getValuesToClose.js.map +1 -1
  137. package/dist/admin/components/SelectTree/utils/getValuesToClose.mjs.map +1 -1
  138. package/dist/admin/components/SortPicker/SortPicker.js.map +1 -1
  139. package/dist/admin/components/SortPicker/SortPicker.mjs.map +1 -1
  140. package/dist/admin/components/TableList/CellContent.js.map +1 -1
  141. package/dist/admin/components/TableList/CellContent.mjs.map +1 -1
  142. package/dist/admin/components/TableList/PreviewCell.js.map +1 -1
  143. package/dist/admin/components/TableList/PreviewCell.mjs +1 -1
  144. package/dist/admin/components/TableList/PreviewCell.mjs.map +1 -1
  145. package/dist/admin/components/TableList/TableList.js.map +1 -1
  146. package/dist/admin/components/TableList/TableList.mjs +1 -1
  147. package/dist/admin/components/TableList/TableList.mjs.map +1 -1
  148. package/dist/admin/components/TableList/TableRows.js.map +1 -1
  149. package/dist/admin/components/TableList/TableRows.mjs.map +1 -1
  150. package/dist/admin/components/UploadAssetDialog/AddAssetStep/AddAssetStep.js.map +1 -1
  151. package/dist/admin/components/UploadAssetDialog/AddAssetStep/AddAssetStep.mjs.map +1 -1
  152. package/dist/admin/components/UploadAssetDialog/AddAssetStep/FromComputerForm.js.map +1 -1
  153. package/dist/admin/components/UploadAssetDialog/AddAssetStep/FromComputerForm.mjs.map +1 -1
  154. package/dist/admin/components/UploadAssetDialog/AddAssetStep/FromUrlForm.js.map +1 -1
  155. package/dist/admin/components/UploadAssetDialog/AddAssetStep/FromUrlForm.mjs.map +1 -1
  156. package/dist/admin/components/UploadAssetDialog/PendingAssetStep/PendingAssetStep.js.map +1 -1
  157. package/dist/admin/components/UploadAssetDialog/PendingAssetStep/PendingAssetStep.mjs.map +1 -1
  158. package/dist/admin/components/UploadAssetDialog/UploadAssetDialog.js.map +1 -1
  159. package/dist/admin/components/UploadAssetDialog/UploadAssetDialog.mjs.map +1 -1
  160. package/dist/admin/components/UploadProgress/UploadProgress.js.map +1 -1
  161. package/dist/admin/components/UploadProgress/UploadProgress.mjs.map +1 -1
  162. package/dist/admin/constants.js.map +1 -1
  163. package/dist/admin/constants.mjs.map +1 -1
  164. package/dist/admin/future/App.js.map +1 -1
  165. package/dist/admin/future/App.mjs.map +1 -1
  166. package/dist/admin/future/components/Drawer.js +189 -0
  167. package/dist/admin/future/components/Drawer.js.map +1 -0
  168. package/dist/admin/future/components/Drawer.mjs +166 -0
  169. package/dist/admin/future/components/Drawer.mjs.map +1 -0
  170. package/dist/admin/future/components/UploadProgressDialog.js +60 -101
  171. package/dist/admin/future/components/UploadProgressDialog.js.map +1 -1
  172. package/dist/admin/future/components/UploadProgressDialog.mjs +62 -84
  173. package/dist/admin/future/components/UploadProgressDialog.mjs.map +1 -1
  174. package/dist/admin/future/pages/Assets/AssetsPage.js +155 -100
  175. package/dist/admin/future/pages/Assets/AssetsPage.js.map +1 -1
  176. package/dist/admin/future/pages/Assets/AssetsPage.mjs +160 -105
  177. package/dist/admin/future/pages/Assets/AssetsPage.mjs.map +1 -1
  178. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.js +406 -0
  179. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.js.map +1 -0
  180. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.mjs +384 -0
  181. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.mjs.map +1 -0
  182. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetPreview.js +215 -0
  183. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetPreview.js.map +1 -0
  184. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetPreview.mjs +194 -0
  185. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetPreview.mjs.map +1 -0
  186. package/dist/admin/future/pages/Assets/components/AssetsGrid.js +37 -8
  187. package/dist/admin/future/pages/Assets/components/AssetsGrid.js.map +1 -1
  188. package/dist/admin/future/pages/Assets/components/AssetsGrid.mjs +38 -9
  189. package/dist/admin/future/pages/Assets/components/AssetsGrid.mjs.map +1 -1
  190. package/dist/admin/future/pages/Assets/components/AssetsTable.js +35 -5
  191. package/dist/admin/future/pages/Assets/components/AssetsTable.js.map +1 -1
  192. package/dist/admin/future/pages/Assets/components/AssetsTable.mjs +37 -7
  193. package/dist/admin/future/pages/Assets/components/AssetsTable.mjs.map +1 -1
  194. package/dist/admin/future/pages/Assets/components/CreateFolderDialog.js +143 -0
  195. package/dist/admin/future/pages/Assets/components/CreateFolderDialog.js.map +1 -0
  196. package/dist/admin/future/pages/Assets/components/CreateFolderDialog.mjs +141 -0
  197. package/dist/admin/future/pages/Assets/components/CreateFolderDialog.mjs.map +1 -0
  198. package/dist/admin/future/pages/Assets/components/DropZone/UploadDropZone.js.map +1 -1
  199. package/dist/admin/future/pages/Assets/components/DropZone/UploadDropZone.mjs.map +1 -1
  200. package/dist/admin/future/pages/Assets/components/DropZone/UploadDropZoneContext.js.map +1 -1
  201. package/dist/admin/future/pages/Assets/components/DropZone/UploadDropZoneContext.mjs.map +1 -1
  202. package/dist/admin/future/pages/Assets/components/ImportFromUrlDialog.js +127 -0
  203. package/dist/admin/future/pages/Assets/components/ImportFromUrlDialog.js.map +1 -0
  204. package/dist/admin/future/pages/Assets/components/ImportFromUrlDialog.mjs +106 -0
  205. package/dist/admin/future/pages/Assets/components/ImportFromUrlDialog.mjs.map +1 -0
  206. package/dist/admin/future/pages/Assets/constants.js.map +1 -1
  207. package/dist/admin/future/pages/Assets/constants.mjs.map +1 -1
  208. package/dist/admin/future/pages/Assets/hooks/useFolderInfo.js.map +1 -1
  209. package/dist/admin/future/pages/Assets/hooks/useFolderInfo.mjs.map +1 -1
  210. package/dist/admin/future/pages/Assets/hooks/useFolderNavigation.js.map +1 -1
  211. package/dist/admin/future/pages/Assets/hooks/useFolderNavigation.mjs.map +1 -1
  212. package/dist/admin/future/pages/Assets/hooks/useInfiniteAssets.js.map +1 -1
  213. package/dist/admin/future/pages/Assets/hooks/useInfiniteAssets.mjs.map +1 -1
  214. package/dist/admin/future/services/api.js +181 -97
  215. package/dist/admin/future/services/api.js.map +1 -1
  216. package/dist/admin/future/services/api.mjs +181 -98
  217. package/dist/admin/future/services/api.mjs.map +1 -1
  218. package/dist/admin/future/services/assets.js +14 -1
  219. package/dist/admin/future/services/assets.js.map +1 -1
  220. package/dist/admin/future/services/assets.mjs +14 -2
  221. package/dist/admin/future/services/assets.mjs.map +1 -1
  222. package/dist/admin/future/services/folders.js +16 -1
  223. package/dist/admin/future/services/folders.js.map +1 -1
  224. package/dist/admin/future/services/folders.mjs +16 -2
  225. package/dist/admin/future/services/folders.mjs.map +1 -1
  226. package/dist/admin/future/store/hooks.js.map +1 -1
  227. package/dist/admin/future/store/hooks.mjs.map +1 -1
  228. package/dist/admin/future/store/uploadProgress.js +7 -4
  229. package/dist/admin/future/store/uploadProgress.js.map +1 -1
  230. package/dist/admin/future/store/uploadProgress.mjs +7 -4
  231. package/dist/admin/future/store/uploadProgress.mjs.map +1 -1
  232. package/dist/admin/future/utils/files.js +105 -3
  233. package/dist/admin/future/utils/files.js.map +1 -1
  234. package/dist/admin/future/utils/files.mjs +104 -4
  235. package/dist/admin/future/utils/files.mjs.map +1 -1
  236. package/dist/admin/future/utils/getAssetIcon.js +3 -3
  237. package/dist/admin/future/utils/getAssetIcon.js.map +1 -1
  238. package/dist/admin/future/utils/getAssetIcon.mjs +4 -4
  239. package/dist/admin/future/utils/getAssetIcon.mjs.map +1 -1
  240. package/dist/admin/future/utils/translations.js.map +1 -1
  241. package/dist/admin/future/utils/translations.mjs.map +1 -1
  242. package/dist/admin/hooks/useAIMetadataJob.js.map +1 -1
  243. package/dist/admin/hooks/useAIMetadataJob.mjs.map +1 -1
  244. package/dist/admin/hooks/useAiAvailability.js.map +1 -1
  245. package/dist/admin/hooks/useAiAvailability.mjs.map +1 -1
  246. package/dist/admin/hooks/useAssets.js.map +1 -1
  247. package/dist/admin/hooks/useAssets.mjs.map +1 -1
  248. package/dist/admin/hooks/useBulkEdit.js.map +1 -1
  249. package/dist/admin/hooks/useBulkEdit.mjs.map +1 -1
  250. package/dist/admin/hooks/useBulkMove.js.map +1 -1
  251. package/dist/admin/hooks/useBulkMove.mjs.map +1 -1
  252. package/dist/admin/hooks/useBulkRemove.js.map +1 -1
  253. package/dist/admin/hooks/useBulkRemove.mjs.map +1 -1
  254. package/dist/admin/hooks/useConfig.js.map +1 -1
  255. package/dist/admin/hooks/useConfig.mjs.map +1 -1
  256. package/dist/admin/hooks/useCropImg.js.map +1 -1
  257. package/dist/admin/hooks/useCropImg.mjs.map +1 -1
  258. package/dist/admin/hooks/useEditAsset.js.map +1 -1
  259. package/dist/admin/hooks/useEditAsset.mjs.map +1 -1
  260. package/dist/admin/hooks/useEditFolder.js.map +1 -1
  261. package/dist/admin/hooks/useEditFolder.mjs.map +1 -1
  262. package/dist/admin/hooks/useFolder.js.map +1 -1
  263. package/dist/admin/hooks/useFolder.mjs.map +1 -1
  264. package/dist/admin/hooks/useFolderStructure.js.map +1 -1
  265. package/dist/admin/hooks/useFolderStructure.mjs.map +1 -1
  266. package/dist/admin/hooks/useFolders.js.map +1 -1
  267. package/dist/admin/hooks/useFolders.mjs.map +1 -1
  268. package/dist/admin/hooks/useMediaLibraryPermissions.js.map +1 -1
  269. package/dist/admin/hooks/useMediaLibraryPermissions.mjs.map +1 -1
  270. package/dist/admin/hooks/useModalQueryParams.js.map +1 -1
  271. package/dist/admin/hooks/useModalQueryParams.mjs.map +1 -1
  272. package/dist/admin/hooks/usePersistentState.js.map +1 -1
  273. package/dist/admin/hooks/usePersistentState.mjs.map +1 -1
  274. package/dist/admin/hooks/useRemoveAsset.js.map +1 -1
  275. package/dist/admin/hooks/useRemoveAsset.mjs.map +1 -1
  276. package/dist/admin/hooks/useSelectionState.js.map +1 -1
  277. package/dist/admin/hooks/useSelectionState.mjs.map +1 -1
  278. package/dist/admin/hooks/useSettings.js.map +1 -1
  279. package/dist/admin/hooks/useSettings.mjs.map +1 -1
  280. package/dist/admin/hooks/useTracking.js.map +1 -1
  281. package/dist/admin/hooks/useTracking.mjs.map +1 -1
  282. package/dist/admin/hooks/useUpload.js.map +1 -1
  283. package/dist/admin/hooks/useUpload.mjs.map +1 -1
  284. package/dist/admin/hooks/utils/renameKeys.js.map +1 -1
  285. package/dist/admin/hooks/utils/renameKeys.mjs.map +1 -1
  286. package/dist/admin/index.js.map +1 -1
  287. package/dist/admin/index.mjs.map +1 -1
  288. package/dist/admin/package.json.js +1 -152
  289. package/dist/admin/package.json.js.map +1 -1
  290. package/dist/admin/package.json.mjs +2 -141
  291. package/dist/admin/package.json.mjs.map +1 -1
  292. package/dist/admin/pages/App/App.js.map +1 -1
  293. package/dist/admin/pages/App/App.mjs.map +1 -1
  294. package/dist/admin/pages/App/ConfigureTheView/ConfigureTheView.js.map +1 -1
  295. package/dist/admin/pages/App/ConfigureTheView/ConfigureTheView.mjs +3 -3
  296. package/dist/admin/pages/App/ConfigureTheView/ConfigureTheView.mjs.map +1 -1
  297. package/dist/admin/pages/App/ConfigureTheView/components/Settings.js.map +1 -1
  298. package/dist/admin/pages/App/ConfigureTheView/components/Settings.mjs.map +1 -1
  299. package/dist/admin/pages/App/ConfigureTheView/state/actionTypes.js.map +1 -1
  300. package/dist/admin/pages/App/ConfigureTheView/state/actionTypes.mjs.map +1 -1
  301. package/dist/admin/pages/App/ConfigureTheView/state/actions.js.map +1 -1
  302. package/dist/admin/pages/App/ConfigureTheView/state/actions.mjs.map +1 -1
  303. package/dist/admin/pages/App/ConfigureTheView/state/init.js.map +1 -1
  304. package/dist/admin/pages/App/ConfigureTheView/state/init.mjs.map +1 -1
  305. package/dist/admin/pages/App/ConfigureTheView/state/reducer.js.map +1 -1
  306. package/dist/admin/pages/App/ConfigureTheView/state/reducer.mjs +1 -1
  307. package/dist/admin/pages/App/ConfigureTheView/state/reducer.mjs.map +1 -1
  308. package/dist/admin/pages/App/MediaLibrary.js +1 -1
  309. package/dist/admin/pages/App/MediaLibrary.js.map +1 -1
  310. package/dist/admin/pages/App/MediaLibrary.mjs +2 -2
  311. package/dist/admin/pages/App/MediaLibrary.mjs.map +1 -1
  312. package/dist/admin/pages/App/components/BulkActions.js.map +1 -1
  313. package/dist/admin/pages/App/components/BulkActions.mjs.map +1 -1
  314. package/dist/admin/pages/App/components/BulkDeleteButton.js.map +1 -1
  315. package/dist/admin/pages/App/components/BulkDeleteButton.mjs.map +1 -1
  316. package/dist/admin/pages/App/components/BulkMoveButton.js.map +1 -1
  317. package/dist/admin/pages/App/components/BulkMoveButton.mjs.map +1 -1
  318. package/dist/admin/pages/App/components/EmptyOrNoPermissions.js.map +1 -1
  319. package/dist/admin/pages/App/components/EmptyOrNoPermissions.mjs.map +1 -1
  320. package/dist/admin/pages/App/components/Filters.js.map +1 -1
  321. package/dist/admin/pages/App/components/Filters.mjs.map +1 -1
  322. package/dist/admin/pages/App/components/Header.js.map +1 -1
  323. package/dist/admin/pages/App/components/Header.mjs +2 -2
  324. package/dist/admin/pages/App/components/Header.mjs.map +1 -1
  325. package/dist/admin/pages/SettingsPage/SettingsPage.js.map +1 -1
  326. package/dist/admin/pages/SettingsPage/SettingsPage.mjs.map +1 -1
  327. package/dist/admin/pages/SettingsPage/reducer.js.map +1 -1
  328. package/dist/admin/pages/SettingsPage/reducer.mjs.map +1 -1
  329. package/dist/admin/pluginId.js.map +1 -1
  330. package/dist/admin/pluginId.mjs.map +1 -1
  331. package/dist/admin/src/future/components/Drawer.d.ts +32 -0
  332. package/dist/admin/src/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.d.ts +8 -0
  333. package/dist/admin/src/future/pages/Assets/components/AssetDetails/AssetPreview.d.ts +6 -0
  334. package/dist/admin/src/future/pages/Assets/components/AssetsGrid.d.ts +2 -1
  335. package/dist/admin/src/future/pages/Assets/components/AssetsTable.d.ts +2 -1
  336. package/dist/admin/src/future/pages/Assets/components/CreateFolderDialog.d.ts +8 -0
  337. package/dist/admin/src/future/pages/Assets/components/ImportFromUrlDialog.d.ts +7 -0
  338. package/dist/admin/src/future/services/api.d.ts +10 -1
  339. package/dist/admin/src/future/services/assets.d.ts +2 -2
  340. package/dist/admin/src/future/services/folders.d.ts +1 -1
  341. package/dist/admin/src/future/store/uploadProgress.d.ts +1 -1
  342. package/dist/admin/src/future/utils/files.d.ts +70 -0
  343. package/dist/admin/translations/en.json.js +35 -3
  344. package/dist/admin/translations/en.json.js.map +1 -1
  345. package/dist/admin/translations/en.json.mjs +35 -3
  346. package/dist/admin/translations/en.json.mjs.map +1 -1
  347. package/dist/admin/translations/es.json.js +26 -2
  348. package/dist/admin/translations/es.json.js.map +1 -1
  349. package/dist/admin/translations/es.json.mjs +26 -2
  350. package/dist/admin/translations/es.json.mjs.map +1 -1
  351. package/dist/admin/utils/appendSearchParamsToUrl.js.map +1 -1
  352. package/dist/admin/utils/appendSearchParamsToUrl.mjs.map +1 -1
  353. package/dist/admin/utils/containsAssetFilter.js.map +1 -1
  354. package/dist/admin/utils/containsAssetFilter.mjs.map +1 -1
  355. package/dist/admin/utils/createAssetUrl.js.map +1 -1
  356. package/dist/admin/utils/createAssetUrl.mjs.map +1 -1
  357. package/dist/admin/utils/displayedFilters.js.map +1 -1
  358. package/dist/admin/utils/displayedFilters.mjs.map +1 -1
  359. package/dist/admin/utils/downloadFile.js.map +1 -1
  360. package/dist/admin/utils/downloadFile.mjs.map +1 -1
  361. package/dist/admin/utils/findRecursiveFolderByValue.js.map +1 -1
  362. package/dist/admin/utils/findRecursiveFolderByValue.mjs.map +1 -1
  363. package/dist/admin/utils/formatBytes.js.map +1 -1
  364. package/dist/admin/utils/formatBytes.mjs.map +1 -1
  365. package/dist/admin/utils/formatDuration.js.map +1 -1
  366. package/dist/admin/utils/formatDuration.mjs.map +1 -1
  367. package/dist/admin/utils/getAPIInnerErrors.js.map +1 -1
  368. package/dist/admin/utils/getAPIInnerErrors.mjs.map +1 -1
  369. package/dist/admin/utils/getAllowedFiles.js.map +1 -1
  370. package/dist/admin/utils/getAllowedFiles.mjs.map +1 -1
  371. package/dist/admin/utils/getBreadcrumbDataCM.js.map +1 -1
  372. package/dist/admin/utils/getBreadcrumbDataCM.mjs.map +1 -1
  373. package/dist/admin/utils/getBreadcrumbDataML.js.map +1 -1
  374. package/dist/admin/utils/getBreadcrumbDataML.mjs.map +1 -1
  375. package/dist/admin/utils/getFileExtension.js.map +1 -1
  376. package/dist/admin/utils/getFileExtension.mjs.map +1 -1
  377. package/dist/admin/utils/getFolderParents.js.map +1 -1
  378. package/dist/admin/utils/getFolderParents.mjs.map +1 -1
  379. package/dist/admin/utils/getFolderURL.js.map +1 -1
  380. package/dist/admin/utils/getFolderURL.mjs.map +1 -1
  381. package/dist/admin/utils/getTrad.js.map +1 -1
  382. package/dist/admin/utils/getTrad.mjs.map +1 -1
  383. package/dist/admin/utils/icons.js.map +1 -1
  384. package/dist/admin/utils/icons.mjs +1 -1
  385. package/dist/admin/utils/icons.mjs.map +1 -1
  386. package/dist/admin/utils/moveElement.js.map +1 -1
  387. package/dist/admin/utils/moveElement.mjs.map +1 -1
  388. package/dist/admin/utils/normalizeAPIError.js.map +1 -1
  389. package/dist/admin/utils/normalizeAPIError.mjs.map +1 -1
  390. package/dist/admin/utils/prefixFileUrlWithBackendUrl.js.map +1 -1
  391. package/dist/admin/utils/prefixFileUrlWithBackendUrl.mjs.map +1 -1
  392. package/dist/admin/utils/prefixPluginTranslations.js.map +1 -1
  393. package/dist/admin/utils/prefixPluginTranslations.mjs.map +1 -1
  394. package/dist/admin/utils/rawFileToAsset.js.map +1 -1
  395. package/dist/admin/utils/rawFileToAsset.mjs.map +1 -1
  396. package/dist/admin/utils/toSingularTypes.js.map +1 -1
  397. package/dist/admin/utils/toSingularTypes.mjs.map +1 -1
  398. package/dist/admin/utils/typeFromMime.js.map +1 -1
  399. package/dist/admin/utils/typeFromMime.mjs.map +1 -1
  400. package/dist/admin/utils/urlYupSchema.js.map +1 -1
  401. package/dist/admin/utils/urlYupSchema.mjs.map +1 -1
  402. package/dist/admin/utils/urlsToAssets.js.map +1 -1
  403. package/dist/admin/utils/urlsToAssets.mjs.map +1 -1
  404. package/dist/server/bootstrap.js.map +1 -1
  405. package/dist/server/bootstrap.mjs.map +1 -1
  406. package/dist/server/config.js.map +1 -1
  407. package/dist/server/config.mjs.map +1 -1
  408. package/dist/server/constants.js.map +1 -1
  409. package/dist/server/constants.mjs.map +1 -1
  410. package/dist/server/content-types/file.js.map +1 -1
  411. package/dist/server/content-types/file.mjs.map +1 -1
  412. package/dist/server/content-types/folder.js.map +1 -1
  413. package/dist/server/content-types/folder.mjs +1 -1
  414. package/dist/server/content-types/folder.mjs.map +1 -1
  415. package/dist/server/content-types/index.js.map +1 -1
  416. package/dist/server/content-types/index.mjs.map +1 -1
  417. package/dist/server/controllers/admin-file.js.map +1 -1
  418. package/dist/server/controllers/admin-file.mjs +1 -1
  419. package/dist/server/controllers/admin-file.mjs.map +1 -1
  420. package/dist/server/controllers/admin-folder-file.js.map +1 -1
  421. package/dist/server/controllers/admin-folder-file.mjs +2 -2
  422. package/dist/server/controllers/admin-folder-file.mjs.map +1 -1
  423. package/dist/server/controllers/admin-folder.js.map +1 -1
  424. package/dist/server/controllers/admin-folder.mjs +1 -1
  425. package/dist/server/controllers/admin-folder.mjs.map +1 -1
  426. package/dist/server/controllers/admin-settings.js.map +1 -1
  427. package/dist/server/controllers/admin-settings.mjs.map +1 -1
  428. package/dist/server/controllers/admin-upload.js +144 -0
  429. package/dist/server/controllers/admin-upload.js.map +1 -1
  430. package/dist/server/controllers/admin-upload.mjs +147 -3
  431. package/dist/server/controllers/admin-upload.mjs.map +1 -1
  432. package/dist/server/controllers/content-api.js.map +1 -1
  433. package/dist/server/controllers/content-api.mjs.map +1 -1
  434. package/dist/server/controllers/index.js.map +1 -1
  435. package/dist/server/controllers/index.mjs.map +1 -1
  436. package/dist/server/controllers/utils/find-entity-and-check-permissions.js.map +1 -1
  437. package/dist/server/controllers/utils/find-entity-and-check-permissions.mjs.map +1 -1
  438. package/dist/server/controllers/utils/folders.js.map +1 -1
  439. package/dist/server/controllers/utils/folders.mjs.map +1 -1
  440. package/dist/server/controllers/validation/admin/configureView.js.map +1 -1
  441. package/dist/server/controllers/validation/admin/configureView.mjs.map +1 -1
  442. package/dist/server/controllers/validation/admin/folder-file.js.map +1 -1
  443. package/dist/server/controllers/validation/admin/folder-file.mjs.map +1 -1
  444. package/dist/server/controllers/validation/admin/folder.js.map +1 -1
  445. package/dist/server/controllers/validation/admin/folder.mjs.map +1 -1
  446. package/dist/server/controllers/validation/admin/settings.js.map +1 -1
  447. package/dist/server/controllers/validation/admin/settings.mjs.map +1 -1
  448. package/dist/server/controllers/validation/admin/upload.js.map +1 -1
  449. package/dist/server/controllers/validation/admin/upload.mjs.map +1 -1
  450. package/dist/server/controllers/validation/admin/utils.js.map +1 -1
  451. package/dist/server/controllers/validation/admin/utils.mjs.map +1 -1
  452. package/dist/server/controllers/validation/content-api/upload.js.map +1 -1
  453. package/dist/server/controllers/validation/content-api/upload.mjs.map +1 -1
  454. package/dist/server/controllers/view-configuration.js.map +1 -1
  455. package/dist/server/controllers/view-configuration.mjs.map +1 -1
  456. package/dist/server/graphql.js.map +1 -1
  457. package/dist/server/graphql.mjs.map +1 -1
  458. package/dist/server/index.js +8 -8
  459. package/dist/server/middlewares/upload.js.map +1 -1
  460. package/dist/server/middlewares/upload.mjs.map +1 -1
  461. package/dist/server/models/ai-metadata-job.js.map +1 -1
  462. package/dist/server/models/ai-metadata-job.mjs.map +1 -1
  463. package/dist/server/register.js.map +1 -1
  464. package/dist/server/register.mjs.map +1 -1
  465. package/dist/server/routes/admin.js +10 -0
  466. package/dist/server/routes/admin.js.map +1 -1
  467. package/dist/server/routes/admin.mjs +10 -0
  468. package/dist/server/routes/admin.mjs.map +1 -1
  469. package/dist/server/routes/content-api.js.map +1 -1
  470. package/dist/server/routes/content-api.mjs.map +1 -1
  471. package/dist/server/routes/index.js.map +1 -1
  472. package/dist/server/routes/index.mjs +4 -4
  473. package/dist/server/routes/index.mjs.map +1 -1
  474. package/dist/server/routes/validation/upload.js.map +1 -1
  475. package/dist/server/routes/validation/upload.mjs.map +1 -1
  476. package/dist/server/routes/view-configuration.js.map +1 -1
  477. package/dist/server/routes/view-configuration.mjs.map +1 -1
  478. package/dist/server/services/ai-metadata-jobs.js.map +1 -1
  479. package/dist/server/services/ai-metadata-jobs.mjs.map +1 -1
  480. package/dist/server/services/ai-metadata.js.map +1 -1
  481. package/dist/server/services/ai-metadata.mjs.map +1 -1
  482. package/dist/server/services/api-upload-folder.js.map +1 -1
  483. package/dist/server/services/api-upload-folder.mjs.map +1 -1
  484. package/dist/server/services/extensions/index.js.map +1 -1
  485. package/dist/server/services/extensions/index.mjs.map +1 -1
  486. package/dist/server/services/extensions/utils.js.map +1 -1
  487. package/dist/server/services/extensions/utils.mjs.map +1 -1
  488. package/dist/server/services/file.js +120 -1
  489. package/dist/server/services/file.js.map +1 -1
  490. package/dist/server/services/file.mjs +122 -3
  491. package/dist/server/services/file.mjs.map +1 -1
  492. package/dist/server/services/folder.js.map +1 -1
  493. package/dist/server/services/folder.mjs +1 -1
  494. package/dist/server/services/folder.mjs.map +1 -1
  495. package/dist/server/services/image-manipulation.js.map +1 -1
  496. package/dist/server/services/image-manipulation.mjs +1 -1
  497. package/dist/server/services/image-manipulation.mjs.map +1 -1
  498. package/dist/server/services/index.js.map +1 -1
  499. package/dist/server/services/index.mjs.map +1 -1
  500. package/dist/server/services/metrics.js.map +1 -1
  501. package/dist/server/services/metrics.mjs.map +1 -1
  502. package/dist/server/services/provider.js.map +1 -1
  503. package/dist/server/services/provider.mjs.map +1 -1
  504. package/dist/server/services/upload.js +6 -1
  505. package/dist/server/services/upload.js.map +1 -1
  506. package/dist/server/services/upload.mjs +7 -2
  507. package/dist/server/services/upload.mjs.map +1 -1
  508. package/dist/server/services/weekly-metrics.js.map +1 -1
  509. package/dist/server/services/weekly-metrics.mjs.map +1 -1
  510. package/dist/server/src/controllers/admin-upload.d.ts +13 -0
  511. package/dist/server/src/controllers/admin-upload.d.ts.map +1 -1
  512. package/dist/server/src/controllers/index.d.ts +1 -0
  513. package/dist/server/src/controllers/index.d.ts.map +1 -1
  514. package/dist/server/src/index.d.ts +2 -0
  515. package/dist/server/src/index.d.ts.map +1 -1
  516. package/dist/server/src/routes/admin.d.ts.map +1 -1
  517. package/dist/server/src/services/file.d.ts +15 -0
  518. package/dist/server/src/services/file.d.ts.map +1 -1
  519. package/dist/server/src/services/index.d.ts +1 -0
  520. package/dist/server/src/services/index.d.ts.map +1 -1
  521. package/dist/server/src/services/upload.d.ts.map +1 -1
  522. package/dist/server/src/utils/mime-validation.d.ts +1 -0
  523. package/dist/server/src/utils/mime-validation.d.ts.map +1 -1
  524. package/dist/server/utils/cron.js.map +1 -1
  525. package/dist/server/utils/cron.mjs.map +1 -1
  526. package/dist/server/utils/images.js.map +1 -1
  527. package/dist/server/utils/images.mjs.map +1 -1
  528. package/dist/server/utils/index.js.map +1 -1
  529. package/dist/server/utils/index.mjs.map +1 -1
  530. package/dist/server/utils/mime-validation.js +184 -47
  531. package/dist/server/utils/mime-validation.js.map +1 -1
  532. package/dist/server/utils/mime-validation.mjs +185 -47
  533. package/dist/server/utils/mime-validation.mjs.map +1 -1
  534. package/dist/shared/contracts/files.d.ts +11 -0
  535. package/dist/shared/contracts/files.d.ts.map +1 -1
  536. package/package.json +19 -10
@@ -1 +1 @@
1
- {"version":3,"file":"ai-metadata.mjs","sources":["../../../server/src/services/ai-metadata.ts"],"sourcesContent":["import type { Core } from '@strapi/types';\nimport { z } from 'zod';\nimport { InputFile, File } from '../types';\nimport { Settings } from '../controllers/validation/admin/settings';\nimport { getService } from '../utils';\nimport { buildFormDataFromFiles } from '../utils/images';\n\n/**\n * Supported image types for AI metadata generation\n * @see https://ai.google.dev/gemini-api/docs/image-understanding\n */\nconst SUPPORTED_IMAGE_TYPES = [\n 'image/png',\n 'image/jpeg',\n 'image/webp',\n 'image/heic',\n 'image/heif',\n] as const;\n\nconst createAIMetadataService = ({ strapi }: { strapi: Core.Strapi }) => {\n const aiServerUrl = process.env.STRAPI_AI_URL || 'https://strapi-ai.apps.strapi.io';\n\n return {\n async isEnabled() {\n // Check if user disabled AI features globally\n const isAIEnabled = strapi.config.get('admin.ai.enabled', true);\n if (!isAIEnabled) {\n return false;\n }\n\n // Check if the user's license grants access to AI features\n const hasAccess = strapi.ee.features.isEnabled('cms-ai');\n if (!hasAccess) {\n return false;\n }\n\n // Check if feature is specifically enabled, defaulting to true\n const settings: Settings = await strapi.plugin('upload').service('upload').getSettings();\n const aiMetadata: boolean = settings.aiMetadata ?? true;\n\n return aiMetadata;\n },\n\n async countImagesWithoutMetadata() {\n const imagesWithoutMetadataCountPromise = strapi.db.query('plugin::upload.file').count({\n where: {\n mime: {\n $in: SUPPORTED_IMAGE_TYPES,\n },\n $or: [\n { alternativeText: { $null: true } },\n { alternativeText: '' },\n { caption: { $null: true } },\n { caption: '' },\n ],\n },\n });\n\n const totalImagesPromise = strapi.db.query('plugin::upload.file').count({\n where: {\n mime: {\n $in: SUPPORTED_IMAGE_TYPES,\n },\n },\n });\n\n const [imagesWithoutMetadataCount, totalImages] = await Promise.all([\n imagesWithoutMetadataCountPromise,\n totalImagesPromise,\n ]);\n\n return { imagesWithoutMetadataCount, totalImages };\n },\n\n /**\n * Update files with AI-generated metadata\n * Shared logic used by both upload flow and retroactive processing\n */\n async updateFilesWithAIMetadata(\n files: File[],\n metadataResults: Array<{ altText: string; caption: string } | null>,\n user: { id: string | number }\n ) {\n const uploadService = strapi.plugin('upload').service('upload');\n\n await Promise.all(\n files.map(async (file, index) => {\n const aiMetadata = metadataResults[index];\n if (aiMetadata) {\n // Only update fields that are missing (null or empty string)\n const updateData: { alternativeText?: string; caption?: string } = {};\n\n if (!file.alternativeText || file.alternativeText === '') {\n updateData.alternativeText = aiMetadata.altText;\n }\n\n if (!file.caption || file.caption === '') {\n updateData.caption = aiMetadata.caption;\n }\n\n // Only update if there are fields to update\n if (Object.keys(updateData).length > 0) {\n await uploadService.updateFileInfo(file.id, updateData, { user });\n\n // Update in-memory file object (needed for upload flow response)\n if (updateData.alternativeText !== undefined) {\n file.alternativeText = updateData.alternativeText;\n }\n if (updateData.caption !== undefined) {\n file.caption = updateData.caption;\n }\n }\n }\n })\n );\n },\n\n /**\n * Process existing files with job tracking for progress updates\n */\n async processExistingFiles(jobId: number, user: { id: string | number }): Promise<void> {\n const jobService = getService('aiMetadataJobs');\n\n try {\n // Mark as processing\n await jobService.updateJob(jobId, { status: 'processing' });\n\n // Query all images without metadata\n const files: File[] = await strapi.db.query('plugin::upload.file').findMany({\n where: {\n mime: {\n $in: SUPPORTED_IMAGE_TYPES,\n },\n $or: [\n { alternativeText: { $null: true } },\n { alternativeText: '' },\n { caption: { $null: true } },\n { caption: '' },\n ],\n },\n });\n\n if (files.length === 0) {\n await jobService.updateJob(jobId, {\n status: 'completed',\n completedAt: new Date(),\n });\n return;\n }\n\n // Process all files at once\n const metadataResults = await this.processFiles(files);\n await this.updateFilesWithAIMetadata(files, metadataResults, user);\n\n // Mark as completed\n await jobService.updateJob(jobId, {\n status: 'completed',\n completedAt: new Date(),\n });\n } catch (error) {\n strapi.log.error('AI metadata job failed', {\n jobId,\n error: error instanceof Error ? error.message : String(error),\n });\n\n await jobService.updateJob(jobId, {\n status: 'failed',\n completedAt: new Date(),\n });\n }\n },\n\n /**\n * Processes provided files for AI metadata generation\n */\n async processFiles(files: File[]): Promise<Array<{ altText: string; caption: string } | null>> {\n if (!(await this.isEnabled()) || !aiServerUrl) {\n throw new Error('AI Metadata service is not enabled');\n }\n\n // Filter for image files only and track their original positions\n // We need to maintain the original indices so we can map AI results back correctly\n const imageFiles = files\n .map((file, index) => ({ file, originalIndex: index }))\n .filter(({ file }) => file.mime?.startsWith('image/'));\n\n // Convert filtered image files to InputFile format (uses thumbnails when available)\n const imageInputFiles = imageFiles.map(({ file }) => {\n const thumbnail = (file.formats as any)?.thumbnail;\n return {\n filepath: thumbnail?.url || file.url || '',\n mimetype: file.mime,\n originalFilename: file.name,\n size: thumbnail?.size || file.size,\n provider: file.provider,\n } as InputFile;\n });\n\n // If no image files, return sparse array with all nulls to avoid calling the AI server\n // This maintains the same array length as input files for proper index alignment\n if (imageFiles.length === 0) {\n return new Array(files.length).fill(null);\n }\n\n const formData = await buildFormDataFromFiles(\n imageInputFiles,\n strapi.config.get('server.absoluteUrl'),\n strapi.log\n );\n\n let token: string;\n try {\n const tokenData = await strapi.get('ai').getAiToken();\n token = tokenData.token;\n } catch (error) {\n throw new Error('Failed to retrieve AI token', {\n cause: error instanceof Error ? error : undefined,\n });\n }\n\n strapi.log.http('Contacting AI Server for media metadata generation', {\n aiServerUrl,\n imageCount: imageFiles.length,\n });\n\n const res = await fetch(`${aiServerUrl}/media-library/generate-metadata`, {\n method: 'POST',\n body: formData,\n headers: {\n Authorization: `Bearer ${token}`,\n },\n });\n\n if (!res.ok) {\n const errorText = await res.text();\n throw Error(`AI metadata generation failed`, { cause: errorText });\n }\n\n const responseSchema = z.object({\n results: z.array(\n z.object({\n altText: z.string(),\n caption: z.string(),\n })\n ),\n });\n\n const { results } = responseSchema.parse(await res.json());\n strapi.log.http(`AI generated metadata successfully for ${results.length} files`);\n\n // Create sparse array with results at original indices\n // Example: files=[img1, pdf, img2] -> imageFiles=[{img1, index:0}, {img2, index:2}]\n // AI results=[meta1, meta2] -> sparse=[meta1, null, meta2]\n // This ensures metadata[i] corresponds to files[i], with null for non-images\n return imageFiles.reduce((sparseResults, { originalIndex }, resultIndex) => {\n sparseResults[originalIndex] = results[resultIndex];\n return sparseResults;\n }, new Array(files.length).fill(null));\n },\n };\n};\n\nexport { createAIMetadataService };\n"],"names":["SUPPORTED_IMAGE_TYPES","createAIMetadataService","strapi","aiServerUrl","process","env","STRAPI_AI_URL","isEnabled","isAIEnabled","config","get","hasAccess","ee","features","settings","plugin","service","getSettings","aiMetadata","countImagesWithoutMetadata","imagesWithoutMetadataCountPromise","db","query","count","where","mime","$in","$or","alternativeText","$null","caption","totalImagesPromise","imagesWithoutMetadataCount","totalImages","Promise","all","updateFilesWithAIMetadata","files","metadataResults","user","uploadService","map","file","index","updateData","altText","Object","keys","length","updateFileInfo","id","undefined","processExistingFiles","jobId","jobService","getService","updateJob","status","findMany","completedAt","Date","processFiles","error","log","Error","message","String","imageFiles","originalIndex","filter","startsWith","imageInputFiles","thumbnail","formats","filepath","url","mimetype","originalFilename","name","size","provider","Array","fill","formData","buildFormDataFromFiles","token","tokenData","getAiToken","cause","http","imageCount","res","fetch","method","body","headers","Authorization","ok","errorText","text","responseSchema","z","object","results","array","string","parse","json","reduce","sparseResults","resultIndex"],"mappings":"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"}
1
+ {"version":3,"file":"ai-metadata.mjs","sources":["../../../server/src/services/ai-metadata.ts"],"sourcesContent":["import type { Core } from '@strapi/types';\nimport { z } from 'zod';\nimport { InputFile, File } from '../types';\nimport { Settings } from '../controllers/validation/admin/settings';\nimport { getService } from '../utils';\nimport { buildFormDataFromFiles } from '../utils/images';\n\n/**\n * Supported image types for AI metadata generation\n * @see https://ai.google.dev/gemini-api/docs/image-understanding\n */\nconst SUPPORTED_IMAGE_TYPES = [\n 'image/png',\n 'image/jpeg',\n 'image/webp',\n 'image/heic',\n 'image/heif',\n] as const;\n\nconst createAIMetadataService = ({ strapi }: { strapi: Core.Strapi }) => {\n const aiServerUrl = process.env.STRAPI_AI_URL || 'https://strapi-ai.apps.strapi.io';\n\n return {\n async isEnabled() {\n // Check if user disabled AI features globally\n const isAIEnabled = strapi.config.get('admin.ai.enabled', true);\n if (!isAIEnabled) {\n return false;\n }\n\n // Check if the user's license grants access to AI features\n const hasAccess = strapi.ee.features.isEnabled('cms-ai');\n if (!hasAccess) {\n return false;\n }\n\n // Check if feature is specifically enabled, defaulting to true\n const settings: Settings = await strapi.plugin('upload').service('upload').getSettings();\n const aiMetadata: boolean = settings.aiMetadata ?? true;\n\n return aiMetadata;\n },\n\n async countImagesWithoutMetadata() {\n const imagesWithoutMetadataCountPromise = strapi.db.query('plugin::upload.file').count({\n where: {\n mime: {\n $in: SUPPORTED_IMAGE_TYPES,\n },\n $or: [\n { alternativeText: { $null: true } },\n { alternativeText: '' },\n { caption: { $null: true } },\n { caption: '' },\n ],\n },\n });\n\n const totalImagesPromise = strapi.db.query('plugin::upload.file').count({\n where: {\n mime: {\n $in: SUPPORTED_IMAGE_TYPES,\n },\n },\n });\n\n const [imagesWithoutMetadataCount, totalImages] = await Promise.all([\n imagesWithoutMetadataCountPromise,\n totalImagesPromise,\n ]);\n\n return { imagesWithoutMetadataCount, totalImages };\n },\n\n /**\n * Update files with AI-generated metadata\n * Shared logic used by both upload flow and retroactive processing\n */\n async updateFilesWithAIMetadata(\n files: File[],\n metadataResults: Array<{ altText: string; caption: string } | null>,\n user: { id: string | number }\n ) {\n const uploadService = strapi.plugin('upload').service('upload');\n\n await Promise.all(\n files.map(async (file, index) => {\n const aiMetadata = metadataResults[index];\n if (aiMetadata) {\n // Only update fields that are missing (null or empty string)\n const updateData: { alternativeText?: string; caption?: string } = {};\n\n if (!file.alternativeText || file.alternativeText === '') {\n updateData.alternativeText = aiMetadata.altText;\n }\n\n if (!file.caption || file.caption === '') {\n updateData.caption = aiMetadata.caption;\n }\n\n // Only update if there are fields to update\n if (Object.keys(updateData).length > 0) {\n await uploadService.updateFileInfo(file.id, updateData, { user });\n\n // Update in-memory file object (needed for upload flow response)\n if (updateData.alternativeText !== undefined) {\n file.alternativeText = updateData.alternativeText;\n }\n if (updateData.caption !== undefined) {\n file.caption = updateData.caption;\n }\n }\n }\n })\n );\n },\n\n /**\n * Process existing files with job tracking for progress updates\n */\n async processExistingFiles(jobId: number, user: { id: string | number }): Promise<void> {\n const jobService = getService('aiMetadataJobs');\n\n try {\n // Mark as processing\n await jobService.updateJob(jobId, { status: 'processing' });\n\n // Query all images without metadata\n const files: File[] = await strapi.db.query('plugin::upload.file').findMany({\n where: {\n mime: {\n $in: SUPPORTED_IMAGE_TYPES,\n },\n $or: [\n { alternativeText: { $null: true } },\n { alternativeText: '' },\n { caption: { $null: true } },\n { caption: '' },\n ],\n },\n });\n\n if (files.length === 0) {\n await jobService.updateJob(jobId, {\n status: 'completed',\n completedAt: new Date(),\n });\n return;\n }\n\n // Process all files at once\n const metadataResults = await this.processFiles(files);\n await this.updateFilesWithAIMetadata(files, metadataResults, user);\n\n // Mark as completed\n await jobService.updateJob(jobId, {\n status: 'completed',\n completedAt: new Date(),\n });\n } catch (error) {\n strapi.log.error('AI metadata job failed', {\n jobId,\n error: error instanceof Error ? error.message : String(error),\n });\n\n await jobService.updateJob(jobId, {\n status: 'failed',\n completedAt: new Date(),\n });\n }\n },\n\n /**\n * Processes provided files for AI metadata generation\n */\n async processFiles(files: File[]): Promise<Array<{ altText: string; caption: string } | null>> {\n if (!(await this.isEnabled()) || !aiServerUrl) {\n throw new Error('AI Metadata service is not enabled');\n }\n\n // Filter for image files only and track their original positions\n // We need to maintain the original indices so we can map AI results back correctly\n const imageFiles = files\n .map((file, index) => ({ file, originalIndex: index }))\n .filter(({ file }) => file.mime?.startsWith('image/'));\n\n // Convert filtered image files to InputFile format (uses thumbnails when available)\n const imageInputFiles = imageFiles.map(({ file }) => {\n const thumbnail = (file.formats as any)?.thumbnail;\n return {\n filepath: thumbnail?.url || file.url || '',\n mimetype: file.mime,\n originalFilename: file.name,\n size: thumbnail?.size || file.size,\n provider: file.provider,\n } as InputFile;\n });\n\n // If no image files, return sparse array with all nulls to avoid calling the AI server\n // This maintains the same array length as input files for proper index alignment\n if (imageFiles.length === 0) {\n return new Array(files.length).fill(null);\n }\n\n const formData = await buildFormDataFromFiles(\n imageInputFiles,\n strapi.config.get('server.absoluteUrl'),\n strapi.log\n );\n\n let token: string;\n try {\n const tokenData = await strapi.get('ai').getAiToken();\n token = tokenData.token;\n } catch (error) {\n throw new Error('Failed to retrieve AI token', {\n cause: error instanceof Error ? error : undefined,\n });\n }\n\n strapi.log.http('Contacting AI Server for media metadata generation', {\n aiServerUrl,\n imageCount: imageFiles.length,\n });\n\n const res = await fetch(`${aiServerUrl}/media-library/generate-metadata`, {\n method: 'POST',\n body: formData,\n headers: {\n Authorization: `Bearer ${token}`,\n },\n });\n\n if (!res.ok) {\n const errorText = await res.text();\n throw Error(`AI metadata generation failed`, { cause: errorText });\n }\n\n const responseSchema = z.object({\n results: z.array(\n z.object({\n altText: z.string(),\n caption: z.string(),\n })\n ),\n });\n\n const { results } = responseSchema.parse(await res.json());\n strapi.log.http(`AI generated metadata successfully for ${results.length} files`);\n\n // Create sparse array with results at original indices\n // Example: files=[img1, pdf, img2] -> imageFiles=[{img1, index:0}, {img2, index:2}]\n // AI results=[meta1, meta2] -> sparse=[meta1, null, meta2]\n // This ensures metadata[i] corresponds to files[i], with null for non-images\n return imageFiles.reduce((sparseResults, { originalIndex }, resultIndex) => {\n sparseResults[originalIndex] = results[resultIndex];\n return sparseResults;\n }, new Array(files.length).fill(null));\n },\n };\n};\n\nexport { createAIMetadataService };\n"],"names":["SUPPORTED_IMAGE_TYPES","createAIMetadataService","strapi","aiServerUrl","process","env","STRAPI_AI_URL","isEnabled","isAIEnabled","config","get","hasAccess","ee","features","settings","plugin","service","getSettings","aiMetadata","countImagesWithoutMetadata","imagesWithoutMetadataCountPromise","db","query","count","where","mime","$in","$or","alternativeText","$null","caption","totalImagesPromise","imagesWithoutMetadataCount","totalImages","Promise","all","updateFilesWithAIMetadata","files","metadataResults","user","uploadService","map","file","index","updateData","altText","Object","keys","length","updateFileInfo","id","undefined","processExistingFiles","jobId","jobService","getService","updateJob","status","findMany","completedAt","Date","processFiles","error","log","Error","message","String","imageFiles","originalIndex","filter","startsWith","imageInputFiles","thumbnail","formats","filepath","url","mimetype","originalFilename","name","size","provider","Array","fill","formData","buildFormDataFromFiles","token","tokenData","getAiToken","cause","http","imageCount","res","fetch","method","body","headers","Authorization","ok","errorText","text","responseSchema","z","object","results","array","string","parse","json","reduce","sparseResults","resultIndex"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"api-upload-folder.js","sources":["../../../server/src/services/api-upload-folder.ts"],"sourcesContent":["import { isNil, get } from 'lodash/fp';\nimport { getService } from '../utils';\nimport { FOLDER_MODEL_UID, API_UPLOAD_FOLDER_BASE_NAME } from '../constants';\n\nconst getStore = () => strapi.store({ type: 'plugin', name: 'upload', key: 'api-folder' });\n\nconst createApiUploadFolder = async () => {\n let name = API_UPLOAD_FOLDER_BASE_NAME;\n const folderService = getService('folder');\n\n let exists = true;\n let index = 1;\n while (exists) {\n exists = await folderService.exists({ name, parent: null });\n if (exists) {\n name = `${API_UPLOAD_FOLDER_BASE_NAME} (${index})`;\n index += 1;\n }\n }\n\n const folder = await folderService.create({ name });\n\n await getStore().set({ value: { id: folder.id } });\n\n return folder;\n};\n\nconst getAPIUploadFolder = async () => {\n const storeValue = await getStore().get({});\n const folderId = get('id', storeValue);\n\n const folder = folderId\n ? await strapi.db.query(FOLDER_MODEL_UID).findOne({ where: { id: folderId } })\n : null;\n\n return isNil(folder) ? createApiUploadFolder() : folder;\n};\n\nexport default {\n getAPIUploadFolder,\n};\n"],"names":["getStore","strapi","store","type","name","key","createApiUploadFolder","API_UPLOAD_FOLDER_BASE_NAME","folderService","getService","exists","index","parent","folder","create","set","value","id","getAPIUploadFolder","storeValue","get","folderId","db","query","FOLDER_MODEL_UID","findOne","where","isNil"],"mappings":";;;;;;AAIA,MAAMA,QAAW,GAAA,IAAMC,MAAOC,CAAAA,KAAK,CAAC;QAAEC,IAAM,EAAA,QAAA;QAAUC,IAAM,EAAA,QAAA;QAAUC,GAAK,EAAA;AAAa,KAAA,CAAA;AAExF,MAAMC,qBAAwB,GAAA,UAAA;AAC5B,IAAA,IAAIF,IAAOG,GAAAA,qCAAAA;AACX,IAAA,MAAMC,gBAAgBC,gBAAW,CAAA,QAAA,CAAA;AAEjC,IAAA,IAAIC,MAAS,GAAA,IAAA;AACb,IAAA,IAAIC,OAAQ,GAAA,CAAA;AACZ,IAAA,MAAOD,MAAQ,CAAA;QACbA,MAAS,GAAA,MAAMF,aAAcE,CAAAA,MAAM,CAAC;AAAEN,YAAAA,IAAAA;YAAMQ,MAAQ,EAAA;AAAK,SAAA,CAAA;AACzD,QAAA,IAAIF,MAAQ,EAAA;AACVN,YAAAA,IAAAA,GAAO,GAAGG,qCAA4B,CAAA,EAAE,EAAEI,OAAAA,CAAM,CAAC,CAAC;YAClDA,OAAS,IAAA,CAAA;AACX;AACF;AAEA,IAAA,MAAME,MAAS,GAAA,MAAML,aAAcM,CAAAA,MAAM,CAAC;AAAEV,QAAAA;AAAK,KAAA,CAAA;IAEjD,MAAMJ,QAAAA,EAAAA,CAAWe,GAAG,CAAC;QAAEC,KAAO,EAAA;AAAEC,YAAAA,EAAAA,EAAIJ,OAAOI;AAAG;AAAE,KAAA,CAAA;IAEhD,OAAOJ,MAAAA;AACT,CAAA;AAEA,MAAMK,kBAAqB,GAAA,UAAA;AACzB,IAAA,MAAMC,UAAa,GAAA,MAAMnB,QAAWoB,EAAAA,CAAAA,GAAG,CAAC,EAAC,CAAA;IACzC,MAAMC,QAAAA,GAAWD,OAAI,IAAMD,EAAAA,UAAAA,CAAAA;IAE3B,MAAMN,MAAAA,GAASQ,QACX,GAAA,MAAMpB,MAAOqB,CAAAA,EAAE,CAACC,KAAK,CAACC,0BAAkBC,CAAAA,CAAAA,OAAO,CAAC;QAAEC,KAAO,EAAA;YAAET,EAAII,EAAAA;AAAS;KACxE,CAAA,GAAA,IAAA;IAEJ,OAAOM,QAAAA,CAAMd,UAAUP,qBAA0BO,EAAAA,GAAAA,MAAAA;AACnD,CAAA;AAEA,sBAAe;AACbK,IAAAA;AACF,CAAE;;;;"}
1
+ {"version":3,"file":"api-upload-folder.js","sources":["../../../server/src/services/api-upload-folder.ts"],"sourcesContent":["import { isNil, get } from 'lodash/fp';\nimport { getService } from '../utils';\nimport { FOLDER_MODEL_UID, API_UPLOAD_FOLDER_BASE_NAME } from '../constants';\n\nconst getStore = () => strapi.store({ type: 'plugin', name: 'upload', key: 'api-folder' });\n\nconst createApiUploadFolder = async () => {\n let name = API_UPLOAD_FOLDER_BASE_NAME;\n const folderService = getService('folder');\n\n let exists = true;\n let index = 1;\n while (exists) {\n exists = await folderService.exists({ name, parent: null });\n if (exists) {\n name = `${API_UPLOAD_FOLDER_BASE_NAME} (${index})`;\n index += 1;\n }\n }\n\n const folder = await folderService.create({ name });\n\n await getStore().set({ value: { id: folder.id } });\n\n return folder;\n};\n\nconst getAPIUploadFolder = async () => {\n const storeValue = await getStore().get({});\n const folderId = get('id', storeValue);\n\n const folder = folderId\n ? await strapi.db.query(FOLDER_MODEL_UID).findOne({ where: { id: folderId } })\n : null;\n\n return isNil(folder) ? createApiUploadFolder() : folder;\n};\n\nexport default {\n getAPIUploadFolder,\n};\n"],"names":["getStore","strapi","store","type","name","key","createApiUploadFolder","API_UPLOAD_FOLDER_BASE_NAME","folderService","getService","exists","index","parent","folder","create","set","value","id","getAPIUploadFolder","storeValue","get","folderId","db","query","FOLDER_MODEL_UID","findOne","where","isNil"],"mappings":";;;;;;AAIA,MAAMA,QAAAA,GAAW,IAAMC,MAAAA,CAAOC,KAAK,CAAC;QAAEC,IAAAA,EAAM,QAAA;QAAUC,IAAAA,EAAM,QAAA;QAAUC,GAAAA,EAAK;AAAa,KAAA,CAAA;AAExF,MAAMC,qBAAAA,GAAwB,UAAA;AAC5B,IAAA,IAAIF,IAAAA,GAAOG,qCAAAA;AACX,IAAA,MAAMC,gBAAgBC,gBAAAA,CAAW,QAAA,CAAA;AAEjC,IAAA,IAAIC,MAAAA,GAAS,IAAA;AACb,IAAA,IAAIC,OAAAA,GAAQ,CAAA;AACZ,IAAA,MAAOD,MAAAA,CAAQ;QACbA,MAAAA,GAAS,MAAMF,aAAAA,CAAcE,MAAM,CAAC;AAAEN,YAAAA,IAAAA;YAAMQ,MAAAA,EAAQ;AAAK,SAAA,CAAA;AACzD,QAAA,IAAIF,MAAAA,EAAQ;AACVN,YAAAA,IAAAA,GAAO,GAAGG,qCAAAA,CAA4B,EAAE,EAAEI,OAAAA,CAAM,CAAC,CAAC;YAClDA,OAAAA,IAAS,CAAA;AACX,QAAA;AACF,IAAA;AAEA,IAAA,MAAME,MAAAA,GAAS,MAAML,aAAAA,CAAcM,MAAM,CAAC;AAAEV,QAAAA;AAAK,KAAA,CAAA;IAEjD,MAAMJ,QAAAA,EAAAA,CAAWe,GAAG,CAAC;QAAEC,KAAAA,EAAO;AAAEC,YAAAA,EAAAA,EAAIJ,OAAOI;AAAG;AAAE,KAAA,CAAA;IAEhD,OAAOJ,MAAAA;AACT,CAAA;AAEA,MAAMK,kBAAAA,GAAqB,UAAA;AACzB,IAAA,MAAMC,UAAAA,GAAa,MAAMnB,QAAAA,EAAAA,CAAWoB,GAAG,CAAC,EAAC,CAAA;IACzC,MAAMC,QAAAA,GAAWD,OAAI,IAAA,EAAMD,UAAAA,CAAAA;IAE3B,MAAMN,MAAAA,GAASQ,QAAAA,GACX,MAAMpB,MAAAA,CAAOqB,EAAE,CAACC,KAAK,CAACC,0BAAAA,CAAAA,CAAkBC,OAAO,CAAC;QAAEC,KAAAA,EAAO;YAAET,EAAAA,EAAII;AAAS;KAAE,CAAA,GAC1E,IAAA;IAEJ,OAAOM,QAAAA,CAAMd,UAAUP,qBAAAA,EAAAA,GAA0BO,MAAAA;AACnD,CAAA;AAEA,sBAAe;AACbK,IAAAA;AACF,CAAA;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"api-upload-folder.mjs","sources":["../../../server/src/services/api-upload-folder.ts"],"sourcesContent":["import { isNil, get } from 'lodash/fp';\nimport { getService } from '../utils';\nimport { FOLDER_MODEL_UID, API_UPLOAD_FOLDER_BASE_NAME } from '../constants';\n\nconst getStore = () => strapi.store({ type: 'plugin', name: 'upload', key: 'api-folder' });\n\nconst createApiUploadFolder = async () => {\n let name = API_UPLOAD_FOLDER_BASE_NAME;\n const folderService = getService('folder');\n\n let exists = true;\n let index = 1;\n while (exists) {\n exists = await folderService.exists({ name, parent: null });\n if (exists) {\n name = `${API_UPLOAD_FOLDER_BASE_NAME} (${index})`;\n index += 1;\n }\n }\n\n const folder = await folderService.create({ name });\n\n await getStore().set({ value: { id: folder.id } });\n\n return folder;\n};\n\nconst getAPIUploadFolder = async () => {\n const storeValue = await getStore().get({});\n const folderId = get('id', storeValue);\n\n const folder = folderId\n ? await strapi.db.query(FOLDER_MODEL_UID).findOne({ where: { id: folderId } })\n : null;\n\n return isNil(folder) ? createApiUploadFolder() : folder;\n};\n\nexport default {\n getAPIUploadFolder,\n};\n"],"names":["getStore","strapi","store","type","name","key","createApiUploadFolder","API_UPLOAD_FOLDER_BASE_NAME","folderService","getService","exists","index","parent","folder","create","set","value","id","getAPIUploadFolder","storeValue","get","folderId","db","query","FOLDER_MODEL_UID","findOne","where","isNil"],"mappings":";;;;AAIA,MAAMA,QAAW,GAAA,IAAMC,MAAOC,CAAAA,KAAK,CAAC;QAAEC,IAAM,EAAA,QAAA;QAAUC,IAAM,EAAA,QAAA;QAAUC,GAAK,EAAA;AAAa,KAAA,CAAA;AAExF,MAAMC,qBAAwB,GAAA,UAAA;AAC5B,IAAA,IAAIF,IAAOG,GAAAA,2BAAAA;AACX,IAAA,MAAMC,gBAAgBC,UAAW,CAAA,QAAA,CAAA;AAEjC,IAAA,IAAIC,MAAS,GAAA,IAAA;AACb,IAAA,IAAIC,KAAQ,GAAA,CAAA;AACZ,IAAA,MAAOD,MAAQ,CAAA;QACbA,MAAS,GAAA,MAAMF,aAAcE,CAAAA,MAAM,CAAC;AAAEN,YAAAA,IAAAA;YAAMQ,MAAQ,EAAA;AAAK,SAAA,CAAA;AACzD,QAAA,IAAIF,MAAQ,EAAA;AACVN,YAAAA,IAAAA,GAAO,GAAGG,2BAA4B,CAAA,EAAE,EAAEI,KAAAA,CAAM,CAAC,CAAC;YAClDA,KAAS,IAAA,CAAA;AACX;AACF;AAEA,IAAA,MAAME,MAAS,GAAA,MAAML,aAAcM,CAAAA,MAAM,CAAC;AAAEV,QAAAA;AAAK,KAAA,CAAA;IAEjD,MAAMJ,QAAAA,EAAAA,CAAWe,GAAG,CAAC;QAAEC,KAAO,EAAA;AAAEC,YAAAA,EAAAA,EAAIJ,OAAOI;AAAG;AAAE,KAAA,CAAA;IAEhD,OAAOJ,MAAAA;AACT,CAAA;AAEA,MAAMK,kBAAqB,GAAA,UAAA;AACzB,IAAA,MAAMC,UAAa,GAAA,MAAMnB,QAAWoB,EAAAA,CAAAA,GAAG,CAAC,EAAC,CAAA;IACzC,MAAMC,QAAAA,GAAWD,IAAI,IAAMD,EAAAA,UAAAA,CAAAA;IAE3B,MAAMN,MAAAA,GAASQ,QACX,GAAA,MAAMpB,MAAOqB,CAAAA,EAAE,CAACC,KAAK,CAACC,gBAAkBC,CAAAA,CAAAA,OAAO,CAAC;QAAEC,KAAO,EAAA;YAAET,EAAII,EAAAA;AAAS;KACxE,CAAA,GAAA,IAAA;IAEJ,OAAOM,KAAAA,CAAMd,UAAUP,qBAA0BO,EAAAA,GAAAA,MAAAA;AACnD,CAAA;AAEA,sBAAe;AACbK,IAAAA;AACF,CAAE;;;;"}
1
+ {"version":3,"file":"api-upload-folder.mjs","sources":["../../../server/src/services/api-upload-folder.ts"],"sourcesContent":["import { isNil, get } from 'lodash/fp';\nimport { getService } from '../utils';\nimport { FOLDER_MODEL_UID, API_UPLOAD_FOLDER_BASE_NAME } from '../constants';\n\nconst getStore = () => strapi.store({ type: 'plugin', name: 'upload', key: 'api-folder' });\n\nconst createApiUploadFolder = async () => {\n let name = API_UPLOAD_FOLDER_BASE_NAME;\n const folderService = getService('folder');\n\n let exists = true;\n let index = 1;\n while (exists) {\n exists = await folderService.exists({ name, parent: null });\n if (exists) {\n name = `${API_UPLOAD_FOLDER_BASE_NAME} (${index})`;\n index += 1;\n }\n }\n\n const folder = await folderService.create({ name });\n\n await getStore().set({ value: { id: folder.id } });\n\n return folder;\n};\n\nconst getAPIUploadFolder = async () => {\n const storeValue = await getStore().get({});\n const folderId = get('id', storeValue);\n\n const folder = folderId\n ? await strapi.db.query(FOLDER_MODEL_UID).findOne({ where: { id: folderId } })\n : null;\n\n return isNil(folder) ? createApiUploadFolder() : folder;\n};\n\nexport default {\n getAPIUploadFolder,\n};\n"],"names":["getStore","strapi","store","type","name","key","createApiUploadFolder","API_UPLOAD_FOLDER_BASE_NAME","folderService","getService","exists","index","parent","folder","create","set","value","id","getAPIUploadFolder","storeValue","get","folderId","db","query","FOLDER_MODEL_UID","findOne","where","isNil"],"mappings":";;;;AAIA,MAAMA,QAAAA,GAAW,IAAMC,MAAAA,CAAOC,KAAK,CAAC;QAAEC,IAAAA,EAAM,QAAA;QAAUC,IAAAA,EAAM,QAAA;QAAUC,GAAAA,EAAK;AAAa,KAAA,CAAA;AAExF,MAAMC,qBAAAA,GAAwB,UAAA;AAC5B,IAAA,IAAIF,IAAAA,GAAOG,2BAAAA;AACX,IAAA,MAAMC,gBAAgBC,UAAAA,CAAW,QAAA,CAAA;AAEjC,IAAA,IAAIC,MAAAA,GAAS,IAAA;AACb,IAAA,IAAIC,KAAAA,GAAQ,CAAA;AACZ,IAAA,MAAOD,MAAAA,CAAQ;QACbA,MAAAA,GAAS,MAAMF,aAAAA,CAAcE,MAAM,CAAC;AAAEN,YAAAA,IAAAA;YAAMQ,MAAAA,EAAQ;AAAK,SAAA,CAAA;AACzD,QAAA,IAAIF,MAAAA,EAAQ;AACVN,YAAAA,IAAAA,GAAO,GAAGG,2BAAAA,CAA4B,EAAE,EAAEI,KAAAA,CAAM,CAAC,CAAC;YAClDA,KAAAA,IAAS,CAAA;AACX,QAAA;AACF,IAAA;AAEA,IAAA,MAAME,MAAAA,GAAS,MAAML,aAAAA,CAAcM,MAAM,CAAC;AAAEV,QAAAA;AAAK,KAAA,CAAA;IAEjD,MAAMJ,QAAAA,EAAAA,CAAWe,GAAG,CAAC;QAAEC,KAAAA,EAAO;AAAEC,YAAAA,EAAAA,EAAIJ,OAAOI;AAAG;AAAE,KAAA,CAAA;IAEhD,OAAOJ,MAAAA;AACT,CAAA;AAEA,MAAMK,kBAAAA,GAAqB,UAAA;AACzB,IAAA,MAAMC,UAAAA,GAAa,MAAMnB,QAAAA,EAAAA,CAAWoB,GAAG,CAAC,EAAC,CAAA;IACzC,MAAMC,QAAAA,GAAWD,IAAI,IAAA,EAAMD,UAAAA,CAAAA;IAE3B,MAAMN,MAAAA,GAASQ,QAAAA,GACX,MAAMpB,MAAAA,CAAOqB,EAAE,CAACC,KAAK,CAACC,gBAAAA,CAAAA,CAAkBC,OAAO,CAAC;QAAEC,KAAAA,EAAO;YAAET,EAAAA,EAAII;AAAS;KAAE,CAAA,GAC1E,IAAA;IAEJ,OAAOM,KAAAA,CAAMd,UAAUP,qBAAAA,EAAAA,GAA0BO,MAAAA;AACnD,CAAA;AAEA,sBAAe;AACbK,IAAAA;AACF,CAAA;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../../../server/src/services/extensions/index.ts"],"sourcesContent":["import { async } from '@strapi/utils';\nimport { signEntityMedia } from './utils';\n\nconst signFileUrlsOnDocumentService = async () => {\n const { provider } = strapi.plugins.upload;\n const isPrivate = await provider.isPrivate();\n\n // We only need to sign the file urls if the provider is private\n if (!isPrivate) {\n return;\n }\n\n strapi.documents.use(async (ctx, next) => {\n const uid = ctx.uid;\n const result: any = await next();\n\n if (ctx.action === 'findMany') {\n // Shape: [ entry ]\n return async.map(result, (entry: any) => signEntityMedia(entry, uid));\n }\n\n if (\n ctx.action === 'findFirst' ||\n ctx.action === 'findOne' ||\n ctx.action === 'create' ||\n ctx.action === 'update'\n ) {\n // Shape: entry\n return signEntityMedia(result, uid);\n }\n\n if (\n ctx.action === 'delete' ||\n ctx.action === 'clone' ||\n ctx.action === 'publish' ||\n ctx.action === 'unpublish' ||\n ctx.action === 'discardDraft'\n ) {\n // Shape: { entries: [ entry ] }\n // ...\n return {\n ...result,\n entries: await async.map(result.entries, (entry: any) => signEntityMedia(entry, uid)),\n };\n }\n\n return result;\n });\n};\n\nexport default {\n signFileUrlsOnDocumentService,\n};\n"],"names":["signFileUrlsOnDocumentService","provider","strapi","plugins","upload","isPrivate","documents","use","ctx","next","uid","result","action","async","map","entry","signEntityMedia","entries"],"mappings":";;;;;AAGA,MAAMA,6BAAgC,GAAA,UAAA;AACpC,IAAA,MAAM,EAAEC,QAAQ,EAAE,GAAGC,MAAOC,CAAAA,OAAO,CAACC,MAAM;IAC1C,MAAMC,SAAAA,GAAY,MAAMJ,QAAAA,CAASI,SAAS,EAAA;;AAG1C,IAAA,IAAI,CAACA,SAAW,EAAA;AACd,QAAA;AACF;AAEAH,IAAAA,MAAAA,CAAOI,SAAS,CAACC,GAAG,CAAC,OAAOC,GAAKC,EAAAA,IAAAA,GAAAA;QAC/B,MAAMC,GAAAA,GAAMF,IAAIE,GAAG;AACnB,QAAA,MAAMC,SAAc,MAAMF,IAAAA,EAAAA;QAE1B,IAAID,GAAAA,CAAII,MAAM,KAAK,UAAY,EAAA;;AAE7B,YAAA,OAAOC,YAAMC,GAAG,CAACH,QAAQ,CAACI,KAAAA,GAAeC,wBAAgBD,KAAOL,EAAAA,GAAAA,CAAAA,CAAAA;AAClE;AAEA,QAAA,IACEF,GAAII,CAAAA,MAAM,KAAK,WAAA,IACfJ,IAAII,MAAM,KAAK,SACfJ,IAAAA,GAAAA,CAAII,MAAM,KAAK,QAAA,IACfJ,GAAII,CAAAA,MAAM,KAAK,QACf,EAAA;;AAEA,YAAA,OAAOI,wBAAgBL,MAAQD,EAAAA,GAAAA,CAAAA;AACjC;AAEA,QAAA,IACEF,IAAII,MAAM,KAAK,YACfJ,GAAII,CAAAA,MAAM,KAAK,OACfJ,IAAAA,GAAAA,CAAII,MAAM,KAAK,SAAA,IACfJ,IAAII,MAAM,KAAK,eACfJ,GAAII,CAAAA,MAAM,KAAK,cACf,EAAA;;;YAGA,OAAO;AACL,gBAAA,GAAGD,MAAM;gBACTM,OAAS,EAAA,MAAMJ,WAAMC,CAAAA,GAAG,CAACH,MAAAA,CAAOM,OAAO,EAAE,CAACF,KAAeC,GAAAA,uBAAAA,CAAgBD,KAAOL,EAAAA,GAAAA,CAAAA;AAClF,aAAA;AACF;QAEA,OAAOC,MAAAA;AACT,KAAA,CAAA;AACF,CAAA;AAEA,iBAAe;AACbX,IAAAA;AACF,CAAE;;;;"}
1
+ {"version":3,"file":"index.js","sources":["../../../../server/src/services/extensions/index.ts"],"sourcesContent":["import { async } from '@strapi/utils';\nimport { signEntityMedia } from './utils';\n\nconst signFileUrlsOnDocumentService = async () => {\n const { provider } = strapi.plugins.upload;\n const isPrivate = await provider.isPrivate();\n\n // We only need to sign the file urls if the provider is private\n if (!isPrivate) {\n return;\n }\n\n strapi.documents.use(async (ctx, next) => {\n const uid = ctx.uid;\n const result: any = await next();\n\n if (ctx.action === 'findMany') {\n // Shape: [ entry ]\n return async.map(result, (entry: any) => signEntityMedia(entry, uid));\n }\n\n if (\n ctx.action === 'findFirst' ||\n ctx.action === 'findOne' ||\n ctx.action === 'create' ||\n ctx.action === 'update'\n ) {\n // Shape: entry\n return signEntityMedia(result, uid);\n }\n\n if (\n ctx.action === 'delete' ||\n ctx.action === 'clone' ||\n ctx.action === 'publish' ||\n ctx.action === 'unpublish' ||\n ctx.action === 'discardDraft'\n ) {\n // Shape: { entries: [ entry ] }\n // ...\n return {\n ...result,\n entries: await async.map(result.entries, (entry: any) => signEntityMedia(entry, uid)),\n };\n }\n\n return result;\n });\n};\n\nexport default {\n signFileUrlsOnDocumentService,\n};\n"],"names":["signFileUrlsOnDocumentService","provider","strapi","plugins","upload","isPrivate","documents","use","ctx","next","uid","result","action","async","map","entry","signEntityMedia","entries"],"mappings":";;;;;AAGA,MAAMA,6BAAAA,GAAgC,UAAA;AACpC,IAAA,MAAM,EAAEC,QAAQ,EAAE,GAAGC,MAAAA,CAAOC,OAAO,CAACC,MAAM;IAC1C,MAAMC,SAAAA,GAAY,MAAMJ,QAAAA,CAASI,SAAS,EAAA;;AAG1C,IAAA,IAAI,CAACA,SAAAA,EAAW;AACd,QAAA;AACF,IAAA;AAEAH,IAAAA,MAAAA,CAAOI,SAAS,CAACC,GAAG,CAAC,OAAOC,GAAAA,EAAKC,IAAAA,GAAAA;QAC/B,MAAMC,GAAAA,GAAMF,IAAIE,GAAG;AACnB,QAAA,MAAMC,SAAc,MAAMF,IAAAA,EAAAA;QAE1B,IAAID,GAAAA,CAAII,MAAM,KAAK,UAAA,EAAY;;AAE7B,YAAA,OAAOC,YAAMC,GAAG,CAACH,QAAQ,CAACI,KAAAA,GAAeC,wBAAgBD,KAAAA,EAAOL,GAAAA,CAAAA,CAAAA;AAClE,QAAA;AAEA,QAAA,IACEF,GAAAA,CAAII,MAAM,KAAK,WAAA,IACfJ,IAAII,MAAM,KAAK,SAAA,IACfJ,GAAAA,CAAII,MAAM,KAAK,QAAA,IACfJ,GAAAA,CAAII,MAAM,KAAK,QAAA,EACf;;AAEA,YAAA,OAAOI,wBAAgBL,MAAAA,EAAQD,GAAAA,CAAAA;AACjC,QAAA;AAEA,QAAA,IACEF,IAAII,MAAM,KAAK,YACfJ,GAAAA,CAAII,MAAM,KAAK,OAAA,IACfJ,GAAAA,CAAII,MAAM,KAAK,SAAA,IACfJ,IAAII,MAAM,KAAK,eACfJ,GAAAA,CAAII,MAAM,KAAK,cAAA,EACf;;;YAGA,OAAO;AACL,gBAAA,GAAGD,MAAM;gBACTM,OAAAA,EAAS,MAAMJ,WAAAA,CAAMC,GAAG,CAACH,MAAAA,CAAOM,OAAO,EAAE,CAACF,KAAAA,GAAeC,uBAAAA,CAAgBD,KAAAA,EAAOL,GAAAA,CAAAA;AAClF,aAAA;AACF,QAAA;QAEA,OAAOC,MAAAA;AACT,IAAA,CAAA,CAAA;AACF,CAAA;AAEA,iBAAe;AACbX,IAAAA;AACF,CAAA;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sources":["../../../../server/src/services/extensions/index.ts"],"sourcesContent":["import { async } from '@strapi/utils';\nimport { signEntityMedia } from './utils';\n\nconst signFileUrlsOnDocumentService = async () => {\n const { provider } = strapi.plugins.upload;\n const isPrivate = await provider.isPrivate();\n\n // We only need to sign the file urls if the provider is private\n if (!isPrivate) {\n return;\n }\n\n strapi.documents.use(async (ctx, next) => {\n const uid = ctx.uid;\n const result: any = await next();\n\n if (ctx.action === 'findMany') {\n // Shape: [ entry ]\n return async.map(result, (entry: any) => signEntityMedia(entry, uid));\n }\n\n if (\n ctx.action === 'findFirst' ||\n ctx.action === 'findOne' ||\n ctx.action === 'create' ||\n ctx.action === 'update'\n ) {\n // Shape: entry\n return signEntityMedia(result, uid);\n }\n\n if (\n ctx.action === 'delete' ||\n ctx.action === 'clone' ||\n ctx.action === 'publish' ||\n ctx.action === 'unpublish' ||\n ctx.action === 'discardDraft'\n ) {\n // Shape: { entries: [ entry ] }\n // ...\n return {\n ...result,\n entries: await async.map(result.entries, (entry: any) => signEntityMedia(entry, uid)),\n };\n }\n\n return result;\n });\n};\n\nexport default {\n signFileUrlsOnDocumentService,\n};\n"],"names":["signFileUrlsOnDocumentService","provider","strapi","plugins","upload","isPrivate","documents","use","ctx","next","uid","result","action","async","map","entry","signEntityMedia","entries"],"mappings":";;;AAGA,MAAMA,6BAAgC,GAAA,UAAA;AACpC,IAAA,MAAM,EAAEC,QAAQ,EAAE,GAAGC,MAAOC,CAAAA,OAAO,CAACC,MAAM;IAC1C,MAAMC,SAAAA,GAAY,MAAMJ,QAAAA,CAASI,SAAS,EAAA;;AAG1C,IAAA,IAAI,CAACA,SAAW,EAAA;AACd,QAAA;AACF;AAEAH,IAAAA,MAAAA,CAAOI,SAAS,CAACC,GAAG,CAAC,OAAOC,GAAKC,EAAAA,IAAAA,GAAAA;QAC/B,MAAMC,GAAAA,GAAMF,IAAIE,GAAG;AACnB,QAAA,MAAMC,SAAc,MAAMF,IAAAA,EAAAA;QAE1B,IAAID,GAAAA,CAAII,MAAM,KAAK,UAAY,EAAA;;AAE7B,YAAA,OAAOC,MAAMC,GAAG,CAACH,QAAQ,CAACI,KAAAA,GAAeC,gBAAgBD,KAAOL,EAAAA,GAAAA,CAAAA,CAAAA;AAClE;AAEA,QAAA,IACEF,GAAII,CAAAA,MAAM,KAAK,WAAA,IACfJ,IAAII,MAAM,KAAK,SACfJ,IAAAA,GAAAA,CAAII,MAAM,KAAK,QAAA,IACfJ,GAAII,CAAAA,MAAM,KAAK,QACf,EAAA;;AAEA,YAAA,OAAOI,gBAAgBL,MAAQD,EAAAA,GAAAA,CAAAA;AACjC;AAEA,QAAA,IACEF,IAAII,MAAM,KAAK,YACfJ,GAAII,CAAAA,MAAM,KAAK,OACfJ,IAAAA,GAAAA,CAAII,MAAM,KAAK,SAAA,IACfJ,IAAII,MAAM,KAAK,eACfJ,GAAII,CAAAA,MAAM,KAAK,cACf,EAAA;;;YAGA,OAAO;AACL,gBAAA,GAAGD,MAAM;gBACTM,OAAS,EAAA,MAAMJ,KAAMC,CAAAA,GAAG,CAACH,MAAAA,CAAOM,OAAO,EAAE,CAACF,KAAeC,GAAAA,eAAAA,CAAgBD,KAAOL,EAAAA,GAAAA,CAAAA;AAClF,aAAA;AACF;QAEA,OAAOC,MAAAA;AACT,KAAA,CAAA;AACF,CAAA;AAEA,iBAAe;AACbX,IAAAA;AACF,CAAE;;;;"}
1
+ {"version":3,"file":"index.mjs","sources":["../../../../server/src/services/extensions/index.ts"],"sourcesContent":["import { async } from '@strapi/utils';\nimport { signEntityMedia } from './utils';\n\nconst signFileUrlsOnDocumentService = async () => {\n const { provider } = strapi.plugins.upload;\n const isPrivate = await provider.isPrivate();\n\n // We only need to sign the file urls if the provider is private\n if (!isPrivate) {\n return;\n }\n\n strapi.documents.use(async (ctx, next) => {\n const uid = ctx.uid;\n const result: any = await next();\n\n if (ctx.action === 'findMany') {\n // Shape: [ entry ]\n return async.map(result, (entry: any) => signEntityMedia(entry, uid));\n }\n\n if (\n ctx.action === 'findFirst' ||\n ctx.action === 'findOne' ||\n ctx.action === 'create' ||\n ctx.action === 'update'\n ) {\n // Shape: entry\n return signEntityMedia(result, uid);\n }\n\n if (\n ctx.action === 'delete' ||\n ctx.action === 'clone' ||\n ctx.action === 'publish' ||\n ctx.action === 'unpublish' ||\n ctx.action === 'discardDraft'\n ) {\n // Shape: { entries: [ entry ] }\n // ...\n return {\n ...result,\n entries: await async.map(result.entries, (entry: any) => signEntityMedia(entry, uid)),\n };\n }\n\n return result;\n });\n};\n\nexport default {\n signFileUrlsOnDocumentService,\n};\n"],"names":["signFileUrlsOnDocumentService","provider","strapi","plugins","upload","isPrivate","documents","use","ctx","next","uid","result","action","async","map","entry","signEntityMedia","entries"],"mappings":";;;AAGA,MAAMA,6BAAAA,GAAgC,UAAA;AACpC,IAAA,MAAM,EAAEC,QAAQ,EAAE,GAAGC,MAAAA,CAAOC,OAAO,CAACC,MAAM;IAC1C,MAAMC,SAAAA,GAAY,MAAMJ,QAAAA,CAASI,SAAS,EAAA;;AAG1C,IAAA,IAAI,CAACA,SAAAA,EAAW;AACd,QAAA;AACF,IAAA;AAEAH,IAAAA,MAAAA,CAAOI,SAAS,CAACC,GAAG,CAAC,OAAOC,GAAAA,EAAKC,IAAAA,GAAAA;QAC/B,MAAMC,GAAAA,GAAMF,IAAIE,GAAG;AACnB,QAAA,MAAMC,SAAc,MAAMF,IAAAA,EAAAA;QAE1B,IAAID,GAAAA,CAAII,MAAM,KAAK,UAAA,EAAY;;AAE7B,YAAA,OAAOC,MAAMC,GAAG,CAACH,QAAQ,CAACI,KAAAA,GAAeC,gBAAgBD,KAAAA,EAAOL,GAAAA,CAAAA,CAAAA;AAClE,QAAA;AAEA,QAAA,IACEF,GAAAA,CAAII,MAAM,KAAK,WAAA,IACfJ,IAAII,MAAM,KAAK,SAAA,IACfJ,GAAAA,CAAII,MAAM,KAAK,QAAA,IACfJ,GAAAA,CAAII,MAAM,KAAK,QAAA,EACf;;AAEA,YAAA,OAAOI,gBAAgBL,MAAAA,EAAQD,GAAAA,CAAAA;AACjC,QAAA;AAEA,QAAA,IACEF,IAAII,MAAM,KAAK,YACfJ,GAAAA,CAAII,MAAM,KAAK,OAAA,IACfJ,GAAAA,CAAII,MAAM,KAAK,SAAA,IACfJ,IAAII,MAAM,KAAK,eACfJ,GAAAA,CAAII,MAAM,KAAK,cAAA,EACf;;;YAGA,OAAO;AACL,gBAAA,GAAGD,MAAM;gBACTM,OAAAA,EAAS,MAAMJ,KAAAA,CAAMC,GAAG,CAACH,MAAAA,CAAOM,OAAO,EAAE,CAACF,KAAAA,GAAeC,eAAAA,CAAgBD,KAAAA,EAAOL,GAAAA,CAAAA;AAClF,aAAA;AACF,QAAA;QAEA,OAAOC,MAAAA;AACT,IAAA,CAAA,CAAA;AACF,CAAA;AAEA,iBAAe;AACbX,IAAAA;AACF,CAAA;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sources":["../../../../server/src/services/extensions/utils.ts"],"sourcesContent":["import { async, traverseEntity } from '@strapi/utils';\n\nimport type { Schema, UID } from '@strapi/types';\n\nimport { getService } from '../../utils';\nimport { FILE_MODEL_UID } from '../../constants';\n\nimport type { File } from '../../types';\n\ntype SignEntityMediaVisitor = (\n args: {\n key: string;\n value: unknown;\n attribute: Schema.Attribute.AnyAttribute;\n },\n utils: {\n set: (key: string, value: unknown) => void;\n }\n) => Promise<void>;\n\nfunction isFile(value: unknown, attribute: Schema.Attribute.AnyAttribute): value is File {\n if (!value || attribute.type !== 'media') {\n return false;\n }\n\n return true;\n}\n\n/**\n * Visitor function to sign media URLs\n */\nconst signEntityMediaVisitor: SignEntityMediaVisitor = async (\n { key, value, attribute },\n { set }\n) => {\n const { signFileUrls } = getService('file');\n\n if (!attribute) {\n return;\n }\n\n if (attribute.type !== 'media') {\n return;\n }\n\n if (isFile(value, attribute)) {\n // If the attribute is repeatable sign each file\n if (attribute.multiple) {\n const signedFiles = await async.map(value, signFileUrls);\n set(key, signedFiles);\n return;\n }\n\n // If the attribute is not repeatable only sign a single file\n const signedFile = await signFileUrls(value);\n set(key, signedFile);\n }\n};\n\n/**\n *\n * Iterate through an entity manager result\n * Check which modelAttributes are media and pre sign the image URLs\n * if they are from the current upload provider\n *\n * @param {Object} entity\n * @param {Object} modelAttributes\n * @returns\n */\nconst signEntityMedia = async (entity: any, uid: UID.Schema) => {\n if (!entity) {\n return entity;\n }\n\n // If the entity itself is a file, sign it directly\n if (uid === FILE_MODEL_UID) {\n const { signFileUrls } = getService('file');\n return signFileUrls(entity);\n }\n\n // If the entity is a regular content type, look for media attributes\n const model = strapi.getModel(uid);\n return traverseEntity(\n // @ts-expect-error - FIXME: fix traverseEntity using wrong types\n signEntityMediaVisitor,\n { schema: model, getModel: strapi.getModel.bind(strapi) },\n entity\n );\n};\n\nexport { signEntityMedia };\n"],"names":["isFile","value","attribute","type","signEntityMediaVisitor","key","set","signFileUrls","getService","multiple","signedFiles","async","map","signedFile","signEntityMedia","entity","uid","FILE_MODEL_UID","model","strapi","getModel","traverseEntity","schema","bind"],"mappings":";;;;;;AAoBA,SAASA,MAAAA,CAAOC,KAAc,EAAEC,SAAwC,EAAA;AACtE,IAAA,IAAI,CAACD,KAAAA,IAASC,SAAUC,CAAAA,IAAI,KAAK,OAAS,EAAA;QACxC,OAAO,KAAA;AACT;IAEA,OAAO,IAAA;AACT;AAEA;;AAEC,IACD,MAAMC,sBAAAA,GAAiD,OACrD,EAAEC,GAAG,EAAEJ,KAAK,EAAEC,SAAS,EAAE,EACzB,EAAEI,GAAG,EAAE,GAAA;AAEP,IAAA,MAAM,EAAEC,YAAY,EAAE,GAAGC,gBAAW,CAAA,MAAA,CAAA;AAEpC,IAAA,IAAI,CAACN,SAAW,EAAA;AACd,QAAA;AACF;IAEA,IAAIA,SAAAA,CAAUC,IAAI,KAAK,OAAS,EAAA;AAC9B,QAAA;AACF;IAEA,IAAIH,MAAAA,CAAOC,OAAOC,SAAY,CAAA,EAAA;;QAE5B,IAAIA,SAAAA,CAAUO,QAAQ,EAAE;AACtB,YAAA,MAAMC,WAAc,GAAA,MAAMC,WAAMC,CAAAA,GAAG,CAACX,KAAOM,EAAAA,YAAAA,CAAAA;AAC3CD,YAAAA,GAAAA,CAAID,GAAKK,EAAAA,WAAAA,CAAAA;AACT,YAAA;AACF;;QAGA,MAAMG,UAAAA,GAAa,MAAMN,YAAaN,CAAAA,KAAAA,CAAAA;AACtCK,QAAAA,GAAAA,CAAID,GAAKQ,EAAAA,UAAAA,CAAAA;AACX;AACF,CAAA;AAEA;;;;;;;;;IAUA,MAAMC,eAAkB,GAAA,OAAOC,MAAaC,EAAAA,GAAAA,GAAAA;AAC1C,IAAA,IAAI,CAACD,MAAQ,EAAA;QACX,OAAOA,MAAAA;AACT;;AAGA,IAAA,IAAIC,QAAQC,wBAAgB,EAAA;AAC1B,QAAA,MAAM,EAAEV,YAAY,EAAE,GAAGC,gBAAW,CAAA,MAAA,CAAA;AACpC,QAAA,OAAOD,YAAaQ,CAAAA,MAAAA,CAAAA;AACtB;;IAGA,MAAMG,KAAAA,GAAQC,MAAOC,CAAAA,QAAQ,CAACJ,GAAAA,CAAAA;AAC9B,IAAA,OAAOK;IAELjB,sBACA,EAAA;QAAEkB,MAAQJ,EAAAA,KAAAA;AAAOE,QAAAA,QAAAA,EAAUD,MAAOC,CAAAA,QAAQ,CAACG,IAAI,CAACJ,MAAAA;KAChDJ,EAAAA,MAAAA,CAAAA;AAEJ;;;;"}
1
+ {"version":3,"file":"utils.js","sources":["../../../../server/src/services/extensions/utils.ts"],"sourcesContent":["import { async, traverseEntity } from '@strapi/utils';\n\nimport type { Schema, UID } from '@strapi/types';\n\nimport { getService } from '../../utils';\nimport { FILE_MODEL_UID } from '../../constants';\n\nimport type { File } from '../../types';\n\ntype SignEntityMediaVisitor = (\n args: {\n key: string;\n value: unknown;\n attribute: Schema.Attribute.AnyAttribute;\n },\n utils: {\n set: (key: string, value: unknown) => void;\n }\n) => Promise<void>;\n\nfunction isFile(value: unknown, attribute: Schema.Attribute.AnyAttribute): value is File {\n if (!value || attribute.type !== 'media') {\n return false;\n }\n\n return true;\n}\n\n/**\n * Visitor function to sign media URLs\n */\nconst signEntityMediaVisitor: SignEntityMediaVisitor = async (\n { key, value, attribute },\n { set }\n) => {\n const { signFileUrls } = getService('file');\n\n if (!attribute) {\n return;\n }\n\n if (attribute.type !== 'media') {\n return;\n }\n\n if (isFile(value, attribute)) {\n // If the attribute is repeatable sign each file\n if (attribute.multiple) {\n const signedFiles = await async.map(value, signFileUrls);\n set(key, signedFiles);\n return;\n }\n\n // If the attribute is not repeatable only sign a single file\n const signedFile = await signFileUrls(value);\n set(key, signedFile);\n }\n};\n\n/**\n *\n * Iterate through an entity manager result\n * Check which modelAttributes are media and pre sign the image URLs\n * if they are from the current upload provider\n *\n * @param {Object} entity\n * @param {Object} modelAttributes\n * @returns\n */\nconst signEntityMedia = async (entity: any, uid: UID.Schema) => {\n if (!entity) {\n return entity;\n }\n\n // If the entity itself is a file, sign it directly\n if (uid === FILE_MODEL_UID) {\n const { signFileUrls } = getService('file');\n return signFileUrls(entity);\n }\n\n // If the entity is a regular content type, look for media attributes\n const model = strapi.getModel(uid);\n return traverseEntity(\n // @ts-expect-error - FIXME: fix traverseEntity using wrong types\n signEntityMediaVisitor,\n { schema: model, getModel: strapi.getModel.bind(strapi) },\n entity\n );\n};\n\nexport { signEntityMedia };\n"],"names":["isFile","value","attribute","type","signEntityMediaVisitor","key","set","signFileUrls","getService","multiple","signedFiles","async","map","signedFile","signEntityMedia","entity","uid","FILE_MODEL_UID","model","strapi","getModel","traverseEntity","schema","bind"],"mappings":";;;;;;AAoBA,SAASA,MAAAA,CAAOC,KAAc,EAAEC,SAAwC,EAAA;AACtE,IAAA,IAAI,CAACD,KAAAA,IAASC,SAAAA,CAAUC,IAAI,KAAK,OAAA,EAAS;QACxC,OAAO,KAAA;AACT,IAAA;IAEA,OAAO,IAAA;AACT;AAEA;;AAEC,IACD,MAAMC,sBAAAA,GAAiD,OACrD,EAAEC,GAAG,EAAEJ,KAAK,EAAEC,SAAS,EAAE,EACzB,EAAEI,GAAG,EAAE,GAAA;AAEP,IAAA,MAAM,EAAEC,YAAY,EAAE,GAAGC,gBAAAA,CAAW,MAAA,CAAA;AAEpC,IAAA,IAAI,CAACN,SAAAA,EAAW;AACd,QAAA;AACF,IAAA;IAEA,IAAIA,SAAAA,CAAUC,IAAI,KAAK,OAAA,EAAS;AAC9B,QAAA;AACF,IAAA;IAEA,IAAIH,MAAAA,CAAOC,OAAOC,SAAAA,CAAAA,EAAY;;QAE5B,IAAIA,SAAAA,CAAUO,QAAQ,EAAE;AACtB,YAAA,MAAMC,WAAAA,GAAc,MAAMC,WAAAA,CAAMC,GAAG,CAACX,KAAAA,EAAOM,YAAAA,CAAAA;AAC3CD,YAAAA,GAAAA,CAAID,GAAAA,EAAKK,WAAAA,CAAAA;AACT,YAAA;AACF,QAAA;;QAGA,MAAMG,UAAAA,GAAa,MAAMN,YAAAA,CAAaN,KAAAA,CAAAA;AACtCK,QAAAA,GAAAA,CAAID,GAAAA,EAAKQ,UAAAA,CAAAA;AACX,IAAA;AACF,CAAA;AAEA;;;;;;;;;IAUA,MAAMC,eAAAA,GAAkB,OAAOC,MAAAA,EAAaC,GAAAA,GAAAA;AAC1C,IAAA,IAAI,CAACD,MAAAA,EAAQ;QACX,OAAOA,MAAAA;AACT,IAAA;;AAGA,IAAA,IAAIC,QAAQC,wBAAAA,EAAgB;AAC1B,QAAA,MAAM,EAAEV,YAAY,EAAE,GAAGC,gBAAAA,CAAW,MAAA,CAAA;AACpC,QAAA,OAAOD,YAAAA,CAAaQ,MAAAA,CAAAA;AACtB,IAAA;;IAGA,MAAMG,KAAAA,GAAQC,MAAAA,CAAOC,QAAQ,CAACJ,GAAAA,CAAAA;AAC9B,IAAA,OAAOK;IAELjB,sBAAAA,EACA;QAAEkB,MAAAA,EAAQJ,KAAAA;AAAOE,QAAAA,QAAAA,EAAUD,MAAAA,CAAOC,QAAQ,CAACG,IAAI,CAACJ,MAAAA;KAAQ,EACxDJ,MAAAA,CAAAA;AAEJ;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"utils.mjs","sources":["../../../../server/src/services/extensions/utils.ts"],"sourcesContent":["import { async, traverseEntity } from '@strapi/utils';\n\nimport type { Schema, UID } from '@strapi/types';\n\nimport { getService } from '../../utils';\nimport { FILE_MODEL_UID } from '../../constants';\n\nimport type { File } from '../../types';\n\ntype SignEntityMediaVisitor = (\n args: {\n key: string;\n value: unknown;\n attribute: Schema.Attribute.AnyAttribute;\n },\n utils: {\n set: (key: string, value: unknown) => void;\n }\n) => Promise<void>;\n\nfunction isFile(value: unknown, attribute: Schema.Attribute.AnyAttribute): value is File {\n if (!value || attribute.type !== 'media') {\n return false;\n }\n\n return true;\n}\n\n/**\n * Visitor function to sign media URLs\n */\nconst signEntityMediaVisitor: SignEntityMediaVisitor = async (\n { key, value, attribute },\n { set }\n) => {\n const { signFileUrls } = getService('file');\n\n if (!attribute) {\n return;\n }\n\n if (attribute.type !== 'media') {\n return;\n }\n\n if (isFile(value, attribute)) {\n // If the attribute is repeatable sign each file\n if (attribute.multiple) {\n const signedFiles = await async.map(value, signFileUrls);\n set(key, signedFiles);\n return;\n }\n\n // If the attribute is not repeatable only sign a single file\n const signedFile = await signFileUrls(value);\n set(key, signedFile);\n }\n};\n\n/**\n *\n * Iterate through an entity manager result\n * Check which modelAttributes are media and pre sign the image URLs\n * if they are from the current upload provider\n *\n * @param {Object} entity\n * @param {Object} modelAttributes\n * @returns\n */\nconst signEntityMedia = async (entity: any, uid: UID.Schema) => {\n if (!entity) {\n return entity;\n }\n\n // If the entity itself is a file, sign it directly\n if (uid === FILE_MODEL_UID) {\n const { signFileUrls } = getService('file');\n return signFileUrls(entity);\n }\n\n // If the entity is a regular content type, look for media attributes\n const model = strapi.getModel(uid);\n return traverseEntity(\n // @ts-expect-error - FIXME: fix traverseEntity using wrong types\n signEntityMediaVisitor,\n { schema: model, getModel: strapi.getModel.bind(strapi) },\n entity\n );\n};\n\nexport { signEntityMedia };\n"],"names":["isFile","value","attribute","type","signEntityMediaVisitor","key","set","signFileUrls","getService","multiple","signedFiles","async","map","signedFile","signEntityMedia","entity","uid","FILE_MODEL_UID","model","strapi","getModel","traverseEntity","schema","bind"],"mappings":";;;;AAoBA,SAASA,MAAAA,CAAOC,KAAc,EAAEC,SAAwC,EAAA;AACtE,IAAA,IAAI,CAACD,KAAAA,IAASC,SAAUC,CAAAA,IAAI,KAAK,OAAS,EAAA;QACxC,OAAO,KAAA;AACT;IAEA,OAAO,IAAA;AACT;AAEA;;AAEC,IACD,MAAMC,sBAAAA,GAAiD,OACrD,EAAEC,GAAG,EAAEJ,KAAK,EAAEC,SAAS,EAAE,EACzB,EAAEI,GAAG,EAAE,GAAA;AAEP,IAAA,MAAM,EAAEC,YAAY,EAAE,GAAGC,UAAW,CAAA,MAAA,CAAA;AAEpC,IAAA,IAAI,CAACN,SAAW,EAAA;AACd,QAAA;AACF;IAEA,IAAIA,SAAAA,CAAUC,IAAI,KAAK,OAAS,EAAA;AAC9B,QAAA;AACF;IAEA,IAAIH,MAAAA,CAAOC,OAAOC,SAAY,CAAA,EAAA;;QAE5B,IAAIA,SAAAA,CAAUO,QAAQ,EAAE;AACtB,YAAA,MAAMC,WAAc,GAAA,MAAMC,KAAMC,CAAAA,GAAG,CAACX,KAAOM,EAAAA,YAAAA,CAAAA;AAC3CD,YAAAA,GAAAA,CAAID,GAAKK,EAAAA,WAAAA,CAAAA;AACT,YAAA;AACF;;QAGA,MAAMG,UAAAA,GAAa,MAAMN,YAAaN,CAAAA,KAAAA,CAAAA;AACtCK,QAAAA,GAAAA,CAAID,GAAKQ,EAAAA,UAAAA,CAAAA;AACX;AACF,CAAA;AAEA;;;;;;;;;IAUA,MAAMC,eAAkB,GAAA,OAAOC,MAAaC,EAAAA,GAAAA,GAAAA;AAC1C,IAAA,IAAI,CAACD,MAAQ,EAAA;QACX,OAAOA,MAAAA;AACT;;AAGA,IAAA,IAAIC,QAAQC,cAAgB,EAAA;AAC1B,QAAA,MAAM,EAAEV,YAAY,EAAE,GAAGC,UAAW,CAAA,MAAA,CAAA;AACpC,QAAA,OAAOD,YAAaQ,CAAAA,MAAAA,CAAAA;AACtB;;IAGA,MAAMG,KAAAA,GAAQC,MAAOC,CAAAA,QAAQ,CAACJ,GAAAA,CAAAA;AAC9B,IAAA,OAAOK;IAELjB,sBACA,EAAA;QAAEkB,MAAQJ,EAAAA,KAAAA;AAAOE,QAAAA,QAAAA,EAAUD,MAAOC,CAAAA,QAAQ,CAACG,IAAI,CAACJ,MAAAA;KAChDJ,EAAAA,MAAAA,CAAAA;AAEJ;;;;"}
1
+ {"version":3,"file":"utils.mjs","sources":["../../../../server/src/services/extensions/utils.ts"],"sourcesContent":["import { async, traverseEntity } from '@strapi/utils';\n\nimport type { Schema, UID } from '@strapi/types';\n\nimport { getService } from '../../utils';\nimport { FILE_MODEL_UID } from '../../constants';\n\nimport type { File } from '../../types';\n\ntype SignEntityMediaVisitor = (\n args: {\n key: string;\n value: unknown;\n attribute: Schema.Attribute.AnyAttribute;\n },\n utils: {\n set: (key: string, value: unknown) => void;\n }\n) => Promise<void>;\n\nfunction isFile(value: unknown, attribute: Schema.Attribute.AnyAttribute): value is File {\n if (!value || attribute.type !== 'media') {\n return false;\n }\n\n return true;\n}\n\n/**\n * Visitor function to sign media URLs\n */\nconst signEntityMediaVisitor: SignEntityMediaVisitor = async (\n { key, value, attribute },\n { set }\n) => {\n const { signFileUrls } = getService('file');\n\n if (!attribute) {\n return;\n }\n\n if (attribute.type !== 'media') {\n return;\n }\n\n if (isFile(value, attribute)) {\n // If the attribute is repeatable sign each file\n if (attribute.multiple) {\n const signedFiles = await async.map(value, signFileUrls);\n set(key, signedFiles);\n return;\n }\n\n // If the attribute is not repeatable only sign a single file\n const signedFile = await signFileUrls(value);\n set(key, signedFile);\n }\n};\n\n/**\n *\n * Iterate through an entity manager result\n * Check which modelAttributes are media and pre sign the image URLs\n * if they are from the current upload provider\n *\n * @param {Object} entity\n * @param {Object} modelAttributes\n * @returns\n */\nconst signEntityMedia = async (entity: any, uid: UID.Schema) => {\n if (!entity) {\n return entity;\n }\n\n // If the entity itself is a file, sign it directly\n if (uid === FILE_MODEL_UID) {\n const { signFileUrls } = getService('file');\n return signFileUrls(entity);\n }\n\n // If the entity is a regular content type, look for media attributes\n const model = strapi.getModel(uid);\n return traverseEntity(\n // @ts-expect-error - FIXME: fix traverseEntity using wrong types\n signEntityMediaVisitor,\n { schema: model, getModel: strapi.getModel.bind(strapi) },\n entity\n );\n};\n\nexport { signEntityMedia };\n"],"names":["isFile","value","attribute","type","signEntityMediaVisitor","key","set","signFileUrls","getService","multiple","signedFiles","async","map","signedFile","signEntityMedia","entity","uid","FILE_MODEL_UID","model","strapi","getModel","traverseEntity","schema","bind"],"mappings":";;;;AAoBA,SAASA,MAAAA,CAAOC,KAAc,EAAEC,SAAwC,EAAA;AACtE,IAAA,IAAI,CAACD,KAAAA,IAASC,SAAAA,CAAUC,IAAI,KAAK,OAAA,EAAS;QACxC,OAAO,KAAA;AACT,IAAA;IAEA,OAAO,IAAA;AACT;AAEA;;AAEC,IACD,MAAMC,sBAAAA,GAAiD,OACrD,EAAEC,GAAG,EAAEJ,KAAK,EAAEC,SAAS,EAAE,EACzB,EAAEI,GAAG,EAAE,GAAA;AAEP,IAAA,MAAM,EAAEC,YAAY,EAAE,GAAGC,UAAAA,CAAW,MAAA,CAAA;AAEpC,IAAA,IAAI,CAACN,SAAAA,EAAW;AACd,QAAA;AACF,IAAA;IAEA,IAAIA,SAAAA,CAAUC,IAAI,KAAK,OAAA,EAAS;AAC9B,QAAA;AACF,IAAA;IAEA,IAAIH,MAAAA,CAAOC,OAAOC,SAAAA,CAAAA,EAAY;;QAE5B,IAAIA,SAAAA,CAAUO,QAAQ,EAAE;AACtB,YAAA,MAAMC,WAAAA,GAAc,MAAMC,KAAAA,CAAMC,GAAG,CAACX,KAAAA,EAAOM,YAAAA,CAAAA;AAC3CD,YAAAA,GAAAA,CAAID,GAAAA,EAAKK,WAAAA,CAAAA;AACT,YAAA;AACF,QAAA;;QAGA,MAAMG,UAAAA,GAAa,MAAMN,YAAAA,CAAaN,KAAAA,CAAAA;AACtCK,QAAAA,GAAAA,CAAID,GAAAA,EAAKQ,UAAAA,CAAAA;AACX,IAAA;AACF,CAAA;AAEA;;;;;;;;;IAUA,MAAMC,eAAAA,GAAkB,OAAOC,MAAAA,EAAaC,GAAAA,GAAAA;AAC1C,IAAA,IAAI,CAACD,MAAAA,EAAQ;QACX,OAAOA,MAAAA;AACT,IAAA;;AAGA,IAAA,IAAIC,QAAQC,cAAAA,EAAgB;AAC1B,QAAA,MAAM,EAAEV,YAAY,EAAE,GAAGC,UAAAA,CAAW,MAAA,CAAA;AACpC,QAAA,OAAOD,YAAAA,CAAaQ,MAAAA,CAAAA;AACtB,IAAA;;IAGA,MAAMG,KAAAA,GAAQC,MAAAA,CAAOC,QAAQ,CAACJ,GAAAA,CAAAA;AAC9B,IAAA,OAAOK;IAELjB,sBAAAA,EACA;QAAEkB,MAAAA,EAAQJ,KAAAA;AAAOE,QAAAA,QAAAA,EAAUD,MAAAA,CAAOC,QAAQ,CAACG,IAAI,CAACJ,MAAAA;KAAQ,EACxDJ,MAAAA,CAAAA;AAEJ;;;;"}
@@ -1,10 +1,128 @@
1
1
  'use strict';
2
2
 
3
+ var path = require('path');
4
+ var dns = require('dns/promises');
5
+ var net = require('net');
6
+ var fse = require('fs-extra');
3
7
  var fp = require('lodash/fp');
4
8
  var utils = require('@strapi/utils');
5
9
  var constants = require('../constants.js');
6
10
  var index = require('../utils/index.js');
7
11
 
12
+ const { ApplicationError } = utils.errors;
13
+ const FETCH_TIMEOUT_MS = 60000; // 60 seconds
14
+ // Blocks loopback, link-local (cloud metadata), and RFC-1918 private ranges to prevent SSRF
15
+ const SSRF_BLOCK_LIST = new net.BlockList();
16
+ SSRF_BLOCK_LIST.addSubnet('127.0.0.0', 8); // loopback
17
+ SSRF_BLOCK_LIST.addSubnet('10.0.0.0', 8); // RFC-1918
18
+ SSRF_BLOCK_LIST.addSubnet('172.16.0.0', 12); // RFC-1918
19
+ SSRF_BLOCK_LIST.addSubnet('192.168.0.0', 16); // RFC-1918
20
+ SSRF_BLOCK_LIST.addSubnet('169.254.0.0', 16); // link-local / cloud metadata (AWS, GCP, Azure)
21
+ SSRF_BLOCK_LIST.addSubnet('::1', 128, 'ipv6'); // IPv6 loopback
22
+ SSRF_BLOCK_LIST.addSubnet('fc00::', 7, 'ipv6'); // IPv6 unique local
23
+ SSRF_BLOCK_LIST.addSubnet('fe80::', 10, 'ipv6'); // IPv6 link-local
24
+ /**
25
+ * Extracts filename from a URL path or Content-Disposition header
26
+ */ const getFilenameFromUrl = (url, contentDisposition)=>{
27
+ // Try Content-Disposition header first
28
+ if (contentDisposition) {
29
+ // Extracts filename from Content-Disposition header (e.g. filename="photo.jpg" or filename*=UTF-8''photo.jpg)
30
+ const filenameMatch = contentDisposition.match(/filename\*?=['"]?(?:UTF-\d['"]*)?([^;\r\n"']*)['"]?/i);
31
+ if (filenameMatch?.[1]) {
32
+ // Use path.basename to prevent path traversal attacks
33
+ return path.basename(decodeURIComponent(filenameMatch[1]));
34
+ }
35
+ }
36
+ // Fall back to URL path
37
+ try {
38
+ const urlObj = new URL(url);
39
+ const pathname = urlObj.pathname;
40
+ const filename = pathname.split('/').pop();
41
+ if (filename && filename.length > 0) {
42
+ // Use path.basename to prevent path traversal attacks (e.g., URL-encoded separators)
43
+ return path.basename(decodeURIComponent(filename));
44
+ }
45
+ } catch {
46
+ // Invalid URL, use default
47
+ }
48
+ // Generate a timestamp-based default filename
49
+ const now = new Date();
50
+ const date = now.toISOString().split('T')[0]; // 2024-02-23
51
+ const time = now.toTimeString().split(' ')[0].replace(/:/g, ''); // 143052
52
+ return `untitled_${date}_${time}`;
53
+ };
54
+ /**
55
+ * Fetches a URL and saves it as a temporary file
56
+ * Returns an InputFile-compatible object for use with the upload pipeline
57
+ */ const fetchUrlToInputFile = async (url, tmpWorkingDirectory, sizeLimit)=>{
58
+ // Validate URL protocol
59
+ let parsedUrl;
60
+ try {
61
+ parsedUrl = new URL(url);
62
+ } catch {
63
+ throw new ApplicationError(`Invalid URL: ${url}`);
64
+ }
65
+ if (![
66
+ 'http:',
67
+ 'https:'
68
+ ].includes(parsedUrl.protocol)) {
69
+ throw new ApplicationError(`Invalid URL protocol. Only http and https are allowed: ${url}`);
70
+ }
71
+ // Resolve hostname and block private/internal IP ranges to prevent SSRF
72
+ try {
73
+ const { address, family } = await dns.lookup(parsedUrl.hostname);
74
+ const type = family === 6 ? 'ipv6' : 'ipv4';
75
+ if (SSRF_BLOCK_LIST.check(address, type)) {
76
+ throw new ApplicationError(`URL resolves to a blocked address: ${url}`);
77
+ }
78
+ } catch (error) {
79
+ if (error instanceof ApplicationError) throw error;
80
+ throw new ApplicationError(`Could not resolve hostname: ${parsedUrl.hostname}`);
81
+ }
82
+ // Fetch the URL with timeout
83
+ let response;
84
+ try {
85
+ response = await fetch(url, {
86
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
87
+ });
88
+ } catch (error) {
89
+ if (error instanceof Error && error.name === 'TimeoutError') {
90
+ throw new ApplicationError(`Request timed out while fetching URL: ${url}`);
91
+ }
92
+ throw error;
93
+ }
94
+ if (!response.ok) {
95
+ throw new ApplicationError(`Failed to fetch URL: ${url} (${response.status} ${response.statusText})`);
96
+ }
97
+ // Check Content-Length header for early rejection of large files
98
+ if (sizeLimit) {
99
+ const contentLength = response.headers.get('content-length');
100
+ if (contentLength && parseInt(contentLength, 10) > sizeLimit) {
101
+ throw new ApplicationError(`File too large: maximum allowed size is ${Math.round(sizeLimit / (1024 * 1024))}MB`);
102
+ }
103
+ }
104
+ // Get content type and filename
105
+ const contentType = response.headers.get('content-type')?.split(';')[0] || 'application/octet-stream';
106
+ const contentDisposition = response.headers.get('content-disposition');
107
+ const filename = getFilenameFromUrl(response.url, contentDisposition);
108
+ // Read response body
109
+ const arrayBuffer = await response.arrayBuffer();
110
+ const buffer = Buffer.from(arrayBuffer);
111
+ // Write to temp file
112
+ const tmpFilePath = path.join(tmpWorkingDirectory, filename);
113
+ await fse.writeFile(tmpFilePath, buffer);
114
+ // Create file object compatible with upload pipeline
115
+ const fetchedFile = {
116
+ filepath: tmpFilePath,
117
+ originalFilename: filename,
118
+ mimetype: contentType,
119
+ size: buffer.length,
120
+ tmpWorkingDirectory
121
+ };
122
+ return {
123
+ file: fetchedFile
124
+ };
125
+ };
8
126
  const getFolderPath = async (folderId)=>{
9
127
  if (!folderId) return '/';
10
128
  const parentFolder = await strapi.db.query(constants.FOLDER_MODEL_UID).findOne({
@@ -50,7 +168,8 @@ const signFileUrls = async (file)=>{
50
168
  var file = {
51
169
  getFolderPath,
52
170
  deleteByIds,
53
- signFileUrls
171
+ signFileUrls,
172
+ fetchUrlToInputFile
54
173
  };
55
174
 
56
175
  module.exports = file;
@@ -1 +1 @@
1
- {"version":3,"file":"file.js","sources":["../../../server/src/services/file.ts"],"sourcesContent":["import { cloneDeep } from 'lodash/fp';\nimport { async } from '@strapi/utils';\n\nimport { FOLDER_MODEL_UID, FILE_MODEL_UID } from '../constants';\nimport { getService } from '../utils';\n\nimport { Config, type File } from '../types';\n\nconst getFolderPath = async (folderId?: number | null) => {\n if (!folderId) return '/';\n\n const parentFolder = await strapi.db.query(FOLDER_MODEL_UID).findOne({ where: { id: folderId } });\n\n return parentFolder.path;\n};\n\nconst deleteByIds = async (ids: number[] = []) => {\n const filesToDelete = await strapi.db\n .query(FILE_MODEL_UID)\n .findMany({ where: { id: { $in: ids } } });\n\n await Promise.all(filesToDelete.map((file: File) => getService('upload').remove(file)));\n\n return filesToDelete;\n};\n\nconst signFileUrls = async (file: File) => {\n const { provider } = strapi.plugins.upload;\n const { provider: providerConfig } = strapi.config.get<Config>('plugin::upload');\n const isPrivate = await provider.isPrivate();\n file.isUrlSigned = false;\n\n // Check file provider and if provider is private\n if (file.provider !== providerConfig || !isPrivate) {\n return file;\n }\n\n const signUrl = async (file: File) => {\n const signedUrl = await provider.getSignedUrl(file);\n file.url = signedUrl.url;\n file.isUrlSigned = true;\n };\n\n const signedFile = cloneDeep(file);\n\n // Sign each file format\n await signUrl(signedFile);\n if (file.formats) {\n await async.map(Object.values(signedFile.formats ?? {}), signUrl);\n }\n\n return signedFile;\n};\n\nexport default { getFolderPath, deleteByIds, signFileUrls };\n"],"names":["getFolderPath","folderId","parentFolder","strapi","db","query","FOLDER_MODEL_UID","findOne","where","id","path","deleteByIds","ids","filesToDelete","FILE_MODEL_UID","findMany","$in","Promise","all","map","file","getService","remove","signFileUrls","provider","plugins","upload","providerConfig","config","get","isPrivate","isUrlSigned","signUrl","signedUrl","getSignedUrl","url","signedFile","cloneDeep","formats","async","Object","values"],"mappings":";;;;;;;AAQA,MAAMA,gBAAgB,OAAOC,QAAAA,GAAAA;IAC3B,IAAI,CAACA,UAAU,OAAO,GAAA;IAEtB,MAAMC,YAAAA,GAAe,MAAMC,MAAOC,CAAAA,EAAE,CAACC,KAAK,CAACC,0BAAkBC,CAAAA,CAAAA,OAAO,CAAC;QAAEC,KAAO,EAAA;YAAEC,EAAIR,EAAAA;AAAS;AAAE,KAAA,CAAA;AAE/F,IAAA,OAAOC,aAAaQ,IAAI;AAC1B,CAAA;AAEA,MAAMC,WAAAA,GAAc,OAAOC,GAAAA,GAAgB,EAAE,GAAA;IAC3C,MAAMC,aAAAA,GAAgB,MAAMV,MAAOC,CAAAA,EAAE,CAClCC,KAAK,CAACS,wBACNC,CAAAA,CAAAA,QAAQ,CAAC;QAAEP,KAAO,EAAA;YAAEC,EAAI,EAAA;gBAAEO,GAAKJ,EAAAA;AAAI;AAAE;AAAE,KAAA,CAAA;IAE1C,MAAMK,OAAAA,CAAQC,GAAG,CAACL,aAAcM,CAAAA,GAAG,CAAC,CAACC,IAAeC,GAAAA,gBAAAA,CAAW,QAAUC,CAAAA,CAAAA,MAAM,CAACF,IAAAA,CAAAA,CAAAA,CAAAA;IAEhF,OAAOP,aAAAA;AACT,CAAA;AAEA,MAAMU,eAAe,OAAOH,IAAAA,GAAAA;AAC1B,IAAA,MAAM,EAAEI,QAAQ,EAAE,GAAGrB,MAAOsB,CAAAA,OAAO,CAACC,MAAM;IAC1C,MAAM,EAAEF,UAAUG,cAAc,EAAE,GAAGxB,MAAOyB,CAAAA,MAAM,CAACC,GAAG,CAAS,gBAAA,CAAA;IAC/D,MAAMC,SAAAA,GAAY,MAAMN,QAAAA,CAASM,SAAS,EAAA;AAC1CV,IAAAA,IAAAA,CAAKW,WAAW,GAAG,KAAA;;AAGnB,IAAA,IAAIX,IAAKI,CAAAA,QAAQ,KAAKG,cAAAA,IAAkB,CAACG,SAAW,EAAA;QAClD,OAAOV,IAAAA;AACT;AAEA,IAAA,MAAMY,UAAU,OAAOZ,IAAAA,GAAAA;AACrB,QAAA,MAAMa,SAAY,GAAA,MAAMT,QAASU,CAAAA,YAAY,CAACd,IAAAA,CAAAA;QAC9CA,IAAKe,CAAAA,GAAG,GAAGF,SAAAA,CAAUE,GAAG;AACxBf,QAAAA,IAAAA,CAAKW,WAAW,GAAG,IAAA;AACrB,KAAA;AAEA,IAAA,MAAMK,aAAaC,YAAUjB,CAAAA,IAAAA,CAAAA;;AAG7B,IAAA,MAAMY,OAAQI,CAAAA,UAAAA,CAAAA;IACd,IAAIhB,IAAAA,CAAKkB,OAAO,EAAE;QAChB,MAAMC,WAAAA,CAAMpB,GAAG,CAACqB,MAAOC,CAAAA,MAAM,CAACL,UAAWE,CAAAA,OAAO,IAAI,EAAKN,CAAAA,EAAAA,OAAAA,CAAAA;AAC3D;IAEA,OAAOI,UAAAA;AACT,CAAA;AAEA,WAAe;AAAEpC,IAAAA,aAAAA;AAAeW,IAAAA,WAAAA;AAAaY,IAAAA;AAAa,CAAE;;;;"}
1
+ {"version":3,"file":"file.js","sources":["../../../server/src/services/file.ts"],"sourcesContent":["import path from 'path';\nimport dns from 'dns/promises';\nimport net from 'net';\nimport fse from 'fs-extra';\nimport { cloneDeep } from 'lodash/fp';\nimport { async, errors } from '@strapi/utils';\n\nimport { FOLDER_MODEL_UID, FILE_MODEL_UID } from '../constants';\nimport { getService } from '../utils';\n\nimport { Config, type File } from '../types';\n\nconst { ApplicationError } = errors;\n\nconst FETCH_TIMEOUT_MS = 60_000; // 60 seconds\n\n// Blocks loopback, link-local (cloud metadata), and RFC-1918 private ranges to prevent SSRF\nconst SSRF_BLOCK_LIST = new net.BlockList();\nSSRF_BLOCK_LIST.addSubnet('127.0.0.0', 8); // loopback\nSSRF_BLOCK_LIST.addSubnet('10.0.0.0', 8); // RFC-1918\nSSRF_BLOCK_LIST.addSubnet('172.16.0.0', 12); // RFC-1918\nSSRF_BLOCK_LIST.addSubnet('192.168.0.0', 16); // RFC-1918\nSSRF_BLOCK_LIST.addSubnet('169.254.0.0', 16); // link-local / cloud metadata (AWS, GCP, Azure)\nSSRF_BLOCK_LIST.addSubnet('::1', 128, 'ipv6'); // IPv6 loopback\nSSRF_BLOCK_LIST.addSubnet('fc00::', 7, 'ipv6'); // IPv6 unique local\nSSRF_BLOCK_LIST.addSubnet('fe80::', 10, 'ipv6'); // IPv6 link-local\n\n/**\n * Represents a file fetched from a URL, compatible with the upload pipeline\n */\ninterface UrlFetchedFile {\n filepath: string;\n originalFilename: string;\n mimetype: string;\n size: number;\n tmpWorkingDirectory?: string;\n}\n\ninterface FetchUrlResult {\n file: UrlFetchedFile;\n}\n\n/**\n * Extracts filename from a URL path or Content-Disposition header\n */\nconst getFilenameFromUrl = (url: string, contentDisposition?: string | null): string => {\n // Try Content-Disposition header first\n if (contentDisposition) {\n // Extracts filename from Content-Disposition header (e.g. filename=\"photo.jpg\" or filename*=UTF-8''photo.jpg)\n const filenameMatch = contentDisposition.match(\n /filename\\*?=['\"]?(?:UTF-\\d['\"]*)?([^;\\r\\n\"']*)['\"]?/i\n );\n if (filenameMatch?.[1]) {\n // Use path.basename to prevent path traversal attacks\n return path.basename(decodeURIComponent(filenameMatch[1]));\n }\n }\n\n // Fall back to URL path\n try {\n const urlObj = new URL(url);\n const pathname = urlObj.pathname;\n const filename = pathname.split('/').pop();\n if (filename && filename.length > 0) {\n // Use path.basename to prevent path traversal attacks (e.g., URL-encoded separators)\n return path.basename(decodeURIComponent(filename));\n }\n } catch {\n // Invalid URL, use default\n }\n\n // Generate a timestamp-based default filename\n const now = new Date();\n const date = now.toISOString().split('T')[0]; // 2024-02-23\n const time = now.toTimeString().split(' ')[0].replace(/:/g, ''); // 143052\n return `untitled_${date}_${time}`;\n};\n\n/**\n * Fetches a URL and saves it as a temporary file\n * Returns an InputFile-compatible object for use with the upload pipeline\n */\nconst fetchUrlToInputFile = async (\n url: string,\n tmpWorkingDirectory: string,\n sizeLimit?: number\n): Promise<FetchUrlResult> => {\n // Validate URL protocol\n let parsedUrl: URL;\n try {\n parsedUrl = new URL(url);\n } catch {\n throw new ApplicationError(`Invalid URL: ${url}`);\n }\n\n if (!['http:', 'https:'].includes(parsedUrl.protocol)) {\n throw new ApplicationError(`Invalid URL protocol. Only http and https are allowed: ${url}`);\n }\n\n // Resolve hostname and block private/internal IP ranges to prevent SSRF\n try {\n const { address, family } = await dns.lookup(parsedUrl.hostname);\n const type = family === 6 ? 'ipv6' : 'ipv4';\n if (SSRF_BLOCK_LIST.check(address, type)) {\n throw new ApplicationError(`URL resolves to a blocked address: ${url}`);\n }\n } catch (error) {\n if (error instanceof ApplicationError) throw error;\n throw new ApplicationError(`Could not resolve hostname: ${parsedUrl.hostname}`);\n }\n\n // Fetch the URL with timeout\n let response: Response;\n try {\n response = await fetch(url, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });\n } catch (error) {\n if (error instanceof Error && error.name === 'TimeoutError') {\n throw new ApplicationError(`Request timed out while fetching URL: ${url}`);\n }\n throw error;\n }\n\n if (!response.ok) {\n throw new ApplicationError(\n `Failed to fetch URL: ${url} (${response.status} ${response.statusText})`\n );\n }\n\n // Check Content-Length header for early rejection of large files\n if (sizeLimit) {\n const contentLength = response.headers.get('content-length');\n if (contentLength && parseInt(contentLength, 10) > sizeLimit) {\n throw new ApplicationError(\n `File too large: maximum allowed size is ${Math.round(sizeLimit / (1024 * 1024))}MB`\n );\n }\n }\n\n // Get content type and filename\n const contentType =\n response.headers.get('content-type')?.split(';')[0] || 'application/octet-stream';\n const contentDisposition = response.headers.get('content-disposition');\n const filename = getFilenameFromUrl(response.url, contentDisposition);\n\n // Read response body\n const arrayBuffer = await response.arrayBuffer();\n const buffer = Buffer.from(arrayBuffer);\n\n // Write to temp file\n const tmpFilePath = path.join(tmpWorkingDirectory, filename);\n await fse.writeFile(tmpFilePath, buffer);\n\n // Create file object compatible with upload pipeline\n const fetchedFile: UrlFetchedFile = {\n filepath: tmpFilePath,\n originalFilename: filename,\n mimetype: contentType,\n size: buffer.length,\n tmpWorkingDirectory,\n };\n\n return { file: fetchedFile };\n};\n\nconst getFolderPath = async (folderId?: number | null) => {\n if (!folderId) return '/';\n\n const parentFolder = await strapi.db.query(FOLDER_MODEL_UID).findOne({ where: { id: folderId } });\n\n return parentFolder.path;\n};\n\nconst deleteByIds = async (ids: number[] = []) => {\n const filesToDelete = await strapi.db\n .query(FILE_MODEL_UID)\n .findMany({ where: { id: { $in: ids } } });\n\n await Promise.all(filesToDelete.map((file: File) => getService('upload').remove(file)));\n\n return filesToDelete;\n};\n\nconst signFileUrls = async (file: File) => {\n const { provider } = strapi.plugins.upload;\n const { provider: providerConfig } = strapi.config.get<Config>('plugin::upload');\n const isPrivate = await provider.isPrivate();\n file.isUrlSigned = false;\n\n // Check file provider and if provider is private\n if (file.provider !== providerConfig || !isPrivate) {\n return file;\n }\n\n const signUrl = async (file: File) => {\n const signedUrl = await provider.getSignedUrl(file);\n file.url = signedUrl.url;\n file.isUrlSigned = true;\n };\n\n const signedFile = cloneDeep(file);\n\n // Sign each file format\n await signUrl(signedFile);\n if (file.formats) {\n await async.map(Object.values(signedFile.formats ?? {}), signUrl);\n }\n\n return signedFile;\n};\n\nexport type { UrlFetchedFile, FetchUrlResult };\nexport default { getFolderPath, deleteByIds, signFileUrls, fetchUrlToInputFile };\n"],"names":["ApplicationError","errors","FETCH_TIMEOUT_MS","SSRF_BLOCK_LIST","net","BlockList","addSubnet","getFilenameFromUrl","url","contentDisposition","filenameMatch","match","path","basename","decodeURIComponent","urlObj","URL","pathname","filename","split","pop","length","now","Date","date","toISOString","time","toTimeString","replace","fetchUrlToInputFile","tmpWorkingDirectory","sizeLimit","parsedUrl","includes","protocol","address","family","dns","lookup","hostname","type","check","error","response","fetch","signal","AbortSignal","timeout","Error","name","ok","status","statusText","contentLength","headers","get","parseInt","Math","round","contentType","arrayBuffer","buffer","Buffer","from","tmpFilePath","join","fse","writeFile","fetchedFile","filepath","originalFilename","mimetype","size","file","getFolderPath","folderId","parentFolder","strapi","db","query","FOLDER_MODEL_UID","findOne","where","id","deleteByIds","ids","filesToDelete","FILE_MODEL_UID","findMany","$in","Promise","all","map","getService","remove","signFileUrls","provider","plugins","upload","providerConfig","config","isPrivate","isUrlSigned","signUrl","signedUrl","getSignedUrl","signedFile","cloneDeep","formats","async","Object","values"],"mappings":";;;;;;;;;;;AAYA,MAAM,EAAEA,gBAAgB,EAAE,GAAGC,YAAAA;AAE7B,MAAMC,gBAAAA,GAAmB;AAEzB;AACA,MAAMC,eAAAA,GAAkB,IAAIC,GAAAA,CAAIC,SAAS,EAAA;AACzCF,eAAAA,CAAgBG,SAAS,CAAC,WAAA,EAAa,CAAA,CAAA,CAAA;AACvCH,eAAAA,CAAgBG,SAAS,CAAC,UAAA,EAAY,CAAA,CAAA,CAAA;AACtCH,eAAAA,CAAgBG,SAAS,CAAC,YAAA,EAAc,EAAA,CAAA,CAAA;AACxCH,eAAAA,CAAgBG,SAAS,CAAC,aAAA,EAAe,EAAA,CAAA,CAAA;AACzCH,eAAAA,CAAgBG,SAAS,CAAC,aAAA,EAAe,EAAA,CAAA,CAAA;AACzCH,eAAAA,CAAgBG,SAAS,CAAC,KAAA,EAAO,GAAA,EAAK;AACtCH,eAAAA,CAAgBG,SAAS,CAAC,QAAA,EAAU,CAAA,EAAG;AACvCH,eAAAA,CAAgBG,SAAS,CAAC,QAAA,EAAU,EAAA,EAAI;AAiBxC;;IAGA,MAAMC,kBAAAA,GAAqB,CAACC,GAAAA,EAAaC,kBAAAA,GAAAA;;AAEvC,IAAA,IAAIA,kBAAAA,EAAoB;;QAEtB,MAAMC,aAAAA,GAAgBD,kBAAAA,CAAmBE,KAAK,CAC5C,sDAAA,CAAA;QAEF,IAAID,aAAAA,GAAgB,CAAA,CAAE,EAAE;;AAEtB,YAAA,OAAOE,KAAKC,QAAQ,CAACC,kBAAAA,CAAmBJ,aAAa,CAAC,CAAA,CAAE,CAAA,CAAA;AAC1D,QAAA;AACF,IAAA;;IAGA,IAAI;QACF,MAAMK,MAAAA,GAAS,IAAIC,GAAAA,CAAIR,GAAAA,CAAAA;QACvB,MAAMS,QAAAA,GAAWF,OAAOE,QAAQ;AAChC,QAAA,MAAMC,QAAAA,GAAWD,QAAAA,CAASE,KAAK,CAAC,KAAKC,GAAG,EAAA;AACxC,QAAA,IAAIF,QAAAA,IAAYA,QAAAA,CAASG,MAAM,GAAG,CAAA,EAAG;;YAEnC,OAAOT,IAAAA,CAAKC,QAAQ,CAACC,kBAAAA,CAAmBI,QAAAA,CAAAA,CAAAA;AAC1C,QAAA;AACF,IAAA,CAAA,CAAE,OAAM;;AAER,IAAA;;AAGA,IAAA,MAAMI,MAAM,IAAIC,IAAAA,EAAAA;IAChB,MAAMC,IAAAA,GAAOF,GAAAA,CAAIG,WAAW,EAAA,CAAGN,KAAK,CAAC,GAAA,CAAI,CAAC,CAAA,CAAE,CAAA;AAC5C,IAAA,MAAMO,IAAAA,GAAOJ,GAAAA,CAAIK,YAAY,EAAA,CAAGR,KAAK,CAAC,GAAA,CAAI,CAAC,CAAA,CAAE,CAACS,OAAO,CAAC,IAAA,EAAM;AAC5D,IAAA,OAAO,CAAC,SAAS,EAAEJ,IAAAA,CAAK,CAAC,EAAEE,IAAAA,CAAAA,CAAM;AACnC,CAAA;AAEA;;;AAGC,IACD,MAAMG,mBAAAA,GAAsB,OAC1BrB,GAAAA,EACAsB,mBAAAA,EACAC,SAAAA,GAAAA;;IAGA,IAAIC,SAAAA;IACJ,IAAI;AACFA,QAAAA,SAAAA,GAAY,IAAIhB,GAAAA,CAAIR,GAAAA,CAAAA;AACtB,IAAA,CAAA,CAAE,OAAM;AACN,QAAA,MAAM,IAAIR,gBAAAA,CAAiB,CAAC,aAAa,EAAEQ,GAAAA,CAAAA,CAAK,CAAA;AAClD,IAAA;AAEA,IAAA,IAAI,CAAC;AAAC,QAAA,OAAA;AAAS,QAAA;AAAS,KAAA,CAACyB,QAAQ,CAACD,SAAAA,CAAUE,QAAQ,CAAA,EAAG;AACrD,QAAA,MAAM,IAAIlC,gBAAAA,CAAiB,CAAC,uDAAuD,EAAEQ,GAAAA,CAAAA,CAAK,CAAA;AAC5F,IAAA;;IAGA,IAAI;QACF,MAAM,EAAE2B,OAAO,EAAEC,MAAM,EAAE,GAAG,MAAMC,GAAAA,CAAIC,MAAM,CAACN,SAAAA,CAAUO,QAAQ,CAAA;QAC/D,MAAMC,IAAAA,GAAOJ,MAAAA,KAAW,CAAA,GAAI,MAAA,GAAS,MAAA;AACrC,QAAA,IAAIjC,eAAAA,CAAgBsC,KAAK,CAACN,OAAAA,EAASK,IAAAA,CAAAA,EAAO;AACxC,YAAA,MAAM,IAAIxC,gBAAAA,CAAiB,CAAC,mCAAmC,EAAEQ,GAAAA,CAAAA,CAAK,CAAA;AACxE,QAAA;AACF,IAAA,CAAA,CAAE,OAAOkC,KAAAA,EAAO;QACd,IAAIA,KAAAA,YAAiB1C,kBAAkB,MAAM0C,KAAAA;AAC7C,QAAA,MAAM,IAAI1C,gBAAAA,CAAiB,CAAC,4BAA4B,EAAEgC,SAAAA,CAAUO,QAAQ,CAAA,CAAE,CAAA;AAChF,IAAA;;IAGA,IAAII,QAAAA;IACJ,IAAI;QACFA,QAAAA,GAAW,MAAMC,MAAMpC,GAAAA,EAAK;YAAEqC,MAAAA,EAAQC,WAAAA,CAAYC,OAAO,CAAC7C,gBAAAA;AAAkB,SAAA,CAAA;AAC9E,IAAA,CAAA,CAAE,OAAOwC,KAAAA,EAAO;AACd,QAAA,IAAIA,KAAAA,YAAiBM,KAAAA,IAASN,KAAAA,CAAMO,IAAI,KAAK,cAAA,EAAgB;AAC3D,YAAA,MAAM,IAAIjD,gBAAAA,CAAiB,CAAC,sCAAsC,EAAEQ,GAAAA,CAAAA,CAAK,CAAA;AAC3E,QAAA;QACA,MAAMkC,KAAAA;AACR,IAAA;IAEA,IAAI,CAACC,QAAAA,CAASO,EAAE,EAAE;AAChB,QAAA,MAAM,IAAIlD,gBAAAA,CACR,CAAC,qBAAqB,EAAEQ,IAAI,EAAE,EAAEmC,QAAAA,CAASQ,MAAM,CAAC,CAAC,EAAER,SAASS,UAAU,CAAC,CAAC,CAAC,CAAA;AAE7E,IAAA;;AAGA,IAAA,IAAIrB,SAAAA,EAAW;AACb,QAAA,MAAMsB,aAAAA,GAAgBV,QAAAA,CAASW,OAAO,CAACC,GAAG,CAAC,gBAAA,CAAA;AAC3C,QAAA,IAAIF,aAAAA,IAAiBG,QAAAA,CAASH,aAAAA,EAAe,EAAA,CAAA,GAAMtB,SAAAA,EAAW;AAC5D,YAAA,MAAM,IAAI/B,gBAAAA,CACR,CAAC,wCAAwC,EAAEyD,IAAAA,CAAKC,KAAK,CAAC3B,SAAAA,IAAa,IAAA,GAAO,IAAG,CAAA,CAAA,CAAI,EAAE,CAAC,CAAA;AAExF,QAAA;AACF,IAAA;;IAGA,MAAM4B,WAAAA,GACJhB,QAAAA,CAASW,OAAO,CAACC,GAAG,CAAC,cAAA,CAAA,EAAiBpC,KAAAA,CAAM,GAAA,CAAI,CAAC,CAAA,CAAE,IAAI,0BAAA;AACzD,IAAA,MAAMV,kBAAAA,GAAqBkC,QAAAA,CAASW,OAAO,CAACC,GAAG,CAAC,qBAAA,CAAA;AAChD,IAAA,MAAMrC,QAAAA,GAAWX,kBAAAA,CAAmBoC,QAAAA,CAASnC,GAAG,EAAEC,kBAAAA,CAAAA;;IAGlD,MAAMmD,WAAAA,GAAc,MAAMjB,QAAAA,CAASiB,WAAW,EAAA;IAC9C,MAAMC,MAAAA,GAASC,MAAAA,CAAOC,IAAI,CAACH,WAAAA,CAAAA;;AAG3B,IAAA,MAAMI,WAAAA,GAAcpD,IAAAA,CAAKqD,IAAI,CAACnC,mBAAAA,EAAqBZ,QAAAA,CAAAA;IACnD,MAAMgD,GAAAA,CAAIC,SAAS,CAACH,WAAAA,EAAaH,MAAAA,CAAAA;;AAGjC,IAAA,MAAMO,WAAAA,GAA8B;QAClCC,QAAAA,EAAUL,WAAAA;QACVM,gBAAAA,EAAkBpD,QAAAA;QAClBqD,QAAAA,EAAUZ,WAAAA;AACVa,QAAAA,IAAAA,EAAMX,OAAOxC,MAAM;AACnBS,QAAAA;AACF,KAAA;IAEA,OAAO;QAAE2C,IAAAA,EAAML;AAAY,KAAA;AAC7B,CAAA;AAEA,MAAMM,gBAAgB,OAAOC,QAAAA,GAAAA;IAC3B,IAAI,CAACA,UAAU,OAAO,GAAA;IAEtB,MAAMC,YAAAA,GAAe,MAAMC,MAAAA,CAAOC,EAAE,CAACC,KAAK,CAACC,0BAAAA,CAAAA,CAAkBC,OAAO,CAAC;QAAEC,KAAAA,EAAO;YAAEC,EAAAA,EAAIR;AAAS;AAAE,KAAA,CAAA;AAE/F,IAAA,OAAOC,aAAahE,IAAI;AAC1B,CAAA;AAEA,MAAMwE,WAAAA,GAAc,OAAOC,GAAAA,GAAgB,EAAE,GAAA;IAC3C,MAAMC,aAAAA,GAAgB,MAAMT,MAAAA,CAAOC,EAAE,CAClCC,KAAK,CAACQ,wBAAAA,CAAAA,CACNC,QAAQ,CAAC;QAAEN,KAAAA,EAAO;YAAEC,EAAAA,EAAI;gBAAEM,GAAAA,EAAKJ;AAAI;AAAE;AAAE,KAAA,CAAA;IAE1C,MAAMK,OAAAA,CAAQC,GAAG,CAACL,aAAAA,CAAcM,GAAG,CAAC,CAACnB,IAAAA,GAAeoB,gBAAAA,CAAW,QAAA,CAAA,CAAUC,MAAM,CAACrB,IAAAA,CAAAA,CAAAA,CAAAA;IAEhF,OAAOa,aAAAA;AACT,CAAA;AAEA,MAAMS,eAAe,OAAOtB,IAAAA,GAAAA;AAC1B,IAAA,MAAM,EAAEuB,QAAQ,EAAE,GAAGnB,MAAAA,CAAOoB,OAAO,CAACC,MAAM;IAC1C,MAAM,EAAEF,UAAUG,cAAc,EAAE,GAAGtB,MAAAA,CAAOuB,MAAM,CAAC7C,GAAG,CAAS,gBAAA,CAAA;IAC/D,MAAM8C,SAAAA,GAAY,MAAML,QAAAA,CAASK,SAAS,EAAA;AAC1C5B,IAAAA,IAAAA,CAAK6B,WAAW,GAAG,KAAA;;AAGnB,IAAA,IAAI7B,IAAAA,CAAKuB,QAAQ,KAAKG,cAAAA,IAAkB,CAACE,SAAAA,EAAW;QAClD,OAAO5B,IAAAA;AACT,IAAA;AAEA,IAAA,MAAM8B,UAAU,OAAO9B,IAAAA,GAAAA;AACrB,QAAA,MAAM+B,SAAAA,GAAY,MAAMR,QAAAA,CAASS,YAAY,CAAChC,IAAAA,CAAAA;QAC9CA,IAAAA,CAAKjE,GAAG,GAAGgG,SAAAA,CAAUhG,GAAG;AACxBiE,QAAAA,IAAAA,CAAK6B,WAAW,GAAG,IAAA;AACrB,IAAA,CAAA;AAEA,IAAA,MAAMI,aAAaC,YAAAA,CAAUlC,IAAAA,CAAAA;;AAG7B,IAAA,MAAM8B,OAAAA,CAAQG,UAAAA,CAAAA;IACd,IAAIjC,IAAAA,CAAKmC,OAAO,EAAE;QAChB,MAAMC,WAAAA,CAAMjB,GAAG,CAACkB,MAAAA,CAAOC,MAAM,CAACL,UAAAA,CAAWE,OAAO,IAAI,EAAC,CAAA,EAAIL,OAAAA,CAAAA;AAC3D,IAAA;IAEA,OAAOG,UAAAA;AACT,CAAA;AAGA,WAAe;AAAEhC,IAAAA,aAAAA;AAAeU,IAAAA,WAAAA;AAAaW,IAAAA,YAAAA;AAAclE,IAAAA;AAAoB,CAAA;;;;"}
@@ -1,8 +1,126 @@
1
+ import path from 'path';
2
+ import dns from 'dns/promises';
3
+ import net from 'net';
4
+ import fse from 'fs-extra';
1
5
  import { cloneDeep } from 'lodash/fp';
2
- import { async } from '@strapi/utils';
3
- import { FOLDER_MODEL_UID, FILE_MODEL_UID } from '../constants.mjs';
6
+ import { errors, async } from '@strapi/utils';
7
+ import { FILE_MODEL_UID, FOLDER_MODEL_UID } from '../constants.mjs';
4
8
  import { getService } from '../utils/index.mjs';
5
9
 
10
+ const { ApplicationError } = errors;
11
+ const FETCH_TIMEOUT_MS = 60000; // 60 seconds
12
+ // Blocks loopback, link-local (cloud metadata), and RFC-1918 private ranges to prevent SSRF
13
+ const SSRF_BLOCK_LIST = new net.BlockList();
14
+ SSRF_BLOCK_LIST.addSubnet('127.0.0.0', 8); // loopback
15
+ SSRF_BLOCK_LIST.addSubnet('10.0.0.0', 8); // RFC-1918
16
+ SSRF_BLOCK_LIST.addSubnet('172.16.0.0', 12); // RFC-1918
17
+ SSRF_BLOCK_LIST.addSubnet('192.168.0.0', 16); // RFC-1918
18
+ SSRF_BLOCK_LIST.addSubnet('169.254.0.0', 16); // link-local / cloud metadata (AWS, GCP, Azure)
19
+ SSRF_BLOCK_LIST.addSubnet('::1', 128, 'ipv6'); // IPv6 loopback
20
+ SSRF_BLOCK_LIST.addSubnet('fc00::', 7, 'ipv6'); // IPv6 unique local
21
+ SSRF_BLOCK_LIST.addSubnet('fe80::', 10, 'ipv6'); // IPv6 link-local
22
+ /**
23
+ * Extracts filename from a URL path or Content-Disposition header
24
+ */ const getFilenameFromUrl = (url, contentDisposition)=>{
25
+ // Try Content-Disposition header first
26
+ if (contentDisposition) {
27
+ // Extracts filename from Content-Disposition header (e.g. filename="photo.jpg" or filename*=UTF-8''photo.jpg)
28
+ const filenameMatch = contentDisposition.match(/filename\*?=['"]?(?:UTF-\d['"]*)?([^;\r\n"']*)['"]?/i);
29
+ if (filenameMatch?.[1]) {
30
+ // Use path.basename to prevent path traversal attacks
31
+ return path.basename(decodeURIComponent(filenameMatch[1]));
32
+ }
33
+ }
34
+ // Fall back to URL path
35
+ try {
36
+ const urlObj = new URL(url);
37
+ const pathname = urlObj.pathname;
38
+ const filename = pathname.split('/').pop();
39
+ if (filename && filename.length > 0) {
40
+ // Use path.basename to prevent path traversal attacks (e.g., URL-encoded separators)
41
+ return path.basename(decodeURIComponent(filename));
42
+ }
43
+ } catch {
44
+ // Invalid URL, use default
45
+ }
46
+ // Generate a timestamp-based default filename
47
+ const now = new Date();
48
+ const date = now.toISOString().split('T')[0]; // 2024-02-23
49
+ const time = now.toTimeString().split(' ')[0].replace(/:/g, ''); // 143052
50
+ return `untitled_${date}_${time}`;
51
+ };
52
+ /**
53
+ * Fetches a URL and saves it as a temporary file
54
+ * Returns an InputFile-compatible object for use with the upload pipeline
55
+ */ const fetchUrlToInputFile = async (url, tmpWorkingDirectory, sizeLimit)=>{
56
+ // Validate URL protocol
57
+ let parsedUrl;
58
+ try {
59
+ parsedUrl = new URL(url);
60
+ } catch {
61
+ throw new ApplicationError(`Invalid URL: ${url}`);
62
+ }
63
+ if (![
64
+ 'http:',
65
+ 'https:'
66
+ ].includes(parsedUrl.protocol)) {
67
+ throw new ApplicationError(`Invalid URL protocol. Only http and https are allowed: ${url}`);
68
+ }
69
+ // Resolve hostname and block private/internal IP ranges to prevent SSRF
70
+ try {
71
+ const { address, family } = await dns.lookup(parsedUrl.hostname);
72
+ const type = family === 6 ? 'ipv6' : 'ipv4';
73
+ if (SSRF_BLOCK_LIST.check(address, type)) {
74
+ throw new ApplicationError(`URL resolves to a blocked address: ${url}`);
75
+ }
76
+ } catch (error) {
77
+ if (error instanceof ApplicationError) throw error;
78
+ throw new ApplicationError(`Could not resolve hostname: ${parsedUrl.hostname}`);
79
+ }
80
+ // Fetch the URL with timeout
81
+ let response;
82
+ try {
83
+ response = await fetch(url, {
84
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
85
+ });
86
+ } catch (error) {
87
+ if (error instanceof Error && error.name === 'TimeoutError') {
88
+ throw new ApplicationError(`Request timed out while fetching URL: ${url}`);
89
+ }
90
+ throw error;
91
+ }
92
+ if (!response.ok) {
93
+ throw new ApplicationError(`Failed to fetch URL: ${url} (${response.status} ${response.statusText})`);
94
+ }
95
+ // Check Content-Length header for early rejection of large files
96
+ if (sizeLimit) {
97
+ const contentLength = response.headers.get('content-length');
98
+ if (contentLength && parseInt(contentLength, 10) > sizeLimit) {
99
+ throw new ApplicationError(`File too large: maximum allowed size is ${Math.round(sizeLimit / (1024 * 1024))}MB`);
100
+ }
101
+ }
102
+ // Get content type and filename
103
+ const contentType = response.headers.get('content-type')?.split(';')[0] || 'application/octet-stream';
104
+ const contentDisposition = response.headers.get('content-disposition');
105
+ const filename = getFilenameFromUrl(response.url, contentDisposition);
106
+ // Read response body
107
+ const arrayBuffer = await response.arrayBuffer();
108
+ const buffer = Buffer.from(arrayBuffer);
109
+ // Write to temp file
110
+ const tmpFilePath = path.join(tmpWorkingDirectory, filename);
111
+ await fse.writeFile(tmpFilePath, buffer);
112
+ // Create file object compatible with upload pipeline
113
+ const fetchedFile = {
114
+ filepath: tmpFilePath,
115
+ originalFilename: filename,
116
+ mimetype: contentType,
117
+ size: buffer.length,
118
+ tmpWorkingDirectory
119
+ };
120
+ return {
121
+ file: fetchedFile
122
+ };
123
+ };
6
124
  const getFolderPath = async (folderId)=>{
7
125
  if (!folderId) return '/';
8
126
  const parentFolder = await strapi.db.query(FOLDER_MODEL_UID).findOne({
@@ -48,7 +166,8 @@ const signFileUrls = async (file)=>{
48
166
  var file = {
49
167
  getFolderPath,
50
168
  deleteByIds,
51
- signFileUrls
169
+ signFileUrls,
170
+ fetchUrlToInputFile
52
171
  };
53
172
 
54
173
  export { file as default };
@@ -1 +1 @@
1
- {"version":3,"file":"file.mjs","sources":["../../../server/src/services/file.ts"],"sourcesContent":["import { cloneDeep } from 'lodash/fp';\nimport { async } from '@strapi/utils';\n\nimport { FOLDER_MODEL_UID, FILE_MODEL_UID } from '../constants';\nimport { getService } from '../utils';\n\nimport { Config, type File } from '../types';\n\nconst getFolderPath = async (folderId?: number | null) => {\n if (!folderId) return '/';\n\n const parentFolder = await strapi.db.query(FOLDER_MODEL_UID).findOne({ where: { id: folderId } });\n\n return parentFolder.path;\n};\n\nconst deleteByIds = async (ids: number[] = []) => {\n const filesToDelete = await strapi.db\n .query(FILE_MODEL_UID)\n .findMany({ where: { id: { $in: ids } } });\n\n await Promise.all(filesToDelete.map((file: File) => getService('upload').remove(file)));\n\n return filesToDelete;\n};\n\nconst signFileUrls = async (file: File) => {\n const { provider } = strapi.plugins.upload;\n const { provider: providerConfig } = strapi.config.get<Config>('plugin::upload');\n const isPrivate = await provider.isPrivate();\n file.isUrlSigned = false;\n\n // Check file provider and if provider is private\n if (file.provider !== providerConfig || !isPrivate) {\n return file;\n }\n\n const signUrl = async (file: File) => {\n const signedUrl = await provider.getSignedUrl(file);\n file.url = signedUrl.url;\n file.isUrlSigned = true;\n };\n\n const signedFile = cloneDeep(file);\n\n // Sign each file format\n await signUrl(signedFile);\n if (file.formats) {\n await async.map(Object.values(signedFile.formats ?? {}), signUrl);\n }\n\n return signedFile;\n};\n\nexport default { getFolderPath, deleteByIds, signFileUrls };\n"],"names":["getFolderPath","folderId","parentFolder","strapi","db","query","FOLDER_MODEL_UID","findOne","where","id","path","deleteByIds","ids","filesToDelete","FILE_MODEL_UID","findMany","$in","Promise","all","map","file","getService","remove","signFileUrls","provider","plugins","upload","providerConfig","config","get","isPrivate","isUrlSigned","signUrl","signedUrl","getSignedUrl","url","signedFile","cloneDeep","formats","async","Object","values"],"mappings":";;;;;AAQA,MAAMA,gBAAgB,OAAOC,QAAAA,GAAAA;IAC3B,IAAI,CAACA,UAAU,OAAO,GAAA;IAEtB,MAAMC,YAAAA,GAAe,MAAMC,MAAOC,CAAAA,EAAE,CAACC,KAAK,CAACC,gBAAkBC,CAAAA,CAAAA,OAAO,CAAC;QAAEC,KAAO,EAAA;YAAEC,EAAIR,EAAAA;AAAS;AAAE,KAAA,CAAA;AAE/F,IAAA,OAAOC,aAAaQ,IAAI;AAC1B,CAAA;AAEA,MAAMC,WAAAA,GAAc,OAAOC,GAAAA,GAAgB,EAAE,GAAA;IAC3C,MAAMC,aAAAA,GAAgB,MAAMV,MAAOC,CAAAA,EAAE,CAClCC,KAAK,CAACS,cACNC,CAAAA,CAAAA,QAAQ,CAAC;QAAEP,KAAO,EAAA;YAAEC,EAAI,EAAA;gBAAEO,GAAKJ,EAAAA;AAAI;AAAE;AAAE,KAAA,CAAA;IAE1C,MAAMK,OAAAA,CAAQC,GAAG,CAACL,aAAcM,CAAAA,GAAG,CAAC,CAACC,IAAeC,GAAAA,UAAAA,CAAW,QAAUC,CAAAA,CAAAA,MAAM,CAACF,IAAAA,CAAAA,CAAAA,CAAAA;IAEhF,OAAOP,aAAAA;AACT,CAAA;AAEA,MAAMU,eAAe,OAAOH,IAAAA,GAAAA;AAC1B,IAAA,MAAM,EAAEI,QAAQ,EAAE,GAAGrB,MAAOsB,CAAAA,OAAO,CAACC,MAAM;IAC1C,MAAM,EAAEF,UAAUG,cAAc,EAAE,GAAGxB,MAAOyB,CAAAA,MAAM,CAACC,GAAG,CAAS,gBAAA,CAAA;IAC/D,MAAMC,SAAAA,GAAY,MAAMN,QAAAA,CAASM,SAAS,EAAA;AAC1CV,IAAAA,IAAAA,CAAKW,WAAW,GAAG,KAAA;;AAGnB,IAAA,IAAIX,IAAKI,CAAAA,QAAQ,KAAKG,cAAAA,IAAkB,CAACG,SAAW,EAAA;QAClD,OAAOV,IAAAA;AACT;AAEA,IAAA,MAAMY,UAAU,OAAOZ,IAAAA,GAAAA;AACrB,QAAA,MAAMa,SAAY,GAAA,MAAMT,QAASU,CAAAA,YAAY,CAACd,IAAAA,CAAAA;QAC9CA,IAAKe,CAAAA,GAAG,GAAGF,SAAAA,CAAUE,GAAG;AACxBf,QAAAA,IAAAA,CAAKW,WAAW,GAAG,IAAA;AACrB,KAAA;AAEA,IAAA,MAAMK,aAAaC,SAAUjB,CAAAA,IAAAA,CAAAA;;AAG7B,IAAA,MAAMY,OAAQI,CAAAA,UAAAA,CAAAA;IACd,IAAIhB,IAAAA,CAAKkB,OAAO,EAAE;QAChB,MAAMC,KAAAA,CAAMpB,GAAG,CAACqB,MAAOC,CAAAA,MAAM,CAACL,UAAWE,CAAAA,OAAO,IAAI,EAAKN,CAAAA,EAAAA,OAAAA,CAAAA;AAC3D;IAEA,OAAOI,UAAAA;AACT,CAAA;AAEA,WAAe;AAAEpC,IAAAA,aAAAA;AAAeW,IAAAA,WAAAA;AAAaY,IAAAA;AAAa,CAAE;;;;"}
1
+ {"version":3,"file":"file.mjs","sources":["../../../server/src/services/file.ts"],"sourcesContent":["import path from 'path';\nimport dns from 'dns/promises';\nimport net from 'net';\nimport fse from 'fs-extra';\nimport { cloneDeep } from 'lodash/fp';\nimport { async, errors } from '@strapi/utils';\n\nimport { FOLDER_MODEL_UID, FILE_MODEL_UID } from '../constants';\nimport { getService } from '../utils';\n\nimport { Config, type File } from '../types';\n\nconst { ApplicationError } = errors;\n\nconst FETCH_TIMEOUT_MS = 60_000; // 60 seconds\n\n// Blocks loopback, link-local (cloud metadata), and RFC-1918 private ranges to prevent SSRF\nconst SSRF_BLOCK_LIST = new net.BlockList();\nSSRF_BLOCK_LIST.addSubnet('127.0.0.0', 8); // loopback\nSSRF_BLOCK_LIST.addSubnet('10.0.0.0', 8); // RFC-1918\nSSRF_BLOCK_LIST.addSubnet('172.16.0.0', 12); // RFC-1918\nSSRF_BLOCK_LIST.addSubnet('192.168.0.0', 16); // RFC-1918\nSSRF_BLOCK_LIST.addSubnet('169.254.0.0', 16); // link-local / cloud metadata (AWS, GCP, Azure)\nSSRF_BLOCK_LIST.addSubnet('::1', 128, 'ipv6'); // IPv6 loopback\nSSRF_BLOCK_LIST.addSubnet('fc00::', 7, 'ipv6'); // IPv6 unique local\nSSRF_BLOCK_LIST.addSubnet('fe80::', 10, 'ipv6'); // IPv6 link-local\n\n/**\n * Represents a file fetched from a URL, compatible with the upload pipeline\n */\ninterface UrlFetchedFile {\n filepath: string;\n originalFilename: string;\n mimetype: string;\n size: number;\n tmpWorkingDirectory?: string;\n}\n\ninterface FetchUrlResult {\n file: UrlFetchedFile;\n}\n\n/**\n * Extracts filename from a URL path or Content-Disposition header\n */\nconst getFilenameFromUrl = (url: string, contentDisposition?: string | null): string => {\n // Try Content-Disposition header first\n if (contentDisposition) {\n // Extracts filename from Content-Disposition header (e.g. filename=\"photo.jpg\" or filename*=UTF-8''photo.jpg)\n const filenameMatch = contentDisposition.match(\n /filename\\*?=['\"]?(?:UTF-\\d['\"]*)?([^;\\r\\n\"']*)['\"]?/i\n );\n if (filenameMatch?.[1]) {\n // Use path.basename to prevent path traversal attacks\n return path.basename(decodeURIComponent(filenameMatch[1]));\n }\n }\n\n // Fall back to URL path\n try {\n const urlObj = new URL(url);\n const pathname = urlObj.pathname;\n const filename = pathname.split('/').pop();\n if (filename && filename.length > 0) {\n // Use path.basename to prevent path traversal attacks (e.g., URL-encoded separators)\n return path.basename(decodeURIComponent(filename));\n }\n } catch {\n // Invalid URL, use default\n }\n\n // Generate a timestamp-based default filename\n const now = new Date();\n const date = now.toISOString().split('T')[0]; // 2024-02-23\n const time = now.toTimeString().split(' ')[0].replace(/:/g, ''); // 143052\n return `untitled_${date}_${time}`;\n};\n\n/**\n * Fetches a URL and saves it as a temporary file\n * Returns an InputFile-compatible object for use with the upload pipeline\n */\nconst fetchUrlToInputFile = async (\n url: string,\n tmpWorkingDirectory: string,\n sizeLimit?: number\n): Promise<FetchUrlResult> => {\n // Validate URL protocol\n let parsedUrl: URL;\n try {\n parsedUrl = new URL(url);\n } catch {\n throw new ApplicationError(`Invalid URL: ${url}`);\n }\n\n if (!['http:', 'https:'].includes(parsedUrl.protocol)) {\n throw new ApplicationError(`Invalid URL protocol. Only http and https are allowed: ${url}`);\n }\n\n // Resolve hostname and block private/internal IP ranges to prevent SSRF\n try {\n const { address, family } = await dns.lookup(parsedUrl.hostname);\n const type = family === 6 ? 'ipv6' : 'ipv4';\n if (SSRF_BLOCK_LIST.check(address, type)) {\n throw new ApplicationError(`URL resolves to a blocked address: ${url}`);\n }\n } catch (error) {\n if (error instanceof ApplicationError) throw error;\n throw new ApplicationError(`Could not resolve hostname: ${parsedUrl.hostname}`);\n }\n\n // Fetch the URL with timeout\n let response: Response;\n try {\n response = await fetch(url, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });\n } catch (error) {\n if (error instanceof Error && error.name === 'TimeoutError') {\n throw new ApplicationError(`Request timed out while fetching URL: ${url}`);\n }\n throw error;\n }\n\n if (!response.ok) {\n throw new ApplicationError(\n `Failed to fetch URL: ${url} (${response.status} ${response.statusText})`\n );\n }\n\n // Check Content-Length header for early rejection of large files\n if (sizeLimit) {\n const contentLength = response.headers.get('content-length');\n if (contentLength && parseInt(contentLength, 10) > sizeLimit) {\n throw new ApplicationError(\n `File too large: maximum allowed size is ${Math.round(sizeLimit / (1024 * 1024))}MB`\n );\n }\n }\n\n // Get content type and filename\n const contentType =\n response.headers.get('content-type')?.split(';')[0] || 'application/octet-stream';\n const contentDisposition = response.headers.get('content-disposition');\n const filename = getFilenameFromUrl(response.url, contentDisposition);\n\n // Read response body\n const arrayBuffer = await response.arrayBuffer();\n const buffer = Buffer.from(arrayBuffer);\n\n // Write to temp file\n const tmpFilePath = path.join(tmpWorkingDirectory, filename);\n await fse.writeFile(tmpFilePath, buffer);\n\n // Create file object compatible with upload pipeline\n const fetchedFile: UrlFetchedFile = {\n filepath: tmpFilePath,\n originalFilename: filename,\n mimetype: contentType,\n size: buffer.length,\n tmpWorkingDirectory,\n };\n\n return { file: fetchedFile };\n};\n\nconst getFolderPath = async (folderId?: number | null) => {\n if (!folderId) return '/';\n\n const parentFolder = await strapi.db.query(FOLDER_MODEL_UID).findOne({ where: { id: folderId } });\n\n return parentFolder.path;\n};\n\nconst deleteByIds = async (ids: number[] = []) => {\n const filesToDelete = await strapi.db\n .query(FILE_MODEL_UID)\n .findMany({ where: { id: { $in: ids } } });\n\n await Promise.all(filesToDelete.map((file: File) => getService('upload').remove(file)));\n\n return filesToDelete;\n};\n\nconst signFileUrls = async (file: File) => {\n const { provider } = strapi.plugins.upload;\n const { provider: providerConfig } = strapi.config.get<Config>('plugin::upload');\n const isPrivate = await provider.isPrivate();\n file.isUrlSigned = false;\n\n // Check file provider and if provider is private\n if (file.provider !== providerConfig || !isPrivate) {\n return file;\n }\n\n const signUrl = async (file: File) => {\n const signedUrl = await provider.getSignedUrl(file);\n file.url = signedUrl.url;\n file.isUrlSigned = true;\n };\n\n const signedFile = cloneDeep(file);\n\n // Sign each file format\n await signUrl(signedFile);\n if (file.formats) {\n await async.map(Object.values(signedFile.formats ?? {}), signUrl);\n }\n\n return signedFile;\n};\n\nexport type { UrlFetchedFile, FetchUrlResult };\nexport default { getFolderPath, deleteByIds, signFileUrls, fetchUrlToInputFile };\n"],"names":["ApplicationError","errors","FETCH_TIMEOUT_MS","SSRF_BLOCK_LIST","net","BlockList","addSubnet","getFilenameFromUrl","url","contentDisposition","filenameMatch","match","path","basename","decodeURIComponent","urlObj","URL","pathname","filename","split","pop","length","now","Date","date","toISOString","time","toTimeString","replace","fetchUrlToInputFile","tmpWorkingDirectory","sizeLimit","parsedUrl","includes","protocol","address","family","dns","lookup","hostname","type","check","error","response","fetch","signal","AbortSignal","timeout","Error","name","ok","status","statusText","contentLength","headers","get","parseInt","Math","round","contentType","arrayBuffer","buffer","Buffer","from","tmpFilePath","join","fse","writeFile","fetchedFile","filepath","originalFilename","mimetype","size","file","getFolderPath","folderId","parentFolder","strapi","db","query","FOLDER_MODEL_UID","findOne","where","id","deleteByIds","ids","filesToDelete","FILE_MODEL_UID","findMany","$in","Promise","all","map","getService","remove","signFileUrls","provider","plugins","upload","providerConfig","config","isPrivate","isUrlSigned","signUrl","signedUrl","getSignedUrl","signedFile","cloneDeep","formats","async","Object","values"],"mappings":"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"}