diff --git a/ghost/core/core/shared/config/helpers.js b/ghost/core/core/shared/config/helpers.js index f5b117e415..2644ae4ee3 100644 --- a/ghost/core/core/shared/config/helpers.js +++ b/ghost/core/core/shared/config/helpers.js @@ -1,3 +1,5 @@ +const crypto = require('crypto'); +const os = require('os'); const path = require('path'); const {URL} = require('url'); @@ -64,6 +66,24 @@ const isPrivacyDisabled = function isPrivacyDisabled(privacyFlag) { return this.get('privacy')[privacyFlag] === false; }; +/** @type {string|null} */ +let processTmpDirPath = null; + +/** + * Get a tmp dir path for the current process + * + * @returns {string} - tmp dir path for the current process + */ +function getProcessTmpDirPath() { + // Memoize the computed path to avoid re-computing it on each call - The + // value should not change during the lifetime of the process. + if (processTmpDirPath === null) { + processTmpDirPath = path.join(os.tmpdir(), `ghost_${crypto.randomUUID()}`); + } + + return processTmpDirPath; +} + /** * @callback getContentPathFn * @param {string} type - the type of context you want the path for @@ -88,7 +108,7 @@ const getContentPath = function getContentPath(type) { case 'settings': return path.join(this.get('paths:contentPath'), 'settings/'); case 'public': - return path.join(this.get('paths:contentPath'), 'public/'); + return path.join(getProcessTmpDirPath(this), 'public/'); default: // new Error is allowed here, as we do not want config to depend on @tryghost/error // @TODO: revisit this decision when @tryghost/error is no longer dependent on all of ghost-ignition diff --git a/ghost/core/test/unit/frontend/web/middleware/serve-public-file.test.js b/ghost/core/test/unit/frontend/web/middleware/serve-public-file.test.js index 487cd51aa6..a55c284217 100644 --- a/ghost/core/test/unit/frontend/web/middleware/serve-public-file.test.js +++ b/ghost/core/test/unit/frontend/web/middleware/serve-public-file.test.js @@ -187,6 +187,6 @@ describe('servePublicFile', function () { res.writeHead.called.should.be.true(); res.writeHead.args[0][0].should.equal(200); - fileStub.firstCall.args[0].should.endWith('content/public/something.css'); + fileStub.firstCall.args[0].should.endWith('/public/something.css'); }); }); diff --git a/ghost/core/test/unit/shared/config/helpers.test.js b/ghost/core/test/unit/shared/config/helpers.test.js index 1c211de0b0..a1210006ce 100644 --- a/ghost/core/test/unit/shared/config/helpers.test.js +++ b/ghost/core/test/unit/shared/config/helpers.test.js @@ -1,6 +1,10 @@ +const os = require('os'); const should = require('should'); + const configUtils = require('../../../utils/configUtils'); +const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/; + describe('vhost utils', function () { beforeEach(function () { configUtils.set('url', 'http://ghost.blog'); @@ -52,3 +56,29 @@ describe('vhost utils', function () { }); }); }); + +describe('getContentPath', function () { + it('should return the correct path for type: public', function () { + const publicPath = configUtils.config.getContentPath('public'); + + // Path should be in the tmpdir + const tmpdir = os.tmpdir(); + + publicPath.startsWith(tmpdir).should.be.true(); + + // Path should end with /public/ + publicPath.endsWith('/public/').should.be.true(); + + // Path should include /ghost_ + publicPath.includes('/ghost_').should.be.true(); + + // Path should contain a uuid at the correct location + const publicPathParts = publicPath.split('/'); + const uuidPart = publicPathParts[publicPathParts.length - 3].replace('ghost_', ''); + + UUID_REGEX.test(uuidPart).should.be.true(); + + // Path should be memoized + configUtils.config.getContentPath('public').should.eql(publicPath); + }); +});