atomicshop 2.16.28__py3-none-any.whl → 2.16.30__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.

Files changed (31) hide show
  1. atomicshop/__init__.py +1 -1
  2. atomicshop/archiver/zips.py +5 -6
  3. atomicshop/basics/strings.py +2 -2
  4. atomicshop/dns.py +8 -6
  5. atomicshop/file_io/file_io.py +7 -7
  6. atomicshop/filesystem.py +6 -7
  7. atomicshop/http_parse.py +82 -67
  8. atomicshop/mitm/connection_thread_worker.py +187 -231
  9. atomicshop/mitm/engines/__parent/parser___parent.py +1 -4
  10. atomicshop/mitm/engines/__parent/recorder___parent.py +6 -2
  11. atomicshop/mitm/message.py +29 -20
  12. atomicshop/mitm/mitm_main.py +36 -32
  13. atomicshop/print_api.py +13 -53
  14. atomicshop/system_resource_monitor.py +8 -8
  15. atomicshop/system_resources.py +8 -8
  16. atomicshop/web.py +10 -10
  17. atomicshop/wrappers/loggingw/filters.py +23 -0
  18. atomicshop/wrappers/loggingw/handlers.py +20 -0
  19. atomicshop/wrappers/loggingw/loggingw.py +130 -7
  20. atomicshop/wrappers/playwrightw/engine.py +6 -7
  21. atomicshop/wrappers/playwrightw/waits.py +9 -7
  22. atomicshop/wrappers/socketw/dns_server.py +2 -2
  23. atomicshop/wrappers/socketw/sender.py +56 -28
  24. atomicshop/wrappers/socketw/sni.py +27 -16
  25. atomicshop/wrappers/socketw/socket_client.py +6 -5
  26. atomicshop/wrappers/socketw/socket_wrapper.py +49 -14
  27. {atomicshop-2.16.28.dist-info → atomicshop-2.16.30.dist-info}/METADATA +1 -1
  28. {atomicshop-2.16.28.dist-info → atomicshop-2.16.30.dist-info}/RECORD +31 -31
  29. {atomicshop-2.16.28.dist-info → atomicshop-2.16.30.dist-info}/LICENSE.txt +0 -0
  30. {atomicshop-2.16.28.dist-info → atomicshop-2.16.30.dist-info}/WHEEL +0 -0
  31. {atomicshop-2.16.28.dist-info → atomicshop-2.16.30.dist-info}/top_level.txt +0 -0
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.16.28'
4
+ __version__ = '2.16.30'
@@ -4,8 +4,7 @@ import zipfile
4
4
  from io import BytesIO
5
5
  from typing import Union, Literal
6
6
 
7
- from .. import filesystem
8
- from ..print_api import print_api
7
+ from .. import filesystem, print_api
9
8
 
10
9
 
11
10
  def is_zip_zipfile(file_object: Union[str, bytes]) -> bool:
@@ -42,7 +41,7 @@ def is_zip_magic_number(file_path: str) -> bool:
42
41
  Each file within the ZIP archive starts with this signature.
43
42
  50 4B 05 06: This is the end of central directory record signature.
44
43
  It's found at the end of a ZIP file and is essential for identifying the structure of the ZIP archive,
45
- especially in cases where the file is split or is a multi-part archive.
44
+ especially in cases where the file is split or is a multipart archive.
46
45
  50 4B 07 08: This signature is used for spanned ZIP archives (also known as split or multi-volume ZIP archives).
47
46
  It's found in the end of central directory locator for ZIP files that are split across multiple volumes.
