Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/h11/_events.py: 72%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

106 statements  

1# High level events that make up HTTP/1.1 conversations. Loosely inspired by 

2# the corresponding events in hyper-h2: 

3# 

4# http://python-hyper.org/h2/en/stable/api.html#events 

5# 

6# Don't subclass these. Stuff will break. 

7 

8import re 

9from abc import ABC 

10from dataclasses import dataclass 

11from typing import List, Tuple, Union 

12 

13from ._abnf import method, request_target 

14from ._headers import Headers, normalize_and_validate 

15from ._util import bytesify, LocalProtocolError, validate 

16 

17# Everything in __all__ gets re-exported as part of the h11 public API. 

18__all__ = [ 

19 "Event", 

20 "Request", 

21 "InformationalResponse", 

22 "Response", 

23 "Data", 

24 "EndOfMessage", 

25 "ConnectionClosed", 

26] 

27 

28method_re = re.compile(method.encode("ascii")) 

29request_target_re = re.compile(request_target.encode("ascii")) 

30 

31 

32class Event(ABC): 

33 """ 

34 Base class for h11 events. 

35 """ 

36 

37 __slots__ = () 

38 

39 

40@dataclass(init=False, frozen=True) 

41class Request(Event): 

42 """The beginning of an HTTP request. 

43 

44 Fields: 

45 

46 .. attribute:: method 

47 

48 An HTTP method, e.g. ``b"GET"`` or ``b"POST"``. Always a byte 

49 string. :term:`Bytes-like objects <bytes-like object>` and native 

50 strings containing only ascii characters will be automatically 

51 converted to byte strings. 

52 

53 .. attribute:: target 

54 

55 The target of an HTTP request, e.g. ``b"/index.html"``, or one of the 

56 more exotic formats described in `RFC 7320, section 5.3 

57 <https://tools.ietf.org/html/rfc7230#section-5.3>`_. Always a byte 

58 string. :term:`Bytes-like objects <bytes-like object>` and native 

59 strings containing only ascii characters will be automatically 

60 converted to byte strings. 

61 

62 .. attribute:: headers 

63 

64 Request headers, represented as a list of (name, value) pairs. See 

65 :ref:`the header normalization rules <headers-format>` for details. 

66 

67 .. attribute:: http_version 

68 

69 The HTTP protocol version, represented as a byte string like 

70 ``b"1.1"``. See :ref:`the HTTP version normalization rules 

71 <http_version-format>` for details. 

72 

73 """ 

74 

75 __slots__ = ("method", "headers", "target", "http_version") 

76 

77 method: bytes 

78 headers: Headers 

79 target: bytes 

80 http_version: bytes 

81 

82 def __init__( 

83 self, 

84 *, 

85 method: Union[bytes, str], 

86 headers: Union[Headers, List[Tuple[bytes, bytes]], List[Tuple[str, str]]], 

87 target: Union[bytes, str], 

88 http_version: Union[bytes, str] = b"1.1", 

89 _parsed: bool = False, 

90 ) -> None: 

91 super().__init__() 

92 if isinstance(headers, Headers): 

93 object.__setattr__(self, "headers", headers) 

94 else: 

95 object.__setattr__( 

96 self, "headers", normalize_and_validate(headers, _parsed=_parsed) 

97 ) 

98 if not _parsed: 

99 object.__setattr__(self, "method", bytesify(method)) 

100 object.__setattr__(self, "target", bytesify(target)) 

101 object.__setattr__(self, "http_version", bytesify(http_version)) 

102 else: 

103 object.__setattr__(self, "method", method) 

104 object.__setattr__(self, "target", target) 

105 object.__setattr__(self, "http_version", http_version) 

106 

107 # "A server MUST respond with a 400 (Bad Request) status code to any 

108 # HTTP/1.1 request message that lacks a Host header field and to any 

109 # request message that contains more than one Host header field or a 

110 # Host header field with an invalid field-value." 

111 # -- https://tools.ietf.org/html/rfc7230#section-5.4 

112 host_count = 0 

113 for name, value in self.headers: 

114 if name == b"host": 

115 host_count += 1 

116 if self.http_version == b"1.1" and host_count == 0: 

117 raise LocalProtocolError("Missing mandatory Host: header") 

118 if host_count > 1: 

