ghost 6.54.0 → 6.54.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/ghost-storage-base-3.0.0.tgz +0 -0
- package/components/tryghost-adapter-base-cache-0.2.0.tgz +0 -0
- package/components/tryghost-adapter-base-scheduling-0.2.0.tgz +0 -0
- package/components/tryghost-adapter-base-sso-0.1.1.tgz +0 -0
- package/components/tryghost-kg-default-nodes-2.1.3.tgz +0 -0
- package/core/built/admin/assets/Growth-BTP1LthE.js +3 -0
- package/core/built/admin/assets/Newsletters-CSEX0myc.js +1 -0
- package/core/built/admin/assets/Overview-BjACEsJF.js +1 -0
- package/core/built/admin/assets/Web-CxxxjCcv.js +2 -0
- package/core/built/admin/assets/{account-migration-DLvVA5yq.js → account-migration-BklF2I_I.js} +1 -1
- package/core/built/admin/assets/activitypub/{account-migration-DHbZcxGM.js → account-migration-CNovr6W-.js} +4 -4
- package/core/built/admin/assets/activitypub/activitypub.js +2 -2
- package/core/built/admin/assets/activitypub/{bluesky-sharing-zfxUey_L.js → bluesky-sharing-Ceslb1_M.js} +4 -4
- package/core/built/admin/assets/activitypub/{dist-BqkDUQxJ2.js → dist-ByFmDSNP2.js} +64 -63
- package/core/built/admin/assets/activitypub/{dist-Ba_QxHHl.js → dist-DHAjBcV3.js} +2 -2
- package/core/built/admin/assets/activitypub/{domain-CmwSZLHK.js → domain-C1qQvJ8C.js} +5 -5
- package/core/built/admin/assets/activitypub/{edit-profile-CXdwNmVG.js → edit-profile-BcH-jEfM.js} +3 -3
- package/core/built/admin/assets/activitypub/{explore-DfbjzGM1.js → explore-AxwBh9dI.js} +3 -3
- package/core/built/admin/assets/activitypub/{feed-DSEBJOqf.js → feed-BaOlbh7a.js} +3 -3
- package/core/built/admin/assets/activitypub/{field-CY7XVcx6.js → field-B7mYYQpN.js} +2 -2
- package/core/built/admin/assets/activitypub/{inbox-DtQ0kytT.js → inbox-vXez-5uJ.js} +4 -4
- package/core/built/admin/assets/activitypub/{label-Dw96GQ7u.js → label-Chv5T8bw.js} +2 -2
- package/core/built/admin/assets/activitypub/layout-CziktJXo.js +1 -0
- package/core/built/admin/assets/activitypub/{moderation-Dg_-I-8C.js → moderation-DtSM2i1I.js} +4 -4
- package/core/built/admin/assets/activitypub/{note-Cy1-uKMY.js → note-Dckp4S-2.js} +3 -3
- package/core/built/admin/assets/activitypub/{notifications-B_fvoLIM.js → notifications-CG5T22vg.js} +3 -3
- package/core/built/admin/assets/activitypub/{preferences-ByJseZp6.js → preferences-D_799jdl.js} +6 -6
- package/core/built/admin/assets/activitypub/{profile-BFtbnIjz.js → profile-D2JfXBQl.js} +6 -6
- package/core/built/admin/assets/activitypub/{routes-Dh3xXfce.js → routes-CCHe2dQp.js} +28 -25
- package/core/built/admin/assets/activitypub/{settings-DTgqJPxM.js → settings-kx-9Enkz.js} +3 -3
- package/core/built/admin/assets/activitypub/{site-CdL9ZLqm.js → site-x4_IjVJ0.js} +2 -2
- package/core/built/admin/assets/activitypub/{tabs-ogqwoBlb.js → tabs-CyPRQQ1N.js} +3 -3
- package/core/built/admin/assets/activitypub/{use-reply-chain-data-CxonteFD.js → use-reply-chain-data-BAB6fNGt.js} +2 -2
- package/core/built/admin/assets/{audience-BKRdsFmy.js → audience-X_XsZivI.js} +1 -1
- package/core/built/admin/assets/{automated-emails-Cn87L5Md.js → automated-emails-of1MvVsv.js} +1 -1
- package/core/built/admin/assets/{automations-BP_aHamJ.js → automations-DRFjW5fw.js} +1 -1
- package/core/built/admin/assets/{bluesky-sharing-Bh36XPh4.js → bluesky-sharing-BbbjWdzs.js} +1 -1
- package/core/built/admin/assets/{breadcrumb-D34YWnaw.js → breadcrumb-B-M9Ip_2.js} +1 -1
- package/core/built/admin/assets/{chart-helpers-DbklaJw9.js → chart-helpers-xiSsW8r0.js} +1 -1
- package/core/built/admin/assets/checkbox-EhJKHFdZ.js +1 -0
- package/core/built/admin/assets/{chunk.962.b73442b07be930bb5d85.js → chunk.211.6f7328d63c41536451b3.js} +737 -559
- package/core/built/admin/assets/{chunk.962.b73442b07be930bb5d85.js.LICENSE.txt → chunk.211.6f7328d63c41536451b3.js.LICENSE.txt} +2 -0
- package/core/built/admin/assets/{chunk.589.096f4d0bb775e5229aa9.js → chunk.361.3cdb584b973959e52ccc.js} +3 -3
- package/core/built/admin/assets/chunk.524.8365cd9280405c0f6216.js +25 -0
- package/core/built/admin/assets/chunk.582.61d078937e6102769f2c.js +27 -0
- package/core/built/admin/assets/code-editor-view-CZtJFGhR.js +1 -0
- package/core/built/admin/assets/comments-CvyXxOyN.js +1 -0
- package/core/built/admin/assets/{date-range-select-B5vSsrpx.js → date-range-select-CTlyo8zW.js} +1 -1
- package/core/built/admin/assets/{disabled-sources-indicator-NkOIoMue.js → disabled-sources-indicator-qs2GfLvl.js} +1 -1
- package/core/built/admin/assets/dist-2Tnz2Yfq.js +1 -0
- package/core/built/admin/assets/dist-CjY-f5Z1.js +1 -0
- package/core/built/admin/assets/dist-DIC8yz5C.js +1 -0
- package/core/built/admin/assets/{domain-B7_n5nfx.js → domain-CPNXKVOV.js} +1 -1
- package/core/built/admin/assets/dropzone-BmCXnw6J.js +5 -0
- package/core/built/admin/assets/{edit-profile-rvDGrS8J.js → edit-profile-SGrJve5M.js} +1 -1
- package/core/built/admin/assets/editor-BOG2YPUm.js +8 -0
- package/core/built/admin/assets/{explore-BZAZOg5X.js → explore-CUYLv9G9.js} +1 -1
- package/core/built/admin/assets/{feed-CFV8xDw9.js → feed-CPdJMttL.js} +1 -1
- package/core/built/admin/assets/{field-BW-M2Zwp.js → field-D0XISCK2.js} +1 -1
- package/core/built/admin/assets/{filter-sources-Fx8wiYYw.js → filter-sources-Bieps75g.js} +1 -1
- package/core/built/admin/assets/{filters-CGX_pf2N.js → filters-CuB-A7ma.js} +1 -1
- package/core/built/admin/assets/filters-bXkkWro8.js +1 -0
- package/core/built/admin/assets/{ghost-b1dce91ad3232cc259ee5fbbf9526243.js → ghost-05f9a7182ed56903dba05878a5b8422b.js} +290 -289
- package/core/built/admin/assets/{gift-link-modal-Bk8qpnMw.js → gift-link-modal-su8MsRdg.js} +1 -1
- package/core/built/admin/assets/{growth-Ck2s3JjR.js → growth-Ba6S5FQX.js} +1 -1
- package/core/built/admin/assets/{inbox-BRp1_aaC.js → inbox-Xx1CdnfO.js} +8 -8
- package/core/built/admin/assets/index-D8PbzuT9.css +1 -0
- package/core/built/admin/assets/index-ukW_0Uie.js +67 -0
- package/core/built/admin/assets/input-group-CGOXcy4l.js +1 -0
- package/core/built/admin/assets/input-surface-BpAjZXY5.js +1 -0
- package/core/built/admin/assets/{koenig-lexical-CM7UFNdL.js → koenig-lexical-B61IALf5.js} +1 -1
- package/core/built/admin/assets/{kpi-card-CIGyijjx.js → kpi-card-BlZaG1Lo.js} +1 -1
- package/core/built/admin/assets/{kpi-card-BrGH6FsT.js → kpi-card-DCdlyaSS.js} +1 -1
- package/core/built/admin/assets/{kpi-helpers-_HRRsklE.js → kpi-helpers-DpAuS1Nb.js} +1 -1
- package/core/built/admin/assets/{kpi-tabs-BSvh9eoq.js → kpi-tabs-Dme44bQS.js} +1 -1
- package/core/built/admin/assets/{label-BOR9XdKl.js → label-dSs5aepE.js} +1 -1
- package/core/built/admin/assets/{label-picker-BdNAWuUX.js → label-picker-Uq91atex.js} +1 -1
- package/core/built/admin/assets/layout-WUcz6tu1.js +1 -0
- package/core/built/admin/assets/{member-custom-fields-DvFyamLu.js → member-custom-fields-NCzrMuba.js} +1 -1
- package/core/built/admin/assets/member-detail-DPWDtYnN.js +1 -0
- package/core/built/admin/assets/{members-DsPxYAVm.js → members-B9rO7qr4.js} +2 -2
- package/core/built/admin/assets/modals-CxRaan1J.js +31 -0
- package/core/built/admin/assets/{moderation-Dlp7M5sa.js → moderation-D8zon60E.js} +1 -1
- package/core/built/admin/assets/{multi-select-combobox-CH1o8XXQ.js → multi-select-combobox-1wYm3nle.js} +1 -1
- package/core/built/admin/assets/newsletter-DbYMRxRS.js +1 -0
- package/core/built/admin/assets/{note-BrAZ7Z0o.js → note-PiWPnnrh.js} +1 -1
- package/core/built/admin/assets/{notifications-BnbHLOPC.js → notifications-DEgqqB9G.js} +1 -1
- package/core/built/admin/assets/{onboarding-route-eU146kuA.js → onboarding-route-BzF6HeVn.js} +1 -1
- package/core/built/admin/assets/{overview-BpbaK-xu.js → overview-DixLeIqY.js} +1 -1
- package/core/built/admin/assets/{page-header-C0jVxtS8.js → page-header-op0eN36I.js} +1 -1
- package/core/built/admin/assets/{post-analytics-Iw7dk1mT.js → post-analytics-WwRDEnrV.js} +1 -1
- package/core/built/admin/assets/post-analytics-header-DsMoLx0R.js +1 -0
- package/core/built/admin/assets/{post-analytics-provider-BDqcfBor.js → post-analytics-provider-BJCPLMIb.js} +1 -1
- package/core/built/admin/assets/{post-share-modal-DBfKl2GZ.js → post-share-modal-Dx-MEjhI.js} +1 -1
- package/core/built/admin/assets/preferences-D7-j6A4C.js +5 -0
- package/core/built/admin/assets/profile-Dl69QPp0.js +1 -0
- package/core/built/admin/assets/select-C1qDZnW4.js +1 -0
- package/core/built/admin/assets/settings-BbmC_Iqq.js +4 -0
- package/core/built/admin/assets/{settings-Bz3RZRmE.js → settings-H6HxFgfI.js} +1 -1
- package/core/built/admin/assets/shade-app-B3heOAfz-CGXQwWeO.js +3 -0
- package/core/built/admin/assets/{share-modal-CkdTTMJd.js → share-modal-BJCadJyp.js} +1 -1
- package/core/built/admin/assets/social-urls-BPL1nADp.js +8 -0
- package/core/built/admin/assets/{sort-button-DZp9Y2r1.js → sort-button-DyYlfYp7.js} +1 -1
- package/core/built/admin/assets/{source-icon-CHr9qJ2Y.js → source-icon-_ubRDbg_.js} +1 -1
- package/core/built/admin/assets/{stats-DGoXZxWE.js → stats-BQZMGMvn.js} +1 -1
- package/core/built/admin/assets/{stats-view-D2-1L0ms.js → stats-view-Doei-qCX.js} +1 -1
- package/core/built/admin/assets/{step-1-RdahbwyG.js → step-1-IKiLBPoC.js} +1 -1
- package/core/built/admin/assets/{step-2-D83PeRwd.js → step-2-jJ41PsDN.js} +1 -1
- package/core/built/admin/assets/{step-3-p_Op4wv4.js → step-3-CaqFAEqf.js} +2 -2
- package/core/built/admin/assets/switch-t-WyENU2.js +1 -0
- package/core/built/admin/assets/{table-B8LgTQJZ.js → table-DxUL78jU.js} +1 -1
- package/core/built/admin/assets/{tabs-D3ZOYkM7.js → tabs-BQcJQ1ke.js} +1 -1
- package/core/built/admin/assets/{tags-B4l891z2.js → tags-CNujGwkT.js} +1 -1
- package/core/built/admin/assets/textarea-Bjccvh_E.js +1 -0
- package/core/built/admin/assets/{toggle-group-DNXDkAwf.js → toggle-group-BeEpQ69p.js} +1 -1
- package/core/built/admin/assets/{topic-filter-DZGjEcz3.js → topic-filter-Bb5MOD-1.js} +1 -1
- package/core/built/admin/assets/{use-analytics-data-CaT8RirX.js → use-analytics-data-Cds6XDjg.js} +1 -1
- package/core/built/admin/assets/use-filter-params-DjXkhO54.js +1 -0
- package/core/built/admin/assets/{use-growth-stats-9Db2CuJe.js → use-growth-stats-D8uf98-G.js} +1 -1
- package/core/built/admin/assets/{use-post-referrers-BV54YcQS.js → use-post-referrers-CRtgyuXB.js} +1 -1
- package/core/built/admin/assets/{use-reply-chain-data-DuYZGDan.js → use-reply-chain-data-DbKYX50v.js} +1 -1
- package/core/built/admin/assets/{use-simple-pagination-8fTua9Nt.js → use-simple-pagination-DC01kpo0.js} +1 -1
- package/core/built/admin/assets/{use-tinybird-query-Dz156HvZ.js → use-tinybird-query-C7bQRJWw.js} +1 -1
- package/core/built/admin/assets/{use-tinybird-token-C9RUdL_n.js → use-tinybird-token-Az4tam7_.js} +1 -1
- package/core/built/admin/assets/{virtual-list-BCvP0YF-.js → virtual-list-Dhuh9fGx.js} +1 -1
- package/core/built/admin/assets/web-LtHNiNBV.js +1 -0
- package/core/built/admin/index.html +11 -7
- package/core/frontend/services/proxy.js +11 -1
- package/core/server/adapters/storage/LocalFilesStorage.js +13 -11
- package/core/server/adapters/storage/LocalFilesStorage.ts +24 -0
- package/core/server/adapters/storage/LocalImagesStorage.js +14 -12
- package/core/server/adapters/storage/LocalImagesStorage.ts +24 -0
- package/core/server/adapters/storage/LocalMediaStorage.js +13 -11
- package/core/server/adapters/storage/LocalMediaStorage.ts +24 -0
- package/core/server/adapters/storage/LocalStorageBase.js +107 -167
- package/core/server/adapters/storage/LocalStorageBase.ts +342 -0
- package/core/server/adapters/storage/utils.js +36 -50
- package/core/server/adapters/storage/utils.ts +60 -0
- package/core/server/api/endpoints/db.js +6 -0
- package/core/server/api/endpoints/members.js +25 -28
- package/core/server/api/endpoints/pages.js +6 -1
- package/core/server/api/endpoints/posts.js +11 -2
- package/core/server/api/endpoints/utils/serializers/output/mappers/comments.js +4 -1
- package/core/server/api/endpoints/utils/serializers/output/members.js +18 -87
- package/core/server/data/exporter/export-filename.js +2 -1
- package/core/server/data/importer/importers/data/revue-subscriber-importer.js +6 -10
- package/core/server/data/importer/importers/json-to-html.js +24 -4
- package/core/server/lib/image/image-size.js +3 -17
- package/core/server/lib/image/image-utils.js +15 -1
- package/core/server/lib/request-external.js +1 -1
- package/core/server/models/user.js +46 -70
- package/core/server/services/auth/session/session-service.js +28 -1
- package/core/server/services/automations/automations-api.js +6 -6
- package/core/server/services/automations/automations-api.ts +6 -6
- package/core/server/services/automations/automations-repository.ts +1 -0
- package/core/server/services/automations/database-automations-repository.js +7 -2
- package/core/server/services/automations/database-automations-repository.ts +8 -2
- package/core/server/services/comments/comments-service-emails.js +4 -1
- package/core/server/services/members/{importer → import-export}/config.js +4 -9
- package/core/server/services/members/{importer → import-export}/config.ts +4 -9
- package/core/server/services/members/{csv → import-export/csv}/index.js +3 -3
- package/core/server/services/members/import-export/csv/index.ts +2 -0
- package/core/server/services/members/import-export/csv/parse.js +64 -0
- package/core/server/services/members/import-export/csv/parse.ts +69 -0
- package/core/server/services/members/import-export/csv/serialize.js +13 -0
- package/core/server/services/members/import-export/csv/serialize.ts +18 -0
- package/core/server/services/members/import-export/export/exporter.js +202 -0
- package/core/server/services/members/import-export/export/exporter.ts +325 -0
- package/core/server/services/members/import-export/import/completion-email.js +65 -0
- package/core/server/services/members/import-export/import/completion-email.ts +119 -0
- package/core/server/services/members/import-export/import/importer.js +276 -0
- package/core/server/services/members/import-export/import/importer.ts +430 -0
- package/core/server/services/members/import-export/import/reader.js +32 -0
- package/core/server/services/members/import-export/import/reader.ts +33 -0
- package/core/server/services/members/import-export/import/row.js +33 -0
- package/core/server/services/members/import-export/import/row.ts +52 -0
- package/core/server/services/members/import-export/import/spool.js +30 -0
- package/core/server/services/members/import-export/import/spool.ts +38 -0
- package/core/server/services/members/import-export/index.js +90 -0
- package/core/server/services/members/import-export/index.ts +118 -0
- package/core/server/services/members/members-api/controllers/router-controller.js +7 -0
- package/core/server/services/members/service.js +17 -19
- package/core/server/services/posts/posts-exporter.js +6 -4
- package/core/server/services/posts/posts-service.js +2 -2
- package/core/server/services/themes/storage.js +9 -0
- package/core/server/services/themes/theme-storage.js +1 -1
- package/core/shared/config/defaults.json +2 -1
- package/core/shared/express.js +6 -1
- package/core/shared/labs.js +2 -1
- package/package.json +9 -9
- package/pnpm-lock.yaml +17 -17
- package/pnpm-workspace.yaml +7 -7
- package/components/tryghost-adapter-base-scheduling-0.1.1.tgz +0 -0
- package/core/built/admin/assets/Growth-CSaHoSVD.js +0 -3
- package/core/built/admin/assets/Newsletters-C5cs6FGo.js +0 -1
- package/core/built/admin/assets/Overview-j4NaeUNm.js +0 -1
- package/core/built/admin/assets/Web-_fD935OX.js +0 -2
- package/core/built/admin/assets/activitypub/layout-IdVq3lRy.js +0 -1
- package/core/built/admin/assets/checkbox-DA4-1UiI.js +0 -1
- package/core/built/admin/assets/chunk.524.45e0875f41b71e46e849.js +0 -25
- package/core/built/admin/assets/chunk.582.51defd4b009f0de8d4d5.js +0 -27
- package/core/built/admin/assets/code-editor-view-BKBrkffZ.js +0 -1
- package/core/built/admin/assets/comments-CzVNIT8b.js +0 -1
- package/core/built/admin/assets/dist-CT46_f_J.js +0 -1
- package/core/built/admin/assets/dropzone-DcuJEEUf.js +0 -5
- package/core/built/admin/assets/editor-BCiIyFjm.js +0 -8
- package/core/built/admin/assets/filters-DtcXMZDU.js +0 -1
- package/core/built/admin/assets/index-BBf502Wg.js +0 -69
- package/core/built/admin/assets/index-BCCZKxP2.css +0 -1
- package/core/built/admin/assets/input-group-DjuYnNCd.js +0 -1
- package/core/built/admin/assets/layout-Bw4GnxXp.js +0 -1
- package/core/built/admin/assets/member-detail-Cg2vTqvJ.js +0 -1
- package/core/built/admin/assets/modals-Bf_fyvHZ.js +0 -31
- package/core/built/admin/assets/newsletter-CQ5CAYUI.js +0 -1
- package/core/built/admin/assets/post-analytics-header-D9pJ4iNm.js +0 -1
- package/core/built/admin/assets/preferences-D4EANGoc.js +0 -5
- package/core/built/admin/assets/profile-dJ44Bn_W.js +0 -1
- package/core/built/admin/assets/select--2uVN9_-.js +0 -1
- package/core/built/admin/assets/settings-SD9bxIpt.js +0 -4
- package/core/built/admin/assets/social-urls-BvFZwYcE.js +0 -8
- package/core/built/admin/assets/switch-qp518gFQ.js +0 -1
- package/core/built/admin/assets/textarea-CTlb3Uu6.js +0 -1
- package/core/built/admin/assets/use-filter-params-DeQ8sX4N.js +0 -1
- package/core/built/admin/assets/web-DTZnnUGr.js +0 -1
- package/core/server/services/mail/templates/newsletter.html +0 -876
- package/core/server/services/members/csv/index.ts +0 -3
- package/core/server/services/members/csv/parse.js +0 -94
- package/core/server/services/members/csv/parse.ts +0 -111
- package/core/server/services/members/csv/types.js +0 -2
- package/core/server/services/members/csv/types.ts +0 -39
- package/core/server/services/members/csv/unparse.js +0 -69
- package/core/server/services/members/csv/unparse.ts +0 -79
- package/core/server/services/members/exporter/query.js +0 -233
- package/core/server/services/members/importer/index.js +0 -30
- package/core/server/services/members/importer/labels.js +0 -125
- package/core/server/services/members/importer/members-csv-importer.js +0 -520
- /package/core/server/services/members/{importer → import-export/import}/email-template.js +0 -0
- /package/core/server/services/members/{importer/members-csv-importer-stripe-utils.js → import-export/import/stripe-utils.js} +0 -0
|
@@ -1,14 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1
6
|
// # Local File Base Storage module
|
|
2
7
|
// The (default) module for storing files using the local file system
|
|
8
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const tpl_1 = __importDefault(require("@tryghost/tpl"));
|
|
11
|
+
const errors_1 = __importDefault(require("@tryghost/errors"));
|
|
12
|
+
const ghost_storage_base_1 = require("ghost-storage-base");
|
|
13
|
+
const url_utils_1 = __importDefault(require("../../../shared/url-utils"));
|
|
14
|
+
const errify_1 = require("../../../shared/errify");
|
|
3
15
|
const serveStatic = require('../../../shared/express').static;
|
|
4
|
-
|
|
5
|
-
const fs = require('fs-extra');
|
|
6
|
-
const path = require('path');
|
|
7
|
-
const tpl = require('@tryghost/tpl');
|
|
8
|
-
const errors = require('@tryghost/errors');
|
|
9
|
-
const urlUtils = require('../../../shared/url-utils').default;
|
|
10
|
-
const {StorageBase} = require('ghost-storage-base');
|
|
11
|
-
|
|
12
16
|
const messages = {
|
|
13
17
|
notFound: 'File not found',
|
|
14
18
|
notFoundWithRef: 'File not found: {file}',
|
|
@@ -16,50 +20,34 @@ const messages = {
|
|
|
16
20
|
invalidUrlParameter: `The URL "{url}" is not a valid URL for this site.`,
|
|
17
21
|
invalidPathParameter: 'The path "{path}" is not valid for this storage.'
|
|
18
22
|
};
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
* @param {string} options.siteUrl
|
|
26
|
-
* @param {string} [options.staticFileURLPrefix]
|
|
27
|
-
* @param {Object} [options.errorMessages]
|
|
28
|
-
* @param {string} [options.errorMessages.notFound]
|
|
29
|
-
* @param {string} [options.errorMessages.notFoundWithRef]
|
|
30
|
-
* @param {string} [options.errorMessages.cannotRead]
|
|
31
|
-
*/
|
|
32
|
-
constructor({storagePath, staticFileURLPrefix, siteUrl, errorMessages}) {
|
|
23
|
+
class LocalStorageBase extends ghost_storage_base_1.StorageBase {
|
|
24
|
+
staticFileURLPrefix;
|
|
25
|
+
siteUrl;
|
|
26
|
+
staticFileUrl;
|
|
27
|
+
errorMessages;
|
|
28
|
+
constructor({ storagePath, staticFileURLPrefix, siteUrl, errorMessages }) {
|
|
33
29
|
super();
|
|
34
|
-
|
|
35
30
|
this.storagePath = storagePath;
|
|
36
31
|
this.staticFileURLPrefix = staticFileURLPrefix;
|
|
37
32
|
this.siteUrl = siteUrl;
|
|
38
33
|
this.staticFileUrl = `${siteUrl}${staticFileURLPrefix}`;
|
|
39
34
|
this.errorMessages = errorMessages || messages;
|
|
40
35
|
}
|
|
41
|
-
|
|
42
36
|
/**
|
|
43
37
|
* Normalizes a relative storage path and rejects traversal outside the storage root.
|
|
44
|
-
*
|
|
45
|
-
* @param {string} filePath
|
|
46
|
-
* @returns {string}
|
|
47
38
|
*/
|
|
48
39
|
_normalizeStorageRelativePath(filePath) {
|
|
49
|
-
const normalized =
|
|
40
|
+
const normalized = path_1.default.posix.normalize(String(filePath || '')
|
|
50
41
|
.replaceAll('\\', '/')
|
|
51
42
|
.replace(/^\/+/, '')
|
|
52
43
|
.replace(/\/+$/, ''));
|
|
53
|
-
|
|
54
44
|
if (normalized === '.' || normalized === '..' || normalized.startsWith('../')) {
|
|
55
|
-
throw new
|
|
56
|
-
message:
|
|
45
|
+
throw new errors_1.default.IncorrectUsageError({
|
|
46
|
+
message: (0, tpl_1.default)(messages.invalidPathParameter, { path: filePath })
|
|
57
47
|
});
|
|
58
48
|
}
|
|
59
|
-
|
|
60
49
|
return normalized;
|
|
61
50
|
}
|
|
62
|
-
|
|
63
51
|
/**
|
|
64
52
|
* Resolves a target directory and optional file name into a full path,
|
|
65
53
|
* validating the result is inside the storage root.
|
|
@@ -67,258 +55,210 @@ class LocalStorageBase extends StorageBase {
|
|
|
67
55
|
* Supports relative paths (preferred) and absolute paths (legacy).
|
|
68
56
|
* TODO: remove absolute path support once all callers pass relative paths
|
|
69
57
|
*
|
|
70
|
-
* @param
|
|
71
|
-
* @param
|
|
72
|
-
* @returns
|
|
58
|
+
* @param targetDir absolute or relative directory
|
|
59
|
+
* @param fileName file name to normalize and append
|
|
60
|
+
* @returns resolved absolute path inside storagePath
|
|
73
61
|
*/
|
|
74
62
|
_resolveAndValidateStoragePath(targetDir, fileName) {
|
|
75
|
-
const resolvedRoot =
|
|
76
|
-
|
|
63
|
+
const resolvedRoot = path_1.default.resolve(this.storagePath);
|
|
77
64
|
// Resolve targetDir: if already inside storage root use as-is, otherwise treat as relative
|
|
78
65
|
let resolvedBase;
|
|
79
66
|
if (targetDir) {
|
|
80
|
-
const resolvedTargetDir =
|
|
81
|
-
const relToRoot =
|
|
82
|
-
if (relToRoot === '' || (!relToRoot.startsWith('..') && !
|
|
67
|
+
const resolvedTargetDir = path_1.default.resolve(targetDir);
|
|
68
|
+
const relToRoot = path_1.default.relative(resolvedRoot, resolvedTargetDir);
|
|
69
|
+
if (relToRoot === '' || (!relToRoot.startsWith('..') && !path_1.default.isAbsolute(relToRoot))) {
|
|
83
70
|
resolvedBase = resolvedTargetDir;
|
|
84
|
-
} else {
|
|
85
|
-
resolvedBase = path.resolve(this.storagePath, targetDir);
|
|
86
71
|
}
|
|
87
|
-
|
|
72
|
+
else {
|
|
73
|
+
resolvedBase = path_1.default.resolve(this.storagePath, targetDir);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
88
77
|
resolvedBase = resolvedRoot;
|
|
89
78
|
}
|
|
90
|
-
|
|
91
79
|
// If fileName provided, normalize and resolve
|
|
92
80
|
let resolvedPath;
|
|
93
81
|
if (fileName) {
|
|
94
82
|
const normalizedFileName = this._normalizeStorageRelativePath(fileName);
|
|
95
|
-
resolvedPath =
|
|
96
|
-
}
|
|
83
|
+
resolvedPath = path_1.default.resolve(resolvedBase, normalizedFileName);
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
97
86
|
resolvedPath = resolvedBase;
|
|
98
87
|
}
|
|
99
|
-
|
|
100
88
|
// Validate the resolved path is strictly inside the storage root (not equal to it)
|
|
101
|
-
const relative =
|
|
102
|
-
if (relative === '' || relative === '..' || relative.startsWith('..' +
|
|
103
|
-
throw new
|
|
104
|
-
message:
|
|
89
|
+
const relative = path_1.default.relative(resolvedRoot, resolvedPath);
|
|
90
|
+
if (relative === '' || relative === '..' || relative.startsWith('..' + path_1.default.sep) || path_1.default.isAbsolute(relative)) {
|
|
91
|
+
throw new errors_1.default.IncorrectUsageError({
|
|
92
|
+
message: (0, tpl_1.default)(messages.invalidPathParameter, { path: fileName || targetDir || '' })
|
|
105
93
|
});
|
|
106
94
|
}
|
|
107
|
-
|
|
108
95
|
return resolvedPath;
|
|
109
96
|
}
|
|
110
|
-
|
|
111
97
|
/**
|
|
112
98
|
* Saves the file to storage (the file system)
|
|
113
99
|
* - returns a promise which ultimately returns the full url to the uploaded file
|
|
114
|
-
*
|
|
115
|
-
* @param {import('ghost-storage-base').StorageFile} file
|
|
116
|
-
* @param {string} targetDir
|
|
117
|
-
* @returns {Promise<String>}
|
|
118
100
|
*/
|
|
119
101
|
async save(file, targetDir) {
|
|
120
102
|
let targetFilename;
|
|
121
|
-
|
|
122
103
|
targetDir = targetDir
|
|
123
104
|
? this._resolveAndValidateStoragePath(targetDir)
|
|
124
105
|
: this.getTargetDir(this.storagePath);
|
|
125
|
-
|
|
126
106
|
const filename = await this.getUniqueFileName(file, targetDir);
|
|
127
|
-
|
|
128
107
|
targetFilename = filename;
|
|
129
|
-
|
|
130
|
-
|
|
108
|
+
// Verify that we are saving directly under `targetDir` and not outside of it.
|
|
109
|
+
const expectedPrefix = path_1.default.join(path_1.default.resolve(targetDir), '/');
|
|
110
|
+
if (!path_1.default.resolve(targetFilename).startsWith(expectedPrefix)) {
|
|
111
|
+
throw new errors_1.default.BadRequestError({ message: 'Cannot save to the given filename' });
|
|
112
|
+
}
|
|
113
|
+
await fs_extra_1.default.mkdirs(targetDir);
|
|
131
114
|
try {
|
|
132
|
-
await
|
|
133
|
-
}
|
|
115
|
+
await fs_extra_1.default.copy(file.path, targetFilename);
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
134
118
|
if (err.code === 'ENAMETOOLONG') {
|
|
135
|
-
throw new
|
|
119
|
+
throw new errors_1.default.BadRequestError({ err: (0, errify_1.errify)(err) });
|
|
136
120
|
}
|
|
137
|
-
|
|
138
121
|
throw err;
|
|
139
122
|
}
|
|
140
|
-
|
|
141
123
|
// The src for the image must be in URI format, not a file system path, which in Windows uses \
|
|
142
124
|
// For local file system storage can use relative path so add a slash
|
|
143
|
-
const fullUrl = (
|
|
144
|
-
urlUtils.urlJoin('/',
|
|
145
|
-
urlUtils.getSubdir(),
|
|
146
|
-
this.staticFileURLPrefix,
|
|
147
|
-
path.relative(this.storagePath, targetFilename))
|
|
148
|
-
).replace(new RegExp(`\\${path.sep}`, 'g'), '/');
|
|
149
|
-
|
|
125
|
+
const fullUrl = (url_utils_1.default.urlJoin('/', url_utils_1.default.getSubdir(), this.staticFileURLPrefix, path_1.default.relative(this.storagePath, targetFilename))).replace(new RegExp(`\\${path_1.default.sep}`, 'g'), '/');
|
|
150
126
|
return fullUrl;
|
|
151
127
|
}
|
|
152
|
-
|
|
153
128
|
/**
|
|
154
129
|
* Saves a buffer in the targetPath
|
|
155
|
-
*
|
|
156
|
-
* @param
|
|
157
|
-
* @
|
|
130
|
+
*
|
|
131
|
+
* @param buffer is an instance of Buffer
|
|
132
|
+
* @param targetPath relative path NOT including storage path to which the buffer should be written
|
|
133
|
+
* @returns a URL to retrieve the data
|
|
158
134
|
*/
|
|
159
135
|
async saveRaw(buffer, targetPath) {
|
|
160
|
-
const storagePath =
|
|
161
|
-
const targetDir =
|
|
162
|
-
|
|
163
|
-
await
|
|
164
|
-
await fs.writeFile(storagePath, buffer);
|
|
165
|
-
|
|
136
|
+
const storagePath = path_1.default.join(this.storagePath, this._normalizeStorageRelativePath(targetPath));
|
|
137
|
+
const targetDir = path_1.default.dirname(storagePath);
|
|
138
|
+
await fs_extra_1.default.mkdirs(targetDir);
|
|
139
|
+
await fs_extra_1.default.writeFile(storagePath, buffer);
|
|
166
140
|
// For local file system storage can use relative path so add a slash
|
|
167
|
-
const fullUrl = (
|
|
168
|
-
urlUtils.urlJoin('/', urlUtils.getSubdir(),
|
|
169
|
-
this.staticFileURLPrefix,
|
|
170
|
-
targetPath)
|
|
171
|
-
).replace(new RegExp(`\\${path.sep}`, 'g'), '/');
|
|
172
|
-
|
|
141
|
+
const fullUrl = (url_utils_1.default.urlJoin('/', url_utils_1.default.getSubdir(), this.staticFileURLPrefix, targetPath)).replace(new RegExp(`\\${path_1.default.sep}`, 'g'), '/');
|
|
173
142
|
return fullUrl;
|
|
174
143
|
}
|
|
175
|
-
|
|
176
144
|
/**
|
|
177
|
-
* @param
|
|
178
|
-
* @returns
|
|
145
|
+
* @param url full url under which the stored content is served, result of save method
|
|
146
|
+
* @returns relative path under which the content is stored
|
|
179
147
|
*/
|
|
180
148
|
urlToPath(url) {
|
|
181
149
|
let relativePath;
|
|
182
|
-
|
|
183
|
-
const prefix = urlUtils.urlJoin('/',
|
|
184
|
-
urlUtils.getSubdir(),
|
|
185
|
-
this.staticFileURLPrefix
|
|
186
|
-
);
|
|
187
|
-
|
|
150
|
+
const prefix = url_utils_1.default.urlJoin('/', url_utils_1.default.getSubdir(), this.staticFileURLPrefix);
|
|
188
151
|
if (url.startsWith(this.staticFileUrl)) {
|
|
189
152
|
// CASE: full path that includes the site url
|
|
190
153
|
relativePath = url.replace(this.staticFileUrl, '');
|
|
191
|
-
}
|
|
154
|
+
}
|
|
155
|
+
else if (url.startsWith(prefix)) {
|
|
192
156
|
// CASE: The result of the save method doesn't include the site url. So we need to handle this case.
|
|
193
157
|
relativePath = url.replace(prefix, '');
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
throw new errors_1.default.IncorrectUsageError({
|
|
161
|
+
message: (0, tpl_1.default)(messages.invalidUrlParameter, { url })
|
|
197
162
|
});
|
|
198
163
|
}
|
|
199
|
-
|
|
200
164
|
try {
|
|
201
165
|
return this._normalizeStorageRelativePath(relativePath);
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
|
|
166
|
+
}
|
|
167
|
+
catch (err) {
|
|
168
|
+
throw new errors_1.default.IncorrectUsageError({
|
|
169
|
+
message: (0, tpl_1.default)(messages.invalidUrlParameter, { url })
|
|
205
170
|
});
|
|
206
171
|
}
|
|
207
172
|
}
|
|
208
|
-
|
|
209
173
|
async exists(fileName, targetDir) {
|
|
210
174
|
let filePath;
|
|
211
|
-
|
|
212
175
|
try {
|
|
213
176
|
filePath = this._resolveAndValidateStoragePath(targetDir, fileName);
|
|
214
|
-
}
|
|
215
|
-
|
|
177
|
+
}
|
|
178
|
+
catch (err) {
|
|
179
|
+
if (err instanceof errors_1.default.IncorrectUsageError) {
|
|
216
180
|
return false;
|
|
217
181
|
}
|
|
218
|
-
|
|
219
182
|
throw err;
|
|
220
183
|
}
|
|
221
|
-
|
|
222
184
|
try {
|
|
223
|
-
await
|
|
185
|
+
await fs_extra_1.default.stat(filePath);
|
|
224
186
|
return true;
|
|
225
|
-
}
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
226
189
|
return false;
|
|
227
190
|
}
|
|
228
191
|
}
|
|
229
|
-
|
|
230
192
|
/**
|
|
231
193
|
* For some reason send divides the max age number by 1000
|
|
232
194
|
* Fallthrough: false ensures that if an image isn't found, it automatically 404s
|
|
233
195
|
* Wrap server static errors
|
|
234
|
-
*
|
|
235
|
-
* @returns {serveStaticContent}
|
|
236
196
|
*/
|
|
237
197
|
serve() {
|
|
238
|
-
const {storagePath, errorMessages} = this;
|
|
239
|
-
|
|
198
|
+
const { storagePath, errorMessages } = this;
|
|
240
199
|
return function serveStaticContent(req, res, next) {
|
|
241
|
-
return serveStatic(
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
fallthrough: false
|
|
246
|
-
}
|
|
247
|
-
)(req, res, (err) => {
|
|
200
|
+
return serveStatic(storagePath, {
|
|
201
|
+
maxAge: (365 * 24 * 60 * 60 * 1000), // 1 year in ms
|
|
202
|
+
fallthrough: false
|
|
203
|
+
})(req, res, (err) => {
|
|
248
204
|
if (err) {
|
|
249
205
|
if (err.statusCode === 404) {
|
|
250
|
-
return next(new
|
|
251
|
-
message:
|
|
206
|
+
return next(new errors_1.default.NotFoundError({
|
|
207
|
+
message: (0, tpl_1.default)(errorMessages.notFound),
|
|
252
208
|
code: 'STATIC_FILE_NOT_FOUND',
|
|
253
209
|
property: err.path
|
|
254
210
|
}));
|
|
255
211
|
}
|
|
256
|
-
|
|
257
212
|
if (err.statusCode === 400) {
|
|
258
|
-
return next(new
|
|
213
|
+
return next(new errors_1.default.BadRequestError({ err: err }));
|
|
259
214
|
}
|
|
260
|
-
|
|
261
215
|
if (err.statusCode === 403) {
|
|
262
|
-
return next(new
|
|
216
|
+
return next(new errors_1.default.NoPermissionError({ err: err }));
|
|
263
217
|
}
|
|
264
|
-
|
|
265
218
|
if (err.name === 'RangeNotSatisfiableError') {
|
|
266
|
-
return next(new
|
|
219
|
+
return next(new errors_1.default.RangeNotSatisfiableError({ err }));
|
|
267
220
|
}
|
|
268
|
-
|
|
269
|
-
return next(new errors.InternalServerError({err: err}));
|
|
221
|
+
return next(new errors_1.default.InternalServerError({ err: err }));
|
|
270
222
|
}
|
|
271
|
-
|
|
272
223
|
next();
|
|
273
224
|
});
|
|
274
225
|
};
|
|
275
226
|
}
|
|
276
|
-
|
|
277
|
-
/**
|
|
278
|
-
* @param {string} filePath
|
|
279
|
-
* @returns {Promise.<*>}
|
|
280
|
-
*/
|
|
281
227
|
async delete(fileName, targetDir) {
|
|
282
228
|
const filePath = this._resolveAndValidateStoragePath(targetDir, fileName);
|
|
283
|
-
return await
|
|
229
|
+
return await fs_extra_1.default.remove(filePath);
|
|
284
230
|
}
|
|
285
|
-
|
|
286
231
|
/**
|
|
287
232
|
* Reads bytes from disk for a target file
|
|
288
233
|
* - path of target file (without content path!)
|
|
289
|
-
*
|
|
290
|
-
* @param options
|
|
291
234
|
*/
|
|
292
235
|
async read(options) {
|
|
293
236
|
options = options || {};
|
|
294
|
-
|
|
295
237
|
const normalizedPath = this._normalizeStorageRelativePath(options.path);
|
|
296
|
-
const targetPath =
|
|
297
|
-
|
|
238
|
+
const targetPath = path_1.default.join(this.storagePath, normalizedPath);
|
|
298
239
|
try {
|
|
299
|
-
return await
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
|
|
240
|
+
return await fs_extra_1.default.readFile(targetPath);
|
|
241
|
+
}
|
|
242
|
+
catch (rawError) {
|
|
243
|
+
const err = (0, errify_1.errify)(rawError);
|
|
244
|
+
const code = rawError.code;
|
|
245
|
+
if (code === 'ENOENT' || code === 'ENOTDIR') {
|
|
246
|
+
throw new errors_1.default.NotFoundError({
|
|
303
247
|
err: err,
|
|
304
|
-
message:
|
|
248
|
+
message: (0, tpl_1.default)(this.errorMessages.notFoundWithRef, { file: options.path })
|
|
305
249
|
});
|
|
306
250
|
}
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
throw new errors.BadRequestError({err: err});
|
|
251
|
+
if (code === 'ENAMETOOLONG') {
|
|
252
|
+
throw new errors_1.default.BadRequestError({ err: err });
|
|
310
253
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
throw new errors.NoPermissionError({err: err});
|
|
254
|
+
if (code === 'EACCES') {
|
|
255
|
+
throw new errors_1.default.NoPermissionError({ err: err });
|
|
314
256
|
}
|
|
315
|
-
|
|
316
|
-
throw new errors.InternalServerError({
|
|
257
|
+
throw new errors_1.default.InternalServerError({
|
|
317
258
|
err: err,
|
|
318
|
-
message:
|
|
259
|
+
message: (0, tpl_1.default)(this.errorMessages.cannotRead, { file: options.path })
|
|
319
260
|
});
|
|
320
261
|
}
|
|
321
262
|
}
|
|
322
263
|
}
|
|
323
|
-
|
|
324
|
-
module.exports = LocalStorageBase;
|
|
264
|
+
exports.default = LocalStorageBase;
|