@strapi/upload 5.55.1 → 5.57.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin/hooks/useAIMetadataAvailability.js +14 -0
- package/dist/admin/hooks/useAIMetadataAvailability.js.map +1 -0
- package/dist/admin/hooks/useAIMetadataAvailability.mjs +12 -0
- package/dist/admin/hooks/useAIMetadataAvailability.mjs.map +1 -0
- package/dist/admin/hooks/useAIMetadataEnabled.js +7 -5
- package/dist/admin/hooks/useAIMetadataEnabled.js.map +1 -1
- package/dist/admin/hooks/useAIMetadataEnabled.mjs +7 -5
- package/dist/admin/hooks/useAIMetadataEnabled.mjs.map +1 -1
- package/dist/admin/hooks/useTracking.js +4 -10
- package/dist/admin/hooks/useTracking.js.map +1 -1
- package/dist/admin/hooks/useTracking.mjs +5 -10
- package/dist/admin/hooks/useTracking.mjs.map +1 -1
- package/dist/admin/legacy/hooks/useAIMetadataAvailability.js +14 -0
- package/dist/admin/legacy/hooks/useAIMetadataAvailability.js.map +1 -0
- package/dist/admin/legacy/hooks/useAIMetadataAvailability.mjs +12 -0
- package/dist/admin/legacy/hooks/useAIMetadataAvailability.mjs.map +1 -0
- package/dist/admin/legacy/hooks/useAIMetadataEnabled.js +23 -0
- package/dist/admin/legacy/hooks/useAIMetadataEnabled.js.map +1 -0
- package/dist/admin/legacy/hooks/useAIMetadataEnabled.mjs +21 -0
- package/dist/admin/legacy/hooks/useAIMetadataEnabled.mjs.map +1 -0
- package/dist/admin/legacy/hooks/useTracking.js +5 -3
- package/dist/admin/legacy/hooks/useTracking.js.map +1 -1
- package/dist/admin/legacy/hooks/useTracking.mjs +5 -3
- package/dist/admin/legacy/hooks/useTracking.mjs.map +1 -1
- package/dist/admin/legacy/pages/App/ConfigureTheView/ConfigureTheView.js +2 -1
- package/dist/admin/legacy/pages/App/ConfigureTheView/ConfigureTheView.js.map +1 -1
- package/dist/admin/legacy/pages/App/ConfigureTheView/ConfigureTheView.mjs +2 -1
- package/dist/admin/legacy/pages/App/ConfigureTheView/ConfigureTheView.mjs.map +1 -1
- package/dist/admin/legacy/pages/App/MediaLibrary.js +6 -5
- package/dist/admin/legacy/pages/App/MediaLibrary.js.map +1 -1
- package/dist/admin/legacy/pages/App/MediaLibrary.mjs +6 -5
- package/dist/admin/legacy/pages/App/MediaLibrary.mjs.map +1 -1
- package/dist/admin/pages/Assets/AssetsPage.js +24 -12
- package/dist/admin/pages/Assets/AssetsPage.js.map +1 -1
- package/dist/admin/pages/Assets/AssetsPage.mjs +25 -13
- package/dist/admin/pages/Assets/AssetsPage.mjs.map +1 -1
- package/dist/admin/pages/Assets/components/AssetActions.js +326 -0
- package/dist/admin/pages/Assets/components/AssetActions.js.map +1 -0
- package/dist/admin/pages/Assets/components/AssetActions.mjs +324 -0
- package/dist/admin/pages/Assets/components/AssetActions.mjs.map +1 -0
- package/dist/admin/pages/Assets/components/AssetActionsMenu.js +28 -309
- package/dist/admin/pages/Assets/components/AssetActionsMenu.js.map +1 -1
- package/dist/admin/pages/Assets/components/AssetActionsMenu.mjs +31 -312
- package/dist/admin/pages/Assets/components/AssetActionsMenu.mjs.map +1 -1
- package/dist/admin/pages/Assets/components/AssetContextMenu.js +79 -0
- package/dist/admin/pages/Assets/components/AssetContextMenu.js.map +1 -0
- package/dist/admin/pages/Assets/components/AssetContextMenu.mjs +77 -0
- package/dist/admin/pages/Assets/components/AssetContextMenu.mjs.map +1 -0
- package/dist/admin/pages/Assets/components/AssetDetails/AssetCropEditor.js +87 -5
- package/dist/admin/pages/Assets/components/AssetDetails/AssetCropEditor.js.map +1 -1
- package/dist/admin/pages/Assets/components/AssetDetails/AssetCropEditor.mjs +89 -7
- package/dist/admin/pages/Assets/components/AssetDetails/AssetCropEditor.mjs.map +1 -1
- package/dist/admin/pages/Assets/components/AssetDetails/AssetDetailsDrawer.js +3 -3
- package/dist/admin/pages/Assets/components/AssetDetails/AssetDetailsDrawer.js.map +1 -1
- package/dist/admin/pages/Assets/components/AssetDetails/AssetDetailsDrawer.mjs +3 -3
- package/dist/admin/pages/Assets/components/AssetDetails/AssetDetailsDrawer.mjs.map +1 -1
- package/dist/admin/pages/Assets/components/AssetDetails/useCropImg.js +62 -5
- package/dist/admin/pages/Assets/components/AssetDetails/useCropImg.js.map +1 -1
- package/dist/admin/pages/Assets/components/AssetDetails/useCropImg.mjs +60 -6
- package/dist/admin/pages/Assets/components/AssetDetails/useCropImg.mjs.map +1 -1
- package/dist/admin/pages/Assets/components/AssetsGrid.js +27 -4
- package/dist/admin/pages/Assets/components/AssetsGrid.js.map +1 -1
- package/dist/admin/pages/Assets/components/AssetsGrid.mjs +27 -4
- package/dist/admin/pages/Assets/components/AssetsGrid.mjs.map +1 -1
- package/dist/admin/pages/Assets/components/AssetsTable.js +27 -4
- package/dist/admin/pages/Assets/components/AssetsTable.js.map +1 -1
- package/dist/admin/pages/Assets/components/AssetsTable.mjs +27 -4
- package/dist/admin/pages/Assets/components/AssetsTable.mjs.map +1 -1
- package/dist/admin/pages/Assets/components/CursorAnchoredMenu.js +77 -0
- package/dist/admin/pages/Assets/components/CursorAnchoredMenu.js.map +1 -0
- package/dist/admin/pages/Assets/components/CursorAnchoredMenu.mjs +75 -0
- package/dist/admin/pages/Assets/components/CursorAnchoredMenu.mjs.map +1 -0
- package/dist/admin/pages/Assets/components/DeleteItemsDialog.js +4 -2
- package/dist/admin/pages/Assets/components/DeleteItemsDialog.js.map +1 -1
- package/dist/admin/pages/Assets/components/DeleteItemsDialog.mjs +4 -2
- package/dist/admin/pages/Assets/components/DeleteItemsDialog.mjs.map +1 -1
- package/dist/admin/pages/Assets/components/FolderActions.js +139 -0
- package/dist/admin/pages/Assets/components/FolderActions.js.map +1 -0
- package/dist/admin/pages/Assets/components/FolderActions.mjs +137 -0
- package/dist/admin/pages/Assets/components/FolderActions.mjs.map +1 -0
- package/dist/admin/pages/Assets/components/FolderActionsMenu.js +25 -119
- package/dist/admin/pages/Assets/components/FolderActionsMenu.js.map +1 -1
- package/dist/admin/pages/Assets/components/FolderActionsMenu.mjs +27 -121
- package/dist/admin/pages/Assets/components/FolderActionsMenu.mjs.map +1 -1
- package/dist/admin/pages/Assets/components/FolderContextMenu.js +67 -0
- package/dist/admin/pages/Assets/components/FolderContextMenu.js.map +1 -0
- package/dist/admin/pages/Assets/components/FolderContextMenu.mjs +65 -0
- package/dist/admin/pages/Assets/components/FolderContextMenu.mjs.map +1 -0
- package/dist/admin/pages/Assets/components/FolderTree/FolderTree.js +139 -45
- package/dist/admin/pages/Assets/components/FolderTree/FolderTree.js.map +1 -1
- package/dist/admin/pages/Assets/components/FolderTree/FolderTree.mjs +140 -47
- package/dist/admin/pages/Assets/components/FolderTree/FolderTree.mjs.map +1 -1
- package/dist/admin/pages/Assets/components/FolderTree/FolderTreeRowMenu.js +115 -0
- package/dist/admin/pages/Assets/components/FolderTree/FolderTreeRowMenu.js.map +1 -0
- package/dist/admin/pages/Assets/components/FolderTree/FolderTreeRowMenu.mjs +113 -0
- package/dist/admin/pages/Assets/components/FolderTree/FolderTreeRowMenu.mjs.map +1 -0
- package/dist/admin/pages/Assets/components/ItemContextMenu.js +125 -0
- package/dist/admin/pages/Assets/components/ItemContextMenu.js.map +1 -0
- package/dist/admin/pages/Assets/components/ItemContextMenu.mjs +122 -0
- package/dist/admin/pages/Assets/components/ItemContextMenu.mjs.map +1 -0
- package/dist/admin/pages/Assets/components/MainAreaContextMenu.js +88 -63
- package/dist/admin/pages/Assets/components/MainAreaContextMenu.js.map +1 -1
- package/dist/admin/pages/Assets/components/MainAreaContextMenu.mjs +88 -63
- package/dist/admin/pages/Assets/components/MainAreaContextMenu.mjs.map +1 -1
- package/dist/admin/pages/Assets/components/SelectionActions.js +111 -0
- package/dist/admin/pages/Assets/components/SelectionActions.js.map +1 -0
- package/dist/admin/pages/Assets/components/SelectionActions.mjs +109 -0
- package/dist/admin/pages/Assets/components/SelectionActions.mjs.map +1 -0
- package/dist/admin/pages/Assets/components/SelectionContextMenu.js +82 -0
- package/dist/admin/pages/Assets/components/SelectionContextMenu.js.map +1 -0
- package/dist/admin/pages/Assets/components/SelectionContextMenu.mjs +80 -0
- package/dist/admin/pages/Assets/components/SelectionContextMenu.mjs.map +1 -0
- package/dist/admin/pages/Assets/hooks/useAssetSelection.js +10 -0
- package/dist/admin/pages/Assets/hooks/useAssetSelection.js.map +1 -1
- package/dist/admin/pages/Assets/hooks/useAssetSelection.mjs +10 -0
- package/dist/admin/pages/Assets/hooks/useAssetSelection.mjs.map +1 -1
- package/dist/admin/pages/SettingsPage/SettingsPage.js +10 -3
- package/dist/admin/pages/SettingsPage/SettingsPage.js.map +1 -1
- package/dist/admin/pages/SettingsPage/SettingsPage.mjs +10 -3
- package/dist/admin/pages/SettingsPage/SettingsPage.mjs.map +1 -1
- package/dist/admin/services/api.js.map +1 -1
- package/dist/admin/services/api.mjs.map +1 -1
- package/dist/admin/src/hooks/useAIMetadataAvailability.d.ts +5 -0
- package/dist/admin/src/hooks/useAIMetadataEnabled.d.ts +5 -3
- package/dist/admin/src/hooks/useTracking.d.ts +0 -7
- package/dist/admin/src/legacy/components/EditAssetDialog/PreviewBox/PreviewComponents.d.ts +2 -2
- package/dist/admin/src/legacy/hooks/useAIMetadataAvailability.d.ts +5 -0
- package/dist/admin/src/legacy/hooks/useAIMetadataEnabled.d.ts +12 -0
- package/dist/admin/src/legacy/hooks/useSettings.d.ts +1 -0
- package/dist/admin/src/pages/Assets/components/ActionsMenuContent.d.ts +2 -2
- package/dist/admin/src/pages/Assets/components/AssetActions.d.ts +70 -0
- package/dist/admin/src/pages/Assets/components/AssetActionsMenu.d.ts +5 -14
- package/dist/admin/src/pages/Assets/components/AssetContextMenu.d.ts +27 -0
- package/dist/admin/src/pages/Assets/components/AssetDetails/useCropImg.d.ts +16 -0
- package/dist/admin/src/pages/Assets/components/CursorAnchoredMenu.d.ts +32 -0
- package/dist/admin/src/pages/Assets/components/FolderActions.d.ts +48 -0
- package/dist/admin/src/pages/Assets/components/FolderActionsMenu.d.ts +4 -8
- package/dist/admin/src/pages/Assets/components/FolderContextMenu.d.ts +21 -0
- package/dist/admin/src/pages/Assets/components/FolderTree/FolderTree.d.ts +5 -1
- package/dist/admin/src/pages/Assets/components/FolderTree/FolderTreeRowMenu.d.ts +25 -0
- package/dist/admin/src/pages/Assets/components/ItemContextMenu.d.ts +53 -0
- package/dist/admin/src/pages/Assets/components/MainAreaContextMenu.d.ts +15 -1
- package/dist/admin/src/pages/Assets/components/SelectionActions.d.ts +40 -0
- package/dist/admin/src/pages/Assets/components/SelectionContextMenu.d.ts +25 -0
- package/dist/admin/src/pages/Assets/hooks/useAssetSelection.d.ts +5 -0
- package/dist/admin/src/utils/mediaLibraryVersion.d.ts +18 -0
- package/dist/admin/translations/en.json.js +9 -3
- package/dist/admin/translations/en.json.js.map +1 -1
- package/dist/admin/translations/en.json.mjs +9 -3
- package/dist/admin/translations/en.json.mjs.map +1 -1
- package/dist/admin/utils/mediaLibraryVersion.js +24 -0
- package/dist/admin/utils/mediaLibraryVersion.js.map +1 -0
- package/dist/admin/utils/mediaLibraryVersion.mjs +20 -0
- package/dist/admin/utils/mediaLibraryVersion.mjs.map +1 -0
- package/dist/server/bootstrap.js +7 -0
- package/dist/server/bootstrap.js.map +1 -1
- package/dist/server/bootstrap.mjs +7 -0
- package/dist/server/bootstrap.mjs.map +1 -1
- package/dist/server/controllers/admin-file.js +2 -2
- package/dist/server/controllers/admin-file.js.map +1 -1
- package/dist/server/controllers/admin-file.mjs +2 -2
- package/dist/server/controllers/admin-file.mjs.map +1 -1
- package/dist/server/controllers/admin-folder-file.js +51 -4
- package/dist/server/controllers/admin-folder-file.js.map +1 -1
- package/dist/server/controllers/admin-folder-file.mjs +52 -5
- package/dist/server/controllers/admin-folder-file.mjs.map +1 -1
- package/dist/server/controllers/admin-folder.js +5 -5
- package/dist/server/controllers/admin-folder.js.map +1 -1
- package/dist/server/controllers/admin-folder.mjs +5 -5
- package/dist/server/controllers/admin-folder.mjs.map +1 -1
- package/dist/server/controllers/admin-settings.js +2 -1
- package/dist/server/controllers/admin-settings.js.map +1 -1
- package/dist/server/controllers/admin-settings.mjs +2 -1
- package/dist/server/controllers/admin-settings.mjs.map +1 -1
- package/dist/server/controllers/content-api.js +30 -7
- package/dist/server/controllers/content-api.js.map +1 -1
- package/dist/server/controllers/content-api.mjs +30 -7
- package/dist/server/controllers/content-api.mjs.map +1 -1
- package/dist/server/controllers/validation/admin/folder-file.js +4 -4
- package/dist/server/controllers/validation/admin/folder-file.js.map +1 -1
- package/dist/server/controllers/validation/admin/folder-file.mjs +2 -2
- package/dist/server/controllers/validation/admin/folder-file.mjs.map +1 -1
- package/dist/server/controllers/validation/admin/folder.js +3 -4
- package/dist/server/controllers/validation/admin/folder.js.map +1 -1
- package/dist/server/controllers/validation/admin/folder.mjs +3 -4
- package/dist/server/controllers/validation/admin/folder.mjs.map +1 -1
- package/dist/server/controllers/validation/admin/upload.js +1 -2
- package/dist/server/controllers/validation/admin/upload.js.map +1 -1
- package/dist/server/controllers/validation/admin/upload.mjs +1 -2
- package/dist/server/controllers/validation/admin/upload.mjs.map +1 -1
- package/dist/server/controllers/validation/admin/utils.js +1 -2
- package/dist/server/controllers/validation/admin/utils.js.map +1 -1
- package/dist/server/controllers/validation/admin/utils.mjs +1 -2
- package/dist/server/controllers/validation/admin/utils.mjs.map +1 -1
- package/dist/server/errors.js +12 -0
- package/dist/server/errors.js.map +1 -0
- package/dist/server/errors.mjs +10 -0
- package/dist/server/errors.mjs.map +1 -0
- package/dist/server/migrations/unsign-richtext-and-blocks-urls.js +2 -2
- package/dist/server/migrations/unsign-richtext-and-blocks-urls.js.map +1 -1
- package/dist/server/migrations/unsign-richtext-and-blocks-urls.mjs +1 -1
- package/dist/server/migrations/unsign-richtext-and-blocks-urls.mjs.map +1 -1
- package/dist/server/routes/content-api.js +3 -3
- package/dist/server/routes/content-api.js.map +1 -1
- package/dist/server/routes/content-api.mjs +3 -3
- package/dist/server/routes/content-api.mjs.map +1 -1
- package/dist/server/routes/validation/upload.js +8 -0
- package/dist/server/routes/validation/upload.js.map +1 -1
- package/dist/server/routes/validation/upload.mjs +8 -0
- package/dist/server/routes/validation/upload.mjs.map +1 -1
- package/dist/server/services/ai-metadata-provider.js +83 -0
- package/dist/server/services/ai-metadata-provider.js.map +1 -0
- package/dist/server/services/ai-metadata-provider.mjs +61 -0
- package/dist/server/services/ai-metadata-provider.mjs.map +1 -0
- package/dist/server/services/ai-metadata-strapi-managed.js +48 -0
- package/dist/server/services/ai-metadata-strapi-managed.js.map +1 -0
- package/dist/server/services/ai-metadata-strapi-managed.mjs +46 -0
- package/dist/server/services/ai-metadata-strapi-managed.mjs.map +1 -0
- package/dist/server/services/ai-metadata.js +5 -58
- package/dist/server/services/ai-metadata.js.map +1 -1
- package/dist/server/services/ai-metadata.mjs +6 -39
- package/dist/server/services/ai-metadata.mjs.map +1 -1
- package/dist/server/services/api-upload-folder.js +3 -3
- package/dist/server/services/api-upload-folder.js.map +1 -1
- package/dist/server/services/api-upload-folder.mjs +3 -3
- package/dist/server/services/api-upload-folder.mjs.map +1 -1
- package/dist/server/services/extensions/utils.js +19 -15
- package/dist/server/services/extensions/utils.js.map +1 -1
- package/dist/server/services/extensions/utils.mjs +19 -15
- package/dist/server/services/extensions/utils.mjs.map +1 -1
- package/dist/server/services/file.js +2 -2
- package/dist/server/services/file.js.map +1 -1
- package/dist/server/services/file.mjs +1 -1
- package/dist/server/services/file.mjs.map +1 -1
- package/dist/server/services/folder.js +9 -6
- package/dist/server/services/folder.js.map +1 -1
- package/dist/server/services/folder.mjs +9 -6
- package/dist/server/services/folder.mjs.map +1 -1
- package/dist/server/services/index.js +3 -1
- package/dist/server/services/index.js.map +1 -1
- package/dist/server/services/index.mjs +3 -1
- package/dist/server/services/index.mjs.map +1 -1
- package/dist/server/services/metrics.js +1 -1
- package/dist/server/services/metrics.js.map +1 -1
- package/dist/server/services/metrics.mjs +1 -1
- package/dist/server/services/metrics.mjs.map +1 -1
- package/dist/server/services/provider.js +3 -4
- package/dist/server/services/provider.js.map +1 -1
- package/dist/server/services/provider.mjs +3 -4
- package/dist/server/services/provider.mjs.map +1 -1
- package/dist/server/services/upload.js +5 -6
- package/dist/server/services/upload.js.map +1 -1
- package/dist/server/services/upload.mjs +4 -5
- package/dist/server/services/upload.mjs.map +1 -1
- package/dist/server/services/weekly-metrics.js +2 -2
- package/dist/server/services/weekly-metrics.js.map +1 -1
- package/dist/server/services/weekly-metrics.mjs +2 -2
- package/dist/server/services/weekly-metrics.mjs.map +1 -1
- package/dist/server/src/bootstrap.d.ts.map +1 -1
- package/dist/server/src/controllers/admin-folder-file.d.ts.map +1 -1
- package/dist/server/src/controllers/admin-folder.d.ts.map +1 -1
- package/dist/server/src/controllers/admin-settings.d.ts.map +1 -1
- package/dist/server/src/controllers/content-api.d.ts.map +1 -1
- package/dist/server/src/controllers/validation/admin/folder.d.ts.map +1 -1
- package/dist/server/src/controllers/validation/admin/upload.d.ts.map +1 -1
- package/dist/server/src/controllers/validation/admin/utils.d.ts.map +1 -1
- package/dist/server/src/errors.d.ts +6 -0
- package/dist/server/src/errors.d.ts.map +1 -0
- package/dist/server/src/index.d.ts +6 -1
- package/dist/server/src/index.d.ts.map +1 -1
- package/dist/server/src/mcp/ambient-instance.d.ts +3 -1
- package/dist/server/src/mcp/ambient-instance.d.ts.map +1 -1
- package/dist/server/src/routes/validation/upload.d.ts +4 -0
- package/dist/server/src/routes/validation/upload.d.ts.map +1 -1
- package/dist/server/src/services/ai-metadata-provider.d.ts +26 -0
- package/dist/server/src/services/ai-metadata-provider.d.ts.map +1 -0
- package/dist/server/src/services/ai-metadata-strapi-managed.d.ts +7 -0
- package/dist/server/src/services/ai-metadata-strapi-managed.d.ts.map +1 -0
- package/dist/server/src/services/ai-metadata.d.ts.map +1 -1
- package/dist/server/src/services/extensions/utils.d.ts +33 -1
- package/dist/server/src/services/extensions/utils.d.ts.map +1 -1
- package/dist/server/src/services/folder.d.ts +5 -2
- package/dist/server/src/services/folder.d.ts.map +1 -1
- package/dist/server/src/services/index.d.ts +6 -1
- package/dist/server/src/services/index.d.ts.map +1 -1
- package/dist/server/src/services/provider.d.ts.map +1 -1
- package/dist/server/src/services/upload.d.ts.map +1 -1
- package/dist/server/src/utils/images.d.ts +2 -2
- package/dist/server/src/utils/images.d.ts.map +1 -1
- package/dist/server/src/utils/index.d.ts +2 -0
- package/dist/server/src/utils/index.d.ts.map +1 -1
- package/dist/server/utils/images.js +6 -7
- package/dist/server/utils/images.js.map +1 -1
- package/dist/server/utils/images.mjs +6 -7
- package/dist/server/utils/images.mjs.map +1 -1
- package/dist/server/utils/index.js.map +1 -1
- package/dist/server/utils/index.mjs.map +1 -1
- package/dist/shared/contracts/settings.d.ts +6 -0
- package/package.json +11 -11
- package/dist/admin/legacy/hooks/useAiAvailability.js +0 -22
- package/dist/admin/legacy/hooks/useAiAvailability.js.map +0 -1
- package/dist/admin/legacy/hooks/useAiAvailability.mjs +0 -20
- package/dist/admin/legacy/hooks/useAiAvailability.mjs.map +0 -1
- package/dist/admin/src/legacy/hooks/useAiAvailability.d.ts +0 -4
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
|
-
import { omit } from 'lodash/fp';
|
|
3
2
|
import { traverseEntity, async } from '@strapi/utils';
|
|
4
3
|
import { getService } from '../../utils/index.mjs';
|
|
5
4
|
import { FILE_MODEL_UID } from '../../constants.mjs';
|
|
@@ -235,24 +234,29 @@ const getImageFormats = (image)=>Object.entries(image.formats ?? {});
|
|
|
235
234
|
return image;
|
|
236
235
|
}
|
|
237
236
|
const names = getQueryParamNames(signedUrl);
|
|
238
|
-
const result =
|
|
239
|
-
'isUrlSigned'
|
|
240
|
-
], {
|
|
237
|
+
const { isUrlSigned: _isUrlSigned, ...result } = {
|
|
241
238
|
...image,
|
|
242
239
|
url: removeQueryParams(image.url ?? '', names)
|
|
243
|
-
}
|
|
240
|
+
};
|
|
244
241
|
if (image.formats) {
|
|
245
|
-
result.formats = Object.fromEntries(getImageFormats(image).map(([key, format])=>
|
|
242
|
+
result.formats = Object.fromEntries(getImageFormats(image).map(([key, format])=>{
|
|
243
|
+
if (!format || typeof format !== 'object') {
|
|
244
|
+
return [
|
|
245
|
+
key,
|
|
246
|
+
format
|
|
247
|
+
];
|
|
248
|
+
}
|
|
249
|
+
const { isUrlSigned: _formatIsUrlSigned, ...unsignedFormat } = {
|
|
250
|
+
...format,
|
|
251
|
+
...format.url ? {
|
|
252
|
+
url: removeQueryParams(format.url, names)
|
|
253
|
+
} : {}
|
|
254
|
+
};
|
|
255
|
+
return [
|
|
246
256
|
key,
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
...format,
|
|
251
|
-
...format.url ? {
|
|
252
|
-
url: removeQueryParams(format.url, names)
|
|
253
|
-
} : {}
|
|
254
|
-
}) : format
|
|
255
|
-
]));
|
|
257
|
+
unsignedFormat
|
|
258
|
+
];
|
|
259
|
+
}));
|
|
256
260
|
}
|
|
257
261
|
return result;
|
|
258
262
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.mjs","sources":["../../../../server/src/services/extensions/utils.ts"],"sourcesContent":["import path from 'path';\n\nimport { omit } from 'lodash/fp';\nimport { 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 { Config, 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\n/**\n * One presign per distinct URL, keyed by the bare (unsigned) URL. The promise\n * is stored rather than its result so concurrent occurrences of the same URL\n * (the `async.map` in `mapRichtextUrls`, or several entries of a `findMany`)\n * share a single provider call.\n *\n * A cache must never outlive the document service call or migration run that\n * created it: a signed URL is only as fresh as the request that produced it.\n */\ntype SignCache = Map<string, Promise<string | null>>;\n\nconst createSignCache = (): SignCache => new Map();\n\n/**\n * URL bearing syntaxes found in a richtext (markdown) value. Each pattern\n * captures a prefix (group 1) and the URL (group 2) so the rebuild loop in\n * `mapRichtextUrls` can splice a new URL in place without touching anything\n * else.\n *\n * - markdown image / link: `` or `[text](url)`. The URL is captured\n * up to the first whitespace or closing parenthesis, which also covers the\n * optional title form `[text](url \"title\")`.\n * - raw HTML `src` / `href` attributes, double or single quoted, on the\n * elements the editor lets users write by hand (`<img src=\"...\" width=\"200\">`\n * is the usual way to size an image).\n */\nconst RICHTEXT_URL_REGEXES = [\n /(!?\\[[^\\]]*\\]\\()([^)\\s]+)/g,\n /(<(?:img|a|source|video|audio)\\b[^>]*?\\b(?:src|href)=\")([^\"]+)/gi,\n /(<(?:img|a|source|video|audio)\\b[^>]*?\\b(?:src|href)=')([^']+)/gi,\n];\n\n/** Cheap short circuit for richtext values that cannot contain a URL we would rewrite. */\nconst RICHTEXT_URL_HINTS = ['](', 'src=', 'href='];\n\ntype BlocksNode = {\n type?: string;\n image?: File;\n children?: unknown;\n [key: string]: unknown;\n};\n\ntype ImageFormat = { url?: string; isUrlSigned?: boolean; [key: string]: unknown };\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\nconst stripQueryString = (url: string) => url.split('?')[0];\n\nconst decodeQueryParamName = (pair: string) => {\n const name = pair.split('=')[0];\n\n try {\n return decodeURIComponent(name);\n } catch {\n return name;\n }\n};\n\n/**\n * Split a URL into what precedes the `?`, the raw `key=value` pairs of its\n * query string and the `#fragment`, if any. Plain string work on purpose:\n * `new URL` throws on the relative `/uploads/...` paths a local provider\n * writes, and would re-encode the pairs we want to hand back untouched.\n */\nconst splitUrl = (url: string) => {\n const hashIndex = url.indexOf('#');\n const fragment = hashIndex === -1 ? '' : url.slice(hashIndex);\n const withoutFragment = hashIndex === -1 ? url : url.slice(0, hashIndex);\n\n const queryIndex = withoutFragment.indexOf('?');\n\n if (queryIndex === -1) {\n return { base: withoutFragment, pairs: [] as string[], fragment };\n }\n\n return {\n base: withoutFragment.slice(0, queryIndex),\n pairs: withoutFragment\n .slice(queryIndex + 1)\n .split('&')\n .filter(Boolean),\n fragment,\n };\n};\n\n/** Names of the query parameters of `url`, decoded, in order. */\nconst getQueryParamNames = (url: string): string[] => splitUrl(url).pairs.map(decodeQueryParamName);\n\n/**\n * `url` with every query parameter whose name is in `names` removed. The\n * surviving pairs keep their order and encoding; the `?` is dropped when\n * nothing is left.\n */\nconst removeQueryParams = (url: string, names: string[]): string => {\n const { base, pairs, fragment } = splitUrl(url);\n\n if (pairs.length === 0 || names.length === 0) {\n return url;\n }\n\n const kept = pairs.filter((pair) => !names.includes(decodeQueryParamName(pair)));\n\n return kept.length === 0 ? `${base}${fragment}` : `${base}?${kept.join('&')}${fragment}`;\n};\n\n/**\n * Build the minimal file shape `signFileUrls` needs out of a bare URL, as found\n * in a richtext value.\n *\n * `hash` and `ext` are taken from the last path segment and `path` is left\n * unset, which rebuilds the S3 key for the default layout and for the\n * `rootPath` provider option (the provider prefixes it itself). A URL whose key\n * has extra segments (a file uploaded with the per-file `path` option, or an\n * older key layout) is still recognised as ours by the provider, but it is\n * signed with a key that does not exist and the link returns 403. Media\n * signing has the same limit because `path` is not persisted on the file\n * (`content-types/file.ts`). Not addressed here.\n */\nconst getFileFromUrl = (url: string): File => {\n const unsignedUrl = stripQueryString(url);\n const { provider } = strapi.config.get<Config>('plugin::upload');\n const { name, ext } = path.parse(unsignedUrl.split('/').pop() ?? '');\n\n return { url: unsignedUrl, hash: name, ext, provider } as File;\n};\n\n/**\n * Ask the provider to sign `file` and return the signed URL if the provider\n * owns it, otherwise `null`. Memoised in `cache` by the bare URL.\n *\n * Ownership is decided by whether the provider changed the URL: `signFileUrls`\n * flags every file of the configured provider as signed, but the provider\n * itself returns URLs it does not own untouched (an S3 URL from another\n * bucket, an external site, a local `/uploads` path), so a changed URL is the\n * only reliable signal. `file.url` must already be the bare URL so the\n * comparison is meaningful.\n */\nconst signFileIfOwned = (file: File, cache: SignCache): Promise<string | null> => {\n const bareUrl = file.url ?? '';\n const cached = cache.get(bareUrl);\n\n if (cached) {\n return cached;\n }\n\n const pending = (async () => {\n const { signFileUrls } = getService('file');\n const signedFile = await signFileUrls(file);\n\n return signedFile.url && signedFile.url !== bareUrl ? signedFile.url : null;\n })();\n\n cache.set(bareUrl, pending);\n\n return pending;\n};\n\n/**\n * Signs a richtext URL if the configured provider owns it, otherwise `null`.\n * See `signFileIfOwned` for how ownership is decided. A single predicate means\n * the read path (sign) and the write path (unsign) can never disagree about\n * what they own.\n */\nconst signIfOwned = (url: string, cache: SignCache): Promise<string | null> =>\n signFileIfOwned(getFileFromUrl(url), cache);\n\n/**\n * Returns a richtext URL with its signature removed, if we own it.\n *\n * Only the query parameters the provider's own fresh signature introduces are\n * removed (for S3, the `X-Amz-*` family), every other parameter is kept. That\n * way a provider that signs whatever it is handed cannot make us drop `?v=x`\n * from a link to an external site. A parameter of an old signature that the\n * fresh one no longer carries (an `X-Amz-Security-Token` from credentials no\n * longer in use, say) survives in the row; it is not a signature, and the read\n * path replaces the whole URL with the provider output anyway.\n */\nconst stripSignedUrl = async (url: string, cache: SignCache) => {\n // Nothing to strip, and no reason to ask the provider\n if (!url.includes('?')) {\n return url;\n }\n\n const signedUrl = await signIfOwned(url, cache);\n\n if (signedUrl === null) {\n return url;\n }\n\n return removeQueryParams(url, getQueryParamNames(signedUrl));\n};\n\n/** Returns a freshly signed richtext URL, if we own it. */\nconst signUrl = async (url: string, cache: SignCache) => (await signIfOwned(url, cache)) ?? url;\n\n/**\n * Apply `mapUrl` to every markdown image / link URL and every raw HTML\n * `src` / `href` URL of a richtext value.\n */\nconst mapRichtextUrls = async (value: unknown, mapUrl: (url: string) => Promise<string>) => {\n if (typeof value !== 'string' || !RICHTEXT_URL_HINTS.some((hint) => value.includes(hint))) {\n return value;\n }\n\n // The rebuild loop splices by match index, so matches must be in ascending order\n const matches = RICHTEXT_URL_REGEXES.flatMap((regex) => Array.from(value.matchAll(regex))).sort(\n (a, b) => (a.index as number) - (b.index as number)\n );\n\n if (matches.length === 0) {\n return value;\n }\n\n const urls: string[] = await async.map(matches, (match: RegExpMatchArray) => mapUrl(match[2]));\n\n let result = '';\n let cursor = 0;\n\n matches.forEach((match, index) => {\n const urlStart = (match.index as number) + match[1].length;\n\n // Two patterns matched overlapping text: keep the first, never splice twice\n if (urlStart < cursor) {\n return;\n }\n\n result += value.slice(cursor, urlStart) + urls[index];\n cursor = urlStart + match[2].length;\n });\n\n return result + value.slice(cursor);\n};\n\n/**\n * Apply `mapImage` to every `image` node of a blocks value, recursively.\n * A new array is always returned, the original value is never mutated.\n */\nconst mapBlocksImages = async (\n value: unknown,\n mapImage: (image: File) => Promise<File>\n): Promise<unknown> => {\n if (!Array.isArray(value)) {\n return value;\n }\n\n return async.map(value, async (node: BlocksNode) => {\n if (!node || typeof node !== 'object') {\n return node;\n }\n\n let result = node;\n\n if (node.type === 'image' && node.image) {\n result = { ...result, image: await mapImage(node.image) };\n }\n\n if (Array.isArray(node.children)) {\n result = { ...result, children: await mapBlocksImages(node.children, mapImage) };\n }\n\n return result;\n });\n};\n\nconst getImageFormats = (image: File): [string, ImageFormat][] =>\n Object.entries(image.formats ?? {}) as [string, ImageFormat][];\n\n/** True when the node still carries anything `unsignImage` would remove. */\nconst hasSignatureTraces = (image: File) => {\n if (image.isUrlSigned !== undefined || (image.url ?? '').includes('?')) {\n return true;\n }\n\n return getImageFormats(image).some(\n ([, format]) => format?.isUrlSigned !== undefined || (format?.url ?? '').includes('?')\n );\n};\n\n/**\n * Remove the signature from a blocks `image` node: the signature parameters on\n * the file URL and on every format URL (the same presigner signs them all, so\n * the same parameter names apply), and the `isUrlSigned` flag `signFileUrls`\n * sets on the file and on each format. The node carries a full file object, so\n * the ownership check is made with it rather than with a URL derived stand in.\n */\nconst unsignImage = async (image: File, cache: SignCache = createSignCache()) => {\n // Already bare: nothing to do, and no reason to ask the provider\n if (!hasSignatureTraces(image)) {\n return image;\n }\n\n const bareUrl = stripQueryString(image.url ?? '');\n const signedUrl = await signFileIfOwned({ ...image, url: bareUrl }, cache);\n\n // Not one of ours (different provider, external URL, public provider): the\n // provider hands the URL back unchanged, leave the node as is\n if (signedUrl === null) {\n return image;\n }\n\n const names = getQueryParamNames(signedUrl);\n const result: File = omit(['isUrlSigned'], {\n ...image,\n url: removeQueryParams(image.url ?? '', names),\n });\n\n if (image.formats) {\n result.formats = Object.fromEntries(\n getImageFormats(image).map(([key, format]) => [\n key,\n format && typeof format === 'object'\n ? omit(['isUrlSigned'], {\n ...format,\n ...(format.url ? { url: removeQueryParams(format.url, names) } : {}),\n })\n : format,\n ])\n );\n }\n\n return result;\n};\n\n/**\n * Visitor function to sign media URLs\n */\nconst createSignEntityMediaVisitor =\n (cache: SignCache): SignEntityMediaVisitor =>\n async ({ key, value, attribute }, { set }) => {\n const { signFileUrls } = getService('file');\n\n if (!attribute) {\n return;\n }\n\n switch (attribute.type) {\n case 'blocks':\n set(key, await mapBlocksImages(value, signFileUrls));\n return;\n\n case 'richtext':\n set(key, await mapRichtextUrls(value, (url) => signUrl(url, cache)));\n return;\n\n case 'media': {\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 break;\n }\n\n default:\n break;\n }\n };\n\n/**\n * Visitor function to remove the signature from richtext / blocks URLs before\n * they are persisted. Media attributes are left alone: the files table already\n * holds the unsigned URL.\n */\nconst createUnsignEntityMediaVisitor =\n (cache: SignCache): SignEntityMediaVisitor =>\n async ({ key, value, attribute }, { set }) => {\n if (!attribute) {\n return;\n }\n\n if (attribute.type === 'blocks') {\n set(key, await mapBlocksImages(value, (image) => unsignImage(image, cache)));\n return;\n }\n\n if (attribute.type === 'richtext') {\n set(key, await mapRichtextUrls(value, (url) => stripSignedUrl(url, cache)));\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 * @param {Map} cache one per document service call, see `SignCache`\n * @returns\n */\nconst signEntityMedia = async (\n entity: any,\n uid: UID.Schema,\n cache: SignCache = createSignCache()\n) => {\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 createSignEntityMediaVisitor(cache),\n { schema: model, getModel: strapi.getModel.bind(strapi) },\n entity\n );\n};\n\n/**\n * Iterate through the input data of a create / update / clone and replace\n * every signed provider URL found in a richtext or blocks attribute with its\n * unsigned form, so that a short lived signature is never persisted.\n */\nconst unsignEntityMedia = async (\n data: any,\n uid: UID.Schema,\n cache: SignCache = createSignCache()\n) => {\n if (!data || uid === FILE_MODEL_UID) {\n return data;\n }\n\n const model = strapi.getModel(uid);\n\n return traverseEntity(\n // @ts-expect-error - FIXME: fix traverseEntity using wrong types\n createUnsignEntityMediaVisitor(cache),\n { schema: model, getModel: strapi.getModel.bind(strapi) },\n data\n );\n};\n\nexport type { SignCache };\n\nexport {\n createSignCache,\n signEntityMedia,\n unsignEntityMedia,\n mapRichtextUrls,\n mapBlocksImages,\n stripSignedUrl,\n unsignImage,\n};\n"],"names":["createSignCache","Map","RICHTEXT_URL_REGEXES","RICHTEXT_URL_HINTS","isFile","value","attribute","type","stripQueryString","url","split","decodeQueryParamName","pair","name","decodeURIComponent","splitUrl","hashIndex","indexOf","fragment","slice","withoutFragment","queryIndex","base","pairs","filter","Boolean","getQueryParamNames","map","removeQueryParams","names","length","kept","includes","join","getFileFromUrl","unsignedUrl","provider","strapi","config","get","ext","path","parse","pop","hash","signFileIfOwned","file","cache","bareUrl","cached","pending","signFileUrls","getService","signedFile","set","signIfOwned","stripSignedUrl","signedUrl","signUrl","mapRichtextUrls","mapUrl","some","hint","matches","flatMap","regex","Array","from","matchAll","sort","a","b","index","urls","async","match","result","cursor","forEach","urlStart","mapBlocksImages","mapImage","isArray","node","image","children","getImageFormats","Object","entries","formats","hasSignatureTraces","isUrlSigned","undefined","format","unsignImage","omit","fromEntries","key","createSignEntityMediaVisitor","multiple","signedFiles","createUnsignEntityMediaVisitor","signEntityMedia","entity","uid","FILE_MODEL_UID","model","getModel","traverseEntity","schema","bind","unsignEntityMedia","data"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"utils.mjs","sources":["../../../../server/src/services/extensions/utils.ts"],"sourcesContent":["import path from 'path';\n\nimport { 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 { Config, 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\n/**\n * One presign per distinct URL, keyed by the bare (unsigned) URL. The promise\n * is stored rather than its result so concurrent occurrences of the same URL\n * (the `async.map` in `mapRichtextUrls`, or several entries of a `findMany`)\n * share a single provider call.\n *\n * A cache must never outlive the document service call or migration run that\n * created it: a signed URL is only as fresh as the request that produced it.\n */\ntype SignCache = Map<string, Promise<string | null>>;\n\nconst createSignCache = (): SignCache => new Map();\n\n/**\n * URL bearing syntaxes found in a richtext (markdown) value. Each pattern\n * captures a prefix (group 1) and the URL (group 2) so the rebuild loop in\n * `mapRichtextUrls` can splice a new URL in place without touching anything\n * else.\n *\n * - markdown image / link: `` or `[text](url)`. The URL is captured\n * up to the first whitespace or closing parenthesis, which also covers the\n * optional title form `[text](url \"title\")`.\n * - raw HTML `src` / `href` attributes, double or single quoted, on the\n * elements the editor lets users write by hand (`<img src=\"...\" width=\"200\">`\n * is the usual way to size an image).\n */\nconst RICHTEXT_URL_REGEXES = [\n /(!?\\[[^\\]]*\\]\\()([^)\\s]+)/g,\n /(<(?:img|a|source|video|audio)\\b[^>]*?\\b(?:src|href)=\")([^\"]+)/gi,\n /(<(?:img|a|source|video|audio)\\b[^>]*?\\b(?:src|href)=')([^']+)/gi,\n];\n\n/** Cheap short circuit for richtext values that cannot contain a URL we would rewrite. */\nconst RICHTEXT_URL_HINTS = ['](', 'src=', 'href='];\n\ntype BlocksNode = {\n type?: string;\n image?: File;\n children?: unknown;\n [key: string]: unknown;\n};\n\ntype ImageFormat = { url?: string; isUrlSigned?: boolean; [key: string]: unknown };\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\nconst stripQueryString = (url: string) => url.split('?')[0];\n\nconst decodeQueryParamName = (pair: string) => {\n const name = pair.split('=')[0];\n\n try {\n return decodeURIComponent(name);\n } catch {\n return name;\n }\n};\n\n/**\n * Split a URL into what precedes the `?`, the raw `key=value` pairs of its\n * query string and the `#fragment`, if any. Plain string work on purpose:\n * `new URL` throws on the relative `/uploads/...` paths a local provider\n * writes, and would re-encode the pairs we want to hand back untouched.\n */\nconst splitUrl = (url: string) => {\n const hashIndex = url.indexOf('#');\n const fragment = hashIndex === -1 ? '' : url.slice(hashIndex);\n const withoutFragment = hashIndex === -1 ? url : url.slice(0, hashIndex);\n\n const queryIndex = withoutFragment.indexOf('?');\n\n if (queryIndex === -1) {\n return { base: withoutFragment, pairs: [] as string[], fragment };\n }\n\n return {\n base: withoutFragment.slice(0, queryIndex),\n pairs: withoutFragment\n .slice(queryIndex + 1)\n .split('&')\n .filter(Boolean),\n fragment,\n };\n};\n\n/** Names of the query parameters of `url`, decoded, in order. */\nconst getQueryParamNames = (url: string): string[] => splitUrl(url).pairs.map(decodeQueryParamName);\n\n/**\n * `url` with every query parameter whose name is in `names` removed. The\n * surviving pairs keep their order and encoding; the `?` is dropped when\n * nothing is left.\n */\nconst removeQueryParams = (url: string, names: string[]): string => {\n const { base, pairs, fragment } = splitUrl(url);\n\n if (pairs.length === 0 || names.length === 0) {\n return url;\n }\n\n const kept = pairs.filter((pair) => !names.includes(decodeQueryParamName(pair)));\n\n return kept.length === 0 ? `${base}${fragment}` : `${base}?${kept.join('&')}${fragment}`;\n};\n\n/**\n * Build the minimal file shape `signFileUrls` needs out of a bare URL, as found\n * in a richtext value.\n *\n * `hash` and `ext` are taken from the last path segment and `path` is left\n * unset, which rebuilds the S3 key for the default layout and for the\n * `rootPath` provider option (the provider prefixes it itself). A URL whose key\n * has extra segments (a file uploaded with the per-file `path` option, or an\n * older key layout) is still recognised as ours by the provider, but it is\n * signed with a key that does not exist and the link returns 403. Media\n * signing has the same limit because `path` is not persisted on the file\n * (`content-types/file.ts`). Not addressed here.\n */\nconst getFileFromUrl = (url: string): File => {\n const unsignedUrl = stripQueryString(url);\n const { provider } = strapi.config.get<Config>('plugin::upload');\n const { name, ext } = path.parse(unsignedUrl.split('/').pop() ?? '');\n\n return { url: unsignedUrl, hash: name, ext, provider } as File;\n};\n\n/**\n * Ask the provider to sign `file` and return the signed URL if the provider\n * owns it, otherwise `null`. Memoised in `cache` by the bare URL.\n *\n * Ownership is decided by whether the provider changed the URL: `signFileUrls`\n * flags every file of the configured provider as signed, but the provider\n * itself returns URLs it does not own untouched (an S3 URL from another\n * bucket, an external site, a local `/uploads` path), so a changed URL is the\n * only reliable signal. `file.url` must already be the bare URL so the\n * comparison is meaningful.\n */\nconst signFileIfOwned = (file: File, cache: SignCache): Promise<string | null> => {\n const bareUrl = file.url ?? '';\n const cached = cache.get(bareUrl);\n\n if (cached) {\n return cached;\n }\n\n const pending = (async () => {\n const { signFileUrls } = getService('file');\n const signedFile = await signFileUrls(file);\n\n return signedFile.url && signedFile.url !== bareUrl ? signedFile.url : null;\n })();\n\n cache.set(bareUrl, pending);\n\n return pending;\n};\n\n/**\n * Signs a richtext URL if the configured provider owns it, otherwise `null`.\n * See `signFileIfOwned` for how ownership is decided. A single predicate means\n * the read path (sign) and the write path (unsign) can never disagree about\n * what they own.\n */\nconst signIfOwned = (url: string, cache: SignCache): Promise<string | null> =>\n signFileIfOwned(getFileFromUrl(url), cache);\n\n/**\n * Returns a richtext URL with its signature removed, if we own it.\n *\n * Only the query parameters the provider's own fresh signature introduces are\n * removed (for S3, the `X-Amz-*` family), every other parameter is kept. That\n * way a provider that signs whatever it is handed cannot make us drop `?v=x`\n * from a link to an external site. A parameter of an old signature that the\n * fresh one no longer carries (an `X-Amz-Security-Token` from credentials no\n * longer in use, say) survives in the row; it is not a signature, and the read\n * path replaces the whole URL with the provider output anyway.\n */\nconst stripSignedUrl = async (url: string, cache: SignCache) => {\n // Nothing to strip, and no reason to ask the provider\n if (!url.includes('?')) {\n return url;\n }\n\n const signedUrl = await signIfOwned(url, cache);\n\n if (signedUrl === null) {\n return url;\n }\n\n return removeQueryParams(url, getQueryParamNames(signedUrl));\n};\n\n/** Returns a freshly signed richtext URL, if we own it. */\nconst signUrl = async (url: string, cache: SignCache) => (await signIfOwned(url, cache)) ?? url;\n\n/**\n * Apply `mapUrl` to every markdown image / link URL and every raw HTML\n * `src` / `href` URL of a richtext value.\n */\nconst mapRichtextUrls = async (value: unknown, mapUrl: (url: string) => Promise<string>) => {\n if (typeof value !== 'string' || !RICHTEXT_URL_HINTS.some((hint) => value.includes(hint))) {\n return value;\n }\n\n // The rebuild loop splices by match index, so matches must be in ascending order\n const matches = RICHTEXT_URL_REGEXES.flatMap((regex) => Array.from(value.matchAll(regex))).sort(\n (a, b) => (a.index as number) - (b.index as number)\n );\n\n if (matches.length === 0) {\n return value;\n }\n\n const urls: string[] = await async.map(matches, (match: RegExpMatchArray) => mapUrl(match[2]));\n\n let result = '';\n let cursor = 0;\n\n matches.forEach((match, index) => {\n const urlStart = (match.index as number) + match[1].length;\n\n // Two patterns matched overlapping text: keep the first, never splice twice\n if (urlStart < cursor) {\n return;\n }\n\n result += value.slice(cursor, urlStart) + urls[index];\n cursor = urlStart + match[2].length;\n });\n\n return result + value.slice(cursor);\n};\n\n/**\n * Apply `mapImage` to every `image` node of a blocks value, recursively.\n * A new array is always returned, the original value is never mutated.\n */\nconst mapBlocksImages = async (\n value: unknown,\n mapImage: (image: File) => Promise<File>\n): Promise<unknown> => {\n if (!Array.isArray(value)) {\n return value;\n }\n\n return async.map(value, async (node: BlocksNode) => {\n if (!node || typeof node !== 'object') {\n return node;\n }\n\n let result = node;\n\n if (node.type === 'image' && node.image) {\n result = { ...result, image: await mapImage(node.image) };\n }\n\n if (Array.isArray(node.children)) {\n result = { ...result, children: await mapBlocksImages(node.children, mapImage) };\n }\n\n return result;\n });\n};\n\nconst getImageFormats = (image: File): [string, ImageFormat][] =>\n Object.entries(image.formats ?? {}) as [string, ImageFormat][];\n\n/** True when the node still carries anything `unsignImage` would remove. */\nconst hasSignatureTraces = (image: File) => {\n if (image.isUrlSigned !== undefined || (image.url ?? '').includes('?')) {\n return true;\n }\n\n return getImageFormats(image).some(\n ([, format]) => format?.isUrlSigned !== undefined || (format?.url ?? '').includes('?')\n );\n};\n\n/**\n * Remove the signature from a blocks `image` node: the signature parameters on\n * the file URL and on every format URL (the same presigner signs them all, so\n * the same parameter names apply), and the `isUrlSigned` flag `signFileUrls`\n * sets on the file and on each format. The node carries a full file object, so\n * the ownership check is made with it rather than with a URL derived stand in.\n */\nconst unsignImage = async (image: File, cache: SignCache = createSignCache()) => {\n // Already bare: nothing to do, and no reason to ask the provider\n if (!hasSignatureTraces(image)) {\n return image;\n }\n\n const bareUrl = stripQueryString(image.url ?? '');\n const signedUrl = await signFileIfOwned({ ...image, url: bareUrl }, cache);\n\n // Not one of ours (different provider, external URL, public provider): the\n // provider hands the URL back unchanged, leave the node as is\n if (signedUrl === null) {\n return image;\n }\n\n const names = getQueryParamNames(signedUrl);\n const { isUrlSigned: _isUrlSigned, ...result }: File = {\n ...image,\n url: removeQueryParams(image.url ?? '', names),\n };\n\n if (image.formats) {\n result.formats = Object.fromEntries(\n getImageFormats(image).map(([key, format]) => {\n if (!format || typeof format !== 'object') {\n return [key, format];\n }\n\n const { isUrlSigned: _formatIsUrlSigned, ...unsignedFormat } = {\n ...format,\n ...(format.url ? { url: removeQueryParams(format.url, names) } : {}),\n };\n\n return [key, unsignedFormat];\n })\n );\n }\n\n return result;\n};\n\n/**\n * Visitor function to sign media URLs\n */\nconst createSignEntityMediaVisitor =\n (cache: SignCache): SignEntityMediaVisitor =>\n async ({ key, value, attribute }, { set }) => {\n const { signFileUrls } = getService('file');\n\n if (!attribute) {\n return;\n }\n\n switch (attribute.type) {\n case 'blocks':\n set(key, await mapBlocksImages(value, signFileUrls));\n return;\n\n case 'richtext':\n set(key, await mapRichtextUrls(value, (url) => signUrl(url, cache)));\n return;\n\n case 'media': {\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 break;\n }\n\n default:\n break;\n }\n };\n\n/**\n * Visitor function to remove the signature from richtext / blocks URLs before\n * they are persisted. Media attributes are left alone: the files table already\n * holds the unsigned URL.\n */\nconst createUnsignEntityMediaVisitor =\n (cache: SignCache): SignEntityMediaVisitor =>\n async ({ key, value, attribute }, { set }) => {\n if (!attribute) {\n return;\n }\n\n if (attribute.type === 'blocks') {\n set(key, await mapBlocksImages(value, (image) => unsignImage(image, cache)));\n return;\n }\n\n if (attribute.type === 'richtext') {\n set(key, await mapRichtextUrls(value, (url) => stripSignedUrl(url, cache)));\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 * @param {Map} cache one per document service call, see `SignCache`\n * @returns\n */\nconst signEntityMedia = async (\n entity: any,\n uid: UID.Schema,\n cache: SignCache = createSignCache()\n) => {\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 createSignEntityMediaVisitor(cache),\n { schema: model, getModel: strapi.getModel.bind(strapi) },\n entity\n );\n};\n\n/**\n * Iterate through the input data of a create / update / clone and replace\n * every signed provider URL found in a richtext or blocks attribute with its\n * unsigned form, so that a short lived signature is never persisted.\n */\nconst unsignEntityMedia = async (\n data: any,\n uid: UID.Schema,\n cache: SignCache = createSignCache()\n) => {\n if (!data || uid === FILE_MODEL_UID) {\n return data;\n }\n\n const model = strapi.getModel(uid);\n\n return traverseEntity(\n // @ts-expect-error - FIXME: fix traverseEntity using wrong types\n createUnsignEntityMediaVisitor(cache),\n { schema: model, getModel: strapi.getModel.bind(strapi) },\n data\n );\n};\n\nexport type { SignCache };\n\nexport {\n createSignCache,\n signEntityMedia,\n unsignEntityMedia,\n mapRichtextUrls,\n mapBlocksImages,\n stripSignedUrl,\n unsignImage,\n};\n"],"names":["createSignCache","Map","RICHTEXT_URL_REGEXES","RICHTEXT_URL_HINTS","isFile","value","attribute","type","stripQueryString","url","split","decodeQueryParamName","pair","name","decodeURIComponent","splitUrl","hashIndex","indexOf","fragment","slice","withoutFragment","queryIndex","base","pairs","filter","Boolean","getQueryParamNames","map","removeQueryParams","names","length","kept","includes","join","getFileFromUrl","unsignedUrl","provider","strapi","config","get","ext","path","parse","pop","hash","signFileIfOwned","file","cache","bareUrl","cached","pending","signFileUrls","getService","signedFile","set","signIfOwned","stripSignedUrl","signedUrl","signUrl","mapRichtextUrls","mapUrl","some","hint","matches","flatMap","regex","Array","from","matchAll","sort","a","b","index","urls","async","match","result","cursor","forEach","urlStart","mapBlocksImages","mapImage","isArray","node","image","children","getImageFormats","Object","entries","formats","hasSignatureTraces","isUrlSigned","undefined","format","unsignImage","_isUrlSigned","fromEntries","key","_formatIsUrlSigned","unsignedFormat","createSignEntityMediaVisitor","multiple","signedFiles","createUnsignEntityMediaVisitor","signEntityMedia","entity","uid","FILE_MODEL_UID","model","getModel","traverseEntity","schema","bind","unsignEntityMedia","data"],"mappings":"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"}
|
|
@@ -7,7 +7,7 @@ var node_fs = require('node:fs');
|
|
|
7
7
|
var node_stream = require('node:stream');
|
|
8
8
|
var promises = require('node:stream/promises');
|
|
9
9
|
var fse = require('fs-extra');
|
|
10
|
-
var
|
|
10
|
+
var _ = require('lodash');
|
|
11
11
|
var utils = require('@strapi/utils');
|
|
12
12
|
var constants = require('../constants.js');
|
|
13
13
|
var index = require('../utils/index.js');
|
|
@@ -238,7 +238,7 @@ const signFileUrls = async (file)=>{
|
|
|
238
238
|
file.url = signedUrl.url;
|
|
239
239
|
file.isUrlSigned = true;
|
|
240
240
|
};
|
|
241
|
-
const signedFile =
|
|
241
|
+
const signedFile = _.cloneDeep(file);
|
|
242
242
|
// Sign each file format
|
|
243
243
|
await signUrl(signedFile);
|
|
244
244
|
if (file.formats) {
|
|
@@ -1 +1 @@
|
|
|
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 { createWriteStream } from 'node:fs';\nimport { Readable, Transform } from 'node:stream';\nimport { pipeline } from 'node:stream/promises';\nimport type { ReadableStream as WebReadableStream } from 'node:stream/web';\nimport fse from 'fs-extra';\nimport { cloneDeep } from 'lodash/fp';\nimport { async, errors, file as fileUtils } 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;\nconst { bytesToHumanReadable } = fileUtils;\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 * Progress of a URL fetch.\n *\n * `bytesWritten` is cumulative and non-decreasing (the first report is always 0).\n * `totalBytes` is the parsed Content-Length when the header is present and valid, `null` otherwise.\n */\ninterface UrlFetchProgress {\n bytesWritten: number;\n totalBytes: number | null;\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 streams it to a temporary file\n * Returns an InputFile-compatible object for use with the upload pipeline\n *\n * The response body is never buffered in memory: it is piped straight to disk, so heap usage\n * stays bounded regardless of the remote file size. `sizeLimit` is enforced on the bytes actually\n * received, not only on the Content-Length header (which may be absent or wrong).\n *\n * `onProgress` (optional) reports raw progress:\n * - It fires **once immediately** with `{ bytesWritten: 0, totalBytes }` after the response headers\n * are validated, before any bytes are read, so callers can size a progress bar up front.\n * - It then fires **once per streamed chunk** with cumulative `bytesWritten`.\n * - It is deliberately **not throttled**. Throttling is the caller's responsibility: a consumer\n * that turns these into SSE frames should coalesce them to its own frame budget (a 512MB file at\n * 64KB chunks yields ~8000 calls). Throttling here would bake one consumer's frame rate into a\n * shared service, and a pre-throttled counter cannot be un-throttled by a caller that needs\n * every byte.\n * - If it throws, the error is logged and swallowed: reporting is observational, so a buggy\n * consumer never fails the import.\n */\nconst fetchUrlToInputFile = async (\n url: string,\n tmpWorkingDirectory: string,\n sizeLimit?: number,\n onProgress?: (progress: UrlFetchProgress) => void\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 // use strapi.fetch so we can intercept requests and support proxy settings\n const doFetch = typeof strapi?.fetch === 'function' ? strapi.fetch : fetch;\n let response: Response;\n try {\n response = await doFetch(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 const tooLargeError = () =>\n new ApplicationError(\n `File too large: maximum allowed size is ${bytesToHumanReadable(sizeLimit ?? 0)}`\n );\n\n const parsedContentLength = parseInt(response.headers.get('content-length') ?? '', 10);\n const totalBytes = Number.isFinite(parsedContentLength) ? parsedContentLength : null;\n\n // Check Content-Length header for early rejection of large files\n if (sizeLimit && totalBytes !== null && totalBytes > sizeLimit) {\n throw tooLargeError();\n }\n\n // Progress reporting is a side-channel for UI: a consumer that throws should not fail the import\n // it is merely observing. Node turns a synchronous throw inside `_transform` into a stream error,\n // so an unguarded callback would reject the pipeline and abort a download that was fine.\n const reportProgress = (progress: UrlFetchProgress) => {\n if (!onProgress) return;\n\n try {\n onProgress(progress);\n } catch (error) {\n strapi?.log?.warn(\n `URL fetch progress callback threw, ignoring: ${\n error instanceof Error ? error.message : error\n }`\n );\n }\n };\n\n // Announce the total before any bytes are dispatched. Consumers that size a progress bar from\n // `totalBytes` need it up front: a consumer clamping bytes to a not-yet-known size would clamp\n // every early chunk to zero. Fires exactly once, even when `totalBytes` is null or the body is\n // empty, so the first call is always a size announcement.\n reportProgress({ bytesWritten: 0, totalBytes });\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 const tmpFilePath = path.join(tmpWorkingDirectory, filename);\n\n // Stream the response body to the temp file, counting bytes as they go so the size limit is\n // enforced even when Content-Length is missing or lying.\n let bytesWritten = 0;\n\n try {\n if (response.body === null) {\n // An empty body is valid per the fetch spec — write an empty file\n await fse.writeFile(tmpFilePath, '');\n } else {\n const counter = new Transform({\n transform(chunk, _encoding, callback) {\n bytesWritten += chunk.length;\n\n if (sizeLimit && bytesWritten > sizeLimit) {\n callback(tooLargeError());\n return;\n }\n\n // Raw, one call per chunk, no throttling — see `onProgress` on the signature above\n reportProgress({ bytesWritten, totalBytes });\n callback(null, chunk);\n },\n });\n\n await pipeline(\n Readable.fromWeb(response.body as WebReadableStream),\n counter,\n createWriteStream(tmpFilePath)\n );\n }\n } catch (error) {\n // Remove the partially written file — the caller only cleans up the temp directory once all\n // URLs have been processed. Cleanup failures must not replace the error the caller cares about\n // (\"File too large\", \"socket hang up\") with an incidental filesystem one, so swallow and log:\n // the request-level temp-directory cleanup is still the backstop for the leftover file.\n try {\n await fse.remove(tmpFilePath);\n } catch (cleanupError) {\n strapi?.log?.warn(\n `Could not remove partial temp file ${tmpFilePath}: ${\n cleanupError instanceof Error ? cleanupError.message : cleanupError\n }`\n );\n }\n\n throw error;\n }\n\n // Derive the size from the file on disk rather than from a buffer we never hold\n const { size } = await fse.stat(tmpFilePath);\n\n // Create file object compatible with upload pipeline\n const fetchedFile: UrlFetchedFile = {\n filepath: tmpFilePath,\n originalFilename: filename,\n mimetype: contentType,\n size,\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, UrlFetchProgress };\nexport default { getFolderPath, deleteByIds, signFileUrls, fetchUrlToInputFile };\n"],"names":["ApplicationError","errors","bytesToHumanReadable","fileUtils","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","onProgress","parsedUrl","includes","protocol","address","family","dns","lookup","hostname","type","check","error","doFetch","strapi","fetch","response","signal","AbortSignal","timeout","Error","name","ok","status","statusText","tooLargeError","parsedContentLength","parseInt","headers","get","totalBytes","Number","isFinite","reportProgress","progress","log","warn","message","bytesWritten","contentType","tmpFilePath","join","body","fse","writeFile","counter","Transform","transform","chunk","_encoding","callback","pipeline","Readable","fromWeb","createWriteStream","remove","cleanupError","size","stat","fetchedFile","filepath","originalFilename","mimetype","file","getFolderPath","folderId","parentFolder","db","query","FOLDER_MODEL_UID","findOne","where","id","deleteByIds","ids","filesToDelete","FILE_MODEL_UID","findMany","$in","Promise","all","map","getService","signFileUrls","provider","plugins","upload","providerConfig","config","isPrivate","isUrlSigned","signUrl","signedUrl","getSignedUrl","signedFile","cloneDeep","formats","async","Object","values"],"mappings":"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"}
|
|
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 { createWriteStream } from 'node:fs';\nimport { Readable, Transform } from 'node:stream';\nimport { pipeline } from 'node:stream/promises';\nimport type { ReadableStream as WebReadableStream } from 'node:stream/web';\nimport fse from 'fs-extra';\nimport { cloneDeep } from 'lodash';\nimport { async, errors, file as fileUtils } 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;\nconst { bytesToHumanReadable } = fileUtils;\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 * Progress of a URL fetch.\n *\n * `bytesWritten` is cumulative and non-decreasing (the first report is always 0).\n * `totalBytes` is the parsed Content-Length when the header is present and valid, `null` otherwise.\n */\ninterface UrlFetchProgress {\n bytesWritten: number;\n totalBytes: number | null;\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 streams it to a temporary file\n * Returns an InputFile-compatible object for use with the upload pipeline\n *\n * The response body is never buffered in memory: it is piped straight to disk, so heap usage\n * stays bounded regardless of the remote file size. `sizeLimit` is enforced on the bytes actually\n * received, not only on the Content-Length header (which may be absent or wrong).\n *\n * `onProgress` (optional) reports raw progress:\n * - It fires **once immediately** with `{ bytesWritten: 0, totalBytes }` after the response headers\n * are validated, before any bytes are read, so callers can size a progress bar up front.\n * - It then fires **once per streamed chunk** with cumulative `bytesWritten`.\n * - It is deliberately **not throttled**. Throttling is the caller's responsibility: a consumer\n * that turns these into SSE frames should coalesce them to its own frame budget (a 512MB file at\n * 64KB chunks yields ~8000 calls). Throttling here would bake one consumer's frame rate into a\n * shared service, and a pre-throttled counter cannot be un-throttled by a caller that needs\n * every byte.\n * - If it throws, the error is logged and swallowed: reporting is observational, so a buggy\n * consumer never fails the import.\n */\nconst fetchUrlToInputFile = async (\n url: string,\n tmpWorkingDirectory: string,\n sizeLimit?: number,\n onProgress?: (progress: UrlFetchProgress) => void\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 // use strapi.fetch so we can intercept requests and support proxy settings\n const doFetch = typeof strapi?.fetch === 'function' ? strapi.fetch : fetch;\n let response: Response;\n try {\n response = await doFetch(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 const tooLargeError = () =>\n new ApplicationError(\n `File too large: maximum allowed size is ${bytesToHumanReadable(sizeLimit ?? 0)}`\n );\n\n const parsedContentLength = parseInt(response.headers.get('content-length') ?? '', 10);\n const totalBytes = Number.isFinite(parsedContentLength) ? parsedContentLength : null;\n\n // Check Content-Length header for early rejection of large files\n if (sizeLimit && totalBytes !== null && totalBytes > sizeLimit) {\n throw tooLargeError();\n }\n\n // Progress reporting is a side-channel for UI: a consumer that throws should not fail the import\n // it is merely observing. Node turns a synchronous throw inside `_transform` into a stream error,\n // so an unguarded callback would reject the pipeline and abort a download that was fine.\n const reportProgress = (progress: UrlFetchProgress) => {\n if (!onProgress) return;\n\n try {\n onProgress(progress);\n } catch (error) {\n strapi?.log?.warn(\n `URL fetch progress callback threw, ignoring: ${\n error instanceof Error ? error.message : error\n }`\n );\n }\n };\n\n // Announce the total before any bytes are dispatched. Consumers that size a progress bar from\n // `totalBytes` need it up front: a consumer clamping bytes to a not-yet-known size would clamp\n // every early chunk to zero. Fires exactly once, even when `totalBytes` is null or the body is\n // empty, so the first call is always a size announcement.\n reportProgress({ bytesWritten: 0, totalBytes });\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 const tmpFilePath = path.join(tmpWorkingDirectory, filename);\n\n // Stream the response body to the temp file, counting bytes as they go so the size limit is\n // enforced even when Content-Length is missing or lying.\n let bytesWritten = 0;\n\n try {\n if (response.body === null) {\n // An empty body is valid per the fetch spec — write an empty file\n await fse.writeFile(tmpFilePath, '');\n } else {\n const counter = new Transform({\n transform(chunk, _encoding, callback) {\n bytesWritten += chunk.length;\n\n if (sizeLimit && bytesWritten > sizeLimit) {\n callback(tooLargeError());\n return;\n }\n\n // Raw, one call per chunk, no throttling — see `onProgress` on the signature above\n reportProgress({ bytesWritten, totalBytes });\n callback(null, chunk);\n },\n });\n\n await pipeline(\n Readable.fromWeb(response.body as WebReadableStream),\n counter,\n createWriteStream(tmpFilePath)\n );\n }\n } catch (error) {\n // Remove the partially written file — the caller only cleans up the temp directory once all\n // URLs have been processed. Cleanup failures must not replace the error the caller cares about\n // (\"File too large\", \"socket hang up\") with an incidental filesystem one, so swallow and log:\n // the request-level temp-directory cleanup is still the backstop for the leftover file.\n try {\n await fse.remove(tmpFilePath);\n } catch (cleanupError) {\n strapi?.log?.warn(\n `Could not remove partial temp file ${tmpFilePath}: ${\n cleanupError instanceof Error ? cleanupError.message : cleanupError\n }`\n );\n }\n\n throw error;\n }\n\n // Derive the size from the file on disk rather than from a buffer we never hold\n const { size } = await fse.stat(tmpFilePath);\n\n // Create file object compatible with upload pipeline\n const fetchedFile: UrlFetchedFile = {\n filepath: tmpFilePath,\n originalFilename: filename,\n mimetype: contentType,\n size,\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, UrlFetchProgress };\nexport default { getFolderPath, deleteByIds, signFileUrls, fetchUrlToInputFile };\n"],"names":["ApplicationError","errors","bytesToHumanReadable","fileUtils","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","onProgress","parsedUrl","includes","protocol","address","family","dns","lookup","hostname","type","check","error","doFetch","strapi","fetch","response","signal","AbortSignal","timeout","Error","name","ok","status","statusText","tooLargeError","parsedContentLength","parseInt","headers","get","totalBytes","Number","isFinite","reportProgress","progress","log","warn","message","bytesWritten","contentType","tmpFilePath","join","body","fse","writeFile","counter","Transform","transform","chunk","_encoding","callback","pipeline","Readable","fromWeb","createWriteStream","remove","cleanupError","size","stat","fetchedFile","filepath","originalFilename","mimetype","file","getFolderPath","folderId","parentFolder","db","query","FOLDER_MODEL_UID","findOne","where","id","deleteByIds","ids","filesToDelete","FILE_MODEL_UID","findMany","$in","Promise","all","map","getService","signFileUrls","provider","plugins","upload","providerConfig","config","isPrivate","isUrlSigned","signUrl","signedUrl","getSignedUrl","signedFile","cloneDeep","formats","async","Object","values"],"mappings":"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"}
|
|
@@ -5,7 +5,7 @@ import { createWriteStream } from 'node:fs';
|
|
|
5
5
|
import { Transform, Readable } from 'node:stream';
|
|
6
6
|
import { pipeline } from 'node:stream/promises';
|
|
7
7
|
import fse from 'fs-extra';
|
|
8
|
-
import { cloneDeep } from 'lodash
|
|
8
|
+
import { cloneDeep } from 'lodash';
|
|
9
9
|
import { errors, file as file$1, async } from '@strapi/utils';
|
|
10
10
|
import { FILE_MODEL_UID, FOLDER_MODEL_UID } from '../constants.mjs';
|
|
11
11
|
import { getService } from '../utils/index.mjs';
|
|
@@ -1 +1 @@
|
|
|
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 { createWriteStream } from 'node:fs';\nimport { Readable, Transform } from 'node:stream';\nimport { pipeline } from 'node:stream/promises';\nimport type { ReadableStream as WebReadableStream } from 'node:stream/web';\nimport fse from 'fs-extra';\nimport { cloneDeep } from 'lodash/fp';\nimport { async, errors, file as fileUtils } 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;\nconst { bytesToHumanReadable } = fileUtils;\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 * Progress of a URL fetch.\n *\n * `bytesWritten` is cumulative and non-decreasing (the first report is always 0).\n * `totalBytes` is the parsed Content-Length when the header is present and valid, `null` otherwise.\n */\ninterface UrlFetchProgress {\n bytesWritten: number;\n totalBytes: number | null;\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 streams it to a temporary file\n * Returns an InputFile-compatible object for use with the upload pipeline\n *\n * The response body is never buffered in memory: it is piped straight to disk, so heap usage\n * stays bounded regardless of the remote file size. `sizeLimit` is enforced on the bytes actually\n * received, not only on the Content-Length header (which may be absent or wrong).\n *\n * `onProgress` (optional) reports raw progress:\n * - It fires **once immediately** with `{ bytesWritten: 0, totalBytes }` after the response headers\n * are validated, before any bytes are read, so callers can size a progress bar up front.\n * - It then fires **once per streamed chunk** with cumulative `bytesWritten`.\n * - It is deliberately **not throttled**. Throttling is the caller's responsibility: a consumer\n * that turns these into SSE frames should coalesce them to its own frame budget (a 512MB file at\n * 64KB chunks yields ~8000 calls). Throttling here would bake one consumer's frame rate into a\n * shared service, and a pre-throttled counter cannot be un-throttled by a caller that needs\n * every byte.\n * - If it throws, the error is logged and swallowed: reporting is observational, so a buggy\n * consumer never fails the import.\n */\nconst fetchUrlToInputFile = async (\n url: string,\n tmpWorkingDirectory: string,\n sizeLimit?: number,\n onProgress?: (progress: UrlFetchProgress) => void\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 // use strapi.fetch so we can intercept requests and support proxy settings\n const doFetch = typeof strapi?.fetch === 'function' ? strapi.fetch : fetch;\n let response: Response;\n try {\n response = await doFetch(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 const tooLargeError = () =>\n new ApplicationError(\n `File too large: maximum allowed size is ${bytesToHumanReadable(sizeLimit ?? 0)}`\n );\n\n const parsedContentLength = parseInt(response.headers.get('content-length') ?? '', 10);\n const totalBytes = Number.isFinite(parsedContentLength) ? parsedContentLength : null;\n\n // Check Content-Length header for early rejection of large files\n if (sizeLimit && totalBytes !== null && totalBytes > sizeLimit) {\n throw tooLargeError();\n }\n\n // Progress reporting is a side-channel for UI: a consumer that throws should not fail the import\n // it is merely observing. Node turns a synchronous throw inside `_transform` into a stream error,\n // so an unguarded callback would reject the pipeline and abort a download that was fine.\n const reportProgress = (progress: UrlFetchProgress) => {\n if (!onProgress) return;\n\n try {\n onProgress(progress);\n } catch (error) {\n strapi?.log?.warn(\n `URL fetch progress callback threw, ignoring: ${\n error instanceof Error ? error.message : error\n }`\n );\n }\n };\n\n // Announce the total before any bytes are dispatched. Consumers that size a progress bar from\n // `totalBytes` need it up front: a consumer clamping bytes to a not-yet-known size would clamp\n // every early chunk to zero. Fires exactly once, even when `totalBytes` is null or the body is\n // empty, so the first call is always a size announcement.\n reportProgress({ bytesWritten: 0, totalBytes });\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 const tmpFilePath = path.join(tmpWorkingDirectory, filename);\n\n // Stream the response body to the temp file, counting bytes as they go so the size limit is\n // enforced even when Content-Length is missing or lying.\n let bytesWritten = 0;\n\n try {\n if (response.body === null) {\n // An empty body is valid per the fetch spec — write an empty file\n await fse.writeFile(tmpFilePath, '');\n } else {\n const counter = new Transform({\n transform(chunk, _encoding, callback) {\n bytesWritten += chunk.length;\n\n if (sizeLimit && bytesWritten > sizeLimit) {\n callback(tooLargeError());\n return;\n }\n\n // Raw, one call per chunk, no throttling — see `onProgress` on the signature above\n reportProgress({ bytesWritten, totalBytes });\n callback(null, chunk);\n },\n });\n\n await pipeline(\n Readable.fromWeb(response.body as WebReadableStream),\n counter,\n createWriteStream(tmpFilePath)\n );\n }\n } catch (error) {\n // Remove the partially written file — the caller only cleans up the temp directory once all\n // URLs have been processed. Cleanup failures must not replace the error the caller cares about\n // (\"File too large\", \"socket hang up\") with an incidental filesystem one, so swallow and log:\n // the request-level temp-directory cleanup is still the backstop for the leftover file.\n try {\n await fse.remove(tmpFilePath);\n } catch (cleanupError) {\n strapi?.log?.warn(\n `Could not remove partial temp file ${tmpFilePath}: ${\n cleanupError instanceof Error ? cleanupError.message : cleanupError\n }`\n );\n }\n\n throw error;\n }\n\n // Derive the size from the file on disk rather than from a buffer we never hold\n const { size } = await fse.stat(tmpFilePath);\n\n // Create file object compatible with upload pipeline\n const fetchedFile: UrlFetchedFile = {\n filepath: tmpFilePath,\n originalFilename: filename,\n mimetype: contentType,\n size,\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, UrlFetchProgress };\nexport default { getFolderPath, deleteByIds, signFileUrls, fetchUrlToInputFile };\n"],"names":["ApplicationError","errors","bytesToHumanReadable","fileUtils","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","onProgress","parsedUrl","includes","protocol","address","family","dns","lookup","hostname","type","check","error","doFetch","strapi","fetch","response","signal","AbortSignal","timeout","Error","name","ok","status","statusText","tooLargeError","parsedContentLength","parseInt","headers","get","totalBytes","Number","isFinite","reportProgress","progress","log","warn","message","bytesWritten","contentType","tmpFilePath","join","body","fse","writeFile","counter","Transform","transform","chunk","_encoding","callback","pipeline","Readable","fromWeb","createWriteStream","remove","cleanupError","size","stat","fetchedFile","filepath","originalFilename","mimetype","file","getFolderPath","folderId","parentFolder","db","query","FOLDER_MODEL_UID","findOne","where","id","deleteByIds","ids","filesToDelete","FILE_MODEL_UID","findMany","$in","Promise","all","map","getService","signFileUrls","provider","plugins","upload","providerConfig","config","isPrivate","isUrlSigned","signUrl","signedUrl","getSignedUrl","signedFile","cloneDeep","formats","async","Object","values"],"mappings":"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"}
|
|
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 { createWriteStream } from 'node:fs';\nimport { Readable, Transform } from 'node:stream';\nimport { pipeline } from 'node:stream/promises';\nimport type { ReadableStream as WebReadableStream } from 'node:stream/web';\nimport fse from 'fs-extra';\nimport { cloneDeep } from 'lodash';\nimport { async, errors, file as fileUtils } 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;\nconst { bytesToHumanReadable } = fileUtils;\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 * Progress of a URL fetch.\n *\n * `bytesWritten` is cumulative and non-decreasing (the first report is always 0).\n * `totalBytes` is the parsed Content-Length when the header is present and valid, `null` otherwise.\n */\ninterface UrlFetchProgress {\n bytesWritten: number;\n totalBytes: number | null;\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 streams it to a temporary file\n * Returns an InputFile-compatible object for use with the upload pipeline\n *\n * The response body is never buffered in memory: it is piped straight to disk, so heap usage\n * stays bounded regardless of the remote file size. `sizeLimit` is enforced on the bytes actually\n * received, not only on the Content-Length header (which may be absent or wrong).\n *\n * `onProgress` (optional) reports raw progress:\n * - It fires **once immediately** with `{ bytesWritten: 0, totalBytes }` after the response headers\n * are validated, before any bytes are read, so callers can size a progress bar up front.\n * - It then fires **once per streamed chunk** with cumulative `bytesWritten`.\n * - It is deliberately **not throttled**. Throttling is the caller's responsibility: a consumer\n * that turns these into SSE frames should coalesce them to its own frame budget (a 512MB file at\n * 64KB chunks yields ~8000 calls). Throttling here would bake one consumer's frame rate into a\n * shared service, and a pre-throttled counter cannot be un-throttled by a caller that needs\n * every byte.\n * - If it throws, the error is logged and swallowed: reporting is observational, so a buggy\n * consumer never fails the import.\n */\nconst fetchUrlToInputFile = async (\n url: string,\n tmpWorkingDirectory: string,\n sizeLimit?: number,\n onProgress?: (progress: UrlFetchProgress) => void\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 // use strapi.fetch so we can intercept requests and support proxy settings\n const doFetch = typeof strapi?.fetch === 'function' ? strapi.fetch : fetch;\n let response: Response;\n try {\n response = await doFetch(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 const tooLargeError = () =>\n new ApplicationError(\n `File too large: maximum allowed size is ${bytesToHumanReadable(sizeLimit ?? 0)}`\n );\n\n const parsedContentLength = parseInt(response.headers.get('content-length') ?? '', 10);\n const totalBytes = Number.isFinite(parsedContentLength) ? parsedContentLength : null;\n\n // Check Content-Length header for early rejection of large files\n if (sizeLimit && totalBytes !== null && totalBytes > sizeLimit) {\n throw tooLargeError();\n }\n\n // Progress reporting is a side-channel for UI: a consumer that throws should not fail the import\n // it is merely observing. Node turns a synchronous throw inside `_transform` into a stream error,\n // so an unguarded callback would reject the pipeline and abort a download that was fine.\n const reportProgress = (progress: UrlFetchProgress) => {\n if (!onProgress) return;\n\n try {\n onProgress(progress);\n } catch (error) {\n strapi?.log?.warn(\n `URL fetch progress callback threw, ignoring: ${\n error instanceof Error ? error.message : error\n }`\n );\n }\n };\n\n // Announce the total before any bytes are dispatched. Consumers that size a progress bar from\n // `totalBytes` need it up front: a consumer clamping bytes to a not-yet-known size would clamp\n // every early chunk to zero. Fires exactly once, even when `totalBytes` is null or the body is\n // empty, so the first call is always a size announcement.\n reportProgress({ bytesWritten: 0, totalBytes });\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 const tmpFilePath = path.join(tmpWorkingDirectory, filename);\n\n // Stream the response body to the temp file, counting bytes as they go so the size limit is\n // enforced even when Content-Length is missing or lying.\n let bytesWritten = 0;\n\n try {\n if (response.body === null) {\n // An empty body is valid per the fetch spec — write an empty file\n await fse.writeFile(tmpFilePath, '');\n } else {\n const counter = new Transform({\n transform(chunk, _encoding, callback) {\n bytesWritten += chunk.length;\n\n if (sizeLimit && bytesWritten > sizeLimit) {\n callback(tooLargeError());\n return;\n }\n\n // Raw, one call per chunk, no throttling — see `onProgress` on the signature above\n reportProgress({ bytesWritten, totalBytes });\n callback(null, chunk);\n },\n });\n\n await pipeline(\n Readable.fromWeb(response.body as WebReadableStream),\n counter,\n createWriteStream(tmpFilePath)\n );\n }\n } catch (error) {\n // Remove the partially written file — the caller only cleans up the temp directory once all\n // URLs have been processed. Cleanup failures must not replace the error the caller cares about\n // (\"File too large\", \"socket hang up\") with an incidental filesystem one, so swallow and log:\n // the request-level temp-directory cleanup is still the backstop for the leftover file.\n try {\n await fse.remove(tmpFilePath);\n } catch (cleanupError) {\n strapi?.log?.warn(\n `Could not remove partial temp file ${tmpFilePath}: ${\n cleanupError instanceof Error ? cleanupError.message : cleanupError\n }`\n );\n }\n\n throw error;\n }\n\n // Derive the size from the file on disk rather than from a buffer we never hold\n const { size } = await fse.stat(tmpFilePath);\n\n // Create file object compatible with upload pipeline\n const fetchedFile: UrlFetchedFile = {\n filepath: tmpFilePath,\n originalFilename: filename,\n mimetype: contentType,\n size,\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, UrlFetchProgress };\nexport default { getFolderPath, deleteByIds, signFileUrls, fetchUrlToInputFile };\n"],"names":["ApplicationError","errors","bytesToHumanReadable","fileUtils","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","onProgress","parsedUrl","includes","protocol","address","family","dns","lookup","hostname","type","check","error","doFetch","strapi","fetch","response","signal","AbortSignal","timeout","Error","name","ok","status","statusText","tooLargeError","parsedContentLength","parseInt","headers","get","totalBytes","Number","isFinite","reportProgress","progress","log","warn","message","bytesWritten","contentType","tmpFilePath","join","body","fse","writeFile","counter","Transform","transform","chunk","_encoding","callback","pipeline","Readable","fromWeb","createWriteStream","remove","cleanupError","size","stat","fetchedFile","filepath","originalFilename","mimetype","file","getFolderPath","folderId","parentFolder","db","query","FOLDER_MODEL_UID","findOne","where","id","deleteByIds","ids","filesToDelete","FILE_MODEL_UID","findMany","$in","Promise","all","map","getService","signFileUrls","provider","plugins","upload","providerConfig","config","isPrivate","isUrlSigned","signUrl","signedUrl","getSignedUrl","signedFile","cloneDeep","formats","async","Object","values"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var _ = require('lodash');
|
|
4
4
|
var utils = require('@strapi/utils');
|
|
5
5
|
var constants = require('../constants.js');
|
|
6
6
|
var index = require('../utils/index.js');
|
|
@@ -43,7 +43,7 @@ const create = async (folderData, opts)=>{
|
|
|
43
43
|
* Recursively delete folders and included files
|
|
44
44
|
* @param ids ids of the folders to delete
|
|
45
45
|
* @returns {Promise<Object[]>}
|
|
46
|
-
*/ const deleteByIds = async (ids = [])=>{
|
|
46
|
+
*/ const deleteByIds = async (ids = [], { validateFiles } = {})=>{
|
|
47
47
|
const folders = await strapi.db.query(constants.FOLDER_MODEL_UID).findMany({
|
|
48
48
|
where: {
|
|
49
49
|
id: {
|
|
@@ -58,7 +58,7 @@ const create = async (folderData, opts)=>{
|
|
|
58
58
|
totalFileNumber: 0
|
|
59
59
|
};
|
|
60
60
|
}
|
|
61
|
-
const pathsToDelete =
|
|
61
|
+
const pathsToDelete = _.map(folders, 'path');
|
|
62
62
|
// delete files
|
|
63
63
|
const filesToDelete = await strapi.db.query(constants.FILE_MODEL_UID).findMany({
|
|
64
64
|
where: {
|
|
@@ -76,6 +76,9 @@ const create = async (folderData, opts)=>{
|
|
|
76
76
|
])
|
|
77
77
|
}
|
|
78
78
|
});
|
|
79
|
+
// A folder deletion cascades to every file in its subtree. Let callers enforce
|
|
80
|
+
// request-scoped permissions against the complete set before the first destructive action.
|
|
81
|
+
await validateFiles?.(filesToDelete);
|
|
79
82
|
await Promise.all(filesToDelete.map((file)=>index.getService('upload').remove(file)));
|
|
80
83
|
// delete folders and subfolders
|
|
81
84
|
const { count: totalFolderNumber } = await strapi.db.query(constants.FOLDER_MODEL_UID).deleteMany({
|
|
@@ -107,7 +110,7 @@ const create = async (folderData, opts)=>{
|
|
|
107
110
|
* Update name and location of a folder and its belonging folders and files
|
|
108
111
|
*/ const update = async (id, { name, parent }, { user })=>{
|
|
109
112
|
// only name is updated
|
|
110
|
-
if (
|
|
113
|
+
if (parent === undefined) {
|
|
111
114
|
const existingFolder = await strapi.db.query(constants.FOLDER_MODEL_UID).findOne({
|
|
112
115
|
where: {
|
|
113
116
|
id
|
|
@@ -123,7 +126,7 @@ const create = async (folderData, opts)=>{
|
|
|
123
126
|
name,
|
|
124
127
|
parent
|
|
125
128
|
});
|
|
126
|
-
if (
|
|
129
|
+
if (parent === undefined) {
|
|
127
130
|
const folder = await strapi.db.query(constants.FOLDER_MODEL_UID).update({
|
|
128
131
|
where: {
|
|
129
132
|
id
|
|
@@ -257,7 +260,7 @@ const create = async (folderData, opts)=>{
|
|
|
257
260
|
};
|
|
258
261
|
}
|
|
259
262
|
folderMap[parentId].children.push(folderMap[f.id]);
|
|
260
|
-
folderMap[parentId].children =
|
|
263
|
+
folderMap[parentId].children = _.sortBy(folderMap[parentId].children, 'name');
|
|
261
264
|
delete folderMap[f.id].parent;
|
|
262
265
|
});
|
|
263
266
|
return folderMap.null.children;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"folder.js","sources":["../../../server/src/services/folder.ts"],"sourcesContent":["import { sortBy, map, isUndefined } from 'lodash/fp';\nimport { strings, setCreatorFields } from '@strapi/utils';\nimport { FOLDER_MODEL_UID, FILE_MODEL_UID } from '../constants';\nimport { getService } from '../utils';\n\nimport type { File, Folder } from '../types';\n\ntype FolderMap = {\n [key: string]: Partial<Folder> & {\n children: FolderNode[];\n };\n};\n\ntype FolderNode = Partial<Folder> & {\n children: FolderNode[];\n};\n\nconst setPathIdAndPath = async (folder: Pick<Folder, 'parent'>) => {\n const { max } = await strapi.db\n .queryBuilder(FOLDER_MODEL_UID)\n .max('pathId')\n .first()\n .execute<{ max: number }>();\n\n const pathId = max + 1;\n let parentPath = '/';\n if (folder.parent) {\n const parentFolder = await strapi.db\n .query(FOLDER_MODEL_UID)\n .findOne({ where: { id: folder.parent } });\n\n parentPath = parentFolder.path;\n }\n\n return Object.assign(folder, {\n pathId,\n path: strings.joinBy('/', parentPath, `${pathId}`),\n });\n};\n\nconst create = async (\n folderData: Pick<Folder, 'name' | 'parent'>,\n opts?: { user: { id: string | number } }\n) => {\n const folderService = getService('folder');\n\n const { user } = opts || {};\n\n let enrichedFolder = await folderService.setPathIdAndPath(folderData);\n if (user) {\n enrichedFolder = await setCreatorFields({ user })(enrichedFolder);\n }\n\n const folder = await strapi.db.query(FOLDER_MODEL_UID).create({ data: enrichedFolder });\n\n strapi.eventHub.emit('media-folder.create', { folder });\n\n return folder;\n};\n\n/**\n * Recursively delete folders and included files\n * @param ids ids of the folders to delete\n * @returns {Promise<Object[]>}\n */\nconst deleteByIds = async (ids: number[] = []) => {\n const folders = await strapi.db.query(FOLDER_MODEL_UID).findMany({ where: { id: { $in: ids } } });\n if (folders.length === 0) {\n return {\n folders: [],\n totalFolderNumber: 0,\n totalFileNumber: 0,\n };\n }\n\n const pathsToDelete = map('path', folders);\n\n // delete files\n const filesToDelete = await strapi.db.query(FILE_MODEL_UID).findMany({\n where: {\n $or: pathsToDelete.flatMap((path) => [\n { folderPath: { $eq: path } },\n { folderPath: { $startsWith: `${path}/` } },\n ]),\n },\n });\n\n await Promise.all(filesToDelete.map((file: File) => getService('upload').remove(file)));\n\n // delete folders and subfolders\n const { count: totalFolderNumber } = await strapi.db.query(FOLDER_MODEL_UID).deleteMany({\n where: {\n $or: pathsToDelete.flatMap((path) => [\n { path: { $eq: path } },\n { path: { $startsWith: `${path}/` } },\n ]),\n },\n });\n\n strapi.eventHub.emit('media-folder.delete', { folders });\n\n return {\n folders,\n totalFolderNumber,\n totalFileNumber: filesToDelete.length,\n };\n};\n\n/**\n * Update name and location of a folder and its belonging folders and files\n */\nconst update = async (\n id: number,\n {\n name,\n parent,\n }: {\n name: string;\n /**\n * Omit to rename in place: the name-only branch skips the transaction that rewrites\n * descendant paths. Pass a number to re-parent, or null to move to the root.\n */\n parent?: number | null;\n },\n { user }: { user: { id: string | number } }\n) => {\n // only name is updated\n if (isUndefined(parent)) {\n const existingFolder = await strapi.db.query(FOLDER_MODEL_UID).findOne({ where: { id } });\n\n if (!existingFolder) {\n return undefined;\n }\n\n const newFolder = setCreatorFields({ user, isEdition: true })({ name, parent });\n\n if (isUndefined(parent)) {\n const folder = await strapi.db\n .query(FOLDER_MODEL_UID)\n .update({ where: { id }, data: newFolder });\n\n return folder;\n }\n // location is updated => using transaction\n } else {\n const trx = await strapi.db.transaction();\n try {\n // fetch existing folder\n const existingFolder = await strapi.db\n .queryBuilder(FOLDER_MODEL_UID)\n .select(['pathId', 'path'])\n .where({ id })\n .transacting(trx.get())\n .forUpdate()\n .first()\n .execute<Folder>();\n\n // update parent folder (delete + insert; upsert not possible)\n // @ts-expect-error - no dynamic types\n const { joinTable } = strapi.db.metadata.get(FOLDER_MODEL_UID).attributes.parent;\n await strapi.db\n .queryBuilder(joinTable.name)\n .transacting(trx.get())\n .delete()\n .where({ [joinTable.joinColumn.name]: id })\n .execute();\n\n if (parent !== null) {\n await strapi.db\n .queryBuilder(joinTable.name)\n .transacting(trx.get())\n .insert({ [joinTable.inverseJoinColumn.name]: parent, [joinTable.joinColumn.name]: id })\n .where({ [joinTable.joinColumn.name]: id })\n .execute();\n }\n\n // fetch destinationFolder path\n let destinationFolderPath = '/';\n if (parent !== null) {\n const destinationFolder = await strapi.db\n .queryBuilder(FOLDER_MODEL_UID)\n .select('path')\n .where({ id: parent })\n .transacting(trx.get())\n .first()\n .execute<Folder>();\n destinationFolderPath = destinationFolder.path;\n }\n\n const folderTable = strapi.getModel(FOLDER_MODEL_UID).collectionName;\n const fileTable = strapi.getModel(FILE_MODEL_UID).collectionName;\n const folderPathColumnName =\n // @ts-expect-error - no dynamic types\n strapi.db.metadata.get(FILE_MODEL_UID).attributes.folderPath.columnName;\n // @ts-expect-error - no dynamic types\n const pathColumnName = strapi.db.metadata.get(FOLDER_MODEL_UID).attributes.path.columnName;\n\n // update folders below\n await strapi.db\n .getConnection(folderTable)\n .transacting(trx.get())\n .where(pathColumnName, existingFolder.path)\n .orWhere(pathColumnName, 'like', `${existingFolder.path}/%`)\n .update(\n pathColumnName,\n strapi.db.connection.raw('REPLACE(??, ?, ?)', [\n pathColumnName,\n existingFolder.path,\n strings.joinBy('/', destinationFolderPath, `${existingFolder.pathId}`),\n ])\n );\n\n // update files below\n await strapi.db\n .getConnection(fileTable)\n .transacting(trx.get())\n .where(folderPathColumnName, existingFolder.path)\n .orWhere(folderPathColumnName, 'like', `${existingFolder.path}/%`)\n .update(\n folderPathColumnName,\n strapi.db.connection.raw('REPLACE(??, ?, ?)', [\n folderPathColumnName,\n existingFolder.path,\n strings.joinBy('/', destinationFolderPath, `${existingFolder.pathId}`),\n ])\n );\n\n await trx.commit();\n } catch (e) {\n await trx.rollback();\n throw e;\n }\n\n // update less critical information (name + updatedBy)\n const newFolder = setCreatorFields({ user, isEdition: true })({ name });\n\n const folder = await strapi.db\n .query(FOLDER_MODEL_UID)\n .update({ where: { id }, data: newFolder });\n\n strapi.eventHub.emit('media-folder.update', { folder });\n return folder;\n }\n};\n\n/**\n * Check if a folder exists in database\n * @param params query params to find the folder\n * @returns {Promise<boolean>}\n */\nconst exists = async (params = {}) => {\n const count = await strapi.db.query(FOLDER_MODEL_UID).count({ where: params });\n return count > 0;\n};\n\n/**\n * Returns the nested structure of folders\n * @returns {Promise<array>}\n */\nconst getStructure = async () => {\n // @ts-expect-error - no dynamic types\n const { joinTable } = strapi.db.metadata.get(FOLDER_MODEL_UID).attributes.parent;\n const qb = strapi.db.queryBuilder(FOLDER_MODEL_UID);\n const alias = qb.getAlias();\n const folders = (await qb\n .select(['id', 'name', `${alias}.${joinTable.inverseJoinColumn.name} as parent`])\n .join({\n alias,\n referencedTable: joinTable.name,\n referencedColumn: joinTable.joinColumn.name,\n rootColumn: joinTable.joinColumn.referencedColumn,\n rootTable: qb.alias,\n })\n .execute({ mapResults: false })) as Folder[];\n\n const folderMap: FolderMap = {\n null: { children: [] },\n };\n\n folders.forEach((f) => {\n folderMap[f.id] = { ...f, children: [] };\n });\n\n folders.forEach((f) => {\n const parentId = f.parent || 'null';\n\n if (!folderMap[parentId]) {\n folderMap[parentId] = { children: [] };\n }\n\n folderMap[parentId].children.push(folderMap[f.id]);\n folderMap[parentId].children = sortBy('name', folderMap[parentId].children);\n delete folderMap[f.id].parent;\n });\n\n return folderMap.null.children;\n};\n\nexport default {\n create,\n exists,\n deleteByIds,\n update,\n setPathIdAndPath,\n getStructure,\n};\n"],"names":["setPathIdAndPath","folder","max","strapi","db","queryBuilder","FOLDER_MODEL_UID","first","execute","pathId","parentPath","parent","parentFolder","query","findOne","where","id","path","Object","assign","strings","joinBy","create","folderData","opts","folderService","getService","user","enrichedFolder","setCreatorFields","data","eventHub","emit","deleteByIds","ids","folders","findMany","$in","length","totalFolderNumber","totalFileNumber","pathsToDelete","map","filesToDelete","FILE_MODEL_UID","$or","flatMap","folderPath","$eq","$startsWith","Promise","all","file","remove","count","deleteMany","update","name","isUndefined","existingFolder","undefined","newFolder","isEdition","trx","transaction","select","transacting","get","forUpdate","joinTable","metadata","attributes","delete","joinColumn","insert","inverseJoinColumn","destinationFolderPath","destinationFolder","folderTable","getModel","collectionName","fileTable","folderPathColumnName","columnName","pathColumnName","getConnection","orWhere","connection","raw","commit","e","rollback","exists","params","getStructure","qb","alias","getAlias","join","referencedTable","referencedColumn","rootColumn","rootTable","mapResults","folderMap","null","children","forEach","f","parentId","push","sortBy"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"folder.js","sources":["../../../server/src/services/folder.ts"],"sourcesContent":["import { map, sortBy } from 'lodash';\nimport { strings, setCreatorFields } from '@strapi/utils';\nimport { FOLDER_MODEL_UID, FILE_MODEL_UID } from '../constants';\nimport { getService } from '../utils';\n\nimport type { File, Folder } from '../types';\n\ntype FolderMap = {\n [key: string]: Partial<Folder> & {\n children: FolderNode[];\n };\n};\n\ntype FolderNode = Partial<Folder> & {\n children: FolderNode[];\n};\n\ntype DeleteByIdsOptions = {\n validateFiles?: (files: File[]) => Promise<void>;\n};\n\nconst setPathIdAndPath = async (folder: Pick<Folder, 'parent'>) => {\n const { max } = await strapi.db\n .queryBuilder(FOLDER_MODEL_UID)\n .max('pathId')\n .first()\n .execute<{ max: number }>();\n\n const pathId = max + 1;\n let parentPath = '/';\n if (folder.parent) {\n const parentFolder = await strapi.db\n .query(FOLDER_MODEL_UID)\n .findOne({ where: { id: folder.parent } });\n\n parentPath = parentFolder.path;\n }\n\n return Object.assign(folder, {\n pathId,\n path: strings.joinBy('/', parentPath, `${pathId}`),\n });\n};\n\nconst create = async (\n folderData: Pick<Folder, 'name' | 'parent'>,\n opts?: { user: { id: string | number } }\n) => {\n const folderService = getService('folder');\n\n const { user } = opts || {};\n\n let enrichedFolder = await folderService.setPathIdAndPath(folderData);\n if (user) {\n enrichedFolder = await setCreatorFields({ user })(enrichedFolder);\n }\n\n const folder = await strapi.db.query(FOLDER_MODEL_UID).create({ data: enrichedFolder });\n\n strapi.eventHub.emit('media-folder.create', { folder });\n\n return folder;\n};\n\n/**\n * Recursively delete folders and included files\n * @param ids ids of the folders to delete\n * @returns {Promise<Object[]>}\n */\nconst deleteByIds = async (ids: number[] = [], { validateFiles }: DeleteByIdsOptions = {}) => {\n const folders = await strapi.db.query(FOLDER_MODEL_UID).findMany({ where: { id: { $in: ids } } });\n if (folders.length === 0) {\n return {\n folders: [],\n totalFolderNumber: 0,\n totalFileNumber: 0,\n };\n }\n\n const pathsToDelete = map(folders, 'path');\n\n // delete files\n const filesToDelete = await strapi.db.query(FILE_MODEL_UID).findMany({\n where: {\n $or: pathsToDelete.flatMap((path) => [\n { folderPath: { $eq: path } },\n { folderPath: { $startsWith: `${path}/` } },\n ]),\n },\n });\n\n // A folder deletion cascades to every file in its subtree. Let callers enforce\n // request-scoped permissions against the complete set before the first destructive action.\n await validateFiles?.(filesToDelete);\n\n await Promise.all(filesToDelete.map((file: File) => getService('upload').remove(file)));\n\n // delete folders and subfolders\n const { count: totalFolderNumber } = await strapi.db.query(FOLDER_MODEL_UID).deleteMany({\n where: {\n $or: pathsToDelete.flatMap((path) => [\n { path: { $eq: path } },\n { path: { $startsWith: `${path}/` } },\n ]),\n },\n });\n\n strapi.eventHub.emit('media-folder.delete', { folders });\n\n return {\n folders,\n totalFolderNumber,\n totalFileNumber: filesToDelete.length,\n };\n};\n\n/**\n * Update name and location of a folder and its belonging folders and files\n */\nconst update = async (\n id: number,\n {\n name,\n parent,\n }: {\n name: string;\n /**\n * Omit to rename in place: the name-only branch skips the transaction that rewrites\n * descendant paths. Pass a number to re-parent, or null to move to the root.\n */\n parent?: number | null;\n },\n { user }: { user: { id: string | number } }\n) => {\n // only name is updated\n if (parent === undefined) {\n const existingFolder = await strapi.db.query(FOLDER_MODEL_UID).findOne({ where: { id } });\n\n if (!existingFolder) {\n return undefined;\n }\n\n const newFolder = setCreatorFields({ user, isEdition: true })({ name, parent });\n\n if (parent === undefined) {\n const folder = await strapi.db\n .query(FOLDER_MODEL_UID)\n .update({ where: { id }, data: newFolder });\n\n return folder;\n }\n // location is updated => using transaction\n } else {\n const trx = await strapi.db.transaction();\n try {\n // fetch existing folder\n const existingFolder = await strapi.db\n .queryBuilder(FOLDER_MODEL_UID)\n .select(['pathId', 'path'])\n .where({ id })\n .transacting(trx.get())\n .forUpdate()\n .first()\n .execute<Folder>();\n\n // update parent folder (delete + insert; upsert not possible)\n // @ts-expect-error - no dynamic types\n const { joinTable } = strapi.db.metadata.get(FOLDER_MODEL_UID).attributes.parent;\n await strapi.db\n .queryBuilder(joinTable.name)\n .transacting(trx.get())\n .delete()\n .where({ [joinTable.joinColumn.name]: id })\n .execute();\n\n if (parent !== null) {\n await strapi.db\n .queryBuilder(joinTable.name)\n .transacting(trx.get())\n .insert({ [joinTable.inverseJoinColumn.name]: parent, [joinTable.joinColumn.name]: id })\n .where({ [joinTable.joinColumn.name]: id })\n .execute();\n }\n\n // fetch destinationFolder path\n let destinationFolderPath = '/';\n if (parent !== null) {\n const destinationFolder = await strapi.db\n .queryBuilder(FOLDER_MODEL_UID)\n .select('path')\n .where({ id: parent })\n .transacting(trx.get())\n .first()\n .execute<Folder>();\n destinationFolderPath = destinationFolder.path;\n }\n\n const folderTable = strapi.getModel(FOLDER_MODEL_UID).collectionName;\n const fileTable = strapi.getModel(FILE_MODEL_UID).collectionName;\n const folderPathColumnName =\n // @ts-expect-error - no dynamic types\n strapi.db.metadata.get(FILE_MODEL_UID).attributes.folderPath.columnName;\n // @ts-expect-error - no dynamic types\n const pathColumnName = strapi.db.metadata.get(FOLDER_MODEL_UID).attributes.path.columnName;\n\n // update folders below\n await strapi.db\n .getConnection(folderTable)\n .transacting(trx.get())\n .where(pathColumnName, existingFolder.path)\n .orWhere(pathColumnName, 'like', `${existingFolder.path}/%`)\n .update(\n pathColumnName,\n strapi.db.connection.raw('REPLACE(??, ?, ?)', [\n pathColumnName,\n existingFolder.path,\n strings.joinBy('/', destinationFolderPath, `${existingFolder.pathId}`),\n ])\n );\n\n // update files below\n await strapi.db\n .getConnection(fileTable)\n .transacting(trx.get())\n .where(folderPathColumnName, existingFolder.path)\n .orWhere(folderPathColumnName, 'like', `${existingFolder.path}/%`)\n .update(\n folderPathColumnName,\n strapi.db.connection.raw('REPLACE(??, ?, ?)', [\n folderPathColumnName,\n existingFolder.path,\n strings.joinBy('/', destinationFolderPath, `${existingFolder.pathId}`),\n ])\n );\n\n await trx.commit();\n } catch (e) {\n await trx.rollback();\n throw e;\n }\n\n // update less critical information (name + updatedBy)\n const newFolder = setCreatorFields({ user, isEdition: true })({ name });\n\n const folder = await strapi.db\n .query(FOLDER_MODEL_UID)\n .update({ where: { id }, data: newFolder });\n\n strapi.eventHub.emit('media-folder.update', { folder });\n return folder;\n }\n};\n\n/**\n * Check if a folder exists in database\n * @param params query params to find the folder\n * @returns {Promise<boolean>}\n */\nconst exists = async (params = {}) => {\n const count = await strapi.db.query(FOLDER_MODEL_UID).count({ where: params });\n return count > 0;\n};\n\n/**\n * Returns the nested structure of folders\n * @returns {Promise<array>}\n */\nconst getStructure = async () => {\n // @ts-expect-error - no dynamic types\n const { joinTable } = strapi.db.metadata.get(FOLDER_MODEL_UID).attributes.parent;\n const qb = strapi.db.queryBuilder(FOLDER_MODEL_UID);\n const alias = qb.getAlias();\n const folders = (await qb\n .select(['id', 'name', `${alias}.${joinTable.inverseJoinColumn.name} as parent`])\n .join({\n alias,\n referencedTable: joinTable.name,\n referencedColumn: joinTable.joinColumn.name,\n rootColumn: joinTable.joinColumn.referencedColumn,\n rootTable: qb.alias,\n })\n .execute({ mapResults: false })) as Folder[];\n\n const folderMap: FolderMap = {\n null: { children: [] },\n };\n\n folders.forEach((f) => {\n folderMap[f.id] = { ...f, children: [] };\n });\n\n folders.forEach((f) => {\n const parentId = f.parent || 'null';\n\n if (!folderMap[parentId]) {\n folderMap[parentId] = { children: [] };\n }\n\n folderMap[parentId].children.push(folderMap[f.id]);\n folderMap[parentId].children = sortBy(folderMap[parentId].children, 'name');\n delete folderMap[f.id].parent;\n });\n\n return folderMap.null.children;\n};\n\nexport default {\n create,\n exists,\n deleteByIds,\n update,\n setPathIdAndPath,\n getStructure,\n};\n"],"names":["setPathIdAndPath","folder","max","strapi","db","queryBuilder","FOLDER_MODEL_UID","first","execute","pathId","parentPath","parent","parentFolder","query","findOne","where","id","path","Object","assign","strings","joinBy","create","folderData","opts","folderService","getService","user","enrichedFolder","setCreatorFields","data","eventHub","emit","deleteByIds","ids","validateFiles","folders","findMany","$in","length","totalFolderNumber","totalFileNumber","pathsToDelete","map","filesToDelete","FILE_MODEL_UID","$or","flatMap","folderPath","$eq","$startsWith","Promise","all","file","remove","count","deleteMany","update","name","undefined","existingFolder","newFolder","isEdition","trx","transaction","select","transacting","get","forUpdate","joinTable","metadata","attributes","delete","joinColumn","insert","inverseJoinColumn","destinationFolderPath","destinationFolder","folderTable","getModel","collectionName","fileTable","folderPathColumnName","columnName","pathColumnName","getConnection","orWhere","connection","raw","commit","e","rollback","exists","params","getStructure","qb","alias","getAlias","join","referencedTable","referencedColumn","rootColumn","rootTable","mapResults","folderMap","null","children","forEach","f","parentId","push","sortBy"],"mappings":"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"}
|