/**
* Echappe tous les tags html (et les &xx;) autorisés et vire le reste
* Il faudra passer le résultat à resumeHtml avant de l'utiliser
* @param {string} ch
* @return {string}
*/
export function escHtml (ch) {
if (!ch) return ''
return ch
// lui n’a pas besoin d’un tag car le \n est géré
.replace(/<br *\/?>/gi, '\n')
// il y a des tonnes de sections qui envoient du html, qu’il faut conserver… on liste qq tags autorisés
// tag auto fermant
.replace(/<hr(\s+\/)?>/gi, '¿¿¿¿hr¡¡¡¡')
// tags sans attributes (*? pour être "non gourmand" => s’arrêter au premier </tagFermant> trouvé et pas au dernier)
// on utilise [\s\S] pour faire du .* avec /s, car les navigateurs comprennent pas tous le flag s (pour que . match aussi \n)
.replace(/<(b|em|i|strong|sub|sup|u)>([\s\S]*?)<\/\1>/gi, (match, tag, content) => `¿¿¿¿${tag}¡¡¡¡${content}¿¿¿¿/${tag}¡¡¡¡`)
// on autorise img|p|span avec attributs, mais pas n’importe lesquels (surtout pas onload par ex, ça permettrait d’exécuter du js arbitraire)
.replace(/<img +((?:[^>"']|"[^"]*"|'[^']*')*)\/?>/gi, (match, strAttrs) => {
if (strAttrs) return `¿¿¿¿img ${cleanAttributes(strAttrs)}/¡¡¡¡`
console.error(Error('tag <img /> sans attributs'))
return ''
})
.replace(/<(p|span) +((?:[^>"']|"[^"]*"|'[^']*')*)>([\s\S]*?)<\/\1>/gi, (match, tag, strAttrs, content) => {
if (strAttrs) return `¿¿¿¿${tag} ${cleanAttributes(strAttrs)}¡¡¡¡${content}¿¿¿¿/${tag}¡¡¡¡`
return `¿¿¿¿${tag}¡¡¡¡${content}¿¿¿¿/${tag}¡¡¡¡`
})
.replace(/&(nbsp|lt|gt|dollar);/g, (match, chunk) => '¿¿¿' + chunk + '¡¡¡')
.replace(/&#([0-9]+);/g, (match, chunk) => '¿¿¡' + chunk + '¡¡¡')
}
/**
* Remet les tags et codes html autorisés
* @param {string} ch
* @return {string}
*/
export function resumeHtml (ch) {
if (!ch) return ''
return ch
.replace(/¿¿¿¿/g, '<')
.replace(/¡¡¡¡/g, '>')
.replace(/¿¿¿/g, '&')
.replace(/¿¿¡/g, '&#')
.replace(/¡¡¡/g, ';')
.replace(/\n/g, '<br>') // on remplace les éventuels \n mis dans contenu au départ
}
/**
* Contrôle les attributs qu’on autorise (class|width|height) et supprime les autres (faut pas de style ni de onload="js arbitraire")
* @private
* @param {string} strAttrs liste d’attributs x="y"
* @returns {string} les attributs autorisés
*/
function cleanAttributes (strAttrs) {
if (!strAttrs?.trim?.()) return ''
let cleanAttrs = ''
// Une seule passe : on enlève du "reste" tout ce qu'on accepte,
// et on construit cleanAttrs au fil de l'eau.
const allowed = /\b(alt|class|height|src|tabindex|title|width)\s*=\s*(["'])(.*?)\2/gi
const reste = strAttrs.replace(allowed, (match, name, quote, value) => {
const attrName = String(name).toLowerCase()
// Filtrage minimal anti-XSS sur src
if (attrName === 'src') {
const v = String(value).trim()
if (/^(?:javascript|vbscript|data):/i.test(v)) {
console.warn(Error(`attribut src refusé (schéma interdit) : ${v}`))
return ''
}
}
// On garde exactement match (ça préserve les quotes originales au cas où le contenu contiendrait l'autre quote)
cleanAttrs += ` ${match.trim()}`
return ''
})
// On ignore les espaces, et on tolère juste un "/" résiduel (cas <img ... />)
const resteCompact = reste.replace(/\s+/g, '')
if (resteCompact && resteCompact !== '/') {
console.warn(Error(`attributs ignorés : ${resteCompact}`))
}
return cleanAttrs
}