Quel est le moyen le plus simple d'échapper au HTML en Python?

137

cgi.escape semble être un choix possible. Ça marche bien? Y a-t-il quelque chose qui est considéré comme meilleur?

Josh Gibson
la source

Réponses:

176

cgi.escapec'est bien. Il s'échappe:

  • < à &lt;
  • > à &gt;
  • & à &amp;

Cela suffit pour tout HTML.

EDIT: Si vous avez des caractères non-ascii que vous souhaitez également échapper, pour l'inclusion dans un autre document encodé qui utilise un encodage différent, comme le dit Craig , utilisez simplement:

data.encode('ascii', 'xmlcharrefreplace')

N'oubliez pas de décoder dataen unicodepremier, en utilisant le codage pour lequel il a été encodé.

Cependant, d'après mon expérience, ce type d'encodage est inutile si vous travaillez avec unicodetout le temps depuis le début. Encodez simplement à la fin le codage spécifié dans l'en-tête du document ( utf-8pour une compatibilité maximale).

Exemple:

>>> cgi.escape(u'<a>bá</a>').encode('ascii', 'xmlcharrefreplace')
'&lt;a&gt;b&#225;&lt;/a&gt;

Il convient également de noter (merci Greg) le quoteparamètre supplémentaire cgi.escapeprend. Lorsqu'il est défini sur True, cgi.escapeéchappe également les caractères entre guillemets doubles ( ") afin que vous puissiez utiliser la valeur résultante dans un attribut XML / HTML.

EDIT: Notez que cgi.escape est obsolète dans Python 3.2 au profit de html.escape, qui fait la même chose sauf que la valeur par quotedéfaut est True.