48
47
  """
@@ -93,7 +92,7 @@ def extract_archive_with_zipfile(
93
92
  filesystem.get_file_directory(archive_path) + os.sep +
94
93
  filesystem.get_file_name_without_extension(archive_path))
95
94
 
96
- print_api(f'Extracting to directory: {extract_directory}', **print_kwargs)
95
+ print_api.print_api(f'Extracting to directory: {extract_directory}', **print_kwargs)
97
96
 
98
97
  # initiating the archived file path as 'zipfile.ZipFile' object.
99
98
  with zipfile.ZipFile(archive_path) as zip_object:
@@ -113,7 +112,7 @@ def extract_archive_with_zipfile(
113
112
  # Cut the first directory from the filename.
114
113
  zip_info.filename = zip_info.filename.split('/', maxsplit=1)[1]
115
114
 
116
- print_api(f'Extracting: {zip_info.filename}', **print_kwargs)
115
+ print_api.print_api(f'Extracting: {zip_info.filename}', **print_kwargs)
117
116
 
118
117
  # Extract current file from the archive using 'zip_info' of the current file with 'filename' that we
119
118
  # updated under specified parameters to specified directory.
@@ -126,7 +125,7 @@ def extract_archive_with_zipfile(
126
125
  date_time = time.mktime(zip_info.date_time + (0, 0, -1))
127
126
  # Using 'os' library, changed the datetime of the file to the object created in previous step.
128
127
  os.utime(extracted_file_path, (date_time, date_time))
129
- print_api('Extraction done.', color="green", **print_kwargs)
128
+ print_api.print_api('Extraction done.', color="green", **print_kwargs)
130
129
 
131
130
  return extract_directory
132
131
 
@@ -4,7 +4,7 @@ from pathlib import Path
4
4
  import argparse
5
5
 
6
6
  from . import lists
7
- from ..print_api import print_api
7
+ from .. import print_api
8
8
 
9
9
 
10
10
  def get_nth_character_from_start(input_string: str, nth: int):
@@ -537,7 +537,7 @@ def replace_string_in_file(
537
537
  file.writelines(lines)
538
538
 
539
539
  # Output the relevant line numbers
540
- print_api(f"Target string found on the following lines: {changed_lines}", **(print_kwargs or {}))
540
+ print_api.print_api(f"Target string found on the following lines: {changed_lines}", **(print_kwargs or {}))
541
541
  return changed_lines
542
542
 
543
543
 
atomicshop/dns.py CHANGED
@@ -1,8 +1,9 @@
1
1
  import argparse
2
2
 
3
+ # noinspection PyPackageRequirements
3
4
  import dns.resolver
4
5
 
5
- from .print_api import print_api
6
+ from . import print_api
6
7
  from .permissions import permissions
7
8
  from .wrappers.pywin32w.wmis import win32networkadapter
8
9
  from .wrappers.winregw import winreg_network
@@ -57,10 +58,10 @@ def resolve_dns_localhost(domain_name: str, dns_servers_list: list = None, print
57
58
  # Get only the first entry of the list of IPs [0]
58
59
  connection_ip = function_server_address[0].to_text()
59
60
  message = f"Resolved to [{connection_ip}]"
60
- print_api(message, **print_kwargs)
61
+ print_api.print_api(message, **print_kwargs)
61
62
  except dns.resolver.NXDOMAIN:
62
63
  message = f"Domain {domain_name} doesn't exist - Couldn't resolve with {dns_servers_list}."
63
- print_api(message, **print_kwargs, error_type=True, logger_method='error')
64
+ print_api.print_api(message, **print_kwargs, error_type=True, logger_method='error')
64
65
  pass
65
66
 
66
67
  return connection_ip
@@ -137,12 +138,13 @@ def default_dns_gateway_main() -> int:
137
138
  args = argparse_obj.parse_args()
138
139
 
139
140
  if (args.set or args.dynamic) and not (args.connection_name or args.connection_default):
140
- print_api("Please provide the connection name [-cn] or use the default connection [-cd].", color='red')
141
+ print_api.print_api(
142
+ "Please provide the connection name [-cn] or use the default connection [-cd].", color='red')
141
143
  return 1
142
144
 
143
145
  if args.set or args.dynamic:
144
146
  if not permissions.is_admin():
145
- print_api("You need to run this script as an administrator", color='red')
147
+ print_api.print_api("You need to run this script as an administrator", color='red')
146
148
  return 1
147
149
 
148
150
  if args.get:
@@ -152,7 +154,7 @@ def default_dns_gateway_main() -> int:
152
154
  is_dynamic_string = 'Dynamic'
153
155
  else:
154
156
  is_dynamic_string = 'Static'
155
- print_api(f'DNS Gateway: {is_dynamic_string} - {dns_servers}', color='blue')
157
+ print_api.print_api(f'DNS Gateway: {is_dynamic_string} - {dns_servers}', color='blue')
156
158
  elif args.set:
157
159
  # dns_servers_list: list = args.dns_servers.split(',')
158
160
  set_connection_dns_gateway_static(
@@ -1,7 +1,7 @@
1
1
  from typing import Union
2
2
  import functools
3
3
 
4
- from ..print_api import print_api
4
+ from .. import print_api
5
5
  from ..inspect_wrapper import get_target_function_default_args_and_combine_with_current
6
6
 
7
7
 
@@ -19,7 +19,7 @@ def write_file_decorator(function_name):
19
19
  # args, kwargs = put_args_to_kwargs(function_name, *args, **kwargs)
20
20
  args, kwargs = get_target_function_default_args_and_combine_with_current(function_name, *args, **kwargs)
21
21
 
22
- print_api(message=f"Writing file: {kwargs['file_path']}", **kwargs)
22
+ print_api.print_api(message=f"Writing file: {kwargs['file_path']}", **kwargs)
23
23
 
24
24
  enable_long_file_path = kwargs.get('enable_long_file_path', False)
25
25
  if enable_long_file_path:
@@ -41,7 +41,7 @@ def write_file_decorator(function_name):
41
41
  except FileExistsError:
42
42
  message = f"Can't write file: {kwargs['file_path']}\n" \
43
43
  f"File exists, you should enable force/overwrite mode."
44
- print_api(message, error_type=True, logger_method='critical', **kwargs)
44
+ print_api.print_api(message, error_type=True, logger_method='critical', **kwargs)
45
45
 
46
46
  return wrapper_write_file_decorator
47
47
 
@@ -56,7 +56,7 @@ def read_file_decorator(function_name):
56
56
  continue_loop: bool = True
57
57
  while continue_loop:
58
58
  try:
59
- print_api(message=f"Reading file: {kwargs['file_path']}", **kwargs)
59
+ print_api.print_api(message=f"Reading file: {kwargs['file_path']}", **kwargs)
60
60
  with open(kwargs['file_path'], kwargs['file_mode'], encoding=kwargs['encoding']) as input_file:
61
61
  # Pass the 'output_file' object to kwargs that will pass the object to the executing function.
62
62
  kwargs['file_object'] = input_file
@@ -64,19 +64,19 @@ def read_file_decorator(function_name):
64
64
  return function_name(**kwargs)
65
65
  except FileNotFoundError:
66
66
  message = f"File doesn't exist: {kwargs['file_path']}"
67
- print_api(message, error_type=True, logger_method='critical', **kwargs)
67
+ print_api.print_api(message, error_type=True, logger_method='critical', **kwargs)
68
68
  raise
69
69
  except UnicodeDecodeError as exception_object:
70
70
  if kwargs["encoding"] != 'utf-8':
71
71
  message = f'File decode error, current encoding: {kwargs["encoding"]}. Will try "utf-8".'
72
- print_api(message, logger_method='error', **kwargs)
72
+ print_api.print_api(message, logger_method='error', **kwargs)
73
73
  kwargs["encoding"] = 'utf-8'
74
74
  pass
75
75
  continue
76
76
  else:
77
77
  message = f'File decode error.\n' \
78
78
  f'{exception_object}'
79
- print_api(message, merror_type=True, logger_method='critical', **kwargs)
79
+ print_api.print_api(message, merror_type=True, logger_method='critical', **kwargs)
80
80
  continue_loop = False
81
81
 
82
82
  return wrapper_read_file_decorator
atomicshop/filesystem.py CHANGED
@@ -12,10 +12,9 @@ import tempfile
12
12
  # noinspection PyPackageRequirements
13
13
  import psutil
14
14
 
15
- from .print_api import print_api, print_status_of_list
16
15
  from .basics import strings, list_of_dicts, list_of_classes
17
16
  from .file_io import file_io
18
- from . import hashing, datetimes
17
+ from . import hashing, datetimes, print_api
19
18
 
20
19
 
21
20
  WINDOWS_DIRECTORY_SPECIAL_CHARACTERS = ['<', '>', ':', '"', '/', '\\', '|', '?', '*']
@@ -280,12 +279,12 @@ def remove_file(file_path: str, **kwargs) -> bool:
280
279
 
281
280
  try:
282
281
  os.remove(file_path)
283
- print_api(f'File Removed: {file_path}')
282
+ print_api.print_api(f'File Removed: {file_path}')
284
283
  return True
285
284
  # Since the file doesn't exist, we actually don't care, since we want to remove it anyway.
286
285
  except FileNotFoundError:
287
286
  message = f'File Removal Failed, File non-existent: {file_path}'
288
- print_api(message, error_type=True, logger_method='critical', **kwargs)
287
+ print_api.print_api(message, error_type=True, logger_method='critical', **kwargs)
289
288
  return False
290
289
 
291
290
 
@@ -321,12 +320,12 @@ def remove_directory(directory_path: str, force_readonly: bool = False, print_kw
321
320
  shutil.rmtree(directory_path, onerror=remove_readonly)
322
321
  else:
323
322
  shutil.rmtree(directory_path)
324
- print_api(f'Directory Removed: {directory_path}', **print_kwargs)
323
+ print_api.print_api(f'Directory Removed: {directory_path}', **print_kwargs)
325
324
  return True
326
325
  # Since the directory doesn't exist, we actually don't care, since we want to remove it anyway.
327
326
  except FileNotFoundError:
328
327
  message = f'Directory Removal Failed, Directory non-existent: {directory_path}'
329
- print_api(message, error_type=True, logger_method='critical', **print_kwargs)
328
+ print_api.print_api(message, error_type=True, logger_method='critical', **print_kwargs)
330
329
  return False
331
330
 
332
331
 
@@ -878,7 +877,7 @@ def get_paths_from_directory(
878
877
  prefix_string = 'Reading File: '
879
878
 
880
879
  for file_index, file_path in enumerate(object_list):
881
- print_status_of_list(
880
+ print_api.print_status_of_list(
882
881
  list_instance=object_list, prefix_string=prefix_string, current_state=(file_index + 1))
883
882
 
884
883
  # If 'add_binary' was passed.
atomicshop/http_parse.py CHANGED
@@ -1,8 +1,8 @@
1
- # v1.0.1 - 26.03.2023 23:40
2
1
  from http.server import BaseHTTPRequestHandler
3
2
  from http.client import HTTPResponse
4
3
  import http
5
4
  from io import BytesIO
5
+ import socket
6
6
 
7
7
 
8
8
  class HTTPRequestParse(BaseHTTPRequestHandler):
@@ -49,41 +49,27 @@ class HTTPRequestParse(BaseHTTPRequestHandler):
49
49
  client_message.request_raw_decoded = request_decoded
50
50
  """
