mod_provider__provider.js

/**
## /provider

The provider module imports the cloudinary, file, and s3 provider modules and exports a provider method that looks up a provider module method matching the provider request parameter and passes the req/res objects as argument to the matched method.

@requires /cloudinary
@requires /file
@requires /s3

@module /provider
*/

import cloudfront from './cloudfront.js';
import file from './file.js';
import s3 from './s3.js';

const providerModules = {
  cloudfront,
  file,
  s3,
};
const allowedContentTypes = new Set(['application/json', 'text/plain']);

/**
@function provider
@async

@description
The provider method looks up a provider module method matching the provider request parameter and passes the req/res objects as argument to the matched method.

The response from the method is parsed as JSON if the response is an object.

The default content type is `text/plain` but can be overridden by the `content_type` request parameter if the value is in the allowed content types list.

The content type is derived from the requested resource if the URL ends with a JavaScript extension.

@param {req} req HTTP request.
@param {Object} res HTTP response.
@param {Object} req.params Request parameter.
@param {string} params.provider Provider module to handle the request.

@returns {Promise} The promise resolves into the response from the provider modules method.
*/
export default async function provider(req, res) {
  if (!Object.hasOwn(providerModules, req.params.provider)) {
    return res.status(404).send('Failed to validate provider param.');
  }

  if (providerModules[req.params.provider] === null) {
    return res.status(405).send('Provider is not configured.');
  }

  const response = await providerModules[req.params.provider](req, res);

  res.setHeader('X-Content-Type-Options', 'nosniff');

  if (response instanceof Error) {
    return res.status(500).send('Provider request failed.');
  }

  if (typeof response === 'object') {
    return res.json(response);
  }

  let contentType = 'text/plain';

  // Executable MIME types must be derived from the requested resource rather than user input.
  if (req.params.url?.match(/\.m?js(?:[?#].*)?$/i)) {
    contentType = 'text/javascript';
  } else if (allowedContentTypes.has(req.params.content_type)) {
    contentType = req.params.content_type;
  }

  res.setHeader('content-type', contentType);

  // Provider endpoints intentionally return static resource bytes with a fixed MIME type and nosniff.
  res.send(response); // NOSONAR
}