119 raise LocalProtocolError("Found multiple Host: headers") 

120 

121 validate(method_re, self.method, "Illegal method characters") 

122 validate(request_target_re, self.target, "Illegal target characters") 

123 

124 # This is an unhashable type. 

125 __hash__ = None # type: ignore 

126 

127 

128@dataclass(init=False, frozen=True) 

129class _ResponseBase(Event): 

130 __slots__ = ("headers", "http_version", "reason", "status_code") 

131 

132 headers: Headers 

133 http_version: bytes 

134 reason: bytes 

135 status_code: int 

136 

137 def __init__( 

138 self, 

139 *, 

140 headers: Union[Headers, List[Tuple[bytes, bytes]], List[Tuple[str, str]]], 

141 status_code: int, 

142 http_version: Union[bytes, str] = b"1.1", 

143 reason: Union[bytes, str] = b"", 

144 _parsed: bool = False, 

145 ) -> None: 

146 super().__init__() 

147 if isinstance(headers, Headers): 

148 object.__setattr__(self, "headers", headers) 

149 else: 

150 object.__setattr__( 

151 self, "headers", normalize_and_validate(headers, _parsed=_parsed) 

152 ) 

153 if not _parsed: 

154 object.__setattr__(self, "reason", bytesify(reason)) 

155 object.__setattr__(self, "http_version", bytesify(http_version)) 

156 if not isinstance(status_code, int): 

157 raise LocalProtocolError("status code must be integer") 

158 # Because IntEnum objects are instances of int, but aren't 

159 # duck-compatible (sigh), see gh-72. 

160 object.__setattr__(self, "status_code", int(status_code)) 

161 else: 

162 object.__setattr__(self, "reason", reason) 

163 object.__setattr__(self, "http_version", http_version) 

164 object.__setattr__(self, "status_code", status_code) 

165 

166 self.__post_init__() 

167 

168 def __post_init__(self) -> None: 

169 pass 

170 

171 # This is an unhashable type. 

172 __hash__ = None # type: ignore 

173 

174 

175@dataclass(init=False, frozen=True) 

176class InformationalResponse(_ResponseBase): 

177 """An HTTP informational response. 

178 

179 Fields: 

180 

181 .. attribute:: status_code 

182 

183 The status code of this response, as an integer. For an 

184 :class:`InformationalResponse`, this is always in the range [100, 

185 200). 

186 

187 .. attribute:: headers 

188 

189 Request headers, represented as a list of (name, value) pairs. See 

190 :ref:`the header normalization rules <headers-format>` for 

191 details. 

192 

193 .. attribute:: http_version 

194 

195 The HTTP protocol version, represented as a byte string like 

196 ``b"1.1"``. See :ref:`the HTTP version normalization rules 

197 <http_version-format>` for details. 

198 

199 .. attribute:: reason 

200 

201 The reason phrase of this response, as a byte string. For example: 

202 ``b"OK"``, or ``b"Not Found"``. 

203 

204 """ 

205 

206 def __post_init__(self) -> None: 

207 if not (100 <= self.status_code < 200): 

208 raise LocalProtocolError( 

209 "InformationalResponse status_code should be in range " 

210 "[100, 200), not {}".format(self.status_code) 

211 ) 

212 

213 # This is an unhashable type. 

214 __hash__ = None # type: ignore 

215 

216 

217@dataclass(init=False, frozen=True) 

218class Response(_ResponseBase): 

219 """The beginning of an HTTP response. 

220 

221 Fields: 

222 

223 .. attribute:: status_code 

224 

225 The status code of this response, as an integer. For an 

226 :class:`Response`, this is always in the range [200, 

227 1000). 

228 

229 .. attribute:: headers 

230 

231 Request headers, represented as a list of (name, value) pairs. See 

232 :ref:`the header normalization rules <headers-format>` for details. 

233 

234 .. attribute:: http_version 

235 

236 The HTTP protocol version, represented as a byte string like 

237 ``b"1.1"``. See :ref:`the HTTP version normalization rules 

238 <http_version-format>` for details. 

239 

240 .. attribute:: reason 

241 

242 The reason phrase of this response, as a byte string. For example: 

243 ``b"OK"``, or ``b"Not Found"``. 

244 

245 """ 

246 

247 def __post_init__(self) -> None: 

