diff --git a/feedparser/api.py b/feedparser/api.py index b11e4a47..af7a77cc 100644 --- a/feedparser/api.py +++ b/feedparser/api.py @@ -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'): diff --git a/tests/runtests.py b/tests/runtests.py index ad45e70a..c1a5b0f1 100644 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -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(''), '', '', '', '', [], {}, {}) + self.assertEqual(r, b'') + + def test_parse_text_stringio(self): + text = 'hello' + 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')