xref: /aosp_15_r20/external/curl/tests/http/testenv/httpd.py (revision 6236dae45794135f37c4eb022389c904c8b0090d)
1*6236dae4SAndroid Build Coastguard Worker#!/usr/bin/env python3
2*6236dae4SAndroid Build Coastguard Worker# -*- coding: utf-8 -*-
3*6236dae4SAndroid Build Coastguard Worker#***************************************************************************
4*6236dae4SAndroid Build Coastguard Worker#                                  _   _ ____  _
5*6236dae4SAndroid Build Coastguard Worker#  Project                     ___| | | |  _ \| |
6*6236dae4SAndroid Build Coastguard Worker#                             / __| | | | |_) | |
7*6236dae4SAndroid Build Coastguard Worker#                            | (__| |_| |  _ <| |___
8*6236dae4SAndroid Build Coastguard Worker#                             \___|\___/|_| \_\_____|
9*6236dae4SAndroid Build Coastguard Worker#
10*6236dae4SAndroid Build Coastguard Worker# Copyright (C) Daniel Stenberg, <[email protected]>, et al.
11*6236dae4SAndroid Build Coastguard Worker#
12*6236dae4SAndroid Build Coastguard Worker# This software is licensed as described in the file COPYING, which
13*6236dae4SAndroid Build Coastguard Worker# you should have received as part of this distribution. The terms
14*6236dae4SAndroid Build Coastguard Worker# are also available at https://curl.se/docs/copyright.html.
15*6236dae4SAndroid Build Coastguard Worker#
16*6236dae4SAndroid Build Coastguard Worker# You may opt to use, copy, modify, merge, publish, distribute and/or sell
17*6236dae4SAndroid Build Coastguard Worker# copies of the Software, and permit persons to whom the Software is
18*6236dae4SAndroid Build Coastguard Worker# furnished to do so, under the terms of the COPYING file.
19*6236dae4SAndroid Build Coastguard Worker#
20*6236dae4SAndroid Build Coastguard Worker# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
21*6236dae4SAndroid Build Coastguard Worker# KIND, either express or implied.
22*6236dae4SAndroid Build Coastguard Worker#
23*6236dae4SAndroid Build Coastguard Worker# SPDX-License-Identifier: curl
24*6236dae4SAndroid Build Coastguard Worker#
25*6236dae4SAndroid Build Coastguard Worker###########################################################################
26*6236dae4SAndroid Build Coastguard Worker#
27*6236dae4SAndroid Build Coastguard Workerimport inspect
28*6236dae4SAndroid Build Coastguard Workerimport logging
29*6236dae4SAndroid Build Coastguard Workerimport os
30*6236dae4SAndroid Build Coastguard Workerimport subprocess
31*6236dae4SAndroid Build Coastguard Workerfrom datetime import timedelta, datetime
32*6236dae4SAndroid Build Coastguard Workerfrom json import JSONEncoder
33*6236dae4SAndroid Build Coastguard Workerimport time
34*6236dae4SAndroid Build Coastguard Workerfrom typing import List, Union, Optional
35*6236dae4SAndroid Build Coastguard Workerimport copy
36*6236dae4SAndroid Build Coastguard Worker
37*6236dae4SAndroid Build Coastguard Workerfrom .curl import CurlClient, ExecResult
38*6236dae4SAndroid Build Coastguard Workerfrom .env import Env
39*6236dae4SAndroid Build Coastguard Worker
40*6236dae4SAndroid Build Coastguard Worker
41*6236dae4SAndroid Build Coastguard Workerlog = logging.getLogger(__name__)
42*6236dae4SAndroid Build Coastguard Worker
43*6236dae4SAndroid Build Coastguard Worker
44*6236dae4SAndroid Build Coastguard Workerclass Httpd:
45*6236dae4SAndroid Build Coastguard Worker
46*6236dae4SAndroid Build Coastguard Worker    MODULES = [
47*6236dae4SAndroid Build Coastguard Worker        'log_config', 'logio', 'unixd', 'version', 'watchdog',
48*6236dae4SAndroid Build Coastguard Worker        'authn_core', 'authn_file',
49*6236dae4SAndroid Build Coastguard Worker        'authz_user', 'authz_core', 'authz_host',
50*6236dae4SAndroid Build Coastguard Worker        'auth_basic', 'auth_digest',
51*6236dae4SAndroid Build Coastguard Worker        'alias', 'env', 'filter', 'headers', 'mime', 'setenvif',
52*6236dae4SAndroid Build Coastguard Worker        'socache_shmcb',
53*6236dae4SAndroid Build Coastguard Worker        'rewrite', 'http2', 'ssl', 'proxy', 'proxy_http', 'proxy_connect',
54*6236dae4SAndroid Build Coastguard Worker        'brotli',
55*6236dae4SAndroid Build Coastguard Worker        'mpm_event',
56*6236dae4SAndroid Build Coastguard Worker    ]
57*6236dae4SAndroid Build Coastguard Worker    COMMON_MODULES_DIRS = [
58*6236dae4SAndroid Build Coastguard Worker        '/usr/lib/apache2/modules',  # debian
59*6236dae4SAndroid Build Coastguard Worker        '/usr/libexec/apache2/',     # macos
60*6236dae4SAndroid Build Coastguard Worker    ]
61*6236dae4SAndroid Build Coastguard Worker
62*6236dae4SAndroid Build Coastguard Worker    MOD_CURLTEST = None
63*6236dae4SAndroid Build Coastguard Worker
64*6236dae4SAndroid Build Coastguard Worker    def __init__(self, env: Env, proxy_auth: bool = False):
65*6236dae4SAndroid Build Coastguard Worker        self.env = env
66*6236dae4SAndroid Build Coastguard Worker        self._cmd = env.apachectl
67*6236dae4SAndroid Build Coastguard Worker        self._apache_dir = os.path.join(env.gen_dir, 'apache')
68*6236dae4SAndroid Build Coastguard Worker        self._run_dir = os.path.join(self._apache_dir, 'run')
69*6236dae4SAndroid Build Coastguard Worker        self._lock_dir = os.path.join(self._apache_dir, 'locks')
70*6236dae4SAndroid Build Coastguard Worker        self._docs_dir = os.path.join(self._apache_dir, 'docs')
71*6236dae4SAndroid Build Coastguard Worker        self._conf_dir = os.path.join(self._apache_dir, 'conf')
72*6236dae4SAndroid Build Coastguard Worker        self._conf_file = os.path.join(self._conf_dir, 'test.conf')
73*6236dae4SAndroid Build Coastguard Worker        self._logs_dir = os.path.join(self._apache_dir, 'logs')
74*6236dae4SAndroid Build Coastguard Worker        self._error_log = os.path.join(self._logs_dir, 'error_log')
75*6236dae4SAndroid Build Coastguard Worker        self._tmp_dir = os.path.join(self._apache_dir, 'tmp')
76*6236dae4SAndroid Build Coastguard Worker        self._basic_passwords = os.path.join(self._conf_dir, 'basic.passwords')
77*6236dae4SAndroid Build Coastguard Worker        self._digest_passwords = os.path.join(self._conf_dir, 'digest.passwords')
78*6236dae4SAndroid Build Coastguard Worker        self._mods_dir = None
79*6236dae4SAndroid Build Coastguard Worker        self._auth_digest = True
80*6236dae4SAndroid Build Coastguard Worker        self._proxy_auth_basic = proxy_auth
81*6236dae4SAndroid Build Coastguard Worker        self._extra_configs = {}
82*6236dae4SAndroid Build Coastguard Worker        self._loaded_extra_configs = None
83*6236dae4SAndroid Build Coastguard Worker        assert env.apxs
84*6236dae4SAndroid Build Coastguard Worker        p = subprocess.run(args=[env.apxs, '-q', 'libexecdir'],
85*6236dae4SAndroid Build Coastguard Worker                           capture_output=True, text=True)
86*6236dae4SAndroid Build Coastguard Worker        if p.returncode != 0:
87*6236dae4SAndroid Build Coastguard Worker            raise Exception(f'{env.apxs} failed to query libexecdir: {p}')
88*6236dae4SAndroid Build Coastguard Worker        self._mods_dir = p.stdout.strip()
89*6236dae4SAndroid Build Coastguard Worker        if self._mods_dir is None:
90*6236dae4SAndroid Build Coastguard Worker            raise Exception('apache modules dir cannot be found')
91*6236dae4SAndroid Build Coastguard Worker        if not os.path.exists(self._mods_dir):
92*6236dae4SAndroid Build Coastguard Worker            raise Exception(f'apache modules dir does not exist: {self._mods_dir}')
93*6236dae4SAndroid Build Coastguard Worker        self._process = None
94*6236dae4SAndroid Build Coastguard Worker        self._rmf(self._error_log)
95*6236dae4SAndroid Build Coastguard Worker        self._init_curltest()
96*6236dae4SAndroid Build Coastguard Worker
97*6236dae4SAndroid Build Coastguard Worker    @property
98*6236dae4SAndroid Build Coastguard Worker    def docs_dir(self):
99*6236dae4SAndroid Build Coastguard Worker        return self._docs_dir
100*6236dae4SAndroid Build Coastguard Worker
101*6236dae4SAndroid Build Coastguard Worker    def clear_logs(self):
102*6236dae4SAndroid Build Coastguard Worker        self._rmf(self._error_log)
103*6236dae4SAndroid Build Coastguard Worker
104*6236dae4SAndroid Build Coastguard Worker    def exists(self):
105*6236dae4SAndroid Build Coastguard Worker        return os.path.exists(self._cmd)
106*6236dae4SAndroid Build Coastguard Worker
107*6236dae4SAndroid Build Coastguard Worker    def set_extra_config(self, domain: str, lines: Optional[Union[str, List[str]]]):
108*6236dae4SAndroid Build Coastguard Worker        if lines is None:
109*6236dae4SAndroid Build Coastguard Worker            self._extra_configs.pop(domain, None)
110*6236dae4SAndroid Build Coastguard Worker        else:
111*6236dae4SAndroid Build Coastguard Worker            self._extra_configs[domain] = lines
112*6236dae4SAndroid Build Coastguard Worker
113*6236dae4SAndroid Build Coastguard Worker    def clear_extra_configs(self):
114*6236dae4SAndroid Build Coastguard Worker        self._extra_configs = {}
115*6236dae4SAndroid Build Coastguard Worker
116*6236dae4SAndroid Build Coastguard Worker    def set_proxy_auth(self, active: bool):
117*6236dae4SAndroid Build Coastguard Worker        self._proxy_auth_basic = active
118*6236dae4SAndroid Build Coastguard Worker
119*6236dae4SAndroid Build Coastguard Worker    def _run(self, args, intext=''):
120*6236dae4SAndroid Build Coastguard Worker        env = os.environ.copy()
121*6236dae4SAndroid Build Coastguard Worker        env['APACHE_RUN_DIR'] = self._run_dir
122*6236dae4SAndroid Build Coastguard Worker        env['APACHE_RUN_USER'] = os.environ['USER']
123*6236dae4SAndroid Build Coastguard Worker        env['APACHE_LOCK_DIR'] = self._lock_dir
124*6236dae4SAndroid Build Coastguard Worker        env['APACHE_CONFDIR'] = self._apache_dir
125*6236dae4SAndroid Build Coastguard Worker        p = subprocess.run(args, stderr=subprocess.PIPE, stdout=subprocess.PIPE,
126*6236dae4SAndroid Build Coastguard Worker                           cwd=self.env.gen_dir,
127*6236dae4SAndroid Build Coastguard Worker                           input=intext.encode() if intext else None,
128*6236dae4SAndroid Build Coastguard Worker                           env=env)
129*6236dae4SAndroid Build Coastguard Worker        start = datetime.now()
130*6236dae4SAndroid Build Coastguard Worker        return ExecResult(args=args, exit_code=p.returncode,
131*6236dae4SAndroid Build Coastguard Worker                          stdout=p.stdout.decode().splitlines(),
132*6236dae4SAndroid Build Coastguard Worker                          stderr=p.stderr.decode().splitlines(),
133*6236dae4SAndroid Build Coastguard Worker                          duration=datetime.now() - start)
134*6236dae4SAndroid Build Coastguard Worker
135*6236dae4SAndroid Build Coastguard Worker    def _apachectl(self, cmd: str):
136*6236dae4SAndroid Build Coastguard Worker        args = [self.env.apachectl,
137*6236dae4SAndroid Build Coastguard Worker                "-d", self._apache_dir,
138*6236dae4SAndroid Build Coastguard Worker                "-f", self._conf_file,
139*6236dae4SAndroid Build Coastguard Worker                "-k", cmd]
140*6236dae4SAndroid Build Coastguard Worker        return self._run(args=args)
141*6236dae4SAndroid Build Coastguard Worker
142*6236dae4SAndroid Build Coastguard Worker    def start(self):
143*6236dae4SAndroid Build Coastguard Worker        if self._process:
144*6236dae4SAndroid Build Coastguard Worker            self.stop()
145*6236dae4SAndroid Build Coastguard Worker        self._write_config()
146*6236dae4SAndroid Build Coastguard Worker        with open(self._error_log, 'a') as fd:
147*6236dae4SAndroid Build Coastguard Worker            fd.write('start of server\n')
148*6236dae4SAndroid Build Coastguard Worker        with open(os.path.join(self._apache_dir, 'xxx'), 'a') as fd:
149*6236dae4SAndroid Build Coastguard Worker            fd.write('start of server\n')
150*6236dae4SAndroid Build Coastguard Worker        r = self._apachectl('start')
151*6236dae4SAndroid Build Coastguard Worker        if r.exit_code != 0:
152*6236dae4SAndroid Build Coastguard Worker            log.error(f'failed to start httpd: {r}')
153*6236dae4SAndroid Build Coastguard Worker            return False
154*6236dae4SAndroid Build Coastguard Worker        self._loaded_extra_configs = copy.deepcopy(self._extra_configs)
155*6236dae4SAndroid Build Coastguard Worker        return self.wait_live(timeout=timedelta(seconds=5))
156*6236dae4SAndroid Build Coastguard Worker
157*6236dae4SAndroid Build Coastguard Worker    def stop(self):
158*6236dae4SAndroid Build Coastguard Worker        r = self._apachectl('stop')
159*6236dae4SAndroid Build Coastguard Worker        self._loaded_extra_configs = None
160*6236dae4SAndroid Build Coastguard Worker        if r.exit_code == 0:
161*6236dae4SAndroid Build Coastguard Worker            return self.wait_dead(timeout=timedelta(seconds=5))
162*6236dae4SAndroid Build Coastguard Worker        log.fatal(f'stopping httpd failed: {r}')
163*6236dae4SAndroid Build Coastguard Worker        return r.exit_code == 0
164*6236dae4SAndroid Build Coastguard Worker
165*6236dae4SAndroid Build Coastguard Worker    def restart(self):
166*6236dae4SAndroid Build Coastguard Worker        self.stop()
167*6236dae4SAndroid Build Coastguard Worker        return self.start()
168*6236dae4SAndroid Build Coastguard Worker
169*6236dae4SAndroid Build Coastguard Worker    def reload(self):
170*6236dae4SAndroid Build Coastguard Worker        self._write_config()
171*6236dae4SAndroid Build Coastguard Worker        r = self._apachectl("graceful")
172*6236dae4SAndroid Build Coastguard Worker        self._loaded_extra_configs = None
173*6236dae4SAndroid Build Coastguard Worker        if r.exit_code != 0:
174*6236dae4SAndroid Build Coastguard Worker            log.error(f'failed to reload httpd: {r}')
175*6236dae4SAndroid Build Coastguard Worker        self._loaded_extra_configs = copy.deepcopy(self._extra_configs)
176*6236dae4SAndroid Build Coastguard Worker        return self.wait_live(timeout=timedelta(seconds=5))
177*6236dae4SAndroid Build Coastguard Worker
178*6236dae4SAndroid Build Coastguard Worker    def reload_if_config_changed(self):
179*6236dae4SAndroid Build Coastguard Worker        if self._loaded_extra_configs == self._extra_configs:
180*6236dae4SAndroid Build Coastguard Worker            return True
181*6236dae4SAndroid Build Coastguard Worker        return self.reload()
182*6236dae4SAndroid Build Coastguard Worker
183*6236dae4SAndroid Build Coastguard Worker    def wait_dead(self, timeout: timedelta):
184*6236dae4SAndroid Build Coastguard Worker        curl = CurlClient(env=self.env, run_dir=self._tmp_dir)
185*6236dae4SAndroid Build Coastguard Worker        try_until = datetime.now() + timeout
186*6236dae4SAndroid Build Coastguard Worker        while datetime.now() < try_until:
187*6236dae4SAndroid Build Coastguard Worker            r = curl.http_get(url=f'http://{self.env.domain1}:{self.env.http_port}/')
188*6236dae4SAndroid Build Coastguard Worker            if r.exit_code != 0:
189*6236dae4SAndroid Build Coastguard Worker                return True
190*6236dae4SAndroid Build Coastguard Worker            time.sleep(.1)
191*6236dae4SAndroid Build Coastguard Worker        log.debug(f"Server still responding after {timeout}")
192*6236dae4SAndroid Build Coastguard Worker        return False
193*6236dae4SAndroid Build Coastguard Worker
194*6236dae4SAndroid Build Coastguard Worker    def wait_live(self, timeout: timedelta):
195*6236dae4SAndroid Build Coastguard Worker        curl = CurlClient(env=self.env, run_dir=self._tmp_dir,
196*6236dae4SAndroid Build Coastguard Worker                          timeout=timeout.total_seconds())
197*6236dae4SAndroid Build Coastguard Worker        try_until = datetime.now() + timeout
198*6236dae4SAndroid Build Coastguard Worker        while datetime.now() < try_until:
199*6236dae4SAndroid Build Coastguard Worker            r = curl.http_get(url=f'http://{self.env.domain1}:{self.env.http_port}/')
200*6236dae4SAndroid Build Coastguard Worker            if r.exit_code == 0:
201*6236dae4SAndroid Build Coastguard Worker                return True
202*6236dae4SAndroid Build Coastguard Worker            time.sleep(.1)
203*6236dae4SAndroid Build Coastguard Worker        log.debug(f"Server still not responding after {timeout}")
204*6236dae4SAndroid Build Coastguard Worker        return False
205*6236dae4SAndroid Build Coastguard Worker
206*6236dae4SAndroid Build Coastguard Worker    def _rmf(self, path):
207*6236dae4SAndroid Build Coastguard Worker        if os.path.exists(path):
208*6236dae4SAndroid Build Coastguard Worker            return os.remove(path)
209*6236dae4SAndroid Build Coastguard Worker
210*6236dae4SAndroid Build Coastguard Worker    def _mkpath(self, path):
211*6236dae4SAndroid Build Coastguard Worker        if not os.path.exists(path):
212*6236dae4SAndroid Build Coastguard Worker            return os.makedirs(path)
213*6236dae4SAndroid Build Coastguard Worker
214*6236dae4SAndroid Build Coastguard Worker    def _write_config(self):
215*6236dae4SAndroid Build Coastguard Worker        domain1 = self.env.domain1
216*6236dae4SAndroid Build Coastguard Worker        domain1brotli = self.env.domain1brotli
217*6236dae4SAndroid Build Coastguard Worker        creds1 = self.env.get_credentials(domain1)
218*6236dae4SAndroid Build Coastguard Worker        assert creds1  # convince pytype this isn't None
219*6236dae4SAndroid Build Coastguard Worker        domain2 = self.env.domain2
220*6236dae4SAndroid Build Coastguard Worker        creds2 = self.env.get_credentials(domain2)
221*6236dae4SAndroid Build Coastguard Worker        assert creds2  # convince pytype this isn't None
222*6236dae4SAndroid Build Coastguard Worker        proxy_domain = self.env.proxy_domain
223*6236dae4SAndroid Build Coastguard Worker        proxy_creds = self.env.get_credentials(proxy_domain)
224*6236dae4SAndroid Build Coastguard Worker        assert proxy_creds  # convince pytype this isn't None
225*6236dae4SAndroid Build Coastguard Worker        self._mkpath(self._conf_dir)
226*6236dae4SAndroid Build Coastguard Worker        self._mkpath(self._logs_dir)
227*6236dae4SAndroid Build Coastguard Worker        self._mkpath(self._tmp_dir)
228*6236dae4SAndroid Build Coastguard Worker        self._mkpath(os.path.join(self._docs_dir, 'two'))
229*6236dae4SAndroid Build Coastguard Worker        with open(os.path.join(self._docs_dir, 'data.json'), 'w') as fd:
230*6236dae4SAndroid Build Coastguard Worker            data = {
231*6236dae4SAndroid Build Coastguard Worker                'server': f'{domain1}',
232*6236dae4SAndroid Build Coastguard Worker            }
233*6236dae4SAndroid Build Coastguard Worker            fd.write(JSONEncoder().encode(data))
234*6236dae4SAndroid Build Coastguard Worker        with open(os.path.join(self._docs_dir, 'two/data.json'), 'w') as fd:
235*6236dae4SAndroid Build Coastguard Worker            data = {
236*6236dae4SAndroid Build Coastguard Worker                'server': f'{domain2}',
237*6236dae4SAndroid Build Coastguard Worker            }
238*6236dae4SAndroid Build Coastguard Worker            fd.write(JSONEncoder().encode(data))
239*6236dae4SAndroid Build Coastguard Worker        if self._proxy_auth_basic:
240*6236dae4SAndroid Build Coastguard Worker            with open(self._basic_passwords, 'w') as fd:
241*6236dae4SAndroid Build Coastguard Worker                fd.write('proxy:$apr1$FQfeInbs$WQZbODJlVg60j0ogEIlTW/\n')
242*6236dae4SAndroid Build Coastguard Worker        if self._auth_digest:
243*6236dae4SAndroid Build Coastguard Worker            with open(self._digest_passwords, 'w') as fd:
244*6236dae4SAndroid Build Coastguard Worker                fd.write('test:restricted area:57123e269fd73d71ae0656594e938e2f\n')
245*6236dae4SAndroid Build Coastguard Worker            self._mkpath(os.path.join(self.docs_dir, 'restricted/digest'))
246*6236dae4SAndroid Build Coastguard Worker            with open(os.path.join(self.docs_dir, 'restricted/digest/data.json'), 'w') as fd:
247*6236dae4SAndroid Build Coastguard Worker                fd.write('{"area":"digest"}\n')
248*6236dae4SAndroid Build Coastguard Worker        with open(self._conf_file, 'w') as fd:
249*6236dae4SAndroid Build Coastguard Worker            for m in self.MODULES:
250*6236dae4SAndroid Build Coastguard Worker                if os.path.exists(os.path.join(self._mods_dir, f'mod_{m}.so')):
251*6236dae4SAndroid Build Coastguard Worker                    fd.write(f'LoadModule {m}_module   "{self._mods_dir}/mod_{m}.so"\n')
252*6236dae4SAndroid Build Coastguard Worker            if Httpd.MOD_CURLTEST is not None:
253*6236dae4SAndroid Build Coastguard Worker                fd.write(f'LoadModule curltest_module   "{Httpd.MOD_CURLTEST}"\n')
254*6236dae4SAndroid Build Coastguard Worker            conf = [   # base server config
255*6236dae4SAndroid Build Coastguard Worker                f'ServerRoot "{self._apache_dir}"',
256*6236dae4SAndroid Build Coastguard Worker                'DefaultRuntimeDir logs',
257*6236dae4SAndroid Build Coastguard Worker                'PidFile httpd.pid',
258*6236dae4SAndroid Build Coastguard Worker                f'ErrorLog {self._error_log}',
259*6236dae4SAndroid Build Coastguard Worker                f'LogLevel {self._get_log_level()}',
260*6236dae4SAndroid Build Coastguard Worker                'StartServers 4',
261*6236dae4SAndroid Build Coastguard Worker                'ReadBufferSize 16000',
262*6236dae4SAndroid Build Coastguard Worker                'H2MinWorkers 16',
263*6236dae4SAndroid Build Coastguard Worker                'H2MaxWorkers 256',
264*6236dae4SAndroid Build Coastguard Worker                f'Listen {self.env.http_port}',
265*6236dae4SAndroid Build Coastguard Worker                f'Listen {self.env.https_port}',
266*6236dae4SAndroid Build Coastguard Worker                f'Listen {self.env.proxy_port}',
267*6236dae4SAndroid Build Coastguard Worker                f'Listen {self.env.proxys_port}',
268*6236dae4SAndroid Build Coastguard Worker                f'TypesConfig "{self._conf_dir}/mime.types',
269*6236dae4SAndroid Build Coastguard Worker                'SSLSessionCache "shmcb:ssl_gcache_data(32000)"',
270*6236dae4SAndroid Build Coastguard Worker            ]
271*6236dae4SAndroid Build Coastguard Worker            if 'base' in self._extra_configs:
272*6236dae4SAndroid Build Coastguard Worker                conf.extend(self._extra_configs['base'])
273*6236dae4SAndroid Build Coastguard Worker            conf.extend([  # plain http host for domain1
274*6236dae4SAndroid Build Coastguard Worker                f'<VirtualHost *:{self.env.http_port}>',
275*6236dae4SAndroid Build Coastguard Worker                f'    ServerName {domain1}',
276*6236dae4SAndroid Build Coastguard Worker                '    ServerAlias localhost',
277*6236dae4SAndroid Build Coastguard Worker                f'    DocumentRoot "{self._docs_dir}"',
278*6236dae4SAndroid Build Coastguard Worker                '    Protocols h2c http/1.1',
279*6236dae4SAndroid Build Coastguard Worker                '    H2Direct on',
280*6236dae4SAndroid Build Coastguard Worker            ])
281*6236dae4SAndroid Build Coastguard Worker            conf.extend(self._curltest_conf(domain1))
282*6236dae4SAndroid Build Coastguard Worker            conf.extend([
283*6236dae4SAndroid Build Coastguard Worker                '</VirtualHost>',
284*6236dae4SAndroid Build Coastguard Worker                '',
285*6236dae4SAndroid Build Coastguard Worker            ])
286*6236dae4SAndroid Build Coastguard Worker            conf.extend([  # https host for domain1, h1 + h2
287*6236dae4SAndroid Build Coastguard Worker                f'<VirtualHost *:{self.env.https_port}>',
288*6236dae4SAndroid Build Coastguard Worker                f'    ServerName {domain1}',
289*6236dae4SAndroid Build Coastguard Worker                '    ServerAlias localhost',
290*6236dae4SAndroid Build Coastguard Worker                '    Protocols h2 http/1.1',
291*6236dae4SAndroid Build Coastguard Worker                '    SSLEngine on',
292*6236dae4SAndroid Build Coastguard Worker                f'    SSLCertificateFile {creds1.cert_file}',
293*6236dae4SAndroid Build Coastguard Worker                f'    SSLCertificateKeyFile {creds1.pkey_file}',
294*6236dae4SAndroid Build Coastguard Worker                f'    DocumentRoot "{self._docs_dir}"',
295*6236dae4SAndroid Build Coastguard Worker            ])
296*6236dae4SAndroid Build Coastguard Worker            conf.extend(self._curltest_conf(domain1))
297*6236dae4SAndroid Build Coastguard Worker            if domain1 in self._extra_configs:
298*6236dae4SAndroid Build Coastguard Worker                conf.extend(self._extra_configs[domain1])
299*6236dae4SAndroid Build Coastguard Worker            conf.extend([
300*6236dae4SAndroid Build Coastguard Worker                '</VirtualHost>',
301*6236dae4SAndroid Build Coastguard Worker                '',
302*6236dae4SAndroid Build Coastguard Worker            ])
303*6236dae4SAndroid Build Coastguard Worker            # Alternate to domain1 with BROTLI compression
304*6236dae4SAndroid Build Coastguard Worker            conf.extend([  # https host for domain1, h1 + h2
305*6236dae4SAndroid Build Coastguard Worker                f'<VirtualHost *:{self.env.https_port}>',
306*6236dae4SAndroid Build Coastguard Worker                f'    ServerName {domain1brotli}',
307*6236dae4SAndroid Build Coastguard Worker                '    Protocols h2 http/1.1',
308*6236dae4SAndroid Build Coastguard Worker                '    SSLEngine on',
309*6236dae4SAndroid Build Coastguard Worker                f'    SSLCertificateFile {creds1.cert_file}',
310*6236dae4SAndroid Build Coastguard Worker                f'    SSLCertificateKeyFile {creds1.pkey_file}',
311*6236dae4SAndroid Build Coastguard Worker                f'    DocumentRoot "{self._docs_dir}"',
312*6236dae4SAndroid Build Coastguard Worker                '    SetOutputFilter BROTLI_COMPRESS',
313*6236dae4SAndroid Build Coastguard Worker            ])
314*6236dae4SAndroid Build Coastguard Worker            conf.extend(self._curltest_conf(domain1))
315*6236dae4SAndroid Build Coastguard Worker            if domain1 in self._extra_configs:
316*6236dae4SAndroid Build Coastguard Worker                conf.extend(self._extra_configs[domain1])
317*6236dae4SAndroid Build Coastguard Worker            conf.extend([
318*6236dae4SAndroid Build Coastguard Worker                '</VirtualHost>',
319*6236dae4SAndroid Build Coastguard Worker                '',
320*6236dae4SAndroid Build Coastguard Worker            ])
321*6236dae4SAndroid Build Coastguard Worker            conf.extend([  # plain http host for domain2
322*6236dae4SAndroid Build Coastguard Worker                f'<VirtualHost *:{self.env.http_port}>',
323*6236dae4SAndroid Build Coastguard Worker                f'    ServerName {domain2}',
324*6236dae4SAndroid Build Coastguard Worker                '    ServerAlias localhost',
325*6236dae4SAndroid Build Coastguard Worker                f'    DocumentRoot "{self._docs_dir}"',
326*6236dae4SAndroid Build Coastguard Worker                '    Protocols h2c http/1.1',
327*6236dae4SAndroid Build Coastguard Worker            ])
328*6236dae4SAndroid Build Coastguard Worker            conf.extend(self._curltest_conf(domain2))
329*6236dae4SAndroid Build Coastguard Worker            conf.extend([
330*6236dae4SAndroid Build Coastguard Worker                '</VirtualHost>',
331*6236dae4SAndroid Build Coastguard Worker                '',
332*6236dae4SAndroid Build Coastguard Worker            ])
333*6236dae4SAndroid Build Coastguard Worker            conf.extend([  # https host for domain2, no h2
334*6236dae4SAndroid Build Coastguard Worker                f'<VirtualHost *:{self.env.https_port}>',
335*6236dae4SAndroid Build Coastguard Worker                f'    ServerName {domain2}',
336*6236dae4SAndroid Build Coastguard Worker                '    Protocols http/1.1',
337*6236dae4SAndroid Build Coastguard Worker                '    SSLEngine on',
338*6236dae4SAndroid Build Coastguard Worker                f'    SSLCertificateFile {creds2.cert_file}',
339*6236dae4SAndroid Build Coastguard Worker                f'    SSLCertificateKeyFile {creds2.pkey_file}',
340*6236dae4SAndroid Build Coastguard Worker                f'    DocumentRoot "{self._docs_dir}/two"',
341*6236dae4SAndroid Build Coastguard Worker            ])
342*6236dae4SAndroid Build Coastguard Worker            conf.extend(self._curltest_conf(domain2))
343*6236dae4SAndroid Build Coastguard Worker            if domain2 in self._extra_configs:
344*6236dae4SAndroid Build Coastguard Worker                conf.extend(self._extra_configs[domain2])
345*6236dae4SAndroid Build Coastguard Worker            conf.extend([
346*6236dae4SAndroid Build Coastguard Worker                '</VirtualHost>',
347*6236dae4SAndroid Build Coastguard Worker                '',
348*6236dae4SAndroid Build Coastguard Worker            ])
349*6236dae4SAndroid Build Coastguard Worker            conf.extend([  # http forward proxy
350*6236dae4SAndroid Build Coastguard Worker                f'<VirtualHost *:{self.env.proxy_port}>',
351*6236dae4SAndroid Build Coastguard Worker                f'    ServerName {proxy_domain}',
352*6236dae4SAndroid Build Coastguard Worker                '    Protocols h2c http/1.1',
353*6236dae4SAndroid Build Coastguard Worker                '    ProxyRequests On',
354*6236dae4SAndroid Build Coastguard Worker                '    H2ProxyRequests On',
355*6236dae4SAndroid Build Coastguard Worker                '    ProxyVia On',
356*6236dae4SAndroid Build Coastguard Worker                f'    AllowCONNECT {self.env.http_port} {self.env.https_port}',
357*6236dae4SAndroid Build Coastguard Worker            ])
358*6236dae4SAndroid Build Coastguard Worker            conf.extend(self._get_proxy_conf())
359*6236dae4SAndroid Build Coastguard Worker            conf.extend([
360*6236dae4SAndroid Build Coastguard Worker                '</VirtualHost>',
361*6236dae4SAndroid Build Coastguard Worker                '',
362*6236dae4SAndroid Build Coastguard Worker            ])
363*6236dae4SAndroid Build Coastguard Worker            conf.extend([  # https forward proxy
364*6236dae4SAndroid Build Coastguard Worker                f'<VirtualHost *:{self.env.proxys_port}>',
365*6236dae4SAndroid Build Coastguard Worker                f'    ServerName {proxy_domain}',
366*6236dae4SAndroid Build Coastguard Worker                '    Protocols h2 http/1.1',
367*6236dae4SAndroid Build Coastguard Worker                '    SSLEngine on',
368*6236dae4SAndroid Build Coastguard Worker                f'    SSLCertificateFile {proxy_creds.cert_file}',
369*6236dae4SAndroid Build Coastguard Worker                f'    SSLCertificateKeyFile {proxy_creds.pkey_file}',
370*6236dae4SAndroid Build Coastguard Worker                '    ProxyRequests On',
371*6236dae4SAndroid Build Coastguard Worker                '    H2ProxyRequests On',
372*6236dae4SAndroid Build Coastguard Worker                '    ProxyVia On',
373*6236dae4SAndroid Build Coastguard Worker                f'    AllowCONNECT {self.env.http_port} {self.env.https_port}',
374*6236dae4SAndroid Build Coastguard Worker            ])
375*6236dae4SAndroid Build Coastguard Worker            conf.extend(self._get_proxy_conf())
376*6236dae4SAndroid Build Coastguard Worker            conf.extend([
377*6236dae4SAndroid Build Coastguard Worker                '</VirtualHost>',
378*6236dae4SAndroid Build Coastguard Worker                '',
379*6236dae4SAndroid Build Coastguard Worker            ])
380*6236dae4SAndroid Build Coastguard Worker
381*6236dae4SAndroid Build Coastguard Worker            fd.write("\n".join(conf))
382*6236dae4SAndroid Build Coastguard Worker        with open(os.path.join(self._conf_dir, 'mime.types'), 'w') as fd:
383*6236dae4SAndroid Build Coastguard Worker            fd.write("\n".join([
384*6236dae4SAndroid Build Coastguard Worker                'text/html             html',
385*6236dae4SAndroid Build Coastguard Worker                'application/json      json',
386*6236dae4SAndroid Build Coastguard Worker                ''
387*6236dae4SAndroid Build Coastguard Worker            ]))
388*6236dae4SAndroid Build Coastguard Worker
389*6236dae4SAndroid Build Coastguard Worker    def _get_proxy_conf(self):
390*6236dae4SAndroid Build Coastguard Worker        if self._proxy_auth_basic:
391*6236dae4SAndroid Build Coastguard Worker            return [
392*6236dae4SAndroid Build Coastguard Worker                '    <Proxy "*">',
393*6236dae4SAndroid Build Coastguard Worker                '      AuthType Basic',
394*6236dae4SAndroid Build Coastguard Worker                '      AuthName "Restricted Proxy"',
395*6236dae4SAndroid Build Coastguard Worker                '      AuthBasicProvider file',
396*6236dae4SAndroid Build Coastguard Worker                f'      AuthUserFile "{self._basic_passwords}"',
397*6236dae4SAndroid Build Coastguard Worker                '      Require user proxy',
398*6236dae4SAndroid Build Coastguard Worker                '    </Proxy>',
399*6236dae4SAndroid Build Coastguard Worker            ]
400*6236dae4SAndroid Build Coastguard Worker        else:
401*6236dae4SAndroid Build Coastguard Worker            return [
402*6236dae4SAndroid Build Coastguard Worker                '    <Proxy "*">',
403*6236dae4SAndroid Build Coastguard Worker                '      Require ip 127.0.0.1',
404*6236dae4SAndroid Build Coastguard Worker                '    </Proxy>',
405*6236dae4SAndroid Build Coastguard Worker            ]
406*6236dae4SAndroid Build Coastguard Worker
407*6236dae4SAndroid Build Coastguard Worker    def _get_log_level(self):
408*6236dae4SAndroid Build Coastguard Worker        if self.env.verbose > 3:
409*6236dae4SAndroid Build Coastguard Worker            return 'trace2'
410*6236dae4SAndroid Build Coastguard Worker        if self.env.verbose > 2:
411*6236dae4SAndroid Build Coastguard Worker            return 'trace1'
412*6236dae4SAndroid Build Coastguard Worker        if self.env.verbose > 1:
413*6236dae4SAndroid Build Coastguard Worker            return 'debug'
414*6236dae4SAndroid Build Coastguard Worker        return 'info'
415*6236dae4SAndroid Build Coastguard Worker
416*6236dae4SAndroid Build Coastguard Worker    def _curltest_conf(self, servername) -> List[str]:
417*6236dae4SAndroid Build Coastguard Worker        lines = []
418*6236dae4SAndroid Build Coastguard Worker        if Httpd.MOD_CURLTEST is not None:
419*6236dae4SAndroid Build Coastguard Worker            lines.extend([
420*6236dae4SAndroid Build Coastguard Worker                '    Redirect 302 /data.json.302 /data.json',
421*6236dae4SAndroid Build Coastguard Worker                '    Redirect 301 /curltest/echo301 /curltest/echo',
422*6236dae4SAndroid Build Coastguard Worker                '    Redirect 302 /curltest/echo302 /curltest/echo',
423*6236dae4SAndroid Build Coastguard Worker                '    Redirect 303 /curltest/echo303 /curltest/echo',
424*6236dae4SAndroid Build Coastguard Worker                '    Redirect 307 /curltest/echo307 /curltest/echo',
425*6236dae4SAndroid Build Coastguard Worker                '    <Location /curltest/sslinfo>',
426*6236dae4SAndroid Build Coastguard Worker                '      SSLOptions StdEnvVars',
427*6236dae4SAndroid Build Coastguard Worker                '      SetHandler curltest-sslinfo',
428*6236dae4SAndroid Build Coastguard Worker                '    </Location>',
429*6236dae4SAndroid Build Coastguard Worker                '    <Location /curltest/echo>',
430*6236dae4SAndroid Build Coastguard Worker                '      SetHandler curltest-echo',
431*6236dae4SAndroid Build Coastguard Worker                '    </Location>',
432*6236dae4SAndroid Build Coastguard Worker                '    <Location /curltest/put>',
433*6236dae4SAndroid Build Coastguard Worker                '      SetHandler curltest-put',
434*6236dae4SAndroid Build Coastguard Worker                '    </Location>',
435*6236dae4SAndroid Build Coastguard Worker                '    <Location /curltest/tweak>',
436*6236dae4SAndroid Build Coastguard Worker                '      SetHandler curltest-tweak',
437*6236dae4SAndroid Build Coastguard Worker                '    </Location>',
438*6236dae4SAndroid Build Coastguard Worker                '    Redirect 302 /tweak /curltest/tweak',
439*6236dae4SAndroid Build Coastguard Worker                '    <Location /curltest/1_1>',
440*6236dae4SAndroid Build Coastguard Worker                '      SetHandler curltest-1_1-required',
441*6236dae4SAndroid Build Coastguard Worker                '    </Location>',
442*6236dae4SAndroid Build Coastguard Worker                '    <Location /curltest/shutdown_unclean>',
443*6236dae4SAndroid Build Coastguard Worker                '      SetHandler curltest-tweak',
444*6236dae4SAndroid Build Coastguard Worker                '      SetEnv force-response-1.0 1',
445*6236dae4SAndroid Build Coastguard Worker                '    </Location>',
446*6236dae4SAndroid Build Coastguard Worker                '    SetEnvIf Request_URI "/shutdown_unclean" ssl-unclean=1',
447*6236dae4SAndroid Build Coastguard Worker            ])
448*6236dae4SAndroid Build Coastguard Worker        if self._auth_digest:
449*6236dae4SAndroid Build Coastguard Worker            lines.extend([
450*6236dae4SAndroid Build Coastguard Worker                f'    <Directory {self.docs_dir}/restricted/digest>',
451*6236dae4SAndroid Build Coastguard Worker                '      AuthType Digest',
452*6236dae4SAndroid Build Coastguard Worker                '      AuthName "restricted area"',
453*6236dae4SAndroid Build Coastguard Worker                f'      AuthDigestDomain "https://{servername}"',
454*6236dae4SAndroid Build Coastguard Worker                '      AuthBasicProvider file',
455*6236dae4SAndroid Build Coastguard Worker                f'      AuthUserFile "{self._digest_passwords}"',
456*6236dae4SAndroid Build Coastguard Worker                '      Require valid-user',
457*6236dae4SAndroid Build Coastguard Worker                '    </Directory>',
458*6236dae4SAndroid Build Coastguard Worker
459*6236dae4SAndroid Build Coastguard Worker            ])
460*6236dae4SAndroid Build Coastguard Worker        return lines
461*6236dae4SAndroid Build Coastguard Worker
462*6236dae4SAndroid Build Coastguard Worker    def _init_curltest(self):
463*6236dae4SAndroid Build Coastguard Worker        if Httpd.MOD_CURLTEST is not None:
464*6236dae4SAndroid Build Coastguard Worker            return
465*6236dae4SAndroid Build Coastguard Worker        local_dir = os.path.dirname(inspect.getfile(Httpd))
466*6236dae4SAndroid Build Coastguard Worker        p = subprocess.run([self.env.apxs, '-c', 'mod_curltest.c'],
467*6236dae4SAndroid Build Coastguard Worker                           capture_output=True,
468*6236dae4SAndroid Build Coastguard Worker                           cwd=os.path.join(local_dir, 'mod_curltest'))
469*6236dae4SAndroid Build Coastguard Worker        rv = p.returncode
470*6236dae4SAndroid Build Coastguard Worker        if rv != 0:
471*6236dae4SAndroid Build Coastguard Worker            log.error(f"compiling mod_curltest failed: {p.stderr}")
472*6236dae4SAndroid Build Coastguard Worker            raise Exception(f"compiling mod_curltest failed: {p.stderr}")
473*6236dae4SAndroid Build Coastguard Worker        Httpd.MOD_CURLTEST = os.path.join(
474*6236dae4SAndroid Build Coastguard Worker            local_dir, 'mod_curltest/.libs/mod_curltest.so')
475