Skip to content

Commit f57a01b

Browse files
authored
Merge pull request from GHSA-g8q7-xv52-hf9f
Prevent XML Denial of Service Attacks
2 parents 9440cca + 0eb12f9 commit f57a01b

File tree

10 files changed

+180
-180
lines changed

10 files changed

+180
-180
lines changed

feedgen/entry.py

Lines changed: 45 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
feedgen.entry
44
~~~~~~~~~~~~~
55
6-
:copyright: 2013, Lars Kiesow <[email protected]>
6+
:copyright: 2013-2020, Lars Kiesow <[email protected]>
77
88
:license: FreeBSD and LGPL, see license.* for more details.
99
'''
@@ -13,18 +13,19 @@
1313
import dateutil.parser
1414
import dateutil.tz
1515
import warnings
16-
from lxml import etree
16+
17+
from lxml.etree import CDATA # nosec - adding CDATA entry is safe
1718

1819
from feedgen.compat import string_types
19-
from feedgen.util import ensure_format, formatRFC2822
20+
from feedgen.util import ensure_format, formatRFC2822, xml_fromstring, xml_elem
2021

2122

2223
def _add_text_elm(entry, data, name):
2324
"""Add a text subelement to an entry"""
2425
if not data:
2526
return
2627

27-
elm = etree.SubElement(entry, name)
28+
elm = xml_elem(name, entry)
2829
type_ = data.get('type')
2930
if data.get('src'):
3031
if name != 'content':
@@ -34,16 +35,14 @@ def _add_text_elm(entry, data, name):
3435
elif data.get(name):
3536
# Surround xhtml with a div tag, parse it and embed it
3637
if type_ == 'xhtml':
37-
elm.append(etree.fromstring(
38-
'<div xmlns="http://www.w3.org/1999/xhtml">' +
39-
data.get(name) + '</div>'))
38+
xhtml = '<div xmlns="http://www.w3.org/1999/xhtml">' \
39+
+ data.get(name) + '</div>'
40+
elm.append(xml_fromstring(xhtml))
4041
elif type_ == 'CDATA':
41-
elm.text = etree.CDATA(
42-
data.get(name))
42+
elm.text = CDATA(data.get(name))
4343
# Parse XML and embed it
4444
elif type_ and (type_.endswith('/xml') or type_.endswith('+xml')):
45-
elm.append(etree.fromstring(
46-
data[name]))
45+
elm.append(xml_fromstring(data[name]))
4746
# Embed the text in escaped form
4847
elif not type_ or type_.startswith('text') or type_ == 'html':
4948
elm.text = data.get(name)
@@ -102,14 +101,14 @@ def __init__(self):
102101

103102
def atom_entry(self, extensions=True):
104103
'''Create an ATOM entry and return it.'''
105-
entry = etree.Element('entry')
104+
entry = xml_elem('entry')
106105
if not (self.__atom_id and self.__atom_title and self.__atom_updated):
107106
raise ValueError('Required fields not set')
108-
id = etree.SubElement(entry, 'id')
107+
id = xml_elem('id', entry)
109108
id.text = self.__atom_id
110-
title = etree.SubElement(entry, 'title')
109+
title = xml_elem('title', entry)
111110
title.text = self.__atom_title
112-
updated = etree.SubElement(entry, 'updated')
111+
updated = xml_elem('updated', entry)
113112
updated.text = self.__atom_updated.isoformat()
114113

115114
# An entry must contain an alternate link if there is no content
@@ -125,20 +124,20 @@ def atom_entry(self, extensions=True):
125124
# Atom requires a name. Skip elements without.
126125
if not a.get('name'):
127126
continue
128-
author = etree.SubElement(entry, 'author')
129-
name = etree.SubElement(author, 'name')
127+
author = xml_elem('author', entry)
128+
name = xml_elem('name', author)
130129
name.text = a.get('name')
131130
if a.get('email'):
132-
email = etree.SubElement(author, 'email')
131+
email = xml_elem('email', author)
133132
email.text = a.get('email')
134133
if a.get('uri'):
135-
uri = etree.SubElement(author, 'uri')
134+
uri = xml_elem('uri', author)
136135
uri.text = a.get('uri')
137136

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

140139
for l in self.__atom_link or []:
141-
link = etree.SubElement(entry, 'link', href=l['href'])
140+
link = xml_elem('link', entry, href=l['href'])
142141
if l.get('rel'):
143142
link.attrib['rel'] = l['rel']
144143
if l.get('type'):
@@ -153,7 +152,7 @@ def atom_entry(self, extensions=True):
153152
_add_text_elm(entry, self.__atom_summary, 'summary')
154153

155154
for c in self.__atom_category or []:
156-
cat = etree.SubElement(entry, 'category', term=c['term'])
155+
cat = xml_elem('category', entry, term=c['term'])
157156
if c.get('scheme'):
158157
cat.attrib['scheme'] = c['scheme']
159158
if c.get('label'):
@@ -164,32 +163,31 @@ def atom_entry(self, extensions=True):
164163
# Atom requires a name. Skip elements without.
165164
if not c.get('name'):
166165
continue
167-
contrib = etree.SubElement(entry, 'contributor')
168-
name = etree.SubElement(contrib, 'name')
166+
contrib = xml_elem('contributor', entry)
167+
name = xml_elem('name', contrib)
169168
name.text = c.get('name')
170169
if c.get('email'):
171-
email = etree.SubElement(contrib, 'email')
170+
email = xml_elem('email', contrib)
172171
email.text = c.get('email')
173172
if c.get('uri'):
174-
uri = etree.SubElement(contrib, 'uri')
173+
uri = xml_elem('uri', contrib)
175174
uri.text = c.get('uri')
176175

177176
if self.__atom_published:
178-
published = etree.SubElement(entry, 'published')
177+
published = xml_elem('published', entry)
179178
published.text = self.__atom_published.isoformat()
180179

181180
if self.__atom_rights:
182-
rights = etree.SubElement(entry, 'rights')
181+
rights = xml_elem('rights', entry)
183182
rights.text = self.__atom_rights
184183

185184
if self.__atom_source:
186-
source = etree.SubElement(entry, 'source')
185+
source = xml_elem('source', entry)
187186
if self.__atom_source.get('title'):
188-
source_title = etree.SubElement(source, 'title')
187+
source_title = xml_elem('title', source)
189188
source_title.text = self.__atom_source['title']
190189
if self.__atom_source.get('link'):
191-
etree.SubElement(source, 'link',
192-
href=self.__atom_source['link'])
190+
xml_elem('link', source, href=self.__atom_source['link'])
193191

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

201199
def rss_entry(self, extensions=True):
202200
'''Create a RSS item and return it.'''
203-
entry = etree.Element('item')
201+
entry = xml_elem('item')
204202
if not (self.__rss_title or
205203
self.__rss_description or
206204
self.__rss_content):
207205
raise ValueError('Required fields not set')
208206
if self.__rss_title:
209-
title = etree.SubElement(entry, 'title')
207+
title = xml_elem('title', entry)
210208
title.text = self.__rss_title
211209
if self.__rss_link:
212-
link = etree.SubElement(entry, 'link')
210+
link = xml_elem('link', entry)
213211
link.text = self.__rss_link
214212
if self.__rss_description and self.__rss_content:
215-
description = etree.SubElement(entry, 'description')
213+
description = xml_elem('description', entry)
216214
description.text = self.__rss_description
217215
XMLNS_CONTENT = 'http://purl.org/rss/1.0/modules/content/'
218-
content = etree.SubElement(entry, '{%s}encoded' % XMLNS_CONTENT)
219-
content.text = etree.CDATA(self.__rss_content['content']) \
216+
content = xml_elem('{%s}encoded' % XMLNS_CONTENT, entry)
217+
content.text = CDATA(self.__rss_content['content']) \
220218
if self.__rss_content.get('type', '') == 'CDATA' \
221219
else self.__rss_content['content']
222220
elif self.__rss_description:
223-
description = etree.SubElement(entry, 'description')
221+
description = xml_elem('description', entry)
224222
description.text = self.__rss_description
225223
elif self.__rss_content:
226-
description = etree.SubElement(entry, 'description')
227-
description.text = etree.CDATA(self.__rss_content['content']) \
224+
description = xml_elem('description', entry)
225+
description.text = CDATA(self.__rss_content['content']) \
228226
if self.__rss_content.get('type', '') == 'CDATA' \
229227
else self.__rss_content['content']
230228
for a in self.__rss_author or []:
231-
author = etree.SubElement(entry, 'author')
229+
author = xml_elem('author', entry)
232230
author.text = a
233231
if self.__rss_guid.get('guid'):
234-
guid = etree.SubElement(entry, 'guid')
232+
guid = xml_elem('guid', entry)
235233
guid.text = self.__rss_guid['guid']
236234
permaLink = str(self.__rss_guid.get('permalink', False)).lower()
237235
guid.attrib['isPermaLink'] = permaLink
238236
for cat in self.__rss_category or []:
239-
category = etree.SubElement(entry, 'category')
237+
category = xml_elem('category', entry)
240238
category.text = cat['value']
241239
if cat.get('domain'):
242240
category.attrib['domain'] = cat['domain']
243241
if self.__rss_comments:
244-
comments = etree.SubElement(entry, 'comments')
242+
comments = xml_elem('comments', entry)
245243
comments.text = self.__rss_comments
246244
if self.__rss_enclosure:
247-
enclosure = etree.SubElement(entry, 'enclosure')
245+
enclosure = xml_elem('enclosure', entry)
248246
enclosure.attrib['url'] = self.__rss_enclosure['url']
249247
enclosure.attrib['length'] = self.__rss_enclosure['length']
250248
enclosure.attrib['type'] = self.__rss_enclosure['type']
251249
if self.__rss_pubDate:
252-
pubDate = etree.SubElement(entry, 'pubDate')
250+
pubDate = xml_elem('pubDate', entry)
253251
pubDate.text = formatRFC2822(self.__rss_pubDate)
254252
if self.__rss_source:
255-
source = etree.SubElement(entry, 'source',
256-
url=self.__rss_source['url'])
253+
source = xml_elem('source', entry, url=self.__rss_source['url'])
257254
source.text = self.__rss_source['title']
258255

259256
if extensions:

feedgen/ext/dc.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,8 @@
1313
:license: FreeBSD and LGPL, see license.* for more details.
1414
'''
1515

