diff --git a/changedetectionio/html_tools.py b/changedetectionio/html_tools.py index 7eade3bc6..0d8ce03c1 100644 --- a/changedetectionio/html_tools.py +++ b/changedetectionio/html_tools.py @@ -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) diff --git a/changedetectionio/tests/test_jsonpath_jq_selector.py b/changedetectionio/tests/test_jsonpath_jq_selector.py index cf035d166..eb47d147d 100644 --- a/changedetectionio/tests/test_jsonpath_jq_selector.py +++ b/changedetectionio/tests/test_jsonpath_jq_selector.py @@ -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 ' + 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(' ', 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.