salt/improve-salt.utils.json.find_json-bsc-1213293.patch

205 lines
7.1 KiB
Diff

From 4e6b445f2dbe8a79d220c697abff946e00b2e57b Mon Sep 17 00:00:00 2001
From: Victor Zhestkov <vzhestkov@suse.com>
Date: Mon, 2 Oct 2023 13:26:20 +0200
Subject: [PATCH] Improve salt.utils.json.find_json (bsc#1213293)
* Improve salt.utils.json.find_json
* Move tests of find_json to pytest
---
salt/utils/json.py | 39 +++++++-
tests/pytests/unit/utils/test_json.py | 122 ++++++++++++++++++++++++++
2 files changed, 158 insertions(+), 3 deletions(-)
create mode 100644 tests/pytests/unit/utils/test_json.py
diff --git a/salt/utils/json.py b/salt/utils/json.py
index 33cdbf401d..0845b64694 100644
--- a/salt/utils/json.py
+++ b/salt/utils/json.py
@@ -32,18 +32,51 @@ def find_json(raw):
"""
ret = {}
lines = __split(raw)
+ lengths = list(map(len, lines))
+ starts = []
+ ends = []
+
+ # Search for possible starts end ends of the json fragments
for ind, _ in enumerate(lines):
+ line = lines[ind].lstrip()
+ if line == "{" or line == "[":
+ starts.append((ind, line))
+ if line == "}" or line == "]":
+ ends.append((ind, line))
+
+ # List all the possible pairs of starts and ends,
+ # and fill the length of each block to sort by size after
+ starts_ends = []
+ for start, start_br in starts:
+ for end, end_br in reversed(ends):
+ if end > start and (
+ (start_br == "{" and end_br == "}")
+ or (start_br == "[" and end_br == "]")
+ ):
+ starts_ends.append((start, end, sum(lengths[start : end + 1])))
+
+ # Iterate through all the possible pairs starting from the largest
+ starts_ends.sort(key=lambda x: (x[2], x[1] - x[0], x[0]), reverse=True)
+ for start, end, _ in starts_ends:
+ working = "\n".join(lines[start : end + 1])
try:
- working = "\n".join(lines[ind:])
- except UnicodeDecodeError:
- working = "\n".join(salt.utils.data.decode(lines[ind:]))
+ ret = json.loads(working)
+ except ValueError:
+ continue
+ if ret:
+ return ret
+ # Fall back to old implementation for backward compatibility
+ # excpecting json after the text
+ for ind, _ in enumerate(lines):
+ working = "\n".join(lines[ind:])
try:
ret = json.loads(working)
except ValueError:
continue
if ret:
return ret
+
if not ret:
# Not json, raise an error
raise ValueError
diff --git a/tests/pytests/unit/utils/test_json.py b/tests/pytests/unit/utils/test_json.py
new file mode 100644
index 0000000000..72b1023003
--- /dev/null
+++ b/tests/pytests/unit/utils/test_json.py
@@ -0,0 +1,122 @@
+"""
+Tests for salt.utils.json
+"""
+
+import textwrap
+
+import pytest
+
+import salt.utils.json
+
+
+def test_find_json():
+ some_junk_text = textwrap.dedent(
+ """
+ Just some junk text
+ with multiline
+ """
+ )
+ some_warning_message = textwrap.dedent(
+ """
+ [WARNING] Test warning message
+ """
+ )
+ test_small_json = textwrap.dedent(
+ """
+ {
+ "local": true
+ }
+ """
+ )
+ test_sample_json = """
+ {
+ "glossary": {
+ "title": "example glossary",
+ "GlossDiv": {
+ "title": "S",
+ "GlossList": {
+ "GlossEntry": {
+ "ID": "SGML",
+ "SortAs": "SGML",
+ "GlossTerm": "Standard Generalized Markup Language",
+ "Acronym": "SGML",
+ "Abbrev": "ISO 8879:1986",
+ "GlossDef": {
+ "para": "A meta-markup language, used to create markup languages such as DocBook.",
+ "GlossSeeAlso": ["GML", "XML"]
+ },
+ "GlossSee": "markup"
+ }
+ }
+ }
+ }
+ }
+ """
+ expected_ret = {
+ "glossary": {
+ "GlossDiv": {
+ "GlossList": {
+ "GlossEntry": {
+ "GlossDef": {
+ "GlossSeeAlso": ["GML", "XML"],
+ "para": (
+ "A meta-markup language, used to create markup"
+ " languages such as DocBook."
+ ),
+ },
+ "GlossSee": "markup",
+ "Acronym": "SGML",
+ "GlossTerm": "Standard Generalized Markup Language",
+ "SortAs": "SGML",
+ "Abbrev": "ISO 8879:1986",
+ "ID": "SGML",
+ }
+ },
+ "title": "S",
+ },
+ "title": "example glossary",
+ }
+ }
+
+ # First test the valid JSON
+ ret = salt.utils.json.find_json(test_sample_json)
+ assert ret == expected_ret
+
+ # Now pre-pend some garbage and re-test
+ garbage_prepend_json = f"{some_junk_text}{test_sample_json}"
+ ret = salt.utils.json.find_json(garbage_prepend_json)
+ assert ret == expected_ret
+
+ # Now post-pend some garbage and re-test
+ garbage_postpend_json = f"{test_sample_json}{some_junk_text}"
+ ret = salt.utils.json.find_json(garbage_postpend_json)
+ assert ret == expected_ret
+
+ # Now pre-pend some warning and re-test
+ warning_prepend_json = f"{some_warning_message}{test_sample_json}"
+ ret = salt.utils.json.find_json(warning_prepend_json)
+ assert ret == expected_ret
+
+ # Now post-pend some warning and re-test
+ warning_postpend_json = f"{test_sample_json}{some_warning_message}"
+ ret = salt.utils.json.find_json(warning_postpend_json)
+ assert ret == expected_ret
+
+ # Now put around some garbage and re-test
+ garbage_around_json = f"{some_junk_text}{test_sample_json}{some_junk_text}"
+ ret = salt.utils.json.find_json(garbage_around_json)
+ assert ret == expected_ret
+
+ # Now pre-pend small json and re-test
+ small_json_pre_json = f"{test_small_json}{test_sample_json}"
+ ret = salt.utils.json.find_json(small_json_pre_json)
+ assert ret == expected_ret
+
+ # Now post-pend small json and re-test
+ small_json_post_json = f"{test_sample_json}{test_small_json}"
+ ret = salt.utils.json.find_json(small_json_post_json)
+ assert ret == expected_ret
+
+ # Test to see if a ValueError is raised if no JSON is passed in
+ with pytest.raises(ValueError):
+ ret = salt.utils.json.find_json(some_junk_text)
--
2.42.0