GNServer 0.0.0.0.10__py3-none-any.whl → 0.0.0.0.12__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
@@ -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
- raise TypeError(
336
- f"{r.handler.__name__} returned {type(result)}; GNResponse expected"
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'})
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: GNServer
3
- Version: 0.0.0.0.10
3
+ Version: 0.0.0.0.12
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=t4h0RFttUAGufXm2moYsWLju52NnghisrK1d4RkSmHs,24029
3
+ gnserver-0.0.0.0.12.dist-info/licenses/LICENSE,sha256=WH_t7dKZyWJ5Ld07eYIkUG4Tv6zZWXtAdsUqYAUesn0,1084
4
+ gnserver-0.0.0.0.12.dist-info/METADATA,sha256=U17Yx7wt5IXf_sTaDuVLFXwcFHrHQdW2X_P7KpCfsU8,805
5
+ gnserver-0.0.0.0.12.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
6
+ gnserver-0.0.0.0.12.dist-info/top_level.txt,sha256=-UOUBuD4u7Qkb1o5PdcwyA3kx8xCH2lwy0tJHi26Wb4,9
7
+ gnserver-0.0.0.0.12.dist-info/RECORD,,
@@ -1,7 +0,0 @@
1
- GNServer/__init__.py,sha256=GLKkKL03phIhxRLpl2X1ibwTw7BvnMMOwKgRD578RaU,1404
2
- GNServer/_app.py,sha256=dKgGAFVwDMGOt7i4HGF2TrVz0XSGjE0TV2hRJHfUnxA,18273
3
- gnserver-0.0.0.0.10.dist-info/licenses/LICENSE,sha256=WH_t7dKZyWJ5Ld07eYIkUG4Tv6zZWXtAdsUqYAUesn0,1084
4
- gnserver-0.0.0.0.10.dist-info/METADATA,sha256=VPHmElOubONY1mAXyNB-GnN-BShR33nJK8eXwvAk8Rg,805
5
- gnserver-0.0.0.0.10.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
6
- gnserver-0.0.0.0.10.dist-info/top_level.txt,sha256=-UOUBuD4u7Qkb1o5PdcwyA3kx8xCH2lwy0tJHi26Wb4,9
7
- gnserver-0.0.0.0.10.dist-info/RECORD,,