mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-07 20:31:06 -05:00
Merge pull request #6523 from overleaf/jpa-translations-check-sanitize
[web] scripts/translations: add script for checking html sanitization GitOrigin-RevId: d4b9c9a7eb1ed0ca9202b0cb6e4c33f3e73bd0e4
This commit is contained in:
parent
64423936b9
commit
58cf92620a
3 changed files with 88 additions and 43 deletions
42
services/web/scripts/translations/checkSanitizeOptions.js
Normal file
42
services/web/scripts/translations/checkSanitizeOptions.js
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
const Path = require('path')
|
||||||
|
const fs = require('fs')
|
||||||
|
const { sanitize } = require('./sanitize')
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
let ok = true
|
||||||
|
const base = Path.join(__dirname, '/../../locales')
|
||||||
|
for (const name of await fs.promises.readdir(base)) {
|
||||||
|
if (name === 'README.md') continue
|
||||||
|
const blob = await fs.promises.readFile(
|
||||||
|
Path.join(__dirname, '/../../locales', name),
|
||||||
|
'utf-8'
|
||||||
|
)
|
||||||
|
const locales = JSON.parse(blob)
|
||||||
|
|
||||||
|
for (const key of Object.keys(locales)) {
|
||||||
|
const want = locales[key]
|
||||||
|
const got = sanitize(locales[key])
|
||||||
|
if (got !== want) {
|
||||||
|
if (want === 'Editor & PDF' && got === 'Editor & PDF') {
|
||||||
|
// Ignore this mismatch. React cannot handle escaped labels.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ok = false
|
||||||
|
console.warn(`${name}: ${key}: want: ${want}`)
|
||||||
|
console.warn(`${name}: ${key}: got: ${got}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!ok) {
|
||||||
|
throw new Error('Check the logs, some values changed.')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
||||||
|
.then(() => {
|
||||||
|
process.exit(0)
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error(error)
|
||||||
|
process.exit(1)
|
||||||
|
})
|
|
@ -1,7 +1,7 @@
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const { promises: fs } = require('fs')
|
const { promises: fs } = require('fs')
|
||||||
const oneSky = require('@brainly/onesky-utils')
|
const oneSky = require('@brainly/onesky-utils')
|
||||||
const sanitizeHtml = require('sanitize-html')
|
const { sanitize } = require('./sanitize')
|
||||||
const { withAuth } = require('./config')
|
const { withAuth } = require('./config')
|
||||||
|
|
||||||
async function run() {
|
async function run() {
|
||||||
|
@ -46,45 +46,3 @@ async function run() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
run()
|
run()
|
||||||
|
|
||||||
/**
|
|
||||||
* Sanitize a translation string to prevent injection attacks
|
|
||||||
*
|
|
||||||
* @param {string} input
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
function sanitize(input) {
|
|
||||||
// Block Angular XSS
|
|
||||||
// Ticket: https://github.com/overleaf/issues/issues/4478
|
|
||||||
input = input.replace(/'/g, '’')
|
|
||||||
// Use left quote where (likely) appropriate.
|
|
||||||
input.replace(/ ’/g, ' ‘')
|
|
||||||
|
|
||||||
return sanitizeHtml(input, {
|
|
||||||
// Allow "replacement" tags (in the format <0>, <1>, <2>, etc) used by
|
|
||||||
// react-i18next to allow for HTML insertion via the Trans component.
|
|
||||||
// See: https://github.com/overleaf/developer-manual/blob/master/code/translations.md
|
|
||||||
// Unfortunately the sanitizeHtml library does not accept regexes or a
|
|
||||||
// function for the allowedTags option, so we are limited to a hard-coded
|
|
||||||
// number of "replacement" tags.
|
|
||||||
allowedTags: ['b', 'strong', 'a', 'code', ...range(10)],
|
|
||||||
allowedAttributes: {
|
|
||||||
a: ['href', 'class'],
|
|
||||||
},
|
|
||||||
textFilter(text) {
|
|
||||||
return text
|
|
||||||
.replace(/\{\{/, '{{')
|
|
||||||
.replace(/\}\}/, '}}')
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a range of numbers as strings up to the given size
|
|
||||||
*
|
|
||||||
* @param {number} size Size of range
|
|
||||||
* @returns {string[]}
|
|
||||||
*/
|
|
||||||
function range(size) {
|
|
||||||
return Array.from(Array(size).keys()).map(n => n.toString())
|
|
||||||
}
|
|
||||||
|
|
45
services/web/scripts/translations/sanitize.js
Normal file
45
services/web/scripts/translations/sanitize.js
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
const sanitizeHtml = require('sanitize-html')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sanitize a translation string to prevent injection attacks
|
||||||
|
*
|
||||||
|
* @param {string} input
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
function sanitize(input) {
|
||||||
|
// Block Angular XSS
|
||||||
|
// Ticket: https://github.com/overleaf/issues/issues/4478
|
||||||
|
input = input.replace(/'/g, '’')
|
||||||
|
// Use left quote where (likely) appropriate.
|
||||||
|
input.replace(/ ’/g, ' ‘')
|
||||||
|
|
||||||
|
return sanitizeHtml(input, {
|
||||||
|
// Allow "replacement" tags (in the format <0>, <1>, <2>, etc) used by
|
||||||
|
// react-i18next to allow for HTML insertion via the Trans component.
|
||||||
|
// See: https://github.com/overleaf/developer-manual/blob/master/code/translations.md
|
||||||
|
// Unfortunately the sanitizeHtml library does not accept regexes or a
|
||||||
|
// function for the allowedTags option, so we are limited to a hard-coded
|
||||||
|
// number of "replacement" tags.
|
||||||
|
allowedTags: ['b', 'strong', 'a', 'code', ...range(10)],
|
||||||
|
allowedAttributes: {
|
||||||
|
a: ['href', 'class'],
|
||||||
|
},
|
||||||
|
textFilter(text) {
|
||||||
|
return text
|
||||||
|
.replace(/\{\{/, '{{')
|
||||||
|
.replace(/\}\}/, '}}')
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a range of numbers as strings up to the given size
|
||||||
|
*
|
||||||
|
* @param {number} size Size of range
|
||||||
|
* @returns {string[]}
|
||||||
|
*/
|
||||||
|
function range(size) {
|
||||||
|
return Array.from(Array(size).keys()).map(n => n.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { sanitize }
|
Loading…
Reference in a new issue