Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tree structure for comparison changed text files #1279

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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