From 811b224d81b8c9cdc8deb0fe0b37831746a2d40b Mon Sep 17 00:00:00 2001 From: M Fahru Date: Thu, 9 Feb 2023 10:04:58 -0700 Subject: [PATCH] Update `req.i18n.translate` to contains better filtering security and inject `appName` variable by default to all translation keys (#11312) Also remove the unused translate wrapper function since the content has been imported directly in the `req.i18n.translate` GitOrigin-RevId: ed9cee76783e4d41819845a82f66afaed47e2ebd --- .../web/app/src/Features/Helpers/Translate.js | 34 ----------------- .../app/src/infrastructure/ExpressLocals.js | 5 +-- .../app/src/infrastructure/Translations.js | 37 ++++++++++++++++++- 3 files changed, 38 insertions(+), 38 deletions(-) delete mode 100644 services/web/app/src/Features/Helpers/Translate.js diff --git a/services/web/app/src/Features/Helpers/Translate.js b/services/web/app/src/Features/Helpers/Translate.js deleted file mode 100644 index 03b6b78a56..0000000000 --- a/services/web/app/src/Features/Helpers/Translate.js +++ /dev/null @@ -1,34 +0,0 @@ -const Settings = require('@overleaf/settings') -const pug = require('pug-runtime') -const logger = require('@overleaf/logger') -const SafeHTMLSubstitute = require('./SafeHTMLSubstitution') -const I18N_HTML_INJECTIONS = new Set() - -function translate(key, req, vars, components) { - vars = vars || {} - - if (Settings.i18n.checkForHTMLInVars) { - Object.entries(vars).forEach(([field, value]) => { - if (pug.escape(value) !== value) { - const violationsKey = key + field - // do not flood the logs, log one sample per pod + key + field - if (!I18N_HTML_INJECTIONS.has(violationsKey)) { - logger.warn( - { key, field, value }, - 'html content in translations context vars' - ) - I18N_HTML_INJECTIONS.add(violationsKey) - } - } - }) - } - - vars.appName = Settings.appName - const locale = req.i18n.translate(key, vars) - if (components) { - return SafeHTMLSubstitute.render(locale, components) - } else { - return locale - } -} -module.exports = { translate } diff --git a/services/web/app/src/infrastructure/ExpressLocals.js b/services/web/app/src/infrastructure/ExpressLocals.js index ebc227a4f0..f73ccd6f07 100644 --- a/services/web/app/src/infrastructure/ExpressLocals.js +++ b/services/web/app/src/infrastructure/ExpressLocals.js @@ -18,7 +18,6 @@ const { const { addOptionalCleanupHandlerAfterDrainingConnections, } = require('./GracefulShutdown') -const { translate } = require('../Features/Helpers/Translate') const IEEE_BRAND_ID = Settings.ieeeBrandId @@ -227,8 +226,8 @@ module.exports = function (webRouter, privateApiRouter, publicApiRouter) { }) webRouter.use(function (req, res, next) { - res.locals.translate = (key, vars, components) => - translate(key, req, vars, components) + res.locals.translate = req.i18n.translate + // Don't include the query string parameters, otherwise Google // treats ?nocdn=true as the canonical version const parsedOriginalUrl = new URL(req.originalUrl, Settings.siteUrl) diff --git a/services/web/app/src/infrastructure/Translations.js b/services/web/app/src/infrastructure/Translations.js index 81f6905d8e..4adffc04e8 100644 --- a/services/web/app/src/infrastructure/Translations.js +++ b/services/web/app/src/infrastructure/Translations.js @@ -4,11 +4,16 @@ const middleware = require('i18next-http-middleware') const path = require('path') const Settings = require('@overleaf/settings') const { URL } = require('url') +const pug = require('pug-runtime') +const logger = require('@overleaf/logger') +const SafeHTMLSubstitution = require('../Features/Helpers/SafeHTMLSubstitution') const fallbackLanguageCode = Settings.i18n.defaultLng || 'en' const availableLanguageCodes = [] const availableHosts = new Map() const subdomainConfigs = new Map() +const I18N_HTML_INJECTIONS = new Set() + Object.values(Settings.i18n.subdomainLang || {}).forEach(function (spec) { availableLanguageCodes.push(spec.lngCode) // prebuild a host->lngCode mapping for the usage at runtime in the @@ -50,6 +55,10 @@ i18n // Disable nesting in interpolated values, preventing user input // injection via another nested value skipOnVariables: true, + + defaultVariables: { + appName: Settings.appName, + }, }, preload: availableLanguageCodes, @@ -83,7 +92,33 @@ function setLangBasedOnDomainMiddleware(req, res, next) { // Decorate req.i18n with translate function alias for backwards // compatibility usage in requests - req.i18n.translate = req.i18n.t + req.i18n.translate = (key, vars, components) => { + vars = vars || {} + + if (Settings.i18n.checkForHTMLInVars) { + Object.entries(vars).forEach(([field, value]) => { + if (pug.escape(value) !== value) { + const violationsKey = key + field + // do not flood the logs, log one sample per pod + key + field + if (!I18N_HTML_INJECTIONS.has(violationsKey)) { + logger.warn( + { key, field, value }, + 'html content in translations context vars' + ) + I18N_HTML_INJECTIONS.add(violationsKey) + } + } + }) + } + + const locale = req.i18n.t(key, vars) + if (components) { + return SafeHTMLSubstitution.render(locale, components) + } else { + return locale + } + } + next() }