Skip to content

Commit

Permalink
feat: tree structure for comparison changed text files
Browse files Browse the repository at this point in the history
  • Loading branch information
jstucke committed Oct 7, 2024
1 parent adfbfe8 commit af82002
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 22 deletions.
24 changes: 2 additions & 22 deletions src/plugins/compare/file_coverage/view/file_coverage.html
Original file line number Diff line number Diff line change
Expand Up @@ -75,35 +75,15 @@

{% elif feature == 'changed_text_files' %}
<tr>
<td data-toggle="tooltip" data-placement="right" title="text files with identical paths but different content">diverging text files</td>
<td data-toggle="tooltip" data-placement="right" title="text files with identical paths but different content">changed text files</td>
<td colspan="{{ uid_list | length }}">
<div class="list-group">
<div role="button" data-toggle="collapse" data-target="#changed_text_files" class="list-group-item list-group-item-primary d-flex justify-content-between align-items-center" aria-expanded="false">
show files<span class="badge badge-pill badge-primary">{{ result['plugins'][plugin][feature] | length }}</span>
</div>
<div id="changed_text_files" class="collapse">
<div class="list-group list-group-flush">
<div class="list-group-item list-group-item-action border-top p-0">
<table class="internal-table table-sm" style="width: 100%;">
{% for path, uid_tuple_list in result['plugins'][plugin][feature].items() | sort %}
{% for (uid_1, uid_2) in uid_tuple_list %}
<tr>
<td style="width: 50%;">
<a href="/analysis/{{ uid_1 }}" target="_blank" rel="noopener noreferrer">{{ path }}</a>
</td>
<td>
<a href="/analysis/{{ uid_2 }}" target="_blank" rel="noopener noreferrer">{{ path }}</a>
</td>
<td style="width: 140px; text-align: right;">
<button class="btn btn-primary" onclick=" window.open('/comparison/text_files/{{ uid_1 }}/{{ uid_2 }}','_blank')">
show file diff
</button>
</td>
</tr>
{% endfor %}
{% endfor %}
</table>
</div>
{{ result['plugins'][plugin][feature] | group_changed_text_files | render_changed_text_files | safe }}
</div>
</div>
</div>
Expand Down
12 changes: 12 additions & 0 deletions src/test/unit/web_interface/test_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'),
[
Expand Down
2 changes: 2 additions & 0 deletions src/web_interface/components/jinja_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
74 changes: 74 additions & 0 deletions src/web_interface/filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import binascii
import json
import logging
import os
import random
import re
import stat
Expand Down Expand Up @@ -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))

Expand All @@ -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 = ['<table class="internal-table table-sm" style="width: 100%;">']
for uid_1, uid_2 in value:
lines.extend(
[
'<tr>',
' <td style="width: 50%;">',
f' <a href="/analysis/{uid_1}" target="_blank" rel="noopener noreferrer">{key}</a>',
' </td>',
' <td>',
f' <a href="/analysis/{uid_2}" target="_blank" rel="noopener noreferrer">{key}</a>',
' </td>',
' <td style="width: 140px; text-align: right;">',
f' <button class="btn btn-primary" onclick=" '
f" window.open('/comparison/text_files/{uid_1}/{uid_2}','_blank')\">",
' show file diff',
' </button>',
' </td>',
'</tr>',
]
)
lines.append('</table>')
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'<div class="p-1" data-toggle="collapse" data-target="#{id_}">'
f' ╰ {key} <i class="fas fa-caret-down"></i>'
f' <span class="badge badge-pill badge-secondary">{count}</span>'
f'</div>\n'
f'<div id="{id_}" class="collapse list-group list-group-flush">\n'
f' {inner}\n'
'</div>\n'
)
elements.append(
f'<div class="list-group-item list-group-item-action border-top" style="padding: 0 0 0 25px">\n'
f'{element}\n'
'</div>'
)
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
Expand Down

0 comments on commit af82002

Please sign in to comment.