"""Supplemental semantic checks for disposable fixture servers; never print bodies."""
import base64
import http.cookiejar
import importlib.util
import json
import os
from pathlib import Path
import re
import shutil
import subprocess
import urllib.parse
import urllib.request
def check(reference, candidate, php, source, work, php_env, timeout):
spec = importlib.util.spec_from_file_location('compat_http', Path(__file__).with_name('differential_http.py'))
harness = importlib.util.module_from_spec(spec)
spec.loader.exec_module(harness)
sides = {}
for side, base in (('reference', reference), ('candidate', candidate)):
jar = http.cookiejar.CookieJar()
opener = urllib.request.build_opener(harness.NoRedirect(), urllib.request.HTTPCookieProcessor(jar))
def fetch(case): return harness.fetch(base, case, side, timeout, opener, jar)
health = fetch({'path': '/up'})
csrf = fetch({'path': '/refresh-csrf'})
headers = {'X-XSRF-TOKEN': 'XSRF-TOKEN'}
login = fetch({'path': '/user/login', 'method': 'POST', 'headers_from_cookies': headers, 'json': {'email': 'compat@example.invalid', 'password': 'Synthetic-compat-password-4620'}})
created = fetch({'path': '/api/v1/twofaccounts', 'method': 'POST', 'headers_from_cookies': headers, 'json': {'service': 'compat-semantic', 'account': 'fixture@example.invalid', 'otp_type': 'hotp', 'secret': 'GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ', 'digits': 6, 'algorithm': 'sha1', 'counter': 0}})
qr = {'error': 'account_not_created'}
if created.get('status') == 201:
account_id = json.loads(created['body'])['id']
qr = fetch({'path': f'/api/v1/twofaccounts/{account_id}/qrcode', 'headers_from_cookies': headers})
sides[side] = {'base': base, 'health': health, 'csrf': csrf, 'login': login, 'qr': qr}
pattern = r'Response successfully rendered in [0-9]+ms\.'
health_bodies = [sides[side]['health'].get('body', b'').decode(errors='replace').replace(sides[side]['base'], '') for side in sides]
health = {'content_types_match': all(sides[side]['health'].get('content_type') == 'text/html' for side in sides), 'timing_field_present_once': all(len(re.findall(pattern, body)) == 1 for body in health_bodies), 'equal_except_render_ms': re.sub(pattern, 'Response successfully rendered in ms.', health_bodies[0]) == re.sub(pattern, 'Response successfully rendered in ms.', health_bodies[1]), 'statuses_ok': all(sides[side]['health'].get('status') == 200 for side in sides)}
csrf = {'text_token_shapes_valid': all(harness.is_text_token(sides[side]['csrf']) for side in sides), 'subsequent_login_succeeded': all(sides[side]['login'].get('status') == 200 for side in sides)}
qr = {'result': 'error', 'decoded_uri_equal': False, 'matches_fixture': False}
try:
node = os.environ.get('CODEX_PRIMARY_RUNTIME_NODE') or shutil.which('node')
if not node: raise RuntimeError('missing_svg_renderer')
decoded = []
for side in sides:
response = sides[side]['qr']
if response.get('status') != 200 or response.get('content_type') != 'application/json': raise RuntimeError('qr_request_failed')
image = json.loads(response['body'])['qrcode']
prefix = 'data:image/svg+xml;base64,'
if not image.startswith(prefix): raise RuntimeError('unexpected_image_format')
svg = work / (side + '-qr.svg')
png = work / (side + '-qr.png')
svg.write_bytes(base64.b64decode(image[len(prefix):], validate=True))
render = subprocess.run([node, '-e', "require('sharp')(process.argv[1],{density:300}).resize(1024,1024).flatten({background:'#fff'}).png().toFile(process.argv[2]).catch(()=>process.exit(1))", str(svg), str(png)], capture_output=True, timeout=30)
if render.returncode: raise RuntimeError('svg_render_failed')
code = "require $argv[1]; $r=new Zxing\\QrReader($argv[2]); $t=$r->text(); if (!is_string($t)||$t==='') exit(1); echo $t;"
result = subprocess.run([str(php), '-r', code, str(source / 'vendor/autoload.php'), str(png)], env=php_env, capture_output=True, timeout=30)
if result.returncode: raise RuntimeError('qr_decode_failed')
uri = result.stdout.decode()
parsed = urllib.parse.urlsplit(uri)
values = dict(urllib.parse.parse_qsl(parsed.query))
normalized = {'type': parsed.netloc.lower(), 'label': urllib.parse.unquote(parsed.path.lstrip('/')), 'secret': values.get('secret'), 'issuer': values.get('issuer'), 'algorithm': values.get('algorithm', 'SHA1').lower(), 'digits': int(values.get('digits', 6)), 'counter': int(values.get('counter', 0))}
if parsed.scheme.lower() != 'otpauth': raise RuntimeError('unexpected_qr_payload')
decoded.append(normalized)
expected = {'type': 'hotp', 'label': 'compat-semantic:fixture@example.invalid', 'secret': 'GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ', 'issuer': 'compat-semantic', 'algorithm': 'sha1', 'digits': 6, 'counter': 0}
qr = {'result': 'decoded', 'decoded_uri_equal': decoded[0] == decoded[1], 'matches_fixture': all(value == expected for value in decoded)}
except Exception as error:
qr['error'] = type(error).__name__
if isinstance(error, RuntimeError): qr['reason'] = str(error)
return {'health_render_timing': health, 'csrf_token_flow': csrf, 'qr_payload': qr}