nosklo
la source
7
Le paramètre booléen supplémentaire de cgi.escape doit également être pris en compte pour les guillemets d'échappement lorsque du texte est utilisé dans les valeurs d'attribut HTML.
Greg Hewgill
Juste pour être sûr: si j'exécute toutes les données non fiables via la cgi.escapefonction, est-ce suffisant pour me protéger contre tous les attacs XSS (connus)?
Tomas Sedovic
@Tomas Sedovic: Cela dépend de l'endroit où vous placerez le texte après y avoir exécuté cgi.escape. S'il est placé dans un contexte HTML racine, alors oui, vous êtes complètement en sécurité.
nosklo
Qu'en est-il d'une entrée comme {{Mesure 12 Ω "H x 17 5/8" L x 8 7/8 "D. Importé.}} Ce n'est pas ascii, donc encode () vous lancera une exception.
Andrew Kolesnikov
@Andrew Kolesnikov: Avez-vous essayé? cgi.escape(yourunicodeobj).encode('ascii', 'xmlcharrefreplace') == '{{Measures 12 &#937;"H x 17 5/8"W x 8 7/8"D. Imported.}}'- comme vous pouvez le voir, l'expression renvoie une chaîne d'octets ascii, avec tous les caractères unicode non-ascii codés à l'aide de la table de référence des caractères xml.
nosklo
112

Dans Python 3.2, un nouveau htmlmodule a été introduit, qui est utilisé pour échapper les caractères réservés du balisage HTML.

Il a une fonction escape():

>>> import html
>>> html.escape('x > 2 && x < 7 single quote: \' double quote: "')
'x &gt; 2 &amp;&amp; x &lt; 7 single quote: &#x27; double quote: &quot;'
Maciej Ziarko
la source
Et quoi quote=True?
2rs2ts
1
@SalmanAbbas Avez-vous peur que les citations ne soient pas échappées? Notez que les html.escape()guillemets d'échappement, par défaut (en revanche, cgi.quote()ne le font pas - et n'échappent que les guillemets doubles, si cela est dit). Ainsi, je dois définir explicitement un paramètre optionnel avec lequel injecter quelque chose dans un attribut html.escape(), c'est-à-dire pour le rendre non sécurisé pour les attributs:t = '" onclick="alert()'; t = html.escape(t, quote=False); s = f'<a href="about.html" class="{t}">foo</a>'
maxschlepzig
@maxschlepzig Je pense que Salman dit que ce escape()n'est pas suffisant pour sécuriser les attributs. En d'autres termes, ce n'est pas sûr:<a href=" {{ html.escape(untrusted_text) }} ">
pianoJames
@pianoJames, je vois. Je considère la vérification des valeurs de lien comme une validation sémantique spécifique au domaine. Pas un lexical comme s'échapper. Outre le script Java en ligne, vous ne voulez vraiment pas créer de liens à partir d'une entrée utilisateur non approuvée sans validation supplémentaire spécifique à l'URL (par exemple à cause de spammeurs). Une méthode simple pour se protéger contre le script Java en ligne dans des attributs tels que la href définition d'une politique de sécurité du contenu qui l'interdit.
maxschlepzig
@pianoJames C'est sûr, car évite les html.escapeguillemets simples et doubles.
Flimm
11

Si vous souhaitez échapper du HTML dans une URL:

Ce n'est probablement PAS ce que l'OP voulait (la question n'indique pas clairement dans quel contexte l'échappement est censé être utilisé), mais la bibliothèque native de Python urllib a une méthode pour échapper aux entités HTML qui doivent être incluses dans une URL en toute sécurité.

Ce qui suit est un exemple:

#!/usr/bin/python
from urllib import quote

x = '+<>^&'
print quote(x) # prints '%2B%3C%3E%5E%26'

Trouvez des documents ici

SuperFamousGuy
la source
10
Ce n'est pas le bon type de fuite; nous recherchons des échappements HTML , par opposition au codage d'URL .
Chaosphere2112
7
Nontheless - c'était ce que je cherchais réellement ;-)
Brad
9

Il existe également l'excellent package markupsafe .

>>> from markupsafe import Markup, escape
>>> escape("<script>alert(document.cookie);</script>")
Markup(u'&lt;script&gt;alert(document.cookie);&lt;/script&gt;')

Le markupsafepaquet est bien conçu, et probablement le moyen le plus polyvalent et le plus pythonique de s'échapper, à mon humble avis, car:

  1. le return ( Markup) est une classe dérivée de unicode (ieisinstance(escape('str'), unicode) == True
  2. il gère correctement l'entrée unicode
  3. cela fonctionne en Python (2.6, 2.7, 3.3 et pypy)
  4. il respecte les méthodes personnalisées d'objets (c'est-à-dire les objets avec une __html__propriété) et les surcharges de gabarit ( __html_format__).
Brian M. Hunt
la source
7

cgi.escape devrait être bon pour échapper au HTML dans le sens limité d'échapper aux balises HTML et aux entités de caractères.

Mais vous devrez peut-être également prendre en compte les problèmes d'encodage: si le code HTML que vous souhaitez citer contient des caractères non ASCII dans un encodage particulier, vous devez également veiller à les représenter de manière raisonnable lorsque vous les citez. Vous pourriez peut-être les convertir en entités. Sinon, vous devez vous assurer que les traductions de codage correctes sont effectuées entre le HTML «source» et la page dans laquelle il est intégré, pour éviter de corrompre les caractères non ASCII.

Craig McQueen
la source
3

Aucune bibliothèque, pur python, échappe en toute sécurité le texte en texte html:

text.replace('&', '&amp;').replace('>', '&gt;').replace('<', '&lt;'
        ).encode('ascii', 'xmlcharrefreplace')
speedplane
la source
1
Votre commande est erronée, le &lt;sera échappé à&amp;lt;
Jason S
@jason s Merci pour le correctif!
speedplane
1

cgi.escape élargi

Cette version s'améliore cgi.escape. Il préserve également les espaces et les nouvelles lignes. Renvoie une unicodechaîne.

def escape_html(text):
    """escape strings for display in HTML"""
    return cgi.escape(text, quote=True).\
           replace(u'\n', u'<br />').\
           replace(u'\t', u'&emsp;').\
           replace(u'  ', u' &nbsp;')

par exemple

>>> escape_html('<foo>\nfoo\t"bar"')
u'&lt;foo&gt;<br />foo&emsp;&quot;bar&quot;'
JamesThomasMoon1979
la source
1

Pas le moyen le plus simple, mais toujours simple. La principale différence avec le module cgi.escape - il fonctionnera toujours correctement si vous en avez déjà &amp;dans votre texte. Comme vous le voyez dans les commentaires:

version cgi.escape

def escape(s, quote=None):
    '''Replace special characters "&", "<" and ">" to HTML-safe sequences.
    If the optional flag quote is true, the quotation mark character (")
is also translated.'''
    s = s.replace("&", "&amp;") # Must be done first!
    s = s.replace("<", "&lt;")
    s = s.replace(">", "&gt;")
    if quote:
        s = s.replace('"', "&quot;")
    return s

version regex

QUOTE_PATTERN = r"""([&<>"'])(?!(amp|lt|gt|quot|#39);)"""
def escape(word):
    """
    Replaces special characters <>&"' to HTML-safe sequences. 
    With attention to already escaped characters.
    """
    replace_with = {
        '<': '&gt;',
        '>': '&lt;',
        '&': '&amp;',
        '"': '&quot;', # should be escaped in attributes
        "'": '&#39'    # should be escaped in attributes
    }
    quote_pattern = re.compile(QUOTE_PATTERN)
    return re.sub(quote_pattern, lambda x: replace_with[x.group(0)], word)
palestamp
la source
0

Pour le code hérité en Python 2.7, vous pouvez le faire via BeautifulSoup4 :

>>> bs4.dammit import EntitySubstitution
>>> esub = EntitySubstitution()
>>> esub.substitute_html("r&d")
'r&amp;d'
scharfmn
la source