51
51
 
52
+ # noinspection PyMissingConstructor
52
53
  def __init__(self, request_text):
53
- self.rfile = BytesIO(request_text)
54
- self.raw_requestline = self.rfile.readline()
55
- self.error_code = self.error_message = None
56
- self.parse_request()
54
+ self.request_text = request_text
57
55
 
58
- # Check if ".path" attribute exists after HTTP request parsing
59
- if not hasattr(self, 'path'):
60
- self.path = None
56
+ # noinspection PyTypeChecker
57
+ self.rfile = None
58
+ self.raw_requestline = None
59
+ self.error_code = None
60
+ self.error_message = None
61
61
 
62
- # Before checking for body, we need to make sure that ".headers" property exists, if not, return empty values
63
- try:
64
- # The "body" of request is in the 'Content-Length' key. If it exists in "headers" - get the body
65
- if 'Content-Length' in self.headers.keys():
66
- # "self.headers.get('Content-Length')" returns number in string format, "int" converts it to integer
67
- self.content_length = int(self.headers.get('Content-Length'))
68
- self.body = self.rfile.read(self.content_length)
69
- else:
70
- self.content_length = None
71
- self.body = None
72
- except Exception:
73
- self.content_length = None
74
- self.body = None
75
- pass
76
-
77
- # Examples:
78
- # Getting path: self.path
79
- # Getting Request Version: self.request_version
80
- # Getting specific header: self.headers['host']
62
+ self.content_length = None
63
+ self.body = None
64
+ # noinspection PyTypeChecker
65
+ self.path = None
81
66
 
