#!/usr/bin/env python3 """Compile the actual C renderer and exercise its emitted app/loader in Node.""" import base64 import ctypes as C import hashlib import json import os from pathlib import Path import re import shlex import subprocess import tempfile import sys sys.dont_write_bytecode = True from layout import check_layout, check_browser_layout HERE = Path(__file__).resolve().parent ROOT = HERE.parents[1] def run(): # Reuse only the HTTPD test double text, not the standalone login renderer. source = (ROOT / 'tests/web_login_ui/run.py').read_text() stub = source.split("STUB = r'''", 1)[1].split("'''", 1)[0].replace('web_login_ui.h', 'web_ui.h') for asset in ('xterm_js_gz', 'xterm_css_gz', 'addon_fit_js_gz', 'logo_png'): stub += f'\nconst unsigned char web_asset_{asset}[] = "stub";\nconst size_t web_asset_{asset}_size = 4;\n' with tempfile.TemporaryDirectory(prefix='web-ui-session-') as directory: tmp = Path(directory) (tmp / 'esp_err.h').write_text('#pragma once\ntypedef int esp_err_t;\n#define ESP_OK 0\n#define ESP_ERR_INVALID_ARG 258\n') (tmp / 'esp_http_server.h').write_text('''#pragma once #include "esp_err.h" #include typedef struct { int unused; } httpd_req_t; esp_err_t httpd_resp_set_type(httpd_req_t *, const char *); esp_err_t httpd_resp_set_hdr(httpd_req_t *, const char *, const char *); esp_err_t httpd_resp_send(httpd_req_t *, const char *, ssize_t); ''') (tmp / 'stub.c').write_text(stub) subprocess.run(shlex.split(os.environ.get('CC', 'cc')) + [ '-std=c11', '-Wall', '-Wextra', '-Werror', '-shared', '-fPIC', '-I', str(tmp), '-I', str(ROOT / 'src'), str(tmp / 'stub.c'), str(ROOT / 'src/web_ui.c'), '-o', str(tmp / 'renderer.so')], check=True) lib = C.CDLL(str(tmp / 'renderer.so')) lib.web_ui_send_response.argtypes = [C.c_void_p, C.c_int] for name in ('header_key', 'header_value', 'body', 'content_type'): getattr(lib, name).restype = C.c_char_p request = C.c_int() send = lambda resource: lib.web_ui_send_response(C.byref(request), resource) lib.reset(0, 0) assert lib.web_ui_send_response(None, 0) == 258 assert send(99) == 258 and lib.call_count() == 0 rendered = {} for resource in range(6): lib.reset(0, 0) assert send(resource) == 0 count, calls = lib.header_count(), lib.call_count() assert count <= 8 headers = {lib.header_key(i).decode(): lib.header_value(i).decode() for i in range(count)} assert headers['Cache-Control'] == ('no-store' if resource in (0, 4) else 'private, max-age=604800') assert headers['X-Content-Type-Options'] == 'nosniff' assert headers['Referrer-Policy'] == 'no-referrer' if resource == 0: rendered.update(html=lib.body().decode(), headers=headers) if resource == 4: rendered['script'] = lib.body().decode() for failure in range(1, calls + 1): lib.reset(failure, 0) assert send(resource) == 73 and lib.send_count() == 0 lib.reset(0, 91) assert send(resource) == 91 scripts = re.findall(r'', rendered['html'], re.S) assert len(scripts) == 1 rendered['loader'] = scripts[0] digest = base64.b64encode(hashlib.sha256(scripts[0].encode()).digest()).decode() csp = rendered['headers']['Content-Security-Policy'] assert csp.count(f"'sha256-{digest}'") == 2, 'loader CSP hash mismatch' assert "frame-ancestors 'none'" in csp and "connect-src 'self'" in csp assert rendered['html'].index('