nopasaran 0.2.96__py3-none-any.whl → 0.2.98__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.
- nopasaran/tools/http_1_socket_server.py +43 -109
- nopasaran/tools/https_1_socket_server.py +41 -75
- {nopasaran-0.2.96.dist-info → nopasaran-0.2.98.dist-info}/METADATA +1 -1
- {nopasaran-0.2.96.dist-info → nopasaran-0.2.98.dist-info}/RECORD +8 -8
- {nopasaran-0.2.96.dist-info → nopasaran-0.2.98.dist-info}/LICENSE +0 -0
- {nopasaran-0.2.96.dist-info → nopasaran-0.2.98.dist-info}/WHEEL +0 -0
- {nopasaran-0.2.96.dist-info → nopasaran-0.2.98.dist-info}/entry_points.txt +0 -0
- {nopasaran-0.2.96.dist-info → nopasaran-0.2.98.dist-info}/top_level.txt +0 -0
@@ -17,21 +17,45 @@ class HTTP1SocketServer:
|
|
17
17
|
self.client_socket = None
|
18
18
|
self.TIMEOUT = 5.0
|
19
19
|
|
20
|
-
def handle_client_connection(self, client_socket):
|
20
|
+
def handle_client_connection(self, client_socket, timeout):
|
21
21
|
"""
|
22
22
|
Handle a client connection by processing the incoming request and sending the appropriate response.
|
23
23
|
"""
|
24
|
-
|
25
|
-
|
24
|
+
client_socket.setblocking(False)
|
25
|
+
ready_to_read, _, _ = select.select([client_socket], [], [], timeout)
|
26
|
+
|
27
|
+
if not ready_to_read:
|
28
|
+
client_socket.close()
|
29
|
+
return None, EventNames.TIMEOUT.name
|
30
|
+
|
31
|
+
try:
|
32
|
+
request = client_socket.recv(4096)
|
33
|
+
except socket.timeout:
|
34
|
+
client_socket.close()
|
35
|
+
return None, EventNames.TIMEOUT.name
|
36
|
+
except ConnectionResetError as e:
|
37
|
+
client_socket.close()
|
38
|
+
return str(e), EventNames.ERROR.name
|
39
|
+
|
40
|
+
if not request:
|
41
|
+
client_socket.close()
|
42
|
+
return None, EventNames.TIMEOUT.name
|
43
|
+
|
44
|
+
request_str = request.decode('utf-8', errors='ignore')
|
45
|
+
|
26
46
|
# Extract request line and headers
|
27
47
|
headers_end_index = request_str.find("\r\n\r\n")
|
28
48
|
headers_part = request_str[:headers_end_index] if headers_end_index != -1 else request_str
|
29
49
|
request_line = headers_part.split("\r\n")[0]
|
30
|
-
|
50
|
+
|
51
|
+
try:
|
52
|
+
method, path, _ = request_line.split(" ", 2)
|
53
|
+
except ValueError:
|
54
|
+
method, path = "GET", "/"
|
31
55
|
|
32
56
|
route_key = (path, method)
|
33
57
|
route_info_list = self.routes.get(route_key)
|
34
|
-
|
58
|
+
|
35
59
|
if route_info_list:
|
36
60
|
response = ""
|
37
61
|
for route_info in route_info_list:
|
@@ -39,42 +63,30 @@ class HTTP1SocketServer:
|
|
39
63
|
status_code = route_info.get('status', 200)
|
40
64
|
headers = route_info.get('headers', [])
|
41
65
|
|
42
|
-
# Construct each part of the response
|
43
66
|
response_part = f"HTTP/1.1 {status_code} OK\r\n"
|
44
67
|
for header_name, header_value in headers:
|
45
68
|
response_part += f"{header_name}: {header_value}\r\n"
|
46
|
-
response_part += f"\r\n{response_body}\r\n\r\n"
|
69
|
+
response_part += f"\r\n{response_body}\r\n\r\n"
|
47
70
|
|
48
|
-
# Append the response part to the full response
|
49
71
|
response += response_part
|
50
72
|
|
51
|
-
# Send the full combined response to the client
|
52
73
|
client_socket.sendall(response.encode())
|
53
|
-
|
54
74
|
else:
|
55
75
|
response_body = 'NoPASARAN HTTP/1.1 Server'
|
56
76
|
status_code = 404
|
57
|
-
|
58
|
-
|
59
|
-
# Construct the HTTP response
|
60
|
-
response = f"HTTP/1.1 {status_code} OK\r\n"
|
61
|
-
for header_name, header_value in headers:
|
62
|
-
response += f"{header_name}: {header_value}\r\n"
|
63
|
-
response += f"\r\n{response_body}"
|
64
|
-
|
65
|
-
# Send response to client
|
77
|
+
response = f"HTTP/1.1 {status_code} Not Found\r\nContent-Length: {len(response_body)}\r\n\r\n{response_body}"
|
66
78
|
client_socket.sendall(response.encode())
|
67
79
|
|
68
80
|
client_socket.close()
|
69
81
|
|
70
|
-
# Store the raw received request data
|
71
82
|
self.received_request_data = request
|
72
83
|
|
73
|
-
# Notify that a request has been received
|
74
84
|
if self.request_received:
|
75
85
|
with self.request_received:
|
76
86
|
self.request_received.notify_all()
|
77
87
|
|
88
|
+
return request, EventNames.REQUEST_RECEIVED.name
|
89
|
+
|
78
90
|
def wait_for_request(self, port, timeout):
|
79
91
|
"""
|
80
92
|
Wait for an HTTP request or timeout.
|
@@ -84,7 +96,7 @@ class HTTP1SocketServer:
|
|
84
96
|
timeout (int): The timeout duration in seconds.
|
85
97
|
|
86
98
|
Returns:
|
87
|
-
Tuple[bytes, str]: The raw received request data or None if a timeout occurs, and the event name.
|
99
|
+
Tuple[bytes or None, str]: The raw received request data or None if a timeout occurs, and the event name.
|
88
100
|
"""
|
89
101
|
server_address = ('', port)
|
90
102
|
self.request_received = threading.Condition()
|
@@ -96,22 +108,18 @@ class HTTP1SocketServer:
|
|
96
108
|
server_socket.listen(1)
|
97
109
|
server_socket.setblocking(False)
|
98
110
|
|
99
|
-
# Initialize the timeout timer
|
100
111
|
start_time = time.time()
|
101
112
|
|
102
113
|
while True:
|
103
|
-
# Check if the timeout has elapsed
|
104
114
|
elapsed_time = time.time() - start_time
|
105
|
-
if elapsed_time
|
115
|
+
if elapsed_time >= timeout:
|
106
116
|
return None, EventNames.TIMEOUT.name
|
107
|
-
|
108
|
-
# Use select to wait for a connection with a timeout
|
117
|
+
|
109
118
|
ready_to_read, _, _ = select.select([server_socket], [], [], timeout - elapsed_time)
|
110
|
-
|
119
|
+
|
111
120
|
if ready_to_read:
|
112
121
|
client_socket, _ = server_socket.accept()
|
113
|
-
self.handle_client_connection(client_socket)
|
114
|
-
return self.received_request_data, EventNames.REQUEST_RECEIVED.name
|
122
|
+
return self.handle_client_connection(client_socket, timeout - elapsed_time)
|
115
123
|
|
116
124
|
def start(self, host, port):
|
117
125
|
"""
|
@@ -121,81 +129,8 @@ class HTTP1SocketServer:
|
|
121
129
|
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
122
130
|
self.sock.bind((host, port))
|
123
131
|
self.sock.listen(5)
|
124
|
-
|
125
|
-
return EventNames.SERVER_STARTED.name, f"Server successfully started at {host}:{port}."
|
126
132
|
|
127
|
-
|
128
|
-
"""
|
129
|
-
Listen for HTTP/1.1 requests until a timeout occurs, then process all received requests.
|
130
|
-
|
131
|
-
Returns:
|
132
|
-
Tuple[str, str, str]: (event_name, message, received_data)
|
133
|
-
- event_name: The type of event that occurred (from EventNames)
|
134
|
-
- TIMEOUT: Timeout occurred before all requests were received
|
135
|
-
- REJECTED: Received a 4xx or 5xx status code
|
136
|
-
- RECEIVED_REQUESTS: All requests have been received
|
137
|
-
- ERROR: Error occurred while receiving requests
|
138
|
-
- message: A descriptive message about what happened
|
139
|
-
- received_data: String representation of the received requests or None
|
140
|
-
"""
|
141
|
-
if not self.sock:
|
142
|
-
return EventNames.ERROR.name, "Server not started", None
|
143
|
-
|
144
|
-
requests_received = []
|
145
|
-
start_time = time.time()
|
146
|
-
|
147
|
-
# Make the server socket non-blocking for accepting connections
|
148
|
-
self.sock.setblocking(False)
|
149
|
-
|
150
|
-
# Keep accepting connections until we hit a timeout
|
151
|
-
while True:
|
152
|
-
# Check for overall timeout
|
153
|
-
elapsed_time = time.time() - start_time
|
154
|
-
if elapsed_time > self.TIMEOUT:
|
155
|
-
if len(requests_received) == 0:
|
156
|
-
return EventNames.TIMEOUT.name, f"Timeout after {self.TIMEOUT}s and received no requests.", None
|
157
|
-
else:
|
158
|
-
break # We've received some requests, so consider it done
|
159
|
-
|
160
|
-
try:
|
161
|
-
# Try to accept a new connection
|
162
|
-
ready_to_read, _, _ = select.select([self.sock], [], [], 0.5)
|
163
|
-
|
164
|
-
if ready_to_read:
|
165
|
-
client_socket, _ = self.sock.accept()
|
166
|
-
client_socket.settimeout(0.5)
|
167
|
-
|
168
|
-
try:
|
169
|
-
data = client_socket.recv(4096)
|
170
|
-
|
171
|
-
if data:
|
172
|
-
# Parse the HTTP/1.1 request
|
173
|
-
request_str = data.decode('utf-8', errors='ignore')
|
174
|
-
requests_received.append(request_str)
|
175
|
-
|
176
|
-
# Check for error status codes
|
177
|
-
if "HTTP/1.1 5" in request_str or "HTTP/1.1 4" in request_str:
|
178
|
-
try:
|
179
|
-
status_line = request_str.split("\r\n")[0]
|
180
|
-
status_code = status_line.split(" ")[1]
|
181
|
-
client_socket.close()
|
182
|
-
return EventNames.REJECTED.name, f"Received {status_code} status code.", request_str
|
183
|
-
except (IndexError, ValueError):
|
184
|
-
client_socket.close()
|
185
|
-
return EventNames.REJECTED.name, "Received 4xx or 5xx status code.", request_str
|
186
|
-
finally:
|
187
|
-
client_socket.close()
|
188
|
-
except socket.timeout:
|
189
|
-
continue
|
190
|
-
except Exception as e:
|
191
|
-
return EventNames.ERROR.name, f"Error while receiving requests: {str(e)}", \
|
192
|
-
str(requests_received) if requests_received else None
|
193
|
-
|
194
|
-
# If we get here, we've either collected all requests or hit the timeout
|
195
|
-
if len(requests_received) == 0:
|
196
|
-
return EventNames.TIMEOUT.name, "No requests received before timeout.", None
|
197
|
-
|
198
|
-
return EventNames.RECEIVED_REQUESTS.name, f"Received {len(requests_received)} HTTP/1.1 requests.", str(requests_received)
|
133
|
+
return EventNames.SERVER_STARTED.name, f"Server successfully started at {host}:{port}."
|
199
134
|
|
200
135
|
def close(self):
|
201
136
|
"""Close the HTTP/1.1 connection and clean up resources"""
|
@@ -204,11 +139,10 @@ class HTTP1SocketServer:
|
|
204
139
|
self.client_socket.close()
|
205
140
|
if self.sock:
|
206
141
|
self.sock.close()
|
207
|
-
|
208
|
-
# Clear references
|
142
|
+
|
209
143
|
self.client_socket = None
|
210
144
|
self.sock = None
|
211
|
-
|
212
|
-
return EventNames.
|
145
|
+
|
146
|
+
return EventNames.CONNECTION_ENDING.name
|
213
147
|
except Exception as e:
|
214
|
-
return EventNames.
|
148
|
+
return EventNames.CONNECTION_ENDING.name
|
@@ -30,13 +30,10 @@ class HTTPS1SocketServer:
|
|
30
30
|
def generate_and_load_cert(self, identifier):
|
31
31
|
cert_path, key_path = self.generate_self_signed_cert(identifier)
|
32
32
|
|
33
|
-
# Check if paths are valid
|
34
33
|
if not cert_path or not os.path.exists(cert_path):
|
35
34
|
raise FileNotFoundError(f"[HTTPS1SocketServer] Certificate file not found at {cert_path}")
|
36
35
|
if not key_path or not os.path.exists(key_path):
|
37
36
|
raise FileNotFoundError(f"[HTTPS1SocketServer] Key file not found at {key_path}")
|
38
|
-
|
39
|
-
# Optional: Ensure they're not empty (some systems fail silently on write)
|
40
37
|
if os.path.getsize(cert_path) == 0:
|
41
38
|
raise ValueError(f"[HTTPS1SocketServer] Certificate file is empty at {cert_path}")
|
42
39
|
if os.path.getsize(key_path) == 0:
|
@@ -50,7 +47,6 @@ class HTTPS1SocketServer:
|
|
50
47
|
self._cert_path = cert_path
|
51
48
|
self._key_path = key_path
|
52
49
|
|
53
|
-
|
54
50
|
def generate_self_signed_cert(self, identifier):
|
55
51
|
key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
|
56
52
|
|
@@ -60,11 +56,10 @@ class HTTPS1SocketServer:
|
|
60
56
|
|
61
57
|
alt_names = [x509.DNSName(identifier)]
|
62
58
|
try:
|
63
|
-
# Try to interpret as IP address
|
64
59
|
import ipaddress
|
65
60
|
alt_names.append(x509.IPAddress(ipaddress.ip_address(identifier)))
|
66
61
|
except ValueError:
|
67
|
-
pass
|
62
|
+
pass
|
68
63
|
|
69
64
|
cert = (
|
70
65
|
x509.CertificateBuilder()
|
@@ -102,16 +97,18 @@ class HTTPS1SocketServer:
|
|
102
97
|
self.sock.bind((host, port))
|
103
98
|
self.sock.listen(5)
|
104
99
|
return EventNames.SERVER_STARTED.name, f"HTTPS server started at {host}:{port}"
|
105
|
-
|
106
|
-
|
107
|
-
|
108
100
|
|
109
101
|
def handle_client_connection(self, tls_socket):
|
110
|
-
|
102
|
+
try:
|
103
|
+
request = tls_socket.recv(4096)
|
104
|
+
if not request:
|
105
|
+
return # Client closed connection or sent nothing
|
106
|
+
except socket.timeout:
|
107
|
+
return # Timed out waiting for client data
|
108
|
+
|
111
109
|
request_str = request.decode("utf-8", errors="ignore")
|
112
110
|
self.received_request_data = request
|
113
111
|
|
114
|
-
# Parse request line
|
115
112
|
headers_end_index = request_str.find("\r\n\r\n")
|
116
113
|
headers_part = request_str[:headers_end_index] if headers_end_index != -1 else request_str
|
117
114
|
request_line = headers_part.split("\r\n")[0]
|
@@ -147,51 +144,12 @@ class HTTPS1SocketServer:
|
|
147
144
|
with self.request_received:
|
148
145
|
self.request_received.notify_all()
|
149
146
|
|
150
|
-
def receive_test_frames(self):
|
151
|
-
if not self.sock:
|
152
|
-
return EventNames.ERROR.name, "Server not started", None
|
153
|
-
|
154
|
-
requests_received = []
|
155
|
-
start_time = time.time()
|
156
|
-
self.sock.setblocking(False)
|
157
|
-
|
158
|
-
while True:
|
159
|
-
if time.time() - start_time > self.TIMEOUT:
|
160
|
-
if not requests_received:
|
161
|
-
return EventNames.TIMEOUT.name, "Timeout with no request", None
|
162
|
-
break
|
163
|
-
|
164
|
-
try:
|
165
|
-
ready_to_read, _, _ = select.select([self.sock], [], [], 0.5)
|
166
|
-
if ready_to_read:
|
167
|
-
client_sock, _ = self.sock.accept()
|
168
|
-
tls_sock = self.context.wrap_socket(client_sock, server_side=True)
|
169
|
-
tls_sock.settimeout(1.0)
|
170
|
-
try:
|
171
|
-
self.handle_client_connection(tls_sock)
|
172
|
-
req_str = self.received_request_data.decode("utf-8", errors="ignore")
|
173
|
-
requests_received.append(req_str)
|
174
|
-
except Exception as e:
|
175
|
-
return EventNames.ERROR.name, f"TLS error: {e}", None
|
176
|
-
except Exception as e:
|
177
|
-
return EventNames.ERROR.name, str(e), None
|
178
|
-
|
179
|
-
return EventNames.RECEIVED_REQUESTS.name, f"Received {len(requests_received)} HTTPS requests.", str(requests_received)
|
180
|
-
|
181
147
|
def wait_for_request(self, port, timeout):
|
182
148
|
"""
|
183
149
|
Wait for a single HTTPS request with TLS handshake and user-defined timeout.
|
184
|
-
|
185
|
-
Args:
|
186
|
-
port (int): The port to bind and listen on.
|
187
|
-
timeout (int): Timeout duration in seconds.
|
188
|
-
|
189
|
-
Returns:
|
190
|
-
Tuple[bytes or None, str]: The raw request data (or None if timed out) and an event name.
|
191
150
|
"""
|
192
151
|
self.received_request_data = None
|
193
|
-
self.request_received = None
|
194
|
-
|
152
|
+
self.request_received = None
|
195
153
|
context = self.context
|
196
154
|
start_time = time.time()
|
197
155
|
|
@@ -207,36 +165,44 @@ class HTTPS1SocketServer:
|
|
207
165
|
return None, EventNames.TIMEOUT.name
|
208
166
|
|
209
167
|
try:
|
210
|
-
|
168
|
+
remaining = timeout - elapsed
|
169
|
+
ready, _, _ = select.select([server_socket], [], [], remaining)
|
211
170
|
if ready:
|
212
171
|
client_sock, _ = server_socket.accept()
|
213
|
-
|
214
|
-
|
215
|
-
|
172
|
+
client_sock.settimeout(2.0) # Important: Timeout for TLS handshake and recv
|
173
|
+
try:
|
174
|
+
with context.wrap_socket(client_sock, server_side=True) as tls_sock:
|
175
|
+
tls_sock.settimeout(2.0) # Also set after TLS handshake
|
216
176
|
self.handle_client_connection(tls_sock)
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
177
|
+
if self.received_request_data:
|
178
|
+
return self.received_request_data, EventNames.REQUEST_RECEIVED.name
|
179
|
+
else:
|
180
|
+
return None, EventNames.TIMEOUT.name
|
181
|
+
except (ssl.SSLError, socket.timeout):
|
182
|
+
return None, EventNames.TIMEOUT.name
|
183
|
+
except Exception:
|
184
|
+
return None, EventNames.ERROR.name
|
185
|
+
except Exception:
|
186
|
+
return None, EventNames.ERROR.name
|
222
187
|
|
223
188
|
def close(self):
|
224
|
-
if self.client_socket:
|
225
|
-
self.client_socket.close()
|
226
|
-
if self.sock:
|
227
|
-
self.sock.close()
|
228
|
-
self.client_socket = None
|
229
|
-
self.sock = None
|
230
|
-
|
231
189
|
try:
|
232
|
-
if self.
|
233
|
-
|
234
|
-
if self.
|
235
|
-
|
236
|
-
except Exception as e:
|
237
|
-
return EventNames.ERROR.name, f"Certificate cleanup error: {str(e)}"
|
190
|
+
if self.client_socket:
|
191
|
+
self.client_socket.close()
|
192
|
+
if self.sock:
|
193
|
+
self.sock.close()
|
238
194
|
|
239
|
-
|
195
|
+
self.client_socket = None
|
196
|
+
self.sock = None
|
240
197
|
|
241
|
-
|
198
|
+
try:
|
199
|
+
if self._cert_path and os.path.exists(self._cert_path):
|
200
|
+
os.remove(self._cert_path)
|
201
|
+
if self._key_path and os.path.exists(self._key_path):
|
202
|
+
os.remove(self._key_path)
|
203
|
+
except Exception as e:
|
204
|
+
return EventNames.ERROR.name, f"Certificate cleanup error: {str(e)}"
|
242
205
|
|
206
|
+
return EventNames.CONNECTION_ENDING.name
|
207
|
+
except Exception:
|
208
|
+
return EventNames.CONNECTION_ENDING.name
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: nopasaran
|
3
|
-
Version: 0.2.
|
3
|
+
Version: 0.2.98
|
4
4
|
Summary: NoPASARAN is an advanced network tool designed to detect, fingerprint, and locate network middleboxes in a unified framework.
|
5
5
|
Home-page: https://github.com/BenIlies/NoPASARAN
|
6
6
|
Author: Ilies Benhabbour
|
@@ -68,17 +68,17 @@ nopasaran/sniffers/sniffer.py,sha256=rmUlW4_IvLurz_qelpFFSdIaiyWXBljFZ2Cdec3ohSE
|
|
68
68
|
nopasaran/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
69
69
|
nopasaran/tools/checks.py,sha256=-Y5gOm0f8ZIkOdiViCCrTR9jC1MTt8IbnOrLp5tBzVo,1299
|
70
70
|
nopasaran/tools/echo_socket_server.py,sha256=_a6bcDq4pXVjeEnNqPuulipgfH2bddf-N-ExXBTuQiE,3748
|
71
|
-
nopasaran/tools/http_1_socket_server.py,sha256
|
71
|
+
nopasaran/tools/http_1_socket_server.py,sha256=jZTxZdRutDnPwxvAiC4GM2gCBgWhwv1by2bzgxCXZFc,5162
|
72
72
|
nopasaran/tools/http_2_overwrite.py,sha256=vKJzAwbisK9qkN3GCp7FuFfSaPAiUGPwZD9ABYi6eIs,25177
|
73
73
|
nopasaran/tools/http_2_socket_base.py,sha256=KDfkU4Nr_TXxqzp1-ZHec_UawfDZ5oIIj-JysEhi0Q0,11233
|
74
74
|
nopasaran/tools/http_2_socket_client.py,sha256=5mmc8FuT2dNVAihbMdvyytVwME_xSc7mez_jwU3YZOA,6256
|
75
75
|
nopasaran/tools/http_2_socket_server.py,sha256=8W-bxmdoGyK5moxpC87V1EGuZ9jJpIKhokvZrDV84kk,5551
|
76
|
-
nopasaran/tools/https_1_socket_server.py,sha256=
|
76
|
+
nopasaran/tools/https_1_socket_server.py,sha256=ytLVYdH5je4L6ElCKKhhsbU_53gT6QF9l5ym7q5TMmo,8162
|
77
77
|
nopasaran/tools/tcp_dns_socket_server.py,sha256=UKOgyhCXj-u9KJoP8qsQLbyyIkjOhyG7-_3GSP8e0DM,7908
|
78
78
|
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
79
|
-
nopasaran-0.2.
|
80
|
-
nopasaran-0.2.
|
81
|
-
nopasaran-0.2.
|
82
|
-
nopasaran-0.2.
|
83
|
-
nopasaran-0.2.
|
84
|
-
nopasaran-0.2.
|
79
|
+
nopasaran-0.2.98.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
80
|
+
nopasaran-0.2.98.dist-info/METADATA,sha256=VwjlwGoctGVL9FLSVDuih3xWpaGIkAgLRr6LwWGKgII,5496
|
81
|
+
nopasaran-0.2.98.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
|
82
|
+
nopasaran-0.2.98.dist-info/entry_points.txt,sha256=LaOz5GlWuMLjzg4KOEB5OVTattCXVW6a4nSW-WQajCw,55
|
83
|
+
nopasaran-0.2.98.dist-info/top_level.txt,sha256=60R1FzpprzU8iiJ1cBMNOA0F083_lYoctFo7pzOpMwY,16
|
84
|
+
nopasaran-0.2.98.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|