67
+ # noinspection PyMethodOverriding
82
68
  def send_error(self, code, message):
83
69
  self.error_code = code
84
70
  self.error_message = message
85
71
 
86
- def check_if_http(self):
72
+ def parse(self):
87
73
  """
88
74
  Function to check if parsed object is HTTP request or not.
89
75
  'reason' will be populated with parsing status and errors.
@@ -117,10 +103,27 @@ class HTTPRequestParse(BaseHTTPRequestHandler):
117
103
 
118
104
  client_message.request_raw_decoded = request_decoded
119
105
  """
120
- # Defining variables
121
- reason = None
122
- function_result: bool = True
123
- error: bool = False
106
+
107
+ error: str = str()
108
+ info: str = str()
109
+
110
+ self.rfile = BytesIO(self.request_text)
111
+ self.raw_requestline = self.rfile.readline()
112
+ self.error_code = self.error_message = None
113
+ self.parse_request()
114
+
115
+ # Before checking for body, we need to make sure that ".headers" property exists, if not, return empty values.
116
+ if hasattr(self, 'headers'):
117
+ # The "body" of request is in the 'Content-Length' key. If it exists in "headers" - get the body
118
+ if 'Content-Length' in self.headers.keys():
119
+ # "self.headers.get('Content-Length')" returns number in string format, "int" converts it to integer
120
+ self.content_length = int(self.headers.get('Content-Length'))
121
+ self.body = self.rfile.read(self.content_length)
122
+
123
+ # Examples:
124
+ # Getting path: self.path
125
+ # Getting Request Version: self.request_version
126
+ # Getting specific header: self.headers['host']
124
127
 
