Qual é a maneira mais fácil de escapar do HTML no Python?

137

O cgi.escape parece ser uma opção possível. Isso funciona bem? Existe algo que é considerado melhor?

Josh Gibson
fonte

Respostas:

176

cgi.escapeestá bem. Escapa:

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

Isso é suficiente para todo o HTML.

EDIT: Se você possui caracteres não-ascii, também deseja escapar, para inclusão em outro documento codificado que usa uma codificação diferente, como Craig diz, basta usar:

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

Não se esqueça de decodificar dataa unicodeprimeira, usando qualquer codificação foi codificada.

No entanto, na minha experiência, esse tipo de codificação é inútil se você trabalhar unicodeo tempo todo desde o início. Basta codificar no final a codificação especificada no cabeçalho do documento ( utf-8para obter compatibilidade máxima).

Exemplo:

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

Também digno de nota (obrigado Greg) é o quoteparâmetro extra cgi.escapenecessário. Com ele definido como True, cgi.escapetambém escapa chars de aspas duplas ( ") para que você possa usar o valor resultante em um atributo XML / HTML.

EDIT: Observe que o cgi.escape foi preterido no Python 3.2 em favor de html.escape, que faz o mesmo, exceto que o quotepadrão é True.

nosklo
fonte
7
O parâmetro booleano adicional para cgi.escape também deve ser considerado para escapar aspas quando o texto é usado nos valores de atributo HTML.
Greg Hewgill 30/06/09
Só para ter certeza: se eu executar todos os dados não confiáveis ​​por meio da cgi.escapefunção, é suficiente para proteger contra todos os attacs XSS (conhecidos)?
Tomas Sedovic
Tomas Sedovic: Depende de onde você colocará o texto depois de executar o cgi.escape. Se colocado no contexto HTML raiz, então sim, você estará completamente seguro.
nosklo
E quanto a entradas como {{Medidas 12 Ω "A x 17 5/8" L x 8 7/8 "D. Importado.}} Isso não é ASCII, portanto, o encode () lançará uma exceção para você.
Andrew Kolesnikov
@ Andrew Kolesnikov: Você já tentou? cgi.escape(yourunicodeobj).encode('ascii', 'xmlcharrefreplace') == '{{Measures 12 &#937;"H x 17 5/8"W x 8 7/8"D. Imported.}}'- como você pode ver, a expressão retorna ascii bytestring, com todos os caracteres unicode não-ascii codificados usando a tabela de referência de caracteres xml.
nosklo
112

No Python 3.2, um novo htmlmódulo foi introduzido, usado para escapar caracteres reservados da marcação HTML.

Tem uma função 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
fonte
Que tal quote=True?
02s2ts
1
@SalmanAbbas Você tem medo de que as aspas não escapem? Observe que html.escape(), por padrão, as aspas escapadas (por outro lado, cgi.quote()não escapam - e somente escapam aspas duplas, se solicitado). Assim, eu tenho que definir explicitamente um parâmetro opcional para injetar algo em um atributo com html.escape(), ou seja, para torná-lo inseguro para os atributos:t = '" onclick="alert()'; t = html.escape(t, quote=False); s = f'<a href="about.html" class="{t}">foo</a>'
maxschlepzig
@maxschlepzig Acho que Salman está dizendo escape()não é suficiente para tornar os atributos seguros. Em outras palavras, isso não é seguro:<a href=" {{ html.escape(untrusted_text) }} ">
pianoJames
@pianoJames, entendo. Considero a verificação dos valores do link uma validação semântica específica do domínio. Não é um léxico como escapar. Além do Java Script embutido, você realmente não deseja criar links a partir de entradas não confiáveis ​​do usuário sem mais validação específica de URL (por exemplo, por causa de Spammers). Um método simples de proteção contra Java Script embutido em atributos como href é definir uma Política de Segurança de Conteúdo que não a permita.
maxschlepzig 31/07/19
@pianoJames É seguro, porque evita html.escapeaspas simples e duplas.
Flimm
11

Se você deseja escapar do HTML em um URL:

Provavelmente NÃO é isso que o OP queria (a pergunta não indica claramente em que contexto a fuga deve ser usada), mas a biblioteca nativa do Python urllib tem um método para escapar de entidades HTML que precisam ser incluídas em uma URL com segurança.

A seguir, um exemplo:

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

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

Encontre documentos aqui

SuperFamousGuy
fonte
10
Este é o tipo errado de fuga; estamos procurando escapes de HTML , em oposição à codificação de URL .
Chaosphere2112
7
Nontheless - foi o que eu estava realmente procurando ;-)
Brad
9

Há também o excelente pacote de marcação segura .

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

O markupsafepacote é bem projetado e provavelmente a maneira mais versátil e pitônica de escapar, IMHO, porque:

  1. o return ( Markup) é uma classe derivada do unicode (ou seja,isinstance(escape('str'), unicode) == True
  2. ele lida adequadamente com a entrada unicode
  3. funciona em Python (2.6, 2.7, 3.3 e pypy)
  4. ele respeita métodos personalizados de objetos (ou seja, objetos com uma __html__propriedade) e sobrecargas de modelo ( __html_format__).
Brian M. Hunt
fonte
7

cgi.escape deve ser bom escapar do HTML no sentido limitado de escapar das tags HTML e das entidades de caracteres.

Mas também é necessário considerar problemas de codificação: se o HTML que você deseja citar tiver caracteres não ASCII em uma codificação específica, também será necessário ter o cuidado de representá-los sensivelmente ao citar. Talvez você possa convertê-los em entidades. Caso contrário, você deve garantir que as traduções de codificação corretas sejam feitas entre o HTML "origem" e a página em que está incorporado, para evitar a corrupção de caracteres não ASCII.

Craig McQueen
fonte
3

Nenhuma biblioteca, python puro, escapa com segurança o texto para o texto html:

text.replace('&', '&amp;').replace('>', '&gt;').replace('<', '&lt;'
        ).encode('ascii', 'xmlcharrefreplace')
speedplane
fonte
1
Seu pedido está errado, o &lt;escape será feito para&amp;lt;
Jason S
@jason s Obrigado pela correção!
speedplane 5/09/18
1

cgi.escape estendido

Esta versão melhora cgi.escape. Também preserva espaços em branco e novas linhas. Retorna uma unicodestring.

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 exemplo

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

Não é o caminho mais fácil, mas ainda é direto. A principal diferença do módulo cgi.escape - ainda funcionará corretamente se você já possui &amp;seu texto. Como você vê nos comentários:

versão 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

versão 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
fonte
0

Para código legado no Python 2.7, é possível fazê-lo via BeautifulSoup4 :

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