GNServer 0.0.0.0.10__tar.gz → 0.0.0.0.12__tar.gz
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.
- {gnserver-0.0.0.0.10 → gnserver-0.0.0.0.12}/GNServer/GNServer/_app.py +137 -3
- {gnserver-0.0.0.0.10 → gnserver-0.0.0.0.12}/GNServer/GNServer.egg-info/PKG-INFO +1 -1
- {gnserver-0.0.0.0.10 → gnserver-0.0.0.0.12}/PKG-INFO +1 -1
- {gnserver-0.0.0.0.10 → gnserver-0.0.0.0.12}/setup.py +1 -1
- {gnserver-0.0.0.0.10 → gnserver-0.0.0.0.12}/GNServer/GNServer/__init__.py +0 -0
- {gnserver-0.0.0.0.10 → gnserver-0.0.0.0.12}/GNServer/GNServer.egg-info/SOURCES.txt +0 -0
- {gnserver-0.0.0.0.10 → gnserver-0.0.0.0.12}/GNServer/GNServer.egg-info/dependency_links.txt +0 -0
- {gnserver-0.0.0.0.10 → gnserver-0.0.0.0.12}/GNServer/GNServer.egg-info/requires.txt +0 -0
- {gnserver-0.0.0.0.10 → gnserver-0.0.0.0.12}/GNServer/GNServer.egg-info/top_level.txt +0 -0
- {gnserver-0.0.0.0.10 → gnserver-0.0.0.0.12}/GNServer/LICENSE +0 -0
- {gnserver-0.0.0.0.10 → gnserver-0.0.0.0.12}/GNServer/mmbConfig.json +0 -0
- {gnserver-0.0.0.0.10 → gnserver-0.0.0.0.12}/LICENSE +0 -0
- {gnserver-0.0.0.0.10 → gnserver-0.0.0.0.12}/MANIFEST.in +0 -0
- {gnserver-0.0.0.0.10 → gnserver-0.0.0.0.12}/setup.cfg +0 -0
@@ -158,8 +158,126 @@ def extract_template_vars(filedata: bytes, mime: str) -> List[str]:
|
|
158
158
|
return list(set(m.group(1) for m in TPL_VAR_RE.finditer(text)))
|
159
159
|
|
160
160
|
|
161
|
+
import re
|
162
|
+
from urllib.parse import urlparse
|
163
|
+
|
164
|
+
def resolve_cors(origin_url: str, rules: list[str]) -> bool:
|
165
|
+
"""
|
166
|
+
Возвращает origin_url если он матчится хотя бы с одним правилом.
|
167
|
+
Правила:
|
168
|
+
- "*.example.com" -> wildcard (одна метка)
|
169
|
+
- "**.example.com" -> globstar (0+ меток)
|
170
|
+
- "pages.*.core.gn" -> смешанное
|
171
|
+
- "https://*.site.tld" -> с проверкой схемы
|
172
|
+
- "!<regex>" -> полное соответствие по regex к origin_url
|
173
|
+
"""
|
174
|
+
origin = origin_url.rstrip("/")
|
175
|
+
pu = urlparse(origin)
|
176
|
+
scheme = (pu.scheme or "").lower()
|
177
|
+
host = (pu.hostname or "").lower()
|
178
|
+
port = pu.port # может быть None
|
179
|
+
|
180
|
+
if not host:
|
181
|
+
return False
|
182
|
+
|
183
|
+
for rule in rules:
|
184
|
+
rule = rule.rstrip("/")
|
185
|
+
|
186
|
+
# 1) Регекс-правило
|
187
|
+
if rule.startswith("!"):
|
188
|
+
pattern = rule[1:]
|
189
|
+
if re.fullmatch(pattern, origin):
|
190
|
+
return True
|
191
|
+
continue
|
161
192
|
|
193
|
+
# 2) Разбор схемы/хоста в правиле
|
194
|
+
r_scheme = ""
|
195
|
+
r_host = ""
|
196
|
+
r_port = None
|
197
|
+
|
198
|
+
if "://" in rule:
|
199
|
+
pr = urlparse(rule)
|
200
|
+
r_scheme = (pr.scheme or "").lower()
|
201
|
+
# pr.netloc может содержать порт
|
202
|
+
netloc = pr.netloc.lower()
|
203
|
+
# разберём порт, если есть
|
204
|
+
if ":" in netloc and not netloc.endswith("]"): # простая обработка IPv6 не требуется здесь
|
205
|
+
name, _, p = netloc.rpartition(":")
|
206
|
+
r_host = name
|
207
|
+
try:
|
208
|
+
r_port = int(p)
|
209
|
+
except ValueError:
|
210
|
+
r_port = None
|
211
|
+
else:
|
212
|
+
r_host = netloc
|
213
|
+
else:
|
214
|
+
r_host = rule.lower()
|
162
215
|
|
216
|
+
# схема в правиле задана -> должна совпасть
|
217
|
+
if r_scheme and r_scheme != scheme:
|
218
|
+
continue
|
219
|
+
# порт в правиле задан -> должен совпасть
|
220
|
+
if r_port is not None and r_port != port:
|
221
|
+
continue
|
222
|
+
|
223
|
+
# 3) Сопоставление хоста по шаблону с * и ** (по меткам)
|
224
|
+
if _host_matches_pattern(host, r_host):
|
225
|
+
return True
|
226
|
+
|
227
|
+
return False
|
228
|
+
|
229
|
+
|
230
|
+
def _host_matches_pattern(host: str, pattern: str) -> bool:
|
231
|
+
"""
|
232
|
+
Матчит host против pattern по доменным меткам:
|
233
|
+
- '*' -> ровно одна метка
|
234
|
+
- '**' -> ноль или больше меток
|
235
|
+
Остальные метки — точное совпадение (без внутр. вайлдкардов).
|
236
|
+
Примеры:
|
237
|
+
host=pages.static.core.gn, pattern=**.core.gn -> True
|
238
|
+
host=pages.static.core.gn, pattern=pages.*.core.gn -> True
|
239
|
+
host=pages.static.core.gn, pattern=*.gn.gn -> False
|
240
|
+
host=abc.def.example.com, pattern=*.example.com -> False (нужно **.example.com)
|
241
|
+
host=abc.example.com, pattern=*.example.com -> True
|
242
|
+
"""
|
243
|
+
host_labels = host.split(".")
|
244
|
+
pat_labels = pattern.split(".")
|
245
|
+
|
246
|
+
# быстрый путь: точное совпадение без вайлдкардов
|
247
|
+
if "*" not in pattern:
|
248
|
+
return host == pattern
|
249
|
+
|
250
|
+
# рекурсивный матч с поддержкой ** (globstar)
|
251
|
+
def match(hi: int, pi: int) -> bool:
|
252
|
+
# оба дошли до конца
|
253
|
+
if pi == len(pat_labels) and hi == len(host_labels):
|
254
|
+
return True
|
255
|
+
# закончился паттерн — нет
|
256
|
+
if pi == len(pat_labels):
|
257
|
+
return False
|
258
|
+
|
259
|
+
token = pat_labels[pi]
|
260
|
+
if token == "**":
|
261
|
+
# два варианта:
|
262
|
+
# - пропустить '**' (ноль меток)
|
263
|
+
if match(hi, pi + 1):
|
264
|
+
return True
|
265
|
+
# - съесть одну метку (если есть) и остаться на '**'
|
266
|
+
if hi < len(host_labels) and match(hi + 1, pi):
|
267
|
+
return True
|
268
|
+
return False
|
269
|
+
elif token == "*":
|
270
|
+
# нужно съесть ровно одну метку
|
271
|
+
if hi < len(host_labels):
|
272
|
+
return match(hi + 1, pi + 1)
|
273
|
+
return False
|
274
|
+
else:
|
275
|
+
# точное совпадение метки
|
276
|
+
if hi < len(host_labels) and host_labels[hi] == token:
|
277
|
+
return match(hi + 1, pi + 1)
|
278
|
+
return False
|
279
|
+
|
280
|
+
return match(0, 0)
|
163
281
|
|
164
282
|
|
165
283
|
|
@@ -307,6 +425,14 @@ class App:
|
|
307
425
|
if r.method != method:
|
308
426
|
continue
|
309
427
|
|
428
|
+
if r.cors is not None:
|
429
|
+
if request._origin is None:
|
430
|
+
return gn.GNResponse("gn:backend:801", {'error': 'Cors error. Route has cors but request has no origin url.'})
|
431
|
+
if not resolve_cors(request._origin, r.cors._allow_origins):
|
432
|
+
return gn.GNResponse("gn:backend:802", {'error': 'Cors error: origin'})
|
433
|
+
if request.method not in r.cors._allow_methods:
|
434
|
+
return gn.GNResponse("gn:backend:803", {'error': 'Cors error: method'})
|
435
|
+
|
310
436
|
sig = inspect.signature(r.handler)
|
311
437
|
def _ann(name: str):
|
312
438
|
param = sig.parameters.get(name)
|
@@ -331,10 +457,18 @@ class App:
|
|
331
457
|
|
332
458
|
result = await r.handler(**kw)
|
333
459
|
if isinstance(result, gn.GNResponse):
|
460
|
+
if result._cors is not None and result._cors != r.cors:
|
461
|
+
if request._origin is None:
|
462
|
+
return gn.GNResponse("gn:backend:801", {'error': 'Cors error. Route has cors but request has no origin url.'})
|
463
|
+
if not resolve_cors(request._origin, r.cors._allow_origins):
|
464
|
+
return gn.GNResponse("gn:backend:802", {'error': 'Cors error: origin'})
|
465
|
+
if request.method not in r.cors._allow_methods:
|
466
|
+
return gn.GNResponse("gn:backend:803", {'error': 'Cors error: method'})
|
334
467
|
return result
|
335
|
-
|
336
|
-
|
337
|
-
|
468
|
+
else:
|
469
|
+
raise TypeError(
|
470
|
+
f"{r.handler.__name__} returned {type(result)}; GNResponse expected"
|
471
|
+
)
|
338
472
|
|
339
473
|
if allowed:
|
340
474
|
return gn.GNResponse("gn:backend:405", {'error': 'Method Not Allowed'})
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|