Skip to content
Permalink
Browse files Browse the repository at this point in the history
Merge pull request from GHSA-g8q7-xv52-hf9f
Prevent XML Denial of Service Attacks
  • Loading branch information
lkiesow committed Jan 28, 2020
2 parents 9440cca + 0eb12f9 commit f57a01b
Show file tree
Hide file tree
Showing 10 changed files with 180 additions and 180 deletions.
93 changes: 45 additions & 48 deletions feedgen/entry.py
Expand Up @@ -3,7 +3,7 @@
feedgen.entry
~~~~~~~~~~~~~
:copyright: 2013, Lars Kiesow <lkiesow@uos.de>
:copyright: 2013-2020, Lars Kiesow <lkiesow@uos.de>
:license: FreeBSD and LGPL, see license.* for more details.
'''
Expand All @@ -13,18 +13,19 @@
import dateutil.parser
import dateutil.tz
import warnings
from lxml import etree

from lxml.etree import CDATA # nosec - adding CDATA entry is safe

from feedgen.compat import string_types
from feedgen.util import ensure_format, formatRFC2822
from feedgen.util import ensure_format, formatRFC2822, xml_fromstring, xml_elem


def _add_text_elm(entry, data, name):
"""Add a text subelement to an entry"""
if not data:
return

elm = etree.SubElement(entry, name)
elm = xml_elem(name, entry)
type_ = data.get('type')
if data.get('src'):
if name != 'content':
Expand All @@ -34,16 +35,14 @@ def _add_text_elm(entry, data, name):
elif data.get(name):
# Surround xhtml with a div tag, parse it and embed it
if type_ == 'xhtml':
elm.append(etree.fromstring(
'<div xmlns="http://www.w3.org/1999/xhtml">' +
data.get(name) + '</div>'))
xhtml = '<div xmlns="http://www.w3.org/1999/xhtml">' \
+ data.get(name) + '</div>'
elm.append(xml_fromstring(xhtml))
elif type_ == 'CDATA':
elm.text = etree.CDATA(
data.get(name))
elm.text = CDATA(data.get(name))
# Parse XML and embed it
elif type_ and (type_.endswith('/xml') or type_.endswith('+xml')):
elm.append(etree.fromstring(
data[name]))
elm.append(xml_fromstring(data[name]))
# Embed the text in escaped form
elif not type_ or type_.startswith('text') or type_ == 'html':
elm.text = data.get(name)
Expand Down Expand Up @@ -102,14 +101,14 @@ def __init__(self):

def atom_entry(self, extensions=True):
'''Create an ATOM entry and return it.'''
entry = etree.Element('entry')
entry = xml_elem('entry')
if not (self.__atom_id and self.__atom_title and self.__atom_updated):
raise ValueError('Required fields not set')
id = etree.SubElement(entry, 'id')
id = xml_elem('id', entry)
id.text = self.__atom_id
title = etree.SubElement(entry, 'title')
title = xml_elem('title', entry)
title.text = self.__atom_title
updated = etree.SubElement(entry, 'updated')
updated = xml_elem('updated', entry)
updated.text = self.__atom_updated.isoformat()

# An entry must contain an alternate link if there is no content
Expand All @@ -125,20 +124,20 @@ def atom_entry(self, extensions=True):
# Atom requires a name. Skip elements without.
if not a.get('name'):
continue
author = etree.SubElement(entry, 'author')
name = etree.SubElement(author, 'name')
author = xml_elem('author', entry)
name = xml_elem('name', author)
name.text = a.get('name')
if a.get('email'):
email = etree.SubElement(author, 'email')
email = xml_elem('email', author)
email.text = a.get('email')
if a.get('uri'):
uri = etree.SubElement(author, 'uri')
uri = xml_elem('uri', author)
uri.text = a.get('uri')

_add_text_elm(entry, self.__atom_content, 'content')

for l in self.__atom_link or []:
link = etree.SubElement(entry, 'link', href=l['href'])
link = xml_elem('link', entry, href=l['href'])
if l.get('rel'):
link.attrib['rel'] = l['rel']
if l.get('type'):
Expand All @@ -153,7 +152,7 @@ def atom_entry(self, extensions=True):
_add_text_elm(entry, self.__atom_summary, 'summary')

for c in self.__atom_category or []:
cat = etree.SubElement(entry, 'category', term=c['term'])
cat = xml_elem('category', entry, term=c['term'])
if c.get('scheme'):
cat.attrib['scheme'] = c['scheme']
if c.get('label'):
Expand All @@ -164,32 +163,31 @@ def atom_entry(self, extensions=True):
# Atom requires a name. Skip elements without.
if not c.get('name'):
continue
contrib = etree.SubElement(entry, 'contributor')
name = etree.SubElement(contrib, 'name')
contrib = xml_elem('contributor', entry)
name = xml_elem('name', contrib)
name.text = c.get('name')
if c.get('email'):
email = etree.SubElement(contrib, 'email')
email = xml_elem('email', contrib)
email.text = c.get('email')
if c.get('uri'):
uri = etree.SubElement(contrib, 'uri')
uri = xml_elem('uri', contrib)
uri.text = c.get('uri')

if self.__atom_published:
published = etree.SubElement(entry, 'published')
published = xml_elem('published', entry)
published.text = self.__atom_published.isoformat()

if self.__atom_rights:
rights = etree.SubElement(entry, 'rights')
rights = xml_elem('rights', entry)
rights.text = self.__atom_rights

if self.__atom_source:
source = etree.SubElement(entry, 'source')
source = xml_elem('source', entry)
if self.__atom_source.get('title'):
source_title = etree.SubElement(source, 'title')
source_title = xml_elem('title', source)
source_title.text = self.__atom_source['title']
if self.__atom_source.get('link'):
etree.SubElement(source, 'link',
href=self.__atom_source['link'])
xml_elem('link', source, href=self.__atom_source['link'])

if extensions:
for ext in self.__extensions.values() or []:
Expand All @@ -200,60 +198,59 @@ def atom_entry(self, extensions=True):

def rss_entry(self, extensions=True):
'''Create a RSS item and return it.'''
entry = etree.Element('item')
entry = xml_elem('item')
if not (self.__rss_title or
self.__rss_description or
self.__rss_content):
raise ValueError('Required fields not set')
if self.__rss_title:
title = etree.SubElement(entry, 'title')
title = xml_elem('title', entry)
title.text = self.__rss_title
if self.__rss_link:
link = etree.SubElement(entry, 'link')
link = xml_elem('link', entry)
link.text = self.__rss_link
if self.__rss_description and self.__rss_content:
description = etree.SubElement(entry, 'description')
description = xml_elem('description', entry)
description.text = self.__rss_description
XMLNS_CONTENT = 'http://purl.org/rss/1.0/modules/content/'
content = etree.SubElement(entry, '{%s}encoded' % XMLNS_CONTENT)
content.text = etree.CDATA(self.__rss_content['content']) \
content = xml_elem('{%s}encoded' % XMLNS_CONTENT, entry)
content.text = CDATA(self.__rss_content['content']) \
if self.__rss_content.get('type', '') == 'CDATA' \
else self.__rss_content['content']
elif self.__rss_description:
description = etree.SubElement(entry, 'description')
description = xml_elem('description', entry)
description.text = self.__rss_description
elif self.__rss_content:
description = etree.SubElement(entry, 'description')
description.text = etree.CDATA(self.__rss_content['content']) \
description = xml_elem('description', entry)
description.text = CDATA(self.__rss_content['content']) \
if self.__rss_content.get('type', '') == 'CDATA' \
else self.__rss_content['content']
for a in self.__rss_author or []:
author = etree.SubElement(entry, 'author')
author = xml_elem('author', entry)
author.text = a
if self.__rss_guid.get('guid'):
guid = etree.SubElement(entry, 'guid')
guid = xml_elem('guid', entry)
guid.text = self.__rss_guid['guid']
permaLink = str(self.__rss_guid.get('permalink', False)).lower()
guid.attrib['isPermaLink'] = permaLink
for cat in self.__rss_category or []:
category = etree.SubElement(entry, 'category')
category = xml_elem('category', entry)
category.text = cat['value']
if cat.get('domain'):
category.attrib['domain'] = cat['domain']
if self.__rss_comments:
comments = etree.SubElement(entry, 'comments')
comments = xml_elem('comments', entry)
comments.text = self.__rss_comments
if self.__rss_enclosure:
enclosure = etree.SubElement(entry, 'enclosure')
enclosure = xml_elem('enclosure', entry)
enclosure.attrib['url'] = self.__rss_enclosure['url']
enclosure.attrib['length'] = self.__rss_enclosure['length']
enclosure.attrib['type'] = self.__rss_enclosure['type']
if self.__rss_pubDate:
pubDate = etree.SubElement(entry, 'pubDate')
pubDate = xml_elem('pubDate', entry)
pubDate.text = formatRFC2822(self.__rss_pubDate)
if self.__rss_source:
source = etree.SubElement(entry, 'source',
url=self.__rss_source['url'])
source = xml_elem('source', entry, url=self.__rss_source['url'])
source.text = self.__rss_source['title']

if extensions:
Expand Down
13 changes: 6 additions & 7 deletions feedgen/ext/dc.py
Expand Up @@ -13,9 +13,8 @@
:license: FreeBSD and LGPL, see license.* for more details.
'''

