library - requests python code
Limpiador/depurador/filtro HTML de Python (9)
¡Usa lxml.html.clean
! ¡Es muy fácil!
from lxml.html.clean import clean_html
print clean_html(html)
Supongamos el siguiente html:
html = ''''''/
<html>
<head>
<script type="text/javascript" src="evil-site"></script>
<link rel="alternate" type="text/rss" src="evil-rss">
<style>
body {background-image: url(javascript:do_evil)};
div {color: expression(evil)};
</style>
</head>
<body onload="evil_function()">
<!-- I am interpreted for EVIL! -->
<a href="javascript:evil_function()">a link</a>
<a href="#" onclick="evil_function()">another link</a>
<p onclick="evil_function()">a paragraph</p>
<div style="display: none">secret EVIL!</div>
<object> of EVIL! </object>
<iframe src="evil-site"></iframe>
<form action="evil-site">
Password: <input type="password" name="password">
</form>
<blink>annoying EVIL!</blink>
<a href="evil-site">spam spam SPAM!</a>
<image src="evil!">
</body>
</html>''''''
Los resultados...
<html>
<body>
<div>
<style>/* deleted */</style>
<a href="">a link</a>
<a href="#">another link</a>
<p>a paragraph</p>
<div>secret EVIL!</div>
of EVIL!
Password:
annoying EVIL!
<a href="evil-site">spam spam SPAM!</a>
<img src="evil!">
</div>
</body>
</html>
Puede personalizar los elementos que desea limpiar y otras cosas.
Estoy buscando un módulo que elimine las etiquetas HTML de una cadena que no se encuentran en una lista blanca.
Aquí hay una solución simple usando BeautifulSoup :
from bs4 import BeautifulSoup
VALID_TAGS = [''strong'', ''em'', ''p'', ''ul'', ''li'', ''br'']
def sanitize_html(value):
soup = BeautifulSoup(value)
for tag in soup.findAll(True):
if tag.name not in VALID_TAGS:
tag.hidden = True
return soup.renderContents()
Si también desea eliminar el contenido de las etiquetas no válidas, sustituya tag.extract()
por tag.hidden
.
Esto es lo que uso en mi propio proyecto. Los elementos / atributos aceptables provienen de feedparser y BeautifulSoup hace el trabajo.
from BeautifulSoup import BeautifulSoup
acceptable_elements = [''a'', ''abbr'', ''acronym'', ''address'', ''area'', ''b'', ''big'',
''blockquote'', ''br'', ''button'', ''caption'', ''center'', ''cite'', ''code'', ''col'',
''colgroup'', ''dd'', ''del'', ''dfn'', ''dir'', ''div'', ''dl'', ''dt'', ''em'',
''font'', ''h1'', ''h2'', ''h3'', ''h4'', ''h5'', ''h6'', ''hr'', ''i'', ''img'',
''ins'', ''kbd'', ''label'', ''legend'', ''li'', ''map'', ''menu'', ''ol'',
''p'', ''pre'', ''q'', ''s'', ''samp'', ''small'', ''span'', ''strike'',
''strong'', ''sub'', ''sup'', ''table'', ''tbody'', ''td'', ''tfoot'', ''th'',
''thead'', ''tr'', ''tt'', ''u'', ''ul'', ''var'']
acceptable_attributes = [''abbr'', ''accept'', ''accept-charset'', ''accesskey'',
''action'', ''align'', ''alt'', ''axis'', ''border'', ''cellpadding'', ''cellspacing'',
''char'', ''charoff'', ''charset'', ''checked'', ''cite'', ''clear'', ''cols'',
''colspan'', ''color'', ''compact'', ''coords'', ''datetime'', ''dir'',
''enctype'', ''for'', ''headers'', ''height'', ''href'', ''hreflang'', ''hspace'',
''id'', ''ismap'', ''label'', ''lang'', ''longdesc'', ''maxlength'', ''method'',
''multiple'', ''name'', ''nohref'', ''noshade'', ''nowrap'', ''prompt'',
''rel'', ''rev'', ''rows'', ''rowspan'', ''rules'', ''scope'', ''shape'', ''size'',
''span'', ''src'', ''start'', ''summary'', ''tabindex'', ''target'', ''title'', ''type'',
''usemap'', ''valign'', ''value'', ''vspace'', ''width'']
def clean_html( fragment ):
while True:
soup = BeautifulSoup( fragment )
removed = False
for tag in soup.findAll(True): # find all tags
if tag.name not in acceptable_elements:
tag.extract() # remove the bad ones
removed = True
else: # it might have bad attributes
# a better way to get all attributes?
for attr in tag._getAttrMap().keys():
if attr not in acceptable_attributes:
del tag[attr]
# turn it back to html
fragment = unicode(soup)
if removed:
# we removed tags and tricky can could exploit that!
# we need to reparse the html until it stops changing
continue # next round
return fragment
Algunas pruebas pequeñas para asegurarse de que esto se comporte correctamente:
tests = [ #text should work
(''<p>this is text</p>but this too'', ''<p>this is text</p>but this too''),
# make sure we cant exploit removal of tags
(''<<script></script>script> alert("Haha, I hacked your page."); <<script></script>/script>'', ''''),
# try the same trick with attributes, gives an Exception
(''<div on<script></script>load="alert("Haha, I hacked your page.");">1</div>'', Exception),
# no tags should be skipped
(''<script>bad</script><script>bad</script><script>bad</script>'', ''''),
# leave valid tags but remove bad attributes
(''<a href="good" onload="bad" onclick="bad" alt="good">1</div>'', ''<a href="good" alt="good">1</a>''),
]
for text, out in tests:
try:
res = clean_html(text)
assert res == out, "%s => %s != %s" % (text, res, out)
except out, e:
assert isinstance(e, out), "Wrong exception %r" % e
Las soluciones anteriores a través de Beautiful Soup no funcionarán. Es posible que pueda piratear algo con Beautiful Soup por encima y más allá, porque Beautiful Soup proporciona acceso al árbol de análisis sintáctico. Dentro de un tiempo, creo que intentaré resolver el problema correctamente, pero es un proyecto de una semana más o menos, y no tengo una semana gratis pronto.
Solo para ser específico, Beautiful Soup arrojará excepciones para algunos errores de análisis que el código anterior no capta; pero también hay muchas vulnerabilidades XSS muy reales que no se detectan, como:
<<script>script> alert("Haha, I hacked your page."); </</script>script>
Probablemente, lo mejor que puede hacer es quitar el <
elemento como <
, prohibir todo el HTML, y luego usar un subconjunto restringido como Markdown para renderizar el formateo de forma adecuada. En particular, también puede volver atrás y volver a introducir bits comunes de HTML con una expresión regular. Así es como se ve el proceso, más o menos:
_lt_ = re.compile(''<'')
_tc_ = ''~(lt)~'' # or whatever, so long as markdown doesn''t mangle it.
_ok_ = re.compile(_tc_ + ''(/?(?:u|b|i|em|strong|sup|sub|p|br|q|blockquote|code))>'', re.I)
_sqrt_ = re.compile(_tc_ + ''sqrt>'', re.I) #just to give an example of extending
_endsqrt_ = re.compile(_tc_ + ''/sqrt>'', re.I) #html syntax with your own elements.
_tcre_ = re.compile(_tc_)
def sanitize(text):
text = _lt_.sub(_tc_, text)
text = markdown(text)
text = _ok_.sub(r''</1>'', text)
text = _sqrt_.sub(r''√<span style="text-decoration:overline;">'', text)
text = _endsqrt_.sub(r''</span>'', text)
return _tcre_.sub(''<'', text)
Todavía no probé ese código, por lo que puede haber errores. Pero ves la idea general: tienes que poner en lista negra todo el HTML en general antes de incluir en la lista blanca lo que está bien.
Prefiero la solución lxml.html.clean
, como señala . Aquí hay que eliminar algunas etiquetas vacías:
from lxml import etree
from lxml.html import clean, fromstring, tostring
remove_attrs = [''class'']
remove_tags = [''table'', ''tr'', ''td'']
nonempty_tags = [''a'', ''p'', ''span'', ''div'']
cleaner = clean.Cleaner(remove_tags=remove_tags)
def squeaky_clean(html):
clean_html = cleaner.clean_html(html)
# now remove the useless empty tags
root = fromstring(clean_html)
context = etree.iterwalk(root) # just the end tag event
for action, elem in context:
clean_text = elem.text and elem.text.strip('' /t/r/n'')
if elem.tag in nonempty_tags and /
not (len(elem) or clean_text): # no children nor text
elem.getparent().remove(elem)
continue
elem.text = clean_text # if you want
# and if you also wanna remove some attrs:
for badattr in remove_attrs:
if elem.attrib.has_key(badattr):
del elem.attrib[badattr]
return tostring(root)
Puede usar html5lib , que usa una lista blanca para desinfectar.
Un ejemplo:
import html5lib
from html5lib import sanitizer, treebuilders, treewalkers, serializer
def clean_html(buf):
"""Cleans HTML of dangerous tags and content."""
buf = buf.strip()
if not buf:
return buf
p = html5lib.HTMLParser(tree=treebuilders.getTreeBuilder("dom"),
tokenizer=sanitizer.HTMLSanitizer)
dom_tree = p.parseFragment(buf)
walker = treewalkers.getTreeWalker("dom")
stream = walker(dom_tree)
s = serializer.htmlserializer.HTMLSerializer(
omit_optional_tags=False,
quote_attr_values=True)
return s.render(stream)
Yo uso esto:
Es simple y le permite definir una lista blanca bien controlada, restringe las URL e incluso compara los valores de los atributos contra expresiones regulares o tiene funciones de filtrado personalizadas por atributo.
Si se usa con cuidado, podría ser una solución segura.
la solución de con BeautifulSoup para abordar el problema planteado por Chris Drost . Un poco crudo, pero hace el trabajo:
from BeautifulSoup import BeautifulSoup, Comment
VALID_TAGS = {''strong'': [],
''em'': [],
''p'': [],
''ol'': [],
''ul'': [],
''li'': [],
''br'': [],
''a'': [''href'', ''title'']
}
def sanitize_html(value, valid_tags=VALID_TAGS):
soup = BeautifulSoup(value)
comments = soup.findAll(text=lambda text:isinstance(text, Comment))
[comment.extract() for comment in comments]
# Some markup can be crafted to slip through BeautifulSoup''s parser, so
# we run this repeatedly until it generates the same output twice.
newoutput = soup.renderContents()
while 1:
oldoutput = newoutput
soup = BeautifulSoup(newoutput)
for tag in soup.findAll(True):
if tag.name not in valid_tags:
tag.hidden = True
else:
tag.attrs = [(attr, value) for attr, value in tag.attrs if attr in valid_tags[tag.name]]
newoutput = soup.renderContents()
if oldoutput == newoutput:
break
return newoutput
Editar: actualizado para admitir atributos válidos.