export function prefixMiddleware()

in src/amo/middleware/prefixMiddleware.js [13:123]


export function prefixMiddleware(req, res, next, { _config = config } = {}) {
  const URLParts = req.originalUrl.split('?');

  // Split on slashes after removing the leading slash.
  const URLPathParts = URLParts[0].replace(/^\//, '').split('/');
  log.debug(`path: ${URLParts[0]}, URLPathParts: [${[URLPathParts]}]`);

  // Get the application from the UA in case one wasn't specified in the URL (or
  // if it turns out to be invalid).
  const userAgentApp = getClientApp(req.headers['user-agent']);
  let isApplicationFromHeader = false;
  // Get language from URL or fall-back to detecting it from accept-language
  // header.
  const acceptLanguage = req.headers['accept-language'];
  const { lang } = getLanguage({
    lang: URLPathParts[0],
    acceptLanguage,
  });

  const hasValidLang = isValidLang(URLPathParts[0]);
  const hasValidClientAppInPartOne = isValidClientApp(URLPathParts[0], {
    _config,
  });
  const hasValidClientAppInPartTwo = isValidClientApp(URLPathParts[1], {
    _config,
  });
  const hasValidClientAppUrlExceptionInPartTwo = isValidClientAppUrlException(
    URLPathParts[1],
    {
      _config,
    },
  );

  // "Fix" URLPathParts to always start /locale/clientApp/
  if (hasValidLang && hasValidClientAppInPartTwo) {
    log.debug('URL already has a valid lang and clientApp, nothing to fix');
  } else if (hasValidLang) {
    log.debug(`Prepending clientApp to URL: ${userAgentApp}`);
    URLPathParts.splice(1, 0, userAgentApp);
    isApplicationFromHeader = true;
  } else if (hasValidClientAppInPartOne) {
    log.debug(`Prepending lang to URL: ${lang}`);
    URLPathParts.splice(0, 0, lang);
  } else if (
    hasValidClientAppInPartTwo ||
    hasValidClientAppUrlExceptionInPartTwo
  ) {
    log.debug(`Replacing lang in URL: ${URLPathParts[0]} with ${lang}`);
    URLPathParts.splice(0, 1, lang);
  } else {
    log.debug(`Prepending lang and clientApp to URL: ${lang}/${userAgentApp}`);
    URLPathParts.splice(0, 0, lang, userAgentApp);
    isApplicationFromHeader = true;
  }

  const hasValidLocaleException = isValidLocaleUrlException(URLPathParts[2], {
    _config,
  });
  const hasValidClientAppUrlException = isValidClientAppUrlException(
    URLPathParts[2],
    {
      _config,
    },
  );
  if (hasValidClientAppUrlException) {
    isApplicationFromHeader = false;
  }

  // Now drop lang and clientApp if required from URLPaths
  if (hasValidLocaleException && hasValidClientAppUrlException) {
    log.debug('URL is a locale and a clientApp exception.');
    URLPathParts.splice(0, 2);
  } else if (hasValidLocaleException) {
    log.debug('URL is a locale exception.');
    URLPathParts.splice(0, 1);
  } else if (hasValidClientAppUrlException) {
    log.debug('URL is a clientApp exception.');
    URLPathParts.splice(1, 1);
  }

  // Redirect to the new URL.
  // For safety we'll deny a redirect to a URL starting with '//' since
  // that will be treated as a protocol-free URL.
  URLParts[0] = `/${URLPathParts.join('/')}`;
  const newURL = URLParts.join('?');

  if (newURL !== req.originalUrl && !newURL.startsWith('//')) {
    // Collect vary headers to apply to the redirect
    // so we can make it cacheable.
    res.vary('accept-language');
    if (isApplicationFromHeader) {
      res.vary('user-agent');
    }
    res.set('Cache-Control', [`max-age=${ONE_YEAR_IN_SECONDS}`]);
    return res.redirect(301, newURL);
  }

  // Add the data to res.locals to be utilised later.
  /* eslint-disable no-param-reassign */
  const [newLang, newApp] = URLPathParts;
  res.locals.lang = newLang;
  // The newApp part of the URL might not be a client application
  // so it's important to re-check that here before assuming it's good.
  res.locals.clientApp = isValidClientApp(newApp) ? newApp : userAgentApp;
  // Get detailed info on the current user agent so we can make sure add-ons
  // are compatible with the current clientApp/version combo.
  res.locals.userAgent = req.headers['user-agent'];
  /* eslint-enable no-param-reassign */

  return next();
}