From b3f69080c0afa136a1a5a034bd0e718aa2b19195 Mon Sep 17 00:00:00 2001 From: afonsojanu Date: Mon, 7 Sep 2026 09:44:04 +0100 Subject: [PATCH] Fix parse() crashing on text-mode file-like objects (e.g. io.StringIO) _open_resource() returned a text-mode file-like object's data untouched, but convert_to_utf8() always matches it against a bytes regex, so passing an io.StringIO ends up raising TypeError instead of parsing. The docstring for parse() even suggests wrapping untrusted strings in io.StringIO, which is exactly the case that breaks. This encodes the read data to utf-8 when it comes back as str, mirroring what already happens a few lines down for plain string input. --- feedparser/api.py | 5 ++++- tests/runtests.py | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) 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')