ghost/api-framework/lib/headers.js
const url = require('url');
const debug = require('@tryghost/debug')('headers');
const INVALIDATE_ALL = '/*';
const cacheInvalidate = (result, options = {}) => {
let value = options.value;
return {
'X-Cache-Invalidate': value || INVALIDATE_ALL
};
};
const disposition = {
/**
* @description Generate CSV header.
*
* @param {Object} result - API response
* @param {Object} options
* @return {Object}
*/
csv(result, options = {}) {
let value = options.value;
if (typeof options.value === 'function') {
value = options.value();
}
return {
'Content-Disposition': `Attachment; filename="${value}"`,
'Content-Type': 'text/csv'
};
},
/**
* @description Generate JSON header.
*
* @param {Object} result - API response
* @param {Object} options
* @return {Object}
*/
json(result, options = {}) {
return {
'Content-Disposition': `Attachment; filename="${options.value}"`,
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(JSON.stringify(result))
};
},
/**
* @description Generate YAML header.
*
* @param {Object} result - API response
* @param {Object} options
* @return {Object}
*/
yaml(result, options = {}) {
return {
'Content-Disposition': `Attachment; filename="${options.value}"`,
'Content-Type': 'application/yaml',
'Content-Length': Buffer.byteLength(JSON.stringify(result))
};
},
/**
* @description Content Disposition Header
*
* Create a header that invokes the 'Save As' dialog in the browser when exporting the database to file. The 'filename'
* parameter is governed by [RFC6266](http://tools.ietf.org/html/rfc6266#section-4.3).
*
* For encoding whitespace and non-ISO-8859-1 characters, you MUST use the "filename*=" attribute, NOT "filename=".
* Ideally, both. Examples: http://tools.ietf.org/html/rfc6266#section-5
*
* We'll use ISO-8859-1 characters here to keep it simple.
*
* @see http://tools.ietf.org/html/rfc598
*/
file(result, options = {}) {
return Promise.resolve()
.then(() => {
let value = options.value;
if (typeof options.value === 'function') {
value = options.value();
}
return value;
})
.then((filename) => {
return {
'Content-Disposition': `Attachment; filename="${filename}"`
};
});
}
};
module.exports = {
/**
* @description Get header based on ctrl configuration.
*
* @param {Object} result - API response
* @param {Object} apiConfigHeaders
* @param {import('@tryghost/api-framework').Frame} frame
* @return {Promise<object>}
*/
async get(result, apiConfigHeaders = {}, frame) {
let headers = {};
if (apiConfigHeaders.disposition) {
const dispositionHeader = await disposition[apiConfigHeaders.disposition.type](result, apiConfigHeaders.disposition);
if (dispositionHeader) {
Object.assign(headers, dispositionHeader);
}
}
if (apiConfigHeaders.cacheInvalidate) {
const cacheInvalidationHeader = cacheInvalidate(result, apiConfigHeaders.cacheInvalidate);
if (cacheInvalidationHeader) {
Object.assign(headers, cacheInvalidationHeader);
}
}
const locationHeaderDisabled = apiConfigHeaders?.location === false;
const hasLocationResolver = apiConfigHeaders?.location?.resolve;
const hasFrameData = (frame?.method === 'add' || hasLocationResolver) && result[frame.docName]?.[0]?.id;
if (!locationHeaderDisabled && hasFrameData) {
const protocol = (frame.original.url.secure === false) ? 'http://' : 'https://';
const resourceId = result[frame.docName][0].id;
let locationURL = url.resolve(`${protocol}${frame.original.url.host}`,frame.original.url.pathname);
if (!locationURL.endsWith('/')) {
locationURL += '/';
}
locationURL += `${resourceId}/`;
if (hasLocationResolver) {
locationURL = apiConfigHeaders.location.resolve(locationURL);
}
const locationHeader = {
Location: locationURL
};
Object.assign(headers, locationHeader);
}
const headersFromFrame = frame.getHeaders();
Object.assign(headers, headersFromFrame);
debug(headers);
return headers;
}
};