atomicshop 3.3.29__py3-none-any.whl → 3.4.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 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__ = '3.3.29'
4
+ __version__ = '3.4.1'
@@ -2,6 +2,8 @@ from datetime import datetime
2
2
  import threading
3
3
  import queue
4
4
  import socket
5
+ import ssl
6
+ from typing import Literal
5
7
 
6
8
  from ..wrappers.socketw import receiver, sender, socket_client, base
7
9
  from .. import websocket_parse, ip_addresses
@@ -187,13 +189,19 @@ def thread_worker_main(
187
189
  def create_requester_request(
188
190
  client_message: ClientMessage,
189
191
  sending_socket: socket.socket
190
- ) -> list[bytes]:
192
+ ) -> tuple[bytes, bool]:
193
+ request_received_raw: bytes = client_message.request_raw_bytes
191
194
  request_custom_raw: bytes = requester.create_request(client_message, sending_socket=sending_socket)
192
195
 
193
- # Output first 100 characters of the request.
194
- requester.logger.info(f"{request_custom_raw[0: 100]}...")
196
+ if request_custom_raw is None or request_received_raw == request_custom_raw:
197
+ is_requester_worked: bool = False
198
+ else:
199
+ is_requester_worked: bool = True
200
+
201
+ # Output first 100 characters of the request.
202
+ requester.logger.info(f"{request_custom_raw[0: 100]}...")
195
203
 
196
- return [request_custom_raw]
204
+ return request_custom_raw, is_requester_worked
197
205
 
198
206
  def create_responder_response(client_message: ClientMessage) -> list[bytes]:
199
207
  if client_message.action == 'service_connect':
