atomicshop 2.11.47__py3-none-any.whl → 3.10.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- atomicshop/__init__.py +1 -1
- atomicshop/{addons/mains → a_mains}/FACT/update_extract.py +3 -2
- atomicshop/a_mains/addons/process_list/compile.cmd +7 -0
- atomicshop/a_mains/addons/process_list/compiled/Win10x64/process_list.dll +0 -0
- atomicshop/a_mains/addons/process_list/compiled/Win10x64/process_list.exp +0 -0
- atomicshop/a_mains/addons/process_list/compiled/Win10x64/process_list.lib +0 -0
- atomicshop/{addons → a_mains/addons}/process_list/process_list.cpp +8 -1
- atomicshop/a_mains/dns_gateway_setting.py +11 -0
- atomicshop/a_mains/get_local_tcp_ports.py +85 -0
- atomicshop/a_mains/github_wrapper.py +11 -0
- atomicshop/a_mains/install_ca_certificate.py +172 -0
- atomicshop/{addons/mains → a_mains}/msi_unpacker.py +3 -1
- atomicshop/a_mains/process_from_port.py +119 -0
- atomicshop/a_mains/set_default_dns_gateway.py +90 -0
- atomicshop/a_mains/update_config_toml.py +38 -0
- atomicshop/appointment_management.py +5 -3
- atomicshop/basics/ansi_escape_codes.py +3 -1
- atomicshop/basics/argparse_template.py +2 -0
- atomicshop/basics/booleans.py +27 -30
- atomicshop/basics/bytes_arrays.py +43 -0
- atomicshop/basics/classes.py +149 -1
- atomicshop/basics/dicts.py +12 -0
- atomicshop/basics/enums.py +2 -2
- atomicshop/basics/exceptions.py +5 -1
- atomicshop/basics/list_of_classes.py +29 -0
- atomicshop/basics/list_of_dicts.py +69 -5
- atomicshop/basics/lists.py +14 -0
- atomicshop/basics/multiprocesses.py +374 -50
- atomicshop/basics/package_module.py +10 -0
- atomicshop/basics/strings.py +160 -7
- atomicshop/basics/threads.py +14 -0
- atomicshop/basics/tracebacks.py +13 -4
- atomicshop/certificates.py +153 -52
- atomicshop/config_init.py +12 -7
- atomicshop/console_user_response.py +7 -14
- atomicshop/consoles.py +9 -0
- atomicshop/datetimes.py +98 -0
- atomicshop/diff_check.py +340 -40
- atomicshop/dns.py +128 -12
- atomicshop/etws/_pywintrace_fix.py +17 -0
- atomicshop/etws/const.py +38 -0
- atomicshop/etws/providers.py +21 -0
- atomicshop/etws/sessions.py +43 -0
- atomicshop/etws/trace.py +168 -0
- atomicshop/etws/traces/trace_dns.py +162 -0
- atomicshop/etws/traces/trace_sysmon_process_creation.py +126 -0
- atomicshop/etws/traces/trace_tcp.py +130 -0
- atomicshop/file_io/csvs.py +222 -24
- atomicshop/file_io/docxs.py +35 -18
- atomicshop/file_io/file_io.py +35 -19
- atomicshop/file_io/jsons.py +49 -0
- atomicshop/file_io/tomls.py +139 -0
- atomicshop/filesystem.py +864 -293
- atomicshop/get_process_list.py +133 -0
- atomicshop/{process_name_cmd.py → get_process_name_cmd_dll.py} +52 -19
- atomicshop/http_parse.py +149 -93
- atomicshop/ip_addresses.py +6 -1
- atomicshop/mitm/centered_settings.py +132 -0
- atomicshop/mitm/config_static.py +207 -0
- atomicshop/mitm/config_toml_editor.py +55 -0
- atomicshop/mitm/connection_thread_worker.py +875 -357
- atomicshop/mitm/engines/__parent/parser___parent.py +4 -17
- atomicshop/mitm/engines/__parent/recorder___parent.py +108 -51
- atomicshop/mitm/engines/__parent/requester___parent.py +116 -0
- atomicshop/mitm/engines/__parent/responder___parent.py +75 -114
- atomicshop/mitm/engines/__reference_general/parser___reference_general.py +10 -7
- atomicshop/mitm/engines/__reference_general/recorder___reference_general.py +5 -5
- atomicshop/mitm/engines/__reference_general/requester___reference_general.py +47 -0
- atomicshop/mitm/engines/__reference_general/responder___reference_general.py +95 -13
- atomicshop/mitm/engines/create_module_template.py +58 -14
- atomicshop/mitm/import_config.py +359 -139
- atomicshop/mitm/initialize_engines.py +160 -74
- atomicshop/mitm/message.py +64 -23
- atomicshop/mitm/mitm_main.py +892 -0
- atomicshop/mitm/recs_files.py +183 -0
- atomicshop/mitm/shared_functions.py +4 -10
- atomicshop/mitm/ssh_tester.py +82 -0
- atomicshop/mitm/statistic_analyzer.py +257 -166
- atomicshop/mitm/statistic_analyzer_helper/analyzer_helper.py +136 -0
- atomicshop/mitm/statistic_analyzer_helper/moving_average_helper.py +525 -0
- atomicshop/monitor/change_monitor.py +96 -120
- atomicshop/monitor/checks/dns.py +139 -70
- atomicshop/monitor/checks/file.py +77 -0
- atomicshop/monitor/checks/network.py +81 -77
- atomicshop/monitor/checks/process_running.py +33 -34
- atomicshop/monitor/checks/url.py +94 -0
- atomicshop/networks.py +671 -0
- atomicshop/on_exit.py +205 -0
- atomicshop/package_mains_processor.py +84 -0
- atomicshop/permissions/permissions.py +22 -0
- atomicshop/permissions/ubuntu_permissions.py +239 -0
- atomicshop/permissions/win_permissions.py +33 -0
- atomicshop/print_api.py +24 -41
- atomicshop/process.py +63 -17
- atomicshop/process_poller/__init__.py +0 -0
- atomicshop/process_poller/pollers/__init__.py +0 -0
- atomicshop/process_poller/pollers/psutil_pywin32wmi_dll.py +95 -0
- atomicshop/process_poller/process_pool.py +207 -0
- atomicshop/process_poller/simple_process_pool.py +311 -0
- atomicshop/process_poller/tracer_base.py +45 -0
- atomicshop/process_poller/tracers/__init__.py +0 -0
- atomicshop/process_poller/tracers/event_log.py +46 -0
- atomicshop/process_poller/tracers/sysmon_etw.py +68 -0
- atomicshop/python_file_patcher.py +1 -1
- atomicshop/python_functions.py +27 -75
- atomicshop/question_answer_engine.py +2 -2
- atomicshop/scheduling.py +24 -5
- atomicshop/sound.py +4 -2
- atomicshop/speech_recognize.py +8 -0
- atomicshop/ssh_remote.py +158 -172
- atomicshop/startup/__init__.py +0 -0
- atomicshop/startup/win/__init__.py +0 -0
- atomicshop/startup/win/startup_folder.py +53 -0
- atomicshop/startup/win/task_scheduler.py +119 -0
- atomicshop/system_resource_monitor.py +61 -46
- atomicshop/system_resources.py +8 -8
- atomicshop/tempfiles.py +1 -2
- atomicshop/timer.py +30 -11
- atomicshop/urls.py +41 -0
- atomicshop/venvs.py +28 -0
- atomicshop/versioning.py +27 -0
- atomicshop/web.py +110 -25
- atomicshop/web_apis/__init__.py +0 -0
- atomicshop/web_apis/google_custom_search.py +44 -0
- atomicshop/web_apis/google_llm.py +188 -0
- atomicshop/websocket_parse.py +450 -0
- atomicshop/wrappers/certauthw/certauth.py +1 -0
- atomicshop/wrappers/cryptographyw.py +29 -8
- atomicshop/wrappers/ctyping/etw_winapi/__init__.py +0 -0
- atomicshop/wrappers/ctyping/etw_winapi/const.py +335 -0
- atomicshop/wrappers/ctyping/etw_winapi/etw_functions.py +393 -0
- atomicshop/wrappers/ctyping/file_details_winapi.py +67 -0
- atomicshop/wrappers/ctyping/msi_windows_installer/cabs.py +2 -1
- atomicshop/wrappers/ctyping/msi_windows_installer/extract_msi_main.py +13 -9
- atomicshop/wrappers/ctyping/msi_windows_installer/tables.py +35 -0
- atomicshop/wrappers/ctyping/setup_device.py +466 -0
- atomicshop/wrappers/ctyping/win_console.py +39 -0
- atomicshop/wrappers/dockerw/dockerw.py +113 -2
- atomicshop/wrappers/elasticsearchw/config_basic.py +0 -12
- atomicshop/wrappers/elasticsearchw/elastic_infra.py +75 -0
- atomicshop/wrappers/elasticsearchw/elasticsearchw.py +2 -20
- atomicshop/wrappers/factw/get_file_data.py +12 -5
- atomicshop/wrappers/factw/install/install_after_restart.py +89 -5
- atomicshop/wrappers/factw/install/pre_install_and_install_before_restart.py +20 -14
- atomicshop/wrappers/factw/postgresql/firmware.py +4 -6
- atomicshop/wrappers/githubw.py +583 -51
- atomicshop/wrappers/loggingw/consts.py +49 -0
- atomicshop/wrappers/loggingw/filters.py +102 -0
- atomicshop/wrappers/loggingw/formatters.py +58 -71
- atomicshop/wrappers/loggingw/handlers.py +459 -40
- atomicshop/wrappers/loggingw/loggers.py +19 -0
- atomicshop/wrappers/loggingw/loggingw.py +1010 -178
- atomicshop/wrappers/loggingw/reading.py +344 -19
- atomicshop/wrappers/mongodbw/__init__.py +0 -0
- atomicshop/wrappers/mongodbw/mongo_infra.py +31 -0
- atomicshop/wrappers/mongodbw/mongodbw.py +1432 -0
- atomicshop/wrappers/netshw.py +271 -0
- atomicshop/wrappers/playwrightw/engine.py +34 -19
- atomicshop/wrappers/playwrightw/infra.py +5 -0
- atomicshop/wrappers/playwrightw/javascript.py +7 -3
- atomicshop/wrappers/playwrightw/keyboard.py +14 -0
- atomicshop/wrappers/playwrightw/scenarios.py +172 -5
- atomicshop/wrappers/playwrightw/waits.py +9 -7
- atomicshop/wrappers/powershell_networking.py +80 -0
- atomicshop/wrappers/psutilw/processes.py +81 -0
- atomicshop/wrappers/psutilw/psutil_networks.py +85 -0
- atomicshop/wrappers/psutilw/psutilw.py +9 -0
- atomicshop/wrappers/pyopensslw.py +9 -2
- atomicshop/wrappers/pywin32w/__init__.py +0 -0
- atomicshop/wrappers/pywin32w/cert_store.py +116 -0
- atomicshop/wrappers/pywin32w/console.py +34 -0
- atomicshop/wrappers/pywin32w/win_event_log/__init__.py +0 -0
- atomicshop/wrappers/pywin32w/win_event_log/fetch.py +174 -0
- atomicshop/wrappers/pywin32w/win_event_log/subscribe.py +212 -0
- atomicshop/wrappers/pywin32w/win_event_log/subscribes/__init__.py +0 -0
- atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_create.py +57 -0
- atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_terminate.py +49 -0
- atomicshop/wrappers/pywin32w/win_event_log/subscribes/schannel_logging.py +97 -0
- atomicshop/wrappers/pywin32w/winshell.py +19 -0
- atomicshop/wrappers/pywin32w/wmis/__init__.py +0 -0
- atomicshop/wrappers/pywin32w/wmis/msft_netipaddress.py +113 -0
- atomicshop/wrappers/pywin32w/wmis/win32_networkadapterconfiguration.py +259 -0
- atomicshop/wrappers/pywin32w/wmis/win32networkadapter.py +112 -0
- atomicshop/wrappers/pywin32w/wmis/wmi_helpers.py +236 -0
- atomicshop/wrappers/socketw/accepter.py +21 -7
- atomicshop/wrappers/socketw/certificator.py +216 -150
- atomicshop/wrappers/socketw/creator.py +190 -50
- atomicshop/wrappers/socketw/dns_server.py +500 -173
- atomicshop/wrappers/socketw/exception_wrapper.py +45 -52
- atomicshop/wrappers/socketw/process_getter.py +86 -0
- atomicshop/wrappers/socketw/receiver.py +144 -102
- atomicshop/wrappers/socketw/sender.py +65 -35
- atomicshop/wrappers/socketw/sni.py +334 -165
- atomicshop/wrappers/socketw/socket_base.py +134 -0
- atomicshop/wrappers/socketw/socket_client.py +137 -95
- atomicshop/wrappers/socketw/socket_server_tester.py +14 -9
- atomicshop/wrappers/socketw/socket_wrapper.py +717 -116
- atomicshop/wrappers/socketw/ssl_base.py +15 -14
- atomicshop/wrappers/socketw/statistics_csv.py +148 -17
- atomicshop/wrappers/sysmonw.py +157 -0
- atomicshop/wrappers/ubuntu_terminal.py +65 -26
- atomicshop/wrappers/win_auditw.py +189 -0
- atomicshop/wrappers/winregw/__init__.py +0 -0
- atomicshop/wrappers/winregw/winreg_installed_software.py +58 -0
- atomicshop/wrappers/winregw/winreg_network.py +232 -0
- {atomicshop-2.11.47.dist-info → atomicshop-3.10.5.dist-info}/METADATA +31 -49
- atomicshop-3.10.5.dist-info/RECORD +306 -0
- {atomicshop-2.11.47.dist-info → atomicshop-3.10.5.dist-info}/WHEEL +1 -1
- atomicshop/_basics_temp.py +0 -101
- atomicshop/addons/a_setup_scripts/install_psycopg2_ubuntu.sh +0 -3
- atomicshop/addons/a_setup_scripts/install_pywintrace_0.3.cmd +0 -2
- atomicshop/addons/mains/install_docker_rootless_ubuntu.py +0 -11
- atomicshop/addons/mains/install_docker_ubuntu_main_sudo.py +0 -11
- atomicshop/addons/mains/install_elastic_search_and_kibana_ubuntu.py +0 -10
- atomicshop/addons/mains/install_wsl_ubuntu_lts_admin.py +0 -9
- atomicshop/addons/package_setup/CreateWheel.cmd +0 -7
- atomicshop/addons/package_setup/Setup in Edit mode.cmd +0 -6
- atomicshop/addons/package_setup/Setup.cmd +0 -7
- atomicshop/addons/process_list/compile.cmd +0 -2
- atomicshop/addons/process_list/compiled/Win10x64/process_list.dll +0 -0
- atomicshop/addons/process_list/compiled/Win10x64/process_list.exp +0 -0
- atomicshop/addons/process_list/compiled/Win10x64/process_list.lib +0 -0
- atomicshop/archiver/_search_in_zip.py +0 -189
- atomicshop/archiver/archiver.py +0 -34
- atomicshop/archiver/search_in_archive.py +0 -250
- atomicshop/archiver/sevenz_app_w.py +0 -86
- atomicshop/archiver/sevenzs.py +0 -44
- atomicshop/archiver/zips.py +0 -293
- atomicshop/etw/dns_trace.py +0 -118
- atomicshop/etw/etw.py +0 -61
- atomicshop/file_types.py +0 -24
- atomicshop/mitm/engines/create_module_template_example.py +0 -13
- atomicshop/mitm/initialize_mitm_server.py +0 -240
- atomicshop/monitor/checks/hash.py +0 -44
- atomicshop/monitor/checks/hash_checks/file.py +0 -55
- atomicshop/monitor/checks/hash_checks/url.py +0 -62
- atomicshop/pbtkmultifile_argparse.py +0 -88
- atomicshop/permissions.py +0 -110
- atomicshop/process_poller.py +0 -237
- atomicshop/script_as_string_processor.py +0 -38
- atomicshop/ssh_scripts/process_from_ipv4.py +0 -37
- atomicshop/ssh_scripts/process_from_port.py +0 -27
- atomicshop/wrappers/_process_wrapper_curl.py +0 -27
- atomicshop/wrappers/_process_wrapper_tar.py +0 -21
- atomicshop/wrappers/dockerw/install_docker.py +0 -209
- atomicshop/wrappers/elasticsearchw/infrastructure.py +0 -265
- atomicshop/wrappers/elasticsearchw/install_elastic.py +0 -232
- atomicshop/wrappers/ffmpegw.py +0 -125
- atomicshop/wrappers/loggingw/checks.py +0 -20
- atomicshop/wrappers/nodejsw/install_nodejs.py +0 -139
- atomicshop/wrappers/process_wrapper_pbtk.py +0 -16
- atomicshop/wrappers/socketw/base.py +0 -59
- atomicshop/wrappers/socketw/get_process.py +0 -107
- atomicshop/wrappers/wslw.py +0 -191
- atomicshop-2.11.47.dist-info/RECORD +0 -251
- /atomicshop/{addons/mains → a_mains}/FACT/factw_fact_extractor_docker_image_main_sudo.py +0 -0
- /atomicshop/{addons → a_mains/addons}/PlayWrightCodegen.cmd +0 -0
- /atomicshop/{addons → a_mains/addons}/ScriptExecution.cmd +0 -0
- /atomicshop/{addons/mains → a_mains/addons}/inits/init_to_import_all_modules.py +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/ReadMe.txt +0 -0
- /atomicshop/{addons/mains → a_mains}/search_for_hyperlinks_in_docx.py +0 -0
- /atomicshop/{archiver → etws}/__init__.py +0 -0
- /atomicshop/{etw → etws/traces}/__init__.py +0 -0
- /atomicshop/{monitor/checks/hash_checks → mitm/statistic_analyzer_helper}/__init__.py +0 -0
- /atomicshop/{wrappers/nodejsw → permissions}/__init__.py +0 -0
- /atomicshop/wrappers/pywin32w/{wmi_win32process.py → wmis/win32process.py} +0 -0
- {atomicshop-2.11.47.dist-info → atomicshop-3.10.5.dist-info/licenses}/LICENSE.txt +0 -0
- {atomicshop-2.11.47.dist-info → atomicshop-3.10.5.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
from .. import trace, const
|
|
2
|
+
from ...wrappers import sysmonw
|
|
3
|
+
from ...basics import dicts
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
DEFAULT_SESSION_NAME: str = 'AtomicShopSysmonProcessCreationTrace'
|
|
7
|
+
|
|
8
|
+
PROVIDER_NAME: str = const.ETW_SYSMON['provider_name']
|
|
9
|
+
PROVIDER_GUID: str = const.ETW_SYSMON['provider_guid']
|
|
10
|
+
PROCESS_CREATION_EVENT_ID: int = const.ETW_SYSMON['event_ids']['process_create']
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SysmonProcessCreationTrace:
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
attrs: list = None,
|
|
17
|
+
session_name: str = None,
|
|
18
|
+
close_existing_session_name: bool = True,
|
|
19
|
+
sysmon_directory: str = None
|
|
20
|
+
):
|
|
21
|
+
"""
|
|
22
|
+
DnsTrace class use to trace DNS events from Windows Event Tracing for EventId 3008.
|
|
23
|
+
|
|
24
|
+
:param attrs: List of attributes to return. If None, all attributes will be returned.
|
|
25
|
+
:param session_name: The name of the session to create. If not provided, a UUID will be generated.
|
|
26
|
+
:param close_existing_session_name: Boolean to close existing session names.
|
|
27
|
+
True: if ETW session with 'session_name' exists, you will be notified and the session will be closed.
|
|
28
|
+
Then the new session with this name will be created.
|
|
29
|
+
False: if ETW session with 'session_name' exists, you will be notified and the new session will not be
|
|
30
|
+
created. Instead, the existing session will be used. If there is a buffer from the previous session,
|
|
31
|
+
you will get the events from the buffer.
|
|
32
|
+
:param sysmon_directory: The directory where Sysmon is located. If not provided, "C:\\Windows\\Sysmon" will be
|
|
33
|
+
used. If 'Sysmon.exe' is not found in the directory, it will be downloaded from the internet.
|
|
34
|
+
|
|
35
|
+
-------------------------------------------------
|
|
36
|
+
|
|
37
|
+
Usage Example:
|
|
38
|
+
from atomicshop.etw import dns_trace
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
dns_trace_w = dns_trace.DnsTrace(
|
|
42
|
+
attrs=['pid', 'name', 'cmdline', 'domain', 'query_type'],
|
|
43
|
+
session_name='MyDnsTrace',
|
|
44
|
+
close_existing_session_name=True,
|
|
45
|
+
enable_process_poller=True,
|
|
46
|
+
process_poller_etw_session_name='MyProcessTrace'
|
|
47
|
+
)
|
|
48
|
+
dns_trace_w.start()
|
|
49
|
+
while True:
|
|
50
|
+
dns_dict = dns_trace_w.emit()
|
|
51
|
+
print(dns_dict)
|
|
52
|
+
dns_trace_w.stop()
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
self.attrs = attrs
|
|
56
|
+
self.sysmon_directory: str = sysmon_directory
|
|
57
|
+
|
|
58
|
+
if not session_name:
|
|
59
|
+
session_name = DEFAULT_SESSION_NAME
|
|
60
|
+
|
|
61
|
+
self.event_trace = trace.EventTrace(
|
|
62
|
+
providers=[(PROVIDER_NAME, PROVIDER_GUID)],
|
|
63
|
+
# lambda x: self.event_queue.put(x),
|
|
64
|
+
event_id_filters=[PROCESS_CREATION_EVENT_ID],
|
|
65
|
+
session_name=session_name,
|
|
66
|
+
close_existing_session_name=close_existing_session_name
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
def start(self):
|
|
70
|
+
sysmonw.start_as_service(
|
|
71
|
+
installation_path=self.sysmon_directory, download_sysmon_if_not_found=True, skip_if_running=True)
|
|
72
|
+
self.event_trace.start()
|
|
73
|
+
|
|
74
|
+
def stop(self):
|
|
75
|
+
self.event_trace.stop()
|
|
76
|
+
|
|
77
|
+
def emit(self):
|
|
78
|
+
"""
|
|
79
|
+
Function that will return the next event from the queue.
|
|
80
|
+
The queue is blocking, so if there is no event in the queue, the function will wait until there is one.
|
|
81
|
+
|
|
82
|
+
Usage Example:
|
|
83
|
+
while True:
|
|
84
|
+
dns_dict = dns_trace.emit()
|
|
85
|
+
print(dns_dict)
|
|
86
|
+
|
|
87
|
+
:return: Dictionary with the event data.
|
|
88
|
+
|
|
89
|
+
-----------------------------------------------
|
|
90
|
+
|
|
91
|
+
Structure of the returned dictionary:
|
|
92
|
+
{
|
|
93
|
+
'event_id': int,
|
|
94
|
+
'ProcessId': int,
|
|
95
|
+
'ProcessGuid': str,
|
|
96
|
+
'Image': str,
|
|
97
|
+
'FileVersion': str,
|
|
98
|
+
'Product': str,
|
|
99
|
+
'Company': str,
|
|
100
|
+
'OriginalFileName': str,
|
|
101
|
+
'CommandLine': str,
|
|
102
|
+
'CurrentDirectory': str,
|
|
103
|
+
'User': str,
|
|
104
|
+
'LogonId': str,
|
|
105
|
+
'LogonGuid': str,
|
|
106
|
+
'TerminalSessionId': int,
|
|
107
|
+
'IntegrityLevel': str,
|
|
108
|
+
'Hashes': dict,
|
|
109
|
+
'ParentProcessGuid': str,
|
|
110
|
+
'ParentProcessId': int,
|
|
111
|
+
'ParentImage': str,
|
|
112
|
+
'ParentCommandLine': str
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
event = self.event_trace.emit()
|
|
118
|
+
|
|
119
|
+
event_dict = {'EventId': event['EventId']}
|
|
120
|
+
event_dict.update(event['EventHeader'])
|
|
121
|
+
|
|
122
|
+
if self.attrs:
|
|
123
|
+
event_dict = dicts.reorder_keys(
|
|
124
|
+
event_dict, self.attrs, skip_keys_not_in_list=True)
|
|
125
|
+
|
|
126
|
+
return event_dict
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import multiprocessing.managers
|
|
2
|
+
|
|
3
|
+
from .. import trace, providers
|
|
4
|
+
from ...basics import dicts
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
ETW_DEFAULT_SESSION_NAME: str = 'AtomicShopTcpTrace'
|
|
8
|
+
|
|
9
|
+
PROVIDER_NAME: str = "Microsoft-Windows-TCPIP"
|
|
10
|
+
PROVIDER_GUID: str = '{' + providers.get_provider_guid_by_name(PROVIDER_NAME) + '}'
|
|
11
|
+
REQUEST_RESP_EVENT_ID: int = 1033
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TcpIpNewConnectionsTrace:
|
|
15
|
+
"""
|
|
16
|
+
TcpIpNewConnectionsTrace class use to trace new connection events from Windows Event Tracing:
|
|
17
|
+
Provider: Microsoft-Windows-TCPIP
|
|
18
|
+
EventId: 1033
|
|
19
|
+
"""
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
attrs: list = None,
|
|
23
|
+
session_name: str = None,
|
|
24
|
+
close_existing_session_name: bool = True,
|
|
25
|
+
process_pool_shared_dict_proxy: multiprocessing.managers.DictProxy = None
|
|
26
|
+
):
|
|
27
|
+
"""
|
|
28
|
+
:param attrs: List of attributes to return. If None, all attributes will be returned.
|
|
29
|
+
:param session_name: The name of the session to create. If not provided, a UUID will be generated.
|
|
30
|
+
:param close_existing_session_name: Boolean to close existing session names.
|
|
31
|
+
True: if ETW session with 'session_name' exists, you will be notified and the session will be closed.
|
|
32
|
+
Then the new session with this name will be created.
|
|
33
|
+
False: if ETW session with 'session_name' exists, you will be notified and the new session will not be
|
|
34
|
+
created. Instead, the existing session will be used. If there is a buffer from the previous session,
|
|
35
|
+
you will get the events from the buffer.
|
|
36
|
+
:param process_pool_shared_dict_proxy: multiprocessing.managers.DictProxy, multiprocessing shared dict proxy
|
|
37
|
+
that contains current processes.
|
|
38
|
+
Check the 'atomicshop\process_poller\simple_process_pool.py' SimpleProcessPool class for more information.
|
|
39
|
+
|
|
40
|
+
For this specific class it means that you can run the process poller outside of this class and pass the
|
|
41
|
+
'process_pool_shared_dict_proxy' to this class. Then you can get the process name and command line for
|
|
42
|
+
the DNS events from the 'process_pool_shared_dict_proxy' and use it also in other classes.
|
|
43
|
+
|
|
44
|
+
-------------------------------------------------
|
|
45
|
+
|
|
46
|
+
Usage Example:
|
|
47
|
+
from atomicshop.etw import tcp_trace
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
tcp_trace_w = tcp_trace.TcpIpNewConnectionsTrace(
|
|
51
|
+
attrs=['pid', 'name', 'cmdline', 'domain', 'query_type'],
|
|
52
|
+
session_name='MyTcpTrace',
|
|
53
|
+
close_existing_session_name=True,
|
|
54
|
+
enable_process_poller=True
|
|
55
|
+
)
|
|
56
|
+
tcp_trace_w.start()
|
|
57
|
+
while True:
|
|
58
|
+
tcp_dict = tcp_trace_w.emit()
|
|
59
|
+
print(tcp_dict)
|
|
60
|
+
tcp_trace_w.stop()
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
self.attrs = attrs
|
|
64
|
+
self.process_pool_shared_dict_proxy: multiprocessing.managers.DictProxy = process_pool_shared_dict_proxy
|
|
65
|
+
|
|
66
|
+
if not session_name:
|
|
67
|
+
session_name = ETW_DEFAULT_SESSION_NAME
|
|
68
|
+
|
|
69
|
+
self.event_trace = trace.EventTrace(
|
|
70
|
+
providers=[(PROVIDER_NAME, PROVIDER_GUID)],
|
|
71
|
+
# lambda x: self.event_queue.put(x),
|
|
72
|
+
event_id_filters=[REQUEST_RESP_EVENT_ID],
|
|
73
|
+
session_name=session_name,
|
|
74
|
+
close_existing_session_name=close_existing_session_name,
|
|
75
|
+
enable_process_poller=True,
|
|
76
|
+
process_pool_shared_dict_proxy=self.process_pool_shared_dict_proxy
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
def start(self):
|
|
80
|
+
self.event_trace.start()
|
|
81
|
+
|
|
82
|
+
def stop(self):
|
|
83
|
+
self.event_trace.stop()
|
|
84
|
+
|
|
85
|
+
def emit(self):
|
|
86
|
+
"""
|
|
87
|
+
Function that will return the next event from the queue.
|
|
88
|
+
The queue is blocking, so if there is no event in the queue, the function will wait until there is one.
|
|
89
|
+
|
|
90
|
+
Usage Example:
|
|
91
|
+
while True:
|
|
92
|
+
tcp_dict = tcp_trace.emit()
|
|
93
|
+
print(tcp_dict)
|
|
94
|
+
|
|
95
|
+
:return: Dictionary with the event data.
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
# Get the event from ETW as is.
|
|
99
|
+
event = self.event_trace.emit()
|
|
100
|
+
|
|
101
|
+
local_address_port: str = event['EventHeader']['LocalAddress']
|
|
102
|
+
remote_address_port: str = event['EventHeader']['RemoteAddress']
|
|
103
|
+
|
|
104
|
+
if 'ffff' in local_address_port:
|
|
105
|
+
pass
|
|
106
|
+
|
|
107
|
+
local_address, local_port = local_address_port.rsplit(':', 1)
|
|
108
|
+
local_address = local_address.replace('[', '').replace(']', '')
|
|
109
|
+
|
|
110
|
+
remote_address, remote_port = remote_address_port.rsplit(':', 1)
|
|
111
|
+
remote_address = remote_address.replace('[', '').replace(']', '')
|
|
112
|
+
|
|
113
|
+
event_dict: dict = {
|
|
114
|
+
'timestamp': event['timestamp'],
|
|
115
|
+
'event_id': event['EventId'],
|
|
116
|
+
'local_ip': local_address,
|
|
117
|
+
'local_port': local_port,
|
|
118
|
+
'remote_ip': remote_address,
|
|
119
|
+
'remote_port': remote_port,
|
|
120
|
+
'status': event['EventHeader']['Status'],
|
|
121
|
+
'pid': event['pid'],
|
|
122
|
+
'name': event['name'],
|
|
123
|
+
'cmdline': event['cmdline']
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if self.attrs:
|
|
127
|
+
event_dict = dicts.reorder_keys(
|
|
128
|
+
event_dict, self.attrs, skip_keys_not_in_list=True)
|
|
129
|
+
|
|
130
|
+
return event_dict
|
atomicshop/file_io/csvs.py
CHANGED
|
@@ -1,29 +1,42 @@
|
|
|
1
1
|
import csv
|
|
2
|
+
import io
|
|
2
3
|
from typing import Tuple, List
|
|
3
4
|
|
|
4
|
-
from .file_io import read_file_decorator
|
|
5
5
|
from . import file_io
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
@read_file_decorator
|
|
9
|
-
def
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
@file_io.read_file_decorator
|
|
9
|
+
def read_csv_to_list_of_dicts_by_header(
|
|
10
|
+
file_path: str,
|
|
11
|
+
file_mode: str = 'r',
|
|
12
|
+
encoding=None,
|
|
13
|
+
header: list = None,
|
|
14
|
+
file_object=None,
|
|
15
|
+
**kwargs
|
|
16
|
+
) -> Tuple[List, List | None]:
|
|
15
17
|
"""
|
|
16
18
|
Function to read csv file and output its contents as list of dictionaries for each row.
|
|
19
|
+
Each key of the dictionary is a header field.
|
|
20
|
+
|
|
21
|
+
Example:
|
|
22
|
+
CSV file:
|
|
23
|
+
name,age,city
|
|
24
|
+
John,25,New York
|
|
25
|
+
|
|
26
|
+
Output:
|
|
27
|
+
[{'name': 'John', 'age': '25', 'city': 'New York'}]
|
|
17
28
|
|
|
18
29
|
:param file_path: String with full file path to json file.
|
|
19
30
|
:param file_mode: string, file reading mode. Examples: 'r', 'rb'. Default is 'r'.
|
|
20
31
|
:param encoding: string, encoding of the file. Default is 'None'.
|
|
21
32
|
:param header: list, list of strings that will be the header of the CSV file. Default is 'None'.
|
|
22
|
-
|
|
23
|
-
|
|
33
|
+
None: the header from the CSV file will be used. The first row of the CSV file will be the header.
|
|
34
|
+
Meaning, that the first line will be skipped and the second line will be the first row of the content.
|
|
35
|
+
List: the list will be used as header.
|
|
36
|
+
All the lines of the CSV file will be considered as content.
|
|
24
37
|
:param file_object: file object of the 'open()' function in the decorator. Decorator executes the 'with open()'
|
|
25
38
|
statement and passes to this function. That's why the default is 'None', since we get it from the decorator.
|
|
26
|
-
:return: list.
|
|
39
|
+
:return: tuple(list of entries, header(list of cell names)).
|
|
27
40
|
"""
|
|
28
41
|
|
|
29
42
|
# The header fields will be separated to list of "csv_reader.fieldnames".
|
|
@@ -39,25 +52,92 @@ def read_csv_to_list(file_path: str,
|
|
|
39
52
|
return csv_list, header
|
|
40
53
|
|
|
41
54
|
|
|
42
|
-
|
|
55
|
+
@file_io.read_file_decorator
|
|
56
|
+
def read_csv_to_list_of_lists(
|
|
57
|
+
file_path: str,
|
|
58
|
+
file_mode: str = 'r',
|
|
59
|
+
encoding=None,
|
|
60
|
+
exclude_header_from_content: bool = False,
|
|
61
|
+
file_object=None,
|
|
62
|
+
**kwargs
|
|
63
|
+
) -> Tuple[List, List | None]:
|
|
64
|
+
"""
|
|
65
|
+
Function to read csv file and output its contents as list of lists for each row.
|
|
66
|
+
|
|
67
|
+
Example:
|
|
68
|
+
CSV file:
|
|
69
|
+
name,age,city
|
|
70
|
+
John,25,New York
|
|
71
|
+
|
|
72
|
+
Output:
|
|
73
|
+
[['name', 'age', 'city'], ['John', '25', 'New York']]
|
|
74
|
+
|
|
75
|
+
:param file_path: String with full file path to json file.
|
|
76
|
+
:param file_mode: string, file reading mode. Examples: 'r', 'rb'. Default is 'r'.
|
|
77
|
+
:param encoding: string, encoding of the file. Default is 'None'.
|
|
78
|
+
:param exclude_header_from_content: Boolean, if True, the header will be excluded from the content.
|
|
79
|
+
:param file_object: file object of the 'open()' function in the decorator. Decorator executes the 'with open()'
|
|
80
|
+
statement and passes to this function. That's why the default is 'None', since we get it from the decorator.
|
|
81
|
+
:param kwargs: Keyword arguments for 'read_file' function.
|
|
82
|
+
:return: list.
|
|
43
83
|
"""
|
|
44
|
-
Function to write list object that each iteration of it contains dict object with same keys and different values.
|
|
45
84
|
|
|
46
|
-
|
|
47
|
-
|
|
85
|
+
# Read CSV file to list of lists.
|
|
86
|
+
csv_reader = csv.reader(file_object)
|
|
87
|
+
|
|
88
|
+
csv_list = list(csv_reader)
|
|
89
|
+
|
|
90
|
+
# Get the header if there is only something in the content.
|
|
91
|
+
if csv_list:
|
|
92
|
+
header = csv_list[0]
|
|
93
|
+
else:
|
|
94
|
+
header = []
|
|
95
|
+
|
|
96
|
+
if exclude_header_from_content and csv_list:
|
|
97
|
+
csv_list.pop(0)
|
|
98
|
+
|
|
99
|
+
return csv_list, header
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def write_list_to_csv(
|
|
103
|
+
content_list: list,
|
|
104
|
+
file_path: str,
|
|
105
|
+
mode: str = 'w',
|
|
106
|
+
encoding: str = None
|
|
107
|
+
) -> None:
|
|
108
|
+
"""
|
|
109
|
+
This function got dual purpose:
|
|
110
|
+
1. Write list object that each iteration of it contains list object with same length.
|
|
111
|
+
2. Write list object that each iteration of it contains dict object with same keys and different values.
|
|
112
|
+
The dictionary inside the function will be identified by the first iteration of the list.
|
|
113
|
+
Other objects (inside the provided list) than dictionary will be identified as regular objects.
|
|
114
|
+
|
|
115
|
+
:param content_list: List object that each iteration contains dictionary with same keys and different values.
|
|
116
|
+
:param file_path: Full file path to CSV file.
|
|
117
|
+
:param mode: String, file writing mode. Default is 'w'.
|
|
118
|
+
:param encoding: String, encoding of the file. Default is 'None'.
|
|
119
|
+
Example: 'utf-8', 'utf-16', 'cp1252'.
|
|
48
120
|
:return: None.
|
|
49
121
|
"""
|
|
50
122
|
|
|
51
|
-
with open(
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
writer = csv.DictWriter(csv_file, fieldnames=header, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL)
|
|
123
|
+
with open(file_path, mode=mode, newline='', encoding=encoding) as csv_file:
|
|
124
|
+
if len(content_list) > 0 and isinstance(content_list[0], dict):
|
|
125
|
+
# Treat the list as list of dictionaries.
|
|
126
|
+
header = content_list[0].keys()
|
|
56
127
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
128
|
+
# Create CSV writer.
|
|
129
|
+
writer = csv.DictWriter(csv_file, fieldnames=header, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL)
|
|
130
|
+
|
|
131
|
+
# Write header.
|
|
132
|
+
writer.writeheader()
|
|
133
|
+
# Write list of dits as rows.
|
|
134
|
+
writer.writerows(content_list)
|
|
135
|
+
# Else, treat the list as list of lists.
|
|
136
|
+
else:
|
|
137
|
+
# Create CSV writer.
|
|
138
|
+
writer = csv.writer(csv_file)
|
|
139
|
+
# Write list of lists as rows.
|
|
140
|
+
writer.writerows(content_list)
|
|
61
141
|
|
|
62
142
|
|
|
63
143
|
def get_header(file_path: str, print_kwargs: dict = None) -> list:
|
|
@@ -78,3 +158,121 @@ def get_header(file_path: str, print_kwargs: dict = None) -> list:
|
|
|
78
158
|
# Split the header to list of keys.
|
|
79
159
|
header = header.split(',')
|
|
80
160
|
return header
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def _escape_csv_value_ref(text):
|
|
164
|
+
"""
|
|
165
|
+
FOR REFERENCE ONLY, better use csv module to do it natively.
|
|
166
|
+
Function to escape text for CSV file.
|
|
167
|
+
This function escapes commas (,) and double quotes (") for csv cell (between commas).
|
|
168
|
+
|
|
169
|
+
Example:
|
|
170
|
+
test1 = 'test1'
|
|
171
|
+
test2 = 'test,2'
|
|
172
|
+
test3 = 'test3,"3",3'
|
|
173
|
+
|
|
174
|
+
csv_line = f'{escape_csv_value(test1)},{escape_csv_value(test2)},{escape_csv_value(test3)}'
|
|
175
|
+
|
|
176
|
+
Output: 'test1,"test,2","test3,""3"",3"'
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
if '"' in text:
|
|
180
|
+
text = text.replace('"', '""') # Escape double quotes
|
|
181
|
+
if ',' in text or '"' in text:
|
|
182
|
+
text = f'"{text}"' # Enclose in double quotes if there are commas or double quotes
|
|
183
|
+
return text
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def escape_csv_value(value):
|
|
187
|
+
"""
|
|
188
|
+
Function to escape text for CSV file.
|
|
189
|
+
This function escapes commas (,) and double quotes (") for csv cell (between commas).
|
|
190
|
+
|
|
191
|
+
Example:
|
|
192
|
+
test1 = 'test1'
|
|
193
|
+
test2 = 'test,2'
|
|
194
|
+
test3 = 'test3,"3",3'
|
|
195
|
+
|
|
196
|
+
csv_line = f'{escape_csv_value(test1)},{escape_csv_value(test2)},{escape_csv_value(test3)}'
|
|
197
|
+
|
|
198
|
+
Output: 'test1,"test,2","test3,""3"",3"'
|
|
199
|
+
"""
|
|
200
|
+
output = io.StringIO()
|
|
201
|
+
writer = csv.writer(output, quoting=csv.QUOTE_MINIMAL)
|
|
202
|
+
writer.writerow([value])
|
|
203
|
+
return output.getvalue().strip()
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def escape_csv_line_to_string(csv_line: list) -> str:
|
|
207
|
+
"""
|
|
208
|
+
Function to escape list of strings for CSV file.
|
|
209
|
+
This function escapes commas (,) and double quotes (") for csv cell (between commas).
|
|
210
|
+
|
|
211
|
+
Example:
|
|
212
|
+
test1 = 'test1'
|
|
213
|
+
test2 = 'test,2'
|
|
214
|
+
test3 = 'test3,"3",3'
|
|
215
|
+
|
|
216
|
+
csv_line = escape_csv_line_to_string([test1, test2, test3])
|
|
217
|
+
|
|
218
|
+
Output:
|
|
219
|
+
csv_line == 'test1,"test,2","test3,""3"",3"'
|
|
220
|
+
"""
|
|
221
|
+
|
|
222
|
+
# Prepare the data as a list of lists
|
|
223
|
+
data = [csv_line]
|
|
224
|
+
|
|
225
|
+
# Use StringIO to create an in-memory file-like object
|
|
226
|
+
output = io.StringIO()
|
|
227
|
+
writer = csv.writer(output, quoting=csv.QUOTE_MINIMAL)
|
|
228
|
+
|
|
229
|
+
# Write the data to the CSV writer
|
|
230
|
+
writer.writerows(data)
|
|
231
|
+
|
|
232
|
+
# Get the CSV string from the StringIO object, Strip to remove any trailing newlines.
|
|
233
|
+
csv_line = output.getvalue().strip()
|
|
234
|
+
|
|
235
|
+
return csv_line
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def escape_csv_line_to_list(csv_line: list) -> list:
|
|
239
|
+
"""
|
|
240
|
+
Function to escape list of strings for CSV file.
|
|
241
|
+
This function escapes commas (,) and double quotes (") for csv cell (between commas).
|
|
242
|
+
|
|
243
|
+
Example:
|
|
244
|
+
test1 = 'test1'
|
|
245
|
+
test2 = 'test,2'
|
|
246
|
+
test3 = 'test3,"3",3'
|
|
247
|
+
|
|
248
|
+
csv_entries_list = escape_csv_line_to_list([test1, test2, test3])
|
|
249
|
+
|
|
250
|
+
Output:
|
|
251
|
+
csv_entries_list == ['test1', '"test,2"', '"test3,""3"",3"']
|
|
252
|
+
"""
|
|
253
|
+
|
|
254
|
+
result_csv_entries: list = []
|
|
255
|
+
for entry in csv_line:
|
|
256
|
+
result_csv_entries.append(escape_csv_value(entry))
|
|
257
|
+
|
|
258
|
+
return result_csv_entries
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def get_number_of_cells_in_string_line(line: str) -> int:
|
|
262
|
+
"""
|
|
263
|
+
Function to get number of cells in CSV line.
|
|
264
|
+
|
|
265
|
+
:param line: String, line of CSV file.
|
|
266
|
+
:return: int, number of cells in the line.
|
|
267
|
+
"""
|
|
268
|
+
|
|
269
|
+
# Create CSV reader from 'input_file'. By default, the first row will be the header if 'fieldnames' is None.
|
|
270
|
+
csv_reader = csv.reader([line])
|
|
271
|
+
|
|
272
|
+
# Get the first row of the CSV file.
|
|
273
|
+
csv_list = list(csv_reader)
|
|
274
|
+
|
|
275
|
+
# Get the number of cells in the first row.
|
|
276
|
+
number_of_cells = len(csv_list[0])
|
|
277
|
+
|
|
278
|
+
return number_of_cells
|
atomicshop/file_io/docxs.py
CHANGED
|
@@ -12,7 +12,7 @@ def get_hyperlinks(docx_path):
|
|
|
12
12
|
:return: list of strings, hyperlinks.
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
|
-
hyperlinks: list = list()
|
|
15
|
+
hyperlinks: list[dict] = list()
|
|
16
16
|
|
|
17
17
|
try:
|
|
18
18
|
doc = Document(docx_path)
|
|
@@ -26,7 +26,19 @@ def get_hyperlinks(docx_path):
|
|
|
26
26
|
if not paragraph.hyperlinks:
|
|
27
27
|
continue
|
|
28
28
|
for hyperlink in paragraph.hyperlinks:
|
|
29
|
-
|
|
29
|
+
# Hyperlinks are stored in docx (document.xml) without the fragment part.
|
|
30
|
+
# Fragment is the anchor of the link, for example: 'https://www.example.com#anchor'.
|
|
31
|
+
# So the hyperlink.address is stored as 'https://www.example.com'.
|
|
32
|
+
# And the fragment is stored in the hyperlink.fragment as 'anchor'.
|
|
33
|
+
# For the full hyperlink, we need to concatenate the address and the fragment.
|
|
34
|
+
# If there is no anchor in the link the fragment will be empty string ('').
|
|
35
|
+
# Basically, we don't need to add the fragment to the hyperlink if it's empty, we can just use the url.
|
|
36
|
+
# if hyperlink.fragment:
|
|
37
|
+
# hyperlinks.append(hyperlink.address + "#" + hyperlink.fragment)
|
|
38
|
+
hyperlinks.append({
|
|
39
|
+
'url': hyperlink.url,
|
|
40
|
+
'text': hyperlink.text
|
|
41
|
+
})
|
|
30
42
|
|
|
31
43
|
return hyperlinks
|
|
32
44
|
|
|
@@ -62,28 +74,31 @@ def search_for_hyperlink_in_files(directory_path: str, hyperlink: str, relative_
|
|
|
62
74
|
input('press Enter')
|
|
63
75
|
"""
|
|
64
76
|
|
|
65
|
-
if not filesystem.
|
|
77
|
+
if not filesystem.is_directory_exists(directory_path):
|
|
66
78
|
raise NotADirectoryError(f"Directory doesn't exist: {directory_path}")
|
|
67
79
|
|
|
68
80
|
# Get all the docx files in the specified directory.
|
|
69
|
-
files = filesystem.
|
|
70
|
-
directory_path, file_name_check_pattern="
|
|
81
|
+
files = filesystem.get_paths_from_directory(
|
|
82
|
+
directory_path, get_file=True, file_name_check_pattern="*.docx",
|
|
71
83
|
add_relative_directory=True, relative_file_name_as_directory=True)
|
|
72
84
|
|
|
73
|
-
found_in_files: list = list()
|
|
85
|
+
found_in_files: list[dict] = list()
|
|
74
86
|
for file_path in files:
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
87
|
+
doc_hyperlinks = get_hyperlinks(file_path.path)
|
|
88
|
+
for doc_link in doc_hyperlinks:
|
|
89
|
+
if hyperlink in doc_link['url']:
|
|
90
|
+
if relative_paths:
|
|
91
|
+
path: str = file_path.relative_dir
|
|
92
|
+
else:
|
|
93
|
+
path: str = file_path.path
|
|
78
94
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
result_list = found_in_files
|
|
95
|
+
found_in_files.append({
|
|
96
|
+
'path':path,
|
|
97
|
+
'link':doc_link['url'],
|
|
98
|
+
'text':doc_link['text']
|
|
99
|
+
})
|
|
85
100
|
|
|
86
|
-
return
|
|
101
|
+
return found_in_files
|
|
87
102
|
|
|
88
103
|
|
|
89
104
|
def search_for_hyperlink_in_files_interface_main(script_directory: str = None):
|
|
@@ -126,10 +141,12 @@ def search_for_hyperlink_in_files_interface_main(script_directory: str = None):
|
|
|
126
141
|
found_in_files = search_for_hyperlink_in_files(
|
|
127
142
|
config['directory_path'], config['hyperlink'], relative_paths=config['relative_paths'])
|
|
128
143
|
|
|
129
|
-
print_api(f"Found
|
|
144
|
+
print_api(f"Found [{len(found_in_files)}] links:", color="blue")
|
|
130
145
|
|
|
131
146
|
for index, found_file in enumerate(found_in_files):
|
|
132
147
|
print_api(f"[{index+1}]", print_end="", color="green")
|
|
133
|
-
print_api(f" {found_file}")
|
|
148
|
+
print_api(f" {found_file['path']}")
|
|
149
|
+
print_api(f" {found_file['link']}", color="cyan")
|
|
150
|
+
print_api(f" {found_file['text']}", color="orange")
|
|
134
151
|
|
|
135
152
|
input('[*] Press [Enter] to exit...')
|