16-
from lxml import etree
17-
1816
from feedgen.ext.base import BaseExtension
17+
from feedgen.util import xml_elem
1918

2019

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

48-
def _extend_xml(self, xml_elem):
49-
'''Extend xml_elem with set DC fields.
47+
def _extend_xml(self, xml_element):
48+
'''Extend xml_element with set DC fields.
5049
51-
:param xml_elem: etree element
50+
:param xml_element: etree element
5251
'''
5352
DCELEMENTS_NS = 'http://purl.org/dc/elements/1.1/'
5453

@@ -58,8 +57,8 @@ def _extend_xml(self, xml_elem):
5857
'identifier']:
5958
if hasattr(self, '_dcelem_%s' % elem):
6059
for val in getattr(self, '_dcelem_%s' % elem) or []:
61-
node = etree.SubElement(xml_elem,
62-
'{%s}%s' % (DCELEMENTS_NS, elem))
60+
node = xml_elem('{%s}%s' % (DCELEMENTS_NS, elem),
61+
xml_element)
6362
node.text = val
6463

6564
def extend_atom(self, atom_feed):

feedgen/ext/geo_entry.py

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
import numbers
1313
import warnings
1414

15-
from lxml import etree
1615
from feedgen.ext.base import BaseEntryExtension
16+
from feedgen.util import xml_elem
1717

1818

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

8888
if self.__point:
89-
point = etree.SubElement(entry, '{%s}point' % GEO_NS)
89+
point = xml_elem('{%s}point' % GEO_NS, entry)
9090
point.text = self.__point
9191

9292
if self.__line:
93-
line = etree.SubElement(entry, '{%s}line' % GEO_NS)
93+
line = xml_elem('{%s}line' % GEO_NS, entry)
9494
line.text = self.__line
9595

9696
if self.__polygon:
97-
polygon = etree.SubElement(entry, '{%s}polygon' % GEO_NS)
97+
polygon = xml_elem('{%s}polygon' % GEO_NS, entry)
9898
polygon.text = self.__polygon
9999

100100
if self.__box:
101-
box = etree.SubElement(entry, '{%s}box' % GEO_NS)
101+
box = xml_elem('{%s}box' % GEO_NS, entry)
102102
box.text = self.__box
103103

104104
if self.__featuretypetag:
105-
featuretypetag = etree.SubElement(
106-
entry,
107-
'{%s}featuretypetag' % GEO_NS
108-
)
105+
featuretypetag = xml_elem('{%s}featuretypetag' % GEO_NS, entry)
109106
featuretypetag.text = self.__featuretypetag
110107

111108
if self.__relationshiptag:
112-
relationshiptag = etree.SubElement(
113-
entry,
114-
'{%s}relationshiptag' % GEO_NS
115-
)
109+
relationshiptag = xml_elem('{%s}relationshiptag' % GEO_NS, entry)
116110
relationshiptag.text = self.__relationshiptag
117111

118112
if self.__featurename:
119-
featurename = etree.SubElement(entry, '{%s}featurename' % GEO_NS)
113+
featurename = xml_elem('{%s}featurename' % GEO_NS, entry)
120114
featurename.text = self.__featurename
121115

122116
if self.__elev:
123-
elevation = etree.SubElement(entry, '{%s}elev' % GEO_NS)
117+
elevation = xml_elem('{%s}elev' % GEO_NS, entry)
124118
elevation.text = str(self.__elev)
125119

126120
if self.__floor:
127-
floor = etree.SubElement(entry, '{%s}floor' % GEO_NS)
121+
floor = xml_elem('{%s}floor' % GEO_NS, entry)
128122
floor.text = str(self.__floor)
129123

130124
if self.__radius:
131-
radius = etree.SubElement(entry, '{%s}radius' % GEO_NS)
125+
radius = xml_elem('{%s}radius' % GEO_NS, entry)
132126
radius.text = str(self.__radius)
133127

134128
return entry

feedgen/ext/media.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,8 @@
1010
:license: FreeBSD and LGPL, see license.* for more details.
1111
'''
1212

