diff --git a/src/plugins/compare/file_coverage/view/file_coverage.html b/src/plugins/compare/file_coverage/view/file_coverage.html
index 0f823247d8..4fa1781eb5 100644
--- a/src/plugins/compare/file_coverage/view/file_coverage.html
+++ b/src/plugins/compare/file_coverage/view/file_coverage.html
@@ -75,7 +75,7 @@
{% elif feature == 'changed_text_files' %}
- diverging text files |
+ changed text files |
@@ -83,27 +83,7 @@
-
-
- {% for path, uid_tuple_list in result['plugins'][plugin][feature].items() | sort %}
- {% for (uid_1, uid_2) in uid_tuple_list %}
-
-
- {{ path }}
- |
-
- {{ path }}
- |
-
-
- |
-
- {% endfor %}
- {% endfor %}
-
-
+ {{ result['plugins'][plugin][feature] | group_changed_text_files | render_changed_text_files | safe }}
diff --git a/src/test/unit/web_interface/test_filter.py b/src/test/unit/web_interface/test_filter.py
index 5aa55dfbc3..6afb3182b2 100644
--- a/src/test/unit/web_interface/test_filter.py
+++ b/src/test/unit/web_interface/test_filter.py
@@ -302,6 +302,18 @@ def test_get_unique_keys_from_list_of_dicts(list_of_dicts, expected_result):
assert flt.get_unique_keys_from_list_of_dicts(list_of_dicts) == expected_result
+@pytest.mark.parametrize(
+ ('input_dict', 'expected_result'),
+ [
+ ({}, {}),
+ ({'a': 1}, {'a': 1}),
+ ({'/a/b/c': 1, '/a/b/d': 2, '/a/e': 3}, {'a': {'b': {'c': 1, 'd': 2}, 'e': 3}}),
+ ],
+)
+def test_grp_changed_text_file_data(input_dict, expected_result):
+ assert flt.group_path_dict_by_dirs(input_dict) == expected_result
+
+
@pytest.mark.parametrize(
('function', 'input_data', 'expected_output', 'error_message'),
[
diff --git a/src/web_interface/components/jinja_filter.py b/src/web_interface/components/jinja_filter.py
index 71bd04bcca..0128ffab24 100644
--- a/src/web_interface/components/jinja_filter.py
+++ b/src/web_interface/components/jinja_filter.py
@@ -195,6 +195,7 @@ def _setup_filters(self): # noqa: PLR0915
self._app.jinja_env.filters['get_canvas_height'] = flt.get_canvas_height
self._app.jinja_env.filters['get_searchable_crypto_block'] = flt.get_searchable_crypto_block
self._app.jinja_env.filters['get_unique_keys_from_list_of_dicts'] = flt.get_unique_keys_from_list_of_dicts
+ self._app.jinja_env.filters['group_changed_text_files'] = flt.group_path_dict_by_dirs
self._app.jinja_env.filters['hex'] = hex
self._app.jinja_env.filters['hide_dts_binary_data'] = flt.hide_dts_binary_data
self._app.jinja_env.filters['infection_color'] = flt.infection_color
@@ -221,6 +222,7 @@ def _setup_filters(self): # noqa: PLR0915
self._app.jinja_env.filters['remaining_time'] = elapsed_time
self._app.jinja_env.filters['render_analysis_tags'] = flt.render_analysis_tags
self._app.jinja_env.filters['render_general_information'] = self._render_general_information_table
+ self._app.jinja_env.filters['render_changed_text_files'] = flt.render_changed_text_files
self._app.jinja_env.filters['render_query_title'] = flt.render_query_title
self._app.jinja_env.filters['render_fw_tags'] = flt.render_fw_tags
self._app.jinja_env.filters['replace_comparison_uid_with_hid'] = self._filter_replace_comparison_uid_with_hid
diff --git a/src/web_interface/filter.py b/src/web_interface/filter.py
index b41eb744af..d52ccf942f 100644
--- a/src/web_interface/filter.py
+++ b/src/web_interface/filter.py
@@ -3,6 +3,7 @@
import binascii
import json
import logging
+import os
import random
import re
import stat
@@ -366,6 +367,20 @@ def get_unique_keys_from_list_of_dicts(list_of_dicts: list[dict]):
return unique_keys
+def group_path_dict_by_dirs(data: dict[str, list[tuple]]) -> dict:
+ # groups paths into folders, resulting in a tree structure
+ result = {}
+ for path, value in data.items():
+ current = result
+ # path is expected to be of structure /dir1/dir2/.../file
+ *folders, file = path.split('/')
+ for dir_name in folders:
+ if dir_name:
+ current = current.setdefault(dir_name, {})
+ current[file] = value
+ return result
+
+
def random_collapse_id():
return ''.join(random.choice(ascii_letters) for _ in range(10))
@@ -387,6 +402,65 @@ def format_duration(duration: float) -> str:
return str(timedelta(seconds=duration))
+def render_changed_text_files(changed_text_files: dict) -> str:
+ elements = []
+ for key, value in sorted(changed_text_files.items()):
+ if isinstance(value, list):
+ # file tuple list (represents a leaf/file in the file tree)
+ lines = ['']
+ for uid_1, uid_2 in value:
+ lines.extend(
+ [
+ '',
+ ' ',
+ f' {key}',
+ ' | ',
+ ' ',
+ f' {key}',
+ ' | ',
+ ' ',
+ f' ',
+ ' | ',
+ ' ',
+ ]
+ )
+ lines.append(' ')
+ element = '\n'.join(lines)
+ else:
+ # directory dict (represents an inner node/folder in the file tree)
+ inner = render_changed_text_files(value)
+ id_ = f'ctf-{os.urandom(8).hex()}'
+ count = _count_changed_files(value)
+ element = (
+ f''
+ f' ╰ {key} '
+ f' {count}'
+ f' \n'
+ f'\n'
+ f' {inner}\n'
+ ' \n'
+ )
+ elements.append(
+ f'\n'
+ f'{element}\n'
+ ' '
+ )
+ return '\n'.join(elements)
+
+
+def _count_changed_files(ctf_dict: dict) -> int:
+ count = 0
+ for value in ctf_dict.values():
+ if isinstance(value, dict):
+ count += _count_changed_files(value)
+ elif isinstance(value, list):
+ count += len(value)
+ return count
+
+
def render_query_title(query_title: None | str | dict):
if query_title is None:
return None
|