atomicshop 2.16.41__py3-none-any.whl → 2.16.43__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.
Potentially problematic release.
This version of atomicshop might be problematic. Click here for more details.
- atomicshop/__init__.py +1 -1
- atomicshop/basics/bytes_arrays.py +24 -0
- atomicshop/dns.py +11 -0
- atomicshop/http_parse.py +4 -4
- atomicshop/mitm/connection_thread_worker.py +2 -7
- atomicshop/mitm/mitm_main.py +9 -4
- atomicshop/web.py +5 -2
- atomicshop/websocket_parse.py +176 -0
- atomicshop/wrappers/factw/install/install_after_restart.py +13 -2
- atomicshop/wrappers/mongodbw/mongodbw.py +312 -110
- atomicshop/wrappers/playwrightw/javascript.py +7 -3
- atomicshop/wrappers/playwrightw/scenarios.py +14 -5
- atomicshop/wrappers/pycharmw/ubuntu.py +3 -2
- atomicshop/wrappers/winregw/winreg_network.py +45 -16
- {atomicshop-2.16.41.dist-info → atomicshop-2.16.43.dist-info}/METADATA +2 -1
- {atomicshop-2.16.41.dist-info → atomicshop-2.16.43.dist-info}/RECORD +19 -18
- {atomicshop-2.16.41.dist-info → atomicshop-2.16.43.dist-info}/LICENSE.txt +0 -0
- {atomicshop-2.16.41.dist-info → atomicshop-2.16.43.dist-info}/WHEEL +0 -0
- {atomicshop-2.16.41.dist-info → atomicshop-2.16.43.dist-info}/top_level.txt +0 -0
atomicshop/__init__.py
CHANGED
|
@@ -53,6 +53,30 @@ def convert_sequence_of_bytes_to_sequence_of_strings(byte_sequence: bytes) -> li
|
|
|
53
53
|
return result
|
|
54
54
|
|
|
55
55
|
|
|
56
|
+
def convert_sequence_of_bytes_to_exact_string(
|
|
57
|
+
byte_sequence: bytes,
|
|
58
|
+
add_space_between_bytes: bool = False,
|
|
59
|
+
) -> str:
|
|
60
|
+
"""
|
|
61
|
+
Convert sequence of bytes to exact string.
|
|
62
|
+
Example: b'\xc0\x00' -> 'c000'
|
|
63
|
+
|
|
64
|
+
:param byte_sequence: bytes, sequence of bytes.
|
|
65
|
+
:param add_space_between_bytes: bool, add space between bytes.
|
|
66
|
+
Example: b'\xc0\x00' -> 'c0 00'
|
|
67
|
+
:return: string.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
# Convert to hex string and format
|
|
71
|
+
byte_list: list = []
|
|
72
|
+
for byte in byte_sequence:
|
|
73
|
+
byte_list.append(f'{byte:02x}')
|
|
74
|
+
|
|
75
|
+
result = ''.join(byte_list)
|
|
76
|
+
|
|
77
|
+
return result
|
|
78
|
+
|
|
79
|
+
|
|
56
80
|
def find_position(target: bytes, file_path: str = None, file_bytes: bytes = None, chunk_size: int = None, starting_position: int = 0) -> int:
|
|
57
81
|
"""
|
|
58
82
|
Find position of the target bytes string in the file.
|
atomicshop/dns.py
CHANGED
|
@@ -77,6 +77,17 @@ def get_default_dns_gateway() -> tuple[bool, list[str]]:
|
|
|
77
77
|
return is_dynamic, dns_servers
|
|
78
78
|
|
|
79
79
|
|
|
80
|
+
def get_default_dns_gateway_with_dns_resolver() -> list[str]:
|
|
81
|
+
"""
|
|
82
|
+
Get the default DNS gateway from the system using dns.resolver.
|
|
83
|
+
:return: tuple(is dynamic boolean, list of DNS server IPv4s).
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
resolver = dns.resolver.Resolver()
|
|
87
|
+
dns_servers = list(resolver.nameservers)
|
|
88
|
+
return dns_servers
|
|
89
|
+
|
|
90
|
+
|
|
80
91
|
def set_connection_dns_gateway_static(
|
|
81
92
|
dns_servers: list[str],
|
|
82
93
|
connection_name: str = None,
|
atomicshop/http_parse.py
CHANGED
|
@@ -2,7 +2,6 @@ from http.server import BaseHTTPRequestHandler
|
|
|
2
2
|
from http.client import HTTPResponse
|
|
3
3
|
import http
|
|
4
4
|
from io import BytesIO
|
|
5
|
-
import socket
|
|
6
5
|
|
|
7
6
|
|
|
8
7
|
class HTTPRequestParse(BaseHTTPRequestHandler):
|
|
@@ -50,8 +49,8 @@ class HTTPRequestParse(BaseHTTPRequestHandler):
|
|
|
50
49
|
"""
|
|
51
50
|
|
|
52
51
|
# noinspection PyMissingConstructor
|
|
53
|
-
def __init__(self,
|
|
54
|
-
self.
|
|
52
|
+
def __init__(self, request_bytes: bytes):
|
|
53
|
+
self.request_bytes: bytes = request_bytes
|
|
55
54
|
|
|
56
55
|
# noinspection PyTypeChecker
|
|
57
56
|
self.rfile = None
|
|
@@ -107,7 +106,7 @@ class HTTPRequestParse(BaseHTTPRequestHandler):
|
|
|
107
106
|
error: str = str()
|
|
108
107
|
info: str = str()
|
|
109
108
|
|
|
110
|
-
self.rfile = BytesIO(self.
|
|
109
|
+
self.rfile = BytesIO(self.request_bytes)
|
|
111
110
|
self.raw_requestline = self.rfile.readline()
|
|
112
111
|
self.error_code = self.error_message = None
|
|
113
112
|
self.parse_request()
|
|
@@ -152,6 +151,7 @@ class FakeSocket:
|
|
|
152
151
|
def __init__(self, response_bytes):
|
|
153
152
|
self._file = BytesIO(response_bytes)
|
|
154
153
|
|
|
154
|
+
# noinspection PyUnusedLocal
|
|
155
155
|
def makefile(self, mode='rb', buffering=-1) -> BytesIO:
|
|
156
156
|
"""
|
|
157
157
|
Mimics the socket's makefile method, returning the BytesIO object.
|
|
@@ -100,15 +100,12 @@ def thread_worker_main(
|
|
|
100
100
|
client_message.protocol = 'HTTP'
|
|
101
101
|
client_message.request_raw_decoded = request_decoded
|
|
102
102
|
print_api(request_parsing_info, logger=network_logger, logger_method='info')
|
|
103
|
-
network_logger.info(f"Method: {request_decoded.command}")
|
|
104
|
-
network_logger.info(f"Path: {request_decoded.path}")
|
|
105
|
-
# If there was error - the request is really HTTP, but there's a problem with its structure.
|
|
103
|
+
network_logger.info(f"Method: {request_decoded.command} | Path: {request_decoded.path}")
|
|
106
104
|
else:
|
|
107
|
-
# client_message.error = request_parsing_error
|
|
108
|
-
print_api(request_parsing_error, logger=network_logger, logger_method='error', color='yellow')
|
|
109
105
|
# It doesn't matter if we have HTTP Parsing error, since the request may not be really HTTP, so it is OK
|
|
110
106
|
# not to log it into statistics.
|
|
111
107
|
# statistics_error_list.append(error_message)
|
|
108
|
+
print_api(request_parsing_error, logger=network_logger, logger_method='error', color='yellow')
|
|
112
109
|
|
|
113
110
|
def finish_thread():
|
|
114
111
|
# At this stage there could be several times that the same socket was used to the service server - we need to
|
|
@@ -204,8 +201,6 @@ def thread_worker_main(
|
|
|
204
201
|
client_message.request_raw_bytes = client_received_raw_data
|
|
205
202
|
|
|
206
203
|
parse_http()
|
|
207
|
-
if client_message.protocol != 'HTTP':
|
|
208
|
-
pass
|
|
209
204
|
|
|
210
205
|
# Custom parser, should parse HTTP body or the whole message if not HTTP.
|
|
211
206
|
parser_instance = parser(client_message)
|
atomicshop/mitm/mitm_main.py
CHANGED
|
@@ -19,6 +19,7 @@ from . import config_static, recs_files
|
|
|
19
19
|
|
|
20
20
|
NETWORK_INTERFACE_IS_DYNAMIC: bool = bool()
|
|
21
21
|
NETWORK_INTERFACE_IPV4_ADDRESS_LIST: list[str] = list()
|
|
22
|
+
IS_SET_DNS_GATEWAY: bool = False
|
|
22
23
|
# noinspection PyTypeChecker
|
|
23
24
|
RECS_PROCESS_INSTANCE: multiprocessing.Process = None
|
|
24
25
|
|
|
@@ -36,7 +37,7 @@ except win_console.NotWindowsConsoleError:
|
|
|
36
37
|
|
|
37
38
|
|
|
38
39
|
def exit_cleanup():
|
|
39
|
-
if permissions.is_admin():
|
|
40
|
+
if permissions.is_admin() and IS_SET_DNS_GATEWAY:
|
|
40
41
|
is_dns_dynamic, current_dns_gateway = dns.get_default_dns_gateway()
|
|
41
42
|
status_string = 'Dynamic' if is_dns_dynamic else 'Static'
|
|
42
43
|
print_api.print_api(f'Current DNS Gateway: {status_string}, {current_dns_gateway}')
|
|
@@ -347,10 +348,14 @@ def mitm_server(config_file_path: str):
|
|
|
347
348
|
dns_gateway_server_list = [base.DEFAULT_IPV4]
|
|
348
349
|
set_dns_gateway = True
|
|
349
350
|
|
|
350
|
-
# Get current network interface state.
|
|
351
|
-
global NETWORK_INTERFACE_IS_DYNAMIC, NETWORK_INTERFACE_IPV4_ADDRESS_LIST
|
|
352
|
-
NETWORK_INTERFACE_IS_DYNAMIC, NETWORK_INTERFACE_IPV4_ADDRESS_LIST = dns.get_default_dns_gateway()
|
|
353
351
|
if set_dns_gateway:
|
|
352
|
+
global IS_SET_DNS_GATEWAY
|
|
353
|
+
IS_SET_DNS_GATEWAY = True
|
|
354
|
+
|
|
355
|
+
# Get current network interface state.
|
|
356
|
+
global NETWORK_INTERFACE_IS_DYNAMIC, NETWORK_INTERFACE_IPV4_ADDRESS_LIST
|
|
357
|
+
NETWORK_INTERFACE_IS_DYNAMIC, NETWORK_INTERFACE_IPV4_ADDRESS_LIST = dns.get_default_dns_gateway()
|
|
358
|
+
|
|
354
359
|
# Set the DNS gateway to the specified one only if the DNS gateway is dynamic, or it is static but different
|
|
355
360
|
# from the one specified in the configuration file.
|
|
356
361
|
if (NETWORK_INTERFACE_IS_DYNAMIC or (not NETWORK_INTERFACE_IS_DYNAMIC and
|
atomicshop/web.py
CHANGED
|
@@ -90,10 +90,13 @@ def get_page_bytes(
|
|
|
90
90
|
|
|
91
91
|
|
|
92
92
|
def get_page_content(
|
|
93
|
-
url: str,
|
|
93
|
+
url: str,
|
|
94
|
+
get_method: str = 'urllib',
|
|
95
|
+
path: str = None,
|
|
94
96
|
playwright_pdf_format: str = 'A4',
|
|
95
97
|
playwright_html_txt_convert_to_bytes: bool = True,
|
|
96
|
-
print_kwargs: dict = None
|
|
98
|
+
print_kwargs: dict = None
|
|
99
|
+
) -> any:
|
|
97
100
|
"""
|
|
98
101
|
Function returns the page content from the given URL.
|
|
99
102
|
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
from typing import Union
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
from websockets.server import ServerProtocol
|
|
5
|
+
from websockets.client import ClientProtocol
|
|
6
|
+
from websockets.extensions.permessage_deflate import ServerPerMessageDeflateFactory, ClientPerMessageDeflateFactory
|
|
7
|
+
from websockets.http11 import Request, Response
|
|
8
|
+
from websockets.frames import Frame, Opcode, Close
|
|
9
|
+
from websockets.uri import parse_uri
|
|
10
|
+
from websockets.exceptions import InvalidHeaderValue
|
|
11
|
+
from websockets.protocol import OPEN
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class WebsocketRequestParse:
|
|
15
|
+
"""
|
|
16
|
+
Parse the websocket request and return the data
|
|
17
|
+
"""
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
enable_logging: bool = False,
|
|
21
|
+
):
|
|
22
|
+
"""
|
|
23
|
+
Initialize the websocket parser.
|
|
24
|
+
|
|
25
|
+
:param enable_logging: bool: Enable logging for the websocket protocol.
|
|
26
|
+
"""
|
|
27
|
+
# noinspection PyTypeChecker
|
|
28
|
+
self.request_bytes: bytes = None
|
|
29
|
+
|
|
30
|
+
# Set up extensions
|
|
31
|
+
permessage_deflate_factory = ServerPerMessageDeflateFactory()
|
|
32
|
+
|
|
33
|
+
# Create the protocol instance
|
|
34
|
+
self.protocol = ServerProtocol(
|
|
35
|
+
extensions=[permessage_deflate_factory],
|
|
36
|
+
)
|
|
37
|
+
# At this state the protocol.state is State.CONNECTING
|
|
38
|
+
|
|
39
|
+
if enable_logging:
|
|
40
|
+
logging.basicConfig(level=logging.DEBUG)
|
|
41
|
+
self.protocol.logger.setLevel(logging.DEBUG)
|
|
42
|
+
|
|
43
|
+
def parse(
|
|
44
|
+
self,
|
|
45
|
+
request_bytes: bytes
|
|
46
|
+
) -> Union[str, bytes, Request]:
|
|
47
|
+
"""
|
|
48
|
+
Parse the websocket request and return the data
|
|
49
|
+
|
|
50
|
+
:param request_bytes: bytes: The raw bytes of the websocket request.
|
|
51
|
+
:return: Request: The parsed request object.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
self.protocol.receive_data(request_bytes)
|
|
55
|
+
events = self.protocol.events_received()
|
|
56
|
+
for event in events:
|
|
57
|
+
if isinstance(event, Request):
|
|
58
|
+
# Accept the handshake.
|
|
59
|
+
# After the response is sent, it means the handshake was successful, the protocol.state is State.OPEN
|
|
60
|
+
# Only after this state we can parse frames.
|
|
61
|
+
response = self.protocol.accept(event)
|
|
62
|
+
self.protocol.send_response(response)
|
|
63
|
+
return event
|
|
64
|
+
elif isinstance(event, Frame):
|
|
65
|
+
frame = event
|
|
66
|
+
if frame.opcode == Opcode.TEXT:
|
|
67
|
+
message = frame.data.decode('utf-8')
|
|
68
|
+
return message
|
|
69
|
+
elif frame.opcode == Opcode.BINARY:
|
|
70
|
+
return frame.data
|
|
71
|
+
|
|
72
|
+
"""
|
|
73
|
+
# Handle control frames, these are here for the future references.
|
|
74
|
+
elif frame.opcode == Opcode.CLOSE:
|
|
75
|
+
close_info = Close.parse(frame.data)
|
|
76
|
+
print(f"Connection closed by client: {close_info.code}, {close_info.reason}")
|
|
77
|
+
# Send a close frame in response if not already sent
|
|
78
|
+
if self.protocol.state == self.protocol.OPEN:
|
|
79
|
+
self.protocol.send_close()
|
|
80
|
+
elif frame.opcode == Opcode.PING:
|
|
81
|
+
# Respond to ping with pong
|
|
82
|
+
self.protocol.send_pong(frame.data)
|
|
83
|
+
elif frame.opcode == Opcode.PONG:
|
|
84
|
+
print("Received pong")
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class WebsocketResponseParse:
|
|
89
|
+
"""
|
|
90
|
+
Parse the websocket response and return the data
|
|
91
|
+
"""
|
|
92
|
+
def __init__(
|
|
93
|
+
self,
|
|
94
|
+
enable_logging: bool = False,
|
|
95
|
+
):
|
|
96
|
+
"""
|
|
97
|
+
Initialize the websocket parser.
|
|
98
|
+
|
|
99
|
+
:param enable_logging: bool: Enable logging for the websocket protocol.
|
|
100
|
+
"""
|
|
101
|
+
# noinspection PyTypeChecker
|
|
102
|
+
self.response_bytes: bytes = None
|
|
103
|
+
|
|
104
|
+
# Set up extensions
|
|
105
|
+
permessage_deflate_factory = ClientPerMessageDeflateFactory()
|
|
106
|
+
|
|
107
|
+
# Parse the WebSocket URI.
|
|
108
|
+
# Since we're parsing the response, we don't need the URI, but the protocol object requires it.
|
|
109
|
+
# So we will just use a dummy URI.
|
|
110
|
+
wsuri = parse_uri('ws://example.com/websocket')
|
|
111
|
+
|
|
112
|
+
# Create the protocol instance
|
|
113
|
+
self.protocol = ClientProtocol(
|
|
114
|
+
wsuri,
|
|
115
|
+
extensions=[permessage_deflate_factory],
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
if enable_logging:
|
|
119
|
+
logging.basicConfig(level=logging.DEBUG)
|
|
120
|
+
# self.protocol.logger.setLevel(logging.DEBUG)
|
|
121
|
+
self.protocol.debug = True
|
|
122
|
+
|
|
123
|
+
# Perform the handshake and emulate the connection and request sending.
|
|
124
|
+
request = self.protocol.connect()
|
|
125
|
+
self.protocol.send_request(request)
|
|
126
|
+
_ = self.protocol.data_to_send()
|
|
127
|
+
# At this state the protocol.state is State.CONNECTING
|
|
128
|
+
|
|
129
|
+
def parse(
|
|
130
|
+
self,
|
|
131
|
+
response_bytes: bytes
|
|
132
|
+
) -> Union[str, bytes, Response]:
|
|
133
|
+
"""
|
|
134
|
+
Parse the websocket response and return the data
|
|
135
|
+
|
|
136
|
+
:param response_bytes: bytes: The raw bytes of the websocket response.
|
|
137
|
+
:return: The parsed response.
|
|
138
|
+
"""
|
|
139
|
+
|
|
140
|
+
self.protocol.receive_data(response_bytes)
|
|
141
|
+
events = self.protocol.events_received()
|
|
142
|
+
for event in events:
|
|
143
|
+
if isinstance(event, Response):
|
|
144
|
+
# Accept the handshake.
|
|
145
|
+
# After the response is sent, it means the handshake was successful, the protocol.state is State.OPEN
|
|
146
|
+
# Only after this state we can parse frames.
|
|
147
|
+
try:
|
|
148
|
+
self.protocol.process_response(event)
|
|
149
|
+
except InvalidHeaderValue as e:
|
|
150
|
+
headers = event.headers
|
|
151
|
+
self.protocol.extensions = self.protocol.process_extensions(headers)
|
|
152
|
+
self.protocol.subprotocol = self.protocol.process_subprotocol(headers)
|
|
153
|
+
self.protocol.state = OPEN
|
|
154
|
+
return event
|
|
155
|
+
elif isinstance(event, Frame):
|
|
156
|
+
frame = event
|
|
157
|
+
if frame.opcode == Opcode.TEXT:
|
|
158
|
+
message = frame.data.decode('utf-8')
|
|
159
|
+
return message
|
|
160
|
+
elif frame.opcode == Opcode.BINARY:
|
|
161
|
+
return frame.data
|
|
162
|
+
|
|
163
|
+
"""
|
|
164
|
+
# Handle control frames, these are here for the future references.
|
|
165
|
+
elif frame.opcode == Opcode.CLOSE:
|
|
166
|
+
close_info = Close.parse(frame.data)
|
|
167
|
+
print(f"Connection closed by client: {close_info.code}, {close_info.reason}")
|
|
168
|
+
# Send a close frame in response if not already sent
|
|
169
|
+
if self.protocol.state == self.protocol.OPEN:
|
|
170
|
+
self.protocol.send_close()
|
|
171
|
+
elif frame.opcode == Opcode.PING:
|
|
172
|
+
# Respond to ping with pong
|
|
173
|
+
self.protocol.send_pong(frame.data)
|
|
174
|
+
elif frame.opcode == Opcode.PONG:
|
|
175
|
+
print("Received pong")
|
|
176
|
+
"""
|
|
@@ -7,7 +7,12 @@ from .. import config_install
|
|
|
7
7
|
|
|
8
8
|
def install_after_restart(
|
|
9
9
|
installation_directory: str,
|
|
10
|
-
install_type: Union[
|
|
10
|
+
install_type: Union[
|
|
11
|
+
None,
|
|
12
|
+
Literal['backend', 'frontend', 'db']] = None,
|
|
13
|
+
log_level: Union[
|
|
14
|
+
None,
|
|
15
|
+
Literal['DEBUG', 'INFO', 'WARNING', 'ERROR']] = None
|
|
11
16
|
):
|
|
12
17
|
"""
|
|
13
18
|
This function will continue the installation the FACT_core after the restart of the computer.
|
|
@@ -20,6 +25,9 @@ def install_after_restart(
|
|
|
20
25
|
--backend: Distributed setup, Install the FACT_core backend.
|
|
21
26
|
--frontend: Distributed setup, Install the FACT_core frontend.
|
|
22
27
|
--db: Distributed setup, Install the FACT_core database.
|
|
28
|
+
:param log_level: string, the log level to use for the installation.
|
|
29
|
+
The same as using the '--log-level' parameter in the 'install.py' script.
|
|
30
|
+
The default is 'INFO' in the 'install.py' script.
|
|
23
31
|
:return:
|
|
24
32
|
"""
|
|
25
33
|
|
|
@@ -28,8 +36,11 @@ def install_after_restart(
|
|
|
28
36
|
if install_type:
|
|
29
37
|
install_command = install_command + ' --' + install_type
|
|
30
38
|
|
|
39
|
+
if log_level:
|
|
40
|
+
install_command = install_command + ' --log-level ' + log_level
|
|
41
|
+
|
|
31
42
|
# Install the FACT_core repo.
|
|
32
43
|
process.execute_with_live_output(cmd=install_command, verbose=True)
|
|
33
44
|
# Remove the FACT_core installation log.
|
|
34
|
-
working_directory_path: str = filesystem.get_working_directory()
|
|
45
|
+
# working_directory_path: str = filesystem.get_working_directory()
|
|
35
46
|
# filesystem.remove_file(str(Path(working_directory_path, config_install.INSTALL_LOG_FILE_NAME)))
|