GNServer 0.0.0.0.11__py3-none-any.whl → 0.0.0.0.13__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.
GNServer/_app.py CHANGED
@@ -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
- parsed = urlparse(origin)
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.match(pattern, origin):
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
- if origin == rule:
183
- return True
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, r.cors._allow_origins):
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 r.cors._allow_methods:
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, 2)
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, 2)
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(3), end_stream=False)
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(3), end_stream=True)
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(3), end_stream=True)
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(3), end_stream=True)
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GNServer
3
- Version: 0.0.0.0.11
3
+ Version: 0.0.0.0.13
4
4
  Summary: GNServer
5
5
  Home-page: https://github.com/KeyisB/libs/tree/main/GNServer
6
6
  Author: KeyisB
@@ -0,0 +1,7 @@
1
+ GNServer/__init__.py,sha256=GLKkKL03phIhxRLpl2X1ibwTw7BvnMMOwKgRD578RaU,1404
2
+ GNServer/_app.py,sha256=NiINMe7Vv2F1H-m1G6ovOfbvGJgmGGQdTHCc8v_igJI,24201
3
+ gnserver-0.0.0.0.13.dist-info/licenses/LICENSE,sha256=WH_t7dKZyWJ5Ld07eYIkUG4Tv6zZWXtAdsUqYAUesn0,1084
4
+ gnserver-0.0.0.0.13.dist-info/METADATA,sha256=Eswpz48CSM0tezRIZGpkU_1Y7aEg1YDDFNNlQduVZJk,805
5
+ gnserver-0.0.0.0.13.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
6
+ gnserver-0.0.0.0.13.dist-info/top_level.txt,sha256=-UOUBuD4u7Qkb1o5PdcwyA3kx8xCH2lwy0tJHi26Wb4,9
7
+ gnserver-0.0.0.0.13.dist-info/RECORD,,
@@ -1,7 +0,0 @@
1
- GNServer/__init__.py,sha256=GLKkKL03phIhxRLpl2X1ibwTw7BvnMMOwKgRD578RaU,1404
2
- GNServer/_app.py,sha256=RFG32H9HhUe-nxiTu8bxfHi8Zhpw-h2GDbFpbiHnWyM,20189
3
- gnserver-0.0.0.0.11.dist-info/licenses/LICENSE,sha256=WH_t7dKZyWJ5Ld07eYIkUG4Tv6zZWXtAdsUqYAUesn0,1084
4
- gnserver-0.0.0.0.11.dist-info/METADATA,sha256=-PSJGgQjF9ru0aEfaN6Q800NrArHzhcXfWpxGw3Z2mM,805
5
- gnserver-0.0.0.0.11.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
6
- gnserver-0.0.0.0.11.dist-info/top_level.txt,sha256=-UOUBuD4u7Qkb1o5PdcwyA3kx8xCH2lwy0tJHi26Wb4,9
7
- gnserver-0.0.0.0.11.dist-info/RECORD,,