test_connection.py 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122
  1. from typing import Any, cast, Dict, List, Optional, Tuple, Type
  2. import pytest
  3. from .._connection import _body_framing, _keep_alive, Connection, NEED_DATA, PAUSED
  4. from .._events import (
  5. ConnectionClosed,
  6. Data,
  7. EndOfMessage,
  8. Event,
  9. InformationalResponse,
  10. Request,
  11. Response,
  12. )
  13. from .._state import (
  14. CLIENT,
  15. CLOSED,
  16. DONE,
  17. ERROR,
  18. IDLE,
  19. MIGHT_SWITCH_PROTOCOL,
  20. MUST_CLOSE,
  21. SEND_BODY,
  22. SEND_RESPONSE,
  23. SERVER,
  24. SWITCHED_PROTOCOL,
  25. )
  26. from .._util import LocalProtocolError, RemoteProtocolError, Sentinel
  27. from .helpers import ConnectionPair, get_all_events, receive_and_get
  28. def test__keep_alive() -> None:
  29. assert _keep_alive(
  30. Request(method="GET", target="/", headers=[("Host", "Example.com")])
  31. )
  32. assert not _keep_alive(
  33. Request(
  34. method="GET",
  35. target="/",
  36. headers=[("Host", "Example.com"), ("Connection", "close")],
  37. )
  38. )
  39. assert not _keep_alive(
  40. Request(
  41. method="GET",
  42. target="/",
  43. headers=[("Host", "Example.com"), ("Connection", "a, b, cLOse, foo")],
  44. )
  45. )
  46. assert not _keep_alive(
  47. Request(method="GET", target="/", headers=[], http_version="1.0") # type: ignore[arg-type]
  48. )
  49. assert _keep_alive(Response(status_code=200, headers=[])) # type: ignore[arg-type]
  50. assert not _keep_alive(Response(status_code=200, headers=[("Connection", "close")]))
  51. assert not _keep_alive(
  52. Response(status_code=200, headers=[("Connection", "a, b, cLOse, foo")])
  53. )
  54. assert not _keep_alive(Response(status_code=200, headers=[], http_version="1.0")) # type: ignore[arg-type]
  55. def test__body_framing() -> None:
  56. def headers(cl: Optional[int], te: bool) -> List[Tuple[str, str]]:
  57. headers = []
  58. if cl is not None:
  59. headers.append(("Content-Length", str(cl)))
  60. if te:
  61. headers.append(("Transfer-Encoding", "chunked"))
  62. return headers
  63. def resp(
  64. status_code: int = 200, cl: Optional[int] = None, te: bool = False
  65. ) -> Response:
  66. return Response(status_code=status_code, headers=headers(cl, te))
  67. def req(cl: Optional[int] = None, te: bool = False) -> Request:
  68. h = headers(cl, te)
  69. h += [("Host", "example.com")]
  70. return Request(method="GET", target="/", headers=h)
  71. # Special cases where the headers are ignored:
  72. for kwargs in [{}, {"cl": 100}, {"te": True}, {"cl": 100, "te": True}]:
  73. kwargs = cast(Dict[str, Any], kwargs)
  74. for meth, r in [
  75. (b"HEAD", resp(**kwargs)),
  76. (b"GET", resp(status_code=204, **kwargs)),
  77. (b"GET", resp(status_code=304, **kwargs)),
  78. ]:
  79. assert _body_framing(meth, r) == ("content-length", (0,))
  80. # Transfer-encoding
  81. for kwargs in [{"te": True}, {"cl": 100, "te": True}]:
  82. kwargs = cast(Dict[str, Any], kwargs)
  83. for meth, r in [(None, req(**kwargs)), (b"GET", resp(**kwargs))]: # type: ignore
  84. assert _body_framing(meth, r) == ("chunked", ())
  85. # Content-Length
  86. for meth, r in [(None, req(cl=100)), (b"GET", resp(cl=100))]: # type: ignore
  87. assert _body_framing(meth, r) == ("content-length", (100,))
  88. # No headers
  89. assert _body_framing(None, req()) == ("content-length", (0,)) # type: ignore
  90. assert _body_framing(b"GET", resp()) == ("http/1.0", ())
  91. def test_Connection_basics_and_content_length() -> None:
  92. with pytest.raises(ValueError):
  93. Connection("CLIENT") # type: ignore
  94. p = ConnectionPair()
  95. assert p.conn[CLIENT].our_role is CLIENT
  96. assert p.conn[CLIENT].their_role is SERVER
  97. assert p.conn[SERVER].our_role is SERVER
  98. assert p.conn[SERVER].their_role is CLIENT
  99. data = p.send(
  100. CLIENT,
  101. Request(
  102. method="GET",
  103. target="/",
  104. headers=[("Host", "example.com"), ("Content-Length", "10")],
  105. ),
  106. )
  107. assert data == (
  108. b"GET / HTTP/1.1\r\n" b"Host: example.com\r\n" b"Content-Length: 10\r\n\r\n"
  109. )
  110. for conn in p.conns:
  111. assert conn.states == {CLIENT: SEND_BODY, SERVER: SEND_RESPONSE}
  112. assert p.conn[CLIENT].our_state is SEND_BODY
  113. assert p.conn[CLIENT].their_state is SEND_RESPONSE
  114. assert p.conn[SERVER].our_state is SEND_RESPONSE
  115. assert p.conn[SERVER].their_state is SEND_BODY
  116. assert p.conn[CLIENT].their_http_version is None
  117. assert p.conn[SERVER].their_http_version == b"1.1"
  118. data = p.send(SERVER, InformationalResponse(status_code=100, headers=[])) # type: ignore[arg-type]
  119. assert data == b"HTTP/1.1 100 \r\n\r\n"
  120. data = p.send(SERVER, Response(status_code=200, headers=[("Content-Length", "11")]))
  121. assert data == b"HTTP/1.1 200 \r\nContent-Length: 11\r\n\r\n"
  122. for conn in p.conns:
  123. assert conn.states == {CLIENT: SEND_BODY, SERVER: SEND_BODY}
  124. assert p.conn[CLIENT].their_http_version == b"1.1"
  125. assert p.conn[SERVER].their_http_version == b"1.1"
  126. data = p.send(CLIENT, Data(data=b"12345"))
  127. assert data == b"12345"
  128. data = p.send(
  129. CLIENT, Data(data=b"67890"), expect=[Data(data=b"67890"), EndOfMessage()]
  130. )
  131. assert data == b"67890"
  132. data = p.send(CLIENT, EndOfMessage(), expect=[])
  133. assert data == b""
  134. for conn in p.conns:
  135. assert conn.states == {CLIENT: DONE, SERVER: SEND_BODY}
  136. data = p.send(SERVER, Data(data=b"1234567890"))
  137. assert data == b"1234567890"
  138. data = p.send(SERVER, Data(data=b"1"), expect=[Data(data=b"1"), EndOfMessage()])
  139. assert data == b"1"
  140. data = p.send(SERVER, EndOfMessage(), expect=[])
  141. assert data == b""
  142. for conn in p.conns:
  143. assert conn.states == {CLIENT: DONE, SERVER: DONE}
  144. def test_chunked() -> None:
  145. p = ConnectionPair()
  146. p.send(
  147. CLIENT,
  148. Request(
  149. method="GET",
  150. target="/",
  151. headers=[("Host", "example.com"), ("Transfer-Encoding", "chunked")],
  152. ),
  153. )
  154. data = p.send(CLIENT, Data(data=b"1234567890", chunk_start=True, chunk_end=True))
  155. assert data == b"a\r\n1234567890\r\n"
  156. data = p.send(CLIENT, Data(data=b"abcde", chunk_start=True, chunk_end=True))
  157. assert data == b"5\r\nabcde\r\n"
  158. data = p.send(CLIENT, Data(data=b""), expect=[])
  159. assert data == b""
  160. data = p.send(CLIENT, EndOfMessage(headers=[("hello", "there")]))
  161. assert data == b"0\r\nhello: there\r\n\r\n"
  162. p.send(
  163. SERVER, Response(status_code=200, headers=[("Transfer-Encoding", "chunked")])
  164. )
  165. p.send(SERVER, Data(data=b"54321", chunk_start=True, chunk_end=True))
  166. p.send(SERVER, Data(data=b"12345", chunk_start=True, chunk_end=True))
  167. p.send(SERVER, EndOfMessage())
  168. for conn in p.conns:
  169. assert conn.states == {CLIENT: DONE, SERVER: DONE}
  170. def test_chunk_boundaries() -> None:
  171. conn = Connection(our_role=SERVER)
  172. request = (
  173. b"POST / HTTP/1.1\r\n"
  174. b"Host: example.com\r\n"
  175. b"Transfer-Encoding: chunked\r\n"
  176. b"\r\n"
  177. )
  178. conn.receive_data(request)
  179. assert conn.next_event() == Request(
  180. method="POST",
  181. target="/",
  182. headers=[("Host", "example.com"), ("Transfer-Encoding", "chunked")],
  183. )
  184. assert conn.next_event() is NEED_DATA
  185. conn.receive_data(b"5\r\nhello\r\n")
  186. assert conn.next_event() == Data(data=b"hello", chunk_start=True, chunk_end=True)
  187. conn.receive_data(b"5\r\nhel")
  188. assert conn.next_event() == Data(data=b"hel", chunk_start=True, chunk_end=False)
  189. conn.receive_data(b"l")
  190. assert conn.next_event() == Data(data=b"l", chunk_start=False, chunk_end=False)
  191. conn.receive_data(b"o\r\n")
  192. assert conn.next_event() == Data(data=b"o", chunk_start=False, chunk_end=True)
  193. conn.receive_data(b"5\r\nhello")
  194. assert conn.next_event() == Data(data=b"hello", chunk_start=True, chunk_end=True)
  195. conn.receive_data(b"\r\n")
  196. assert conn.next_event() == NEED_DATA
  197. conn.receive_data(b"0\r\n\r\n")
  198. assert conn.next_event() == EndOfMessage()
  199. def test_client_talking_to_http10_server() -> None:
  200. c = Connection(CLIENT)
  201. c.send(Request(method="GET", target="/", headers=[("Host", "example.com")]))
  202. c.send(EndOfMessage())
  203. assert c.our_state is DONE
  204. # No content-length, so Http10 framing for body
  205. assert receive_and_get(c, b"HTTP/1.0 200 OK\r\n\r\n") == [
  206. Response(status_code=200, headers=[], http_version="1.0", reason=b"OK") # type: ignore[arg-type]
  207. ]
  208. assert c.our_state is MUST_CLOSE
  209. assert receive_and_get(c, b"12345") == [Data(data=b"12345")]
  210. assert receive_and_get(c, b"67890") == [Data(data=b"67890")]
  211. assert receive_and_get(c, b"") == [EndOfMessage(), ConnectionClosed()]
  212. assert c.their_state is CLOSED
  213. def test_server_talking_to_http10_client() -> None:
  214. c = Connection(SERVER)
  215. # No content-length, so no body
  216. # NB: no host header
  217. assert receive_and_get(c, b"GET / HTTP/1.0\r\n\r\n") == [
  218. Request(method="GET", target="/", headers=[], http_version="1.0"), # type: ignore[arg-type]
  219. EndOfMessage(),
  220. ]
  221. assert c.their_state is MUST_CLOSE
  222. # We automatically Connection: close back at them
  223. assert (
  224. c.send(Response(status_code=200, headers=[])) # type: ignore[arg-type]
  225. == b"HTTP/1.1 200 \r\nConnection: close\r\n\r\n"
  226. )
  227. assert c.send(Data(data=b"12345")) == b"12345"
  228. assert c.send(EndOfMessage()) == b""
  229. assert c.our_state is MUST_CLOSE
  230. # Check that it works if they do send Content-Length
  231. c = Connection(SERVER)
  232. # NB: no host header
  233. assert receive_and_get(c, b"POST / HTTP/1.0\r\nContent-Length: 10\r\n\r\n1") == [
  234. Request(
  235. method="POST",
  236. target="/",
  237. headers=[("Content-Length", "10")],
  238. http_version="1.0",
  239. ),
  240. Data(data=b"1"),
  241. ]
  242. assert receive_and_get(c, b"234567890") == [Data(data=b"234567890"), EndOfMessage()]
  243. assert c.their_state is MUST_CLOSE
  244. assert receive_and_get(c, b"") == [ConnectionClosed()]
  245. def test_automatic_transfer_encoding_in_response() -> None:
  246. # Check that in responses, the user can specify either Transfer-Encoding:
  247. # chunked or no framing at all, and in both cases we automatically select
  248. # the right option depending on whether the peer speaks HTTP/1.0 or
  249. # HTTP/1.1
  250. for user_headers in [
  251. [("Transfer-Encoding", "chunked")],
  252. [],
  253. # In fact, this even works if Content-Length is set,
  254. # because if both are set then Transfer-Encoding wins
  255. [("Transfer-Encoding", "chunked"), ("Content-Length", "100")],
  256. ]:
  257. user_headers = cast(List[Tuple[str, str]], user_headers)
  258. p = ConnectionPair()
  259. p.send(
  260. CLIENT,
  261. [
  262. Request(method="GET", target="/", headers=[("Host", "example.com")]),
  263. EndOfMessage(),
  264. ],
  265. )
  266. # When speaking to HTTP/1.1 client, all of the above cases get
  267. # normalized to Transfer-Encoding: chunked
  268. p.send(
  269. SERVER,
  270. Response(status_code=200, headers=user_headers),
  271. expect=Response(
  272. status_code=200, headers=[("Transfer-Encoding", "chunked")]
  273. ),
  274. )
  275. # When speaking to HTTP/1.0 client, all of the above cases get
  276. # normalized to no-framing-headers
  277. c = Connection(SERVER)
  278. receive_and_get(c, b"GET / HTTP/1.0\r\n\r\n")
  279. assert (
  280. c.send(Response(status_code=200, headers=user_headers))
  281. == b"HTTP/1.1 200 \r\nConnection: close\r\n\r\n"
  282. )
  283. assert c.send(Data(data=b"12345")) == b"12345"
  284. def test_automagic_connection_close_handling() -> None:
  285. p = ConnectionPair()
  286. # If the user explicitly sets Connection: close, then we notice and
  287. # respect it
  288. p.send(
  289. CLIENT,
  290. [
  291. Request(
  292. method="GET",
  293. target="/",
  294. headers=[("Host", "example.com"), ("Connection", "close")],
  295. ),
  296. EndOfMessage(),
  297. ],
  298. )
  299. for conn in p.conns:
  300. assert conn.states[CLIENT] is MUST_CLOSE
  301. # And if the client sets it, the server automatically echoes it back
  302. p.send(
  303. SERVER,
  304. # no header here...
  305. [Response(status_code=204, headers=[]), EndOfMessage()], # type: ignore[arg-type]
  306. # ...but oh look, it arrived anyway
  307. expect=[
  308. Response(status_code=204, headers=[("connection", "close")]),
  309. EndOfMessage(),
  310. ],
  311. )
  312. for conn in p.conns:
  313. assert conn.states == {CLIENT: MUST_CLOSE, SERVER: MUST_CLOSE}
  314. def test_100_continue() -> None:
  315. def setup() -> ConnectionPair:
  316. p = ConnectionPair()
  317. p.send(
  318. CLIENT,
  319. Request(
  320. method="GET",
  321. target="/",
  322. headers=[
  323. ("Host", "example.com"),
  324. ("Content-Length", "100"),
  325. ("Expect", "100-continue"),
  326. ],
  327. ),
  328. )
  329. for conn in p.conns:
  330. assert conn.client_is_waiting_for_100_continue
  331. assert not p.conn[CLIENT].they_are_waiting_for_100_continue
  332. assert p.conn[SERVER].they_are_waiting_for_100_continue
  333. return p
  334. # Disabled by 100 Continue
  335. p = setup()
  336. p.send(SERVER, InformationalResponse(status_code=100, headers=[])) # type: ignore[arg-type]
  337. for conn in p.conns:
  338. assert not conn.client_is_waiting_for_100_continue
  339. assert not conn.they_are_waiting_for_100_continue
  340. # Disabled by a real response
  341. p = setup()
  342. p.send(
  343. SERVER, Response(status_code=200, headers=[("Transfer-Encoding", "chunked")])
  344. )
  345. for conn in p.conns:
  346. assert not conn.client_is_waiting_for_100_continue
  347. assert not conn.they_are_waiting_for_100_continue
  348. # Disabled by the client going ahead and sending stuff anyway
  349. p = setup()
  350. p.send(CLIENT, Data(data=b"12345"))
  351. for conn in p.conns:
  352. assert not conn.client_is_waiting_for_100_continue
  353. assert not conn.they_are_waiting_for_100_continue
  354. def test_max_incomplete_event_size_countermeasure() -> None:
  355. # Infinitely long headers are definitely not okay
  356. c = Connection(SERVER)
  357. c.receive_data(b"GET / HTTP/1.0\r\nEndless: ")
  358. assert c.next_event() is NEED_DATA
  359. with pytest.raises(RemoteProtocolError):
  360. while True:
  361. c.receive_data(b"a" * 1024)
  362. c.next_event()
  363. # Checking that the same header is accepted / rejected depending on the
  364. # max_incomplete_event_size setting:
  365. c = Connection(SERVER, max_incomplete_event_size=5000)
  366. c.receive_data(b"GET / HTTP/1.0\r\nBig: ")
  367. c.receive_data(b"a" * 4000)
  368. c.receive_data(b"\r\n\r\n")
  369. assert get_all_events(c) == [
  370. Request(
  371. method="GET", target="/", http_version="1.0", headers=[("big", "a" * 4000)]
  372. ),
  373. EndOfMessage(),
  374. ]
  375. c = Connection(SERVER, max_incomplete_event_size=4000)
  376. c.receive_data(b"GET / HTTP/1.0\r\nBig: ")
  377. c.receive_data(b"a" * 4000)
  378. with pytest.raises(RemoteProtocolError):
  379. c.next_event()
  380. # Temporarily exceeding the size limit is fine, as long as its done with
  381. # complete events:
  382. c = Connection(SERVER, max_incomplete_event_size=5000)
  383. c.receive_data(b"GET / HTTP/1.0\r\nContent-Length: 10000")
  384. c.receive_data(b"\r\n\r\n" + b"a" * 10000)
  385. assert get_all_events(c) == [
  386. Request(
  387. method="GET",
  388. target="/",
  389. http_version="1.0",
  390. headers=[("Content-Length", "10000")],
  391. ),
  392. Data(data=b"a" * 10000),
  393. EndOfMessage(),
  394. ]
  395. c = Connection(SERVER, max_incomplete_event_size=100)
  396. # Two pipelined requests to create a way-too-big receive buffer... but
  397. # it's fine because we're not checking
  398. c.receive_data(
  399. b"GET /1 HTTP/1.1\r\nHost: a\r\n\r\n"
  400. b"GET /2 HTTP/1.1\r\nHost: b\r\n\r\n" + b"X" * 1000
  401. )
  402. assert get_all_events(c) == [
  403. Request(method="GET", target="/1", headers=[("host", "a")]),
  404. EndOfMessage(),
  405. ]
  406. # Even more data comes in, still no problem
  407. c.receive_data(b"X" * 1000)
  408. # We can respond and reuse to get the second pipelined request
  409. c.send(Response(status_code=200, headers=[])) # type: ignore[arg-type]
  410. c.send(EndOfMessage())
  411. c.start_next_cycle()
  412. assert get_all_events(c) == [
  413. Request(method="GET", target="/2", headers=[("host", "b")]),
  414. EndOfMessage(),
  415. ]
  416. # But once we unpause and try to read the next message, and find that it's
  417. # incomplete and the buffer is *still* way too large, then *that's* a
  418. # problem:
  419. c.send(Response(status_code=200, headers=[])) # type: ignore[arg-type]
  420. c.send(EndOfMessage())
  421. c.start_next_cycle()
  422. with pytest.raises(RemoteProtocolError):
  423. c.next_event()
  424. def test_reuse_simple() -> None:
  425. p = ConnectionPair()
  426. p.send(
  427. CLIENT,
  428. [Request(method="GET", target="/", headers=[("Host", "a")]), EndOfMessage()],
  429. )
  430. p.send(
  431. SERVER,
  432. [
  433. Response(status_code=200, headers=[(b"transfer-encoding", b"chunked")]),
  434. EndOfMessage(),
  435. ],
  436. )
  437. for conn in p.conns:
  438. assert conn.states == {CLIENT: DONE, SERVER: DONE}
  439. conn.start_next_cycle()
  440. p.send(
  441. CLIENT,
  442. [
  443. Request(method="DELETE", target="/foo", headers=[("Host", "a")]),
  444. EndOfMessage(),
  445. ],
  446. )
  447. p.send(
  448. SERVER,
  449. [
  450. Response(status_code=404, headers=[(b"transfer-encoding", b"chunked")]),
  451. EndOfMessage(),
  452. ],
  453. )
  454. def test_pipelining() -> None:
  455. # Client doesn't support pipelining, so we have to do this by hand
  456. c = Connection(SERVER)
  457. assert c.next_event() is NEED_DATA
  458. # 3 requests all bunched up
  459. c.receive_data(
  460. b"GET /1 HTTP/1.1\r\nHost: a.com\r\nContent-Length: 5\r\n\r\n"
  461. b"12345"
  462. b"GET /2 HTTP/1.1\r\nHost: a.com\r\nContent-Length: 5\r\n\r\n"
  463. b"67890"
  464. b"GET /3 HTTP/1.1\r\nHost: a.com\r\n\r\n"
  465. )
  466. assert get_all_events(c) == [
  467. Request(
  468. method="GET",
  469. target="/1",
  470. headers=[("Host", "a.com"), ("Content-Length", "5")],
  471. ),
  472. Data(data=b"12345"),
  473. EndOfMessage(),
  474. ]
  475. assert c.their_state is DONE
  476. assert c.our_state is SEND_RESPONSE
  477. assert c.next_event() is PAUSED
  478. c.send(Response(status_code=200, headers=[])) # type: ignore[arg-type]
  479. c.send(EndOfMessage())
  480. assert c.their_state is DONE
  481. assert c.our_state is DONE
  482. c.start_next_cycle()
  483. assert get_all_events(c) == [
  484. Request(
  485. method="GET",
  486. target="/2",
  487. headers=[("Host", "a.com"), ("Content-Length", "5")],
  488. ),
  489. Data(data=b"67890"),
  490. EndOfMessage(),
  491. ]
  492. assert c.next_event() is PAUSED
  493. c.send(Response(status_code=200, headers=[])) # type: ignore[arg-type]
  494. c.send(EndOfMessage())
  495. c.start_next_cycle()
  496. assert get_all_events(c) == [
  497. Request(method="GET", target="/3", headers=[("Host", "a.com")]),
  498. EndOfMessage(),
  499. ]
  500. # Doesn't pause this time, no trailing data
  501. assert c.next_event() is NEED_DATA
  502. c.send(Response(status_code=200, headers=[])) # type: ignore[arg-type]
  503. c.send(EndOfMessage())
  504. # Arrival of more data triggers pause
  505. assert c.next_event() is NEED_DATA
  506. c.receive_data(b"SADF")
  507. assert c.next_event() is PAUSED
  508. assert c.trailing_data == (b"SADF", False)
  509. # If EOF arrives while paused, we don't see that either:
  510. c.receive_data(b"")
  511. assert c.trailing_data == (b"SADF", True)
  512. assert c.next_event() is PAUSED
  513. c.receive_data(b"")
  514. assert c.next_event() is PAUSED
  515. # Can't call receive_data with non-empty buf after closing it
  516. with pytest.raises(RuntimeError):
  517. c.receive_data(b"FDSA")
  518. def test_protocol_switch() -> None:
  519. for (req, deny, accept) in [
  520. (
  521. Request(
  522. method="CONNECT",
  523. target="example.com:443",
  524. headers=[("Host", "foo"), ("Content-Length", "1")],
  525. ),
  526. Response(status_code=404, headers=[(b"transfer-encoding", b"chunked")]),
  527. Response(status_code=200, headers=[(b"transfer-encoding", b"chunked")]),
  528. ),
  529. (
  530. Request(
  531. method="GET",
  532. target="/",
  533. headers=[("Host", "foo"), ("Content-Length", "1"), ("Upgrade", "a, b")],
  534. ),
  535. Response(status_code=200, headers=[(b"transfer-encoding", b"chunked")]),
  536. InformationalResponse(status_code=101, headers=[("Upgrade", "a")]),
  537. ),
  538. (
  539. Request(
  540. method="CONNECT",
  541. target="example.com:443",
  542. headers=[("Host", "foo"), ("Content-Length", "1"), ("Upgrade", "a, b")],
  543. ),
  544. Response(status_code=404, headers=[(b"transfer-encoding", b"chunked")]),
  545. # Accept CONNECT, not upgrade
  546. Response(status_code=200, headers=[(b"transfer-encoding", b"chunked")]),
  547. ),
  548. (
  549. Request(
  550. method="CONNECT",
  551. target="example.com:443",
  552. headers=[("Host", "foo"), ("Content-Length", "1"), ("Upgrade", "a, b")],
  553. ),
  554. Response(status_code=404, headers=[(b"transfer-encoding", b"chunked")]),
  555. # Accept Upgrade, not CONNECT
  556. InformationalResponse(status_code=101, headers=[("Upgrade", "b")]),
  557. ),
  558. ]:
  559. def setup() -> ConnectionPair:
  560. p = ConnectionPair()
  561. p.send(CLIENT, req)
  562. # No switch-related state change stuff yet; the client has to
  563. # finish the request before that kicks in
  564. for conn in p.conns:
  565. assert conn.states[CLIENT] is SEND_BODY
  566. p.send(CLIENT, [Data(data=b"1"), EndOfMessage()])
  567. for conn in p.conns:
  568. assert conn.states[CLIENT] is MIGHT_SWITCH_PROTOCOL
  569. assert p.conn[SERVER].next_event() is PAUSED
  570. return p
  571. # Test deny case
  572. p = setup()
  573. p.send(SERVER, deny)
  574. for conn in p.conns:
  575. assert conn.states == {CLIENT: DONE, SERVER: SEND_BODY}
  576. p.send(SERVER, EndOfMessage())
  577. # Check that re-use is still allowed after a denial
  578. for conn in p.conns:
  579. conn.start_next_cycle()
  580. # Test accept case
  581. p = setup()
  582. p.send(SERVER, accept)
  583. for conn in p.conns:
  584. assert conn.states == {CLIENT: SWITCHED_PROTOCOL, SERVER: SWITCHED_PROTOCOL}
  585. conn.receive_data(b"123")
  586. assert conn.next_event() is PAUSED
  587. conn.receive_data(b"456")
  588. assert conn.next_event() is PAUSED
  589. assert conn.trailing_data == (b"123456", False)
  590. # Pausing in might-switch, then recovery
  591. # (weird artificial case where the trailing data actually is valid
  592. # HTTP for some reason, because this makes it easier to test the state
  593. # logic)
  594. p = setup()
  595. sc = p.conn[SERVER]
  596. sc.receive_data(b"GET / HTTP/1.0\r\n\r\n")
  597. assert sc.next_event() is PAUSED
  598. assert sc.trailing_data == (b"GET / HTTP/1.0\r\n\r\n", False)
  599. sc.send(deny)
  600. assert sc.next_event() is PAUSED
  601. sc.send(EndOfMessage())
  602. sc.start_next_cycle()
  603. assert get_all_events(sc) == [
  604. Request(method="GET", target="/", headers=[], http_version="1.0"), # type: ignore[arg-type]
  605. EndOfMessage(),
  606. ]
  607. # When we're DONE, have no trailing data, and the connection gets
  608. # closed, we report ConnectionClosed(). When we're in might-switch or
  609. # switched, we don't.
  610. p = setup()
  611. sc = p.conn[SERVER]
  612. sc.receive_data(b"")
  613. assert sc.next_event() is PAUSED
  614. assert sc.trailing_data == (b"", True)
  615. p.send(SERVER, accept)
  616. assert sc.next_event() is PAUSED
  617. p = setup()
  618. sc = p.conn[SERVER]
  619. sc.receive_data(b"")
  620. assert sc.next_event() is PAUSED
  621. sc.send(deny)
  622. assert sc.next_event() == ConnectionClosed()
  623. # You can't send after switching protocols, or while waiting for a
  624. # protocol switch
  625. p = setup()
  626. with pytest.raises(LocalProtocolError):
  627. p.conn[CLIENT].send(
  628. Request(method="GET", target="/", headers=[("Host", "a")])
  629. )
  630. p = setup()
  631. p.send(SERVER, accept)
  632. with pytest.raises(LocalProtocolError):
  633. p.conn[SERVER].send(Data(data=b"123"))
  634. def test_close_simple() -> None:
  635. # Just immediately closing a new connection without anything having
  636. # happened yet.
  637. for (who_shot_first, who_shot_second) in [(CLIENT, SERVER), (SERVER, CLIENT)]:
  638. def setup() -> ConnectionPair:
  639. p = ConnectionPair()
  640. p.send(who_shot_first, ConnectionClosed())
  641. for conn in p.conns:
  642. assert conn.states == {
  643. who_shot_first: CLOSED,
  644. who_shot_second: MUST_CLOSE,
  645. }
  646. return p
  647. # You can keep putting b"" into a closed connection, and you keep
  648. # getting ConnectionClosed() out:
  649. p = setup()
  650. assert p.conn[who_shot_second].next_event() == ConnectionClosed()
  651. assert p.conn[who_shot_second].next_event() == ConnectionClosed()
  652. p.conn[who_shot_second].receive_data(b"")
  653. assert p.conn[who_shot_second].next_event() == ConnectionClosed()
  654. # Second party can close...
  655. p = setup()
  656. p.send(who_shot_second, ConnectionClosed())
  657. for conn in p.conns:
  658. assert conn.our_state is CLOSED
  659. assert conn.their_state is CLOSED
  660. # But trying to receive new data on a closed connection is a
  661. # RuntimeError (not ProtocolError, because the problem here isn't
  662. # violation of HTTP, it's violation of physics)
  663. p = setup()
  664. with pytest.raises(RuntimeError):
  665. p.conn[who_shot_second].receive_data(b"123")
  666. # And receiving new data on a MUST_CLOSE connection is a ProtocolError
  667. p = setup()
  668. p.conn[who_shot_first].receive_data(b"GET")
  669. with pytest.raises(RemoteProtocolError):
  670. p.conn[who_shot_first].next_event()
  671. def test_close_different_states() -> None:
  672. req = [
  673. Request(method="GET", target="/foo", headers=[("Host", "a")]),
  674. EndOfMessage(),
  675. ]
  676. resp = [
  677. Response(status_code=200, headers=[(b"transfer-encoding", b"chunked")]),
  678. EndOfMessage(),
  679. ]
  680. # Client before request
  681. p = ConnectionPair()
  682. p.send(CLIENT, ConnectionClosed())
  683. for conn in p.conns:
  684. assert conn.states == {CLIENT: CLOSED, SERVER: MUST_CLOSE}
  685. # Client after request
  686. p = ConnectionPair()
  687. p.send(CLIENT, req)
  688. p.send(CLIENT, ConnectionClosed())
  689. for conn in p.conns:
  690. assert conn.states == {CLIENT: CLOSED, SERVER: SEND_RESPONSE}
  691. # Server after request -> not allowed
  692. p = ConnectionPair()
  693. p.send(CLIENT, req)
  694. with pytest.raises(LocalProtocolError):
  695. p.conn[SERVER].send(ConnectionClosed())
  696. p.conn[CLIENT].receive_data(b"")
  697. with pytest.raises(RemoteProtocolError):
  698. p.conn[CLIENT].next_event()
  699. # Server after response
  700. p = ConnectionPair()
  701. p.send(CLIENT, req)
  702. p.send(SERVER, resp)
  703. p.send(SERVER, ConnectionClosed())
  704. for conn in p.conns:
  705. assert conn.states == {CLIENT: MUST_CLOSE, SERVER: CLOSED}
  706. # Both after closing (ConnectionClosed() is idempotent)
  707. p = ConnectionPair()
  708. p.send(CLIENT, req)
  709. p.send(SERVER, resp)
  710. p.send(CLIENT, ConnectionClosed())
  711. p.send(SERVER, ConnectionClosed())
  712. p.send(CLIENT, ConnectionClosed())
  713. p.send(SERVER, ConnectionClosed())
  714. # In the middle of sending -> not allowed
  715. p = ConnectionPair()
  716. p.send(
  717. CLIENT,
  718. Request(
  719. method="GET", target="/", headers=[("Host", "a"), ("Content-Length", "10")]
  720. ),
  721. )
  722. with pytest.raises(LocalProtocolError):
  723. p.conn[CLIENT].send(ConnectionClosed())
  724. p.conn[SERVER].receive_data(b"")
  725. with pytest.raises(RemoteProtocolError):
  726. p.conn[SERVER].next_event()
  727. # Receive several requests and then client shuts down their side of the
  728. # connection; we can respond to each
  729. def test_pipelined_close() -> None:
  730. c = Connection(SERVER)
  731. # 2 requests then a close
  732. c.receive_data(
  733. b"GET /1 HTTP/1.1\r\nHost: a.com\r\nContent-Length: 5\r\n\r\n"
  734. b"12345"
  735. b"GET /2 HTTP/1.1\r\nHost: a.com\r\nContent-Length: 5\r\n\r\n"
  736. b"67890"
  737. )
  738. c.receive_data(b"")
  739. assert get_all_events(c) == [
  740. Request(
  741. method="GET",
  742. target="/1",
  743. headers=[("host", "a.com"), ("content-length", "5")],
  744. ),
  745. Data(data=b"12345"),
  746. EndOfMessage(),
  747. ]
  748. assert c.states[CLIENT] is DONE
  749. c.send(Response(status_code=200, headers=[])) # type: ignore[arg-type]
  750. c.send(EndOfMessage())
  751. assert c.states[SERVER] is DONE
  752. c.start_next_cycle()
  753. assert get_all_events(c) == [
  754. Request(
  755. method="GET",
  756. target="/2",
  757. headers=[("host", "a.com"), ("content-length", "5")],
  758. ),
  759. Data(data=b"67890"),
  760. EndOfMessage(),
  761. ConnectionClosed(),
  762. ]
  763. assert c.states == {CLIENT: CLOSED, SERVER: SEND_RESPONSE}
  764. c.send(Response(status_code=200, headers=[])) # type: ignore[arg-type]
  765. c.send(EndOfMessage())
  766. assert c.states == {CLIENT: CLOSED, SERVER: MUST_CLOSE}
  767. c.send(ConnectionClosed())
  768. assert c.states == {CLIENT: CLOSED, SERVER: CLOSED}
  769. def test_sendfile() -> None:
  770. class SendfilePlaceholder:
  771. def __len__(self) -> int:
  772. return 10
  773. placeholder = SendfilePlaceholder()
  774. def setup(
  775. header: Tuple[str, str], http_version: str
  776. ) -> Tuple[Connection, Optional[List[bytes]]]:
  777. c = Connection(SERVER)
  778. receive_and_get(
  779. c, "GET / HTTP/{}\r\nHost: a\r\n\r\n".format(http_version).encode("ascii")
  780. )
  781. headers = []
  782. if header:
  783. headers.append(header)
  784. c.send(Response(status_code=200, headers=headers))
  785. return c, c.send_with_data_passthrough(Data(data=placeholder)) # type: ignore
  786. c, data = setup(("Content-Length", "10"), "1.1")
  787. assert data == [placeholder] # type: ignore
  788. # Raises an error if the connection object doesn't think we've sent
  789. # exactly 10 bytes
  790. c.send(EndOfMessage())
  791. _, data = setup(("Transfer-Encoding", "chunked"), "1.1")
  792. assert placeholder in data # type: ignore
  793. data[data.index(placeholder)] = b"x" * 10 # type: ignore
  794. assert b"".join(data) == b"a\r\nxxxxxxxxxx\r\n" # type: ignore
  795. c, data = setup(None, "1.0") # type: ignore
  796. assert data == [placeholder] # type: ignore
  797. assert c.our_state is SEND_BODY
  798. def test_errors() -> None:
  799. # After a receive error, you can't receive
  800. for role in [CLIENT, SERVER]:
  801. c = Connection(our_role=role)
  802. c.receive_data(b"gibberish\r\n\r\n")
  803. with pytest.raises(RemoteProtocolError):
  804. c.next_event()
  805. # Now any attempt to receive continues to raise
  806. assert c.their_state is ERROR
  807. assert c.our_state is not ERROR
  808. print(c._cstate.states)
  809. with pytest.raises(RemoteProtocolError):
  810. c.next_event()
  811. # But we can still yell at the client for sending us gibberish
  812. if role is SERVER:
  813. assert (
  814. c.send(Response(status_code=400, headers=[])) # type: ignore[arg-type]
  815. == b"HTTP/1.1 400 \r\nConnection: close\r\n\r\n"
  816. )
  817. # After an error sending, you can no longer send
  818. # (This is especially important for things like content-length errors,
  819. # where there's complex internal state being modified)
  820. def conn(role: Type[Sentinel]) -> Connection:
  821. c = Connection(our_role=role)
  822. if role is SERVER:
  823. # Put it into the state where it *could* send a response...
  824. receive_and_get(c, b"GET / HTTP/1.0\r\n\r\n")
  825. assert c.our_state is SEND_RESPONSE
  826. return c
  827. for role in [CLIENT, SERVER]:
  828. if role is CLIENT:
  829. # This HTTP/1.0 request won't be detected as bad until after we go
  830. # through the state machine and hit the writing code
  831. good = Request(method="GET", target="/", headers=[("Host", "example.com")])
  832. bad = Request(
  833. method="GET",
  834. target="/",
  835. headers=[("Host", "example.com")],
  836. http_version="1.0",
  837. )
  838. elif role is SERVER:
  839. good = Response(status_code=200, headers=[]) # type: ignore[arg-type,assignment]
  840. bad = Response(status_code=200, headers=[], http_version="1.0") # type: ignore[arg-type,assignment]
  841. # Make sure 'good' actually is good
  842. c = conn(role)
  843. c.send(good)
  844. assert c.our_state is not ERROR
  845. # Do that again, but this time sending 'bad' first
  846. c = conn(role)
  847. with pytest.raises(LocalProtocolError):
  848. c.send(bad)
  849. assert c.our_state is ERROR
  850. assert c.their_state is not ERROR
  851. # Now 'good' is not so good
  852. with pytest.raises(LocalProtocolError):
  853. c.send(good)
  854. # And check send_failed() too
  855. c = conn(role)
  856. c.send_failed()
  857. assert c.our_state is ERROR
  858. assert c.their_state is not ERROR
  859. # This is idempotent
  860. c.send_failed()
  861. assert c.our_state is ERROR
  862. assert c.their_state is not ERROR
  863. def test_idle_receive_nothing() -> None:
  864. # At one point this incorrectly raised an error
  865. for role in [CLIENT, SERVER]:
  866. c = Connection(role)
  867. assert c.next_event() is NEED_DATA
  868. def test_connection_drop() -> None:
  869. c = Connection(SERVER)
  870. c.receive_data(b"GET /")
  871. assert c.next_event() is NEED_DATA
  872. c.receive_data(b"")
  873. with pytest.raises(RemoteProtocolError):
  874. c.next_event()
  875. def test_408_request_timeout() -> None:
  876. # Should be able to send this spontaneously as a server without seeing
  877. # anything from client
  878. p = ConnectionPair()
  879. p.send(SERVER, Response(status_code=408, headers=[(b"connection", b"close")]))
  880. # This used to raise IndexError
  881. def test_empty_request() -> None:
  882. c = Connection(SERVER)
  883. c.receive_data(b"\r\n")
  884. with pytest.raises(RemoteProtocolError):
  885. c.next_event()
  886. # This used to raise IndexError
  887. def test_empty_response() -> None:
  888. c = Connection(CLIENT)
  889. c.send(Request(method="GET", target="/", headers=[("Host", "a")]))
  890. c.receive_data(b"\r\n")
  891. with pytest.raises(RemoteProtocolError):
  892. c.next_event()
  893. @pytest.mark.parametrize(
  894. "data",
  895. [
  896. b"\x00",
  897. b"\x20",
  898. b"\x16\x03\x01\x00\xa5", # Typical start of a TLS Client Hello
  899. ],
  900. )
  901. def test_early_detection_of_invalid_request(data: bytes) -> None:
  902. c = Connection(SERVER)
  903. # Early detection should occur before even receiving a `\r\n`
  904. c.receive_data(data)
  905. with pytest.raises(RemoteProtocolError):
  906. c.next_event()
  907. @pytest.mark.parametrize(
  908. "data",
  909. [
  910. b"\x00",
  911. b"\x20",
  912. b"\x16\x03\x03\x00\x31", # Typical start of a TLS Server Hello
  913. ],
  914. )
  915. def test_early_detection_of_invalid_response(data: bytes) -> None:
  916. c = Connection(CLIENT)
  917. # Early detection should occur before even receiving a `\r\n`
  918. c.receive_data(data)
  919. with pytest.raises(RemoteProtocolError):
  920. c.next_event()
  921. # This used to give different headers for HEAD and GET.
  922. # The correct way to handle HEAD is to put whatever headers we *would* have
  923. # put if it were a GET -- even though we know that for HEAD, those headers
  924. # will be ignored.
  925. def test_HEAD_framing_headers() -> None:
  926. def setup(method: bytes, http_version: bytes) -> Connection:
  927. c = Connection(SERVER)
  928. c.receive_data(
  929. method + b" / HTTP/" + http_version + b"\r\n" + b"Host: example.com\r\n\r\n"
  930. )
  931. assert type(c.next_event()) is Request
  932. assert type(c.next_event()) is EndOfMessage
  933. return c
  934. for method in [b"GET", b"HEAD"]:
  935. # No Content-Length, HTTP/1.1 peer, should use chunked
  936. c = setup(method, b"1.1")
  937. assert (
  938. c.send(Response(status_code=200, headers=[])) == b"HTTP/1.1 200 \r\n" # type: ignore[arg-type]
  939. b"Transfer-Encoding: chunked\r\n\r\n"
  940. )
  941. # No Content-Length, HTTP/1.0 peer, frame with connection: close
  942. c = setup(method, b"1.0")
  943. assert (
  944. c.send(Response(status_code=200, headers=[])) == b"HTTP/1.1 200 \r\n" # type: ignore[arg-type]
  945. b"Connection: close\r\n\r\n"
  946. )
  947. # Content-Length + Transfer-Encoding, TE wins
  948. c = setup(method, b"1.1")
  949. assert (
  950. c.send(
  951. Response(
  952. status_code=200,
  953. headers=[
  954. ("Content-Length", "100"),
  955. ("Transfer-Encoding", "chunked"),
  956. ],
  957. )
  958. )
  959. == b"HTTP/1.1 200 \r\n"
  960. b"Transfer-Encoding: chunked\r\n\r\n"
  961. )
  962. def test_special_exceptions_for_lost_connection_in_message_body() -> None:
  963. c = Connection(SERVER)
  964. c.receive_data(
  965. b"POST / HTTP/1.1\r\n" b"Host: example.com\r\n" b"Content-Length: 100\r\n\r\n"
  966. )
  967. assert type(c.next_event()) is Request
  968. assert c.next_event() is NEED_DATA
  969. c.receive_data(b"12345")
  970. assert c.next_event() == Data(data=b"12345")
  971. c.receive_data(b"")
  972. with pytest.raises(RemoteProtocolError) as excinfo:
  973. c.next_event()
  974. assert "received 5 bytes" in str(excinfo.value)
  975. assert "expected 100" in str(excinfo.value)
  976. c = Connection(SERVER)
  977. c.receive_data(
  978. b"POST / HTTP/1.1\r\n"
  979. b"Host: example.com\r\n"
  980. b"Transfer-Encoding: chunked\r\n\r\n"
  981. )
  982. assert type(c.next_event()) is Request
  983. assert c.next_event() is NEED_DATA
  984. c.receive_data(b"8\r\n012345")
  985. assert c.next_event().data == b"012345" # type: ignore
  986. c.receive_data(b"")
  987. with pytest.raises(RemoteProtocolError) as excinfo:
  988. c.next_event()
  989. assert "incomplete chunked read" in str(excinfo.value)