125
128
  # If there's any error in HTTP parsing
126
129
  if self.error_message:
@@ -129,65 +132,82 @@ class HTTPRequestParse(BaseHTTPRequestHandler):
129
132
  # If it's 'Bad request' this is not HTTP request, so we can
130
133
  # continue the execution and parse the code as NON-HTTP Request.
131
134
  # Currently, seen 'Bad request syntax' and 'Bad request version'.
132
- reason = f"HTTP Request Parsing: Not HTTP request: {self.error_message}"
133
- function_result = False
134
- error: False
135
+ error = f"HTTP Request Parsing: Not HTTP request: {self.error_message}"
136
+ is_http = False
135
137
  else:
136
- reason = f"HTTP Request Parsing: HTTP Request with Script Undocumented ERROR: {self.error_message}"
137
- function_result = True
138
- error = True
138
+ error = f"HTTP Request Parsing: HTTP Request with Script Undocumented ERROR: {self.error_message}"
139
+ is_http = False
139
140
  # If there's no error at all in HTTP Parsing, then it's fine HTTP Request
140
141
  else:
141
- reason = "HTTP Request Parsing: Valid HTTP request"
142
- function_result = True
143
- error = False
142
+ is_http = True
143
+ info = "HTTP Request Parsing: Valid HTTP request"
144
144
 
145
- return function_result, reason, error
145
+ return self, is_http, info, error
146
146
 
147
147
 
148
148
  class FakeSocket:
149
149
  """
150
- FakeSocket is needed to parse HTTP Response. Socket object is needed for HTTPResponse class input.
150
+ FakeSocket mimics a socket object for parsing HTTP responses.
151
151
  """
152
152
  def __init__(self, response_bytes):
153
153
  self._file = BytesIO(response_bytes)
154
154
 
155
- def makefile(self, *args, **kwargs):
155
+ def makefile(self, mode='rb', buffering=-1) -> BytesIO:
156
+ """
157
+ Mimics the socket's makefile method, returning the BytesIO object.
158
+ """
156
159
  return self._file
