cow_http3_machine.erl 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686
  1. %% Copyright (c) 2023, Loïc Hoguin <essen@ninenines.eu>
  2. %%
  3. %% Permission to use, copy, modify, and/or distribute this software for any
  4. %% purpose with or without fee is hereby granted, provided that the above
  5. %% copyright notice and this permission notice appear in all copies.
  6. %%
  7. %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  8. %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  9. %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  10. %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  11. %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  12. %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  13. %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  14. -module(cow_http3_machine).
  15. -export([init/2]).
  16. -export([init_unidi_local_streams/4]).
  17. -export([init_unidi_stream/3]).
  18. -export([set_unidi_remote_stream_type/3]).
  19. -export([init_bidi_stream/2]).
  20. -export([init_bidi_stream/3]).
  21. -export([close_stream/2]).
  22. -export([frame/4]).
  23. -export([ignored_frame/2]).
  24. -export([prepare_headers/5]).
  25. -export([reset_stream/2]).
  26. -record(stream, {
  27. ref :: any(), %% @todo specs
  28. dir :: unidi_local | unidi_remote | bidi,
  29. type :: undefined | req | control | push | encoder | decoder,
  30. %% Further fields are only used by bidi streams.
  31. %% @todo Perhaps have two different records?
  32. %% Request method.
  33. method = undefined :: binary(),
  34. %% Whether we finished sending data.
  35. local = idle :: idle | cow_http2:fin(),
  36. %% Whether we finished receiving data.
  37. remote = idle :: idle | cow_http2:fin(),
  38. %% Size expected and read from the request body.
  39. remote_expected_size = undefined :: undefined | non_neg_integer(),
  40. remote_read_size = 0 :: non_neg_integer(),
  41. %% Unparsed te header. Used to know if we can send trailers.
  42. %% Note that we can always send trailers to the server.
  43. te :: undefined | binary()
  44. }).
  45. -type stream() :: #stream{}.
  46. -record(http3_machine, {
  47. %% Whether the HTTP/3 endpoint is a client or a server.
  48. mode :: client | server,
  49. %% Maximum Push ID.
  50. max_push_id = -1 :: -1 | non_neg_integer(),
  51. %% Quick pointers for commonly used streams.
  52. local_encoder_ref :: any(), %% @todo specs
  53. local_decoder_ref :: any(), %% @todo specs
  54. %% Currently active HTTP/3 streams. Streams may be initiated either
  55. %% by the client or by the server through PUSH_PROMISE frames.
  56. streams = #{} :: #{reference() => stream()},
  57. %% @todo Maybe merge these two in some kind of control stream state.
  58. has_peer_control_stream = false :: boolean(),
  59. has_received_peer_settings = false :: boolean(),
  60. %% QPACK decoding and encoding state.
  61. decode_state = cow_qpack:init() :: cow_qpack:state(),
  62. encode_state = cow_qpack:init() :: cow_qpack:state()
  63. }).
  64. -spec init(_, _) -> _. %% @todo
  65. init(Mode, _Opts) ->
  66. {ok, cow_http3:settings(#{}), #http3_machine{mode=Mode}}.
  67. -spec init_unidi_local_streams(_, _ ,_ ,_) -> _. %% @todo
  68. init_unidi_local_streams(ControlRef, EncoderRef, DecoderRef,
  69. State=#http3_machine{streams=Streams}) ->
  70. State#http3_machine{
  71. local_encoder_ref=EncoderRef,
  72. local_decoder_ref=DecoderRef,
  73. streams=Streams#{
  74. ControlRef => #stream{ref=ControlRef, dir=unidi_local, type=control},
  75. EncoderRef => #stream{ref=EncoderRef, dir=unidi_local, type=encoder},
  76. DecoderRef => #stream{ref=DecoderRef, dir=unidi_local, type=decoder}
  77. }}.
  78. -spec init_unidi_stream(_, _, _) -> _. %% @todo
  79. init_unidi_stream(StreamRef, StreamDir, State=#http3_machine{streams=Streams}) ->
  80. State#http3_machine{streams=Streams#{StreamRef => #stream{
  81. ref=StreamRef, dir=StreamDir, type=undefined}}}.
  82. -spec set_unidi_remote_stream_type(_, _, _) -> _. %% @todo
  83. set_unidi_remote_stream_type(_, control, State=#http3_machine{has_peer_control_stream=true}) ->
  84. {error, {connection_error, h3_stream_creation_error,
  85. 'There can be only one control stream; yet a second one was opened. (RFC9114 6.2.1)'},
  86. State};
  87. set_unidi_remote_stream_type(StreamRef, Type,
  88. State=#http3_machine{streams=Streams, has_peer_control_stream=HasControl}) ->
  89. #{StreamRef := Stream} = Streams,
  90. {ok, State#http3_machine{
  91. streams=Streams#{StreamRef => Stream#stream{type=Type}},
  92. has_peer_control_stream=HasControl orelse (Type =:= control)
  93. }}.
  94. -spec init_bidi_stream(_, _) -> _. %% @todo
  95. %% All bidi streams are request/response.
  96. init_bidi_stream(StreamRef, State=#http3_machine{streams=Streams}) ->
  97. State#http3_machine{streams=Streams#{StreamRef => #stream{
  98. ref=StreamRef, dir=bidi, type=req}}}.
  99. -spec init_bidi_stream(_, _, _) -> _. %% @todo
  100. %% All bidi streams are request/response.
  101. init_bidi_stream(StreamRef, Method, State=#http3_machine{streams=Streams}) ->
  102. State#http3_machine{streams=Streams#{StreamRef => #stream{
  103. ref=StreamRef, dir=bidi, type=req, method=Method}}}.
  104. %% @todo set_bidi_method?
  105. -spec close_stream(_, _) -> _. %% @todo
  106. close_stream(StreamRef, State=#http3_machine{streams=Streams0}) ->
  107. case maps:take(StreamRef, Streams0) of
  108. {#stream{type=control}, Streams} ->
  109. {error, {connection_error, h3_closed_critical_stream,
  110. 'The control stream was closed. (RFC9114 6.2.1)'},
  111. State#http3_machine{streams=Streams}};
  112. {_, Streams} ->
  113. {ok, State#http3_machine{streams=Streams}}
  114. end.
  115. -spec frame(_, _, _, _) -> _. %% @todo
  116. frame(Frame, IsFin, StreamRef, State) ->
  117. case element(1, Frame) of
  118. data -> data_frame(Frame, IsFin, StreamRef, State);
  119. headers -> headers_frame(Frame, IsFin, StreamRef, State);
  120. cancel_push -> cancel_push_frame(Frame, IsFin, StreamRef, State);
  121. settings -> settings_frame(Frame, IsFin, StreamRef, State);
  122. push_promise -> push_promise_frame(Frame, IsFin, StreamRef, State);
  123. goaway -> goaway_frame(Frame, IsFin, StreamRef, State);
  124. max_push_id -> max_push_id_frame(Frame, IsFin, StreamRef, State)
  125. end.
  126. %% DATA frame.
  127. %data_frame({data, StreamID, _, _}, State=#http2_machine{mode=Mode,
  128. % local_streamid=LocalStreamID, remote_streamid=RemoteStreamID})
  129. % when (?IS_LOCAL(Mode, StreamID) andalso (StreamID >= LocalStreamID))
  130. % orelse ((not ?IS_LOCAL(Mode, StreamID)) andalso (StreamID > RemoteStreamID)) ->
  131. % {error, {connection_error, protocol_error,
  132. % 'DATA frame received on a stream in idle state. (RFC7540 5.1)'},
  133. % State};
  134. data_frame(Frame={data, Data}, IsFin, StreamRef, State) ->
  135. DataLen = byte_size(Data),
  136. case stream_get(StreamRef, State) of
  137. Stream = #stream{type=req, remote=nofin} ->
  138. data_frame(Frame, IsFin, Stream, State, DataLen);
  139. #stream{type=req, remote=idle} ->
  140. {error, {connection_error, h3_frame_unexpected,
  141. 'DATA frame received before a HEADERS frame. (RFC9114 4.1)'},
  142. State};
  143. #stream{type=req, remote=fin} ->
  144. {error, {connection_error, h3_frame_unexpected,
  145. 'DATA frame received after trailer HEADERS frame. (RFC9114 4.1)'},
  146. State};
  147. #stream{type=control} ->
  148. control_frame(Frame, State)
  149. end.
  150. data_frame(Frame, IsFin, Stream0=#stream{remote_read_size=StreamRead}, State0, DataLen) ->
  151. Stream = Stream0#stream{remote=IsFin,
  152. remote_read_size=StreamRead + DataLen},
  153. State = stream_store(Stream, State0),
  154. case is_body_size_valid(Stream) of
  155. true ->
  156. {ok, Frame, State}%;
  157. % false ->
  158. % stream_reset(StreamID, State, protocol_error,
  159. % 'The total size of DATA frames is different than the content-length. (RFC7540 8.1.2.6)')
  160. end.
  161. %% It's always valid when no content-length header was specified.
  162. is_body_size_valid(#stream{remote_expected_size=undefined}) ->
  163. true;
  164. %% We didn't finish reading the body but the size is already larger than expected.
  165. is_body_size_valid(#stream{remote=nofin, remote_expected_size=Expected,
  166. remote_read_size=Read}) when Read > Expected ->
  167. false;
  168. is_body_size_valid(#stream{remote=nofin}) ->
  169. true;
  170. is_body_size_valid(#stream{remote=fin, remote_expected_size=Expected,
  171. remote_read_size=Expected}) ->
  172. true;
  173. %% We finished reading the body and the size read is not the one expected.
  174. is_body_size_valid(_) ->
  175. false.
  176. %% HEADERS frame.
  177. headers_frame(Frame, IsFin, StreamRef, State=#http3_machine{mode=Mode}) ->
  178. case stream_get(StreamRef, State) of
  179. %% Headers.
  180. Stream=#stream{type=req, remote=idle} ->
  181. headers_decode(Frame, IsFin, Stream, State, case Mode of
  182. server -> request;
  183. client -> response
  184. end);
  185. %% Trailers.
  186. Stream=#stream{type=req, remote=nofin} ->
  187. headers_decode(Frame, IsFin, Stream, State, trailers);
  188. %% Additional frame received after trailers.
  189. #stream{type=req, remote=fin} ->
  190. {error, {connection_error, h3_frame_unexpected,
  191. 'HEADERS frame received after trailer HEADERS frame. (RFC9114 4.1)'},
  192. State};
  193. #stream{type=control} ->
  194. control_frame(Frame, State)
  195. end.
  196. %% @todo Check whether connection_error or stream_error fits better.
  197. headers_decode({headers, EncodedFieldSection}, IsFin, Stream=#stream{ref=StreamRef},
  198. State=#http3_machine{decode_state=DecodeState0}, Type) ->
  199. {ok, StreamID} = quicer:get_stream_id(StreamRef),
  200. try cow_qpack:decode_field_section(EncodedFieldSection, StreamID, DecodeState0) of
  201. {ok, Headers, DecData, DecodeState} ->
  202. headers_pseudo_headers(Stream,
  203. State#http3_machine{decode_state=DecodeState}, IsFin, Type, DecData, Headers);
  204. {error, Reason, Human} ->
  205. {error, {connection_error, Reason, Human}, State}
  206. catch _:_ ->
  207. {error, {connection_error, qpack_decompression_failed,
  208. 'Error while trying to decode QPACK-encoded header block. (RFC9204 6)'},
  209. State}
  210. end.
  211. %% @todo Much of the headers handling past this point is common between h2 and h3.
  212. headers_pseudo_headers(Stream, State,%=#http3_machine{local_settings=LocalSettings},
  213. IsFin, Type, DecData, Headers0) when Type =:= request ->%; Type =:= push_promise ->
  214. % IsExtendedConnectEnabled = maps:get(enable_connect_protocol, LocalSettings, false),
  215. case request_pseudo_headers(Headers0, #{}) of
  216. %% Extended CONNECT method (RFC9220).
  217. % {ok, PseudoHeaders=#{method := <<"CONNECT">>, scheme := _,
  218. % authority := _, path := _, protocol := _}, Headers}
  219. % when IsExtendedConnectEnabled ->
  220. % headers_regular_headers(Frame, State, Type, Stream, PseudoHeaders, Headers);
  221. % {ok, #{method := <<"CONNECT">>, scheme := _,
  222. % authority := _, path := _}, _}
  223. % when IsExtendedConnectEnabled ->
  224. % headers_malformed(Stream, State,
  225. % 'The :protocol pseudo-header MUST be sent with an extended CONNECT. (RFC8441 4)');
  226. {ok, #{protocol := _}, _} ->
  227. headers_malformed(Stream, State,
  228. 'The :protocol pseudo-header is only defined for the extended CONNECT. (RFC8441 4)');
  229. %% Normal CONNECT (no scheme/path).
  230. {ok, PseudoHeaders=#{method := <<"CONNECT">>, authority := _}, Headers}
  231. when map_size(PseudoHeaders) =:= 2 ->
  232. headers_regular_headers(Stream, State, IsFin, Type, DecData, PseudoHeaders, Headers);
  233. {ok, #{method := <<"CONNECT">>}, _} ->
  234. headers_malformed(Stream, State,
  235. 'CONNECT requests only use the :method and :authority pseudo-headers. (RFC7540 8.3)');
  236. %% Other requests.
  237. {ok, PseudoHeaders=#{method := _, scheme := _, path := _}, Headers} ->
  238. headers_regular_headers(Stream, State, IsFin, Type, DecData, PseudoHeaders, Headers);
  239. {ok, _, _} ->
  240. headers_malformed(Stream, State,
  241. 'A required pseudo-header was not found. (RFC7540 8.1.2.3)');
  242. {error, HumanReadable} ->
  243. headers_malformed(Stream, State, HumanReadable)
  244. end;
  245. headers_pseudo_headers(Stream, State, IsFin, Type=response, DecData, Headers0) ->
  246. case response_pseudo_headers(Headers0, #{}) of
  247. {ok, PseudoHeaders=#{status := _}, Headers} ->
  248. headers_regular_headers(Stream, State, IsFin, Type, DecData, PseudoHeaders, Headers);
  249. {ok, _, _} ->
  250. {error, {stream_error, protocol_error,
  251. 'A required pseudo-header was not found. (RFC7540 8.1.2.4)'},
  252. State};
  253. {error, HumanReadable} ->
  254. {error, {stream_error, protocol_error, HumanReadable}, State}
  255. end;
  256. headers_pseudo_headers(Stream, State, IsFin, Type=trailers, DecData, Headers) ->
  257. case trailers_contain_pseudo_headers(Headers) of
  258. false ->
  259. headers_regular_headers(Stream, State, IsFin, Type, DecData, #{}, Headers);
  260. true ->
  261. {error, {stream_error, h3_message_error,
  262. 'Trailer header blocks must not contain pseudo-headers. (RFC9114 4.3)'},
  263. State}
  264. end.
  265. %% @todo This function was copy pasted from cow_http2_machine. Export instead.
  266. request_pseudo_headers([{<<":method">>, _}|_], #{method := _}) ->
  267. {error, 'Multiple :method pseudo-headers were found. (RFC7540 8.1.2.3)'};
  268. request_pseudo_headers([{<<":method">>, Method}|Tail], PseudoHeaders) ->
  269. request_pseudo_headers(Tail, PseudoHeaders#{method => Method});
  270. request_pseudo_headers([{<<":scheme">>, _}|_], #{scheme := _}) ->
  271. {error, 'Multiple :scheme pseudo-headers were found. (RFC7540 8.1.2.3)'};
  272. request_pseudo_headers([{<<":scheme">>, Scheme}|Tail], PseudoHeaders) ->
  273. request_pseudo_headers(Tail, PseudoHeaders#{scheme => Scheme});
  274. request_pseudo_headers([{<<":authority">>, _}|_], #{authority := _}) ->
  275. {error, 'Multiple :authority pseudo-headers were found. (RFC7540 8.1.2.3)'};
  276. request_pseudo_headers([{<<":authority">>, Authority}|Tail], PseudoHeaders) ->
  277. request_pseudo_headers(Tail, PseudoHeaders#{authority => Authority});
  278. request_pseudo_headers([{<<":path">>, _}|_], #{path := _}) ->
  279. {error, 'Multiple :path pseudo-headers were found. (RFC7540 8.1.2.3)'};
  280. request_pseudo_headers([{<<":path">>, Path}|Tail], PseudoHeaders) ->
  281. request_pseudo_headers(Tail, PseudoHeaders#{path => Path});
  282. request_pseudo_headers([{<<":protocol">>, _}|_], #{protocol := _}) ->
  283. {error, 'Multiple :protocol pseudo-headers were found. (RFC7540 8.1.2.3)'};
  284. request_pseudo_headers([{<<":protocol">>, Protocol}|Tail], PseudoHeaders) ->
  285. request_pseudo_headers(Tail, PseudoHeaders#{protocol => Protocol});
  286. request_pseudo_headers([{<<":", _/bits>>, _}|_], _) ->
  287. {error, 'An unknown or invalid pseudo-header was found. (RFC7540 8.1.2.1)'};
  288. request_pseudo_headers(Headers, PseudoHeaders) ->
  289. {ok, PseudoHeaders, Headers}.
  290. response_pseudo_headers([{<<":status">>, _}|_], #{status := _}) ->
  291. {error, 'Multiple :status pseudo-headers were found. (RFC7540 8.1.2.3)'};
  292. response_pseudo_headers([{<<":status">>, Status}|Tail], PseudoHeaders) ->
  293. try cow_http:status_to_integer(Status) of
  294. IntStatus ->
  295. response_pseudo_headers(Tail, PseudoHeaders#{status => IntStatus})
  296. catch _:_ ->
  297. {error, 'The :status pseudo-header value is invalid. (RFC7540 8.1.2.4)'}
  298. end;
  299. response_pseudo_headers([{<<":", _/bits>>, _}|_], _) ->
  300. {error, 'An unknown or invalid pseudo-header was found. (RFC7540 8.1.2.1)'};
  301. response_pseudo_headers(Headers, PseudoHeaders) ->
  302. {ok, PseudoHeaders, Headers}.
  303. trailers_contain_pseudo_headers([]) ->
  304. false;
  305. trailers_contain_pseudo_headers([{<<":", _/bits>>, _}|_]) ->
  306. true;
  307. trailers_contain_pseudo_headers([_|Tail]) ->
  308. trailers_contain_pseudo_headers(Tail).
  309. headers_malformed(#stream{}, State, HumanReadable) ->
  310. %% @todo StreamID?
  311. {error, {stream_error, h3_message_error, HumanReadable}, State}.
  312. %% Rejecting invalid regular headers might be a bit too strong for clients.
  313. headers_regular_headers(Stream=#stream{},
  314. State, IsFin, Type, DecData, PseudoHeaders, Headers) ->
  315. case regular_headers(Headers, Type) of
  316. ok when Type =:= request ->
  317. request_expected_size(Stream, State, IsFin, Type, DecData, PseudoHeaders, Headers);
  318. % ok when Type =:= push_promise ->
  319. % push_promise_frame(Frame, State, Stream, PseudoHeaders, Headers);
  320. ok when Type =:= response ->
  321. response_expected_size(Stream, State, IsFin, Type, DecData, PseudoHeaders, Headers);
  322. ok when Type =:= trailers ->
  323. trailers_frame(Stream, State, DecData, Headers);
  324. {error, HumanReadable} when Type =:= request ->
  325. headers_malformed(Stream, State, HumanReadable)%;
  326. % {error, HumanReadable} ->
  327. % stream_reset(StreamID, State, protocol_error, HumanReadable)
  328. end.
  329. %% @todo This function was copy pasted from cow_http2_machine. Export instead.
  330. %% @todo The error reasons refer to the h2 RFC but then again h3 doesn't cover it in as much details.
  331. regular_headers([{<<>>, _}|_], _) ->
  332. {error, 'Empty header names are not valid regular headers. (CVE-2019-9516)'};
  333. regular_headers([{<<":", _/bits>>, _}|_], _) ->
  334. {error, 'Pseudo-headers were found after regular headers. (RFC7540 8.1.2.1)'};
  335. regular_headers([{<<"connection">>, _}|_], _) ->
  336. {error, 'The connection header is not allowed. (RFC7540 8.1.2.2)'};
  337. regular_headers([{<<"keep-alive">>, _}|_], _) ->
  338. {error, 'The keep-alive header is not allowed. (RFC7540 8.1.2.2)'};
  339. regular_headers([{<<"proxy-authenticate">>, _}|_], _) ->
  340. {error, 'The proxy-authenticate header is not allowed. (RFC7540 8.1.2.2)'};
  341. regular_headers([{<<"proxy-authorization">>, _}|_], _) ->
  342. {error, 'The proxy-authorization header is not allowed. (RFC7540 8.1.2.2)'};
  343. regular_headers([{<<"transfer-encoding">>, _}|_], _) ->
  344. {error, 'The transfer-encoding header is not allowed. (RFC7540 8.1.2.2)'};
  345. regular_headers([{<<"upgrade">>, _}|_], _) ->
  346. {error, 'The upgrade header is not allowed. (RFC7540 8.1.2.2)'};
  347. regular_headers([{<<"te">>, Value}|_], request) when Value =/= <<"trailers">> ->
  348. {error, 'The te header with a value other than "trailers" is not allowed. (RFC7540 8.1.2.2)'};
  349. regular_headers([{<<"te">>, _}|_], Type) when Type =/= request ->
  350. {error, 'The te header is only allowed in request headers. (RFC7540 8.1.2.2)'};
  351. regular_headers([{Name, _}|Tail], Type) ->
  352. Pattern = [
  353. <<$A>>, <<$B>>, <<$C>>, <<$D>>, <<$E>>, <<$F>>, <<$G>>, <<$H>>, <<$I>>,
  354. <<$J>>, <<$K>>, <<$L>>, <<$M>>, <<$N>>, <<$O>>, <<$P>>, <<$Q>>, <<$R>>,
  355. <<$S>>, <<$T>>, <<$U>>, <<$V>>, <<$W>>, <<$X>>, <<$Y>>, <<$Z>>
  356. ],
  357. case binary:match(Name, Pattern) of
  358. nomatch -> regular_headers(Tail, Type);
  359. _ -> {error, 'Header names must be lowercase. (RFC7540 8.1.2)'}
  360. end;
  361. regular_headers([], _) ->
  362. ok.
  363. %% @todo Much of the logic can probably be put in its own function shared between h2 and h3.
  364. request_expected_size(Stream, State, IsFin, Type, DecData, PseudoHeaders, Headers) ->
  365. case [CL || {<<"content-length">>, CL} <- Headers] of
  366. [] when IsFin =:= fin ->
  367. headers_frame(Stream, State, IsFin, Type, DecData, PseudoHeaders, Headers, 0);
  368. [] ->
  369. headers_frame(Stream, State, IsFin, Type, DecData, PseudoHeaders, Headers, undefined);
  370. [<<"0">>] ->
  371. headers_frame(Stream, State, IsFin, Type, DecData, PseudoHeaders, Headers, 0);
  372. [_] when IsFin =:= fin ->
  373. headers_malformed(Stream, State,
  374. 'HEADERS frame with the END_STREAM flag contains a non-zero content-length. (RFC7540 8.1.2.6)');
  375. [BinLen] ->
  376. headers_parse_expected_size(Stream, State, IsFin, Type, DecData,
  377. PseudoHeaders, Headers, BinLen);
  378. _ ->
  379. headers_malformed(Stream, State,
  380. 'Multiple content-length headers were received. (RFC7230 3.3.2)')
  381. end.
  382. response_expected_size(Stream=#stream{method=Method}, State, IsFin, Type, DecData,
  383. PseudoHeaders=#{status := Status}, Headers) ->
  384. case [CL || {<<"content-length">>, CL} <- Headers] of
  385. [] when IsFin =:= fin ->
  386. headers_frame(Stream, State, IsFin, Type, DecData, PseudoHeaders, Headers, 0);
  387. [] ->
  388. headers_frame(Stream, State, IsFin, Type, DecData, PseudoHeaders, Headers, undefined);
  389. [_] when Status >= 100, Status =< 199 ->
  390. {error, {stream_error, protocol_error,
  391. 'Content-length header received in a 1xx response. (RFC7230 3.3.2)'},
  392. State};
  393. [_] when Status =:= 204 ->
  394. {error, {stream_error, protocol_error,
  395. 'Content-length header received in a 204 response. (RFC7230 3.3.2)'},
  396. State};
  397. [_] when Status >= 200, Status =< 299, Method =:= <<"CONNECT">> ->
  398. {error, {stream_error, protocol_error,
  399. 'Content-length header received in a 2xx response to a CONNECT request. (RFC7230 3.3.2).'},
  400. State};
  401. %% Responses to HEAD requests, and 304 responses may contain
  402. %% a content-length header that must be ignored. (RFC7230 3.3.2)
  403. [_] when Method =:= <<"HEAD">> ->
  404. headers_frame(Stream, State, IsFin, Type, DecData, PseudoHeaders, Headers, 0);
  405. [_] when Status =:= 304 ->
  406. headers_frame(Stream, State, IsFin, Type, DecData, PseudoHeaders, Headers, 0);
  407. [<<"0">>] when IsFin =:= fin ->
  408. headers_frame(Stream, State, IsFin, Type, DecData, PseudoHeaders, Headers, 0);
  409. [_] when IsFin =:= fin ->
  410. {error, {stream_error, protocol_error,
  411. 'HEADERS frame with the END_STREAM flag contains a non-zero content-length. (RFC7540 8.1.2.6)'},
  412. State};
  413. [BinLen] ->
  414. headers_parse_expected_size(Stream, State, IsFin, Type, DecData,
  415. PseudoHeaders, Headers, BinLen);
  416. _ ->
  417. {error, {stream_error, protocol_error,
  418. 'Multiple content-length headers were received. (RFC7230 3.3.2)'},
  419. State}
  420. end.
  421. headers_parse_expected_size(Stream, State, IsFin, Type, DecData,
  422. PseudoHeaders, Headers, BinLen) ->
  423. try cow_http_hd:parse_content_length(BinLen) of
  424. Len ->
  425. headers_frame(Stream, State, IsFin, Type, DecData, PseudoHeaders, Headers, Len)
  426. catch
  427. _:_ ->
  428. HumanReadable = 'The content-length header is invalid. (RFC7230 3.3.2)',
  429. case Type of
  430. request -> headers_malformed(Stream, State, HumanReadable)%;
  431. % response -> stream_reset(StreamID, State, protocol_error, HumanReadable)
  432. end
  433. end.
  434. headers_frame(Stream0, State0=#http3_machine{local_decoder_ref=DecoderRef},
  435. IsFin, Type, DecData, PseudoHeaders, Headers, Len) ->
  436. Stream = case Type of
  437. request ->
  438. TE = case lists:keyfind(<<"te">>, 1, Headers) of
  439. {_, TE0} -> TE0;
  440. false -> undefined
  441. end,
  442. Stream0#stream{method=maps:get(method, PseudoHeaders),
  443. remote=IsFin, remote_expected_size=Len, te=TE};
  444. response ->
  445. case PseudoHeaders of
  446. #{status := Status} when Status >= 100, Status =< 199 -> Stream0;
  447. _ -> Stream0#stream{remote=IsFin, remote_expected_size=Len}
  448. end
  449. end,
  450. State = stream_store(Stream, State0),
  451. case DecData of
  452. <<>> ->
  453. {ok, {headers, IsFin, Headers, PseudoHeaders, Len}, State};
  454. _ ->
  455. {ok, {headers, IsFin, Headers, PseudoHeaders, Len}, {DecoderRef, DecData}, State}
  456. end.
  457. trailers_frame(Stream0, State0=#http3_machine{local_decoder_ref=DecoderRef}, DecData, Headers) ->
  458. Stream = Stream0#stream{remote=fin},
  459. State = stream_store(Stream, State0),
  460. %% @todo Error out if we didn't get the full body.
  461. % case is_body_size_valid(Stream) of
  462. % true ->
  463. case DecData of
  464. <<>> ->
  465. {ok, {trailers, Headers}, State};
  466. _ ->
  467. {ok, {trailers, Headers}, {DecoderRef, DecData}, State}
  468. end.%;
  469. % false ->
  470. % stream_reset(StreamID, State, protocol_error,
  471. % 'The total size of DATA frames is different than the content-length. (RFC7540 8.1.2.6)')
  472. % end.
  473. cancel_push_frame(Frame, _IsFin, StreamRef, State) ->
  474. case stream_get(StreamRef, State) of
  475. #stream{type=control} ->
  476. control_frame(Frame, State)
  477. end.
  478. settings_frame(Frame, _IsFin, StreamRef, State) ->
  479. case stream_get(StreamRef, State) of
  480. #stream{type=control} ->
  481. control_frame(Frame, State);
  482. #stream{type=req} ->
  483. {error, {connection_error, h3_frame_unexpected,
  484. 'The SETTINGS frame is not allowed on a bidi stream. (RFC9114 7.2.4)'},
  485. State}
  486. end.
  487. push_promise_frame(Frame, _IsFin, StreamRef, State) ->
  488. case stream_get(StreamRef, State) of
  489. #stream{type=control} ->
  490. control_frame(Frame, State)
  491. end.
  492. goaway_frame(Frame, _IsFin, StreamRef, State) ->
  493. case stream_get(StreamRef, State) of
  494. #stream{type=control} ->
  495. control_frame(Frame, State);
  496. #stream{type=req} ->
  497. {error, {connection_error, h3_frame_unexpected,
  498. 'The GOAWAY frame is not allowed on a bidi stream. (RFC9114 7.2.6)'},
  499. State}
  500. end.
  501. max_push_id_frame(Frame, _IsFin, StreamRef, State) ->
  502. case stream_get(StreamRef, State) of
  503. #stream{type=control} ->
  504. control_frame(Frame, State);
  505. #stream{type=req} ->
  506. {error, {connection_error, h3_frame_unexpected,
  507. 'The MAX_PUSH_ID frame is not allowed on a bidi stream. (RFC9114 7.2.7)'},
  508. State}
  509. end.
  510. control_frame({settings, _Settings}, State=#http3_machine{has_received_peer_settings=false}) ->
  511. {ok, State#http3_machine{has_received_peer_settings=true}};
  512. control_frame({settings, _}, State) ->
  513. {error, {connection_error, h3_frame_unexpected,
  514. 'The SETTINGS frame cannot be sent more than once. (RFC9114 7.2.4)'},
  515. State};
  516. control_frame(_Frame, State=#http3_machine{has_received_peer_settings=false}) ->
  517. {error, {connection_error, h3_missing_settings,
  518. 'The first frame on the control stream must be a SETTINGS frame. (RFC9114 6.2.1)'},
  519. State};
  520. control_frame(Frame = {goaway, _}, State) ->
  521. {ok, Frame, State};
  522. %% @todo Implement server push.
  523. control_frame({max_push_id, PushID}, State=#http3_machine{max_push_id=MaxPushID}) ->
  524. if
  525. PushID >= MaxPushID ->
  526. {ok, State#http3_machine{max_push_id=PushID}};
  527. true ->
  528. {error, {connection_error, h3_id_error,
  529. 'MAX_PUSH_ID must not be lower than previously received. (RFC9114 7.2.7)'},
  530. State}
  531. end;
  532. control_frame(ignored_frame, State) ->
  533. {ok, State};
  534. control_frame(_Frame, State) ->
  535. {error, {connection_error, h3_frame_unexpected,
  536. 'DATA and HEADERS frames are not allowed on the control stream. (RFC9114 7.2.1, RFC9114 7.2.2)'},
  537. State}.
  538. %% Ignored frames.
  539. -spec ignored_frame(_, _) -> _. %% @todo
  540. ignored_frame(StreamRef, State) ->
  541. case stream_get(StreamRef, State) of
  542. #stream{type=control} ->
  543. control_frame(ignored_frame, State);
  544. _ ->
  545. {ok, State}
  546. end.
  547. %% Functions for sending a message header or body. Note that
  548. %% this module does not send data directly, instead it returns
  549. %% a value that can then be used to send the frames.
  550. %-spec prepare_headers(cow_http2:streamid(), State, idle | cow_http2:fin(),
  551. % pseudo_headers(), cow_http:headers())
  552. % -> {ok, cow_http2:fin(), iodata(), State} when State::http2_machine().
  553. -spec prepare_headers(_, _, _, _, _) -> todo.
  554. prepare_headers(StreamRef, State=#http3_machine{encode_state=EncodeState0},
  555. IsFin0, PseudoHeaders, Headers0) ->
  556. {ok, StreamID} = quicer:get_stream_id(StreamRef),
  557. Stream = #stream{method=Method, local=idle} = stream_get(StreamRef, State),
  558. IsFin = case {IsFin0, Method} of
  559. {idle, _} -> nofin;
  560. {_, <<"HEAD">>} -> fin;
  561. _ -> IsFin0
  562. end,
  563. Headers = merge_pseudo_headers(PseudoHeaders, remove_http11_headers(Headers0)),
  564. {ok, HeaderBlock, EncData, EncodeState} = cow_qpack:encode_field_section(Headers, StreamID, EncodeState0),
  565. %% @todo Return the EncoderRef with the EncData.
  566. {ok, IsFin, HeaderBlock, EncData, stream_store(Stream#stream{local=IsFin0},
  567. State#http3_machine{encode_state=EncodeState})}.
  568. %% @todo Function copied from cow_http2_machine.
  569. remove_http11_headers(Headers) ->
  570. RemoveHeaders0 = [
  571. <<"keep-alive">>,
  572. <<"proxy-connection">>,
  573. <<"transfer-encoding">>,
  574. <<"upgrade">>
  575. ],
  576. RemoveHeaders = case lists:keyfind(<<"connection">>, 1, Headers) of
  577. false ->
  578. RemoveHeaders0;
  579. {_, ConnHd} ->
  580. %% We do not need to worry about any "close" header because
  581. %% that header name is reserved.
  582. Connection = cow_http_hd:parse_connection(ConnHd),
  583. Connection ++ [<<"connection">>|RemoveHeaders0]
  584. end,
  585. lists:filter(fun({Name, _}) ->
  586. not lists:member(Name, RemoveHeaders)
  587. end, Headers).
  588. %% @todo Function copied from cow_http2_machine.
  589. merge_pseudo_headers(PseudoHeaders, Headers0) ->
  590. lists:foldl(fun
  591. ({status, Status}, Acc) when is_integer(Status) ->
  592. [{<<":status">>, integer_to_binary(Status)}|Acc];
  593. ({Name, Value}, Acc) ->
  594. [{iolist_to_binary([$:, atom_to_binary(Name, latin1)]), Value}|Acc]
  595. end, Headers0, maps:to_list(PseudoHeaders)).
  596. %% Public interface to reset streams.
  597. -spec reset_stream(_, _) -> todo.
  598. reset_stream(StreamRef, State=#http3_machine{streams=Streams0}) ->
  599. case maps:take(StreamRef, Streams0) of
  600. {_, Streams} ->
  601. {ok, State#http3_machine{streams=Streams}};
  602. error ->
  603. {error, not_found}
  604. end.
  605. %% Stream-related functions.
  606. stream_get(StreamRef, #http3_machine{streams=Streams}) ->
  607. maps:get(StreamRef, Streams, undefined).
  608. stream_store(#stream{ref=StreamRef, local=fin, remote=fin},
  609. State=#http3_machine{streams=Streams0}) ->
  610. Streams = maps:remove(StreamRef, Streams0),
  611. State#http3_machine{streams=Streams};
  612. stream_store(Stream=#stream{ref=StreamRef},
  613. State=#http3_machine{streams=Streams}) ->
  614. State#http3_machine{streams=Streams#{StreamRef => Stream}}.