¿Cuál es la forma más fácil de escapar de HTML en Python?

137

cgi.escape parece una opción posible. ¿Funciona bien? ¿Hay algo que se considere mejor?

Josh Gibson
fuente

Respuestas:

176

cgi.escapeestá bien. Se escapa:

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

Eso es suficiente para todo HTML.

EDITAR: si tiene caracteres no ascii de los que también quiere escapar, para incluirlos en otro documento codificado que use una codificación diferente, como dice Craig , simplemente use:

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

No se olvide de decodificación datade unicodeprimera, utilizando cualquier codificación que se ha codificado.

Sin embargo, en mi experiencia, ese tipo de codificación es inútil si solo trabajas unicodetodo el tiempo desde el principio. Simplemente codifique al final la codificación especificada en el encabezado del documento ( utf-8para una compatibilidad máxima).

Ejemplo:

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

También vale la pena señalar (gracias Greg) son las tomas de quoteparámetros adicionales cgi.escape. Con esto establecido en True, cgi.escapetambién escapa caracteres de comillas dobles ( ") para que pueda usar el valor resultante en un atributo XML / HTML.

EDITAR: Tenga en cuenta que cgi.escape ha quedado en desuso en Python 3.2 a favor de html.escape, lo que hace lo mismo, excepto que el valor quotepredeterminado es True.

nosklo
fuente
77
El parámetro booleano adicional a cgi.escape también debe considerarse para las comillas de escape cuando el texto se usa en valores de atributos HTML.
Greg Hewgill
Solo para estar seguro: si ejecuto todos los datos no confiables a través de la cgi.escapefunción, ¿es suficiente para proteger contra todos los ataques XSS (conocidos)?
Tomás Sedovic
@Tomas Sedovic: Depende de dónde coloque el texto después de ejecutar cgi.escape en él. Si se coloca en el contexto HTML raíz, entonces sí, está completamente seguro.
nosklo
¿Qué pasa con la entrada como {{Medidas 12 Ω "H x 17 5/8" W x 8 7/8 "D. Importado.}} Eso no es ascii, por lo que codificar () te lanzará una excepción.
Andrew Kolesnikov
@ Andrew Kolesnikov: ¿Lo has intentado? cgi.escape(yourunicodeobj).encode('ascii', 'xmlcharrefreplace') == '{{Measures 12 &#937;"H x 17 5/8"W x 8 7/8"D. Imported.}}'- como puede ver, la expresión devuelve una cadena de bytes ascii, con todos los caracteres unicode no ascii codificados utilizando la tabla de referencia de caracteres xml.
nosklo
112

En Python 3.2 htmlse introdujo un nuevo módulo, que se utiliza para escapar de los caracteres reservados del marcado HTML.

Tiene una función 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
fuente
¿Qué hay de quote=True?
2rs2ts
1
@SalmanAbbas ¿Temes que no se escapen las citas? Tenga en cuenta que las html.escape()comillas de escape se omiten de manera predeterminada (en contraste, cgi.quote()no lo hacen, y solo se escapan las comillas dobles, si se le indica). Por lo tanto, tengo que establecer explícitamente un parámetro opcional para inyectar algo en un atributo html.escape(), es decir, hacerlo inseguro para los atributos:t = '" onclick="alert()'; t = html.escape(t, quote=False); s = f'<a href="about.html" class="{t}">foo</a>'
maxschlepzig
@maxschlepzig Creo que Salman dice escape()que no es suficiente para que los atributos sean seguros. En otras palabras, esto no es seguro:<a href=" {{ html.escape(untrusted_text) }} ">
pianoJames
@pianoJames, ya veo. Considero verificar los valores de enlace una validación semántica específica del dominio. No es léxico como escapar. Además de Java Script en línea, realmente no desea crear enlaces a partir de entradas de usuarios no confiables sin más validación específica de URL (por ejemplo, debido a Spammers). Un método simple para proteger contra Java Script en línea en atributos como href es establecer una Política de seguridad de contenido que no lo permita.
maxschlepzig
@pianoJames Es seguro, porque html.escapese escapa entre comillas simples y comillas dobles.
Flimm
11

Si desea escapar de HTML en una URL:

Probablemente esto NO sea lo que el OP quería (la pregunta no indica claramente en qué contexto se pretende usar el escape), pero la biblioteca nativa de Python urllib tiene un método para escapar de las entidades HTML que deben incluirse en una URL de forma segura.

Lo siguiente es un ejemplo:

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

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

Encuentra documentos aquí

SuperFamousGuy
fuente
10
Este es el tipo equivocado de escape; Estamos buscando escapes de HTML , en lugar de la codificación de URL .
Chaosphere2112
77
No obstante: era lo que estaba buscando ;-)
Brad
9

También está el excelente paquete de marcado seguro .

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

El markupsafepaquete está bien diseñado, y probablemente la forma más versátil y pitónica de escapar, en mi humilde opinión, porque:

  1. return ( Markup) es una clase derivada de unicode (es decirisinstance(escape('str'), unicode) == True
  2. maneja correctamente la entrada unicode
  3. funciona en Python (2.6, 2.7, 3.3 y pypy)
  4. respeta métodos personalizados de objetos (es decir, objetos con una __html__propiedad) y sobrecargas de plantillas ( __html_format__).
Brian M. Hunt
fuente
7

cgi.escape debería ser bueno escapar de HTML en el sentido limitado de escapar de las etiquetas HTML y las entidades de caracteres.

Pero es posible que también deba considerar problemas de codificación: si el HTML que desea citar tiene caracteres que no son ASCII en una codificación particular, entonces también deberá asegurarse de representarlos de manera sensata al citarlos. Quizás podrías convertirlos en entidades. De lo contrario, debe asegurarse de que se realicen las traducciones de codificación correctas entre el HTML "fuente" y la página en la que está incrustado, para evitar corromper los caracteres no ASCII.

Craig McQueen
fuente
3

Ninguna biblioteca, Python puro, escapa de forma segura de texto a texto html:

text.replace('&', '&amp;').replace('>', '&gt;').replace('<', '&lt;'
        ).encode('ascii', 'xmlcharrefreplace')
Speedplane
fuente
1
Su pedido es incorrecto, &lt;se escapará a&amp;lt;
Jason S
@jason s ¡Gracias por la solución!
Speedplane
1

cgi.escape extendido

Esta versión mejora cgi.escape. También conserva espacios en blanco y nuevas líneas. Devuelve una unicodecadena.

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;')

por ejemplo

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

No es la forma más fácil, pero sigue siendo sencilla. La principal diferencia con el módulo cgi.escape : seguirá funcionando correctamente si ya lo tiene &amp;en su texto. Como ves en los comentarios:

cgi.escape version

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

versión 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
fuente
0

Para el código heredado en Python 2.7, puede hacerlo a través de BeautifulSoup4 :

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