157
160
 
161
+ def fileno(self) -> int:
162
+ """
163
+ Provide a dummy file descriptor, as some code might call this.
164
+ """
165
+ raise OSError("File descriptor not available in FakeSocket")
166
+
158
167
 
159
168
  class HTTPResponseParse:
160
169
  def __init__(self, response_raw_bytes: bytes):
161
- self.error = None
162
170
  self.response_raw_bytes: bytes = response_raw_bytes
171
+
172
+ self.error = None
173
+ self.source = None
174
+ self.response_raw_decoded = None
175
+ self.is_http: bool = False
176
+
177
+ def parse(self):
163
178
  # Assigning FakeSocket with response_raw_bytes.
164
179
  self.source = FakeSocket(self.response_raw_bytes)
165
180
 
166
181
  # Initializing HTTPResponse class with the FakeSocket with response_raw_bytes as input.
182
+ # noinspection PyTypeChecker
167
183
  self.response_raw_decoded = HTTPResponse(self.source)
168
184
 
169
185
  # Try to parse HTTP Response.
170
186
  try:
171
187
  self.response_raw_decoded.begin()
188
+ self.is_http = True
172
189
  # If there were problems with the status line.
173
190
  except http.client.BadStatusLine:
174
191
  self.error = "HTTP Response Parsing: Not a valid HTTP Response: Bad Status Line."
175
- pass
192
+ self.is_http = False
176
193
 
177
- try:
178
- # If no exception was thrown, but there are some problems with headers.
179
- if self.response_raw_decoded.headers.defects:
180
- self.error = f"HTTP Response Parsing: Not a valid HTTP Response: Some defects in headers: " \
181
- f"{self.response_raw_decoded.headers.defects}"
182
- # If the attribute of defects doesn't exist, probably the response wasn't parsed at all by the library,
183
- # Meaning, that the exception was already handled.
184
- except AttributeError:
185
- pass
186
-
187
- # Before checking for body, we need to make sure that ".headers" property exists, if not, return empty values
188
- try:
189
- # The "body" of response is in the 'Content-Length' key. If it exists in "headers" - get the body.
190
- if 'Content-Length' in self.response_raw_decoded.headers.keys():
194
+ header_exists: bool = False
195
+ if (self.response_raw_decoded is not None and hasattr(self.response_raw_decoded, 'headers') and
196
+ self.response_raw_decoded.headers is not None):
197
+ header_exists = True
198
+
199
+ if header_exists and self.response_raw_decoded.headers.defects:
200
+ self.error = f"HTTP Response Parsing: Not a valid HTTP Response: Some defects in headers: " \
201
+ f"{self.response_raw_decoded.headers.defects}"
202
+ self.is_http = False
203
+
204
+ if self.is_http:
205
+ # Before checking for body, we need to make sure that ".headers" property exists,
206
+ # if not, return empty values.
207
+ self.response_raw_decoded.content_length = None
208
+ self.response_raw_decoded.body = None
209
+ if header_exists and 'Content-Length' in self.response_raw_decoded.headers.keys():
210
+ # The "body" of response is in the 'Content-Length' key. If it exists in "headers" - get the body.
191
211
  # "self.response_raw_decoded.headers.get('Content-Length')" returns number in string format,
192
212
  # "int" converts it to integer.
193
213
  self.response_raw_decoded.content_length = int(self.response_raw_decoded.headers.get('Content-Length'))
@@ -195,10 +215,5 @@ class HTTPResponseParse:
195
215
  # of the response that we received.
196
216
  # self.response_raw_bytes[-23:]
197
217
  self.response_raw_decoded.body = self.response_raw_bytes[-self.response_raw_decoded.content_length:]
198
- else:
199
- self.response_raw_decoded.content_length = None
200
- self.response_raw_decoded.body = None
201
- except Exception:
202
- self.response_raw_decoded.content_length = None
203
- self.response_raw_decoded.body = None
204
- pass
218
+
219
+ return self.response_raw_decoded, self.is_http, self.error