python-feedgen/feedgen/util.py
Lars Kiesow 0eb12f9133
Prevent XML Denial of Service Attacks
This patch prevents entity expansion for provided XML content to guard
against XML denial of service attacks like XML bomb or Billion laughs
attack.
2020-01-28 17:29:11 +01:00

96 lines
3 KiB
Python

# -*- coding: utf-8 -*-
'''
feedgen.util
~~~~~~~~~~~~
This file contains helper functions for the feed generator module.
:copyright: 2013, Lars Kiesow <lkiesow@uos.de>
:license: FreeBSD and LGPL, see license.* for more details.
'''
import locale
import sys
import lxml # nosec - we configure a safe parser below
# Configure a safe parser which does not allow XML entity expansion
parser = lxml.etree.XMLParser(
attribute_defaults=False,
dtd_validation=False,
load_dtd=False,
no_network=True,
recover=False,
remove_pis=True,
resolve_entities=False,
huge_tree=False)
def xml_fromstring(xmlstring):
return lxml.etree.fromstring(xmlstring, parser) # nosec - safe parser
def xml_elem(name, parent=None, **kwargs):
if parent is not None:
return lxml.etree.SubElement(parent, name, **kwargs)
return lxml.etree.Element(name, **kwargs)
def ensure_format(val, allowed, required, allowed_values=None, defaults=None):
'''Takes a dictionary or a list of dictionaries and check if all keys are in
the set of allowed keys, if all required keys are present and if the values
of a specific key are ok.
:param val: Dictionaries to check.
:param allowed: Set of allowed keys.
:param required: Set of required keys.
:param allowed_values: Dictionary with keys and sets of their allowed
values.
:param defaults: Dictionary with default values.
:returns: List of checked dictionaries.
'''
if not val:
return []
if allowed_values is None:
allowed_values = {}
if defaults is None:
defaults = {}
# Make shure that we have a list of dicts. Even if there is only one.
if not isinstance(val, list):
val = [val]
for elem in val:
if not isinstance(elem, dict):
raise ValueError('Invalid data (value is no dictionary)')
# Set default values
version = sys.version_info[0]
if version == 2:
items = defaults.iteritems()
else:
items = defaults.items()
for k, v in items:
elem[k] = elem.get(k, v)
if not set(elem.keys()) <= allowed:
raise ValueError('Data contains invalid keys')
if not set(elem.keys()) >= required:
raise ValueError('Data contains not all required keys')
if version == 2:
values = allowed_values.iteritems()
else:
values = allowed_values.items()
for k, v in values:
if elem.get(k) and not elem[k] in v:
raise ValueError('Invalid value for %s' % k)
return val
def formatRFC2822(date):
'''Make sure the locale setting do not interfere with the time format.
'''
old = locale.setlocale(locale.LC_ALL)
locale.setlocale(locale.LC_ALL, 'C')
date = date.strftime('%a, %d %b %Y %H:%M:%S %z')
locale.setlocale(locale.LC_ALL, old)
return date