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.
This commit is contained in:
parent
9440ccaffe
commit
0eb12f9133
10 changed files with 180 additions and 180 deletions
|
@ -3,7 +3,7 @@
|
||||||
feedgen.entry
|
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.
|
:license: FreeBSD and LGPL, see license.* for more details.
|
||||||
'''
|
'''
|
||||||
|
@ -13,10 +13,11 @@ from datetime import datetime
|
||||||
import dateutil.parser
|
import dateutil.parser
|
||||||
import dateutil.tz
|
import dateutil.tz
|
||||||
import warnings
|
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.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):
|
def _add_text_elm(entry, data, name):
|
||||||
|
@ -24,7 +25,7 @@ def _add_text_elm(entry, data, name):
|
||||||
if not data:
|
if not data:
|
||||||
return
|
return
|
||||||
|
|
||||||
elm = etree.SubElement(entry, name)
|
elm = xml_elem(name, entry)
|
||||||
type_ = data.get('type')
|
type_ = data.get('type')
|
||||||
if data.get('src'):
|
if data.get('src'):
|
||||||
if name != 'content':
|
if name != 'content':
|
||||||
|
@ -34,16 +35,14 @@ def _add_text_elm(entry, data, name):
|
||||||
elif data.get(name):
|
elif data.get(name):
|
||||||
# Surround xhtml with a div tag, parse it and embed it
|
# Surround xhtml with a div tag, parse it and embed it
|
||||||
if type_ == 'xhtml':
|
if type_ == 'xhtml':
|
||||||
elm.append(etree.fromstring(
|
xhtml = '<div xmlns="http://www.w3.org/1999/xhtml">' \
|
||||||
'<div xmlns="http://www.w3.org/1999/xhtml">' +
|
+ data.get(name) + '</div>'
|
||||||
data.get(name) + '</div>'))
|
elm.append(xml_fromstring(xhtml))
|
||||||
elif type_ == 'CDATA':
|
elif type_ == 'CDATA':
|
||||||
elm.text = etree.CDATA(
|
elm.text = CDATA(data.get(name))
|
||||||
data.get(name))
|
|
||||||
# Parse XML and embed it
|
# Parse XML and embed it
|
||||||
elif type_ and (type_.endswith('/xml') or type_.endswith('+xml')):
|
elif type_ and (type_.endswith('/xml') or type_.endswith('+xml')):
|
||||||
elm.append(etree.fromstring(
|
elm.append(xml_fromstring(data[name]))
|
||||||
data[name]))
|
|
||||||
# Embed the text in escaped form
|
# Embed the text in escaped form
|
||||||
elif not type_ or type_.startswith('text') or type_ == 'html':
|
elif not type_ or type_.startswith('text') or type_ == 'html':
|
||||||
elm.text = data.get(name)
|
elm.text = data.get(name)
|
||||||
|
@ -102,14 +101,14 @@ class FeedEntry(object):
|
||||||
|
|
||||||
def atom_entry(self, extensions=True):
|
def atom_entry(self, extensions=True):
|
||||||
'''Create an ATOM entry and return it.'''
|
'''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):
|
if not (self.__atom_id and self.__atom_title and self.__atom_updated):
|
||||||
raise ValueError('Required fields not set')
|
raise ValueError('Required fields not set')
|
||||||
id = etree.SubElement(entry, 'id')
|
id = xml_elem('id', entry)
|
||||||
id.text = self.__atom_id
|
id.text = self.__atom_id
|
||||||
title = etree.SubElement(entry, 'title')
|
title = xml_elem('title', entry)
|
||||||
title.text = self.__atom_title
|
title.text = self.__atom_title
|
||||||
updated = etree.SubElement(entry, 'updated')
|
updated = xml_elem('updated', entry)
|
||||||
updated.text = self.__atom_updated.isoformat()
|
updated.text = self.__atom_updated.isoformat()
|
||||||
|
|
||||||
# An entry must contain an alternate link if there is no content
|
# An entry must contain an alternate link if there is no content
|
||||||
|
@ -125,20 +124,20 @@ class FeedEntry(object):
|
||||||
# Atom requires a name. Skip elements without.
|
# Atom requires a name. Skip elements without.
|
||||||
if not a.get('name'):
|
if not a.get('name'):
|
||||||
continue
|
continue
|
||||||
author = etree.SubElement(entry, 'author')
|
author = xml_elem('author', entry)
|
||||||
name = etree.SubElement(author, 'name')
|
name = xml_elem('name', author)
|
||||||
name.text = a.get('name')
|
name.text = a.get('name')
|
||||||
if a.get('email'):
|
if a.get('email'):
|
||||||
email = etree.SubElement(author, 'email')
|
email = xml_elem('email', author)
|
||||||
email.text = a.get('email')
|
email.text = a.get('email')
|
||||||
if a.get('uri'):
|
if a.get('uri'):
|
||||||
uri = etree.SubElement(author, 'uri')
|
uri = xml_elem('uri', author)
|
||||||
uri.text = a.get('uri')
|
uri.text = a.get('uri')
|
||||||
|
|
||||||
_add_text_elm(entry, self.__atom_content, 'content')
|
_add_text_elm(entry, self.__atom_content, 'content')
|
||||||
|
|
||||||
for l in self.__atom_link or []:
|
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'):
|
if l.get('rel'):
|
||||||
link.attrib['rel'] = l['rel']
|
link.attrib['rel'] = l['rel']
|
||||||
if l.get('type'):
|
if l.get('type'):
|
||||||
|
@ -153,7 +152,7 @@ class FeedEntry(object):
|
||||||
_add_text_elm(entry, self.__atom_summary, 'summary')
|
_add_text_elm(entry, self.__atom_summary, 'summary')
|
||||||
|
|
||||||
for c in self.__atom_category or []:
|
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'):
|
if c.get('scheme'):
|
||||||
cat.attrib['scheme'] = c['scheme']
|
cat.attrib['scheme'] = c['scheme']
|
||||||
if c.get('label'):
|
if c.get('label'):
|
||||||
|
@ -164,32 +163,31 @@ class FeedEntry(object):
|
||||||
# Atom requires a name. Skip elements without.
|
# Atom requires a name. Skip elements without.
|
||||||
if not c.get('name'):
|
if not c.get('name'):
|
||||||
continue
|
continue
|
||||||
contrib = etree.SubElement(entry, 'contributor')
|
contrib = xml_elem('contributor', entry)
|
||||||
name = etree.SubElement(contrib, 'name')
|
name = xml_elem('name', contrib)
|
||||||
name.text = c.get('name')
|
name.text = c.get('name')
|
||||||
if c.get('email'):
|
if c.get('email'):
|
||||||
email = etree.SubElement(contrib, 'email')
|
email = xml_elem('email', contrib)
|
||||||
email.text = c.get('email')
|
email.text = c.get('email')
|
||||||
if c.get('uri'):
|
if c.get('uri'):
|
||||||
uri = etree.SubElement(contrib, 'uri')
|
uri = xml_elem('uri', contrib)
|
||||||
uri.text = c.get('uri')
|
uri.text = c.get('uri')
|
||||||
|
|
||||||
if self.__atom_published:
|
if self.__atom_published:
|
||||||
published = etree.SubElement(entry, 'published')
|
published = xml_elem('published', entry)
|
||||||
published.text = self.__atom_published.isoformat()
|
published.text = self.__atom_published.isoformat()
|
||||||
|
|
||||||
if self.__atom_rights:
|
if self.__atom_rights:
|
||||||
rights = etree.SubElement(entry, 'rights')
|
rights = xml_elem('rights', entry)
|
||||||
rights.text = self.__atom_rights
|
rights.text = self.__atom_rights
|
||||||
|
|
||||||
if self.__atom_source:
|
if self.__atom_source:
|
||||||
source = etree.SubElement(entry, 'source')
|
source = xml_elem('source', entry)
|
||||||
if self.__atom_source.get('title'):
|
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']
|
source_title.text = self.__atom_source['title']
|
||||||
if self.__atom_source.get('link'):
|
if self.__atom_source.get('link'):
|
||||||
etree.SubElement(source, 'link',
|
xml_elem('link', source, href=self.__atom_source['link'])
|
||||||
href=self.__atom_source['link'])
|
|
||||||
|
|
||||||
if extensions:
|
if extensions:
|
||||||
for ext in self.__extensions.values() or []:
|
for ext in self.__extensions.values() or []:
|
||||||
|
@ -200,60 +198,59 @@ class FeedEntry(object):
|
||||||
|
|
||||||
def rss_entry(self, extensions=True):
|
def rss_entry(self, extensions=True):
|
||||||
'''Create a RSS item and return it.'''
|
'''Create a RSS item and return it.'''
|
||||||
entry = etree.Element('item')
|
entry = xml_elem('item')
|
||||||
if not (self.__rss_title or
|
if not (self.__rss_title or
|
||||||
self.__rss_description or
|
self.__rss_description or
|
||||||
self.__rss_content):
|
self.__rss_content):
|
||||||
raise ValueError('Required fields not set')
|
raise ValueError('Required fields not set')
|
||||||
if self.__rss_title:
|
if self.__rss_title:
|
||||||
title = etree.SubElement(entry, 'title')
|
title = xml_elem('title', entry)
|
||||||
title.text = self.__rss_title
|
title.text = self.__rss_title
|
||||||
if self.__rss_link:
|
if self.__rss_link:
|
||||||
link = etree.SubElement(entry, 'link')
|
link = xml_elem('link', entry)
|
||||||
link.text = self.__rss_link
|
link.text = self.__rss_link
|
||||||
if self.__rss_description and self.__rss_content:
|
if self.__rss_description and self.__rss_content:
|
||||||
description = etree.SubElement(entry, 'description')
|
description = xml_elem('description', entry)
|
||||||
description.text = self.__rss_description
|
description.text = self.__rss_description
|
||||||
XMLNS_CONTENT = 'http://purl.org/rss/1.0/modules/content/'
|
XMLNS_CONTENT = 'http://purl.org/rss/1.0/modules/content/'
|
||||||
content = etree.SubElement(entry, '{%s}encoded' % XMLNS_CONTENT)
|
content = xml_elem('{%s}encoded' % XMLNS_CONTENT, entry)
|
||||||
content.text = etree.CDATA(self.__rss_content['content']) \
|
content.text = CDATA(self.__rss_content['content']) \
|
||||||
if self.__rss_content.get('type', '') == 'CDATA' \
|
if self.__rss_content.get('type', '') == 'CDATA' \
|
||||||
else self.__rss_content['content']
|
else self.__rss_content['content']
|
||||||
elif self.__rss_description:
|
elif self.__rss_description:
|
||||||
description = etree.SubElement(entry, 'description')
|
description = xml_elem('description', entry)
|
||||||
description.text = self.__rss_description
|
description.text = self.__rss_description
|
||||||
elif self.__rss_content:
|
elif self.__rss_content:
|
||||||
description = etree.SubElement(entry, 'description')
|
description = xml_elem('description', entry)
|
||||||
description.text = etree.CDATA(self.__rss_content['content']) \
|
description.text = CDATA(self.__rss_content['content']) \
|
||||||
if self.__rss_content.get('type', '') == 'CDATA' \
|
if self.__rss_content.get('type', '') == 'CDATA' \
|
||||||
else self.__rss_content['content']
|
else self.__rss_content['content']
|
||||||
for a in self.__rss_author or []:
|
for a in self.__rss_author or []:
|
||||||
author = etree.SubElement(entry, 'author')
|
author = xml_elem('author', entry)
|
||||||
author.text = a
|
author.text = a
|
||||||
if self.__rss_guid.get('guid'):
|
if self.__rss_guid.get('guid'):
|
||||||
guid = etree.SubElement(entry, 'guid')
|
guid = xml_elem('guid', entry)
|
||||||
guid.text = self.__rss_guid['guid']
|
guid.text = self.__rss_guid['guid']
|
||||||
permaLink = str(self.__rss_guid.get('permalink', False)).lower()
|
permaLink = str(self.__rss_guid.get('permalink', False)).lower()
|
||||||
guid.attrib['isPermaLink'] = permaLink
|
guid.attrib['isPermaLink'] = permaLink
|
||||||
for cat in self.__rss_category or []:
|
for cat in self.__rss_category or []:
|
||||||
category = etree.SubElement(entry, 'category')
|
category = xml_elem('category', entry)
|
||||||
category.text = cat['value']
|
category.text = cat['value']
|
||||||
if cat.get('domain'):
|
if cat.get('domain'):
|
||||||
category.attrib['domain'] = cat['domain']
|
category.attrib['domain'] = cat['domain']
|
||||||
if self.__rss_comments:
|
if self.__rss_comments:
|
||||||
comments = etree.SubElement(entry, 'comments')
|
comments = xml_elem('comments', entry)
|
||||||
comments.text = self.__rss_comments
|
comments.text = self.__rss_comments
|
||||||
if self.__rss_enclosure:
|
if self.__rss_enclosure:
|
||||||
enclosure = etree.SubElement(entry, 'enclosure')
|
enclosure = xml_elem('enclosure', entry)
|
||||||
enclosure.attrib['url'] = self.__rss_enclosure['url']
|
enclosure.attrib['url'] = self.__rss_enclosure['url']
|
||||||
enclosure.attrib['length'] = self.__rss_enclosure['length']
|
enclosure.attrib['length'] = self.__rss_enclosure['length']
|
||||||
enclosure.attrib['type'] = self.__rss_enclosure['type']
|
enclosure.attrib['type'] = self.__rss_enclosure['type']
|
||||||
if self.__rss_pubDate:
|
if self.__rss_pubDate:
|
||||||
pubDate = etree.SubElement(entry, 'pubDate')
|
pubDate = xml_elem('pubDate', entry)
|
||||||
pubDate.text = formatRFC2822(self.__rss_pubDate)
|
pubDate.text = formatRFC2822(self.__rss_pubDate)
|
||||||
if self.__rss_source:
|
if self.__rss_source:
|
||||||
source = etree.SubElement(entry, 'source',
|
source = xml_elem('source', entry, url=self.__rss_source['url'])
|
||||||
url=self.__rss_source['url'])
|
|
||||||
source.text = self.__rss_source['title']
|
source.text = self.__rss_source['title']
|
||||||
|
|
||||||
if extensions:
|
if extensions:
|
||||||
|
|
|
@ -13,9 +13,8 @@
|
||||||
:license: FreeBSD and LGPL, see license.* for more details.
|
:license: FreeBSD and LGPL, see license.* for more details.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from lxml import etree
|
|
||||||
|
|
||||||
from feedgen.ext.base import BaseExtension
|
from feedgen.ext.base import BaseExtension
|
||||||
|
from feedgen.util import xml_elem
|
||||||
|
|
||||||
|
|
||||||
class DcBaseExtension(BaseExtension):
|
class DcBaseExtension(BaseExtension):
|
||||||
|
@ -45,10 +44,10 @@ class DcBaseExtension(BaseExtension):
|
||||||
def extend_ns(self):
|
def extend_ns(self):
|
||||||
return {'dc': 'http://purl.org/dc/elements/1.1/'}
|
return {'dc': 'http://purl.org/dc/elements/1.1/'}
|
||||||
|
|
||||||
def _extend_xml(self, xml_elem):
|
def _extend_xml(self, xml_element):
|
||||||
'''Extend xml_elem with set DC fields.
|
'''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/'
|
DCELEMENTS_NS = 'http://purl.org/dc/elements/1.1/'
|
||||||
|
|
||||||
|
@ -58,8 +57,8 @@ class DcBaseExtension(BaseExtension):
|
||||||
'identifier']:
|
'identifier']:
|
||||||
if hasattr(self, '_dcelem_%s' % elem):
|
if hasattr(self, '_dcelem_%s' % elem):
|
||||||
for val in getattr(self, '_dcelem_%s' % elem) or []:
|
for val in getattr(self, '_dcelem_%s' % elem) or []:
|
||||||
node = etree.SubElement(xml_elem,
|
node = xml_elem('{%s}%s' % (DCELEMENTS_NS, elem),
|
||||||
'{%s}%s' % (DCELEMENTS_NS, elem))
|
xml_element)
|
||||||
node.text = val
|
node.text = val
|
||||||
|
|
||||||
def extend_atom(self, atom_feed):
|
def extend_atom(self, atom_feed):
|
||||||
|
|
|
@ -12,8 +12,8 @@
|
||||||
import numbers
|
import numbers
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
from lxml import etree
|
|
||||||
from feedgen.ext.base import BaseEntryExtension
|
from feedgen.ext.base import BaseEntryExtension
|
||||||
|
from feedgen.util import xml_elem
|
||||||
|
|
||||||
|
|
||||||
class GeoRSSPolygonInteriorWarning(Warning):
|
class GeoRSSPolygonInteriorWarning(Warning):
|
||||||
|
@ -86,49 +86,43 @@ class GeoEntryExtension(BaseEntryExtension):
|
||||||
GEO_NS = 'http://www.georss.org/georss'
|
GEO_NS = 'http://www.georss.org/georss'
|
||||||
|
|
||||||
if self.__point:
|
if self.__point:
|
||||||
point = etree.SubElement(entry, '{%s}point' % GEO_NS)
|
point = xml_elem('{%s}point' % GEO_NS, entry)
|
||||||
point.text = self.__point
|
point.text = self.__point
|
||||||
|
|
||||||
if self.__line:
|
if self.__line:
|
||||||
line = etree.SubElement(entry, '{%s}line' % GEO_NS)
|
line = xml_elem('{%s}line' % GEO_NS, entry)
|
||||||
line.text = self.__line
|
line.text = self.__line
|
||||||
|
|
||||||
if self.__polygon:
|
if self.__polygon:
|
||||||
polygon = etree.SubElement(entry, '{%s}polygon' % GEO_NS)
|
polygon = xml_elem('{%s}polygon' % GEO_NS, entry)
|
||||||
polygon.text = self.__polygon
|
polygon.text = self.__polygon
|
||||||
|
|
||||||
if self.__box:
|
if self.__box:
|
||||||
box = etree.SubElement(entry, '{%s}box' % GEO_NS)
|
box = xml_elem('{%s}box' % GEO_NS, entry)
|
||||||
box.text = self.__box
|
box.text = self.__box
|
||||||
|
|
||||||
if self.__featuretypetag:
|
if self.__featuretypetag:
|
||||||
featuretypetag = etree.SubElement(
|
featuretypetag = xml_elem('{%s}featuretypetag' % GEO_NS, entry)
|
||||||
entry,
|
|
||||||
'{%s}featuretypetag' % GEO_NS
|
|
||||||
)
|
|
||||||
featuretypetag.text = self.__featuretypetag
|
featuretypetag.text = self.__featuretypetag
|
||||||
|
|
||||||
if self.__relationshiptag:
|
if self.__relationshiptag:
|
||||||
relationshiptag = etree.SubElement(
|
relationshiptag = xml_elem('{%s}relationshiptag' % GEO_NS, entry)
|
||||||
entry,
|
|
||||||
'{%s}relationshiptag' % GEO_NS
|
|
||||||
)
|
|
||||||
relationshiptag.text = self.__relationshiptag
|
relationshiptag.text = self.__relationshiptag
|
||||||
|
|
||||||
if self.__featurename:
|
if self.__featurename:
|
||||||
featurename = etree.SubElement(entry, '{%s}featurename' % GEO_NS)
|
featurename = xml_elem('{%s}featurename' % GEO_NS, entry)
|
||||||
featurename.text = self.__featurename
|
featurename.text = self.__featurename
|
||||||
|
|
||||||
if self.__elev:
|
if self.__elev:
|
||||||
elevation = etree.SubElement(entry, '{%s}elev' % GEO_NS)
|
elevation = xml_elem('{%s}elev' % GEO_NS, entry)
|
||||||
elevation.text = str(self.__elev)
|
elevation.text = str(self.__elev)
|
||||||
|
|
||||||
if self.__floor:
|
if self.__floor:
|
||||||
floor = etree.SubElement(entry, '{%s}floor' % GEO_NS)
|
floor = xml_elem('{%s}floor' % GEO_NS, entry)
|
||||||
floor.text = str(self.__floor)
|
floor.text = str(self.__floor)
|
||||||
|
|
||||||
if self.__radius:
|
if self.__radius:
|
||||||
radius = etree.SubElement(entry, '{%s}radius' % GEO_NS)
|
radius = xml_elem('{%s}radius' % GEO_NS, entry)
|
||||||
radius.text = str(self.__radius)
|
radius.text = str(self.__radius)
|
||||||
|
|
||||||
return entry
|
return entry
|
||||||
|
|
|
@ -10,10 +10,8 @@
|
||||||
:license: FreeBSD and LGPL, see license.* for more details.
|
:license: FreeBSD and LGPL, see license.* for more details.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from lxml import etree
|
|
||||||
|
|
||||||
from feedgen.ext.base import BaseEntryExtension, BaseExtension
|
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/'
|
MEDIA_NS = 'http://search.yahoo.com/mrss/'
|
||||||
|
|
||||||
|
@ -45,10 +43,10 @@ class MediaEntryExtension(BaseEntryExtension):
|
||||||
# Define current media:group
|
# Define current media:group
|
||||||
group = groups.get(media_content.get('group'))
|
group = groups.get(media_content.get('group'))
|
||||||
if group is None:
|
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
|
groups[media_content.get('group')] = group
|
||||||
# Add content
|
# 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',
|
for attr in ('url', 'fileSize', 'type', 'medium', 'isDefault',
|
||||||
'expression', 'bitrate', 'framerate', 'samplingrate',
|
'expression', 'bitrate', 'framerate', 'samplingrate',
|
||||||
'channels', 'duration', 'height', 'width', 'lang'):
|
'channels', 'duration', 'height', 'width', 'lang'):
|
||||||
|
@ -59,10 +57,10 @@ class MediaEntryExtension(BaseEntryExtension):
|
||||||
# Define current media:group
|
# Define current media:group
|
||||||
group = groups.get(media_thumbnail.get('group'))
|
group = groups.get(media_thumbnail.get('group'))
|
||||||
if group is None:
|
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
|
groups[media_thumbnail.get('group')] = group
|
||||||
# Add thumbnails
|
# 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'):
|
for attr in ('url', 'height', 'width', 'time'):
|
||||||
if media_thumbnail.get(attr):
|
if media_thumbnail.get(attr):
|
||||||
thumbnail.set(attr, media_thumbnail[attr])
|
thumbnail.set(attr, media_thumbnail[attr])
|
||||||
|
|
|
@ -10,11 +10,9 @@
|
||||||
:license: FreeBSD and LGPL, see license.* for more details.
|
:license: FreeBSD and LGPL, see license.* for more details.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from lxml import etree
|
|
||||||
|
|
||||||
from feedgen.compat import string_types
|
from feedgen.compat import string_types
|
||||||
from feedgen.ext.base import BaseExtension
|
from feedgen.ext.base import BaseExtension
|
||||||
from feedgen.util import ensure_format
|
from feedgen.util import ensure_format, xml_elem
|
||||||
|
|
||||||
|
|
||||||
class PodcastExtension(BaseExtension):
|
class PodcastExtension(BaseExtension):
|
||||||
|
@ -47,11 +45,11 @@ class PodcastExtension(BaseExtension):
|
||||||
channel = rss_feed[0]
|
channel = rss_feed[0]
|
||||||
|
|
||||||
if self.__itunes_author:
|
if self.__itunes_author:
|
||||||
author = etree.SubElement(channel, '{%s}author' % ITUNES_NS)
|
author = xml_elem('{%s}author' % ITUNES_NS, channel)
|
||||||
author.text = self.__itunes_author
|
author.text = self.__itunes_author
|
||||||
|
|
||||||
if self.__itunes_block is not None:
|
if self.__itunes_block is not None:
|
||||||
block = etree.SubElement(channel, '{%s}block' % ITUNES_NS)
|
block = xml_elem('{%s}block' % ITUNES_NS, channel)
|
||||||
block.text = 'yes' if self.__itunes_block else 'no'
|
block.text = 'yes' if self.__itunes_block else 'no'
|
||||||
|
|
||||||
for c in self.__itunes_category or []:
|
for c in self.__itunes_category or []:
|
||||||
|
@ -60,45 +58,42 @@ class PodcastExtension(BaseExtension):
|
||||||
category = channel.find(
|
category = channel.find(
|
||||||
'{%s}category[@text="%s"]' % (ITUNES_NS, c.get('cat')))
|
'{%s}category[@text="%s"]' % (ITUNES_NS, c.get('cat')))
|
||||||
if category is None:
|
if category is None:
|
||||||
category = etree.SubElement(channel,
|
category = xml_elem('{%s}category' % ITUNES_NS, channel)
|
||||||
'{%s}category' % ITUNES_NS)
|
|
||||||
category.attrib['text'] = c.get('cat')
|
category.attrib['text'] = c.get('cat')
|
||||||
|
|
||||||
if c.get('sub'):
|
if c.get('sub'):
|
||||||
subcategory = etree.SubElement(category,
|
subcategory = xml_elem('{%s}category' % ITUNES_NS, category)
|
||||||
'{%s}category' % ITUNES_NS)
|
|
||||||
subcategory.attrib['text'] = c.get('sub')
|
subcategory.attrib['text'] = c.get('sub')
|
||||||
|
|
||||||
if self.__itunes_image:
|
if self.__itunes_image:
|
||||||
image = etree.SubElement(channel, '{%s}image' % ITUNES_NS)
|
image = xml_elem('{%s}image' % ITUNES_NS, channel)
|
||||||
image.attrib['href'] = self.__itunes_image
|
image.attrib['href'] = self.__itunes_image
|
||||||
|
|
||||||
if self.__itunes_explicit in ('yes', 'no', 'clean'):
|
if self.__itunes_explicit in ('yes', 'no', 'clean'):
|
||||||
explicit = etree.SubElement(channel, '{%s}explicit' % ITUNES_NS)
|
explicit = xml_elem('{%s}explicit' % ITUNES_NS, channel)
|
||||||
explicit.text = self.__itunes_explicit
|
explicit.text = self.__itunes_explicit
|
||||||
|
|
||||||
if self.__itunes_complete in ('yes', 'no'):
|
if self.__itunes_complete in ('yes', 'no'):
|
||||||
complete = etree.SubElement(channel, '{%s}complete' % ITUNES_NS)
|
complete = xml_elem('{%s}complete' % ITUNES_NS, channel)
|
||||||
complete.text = self.__itunes_complete
|
complete.text = self.__itunes_complete
|
||||||
|
|
||||||
if self.__itunes_new_feed_url:
|
if self.__itunes_new_feed_url:
|
||||||
new_feed_url = etree.SubElement(channel,
|
new_feed_url = xml_elem('{%s}new-feed-url' % ITUNES_NS, channel)
|
||||||
'{%s}new-feed-url' % ITUNES_NS)
|
|
||||||
new_feed_url.text = self.__itunes_new_feed_url
|
new_feed_url.text = self.__itunes_new_feed_url
|
||||||
|
|
||||||
if self.__itunes_owner:
|
if self.__itunes_owner:
|
||||||
owner = etree.SubElement(channel, '{%s}owner' % ITUNES_NS)
|
owner = xml_elem('{%s}owner' % ITUNES_NS, channel)
|
||||||
owner_name = etree.SubElement(owner, '{%s}name' % ITUNES_NS)
|
owner_name = xml_elem('{%s}name' % ITUNES_NS, owner)
|
||||||
owner_name.text = self.__itunes_owner.get('name')
|
owner_name.text = self.__itunes_owner.get('name')
|
||||||
owner_email = etree.SubElement(owner, '{%s}email' % ITUNES_NS)
|
owner_email = xml_elem('{%s}email' % ITUNES_NS, owner)
|
||||||
owner_email.text = self.__itunes_owner.get('email')
|
owner_email.text = self.__itunes_owner.get('email')
|
||||||
|
|
||||||
if self.__itunes_subtitle:
|
if self.__itunes_subtitle:
|
||||||
subtitle = etree.SubElement(channel, '{%s}subtitle' % ITUNES_NS)
|
subtitle = xml_elem('{%s}subtitle' % ITUNES_NS, channel)
|
||||||
subtitle.text = self.__itunes_subtitle
|
subtitle.text = self.__itunes_subtitle
|
||||||
|
|
||||||
if self.__itunes_summary:
|
if self.__itunes_summary:
|
||||||
summary = etree.SubElement(channel, '{%s}summary' % ITUNES_NS)
|
summary = xml_elem('{%s}summary' % ITUNES_NS, channel)
|
||||||
summary.text = self.__itunes_summary
|
summary.text = self.__itunes_summary
|
||||||
|
|
||||||
return rss_feed
|
return rss_feed
|
||||||
|
|
|
@ -10,9 +10,8 @@
|
||||||
:license: FreeBSD and LGPL, see license.* for more details.
|
:license: FreeBSD and LGPL, see license.* for more details.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from lxml import etree
|
|
||||||
|
|
||||||
from feedgen.ext.base import BaseEntryExtension
|
from feedgen.ext.base import BaseEntryExtension
|
||||||
|
from feedgen.util import xml_elem
|
||||||
|
|
||||||
|
|
||||||
class PodcastEntryExtension(BaseEntryExtension):
|
class PodcastEntryExtension(BaseEntryExtension):
|
||||||
|
@ -40,43 +39,43 @@ class PodcastEntryExtension(BaseEntryExtension):
|
||||||
ITUNES_NS = 'http://www.itunes.com/dtds/podcast-1.0.dtd'
|
ITUNES_NS = 'http://www.itunes.com/dtds/podcast-1.0.dtd'
|
||||||
|
|
||||||
if self.__itunes_author:
|
if self.__itunes_author:
|
||||||
author = etree.SubElement(entry, '{%s}author' % ITUNES_NS)
|
author = xml_elem('{%s}author' % ITUNES_NS, entry)
|
||||||
author.text = self.__itunes_author
|
author.text = self.__itunes_author
|
||||||
|
|
||||||
if self.__itunes_block is not None:
|
if self.__itunes_block is not None:
|
||||||
block = etree.SubElement(entry, '{%s}block' % ITUNES_NS)
|
block = xml_elem('{%s}block' % ITUNES_NS, entry)
|
||||||
block.text = 'yes' if self.__itunes_block else 'no'
|
block.text = 'yes' if self.__itunes_block else 'no'
|
||||||
|
|
||||||
if self.__itunes_image:
|
if self.__itunes_image:
|
||||||
image = etree.SubElement(entry, '{%s}image' % ITUNES_NS)
|
image = xml_elem('{%s}image' % ITUNES_NS, entry)
|
||||||
image.attrib['href'] = self.__itunes_image
|
image.attrib['href'] = self.__itunes_image
|
||||||
|
|
||||||
if self.__itunes_duration:
|
if self.__itunes_duration:
|
||||||
duration = etree.SubElement(entry, '{%s}duration' % ITUNES_NS)
|
duration = xml_elem('{%s}duration' % ITUNES_NS, entry)
|
||||||
duration.text = self.__itunes_duration
|
duration.text = self.__itunes_duration
|
||||||
|
|
||||||
if self.__itunes_explicit in ('yes', 'no', 'clean'):
|
if self.__itunes_explicit in ('yes', 'no', 'clean'):
|
||||||
explicit = etree.SubElement(entry, '{%s}explicit' % ITUNES_NS)
|
explicit = xml_elem('{%s}explicit' % ITUNES_NS, entry)
|
||||||
explicit.text = self.__itunes_explicit
|
explicit.text = self.__itunes_explicit
|
||||||
|
|
||||||
if self.__itunes_is_closed_captioned is not None:
|
if self.__itunes_is_closed_captioned is not None:
|
||||||
is_closed_captioned = etree.SubElement(
|
is_closed_captioned = xml_elem(
|
||||||
entry, '{%s}isClosedCaptioned' % ITUNES_NS)
|
'{%s}isClosedCaptioned' % ITUNES_NS, entry)
|
||||||
if self.__itunes_is_closed_captioned:
|
if self.__itunes_is_closed_captioned:
|
||||||
is_closed_captioned.text = 'yes'
|
is_closed_captioned.text = 'yes'
|
||||||
else:
|
else:
|
||||||
is_closed_captioned.text = 'no'
|
is_closed_captioned.text = 'no'
|
||||||
|
|
||||||
if self.__itunes_order is not None and self.__itunes_order >= 0:
|
if self.__itunes_order is not None and self.__itunes_order >= 0:
|
||||||
order = etree.SubElement(entry, '{%s}order' % ITUNES_NS)
|
order = xml_elem('{%s}order' % ITUNES_NS, entry)
|
||||||
order.text = str(self.__itunes_order)
|
order.text = str(self.__itunes_order)
|
||||||
|
|
||||||
if self.__itunes_subtitle:
|
if self.__itunes_subtitle:
|
||||||
subtitle = etree.SubElement(entry, '{%s}subtitle' % ITUNES_NS)
|
subtitle = xml_elem('{%s}subtitle' % ITUNES_NS, entry)
|
||||||
subtitle.text = self.__itunes_subtitle
|
subtitle.text = self.__itunes_subtitle
|
||||||
|
|
||||||
if self.__itunes_summary:
|
if self.__itunes_summary:
|
||||||
summary = etree.SubElement(entry, '{%s}summary' % ITUNES_NS)
|
summary = xml_elem('{%s}summary' % ITUNES_NS, entry)
|
||||||
summary.text = self.__itunes_summary
|
summary.text = self.__itunes_summary
|
||||||
return entry
|
return entry
|
||||||
|
|
||||||
|
|
|
@ -10,9 +10,8 @@ See below for details
|
||||||
http://web.resource.org/rss/1.0/modules/syndication/
|
http://web.resource.org/rss/1.0/modules/syndication/
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from lxml import etree
|
|
||||||
|
|
||||||
from feedgen.ext.base import BaseExtension
|
from feedgen.ext.base import BaseExtension
|
||||||
|
from feedgen.util import xml_elem
|
||||||
|
|
||||||
SYNDICATION_NS = 'http://purl.org/rss/1.0/modules/syndication/'
|
SYNDICATION_NS = 'http://purl.org/rss/1.0/modules/syndication/'
|
||||||
PERIOD_TYPE = ('hourly', 'daily', 'weekly', 'monthly', 'yearly')
|
PERIOD_TYPE = ('hourly', 'daily', 'weekly', 'monthly', 'yearly')
|
||||||
|
@ -20,7 +19,7 @@ PERIOD_TYPE = ('hourly', 'daily', 'weekly', 'monthly', 'yearly')
|
||||||
|
|
||||||
def _set_value(channel, name, value):
|
def _set_value(channel, name, value):
|
||||||
if value:
|
if value:
|
||||||
newelem = etree.SubElement(channel, '{%s}' % SYNDICATION_NS + name)
|
newelem = xml_elem('{%s}' % SYNDICATION_NS + name, channel)
|
||||||
newelem.text = value
|
newelem.text = value
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -10,9 +10,8 @@
|
||||||
:license: FreeBSD and LGPL, see license.* for more details.
|
:license: FreeBSD and LGPL, see license.* for more details.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from lxml import etree
|
|
||||||
|
|
||||||
from feedgen.ext.base import BaseEntryExtension, BaseExtension
|
from feedgen.ext.base import BaseEntryExtension, BaseExtension
|
||||||
|
from feedgen.util import xml_elem
|
||||||
|
|
||||||
TORRENT_NS = 'http://xmlns.ezrss.it/0.1/dtd/'
|
TORRENT_NS = 'http://xmlns.ezrss.it/0.1/dtd/'
|
||||||
|
|
||||||
|
@ -41,30 +40,29 @@ class TorrentEntryExtension(BaseEntryExtension):
|
||||||
:param feed: The RSS item XML element to use.
|
:param feed: The RSS item XML element to use.
|
||||||
'''
|
'''
|
||||||
if self.__torrent_filename:
|
if self.__torrent_filename:
|
||||||
filename = etree.SubElement(entry, '{%s}filename' % TORRENT_NS)
|
filename = xml_elem('{%s}filename' % TORRENT_NS, entry)
|
||||||
filename.text = self.__torrent_filename
|
filename.text = self.__torrent_filename
|
||||||
|
|
||||||
if self.__torrent_contentlength:
|
if self.__torrent_contentlength:
|
||||||
contentlength = etree.SubElement(entry,
|
contentlength = xml_elem('{%s}contentlength' % TORRENT_NS, entry)
|
||||||
'{%s}contentlength' % TORRENT_NS)
|
|
||||||
contentlength.text = self.__torrent_contentlength
|
contentlength.text = self.__torrent_contentlength
|
||||||
|
|
||||||
if self.__torrent_infohash:
|
if self.__torrent_infohash:
|
||||||
infohash = etree.SubElement(entry, '{%s}infohash' % TORRENT_NS)
|
infohash = xml_elem('{%s}infohash' % TORRENT_NS, entry)
|
||||||
infohash.text = self.__torrent_infohash
|
infohash.text = self.__torrent_infohash
|
||||||
magnet = etree.SubElement(entry, '{%s}magneturi' % TORRENT_NS)
|
magnet = xml_elem('{%s}magneturi' % TORRENT_NS, entry)
|
||||||
magnet.text = 'magnet:?xt=urn:btih:' + self.__torrent_infohash
|
magnet.text = 'magnet:?xt=urn:btih:' + self.__torrent_infohash
|
||||||
|
|
||||||
if self.__torrent_seeds:
|
if self.__torrent_seeds:
|
||||||
seeds = etree.SubElement(entry, '{%s}seed' % TORRENT_NS)
|
seeds = xml_elem('{%s}seed' % TORRENT_NS, entry)
|
||||||
seeds.text = self.__torrent_seeds
|
seeds.text = self.__torrent_seeds
|
||||||
|
|
||||||
if self.__torrent_peers:
|
if self.__torrent_peers:
|
||||||
peers = etree.SubElement(entry, '{%s}peers' % TORRENT_NS)
|
peers = xml_elem('{%s}peers' % TORRENT_NS, entry)
|
||||||
peers.text = self.__torrent_peers
|
peers.text = self.__torrent_peers
|
||||||
|
|
||||||
if self.__torrent_verified:
|
if self.__torrent_verified:
|
||||||
verified = etree.SubElement(entry, '{%s}verified' % TORRENT_NS)
|
verified = xml_elem('{%s}verified' % TORRENT_NS, entry)
|
||||||
verified.text = self.__torrent_verified
|
verified.text = self.__torrent_verified
|
||||||
|
|
||||||
def filename(self, torrent_filename=None):
|
def filename(self, torrent_filename=None):
|
||||||
|
|
109
feedgen/feed.py
109
feedgen/feed.py
|
@ -3,7 +3,7 @@
|
||||||
feedgen.feed
|
feedgen.feed
|
||||||
~~~~~~~~~~~~
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
:copyright: 2013-2016, Lars Kiesow <lkiesow@uos.de>
|
:copyright: 2013-2020, Lars Kiesow <lkiesow@uos.de>
|
||||||
|
|
||||||
:license: FreeBSD and LGPL, see license.* for more details.
|
:license: FreeBSD and LGPL, see license.* for more details.
|
||||||
|
|
||||||
|
@ -14,12 +14,12 @@ from datetime import datetime
|
||||||
|
|
||||||
import dateutil.parser
|
import dateutil.parser
|
||||||
import dateutil.tz
|
import dateutil.tz
|
||||||
from lxml import etree
|
from lxml import etree # nosec - not using this for parsing
|
||||||
|
|
||||||
import feedgen.version
|
import feedgen.version
|
||||||
from feedgen.compat import string_types
|
from feedgen.compat import string_types
|
||||||
from feedgen.entry import FeedEntry
|
from feedgen.entry import FeedEntry
|
||||||
from feedgen.util import ensure_format, formatRFC2822
|
from feedgen.util import ensure_format, formatRFC2822, xml_elem
|
||||||
|
|
||||||
_feedgen_version = feedgen.version.version_str
|
_feedgen_version = feedgen.version.version_str
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ class FeedGenerator(object):
|
||||||
self.__atom_contributor = None
|
self.__atom_contributor = None
|
||||||
self.__atom_generator = {
|
self.__atom_generator = {
|
||||||
'value': 'python-feedgen',
|
'value': 'python-feedgen',
|
||||||
'uri': 'http://lkiesow.github.io/python-feedgen',
|
'uri': 'https://lkiesow.github.io/python-feedgen',
|
||||||
'version': feedgen.version.version_str} # {value*,uri,version}
|
'version': feedgen.version.version_str} # {value*,uri,version}
|
||||||
self.__atom_icon = None
|
self.__atom_icon = None
|
||||||
self.__atom_logo = None
|
self.__atom_logo = None
|
||||||
|
@ -95,7 +95,7 @@ class FeedGenerator(object):
|
||||||
if ext.get('atom'):
|
if ext.get('atom'):
|
||||||
nsmap.update(ext['inst'].extend_ns())
|
nsmap.update(ext['inst'].extend_ns())
|
||||||
|
|
||||||
feed = etree.Element('feed',
|
feed = xml_elem('feed',
|
||||||
xmlns='http://www.w3.org/2005/Atom',
|
xmlns='http://www.w3.org/2005/Atom',
|
||||||
nsmap=nsmap)
|
nsmap=nsmap)
|
||||||
if self.__atom_feed_xml_lang:
|
if self.__atom_feed_xml_lang:
|
||||||
|
@ -108,11 +108,11 @@ class FeedGenerator(object):
|
||||||
([] if self.__atom_updated else ['updated'])
|
([] if self.__atom_updated else ['updated'])
|
||||||
missing = ', '.join(missing)
|
missing = ', '.join(missing)
|
||||||
raise ValueError('Required fields not set (%s)' % missing)
|
raise ValueError('Required fields not set (%s)' % missing)
|
||||||
id = etree.SubElement(feed, 'id')
|
id = xml_elem('id', feed)
|
||||||
id.text = self.__atom_id
|
id.text = self.__atom_id
|
||||||
title = etree.SubElement(feed, 'title')
|
title = xml_elem('title', feed)
|
||||||
title.text = self.__atom_title
|
title.text = self.__atom_title
|
||||||
updated = etree.SubElement(feed, 'updated')
|
updated = xml_elem('updated', feed)
|
||||||
updated.text = self.__atom_updated.isoformat()
|
updated.text = self.__atom_updated.isoformat()
|
||||||
|
|
||||||
# Add author elements
|
# Add author elements
|
||||||
|
@ -120,18 +120,18 @@ class FeedGenerator(object):
|
||||||
# Atom requires a name. Skip elements without.
|
# Atom requires a name. Skip elements without.
|
||||||
if not a.get('name'):
|
if not a.get('name'):
|
||||||
continue
|
continue
|
||||||
author = etree.SubElement(feed, 'author')
|
author = xml_elem('author', feed)
|
||||||
name = etree.SubElement(author, 'name')
|
name = xml_elem('name', author)
|
||||||
name.text = a.get('name')
|
name.text = a.get('name')
|
||||||
if a.get('email'):
|
if a.get('email'):
|
||||||
email = etree.SubElement(author, 'email')
|
email = xml_elem('email', author)
|
||||||
email.text = a.get('email')
|
email.text = a.get('email')
|
||||||
if a.get('uri'):
|
if a.get('uri'):
|
||||||
uri = etree.SubElement(author, 'uri')
|
uri = xml_elem('uri', author)
|
||||||
uri.text = a.get('uri')
|
uri.text = a.get('uri')
|
||||||
|
|
||||||
for l in self.__atom_link or []:
|
for l in self.__atom_link or []:
|
||||||
link = etree.SubElement(feed, 'link', href=l['href'])
|
link = xml_elem('link', feed, href=l['href'])
|
||||||
if l.get('rel'):
|
if l.get('rel'):
|
||||||
link.attrib['rel'] = l['rel']
|
link.attrib['rel'] = l['rel']
|
||||||
if l.get('type'):
|
if l.get('type'):
|
||||||
|
@ -144,7 +144,7 @@ class FeedGenerator(object):
|
||||||
link.attrib['length'] = l['length']
|
link.attrib['length'] = l['length']
|
||||||
|
|
||||||
for c in self.__atom_category or []:
|
for c in self.__atom_category or []:
|
||||||
cat = etree.SubElement(feed, 'category', term=c['term'])
|
cat = xml_elem('category', feed, term=c['term'])
|
||||||
if c.get('scheme'):
|
if c.get('scheme'):
|
||||||
cat.attrib['scheme'] = c['scheme']
|
cat.attrib['scheme'] = c['scheme']
|
||||||
if c.get('label'):
|
if c.get('label'):
|
||||||
|
@ -155,18 +155,18 @@ class FeedGenerator(object):
|
||||||
# Atom requires a name. Skip elements without.
|
# Atom requires a name. Skip elements without.
|
||||||
if not c.get('name'):
|
if not c.get('name'):
|
||||||
continue
|
continue
|
||||||
contrib = etree.SubElement(feed, 'contributor')
|
contrib = xml_elem('contributor', feed)
|
||||||
name = etree.SubElement(contrib, 'name')
|
name = xml_elem('name', contrib)
|
||||||
name.text = c.get('name')
|
name.text = c.get('name')
|
||||||
if c.get('email'):
|
if c.get('email'):
|
||||||
email = etree.SubElement(contrib, 'email')
|
email = xml_elem('email', contrib)
|
||||||
email.text = c.get('email')
|
email.text = c.get('email')
|
||||||
if c.get('uri'):
|
if c.get('uri'):
|
||||||
uri = etree.SubElement(contrib, 'uri')
|
uri = xml_elem('uri', contrib)
|
||||||
uri.text = c.get('uri')
|
uri.text = c.get('uri')
|
||||||
|
|
||||||
if self.__atom_generator and self.__atom_generator.get('value'):
|
if self.__atom_generator and self.__atom_generator.get('value'):
|
||||||
generator = etree.SubElement(feed, 'generator')
|
generator = xml_elem('generator', feed)
|
||||||
generator.text = self.__atom_generator['value']
|
generator.text = self.__atom_generator['value']
|
||||||
if self.__atom_generator.get('uri'):
|
if self.__atom_generator.get('uri'):
|
||||||
generator.attrib['uri'] = self.__atom_generator['uri']
|
generator.attrib['uri'] = self.__atom_generator['uri']
|
||||||
|
@ -174,19 +174,19 @@ class FeedGenerator(object):
|
||||||
generator.attrib['version'] = self.__atom_generator['version']
|
generator.attrib['version'] = self.__atom_generator['version']
|
||||||
|
|
||||||
if self.__atom_icon:
|
if self.__atom_icon:
|
||||||
icon = etree.SubElement(feed, 'icon')
|
icon = xml_elem('icon', feed)
|
||||||
icon.text = self.__atom_icon
|
icon.text = self.__atom_icon
|
||||||
|
|
||||||
if self.__atom_logo:
|
if self.__atom_logo:
|
||||||
logo = etree.SubElement(feed, 'logo')
|
logo = xml_elem('logo', feed)
|
||||||
logo.text = self.__atom_logo
|
logo.text = self.__atom_logo
|
||||||
|
|
||||||
if self.__atom_rights:
|
if self.__atom_rights:
|
||||||
rights = etree.SubElement(feed, 'rights')
|
rights = xml_elem('rights', feed)
|
||||||
rights.text = self.__atom_rights
|
rights.text = self.__atom_rights
|
||||||
|
|
||||||
if self.__atom_subtitle:
|
if self.__atom_subtitle:
|
||||||
subtitle = etree.SubElement(feed, 'subtitle')
|
subtitle = xml_elem('subtitle', feed)
|
||||||
subtitle.text = self.__atom_subtitle
|
subtitle.text = self.__atom_subtitle
|
||||||
|
|
||||||
if extensions:
|
if extensions:
|
||||||
|
@ -255,8 +255,8 @@ class FeedGenerator(object):
|
||||||
nsmap.update({'atom': 'http://www.w3.org/2005/Atom',
|
nsmap.update({'atom': 'http://www.w3.org/2005/Atom',
|
||||||
'content': 'http://purl.org/rss/1.0/modules/content/'})
|
'content': 'http://purl.org/rss/1.0/modules/content/'})
|
||||||
|
|
||||||
feed = etree.Element('rss', version='2.0', nsmap=nsmap)
|
feed = xml_elem('rss', version='2.0', nsmap=nsmap)
|
||||||
channel = etree.SubElement(feed, 'channel')
|
channel = xml_elem('channel', feed)
|
||||||
if not (self.__rss_title and
|
if not (self.__rss_title and
|
||||||
self.__rss_link and
|
self.__rss_link and
|
||||||
self.__rss_description):
|
self.__rss_description):
|
||||||
|
@ -265,18 +265,17 @@ class FeedGenerator(object):
|
||||||
([] if self.__rss_description else ['description'])
|
([] if self.__rss_description else ['description'])
|
||||||
missing = ', '.join(missing)
|
missing = ', '.join(missing)
|
||||||
raise ValueError('Required fields not set (%s)' % missing)
|
raise ValueError('Required fields not set (%s)' % missing)
|
||||||
title = etree.SubElement(channel, 'title')
|
title = xml_elem('title', channel)
|
||||||
title.text = self.__rss_title
|
title.text = self.__rss_title
|
||||||
link = etree.SubElement(channel, 'link')
|
link = xml_elem('link', channel)
|
||||||
link.text = self.__rss_link
|
link.text = self.__rss_link
|
||||||
desc = etree.SubElement(channel, 'description')
|
desc = xml_elem('description', channel)
|
||||||
desc.text = self.__rss_description
|
desc.text = self.__rss_description
|
||||||
for ln in self.__atom_link or []:
|
for ln in self.__atom_link or []:
|
||||||
# It is recommended to include a atom self link in rss documents…
|
# It is recommended to include a atom self link in rss documents…
|
||||||
if ln.get('rel') == 'self':
|
if ln.get('rel') == 'self':
|
||||||
selflink = etree.SubElement(
|
selflink = xml_elem('{http://www.w3.org/2005/Atom}link',
|
||||||
channel, '{http://www.w3.org/2005/Atom}link',
|
channel, href=ln['href'], rel='self')
|
||||||
href=ln['href'], rel='self')
|
|
||||||
if ln.get('type'):
|
if ln.get('type'):
|
||||||
selflink.attrib['type'] = ln['type']
|
selflink.attrib['type'] = ln['type']
|
||||||
if ln.get('hreflang'):
|
if ln.get('hreflang'):
|
||||||
|
@ -288,12 +287,12 @@ class FeedGenerator(object):
|
||||||
break
|
break
|
||||||
if self.__rss_category:
|
if self.__rss_category:
|
||||||
for cat in self.__rss_category:
|
for cat in self.__rss_category:
|
||||||
category = etree.SubElement(channel, 'category')
|
category = xml_elem('category', channel)
|
||||||
category.text = cat['value']
|
category.text = cat['value']
|
||||||
if cat.get('domain'):
|
if cat.get('domain'):
|
||||||
category.attrib['domain'] = cat['domain']
|
category.attrib['domain'] = cat['domain']
|
||||||
if self.__rss_cloud:
|
if self.__rss_cloud:
|
||||||
cloud = etree.SubElement(channel, 'cloud')
|
cloud = xml_elem('cloud', channel)
|
||||||
cloud.attrib['domain'] = self.__rss_cloud.get('domain')
|
cloud.attrib['domain'] = self.__rss_cloud.get('domain')
|
||||||
cloud.attrib['port'] = self.__rss_cloud.get('port')
|
cloud.attrib['port'] = self.__rss_cloud.get('port')
|
||||||
cloud.attrib['path'] = self.__rss_cloud.get('path')
|
cloud.attrib['path'] = self.__rss_cloud.get('path')
|
||||||
|
@ -301,69 +300,69 @@ class FeedGenerator(object):
|
||||||
'registerProcedure')
|
'registerProcedure')
|
||||||
cloud.attrib['protocol'] = self.__rss_cloud.get('protocol')
|
cloud.attrib['protocol'] = self.__rss_cloud.get('protocol')
|
||||||
if self.__rss_copyright:
|
if self.__rss_copyright:
|
||||||
copyright = etree.SubElement(channel, 'copyright')
|
copyright = xml_elem('copyright', channel)
|
||||||
copyright.text = self.__rss_copyright
|
copyright.text = self.__rss_copyright
|
||||||
if self.__rss_docs:
|
if self.__rss_docs:
|
||||||
docs = etree.SubElement(channel, 'docs')
|
docs = xml_elem('docs', channel)
|
||||||
docs.text = self.__rss_docs
|
docs.text = self.__rss_docs
|
||||||
if self.__rss_generator:
|
if self.__rss_generator:
|
||||||
generator = etree.SubElement(channel, 'generator')
|
generator = xml_elem('generator', channel)
|
||||||
generator.text = self.__rss_generator
|
generator.text = self.__rss_generator
|
||||||
if self.__rss_image:
|
if self.__rss_image:
|
||||||
image = etree.SubElement(channel, 'image')
|
image = xml_elem('image', channel)
|
||||||
url = etree.SubElement(image, 'url')
|
url = xml_elem('url', image)
|
||||||
url.text = self.__rss_image.get('url')
|
url.text = self.__rss_image.get('url')
|
||||||
title = etree.SubElement(image, 'title')
|
title = xml_elem('title', image)
|
||||||
title.text = self.__rss_image.get('title', self.__rss_title)
|
title.text = self.__rss_image.get('title', self.__rss_title)
|
||||||
link = etree.SubElement(image, 'link')
|
link = xml_elem('link', image)
|
||||||
link.text = self.__rss_image.get('link', self.__rss_link)
|
link.text = self.__rss_image.get('link', self.__rss_link)
|
||||||
if self.__rss_image.get('width'):
|
if self.__rss_image.get('width'):
|
||||||
width = etree.SubElement(image, 'width')
|
width = xml_elem('width', image)
|
||||||
width.text = self.__rss_image.get('width')
|
width.text = self.__rss_image.get('width')
|
||||||
if self.__rss_image.get('height'):
|
if self.__rss_image.get('height'):
|
||||||
height = etree.SubElement(image, 'height')
|
height = xml_elem('height', image)
|
||||||
height.text = self.__rss_image.get('height')
|
height.text = self.__rss_image.get('height')
|
||||||
if self.__rss_image.get('description'):
|
if self.__rss_image.get('description'):
|
||||||
description = etree.SubElement(image, 'description')
|
description = xml_elem('description', image)
|
||||||
description.text = self.__rss_image.get('description')
|
description.text = self.__rss_image.get('description')
|
||||||
if self.__rss_language:
|
if self.__rss_language:
|
||||||
language = etree.SubElement(channel, 'language')
|
language = xml_elem('language', channel)
|
||||||
language.text = self.__rss_language
|
language.text = self.__rss_language
|
||||||
if self.__rss_lastBuildDate:
|
if self.__rss_lastBuildDate:
|
||||||
lastBuildDate = etree.SubElement(channel, 'lastBuildDate')
|
lastBuildDate = xml_elem('lastBuildDate', channel)
|
||||||
|
|
||||||
lastBuildDate.text = formatRFC2822(self.__rss_lastBuildDate)
|
lastBuildDate.text = formatRFC2822(self.__rss_lastBuildDate)
|
||||||
if self.__rss_managingEditor:
|
if self.__rss_managingEditor:
|
||||||
managingEditor = etree.SubElement(channel, 'managingEditor')
|
managingEditor = xml_elem('managingEditor', channel)
|
||||||
managingEditor.text = self.__rss_managingEditor
|
managingEditor.text = self.__rss_managingEditor
|
||||||
if self.__rss_pubDate:
|
if self.__rss_pubDate:
|
||||||
pubDate = etree.SubElement(channel, 'pubDate')
|
pubDate = xml_elem('pubDate', channel)
|
||||||
pubDate.text = formatRFC2822(self.__rss_pubDate)
|
pubDate.text = formatRFC2822(self.__rss_pubDate)
|
||||||
if self.__rss_rating:
|
if self.__rss_rating:
|
||||||
rating = etree.SubElement(channel, 'rating')
|
rating = xml_elem('rating', channel)
|
||||||
rating.text = self.__rss_rating
|
rating.text = self.__rss_rating
|
||||||
if self.__rss_skipHours:
|
if self.__rss_skipHours:
|
||||||
skipHours = etree.SubElement(channel, 'skipHours')
|
skipHours = xml_elem('skipHours', channel)
|
||||||
for h in self.__rss_skipHours:
|
for h in self.__rss_skipHours:
|
||||||
hour = etree.SubElement(skipHours, 'hour')
|
hour = xml_elem('hour', skipHours)
|
||||||
hour.text = str(h)
|
hour.text = str(h)
|
||||||
if self.__rss_skipDays:
|
if self.__rss_skipDays:
|
||||||
skipDays = etree.SubElement(channel, 'skipDays')
|
skipDays = xml_elem('skipDays', channel)
|
||||||
for d in self.__rss_skipDays:
|
for d in self.__rss_skipDays:
|
||||||
day = etree.SubElement(skipDays, 'day')
|
day = xml_elem('day', skipDays)
|
||||||
day.text = d
|
day.text = d
|
||||||
if self.__rss_textInput:
|
if self.__rss_textInput:
|
||||||
textInput = etree.SubElement(channel, 'textInput')
|
textInput = xml_elem('textInput', channel)
|
||||||
textInput.attrib['title'] = self.__rss_textInput.get('title')
|
textInput.attrib['title'] = self.__rss_textInput.get('title')
|
||||||
textInput.attrib['description'] = \
|
textInput.attrib['description'] = \
|
||||||
self.__rss_textInput.get('description')
|
self.__rss_textInput.get('description')
|
||||||
textInput.attrib['name'] = self.__rss_textInput.get('name')
|
textInput.attrib['name'] = self.__rss_textInput.get('name')
|
||||||
textInput.attrib['link'] = self.__rss_textInput.get('link')
|
textInput.attrib['link'] = self.__rss_textInput.get('link')
|
||||||
if self.__rss_ttl:
|
if self.__rss_ttl:
|
||||||
ttl = etree.SubElement(channel, 'ttl')
|
ttl = xml_elem('ttl', channel)
|
||||||
ttl.text = str(self.__rss_ttl)
|
ttl.text = str(self.__rss_ttl)
|
||||||
if self.__rss_webMaster:
|
if self.__rss_webMaster:
|
||||||
webMaster = etree.SubElement(channel, 'webMaster')
|
webMaster = xml_elem('webMaster', channel)
|
||||||
webMaster.text = self.__rss_webMaster
|
webMaster.text = self.__rss_webMaster
|
||||||
|
|
||||||
if extensions:
|
if extensions:
|
||||||
|
|
|
@ -10,6 +10,28 @@
|
||||||
'''
|
'''
|
||||||
import locale
|
import locale
|
||||||
import sys
|
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):
|
def ensure_format(val, allowed, required, allowed_values=None, defaults=None):
|
||||||
|
|
Loading…
Reference in a new issue