env.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. #***************************************************************************
  4. # _ _ ____ _
  5. # Project ___| | | | _ \| |
  6. # / __| | | | |_) | |
  7. # | (__| |_| | _ <| |___
  8. # \___|\___/|_| \_\_____|
  9. #
  10. # Copyright (C) 2008 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
  11. #
  12. # This software is licensed as described in the file COPYING, which
  13. # you should have received as part of this distribution. The terms
  14. # are also available at https://curl.se/docs/copyright.html.
  15. #
  16. # You may opt to use, copy, modify, merge, publish, distribute and/or sell
  17. # copies of the Software, and permit persons to whom the Software is
  18. # furnished to do so, under the terms of the COPYING file.
  19. #
  20. # This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
  21. # KIND, either express or implied.
  22. #
  23. # SPDX-License-Identifier: curl
  24. #
  25. ###########################################################################
  26. #
  27. import logging
  28. import os
  29. import re
  30. import subprocess
  31. from configparser import ConfigParser, ExtendedInterpolation
  32. from typing import Optional
  33. from .certs import CertificateSpec, TestCA, Credentials
  34. log = logging.getLogger(__name__)
  35. def init_config_from(conf_path):
  36. if os.path.isfile(conf_path):
  37. config = ConfigParser(interpolation=ExtendedInterpolation())
  38. config.read(conf_path)
  39. return config
  40. return None
  41. TESTS_HTTPD_PATH = os.path.dirname(os.path.dirname(__file__))
  42. DEF_CONFIG = init_config_from(os.path.join(TESTS_HTTPD_PATH, 'config.ini'))
  43. TOP_PATH = os.path.dirname(os.path.dirname(TESTS_HTTPD_PATH))
  44. CURL = os.path.join(TOP_PATH, 'src/curl')
  45. class EnvConfig:
  46. def __init__(self):
  47. self.tests_dir = TESTS_HTTPD_PATH
  48. self.gen_dir = os.path.join(self.tests_dir, 'gen')
  49. self.config = DEF_CONFIG
  50. # check cur and its features
  51. self.curl = CURL
  52. self.curl_features = []
  53. self.curl_protos = []
  54. p = subprocess.run(args=[self.curl, '-V'],
  55. capture_output=True, text=True)
  56. if p.returncode != 0:
  57. assert False, f'{self.curl} -V failed with exit code: {p.returncode}'
  58. for l in p.stdout.splitlines(keepends=False):
  59. if l.startswith('Features: '):
  60. self.curl_features = [feat.lower() for feat in l[10:].split(' ')]
  61. if l.startswith('Protocols: '):
  62. self.curl_protos = [prot.lower() for prot in l[11:].split(' ')]
  63. self.nghttpx_with_h3 = re.match(r'.* nghttp3/.*', p.stdout.strip())
  64. log.error(f'nghttpx -v: {p.stdout}')
  65. self.http_port = self.config['test']['http_port']
  66. self.https_port = self.config['test']['https_port']
  67. self.h3_port = self.config['test']['h3_port']
  68. self.httpd = self.config['httpd']['httpd']
  69. self.apachectl = self.config['httpd']['apachectl']
  70. self.apxs = self.config['httpd']['apxs']
  71. if len(self.apxs) == 0:
  72. self.apxs = None
  73. self.examples_pem = {
  74. 'key': 'xxx',
  75. 'cert': 'xxx',
  76. }
  77. self.htdocs_dir = os.path.join(self.gen_dir, 'htdocs')
  78. self.tld = 'tests-httpd.curl.se'
  79. self.domain1 = f"one.{self.tld}"
  80. self.domain2 = f"two.{self.tld}"
  81. self.cert_specs = [
  82. CertificateSpec(domains=[self.domain1], key_type='rsa2048'),
  83. CertificateSpec(domains=[self.domain2], key_type='rsa2048'),
  84. CertificateSpec(name="clientsX", sub_specs=[
  85. CertificateSpec(name="user1", client=True),
  86. ]),
  87. ]
  88. self.nghttpx = self.config['nghttpx']['nghttpx']
  89. self.nghttpx_with_h3 = False
  90. if len(self.nghttpx) == 0:
  91. self.nghttpx = 'nghttpx'
  92. if self.nghttpx is not None:
  93. p = subprocess.run(args=[self.nghttpx, '-v'],
  94. capture_output=True, text=True)
  95. if p.returncode != 0:
  96. # not a working nghttpx
  97. self.nghttpx = None
  98. else:
  99. self.nghttpx_with_h3 = re.match(r'.* nghttp3/.*', p.stdout.strip()) is not None
  100. log.error(f'nghttpx -v: {p.stdout}')
  101. def is_complete(self) -> bool:
  102. return os.path.isfile(self.httpd) and \
  103. os.path.isfile(self.apachectl) and \
  104. self.apxs is not None and \
  105. os.path.isfile(self.apxs)
  106. def get_incomplete_reason(self) -> Optional[str]:
  107. if not os.path.isfile(self.httpd):
  108. return f'httpd ({self.httpd}) not found'
  109. if not os.path.isfile(self.apachectl):
  110. return f'apachectl ({self.apachectl}) not found'
  111. if self.apxs is None:
  112. return f"apxs (provided by apache2-dev) not found"
  113. if not os.path.isfile(self.apxs):
  114. return f"apxs ({self.apxs}) not found"
  115. return None
  116. class Env:
  117. CONFIG = EnvConfig()
  118. @staticmethod
  119. def setup_incomplete() -> bool:
  120. return not Env.CONFIG.is_complete()
  121. @staticmethod
  122. def incomplete_reason() -> Optional[str]:
  123. return Env.CONFIG.get_incomplete_reason()
  124. @staticmethod
  125. def have_h3_server() -> bool:
  126. return Env.CONFIG.nghttpx_with_h3
  127. @staticmethod
  128. def have_h3_curl() -> bool:
  129. return 'http3' in Env.CONFIG.curl_features
  130. @staticmethod
  131. def have_h3() -> bool:
  132. return Env.have_h3_curl() and Env.have_h3_server()
  133. def __init__(self, pytestconfig=None):
  134. self._verbose = pytestconfig.option.verbose \
  135. if pytestconfig is not None else 0
  136. self._ca = None
  137. def issue_certs(self):
  138. if self._ca is None:
  139. ca_dir = os.path.join(self.CONFIG.gen_dir, 'ca')
  140. self._ca = TestCA.create_root(name=self.CONFIG.tld,
  141. store_dir=ca_dir,
  142. key_type="rsa2048")
  143. self._ca.issue_certs(self.CONFIG.cert_specs)
  144. def setup(self):
  145. os.makedirs(self.gen_dir, exist_ok=True)
  146. os.makedirs(self.htdocs_dir, exist_ok=True)
  147. self.issue_certs()
  148. def get_credentials(self, domain) -> Optional[Credentials]:
  149. creds = self.ca.get_credentials_for_name(domain)
  150. if len(creds) > 0:
  151. return creds[0]
  152. return None
  153. @property
  154. def verbose(self) -> int:
  155. return self._verbose
  156. @property
  157. def gen_dir(self) -> str:
  158. return self.CONFIG.gen_dir
  159. @property
  160. def ca(self):
  161. return self._ca
  162. @property
  163. def htdocs_dir(self) -> str:
  164. return self.CONFIG.htdocs_dir
  165. @property
  166. def domain1(self) -> str:
  167. return self.CONFIG.domain1
  168. @property
  169. def domain2(self) -> str:
  170. return self.CONFIG.domain2
  171. @property
  172. def http_port(self) -> str:
  173. return self.CONFIG.http_port
  174. @property
  175. def https_port(self) -> str:
  176. return self.CONFIG.https_port
  177. @property
  178. def h3_port(self) -> str:
  179. return self.CONFIG.h3_port
  180. @property
  181. def curl(self) -> str:
  182. return self.CONFIG.curl
  183. @property
  184. def httpd(self) -> str:
  185. return self.CONFIG.httpd
  186. @property
  187. def apachectl(self) -> str:
  188. return self.CONFIG.apachectl
  189. @property
  190. def apxs(self) -> str:
  191. return self.CONFIG.apxs
  192. @property
  193. def nghttpx(self) -> Optional[str]:
  194. return self.CONFIG.nghttpx
  195. def authority_for(self, domain: str, alpn_proto: Optional[str] = None):
  196. if alpn_proto is None or \
  197. alpn_proto in ['h2', 'http/1.1', 'http/1.0', 'http/0.9']:
  198. return f'{domain}:{self.https_port}'
  199. if alpn_proto in ['h3']:
  200. return f'{domain}:{self.h3_port}'
  201. return f'{domain}:{self.http_port}'