@@ -335,6 +343,260 @@ def thread_worker_main(
335
343
 
336
344
  return client_message
337
345
 
346
+ def receive_send_service_connect(
347
+ client_connection_message: ClientMessage,
348
+ sending_socket: ssl.SSLSocket | socket.socket
349
+ ) -> Literal['continue', 'return'] | None:
350
+
351
+ nonlocal exception_or_close_in_receiving_thread
352
+
353
+ client_message = client_connection_message
354
+
355
+ bytes_to_send_list: list[bytes] = create_responder_response(client_message)
356
+ print_api(f"Got responses from connect responder, count: [{len(bytes_to_send_list)}]",
357
+ logger=network_logger, logger_method='info')
358
+
359
+ # If the client message is the connection message, then we'll skip to the next iteration.
360
+ if not bytes_to_send_list:
361
+ return 'continue'
362
+
363
+ # is_socket_closed: bool = False
364
+ error_on_send: str = str()
365
+ for bytes_to_send_single in bytes_to_send_list:
366
+ client_message.reinitialize_dynamic_vars()
367
+ client_message.timestamp = datetime.now()
368
+ client_message.response_raw_bytes = bytes_to_send_single
369
+ client_message.action = 'service_responder'
370
+ record_and_statistics_write(client_message)
371
+
372
+ # Send the bytes back to the client socket.
373
+ error_on_send: str = sender.Sender(
374
+ ssl_socket=sending_socket, class_message=bytes_to_send_single,
375
+ logger=network_logger).send()
376
+
377
+ if error_on_send:
378
+ client_message.reinitialize_dynamic_vars()
379
+ client_message.errors.append(error_on_send)
380
+ client_message.timestamp = datetime.now()
381
+ client_message.action = 'client_send'
382
+ record_and_statistics_write(client_message)
383
+
384
+ if error_on_send:
385
+ exception_or_close_in_receiving_thread = True
386
+ finish_thread()
387
+ return 'return'
388
+
389
+ return None
390
+
391
+ def receive_send_client_offline(
392
+ client_message: ClientMessage,
393
+ receiving_socket: ssl.SSLSocket | socket.socket,
394
+ sending_socket: ssl.SSLSocket | socket.socket
395
+ ) -> Literal['return'] | None:
396
+ nonlocal exception_or_close_in_receiving_thread
397
+ nonlocal client_receive_count
398
+
399
+ client_receive_count += 1
400
+
401
+ network_logger.info(f"Initializing Receiver for Client cycle: {str(client_receive_count)}")
402
+ client_message.timestamp = datetime.now()
403
+
404
+ received_raw_data, is_socket_closed, error_message = receiver.Receiver(
405
+ ssl_socket=receiving_socket, logger=network_logger).receive()
406
+
407
+ process_client_raw_data(received_raw_data, error_message, client_message)
408
+ client_message.action = 'client_receive'
409
+
410
+ record_and_statistics_write(client_message)
411
+
412
+ # If there was an exception in the service thread, then receiving empty bytes doesn't mean that
413
+ # the socket was closed by the other side, it means that the service thread closed the socket.
414
+ if (received_raw_data == b'' or error_message) and exception_or_close_in_receiving_thread:
415
+ print_api("Both sockets are closed, breaking the loop", logger=network_logger,
416
+ logger_method='info')
417
+ return 'return'
418
+
419
+ # If the socket was closed on receive, and we're in offline mode, then we'll finish the thread right away.
420
+ # Since nothing more can be done, like responding to service or using requester.
421
+ if is_socket_closed:
422
+ exception_or_close_in_receiving_thread = True
423
+ finish_thread()
424
+ return 'return'
425
+
426
+ # Send to requester.
427
+ # THERE IS ALWAYS WILL BE ONLY ONE REQUEST FROM REQUESTER, SINCE THIS IS WHAT WE GOT FROM THE CLIENT.
428
+ request_custom_raw, is_requester_worked = create_requester_request(client_message, sending_socket=sending_socket)
429
+ # We will not process the raw data if requester didn't change anything.
430
+ if is_requester_worked:
431
+ client_message.reinitialize_dynamic_vars()
432
+ client_message.timestamp = datetime.now()
433
+ client_message.request_raw_bytes = request_custom_raw
434
+ client_message.action = 'client_requester'
435
+ process_client_raw_data(request_custom_raw, error_message, client_message)
436
+ record_and_statistics_write(client_message)
437
+
438
+ print_api("Offline Mode, sending to responder directly.", logger=network_logger,
439
+ logger_method='info')
440
+ bytes_to_send_list: list[bytes] = create_responder_response(client_message)
441
+
442
+ error_on_send: str = str()
443
+ for bytes_to_send_single in bytes_to_send_list:
444
+ client_message.reinitialize_dynamic_vars()
445
+ client_message.timestamp = datetime.now()
446
+ client_message.response_raw_bytes = bytes_to_send_single
447
+ client_message.action = 'client_responder_offline'
448
+ process_server_raw_data(bytes_to_send_single, '', client_message)
449
+ record_and_statistics_write(client_message)
450
+
451
+ error_on_send: str = sender.Sender(
452
+ ssl_socket=receiving_socket, class_message=bytes_to_send_single,
453
+ logger=network_logger).send()
454
+
455
+ if error_on_send:
456
+ client_message.reinitialize_dynamic_vars()
457
+ client_message.errors.append(error_on_send)
458
+ client_message.timestamp = datetime.now()
459
+ client_message.action = 'service_send'
460
+
461
+ record_and_statistics_write(client_message)
462
+
463
+ # If the socket was closed on message receive, then we'll break the loop only after send.
464
+ if is_socket_closed or error_on_send:
465
+ exception_or_close_in_receiving_thread = True
466
+ finish_thread()
467
+ return 'return'
468
+
469
+ return None
470
+
471
+ def receive_send_client(
472
+ client_message: ClientMessage,
473
+ receiving_socket: ssl.SSLSocket | socket.socket,
474
+ sending_socket: ssl.SSLSocket | socket.socket
475
+ ) -> Literal['return'] | None:
476
+
477
+ nonlocal exception_or_close_in_receiving_thread
478
+ nonlocal client_receive_count
479
+
480
+ client_receive_count += 1
481
+
482
+ network_logger.info(f"Initializing Receiver for Client cycle: {str(client_receive_count)}")
483
+
484
+ # Getting message from the client over the socket using specific class.
485
+ client_message.timestamp = datetime.now()
486
+ received_raw_data, is_socket_closed, error_message = receiver.Receiver(
487
+ ssl_socket=receiving_socket, logger=network_logger).receive()
488
+
489
+ process_client_raw_data(received_raw_data, error_message, client_message)
490
+ client_message.action = 'client_receive'
491
+
492
+ record_and_statistics_write(client_message)
493
+
494
+ # If there was an exception in the service thread, then receiving empty bytes doesn't mean that
495
+ # the socket was closed by the other side, it means that the service thread closed the socket.
496
+ if (received_raw_data == b'' or error_message) and exception_or_close_in_receiving_thread:
497
+ print_api("Both sockets are closed, breaking the loop", logger=network_logger,
498
+ logger_method='info')
499
+ return 'return'
500
+
501
+ # Send to requester.
502
+ # THERE IS ALWAYS WILL BE ONLY ONE REQUEST FROM REQUESTER, SINCE THIS IS WHAT WE GOT FROM THE CLIENT.
503
+ request_custom_raw, is_requester_worked = create_requester_request(client_message, sending_socket=sending_socket)
504
+ # We will not process the raw data if requester didn't change anything.
505
+ if is_requester_worked:
506
+ client_message.reinitialize_dynamic_vars()
507
+ client_message.timestamp = datetime.now()
508
+ client_message.request_raw_bytes = request_custom_raw
509
+ client_message.action = 'client_requester'
510
+ process_client_raw_data(request_custom_raw, error_message, client_message)
511
+ record_and_statistics_write(client_message)
512
+
513
+ error_on_send: str = sender.Sender(
514
+ ssl_socket=sending_socket, class_message=client_message.request_raw_bytes,
515
+ logger=network_logger).send()
516
+
517
+ if error_on_send:
518
+ client_message.reinitialize_dynamic_vars()
519
+ client_message.errors.append(error_on_send)
520
+ client_message.timestamp = datetime.now()
521
+ client_message.action = 'service_send'
522
+ record_and_statistics_write(client_message)
523
+
524
+ # If the socket was closed on message receive, then we'll break the loop only after send.
525
+ if is_socket_closed or error_on_send:
526
+ exception_or_close_in_receiving_thread = True
527
+ finish_thread()
528
+ return 'return'
529
+
530
+ return None
531
+
532
+ def receive_send_service(
533
+ client_message: ClientMessage,
534
+ receiving_socket: ssl.SSLSocket | socket.socket,
535
+ sending_socket: ssl.SSLSocket | socket.socket
536
+ ) -> Literal['return'] | None:
537
+
538
+ nonlocal exception_or_close_in_receiving_thread
539
+ nonlocal server_receive_count
540
+
541
+ server_receive_count += 1
542
+
543
+ network_logger.info(f"Initializing Receiver for Service cycle: {str(server_receive_count)}")
544
+
545
+ # Getting message from the client over the socket using specific class.
546
+ client_message.timestamp = datetime.now()
547
+ received_raw_data, is_socket_closed, error_message = receiver.Receiver(
548
+ ssl_socket=receiving_socket, logger=network_logger).receive()
549
+
550
+ process_server_raw_data(received_raw_data, error_message, client_message)
551
+ client_message.action = 'service_receive'
552
+ record_and_statistics_write(client_message)
553
+
554
+ # If there was an exception in the service thread, then receiving empty bytes doesn't mean that
555
+ # the socket was closed by the other side, it means that the service thread closed the socket.
556
+ if (received_raw_data == b'' or error_message) and exception_or_close_in_receiving_thread:
557
+ print_api("Both sockets are closed, breaking the loop", logger=network_logger,
558
+ logger_method='info')
559
+ return 'return'
560
+
561
+ # Now send it to requester/responder.
562
+ bytes_to_send_list: list[bytes] = create_responder_response(client_message)
563
+ print_api(f"Got responses from responder, count: [{len(bytes_to_send_list)}]",
564
+ logger=network_logger, logger_method='info')
565
+
566
+ # is_socket_closed: bool = False
567
+ error_on_send: str = str()
568
+ for bytes_to_send_single in bytes_to_send_list:
569
+ client_message.reinitialize_dynamic_vars()
570
+ client_message.timestamp = datetime.now()
571
+ client_message.response_raw_bytes = bytes_to_send_single
572
+
573
+ # This records the requester or responder output, only if it is not the same as the original
574
+ # message.
575
+ if bytes_to_send_single != received_raw_data:
576
+ client_message.action = 'service_responder'
577
+ record_and_statistics_write(client_message)
578
+
579
+ error_on_send: str = sender.Sender(
580
+ ssl_socket=sending_socket, class_message=bytes_to_send_single,
581
+ logger=network_logger).send()
582
+
583
+ if error_on_send:
584
+ client_message.reinitialize_dynamic_vars()
585
+ client_message.errors.append(error_on_send)
586
+ client_message.timestamp = datetime.now()
587
+ client_message.action = 'client_send'
588
+
589
+ record_and_statistics_write(client_message)
590
+
591
+ # If the socket was closed on message receive, then we'll break the loop only after send.
592
+ if is_socket_closed or error_on_send:
593
+ exception_or_close_in_receiving_thread = True
594
+ finish_thread()
595
+ return 'return'
596
+
597
+ return None
598
+
599
+
338
600
  def receive_send_start(
339
601
  receiving_socket,
340
602
  sending_socket = None,
@@ -360,146 +622,25 @@ def thread_worker_main(
360
622
  raise ValueError(f"Unknown side of the socket: {receiving_socket}")
361
623
 
362
624
  while True:
363
- is_socket_closed: bool = False
364
- # pass the socket connect to responder.
365
- if side == 'Service' and client_connection_message:
366
- client_message = client_connection_message
367
-
368
- bytes_to_send_list: list[bytes] = create_responder_response(client_message)
369
- print_api(f"Got responses from connect responder, count: [{len(bytes_to_send_list)}]", logger=network_logger,
370
- logger_method='info')
625
+ client_message.reinitialize_dynamic_vars()
371
626
 
372
- received_raw_data = None
373
- else:
374
- client_message.reinitialize_dynamic_vars()
375
-
376
- if side == 'Client':
377
- client_receive_count += 1
378
- current_count = client_receive_count
379
- else:
380
- server_receive_count += 1
381
- current_count = server_receive_count
382
-
383
- # Getting current time of message received, either from client or service.
384
- client_message.timestamp = datetime.now()
385
-
386
- # # No need to receive on service socket if we're in offline mode, because there is no service to connect to.
387
- # if config_static.MainConfig.offline and side == 'Service':
388
- # print_api("Offline Mode, skipping receiving on service socket.", logger=network_logger,
389
- # logger_method='info')
390
- # else:
391
-
392
- network_logger.info(
393
- f"Initializing Receiver for {side} cycle: {str(current_count)}")
394
-
395
- # Getting message from the client over the socket using specific class.
396
- received_raw_data, is_socket_closed, error_message = receiver.Receiver(
397
- ssl_socket=receiving_socket, logger=network_logger).receive()
398
-
399
- # In case of client socket, we'll process the raw data specifically for the client.
400
- if side == 'Client':
401
- process_client_raw_data(received_raw_data, error_message, client_message)
402
- client_message.action = 'client_receive'
403
- # In case of service socket, we'll process the raw data specifically for the service.
404
- else:
405
- process_server_raw_data(received_raw_data, error_message, client_message)
406
- client_message.action = 'service_receive'
407
-
408
- # If there was an exception in the service thread, then receiving empty bytes doesn't mean that
409
- # the socket was closed by the other side, it means that the service thread closed the socket.
410
- if (received_raw_data == b'' or error_message) and exception_or_close_in_receiving_thread:
411
- print_api("Both sockets are closed, breaking the loop", logger=network_logger,
412
- logger_method='info')
413
- return
414
-
415
- # We will record only if there was no closing signal, because if there was, it means that we initiated
416
- # the close on the opposite socket.
417
- record_and_statistics_write(client_message)
418
-
419
- # if is_socket_closed:
420
- # exception_or_close_in_receiving_thread = True
421
- # finish_thread()
422
- # return
423
-
424
- # Now send it to requester/responder.
425
- if side == 'Client':
426
- # Send to requester.
427
- bytes_to_send_list: list[bytes] = create_requester_request(
428
- client_message, sending_socket=sending_socket)
429
-
430
- # If we're in offline mode, then we'll put the request to the responder right away.
431
- if config_static.MainConfig.offline:
432
- print_api("Offline Mode, sending to responder directly.", logger=network_logger,
433
- logger_method='info')
434
- process_client_raw_data(bytes_to_send_list[0], error_message, client_message)
435
- bytes_to_send_list = create_responder_response(client_message)
436
- elif side == 'Service':
437
- bytes_to_send_list: list[bytes] = create_responder_response(client_message)
438
- print_api(f"Got responses from responder, count: [{len(bytes_to_send_list)}]",
439
- logger=network_logger,
440
- logger_method='info')
441
- else:
442
- raise ValueError(f"Unknown side [{side}] of the socket: {receiving_socket}")
443
-
444
-
445
- # If nothing was passed from the responder, and the client message is the connection message, then we'll skip to the next iteration.
446
- if not bytes_to_send_list and client_connection_message:
627
+ if side == 'Service' and client_connection_message:
628
+ result: Literal['continue', 'return'] | None = (
629
+ receive_send_service_connect(client_connection_message, sending_socket))
447
630
  client_connection_message = None
448
- continue
449
-
450
- # is_socket_closed: bool = False
451
- error_on_send: str = str()
452
- for bytes_to_send_single in bytes_to_send_list:
453
- client_message.reinitialize_dynamic_vars()
454
- client_message.timestamp = datetime.now()
455
-
456
- if side == 'Client':
457
- client_message.request_raw_bytes = bytes_to_send_single
458
- else:
459
- client_message.response_raw_bytes = bytes_to_send_single
460
-
461
- # This records the requester or responder output, only if it is not the same as the original
462
- # message.
463
- if bytes_to_send_single != received_raw_data:
464
- if side == 'Client':
465
- client_message.action = 'client_requester'
466
-
467
- if config_static.MainConfig.offline:
468
- client_message.action = 'client_responder_offline'
469
- elif side == 'Service':
470
- client_message.action = 'service_responder'
471
- record_and_statistics_write(client_message)
472
-
473
- # If we're in offline mode, it means we're in the client thread, and we'll send the
474
- # bytes back to the client socket.
475
- if config_static.MainConfig.offline:
476
- error_on_send: str = sender.Sender(
477
- ssl_socket=receiving_socket, class_message=bytes_to_send_single,
478
- logger=network_logger).send()
479
- else:
480
- error_on_send: str = sender.Sender(
481
- ssl_socket=sending_socket, class_message=bytes_to_send_single,
482
- logger=network_logger).send()
483
-
484
- if error_on_send:
485
- client_message.reinitialize_dynamic_vars()
486
- client_message.errors.append(error_on_send)
487
- client_message.timestamp = datetime.now()
488
- if side == 'Client':
489
- client_message.action = 'service_send'
490
- else:
491
- client_message.action = 'client_send'
492
-
493
- record_and_statistics_write(client_message)
494
-
495
- # If the socket was closed on message receive, then we'll break the loop only after send.
496
- if is_socket_closed or error_on_send:
497
- exception_or_close_in_receiving_thread = True
498
- finish_thread()
499
- return
631
+ if result == 'continue':
632
+ continue
633
+ elif side == 'Client' and config_static.MainConfig.offline:
634
+ result: Literal['return'] | None = receive_send_client_offline(client_message, receiving_socket, sending_socket)
635
+ elif side == 'Client':
636
+ result: Literal['return'] | None = receive_send_client(client_message, receiving_socket, sending_socket)
637
+ elif side == 'Service':
638
+ result: Literal['return'] | None = receive_send_service(client_message, receiving_socket, sending_socket)
639
+ else:
640
+ raise ValueError(f"Unknown side [{side}] of the socket: {receiving_socket}")
500
641
 
501
- # For next iteration to start in case this iteration was responsible to process connection message, we need to set it to None.
502
- client_connection_message = None
642
+ if result == 'return':
643
+ return
503
644
  except Exception as exc:
504
645
  # If the sockets were already closed, then there is nothing to do here besides log.
505
646
  # if (isinstance(exc, OSError) and exc.errno == 10038 and
@@ -602,7 +743,13 @@ def thread_worker_main(
602
743
  try:
603
744
  engine_name: str = recorder.engine_name
604
745
  client_ip, source_port = client_socket.getpeername()
605
- client_name: str = socket.gethostbyaddr(client_ip)[0]
746
+
747
+ try:
748
+ client_name: str = socket.gethostbyaddr(client_ip)[0]
749
+ # This can happen if the host changed IP address, but it wasn't propagated over DHCP.
750
+ except socket.herror:
751
+ client_name = ""
752
+
606
753
  client_name = client_name.lower()
607
754
  destination_port: int = client_socket.getsockname()[1]
608
755
  destination_port_str: str = str(destination_port)
@@ -112,5 +112,5 @@ class RequesterParent:
112
112
  def create_request(self, class_client_message: ClientMessage, **kwargs) -> bytes:
113
113
  """ This function should be overridden in the child class. """
114
114
 
115
- request_bytes: bytes = class_client_message.request_raw_bytes
115
+ request_bytes: bytes = None
116
116
  return request_bytes
@@ -502,6 +502,8 @@ def mitm_server(config_file_path: str, script_version: str):
502
502
  exceptions_logger_queue=EXCEPTIONS_CSV_LOGGER_QUEUE,
503
503
  forwarding_dns_service_ipv4_list___only_for_localhost=[config_static.DNSServer.forwarding_dns_service_ipv4],
504
504
  skip_extension_id_list=config_static.SkipExtensions.SKIP_EXTENSION_ID_LIST,
505
+ enable_sslkeylogfile_env_to_client_ssl_context=config_static.Certificates.enable_sslkeylogfile_env_to_client_ssl_context,
506
+ sslkeylog_file_path=config_static.Certificates.sslkeylog_file_path,
505
507
  print_kwargs=dict(stdout=False)
506
508
  )
507
509
 
@@ -163,11 +163,103 @@ class GitHubWrapper:
163
163
 
164
164
  self.build_links_from_user_and_repo()
165
165
 
166
- def check_github_domain(self, domain):
166
+ def check_github_domain(
167
+ self,
168
+ domain: str
169
+ ):
167
170
  if self.domain not in domain:
168
171
  print_api(
169
172
  f'This is not [{self.domain}] domain.', color="red", error_type=True)
170
173
 
174
+ def download_file(
175
+ self,
176
+ file_name: str,
177
+ target_dir: str
178
+ ) -> str:
179
+ """
180
+ Download a single repo file to a local directory.
181
+
182
+ :param file_name: string, Full repo-relative path to the file. Example:
183
+ "eng.traineddata"
184
+ "script\\English.script"
185
+ :param target_dir: string, Local directory to save into.
186
+
187
+ :return: The local path to the downloaded file.
188
+ """
189
+
190
+ # Normalize to GitHub path format
191
+ file_path = file_name.replace("\\", "/").strip("/")
192
+
193
+ headers = self._get_headers()
194
+ url = f"{self.contents_url}/{file_path}"
195
+ params = {"ref": self.branch}
196
+
197
+ resp = requests.get(url, headers=headers, params=params)
198
+ resp.raise_for_status()
199
+ item = resp.json()
200
+
201
+ # Expect a single file object
202
+ if isinstance(item, list) or item.get("type") != "file":
203
+ raise ValueError(f"'{file_name}' is not a file in branch '{self.branch}'.")
204
+
205
+ download_url = item.get("download_url")
206
+ if not download_url:
207
+ raise ValueError(f"Unable to obtain download URL for '{file_name}'.")
208
+
209
+ os.makedirs(target_dir, exist_ok=True)
210
+ local_name = item.get("name") or os.path.basename(file_path)
211
+
212
+ from .. import web # ensure available in your module structure
213
+ web.download(
214
+ file_url=download_url,
215
+ target_directory=target_dir,
216
+ file_name=local_name,
217
+ headers=headers,
218
+ )
219
+ return os.path.join(target_dir, local_name)
220
+
221
+ def download_directory(
222
+ self,
223
+ folder_name: str,
224
+ target_dir: str
225
+ ) -> None:
226
+ """
227
+ Recursively download a repo directory to a local directory.
228
+
229
+ :param folder_name: string, Repo-relative directory path to download (e.g., "tests/langs").
230
+ :param target_dir: string, Local directory to save the folder tree into.
231
+ """
232
+ headers = self._get_headers()
233
+ root_path = folder_name.replace("\\", "/").strip("/")
234
+
235
+ def _walk_dir(rel_path: str, local_dir: str) -> None:
236
+ contents_url = f"{self.contents_url}/{rel_path}" if rel_path else self.contents_url
237
+ params = {"ref": self.branch}
238
+
239
+ response = requests.get(contents_url, headers=headers, params=params)
240
+ response.raise_for_status()
241
+ items = response.json()
242
+
243
+ # If a file path was passed accidentally, delegate to download_file
244
+ if isinstance(items, dict) and items.get("type") == "file":
245
+ self.download_file(rel_path, local_dir)
246
+ return
247
+
248
+ if not isinstance(items, list):
249
+ raise ValueError(f"Unexpected response shape when listing '{rel_path or '/'}'.")
250
+
251
+ os.makedirs(local_dir, exist_ok=True)
252
+
253
+ for item in items:
254
+ name = item["name"]
255
+ if item["type"] == "file":
256
+ self.download_file(f"{rel_path}/{name}" if rel_path else name, local_dir)
257
+ elif item["type"] == "dir":
258
+ _walk_dir(f"{rel_path}/{name}" if rel_path else name, os.path.join(local_dir, name))
259
+ # ignore symlinks/submodules if present
260
+
261
+ _walk_dir(root_path, target_dir)
262
+
171
263
  def download_and_extract_branch(
172
264
  self,
173
265
  target_directory: str,
@@ -195,48 +287,6 @@ class GitHubWrapper:
195
287
  :return:
196
288
  """
197
289
 
198
- def download_file(file_url: str, target_dir: str, file_name: str, current_headers: dict) -> None:
199
- os.makedirs(target_dir, exist_ok=True)
200
-
201
- web.download(
202
- file_url=file_url,
203
- target_directory=target_dir,
204
- file_name=file_name,
205
- headers=current_headers
206
- )
207
-
208
- def download_directory(folder_path: str, target_dir: str, current_headers: dict) -> None:
209
- # Construct the API URL for the current folder.
210
- contents_url = f"{self.contents_url}/{folder_path}"
211
- params = {'ref': self.branch}
212
-
213
- response = requests.get(contents_url, headers=current_headers, params=params)
214
- response.raise_for_status()
215
-
216
- # Get the list of items (files and subdirectories) in the folder.
217
- items = response.json()
218
-
219
- # Ensure the local target directory exists.
220
- os.makedirs(target_dir, exist_ok=True)
221
-
222
- # Process each item.
223
- for item in items:
224
- local_item_path = os.path.join(target_dir, item['name'])
225
- if item['type'] == 'file':
226
- download_file(
227
- file_url=item['download_url'],
228
- target_dir=target_dir,
229
- file_name=item['name'],
230
- current_headers=current_headers
231
- )
232
- elif item['type'] == 'dir':
233
- # Recursively download subdirectories.
234
- download_directory(
235
- folder_path=f"{folder_path}/{item['name']}",
236
- target_dir=local_item_path,
237
- current_headers=current_headers
238
- )
239
-
240
290
  headers: dict = self._get_headers()
241
291
 
242
292
  if not download_each_file:
@@ -275,7 +325,7 @@ class GitHubWrapper:
275
325
  else:
276
326
  current_target_directory = os.path.join(target_directory, self.path)
277
327
 
278
- download_directory(self.path, current_target_directory, headers)
328
+ self.download_directory(self.path, current_target_directory)
279
329
 
280
330
  def get_releases_json(
281
331
  self,
@@ -530,8 +580,77 @@ class GitHubWrapper:
530
580
  commit_message = latest_commit.get("commit", {}).get("message", "")
531
581
  return commit_message
532
582
 
583
+ def list_files(
584
+ self,
585
+ pattern: str = "*",
586
+ recursive: bool = True,
587
+ path: str | None = None,
588
+ ) -> list[str]:
589
+ """
590
+ List files in the repository (or in a specific subfolder).
533
591
 
534
- def parse_github_args():
592
+ :param pattern: Glob-style pattern (e.g., "*.ex*", "*test*.py"). Matching is done
593
+ against the file's base name (not the full path).
594
+ :param recursive: If True, include files in all subfolders (returns full repo-relative
595
+ paths). If False, list only the immediate files in the chosen folder.
596
+ :param path: Optional subfolder to list from (e.g., "tests/langs"). If omitted,
597
+ uses self.path if set, otherwise the repo root.
598
+
599
+ :return: A list of repo-relative file paths that match the pattern.
600
+ """
601
+ headers = self._get_headers()
602
+ base_path = (path or self.path or "").strip("/")
603
+
604
+ if recursive:
605
+ # Use the Git Trees API to fetch all files in one call, then filter.
606
+ tree_url = f"{self.api_url}/git/trees/{self.branch}"
607
+ params = {"recursive": "1"}
608
+ resp = requests.get(tree_url, headers=headers, params=params)
609
+ resp.raise_for_status()
610
+ data = resp.json()
611
+
612
+ files = []
613
+ for entry in data.get("tree", []):
614
+ if entry.get("type") != "blob":
615
+ continue # only files
616
+ entry_path = entry.get("path", "")
617
+ # If a base_path was provided, keep only files under it
618
+ if base_path and not entry_path.startswith(base_path + "/") and entry_path != base_path:
619
+ continue
620
+ # Match pattern against the *file name* (basename)
621
+ if fnmatch.fnmatch(os.path.basename(entry_path), pattern):
622
+ files.append(entry_path)
623
+ return files
624
+
625
+ else:
626
+ # Non-recursive: use the Contents API to list a single directory.
627
+ # If base_path is empty, list the repo root.
628
+ if base_path:
629
+ contents_url = f"{self.contents_url}/{base_path}"
630
+ else:
631
+ contents_url = self.contents_url
632
+
633
+ params = {"ref": self.branch}
634
+ resp = requests.get(contents_url, headers=headers, params=params)
635
+ resp.raise_for_status()
636
+ items = resp.json()
637
+
638
+ # The Contents API returns a dict when the path points to a single file;
639
+ # normalize to a list to simplify handling.
640
+ if isinstance(items, dict):
641
+ items = [items]
642
+
643
+ files = []
644
+ for item in items:
645
+ if item.get("type") == "file":
646
+ name = item.get("name", "")
647
+ if fnmatch.fnmatch(name, pattern):
648
+ # item["path"] is the full repo-relative path we want to return
649
+ files.append(item.get("path", name))
650
+ return files
651
+
652
+
653
+ def _make_parser():
535
654
  import argparse
536
655
 
537
656
  parser = argparse.ArgumentParser(description='GitHub Wrapper')
@@ -562,7 +681,7 @@ def parse_github_args():
562
681
  '-db', '--download_branch', action='store_true', default=False,
563
682
  help='Sets if the branch will be downloaded. In conjunction with path, only the path will be downloaded.')
564
683
 
565
- return parser.parse_args()
684
+ return parser
566
685
 
567
686
 
568
687
  def github_wrapper_main(
@@ -571,8 +690,8 @@ def github_wrapper_main(
571
690
  path: str = None,
572
691
  target_directory: str = None,
573
692
  pat: str = None,
574
- get_latest_commit_json: bool = False,
575
693
  get_latest_commit_message: bool = False,
694
+ get_latest_commit_json: bool = False,
576
695
  download_branch: bool = False
577
696
  ):
578
697
  """
@@ -610,15 +729,7 @@ def github_wrapper_main(
610
729
 
611
730
 
612
731
  def github_wrapper_main_with_args():
613
- args = parse_github_args()
614
-
615
- return github_wrapper_main(
616
- repo_url=args.repo_url,
617
- branch=args.branch,
618
- path=args.path,
619
- target_directory=args.target_directory,
620
- pat=args.pat,
621
- get_latest_commit_json=args.get_latest_commit_json,
622
- get_latest_commit_message=args.get_latest_commit_message,
623
- download_branch=args.download_branch
624
- )
732
+ main_parser = _make_parser()
733
+ args = main_parser.parse_args()
734
+
735
+ return github_wrapper_main(**vars(args))
@@ -30,7 +30,9 @@ class Certificator:
30
30
  custom_private_key_path: str,
31
31
  forwarding_dns_service_ipv4_list___only_for_localhost: list,
32
32
  skip_extension_id_list: list,
33
- tls: bool
33
+ tls: bool,
34
+ enable_sslkeylogfile_env_to_client_ssl_context: bool,
35
+ sslkeylog_file_path: str
34
36
  ):
35
37
  self.ca_certificate_name = ca_certificate_name
36
38
  self.ca_certificate_filepath = ca_certificate_filepath
@@ -49,6 +51,9 @@ class Certificator:
49
51
  forwarding_dns_service_ipv4_list___only_for_localhost)
50
52
  self.skip_extension_id_list = skip_extension_id_list
51
53
  self.tls = tls
54
+ self.enable_sslkeylogfile_env_to_client_ssl_context: bool = (
55
+ enable_sslkeylogfile_env_to_client_ssl_context)
56
+ self.sslkeylog_file_path: str = sslkeylog_file_path
52
57
 
53
58
  # noinspection PyTypeChecker
54
59
  self.certauth_wrapper: CertAuthWrapper = None
@@ -221,5 +226,10 @@ class Certificator:
221
226
 
222
227
  # You need to build new context and exchange the context that being inherited from the main socket,
223
228
  # or else the context will receive previous certificate each time.
224
- sni_received_parameters.ssl_socket.context = \
225
- creator.create_server_ssl_context___load_certificate_and_key(sni_server_certificate_file_path, None)
229
+ sni_received_parameters.ssl_socket.context = (
230
+ creator.create_server_ssl_context___load_certificate_and_key(
231
+ certificate_file_path=sni_server_certificate_file_path, key_file_path=None,
232
+ enable_sslkeylogfile_env_to_client_ssl_context=self.enable_sslkeylogfile_env_to_client_ssl_context,
233
+ sslkeylog_file_path=self.sslkeylog_file_path
234
+ )
235
+ )
@@ -25,7 +25,15 @@ def add_reusable_address_option(socket_instance):
25
25
  socket_instance.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
26
26
 
27
27
 
28
- def create_ssl_context_for_server(allow_legacy=False) -> ssl.SSLContext:
28
+ def create_ssl_context_for_server(
29
+ enable_sslkeylogfile_env_to_client_ssl_context: bool = False,
30
+ sslkeylog_file_path: str = None,
31
+ allow_legacy: bool = False
32
+ ) -> ssl.SSLContext:
33
+ """
34
+ This function creates the SSL context for the server.
35
+ Meaning that your script will act like a server, and the client will connect to it.
36
+ """
29
37
  # Creating context with SSL certificate and the private key before the socket
30
38
  # https://docs.python.org/3/library/ssl.html
31
39
  # Creating context for SSL wrapper, specifying "PROTOCOL_TLS_SERVER" will pick the best TLS version protocol for
@@ -48,6 +56,14 @@ def create_ssl_context_for_server(allow_legacy=False) -> ssl.SSLContext:
48
56
  ssl_context.verify_mode = ssl.CERT_NONE
49
57
  ssl_context.check_hostname = False
50
58
 
59
+ if enable_sslkeylogfile_env_to_client_ssl_context:
60
+ if sslkeylog_file_path is None:
61
+ sslkeylog_file_path = os.environ.get('SSLKEYLOGFILE')
62
+
63
+ if not os.path.exists(sslkeylog_file_path):
64
+ open(sslkeylog_file_path, "a").close()
65
+ ssl_context.keylog_filename = sslkeylog_file_path
66
+
51
67
  # If you must support old clients that only offer TLS_RSA_* suites under OpenSSL 3:
52
68
  if allow_legacy:
53
69
  # This enables RSA key exchange and other legacy bits at security level 1
@@ -64,6 +80,7 @@ def create_ssl_context_for_client(
64
80
  ) -> ssl.SSLContext:
65
81
  """
66
82
  This function creates the SSL context for the client.
83
+ This means that your script will act like a client, and will connect to a server.
67
84
  The SSL context is created with the "PROTOCOL_TLS_CLIENT" protocol.
68
85
 
69
86
  :param enable_sslkeylogfile_env_to_client_ssl_context: boolean, enables the SSLKEYLOGFILE environment variable
@@ -83,9 +100,9 @@ def create_ssl_context_for_client(
83
100
  if sslkeylog_file_path is None:
84
101
  sslkeylog_file_path = os.environ.get('SSLKEYLOGFILE')
85
102
 
86
- # This will create the file if it doesn't exist
87
- open(sslkeylog_file_path, "a").close()
88
- ssl_context.keylog_filename = sslkeylog_file_path
103
+ if not os.path.exists(sslkeylog_file_path):
104
+ open(sslkeylog_file_path, "a").close()
105
+ ssl_context.keylog_filename = sslkeylog_file_path
89
106
 
90
107
  current_ciphers = 'AES256-GCM-SHA384:' + ssl._DEFAULT_CIPHERS
91
108
  ssl_context.set_ciphers(current_ciphers)
@@ -191,10 +208,14 @@ def copy_server_ctx_settings(src: ssl.SSLContext, dst: ssl.SSLContext) -> None:
191
208
  def create_server_ssl_context___load_certificate_and_key(
192
209
  certificate_file_path: str,
193
210
  key_file_path: str | None,
194
- inherit_from: ssl.SSLContext | None = None
211
+ inherit_from: ssl.SSLContext | None = None,
212
+ enable_sslkeylogfile_env_to_client_ssl_context: bool = False,
213
+ sslkeylog_file_path: str = None,
195
214
  ) -> ssl.SSLContext:
196
215
  # Create and set ssl context for server.
197
- ssl_context: ssl.SSLContext = create_ssl_context_for_server(True)
216
+ ssl_context: ssl.SSLContext = create_ssl_context_for_server(
217
+ allow_legacy=True, enable_sslkeylogfile_env_to_client_ssl_context=enable_sslkeylogfile_env_to_client_ssl_context,
218
+ sslkeylog_file_path=sslkeylog_file_path)
198
219
 
199
220
  # If you replaced contexts during SNI, copy policy from the old one
200
221
  if inherit_from is not None:
@@ -478,7 +478,7 @@ class DnsServer:
478
478
  dns_cached_request = False
479
479
  # Check if the received data request from client is already in the cache
480
480
  if client_data in self.dns_questions_to_answers_cache:
481
- # message = "!!! Question / Answer is already in the dictionary..."
481
+ # message = "!!! Request / Response is already in the dictionary..."
482
482
  # self.logger.info(message)
483
483
 
484
484
  # Get the response from the cached answers list
@@ -561,7 +561,7 @@ class DnsServer:
561
561
  f'{self.offline_route_ipv6}')
562
562
  )
563
563
 
564
- message = f"!!! Question / Answer is in offline mode returning " \
564
+ message = f"!!! Request / Response is in offline mode returning " \
565
565
  f"{self.offline_route_ipv6}."
566
566
  self.logger.info(message)
567
567
 
@@ -577,7 +577,7 @@ class DnsServer:
577
577
  elif qtype_string == "SRV" or qtype_string == "SOA" or qtype_string == "HTTPS":
578
578
  dns_built_response.add_answer(*RR.fromZone(self.offline_srv_answer))
579
579
 
580
- message = f"!!! Question / Answer is in offline mode returning: " \
580
+ message = f"!!! Request / Response is in offline mode returning: " \
581
581
  f"{self.offline_srv_answer}."
582
582
  self.logger.info(message)
583
583
  elif qtype_string == "ANY":
@@ -586,7 +586,7 @@ class DnsServer:
586
586
  self.offline_route_domain)
587
587
  )
588
588
 
589
- message = f"!!! Question / Answer is in offline mode returning " \
589
+ message = f"!!! Request / Response is in offline mode returning " \
590
590
  f"{self.offline_route_domain}."
591
591
  self.logger.info(message)
592
592
  else:
@@ -596,7 +596,7 @@ class DnsServer:
596
596
  " " + self.offline_route_ipv4)
597
597
  )
598
598
 
599
- message = f"!!! Question / Answer is in offline mode returning " \
599
+ message = f"!!! Request / Response is in offline mode returning " \
600
600
  f"{self.offline_route_ipv4}."
601
601
  self.logger.info(message)
602
602
  # Values error means in most cases that you create wrong response
@@ -1,3 +1,4 @@
1
+ import socket
1
2
  import ssl
2
3
  import logging
3
4
  from pathlib import Path
@@ -12,12 +13,12 @@ from . import base
12
13
  class Sender:
13
14
  def __init__(
14
15
  self,
15
- ssl_socket: ssl.SSLSocket,
16
+ ssl_socket: ssl.SSLSocket | socket.socket,
16
17
  class_message: bytes,
17
18
  logger: logging.Logger = None
18
19
  ):
19
20
  self.class_message: bytes = class_message
20
- self.ssl_socket: ssl.SSLSocket = ssl_socket
21
+ self.ssl_socket: ssl.SSLSocket | socket.socket = ssl_socket
21
22
 
22
23
  if logger:
23
24
  # Create child logger for the provided logger with the module's name.
@@ -47,7 +47,9 @@ class SNISetup:
47
47
  tls: bool,
48
48
  domain_from_dns_server: str = None,
49
49
  skip_extension_id_list: list = None,
50
- exceptions_logger: loggingw.ExceptionCsvLogger = None
50
+ exceptions_logger: loggingw.ExceptionCsvLogger = None,
51
+ enable_sslkeylogfile_env_to_client_ssl_context: bool = False,
52
+ sslkeylog_file_path: str = None
51
53
  ):
52
54
  self.ca_certificate_name = ca_certificate_name
53
55
  self.ca_certificate_filepath = ca_certificate_filepath
@@ -74,6 +76,8 @@ class SNISetup:
74
76
  self.tls = tls
75
77
  self.exceptions_logger = exceptions_logger
76
78
  self.certificator_instance = None
79
+ self.enable_sslkeylogfile_env_to_client_ssl_context: bool = enable_sslkeylogfile_env_to_client_ssl_context
80
+ self.sslkeylog_file_path: str = sslkeylog_file_path
77
81
 
78
82
  def wrap_socket_with_ssl_context_server_sni_extended(
79
83
  self,
@@ -82,7 +86,7 @@ class SNISetup:
82
86
  ):
83
87
 
84
88
  # Create SSL Socket to wrap the raw socket with.
85
- ssl_context: ssl.SSLContext = creator.create_ssl_context_for_server(True)
89
+ ssl_context: ssl.SSLContext = creator.create_ssl_context_for_server(allow_legacy=True)
86
90
 
87
91
  self.certificator_instance = certificator.Certificator(
88
92
  ca_certificate_name=self.ca_certificate_name,
@@ -101,7 +105,9 @@ class SNISetup:
101
105
  forwarding_dns_service_ipv4_list___only_for_localhost=(
102
106
  self.forwarding_dns_service_ipv4_list___only_for_localhost),
103
107
  skip_extension_id_list=self.skip_extension_id_list,
104
- tls=self.tls
108
+ tls=self.tls,
109
+ enable_sslkeylogfile_env_to_client_ssl_context=self.enable_sslkeylogfile_env_to_client_ssl_context,
110
+ sslkeylog_file_path=self.sslkeylog_file_path
105
111
  )
106
112
 
107
113
  # Add SNI callback function to the SSL context.
@@ -160,7 +166,10 @@ class SNISetup:
160
166
  certificator_instance=self.certificator_instance,
161
167
  domain_from_dns_server=self.domain_from_dns_server,
162
168
  default_certificate_domain_list=self.default_certificate_domain_list,
163
- exceptions_logger=self.exceptions_logger )
169
+ exceptions_logger=self.exceptions_logger,
170
+ enable_sslkeylogfile_env_to_client_ssl_context=(
171
+ self.certificator_instance.enable_sslkeylogfile_env_to_client_ssl_context),
172
+ sslkeylog_file_path=self.certificator_instance.sslkeylog_file_path)
164
173
  ssl_context.set_servername_callback(
165
174
  sni_handler_instance.setup_sni_callback(print_kwargs=print_kwargs))
166
175
 
@@ -178,7 +187,9 @@ class SNIHandler:
178
187
  certificator_instance: certificator.Certificator,
179
188
  domain_from_dns_server: str,
180
189
  default_certificate_domain_list: list,
181
- exceptions_logger: loggingw.ExceptionCsvLogger
190
+ exceptions_logger: loggingw.ExceptionCsvLogger,
191
+ enable_sslkeylogfile_env_to_client_ssl_context: bool,
192
+ sslkeylog_file_path: str
182
193
  ):
183
194
  self.sni_use_default_callback_function_extended = sni_use_default_callback_function_extended
184
195
  self.sni_add_new_domains_to_default_server_certificate = sni_add_new_domains_to_default_server_certificate
@@ -187,6 +198,8 @@ class SNIHandler:
187
198
  self.domain_from_dns_server: str = domain_from_dns_server
188
199
  self.default_certificate_domain_list = default_certificate_domain_list
189
200
  self.exceptions_logger = exceptions_logger
201
+ self.enable_sslkeylogfile_env_to_client_ssl_context: bool = enable_sslkeylogfile_env_to_client_ssl_context
202
+ self.sslkeylog_file_path: str = sslkeylog_file_path
190
203
 
191
204
  # noinspection PyTypeChecker
192
205
  self.sni_received_parameters: SNIReceivedParameters = None
@@ -325,7 +338,9 @@ class SNIHandler:
325
338
  creator.create_server_ssl_context___load_certificate_and_key(
326
339
  default_server_certificate_path,
327
340
  None,
328
- inherit_from=self.sni_received_parameters.ssl_socket.context
341
+ inherit_from=self.sni_received_parameters.ssl_socket.context,
342
+ enable_sslkeylogfile_env_to_client_ssl_context=self.enable_sslkeylogfile_env_to_client_ssl_context,
343
+ sslkeylog_file_path=self.sslkeylog_file_path
329
344
  )
330
345
  )
331
346
  else:
@@ -1,6 +1,5 @@
1
1
  import multiprocessing
2
2
  import threading
3
- import time
4
3
 
5
4
  import select
6
5
  from typing import Literal, Union, Callable, Any
@@ -80,6 +79,8 @@ class SocketWrapper:
80
79
  statistics_logger_queue: multiprocessing.Queue = None,
81
80
  exceptions_logger_name: str = 'SocketWrapperExceptions',
82
81
  exceptions_logger_queue: multiprocessing.Queue = None,
82
+ enable_sslkeylogfile_env_to_client_ssl_context: bool = False,
83
+ sslkeylog_file_path: str = None,
83
84
  print_kwargs: dict = None,
84
85
  ):
85
86
  """
@@ -173,6 +174,12 @@ class SocketWrapper:
173
174
  :param exceptions_logger_name: string, name of the logger that will be used to log exceptions.
174
175
  :param exceptions_logger_queue: multiprocessing.Queue, queue that will be used to log exceptions in
175
176
  multiprocessing. You need to start the logger listener in the main process to handle the queue.
177
+ :param enable_sslkeylogfile_env_to_client_ssl_context: boolean, if True, each client SSL context
178
+ that will be created by the SocketWrapper will have save the SSL handshake keys to the file
179
+ defined in 'sslkeylog_file_path' parameter.
180
+ :param sslkeylog_file_path: string, path to file where SSL handshake keys will be saved.
181
+ If not provided and 'enable_sslkeylogfile_env_to_client_ssl_context' is True, then
182
+ the environment variable 'SSLKEYLOGFILE' will be used.
176
183
  :param print_kwargs: dict, additional arguments to pass to the print function.
177
184
  """
178
185
 
@@ -208,6 +215,9 @@ class SocketWrapper:
208
215
  self.ssh_script_to_execute = ssh_script_to_execute
209
216
  self.forwarding_dns_service_ipv4_list___only_for_localhost = (
210
217
  forwarding_dns_service_ipv4_list___only_for_localhost)
218
+ self.enable_sslkeylogfile_env_to_client_ssl_context: bool = (
219
+ enable_sslkeylogfile_env_to_client_ssl_context)
220
+ self.sslkeylog_file_path: str = sslkeylog_file_path
211
221
  self.print_kwargs: dict = print_kwargs
212
222
 
213
223
  self.socket_object = None
@@ -616,7 +626,9 @@ class SocketWrapper:
616
626
  forwarding_dns_service_ipv4_list___only_for_localhost=(
617
627
  self.forwarding_dns_service_ipv4_list___only_for_localhost),
618
628
  tls=is_tls,
619
- exceptions_logger=self.exceptions_logger
629
+ exceptions_logger=self.exceptions_logger,
630
+ enable_sslkeylogfile_env_to_client_ssl_context=self.enable_sslkeylogfile_env_to_client_ssl_context,
631
+ sslkeylog_file_path=self.sslkeylog_file_path
620
632
  )
621
633
 
622
634
  ssl_client_socket, accept_error_message = \
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: atomicshop
3
- Version: 3.3.29
3
+ Version: 3.4.1
4
4
  Summary: Atomic functions and classes to make developer life easier
5
5
  Author: Denis Kras
6
6
  License-Expression: MIT
@@ -1,4 +1,4 @@
1
- atomicshop/__init__.py,sha256=g-G7s5YKUYY2IiQIVuIj3eClhzuHJN9m-26BeB666_4,123
1
+ atomicshop/__init__.py,sha256=2_vm_IGYQueXrDSwBdgjXn7kHxyeKqTdQmDo1JOD4_0,122
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
@@ -126,11 +126,11 @@ atomicshop/file_io/xmls.py,sha256=zh3SuK-dNaFq2NDNhx6ivcf4GYCfGM8M10PcEwDSpxk,21
126
126
  atomicshop/mitm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
127
127
  atomicshop/mitm/config_static.py,sha256=KzO8DjZjRHfkQMYSIGTkW4jLNPzMR8visTqs1H6ZQ-U,8926
128
128
  atomicshop/mitm/config_toml_editor.py,sha256=2p1CMcktWRR_NW-SmyDwylu63ad5e0-w1QPMa8ZLDBw,1635
129
- atomicshop/mitm/connection_thread_worker.py,sha256=zPgDoIrZw6iIfjt_vwAYBanjOz23DMUZaWsBtHPUm34,33298
129
+ atomicshop/mitm/connection_thread_worker.py,sha256=NPHizpPJOaYjP05EEGxEOOKHhgbe4CSzUuk7WTPgZd4,38858
130
130
  atomicshop/mitm/import_config.py,sha256=7aLfKqflc3ZnzKc2_Y4T0eenzQpKG94M0r-PaVwF99M,18881
131
131
  atomicshop/mitm/initialize_engines.py,sha256=qzz5jzh_lKC03bI1w5ebngVXo1K-RV3poAyW-nObyqo,11042
132
132
  atomicshop/mitm/message.py,sha256=CDhhm4BTuZE7oNZCjvIZ4BuPOW4MuIzQLOg91hJaxDI,3065
133
- atomicshop/mitm/mitm_main.py,sha256=vjdK18ix3oH3thTgCi5qlAL13Bw_PgHaLGH2D9xic8w,40229
133
+ atomicshop/mitm/mitm_main.py,sha256=i0YcLDKAYH1aUS_Rf7IbwXiSQzndsdOY_JuYTvTiTN8,40453
134
134
  atomicshop/mitm/recs_files.py,sha256=tv8XFhYZMkBv4DauvpiAdPgvSo0Bcm1CghnmwO7dx8M,5018
135
135
  atomicshop/mitm/shared_functions.py,sha256=0lzeyINd44sVEfFbahJxQmz6KAMWbYrW5ou3UYfItvw,1777
136
136
  atomicshop/mitm/statistic_analyzer.py,sha256=EC9g21ocOsFzNfntV-nZHSGtrS1-Kxb0QDSGWS5FuNA,28942
@@ -140,7 +140,7 @@ atomicshop/mitm/engines/create_module_template_main_example.py,sha256=LeQ44Rp2Gi
140
140
  atomicshop/mitm/engines/__parent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
141
141
  atomicshop/mitm/engines/__parent/parser___parent.py,sha256=HHaCXhScl3OlPjz6eUxsDpJaZyk6BNuDMc9xCkeo2Ws,661
142
142
  atomicshop/mitm/engines/__parent/recorder___parent.py,sha256=D99cbpMneY9STSAPETa6eIxyfs_Q9etRYxm2dosA-DI,6203
143
- atomicshop/mitm/engines/__parent/requester___parent.py,sha256=j3QYOfQFEPSzIEWihkssNcfaLWC8cpdpi-ciPgjKNBc,5126
143
+ atomicshop/mitm/engines/__parent/requester___parent.py,sha256=-S817ERzs1pdY2rXiQBnG4eB4sm2jsy2wycpvuiDlpI,5092
144
144
  atomicshop/mitm/engines/__parent/responder___parent.py,sha256=mtiS_6ej9nxT9UhAQR4ftMqnqL-j_kO3u8KEaoEaI9k,9495
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
@@ -187,7 +187,7 @@ atomicshop/wrappers/astw.py,sha256=VkYfkfyc_PJLIOxByT6L7B8uUmKY6-I8XGZl4t_z828,4
187
187
  atomicshop/wrappers/configparserw.py,sha256=JwDTPjZoSrv44YKwIRcjyUnpN-FjgXVfMqMK_tJuSgU,22800
188
188
  atomicshop/wrappers/cryptographyw.py,sha256=QEUpDn8vUvMg3ADz6-4oC2kbDNC_woDlw7C0zU7qFVM,14233
189
189
  atomicshop/wrappers/ffmpegw.py,sha256=wcq0ZnAe0yajBOuTKZCCaKI7CDBjkq7FAgdW5IsKcVE,6031
190
- atomicshop/wrappers/githubw.py,sha256=BEEJuWd1eBfiv1FSgb76D4nEBRpFU9j37lztn-KbYa4,27548
190
+ atomicshop/wrappers/githubw.py,sha256=95RAbxomp4GA9-7B1pk6q3aYOmrCO0vkHyhSPgr3n3Q,31924
191
191
  atomicshop/wrappers/netshw.py,sha256=8WE_576XiiHykwFuE-VkCx5CydMpFlztX4frlEteCtI,6350
192
192
  atomicshop/wrappers/numpyw.py,sha256=sBV4gSKyr23kXTalqAb1oqttzE_2XxBooCui66jbAqc,1025
193
193
  atomicshop/wrappers/olefilew.py,sha256=biD5m58rogifCYmYhJBrAFb9O_Bn_spLek_9HofLeYE,2051
@@ -298,24 +298,24 @@ atomicshop/wrappers/pywin32w/wmis/wmi_helpers.py,sha256=Ng5pbWeQBNcPqfFuxHEIdkIU
298
298
  atomicshop/wrappers/socketw/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
299
299
  atomicshop/wrappers/socketw/accepter.py,sha256=4I9ORugRDvwaqSzm_gWSjZnRwQGY8hDTlCdsYHwH_ZE,2377
300
300
  atomicshop/wrappers/socketw/base.py,sha256=EcosGkD8VzgBY3GeIHDSG29ThQfXwg3-GQPmBTAqTdw,3048
301
- atomicshop/wrappers/socketw/certificator.py,sha256=mtWPJ_ew3OSwt0-1W4jaoco1VIY4NRCrMv3mDUxb_Cc,12418
302
- atomicshop/wrappers/socketw/creator.py,sha256=hHq8frKQtqZ1-Xfdm2kAsxqtsLFxXKDNwgGKdVKV6yg,16192
303
- atomicshop/wrappers/socketw/dns_server.py,sha256=GOYMvHvS6Fx7s-DRygGqO7_o8_Qt9on3HmKxgOSznRE,55956
301
+ atomicshop/wrappers/socketw/certificator.py,sha256=mA78HXg6j_WKQ7D26Ue66QKA9y1Iu-EWsnc6cI6RC8w,12976
302
+ atomicshop/wrappers/socketw/creator.py,sha256=-Xnc59CLHfBEmncwjIhyHBG-p07uNeSxTzr2S1LW7a4,17200
303
+ atomicshop/wrappers/socketw/dns_server.py,sha256=UHq1a3NVdOrclEOOQIe-wNtIgbF8DFeNXsobvtoM1U8,55961
304
304
  atomicshop/wrappers/socketw/exception_wrapper.py,sha256=_p98OdOaKYSMqJ23pHLXBUA7NkbVmpgqcSJAdWr6wwc,7560
305
305
  atomicshop/wrappers/socketw/get_process.py,sha256=aJC-_qFUv3NgWCSUzDI72E4z8_-VTZE9NVZ0CwUoNlM,5698
306
306
  atomicshop/wrappers/socketw/receiver.py,sha256=9B3MvcDqr4C3x2fsnjG5SQognd1wRqsBgikxZa0wXG8,8243
307
- atomicshop/wrappers/socketw/sender.py,sha256=aX_K8l_rHjd5AWb8bi5mt8-YTkMYVRDB6DnPqK_XDUE,4754
308
- atomicshop/wrappers/socketw/sni.py,sha256=uj6KKYKmSrzXcKBhVLaHQhYn1wNfIUpdnmcvn21V9iE,18176
307
+ atomicshop/wrappers/socketw/sender.py,sha256=5ecHUlz4Sxt4oWevBFfy33jQLRXmmVLOF34njfvSbxY,4801
308
+ atomicshop/wrappers/socketw/sni.py,sha256=XlTmNIdJYjW_hkF9eCXu7GdhSJYJvKDmTiPrLV1-TdQ,19374
309
309
  atomicshop/wrappers/socketw/socket_client.py,sha256=WWIiCxUX9irN9aWzJ6-1xrXNB_iv_diq3ha1yrWsNGU,22671
310
310
  atomicshop/wrappers/socketw/socket_server_tester.py,sha256=Qobmh4XV8ZxLUaw-eW4ESKAbeSLecCKn2OWFzMhadk0,6420
311
- atomicshop/wrappers/socketw/socket_wrapper.py,sha256=abvs3Jb7PZ6H5il0Yto6gCLkwY5tD40f0GYOzZVb8ng,42581
311
+ atomicshop/wrappers/socketw/socket_wrapper.py,sha256=KdwZvATt7iEC2R0to9jcE0kBtG1cjcZwmF_cMn5fFms,43616
312
312
  atomicshop/wrappers/socketw/ssl_base.py,sha256=62-hPm7zla1rh3m_WvDnXqKH-sDUTdiRptD8STCkgdk,2313
313
313
  atomicshop/wrappers/socketw/statistics_csv.py,sha256=_gA8bMX6Sw_UCXKi2y9wNAwlqifgExgDGfQIa9pFxQA,5543
314
314
  atomicshop/wrappers/winregw/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
315
315
  atomicshop/wrappers/winregw/winreg_installed_software.py,sha256=Qzmyktvob1qp6Tjk2DjLfAqr_yXV0sgWzdMW_9kwNjY,2345
316
316
  atomicshop/wrappers/winregw/winreg_network.py,sha256=ih0BVNwByLvf9F_Lac4EdmDYYJA3PzMvmG0PieDZrsE,9905
317
- atomicshop-3.3.29.dist-info/licenses/LICENSE.txt,sha256=lLU7EYycfYcK2NR_1gfnhnRC8b8ccOTElACYplgZN88,1094
318
- atomicshop-3.3.29.dist-info/METADATA,sha256=YW-NNKdGGuhqL6_KPk_ghosjrrYz0cj38rYlWINOAGI,9318
319
- atomicshop-3.3.29.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
320
- atomicshop-3.3.29.dist-info/top_level.txt,sha256=EgKJB-7xcrAPeqTRF2laD_Np2gNGYkJkd4OyXqpJphA,11
321
- atomicshop-3.3.29.dist-info/RECORD,,
317
+ atomicshop-3.4.1.dist-info/licenses/LICENSE.txt,sha256=lLU7EYycfYcK2NR_1gfnhnRC8b8ccOTElACYplgZN88,1094
318
+ atomicshop-3.4.1.dist-info/METADATA,sha256=H6vq6tDZmmoszao5uweXIMSDyVq0QhP4_-cJ4Ow_PDk,9317
319
+ atomicshop-3.4.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
320
+ atomicshop-3.4.1.dist-info/top_level.txt,sha256=EgKJB-7xcrAPeqTRF2laD_Np2gNGYkJkd4OyXqpJphA,11
321
+ atomicshop-3.4.1.dist-info/RECORD,,