atomicshop 2.18.6__py3-none-any.whl → 2.18.8__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 CHANGED
@@ -1,4 +1,4 @@
1
1
  """Atomic Basic functions and classes to make developer life easier"""
2
2
 
3
3
  __author__ = "Den Kras"
4
- __version__ = '2.18.6'
4
+ __version__ = '2.18.8'
@@ -107,18 +107,27 @@ def _search_in_archive(
107
107
 
108
108
  # Iterate over each file in the archive.
109
109
  for item_index, item in enumerate(file_info_list):
110
- if item.filename.endswith('/'): # Skip directories
111
- continue
112
-
113
110
  # At this stage we will get the bytes of the archived file, which is an 'item' in the archive.
114
111
  archived_file_bytes = None
115
112
  # If the main archive is zip we will use the 'open' method, if it's 7z we will use the 'read' method.
116
113
  if archive_type == 'zip':
114
+ # Skip directories.
115
+ if item.filename.endswith('/'):
116
+ continue
117
+
117
118
  with arch_obj.open(item) as file_data:
118
119
  archived_file_bytes = file_data.read()
119
120
  elif archive_type == '7z':
121
+ # Skip directories.
122
+ if item.is_directory:
123
+ continue
124
+
125
+ # If 'SevenZipFile.red()' is used once, the second time you need to read it you will need to reset the
126
+ # SevenZipFile object in order to read again:
127
+ # https://py7zr.readthedocs.io/en/latest/api.html#py7zr.SevenZipFile.read
120
128
  file_dict = arch_obj.read([item.filename])
121
129
  archived_file_bytes = file_dict[item.filename].read()
130
+ arch_obj.reset()
122
131
 
123
132
  # After we get the file bytes we will check if the file matches the callback functions.
124
133
  callback_matched = False
@@ -129,7 +138,7 @@ def _search_in_archive(
129
138
  if callback_matched:
130
139
  _handle_file_extraction(item, extract_file_to_path, archived_file_bytes)
131
140
  else:
132
- if recursive and (zips.is_zip_zipfile(archived_file_bytes) or sevenzs.is_7z(archived_file_bytes)):
141
+ if recursive and (zips.is_zip_zipfile(archived_file_bytes) or sevenzs.is_7z_magic_number(archived_file_bytes)):
133
142
  _search_archive_content(
134
143
  archived_file_bytes, file_names, results, found_set, case_sensitive, return_first_only,
135
144
  recursive, callback_functions, extract_file_to_path)
@@ -187,7 +196,7 @@ def _get_archive_type(file_object) -> Union[str, None]:
187
196
 
188
197
  if zips.is_zip_zipfile(file_object):
189
198
  return 'zip'
190
- elif sevenzs.is_7z(file_object):
199
+ elif sevenzs.is_7z_magic_number(file_object):
191
200
  return '7z'
192
201
  else:
193
202
  raise UnknownArchiveType(f"{file_object[:10]} is not a known archive type.")
@@ -4,8 +4,50 @@ from typing import Union
4
4
  import py7zr
5
5
 
6
6
 
7
- def is_7z(file_object: Union[str, bytes]) -> bool:
7
+ def is_7z_magic_number(
8
+ file_object: Union[str, bytes]
9
+ ) -> bool:
8
10
  """
11
+ Function checks if the file is a 7z file, by checking the magic number.
12
+
13
+ :param file_object: can be two types:
14
+ string, full path to the file.
15
+ bytes or BytesIO, the bytes of the file.
16
+ :return: boolean.
17
+ """
18
+
19
+ if isinstance(file_object, str):
20
+ with open(file_object, 'rb') as file:
21
+ data = file.read(6)
22
+ elif isinstance(file_object, bytes):
23
+ data = file_object
24
+ else:
25
+ raise TypeError(f'The file_object must be a string or bytes, not {type(file_object)}')
26
+
27
+ # Check if the data is at least 6 bytes long
28
+ if len(data) < 6:
29
+ return False
30
+
31
+ # 7z file signature (magic number)
32
+ # The signature is '7z' followed by 'BCAF271C'
33
+ seven_z_signature = b'7z\xBC\xAF\x27\x1C'
34
+
35
+ # Compare the first 6 bytes of the data with the 7z signature
36
+ result = data.startswith(seven_z_signature)
37
+
38
+ return result
39
+
40
+
41
+ def _is_7z(file_object: Union[str, bytes]) -> bool:
42
+ """
43
+ THIS IS ONLY FOR THE REFERENCE.
44
+
45
+ Used to use this function since it raised 'py7zr.Bad7zFile' if the file was not a 7z file.
46
+ The problem that 'SevenZipFile.testzip()' checks archived files CRCs and returns the first bad file:
47
+ https://py7zr.readthedocs.io/en/latest/api.html#py7zr.SevenZipFile.testzip
48
+ The problem is when the file IS 7z file, but there can be other problems with the file, it can raise a RuntimeError.
49
+ So, better use the 'is_7z_magic_number' function.
50
+
9
51
  Function checks if the file is a 7z file.
10
52
  :param file_object: can be two types:
11
53
  string, full path to the file.
@@ -29,16 +71,3 @@ def is_7z(file_object: Union[str, bytes]) -> bool:
29
71
  except OSError as e:
30
72
  if e.args[0] == 22:
31
73
  return False
32
-
33
-
34
- def _is_7z_magic_number(data):
35
- # Check if the data is at least 6 bytes long
36
- if len(data) < 6:
37
- return False
38
-
39
- # 7z file signature (magic number)
40
- # The signature is '7z' followed by 'BCAF271C'
41
- seven_z_signature = b'7z\xBC\xAF\x27\x1C'
42
-
43
- # Compare the first 6 bytes of the data with the 7z signature
44
- return data.startswith(seven_z_signature)
atomicshop/filesystem.py CHANGED
@@ -1280,70 +1280,20 @@ def create_dict_of_paths_list(list_of_paths: list) -> list:
1280
1280
 
1281
1281
  def create_dict_of_path(
1282
1282
  path: str,
1283
- # structure_dict: dict,
1284
1283
  structure_list: list,
1285
- add_data_to_entry: any = None,
1286
- add_data_key: str = 'addon',
1287
- parent_entry: str = None
1284
+ add_data_to_entry: list[dict[str, any]] = None
1288
1285
  ):
1289
1286
  """
1290
1287
  The function receives a path and a list, and adds the path to the list.
1291
-
1292
1288
  Check the working example from 'create_dict_of_paths_list' function.
1293
1289
 
1294
1290
  :param path: string, path.
1295
1291
  :param structure_list: list to add the path to.
1296
- :param add_data_to_entry: any, data to add to the entry.
1297
- :param add_data_key: string, key to add the data to.
1298
- :param parent_entry: string, for internal use to pass the current parent entry.
1292
+ :param add_data_to_entry: a list of dicts with data to add to the entry.
1293
+ dict format: {key: data}
1299
1294
  :return:
1300
1295
  """
1301
1296
 
1302
- # # Normalize path for cross-platform compatibility
1303
- # normalized_path = path.replace("\\", "/")
1304
- # parts = normalized_path.strip("/").split("/")
1305
- # current_level = structure_dict
1306
- #
1307
- # for part in parts[:-1]: # Iterate through the directories
1308
- # # If the part is not already a key in the current level of the structure, add it
1309
- # if part not in current_level:
1310
- # current_level[part] = {}
1311
- # current_level = current_level[part]
1312
- #
1313
- # # Create the entry for the file with additional data
1314
- # file_entry = {"entry": parts[-1], add_data_key: add_data_to_entry}
1315
- #
1316
- # # We're adding file entries under numeric keys.
1317
- # if isinstance(current_level, dict) and all(isinstance(key, int) for key in current_level.keys()):
1318
- # current_level[len(current_level)] = file_entry
1319
- # else:
1320
- # # Handle cases where there's a mix of numeric keys and directory names
1321
- # # Find the next available numeric key
1322
- # next_key = max([key if isinstance(key, int) else -1 for key in current_level.keys()], default=-1) + 1
1323
- # current_level[next_key] = file_entry
1324
-
1325
- # entries_key_name = "__entries__"
1326
- #
1327
- # # Normalize path for cross-platform compatibility
1328
- # normalized_path = path.replace("\\", "/")
1329
- # parts = normalized_path.strip("/").split("/")
1330
- # current_level = structure_dict
1331
- #
1332
- # for part in parts[:-1]: # Navigate through or create directory structure
1333
- # if part not in current_level:
1334
- # current_level[part] = {}
1335
- # current_level = current_level[part]
1336
- #
1337
- # # Create the entry for the file with additional data
1338
- # file_entry = {"entry": parts[-1], add_data_key: add_data_to_entry}
1339
- #
1340
- # # If the current level (final directory) does not have an "entries" key for files, create it
1341
- # if entries_key_name not in current_level:
1342
- # current_level[entries_key_name] = []
1343
- #
1344
- # # Append the file entry to the list associated with the "entries" key
1345
- # current_level[entries_key_name].append(file_entry)
1346
-
1347
1297
  # Normalize path for cross-platform compatibility
1348
1298
  normalized_path = path.replace("\\", "/")
1349
1299
  parts = normalized_path.strip("/").split("/")
@@ -1351,29 +1301,36 @@ def create_dict_of_path(
1351
1301
  current_level = structure_list
1352
1302
 
1353
1303
  for i, part in enumerate(parts):
1354
- # Determine if this is the last part (a file)
1304
+ # Determine if this is the last part (a file or final component of the path)
1355
1305
  is_last_part = (i == len(parts) - 1)
1356
1306
 
1357
1307
  # Try to find an existing entry for this part
1358
- # noinspection PyTypeChecker
1359
1308
  existing_entry = next((item for item in current_level if item["entry"] == part), None)
1360
1309
 
1361
1310
  if existing_entry is None:
1362
- # For the last part, add the additional data; for directories, just create the structure
1363
- if is_last_part:
1364
- new_entry = {"entry": part, add_data_key: add_data_to_entry, "included": []}
1365
- else:
1366
- new_entry = {"entry": part, "included": []}
1311
+ # Create a new entry
1312
+ new_entry = {"entry": part, "included": []}
1313
+
1314
+ # Add additional data if it's the last part
1315
+ if is_last_part and add_data_to_entry:
1316
+ for data_dict in add_data_to_entry:
1317
+ new_entry.update(data_dict)
1367
1318
 
1368
1319
  current_level.append(new_entry)
1320
+
1369
1321
  # Only update current_level if it's not the last part
1370
1322
  if not is_last_part:
1371
1323
  current_level = new_entry["included"]
1372
1324
  else:
1373
- # If it's not the last part and the entry exists, navigate deeper
1325
+ # If the entry exists and it's not the last part, navigate deeper
1374
1326
  if not is_last_part:
1375
1327
  current_level = existing_entry["included"]
1376
1328
 
1329
+ # If the entry exists and it's the last part, update with additional data
1330
+ if is_last_part and add_data_to_entry:
1331
+ for data_dict in add_data_to_entry:
1332
+ existing_entry.update(data_dict)
1333
+
1377
1334
 
1378
1335
  def list_open_files_in_directory(directory):
1379
1336
  """
@@ -2,7 +2,12 @@ import ipaddress
2
2
  from typing import Union, Literal
3
3
 
4
4
 
5
- def is_ip_address(string_value: str, ip_type: Union[Literal['ipv4', 'ipv6'], None] = None) -> bool:
5
+ def is_ip_address(
6
+ string_value: str,
7
+ ip_type: Union[
8
+ Literal['ipv4', 'ipv6'],
9
+ None] = None
10
+ ) -> bool:
6
11
  """
7
12
  The function checks if the string is an IPv4 or IPv6 address.
8
13
 
@@ -1,6 +1,7 @@
1
1
  from datetime import datetime
2
2
  import threading
3
3
  import queue
4
+ import copy
4
5
 
5
6
  from ..wrappers.socketw import receiver, sender, socket_client, base
6
7
  from .. import websocket_parse
@@ -297,7 +298,10 @@ def thread_worker_main(
297
298
  if not isinstance(client_message, ClientMessage):
298
299
  return
299
300
 
300
- raw_responses: list[bytes] = create_responder_response(client_message)
301
+ if client_message.action == 'service_connect':
302
+ raw_responses: list[bytes] = responder.create_connect_response(client_message)
303
+ else:
304
+ raw_responses: list[bytes] = create_responder_response(client_message)
301
305
 
302
306
  is_socket_closed: bool = False
303
307
  for response_raw_bytes in raw_responses:
@@ -383,14 +387,14 @@ def thread_worker_main(
383
387
  # the close on the opposite socket.
384
388
  record_and_statistics_write(client_message)
385
389
 
386
- # if is_socket_closed:
387
- # exception_or_close_in_receiving_thread = True
388
- # finish_thread()
389
- # return
390
+ if is_socket_closed:
391
+ exception_or_close_in_receiving_thread = True
392
+ finish_thread()
393
+ return
390
394
 
391
395
  # If we're in response mode, execute responder.
392
396
  if config_static.TCPServer.server_response_mode:
393
- responder_queue.put(client_message)
397
+ responder_queue.put(copy.deepcopy(client_message))
394
398
  else:
395
399
  # if side == 'Client':
396
400
  # raise NotImplementedError
@@ -543,7 +547,7 @@ def thread_worker_main(
543
547
  client_message_connection.timestamp = datetime.now()
544
548
  client_message_connection.action = 'service_connect'
545
549
  client_message_connection.info = 'Server Response Mode'
546
- responder_queue.put(client_message_connection)
550
+ responder_queue.put(copy.deepcopy(client_message_connection))
547
551
 
548
552
  if connection_error:
549
553
  client_message_connection.timestamp = datetime.now()
@@ -552,30 +556,32 @@ def thread_worker_main(
552
556
  record_and_statistics_write(client_message_connection)
553
557
  else:
554
558
  client_exception_queue: queue.Queue = queue.Queue()
555
- service_exception_queue: queue.Queue = queue.Queue()
556
-
557
559
  client_thread = threading.Thread(
558
560
  target=receive_send_start, args=(client_socket, service_socket_instance, client_exception_queue),
559
561
  name=f"Thread-{thread_id}-Client")
560
562
  client_thread.daemon = True
561
563
  client_thread.start()
562
564
 
563
- service_thread = threading.Thread(
564
- target=receive_send_start, args=(service_socket_instance, client_socket, service_exception_queue),
565
- name=f"Thread-{thread_id}-Service")
566
- service_thread.daemon = True
567
- service_thread.start()
565
+ if not config_static.TCPServer.server_response_mode:
566
+ service_exception_queue: queue.Queue = queue.Queue()
567
+ service_thread = threading.Thread(
568
+ target=receive_send_start, args=(service_socket_instance, client_socket, service_exception_queue),
569
+ name=f"Thread-{thread_id}-Service")
570
+ service_thread.daemon = True
571
+ service_thread.start()
568
572
 
569
573
  client_thread.join()
570
- service_thread.join()
571
574
  if config_static.TCPServer.server_response_mode:
572
575
  responder_thread.join()
576
+ else:
577
+ service_thread.join()
573
578
 
574
579
  # If there was an exception in any of the threads, then we'll raise it here.
575
580
  if not client_exception_queue.empty():
576
581
  raise client_exception_queue.get()
577
- if not service_exception_queue.empty():
578
- raise service_exception_queue.get()
582
+ if not config_static.TCPServer.server_response_mode:
583
+ if not service_exception_queue.empty():
584
+ raise service_exception_queue.get()
579
585
 
580
586
  finish_thread()
581
587
  except Exception as e:
@@ -103,8 +103,13 @@ class ResponderParent:
103
103
 
104
104
  return parameter_value
105
105
 
106
- def build_byte_response_and_fill_lists(
107
- self, http_version: str, status_code: int, headers: dict, body: bytes, client_message: ClientMessage):
106
+ def build_byte_response(
107
+ self,
108
+ http_version: str,
109
+ status_code: int,
110
+ headers: dict,
111
+ body: bytes
112
+ ) -> bytes:
108
113
  """
109
114
  Create genuine response from input parameters.
110
115
  ---------------
@@ -145,8 +150,7 @@ class ResponderParent:
145
150
  :param status_code: HTTP Status Code of Response in HTTP Status line.
146
151
  :param headers: HTTP Headers of Response.
147
152
  :param body: HTTP body data of Response, bytes.
148
- :param client_message: client message class.
149
- :return:
153
+ :return: bytes of the response.
150
154
  """
151
155
 
152
156
  try:
@@ -174,7 +178,6 @@ class ResponderParent:
174
178
  print_api(message, error_type=True, logger=self.logger, logger_method='error', color='red')
175
179
 
176
180
  response_raw_bytes = b''
177
- pass
178
181
 
179
182
  # Parsing the response we created.
180
183
  response_parse_test = HTTPResponseParse(response_raw_bytes)
@@ -182,73 +185,15 @@ class ResponderParent:
182
185
  if response_parse_test.error:
183
186
  self.logger.error(response_parse_test.error)
184
187
  response_raw_bytes = b''
185
- response_decoded = None
186
188
  else:
187
189
  self.logger.info("Created Valid Byte Response.")
188
- response_decoded = response_parse_test.response_raw_decoded
189
-
190
- # Add 'response_raw_bytes' and 'response_decoded' to appropriate response lists in 'class_message'.
191
- self.add_response_elements_to_lists(client_message, response_raw_bytes, response_decoded)
192
190
 
193
- @staticmethod
194
- def add_response_elements_to_lists(class_client_message: ClientMessage, byte_response, decoded_response):
195
- """
196
- Function just adds the byte response to the 'response_list_of_raw_bytes'.
197
- :param class_client_message:
198
- :param byte_response:
199
- :param decoded_response:
200
- :return:
201
- """
191
+ return response_raw_bytes
202
192
 
203
- class_client_message.response_list_of_raw_bytes.append(byte_response)
204
- class_client_message.response_list_of_raw_decoded.append(decoded_response)
193
+ def create_connect_response(self, class_client_message: ClientMessage):
194
+ """ This function should be overridden in the child class. """
195
+ pass
205
196
 
206
197
  def create_response(self, class_client_message: ClientMessage):
207
- # noinspection GrazieInspection
208
- """
209
- Function to create Response based on ClientMessage and its Request.
210
-
211
- :param class_client_message: contains request and other parameters to help creating response.
212
- :return: "class_client_message.response_list_of_raw_bytes" is populated with list of responses in bytes.
213
- -----------------------------------
214
- # Remember that 'response_list_of_raw_bytes' is a list object that will contain byte response/s.
215
-
216
- # Example of creating 'response_list_of_raw_bytes' using 'build_byte_response' function:
217
- class_client_message.response_list_of_raw_bytes.append(
218
- self.build_byte_response(
219
- http_version=class_client_message.request_raw_decoded.request_version,
220
- status_code=200,
221
- headers=response_headers,
222
- body=b''
223
- )
224
- )
225
-
226
- # Or you can use the 'add_byte_response_to_response_list' function that will do it for you:
227
- byte_response = self.build_byte_response(
228
- http_version=class_client_message.request_raw_decoded.request_version,
229
- status_code=200,
230
- headers=response_headers,
231
- body=b''
232
- )
233
- self.add_byte_response_to_response_list(class_client_message, byte_response)
234
- -----------------------------------
235
- # Example of extracting variables from URL PATH based on custom PATH TEMPLATE:
236
- # (more examples in 'self.extract_variables_from_path_template' function description)
237
- template_path: str = "/hithere/<variable1>/else/<variable2>/tested/"
238
- path_variables: dict = extract_variables_from_path_template(
239
- path=class_client_message.request_raw_decoded.path,
240
- template_path=template_path
241
- )
242
- -----------------------------------
243
- # Example of extracting value from URL PATH parameters after question mark:
244
- parameter_value = extract_value_from_path_parameter(
245
- path=class_client_message.request_raw_decoded.path,
246
- parameter='test_id'
247
- )
248
- """
249
-
250
- byte_response = None
251
- decoded_response = None
252
- # class_client_message.response_list_of_raw_bytes.append(byte_response)
253
- self.add_response_elements_to_lists(class_client_message, byte_response, decoded_response)
254
- self.logger.info(f"Response: {class_client_message.response_list_of_raw_bytes}")
198
+ """ This function should be overridden in the child class. """
199
+ pass
@@ -1,6 +1,7 @@
1
1
  # These are specified with hardcoded paths instead of relative, because 'create_module_template.py' copies the content.
2
2
  from atomicshop.mitm.engines.__parent.responder___parent import ResponderParent
3
3
  from atomicshop.mitm.shared_functions import create_custom_logger
4
+ from atomicshop.mitm.message import ClientMessage
4
5
 
5
6
  """
6
7
  import time
@@ -22,6 +23,63 @@ class ResponderGeneral(ResponderParent):
22
23
 
23
24
  self.logger = create_custom_logger()
24
25
 
26
+ def create_response(self, class_client_message: ClientMessage):
27
+ # noinspection GrazieInspection
28
+ """
29
+ Function to create Response based on ClientMessage and its Request.
30
+
31
+ :param class_client_message: contains request and other parameters to help creating response.
32
+ :return: list of responses in bytes.
33
+ -----------------------------------
34
+
35
+ # Example of creating list of bytes using 'build_byte_response' function:
36
+ result_list: list[bytes] = list()
37
+ result_list.append(
38
+ self.build_byte_response(
39
+ http_version=class_client_message.request_raw_decoded.request_version,
40
+ status_code=200,
41
+ headers=response_headers,
42
+ body=b''
43
+ )
44
+ )
45
+
46
+ return result_list
47
+ -----------------------------------
48
+ # Example of extracting variables from URL PATH based on custom PATH TEMPLATE:
49
+ # (more examples in 'self.extract_variables_from_path_template' function description)
50
+ template_path: str = "/hithere/<variable1>/else/<variable2>/tested/"
51
+ path_variables: dict = extract_variables_from_path_template(
52
+ path=class_client_message.request_raw_decoded.path,
53
+ template_path=template_path
54
+ )
55
+ -----------------------------------
56
+ # Example of extracting value from URL PATH parameters after question mark:
57
+ parameter_value = extract_value_from_path_parameter(
58
+ path=class_client_message.request_raw_decoded.path,
59
+ parameter='test_id'
60
+ )
61
+ """
62
+
63
+ # byte_response: bytes = b''
64
+ # self.logger.info(f"Response: {byte_response}")
65
+
66
+ response_bytes_list: list[bytes] = list()
67
+ # response_bytes_list.append(byte_response)
68
+ return response_bytes_list
69
+
70
+ def create_connect_response(self, class_client_message: ClientMessage):
71
+ """
72
+ This is almost the same as 'create_response' function, but it's used only when the client connects and before
73
+ sending any data.
74
+ """
75
+
76
+ # byte_response: bytes = b''
77
+ # self.logger.info(f"Response: {byte_response}")
78
+
79
+ response_bytes_list: list[bytes] = list()
80
+ # response_bytes_list.append(byte_response)
81
+ return response_bytes_list
82
+
25
83
  # ==================================================================================================================
26
84
  # Uncomment this section in order to begin building custom responder.
27
85
  # @staticmethod
@@ -89,7 +147,7 @@ class ResponderGeneral(ResponderParent):
89
147
  # # === Building Headers. ===========================
90
148
  # # Response Date example: 'Tue, 08 Nov 2022 14:23: 00 GMT'
91
149
  # resp_headers = {
92
- # 'Date': self.get_current_formatted_time(),
150
+ # 'Date': self.get_current_formatted_time_http(),
93
151
  # 'Content-Length': str(len(resp_body)),
94
152
  # }
95
153
  #
@@ -109,7 +167,7 @@ class ResponderGeneral(ResponderParent):
109
167
  # # === Building Headers. ===========================
110
168
  # # Response Date example: 'Tue, 08 Nov 2022 14:23: 00 GMT'
111
169
  # resp_headers = {
112
- # 'Date': self.get_current_formatted_time(),
170
+ # 'Date': self.get_current_formatted_time_http(),
113
171
  # 'Content-Length': str(len(resp_body)),
114
172
  # 'Connection': 'keep-alive'
115
173
  # }
@@ -118,10 +176,10 @@ class ResponderGeneral(ResponderParent):
118
176
  #
119
177
  # def create_response(self, class_client_message: ClientMessage):
120
178
  # # Arranging important request entries to appropriate variables.
121
- # req_path = class_client_message.request_raw_decoded.path
122
- # req_command = class_client_message.request_raw_decoded.command
123
- # req_headers = class_client_message.request_raw_decoded.headers
124
- # req_body = class_client_message.request_raw_decoded.body
179
+ # req_path = class_client_message.request_auto_parsed.path
180
+ # req_command = class_client_message.request_auto_parsed.command
181
+ # req_headers = class_client_message.request_auto_parsed.headers
182
+ # req_body = class_client_message.request_auto_parsed.body
125
183
  #
126
184
  # # ====================================
127
185
  # # Case specific.
@@ -144,10 +202,12 @@ class ResponderGeneral(ResponderParent):
144
202
  #
145
203
  # # ==============================================================================
146
204
  # # === Building byte response. ==================================================
147
- # self.build_byte_response(
148
- # http_version=class_client_message.request_raw_decoded.request_version,
205
+ # byte_response = self.build_byte_response(
206
+ # http_version=class_client_message.request_auto_parsed.request_version,
149
207
  # status_code=resp_status_code,
150
208
  # headers=resp_headers,
151
- # body=resp_body_bytes,
152
- # client_message=class_client_message
209
+ # body=resp_body_bytes
153
210
  # )
211
+ #
212
+ # result_response_list: list[bytes] = [byte_response]
213
+ # return result_response_list
atomicshop/process.py CHANGED
@@ -82,7 +82,18 @@ def execute_with_live_output(
82
82
  :return: Boolean, If execution was successful, return True, if not - False.
83
83
  """
84
84
 
85
- cmd = _execution_parameters_processing(cmd, wsl)
85
+ if isinstance(cmd, str):
86
+ shell = True
87
+ elif isinstance(cmd, list):
88
+ shell = False
89
+ else:
90
+ raise TypeError(f'cmd must be a string or list, not {type(cmd)}')
91
+
92
+ if wsl:
93
+ if isinstance(cmd, str):
94
+ cmd = 'wsl ' + cmd
95
+ elif isinstance(cmd, list):
96
+ cmd = ['wsl'] + cmd
86
97
 
87
98
  # Needed imports:
88
99
  # from subprocess import Popen, PIPE, STDOUT
@@ -103,7 +114,7 @@ def execute_with_live_output(
103
114
  # The buffer size is system-dependent and usually chosen by the underlying implementation to optimize performance.
104
115
  # # bufsize=0: This means no buffering.
105
116
  # The I/O is unbuffered, and data is written or read from the stream immediately.
106
- with subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=1, text=True) as process:
117
+ with subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, bufsize=1, text=True, shell=shell) as process:
107
118
  # We'll count the number of lines from 'process.stdout'.
108
119
  counter: int = 0
109
120
  # And also get list of all the lines.
@@ -698,6 +698,10 @@ def find(
698
698
 
699
699
  Example for searching for a value that starts with 'test':
700
700
  filter_query = {'field_name': {'$regex': '^test'}}
701
+
702
+ If you need to escape the string for regex special characters you will typically use:
703
+ re.escape(test)
704
+ If you're string contains characters like parentheses "()", you will need to escape them.
701
705
  $options: The options for the regex search.
702
706
  'i': case-insensitive search.
703
707
  Example for case-insensitive search:
@@ -170,10 +170,24 @@ def fetch_urls_content_in_threads(
170
170
  return contents
171
171
 
172
172
 
173
- def _fetch_content(url, number_of_characters_per_link):
173
+ def fetch_urls_content(
174
+ urls: list[str],
175
+ number_of_characters_per_link: int
176
+ ) -> list[str]:
177
+ """ The function to fetch all URLs not concurrently without using threads """
178
+ contents = []
179
+
180
+ for url in urls:
181
+ data = _fetch_content(url, number_of_characters_per_link)
182
+ contents.append(data)
183
+
184
+ return contents
185
+
186
+
187
+ def _fetch_content(url, number_of_characters_per_link, headless: bool = True):
174
188
  """ Function to fetch content from a single URL using the synchronous Playwright API """
175
189
  with sync_playwright() as p:
176
- browser = p.chromium.launch(headless=True)
190
+ browser = p.chromium.launch(headless=headless)
177
191
  page = browser.new_page()
178
192
  page.goto(url)
179
193
 
@@ -67,6 +67,8 @@ def get_wmi_network_configuration(
67
67
  current_adapter = None
68
68
  if use_default_interface:
69
69
  default_connection_name_dict: dict = networks.get_default_connection_name()
70
+ if not default_connection_name_dict:
71
+ raise NetworkAdapterNotFoundError("Default network adapter not found.")
70
72
  # Get the first key from the dictionary.
71
73
  connection_name: str = list(default_connection_name_dict.keys())[0]
72
74
 
@@ -142,6 +142,7 @@ def get_default_dns_gateway() -> tuple[bool, list[str]]:
142
142
  Get the default DNS gateway from the Windows registry.
143
143
 
144
144
  :return: tuple(is dynamic boolean, list of DNS server IPv4s).
145
+ If nothing found will return (None, None).
145
146
  """
146
147
 
147
148
  def get_current_interface_status(current_interface_settings: dict) -> tuple[bool, list[str]]:
@@ -199,5 +200,8 @@ def get_default_dns_gateway() -> tuple[bool, list[str]]:
199
200
 
200
201
  break
201
202
 
203
+ if not function_result:
204
+ function_result = (None, None)
205
+
202
206
  # noinspection PyTypeChecker
203
207
  return function_result
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: atomicshop
3
- Version: 2.18.6
3
+ Version: 2.18.8
4
4
  Summary: Atomic functions and classes to make developer life easier
5
5
  Author: Denis Kras
6
6
  License: MIT License
@@ -1,4 +1,4 @@
1
- atomicshop/__init__.py,sha256=MFXW2AXFnL4Ju9Vx1r8oICO19rBqtQx1mgjqa83ULSM,123
1
+ atomicshop/__init__.py,sha256=4FSjqwMFkYpX_2ecHcCr9_kKJr3wxoGRDCSyZgRd6Dw,123
2
2
  atomicshop/_basics_temp.py,sha256=6cu2dd6r2dLrd1BRNcVDKTHlsHs_26Gpw8QS6v32lQ0,3699
3
3
  atomicshop/_create_pdf_demo.py,sha256=Yi-PGZuMg0RKvQmLqVeLIZYadqEZwUm-4A9JxBl_vYA,3713
4
4
  atomicshop/_patch_import.py,sha256=ENp55sKVJ0e6-4lBvZnpz9PQCt3Otbur7F6aXDlyje4,6334
@@ -14,19 +14,19 @@ atomicshop/dns.py,sha256=5Gimq_WY2arqg7BeGmR7P--fGfnH0Dsh8lrOt_H0jRY,6817
14
14
  atomicshop/domains.py,sha256=Rxu6JhhMqFZRcoFs69IoEd1PtYca0lMCG6F1AomP7z4,3197
15
15
  atomicshop/emails.py,sha256=I0KyODQpIMEsNRi9YWSOL8EUPBiWyon3HRdIuSj3AEU,1410
16
16
  atomicshop/file_types.py,sha256=-0jzQMRlmU1AP9DARjk-HJm1tVE22E6ngP2mRblyEjY,763
17
- atomicshop/filesystem.py,sha256=C5GaGmpcGTh11Bd2plZxJmJQVMOZNcZf35mqel2PUxo,60314
17
+ atomicshop/filesystem.py,sha256=8Y5xLaTutQtEjWPxIwH_H88laN7plEMp2r4OLHk-3xc,58259
18
18
  atomicshop/functions.py,sha256=pK8hoCE9z61PtWCxQJsda7YAphrLH1wxU5x-1QJP-sY,499
19
19
  atomicshop/get_process_list.py,sha256=8cxb7gKe9sl4R6H2yMi8J6oe-RkonTvCdKjRFqi-Fs4,6075
20
20
  atomicshop/get_process_name_cmd_dll.py,sha256=CtaSp3mgxxJKCCVW8BLx6BJNx4giCklU_T7USiCEwfc,5162
21
21
  atomicshop/hashing.py,sha256=Le8qGFyt3_wX-zGTeQShz7L2HL_b6nVv9PnawjglyHo,3474
22
22
  atomicshop/http_parse.py,sha256=1Tna9YbOM0rE3t6i_M-klBlwd1KNSA9skA_BqKGXDFc,11861
23
23
  atomicshop/inspect_wrapper.py,sha256=sGRVQhrJovNygHTydqJj0hxES-aB2Eg9KbIk3G31apw,11429
24
- atomicshop/ip_addresses.py,sha256=Hvi4TumEFoTEpKWaq5WNF-YzcRzt24IxmNgv-Mgax1s,1190
24
+ atomicshop/ip_addresses.py,sha256=penRFeJ1-LDVTko4Q0EwK4JiN5cU-KzCBR2VXg9qbUY,1238
25
25
  atomicshop/keyboard_press.py,sha256=1W5kRtOB75fulVx-uF2yarBhW0_IzdI1k73AnvXstk0,452
26
26
  atomicshop/on_exit.py,sha256=Rpg2SaF0aginuO7JYwA49YJYnS8F6K2jUqhjH65WzuU,6889
27
27
  atomicshop/pbtkmultifile_argparse.py,sha256=aEk8nhvoQVu-xyfZosK3ma17CwIgOjzO1erXXdjwtS4,4574
28
28
  atomicshop/print_api.py,sha256=q9dAQCASk3pHp_PtYIpr6iZmRcW_lvrV_gPPNwTMRsw,11152
29
- atomicshop/process.py,sha256=PeLvyixXaCfftdUF3oMbohI1L4MdLtvQVDx2V1Tz_Rk,16662
29
+ atomicshop/process.py,sha256=dmje2YIDPVM8zS38ylAqyOhDBXk6ay_N1xeewKdEIX4,16966
30
30
  atomicshop/python_file_patcher.py,sha256=-uhbUX-um5k-If_XXuOfCr8wMzZ3QE6h9N8xGWw6W_o,5486
31
31
  atomicshop/python_functions.py,sha256=BPZ3sv5DgQs6Xrl3nIMdPABRpgrau3XSrsnDIz-LEwY,6175
32
32
  atomicshop/question_answer_engine.py,sha256=7nM6kGDSFjQNi87b87-kP9lYM0vTjBHn1rEQGNAfdGA,825
@@ -77,9 +77,9 @@ atomicshop/addons/process_list/compiled/Win10x64/process_list.exp,sha256=cbvukIT
77
77
  atomicshop/addons/process_list/compiled/Win10x64/process_list.lib,sha256=T2Ncs0MwKlAaCq8UKFMvfQAfcJdDx-nWiHVBfglrLIU,2112
78
78
  atomicshop/archiver/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
79
79
  atomicshop/archiver/_search_in_zip.py,sha256=dd8qFSvIhcKmtnPj_uYNJFPmMwZp4tZys0kKgTw_ACw,8385
80
- atomicshop/archiver/search_in_archive.py,sha256=tCbYZZ53oJrdjatj8_AYre6AZujuii4wRF42PH9falY,11865
80
+ atomicshop/archiver/search_in_archive.py,sha256=EmWif1EmIy-IpOLgcj-y-Sqrh2M3MtLN94aNQzUhrrQ,12300
81
81
  atomicshop/archiver/sevenz_app_w.py,sha256=BWcJb4f7jZEiETDBKyNLE0f5YLFPQx6B91_ObEIXWf8,3007
82
- atomicshop/archiver/sevenzs.py,sha256=5i_C50-deC1Cz_GQdMGofV2jeMPbbGWAvih-QnA72cg,1370
82
+ atomicshop/archiver/sevenzs.py,sha256=b9rI-nF36ZNawwKsPWOgsnm0p-jYDfD1NYV3eA8LoQ0,2491
83
83
  atomicshop/archiver/shutils.py,sha256=BomnK7zT-nQXA1z0i2R2aTv8eu88wPx7tf2HtOdbmEc,1280
84
84
  atomicshop/archiver/zips.py,sha256=0Z_1MWs7YRiCBVpyaG8llnzRguHSO4R51KDMN3FJZt8,16984
85
85
  atomicshop/basics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -127,7 +127,7 @@ atomicshop/file_io/xmls.py,sha256=zh3SuK-dNaFq2NDNhx6ivcf4GYCfGM8M10PcEwDSpxk,21
127
127
  atomicshop/mitm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
128
128
  atomicshop/mitm/config_static.py,sha256=HIzxyMEj7DZksYvJsN5VuKpB-_HSVvuR6U59ztS9gi0,7871
129
129
  atomicshop/mitm/config_toml_editor.py,sha256=2p1CMcktWRR_NW-SmyDwylu63ad5e0-w1QPMa8ZLDBw,1635
130
- atomicshop/mitm/connection_thread_worker.py,sha256=Of-QmuIbUO8Qd1N_BXdGQk2TnPpZd2z-Pxrj0wyq9S8,26758
130
+ atomicshop/mitm/connection_thread_worker.py,sha256=9IWN4LP5ZoYqVJTpoIhaOUiusn6kXUbj7_g4kXOKFIU,27169
131
131
  atomicshop/mitm/import_config.py,sha256=0Ij14aISTllTOiWYJpIUMOWobQqGofD6uafui5uWllE,9272
132
132
  atomicshop/mitm/initialize_engines.py,sha256=NWz0yBErSrYBn0xWkJDBcHStBJ-kcsv9VtorcSP9x5M,8258
133
133
  atomicshop/mitm/message.py,sha256=mNo4Lphr_Jo6IlNX5mPJzABpogWGkjOhwI4meAivwHw,2987
@@ -141,11 +141,11 @@ atomicshop/mitm/engines/create_module_template_example.py,sha256=X5xhvbV6-g9jU_b
141
141
  atomicshop/mitm/engines/__parent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
142
142
  atomicshop/mitm/engines/__parent/parser___parent.py,sha256=HHaCXhScl3OlPjz6eUxsDpJaZyk6BNuDMc9xCkeo2Ws,661
143
143
  atomicshop/mitm/engines/__parent/recorder___parent.py,sha256=exfElkgOU57hupV6opRCeYPHw91GIkIZL6UY3f2OClM,5635
144
- atomicshop/mitm/engines/__parent/responder___parent.py,sha256=7WQeR3UmMnN74bDwn-0nz2OfhXJ3-ClXpNGUFZ7wJUE,12004
144
+ atomicshop/mitm/engines/__parent/responder___parent.py,sha256=qIxfPiVZs4ZDDFYYeJJAiBWYg0cSfNDC-ibpGYnB2tA,8926
145
145
  atomicshop/mitm/engines/__reference_general/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
146
146
  atomicshop/mitm/engines/__reference_general/parser___reference_general.py,sha256=57MEPZMAjTO6xBDZ-yt6lgGJyqRrP0Do5Gk_cgCiPns,2998
147
147
  atomicshop/mitm/engines/__reference_general/recorder___reference_general.py,sha256=El2_YHLoHUCiKfkAmGlXxtFpmSjsUFdsb8I1MvSAFaM,653
148
- atomicshop/mitm/engines/__reference_general/responder___reference_general.py,sha256=IUyQYMPeEhIARfALWiKPFeXagSQD6lRzAxUdi4ZIT88,7010
148
+ atomicshop/mitm/engines/__reference_general/responder___reference_general.py,sha256=r7WMv59xlVNttz15ppUvQbH2j5eKHAI-yIy1SCCu2lw,9523
149
149
  atomicshop/mitm/statistic_analyzer_helper/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
150
150
  atomicshop/mitm/statistic_analyzer_helper/analyzer_helper.py,sha256=pk6L1t1ea1kvlBoR9QEJptOmaX-mumhwLsP2GCKukbk,5920
151
151
  atomicshop/mitm/statistic_analyzer_helper/moving_average_helper.py,sha256=UnnY_FSTiXEfZ8SkDKU2s2qpgPYu1oOT99ghmY-zzas,19992
@@ -262,7 +262,7 @@ atomicshop/wrappers/mongodbw/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NM
262
262
  atomicshop/wrappers/mongodbw/install_mongodb_ubuntu.py,sha256=pmI9AwWJ2cv5h8GionSpSJkllg6kfp0M381pk6y4Y5U,4015
263
263
  atomicshop/wrappers/mongodbw/install_mongodb_win.py,sha256=64EUQYx7VuMC3ndO2x3nSErh5NZ_BsqMwGvPcybfC-Q,8499
264
264
  atomicshop/wrappers/mongodbw/mongo_infra.py,sha256=IjEF0jPzQz866MpTm7rnksnyyWQeUT_B2h2DA9ryAio,2034
265
- atomicshop/wrappers/mongodbw/mongodbw.py,sha256=IkEw86QFyVRU-5p5s6_6yupvSxmaQxr59GKNgSEkAm4,52617
265
+ atomicshop/wrappers/mongodbw/mongodbw.py,sha256=ih3Gd45rg_70y4sGeu0eEJ3sJd9tEN4I5IqHZelRZJw,52854
266
266
  atomicshop/wrappers/nodejsw/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
267
267
  atomicshop/wrappers/nodejsw/install_nodejs.py,sha256=TKGa3jSlSqZTL2NA0nMkWDFtlkz7rxGGn44ywCg7MN8,5228
268
268
  atomicshop/wrappers/playwrightw/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -275,7 +275,7 @@ atomicshop/wrappers/playwrightw/javascript.py,sha256=_bW7CAtm0Y8IHYrAalg5HpPFnk6
275
275
  atomicshop/wrappers/playwrightw/keyboard.py,sha256=zN3YddGO-qUkn6C0CRVFejP4cTuaUwXLDNFhFREjERY,422
276
276
  atomicshop/wrappers/playwrightw/locators.py,sha256=6wsLywZxDuii7mwv-zQsRbqQC8r7j96Bma5b5_7ZoVo,2411
277
277
  atomicshop/wrappers/playwrightw/mouse.py,sha256=-2FZbQtjgH7tdXWld6ZPGqlKFUdf5in0ujN0hewxa50,656
278
- atomicshop/wrappers/playwrightw/scenarios.py,sha256=RY56hH7UKvDoBr5j1JwP5xRoQtaz0AnCAkA602MurPk,8396
278
+ atomicshop/wrappers/playwrightw/scenarios.py,sha256=HopJJ-caAHuXxH8kiJHtlcFSI-89Zx7Fc6caGPOHC2A,8786
279
279
  atomicshop/wrappers/playwrightw/waits.py,sha256=PBFdz_PoM7Fo7O8hLqMrxNPzBEYgPoXwZceFFCGGeu8,7182
280
280
  atomicshop/wrappers/psutilw/cpus.py,sha256=w6LPBMINqS-T_X8vzdYkLS2Wzuve28Ydp_GafTCngrc,236
281
281
  atomicshop/wrappers/psutilw/disks.py,sha256=3ZSVoommKH1TWo37j_83frB-NqXF4Nf5q5mBCX8G4jE,9221
@@ -299,7 +299,7 @@ atomicshop/wrappers/pywin32w/win_event_log/subscribes/process_terminate.py,sha25
299
299
  atomicshop/wrappers/pywin32w/win_event_log/subscribes/schannel_logging.py,sha256=8nxIcNcbeEuvoBwhujgh7-oIpL9A6J-gg1NM8hOGAVA,3442
300
300
  atomicshop/wrappers/pywin32w/wmis/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
301
301
  atomicshop/wrappers/pywin32w/wmis/helpers.py,sha256=uMXa27UfBpqXInvnmk7CZlqwRI2pg_I_HXelxO9nLLg,5020
302
- atomicshop/wrappers/pywin32w/wmis/win32networkadapter.py,sha256=9H9MdS__GDBMm8H-xINEPFrJ8j-ErIb1ZqzkulTwLTo,5443
302
+ atomicshop/wrappers/pywin32w/wmis/win32networkadapter.py,sha256=Jzl95viXZExrrlDTGHR0-wiXJo3jQRztmLXkigahFww,5574
303
303
  atomicshop/wrappers/pywin32w/wmis/win32process.py,sha256=qMzXtJ5hBZ5ydAyqpDbSx0nO2RJQL95HdmV5SzNKMhk,6826
304
304
  atomicshop/wrappers/socketw/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
305
305
  atomicshop/wrappers/socketw/accepter.py,sha256=hZZKVYlF3LOHQJsSIEKXZUf6QXXWm-AtqXZevvaYigE,1732
@@ -318,9 +318,9 @@ atomicshop/wrappers/socketw/socket_wrapper.py,sha256=WtylpezgIIBuz-A6PfM0hO1sm9E
318
318
  atomicshop/wrappers/socketw/ssl_base.py,sha256=kmiif84kMhBr5yjQW17p935sfjR5JKG0LxIwBA4iVvU,2275
319
319
  atomicshop/wrappers/socketw/statistics_csv.py,sha256=fgMzDXI0cybwUEqAxprRmY3lqbh30KAV-jOpoFKT-m8,3395
320
320
  atomicshop/wrappers/winregw/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
321
- atomicshop/wrappers/winregw/winreg_network.py,sha256=zZQfps-CdODQaTUADbHAwKHr5RUg7BLafnKWBbKaLN4,8728
322
- atomicshop-2.18.6.dist-info/LICENSE.txt,sha256=lLU7EYycfYcK2NR_1gfnhnRC8b8ccOTElACYplgZN88,1094
323
- atomicshop-2.18.6.dist-info/METADATA,sha256=sMVjybxEDSWZC48hrJM-Md_y3VH4v5p9UbmQEc1XdOg,10576
324
- atomicshop-2.18.6.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
325
- atomicshop-2.18.6.dist-info/top_level.txt,sha256=EgKJB-7xcrAPeqTRF2laD_Np2gNGYkJkd4OyXqpJphA,11
326
- atomicshop-2.18.6.dist-info/RECORD,,
321
+ atomicshop/wrappers/winregw/winreg_network.py,sha256=AENV88H1qDidrcpyM9OwEZxX5svfi-Jb4N6FkS1xtqA,8851
322
+ atomicshop-2.18.8.dist-info/LICENSE.txt,sha256=lLU7EYycfYcK2NR_1gfnhnRC8b8ccOTElACYplgZN88,1094
323
+ atomicshop-2.18.8.dist-info/METADATA,sha256=37oxDInywUGEbgwoNSHN5Lu2UUt4zTNruStPlhVdfmg,10576
324
+ atomicshop-2.18.8.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
325
+ atomicshop-2.18.8.dist-info/top_level.txt,sha256=EgKJB-7xcrAPeqTRF2laD_Np2gNGYkJkd4OyXqpJphA,11
326
+ atomicshop-2.18.8.dist-info/RECORD,,