diff --git a/changelog.d/_5-larkindom-empty-document.rst b/changelog.d/_5-larkindom-empty-document.rst new file mode 100644 index 00000000..827998ae --- /dev/null +++ b/changelog.d/_5-larkindom-empty-document.rst @@ -0,0 +1,6 @@ +Fixed +----- + +* Set ``bozo`` and ``bozo_exception`` (to a new ``EmptyDocument`` error) + when parsing empty input, instead of silently returning a result that + looks like a valid, empty feed. (#461) diff --git a/feedparser/__init__.py b/feedparser/__init__.py index f9d15494..a392f2d2 100644 --- a/feedparser/__init__.py +++ b/feedparser/__init__.py @@ -30,6 +30,7 @@ from .exceptions import ( CharacterEncodingOverride, CharacterEncodingUnknown, + EmptyDocument, FeedparserError, NonXMLContentType, UndeclaredNamespace, @@ -61,6 +62,7 @@ "FeedparserError", "CharacterEncodingOverride", "CharacterEncodingUnknown", + "EmptyDocument", "NonXMLContentType", "UndeclaredNamespace", ) diff --git a/feedparser/api.py b/feedparser/api.py index 8b95ed5c..f3173ffd 100644 --- a/feedparser/api.py +++ b/feedparser/api.py @@ -34,6 +34,7 @@ from . import http from .encodings import MissingEncoding, convert_file_to_utf8 +from .exceptions import EmptyDocument from .html import BaseHTMLProcessor from .mixin import XMLParserMixin from .parsers.json import JSONParser @@ -214,9 +215,12 @@ def parse( # at this point, the file is guaranteed to be seekable; # we read 1 byte/character to see if it's empty and return early - # (this preserves the behavior in 6.0.8) + # (this preserves the 6.0.8 behavior of not attempting to parse empty + # input, but flags it via bozo instead of silently reporting success) initial_file_offset = file.tell() if not file.read(1): + result["bozo"] = True + result["bozo_exception"] = EmptyDocument("document is empty") return result file.seek(initial_file_offset) diff --git a/feedparser/exceptions.py b/feedparser/exceptions.py index 8718c56a..48ff82eb 100644 --- a/feedparser/exceptions.py +++ b/feedparser/exceptions.py @@ -30,6 +30,7 @@ "FeedparserError", "CharacterEncodingOverride", "CharacterEncodingUnknown", + "EmptyDocument", "NonXMLContentType", "UndeclaredNamespace", ] @@ -47,6 +48,10 @@ class CharacterEncodingUnknown(FeedparserError): pass +class EmptyDocument(FeedparserError): + pass + + class NonXMLContentType(FeedparserError): pass diff --git a/tests/test_empty_document.py b/tests/test_empty_document.py new file mode 100644 index 00000000..e225bd29 --- /dev/null +++ b/tests/test_empty_document.py @@ -0,0 +1,23 @@ +import io + +import feedparser + + +def test_empty_bytestring_sets_bozo(): + result = feedparser.parse(io.BytesIO(b"")) + assert result["bozo"] is True + assert isinstance(result["bozo_exception"], feedparser.EmptyDocument) + assert result["entries"] == [] + assert result["feed"] == {} + + +def test_empty_string_sets_bozo(): + result = feedparser.parse(io.StringIO("")) + assert result["bozo"] is True + assert isinstance(result["bozo_exception"], feedparser.EmptyDocument) + + +def test_nonempty_feed_does_not_set_bozo_via_empty_document(): + result = feedparser.parse(b"t") + exc = result.get("bozo_exception") + assert not isinstance(exc, feedparser.EmptyDocument)