atomicshop 2.15.11__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/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/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/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/enums.py +2 -2
- atomicshop/basics/exceptions.py +5 -1
- atomicshop/basics/list_of_classes.py +29 -0
- atomicshop/basics/multiprocesses.py +374 -50
- atomicshop/basics/strings.py +72 -3
- atomicshop/basics/threads.py +14 -0
- atomicshop/basics/tracebacks.py +13 -3
- atomicshop/certificates.py +153 -52
- atomicshop/config_init.py +11 -6
- atomicshop/console_user_response.py +7 -14
- atomicshop/consoles.py +9 -0
- atomicshop/datetimes.py +1 -1
- atomicshop/diff_check.py +3 -3
- atomicshop/dns.py +128 -3
- atomicshop/etws/_pywintrace_fix.py +17 -0
- atomicshop/etws/trace.py +40 -42
- atomicshop/etws/traces/trace_dns.py +56 -44
- atomicshop/etws/traces/trace_tcp.py +130 -0
- atomicshop/file_io/csvs.py +27 -5
- atomicshop/file_io/docxs.py +34 -17
- atomicshop/file_io/file_io.py +31 -17
- atomicshop/file_io/jsons.py +49 -0
- atomicshop/file_io/tomls.py +139 -0
- atomicshop/filesystem.py +616 -291
- atomicshop/get_process_list.py +3 -3
- 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 -80
- 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 +136 -40
- atomicshop/mitm/statistic_analyzer_helper/moving_average_helper.py +265 -83
- atomicshop/monitor/checks/dns.py +1 -1
- atomicshop/networks.py +671 -0
- atomicshop/on_exit.py +39 -9
- 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 -42
- atomicshop/process.py +24 -6
- atomicshop/process_poller/process_pool.py +0 -1
- atomicshop/process_poller/simple_process_pool.py +204 -5
- atomicshop/python_file_patcher.py +1 -1
- atomicshop/python_functions.py +27 -75
- atomicshop/speech_recognize.py +8 -0
- atomicshop/ssh_remote.py +158 -172
- atomicshop/system_resource_monitor.py +61 -47
- atomicshop/system_resources.py +8 -8
- atomicshop/tempfiles.py +1 -2
- atomicshop/urls.py +6 -0
- atomicshop/venvs.py +28 -0
- atomicshop/versioning.py +27 -0
- atomicshop/web.py +98 -27
- 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/const.py +97 -47
- atomicshop/wrappers/ctyping/etw_winapi/etw_functions.py +178 -49
- 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 +2 -2
- 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/githubw.py +537 -54
- atomicshop/wrappers/loggingw/consts.py +1 -1
- atomicshop/wrappers/loggingw/filters.py +23 -0
- atomicshop/wrappers/loggingw/formatters.py +12 -0
- atomicshop/wrappers/loggingw/handlers.py +214 -107
- atomicshop/wrappers/loggingw/loggers.py +19 -0
- atomicshop/wrappers/loggingw/loggingw.py +860 -22
- atomicshop/wrappers/loggingw/reading.py +134 -112
- atomicshop/wrappers/mongodbw/mongo_infra.py +31 -0
- atomicshop/wrappers/mongodbw/mongodbw.py +1324 -36
- 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 +37 -1
- atomicshop/wrappers/psutilw/psutil_networks.py +85 -0
- atomicshop/wrappers/pyopensslw.py +9 -2
- atomicshop/wrappers/pywin32w/cert_store.py +116 -0
- atomicshop/wrappers/pywin32w/win_event_log/fetch.py +174 -0
- atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_create.py +3 -105
- atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_terminate.py +3 -57
- 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 +491 -182
- 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 +11 -7
- 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 +1 -1
- 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.15.11.dist-info → atomicshop-3.10.5.dist-info}/METADATA +31 -51
- atomicshop-3.10.5.dist-info/RECORD +306 -0
- {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info}/WHEEL +1 -1
- atomicshop/_basics_temp.py +0 -101
- atomicshop/a_installs/win/fibratus.py +0 -9
- atomicshop/a_installs/win/mongodb.py +0 -9
- atomicshop/a_installs/win/pycharm.py +0 -9
- 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/__pycache__/install_fibratus_windows.cpython-312.pyc +0 -0
- atomicshop/addons/mains/__pycache__/msi_unpacker.cpython-312.pyc +0 -0
- 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/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/file_types.py +0 -24
- atomicshop/mitm/config_editor.py +0 -37
- atomicshop/mitm/engines/create_module_template_example.py +0 -13
- atomicshop/mitm/initialize_mitm_server.py +0 -268
- atomicshop/pbtkmultifile_argparse.py +0 -88
- atomicshop/permissions.py +0 -151
- 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/fibratusw/install.py +0 -81
- atomicshop/wrappers/mongodbw/infrastructure.py +0 -53
- atomicshop/wrappers/mongodbw/install_mongodb.py +0 -190
- atomicshop/wrappers/msiw.py +0 -149
- atomicshop/wrappers/nodejsw/install_nodejs.py +0 -139
- atomicshop/wrappers/process_wrapper_pbtk.py +0 -16
- atomicshop/wrappers/psutilw/networks.py +0 -45
- atomicshop/wrappers/pycharmw.py +0 -81
- atomicshop/wrappers/socketw/base.py +0 -59
- atomicshop/wrappers/socketw/get_process.py +0 -107
- atomicshop/wrappers/wslw.py +0 -191
- atomicshop-2.15.11.dist-info/RECORD +0 -302
- /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 → 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 → a_mains/addons}/process_list/compile.cmd +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.dll +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.exp +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/compiled/Win10x64/process_list.lib +0 -0
- /atomicshop/{addons → a_mains/addons}/process_list/process_list.cpp +0 -0
- /atomicshop/{archiver → permissions}/__init__.py +0 -0
- /atomicshop/{wrappers/fibratusw → web_apis}/__init__.py +0 -0
- /atomicshop/wrappers/{nodejsw → pywin32w/wmis}/__init__.py +0 -0
- /atomicshop/wrappers/pywin32w/{wmi_win32process.py → wmis/win32process.py} +0 -0
- {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info/licenses}/LICENSE.txt +0 -0
- {atomicshop-2.15.11.dist-info → atomicshop-3.10.5.dist-info}/top_level.txt +0 -0
|
@@ -1,28 +1,15 @@
|
|
|
1
|
-
# v1.0.0 - 26.03.2023 14:00
|
|
2
1
|
from ...shared_functions import create_custom_logger
|
|
3
2
|
from ...message import ClientMessage
|
|
4
3
|
|
|
5
4
|
|
|
6
|
-
# Class that parses the message received from client.
|
|
7
5
|
class ParserParent:
|
|
8
|
-
|
|
9
|
-
# and the rest of the instances of the class will use the same logger.
|
|
10
|
-
# It is not in the "__init__" section, so it's not going to be initiated again.
|
|
11
|
-
# The name of the logger using "__name__" variable, which is the full name of the module package.
|
|
12
|
-
# Example: classes.parsers.parser_1_reference_general
|
|
13
|
-
|
|
14
|
-
# The code outside the functions will be executed during import of the module. When initializing a class
|
|
15
|
-
# in the script these lines will not be called again, only the "init" function.
|
|
16
|
-
logger = create_custom_logger()
|
|
17
|
-
|
|
6
|
+
"""Class that parses the message received from client."""
|
|
18
7
|
def __init__(self, class_client_message: ClientMessage):
|
|
19
8
|
self.class_client_message: ClientMessage = class_client_message
|
|
9
|
+
self.logger = create_custom_logger()
|
|
20
10
|
|
|
21
11
|
def parse(self):
|
|
22
12
|
# This is general parser, so we don't parse anything and 'request_body_parsed' gets empty byte string.
|
|
23
|
-
self.class_client_message.
|
|
13
|
+
self.class_client_message.request_custom_parsed = b''
|
|
24
14
|
|
|
25
|
-
|
|
26
|
-
self.logger.info(f"Parsed: {self.class_client_message.request_body_parsed[0: 100]}...")
|
|
27
|
-
except Exception:
|
|
28
|
-
pass
|
|
15
|
+
self.logger.info(f"Parsed: {self.class_client_message.request_custom_parsed[0: 100]}...")
|
|
@@ -1,35 +1,45 @@
|
|
|
1
1
|
import os
|
|
2
2
|
from datetime import datetime
|
|
3
|
+
import queue
|
|
4
|
+
import threading
|
|
5
|
+
from pathlib import Path
|
|
3
6
|
|
|
4
|
-
from ...shared_functions import build_module_names, create_custom_logger
|
|
5
|
-
from ...
|
|
6
|
-
from ....urls import url_parser
|
|
7
|
+
from ...shared_functions import build_module_names, create_custom_logger
|
|
8
|
+
from ... import message, recs_files
|
|
7
9
|
from .... import filesystem
|
|
10
|
+
from ....file_io import jsons
|
|
11
|
+
from ....print_api import print_api
|
|
8
12
|
|
|
9
13
|
|
|
10
14
|
# The class that is responsible for Recording Requests / Responses.
|
|
11
15
|
class RecorderParent:
|
|
12
|
-
# The code outside the functions will be executed during import of the module. When initializing a class
|
|
13
|
-
# in the script these lines will not be called again, only the "init" function.
|
|
14
|
-
logger = create_custom_logger()
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
# noinspection PyTypeChecker
|
|
18
|
+
def __init__(self, record_path: str):
|
|
18
19
|
self.record_path: str = record_path
|
|
20
|
+
|
|
19
21
|
self.file_extension: str = ".json"
|
|
20
22
|
self.engine_name = None
|
|
21
23
|
self.module_name = None
|
|
22
24
|
self.engine_record_path: str = str()
|
|
23
25
|
self.record_file_path: str = str()
|
|
26
|
+
self.class_client_message: message.ClientMessage = None
|
|
27
|
+
|
|
28
|
+
self.logger = create_custom_logger()
|
|
24
29
|
|
|
25
30
|
# Get engine name and module name
|
|
26
31
|
self.get_engine_module()
|
|
27
|
-
|
|
28
|
-
|
|
32
|
+
|
|
33
|
+
# Build the record path with file name
|
|
34
|
+
self.build_record_path_to_engine()
|
|
29
35
|
|
|
30
36
|
# Create folder.
|
|
31
37
|
filesystem.create_directory(self.engine_record_path)
|
|
32
38
|
|
|
39
|
+
# Initialize a queue to hold messages
|
|
40
|
+
self.message_queue: queue.Queue = queue.Queue()
|
|
41
|
+
self.recorder_worker_thread = None
|
|
42
|
+
|
|
33
43
|
# "self.__module__" is fully qualified module name: classes.engines.ENGINE-NAME.MODULE-NAME
|
|
34
44
|
def get_engine_module(self):
|
|
35
45
|
_, self.engine_name, self.module_name = build_module_names(self.__module__)
|
|
@@ -38,33 +48,10 @@ class RecorderParent:
|
|
|
38
48
|
self.engine_record_path = self.record_path + os.sep + self.engine_name
|
|
39
49
|
|
|
40
50
|
def build_record_full_file_path(self):
|
|
41
|
-
# current date and time in object
|
|
42
|
-
now = datetime.now()
|
|
43
|
-
# Formatting the date and time and converting it to string object
|
|
44
|
-
day_time_format: str = now.strftime("%Y_%m_%d-%H_%M_%S_%f")
|
|
45
|
-
|
|
46
|
-
# Build the record path with file name
|
|
47
|
-
self.build_record_path_to_engine()
|
|
48
|
-
|
|
49
|
-
# Define empty 'http_path'.
|
|
50
|
-
http_path: str = str()
|
|
51
|
-
# If 'self.class_client_message.request_raw_decoded.path' will be undefined, exception will raise.
|
|
52
|
-
# This will happen if the message is not HTTP.
|
|
53
|
-
try:
|
|
54
|
-
# Parse the url to components.
|
|
55
|
-
http_path_parsed = url_parser(self.class_client_message.request_raw_decoded.path)
|
|
56
|
-
# Get only directories.
|
|
57
|
-
http_path_directories_string = '-'.join(http_path_parsed['directories'])
|
|
58
|
-
# Add '_' character before 'http_path' to look better on the file name.
|
|
59
|
-
http_path = f'_{http_path_directories_string}'
|
|
60
|
-
# If 'self.class_client_message.request_raw_decoded.path' is not defined, we'll pass the exception.
|
|
61
|
-
except Exception:
|
|
62
|
-
pass
|
|
63
|
-
|
|
64
51
|
# If HTTP Path is not defined, 'http_path' will be empty, and it will not interfere with file name.
|
|
65
|
-
self.record_file_path: str =
|
|
66
|
-
self.engine_record_path
|
|
67
|
-
|
|
52
|
+
self.record_file_path: str = (
|
|
53
|
+
f"{self.engine_record_path}{os.sep}th{self.class_client_message.thread_id}_"
|
|
54
|
+
f"{self.class_client_message.server_name}{self.file_extension}")
|
|
68
55
|
|
|
69
56
|
def convert_messages(self):
|
|
70
57
|
"""
|
|
@@ -74,26 +61,96 @@ class RecorderParent:
|
|
|
74
61
|
# We need to check that the values that we want to convert aren't empty or 'None'.
|
|
75
62
|
if self.class_client_message.request_raw_bytes:
|
|
76
63
|
self.class_client_message.request_raw_hex = self.class_client_message.request_raw_bytes.hex()
|
|
77
|
-
if self.class_client_message.
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
for response_raw_bytes in self.class_client_message.response_list_of_raw_bytes:
|
|
83
|
-
self.class_client_message.response_list_of_raw_hex.append(response_raw_bytes.hex())
|
|
64
|
+
if self.class_client_message.response_raw_bytes:
|
|
65
|
+
self.class_client_message.response_raw_hex = self.class_client_message.response_raw_bytes.hex()
|
|
66
|
+
|
|
67
|
+
def record(self, class_client_message: message.ClientMessage):
|
|
68
|
+
self.class_client_message = class_client_message
|
|
84
69
|
|
|
85
|
-
|
|
86
|
-
self.
|
|
70
|
+
# Build full file path if it is not already built.
|
|
71
|
+
if not self.record_file_path:
|
|
72
|
+
self.build_record_full_file_path()
|
|
73
|
+
|
|
74
|
+
# Start the worker thread if it is not already running
|
|
75
|
+
if not self.recorder_worker_thread:
|
|
76
|
+
self.recorder_worker_thread = threading.Thread(
|
|
77
|
+
target=save_message_worker,
|
|
78
|
+
args=(self.record_file_path, self.message_queue, self.logger),
|
|
79
|
+
name=f"{self.class_client_message.thread_process} | Thread-{self.class_client_message.thread_id}-Recorder",
|
|
80
|
+
daemon=True
|
|
81
|
+
)
|
|
82
|
+
self.recorder_worker_thread.start()
|
|
83
|
+
|
|
84
|
+
self.logger.info("Putting Message to Recorder Thread Queue...")
|
|
87
85
|
|
|
88
86
|
# Convert the requests and responses to hex.
|
|
89
87
|
self.convert_messages()
|
|
90
88
|
# Get the message in dict / JSON format
|
|
91
|
-
|
|
89
|
+
record_message_dict: dict = dict(self.class_client_message)
|
|
92
90
|
|
|
93
|
-
#
|
|
94
|
-
|
|
95
|
-
output_file.write(record_message)
|
|
96
|
-
|
|
97
|
-
self.logger.info(f"Recorded to file: {self.record_file_path}")
|
|
91
|
+
# Put the message in the queue to be processed by the worker thread
|
|
92
|
+
self.message_queue.put(record_message_dict)
|
|
98
93
|
|
|
99
94
|
return self.record_file_path
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def save_message_worker(
|
|
98
|
+
record_file_path_no_date: str,
|
|
99
|
+
message_queue: queue.Queue,
|
|
100
|
+
logger
|
|
101
|
+
):
|
|
102
|
+
"""Worker function to process messages from the queue and write them to the file."""
|
|
103
|
+
original_file_path_object: Path = Path(record_file_path_no_date)
|
|
104
|
+
original_file_stem: str = original_file_path_object.stem
|
|
105
|
+
original_file_extension: str = original_file_path_object.suffix
|
|
106
|
+
original_file_directory: str = str(original_file_path_object.parent)
|
|
107
|
+
|
|
108
|
+
original_datetime_string: str = get_datetime_string()
|
|
109
|
+
previous_date_string: str = get_date_string()
|
|
110
|
+
|
|
111
|
+
record_file_path: str = f'{original_file_directory}{os.sep}{original_datetime_string}_{original_file_stem}{original_file_extension}'
|
|
112
|
+
|
|
113
|
+
while True:
|
|
114
|
+
# Get a message from the queue
|
|
115
|
+
record_message_dict = message_queue.get()
|
|
116
|
+
|
|
117
|
+
# Check for the "stop" signal
|
|
118
|
+
if record_message_dict is None:
|
|
119
|
+
break
|
|
120
|
+
|
|
121
|
+
current_date_string: str = get_date_string()
|
|
122
|
+
|
|
123
|
+
# If current datetime string is different from the original datetime string, we will create a new file path.
|
|
124
|
+
if current_date_string != previous_date_string:
|
|
125
|
+
previous_date_string = current_date_string
|
|
126
|
+
current_datetime_string: str = get_datetime_string()
|
|
127
|
+
record_file_path = f'{original_file_directory}{os.sep}{current_datetime_string}_{original_file_stem}_partof_{original_datetime_string}{original_file_extension}'
|
|
128
|
+
|
|
129
|
+
try:
|
|
130
|
+
jsons.append_to_json(
|
|
131
|
+
record_message_dict, record_file_path, indent=2,
|
|
132
|
+
enable_long_file_path=True, print_kwargs={'logger': logger}
|
|
133
|
+
)
|
|
134
|
+
except TypeError as e:
|
|
135
|
+
print_api(str(e), logger_method="critical", logger=logger)
|
|
136
|
+
raise e
|
|
137
|
+
|
|
138
|
+
logger.info(f"Recorded to file: {record_file_path}")
|
|
139
|
+
|
|
140
|
+
# Indicate task completion
|
|
141
|
+
message_queue.task_done()
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def get_datetime_string():
|
|
145
|
+
# current date and time in object
|
|
146
|
+
now = datetime.now()
|
|
147
|
+
# Formatting the date and time and converting it to string object
|
|
148
|
+
day_time_format: str = now.strftime(recs_files.REC_FILE_DATE_TIME_FORMAT)
|
|
149
|
+
return day_time_format
|
|
150
|
+
|
|
151
|
+
def get_date_string():
|
|
152
|
+
# current date and time in object
|
|
153
|
+
now = datetime.now()
|
|
154
|
+
# Formatting the date and time and converting it to string object
|
|
155
|
+
date_format: str = now.strftime(recs_files.REC_FILE_DATE_FORMAT)
|
|
156
|
+
return date_format
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Using to convert status code to status phrase / string.
|
|
2
|
+
from http import HTTPStatus
|
|
3
|
+
# Parsing PATH template to variables.
|
|
4
|
+
from pathlib import PurePosixPath
|
|
5
|
+
from urllib.parse import unquote
|
|
6
|
+
# Needed to extract parameters after question mark in URL / Path.
|
|
7
|
+
from urllib.parse import urlparse
|
|
8
|
+
from urllib.parse import parse_qs
|
|
9
|
+
|
|
10
|
+
from ...message import ClientMessage
|
|
11
|
+
from .... import http_parse
|
|
12
|
+
from ....print_api import print_api
|
|
13
|
+
|
|
14
|
+
from atomicshop.mitm.shared_functions import create_custom_logger
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class RequesterParent:
|
|
18
|
+
"""The class that is responsible for generating request to client based on the received message."""
|
|
19
|
+
def __init__(self):
|
|
20
|
+
self.logger = create_custom_logger()
|
|
21
|
+
|
|
22
|
+
def build_byte_request(
|
|
23
|
+
self,
|
|
24
|
+
http_method: str,
|
|
25
|
+
endpoint: str,
|
|
26
|
+
http_version: str,
|
|
27
|
+
headers: dict,
|
|
28
|
+
body: bytes
|
|
29
|
+
) -> bytes:
|
|
30
|
+
# noinspection GrazieInspection
|
|
31
|
+
"""
|
|
32
|
+
Create genuine request from input parameters.
|
|
33
|
+
---------------
|
|
34
|
+
The request is built from:
|
|
35
|
+
<http_method> <endpoint> <http_version>\r\n
|
|
36
|
+
Headers1: Value\r\n
|
|
37
|
+
Headers2: Value\r\n
|
|
38
|
+
\r\n # This is meant to end the headers' section
|
|
39
|
+
Body # Request doesn't end with '\r\n\r\n'
|
|
40
|
+
---------------
|
|
41
|
+
Example for POST request:
|
|
42
|
+
POST /api/v1/resource HTTP/1.1\r\n
|
|
43
|
+
Cache-Control: max-age=86400\r\n
|
|
44
|
+
Content-Type: application/json; charset=utf-8\r\n
|
|
45
|
+
\r\n
|
|
46
|
+
{"id":1,"name":"something"}
|
|
47
|
+
---------------
|
|
48
|
+
You can create response as:
|
|
49
|
+
|
|
50
|
+
...POST endpoint/api/1 HTTP/1.1
|
|
51
|
+
header1: value
|
|
52
|
+
header2: value
|
|
53
|
+
|
|
54
|
+
{data: value}...
|
|
55
|
+
|
|
56
|
+
Change 3 dots ("...") to 3 double quotes before "POST" and after "value}".
|
|
57
|
+
This way there will be "\n" added automatically after each line.
|
|
58
|
+
While, the HTTP Client does the parsing of the text and not raw data, most probably it will be parsed well,
|
|
59
|
+
but genuine requests from HTTP sources come with "\r\n" at the end of the line, so better use these for
|
|
60
|
+
better compatibility.
|
|
61
|
+
---------------
|
|
62
|
+
|
|
63
|
+
:param http_method: HTTP Method of Request, e.g. 'GET', 'POST', etc.
|
|
64
|
+
:param endpoint: Endpoint of Request, e.g. '/api/v1/resource'.
|
|
65
|
+
:param http_version: HTTP Version of Response in HTTP Status line.
|
|
66
|
+
:param headers: HTTP Headers of Response.
|
|
67
|
+
:param body: HTTP body data of Response, bytes.
|
|
68
|
+
:return: bytes of the response.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
# CHeck if the HTTP method is valid.
|
|
73
|
+
if http_method not in http_parse.get_request_methods():
|
|
74
|
+
raise ValueError(f"Invalid HTTP Method: {http_method}")
|
|
75
|
+
|
|
76
|
+
# Building the full method endpoint string line and the "\r\n" in the end.
|
|
77
|
+
method_full: str = f"{http_method} {endpoint} {http_version}\r\n"
|
|
78
|
+
|
|
79
|
+
# Defining headers string.
|
|
80
|
+
headers_string: str = str()
|
|
81
|
+
# Adding all the headers to the full response
|
|
82
|
+
for keys, values in headers.items():
|
|
83
|
+
headers_string = headers_string + str(keys) + ": " + str(values) + "\r\n"
|
|
84
|
+
|
|
85
|
+
# Building full string request.
|
|
86
|
+
# 1. Adding full method line.
|
|
87
|
+
# 2. Adding headers string.
|
|
88
|
+
# 3. Adding a line that end headers (with "\r\n").
|
|
89
|
+
# 4. Adding body as byte string.
|
|
90
|
+
request_full_no_body: str = method_full + headers_string + "\r\n"
|
|
91
|
+
|
|
92
|
+
# Converting the HTTP Request string to bytes and adding 'body' bytes.
|
|
93
|
+
request_raw_bytes = request_full_no_body.encode() + body
|
|
94
|
+
except ValueError as exception_object:
|
|
95
|
+
message = \
|
|
96
|
+
f'Create Byte request function error, of the of values provided is not standard: {exception_object}'
|
|
97
|
+
print_api(message, error_type=True, logger=self.logger, logger_method='error', color='red')
|
|
98
|
+
|
|
99
|
+
request_raw_bytes = b''
|
|
100
|
+
|
|
101
|
+
# Parsing the request we created.
|
|
102
|
+
request_parse_test = http_parse.HTTPRequestParse(request_raw_bytes)
|
|
103
|
+
# If there were errors during parsing, it means that something is wrong with response created.
|
|
104
|
+
if request_parse_test.error_message:
|
|
105
|
+
self.logger.error(request_parse_test.error_message)
|
|
106
|
+
request_raw_bytes = b''
|
|
107
|
+
else:
|
|
108
|
+
self.logger.info("Created Valid Byte Request.")
|
|
109
|
+
|
|
110
|
+
return request_raw_bytes
|
|
111
|
+
|
|
112
|
+
def create_request(self, class_client_message: ClientMessage, **kwargs) -> bytes:
|
|
113
|
+
""" This function should be overridden in the child class. """
|
|
114
|
+
|
|
115
|
+
request_bytes: bytes = None
|
|
116
|
+
return request_bytes
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
# v1.0.0 - 26.03.2023 14:20
|
|
2
1
|
# Using to convert status code to status phrase / string.
|
|
3
2
|
from http import HTTPStatus
|
|
4
3
|
# Parsing PATH template to variables.
|
|
@@ -8,20 +7,31 @@ from urllib.parse import unquote
|
|
|
8
7
|
from urllib.parse import urlparse
|
|
9
8
|
from urllib.parse import parse_qs
|
|
10
9
|
|
|
11
|
-
from ...shared_functions import create_custom_logger
|
|
12
10
|
from ...message import ClientMessage
|
|
13
11
|
from ....http_parse import HTTPResponseParse
|
|
14
12
|
from ....print_api import print_api
|
|
15
13
|
|
|
14
|
+
from atomicshop.mitm.shared_functions import create_custom_logger
|
|
16
15
|
|
|
17
|
-
# The class that is responsible for generating response to client based on the received message.
|
|
18
|
-
class ResponderParent:
|
|
19
|
-
# The code outside the functions will be executed during import of the module. When initializing a class
|
|
20
|
-
# in the script these lines will not be called again, only the "init" function.
|
|
21
|
-
logger = create_custom_logger()
|
|
22
16
|
|
|
17
|
+
class ResponderParent:
|
|
18
|
+
"""The class that is responsible for generating response to client based on the received message."""
|
|
23
19
|
def __init__(self):
|
|
24
|
-
|
|
20
|
+
self.logger = create_custom_logger()
|
|
21
|
+
# engine: initialize_engines.ModuleCategory
|
|
22
|
+
self.engine = None
|
|
23
|
+
|
|
24
|
+
def add_args(
|
|
25
|
+
self,
|
|
26
|
+
# engine: initialize_engines.ModuleCategory
|
|
27
|
+
engine = None
|
|
28
|
+
):
|
|
29
|
+
"""
|
|
30
|
+
Add more arguments to the class.
|
|
31
|
+
This is needed to be backwards compatible and not to change the child class apis.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
self.engine = engine
|
|
25
35
|
|
|
26
36
|
@staticmethod
|
|
27
37
|
def get_path_parts(path: str):
|
|
@@ -107,51 +117,56 @@ class ResponderParent:
|
|
|
107
117
|
|
|
108
118
|
return parameter_value
|
|
109
119
|
|
|
110
|
-
def
|
|
111
|
-
self,
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
Headers2: Value\r\n
|
|
119
|
-
\r\n # This is meant to end the headers' section
|
|
120
|
-
Body\r\n\r\n # In most cases Body is ended with '\r\n\r\n'
|
|
121
|
-
---------------
|
|
122
|
-
Example for 200 response:
|
|
123
|
-
HTTP/1.1 200 OK\r\n
|
|
124
|
-
Cache-Control: max-age=86400\r\n
|
|
125
|
-
Content-Type: application/json; charset=utf-8\r\n
|
|
126
|
-
\r\n
|
|
127
|
-
{"id":1,"name":"something"}
|
|
128
|
-
---------------
|
|
129
|
-
The final response will look like oneline string:
|
|
130
|
-
HTTP/1.1 200 OK\r\nCache-Control: max-age=86400\r\n
|
|
131
|
-
Content-Type: application/json; charset=utf-8\r\n\r\n{"id":1,"name":"something"}
|
|
132
|
-
---------------
|
|
133
|
-
You can create response as:
|
|
134
|
-
|
|
135
|
-
...HTTP/1.1 200 OK
|
|
136
|
-
header1: value
|
|
137
|
-
header2: value
|
|
138
|
-
|
|
139
|
-
{data: value}...
|
|
140
|
-
|
|
141
|
-
Change 3 dots ("...") to 3 double quotes before "HTTP" and after "value}".
|
|
142
|
-
This way there will be "\n" added automatically after each line.
|
|
143
|
-
While, the HTTP Client does the parsing of the text and not raw data, most probably it will be parsed well,
|
|
144
|
-
but genuine responses from HTTP sources come with "\r\n" at the end of the line, so better use these for
|
|
145
|
-
better compatibility.
|
|
146
|
-
---------------
|
|
147
|
-
|
|
148
|
-
:param http_version: HTTP Version of Response in HTTP Status line.
|
|
149
|
-
:param status_code: HTTP Status Code of Response in HTTP Status line.
|
|
150
|
-
:param headers: HTTP Headers of Response.
|
|
151
|
-
:param body: HTTP body data of Response, bytes.
|
|
152
|
-
:param client_message: client message class.
|
|
153
|
-
:return:
|
|
120
|
+
def build_byte_response(
|
|
121
|
+
self,
|
|
122
|
+
http_version: str,
|
|
123
|
+
status_code: int,
|
|
124
|
+
headers: dict,
|
|
125
|
+
body: bytes
|
|
126
|
+
) -> bytes:
|
|
127
|
+
# noinspection GrazieInspection
|
|
154
128
|
"""
|
|
129
|
+
Create genuine response from input parameters.
|
|
130
|
+
---------------
|
|
131
|
+
The response is built from:
|
|
132
|
+
HTTP-Version HTTP-Status HTTP-Status-String\r\n
|
|
133
|
+
Headers1: Value\r\n
|
|
134
|
+
Headers2: Value\r\n
|
|
135
|
+
\r\n # This is meant to end the headers' section
|
|
136
|
+
Body\r\n\r\n # In most cases Body is ended with '\r\n\r\n'
|
|
137
|
+
---------------
|
|
138
|
+
Example for 200 response:
|
|
139
|
+
HTTP/1.1 200 OK\r\n
|
|
140
|
+
Cache-Control: max-age=86400\r\n
|
|
141
|
+
Content-Type: application/json; charset=utf-8\r\n
|
|
142
|
+
\r\n
|
|
143
|
+
{"id":1,"name":"something"}
|
|
144
|
+
---------------
|
|
145
|
+
The final response will look like oneline string:
|
|
146
|
+
HTTP/1.1 200 OK\r\nCache-Control: max-age=86400\r\n
|
|
147
|
+
Content-Type: application/json; charset=utf-8\r\n\r\n{"id":1,"name":"something"}
|
|
148
|
+
---------------
|
|
149
|
+
You can create response as:
|
|
150
|
+
|
|
151
|
+
...HTTP/1.1 200 OK
|
|
152
|
+
header1: value
|
|
153
|
+
header2: value
|
|
154
|
+
|
|
155
|
+
{data: value}...
|
|
156
|
+
|
|
157
|
+
Change 3 dots ("...") to 3 double quotes before "HTTP" and after "value}".
|
|
158
|
+
This way there will be "\n" added automatically after each line.
|
|
159
|
+
While, the HTTP Client does the parsing of the text and not raw data, most probably it will be parsed well,
|
|
160
|
+
but genuine responses from HTTP sources come with "\r\n" at the end of the line, so better use these for
|
|
161
|
+
better compatibility.
|
|
162
|
+
---------------
|
|
163
|
+
|
|
164
|
+
:param http_version: HTTP Version of Response in HTTP Status line.
|
|
165
|
+
:param status_code: HTTP Status Code of Response in HTTP Status line.
|
|
166
|
+
:param headers: HTTP Headers of Response.
|
|
167
|
+
:param body: HTTP body data of Response, bytes.
|
|
168
|
+
:return: bytes of the response.
|
|
169
|
+
"""
|
|
155
170
|
|
|
156
171
|
try:
|
|
157
172
|
# Building full status string line and the "\r\n" to the end of the status line
|
|
@@ -178,7 +193,6 @@ class ResponderParent:
|
|
|
178
193
|
print_api(message, error_type=True, logger=self.logger, logger_method='error', color='red')
|
|
179
194
|
|
|
180
195
|
response_raw_bytes = b''
|
|
181
|
-
pass
|
|
182
196
|
|
|
183
197
|
# Parsing the response we created.
|
|
184
198
|
response_parse_test = HTTPResponseParse(response_raw_bytes)
|
|
@@ -186,73 +200,20 @@ class ResponderParent:
|
|
|
186
200
|
if response_parse_test.error:
|
|
187
201
|
self.logger.error(response_parse_test.error)
|
|
188
202
|
response_raw_bytes = b''
|
|
189
|
-
response_decoded = None
|
|
190
203
|
else:
|
|
191
204
|
self.logger.info("Created Valid Byte Response.")
|
|
192
|
-
response_decoded = response_parse_test.response_raw_decoded
|
|
193
205
|
|
|
194
|
-
|
|
195
|
-
self.add_response_elements_to_lists(client_message, response_raw_bytes, response_decoded)
|
|
206
|
+
return response_raw_bytes
|
|
196
207
|
|
|
197
208
|
@staticmethod
|
|
198
|
-
def
|
|
199
|
-
"""
|
|
200
|
-
Function just adds the byte response to the 'response_list_of_raw_bytes'.
|
|
201
|
-
:param class_client_message:
|
|
202
|
-
:param byte_response:
|
|
203
|
-
:param decoded_response:
|
|
204
|
-
:return:
|
|
205
|
-
"""
|
|
209
|
+
def create_connect_response(class_client_message: ClientMessage):
|
|
210
|
+
""" This function should be overridden in the child class. """
|
|
206
211
|
|
|
207
|
-
class_client_message
|
|
208
|
-
|
|
212
|
+
_ = class_client_message
|
|
213
|
+
response_bytes_list: list[bytes] = list()
|
|
214
|
+
return response_bytes_list
|
|
209
215
|
|
|
210
216
|
def create_response(self, class_client_message: ClientMessage):
|
|
211
|
-
|
|
212
|
-
"""
|
|
213
|
-
Function to create Response based on ClientMessage and its Request.
|
|
214
|
-
|
|
215
|
-
:param class_client_message: contains request and other parameters to help creating response.
|
|
216
|
-
:return: "class_client_message.response_list_of_raw_bytes" is populated with list of responses in bytes.
|
|
217
|
-
-----------------------------------
|
|
218
|
-
# Remember that 'response_list_of_raw_bytes' is a list object that will contain byte response/s.
|
|
219
|
-
|
|
220
|
-
# Example of creating 'response_list_of_raw_bytes' using 'build_byte_response' function:
|
|
221
|
-
class_client_message.response_list_of_raw_bytes.append(
|
|
222
|
-
self.build_byte_response(
|
|
223
|
-
http_version=class_client_message.request_raw_decoded.request_version,
|
|
224
|
-
status_code=200,
|
|
225
|
-
headers=response_headers,
|
|
226
|
-
body=b''
|
|
227
|
-
)
|
|
228
|
-
)
|
|
229
|
-
|
|
230
|
-
# Or you can use the 'add_byte_response_to_response_list' function that will do it for you:
|
|
231
|
-
byte_response = self.build_byte_response(
|
|
232
|
-
http_version=class_client_message.request_raw_decoded.request_version,
|
|
233
|
-
status_code=200,
|
|
234
|
-
headers=response_headers,
|
|
235
|
-
body=b''
|
|
236
|
-
)
|
|
237
|
-
self.add_byte_response_to_response_list(class_client_message, byte_response)
|
|
238
|
-
-----------------------------------
|
|
239
|
-
# Example of extracting variables from URL PATH based on custom PATH TEMPLATE:
|
|
240
|
-
# (more examples in 'self.extract_variables_from_path_template' function description)
|
|
241
|
-
template_path: str = "/hithere/<variable1>/else/<variable2>/tested/"
|
|
242
|
-
path_variables: dict = extract_variables_from_path_template(
|
|
243
|
-
path=class_client_message.request_raw_decoded.path,
|
|
244
|
-
template_path=template_path
|
|
245
|
-
)
|
|
246
|
-
-----------------------------------
|
|
247
|
-
# Example of extracting value from URL PATH parameters after question mark:
|
|
248
|
-
parameter_value = extract_value_from_path_parameter(
|
|
249
|
-
path=class_client_message.request_raw_decoded.path,
|
|
250
|
-
parameter='test_id'
|
|
251
|
-
)
|
|
252
|
-
"""
|
|
217
|
+
""" This function should be overridden in the child class. """
|
|
253
218
|
|
|
254
|
-
|
|
255
|
-
decoded_response = None
|
|
256
|
-
# class_client_message.response_list_of_raw_bytes.append(byte_response)
|
|
257
|
-
self.add_response_elements_to_lists(class_client_message, byte_response, decoded_response)
|
|
258
|
-
self.logger.info(f"Response: {class_client_message.response_list_of_raw_bytes}")
|
|
219
|
+
return None
|
|
@@ -3,21 +3,24 @@ from atomicshop.mitm.engines.__parent.parser___parent import ParserParent
|
|
|
3
3
|
from atomicshop.mitm.shared_functions import create_custom_logger
|
|
4
4
|
from atomicshop.mitm.message import ClientMessage
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
#
|
|
9
|
-
|
|
10
|
-
# from
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
# This is 'example' '.proto' file that contains message 'ExampleRequest'.
|
|
9
|
+
from .example_pb2 import ExampleRequest
|
|
10
|
+
# Import from 'protobuf' library of function 'MessageToDict' that
|
|
11
|
+
converts protobuf message object type to python dict.
|
|
12
|
+
from google.protobuf.json_format import MessageToDict
|
|
13
|
+
"""
|
|
11
14
|
|
|
12
15
|
|
|
13
16
|
# Class that parses the message received from client.
|
|
14
17
|
class ParserGeneral(ParserParent):
|
|
15
|
-
logger = create_custom_logger()
|
|
16
|
-
|
|
17
18
|
# When initializing main classes through "super" you need to pass parameters to init
|
|
18
19
|
def __init__(self, class_client_message: ClientMessage):
|
|
19
20
|
super().__init__(class_client_message)
|
|
20
21
|
|
|
22
|
+
self.logger = create_custom_logger()
|
|
23
|
+
|
|
21
24
|
# ==================================================================================================================
|
|
22
25
|
# Uncomment this section in order to begin building custom responder.
|
|
23
26
|
# def parse_example_request(self):
|
|
@@ -4,10 +4,10 @@ from atomicshop.mitm.shared_functions import create_custom_logger
|
|
|
4
4
|
from atomicshop.mitm.message import ClientMessage
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
# The class that is responsible for Recording Requests / Responses
|
|
8
7
|
class RecorderGeneral(RecorderParent):
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
"""The class that is responsible for Recording Requests / Responses"""
|
|
11
9
|
# When initializing main classes through "super" you need to pass parameters to init
|
|
12
|
-
def __init__(self,
|
|
13
|
-
super().__init__(
|
|
10
|
+
def __init__(self, record_path):
|
|
11
|
+
super().__init__(record_path)
|
|
12
|
+
|
|
13
|
+
self.logger = create_custom_logger()
|