Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion feedparser/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,10 @@ def _open_resource(url_file_stream_or_string, etag, modified, agent, referrer, h
"""

if hasattr(url_file_stream_or_string, 'read'):
return url_file_stream_or_string.read()
data = url_file_stream_or_string.read()
if not isinstance(data, bytes):
return data.encode('utf-8')
return data

if isinstance(url_file_stream_or_string, str) \
and urllib.parse.urlparse(url_file_stream_or_string)[0] in ('http', 'https', 'ftp', 'file', 'feed'):
Expand Down
16 changes: 16 additions & 0 deletions tests/runtests.py
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,22 @@ def test_fileobj(self):
r = feedparser.api._open_resource(io.BytesIO(b''), '', '', '', '', [], {}, {})
self.assertEqual(r, b'')

def test_text_fileobj(self):
"""A text-mode file-like object (e.g. io.StringIO) must come back as bytes.

Everything downstream, starting with convert_to_utf8(), works with
raw bytes and matches against byte regex patterns, so returning the
str data untouched breaks parsing of any text-mode stream.
"""
r = feedparser.api._open_resource(io.StringIO('<feed></feed>'), '', '', '', '', [], {}, {})
self.assertEqual(r, b'<feed></feed>')

def test_parse_text_stringio(self):
text = '<rss version="2.0"><channel><item><title>hello</title></item></channel></rss>'
result = feedparser.parse(io.StringIO(text))
self.assertFalse(result.bozo)
self.assertEqual(result.entries[0].title, 'hello')

def test_feed(self):
f = feedparser.parse('feed://localhost:8097/tests/http/target.xml')
self.assertEqual(f.href, 'http://localhost:8097/tests/http/target.xml')
Expand Down