flask-Humanify 0.1.3.2__py3-none-any.whl → 0.2.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.
flask_humanify/ipset.py DELETED
@@ -1,315 +0,0 @@
1
- import json
2
- import logging
3
- import socket
4
- import time
5
- import threading
6
- import os
7
- import importlib.metadata
8
- import importlib.resources
9
- import urllib.request
10
- from pathlib import Path
11
- from typing import Dict, List, Optional, Tuple
12
- from datetime import datetime, timedelta
13
- from netaddr import IPNetwork, IPAddress
14
-
15
-
16
- logger = logging.getLogger(__name__)
17
-
18
-
19
- try:
20
- importlib.metadata.distribution("flask-humanify")
21
- BASE_DIR = importlib.resources.files("flask_humanify")
22
- except importlib.metadata.PackageNotFoundError:
23
- BASE_DIR = Path(__file__).parent
24
-
25
- DATASET_DIR = BASE_DIR / "datasets"
26
- if not DATASET_DIR.exists():
27
- DATASET_DIR.mkdir(parents=True)
28
-
29
- IPSET_DATA_PATH = str(DATASET_DIR / "ipset.json")
30
-
31
-
32
- class IPSetMemoryServer:
33
- """A singleton memory server that manages IP sets and provides lookup functionality."""
34
-
35
- _instance = None
36
- _lock = threading.Lock()
37
-
38
- def __new__(cls, port: int = 9876, data_path: Optional[str] = None):
39
- if data_path is None:
40
- data_path = IPSET_DATA_PATH
41
-
42
- with cls._lock:
43
- if cls._instance is None:
44
- cls._instance = super(IPSetMemoryServer, cls).__new__(cls)
45
- cls._instance.initialized = False
46
- return cls._instance
47
-
48
- def __init__(self, port: int = 9876, data_path: Optional[str] = None):
49
- if data_path is None:
50
- data_path = IPSET_DATA_PATH
51
-
52
- if getattr(self, "initialized", False):
53
- return
54
-
55
- self.port = port
56
- self.data_path = data_path
57
- self.ip_to_groups: Dict[str, List[str]] = {}
58
- self.cidrs_to_ips: Dict[IPNetwork, List[str]] = {}
59
- self.last_update: Optional[datetime] = None
60
- self.server_socket = None
61
- self.server_thread = None
62
- self.running = False
63
- self.initialized = True
64
-
65
- def is_server_running(self) -> bool:
66
- """Check if the server is already running on the specified port."""
67
- try:
68
- with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
69
- s.connect(("127.0.0.1", self.port))
70
- return True
71
- except (ConnectionRefusedError, socket.error):
72
- return False
73
-
74
- def download_data(self, force: bool = False) -> bool:
75
- """Download IP set data from GitHub and update the timestamp."""
76
- try:
77
- if not force and os.path.exists(self.data_path):
78
- with open(self.data_path, "r", encoding="utf-8") as f:
79
- try:
80
- data = json.load(f)
81
- if isinstance(data, dict) and "_timestamp" in data:
82
- timestamp = datetime.fromisoformat(data["_timestamp"])
83
- if datetime.now() - timestamp < timedelta(days=7):
84
- return True
85
- except (json.JSONDecodeError, KeyError, ValueError):
86
- pass
87
-
88
- url = "https://raw.githubusercontent.com/tn3w/IPSet/refs/heads/master/ipset.json"
89
- with urllib.request.urlopen(url, timeout=30) as response:
90
- response_data = response.read().decode("utf-8")
91
-
92
- data = json.loads(response_data)
93
- data["_timestamp"] = datetime.now().isoformat()
94
-
95
- with open(self.data_path, "w", encoding="utf-8") as f:
96
- json.dump(data, f)
97
-
98
- return True
99
- except Exception as e:
100
- logger.error("Error downloading IP set data: %s", e)
101
- return False
102
-
103
- def load_data(self) -> bool:
104
- """Load IP set data into memory."""
105
- try:
106
- with open(self.data_path, "r", encoding="utf-8") as f:
107
- data = json.load(f)
108
-
109
- if "_timestamp" in data:
110
- self.last_update = datetime.fromisoformat(data.pop("_timestamp"))
111
-
112
- self.ip_to_groups = {}
113
- self.cidrs_to_ips = {}
114
-
115
- for group, ips in data.items():
116
- for ip in ips:
117
- if "/" in ip:
118
- try:
119
- ip_obj = IPNetwork(ip)
120
- if ip_obj not in self.cidrs_to_ips:
121
- self.cidrs_to_ips[ip_obj] = []
122
- self.cidrs_to_ips[ip_obj].append(group)
123
- except Exception:
124
- continue
125
- continue
126
-
127
- if ip not in self.ip_to_groups:
128
- self.ip_to_groups[ip] = []
129
- self.ip_to_groups[ip].append(group)
130
-
131
- return True
132
- except Exception as e:
133
- logger.error("Error loading IP set data: %s", e)
134
- return False
135
-
136
- def check_and_update_data(self) -> None:
137
- """Check if data needs updating and update if necessary."""
138
- if self.last_update is None or datetime.now() - self.last_update > timedelta(
139
- days=7
140
- ):
141
- threading.Thread(target=self._async_update).start()
142
-
143
- def _async_update(self) -> None:
144
- """Update data in the background without affecting current operations."""
145
- if self.download_data(force=True):
146
- self.load_data()
147
-
148
- def find_matching_groups(self, ip: str) -> List[str]:
149
- """Find all groups matching the given IP."""
150
- self.check_and_update_data()
151
-
152
- matching_groups = self.ip_to_groups.get(ip, [])
153
-
154
- try:
155
- ip_obj = IPAddress(ip)
156
- ip_version = ip_obj.version
157
-
158
- for cidr, groups in self.cidrs_to_ips.items():
159
- if cidr.version != ip_version:
160
- continue
161
-
162
- if ip_obj in cidr:
163
- for group in groups:
164
- if group not in matching_groups:
165
- matching_groups.append(group)
166
-
167
- except Exception:
168
- return []
169
-
170
- return matching_groups
171
-
172
- def handle_client(
173
- self, client_socket: socket.socket, addr: Tuple[str, int]
174
- ) -> None:
175
- """Handle client connection and queries."""
176
- try:
177
- while True:
178
- data = client_socket.recv(1024).decode("utf-8").strip()
179
- if not data:
180
- break
181
-
182
- result = self.find_matching_groups(data)
183
- response = json.dumps(result)
184
- client_socket.send(f"{response}\n".encode("utf-8"))
185
- except Exception as e:
186
- logger.error("Error handling client %s: %s", addr, e)
187
- finally:
188
- client_socket.close()
189
-
190
- def run_server(self) -> None:
191
- """Run the memory server."""
192
- if self.is_server_running():
193
- logger.info("Server already running on port %s", self.port)
194
- return
195
-
196
- if not os.path.exists(self.data_path):
197
- logger.info("IP data file not found at %s, downloading...", self.data_path)
198
- if not self.download_data():
199
- logger.error("Failed to download data, cannot start server")
200
- return
201
-
202
- if not self.load_data():
203
- logger.error("Failed to load data, cannot start server")
204
- return
205
-
206
- self.check_and_update_data()
207
- try:
208
- self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
209
- self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
210
- self.server_socket.bind(("0.0.0.0", self.port))
211
- self.server_socket.listen(10)
212
- self.running = True
213
-
214
- logger.info(
215
- "Memory server started on port %s with data from %s",
216
- self.port,
217
- self.data_path,
218
- )
219
-
220
- while self.running:
221
- try:
222
- client_socket, addr = self.server_socket.accept()
223
- client_thread = threading.Thread(
224
- target=self.handle_client, args=(client_socket, addr)
225
- )
226
- client_thread.daemon = True
227
- client_thread.start()
228
- except Exception as e:
229
- if self.running:
230
- logger.error("Error accepting connection: %s", e)
231
-
232
- except Exception as e:
233
- logger.error("Server error: %s", e)
234
- finally:
235
- if self.server_socket:
236
- self.server_socket.close()
237
-
238
- def start(self) -> None:
239
- """Start the server in a background thread."""
240
- if self.server_thread and self.server_thread.is_alive():
241
- return
242
-
243
- self.server_thread = threading.Thread(target=self.run_server)
244
- self.server_thread.daemon = True
245
- self.server_thread.start()
246
-
247
- def stop(self) -> None:
248
- """Stop the server."""
249
- self.running = False
250
- if self.server_socket:
251
- self.server_socket.close()
252
-
253
-
254
- class IPSetClient:
255
- """Client to connect to the IPSetMemoryServer."""
256
-
257
- def __init__(self, host: str = "127.0.0.1", port: int = 9876):
258
- self.host = host
259
- self.port = port
260
- self.socket = None
261
-
262
- def connect(self) -> bool:
263
- """Connect to the memory server."""
264
- try:
265
- self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
266
- self.socket.connect((self.host, self.port))
267
- return True
268
- except Exception as e:
269
- logger.error("Failed to connect to memory server: %s", e)
270
- return False
271
-
272
- def lookup_ip(self, ip: str) -> List[str]:
273
- """Look up an IP in the memory server."""
274
- if not self.socket:
275
- if not self.connect():
276
- return []
277
-
278
- try:
279
- if self.socket:
280
- self.socket.send(f"{ip}\n".encode("utf-8"))
281
- response = self.socket.recv(4096).decode("utf-8").strip()
282
- return json.loads(response)
283
- return []
284
- except Exception as e:
285
- logger.error("Error looking up IP: %s", e)
286
- if self.connect():
287
- try:
288
- if self.socket:
289
- self.socket.send(f"{ip}\n".encode("utf-8"))
290
- response = self.socket.recv(4096).decode("utf-8").strip()
291
- return json.loads(response)
292
- except Exception:
293
- pass
294
- return []
295
-
296
- def close(self) -> None:
297
- """Close the connection to the memory server."""
298
- if self.socket:
299
- try:
300
- self.socket.close()
301
- except Exception:
302
- pass
303
- self.socket = None
304
-
305
-
306
- def ensure_server_running(port: int = 9876, data_path: Optional[str] = None) -> None:
307
- """Ensure that the memory server is running."""
308
- if data_path is None:
309
- data_path = IPSET_DATA_PATH
310
-
311
- server = IPSetMemoryServer(port=port, data_path=data_path)
312
- server.start()
313
-
314
- while not server.is_server_running():
315
- time.sleep(0.1)
@@ -1,14 +0,0 @@
1
- flask_humanify/__init__.py,sha256=Yd_o67mcYCoDkHYrokGNt6ZB1hYdUjFsuym1eav5XB8,271
2
- flask_humanify/humanify.py,sha256=aPzrHkDjFN6cSXCO-3ivvEDJ-7WsPGn8RSL1T2zAv9s,4325
3
- flask_humanify/ipset.py,sha256=ZZZRtgtddkZr2q1zuI6803hJQ8OodVHNdyZvZGqpmMI,10866
4
- flask_humanify/utils.py,sha256=CJ4FPhNJ75FuWcAn_ZuZkqRa9HR3jVvYOu9NZaN4L1o,2461
5
- flask_humanify/datasets/ipset.json,sha256=YNPqwI109lYkfvZeOPsoDH_dKJxOCs0G2nvx_s2mvqU,30601191
6
- flask_humanify/features/rate_limiter.py,sha256=YhAh4xAZTiqdMbS028jA_v6FeZEB1v6JtbOVubjGZao,2203
7
- flask_humanify/templates/access_denied.html,sha256=Y7EzM53LRBMJBniczTWYvinHuPPFQVKrqvCmKaqLHak,3165
8
- flask_humanify/templates/oneclick_captcha.html,sha256=JgyLyCjh_oR_lf0btAXWqPtfOXvZxfcIXX_GqCTEuBw,6237
9
- flask_humanify/templates/rate_limited.html,sha256=LSS1-c9F3YLxXF-jiyyNN6vIMoIAdmd6ObO1PDcjIxI,3110
10
- flask_humanify-0.1.3.2.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
11
- flask_humanify-0.1.3.2.dist-info/METADATA,sha256=ocFiQhM_sRhp4RVNrI7zD18JZc7Q3bJvjZ0TllFxQC8,3050
12
- flask_humanify-0.1.3.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
13
- flask_humanify-0.1.3.2.dist-info/top_level.txt,sha256=9-c6uhxwCpPE3BJYge1Y9Z_bYmWitI0fY5RgqMiFWr0,15
14
- flask_humanify-0.1.3.2.dist-info/RECORD,,