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
@@ -2,7 +2,7 @@ from datetime import datetime
2
2
 
3
3
  from ..wrappers.socketw import receiver, sender, socket_client, base
4
4
  from ..http_parse import HTTPRequestParse, HTTPResponseParse
5
- from ..basics.threads import current_thread_id
5
+ from ..basics import threads, tracebacks
6
6
  from ..print_api import print_api
7
7
 
8
8
  from .message import ClientMessage
@@ -10,9 +10,8 @@ from .initialize_engines import assign_class_by_domain
10
10
  from . import config_static
11
11
 
12
12
 
13
- # Thread function on client connect.
14
13
  def thread_worker_main(
15
- function_client_socket_object,
14
+ client_socket,
16
15
  process_commandline: str,
17
16
  is_tls: bool,
18
17
  tls_type: str,
@@ -24,7 +23,7 @@ def thread_worker_main(
24
23
  reference_module
25
24
  ):
26
25
  def output_statistics_csv_row():
27
- # If there is no '.code' attribute in HTTPResponse, this means that this is not a HTTP message, so there is no
26
+ # If there is no '.code' attribute in HTTPResponse, this means that this is not an HTTP message, so there is no
28
27
  # status code.
29
28
  try:
30
29
  http_status_code: str = ','.join([str(x.code) for x in client_message.response_list_of_raw_decoded])
@@ -45,6 +44,15 @@ def thread_worker_main(
45
44
 
46
45
  response_size_bytes = ','.join([str(len(x)) for x in client_message.response_list_of_raw_bytes])
47
46
 
47
+ if statistics_error_list and len(statistics_error_list) > 1:
48
+ error_string = '||'.join(statistics_error_list)
49
+ elif statistics_error_list and len(statistics_error_list) == 1:
50
+ error_string = statistics_error_list[0]
51
+ elif not statistics_error_list:
52
+ error_string = str()
53
+ else:
54
+ raise ValueError(f"Error in statistics error list. Values: {statistics_error_list}")
55
+
48
56
  statistics_writer.write_row(
49
57
  host=client_message.server_name,
50
58
  tls_type=tls_type,
@@ -58,75 +66,124 @@ def thread_worker_main(
58
66
  response_size_bytes=response_size_bytes,
59
67
  recorded_file_path=client_message.recorded_file_path,
60
68
  process_cmd=process_commandline,
61
- error=str())
69
+ error=error_string
70
+ )
71
+
72
+ def record_and_statistics_write():
73
+ # If recorder wasn't executed before, then execute it now
74
+ if config_static.LogRec.enable_request_response_recordings_in_logs:
75
+ recorded_file = recorder(
76
+ class_client_message=client_message, record_path=config_static.LogRec.recordings_path).record()
77
+ client_message.recorded_file_path = recorded_file
78
+
79
+ # Save statistics file.
80
+ output_statistics_csv_row()
81
+
82
+ def parse_http():
83
+ nonlocal error_message
84
+ # Parsing the raw bytes as HTTP.
85
+ request_decoded, is_http_request, request_parsing_info, request_parsing_error = (
86
+ HTTPRequestParse(client_message.request_raw_bytes).parse())
87
+
88
+ if is_http_request:
89
+ client_message.protocol = 'HTTP'
90
+ client_message.request_raw_decoded = request_decoded
91
+ print_api(request_parsing_info, logger=network_logger, logger_method='info')
92
+ network_logger.info(f"Method: {request_decoded.command}")
93
+ network_logger.info(f"Path: {request_decoded.path}")
94
+ # If there was error - the request is really HTTP, but there's a problem with its structure.
95
+ else:
96
+ # client_message.error = request_parsing_error
97
+ print_api(request_parsing_error, logger=network_logger, logger_method='error', color='yellow')
98
+ # It doesn't matter if we have HTTP Parsing error, since the request may not be really HTTP, so it is OK
99
+ # not to log it into statistics.
100
+ # statistics_error_list.append(error_message)
101
+
102
+ def finish_thread():
103
+ # At this stage there could be several times that the same socket was used to the service server - we need to
104
+ # close this socket as well if it still opened.
105
+ if service_client:
106
+ if service_client.socket_instance:
107
+ service_client.close_socket()
108
+
109
+ # If client socket is still opened - close
110
+ if client_socket:
111
+ client_socket.close()
112
+ network_logger.info(f"Closed client socket [{client_message.client_ip}:{client_message.source_port}]...")
113
+
114
+ network_logger.info("Thread Finished. Will continue listening on the Main thread")
115
+
116
+ # Building client message object before the loop only for any exception to occurs, since we write it to
117
+ # recording file in its current state.
118
+ client_message: ClientMessage = ClientMessage()
119
+ # 'recorded' boolean is needed only to write the message in case of exception in the loop or before that.
120
+ recorded: bool = False
121
+ statistics_error_list: list[str] = list()
122
+
123
+ # Only protocols that are encrypted with TLS have the server name attribute.
124
+ if is_tls:
125
+ # Get current destination domain
126
+ server_name = client_socket.server_hostname
127
+ # client_message.server_name = domain_from_dns
128
+ # If the protocol is not TLS, then we'll use the domain from the DNS.
129
+ else:
130
+ server_name = domain_from_dns
131
+ client_message.server_name = server_name
132
+
133
+ thread_id = threads.current_thread_id()
134
+ client_message.thread_id = thread_id
135
+
136
+ # Loading parser by domain, if there is no parser for current domain - general reference parser is loaded.
137
+ # These should be outside any loop and initialized only once entering the thread.
138
+ parser, responder, recorder = assign_class_by_domain(
139
+ engines_usage=config_static.TCPServer.engines_usage,
140
+ engines_list=engines_list,
141
+ message_domain_name=server_name,
142
+ reference_module=reference_module,
143
+ logger=network_logger
144
+ )
62
145
 
63
146
  try:
64
- # Defining variables before assignment
65
- function_recorded: bool = False
66
- client_message: ClientMessage = ClientMessage()
67
- request_decoded = None
68
- service_client = None
147
+ client_ip, source_port = client_socket.getpeername()
148
+ client_message.client_ip = client_ip
149
+ client_message.source_port = source_port
69
150
 
70
- # Getting thread ID of the current thread and putting to the client message class
71
- client_message.thread_id = current_thread_id()
72
- # Get client ip and port
73
- client_message.client_ip, client_message.source_port = function_client_socket_object.getpeername()
74
- # Get destination port
75
- client_message.destination_port = function_client_socket_object.getsockname()[1]
76
- # Putting the process command line.
77
- client_message.process_name = process_commandline
78
-
79
- # Only protocols that are encrypted with TLS have the server name attribute.
80
- if is_tls:
81
- # Get current destination domain
82
- client_message.server_name = function_client_socket_object.server_hostname
83
- # client_message.server_name = domain_from_dns
84
- # If the protocol is not TLS, then we'll use the domain from the DNS.
85
- else:
86
- client_message.server_name = domain_from_dns
87
-
88
- network_logger.info(f"Thread Created - Client [{client_message.client_ip}:{client_message.source_port}] | "
89
- f"Destination service: [{client_message.server_name}:{client_message.destination_port}]")
90
-
91
- # Loading parser by domain, if there is no parser for current domain - general reference parser is loaded.
92
- # These should be outside any loop and initialized only once entering the thread.
93
- parser, responder, recorder = assign_class_by_domain(
94
- engines_usage=config_static.TCPServer.engines_usage,
95
- engines_list=engines_list,
96
- message_domain_name=client_message.server_name,
97
- reference_module=reference_module,
98
- logger=network_logger
99
- )
151
+ destination_port = client_socket.getsockname()[1]
152
+ client_message.destination_port = destination_port
100
153
 
101
- # Defining client connection boolean variable to enter the loop
102
- client_connection_boolean: bool = True
154
+ network_logger.info(f"Thread Created - Client [{client_ip}:{source_port}] | "
155
+ f"Destination service: [{server_name}:{destination_port}]")
103
156
 
157
+ service_client = None
104
158
  # Loop while received message is not empty, if so, close socket, since other side already closed.
105
- while client_connection_boolean:
106
- # Don't forget that 'ClientMessage' object is being reused at this step.
107
- # Meaning, that each list / dictionary that is used to update at
108
- # this loop section needs to be reinitialized.
109
- # Add any variables that need reinitializing in the 'ClientMessage' class 'reinitialize' function.
110
- client_message.reinitialize()
111
-
112
- # Initialize statistics_dict for the same reason as 'client_message.reinitialize()'.
113
- # statistics_dict: dict = dict()
114
- # statistics_dict['host'] = client_message.server_name
115
- # statistics_dict['path'] = str()
116
- # statistics_dict['status_code'] = str()
117
- # statistics_dict['command'] = str()
118
- # statistics_dict['request_time_sent'] = str()
119
- # statistics_dict['request_size_bytes'] = str()
120
- # # statistics_dict['response_time_sent'] = str()
121
- # statistics_dict['response_size_bytes'] = str()
122
- # statistics_dict['file_path'] = str()
123
- # statistics_dict['process_cmd'] = str()
124
- # statistics_dict['error'] = str()
125
-
126
- network_logger.info("Initializing Receiver")
159
+ # noinspection PyTypeChecker
160
+ cycle_count: int = None
161
+ while True:
162
+ # If cycle count is None, then it's the first cycle, else it's not.
163
+ # The cycle_count should be added 1 in the beginning of each cycle, and not in the end, since not always
164
+ # the cycle will be executed till the end.
165
+ if cycle_count is None:
166
+ cycle_count = 0
167
+ else:
168
+ cycle_count += 1
169
+
170
+ recorded: bool = False
171
+ statistics_error_list: list[str] = list()
172
+
173
+ client_message = ClientMessage()
174
+ client_message.thread_id = thread_id
175
+ client_message.client_ip = client_ip
176
+ client_message.source_port = source_port
177
+ client_message.destination_port = destination_port
178
+ client_message.process_name = process_commandline
179
+ client_message.server_name = server_name
180
+ # Getting current time of message received from client.
181
+ client_message.request_time_received = datetime.now()
182
+
183
+ network_logger.info(f"Initializing Receiver on cycle: {str(cycle_count+1)}")
127
184
  # Getting message from the client over the socket using specific class.
128
185
  client_received_raw_data = receiver.Receiver(
129
- ssl_socket=function_client_socket_object, logger=network_logger).receive()
186
+ ssl_socket=client_socket, logger=network_logger).receive()
130
187
 
131
188
  # If the message is empty, then the connection was closed already by the other side,
132
189
  # so we can close the socket as well.
@@ -134,70 +191,21 @@ def thread_worker_main(
134
191
  if client_received_raw_data:
135
192
  # Putting the received message to the aggregating message class.
136
193
  client_message.request_raw_bytes = client_received_raw_data
137
- # Getting current time of message received from client.
138
- client_message.request_time_received = datetime.now()
139
-
140
- # HTTP Parsing section =================================================================================
141
- # Parsing the raw bytes as HTTP.
142
- try:
143
- request_decoded = HTTPRequestParse(client_message.request_raw_bytes)
144
- except Exception:
145
- message = "There was an exception in HTTP Parsing module!"
146
- print_api(
147
- message, error_type=True, logger=network_logger, logger_method='critical',
148
- traceback_string=True)
149
- # Socket connection can be closed since we have a problem in current thread and break the loop
150
- client_connection_boolean = False
151
- break
152
-
153
- # Getting the status of http parsing
154
- request_is_http, http_parsing_reason, http_parsing_error = request_decoded.check_if_http()
155
194
 
156
- # Currently, we don't care if it's HTTP. If there was no error we can continue. Just log the reason.
157
- if not http_parsing_error:
158
- network_logger.info(http_parsing_reason)
159
- # If there was error - the request is really HTTP, but there's a problem with its structure.
160
- # So, we'll stop the loop.
161
- else:
162
- client_message.error = http_parsing_reason
163
- network_logger.critical(client_message.error)
164
- break
165
-
166
- # If the request is HTTP protocol.
167
- if request_is_http:
168
- client_message.protocol = 'HTTP'
169
- network_logger.info(f"Method: {request_decoded.command}")
170
- network_logger.info(f"Path: {request_decoded.path}")
171
- # statistics.dict['path'] = request_decoded.path
172
- client_message.request_raw_decoded = request_decoded
173
- # HTTP Parsing section EOF =============================================================================
174
-
175
- # Catching exceptions in the parser
176
- try:
177
- parser(client_message).parse()
178
- except Exception:
179
- message = "Exception in Parser"
180
- print_api(
181
- message, error_type=True, logger=parser.logger, logger_method='critical',
182
- traceback_string=True)
183
- print_api(
184
- message, error_type=True, logger=network_logger, logger_method='critical',
185
- traceback_string=True)
186
- # At this point we can pass the exception and continue the script.
195
+ parse_http()
196
+ if client_message.protocol != 'HTTP':
187
197
  pass
188
- # Socket connection can be closed since we have a problem in current thread and break the loop
189
- client_connection_boolean = False
190
- break
191
198
 
192
- # Converting body parsed to string, since there is no strict rule for the parameter to be string.
193
- # Still going to put exception on it, since it is not critical for the server.
194
- # Won't even log the exception.
195
- try:
196
- parser.logger.info(f"{str(client_message.request_body_parsed)[0: 100]}...")
197
- except Exception:
198
- pass
199
+ # Custom parser, should parse HTTP body or the whole message if not HTTP.
200
+ parser_instance = parser(client_message)
201
+ parser_instance.parse()
202
+
203
+ # Converting body parsed to string on logging, since there is no strict rule for the parameter
204
+ # to be string.
205
+ parser_instance.logger.info(f"{str(client_message.request_body_parsed)[0: 100]}...")
199
206
 
200
207
  # If we're in response mode, execute responder.
208
+ response_raw_bytes = None
201
209
  if config_static.TCPServer.server_response_mode:
202
210
  # Since we're in response mode, we'll record the request anyway, after the responder did its job.
203
211
  client_message.info = "In Server Response Mode"
@@ -206,20 +214,7 @@ def thread_worker_main(
206
214
  # new entries for empty list.
207
215
  client_message.response_list_of_raw_bytes = list()
208
216
  # Creating response for parsed message and printing
209
- try:
210
- responder.create_response(client_message)
211
- except Exception:
212
- message = "Exception in Responder"
213
- print_api(
214
- message, error_type=True, logger=responder.logger, logger_method='critical',
215
- traceback_string=True)
216
- print_api(
217
- message, error_type=True, logger=network_logger, logger_method='critical',
218
- traceback_string=True)
219
- pass
220
- # Socket connection can be closed since we have a problem in current thread and break the loop.
221
- client_connection_boolean = False
222
- break
217
+ responder.create_response(client_message)
223
218
 
224
219
  # Output first 100 characters of all the responses in the list.
225
220
  for response_raw_bytes in client_message.response_list_of_raw_bytes:
@@ -254,8 +249,11 @@ def thread_worker_main(
254
249
  # be passed.
255
250
  # If there was connection error or socket close, then "ssl_socket" of the "service_client"
256
251
  # will be empty.
257
- response_raw_bytes, client_message.error, client_message.server_ip, service_ssl_socket =\
258
- service_client.send_receive_to_service(client_message.request_raw_bytes)
252
+ response_raw_bytes, client_message.error, client_message.server_ip, service_ssl_socket = (
253
+ service_client.send_receive_to_service(client_message.request_raw_bytes))
254
+
255
+ if client_message.error is not None:
256
+ statistics_error_list.append(client_message.error)
259
257
 
260
258
  # Since we need a list for raw bytes, we'll add the 'response_raw_bytes' to our list object.
261
259
  # But we need to re-initiate it first.
@@ -265,107 +263,65 @@ def thread_worker_main(
265
263
  client_message.response_list_of_raw_decoded = list()
266
264
  # Make HTTP Response parsing only if there was response at all.
267
265
  if response_raw_bytes:
268
- response_raw_decoded = HTTPResponseParse(response_raw_bytes).response_raw_decoded
269
- client_message.response_list_of_raw_decoded.append(response_raw_decoded)
266
+ response_raw_decoded, is_http_response, response_parsing_error = (
267
+ HTTPResponseParse(response_raw_bytes).parse())
268
+
269
+ if is_http_response:
270
+ client_message.response_list_of_raw_decoded.append(response_raw_decoded)
271
+ else:
272
+ client_message.response_list_of_raw_decoded.append(None)
270
273
 
271
274
  # So if the socket was closed and there was an error we can break the loop
272
275
  if not service_ssl_socket:
273
276
  break
274
277
 
275
- # This is the point after the response mode check was finished.
276
- # Recording the message, doesn't matter what type of mode this is.
277
- if config_static.LogRec.enable_request_response_recordings_in_logs:
278
- try:
279
- recorded_file = recorder(class_client_message=client_message,
280
- record_path=config_static.LogRec.recordings_path).record()
281
- client_message.recorded_file_path = recorded_file
282
- except Exception:
283
- message = "Exception in Recorder"
284
- print_api(
285
- message, error_type=True, logger=recorder.logger, logger_method='critical',
286
- traceback_string=True)
287
- print_api(
288
- message, error_type=True, logger=network_logger, logger_method='critical',
289
- traceback_string=True)
290
-
291
- function_recorded = True
292
-
293
- # Save statistics file.
294
- output_statistics_csv_row()
295
-
296
- try:
297
- # If there is a response, then send it.
298
- if response_raw_bytes:
299
- # Sending response/s to client no matter if in record mode or not.
300
- network_logger.info(
301
- f"Sending messages to client: {len(client_message.response_list_of_raw_bytes)}")
302
- function_data_sent = None
303
-
304
- # Iterate through the list of byte responses.
305
- for response_raw_bytes in client_message.response_list_of_raw_bytes:
306
- function_data_sent = sender.Sender(
307
- ssl_socket=function_client_socket_object, class_message=response_raw_bytes,
308
- logger=network_logger).send()
309
-
310
- # If there was problem with sending data, we'll break current loop.
311
- if not function_data_sent:
312
- break
313
- # If there is no response, close the socket.
314
- else:
315
- function_data_sent = None
316
- network_logger.info(f"Response empty, nothing to send to client.")
317
- except Exception:
318
- message = "Not sending anything to the client, since there is no response available"
319
- print_api(
320
- message, error_type=True, logger=network_logger, logger_method='critical',
321
- traceback_string=True)
322
- # Pass the exception
323
- pass
324
- # Break the while loop
325
- break
278
+ # If there is a response, then send it.
279
+ if response_raw_bytes:
280
+ # Sending response/s to client no matter if in record mode or not.
281
+ network_logger.info(
282
+ f"Sending messages to client: {len(client_message.response_list_of_raw_bytes)}")
326
283
 
327
- # If there was problem with sending data, we'll break the while loop
328
- if not function_data_sent:
284
+ # Iterate through the list of byte responses.
285
+ for response_raw_bytes in client_message.response_list_of_raw_bytes:
286
+ error_on_send: str = sender.Sender(
287
+ ssl_socket=client_socket, class_message=response_raw_bytes,
288
+ logger=network_logger).send()
289
+
290
+ # If there was problem with sending data, we'll break current loop.
291
+ if error_on_send:
292
+ statistics_error_list.append(error_on_send)
293
+ break
294
+ # If response from server came back empty, then the server has closed the connection,
295
+ # we will do the same.
296
+ else:
297
+ network_logger.info(f"Response empty, nothing to send to client.")
329
298
  break
299
+
300
+ record_and_statistics_write()
301
+ recorded = True
330
302
  else:
331
- # Ending the while loop, basically we can use 'break'
332
- client_connection_boolean = False
333
- # We don't need to record empty message so setting the recorder state to recorded
334
- function_recorded = True
303
+ # If it's the first cycle we will record the message from the client if it came empty.
304
+ if cycle_count == 0:
305
+ record_and_statistics_write()
306
+
307
+ # In other cases, we'll just break the loop, since empty message means that the other side closed the
308
+ # connection.
309
+ recorded = True
310
+ break
311
+
312
+ finish_thread()
313
+ except Exception as e:
314
+ exception_message = tracebacks.get_as_string(one_line=True)
315
+ error_message = f'Socket Thread [{str(thread_id)}] Exception: {exception_message}'
316
+ print_api(error_message, logger_method='critical', logger=network_logger)
317
+ statistics_error_list.append(error_message)
335
318
 
336
319
  # === At this point while loop of 'client_connection_boolean' was broken =======================================
337
320
  # If recorder wasn't executed before, then execute it now
338
- if not function_recorded:
339
- if config_static.LogRec.enable_request_response_recordings_in_logs:
340
- try:
341
- recorded_file = recorder(
342
- class_client_message=client_message, record_path=config_static.LogRec.recordings_path).record()
343
- client_message.recorded_file_path = recorded_file
344
- except Exception:
345
- message = "Exception in Recorder"
346
- print_api(
347
- message, error_type=True, logger=recorder.logger, logger_method='critical',
348
- traceback_string=True)
349
- print_api(
350
- message, error_type=True, logger=network_logger, logger_method='critical',
351
- traceback_string=True)
352
-
353
- # Save statistics file.
354
- output_statistics_csv_row()
355
-
356
- # At this stage there could be several times that the same socket was used to the service server - we need to
357
- # close this socket as well if it still opened.
358
- if service_client:
359
- if service_client.socket_instance:
360
- service_client.close_socket()
321
+ if not recorded:
322
+ record_and_statistics_write()
361
323
 
362
- # If client socket is still opened - close
363
- if function_client_socket_object:
364
- function_client_socket_object.close()
365
- network_logger.info(f"Closed client socket [{client_message.client_ip}:{client_message.source_port}]...")
324
+ finish_thread()
366
325
 
367
- network_logger.info("Thread Finished. Will continue listening on the Main thread")
368
- except Exception:
369
- message = "Undocumented exception in thread worker"
370
- print_api(
371
- message, error_type=True, logger=network_logger, logger_method='critical', traceback_string=True)
326
+ # After the socket clean up, we will still raise the exception to the main thread.
327
+ raise e
@@ -12,7 +12,4 @@ class ParserParent:
12
12
  # This is general parser, so we don't parse anything and 'request_body_parsed' gets empty byte string.
13
13
  self.class_client_message.request_body_parsed = b''
14
14
 
15
- try:
16
- self.logger.info(f"Parsed: {self.class_client_message.request_body_parsed[0: 100]}...")
17
- except Exception:
18
- pass
15
+ self.logger.info(f"Parsed: {self.class_client_message.request_body_parsed[0: 100]}...")
@@ -1,5 +1,6 @@
1
1
  import os
2
2
  from datetime import datetime
3
+ import json
3
4
 
4
5
  from ...shared_functions import build_module_names, create_custom_logger, get_json
5
6
  from ... import message, recs_files
@@ -87,10 +88,13 @@ class RecorderParent:
87
88
  # Convert the requests and responses to hex.
88
89
  self.convert_messages()
89
90
  # Get the message in dict / JSON format
90
- record_message = get_json(self.class_client_message)
91
+ # record_message = get_json(self.class_client_message)
92
+ record_message_dict: dict = dict(self.class_client_message)
93
+ recorded_message_json_string = json.dumps(record_message_dict)
91
94
 
92
95
  # Since we already dumped the object to dictionary string, we'll just save the object to regular file.
93
- file_io.write_file(record_message, self.record_file_path, enable_long_file_path=True)
96
+ file_io.write_file(
97
+ recorded_message_json_string, self.record_file_path, enable_long_file_path=True, **{'logger': self.logger})
94
98
 
95
99
  self.logger.info(f"Recorded to file: {self.record_file_path}")
96
100
 
@@ -1,15 +1,21 @@
1
+ from datetime import datetime
2
+ from typing import Union
3
+
4
+ from .. import http_parse
5
+ from ..basics import dicts
6
+
7
+
1
8
  class ClientMessage:
2
9
  """ A class that will store all the message details from the client """
3
10
  def __init__(self):
4
11
  self.request_raw_bytes: bytearray = bytearray()
5
- self.request_time_received = None
6
- self.request_raw_decoded = None
12
+ # noinspection PyTypeChecker
13
+ self.request_time_received: datetime = None
14
+ self.request_raw_decoded: Union[http_parse.HTTPRequestParse, any] = None
7
15
  self.request_body_parsed = None
8
16
  self.request_raw_hex: hex = None
9
- # self.response_raw_bytes: bytearray = bytearray()
10
17
  self.response_list_of_raw_bytes: list = list()
11
18
  self.response_list_of_raw_decoded: list = list()
12
- # self.response_raw_hex: hex = None
13
19
  self.response_list_of_raw_hex: list = list()
14
20
  self.server_name: str = str()
15
21
  self.server_ip: str = str()
@@ -23,19 +29,22 @@ class ClientMessage:
23
29
  self.protocol: str = str()
24
30
  self.recorded_file_path: str = str()
25
31
 
26
- def reinitialize(self) -> None:
27
- """
28
- 'ClientMessage' is being reused, since connection is still established to the server and new requests
29
- are being processed on the same socket, so we need to reinitialize variables that are being updated, like
30
- lists and dictionaries. Added the rest pf the variables that are repopulated to be on the safe side.
31
- :return:
32
- """
33
-
34
- self.request_raw_bytes = bytearray()
35
- self.request_time_received = None
36
- self.request_raw_decoded = None
37
- self.request_body_parsed = None
38
- self.response_list_of_raw_bytes = list()
39
- self.request_raw_hex = None
40
- self.response_list_of_raw_hex = list()
41
- self.response_list_of_raw_decoded = list()
32
+ def __iter__(self):
33
+ # __dict__ returns a dictionary containing the instance's attributes
34
+ for key, value in self.__dict__.items():
35
+ if key == 'request_raw_bytes':
36
+ value = str(value)
37
+ elif key == 'request_time_received':
38
+ value = value.strftime('%Y-%m-%d-%H:%M:%S.%f')
39
+ elif key == 'request_raw_decoded':
40
+ if isinstance(value, http_parse.HTTPRequestParse):
41
+ value = dicts.convert_complex_object_to_dict(value)
42
+ else:
43
+ value = str(value)
44
+ elif key == 'request_body_parsed':
45
+ value = dicts.convert_complex_object_to_dict(value)
46
+ elif key == 'response_list_of_raw_bytes':
47
+ value = [str(bytes_response) for bytes_response in value]
48
+ elif key == 'response_list_of_raw_decoded':
49
+ value = [dicts.convert_complex_object_to_dict(complex_response) for complex_response in value]
50
+ yield key, value