@@ -544,13 +544,13 @@ def run_command_interruptible(cmd):
def sanitise_html(html):
soup = BeautifulSoup(html, "html.parser")
- for tag in soup.findAll(True):
+ for tag in soup.find_all(True):
if tag.name not in ['strong', 'em', 'b', 'i', 'p', 'ul', 'ol', 'li', 'br', 'p']:
tag.hidden = True
elif tag.attrs:
- tag.attrs = []
+ tag.attrs = {}
- return soup.renderContents()
+ return soup.decode_contents()
def squashspaces(string):
return re.sub(r"\s+", " ", string).strip()
@@ -85,3 +85,16 @@ def test_in_nested_layer(repo_with_nested_layers, path, expected):
assert utils.in_nested_layer(path, nested, repodir) is expected
# Absolute paths give the same answer
assert utils.in_nested_layer(os.path.join(repodir, path), nested, repodir) is expected
+
+
+@pytest.mark.parametrize('html, expected', [
+ # Allowed tags are kept, but lose their attributes
+ ('<p class="x" onclick="y">Hi <b>there</b></p>', '<p>Hi <b>there</b></p>'),
+ ('<ul><li>one</li></ul>', '<ul><li>one</li></ul>'),
+ # Other tags are dropped, but their text stays (escaped)
+ ('<a href="http://example.com">link</a>', 'link'),
+ ('<script>alert(1)</script>', 'alert(1)'),
+ ('<div><i>x</i> <y></div>', '<i>x</i> <y>'),
+])
+def test_sanitise_html(html, expected):
+ assert utils.sanitise_html(html) == expected
Site notices showed a literal b'...', because sanitise_html() returned bytes, and a notice with an attribute on an allowed tag, such as <p class="x">, failed to render, because current Beautiful Soup needs a dict for tag.attrs. Return a str, clear attributes with a dict, and move off the deprecated findAll() and renderContents(). Add tests. AI-Generated: Claude Opus 5.5 Signed-off-by: Tim Orling <tim.orling@konsulko.com> --- layerindex/utils.py | 6 +++--- tests/test_utils.py | 13 +++++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-)