Fix IndexError on empty/whitespace-only JSON content (#4534)

extract_json_blob_from_html() and extract_json_as_string() both peeked at
content_start[0] to decide whether the body looks like JSON. A blank body
leaves content_start empty, so the peek raised IndexError instead of the
watch reporting that no JSON was found.

Guard the index in both places so empty and whitespace-only content takes
the same JSONNotFound path as any other non-JSON body.

Fixes #4531

Co-authored-by: Yi-111-a <yi-111-a@users.noreply.github.com>
This commit is contained in:
yi111andYi-111-a authored and GitHub committed 2026-10-23 12:00:11 +02:00
1 parent e3e51a3a33
commit b82750421b
2 files changed
+27 -2

No files matched your search

+2 -2
View File
@@ -630,7 +630,7 @@ def extract_json_blob_from_html(content, ensure_is_ldjson_info_type, json_filter
# result.text is how bs4 magically strips JSON from the body
content_start = result.text.lstrip("\ufeff").strip()[:100] if result.text else ''
# Skip empty tags, and things that dont even look like JSON
if not result.text or not (content_start[0] == '{' or content_start[0] == '['):
if not content_start or not (content_start[0] == '{' or content_start[0] == '['):
continue
try:
json_data = json.loads(result.text)
@@ -682,7 +682,7 @@ def extract_json_as_string(content, json_filter, ensure_is_ldjson_info_type=None
content_start = content.lstrip("\ufeff").strip()[:100]
if content_start[0] == '{' or content_start[0] == '[':
if content_start and (content_start[0] == '{' or content_start[0] == '['):
try:
# .lstrip("\ufeff") strings ByteOrderMark from UTF8 and still lets the UTF work
stripped_text_from_html = _parse_json(json.loads(content.lstrip("\ufeff")), json_filter)
@@ -121,6 +121,31 @@ and it can also be repeated
html_tools.extract_json_as_string('COMPLETE GIBBERISH, NO JSON!', "jqraw:.id")
def test_blank_content_does_not_raise_indexerror():
"""Empty / whitespace-only content must take the normal JSONNotFound path, not crash.
The content-sniffing peeked at content_start[0] to see if the body looked like JSON.
A blank body left content_start empty, so that peek raised IndexError instead of
letting the caller report 'no JSON found' the way any other non-JSON body does.
See https://github.com/dgtlmoon/changedetection.io/issues/4531
"""
from .. import html_tools
# Blank bodies with a json: filter set - the same as any other non-JSON content
for blank in ('', ' ', '\n', '\r\n\t '):
with pytest.raises(html_tools.JSONNotFound):
html_tools.extract_json_as_string(blank, "json:$.id")
# Same on the html blob path, where a whitespace-only <script> or <body> is skipped
# and the real ld+json that follows it is still found
html = '<html><script>\n</script><script type="application/ld+json">{"a":1}</script></html>'
assert html_tools.extract_json_blob_from_html(html, None, "json:$.a") == "1"
# A document that is nothing but blank markup has no JSON in it at all
with pytest.raises(html_tools.JSONNotFound):
html_tools.extract_json_blob_from_html('<html><body> </body></html>', None, "json:$.a")
def test_lone_surrogate_escapes_do_not_break_filters():
"""A \\uD800-style escape in the watched JSON must not take the whole document down.