$(cat …) срезал последний перевод строки, и страница masquerade была на байт короче той, что отдаёт nginx. Читаю файл целиком (установщик и kit net masq on), запасная запись страницы – без лишнего перевода строки.
101 lines
4.9 KiB
Python
101 lines
4.9 KiB
Python
"""Проверки сайта-заглушки (stub_site в 3x-ui.sh): python3 tools/test/stub_test.py"""
|
|
import html.parser, os, re, subprocess, unittest
|
|
|
|
root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
src = open(os.path.join(root, "scripts", "3x-ui.sh"), encoding="utf-8").read()
|
|
func = re.search(r"^stub_site\(\) \{.*?^\}\n", src, re.S | re.M).group(0)
|
|
N = 300
|
|
# rnd на сервере – через shuf, здесь – через /dev/urandom (на Mac нет shuf; $RANDOM в подоболочках повторяется).
|
|
script = ('set -Eeuo pipefail\nrnd() { echo $(( $1 + $(od -An -N2 -tu2 /dev/urandom) % ($2 - $1 + 1) )); }\n' + func
|
|
+ 'for i in $(seq %d); do stub_site; echo "@@END@@"; done\n' % N)
|
|
raw = subprocess.run(["bash", "-c", script], capture_output=True, check=True).stdout
|
|
PAGES = [p.strip() for p in raw.decode("utf-8").split("@@END@@") if p.strip()] # strict: только UTF-8
|
|
|
|
forbidden = []
|
|
fp = os.path.join(root, ".git", "hooks", "forbidden.txt")
|
|
if os.path.exists(fp):
|
|
forbidden = [w.strip().lower() for w in open(fp, encoding="utf-8") if w.strip() and not w.startswith("#")]
|
|
PUBLIC = re.compile(r"vpn|proxy|xray|x-ui|cumulo|hysteria|reality|v2ray|clash|\bkit\b|tunnel|censor|block|bypass|server", re.I)
|
|
EXTERNAL = re.compile(r"https?:|//|\bsrc=|\bhref=|<script|<link|<iframe|<img|@import|url\(|\bon[a-z]+=", re.I)
|
|
VOID = {"meta", "br", "hr", "input", "img"}
|
|
|
|
|
|
class Balance(html.parser.HTMLParser):
|
|
def __init__(self):
|
|
super().__init__(); self.stack = []; self.bad = []
|
|
|
|
def handle_starttag(self, tag, attrs):
|
|
if tag not in VOID:
|
|
self.stack.append(tag)
|
|
|
|
def handle_endtag(self, tag):
|
|
if not self.stack or self.stack.pop() != tag:
|
|
self.bad.append(tag)
|
|
|
|
|
|
class Stub(unittest.TestCase):
|
|
def test_count(self):
|
|
self.assertEqual(len(PAGES), N)
|
|
|
|
def test_valid_html(self):
|
|
for p in PAGES:
|
|
self.assertTrue(p.startswith('<!DOCTYPE html><html lang="en"><head><meta charset="utf-8">'), p[:80])
|
|
self.assertTrue(p.endswith("</body></html>"), p[-40:])
|
|
b = Balance(); b.feed(p); b.close()
|
|
self.assertEqual((b.stack, b.bad), ([], []), p)
|
|
self.assertEqual(p.count("<title>"), 1)
|
|
|
|
def test_no_external_and_no_leftovers(self):
|
|
for p in PAGES:
|
|
self.assertIsNone(EXTERNAL.search(p), p)
|
|
text = re.sub(r"<style>.*?</style>", "", p)
|
|
self.assertIsNone(re.search(r"[$|^~`\\]", text), p)
|
|
self.assertNotIn("\n", p)
|
|
|
|
def test_words(self):
|
|
for p in PAGES:
|
|
low = p.lower()
|
|
self.assertIsNone(PUBLIC.search(re.sub(r"<style>.*?</style>", "", p)), p) # в стилях есть display:block
|
|
for w in forbidden:
|
|
self.assertNotIn(w, low)
|
|
|
|
def test_variety(self):
|
|
titles = {re.search(r"<title>(.*?)</title>", p).group(1) for p in PAGES}
|
|
self.assertGreater(len(titles), N * 0.8)
|
|
layouts = {k for p in PAGES for k, m in (("hero", "place-items:center;text-align"), ("cards", "header{background"),
|
|
("nav", "nav{width"), ("notes", "article{"), ("band", ".t{background")) if m in p}
|
|
self.assertEqual(len(layouts), 5)
|
|
themes = {re.search(r"<title>.*? (\S+)</title>", p).group(1) for p in PAGES}
|
|
self.assertGreaterEqual(len(themes), 25) # последние слова названий: Coffee, Bakery, Photography…
|
|
# Ни одно окончание названия не встречается заметно чаще других (было: «Studio» у каждого пятого).
|
|
ends = [re.search(r"<title>.*? (\S+)</title>", p).group(1) for p in PAGES]
|
|
top = max(set(ends), key=ends.count)
|
|
self.assertLess(ends.count(top), N * 0.08, (top, ends.count(top)))
|
|
|
|
|
|
class SamePage(unittest.TestCase):
|
|
"""Hysteria2 (masquerade) отдаёт заглушку байт в байт как nginx – с последним переводом строки."""
|
|
|
|
def read(self, line, var):
|
|
import tempfile
|
|
d = tempfile.mkdtemp()
|
|
f = os.path.join(d, "index.html")
|
|
with open(f, "wb") as fh:
|
|
fh.write(b"<html>\n<p>x</p>\n\n")
|
|
line = line.replace("/var/www/kit/index.html", f)
|
|
r = subprocess.run(["bash", "-c", line + '\nprintf %s "$' + var + '"'], capture_output=True)
|
|
return r.stdout
|
|
|
|
def test_installer(self):
|
|
line = re.search(r"^ STUB_HTML=\$\(cat /var/www/kit/index\.html.*$", src, re.M).group(0)
|
|
self.assertEqual(self.read(line, "STUB_HTML"), b"<html>\n<p>x</p>\n\n")
|
|
|
|
def test_kit_masq(self):
|
|
kit = open(os.path.join(root, "scripts", "kit.sh"), encoding="utf-8").read()
|
|
line = re.search(r"^ page=\$\(cat /var/www/kit/index\.html.*$", kit, re.M).group(0)
|
|
self.assertEqual(self.read(line, "page"), b"<html>\n<p>x</p>\n\n")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|