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
This commit is contained in:
M Fahru 2023-02-09 10:04:58 -07:00 committed by Copybot
parent 9719b0439c
commit 811b224d81
3 changed files with 38 additions and 38 deletions

View file

@ -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 }

View file

@ -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)

View file

@ -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()
}