2018-09-20 16:03:33 +03:00
|
|
|
const crypto = require('crypto');
|
|
|
|
const fs = require('fs-extra');
|
|
|
|
const path = require('path');
|
2020-04-09 21:40:00 +03:00
|
|
|
const errors = require('@tryghost/errors');
|
2021-10-16 18:26:05 +03:00
|
|
|
const config = require('../../../shared/config');
|
|
|
|
const urlUtils = require('../../../shared/url-utils');
|
2021-10-05 12:00:28 +03:00
|
|
|
const tpl = require('@tryghost/tpl');
|
|
|
|
|
|
|
|
const messages = {
|
2021-11-04 14:54:22 +03:00
|
|
|
imageNotFound: 'Image not found',
|
|
|
|
fileNotFound: 'File not found'
|
2021-10-05 12:00:28 +03:00
|
|
|
};
|
2015-07-15 19:01:23 +03:00
|
|
|
|
2021-11-19 12:43:48 +03:00
|
|
|
function createPublicFileMiddleware(location, file, mime, maxAge) {
|
2018-09-10 15:07:57 +03:00
|
|
|
let content;
|
2021-11-19 12:43:48 +03:00
|
|
|
// These files are provided by Ghost, and therefore live inside of the core folder
|
|
|
|
const staticFilePath = config.get('paths').publicFilePath;
|
|
|
|
// These files are built on the fly, and must be saved in the content folder
|
|
|
|
const builtFilePath = config.getContentPath('public');
|
|
|
|
|
|
|
|
let locationPath = location === 'static' ? staticFilePath : builtFilePath;
|
|
|
|
|
|
|
|
const filePath = file.match(/^public/) ? path.join(locationPath, file.replace(/^public/, '')) : path.join(locationPath, file);
|
2018-09-20 16:03:33 +03:00
|
|
|
const blogRegex = /(\{\{blog-url\}\})/g;
|
2015-12-15 13:41:53 +03:00
|
|
|
|
2020-10-20 02:02:56 +03:00
|
|
|
return function servePublicFileMiddleware(req, res, next) {
|
2019-04-23 17:24:33 +03:00
|
|
|
if (content) {
|
|
|
|
res.writeHead(200, content.headers);
|
|
|
|
return res.end(content.body);
|
|
|
|
}
|
2020-02-10 12:51:19 +03:00
|
|
|
|
|
|
|
// send image files directly and let express handle content-length, etag, etc
|
2021-11-19 12:43:48 +03:00
|
|
|
if (mime.match(/^image/)) {
|
2020-02-10 12:51:19 +03:00
|
|
|
return res.sendFile(filePath, (err) => {
|
|
|
|
if (err && err.status === 404) {
|
|
|
|
// ensure we're triggering basic asset 404 and not a templated 404
|
2020-04-09 21:40:00 +03:00
|
|
|
return next(new errors.NotFoundError({
|
2021-10-05 12:00:28 +03:00
|
|
|
message: tpl(messages.imageNotFound),
|
2020-02-10 12:51:19 +03:00
|
|
|
code: 'STATIC_FILE_NOT_FOUND',
|
|
|
|
property: err.path
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
2020-02-17 02:24:01 +03:00
|
|
|
if (err) {
|
|
|
|
return next(err);
|
|
|
|
}
|
2020-02-10 12:51:19 +03:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// modify text files before caching+serving to ensure URL placeholders are transformed
|
2019-04-23 17:24:33 +03:00
|
|
|
fs.readFile(filePath, (err, buf) => {
|
|
|
|
if (err) {
|
2021-11-04 14:54:22 +03:00
|
|
|
// Downgrade to a simple 404 if the file didn't exist
|
|
|
|
if (err.code === 'ENOENT') {
|
|
|
|
err = new errors.NotFoundError({
|
|
|
|
message: tpl(messages.fileNotFound),
|
|
|
|
code: 'PUBLIC_FILE_NOT_FOUND',
|
|
|
|
property: err.path
|
|
|
|
});
|
|
|
|
}
|
2019-04-23 17:24:33 +03:00
|
|
|
return next(err);
|
2015-07-15 19:01:23 +03:00
|
|
|
}
|
2019-04-23 17:24:33 +03:00
|
|
|
|
|
|
|
let str = buf.toString();
|
|
|
|
|
2021-11-19 12:43:48 +03:00
|
|
|
if (mime === 'text/xsl' || mime === 'text/plain' || mime === 'application/javascript') {
|
2019-06-18 16:13:55 +03:00
|
|
|
str = str.replace(blogRegex, urlUtils.urlFor('home', true).replace(/\/$/, ''));
|
2019-04-23 17:24:33 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
content = {
|
|
|
|
headers: {
|
2021-11-19 12:43:48 +03:00
|
|
|
'Content-Type': mime,
|
2019-04-23 17:24:33 +03:00
|
|
|
'Content-Length': Buffer.from(str).length,
|
|
|
|
ETag: `"${crypto.createHash('md5').update(str, 'utf8').digest('hex')}"`,
|
|
|
|
'Cache-Control': `public, max-age=${maxAge}`
|
|
|
|
},
|
|
|
|
body: str
|
|
|
|
};
|
|
|
|
res.writeHead(200, content.headers);
|
|
|
|
res.end(content.body);
|
|
|
|
});
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// ### servePublicFile Middleware
|
|
|
|
// Handles requests to robots.txt and favicon.ico (and caches them)
|
2021-11-19 12:43:48 +03:00
|
|
|
function servePublicFile(location, file, type, maxAge) {
|
|
|
|
const publicFileMiddleware = createPublicFileMiddleware(location, file, type, maxAge);
|
2019-04-23 17:24:33 +03:00
|
|
|
|
2020-10-20 02:02:56 +03:00
|
|
|
return function servePublicFileMiddleware(req, res, next) {
|
2019-04-23 17:24:33 +03:00
|
|
|
if (req.path === '/' + file) {
|
|
|
|
return publicFileMiddleware(req, res, next);
|
2015-07-15 19:01:23 +03:00
|
|
|
} else {
|
2017-11-01 16:44:54 +03:00
|
|
|
return next();
|
2015-07-15 19:01:23 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2017-04-07 15:21:41 +03:00
|
|
|
module.exports = servePublicFile;
|
2019-04-23 17:24:33 +03:00
|
|
|
module.exports.servePublicFile = servePublicFile;
|
|
|
|
module.exports.createPublicFileMiddleware = createPublicFileMiddleware;
|