atomicshop 2.17.3__py3-none-any.whl → 2.18.1__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/ansi_escape_codes.py +3 -1
- atomicshop/file_io/docxs.py +26 -17
- atomicshop/http_parse.py +118 -77
- atomicshop/mitm/config_static.py +1 -1
- atomicshop/mitm/connection_thread_worker.py +387 -257
- atomicshop/mitm/engines/__parent/parser___parent.py +2 -2
- atomicshop/mitm/engines/__parent/recorder___parent.py +83 -24
- atomicshop/mitm/engines/__reference_general/recorder___reference_general.py +2 -2
- atomicshop/mitm/message.py +42 -15
- atomicshop/mitm/mitm_main.py +3 -2
- atomicshop/mitm/recs_files.py +2 -1
- atomicshop/system_resource_monitor.py +16 -3
- atomicshop/wrappers/socketw/base.py +17 -0
- atomicshop/wrappers/socketw/receiver.py +90 -100
- atomicshop/wrappers/socketw/sender.py +5 -8
- atomicshop/wrappers/socketw/sni.py +0 -1
- atomicshop/wrappers/socketw/socket_client.py +2 -2
- atomicshop/wrappers/socketw/statistics_csv.py +15 -6
- {atomicshop-2.17.3.dist-info → atomicshop-2.18.1.dist-info}/METADATA +1 -1
- {atomicshop-2.17.3.dist-info → atomicshop-2.18.1.dist-info}/RECORD +24 -24
- {atomicshop-2.17.3.dist-info → atomicshop-2.18.1.dist-info}/LICENSE.txt +0 -0
- {atomicshop-2.17.3.dist-info → atomicshop-2.18.1.dist-info}/WHEEL +0 -0
- {atomicshop-2.17.3.dist-info → atomicshop-2.18.1.dist-info}/top_level.txt +0 -0
atomicshop/__init__.py
CHANGED
|
@@ -23,7 +23,8 @@ def get_colors_basic_dict(color):
|
|
|
23
23
|
'yellow': ColorsBasic.YELLOW,
|
|
24
24
|
'blue': ColorsBasic.BLUE,
|
|
25
25
|
'header': ColorsBasic.HEADER,
|
|
26
|
-
'cyan': ColorsBasic.CYAN
|
|
26
|
+
'cyan': ColorsBasic.CYAN,
|
|
27
|
+
'orange': ColorsBasic.ORANGE
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
return colors_basic_dict[color]
|
|
@@ -48,6 +49,7 @@ class ColorsBasic:
|
|
|
48
49
|
BLUE = '\033[94m'
|
|
49
50
|
HEADER = '\033[95m'
|
|
50
51
|
CYAN = '\033[96m'
|
|
52
|
+
ORANGE = '\033[38;2;255;165;0m'
|
|
51
53
|
END = ANSI_END
|
|
52
54
|
|
|
53
55
|
|
atomicshop/file_io/docxs.py
CHANGED
|
@@ -12,7 +12,7 @@ def get_hyperlinks(docx_path):
|
|
|
12
12
|
:return: list of strings, hyperlinks.
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
|
-
hyperlinks: list = list()
|
|
15
|
+
hyperlinks: list[dict] = list()
|
|
16
16
|
|
|
17
17
|
try:
|
|
18
18
|
doc = Document(docx_path)
|
|
@@ -32,9 +32,13 @@ def get_hyperlinks(docx_path):
|
|
|
32
32
|
# And the fragment is stored in the hyperlink.fragment as 'anchor'.
|
|
33
33
|
# For the full hyperlink, we need to concatenate the address and the fragment.
|
|
34
34
|
# If there is no anchor in the link the fragment will be empty string ('').
|
|
35
|
-
if
|
|
36
|
-
|
|
37
|
-
hyperlinks.append(hyperlink.address)
|
|
35
|
+
# Basically, we don't need to add the fragment to the hyperlink if it's empty, we can just use the url.
|
|
36
|
+
# if hyperlink.fragment:
|
|
37
|
+
# hyperlinks.append(hyperlink.address + "#" + hyperlink.fragment)
|
|
38
|
+
hyperlinks.append({
|
|
39
|
+
'url': hyperlink.url,
|
|
40
|
+
'text': hyperlink.text
|
|
41
|
+
})
|
|
38
42
|
|
|
39
43
|
return hyperlinks
|
|
40
44
|
|
|
@@ -78,20 +82,23 @@ def search_for_hyperlink_in_files(directory_path: str, hyperlink: str, relative_
|
|
|
78
82
|
directory_path, get_file=True, file_name_check_pattern="*.docx",
|
|
79
83
|
add_relative_directory=True, relative_file_name_as_directory=True)
|
|
80
84
|
|
|
81
|
-
found_in_files: list = list()
|
|
85
|
+
found_in_files: list[dict] = list()
|
|
82
86
|
for file_path in files:
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
87
|
+
doc_hyperlinks = get_hyperlinks(file_path.path)
|
|
88
|
+
for doc_link in doc_hyperlinks:
|
|
89
|
+
if hyperlink in doc_link['url']:
|
|
90
|
+
if relative_paths:
|
|
91
|
+
path: str = file_path.relative_dir
|
|
92
|
+
else:
|
|
93
|
+
path: str = file_path.path
|
|
86
94
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
result_list = found_in_files
|
|
95
|
+
found_in_files.append({
|
|
96
|
+
'path':path,
|
|
97
|
+
'link':doc_link['url'],
|
|
98
|
+
'text':doc_link['text']
|
|
99
|
+
})
|
|
93
100
|
|
|
94
|
-
return
|
|
101
|
+
return found_in_files
|
|
95
102
|
|
|
96
103
|
|
|
97
104
|
def search_for_hyperlink_in_files_interface_main(script_directory: str = None):
|
|
@@ -134,10 +141,12 @@ def search_for_hyperlink_in_files_interface_main(script_directory: str = None):
|
|
|
134
141
|
found_in_files = search_for_hyperlink_in_files(
|
|
135
142
|
config['directory_path'], config['hyperlink'], relative_paths=config['relative_paths'])
|
|
136
143
|
|
|
137
|
-
print_api(f"Found
|
|
144
|
+
print_api(f"Found [{len(found_in_files)}] links:", color="blue")
|
|
138
145
|
|
|
139
146
|
for index, found_file in enumerate(found_in_files):
|
|
140
147
|
print_api(f"[{index+1}]", print_end="", color="green")
|
|
141
|
-
print_api(f" {found_file}")
|
|
148
|
+
print_api(f" {found_file['path']}")
|
|
149
|
+
print_api(f" {found_file['link']}", color="cyan")
|
|
150
|
+
print_api(f" {found_file['text']}", color="orange")
|
|
142
151
|
|
|
143
152
|
input('[*] Press [Enter] to exit...')
|
atomicshop/http_parse.py
CHANGED
|
@@ -4,6 +4,40 @@ import http
|
|
|
4
4
|
from io import BytesIO
|
|
5
5
|
|
|
6
6
|
|
|
7
|
+
def get_request_methods() -> list[str]:
|
|
8
|
+
"""
|
|
9
|
+
Function to return all available HTTP request methods.
|
|
10
|
+
"""
|
|
11
|
+
# noinspection PyUnresolvedReferences
|
|
12
|
+
return [method.value for method in http.HTTPMethod]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def is_first_bytes_http_request(request_bytes: bytes) -> bool:
|
|
16
|
+
"""
|
|
17
|
+
Function to check if the first bytes are HTTP request or not.
|
|
18
|
+
"""
|
|
19
|
+
http_request_methods_list: list = get_request_methods()
|
|
20
|
+
http_request_methods_list = [method.encode() for method in http_request_methods_list]
|
|
21
|
+
|
|
22
|
+
# If the first bytes are HTTP request, then the first word should be one of the HTTP request methods.
|
|
23
|
+
for method in http_request_methods_list:
|
|
24
|
+
if request_bytes.startswith(method):
|
|
25
|
+
return True
|
|
26
|
+
return False
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def is_first_bytes_http_response(response_bytes: bytes) -> bool:
|
|
30
|
+
"""
|
|
31
|
+
Function to check if the first bytes are HTTP response or not.
|
|
32
|
+
"""
|
|
33
|
+
http_response_beginning: bytes = b'HTTP/'
|
|
34
|
+
|
|
35
|
+
# If the first bytes are HTTP response, then the first word should be 'HTTP/'.
|
|
36
|
+
if response_bytes.startswith(http_response_beginning):
|
|
37
|
+
return True
|
|
38
|
+
return False
|
|
39
|
+
|
|
40
|
+
|
|
7
41
|
class HTTPRequestParse(BaseHTTPRequestHandler):
|
|
8
42
|
"""
|
|
9
43
|
The class will parse HTTP requests.
|
|
@@ -103,45 +137,47 @@ class HTTPRequestParse(BaseHTTPRequestHandler):
|
|
|
103
137
|
client_message.request_raw_decoded = request_decoded
|
|
104
138
|
"""
|
|
105
139
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
self.rfile = BytesIO(self.request_bytes)
|
|
110
|
-
self.raw_requestline = self.rfile.readline()
|
|
111
|
-
self.error_code = self.error_message = None
|
|
112
|
-
self.parse_request()
|
|
113
|
-
|
|
114
|
-
# Before checking for body, we need to make sure that ".headers" property exists, if not, return empty values.
|
|
115
|
-
if hasattr(self, 'headers'):
|
|
116
|
-
# The "body" of request is in the 'Content-Length' key. If it exists in "headers" - get the body
|
|
117
|
-
if 'Content-Length' in self.headers.keys():
|
|
118
|
-
# "self.headers.get('Content-Length')" returns number in string format, "int" converts it to integer
|
|
119
|
-
self.content_length = int(self.headers.get('Content-Length'))
|
|
120
|
-
self.body = self.rfile.read(self.content_length)
|
|
121
|
-
|
|
122
|
-
# Examples:
|
|
123
|
-
# Getting path: self.path
|
|
124
|
-
# Getting Request Version: self.request_version
|
|
125
|
-
# Getting specific header: self.headers['host']
|
|
126
|
-
|
|
127
|
-
# If there's any error in HTTP parsing
|
|
128
|
-
if self.error_message:
|
|
129
|
-
# If error status is "BAD_REQUEST"
|
|
130
|
-
if self.error_message.startswith("Bad request"):
|
|
131
|
-
# If it's 'Bad request' this is not HTTP request, so we can
|
|
132
|
-
# continue the execution and parse the code as NON-HTTP Request.
|
|
133
|
-
# Currently, seen 'Bad request syntax' and 'Bad request version'.
|
|
134
|
-
error = f"HTTP Request Parsing: Not HTTP request: {self.error_message}"
|
|
135
|
-
is_http = False
|
|
136
|
-
else:
|
|
137
|
-
error = f"HTTP Request Parsing: HTTP Request with Script Undocumented ERROR: {self.error_message}"
|
|
138
|
-
is_http = False
|
|
139
|
-
# If there's no error at all in HTTP Parsing, then it's fine HTTP Request
|
|
140
|
+
if self.request_bytes is None or not is_first_bytes_http_request(self.request_bytes):
|
|
141
|
+
error = "HTTP Request Parsing: Not HTTP request by first bytes."
|
|
142
|
+
is_http = False
|
|
140
143
|
else:
|
|
141
|
-
|
|
142
|
-
|
|
144
|
+
error: str = str()
|
|
145
|
+
|
|
146
|
+
self.rfile = BytesIO(self.request_bytes)
|
|
147
|
+
self.raw_requestline = self.rfile.readline()
|
|
148
|
+
self.error_code = self.error_message = None
|
|
149
|
+
self.parse_request()
|
|
150
|
+
|
|
151
|
+
# Before checking for body, we need to make sure that ".headers" property exists, if not, return empty values.
|
|
152
|
+
if hasattr(self, 'headers'):
|
|
153
|
+
# The "body" of request is in the 'Content-Length' key. If it exists in "headers" - get the body
|
|
154
|
+
if 'Content-Length' in self.headers.keys():
|
|
155
|
+
# "self.headers.get('Content-Length')" returns number in string format, "int" converts it to integer
|
|
156
|
+
self.content_length = int(self.headers.get('Content-Length'))
|
|
157
|
+
self.body = self.rfile.read(self.content_length)
|
|
158
|
+
|
|
159
|
+
# Examples:
|
|
160
|
+
# Getting path: self.path
|
|
161
|
+
# Getting Request Version: self.request_version
|
|
162
|
+
# Getting specific header: self.headers['host']
|
|
143
163
|
|
|
144
|
-
|
|
164
|
+
# If there's any error in HTTP parsing
|
|
165
|
+
if self.error_message:
|
|
166
|
+
# If error status is "BAD_REQUEST"
|
|
167
|
+
if self.error_message.startswith("Bad request"):
|
|
168
|
+
# If it's 'Bad request' this is not HTTP request, so we can
|
|
169
|
+
# continue the execution and parse the code as NON-HTTP Request.
|
|
170
|
+
# Currently, seen 'Bad request syntax' and 'Bad request version'.
|
|
171
|
+
error = f"HTTP Request Parsing: Not HTTP request: {self.error_message}"
|
|
172
|
+
is_http = False
|
|
173
|
+
else:
|
|
174
|
+
error = f"HTTP Request Parsing: HTTP Request with Script Undocumented ERROR: {self.error_message}"
|
|
175
|
+
is_http = False
|
|
176
|
+
# If there's no error at all in HTTP Parsing, then it's fine HTTP Request
|
|
177
|
+
else:
|
|
178
|
+
is_http = True
|
|
179
|
+
|
|
180
|
+
return self, is_http, error
|
|
145
181
|
|
|
146
182
|
|
|
147
183
|
class FakeSocket:
|
|
@@ -171,49 +207,54 @@ class HTTPResponseParse:
|
|
|
171
207
|
|
|
172
208
|
self.error = None
|
|
173
209
|
self.source = None
|
|
174
|
-
self.
|
|
210
|
+
self.response_raw_parsed = None
|
|
175
211
|
self.is_http: bool = False
|
|
176
212
|
|
|
177
213
|
def parse(self):
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
# Initializing HTTPResponse class with the FakeSocket with response_raw_bytes as input.
|
|
182
|
-
# noinspection PyTypeChecker
|
|
183
|
-
self.response_raw_decoded = HTTPResponse(self.source)
|
|
184
|
-
|
|
185
|
-
# Try to parse HTTP Response.
|
|
186
|
-
try:
|
|
187
|
-
self.response_raw_decoded.begin()
|
|
188
|
-
self.is_http = True
|
|
189
|
-
# If there were problems with the status line.
|
|
190
|
-
except http.client.BadStatusLine:
|
|
191
|
-
self.error = "HTTP Response Parsing: Not a valid HTTP Response: Bad Status Line."
|
|
214
|
+
if self.response_raw_bytes is None or not is_first_bytes_http_response(self.response_raw_bytes):
|
|
215
|
+
self.error = "HTTP Response Parsing: Not a valid HTTP Response by first bytes."
|
|
192
216
|
self.is_http = False
|
|
217
|
+
self.response_raw_parsed = None
|
|
218
|
+
else:
|
|
219
|
+
# Assigning FakeSocket with response_raw_bytes.
|
|
220
|
+
self.source = FakeSocket(self.response_raw_bytes)
|
|
193
221
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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
|
|
222
|
+
# Initializing HTTPResponse class with the FakeSocket with response_raw_bytes as input.
|
|
223
|
+
# noinspection PyTypeChecker
|
|
224
|
+
self.response_raw_parsed = HTTPResponse(self.source)
|
|
203
225
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
226
|
+
# Try to parse HTTP Response.
|
|
227
|
+
try:
|
|
228
|
+
self.response_raw_parsed.begin()
|
|
229
|
+
self.is_http = True
|
|
230
|
+
# If there were problems with the status line.
|
|
231
|
+
except http.client.BadStatusLine:
|
|
232
|
+
self.error = "HTTP Response Parsing: Not a valid HTTP Response: Bad Status Line."
|
|
233
|
+
self.is_http = False
|
|
234
|
+
|
|
235
|
+
header_exists: bool = False
|
|
236
|
+
if (self.response_raw_parsed is not None and hasattr(self.response_raw_parsed, 'headers') and
|
|
237
|
+
self.response_raw_parsed.headers is not None):
|
|
238
|
+
header_exists = True
|
|
239
|
+
|
|
240
|
+
if header_exists and self.response_raw_parsed.headers.defects:
|
|
241
|
+
self.error = f"HTTP Response Parsing: Not a valid HTTP Response: Some defects in headers: " \
|
|
242
|
+
f"{self.response_raw_parsed.headers.defects}"
|
|
243
|
+
self.is_http = False
|
|
244
|
+
|
|
245
|
+
if self.is_http:
|
|
246
|
+
# Before checking for body, we need to make sure that ".headers" property exists,
|
|
247
|
+
# if not, return empty values.
|
|
248
|
+
self.response_raw_parsed.content_length = None
|
|
249
|
+
self.response_raw_parsed.body = None
|
|
250
|
+
if header_exists and 'Content-Length' in self.response_raw_parsed.headers.keys():
|
|
251
|
+
# The "body" of response is in the 'Content-Length' key. If it exists in "headers" - get the body.
|
|
252
|
+
# "self.response_raw_decoded.headers.get('Content-Length')" returns number in string format,
|
|
253
|
+
# "int" converts it to integer.
|
|
254
|
+
self.response_raw_parsed.content_length = int(self.response_raw_parsed.headers.get('Content-Length'))
|
|
255
|
+
# Basically we need to extract only the number of bytes specified in 'Content-Length' from the end
|
|
256
|
+
# of the response that we received.
|
|
257
|
+
# self.response_raw_bytes[-23:]
|
|
258
|
+
self.response_raw_parsed.body = self.response_raw_bytes[-self.response_raw_parsed.content_length:]
|
|
259
|
+
|
|
260
|
+
return self.response_raw_parsed, self.is_http, self.error
|