atomicshop 3.0.2__py3-none-any.whl → 3.1.0__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/mitm/connection_thread_worker.py +17 -8
- atomicshop/mitm/engines/__parent/requester___parent.py +116 -0
- atomicshop/mitm/engines/__reference_general/requester___reference_general.py +46 -0
- atomicshop/mitm/engines/create_module_template.py +7 -1
- {atomicshop-3.0.2.dist-info → atomicshop-3.1.0.dist-info}/METADATA +1 -1
- {atomicshop-3.0.2.dist-info → atomicshop-3.1.0.dist-info}/RECORD +10 -8
- {atomicshop-3.0.2.dist-info → atomicshop-3.1.0.dist-info}/LICENSE.txt +0 -0
- {atomicshop-3.0.2.dist-info → atomicshop-3.1.0.dist-info}/WHEEL +0 -0
- {atomicshop-3.0.2.dist-info → atomicshop-3.1.0.dist-info}/top_level.txt +0 -0
atomicshop/__init__.py
CHANGED
|
@@ -180,16 +180,22 @@ def thread_worker_main(
|
|
|
180
180
|
|
|
181
181
|
network_logger.info("Thread Finished. Will continue listening on the Main thread")
|
|
182
182
|
|
|
183
|
+
def create_requester_request(client_message: ClientMessage) -> list[bytes]:
|
|
184
|
+
requests: list = [requester.create_response(client_message)]
|
|
185
|
+
|
|
186
|
+
# Output first 100 characters of all the requests in the list.
|
|
187
|
+
for request_raw_bytes_single in requests:
|
|
188
|
+
requester.logger.info(f"{request_raw_bytes_single[0: 100]}...")
|
|
189
|
+
|
|
190
|
+
return requests
|
|
191
|
+
|
|
183
192
|
def create_responder_response(client_message: ClientMessage) -> list[bytes]:
|
|
184
193
|
if client_message.action == 'service_connect':
|
|
185
194
|
return responder.create_connect_response(client_message)
|
|
186
195
|
else:
|
|
187
|
-
#
|
|
188
|
-
# client_message.info = "In Server Response Mode"
|
|
189
|
-
|
|
190
|
-
# If it's the first cycle and the protocol is Websocket, then we'll create the HTTP Handshake
|
|
196
|
+
# If we're in offline mode, and it's the first cycle and the protocol is Websocket, then we'll create the HTTP Handshake
|
|
191
197
|
# response automatically.
|
|
192
|
-
if protocol == 'Websocket' and client_receive_count == 1:
|
|
198
|
+
if config_static.MainConfig.offline and protocol == 'Websocket' and client_receive_count == 1:
|
|
193
199
|
responses: list = list()
|
|
194
200
|
responses.append(
|
|
195
201
|
websocket_parse.create_byte_http_response(client_message.request_raw_bytes))
|
|
@@ -380,12 +386,14 @@ def thread_worker_main(
|
|
|
380
386
|
# Now send it to requester/responder.
|
|
381
387
|
if side == 'Client':
|
|
382
388
|
# Send to requester.
|
|
383
|
-
bytes_to_send_list: list[bytes] =
|
|
384
|
-
|
|
389
|
+
bytes_to_send_list: list[bytes] = create_requester_request(client_message)
|
|
390
|
+
elif side == 'Service':
|
|
385
391
|
bytes_to_send_list: list[bytes] = create_responder_response(client_message)
|
|
386
392
|
print_api(f"Got responses from responder, count: [{len(bytes_to_send_list)}]",
|
|
387
393
|
logger=network_logger,
|
|
388
394
|
logger_method='info')
|
|
395
|
+
else:
|
|
396
|
+
raise ValueError(f"Unknown side [{side}] of the socket: {receiving_socket}")
|
|
389
397
|
|
|
390
398
|
if is_socket_closed:
|
|
391
399
|
exception_or_close_in_receiving_thread = True
|
|
@@ -511,15 +519,16 @@ def thread_worker_main(
|
|
|
511
519
|
reference_module=reference_module
|
|
512
520
|
)
|
|
513
521
|
parser = found_domain_module.parser_class_object
|
|
522
|
+
requester = found_domain_module.requester_class_object()
|
|
514
523
|
responder = found_domain_module.responder_class_object()
|
|
515
524
|
recorder = found_domain_module.recorder_class_object(record_path=config_static.LogRec.recordings_path)
|
|
516
525
|
|
|
517
526
|
network_logger.info(f"Assigned Modules for [{server_name}]: "
|
|
518
527
|
f"{parser.__name__}, "
|
|
528
|
+
f"{requester.__class__.__name__}, "
|
|
519
529
|
f"{responder.__class__.__name__}, "
|
|
520
530
|
f"{recorder.__class__.__name__}")
|
|
521
531
|
|
|
522
|
-
|
|
523
532
|
# Initializing the client message object with current thread's data.
|
|
524
533
|
# This is needed only to skip error alerts after 'try'.
|
|
525
534
|
client_message_connection: ClientMessage = ClientMessage()
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Using to convert status code to status phrase / string.
|
|
2
|
+
from http import HTTPStatus
|
|
3
|
+
# Parsing PATH template to variables.
|
|
4
|
+
from pathlib import PurePosixPath
|
|
5
|
+
from urllib.parse import unquote
|
|
6
|
+
# Needed to extract parameters after question mark in URL / Path.
|
|
7
|
+
from urllib.parse import urlparse
|
|
8
|
+
from urllib.parse import parse_qs
|
|
9
|
+
|
|
10
|
+
from ...message import ClientMessage
|
|
11
|
+
from .... import http_parse
|
|
12
|
+
from ....print_api import print_api
|
|
13
|
+
|
|
14
|
+
from atomicshop.mitm.shared_functions import create_custom_logger
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class RequesterParent:
|
|
18
|
+
"""The class that is responsible for generating request to client based on the received message."""
|
|
19
|
+
def __init__(self):
|
|
20
|
+
self.logger = create_custom_logger()
|
|
21
|
+
|
|
22
|
+
def build_byte_request(
|
|
23
|
+
self,
|
|
24
|
+
http_method: str,
|
|
25
|
+
endpoint: str,
|
|
26
|
+
http_version: str,
|
|
27
|
+
headers: dict,
|
|
28
|
+
body: bytes
|
|
29
|
+
) -> bytes:
|
|
30
|
+
# noinspection GrazieInspection
|
|
31
|
+
"""
|
|
32
|
+
Create genuine request from input parameters.
|
|
33
|
+
---------------
|
|
34
|
+
The request is built from:
|
|
35
|
+
<http_method> <endpoint> <http_version>\r\n
|
|
36
|
+
Headers1: Value\r\n
|
|
37
|
+
Headers2: Value\r\n
|
|
38
|
+
\r\n # This is meant to end the headers' section
|
|
39
|
+
Body # Request doesn't end with '\r\n\r\n'
|
|
40
|
+
---------------
|
|
41
|
+
Example for POST request:
|
|
42
|
+
POST /api/v1/resource HTTP/1.1\r\n
|
|
43
|
+
Cache-Control: max-age=86400\r\n
|
|
44
|
+
Content-Type: application/json; charset=utf-8\r\n
|
|
45
|
+
\r\n
|
|
46
|
+
{"id":1,"name":"something"}
|
|
47
|
+
---------------
|
|
48
|
+
You can create response as:
|
|
49
|
+
|
|
50
|
+
...POST endpoint/api/1 HTTP/1.1
|
|
51
|
+
header1: value
|
|
52
|
+
header2: value
|
|
53
|
+
|
|
54
|
+
{data: value}...
|
|
55
|
+
|
|
56
|
+
Change 3 dots ("...") to 3 double quotes before "POST" and after "value}".
|
|
57
|
+
This way there will be "\n" added automatically after each line.
|
|
58
|
+
While, the HTTP Client does the parsing of the text and not raw data, most probably it will be parsed well,
|
|
59
|
+
but genuine requests from HTTP sources come with "\r\n" at the end of the line, so better use these for
|
|
60
|
+
better compatibility.
|
|
61
|
+
---------------
|
|
62
|
+
|
|
63
|
+
:param http_method: HTTP Method of Request, e.g. 'GET', 'POST', etc.
|
|
64
|
+
:param endpoint: Endpoint of Request, e.g. '/api/v1/resource'.
|
|
65
|
+
:param http_version: HTTP Version of Response in HTTP Status line.
|
|
66
|
+
:param headers: HTTP Headers of Response.
|
|
67
|
+
:param body: HTTP body data of Response, bytes.
|
|
68
|
+
:return: bytes of the response.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
# CHeck if the HTTP method is valid.
|
|
73
|
+
if http_method not in http_parse.get_request_methods():
|
|
74
|
+
raise ValueError(f"Invalid HTTP Method: {http_method}")
|
|
75
|
+
|
|
76
|
+
# Building the full method endpoint string line and the "\r\n" in the end.
|
|
77
|
+
method_full: str = f"{http_method} {endpoint} {http_version}\r\n"
|
|
78
|
+
|
|
79
|
+
# Defining headers string.
|
|
80
|
+
headers_string: str = str()
|
|
81
|
+
# Adding all the headers to the full response
|
|
82
|
+
for keys, values in headers.items():
|
|
83
|
+
headers_string = headers_string + str(keys) + ": " + str(values) + "\r\n"
|
|
84
|
+
|
|
85
|
+
# Building full string request.
|
|
86
|
+
# 1. Adding full method line.
|
|
87
|
+
# 2. Adding headers string.
|
|
88
|
+
# 3. Adding a line that end headers (with "\r\n").
|
|
89
|
+
# 4. Adding body as byte string.
|
|
90
|
+
request_full_no_body: str = method_full + headers_string + "\r\n"
|
|
91
|
+
|
|
92
|
+
# Converting the HTTP Request string to bytes and adding 'body' bytes.
|
|
93
|
+
request_raw_bytes = request_full_no_body.encode() + body
|
|
94
|
+
except ValueError as exception_object:
|
|
95
|
+
message = \
|
|
96
|
+
f'Create Byte request function error, of the of values provided is not standard: {exception_object}'
|
|
97
|
+
print_api(message, error_type=True, logger=self.logger, logger_method='error', color='red')
|
|
98
|
+
|
|
99
|
+
request_raw_bytes = b''
|
|
100
|
+
|
|
101
|
+
# Parsing the request we created.
|
|
102
|
+
request_parse_test = http_parse.HTTPRequestParse(request_raw_bytes)
|
|
103
|
+
# If there were errors during parsing, it means that something is wrong with response created.
|
|
104
|
+
if request_parse_test.error_message:
|
|
105
|
+
self.logger.error(request_parse_test.error_message)
|
|
106
|
+
request_raw_bytes = b''
|
|
107
|
+
else:
|
|
108
|
+
self.logger.info("Created Valid Byte Request.")
|
|
109
|
+
|
|
110
|
+
return request_raw_bytes
|
|
111
|
+
|
|
112
|
+
def create_request(self, class_client_message: ClientMessage):
|
|
113
|
+
""" This function should be overridden in the child class. """
|
|
114
|
+
|
|
115
|
+
request_bytes: bytes = class_client_message.request_raw_bytes
|
|
116
|
+
return request_bytes
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# These are specified with hardcoded paths instead of relative, because 'create_module_template.py' copies the content.
|
|
2
|
+
from atomicshop.mitm.engines.__parent.requester___parent import RequesterParent
|
|
3
|
+
from atomicshop.mitm.shared_functions import create_custom_logger
|
|
4
|
+
from atomicshop.mitm.message import ClientMessage
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
import time
|
|
8
|
+
datetime
|
|
9
|
+
import binascii
|
|
10
|
+
|
|
11
|
+
# This is 'example' '.proto' file that contains message 'ExampleResponse'.
|
|
12
|
+
from .example_pb2 import ExampleRequest
|
|
13
|
+
# Import from 'protobuf' the 'json_format' library.
|
|
14
|
+
from google.protobuf import json_format
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class RequesterGeneral(RequesterParent):
|
|
19
|
+
"""The class that is responsible for generating request to client based on the received message."""
|
|
20
|
+
# When initializing main classes through "super" you need to pass parameters to init
|
|
21
|
+
def __init__(self):
|
|
22
|
+
super().__init__()
|
|
23
|
+
|
|
24
|
+
self.logger = create_custom_logger()
|
|
25
|
+
|
|
26
|
+
# def create_request(self, class_client_message: ClientMessage):
|
|
27
|
+
# # noinspection GrazieInspection
|
|
28
|
+
# """
|
|
29
|
+
# For more examples check the responder.
|
|
30
|
+
# Function to create Response based on ClientMessage and its Request.
|
|
31
|
+
#
|
|
32
|
+
# :param class_client_message: contains request and other parameters to help creating response.
|
|
33
|
+
# :return: 1 request in byte string.
|
|
34
|
+
# -----------------------------------
|
|
35
|
+
#
|
|
36
|
+
# # Example of creating byte string using 'build_byte_request' function:
|
|
37
|
+
# request_bytes: bytes = self.build_byte_request(
|
|
38
|
+
# http_method=class_client_message.request_raw_decoded.command,
|
|
39
|
+
# endpoint=class_client_message.request_raw_decoded.path,
|
|
40
|
+
# http_version=class_client_message.request_raw_decoded.request_version,
|
|
41
|
+
# headers=response_headers,
|
|
42
|
+
# body=b''
|
|
43
|
+
# )
|
|
44
|
+
#
|
|
45
|
+
# return request_bytes
|
|
46
|
+
# -----------------------------------
|
|
@@ -14,6 +14,7 @@ CONFIG_FILE_NAME: str = "engine_config.toml"
|
|
|
14
14
|
|
|
15
15
|
REFERENCE_PARSER_FILE_NAME: str = f"parser_{REFERENCE_ENGINE_NAME}.py"
|
|
16
16
|
REFERENCE_RESPONDER_FILE_NAME: str = f"responder_{REFERENCE_ENGINE_NAME}.py"
|
|
17
|
+
REFERENCE_REQUESTER_FILE_NAME: str = f"requester_{REFERENCE_ENGINE_NAME}.py"
|
|
17
18
|
REFERENCE_RECORDER_FILE_NAME: str = f"recorder_{REFERENCE_ENGINE_NAME}.py"
|
|
18
19
|
|
|
19
20
|
SCRIPT_DIRECTORY: str = filesystem.get_file_directory(__file__)
|
|
@@ -43,10 +44,12 @@ class CreateModuleTemplate:
|
|
|
43
44
|
reference_folder_path: str = SCRIPT_DIRECTORY + os.sep + REFERENCE_ENGINE_NAME
|
|
44
45
|
self.parser_general_path: str = reference_folder_path + os.sep + REFERENCE_PARSER_FILE_NAME
|
|
45
46
|
self.responder_general_path: str = reference_folder_path + os.sep + REFERENCE_RESPONDER_FILE_NAME
|
|
47
|
+
self.requester_general_path: str = reference_folder_path + os.sep + REFERENCE_REQUESTER_FILE_NAME
|
|
46
48
|
self.recorder_general_path: str = reference_folder_path + os.sep + REFERENCE_RECORDER_FILE_NAME
|
|
47
49
|
|
|
48
50
|
self.parser_file_name: str = f"parser.py"
|
|
49
51
|
self.responder_file_name: str = f"responder.py"
|
|
52
|
+
self.requester_file_name: str = f"requester.py"
|
|
50
53
|
self.recorder_file_name: str = f"recorder.py"
|
|
51
54
|
|
|
52
55
|
self.create_template()
|
|
@@ -63,6 +66,7 @@ class CreateModuleTemplate:
|
|
|
63
66
|
|
|
64
67
|
self._create_engine_module_from_reference(file_path=self.parser_general_path, module_type='parser')
|
|
65
68
|
self._create_engine_module_from_reference(file_path=self.responder_general_path, module_type='responder')
|
|
69
|
+
self._create_engine_module_from_reference(file_path=self.requester_general_path, module_type='requester')
|
|
66
70
|
self._create_engine_module_from_reference(file_path=self.recorder_general_path, module_type='recorder')
|
|
67
71
|
|
|
68
72
|
self.create_config_file()
|
|
@@ -91,13 +95,15 @@ class CreateModuleTemplate:
|
|
|
91
95
|
def _create_engine_module_from_reference(
|
|
92
96
|
self,
|
|
93
97
|
file_path: str,
|
|
94
|
-
module_type: Literal['parser', 'responder', 'recorder']
|
|
98
|
+
module_type: Literal['parser', 'responder', 'requester', 'recorder']
|
|
95
99
|
):
|
|
96
100
|
|
|
97
101
|
if module_type == 'parser':
|
|
98
102
|
new_module_file_name = self.parser_file_name
|
|
99
103
|
elif module_type == 'responder':
|
|
100
104
|
new_module_file_name = self.responder_file_name
|
|
105
|
+
elif module_type == 'requester':
|
|
106
|
+
new_module_file_name = self.requester_file_name
|
|
101
107
|
elif module_type == 'recorder':
|
|
102
108
|
new_module_file_name = self.recorder_file_name
|
|
103
109
|
else:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
atomicshop/__init__.py,sha256=
|
|
1
|
+
atomicshop/__init__.py,sha256=91TkRXyPpHainjVtqMX8IhnOPsq4mLI4-W-pBKoiPbk,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
|
|
@@ -136,7 +136,7 @@ atomicshop/file_io/xmls.py,sha256=zh3SuK-dNaFq2NDNhx6ivcf4GYCfGM8M10PcEwDSpxk,21
|
|
|
136
136
|
atomicshop/mitm/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
137
137
|
atomicshop/mitm/config_static.py,sha256=N3D06C_wUytwm80PQCL90ZGHLMHeQlCcI2XQQU2FZ1I,8145
|
|
138
138
|
atomicshop/mitm/config_toml_editor.py,sha256=2p1CMcktWRR_NW-SmyDwylu63ad5e0-w1QPMa8ZLDBw,1635
|
|
139
|
-
atomicshop/mitm/connection_thread_worker.py,sha256=
|
|
139
|
+
atomicshop/mitm/connection_thread_worker.py,sha256=H3Ogwo9iMG4WgKZabJGl69_haIkEsMR4p6l4CGbwBYU,28500
|
|
140
140
|
atomicshop/mitm/import_config.py,sha256=MbMgX5IpqC6fUZUTHLOtHyHbzab2tG4RgD2O18csKcI,16506
|
|
141
141
|
atomicshop/mitm/initialize_engines.py,sha256=lucq9LwkWTnrYKsnt8bgs8t9R_y3gOPmzLyEmRuXD8s,6628
|
|
142
142
|
atomicshop/mitm/message.py,sha256=CDhhm4BTuZE7oNZCjvIZ4BuPOW4MuIzQLOg91hJaxDI,3065
|
|
@@ -145,15 +145,17 @@ atomicshop/mitm/recs_files.py,sha256=ZAAD0twun-FtmbSniXe3XQhIlawvANNB_HxwbHj7kwI
|
|
|
145
145
|
atomicshop/mitm/shared_functions.py,sha256=0lzeyINd44sVEfFbahJxQmz6KAMWbYrW5ou3UYfItvw,1777
|
|
146
146
|
atomicshop/mitm/statistic_analyzer.py,sha256=5_sAYGX2Xunzo_pS2W5WijNCwr_BlGJbbOO462y_wN4,27533
|
|
147
147
|
atomicshop/mitm/engines/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
148
|
-
atomicshop/mitm/engines/create_module_template.py,sha256=
|
|
148
|
+
atomicshop/mitm/engines/create_module_template.py,sha256=nZE99lwHc_Wm4zmEt7P3cEw3E32Ms65dVbhqZbqOV6U,5577
|
|
149
149
|
atomicshop/mitm/engines/create_module_template_main_example.py,sha256=LeQ44Rp2Gi_KbIDY_4OMS0odkSK3zFZWra_oAka5eJY,243
|
|
150
150
|
atomicshop/mitm/engines/__parent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
151
151
|
atomicshop/mitm/engines/__parent/parser___parent.py,sha256=HHaCXhScl3OlPjz6eUxsDpJaZyk6BNuDMc9xCkeo2Ws,661
|
|
152
152
|
atomicshop/mitm/engines/__parent/recorder___parent.py,sha256=JbbcpV6j4odvREmvcXQlzKr5_hwLiEHhF0O414PlTes,6010
|
|
153
|
+
atomicshop/mitm/engines/__parent/requester___parent.py,sha256=mKHR1xrfx0CCO74vv164Fr_dVDhptqvYsE3ORoM4_7k,5107
|
|
153
154
|
atomicshop/mitm/engines/__parent/responder___parent.py,sha256=mtiS_6ej9nxT9UhAQR4ftMqnqL-j_kO3u8KEaoEaI9k,9495
|
|
154
155
|
atomicshop/mitm/engines/__reference_general/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
155
156
|
atomicshop/mitm/engines/__reference_general/parser___reference_general.py,sha256=57MEPZMAjTO6xBDZ-yt6lgGJyqRrP0Do5Gk_cgCiPns,2998
|
|
156
157
|
atomicshop/mitm/engines/__reference_general/recorder___reference_general.py,sha256=El2_YHLoHUCiKfkAmGlXxtFpmSjsUFdsb8I1MvSAFaM,653
|
|
158
|
+
atomicshop/mitm/engines/__reference_general/requester___reference_general.py,sha256=yXw8cJ5_0XlupnEtEhqyOR4rvQEAg68Ok39vKa8vKZY,1949
|
|
157
159
|
atomicshop/mitm/engines/__reference_general/responder___reference_general.py,sha256=zVMmON1sy6HH-A35uZZuAURBMfKS5X9ivkPVjPl-sCw,9654
|
|
158
160
|
atomicshop/mitm/statistic_analyzer_helper/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
159
161
|
atomicshop/mitm/statistic_analyzer_helper/analyzer_helper.py,sha256=pk6L1t1ea1kvlBoR9QEJptOmaX-mumhwLsP2GCKukbk,5920
|
|
@@ -334,8 +336,8 @@ atomicshop/wrappers/socketw/statistics_csv.py,sha256=WcNyaqEZ82S5-f3kzqi1nllNT2N
|
|
|
334
336
|
atomicshop/wrappers/winregw/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
335
337
|
atomicshop/wrappers/winregw/winreg_installed_software.py,sha256=Qzmyktvob1qp6Tjk2DjLfAqr_yXV0sgWzdMW_9kwNjY,2345
|
|
336
338
|
atomicshop/wrappers/winregw/winreg_network.py,sha256=3Ts1sVqSUiCDsHRHwJCbiZ9EYvv2ELGxF0Y_pibGU4k,9596
|
|
337
|
-
atomicshop-3.0.
|
|
338
|
-
atomicshop-3.0.
|
|
339
|
-
atomicshop-3.0.
|
|
340
|
-
atomicshop-3.0.
|
|
341
|
-
atomicshop-3.0.
|
|
339
|
+
atomicshop-3.1.0.dist-info/LICENSE.txt,sha256=lLU7EYycfYcK2NR_1gfnhnRC8b8ccOTElACYplgZN88,1094
|
|
340
|
+
atomicshop-3.1.0.dist-info/METADATA,sha256=CvGoKMdS-InZlR0Mk23zWG7_g9YqXprrxx05qwPwHiY,10653
|
|
341
|
+
atomicshop-3.1.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
342
|
+
atomicshop-3.1.0.dist-info/top_level.txt,sha256=EgKJB-7xcrAPeqTRF2laD_Np2gNGYkJkd4OyXqpJphA,11
|
|
343
|
+
atomicshop-3.1.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|