248 if not (200 <= self.status_code < 1000): 

249 raise LocalProtocolError( 

250 "Response status_code should be in range [200, 1000), not {}".format( 

251 self.status_code 

252 ) 

253 ) 

254 

255 # This is an unhashable type. 

256 __hash__ = None # type: ignore 

257 

258 

259@dataclass(init=False, frozen=True) 

260class Data(Event): 

261 """Part of an HTTP message body. 

262 

263 Fields: 

264 

265 .. attribute:: data 

266 

267 A :term:`bytes-like object` containing part of a message body. Or, if 

268 using the ``combine=False`` argument to :meth:`Connection.send`, then 

269 any object that your socket writing code knows what to do with, and for 

270 which calling :func:`len` returns the number of bytes that will be 

271 written -- see :ref:`sendfile` for details. 

272 

273 .. attribute:: chunk_start 

274 

275 A marker that indicates whether this data object is from the start of a 

276 chunked transfer encoding chunk. This field is ignored when when a Data 

277 event is provided to :meth:`Connection.send`: it is only valid on 

278 events emitted from :meth:`Connection.next_event`. You probably 

279 shouldn't use this attribute at all; see 

280 :ref:`chunk-delimiters-are-bad` for details. 

281 

282 .. attribute:: chunk_end 

283 

284 A marker that indicates whether this data object is the last for a 

285 given chunked transfer encoding chunk. This field is ignored when when 

286 a Data event is provided to :meth:`Connection.send`: it is only valid 

287 on events emitted from :meth:`Connection.next_event`. You probably 

288 shouldn't use this attribute at all; see 

289 :ref:`chunk-delimiters-are-bad` for details. 

290 

291 """ 

292 

293 __slots__ = ("data", "chunk_start", "chunk_end") 

294 

295 data: bytes 

296 chunk_start: bool 

297 chunk_end: bool 

298 

299 def __init__( 

300 self, data: bytes, chunk_start: bool = False, chunk_end: bool = False 

301 ) -> None: 

302 object.__setattr__(self, "data", data) 

303 object.__setattr__(self, "chunk_start", chunk_start) 

304 object.__setattr__(self, "chunk_end", chunk_end) 

305 

306 # This is an unhashable type. 

307 __hash__ = None # type: ignore 

308 

309 

310# XX FIXME: "A recipient MUST ignore (or consider as an error) any fields that 

311# are forbidden to be sent in a trailer, since processing them as if they were 

312# present in the header section might bypass external security filters." 

313# https://svn.tools.ietf.org/svn/wg/httpbis/specs/rfc7230.html#chunked.trailer.part 

314# Unfortunately, the list of forbidden fields is long and vague :-/ 

315@dataclass(init=False, frozen=True) 

316class EndOfMessage(Event): 

317 """The end of an HTTP message. 

318 

319 Fields: 

320 

321 .. attribute:: headers 

322 

323 Default value: ``[]`` 

324 

325 Any trailing headers attached to this message, represented as a list of 

326 (name, value) pairs. See :ref:`the header normalization rules 

327 <headers-format>` for details. 

328 

329 Must be empty unless ``Transfer-Encoding: chunked`` is in use. 

330 

331 """ 

332 

333 __slots__ = ("headers",) 

334 

335 headers: Headers 

336 

337 def __init__( 

338 self, 

339 *, 

340 headers: Union[ 

341 Headers, List[Tuple[bytes, bytes]], List[Tuple[str, str]], None 

342 ] = None, 

343 _parsed: bool = False, 

344 ) -> None: 

345 super().__init__() 

346 if headers is None: 

347 headers = Headers([]) 

348 elif not isinstance(headers, Headers): 

349 headers = normalize_and_validate(headers, _parsed=_parsed) 

350 

351 object.__setattr__(self, "headers", headers) 

352 

353 # This is an unhashable type. 

354 __hash__ = None # type: ignore 

355 

356 

357@dataclass(frozen=True) 

358class ConnectionClosed(Event): 

359 """This event indicates that the sender has closed their outgoing 

360 connection. 

361 

362 Note that this does not necessarily mean that they can't *receive* further 

363 data, because TCP connections are composed to two one-way channels which 

364 can be closed independently. See :ref:`closing` for details. 

365 

366 No fields. 

367 """ 

368 

369 pass