from lxml import etree

from feedgen.ext.base import BaseExtension
from feedgen.util import xml_elem


class DcBaseExtension(BaseExtension):
Expand Down Expand Up @@ -45,10 +44,10 @@ def __init__(self):
def extend_ns(self):
return {'dc': 'http://purl.org/dc/elements/1.1/'}

def _extend_xml(self, xml_elem):
'''Extend xml_elem with set DC fields.
def _extend_xml(self, xml_element):
'''Extend xml_element with set DC fields.
:param xml_elem: etree element
:param xml_element: etree element
'''
DCELEMENTS_NS = 'http://purl.org/dc/elements/1.1/'

Expand All @@ -58,8 +57,8 @@ def _extend_xml(self, xml_elem):
'identifier']:
if hasattr(self, '_dcelem_%s' % elem):
for val in getattr(self, '_dcelem_%s' % elem) or []:
node = etree.SubElement(xml_elem,
'{%s}%s' % (DCELEMENTS_NS, elem))
node = xml_elem('{%s}%s' % (DCELEMENTS_NS, elem),
xml_element)
node.text = val

def extend_atom(self, atom_feed):
Expand Down
28 changes: 11 additions & 17 deletions feedgen/ext/geo_entry.py
Expand Up @@ -12,8 +12,8 @@
import numbers
import warnings

from lxml import etree
from feedgen.ext.base import BaseEntryExtension
from feedgen.util import xml_elem


class GeoRSSPolygonInteriorWarning(Warning):
Expand Down Expand Up @@ -86,49 +86,43 @@ def extend_file(self, entry):
GEO_NS = 'http://www.georss.org/georss'

if self.__point:
point = etree.SubElement(entry, '{%s}point' % GEO_NS)
point = xml_elem('{%s}point' % GEO_NS, entry)
point.text = self.__point

if self.__line:
line = etree.SubElement(entry, '{%s}line' % GEO_NS)
line = xml_elem('{%s}line' % GEO_NS, entry)
line.text = self.__line

if self.__polygon:
polygon = etree.SubElement(entry, '{%s}polygon' % GEO_NS)
polygon = xml_elem('{%s}polygon' % GEO_NS, entry)
polygon.text = self.__polygon

if self.__box:
box = etree.SubElement(entry, '{%s}box' % GEO_NS)
box = xml_elem('{%s}box' % GEO_NS, entry)
box.text = self.__box

if self.__featuretypetag:
featuretypetag = etree.SubElement(
entry,
'{%s}featuretypetag' % GEO_NS
)
featuretypetag = xml_elem('{%s}featuretypetag' % GEO_NS, entry)
featuretypetag.text = self.__featuretypetag

if self.__relationshiptag:
relationshiptag = etree.SubElement(
entry,
'{%s}relationshiptag' % GEO_NS
)
relationshiptag = xml_elem('{%s}relationshiptag' % GEO_NS, entry)
relationshiptag.text = self.__relationshiptag

if self.__featurename:
featurename = etree.SubElement(entry, '{%s}featurename' % GEO_NS)
featurename = xml_elem('{%s}featurename' % GEO_NS, entry)
featurename.text = self.__featurename

if self.__elev:
elevation = etree.SubElement(entry, '{%s}elev' % GEO_NS)
elevation = xml_elem('{%s}elev' % GEO_NS, entry)
elevation.text = str(self.__elev)

if self.__floor:
floor = etree.SubElement(entry, '{%s}floor' % GEO_NS)
floor = xml_elem('{%s}floor' % GEO_NS, entry)
floor.text = str(self.__floor)

if self.__radius:
radius = etree.SubElement(entry, '{%s}radius' % GEO_NS)
radius = xml_elem('{%s}radius' % GEO_NS, entry)
radius.text = str(self.__radius)

return entry
Expand Down
12 changes: 5 additions & 7 deletions feedgen/ext/media.py
Expand Up @@ -10,10 +10,8 @@
:license: FreeBSD and LGPL, see license.* for more details.
'''

