#!/usr/bin/env python3 """Exercise real Nginx mapping and Origin/Referer isolation using localhost only.""" import importlib.util import pathlib import shutil import socket import subprocess import tempfile import time import urllib.request spec = importlib.util.spec_from_file_location('render', pathlib.Path(__file__).with_name('render.py')) render = importlib.util.module_from_spec(spec) spec.loader.exec_module(render) nginx = shutil.which('nginx') if not nginx: raise SystemExit('Nginx is required for this isolated integration check') with tempfile.TemporaryDirectory(prefix='gateway-proxy-maps-') as folder: root = pathlib.Path(folder) with socket.socket() as sock: sock.bind(('127.0.0.1', 0)) port = sock.getsockname()[1] maps = render.render_white('https', '/unused')['00-gateway-admin-proxy-maps.conf'] bodies = [ ('weiwei', 'weiwei.tomtojapan.online', 'hw.lshan.eu.org', 'weiwei.tomtojapan.online', 'weiwei', '海外转发'), ('dpl', 'dpl.tomtojapan.online', 'dp-link.net', 'dpl.tomtojapan.online', 'dpl-site', 'DP-LINK.NET'), ('fuc', 'fuc.tomtojapan.online', 'xn--9kqy16h.com', 'fuc.tomtojapan.online', 'fuc-site', '福云.com'), ('new', 'getway-dpl.tomshell.online', 'gemcook.net', 'tomshell.online', 'new', 'GEMCOOK.NET'), ('old', 'nasa.tomshell.com.de', 'diaodu.us', 'tomshell.com.de', 'old', 'DIAODU.US'), ] locations = '' for name, host, source, alias, key, brand in bodies: upstream = ('getway-dpl.gemcook.net' if key == 'new' else 'nasa.diaodu.us' if key == 'old' else source) body = f'{brand}link' locations += f''' location /body/{name} {{ default_type text/html; {render.body_rewrites(source, alias, key)} return 200 '{body}'; }} ''' config = root / 'nginx.conf' config.write_text(f'''daemon off; pid {root}/nginx.pid; error_log {root}/error.log; events {{ worker_connections 64; }} http {{ access_log off; {maps} server {{ listen 127.0.0.1:{port}; location / {{ return 200 "$gateway_admin_origin_host|$gateway_admin_origin|$gateway_admin_referer"; }} {locations} }} }} ''') subprocess.run([nginx, '-t', '-c', str(config), '-p', folder], check=True, capture_output=True, text=True) process = subprocess.Popen([nginx, '-c', str(config), '-p', folder], stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, text=True) try: for _ in range(40): try: with socket.create_connection(('127.0.0.1', port), timeout=.1): break except OSError: if process.poll() is not None: raise RuntimeError(process.stderr.read()) time.sleep(.05) cases = [ ('tomshell.online', 'gemcook.net'), ('getway-fuc.tomshell.online', 'getway-fuc.gemcook.net'), ('getway-dpl.tomshell.online', 'getway-dpl.gemcook.net'), ('nasa.tomshell.com.de', 'nasa.diaodu.us'), ('tomshell.com.de', 'diaodu.us'), ('dpl.tomtojapan.online', 'dp-link.net'), ('fuc.tomtojapan.online', 'xn--9kqy16h.com'), ('not-registered.example', ''), ('two.levels.tomshell.online', ''), ] assertions = 0 for host, upstream in cases: for origin in ['https://' + host, 'https://other.tomshell.online', 'https://outside.example']: referer = origin + '/example?keep=1' req = urllib.request.Request('http://127.0.0.1:' + str(port) + '/', headers={'Host': host, 'Origin': origin, 'Referer': referer}) with urllib.request.urlopen(req, timeout=3) as response: actual = response.read().decode().split('|') assert actual[0] == upstream, (host, actual[0], upstream) own = origin == 'https://' + host assert actual[1] == ('https://' + upstream if own else origin), (host, origin, actual[1]) assert actual[2] == ('https://' + upstream + '/example?keep=1' if own else referer) assertions += 3 for name, host, source, alias, key, brand in bodies: req = urllib.request.Request(f'http://127.0.0.1:{port}/body/{name}', headers={'Host': host}) with urllib.request.urlopen(req, timeout=3) as response: actual = response.read().decode() assert f'{brand}' in actual, (name, actual) assert f'https://{host}/api?q=1' in actual, (name, actual) assert f'//{host}/logo' in actual, (name, actual) assertions += 3 print(f'PASS: {assertions} real-Nginx map/header checks; localhost fixture only, no origin requests') finally: process.terminate() try: process.wait(timeout=5) except subprocess.TimeoutExpired: process.kill() process.wait()