aboutsummaryrefslogtreecommitdiffstats
path: root/src/cow_http2_machine.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/cow_http2_machine.erl')
-rw-r--r--src/cow_http2_machine.erl396
1 files changed, 108 insertions, 288 deletions
diff --git a/src/cow_http2_machine.erl b/src/cow_http2_machine.erl
index 69df267..808c6cf 100644
--- a/src/cow_http2_machine.erl
+++ b/src/cow_http2_machine.erl
@@ -76,19 +76,19 @@
method = undefined :: binary(),
%% Whether we finished sending data.
- local = idle :: idle | cow_http2:fin(),
+ local = idle :: idle | cow_http:fin(),
%% Local flow control window (how much we can send).
local_window :: integer(),
%% Buffered data waiting for the flow control window to increase.
local_buffer = queue:new() ::
- queue:queue({cow_http2:fin(), non_neg_integer(), {data, iodata()} | #sendfile{}}),
+ queue:queue({cow_http:fin(), non_neg_integer(), {data, iodata()} | #sendfile{}}),
local_buffer_size = 0 :: non_neg_integer(),
local_trailers = undefined :: undefined | cow_http:headers(),
%% Whether we finished receiving data.
- remote = idle :: idle | cow_http2:fin(),
+ remote = idle :: idle | cow_http:fin(),
%% Remote flow control window (how much we accept to receive).
remote_window :: integer(),
@@ -105,7 +105,7 @@
-type stream() :: #stream{}.
-type continued_frame() ::
- {headers, cow_http2:streamid(), cow_http2:fin(), cow_http2:head_fin(), binary()} |
+ {headers, cow_http2:streamid(), cow_http:fin(), cow_http2:head_fin(), binary()} |
{push_promise, cow_http2:streamid(), cow_http2:head_fin(), cow_http2:streamid(), binary()}.
-record(http2_machine, {
@@ -134,8 +134,9 @@
initial_window_size => 65535
% max_frame_size => 16384
% max_header_list_size => infinity
+% enable_connect_protocol => false
} :: map(),
- next_settings = undefined :: undefined | map(),
+ next_settings = #{} :: map(),
remote_settings = #{
initial_window_size => 65535
} :: map(),
@@ -171,20 +172,6 @@
-opaque http2_machine() :: #http2_machine{}.
-export_type([http2_machine/0]).
--type pseudo_headers() :: #{} %% Trailers
- | #{ %% Responses.
- status := cow_http:status()
- } | #{ %% Normal CONNECT requests.
- method := binary(),
- authority := binary()
- } | #{ %% Other requests and extended CONNECT requests.
- method := binary(),
- scheme := binary(),
- authority := binary(),
- path := binary(),
- protocol => binary()
- }.
-
%% Returns true when the given StreamID is for a local-initiated stream.
-define(IS_SERVER_LOCAL(StreamID), ((StreamID rem 2) =:= 0)).
-define(IS_CLIENT_LOCAL(StreamID), ((StreamID rem 2) =:= 1)).
@@ -292,15 +279,16 @@ init_upgrade_stream(Method, State=#http2_machine{mode=server, remote_streamid=0,
-spec frame(cow_http2:frame(), State)
-> {ok, State}
- | {ok, {data, cow_http2:streamid(), cow_http2:fin(), binary()}, State}
- | {ok, {headers, cow_http2:streamid(), cow_http2:fin(),
- cow_http:headers(), pseudo_headers(), non_neg_integer() | undefined}, State}
+ | {ok, {data, cow_http2:streamid(), cow_http:fin(), binary()}, State}
+ | {ok, {headers, cow_http2:streamid(), cow_http:fin(),
+ cow_http:headers(), cow_http:pseudo_headers(),
+ non_neg_integer() | undefined}, State}
| {ok, {trailers, cow_http2:streamid(), cow_http:headers()}, State}
| {ok, {rst_stream, cow_http2:streamid(), cow_http2:error()}, State}
| {ok, {push_promise, cow_http2:streamid(), cow_http2:streamid(),
- cow_http:headers(), pseudo_headers()}, State}
+ cow_http:headers(), cow_http:pseudo_headers()}, State}
| {ok, {goaway, cow_http2:streamid(), cow_http2:error(), binary()}, State}
- | {send, [{cow_http2:streamid(), cow_http2:fin(),
+ | {send, [{cow_http2:streamid(), cow_http:fin(),
[{data, iodata()} | #sendfile{} | {trailers, cow_http:headers()}]}], State}
| {error, {stream_error, cow_http2:streamid(), cow_http2:error(), atom()}, State}
| {error, {connection_error, cow_http2:error(), atom()}, State}
@@ -434,7 +422,7 @@ is_body_size_valid(_) ->
%% The order of the fields matter.
-record(headers, {
id :: cow_http2:streamid(),
- fin :: cow_http2:fin(),
+ fin :: cow_http:fin(),
head :: cow_http2:head_fin(),
data :: binary()
}).
@@ -444,8 +432,8 @@ headers_frame(Frame=#headers{}, State=#http2_machine{mode=Mode}) ->
server -> server_headers_frame(Frame, State);
client -> client_headers_frame(Frame, State)
end;
-%% @todo Handle the PRIORITY data, but only if this returns an ok tuple.
-%% @todo Do not lose the PRIORITY information if CONTINUATION frames follow.
+%% The PRIORITY mechanism is seen as flawed and deprecated.
+%% We will not implement it.
headers_frame({headers, StreamID, IsFin, IsHeadFin,
_IsExclusive, _DepStreamID, _Weight, HeaderData},
State=#http2_machine{mode=Mode}) ->
@@ -536,7 +524,7 @@ headers_decode(Frame=#headers{head=head_fin, data=HeaderData},
headers_enforce_concurrency_limit(Frame,
State#http2_machine{decode_state=DecodeState}, Type, Stream, Headers);
{Headers, DecodeState} ->
- headers_pseudo_headers(Frame,
+ headers_process(Frame,
State#http2_machine{decode_state=DecodeState}, Type, Stream, Headers)
catch _:_ ->
{error, {connection_error, compression_error,
@@ -552,239 +540,95 @@ headers_enforce_concurrency_limit(Frame=#headers{id=StreamID},
%% in the Streams variable yet and so we'll end up with +1 stream.
case map_size(Streams) < MaxConcurrentStreams of
true ->
- headers_pseudo_headers(Frame, State, Type, Stream, Headers);
+ headers_process(Frame, State, Type, Stream, Headers);
false ->
{error, {stream_error, StreamID, refused_stream,
'Maximum number of concurrent streams has been reached. (RFC7540 5.1.2)'},
State}
end.
-headers_pseudo_headers(Frame, State=#http2_machine{local_settings=LocalSettings},
- Type, Stream, Headers0) when Type =:= request; Type =:= push_promise ->
- IsExtendedConnectEnabled = maps:get(enable_connect_protocol, LocalSettings, false),
- case request_pseudo_headers(Headers0, #{}) of
- %% Extended CONNECT method (RFC8441).
- {ok, PseudoHeaders=#{method := <<"CONNECT">>, scheme := _,
- authority := _, path := _, protocol := _}, Headers}
- when IsExtendedConnectEnabled ->
- headers_regular_headers(Frame, State, Type, Stream, PseudoHeaders, Headers);
- {ok, #{method := <<"CONNECT">>, scheme := _,
- authority := _, path := _}, _}
- when IsExtendedConnectEnabled ->
- headers_malformed(Frame, State,
- 'The :protocol pseudo-header MUST be sent with an extended CONNECT. (RFC8441 4)');
- {ok, #{protocol := _}, _} ->
- headers_malformed(Frame, State,
- 'The :protocol pseudo-header is only defined for the extended CONNECT. (RFC8441 4)');
- %% Normal CONNECT (no scheme/path).
- {ok, PseudoHeaders=#{method := <<"CONNECT">>, authority := _}, Headers}
- when map_size(PseudoHeaders) =:= 2 ->
- headers_regular_headers(Frame, State, Type, Stream, PseudoHeaders, Headers);
- {ok, #{method := <<"CONNECT">>}, _} ->
- headers_malformed(Frame, State,
- 'CONNECT requests only use the :method and :authority pseudo-headers. (RFC7540 8.3)');
- %% Other requests.
- {ok, PseudoHeaders=#{method := _, scheme := _, path := _}, Headers} ->
- headers_regular_headers(Frame, State, Type, Stream, PseudoHeaders, Headers);
- {ok, _, _} ->
- headers_malformed(Frame, State,
- 'A required pseudo-header was not found. (RFC7540 8.1.2.3)');
- {error, HumanReadable} ->
- headers_malformed(Frame, State, HumanReadable)
- end;
-headers_pseudo_headers(Frame=#headers{id=StreamID},
- State, Type=response, Stream, Headers0) ->
- case response_pseudo_headers(Headers0, #{}) of
- {ok, PseudoHeaders=#{status := _}, Headers} ->
- headers_regular_headers(Frame, State, Type, Stream, PseudoHeaders, Headers);
- {ok, _, _} ->
- stream_reset(StreamID, State, protocol_error,
- 'A required pseudo-header was not found. (RFC7540 8.1.2.4)');
- {error, HumanReadable} ->
- stream_reset(StreamID, State, protocol_error, HumanReadable)
- end;
-headers_pseudo_headers(Frame=#headers{id=StreamID},
- State, Type=trailers, Stream, Headers) ->
- case trailers_contain_pseudo_headers(Headers) of
- false ->
- headers_regular_headers(Frame, State, Type, Stream, #{}, Headers);
- true ->
- stream_reset(StreamID, State, protocol_error,
- 'Trailer header blocks must not contain pseudo-headers. (RFC7540 8.1.2.1)')
+headers_process(Frame=#headers{id=StreamID, fin=IsFin},
+ State=#http2_machine{local_settings=LocalSettings},
+ Type, Stream, Headers0) ->
+ ReqMethod = case Stream of
+ #stream{method=ReqMethod0} -> ReqMethod0;
+ undefined -> undefined
+ end,
+ case cow_http:process_headers(Headers0, Type, ReqMethod, IsFin, LocalSettings) of
+ {headers, Headers, PseudoHeaders, Len} ->
+ headers_frame(Frame, State, Type, Stream, Headers, PseudoHeaders, Len);
+ {push_promise, Headers, PseudoHeaders} ->
+ push_promise_frame(Frame, State, Stream, Headers, PseudoHeaders);
+ {trailers, Headers} ->
+ trailers_frame(Frame, State, Stream, Headers);
+ {error, Reason} when Type =:= request ->
+ headers_malformed(Frame, State, format_error(Reason));
+ {error, Reason} ->
+ stream_reset(StreamID, State, protocol_error, format_error(Reason))
end.
headers_malformed(#headers{id=StreamID}, State, HumanReadable) ->
{error, {stream_error, StreamID, protocol_error, HumanReadable}, State}.
-request_pseudo_headers([{<<":method">>, _}|_], #{method := _}) ->
- {error, 'Multiple :method pseudo-headers were found. (RFC7540 8.1.2.3)'};
-request_pseudo_headers([{<<":method">>, Method}|Tail], PseudoHeaders) ->
- request_pseudo_headers(Tail, PseudoHeaders#{method => Method});
-request_pseudo_headers([{<<":scheme">>, _}|_], #{scheme := _}) ->
- {error, 'Multiple :scheme pseudo-headers were found. (RFC7540 8.1.2.3)'};
-request_pseudo_headers([{<<":scheme">>, Scheme}|Tail], PseudoHeaders) ->
- request_pseudo_headers(Tail, PseudoHeaders#{scheme => Scheme});
-request_pseudo_headers([{<<":authority">>, _}|_], #{authority := _}) ->
- {error, 'Multiple :authority pseudo-headers were found. (RFC7540 8.1.2.3)'};
-request_pseudo_headers([{<<":authority">>, Authority}|Tail], PseudoHeaders) ->
- request_pseudo_headers(Tail, PseudoHeaders#{authority => Authority});
-request_pseudo_headers([{<<":path">>, _}|_], #{path := _}) ->
- {error, 'Multiple :path pseudo-headers were found. (RFC7540 8.1.2.3)'};
-request_pseudo_headers([{<<":path">>, Path}|Tail], PseudoHeaders) ->
- request_pseudo_headers(Tail, PseudoHeaders#{path => Path});
-request_pseudo_headers([{<<":protocol">>, _}|_], #{protocol := _}) ->
- {error, 'Multiple :protocol pseudo-headers were found. (RFC7540 8.1.2.3)'};
-request_pseudo_headers([{<<":protocol">>, Protocol}|Tail], PseudoHeaders) ->
- request_pseudo_headers(Tail, PseudoHeaders#{protocol => Protocol});
-request_pseudo_headers([{<<":", _/bits>>, _}|_], _) ->
- {error, 'An unknown or invalid pseudo-header was found. (RFC7540 8.1.2.1)'};
-request_pseudo_headers(Headers, PseudoHeaders) ->
- {ok, PseudoHeaders, Headers}.
-
-response_pseudo_headers([{<<":status">>, _}|_], #{status := _}) ->
- {error, 'Multiple :status pseudo-headers were found. (RFC7540 8.1.2.3)'};
-response_pseudo_headers([{<<":status">>, Status}|Tail], PseudoHeaders) ->
- try cow_http:status_to_integer(Status) of
- IntStatus ->
- response_pseudo_headers(Tail, PseudoHeaders#{status => IntStatus})
- catch _:_ ->
- {error, 'The :status pseudo-header value is invalid. (RFC7540 8.1.2.4)'}
- end;
-response_pseudo_headers([{<<":", _/bits>>, _}|_], _) ->
- {error, 'An unknown or invalid pseudo-header was found. (RFC7540 8.1.2.1)'};
-response_pseudo_headers(Headers, PseudoHeaders) ->
- {ok, PseudoHeaders, Headers}.
-
-trailers_contain_pseudo_headers([]) ->
- false;
-trailers_contain_pseudo_headers([{<<":", _/bits>>, _}|_]) ->
- true;
-trailers_contain_pseudo_headers([_|Tail]) ->
- trailers_contain_pseudo_headers(Tail).
-
-%% Rejecting invalid regular headers might be a bit too strong for clients.
-headers_regular_headers(Frame=#headers{id=StreamID},
- State, Type, Stream, PseudoHeaders, Headers) ->
- case regular_headers(Headers, Type) of
- ok when Type =:= request ->
- request_expected_size(Frame, State, Type, Stream, PseudoHeaders, Headers);
- ok when Type =:= push_promise ->
- push_promise_frame(Frame, State, Stream, PseudoHeaders, Headers);
- ok when Type =:= response ->
- response_expected_size(Frame, State, Type, Stream, PseudoHeaders, Headers);
- ok when Type =:= trailers ->
- trailers_frame(Frame, State, Stream, Headers);
- {error, HumanReadable} when Type =:= request ->
- headers_malformed(Frame, State, HumanReadable);
- {error, HumanReadable} ->
- stream_reset(StreamID, State, protocol_error, HumanReadable)
- end.
-
-regular_headers([{<<>>, _}|_], _) ->
- {error, 'Empty header names are not valid regular headers. (CVE-2019-9516)'};
-regular_headers([{<<":", _/bits>>, _}|_], _) ->
- {error, 'Pseudo-headers were found after regular headers. (RFC7540 8.1.2.1)'};
-regular_headers([{<<"connection">>, _}|_], _) ->
- {error, 'The connection header is not allowed. (RFC7540 8.1.2.2)'};
-regular_headers([{<<"keep-alive">>, _}|_], _) ->
- {error, 'The keep-alive header is not allowed. (RFC7540 8.1.2.2)'};
-regular_headers([{<<"proxy-authenticate">>, _}|_], _) ->
- {error, 'The proxy-authenticate header is not allowed. (RFC7540 8.1.2.2)'};
-regular_headers([{<<"proxy-authorization">>, _}|_], _) ->
- {error, 'The proxy-authorization header is not allowed. (RFC7540 8.1.2.2)'};
-regular_headers([{<<"transfer-encoding">>, _}|_], _) ->
- {error, 'The transfer-encoding header is not allowed. (RFC7540 8.1.2.2)'};
-regular_headers([{<<"upgrade">>, _}|_], _) ->
- {error, 'The upgrade header is not allowed. (RFC7540 8.1.2.2)'};
-regular_headers([{<<"te">>, Value}|_], request) when Value =/= <<"trailers">> ->
- {error, 'The te header with a value other than "trailers" is not allowed. (RFC7540 8.1.2.2)'};
-regular_headers([{<<"te">>, _}|_], Type) when Type =/= request ->
- {error, 'The te header is only allowed in request headers. (RFC7540 8.1.2.2)'};
-regular_headers([{Name, _}|Tail], Type) ->
- Pattern = [
- <<$A>>, <<$B>>, <<$C>>, <<$D>>, <<$E>>, <<$F>>, <<$G>>, <<$H>>, <<$I>>,
- <<$J>>, <<$K>>, <<$L>>, <<$M>>, <<$N>>, <<$O>>, <<$P>>, <<$Q>>, <<$R>>,
- <<$S>>, <<$T>>, <<$U>>, <<$V>>, <<$W>>, <<$X>>, <<$Y>>, <<$Z>>
- ],
- case binary:match(Name, Pattern) of
- nomatch -> regular_headers(Tail, Type);
- _ -> {error, 'Header names must be lowercase. (RFC7540 8.1.2)'}
- end;
-regular_headers([], _) ->
- ok.
-
-request_expected_size(Frame=#headers{fin=IsFin}, State, Type, Stream, PseudoHeaders, Headers) ->
- case [CL || {<<"content-length">>, CL} <- Headers] of
- [] when IsFin =:= fin ->
- headers_frame(Frame, State, Type, Stream, PseudoHeaders, Headers, 0);
- [] ->
- headers_frame(Frame, State, Type, Stream, PseudoHeaders, Headers, undefined);
- [<<"0">>] when IsFin =:= fin ->
- headers_frame(Frame, State, Type, Stream, PseudoHeaders, Headers, 0);
- [_] when IsFin =:= fin ->
- headers_malformed(Frame, State,
- 'HEADERS frame with the END_STREAM flag contains a non-zero content-length. (RFC7540 8.1.2.6)');
- [BinLen] ->
- headers_parse_expected_size(Frame, State, Type, Stream,
- PseudoHeaders, Headers, BinLen);
- _ ->
- headers_malformed(Frame, State,
- 'Multiple content-length headers were received. (RFC7230 3.3.2)')
- end.
-
-response_expected_size(Frame=#headers{id=StreamID, fin=IsFin}, State, Type,
- Stream=#stream{method=Method}, PseudoHeaders=#{status := Status}, Headers) ->
- case [CL || {<<"content-length">>, CL} <- Headers] of
- [] when IsFin =:= fin ->
- headers_frame(Frame, State, Type, Stream, PseudoHeaders, Headers, 0);
- [] ->
- headers_frame(Frame, State, Type, Stream, PseudoHeaders, Headers, undefined);
- [_] when Status >= 100, Status =< 199 ->
- stream_reset(StreamID, State, protocol_error,
- 'Content-length header received in a 1xx response. (RFC7230 3.3.2)');
- [_] when Status =:= 204 ->
- stream_reset(StreamID, State, protocol_error,
- 'Content-length header received in a 204 response. (RFC7230 3.3.2)');
- [_] when Status >= 200, Status =< 299, Method =:= <<"CONNECT">> ->
- stream_reset(StreamID, State, protocol_error,
- 'Content-length header received in a 2xx response to a CONNECT request. (RFC7230 3.3.2).');
- %% Responses to HEAD requests, and 304 responses may contain
- %% a content-length header that must be ignored. (RFC7230 3.3.2)
- [_] when Method =:= <<"HEAD">> ->
- headers_frame(Frame, State, Type, Stream, PseudoHeaders, Headers, 0);
- [_] when Status =:= 304 ->
- headers_frame(Frame, State, Type, Stream, PseudoHeaders, Headers, 0);
- [<<"0">>] when IsFin =:= fin ->
- headers_frame(Frame, State, Type, Stream, PseudoHeaders, Headers, 0);
- [_] when IsFin =:= fin ->
- stream_reset(StreamID, State, protocol_error,
- 'HEADERS frame with the END_STREAM flag contains a non-zero content-length. (RFC7540 8.1.2.6)');
- [BinLen] ->
- headers_parse_expected_size(Frame, State, Type, Stream,
- PseudoHeaders, Headers, BinLen);
- _ ->
- stream_reset(StreamID, State, protocol_error,
- 'Multiple content-length headers were received. (RFC7230 3.3.2)')
- end.
-
-headers_parse_expected_size(Frame=#headers{id=StreamID},
- State, Type, Stream, PseudoHeaders, Headers, BinLen) ->
- try cow_http_hd:parse_content_length(BinLen) of
- Len ->
- headers_frame(Frame, State, Type, Stream, PseudoHeaders, Headers, Len)
- catch
- _:_ ->
- HumanReadable = 'The content-length header is invalid. (RFC7230 3.3.2)',
- case Type of
- request -> headers_malformed(Frame, State, HumanReadable);
- response -> stream_reset(StreamID, State, protocol_error, HumanReadable)
- end
- end.
+format_error(connect_invalid_pseudo_header) ->
+ 'CONNECT requests only use the :method and :authority pseudo-headers. (RFC7540 8.3)';
+format_error(connect_missing_authority) ->
+ 'CONNECT requests must include the :authority pseudo-header. (RFC7540 8.3)';
+format_error(empty_header_name) ->
+ 'Empty header names are not valid regular headers. (CVE-2019-9516)';
+format_error(extended_connect_missing_protocol) ->
+ 'The :protocol pseudo-header MUST be sent with an extended CONNECT. (RFC8441 4)';
+format_error(invalid_connection_header) ->
+ 'The connection header is not allowed. (RFC7540 8.1.2.2)';
+format_error(invalid_keep_alive_header) ->
+ 'The keep-alive header is not allowed. (RFC7540 8.1.2.2)';
+format_error(invalid_protocol_pseudo_header) ->
+ 'The :protocol pseudo-header is only defined for the extended CONNECT. (RFC8441 4)';
+format_error(invalid_proxy_authenticate_header) ->
+ 'The proxy-authenticate header is not allowed. (RFC7540 8.1.2.2)';
+format_error(invalid_proxy_authorization_header) ->
+ 'The proxy-authorization header is not allowed. (RFC7540 8.1.2.2)';
+format_error(invalid_pseudo_header) ->
+ 'An unknown or invalid pseudo-header was found. (RFC7540 8.1.2.1)';
+format_error(invalid_status_pseudo_header) ->
+ 'The :status pseudo-header value is invalid. (RFC7540 8.1.2.4)';
+format_error(invalid_te_header) ->
+ 'The te header is only allowed in request headers. (RFC7540 8.1.2.2)';
+format_error(invalid_te_value) ->
+ 'The te header with a value other than "trailers" is not allowed. (RFC7540 8.1.2.2)';
+format_error(invalid_transfer_encoding_header) ->
+ 'The transfer-encoding header is not allowed. (RFC7540 8.1.2.2)';
+format_error(invalid_upgrade_header) ->
+ 'The upgrade header is not allowed. (RFC7540 8.1.2.2)';
+format_error(missing_pseudo_header) ->
+ 'A required pseudo-header was not found. (RFC7540 8.1.2.3, RFC7540 8.1.2.4)';
+format_error(multiple_authority_pseudo_headers) ->
+ 'Multiple :authority pseudo-headers were found. (RFC7540 8.1.2.3)';
+format_error(multiple_method_pseudo_headers) ->
+ 'Multiple :method pseudo-headers were found. (RFC7540 8.1.2.3)';
+format_error(multiple_path_pseudo_headers) ->
+ 'Multiple :path pseudo-headers were found. (RFC7540 8.1.2.3)';
+format_error(multiple_protocol_pseudo_headers) ->
+ 'Multiple :protocol pseudo-headers were found. (RFC7540 8.1.2.3)';
+format_error(multiple_scheme_pseudo_headers) ->
+ 'Multiple :scheme pseudo-headers were found. (RFC7540 8.1.2.3)';
+format_error(multiple_status_pseudo_headers) ->
+ 'Multiple :status pseudo-headers were found. (RFC7540 8.1.2.3)';
+format_error(non_zero_length_with_fin_flag) ->
+ 'HEADERS frame with the END_STREAM flag contains a non-zero content-length. (RFC7540 8.1.2.6)';
+format_error(pseudo_header_after_regular) ->
+ 'Pseudo-headers were found after regular headers. (RFC7540 8.1.2.1)';
+format_error(trailer_invalid_pseudo_header) ->
+ 'Trailer header blocks must not contain pseudo-headers. (RFC7540 8.1.2.1)';
+format_error(uppercase_header_name) ->
+ 'Header names must be lowercase. (RFC7540 8.1.2)';
+format_error(Reason) ->
+ cow_http:format_semantic_error(Reason).
headers_frame(#headers{id=StreamID, fin=IsFin}, State0=#http2_machine{
local_settings=#{initial_window_size := RemoteWindow},
remote_settings=#{initial_window_size := LocalWindow}},
- Type, Stream0, PseudoHeaders, Headers, Len) ->
+ Type, Stream0, Headers, PseudoHeaders, Len) ->
{Stream, State1} = case Type of
request ->
TE = case lists:keyfind(<<"te">>, 1, Headers) of
@@ -818,7 +662,8 @@ trailers_frame(#headers{id=StreamID}, State0, Stream0, Headers) ->
%% PRIORITY frame.
%%
-%% @todo Handle PRIORITY frames.
+%% The PRIORITY mechanism is seen as flawed and deprecated.
+%% We will not implement it.
priority_frame(_Frame, State) ->
{ok, State}.
@@ -967,7 +812,7 @@ push_promise_frame(#headers{id=PromisedStreamID},
State0=#http2_machine{
local_settings=#{initial_window_size := RemoteWindow},
remote_settings=#{initial_window_size := LocalWindow}},
- #stream{id=StreamID}, PseudoHeaders=#{method := Method}, Headers) ->
+ #stream{id=StreamID}, Headers, PseudoHeaders=#{method := Method}) ->
TE = case lists:keyfind(<<"te">>, 1, Headers) of
{_, TE0} -> TE0;
false -> undefined
@@ -1141,9 +986,9 @@ timeout(_, _, State) ->
%% this module does not send data directly, instead it returns
%% a value that can then be used to send the frames.
--spec prepare_headers(cow_http2:streamid(), State, idle | cow_http2:fin(),
- pseudo_headers(), cow_http:headers())
- -> {ok, cow_http2:fin(), iodata(), State} when State::http2_machine().
+-spec prepare_headers(cow_http2:streamid(), State, idle | cow_http:fin(),
+ cow_http:pseudo_headers(), cow_http:headers())
+ -> {ok, cow_http:fin(), iodata(), State} when State::http2_machine().
prepare_headers(StreamID, State=#http2_machine{encode_state=EncodeState0},
IsFin0, PseudoHeaders, Headers0) ->
Stream = #stream{method=Method, local=idle} = stream_get(StreamID, State),
@@ -1152,12 +997,14 @@ prepare_headers(StreamID, State=#http2_machine{encode_state=EncodeState0},
{_, <<"HEAD">>} -> fin;
_ -> IsFin0
end,
- Headers = merge_pseudo_headers(PseudoHeaders, remove_http11_headers(Headers0)),
+ Headers = cow_http:merge_pseudo_headers(PseudoHeaders,
+ cow_http:remove_http1_headers(Headers0)),
{HeaderBlock, EncodeState} = cow_hpack:encode(Headers, EncodeState0),
{ok, IsFin, HeaderBlock, stream_store(Stream#stream{local=IsFin0},
State#http2_machine{encode_state=EncodeState})}.
--spec prepare_push_promise(cow_http2:streamid(), State, pseudo_headers(), cow_http:headers())
+-spec prepare_push_promise(cow_http2:streamid(), State,
+ cow_http:pseudo_headers(), cow_http:headers())
-> {ok, cow_http2:streamid(), iodata(), State}
| {error, no_push} when State::http2_machine().
prepare_push_promise(_, #http2_machine{remote_settings=#{enable_push := false}}, _, _) ->
@@ -1171,7 +1018,8 @@ prepare_push_promise(StreamID, State=#http2_machine{encode_state=EncodeState0,
{_, TE0} -> TE0;
false -> undefined
end,
- Headers = merge_pseudo_headers(PseudoHeaders, remove_http11_headers(Headers0)),
+ Headers = cow_http:merge_pseudo_headers(PseudoHeaders,
+ cow_http:remove_http1_headers(Headers0)),
{HeaderBlock, EncodeState} = cow_hpack:encode(Headers, EncodeState0),
{ok, LocalStreamID, HeaderBlock, stream_store(
#stream{id=LocalStreamID, method=maps:get(method, PseudoHeaders),
@@ -1179,34 +1027,6 @@ prepare_push_promise(StreamID, State=#http2_machine{encode_state=EncodeState0,
local_window=LocalWindow, remote_window=RemoteWindow, te=TE},
State#http2_machine{encode_state=EncodeState, local_streamid=LocalStreamID + 2})}.
-remove_http11_headers(Headers) ->
- RemoveHeaders0 = [
- <<"keep-alive">>,
- <<"proxy-connection">>,
- <<"transfer-encoding">>,
- <<"upgrade">>
- ],
- RemoveHeaders = case lists:keyfind(<<"connection">>, 1, Headers) of
- false ->
- RemoveHeaders0;
- {_, ConnHd} ->
- %% We do not need to worry about any "close" header because
- %% that header name is reserved.
- Connection = cow_http_hd:parse_connection(ConnHd),
- Connection ++ [<<"connection">>|RemoveHeaders0]
- end,
- lists:filter(fun({Name, _}) ->
- not lists:member(Name, RemoveHeaders)
- end, Headers).
-
-merge_pseudo_headers(PseudoHeaders, Headers0) ->
- lists:foldl(fun
- ({status, Status}, Acc) when is_integer(Status) ->
- [{<<":status">>, integer_to_binary(Status)}|Acc];
- ({Name, Value}, Acc) ->
- [{iolist_to_binary([$:, atom_to_binary(Name, latin1)]), Value}|Acc]
- end, Headers0, maps:to_list(PseudoHeaders)).
-
-spec prepare_trailers(cow_http2:streamid(), State, cow_http:headers())
-> {ok, iodata(), State} when State::http2_machine().
prepare_trailers(StreamID, State=#http2_machine{encode_state=EncodeState0}, Trailers) ->
@@ -1215,9 +1035,9 @@ prepare_trailers(StreamID, State=#http2_machine{encode_state=EncodeState0}, Trai
{ok, HeaderBlock, stream_store(Stream#stream{local=fin},
State#http2_machine{encode_state=EncodeState})}.
--spec send_or_queue_data(cow_http2:streamid(), State, cow_http2:fin(), DataOrFileOrTrailers)
+-spec send_or_queue_data(cow_http2:streamid(), State, cow_http:fin(), DataOrFileOrTrailers)
-> {ok, State}
- | {send, [{cow_http2:streamid(), cow_http2:fin(), [DataOrFileOrTrailers]}], State}
+ | {send, [{cow_http2:streamid(), cow_http:fin(), [DataOrFileOrTrailers]}], State}
when State::http2_machine(), DataOrFileOrTrailers::
{data, iodata()} | #sendfile{} | {trailers, cow_http:headers()}.
send_or_queue_data(StreamID, State0=#http2_machine{opts=Opts, local_window=ConnWindow},
@@ -1272,8 +1092,8 @@ send_or_queue_data(StreamID, State0=#http2_machine{opts=Opts, local_window=ConnW
%% Internal data sending/queuing functions.
-%% @todo Should we ever want to implement the PRIORITY mechanism,
-%% this would be the place to do it. Right now, we just go over
+%% The PRIORITY mechanism is seen as flawed and deprecated.
+%% We will not implement it. So we just go over
%% all streams and send what we can until either everything is
%% sent or we run out of space in the window.
send_data(State0=#http2_machine{streams=Streams0}) ->
@@ -1607,7 +1427,7 @@ get_stream_local_buffer_size(StreamID, State=#http2_machine{mode=Mode,
%% Retrieve the local state for a stream, including the state in the queue.
-spec get_stream_local_state(cow_http2:streamid(), http2_machine())
- -> {ok, idle | cow_http2:fin(), empty | nofin | fin} | {error, not_found | closed}.
+ -> {ok, idle | cow_http:fin(), empty | nofin | fin} | {error, not_found | closed}.
get_stream_local_state(StreamID, State=#http2_machine{mode=Mode,
local_streamid=LocalStreamID, remote_streamid=RemoteStreamID}) ->
case stream_get(StreamID, State) of
@@ -1630,7 +1450,7 @@ get_stream_local_state(StreamID, State=#http2_machine{mode=Mode,
%% Retrieve the remote state for a stream.
-spec get_stream_remote_state(cow_http2:streamid(), http2_machine())
- -> {ok, idle | cow_http2:fin()} | {error, not_found | closed}.
+ -> {ok, idle | cow_http:fin()} | {error, not_found | closed}.
get_stream_remote_state(StreamID, State=#http2_machine{mode=Mode,
local_streamid=LocalStreamID, remote_streamid=RemoteStreamID}) ->
case stream_get(StreamID, State) of