GNServer 0.0.0.0.11__tar.gz → 0.0.0.0.13__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.11 → gnserver-0.0.0.0.13}/GNServer/GNServer/_app.py +128 -25
- {gnserver-0.0.0.0.11 → gnserver-0.0.0.0.13}/GNServer/GNServer.egg-info/PKG-INFO +1 -1
- {gnserver-0.0.0.0.11 → gnserver-0.0.0.0.13}/PKG-INFO +1 -1
- {gnserver-0.0.0.0.11 → gnserver-0.0.0.0.13}/setup.py +1 -1
- {gnserver-0.0.0.0.11 → gnserver-0.0.0.0.13}/GNServer/GNServer/__init__.py +0 -0
- {gnserver-0.0.0.0.11 → gnserver-0.0.0.0.13}/GNServer/GNServer.egg-info/SOURCES.txt +0 -0
- {gnserver-0.0.0.0.11 → gnserver-0.0.0.0.13}/GNServer/GNServer.egg-info/dependency_links.txt +0 -0
- {gnserver-0.0.0.0.11 → gnserver-0.0.0.0.13}/GNServer/GNServer.egg-info/requires.txt +0 -0
- {gnserver-0.0.0.0.11 → gnserver-0.0.0.0.13}/GNServer/GNServer.egg-info/top_level.txt +0 -0
- {gnserver-0.0.0.0.11 → gnserver-0.0.0.0.13}/GNServer/LICENSE +0 -0
- {gnserver-0.0.0.0.11 → gnserver-0.0.0.0.13}/GNServer/mmbConfig.json +0 -0
- {gnserver-0.0.0.0.11 → gnserver-0.0.0.0.13}/LICENSE +0 -0
- {gnserver-0.0.0.0.11 → gnserver-0.0.0.0.13}/MANIFEST.in +0 -0
- {gnserver-0.0.0.0.11 → gnserver-0.0.0.0.13}/setup.cfg +0 -0
@@ -75,6 +75,7 @@ def guess_type(filename: str) -> str:
|
|
75
75
|
"webp": "image/webp",
|
76
76
|
"svg": "image/svg+xml",
|
77
77
|
"avif": "image/avif",
|
78
|
+
"ico": "image/x-icon",
|
78
79
|
|
79
80
|
# 🔹 Видео (современные форматы)
|
80
81
|
"mp4": "video/mp4",
|
@@ -162,29 +163,132 @@ import re
|
|
162
163
|
from urllib.parse import urlparse
|
163
164
|
|
164
165
|
def resolve_cors(origin_url: str, rules: list[str]) -> bool:
|
166
|
+
"""
|
167
|
+
Возвращает origin_url если он матчится хотя бы с одним правилом.
|
168
|
+
Правила:
|
169
|
+
- "*.example.com" -> wildcard (одна метка)
|
170
|
+
- "**.example.com" -> globstar (0+ меток)
|
171
|
+
- "pages.*.core.gn" -> смешанное
|
172
|
+
- "https://*.site.tld" -> с проверкой схемы
|
173
|
+
- "!<regex>" -> полное соответствие по regex к origin_url
|
174
|
+
"""
|
175
|
+
|
176
|
+
if origin_url == 'gn:proxy:sys':
|
177
|
+
return True
|
178
|
+
|
179
|
+
|
180
|
+
|
181
|
+
|
182
|
+
|
165
183
|
origin = origin_url.rstrip("/")
|
166
|
-
|
167
|
-
|
184
|
+
pu = urlparse(origin)
|
185
|
+
scheme = (pu.scheme or "").lower()
|
186
|
+
host = (pu.hostname or "").lower()
|
187
|
+
port = pu.port # может быть None
|
188
|
+
|
189
|
+
if not host:
|
190
|
+
return False
|
191
|
+
|
168
192
|
for rule in rules:
|
169
193
|
rule = rule.rstrip("/")
|
194
|
+
|
195
|
+
# 1) Регекс-правило
|
170
196
|
if rule.startswith("!"):
|
171
197
|
pattern = rule[1:]
|
172
|
-
if re.
|
173
|
-
return True
|
174
|
-
elif "*" in rule:
|
175
|
-
r = urlparse(rule.replace("*.", ""))
|
176
|
-
if r.scheme and r.scheme != parsed.scheme:
|
177
|
-
continue
|
178
|
-
regex = "^" + re.escape(rule).replace("\\*", "[^.]+") + "$"
|
179
|
-
if re.match(regex, origin):
|
198
|
+
if re.fullmatch(pattern, origin):
|
180
199
|
return True
|
200
|
+
continue
|
201
|
+
|
202
|
+
# 2) Разбор схемы/хоста в правиле
|
203
|
+
r_scheme = ""
|
204
|
+
r_host = ""
|
205
|
+
r_port = None
|
206
|
+
|
207
|
+
if "://" in rule:
|
208
|
+
pr = urlparse(rule)
|
209
|
+
r_scheme = (pr.scheme or "").lower()
|
210
|
+
# pr.netloc может содержать порт
|
211
|
+
netloc = pr.netloc.lower()
|
212
|
+
# разберём порт, если есть
|
213
|
+
if ":" in netloc and not netloc.endswith("]"): # простая обработка IPv6 не требуется здесь
|
214
|
+
name, _, p = netloc.rpartition(":")
|
215
|
+
r_host = name
|
216
|
+
try:
|
217
|
+
r_port = int(p)
|
218
|
+
except ValueError:
|
219
|
+
r_port = None
|
220
|
+
else:
|
221
|
+
r_host = netloc
|
181
222
|
else:
|
182
|
-
|
183
|
-
|
184
|
-
|
223
|
+
r_host = rule.lower()
|
224
|
+
|
225
|
+
# схема в правиле задана -> должна совпасть
|
226
|
+
if r_scheme and r_scheme != scheme:
|
227
|
+
continue
|
228
|
+
# порт в правиле задан -> должен совпасть
|
229
|
+
if r_port is not None and r_port != port:
|
230
|
+
continue
|
231
|
+
|
232
|
+
# 3) Сопоставление хоста по шаблону с * и ** (по меткам)
|
233
|
+
if _host_matches_pattern(host, r_host):
|
234
|
+
return True
|
235
|
+
|
185
236
|
return False
|
186
237
|
|
187
238
|
|
239
|
+
def _host_matches_pattern(host: str, pattern: str) -> bool:
|
240
|
+
"""
|
241
|
+
Матчит host против pattern по доменным меткам:
|
242
|
+
- '*' -> ровно одна метка
|
243
|
+
- '**' -> ноль или больше меток
|
244
|
+
Остальные метки — точное совпадение (без внутр. вайлдкардов).
|
245
|
+
Примеры:
|
246
|
+
host=pages.static.core.gn, pattern=**.core.gn -> True
|
247
|
+
host=pages.static.core.gn, pattern=pages.*.core.gn -> True
|
248
|
+
host=pages.static.core.gn, pattern=*.gn.gn -> False
|
249
|
+
host=abc.def.example.com, pattern=*.example.com -> False (нужно **.example.com)
|
250
|
+
host=abc.example.com, pattern=*.example.com -> True
|
251
|
+
"""
|
252
|
+
host_labels = host.split(".")
|
253
|
+
pat_labels = pattern.split(".")
|
254
|
+
|
255
|
+
# быстрый путь: точное совпадение без вайлдкардов
|
256
|
+
if "*" not in pattern:
|
257
|
+
return host == pattern
|
258
|
+
|
259
|
+
# рекурсивный матч с поддержкой ** (globstar)
|
260
|
+
def match(hi: int, pi: int) -> bool:
|
261
|
+
# оба дошли до конца
|
262
|
+
if pi == len(pat_labels) and hi == len(host_labels):
|
263
|
+
return True
|
264
|
+
# закончился паттерн — нет
|
265
|
+
if pi == len(pat_labels):
|
266
|
+
return False
|
267
|
+
|
268
|
+
token = pat_labels[pi]
|
269
|
+
if token == "**":
|
270
|
+
# два варианта:
|
271
|
+
# - пропустить '**' (ноль меток)
|
272
|
+
if match(hi, pi + 1):
|
273
|
+
return True
|
274
|
+
# - съесть одну метку (если есть) и остаться на '**'
|
275
|
+
if hi < len(host_labels) and match(hi + 1, pi):
|
276
|
+
return True
|
277
|
+
return False
|
278
|
+
elif token == "*":
|
279
|
+
# нужно съесть ровно одну метку
|
280
|
+
if hi < len(host_labels):
|
281
|
+
return match(hi + 1, pi + 1)
|
282
|
+
return False
|
283
|
+
else:
|
284
|
+
# точное совпадение метки
|
285
|
+
if hi < len(host_labels) and host_labels[hi] == token:
|
286
|
+
return match(hi + 1, pi + 1)
|
287
|
+
return False
|
288
|
+
|
289
|
+
return match(0, 0)
|
290
|
+
|
291
|
+
|
188
292
|
|
189
293
|
@dataclass
|
190
294
|
class Route:
|
@@ -335,7 +439,7 @@ class App:
|
|
335
439
|
return gn.GNResponse("gn:backend:801", {'error': 'Cors error. Route has cors but request has no origin url.'})
|
336
440
|
if not resolve_cors(request._origin, r.cors._allow_origins):
|
337
441
|
return gn.GNResponse("gn:backend:802", {'error': 'Cors error: origin'})
|
338
|
-
if request.method not in r.cors._allow_methods:
|
442
|
+
if request.method not in r.cors._allow_methods and '*' not in r.cors._allow_methods:
|
339
443
|
return gn.GNResponse("gn:backend:803", {'error': 'Cors error: method'})
|
340
444
|
|
341
445
|
sig = inspect.signature(r.handler)
|
@@ -362,12 +466,14 @@ class App:
|
|
362
466
|
|
363
467
|
result = await r.handler(**kw)
|
364
468
|
if isinstance(result, gn.GNResponse):
|
469
|
+
if result._cors is None:
|
470
|
+
result._cors = self._cors
|
365
471
|
if result._cors is not None and result._cors != r.cors:
|
366
472
|
if request._origin is None:
|
367
473
|
return gn.GNResponse("gn:backend:801", {'error': 'Cors error. Route has cors but request has no origin url.'})
|
368
|
-
if not resolve_cors(request._origin,
|
474
|
+
if not resolve_cors(request._origin, result._cors._allow_origins):
|
369
475
|
return gn.GNResponse("gn:backend:802", {'error': 'Cors error: origin'})
|
370
|
-
if request.method not in
|
476
|
+
if request.method not in result._cors._allow_methods and '*' not in result._cors._allow_methods:
|
371
477
|
return gn.GNResponse("gn:backend:803", {'error': 'Cors error: method'})
|
372
478
|
return result
|
373
479
|
else:
|
@@ -447,7 +553,7 @@ class App:
|
|
447
553
|
|
448
554
|
if not stream: # если не стрим, то ждем конец quic стрима и запускаем обработку ответа
|
449
555
|
if event.end_stream:
|
450
|
-
request = gn.GNRequest.deserialize(buf,
|
556
|
+
request = gn.GNRequest.deserialize(buf, 1)
|
451
557
|
# request.stream_id = event.stream_id
|
452
558
|
# loop = asyncio.get_event_loop()
|
453
559
|
# request.fut = loop.create_future()
|
@@ -472,7 +578,7 @@ class App:
|
|
472
578
|
del buf[:lenght]
|
473
579
|
|
474
580
|
# формируем запрос
|
475
|
-
request = gn.GNRequest.deserialize(data,
|
581
|
+
request = gn.GNRequest.deserialize(data, 1)
|
476
582
|
|
477
583
|
logger.debug(request, f'event.stream_id -> {event.stream_id}')
|
478
584
|
|
@@ -520,32 +626,29 @@ class App:
|
|
520
626
|
async for chunk in response: # type: ignore[misc]
|
521
627
|
chunk._stream = True
|
522
628
|
chunk = await self.resolve_response(chunk)
|
523
|
-
self._quic.send_stream_data(request.stream_id, chunk.serialize(
|
629
|
+
self._quic.send_stream_data(request.stream_id, chunk.serialize(1), end_stream=False)
|
524
630
|
self.transmit()
|
525
631
|
|
526
632
|
l = gn.GNResponse('gn:end-stream')
|
527
633
|
l._stream = True
|
528
634
|
l = self.resolve_response(l)
|
529
|
-
self._quic.send_stream_data(request.stream_id, l.serialize(
|
635
|
+
self._quic.send_stream_data(request.stream_id, l.serialize(1), end_stream=True)
|
530
636
|
self.transmit()
|
531
637
|
return
|
532
638
|
|
533
639
|
|
534
640
|
response = await self.resolve_response(response)
|
535
|
-
self._quic.send_stream_data(request.stream_id, response.serialize(
|
641
|
+
self._quic.send_stream_data(request.stream_id, response.serialize(1), end_stream=True)
|
536
642
|
logger.debug(f'Отправлен на сервер ответ -> {response.command} {response.payload if response.payload and len((response.payload)) < 200 else ''}')
|
537
643
|
self.transmit()
|
538
644
|
except Exception as e:
|
539
645
|
logger.error('GNServer: error\n' + traceback.format_exc())
|
540
646
|
|
541
647
|
response = gn.GNResponse('gn:backend:500')
|
542
|
-
self._quic.send_stream_data(request.stream_id, response.serialize(
|
648
|
+
self._quic.send_stream_data(request.stream_id, response.serialize(1), end_stream=True)
|
543
649
|
self.transmit()
|
544
650
|
|
545
651
|
async def resolve_response(self, response: gn.GNResponse) -> gn.GNResponse:
|
546
|
-
if response._cors is None:
|
547
|
-
response._cors = self._api._cors
|
548
|
-
|
549
652
|
await response.assembly()
|
550
653
|
|
551
654
|
return response
|
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
|