13-
from lxml import etree
14-
1513
from feedgen.ext.base import BaseEntryExtension, BaseExtension
16-
from feedgen.util import ensure_format
14+
from feedgen.util import ensure_format, xml_elem
1715

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

@@ -45,10 +43,10 @@ def extend_atom(self, entry):
4543
# Define current media:group
4644
group = groups.get(media_content.get('group'))
4745
if group is None:
48-
group = etree.SubElement(entry, '{%s}group' % MEDIA_NS)
46+
group = xml_elem('{%s}group' % MEDIA_NS, entry)
4947
groups[media_content.get('group')] = group
5048
# Add content
51-
content = etree.SubElement(group, '{%s}content' % MEDIA_NS)
49+
content = xml_elem('{%s}content' % MEDIA_NS, group)
5250
for attr in ('url', 'fileSize', 'type', 'medium', 'isDefault',
5351
'expression', 'bitrate', 'framerate', 'samplingrate',
5452
'channels', 'duration', 'height', 'width', 'lang'):
@@ -59,10 +57,10 @@ def extend_atom(self, entry):
5957
# Define current media:group
6058
group = groups.get(media_thumbnail.get('group'))
6159
if group is None:
62-
group = etree.SubElement(entry, '{%s}group' % MEDIA_NS)
60+
group = xml_elem('{%s}group' % MEDIA_NS, entry)
6361
groups[media_thumbnail.get('group')] = group
6462
# Add thumbnails
65-
thumbnail = etree.SubElement(group, '{%s}thumbnail' % MEDIA_NS)
63+
thumbnail = xml_elem('{%s}thumbnail' % MEDIA_NS, group)
6664
for attr in ('url', 'height', 'width', 'time'):
6765
if media_thumbnail.get(attr):
6866
thumbnail.set(attr, media_thumbnail[attr])

0 commit comments

Comments
 (0)