from lxml import etree

from feedgen.ext.base import BaseEntryExtension, BaseExtension
from feedgen.util import ensure_format
from feedgen.util import ensure_format, xml_elem

MEDIA_NS = 'http://search.yahoo.com/mrss/'

Expand Down Expand Up @@ -45,10 +43,10 @@ def extend_atom(self, entry):
# Define current media:group
group = groups.get(media_content.get('group'))
if group is None:
group = etree.SubElement(entry, '{%s}group' % MEDIA_NS)
group = xml_elem('{%s}group' % MEDIA_NS, entry)
groups[media_content.get('group')] = group
# Add content
content = etree.SubElement(group, '{%s}content' % MEDIA_NS)
content = xml_elem('{%s}content' % MEDIA_NS, group)
for attr in ('url', 'fileSize', 'type', 'medium', 'isDefault',
'expression', 'bitrate', 'framerate', 'samplingrate',
'channels', 'duration', 'height', 'width', 'lang'):
Expand All @@ -59,10 +57,10 @@ def extend_atom(self, entry):
# Define current media:group
group = groups.get(media_thumbnail.get('group'))
if group is None:
group = etree.SubElement(entry, '{%s}group' % MEDIA_NS)
group = xml_elem('{%s}group' % MEDIA_NS, entry)
groups[media_thumbnail.get('group')] = group
# Add thumbnails
thumbnail = etree.SubElement(group, '{%s}thumbnail' % MEDIA_NS)
thumbnail = xml_elem('{%s}thumbnail' % MEDIA_NS, group)
for attr in ('url', 'height', 'width', 'time'):
if media_thumbnail.get(attr):
thumbnail.set(attr, media_thumbnail[attr])
Expand Down

0 comments on commit f57a01b

Please sign in to comment.