aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLoïc Hoguin <[email protected]>2020-10-21 19:15:48 +0200
committerLoïc Hoguin <[email protected]>2020-10-21 20:17:18 +0200
commit465d072abf4a76104d4562ed15345b27fe9a0cff (patch)
tree853338dc133fda51ed65e74dedc81f845dc090de
parent69f19635df64ea48af3120c9685f7ad51b338f8f (diff)
downloadgun-465d072abf4a76104d4562ed15345b27fe9a0cff.tar.gz
gun-465d072abf4a76104d4562ed15345b27fe9a0cff.tar.bz2
gun-465d072abf4a76104d4562ed15345b27fe9a0cff.zip
Fix cookie handling when tunnel and origin schemes mismatch
The cookie_ignore_informational has been moved to http_opts and http2_opts. Also fix an issue when using 'protocols' in gun:open. When connecting via TLS the protocol's options were discarded.
-rw-r--r--src/gun.erl87
-rw-r--r--src/gun_cookies.erl32
-rw-r--r--src/gun_http.erl147
-rw-r--r--src/gun_http2.erl182
-rw-r--r--src/gun_protocols.erl22
-rw-r--r--src/gun_raw.erl6
-rw-r--r--src/gun_socks.erl6
-rw-r--r--src/gun_tunnel.erl98
-rw-r--r--src/gun_ws.erl6
-rw-r--r--test/rfc6265bis_SUITE.erl42
10 files changed, 330 insertions, 298 deletions
diff --git a/src/gun.erl b/src/gun.erl
index 177d395..69dbb6b 100644
--- a/src/gun.erl
+++ b/src/gun.erl
@@ -135,7 +135,6 @@
-type opts() :: #{
connect_timeout => timeout(),
- cookie_ignore_informational => boolean(),
cookie_store => gun_cookies:store(),
domain_lookup_timeout => timeout(),
event_handler => {module(), any()},
@@ -212,6 +211,7 @@
-type http_opts() :: #{
closing_timeout => timeout(),
content_handlers => gun_content_handler:opt(),
+ cookie_ignore_informational => boolean(),
flow => pos_integer(),
keepalive => timeout(),
transform_header_name => fun((binary()) -> binary()),
@@ -226,6 +226,7 @@
-type http2_opts() :: #{
closing_timeout => timeout(),
content_handlers => gun_content_handler:opt(),
+ cookie_ignore_informational => boolean(),
flow => pos_integer(),
keepalive => timeout(),
@@ -350,8 +351,6 @@ check_options([{connect_timeout, T}|Opts]) when is_integer(T), T >= 0 ->
check_options(Opts);
check_options([{cookie_store, {Mod, _}}|Opts]) when is_atom(Mod) ->
check_options(Opts);
-check_options([{cookie_ignore_informational, B}|Opts]) when is_boolean(B) ->
- check_options(Opts);
check_options([{domain_lookup_timeout, infinity}|Opts]) ->
check_options(Opts);
check_options([{domain_lookup_timeout, T}|Opts]) when is_integer(T), T >= 0 ->
@@ -1097,10 +1096,13 @@ initial_tls_handshake(_, {retries, Retries, Socket}, State0=#state{opts=Opts, or
ensure_alpn_sni(Protocols0, TransOpts0, OriginHost) ->
%% ALPN.
- Protocols = [case P of
- http -> <<"http/1.1">>;
- http2 -> <<"h2">>
- end || P <- Protocols0, lists:member(P, [http, http2])],
+ Protocols = lists:foldl(fun
+ (http, Acc) -> [<<"http/1.1">>|Acc];
+ ({http, _}, Acc) -> [<<"http/1.1">>|Acc];
+ (http2, Acc) -> [<<"h2">>|Acc];
+ ({http2, _}, Acc) -> [<<"h2">>|Acc];
+ (_, Acc) -> Acc
+ end, [], Protocols0),
TransOpts = [
{alpn_advertised_protocols, Protocols},
{client_preferred_next_protocols, {client, Protocols, <<"http/1.1">>}}
@@ -1188,12 +1190,13 @@ normal_tls_handshake(Socket, State=#state{
EvHandlerState1 = EvHandler:tls_handshake_start(HandshakeEvent, EvHandlerState0),
case gun_tls:connect(Socket, TLSOpts, TLSTimeout) of
{ok, TLSSocket} ->
- Protocol = gun_protocols:negotiated(ssl:negotiated_protocol(TLSSocket), Protocols),
+ NewProtocol = gun_protocols:negotiated(ssl:negotiated_protocol(TLSSocket), Protocols),
+ Protocol = gun_protocols:handler(NewProtocol),
EvHandlerState = EvHandler:tls_handshake_end(HandshakeEvent#{
socket => TLSSocket,
- protocol => Protocol
+ protocol => Protocol:name()
}, EvHandlerState1),
- {ok, TLSSocket, Protocol, State#state{event_handler_state=EvHandlerState}};
+ {ok, TLSSocket, NewProtocol, State#state{event_handler_state=EvHandlerState}};
{error, Reason} ->
EvHandlerState = EvHandler:tls_handshake_end(HandshakeEvent#{
error => Reason
@@ -1381,10 +1384,12 @@ handle_common_connected(Type, Event, StateName, StateData) ->
%% Socket events.
handle_common_connected_no_input(info, {OK, Socket, Data}, _,
State0=#state{socket=Socket, messages={OK, _, _},
- protocol=Protocol, protocol_state=ProtoState,
+ protocol=Protocol, protocol_state=ProtoState, cookie_store=CookieStore0,
event_handler=EvHandler, event_handler_state=EvHandlerState0}) ->
- {Commands, EvHandlerState} = Protocol:handle(Data, ProtoState, EvHandler, EvHandlerState0),
- maybe_active(commands(Commands, State0#state{event_handler_state=EvHandlerState}));
+ {Commands, CookieStore, EvHandlerState} = Protocol:handle(Data,
+ ProtoState, CookieStore0, EvHandler, EvHandlerState0),
+ maybe_active(commands(Commands, State0#state{cookie_store=CookieStore,
+ event_handler_state=EvHandlerState}));
handle_common_connected_no_input(info, {Closed, Socket}, _,
State=#state{socket=Socket, messages={_, Closed, _}}) ->
disconnect(State, closed);
@@ -1395,19 +1400,21 @@ handle_common_connected_no_input(info, {Error, Socket, Reason}, _,
%% We always forward the messages to Protocol:handle_continue.
handle_common_connected_no_input(info,
Msg={gun_tls_proxy, _, _, {handle_continue, StreamRef, _, _}}, _,
- State0=#state{protocol=Protocol, protocol_state=ProtoState,
+ State0=#state{protocol=Protocol, protocol_state=ProtoState, cookie_store=CookieStore0,
event_handler=EvHandler, event_handler_state=EvHandlerState0}) ->
- {Commands, EvHandlerState} = Protocol:handle_continue(
+ {Commands, CookieStore, EvHandlerState} = Protocol:handle_continue(
dereference_stream_ref(StreamRef, State0),
- Msg, ProtoState, EvHandler, EvHandlerState0),
- maybe_active(commands(Commands, State0#state{event_handler_state=EvHandlerState}));
+ Msg, ProtoState, CookieStore0, EvHandler, EvHandlerState0),
+ maybe_active(commands(Commands, State0#state{cookie_store=CookieStore,
+ event_handler_state=EvHandlerState}));
handle_common_connected_no_input(info, {handle_continue, StreamRef, Msg}, _,
- State0=#state{protocol=Protocol, protocol_state=ProtoState,
+ State0=#state{protocol=Protocol, protocol_state=ProtoState, cookie_store=CookieStore0,
event_handler=EvHandler, event_handler_state=EvHandlerState0}) ->
- {Commands, EvHandlerState} = Protocol:handle_continue(
+ {Commands, CookieStore, EvHandlerState} = Protocol:handle_continue(
dereference_stream_ref(StreamRef, State0),
- Msg, ProtoState, EvHandler, EvHandlerState0),
- maybe_active(commands(Commands, State0#state{event_handler_state=EvHandlerState}));
+ Msg, ProtoState, CookieStore0, EvHandler, EvHandlerState0),
+ maybe_active(commands(Commands, State0#state{cookie_store=CookieStore,
+ event_handler_state=EvHandlerState}));
%% Timeouts.
%% @todo HTTP/2 requires more timeouts than just the keepalive timeout.
%% We should have a timeout function in protocols that deal with
@@ -1622,44 +1629,6 @@ commands([{active, Active}|Tail], State) when is_boolean(Active) ->
commands(Tail, State#state{active=Active});
commands([{state, ProtoState}|Tail], State) ->
commands(Tail, State#state{protocol_state=ProtoState});
-%% Don't set cookies when cookie store isn't configured.
-commands([{set_cookie, _, _, _, _}|Tail], State=#state{cookie_store=undefined}) ->
- commands(Tail, State);
-%% Ignore cookies set on informational responses when configured to do so.
-%% This includes cookies set to Websocket upgrade responses!
-commands([{set_cookie, _, _, Status, _}|Tail], State=#state{opts=#{cookie_ignore_informational := true}})
- when Status >= 100, Status =< 199 ->
- commands(Tail, State);
-%% @todo Make sure this works for proxied requests too.
-commands([{set_cookie, Authority, PathWithQs, _, Headers}|Tail], State=#state{
- transport=Transport, cookie_store=Store0}) ->
- %% @todo This is wrong. Also we should probably not do a command for this.
- %% We should instead give the CookieStore to all callbacks.
- Scheme = case Transport of
- gun_tls -> <<"https">>;
- gun_tls_proxy -> <<"https">>;
- gun_tcp -> <<"http">>
- end,
- %% @todo Not sure if this is best done here or in the protocol code or elsewhere.
- #{host := Host, path := Path} = uri_string:parse([Scheme, <<"://">>, Authority, PathWithQs]),
- URIMap = uri_string:normalize(#{
- scheme => Scheme,
- host => iolist_to_binary(Host),
- path => iolist_to_binary(Path)
- }, [return_map]),
- SetCookies = [SC || {<<"set-cookie">>, SC} <- Headers],
- Store = lists:foldl(fun(SC, Store1) ->
- case cow_cookie:parse_set_cookie(SC) of
- {ok, N, V, A} ->
- case gun_cookies:set_cookie(Store1, URIMap, N, V, A) of
- {ok, Store2} -> Store2;
- {error, _} -> Store1
- end;
- ignore ->
- Store1
- end
- end, Store0, SetCookies),
- commands(Tail, State#state{cookie_store=Store});
%% Order is important: the origin must be changed before
%% the transport and/or protocol in order to keep track
%% of the intermediaries properly.
diff --git a/src/gun_cookies.erl b/src/gun_cookies.erl
index 965c3a5..0537832 100644
--- a/src/gun_cookies.erl
+++ b/src/gun_cookies.erl
@@ -21,6 +21,7 @@
-export([query/2]).
-export([session_gc/1]).
-export([set_cookie/5]).
+-export([set_cookie_header/7]).
-ifdef(TEST).
-export([wpt_http_state_test_files/1]). %% Also used in rfc6265bis_SUITE.
@@ -341,6 +342,37 @@ store({Mod, State0}, Cookie) ->
Error
end.
+-spec set_cookie_header(binary(), iodata(), iodata(), cow_http:status(),
+ Headers, Store, #{cookie_ignore_informational := boolean()})
+ -> {Headers, Store} when Headers :: [{binary(), iodata()}], Store :: undefined | store().
+%% Don't set cookies when cookie store isn't configured.
+set_cookie_header(_, _, _, _, _, Store=undefined, _) ->
+ Store;
+%% Ignore cookies set on informational responses when configured to do so.
+%% This includes cookies set to Websocket upgrade responses!
+set_cookie_header(_, _, _, Status, _, Store, #{cookie_ignore_informational := true})
+ when Status >= 100, Status =< 199 ->
+ Store;
+set_cookie_header(Scheme, Authority, PathWithQs, _, Headers, Store0, _) ->
+ #{host := Host, path := Path} = uri_string:parse([Scheme, <<"://">>, Authority, PathWithQs]),
+ URIMap = uri_string:normalize(#{
+ scheme => Scheme,
+ host => iolist_to_binary(Host),
+ path => iolist_to_binary(Path)
+ }, [return_map]),
+ SetCookies = [SC || {<<"set-cookie">>, SC} <- Headers],
+ lists:foldl(fun(SC, Store1) ->
+ case cow_cookie:parse_set_cookie(SC) of
+ {ok, N, V, A} ->
+ case set_cookie(Store1, URIMap, N, V, A) of
+ {ok, Store} -> Store;
+ {error, _} -> Store1
+ end;
+ ignore ->
+ Store1
+ end
+ end, Store0, SetCookies).
+
-ifdef(TEST).
gc_test() ->
URIMap = #{scheme => <<"http">>, host => <<"example.org">>, path => <<"/path/to/resource">>},
diff --git a/src/gun_http.erl b/src/gun_http.erl
index 62d9490..8b716a5 100644
--- a/src/gun_http.erl
+++ b/src/gun_http.erl
@@ -21,7 +21,7 @@
-export([default_keepalive/0]).
-export([init/4]).
-export([switch_transport/3]).
--export([handle/4]).
+-export([handle/5]).
-export([update_flow/4]).
-export([closing/4]).
-export([close/4]).
@@ -80,10 +80,7 @@
streams = [] :: [#stream{}],
in = head :: io(),
in_state = {0, 0} :: {non_neg_integer(), non_neg_integer()},
- out = head :: io(),
-
- %% We must queue commands when parsing the incoming data.
- commands_queue = [] :: [{set_cookie, iodata(), iodata(), cow_http:status(), cow_http:headers()}]
+ out = head :: io()
}).
check_options(Opts) ->
@@ -100,6 +97,8 @@ do_check_options([Opt={content_handlers, Handlers}|Opts]) ->
ok -> do_check_options(Opts);
error -> {error, {options, {http, Opt}}}
end;
+do_check_options([{cookie_ignore_informational, B}|Opts]) when is_boolean(B) ->
+ do_check_options(Opts);
do_check_options([{flow, InitialFlow}|Opts]) when is_integer(InitialFlow), InitialFlow > 0 ->
do_check_options(Opts);
do_check_options([{keepalive, infinity}|Opts]) ->
@@ -127,29 +126,16 @@ init(_ReplyTo, Socket, Transport, Opts) ->
switch_transport(Transport, Socket, State) ->
State#http_state{socket=Socket, transport=Transport}.
-%% This function is called before returning from handle/4.
-handle_ret(CommandOrCommands, #http_state{commands_queue=[]}) ->
- empty_commands_queue(CommandOrCommands);
-handle_ret(Commands, #http_state{commands_queue=Queue}) when is_list(Commands) ->
- lists:reverse(Queue, empty_commands_queue(Commands));
-handle_ret(Command, #http_state{commands_queue=Queue}) ->
- lists:reverse([empty_commands_queue(Command)|Queue]).
-
-empty_commands_queue([{state, State}|Tail]) -> [{state, State#http_state{commands_queue=[]}}|Tail];
-empty_commands_queue([Command|Tail]) -> [Command|empty_commands_queue(Tail)];
-empty_commands_queue([]) -> [];
-empty_commands_queue({state, State}) -> {state, State#http_state{commands_queue=[]}};
-empty_commands_queue(Command) -> Command.
-
%% Stop looping when we got no more data.
-handle(<<>>, State, _, EvHandlerState) ->
- {handle_ret({state, State}, State), EvHandlerState};
+handle(<<>>, State, CookieStore, _, EvHandlerState) ->
+ {{state, State}, CookieStore, EvHandlerState};
%% Close when server responds and we don't have any open streams.
-handle(_, State=#http_state{streams=[]}, _, EvHandlerState) ->
- {handle_ret(close, State), EvHandlerState};
+handle(_, #http_state{streams=[]}, CookieStore, _, EvHandlerState) ->
+ {close, CookieStore, EvHandlerState};
%% Wait for the full response headers before trying to parse them.
handle(Data, State=#http_state{in=head, buffer=Buffer,
- streams=[#stream{ref=StreamRef, reply_to=ReplyTo}|_]}, EvHandler, EvHandlerState0) ->
+ streams=[#stream{ref=StreamRef, reply_to=ReplyTo}|_]},
+ CookieStore, EvHandler, EvHandlerState0) ->
%% Send the event only if there was no data in the buffer.
%% If there is data in the buffer then we already sent the event.
EvHandlerState = case Buffer of
@@ -163,31 +149,34 @@ handle(Data, State=#http_state{in=head, buffer=Buffer,
end,
Data2 = << Buffer/binary, Data/binary >>,
case binary:match(Data2, <<"\r\n\r\n">>) of
- nomatch -> {handle_ret({state, State#http_state{buffer=Data2}}, State), EvHandlerState};
- {_, _} -> handle_head(Data2, State#http_state{buffer= <<>>}, EvHandler, EvHandlerState)
+ nomatch ->
+ {{state, State#http_state{buffer=Data2}}, CookieStore, EvHandlerState};
+ {_, _} ->
+ handle_head(Data2, State#http_state{buffer= <<>>},
+ CookieStore, EvHandler, EvHandlerState)
end;
%% Everything sent to the socket until it closes is part of the response body.
-handle(Data, State=#http_state{in=body_close}, _, EvHandlerState) ->
- {handle_ret(send_data(Data, State, nofin), State), EvHandlerState};
+handle(Data, State=#http_state{in=body_close}, CookieStore, _, EvHandlerState) ->
+ {send_data(Data, State, nofin), CookieStore, EvHandlerState};
%% Chunked transfer-encoding may contain both data and trailers.
-handle(Data, State=#http_state{in=body_chunked, in_state=InState,
- buffer=Buffer, streams=[#stream{ref=StreamRef, reply_to=ReplyTo}|_],
- connection=Conn}, EvHandler, EvHandlerState0) ->
+handle(Data, State=#http_state{in=body_chunked, in_state=InState, buffer=Buffer,
+ streams=[#stream{ref=StreamRef, reply_to=ReplyTo}|_], connection=Conn},
+ CookieStore, EvHandler, EvHandlerState0) ->
Buffer2 = << Buffer/binary, Data/binary >>,
case cow_http_te:stream_chunked(Buffer2, InState) of
more ->
- {handle_ret({state, State#http_state{buffer=Buffer2}}, State), EvHandlerState0};
+ {{state, State#http_state{buffer=Buffer2}}, CookieStore, EvHandlerState0};
{more, Data2, InState2} ->
- {handle_ret(send_data(Data2, State#http_state{buffer= <<>>, in_state=InState2}, nofin), State),
- EvHandlerState0};
+ {send_data(Data2, State#http_state{buffer= <<>>, in_state=InState2}, nofin),
+ CookieStore, EvHandlerState0};
{more, Data2, Length, InState2} when is_integer(Length) ->
%% @todo See if we can recv faster than one message at a time.
- {handle_ret(send_data(Data2, State#http_state{buffer= <<>>, in_state=InState2}, nofin), State),
- EvHandlerState0};
+ {send_data(Data2, State#http_state{buffer= <<>>, in_state=InState2}, nofin),
+ CookieStore, EvHandlerState0};
{more, Data2, Rest, InState2} ->
%% @todo See if we can recv faster than one message at a time.
- {handle_ret(send_data(Data2, State#http_state{buffer=Rest, in_state=InState2}, nofin), State),
- EvHandlerState0};
+ {send_data(Data2, State#http_state{buffer=Rest, in_state=InState2}, nofin),
+ CookieStore, EvHandlerState0};
{done, HasTrailers, Rest} ->
%% @todo response_end should be called AFTER send_data
{IsFin, EvHandlerState} = case HasTrailers of
@@ -205,11 +194,13 @@ handle(Data, State=#http_state{in=body_chunked, in_state=InState,
[{state, State1}|_] = send_data(<<>>, State, IsFin),
case {HasTrailers, Conn} of
{trailers, _} ->
- handle(Rest, State1#http_state{buffer = <<>>, in=body_trailer}, EvHandler, EvHandlerState);
+ handle(Rest, State1#http_state{buffer = <<>>, in=body_trailer},
+ CookieStore, EvHandler, EvHandlerState);
{no_trailers, keepalive} ->
- handle(Rest, end_stream(State1#http_state{buffer= <<>>}), EvHandler, EvHandlerState);
+ handle(Rest, end_stream(State1#http_state{buffer= <<>>}),
+ CookieStore, EvHandler, EvHandlerState);
{no_trailers, close} ->
- {handle_ret([{state, end_stream(State1)}, close], State1), EvHandlerState}
+ {[{state, end_stream(State1)}, close], CookieStore, EvHandlerState}
end;
{done, Data2, HasTrailers, Rest} ->
%% @todo response_end should be called AFTER send_data
@@ -227,19 +218,22 @@ handle(Data, State=#http_state{in=body_chunked, in_state=InState,
[{state, State1}|_] = send_data(Data2, State, IsFin),
case {HasTrailers, Conn} of
{trailers, _} ->
- handle(Rest, State1#http_state{buffer = <<>>, in=body_trailer}, EvHandler, EvHandlerState);
+ handle(Rest, State1#http_state{buffer = <<>>, in=body_trailer},
+ CookieStore, EvHandler, EvHandlerState);
{no_trailers, keepalive} ->
- handle(Rest, end_stream(State1#http_state{buffer= <<>>}), EvHandler, EvHandlerState);
+ handle(Rest, end_stream(State1#http_state{buffer= <<>>}),
+ CookieStore, EvHandler, EvHandlerState);
{no_trailers, close} ->
- {handle_ret([{state, end_stream(State1)}, close], State1), EvHandlerState}
+ {[{state, end_stream(State1)}, close], CookieStore, EvHandlerState}
end
end;
handle(Data, State=#http_state{in=body_trailer, buffer=Buffer, connection=Conn,
- streams=[#stream{ref=StreamRef, reply_to=ReplyTo}|_]}, EvHandler, EvHandlerState0) ->
+ streams=[#stream{ref=StreamRef, reply_to=ReplyTo}|_]},
+ CookieStore, EvHandler, EvHandlerState0) ->
Data2 = << Buffer/binary, Data/binary >>,
case binary:match(Data2, <<"\r\n\r\n">>) of
nomatch ->
- {handle_ret({state, State#http_state{buffer=Data2}}, State), EvHandlerState0};
+ {{state, State#http_state{buffer=Data2}}, CookieStore, EvHandlerState0};
{_, _} ->
{Trailers, Rest} = cow_http:parse_headers(Data2),
%% @todo We probably want to pass this to gun_content_handler?
@@ -253,21 +247,22 @@ handle(Data, State=#http_state{in=body_trailer, buffer=Buffer, connection=Conn,
EvHandlerState = EvHandler:response_end(ResponseEvent, EvHandlerState1),
case Conn of
keepalive ->
- handle(Rest, end_stream(State#http_state{buffer= <<>>}), EvHandler, EvHandlerState);
+ handle(Rest, end_stream(State#http_state{buffer= <<>>}),
+ CookieStore, EvHandler, EvHandlerState);
close ->
- {handle_ret([{state, end_stream(State)}, close], State), EvHandlerState}
+ {[{state, end_stream(State)}, close], CookieStore, EvHandlerState}
end
end;
%% We know the length of the rest of the body.
handle(Data, State=#http_state{in={body, Length}, connection=Conn,
streams=[#stream{ref=StreamRef, reply_to=ReplyTo}|_]},
- EvHandler, EvHandlerState0) ->
+ CookieStore, EvHandler, EvHandlerState0) ->
DataSize = byte_size(Data),
if
%% More data coming.
DataSize < Length ->
- {handle_ret(send_data(Data, State#http_state{in={body, Length - DataSize}}, nofin), State),
- EvHandlerState0};
+ {send_data(Data, State#http_state{in={body, Length - DataSize}}, nofin),
+ CookieStore, EvHandlerState0};
%% Stream finished, no rest.
DataSize =:= Length ->
%% We ignore the active command because the stream ended.
@@ -278,9 +273,9 @@ handle(Data, State=#http_state{in={body, Length}, connection=Conn,
}, EvHandlerState0),
case Conn of
keepalive ->
- {handle_ret([{state, end_stream(State1)}, {active, true}], State1), EvHandlerState};
+ {[{state, end_stream(State1)}, {active, true}], CookieStore, EvHandlerState};
close ->
- {handle_ret([{state, end_stream(State1)}, close], State1), EvHandlerState}
+ {[{state, end_stream(State1)}, close], CookieStore, EvHandlerState}
end;
%% Stream finished, rest.
true ->
@@ -292,28 +287,30 @@ handle(Data, State=#http_state{in={body, Length}, connection=Conn,
reply_to => ReplyTo
}, EvHandlerState0),
case Conn of
- keepalive -> handle(Rest, end_stream(State1), EvHandler, EvHandlerState);
- close -> {handle_ret([{state, end_stream(State1)}, close], State1), EvHandlerState}
+ keepalive -> handle(Rest, end_stream(State1), CookieStore, EvHandler, EvHandlerState);
+ close -> {[{state, end_stream(State1)}, close], CookieStore, EvHandlerState}
end
end.
-handle_head(Data, State0=#http_state{streams=[#stream{ref=StreamRef, authority=Authority, path=Path}|_],
- commands_queue=Commands}, EvHandler, EvHandlerState) ->
+handle_head(Data, State=#http_state{opts=Opts,
+ streams=[#stream{ref=StreamRef, authority=Authority, path=Path}|_]},
+ CookieStore0, EvHandler, EvHandlerState) ->
{Version, Status, _, Rest0} = cow_http:parse_status_line(Data),
{Headers, Rest} = cow_http:parse_headers(Rest0),
- State = State0#http_state{commands_queue=[{set_cookie, Authority, Path, Status, Headers}|Commands]},
+ CookieStore = gun_cookies:set_cookie_header(scheme(State),
+ Authority, Path, Status, Headers, CookieStore0, Opts),
case StreamRef of
{connect, _, _} when Status >= 200, Status < 300 ->
- handle_connect(Rest, State, EvHandler, EvHandlerState, Version, Status, Headers);
+ handle_connect(Rest, State, CookieStore, EvHandler, EvHandlerState, Version, Status, Headers);
_ when Status >= 100, Status =< 199 ->
- handle_inform(Rest, State, EvHandler, EvHandlerState, Version, Status, Headers);
+ handle_inform(Rest, State, CookieStore, EvHandler, EvHandlerState, Version, Status, Headers);
_ ->
- handle_response(Rest, State, EvHandler, EvHandlerState, Version, Status, Headers)
+ handle_response(Rest, State, CookieStore, EvHandler, EvHandlerState, Version, Status, Headers)
end.
handle_connect(Rest, State=#http_state{
streams=[Stream=#stream{ref={_, StreamRef, Destination}, reply_to=ReplyTo}|Tail]},
- EvHandler, EvHandlerState0, 'HTTP/1.1', Status, Headers) ->
+ CookieStore, EvHandler, EvHandlerState0, 'HTTP/1.1', Status, Headers) ->
RealStreamRef = stream_ref(State, StreamRef),
%% @todo If the stream is cancelled we probably shouldn't finish the CONNECT setup.
_ = case Stream of
@@ -342,25 +339,25 @@ handle_connect(Rest, State=#http_state{
timeout => maps:get(tls_handshake_timeout, Destination, infinity)
},
Protocols = maps:get(protocols, Destination, [http2, http]),
- {handle_ret([
+ {[
{origin, <<"https">>, NewHost, NewPort, connect},
{tls_handshake, HandshakeEvent, Protocols, ReplyTo}
- ], State), EvHandlerState1};
+ ], CookieStore, EvHandlerState1};
_ ->
[NewProtocol0] = maps:get(protocols, Destination, [http]),
NewProtocol = gun_protocols:add_stream_ref(NewProtocol0, RealStreamRef),
Protocol = gun_protocols:handler(NewProtocol),
ReplyTo ! {gun_tunnel_up, self(), RealStreamRef, Protocol:name()},
- {handle_ret([
+ {[
{origin, <<"http">>, NewHost, NewPort, connect},
{switch_protocol, NewProtocol, ReplyTo}
- ], State), EvHandlerState1}
+ ], CookieStore, EvHandlerState1}
end.
%% @todo We probably shouldn't send info messages if the stream is not alive.
handle_inform(Rest, State=#http_state{
streams=[#stream{ref=StreamRef, reply_to=ReplyTo}|_]},
- EvHandler, EvHandlerState0, Version, Status, Headers) ->
+ CookieStore, EvHandler, EvHandlerState0, Version, Status, Headers) ->
EvHandlerState = EvHandler:response_inform(#{
stream_ref => stream_ref(State, StreamRef),
reply_to => ReplyTo,
@@ -370,7 +367,7 @@ handle_inform(Rest, State=#http_state{
%% @todo We might want to switch to the HTTP/2 protocol or to the TLS transport as well.
case {Version, Status, StreamRef} of
{'HTTP/1.1', 101, #websocket{}} ->
- {handle_ret(ws_handshake(Rest, State, StreamRef, Headers), State), EvHandlerState};
+ {ws_handshake(Rest, State, StreamRef, Headers), CookieStore, EvHandlerState};
%% Any other 101 response results in us switching to the raw protocol.
%% @todo We should check that we asked for an upgrade before accepting it.
{'HTTP/1.1', 101, _} when is_reference(StreamRef) ->
@@ -380,21 +377,21 @@ handle_inform(Rest, State=#http_state{
Upgrade = cow_http_hd:parse_upgrade(Upgrade0),
ReplyTo ! {gun_upgrade, self(), stream_ref(State, StreamRef), Upgrade, Headers},
%% @todo We probably need to add_stream_ref?
- {handle_ret({switch_protocol, raw, ReplyTo}, State), EvHandlerState0}
+ {{switch_protocol, raw, ReplyTo}, CookieStore, EvHandlerState0}
catch _:_ ->
%% When the Upgrade header is missing or invalid we treat
%% the response as any other informational response.
ReplyTo ! {gun_inform, self(), stream_ref(State, StreamRef), Status, Headers},
- handle(Rest, State, EvHandler, EvHandlerState)
+ handle(Rest, State, CookieStore, EvHandler, EvHandlerState)
end;
_ ->
ReplyTo ! {gun_inform, self(), stream_ref(State, StreamRef), Status, Headers},
- handle(Rest, State, EvHandler, EvHandlerState)
+ handle(Rest, State, CookieStore, EvHandler, EvHandlerState)
end.
handle_response(Rest, State=#http_state{version=ClientVersion, opts=Opts, connection=Conn,
streams=[Stream=#stream{ref=StreamRef, reply_to=ReplyTo, method=Method, is_alive=IsAlive}|Tail]},
- EvHandler, EvHandlerState0, Version, Status, Headers) ->
+ CookieStore, EvHandler, EvHandlerState0, Version, Status, Headers) ->
In = response_io_from_headers(Method, Version, Status, Headers),
IsFin = case In of head -> fin; _ -> nofin end,
RealStreamRef = stream_ref(State, StreamRef),
@@ -436,17 +433,17 @@ handle_response(Rest, State=#http_state{version=ClientVersion, opts=Opts, connec
%% We always reset in_state even if not chunked.
if
IsFin =:= fin, Conn2 =:= close ->
- {handle_ret(close, State), EvHandlerState};
+ {close, CookieStore, EvHandlerState};
IsFin =:= fin ->
handle(Rest, end_stream(State#http_state{in=In,
in_state={0, 0}, connection=Conn2,
streams=[Stream#stream{handler_state=Handlers}|Tail]}),
- EvHandler, EvHandlerState);
+ CookieStore, EvHandler, EvHandlerState);
true ->
handle(Rest, State#http_state{in=In,
in_state={0, 0}, connection=Conn2,
streams=[Stream#stream{handler_state=Handlers}|Tail]},
- EvHandler, EvHandlerState)
+ CookieStore, EvHandler, EvHandlerState)
end.
%% The state must be first in order to retrieve it when the stream ended.
diff --git a/src/gun_http2.erl b/src/gun_http2.erl
index e8eb3aa..cb10029 100644
--- a/src/gun_http2.erl
+++ b/src/gun_http2.erl
@@ -21,8 +21,8 @@
-export([default_keepalive/0]).
-export([init/4]).
-export([switch_transport/3]).
--export([handle/4]).
--export([handle_continue/5]).
+-export([handle/5]).
+-export([handle_continue/6]).
-export([update_flow/4]).
-export([closing/4]).
-export([close/4]).
@@ -110,10 +110,7 @@
%% the idea, that's why the main map has the ID as key. Then we also
%% have a Ref->ID index for faster lookup when we only have the Ref.
streams = #{} :: #{cow_http2:streamid() => #stream{}},
- stream_refs = #{} :: #{reference() => cow_http2:streamid()},
-
- %% We must queue commands when parsing the incoming data.
- commands_queue = [] :: [{set_cookie, iodata(), iodata(), cow_http:status(), cow_http:headers()}]
+ stream_refs = #{} :: #{reference() => cow_http2:streamid()}
}).
check_options(Opts) ->
@@ -131,6 +128,8 @@ do_check_options([Opt={content_handlers, Handlers}|Opts]) ->
ok -> do_check_options(Opts);
error -> {error, {options, {http2, Opt}}}
end;
+do_check_options([{cookie_ignore_informational, B}|Opts]) when is_boolean(B) ->
+ do_check_options(Opts);
do_check_options([{flow, InitialFlow}|Opts]) when is_integer(InitialFlow), InitialFlow > 0 ->
do_check_options(Opts);
do_check_options([{keepalive, infinity}|Opts]) ->
@@ -188,35 +187,23 @@ init(_ReplyTo, Socket, Transport, Opts0) ->
switch_transport(Transport, Socket, State) ->
State#http2_state{socket=Socket, transport=Transport}.
-%% This function is called before returning from handle/4.
-handle_ret(CommandOrCommands, #http2_state{commands_queue=[]}) ->
- empty_commands_queue(CommandOrCommands);
-handle_ret(Commands, #http2_state{commands_queue=Queue}) when is_list(Commands) ->
- lists:reverse(Queue, empty_commands_queue(Commands));
-handle_ret(Command, #http2_state{commands_queue=Queue}) ->
- lists:reverse([empty_commands_queue(Command)|Queue]).
-
-empty_commands_queue([{state, State}|Tail]) -> [{state, State#http2_state{commands_queue=[]}}|Tail];
-empty_commands_queue([Command|Tail]) -> [Command|empty_commands_queue(Tail)];
-empty_commands_queue([]) -> [];
-empty_commands_queue({state, State}) -> {state, State#http2_state{commands_queue=[]}};
-empty_commands_queue(Command) -> Command.
-
-handle(Data, State=#http2_state{buffer=Buffer}, EvHandler, EvHandlerState) ->
+handle(Data, State=#http2_state{buffer=Buffer}, CookieStore, EvHandler, EvHandlerState) ->
parse(<< Buffer/binary, Data/binary >>, State#http2_state{buffer= <<>>},
- EvHandler, EvHandlerState).
+ CookieStore, EvHandler, EvHandlerState).
parse(Data, State0=#http2_state{status=preface, http2_machine=HTTP2Machine},
- EvHandler, EvHandlerState0) ->
+ CookieStore0, EvHandler, EvHandlerState0) ->
MaxFrameSize = cow_http2_machine:get_local_setting(max_frame_size, HTTP2Machine),
case cow_http2:parse(Data, MaxFrameSize) of
{ok, Frame, Rest} when element(1, Frame) =:= settings ->
- case frame(State0#http2_state{status=connected}, Frame, EvHandler, EvHandlerState0) of
- {Error={error, _}, EvHandlerState} -> {handle_ret(Error, State0), EvHandlerState};
- {State, EvHandlerState} -> parse(Rest, State, EvHandler, EvHandlerState)
+ case frame(State0#http2_state{status=connected}, Frame, CookieStore0, EvHandler, EvHandlerState0) of
+ {Error={error, _}, CookieStore, EvHandlerState} ->
+ {Error, CookieStore, EvHandlerState};
+ {State, CookieStore, EvHandlerState} ->
+ parse(Rest, State, CookieStore, EvHandler, EvHandlerState)
end;
more ->
- {handle_ret({state, State0#http2_state{buffer=Data}}, State0), EvHandlerState0};
+ {{state, State0#http2_state{buffer=Data}}, CookieStore0, EvHandlerState0};
%% Any error in the preface is converted to this specific error
%% to make debugging the problem easier (it's the server's fault).
_ ->
@@ -226,44 +213,49 @@ parse(Data, State0=#http2_state{status=preface, http2_machine=HTTP2Machine},
_ ->
'Invalid connection preface received. (RFC7540 3.5)'
end,
- {handle_ret(connection_error(State0, {connection_error, protocol_error, Reason}), State0),
- EvHandlerState0}
+ {connection_error(State0, {connection_error, protocol_error, Reason}),
+ CookieStore0, EvHandlerState0}
end;
parse(Data, State0=#http2_state{status=Status, http2_machine=HTTP2Machine, streams=Streams},
- EvHandler, EvHandlerState0) ->
+ CookieStore0, EvHandler, EvHandlerState0) ->
MaxFrameSize = cow_http2_machine:get_local_setting(max_frame_size, HTTP2Machine),
case cow_http2:parse(Data, MaxFrameSize) of
{ok, Frame, Rest} ->
- case frame(State0, Frame, EvHandler, EvHandlerState0) of
- {Error={error, _}, EvHandlerState} -> {handle_ret(Error, State0), EvHandlerState};
- {State, EvHandlerState} -> parse(Rest, State, EvHandler, EvHandlerState)
+ case frame(State0, Frame, CookieStore0, EvHandler, EvHandlerState0) of
+ {Error={error, _}, CookieStore, EvHandlerState} ->
+ {Error, CookieStore, EvHandlerState};
+ {State, CookieStore, EvHandlerState} ->
+ parse(Rest, State, CookieStore, EvHandler, EvHandlerState)
end;
{ignore, Rest} ->
case ignored_frame(State0) of
- Error = {error, _} -> {handle_ret(Error, State0), EvHandlerState0};
- State -> parse(Rest, State, EvHandler, EvHandlerState0)
+ Error = {error, _} ->
+ {Error, CookieStore0, EvHandlerState0};
+ State ->
+ parse(Rest, State, CookieStore0, EvHandler, EvHandlerState0)
end;
{stream_error, StreamID, Reason, Human, Rest} ->
parse(Rest, reset_stream(State0, StreamID, {stream_error, Reason, Human}),
- EvHandler, EvHandlerState0);
+ CookieStore0, EvHandler, EvHandlerState0);
Error = {connection_error, _, _} ->
- {handle_ret(connection_error(State0, Error), State0), EvHandlerState0};
+ {connection_error(State0, Error), CookieStore0, EvHandlerState0};
%% If we both received and sent a GOAWAY frame and there are no streams
%% currently running, we can close the connection immediately.
more when Status =/= connected, Streams =:= #{} ->
- {handle_ret([{state, State0#http2_state{buffer=Data, status=closing}}, close], State0),
- EvHandlerState0};
+ {[{state, State0#http2_state{buffer=Data, status=closing}}, close],
+ CookieStore0, EvHandlerState0};
%% Otherwise we enter the closing state.
more when Status =:= goaway ->
- {handle_ret([{state, State0#http2_state{buffer=Data, status=closing}}, closing(State0)], State0),
- EvHandlerState0};
+ {[{state, State0#http2_state{buffer=Data, status=closing}}, closing(State0)],
+ CookieStore0, EvHandlerState0};
more ->
- {handle_ret({state, State0#http2_state{buffer=Data}}, State0), EvHandlerState0}
+ {{state, State0#http2_state{buffer=Data}},
+ CookieStore0, EvHandlerState0}
end.
%% Frames received.
-frame(State=#http2_state{http2_machine=HTTP2Machine0}, Frame, EvHandler, EvHandlerState0) ->
+frame(State=#http2_state{http2_machine=HTTP2Machine0}, Frame, CookieStore, EvHandler, EvHandlerState0) ->
EvHandlerState = if
element(1, Frame) =:= headers; element(1, Frame) =:= push_promise ->
EvStreamID = element(2, Frame),
@@ -288,40 +280,49 @@ frame(State=#http2_state{http2_machine=HTTP2Machine0}, Frame, EvHandler, EvHandl
case cow_http2_machine:frame(Frame, HTTP2Machine0) of
%% We only update the connection's window when receiving a lingering data frame.
{ok, HTTP2Machine} when element(1, Frame) =:= data ->
- {update_window(State#http2_state{http2_machine=HTTP2Machine}), EvHandlerState};
+ {update_window(State#http2_state{http2_machine=HTTP2Machine}),
+ CookieStore, EvHandlerState};
{ok, HTTP2Machine} ->
{maybe_ack(State#http2_state{http2_machine=HTTP2Machine}, Frame),
- EvHandlerState};
+ CookieStore, EvHandlerState};
{ok, {data, StreamID, IsFin, Data}, HTTP2Machine} ->
data_frame(State#http2_state{http2_machine=HTTP2Machine}, StreamID, IsFin, Data,
- EvHandler, EvHandlerState);
+ CookieStore, EvHandler, EvHandlerState);
{ok, {headers, StreamID, IsFin, Headers, PseudoHeaders, BodyLen}, HTTP2Machine} ->
headers_frame(State#http2_state{http2_machine=HTTP2Machine},
StreamID, IsFin, Headers, PseudoHeaders, BodyLen,
- EvHandler, EvHandlerState);
+ CookieStore, EvHandler, EvHandlerState);
{ok, {trailers, StreamID, Trailers}, HTTP2Machine} ->
- trailers_frame(State#http2_state{http2_machine=HTTP2Machine},
- StreamID, Trailers, EvHandler, EvHandlerState);
+ {StateRet, EvHandlerStateRet} = trailers_frame(
+ State#http2_state{http2_machine=HTTP2Machine},
+ StreamID, Trailers, EvHandler, EvHandlerState),
+ {StateRet, CookieStore, EvHandlerStateRet};
{ok, {rst_stream, StreamID, Reason}, HTTP2Machine} ->
- rst_stream_frame(State#http2_state{http2_machine=HTTP2Machine},
- StreamID, Reason, EvHandler, EvHandlerState);
+ {StateRet, EvHandlerStateRet} = rst_stream_frame(
+ State#http2_state{http2_machine=HTTP2Machine},
+ StreamID, Reason, EvHandler, EvHandlerState),
+ {StateRet, CookieStore, EvHandlerStateRet};
{ok, {push_promise, StreamID, PromisedStreamID, Headers, PseudoHeaders}, HTTP2Machine} ->
- push_promise_frame(State#http2_state{http2_machine=HTTP2Machine},
+ {StateRet, EvHandlerStateRet} = push_promise_frame(
+ State#http2_state{http2_machine=HTTP2Machine},
StreamID, PromisedStreamID, Headers, PseudoHeaders,
- EvHandler, EvHandlerState);
+ EvHandler, EvHandlerState),
+ {StateRet, CookieStore, EvHandlerStateRet};
{ok, GoAway={goaway, _, _, _}, HTTP2Machine} ->
{goaway(State#http2_state{http2_machine=HTTP2Machine}, GoAway),
- EvHandlerState};
+ CookieStore, EvHandlerState};
{send, SendData, HTTP2Machine} ->
- send_data(maybe_ack(State#http2_state{http2_machine=HTTP2Machine}, Frame), SendData,
- EvHandler, EvHandlerState);
+ {StateRet, EvHandlerStateRet} = send_data(
+ maybe_ack(State#http2_state{http2_machine=HTTP2Machine}, Frame),
+ SendData, EvHandler, EvHandlerState),
+ {StateRet, CookieStore, EvHandlerStateRet};
{error, {stream_error, StreamID, Reason, Human}, HTTP2Machine} ->
{reset_stream(State#http2_state{http2_machine=HTTP2Machine},
StreamID, {stream_error, Reason, Human}),
- EvHandlerState};
+ CookieStore, EvHandlerState};
{error, Error={connection_error, _, _}, HTTP2Machine} ->
{connection_error(State#http2_state{http2_machine=HTTP2Machine}, Error),
- EvHandlerState}
+ CookieStore, EvHandlerState}
end.
maybe_ack(State=#http2_state{socket=Socket, transport=Transport}, Frame) ->
@@ -332,14 +333,18 @@ maybe_ack(State=#http2_state{socket=Socket, transport=Transport}, Frame) ->
end,
State.
-data_frame(State, StreamID, IsFin, Data, EvHandler, EvHandlerState0) ->
- case get_stream_by_id(State, StreamID) of
+data_frame(State0, StreamID, IsFin, Data, CookieStore0, EvHandler, EvHandlerState0) ->
+ case get_stream_by_id(State0, StreamID) of
Stream=#stream{tunnel=undefined} ->
- data_frame(State, StreamID, IsFin, Data, EvHandler, EvHandlerState0, Stream);
+ {State, EvHandlerState} = data_frame1(State0,
+ StreamID, IsFin, Data, EvHandler, EvHandlerState0, Stream),
+ {State, CookieStore0, EvHandlerState};
Stream=#stream{tunnel=#tunnel{protocol=Proto, protocol_state=ProtoState0}} ->
% %% @todo What about IsFin?
- {Commands, EvHandlerState} = Proto:handle(Data, ProtoState0, EvHandler, EvHandlerState0),
- tunnel_commands(Commands, Stream, State, EvHandler, EvHandlerState)
+ {Commands, CookieStore, EvHandlerState1} = Proto:handle(Data,
+ ProtoState0, CookieStore0, EvHandler, EvHandlerState0),
+ {State, EvHandlerState} = tunnel_commands(Commands, Stream, State0, EvHandler, EvHandlerState1),
+ {State, CookieStore, EvHandlerState}
end.
tunnel_commands(Command, Stream, State, EvHandler, EvHandlerState)
@@ -356,10 +361,6 @@ tunnel_commands([{state, ProtoState}|Tail], Stream=#stream{tunnel=Tunnel},
State, EvHandler, EvHandlerState) ->
tunnel_commands(Tail, Stream#stream{tunnel=Tunnel#tunnel{protocol_state=ProtoState}},
State, EvHandler, EvHandlerState);
-tunnel_commands([SetCookie={set_cookie, _, _, _, _}|Tail], Stream,
- State=#http2_state{commands_queue=Queue}, EvHandler, EvHandlerState) ->
- tunnel_commands(Tail, Stream, State#http2_state{commands_queue=[SetCookie|Queue]},
- EvHandler, EvHandlerState);
tunnel_commands([{error, _Reason}|_], #stream{id=StreamID},
State, _EvHandler, EvHandlerState) ->
{delete_stream(State, StreamID), EvHandlerState}.
@@ -372,7 +373,7 @@ continue_stream_ref(#http2_state{socket=#{handle_continue_stream_ref := Continue
continue_stream_ref(State, StreamRef) ->
stream_ref(State, StreamRef).
-data_frame(State0, StreamID, IsFin, Data, EvHandler, EvHandlerState0,
+data_frame1(State0, StreamID, IsFin, Data, EvHandler, EvHandlerState0,
Stream=#stream{ref=StreamRef, reply_to=ReplyTo, flow=Flow0, handler_state=Handlers0}) ->
{ok, Dec, Handlers} = gun_content_handler:handle(IsFin, Data, Handlers0),
Flow = case Flow0 of
@@ -409,11 +410,11 @@ data_frame(State0, StreamID, IsFin, Data, EvHandler, EvHandlerState0,
{maybe_delete_stream(State, StreamID, remote, IsFin), EvHandlerState}.
%% @todo Make separate functions for inform/connect/normal.
-headers_frame(State0=#http2_state{transport=Transport, opts=Opts,
- tunnel_transport=TunnelTransport, content_handlers=Handlers0, commands_queue=Commands},
+headers_frame(State=#http2_state{transport=Transport, opts=Opts,
+ tunnel_transport=TunnelTransport, content_handlers=Handlers0},
StreamID, IsFin, Headers, #{status := Status}, _BodyLen,
- EvHandler, EvHandlerState0) ->
- Stream = get_stream_by_id(State0, StreamID),
+ CookieStore0, EvHandler, EvHandlerState0) ->
+ Stream = get_stream_by_id(State, StreamID),
#stream{
ref=StreamRef,
reply_to=ReplyTo,
@@ -421,7 +422,8 @@ headers_frame(State0=#http2_state{transport=Transport, opts=Opts,
path=Path,
tunnel=Tunnel
} = Stream,
- State = State0#http2_state{commands_queue=[{set_cookie, Authority, Path, Status, Headers}|Commands]},
+ CookieStore = gun_cookies:set_cookie_header(scheme(State),
+ Authority, Path, Status, Headers, CookieStore0, Opts),
RealStreamRef = stream_ref(State, StreamRef),
if
Status >= 100, Status =< 199 ->
@@ -432,7 +434,7 @@ headers_frame(State0=#http2_state{transport=Transport, opts=Opts,
status => Status,
headers => Headers
}, EvHandlerState0),
- {State, EvHandlerState};
+ {State, CookieStore, EvHandlerState};
Status >= 200, Status =< 299, element(#tunnel.state, Tunnel) =:= requested ->
#tunnel{destination=Destination, info=TunnelInfo0} = Tunnel,
#{host := DestHost, port := DestPort} = Destination,
@@ -509,7 +511,7 @@ headers_frame(State0=#http2_state{transport=Transport, opts=Opts,
ReplyTo, OriginSocket, gun_tcp_proxy, ProtoOpts, EvHandler, EvHandlerState2),
{store_stream(State, Stream#stream{tunnel=Tunnel#tunnel{
info=TunnelInfo, protocol=Proto, protocol_state=ProtoState}}),
- EvHandlerState};
+ CookieStore, EvHandlerState};
true ->
ReplyTo ! {gun_response, self(), RealStreamRef, IsFin, Status, Headers},
EvHandlerState1 = EvHandler:response_headers(#{
@@ -532,7 +534,7 @@ headers_frame(State0=#http2_state{transport=Transport, opts=Opts,
%% @todo Disable the tunnel if any.
{maybe_delete_stream(store_stream(State, Stream#stream{handler_state=Handlers}),
StreamID, remote, IsFin),
- EvHandlerState}
+ CookieStore, EvHandlerState}
end.
trailers_frame(State, StreamID, Trailers, EvHandler, EvHandlerState0) ->
@@ -612,17 +614,17 @@ ignored_frame(State=#http2_state{http2_machine=HTTP2Machine0}) ->
end.
%% We always pass handle_continue messages to the tunnel.
-handle_continue(ContinueStreamRef, Msg, State0, EvHandler, EvHandlerState0) ->
+handle_continue(ContinueStreamRef, Msg, State0, CookieStore0, EvHandler, EvHandlerState0) ->
StreamRef = case ContinueStreamRef of
[SR|_] -> SR;
_ -> ContinueStreamRef
end,
case get_stream_by_ref(State0, StreamRef) of
Stream=#stream{tunnel=#tunnel{protocol=Proto, protocol_state=ProtoState0}} ->
- {Commands, EvHandlerState1} = Proto:handle_continue(ContinueStreamRef,
- Msg, ProtoState0, EvHandler, EvHandlerState0),
+ {Commands, CookieStore, EvHandlerState1} = Proto:handle_continue(ContinueStreamRef,
+ Msg, ProtoState0, CookieStore0, EvHandler, EvHandlerState0),
{State, EvHandlerState} = tunnel_commands(Commands, Stream, State0, EvHandler, EvHandlerState1),
- {handle_ret({state, State}, State), EvHandlerState}
+ {{state, State}, CookieStore, EvHandlerState}
%% The stream may have ended while TLS was being decoded. @todo What should we do?
% error ->
% {error_stream_not_found(State, StreamRef, ReplyTo), EvHandlerState0}
@@ -867,14 +869,9 @@ request(State, RealStreamRef=[StreamRef|_], ReplyTo, Method, _Host, _Port,
initial_flow(infinity, #{flow := InitialFlow}) -> InitialFlow;
initial_flow(InitialFlow, _) -> InitialFlow.
-prepare_headers(#http2_state{transport=Transport}, Method, Host0, Port, Path, Headers0, CookieStore0) ->
- Scheme = case Transport of
- gun_tls -> <<"https">>;
- gun_tls_proxy -> <<"https">>;
- gun_tcp -> <<"http">>;
- gun_tcp_proxy -> <<"http">>;
- gun_tls_proxy_http2_connect -> <<"http">>
- end,
+prepare_headers(State=#http2_state{transport=Transport},
+ Method, Host0, Port, Path, Headers0, CookieStore0) ->
+ Scheme = scheme(State),
Authority = case lists:keyfind(<<"host">>, 1, Headers0) of
{_, Host} -> Host;
_ -> gun_http:host_header(Transport, Host0, Port)
@@ -898,6 +895,15 @@ prepare_headers(#http2_state{transport=Transport}, Method, Host0, Port, Path, He
},
{ok, PseudoHeaders, Headers, CookieStore}.
+scheme(#http2_state{transport=Transport}) ->
+ case Transport of
+ gun_tls -> <<"https">>;
+ gun_tls_proxy -> <<"https">>;
+ gun_tcp -> <<"http">>;
+ gun_tcp_proxy -> <<"http">>;
+ gun_tls_proxy_http2_connect -> <<"http">>
+ end.
+
%% @todo Make all calls go through this clause.
data(State=#http2_state{http2_machine=HTTP2Machine}, StreamRef, ReplyTo, IsFin, Data,
EvHandler, EvHandlerState) when is_reference(StreamRef) ->
diff --git a/src/gun_protocols.erl b/src/gun_protocols.erl
index 65d5211..d297019 100644
--- a/src/gun_protocols.erl
+++ b/src/gun_protocols.erl
@@ -49,11 +49,23 @@ handler_and_opts(ProtocolName, Opts) ->
{Protocol, maps:get(Protocol:opts_name(), Opts, #{})}.
-spec negotiated({ok, binary()} | {error, protocol_not_negotiated}, gun:protocols())
- -> http | http2 | raw | socks.
-negotiated({ok, <<"h2">>}, _) -> http2;
-negotiated({ok, <<"http/1.1">>}, _) -> http;
-negotiated({error, protocol_not_negotiated}, [Protocol]) -> Protocol;
-negotiated({error, protocol_not_negotiated}, _) -> http.
+ -> gun:protocol().
+negotiated({ok, <<"h2">>}, Protocols) ->
+ lists:foldl(fun
+ (E = http2, _) -> E;
+ (E = {http2, _}, _) -> E;
+ (_, Acc) -> Acc
+ end, http2, Protocols);
+negotiated({ok, <<"http/1.1">>}, Protocols) ->
+ lists:foldl(fun
+ (E = http, _) -> E;
+ (E = {http, _}, _) -> E;
+ (_, Acc) -> Acc
+ end, http, Protocols);
+negotiated({error, protocol_not_negotiated}, [Protocol]) ->
+ Protocol;
+negotiated({error, protocol_not_negotiated}, _) ->
+ http.
-spec stream_ref(gun:protocol()) -> undefined | gun:stream_ref().
stream_ref({_, ProtocolOpts}) -> maps:get(stream_ref, ProtocolOpts, undefined);
diff --git a/src/gun_raw.erl b/src/gun_raw.erl
index 8d0aa42..11b875c 100644
--- a/src/gun_raw.erl
+++ b/src/gun_raw.erl
@@ -19,7 +19,7 @@
-export([opts_name/0]).
-export([has_keepalive/0]).
-export([init/4]).
--export([handle/4]).
+-export([handle/5]).
-export([closing/4]).
-export([close/4]).
-export([data/7]).
@@ -44,10 +44,10 @@ init(ReplyTo, Socket, Transport, Opts) ->
StreamRef = maps:get(stream_ref, Opts, undefined),
{connected_data_only, #raw_state{ref=StreamRef, reply_to=ReplyTo, socket=Socket, transport=Transport}}.
-handle(Data, State=#raw_state{ref=StreamRef, reply_to=ReplyTo}, _, EvHandlerState) ->
+handle(Data, State=#raw_state{ref=StreamRef, reply_to=ReplyTo}, CookieStore, _, EvHandlerState) ->
%% When we take over the entire connection there is no stream reference.
ReplyTo ! {gun_data, self(), StreamRef, nofin, Data},
- {{state, State}, EvHandlerState}.
+ {{state, State}, CookieStore, EvHandlerState}.
%% We can always close immediately.
closing(_, _, _, EvHandlerState) ->
diff --git a/src/gun_socks.erl b/src/gun_socks.erl
index 752daec..5c89e26 100644
--- a/src/gun_socks.erl
+++ b/src/gun_socks.erl
@@ -20,7 +20,7 @@
-export([has_keepalive/0]).
-export([init/4]).
-export([switch_transport/3]).
--export([handle/4]).
+-export([handle/5]).
-export([closing/4]).
-export([close/4]).
%% @todo down
@@ -99,8 +99,8 @@ init(ReplyTo, Socket, Transport, Opts) ->
switch_transport(Transport, Socket, State) ->
State#socks_state{socket=Socket, transport=Transport}.
-handle(Data, State, _, EvHandlerState) ->
- {handle(Data, State), EvHandlerState}.
+handle(Data, State, CookieStore, _, EvHandlerState) ->
+ {handle(Data, State), CookieStore, EvHandlerState}.
%% No authentication.
handle(<<5, 0>>, State=#socks_state{version=5, status=auth_method_select}) ->
diff --git a/src/gun_tunnel.erl b/src/gun_tunnel.erl
index a1435f3..7c29684 100644
--- a/src/gun_tunnel.erl
+++ b/src/gun_tunnel.erl
@@ -18,8 +18,8 @@
-module(gun_tunnel).
-export([init/6]).
--export([handle/4]).
--export([handle_continue/5]).
+-export([handle/5]).
+-export([handle_continue/6]).
-export([update_flow/4]).
-export([closing/4]).
-export([close/4]).
@@ -87,10 +87,7 @@
%% We keep the new information to provide it in TunnelInfo of
%% the new protocol when the switch occurs.
protocol_origin = undefined :: undefined
- | {origin, binary(), inet:hostname() | inet:ip_address(), inet:port_number(), connect | socks5},
-
- %% We must queue some commands given by the sub-protocol.
- commands_queue = [] :: [{set_cookie, iodata(), iodata(), cow_http:status(), cow_http:headers()}]
+ | {origin, binary(), inet:hostname() | inet:ip_address(), inet:port_number(), connect | socks5}
}).
%% Socket is the "origin socket" and Transport the "origin transport".
@@ -158,20 +155,21 @@ init(ReplyTo, OriginSocket, OriginTransport, Opts=#{stream_ref := StreamRef, tun
%% or we decrypt it and pass it via handle_continue for TLS.
handle(Data, State0=#tunnel_state{transport=gun_tcp_proxy,
protocol=Proto, protocol_state=ProtoState0},
- EvHandler, EvHandlerState0) ->
- {Commands, EvHandlerState1} = Proto:handle(Data, ProtoState0, EvHandler, EvHandlerState0),
+ CookieStore0, EvHandler, EvHandlerState0) ->
+ {Commands, CookieStore, EvHandlerState1} = Proto:handle(
+ Data, ProtoState0, CookieStore0, EvHandler, EvHandlerState0),
{State, EvHandlerState} = commands(Commands, State0, EvHandler, EvHandlerState1),
- {ret({state, State}, State), EvHandlerState};
+ {{state, State}, CookieStore, EvHandlerState};
handle(Data, State=#tunnel_state{transport=gun_tls_proxy,
socket=ProxyPid, tls_origin_socket=OriginSocket},
- _EvHandler, EvHandlerState) ->
+ CookieStore, _EvHandler, EvHandlerState) ->
%% When we receive a DATA frame that contains TLS-encoded data,
%% we must first forward it to the ProxyPid to be decoded. The
%% Gun process will receive it back as a tls_proxy_http2_connect
%% message and forward it to the right stream via the handle_continue
%% callback.
ProxyPid ! {tls_proxy_http2_connect, OriginSocket, Data},
- {ret({state, State}, State), EvHandlerState}.
+ {{state, State}, CookieStore, EvHandlerState}.
%% This callback will only be called for TLS.
%%
@@ -180,18 +178,18 @@ handle(Data, State=#tunnel_state{transport=gun_tls_proxy,
handle_continue(ContinueStreamRef, {gun_tls_proxy, ProxyPid, {ok, Negotiated},
{handle_continue, _, HandshakeEvent, Protocols}},
State=#tunnel_state{socket=ProxyPid, stream_ref=StreamRef, opts=Opts},
- EvHandler, EvHandlerState0)
+ CookieStore, EvHandler, EvHandlerState0)
when is_reference(ContinueStreamRef) ->
#{reply_to := ReplyTo} = HandshakeEvent,
NewProtocol = gun_protocols:negotiated(Negotiated, Protocols),
{Proto, ProtoOpts} = gun_protocols:handler_and_opts(NewProtocol, Opts),
EvHandlerState1 = EvHandler:tls_handshake_end(HandshakeEvent#{
socket => ProxyPid,
- protocol => NewProtocol
+ protocol => Proto:name()
}, EvHandlerState0),
EvHandlerState = EvHandler:protocol_changed(#{
stream_ref => StreamRef,
- protocol => NewProtocol
+ protocol => Proto:name()
}, EvHandlerState1),
%% @todo Terminate the current protocol or something?
OriginSocket = #{
@@ -202,11 +200,11 @@ handle_continue(ContinueStreamRef, {gun_tls_proxy, ProxyPid, {ok, Negotiated},
{_, ProtoState} = Proto:init(ReplyTo, OriginSocket, gun_tcp_proxy,
ProtoOpts#{stream_ref => StreamRef, tunnel_transport => tls}),
ReplyTo ! {gun_tunnel_up, self(), StreamRef, Proto:name()},
- {ret({state, State#tunnel_state{protocol=Proto, protocol_state=ProtoState}}, State),
- EvHandlerState};
+ {{state, State#tunnel_state{protocol=Proto, protocol_state=ProtoState}},
+ CookieStore, EvHandlerState};
handle_continue(ContinueStreamRef, {gun_tls_proxy, ProxyPid, {error, Reason},
{handle_continue, _, HandshakeEvent, _}},
- State=#tunnel_state{socket=ProxyPid}, EvHandler, EvHandlerState0)
+ #tunnel_state{socket=ProxyPid}, CookieStore, EvHandler, EvHandlerState0)
when is_reference(ContinueStreamRef) ->
EvHandlerState = EvHandler:tls_handshake_end(HandshakeEvent#{
error => Reason
@@ -221,44 +219,45 @@ handle_continue(ContinueStreamRef, {gun_tls_proxy, ProxyPid, {error, Reason},
%% receives a TCP segment with the FIN bit set sends a DATA frame with
%% the END_STREAM flag set. Note that the final TCP segment or DATA
%% frame could be empty.
- {ret({error, Reason}, State), EvHandlerState};
+ {{error, Reason}, CookieStore, EvHandlerState};
%% Send the data. This causes TLS to encrypt the data and send it to the inner layer.
handle_continue(ContinueStreamRef, {data, _ReplyTo, _StreamRef, IsFin, Data},
- State=#tunnel_state{}, _EvHandler, EvHandlerState)
+ #tunnel_state{}, CookieStore, _EvHandler, EvHandlerState)
when is_reference(ContinueStreamRef) ->
- {ret({send, IsFin, Data}, State), EvHandlerState};
+ {{send, IsFin, Data}, CookieStore, EvHandlerState};
handle_continue(ContinueStreamRef, {tls_proxy, ProxyPid, Data},
State0=#tunnel_state{socket=ProxyPid, protocol=Proto, protocol_state=ProtoState},
- EvHandler, EvHandlerState0)
+ CookieStore0, EvHandler, EvHandlerState0)
when is_reference(ContinueStreamRef) ->
- {Commands, EvHandlerState1} = Proto:handle(Data, ProtoState, EvHandler, EvHandlerState0),
+ {Commands, CookieStore, EvHandlerState1} = Proto:handle(
+ Data, ProtoState, CookieStore0, EvHandler, EvHandlerState0),
{State, EvHandlerState} = commands(Commands, State0, EvHandler, EvHandlerState1),
- {ret({state, State}, State), EvHandlerState};
+ {{state, State}, CookieStore, EvHandlerState};
handle_continue(ContinueStreamRef, {tls_proxy_closed, ProxyPid},
- State=#tunnel_state{socket=ProxyPid}, _EvHandler, EvHandlerState0)
+ #tunnel_state{socket=ProxyPid}, CookieStore, _EvHandler, EvHandlerState0)
when is_reference(ContinueStreamRef) ->
%% @todo All sub-streams must produce a stream_error.
- {ret({error, closed}, State), EvHandlerState0};
+ {{error, closed}, CookieStore, EvHandlerState0};
handle_continue(ContinueStreamRef, {tls_proxy_error, ProxyPid, Reason},
- State=#tunnel_state{socket=ProxyPid}, _EvHandler, EvHandlerState0)
+ #tunnel_state{socket=ProxyPid}, CookieStore, _EvHandler, EvHandlerState0)
when is_reference(ContinueStreamRef) ->
%% @todo All sub-streams must produce a stream_error.
- {ret({error, Reason}, State), EvHandlerState0};
+ {{error, Reason}, CookieStore, EvHandlerState0};
%% We always dereference the ContinueStreamRef because it includes a
%% reference() for Socks layers too.
%%
%% @todo Assert StreamRef to be our reference().
handle_continue([_StreamRef|ContinueStreamRef0], Msg,
State0=#tunnel_state{protocol=Proto, protocol_state=ProtoState},
- EvHandler, EvHandlerState0) ->
+ CookieStore0, EvHandler, EvHandlerState0) ->
ContinueStreamRef = case ContinueStreamRef0 of
[CSR] -> CSR;
_ -> ContinueStreamRef0
end,
- {Commands, EvHandlerState1} = Proto:handle_continue(ContinueStreamRef,
- Msg, ProtoState, EvHandler, EvHandlerState0),
+ {Commands, CookieStore, EvHandlerState1} = Proto:handle_continue(
+ ContinueStreamRef, Msg, ProtoState, CookieStore0, EvHandler, EvHandlerState0),
{State, EvHandlerState} = commands(Commands, State0, EvHandler, EvHandlerState1),
- {ret({state, State}, State), EvHandlerState}.
+ {{state, State}, CookieStore, EvHandlerState}.
%% @todo This function will need EvHandler/EvHandlerState?
update_flow(State0=#tunnel_state{protocol=Proto, protocol_state=ProtoState},
@@ -266,11 +265,11 @@ update_flow(State0=#tunnel_state{protocol=Proto, protocol_state=ProtoState},
StreamRef = maybe_dereference(State0, StreamRef0),
Commands = Proto:update_flow(ProtoState, ReplyTo, StreamRef, Inc),
{State, undefined} = commands(Commands, State0, undefined, undefined),
- ret({state, State}, State).
+ {state, State}.
-closing(_Reason, State, _EvHandler, EvHandlerState) ->
+closing(_Reason, _State, _EvHandler, EvHandlerState) ->
%% @todo Graceful shutdown must be propagated to tunnels.
- {ret([], State), EvHandlerState}.
+ {[], EvHandlerState}.
close(_Reason, _State, _EvHandler, EvHandlerState) ->
%% @todo Closing must be propagated to tunnels.
@@ -346,14 +345,14 @@ cancel(State0=#tunnel_state{protocol=Proto, protocol_state=ProtoState},
StreamRef = maybe_dereference(State0, StreamRef0),
{Commands, EvHandlerState1} = Proto:cancel(ProtoState, StreamRef, ReplyTo, EvHandler, EvHandlerState0),
{State, EvHandlerState} = commands(Commands, State0, EvHandler, EvHandlerState1),
- {ret({state, State}, State), EvHandlerState}.
+ {{state, State}, EvHandlerState}.
timeout(State=#tunnel_state{protocol=Proto, protocol_state=ProtoState0}, Msg, TRef) ->
case Proto:timeout(ProtoState0, Msg, TRef) of
{state, ProtoState} ->
- ret({state, State#tunnel_state{protocol_state=ProtoState}}, State);
+ {state, State#tunnel_state{protocol_state=ProtoState}};
Other ->
- ret(Other, State)
+ Other
end.
stream_info(#tunnel_state{transport=Transport0, stream_ref=TunnelStreamRef, reply_to=ReplyTo,
@@ -415,9 +414,9 @@ tunneled_name(#tunnel_state{tunnel_protocol=TunnelProto}, false) ->
tunneled_name(#tunnel_state{protocol=Proto}, _) ->
Proto:name().
-down(State) ->
+down(_State) ->
%% @todo Tunnels must be included in the gun_down message.
- ret([], State).
+ [].
ws_upgrade(State=#tunnel_state{info=TunnelInfo, protocol=Proto, protocol_state=ProtoState0},
StreamRef0, ReplyTo, _, _, Path, Headers, WsOpts,
@@ -439,7 +438,7 @@ ws_send(Frames, State0=#tunnel_state{protocol=Proto, protocol_state=ProtoState},
{Commands, EvHandlerState1} = Proto:ws_send(Frames,
ProtoState, StreamRef, ReplyTo, EvHandler, EvHandlerState0),
{State, EvHandlerState} = commands(Commands, State0, EvHandler, EvHandlerState1),
- {ret({state, State}, State), EvHandlerState}.
+ {{state, State}, EvHandlerState}.
%% Internal.
@@ -449,11 +448,6 @@ commands([], State, _, EvHandlerState) ->
{State, EvHandlerState};
commands([{state, ProtoState}|Tail], State, EvHandler, EvHandlerState) ->
commands(Tail, State#tunnel_state{protocol_state=ProtoState}, EvHandler, EvHandlerState);
-commands([SetCookie={set_cookie, _, _, _, _}|Tail],
- State=#tunnel_state{commands_queue=Queue},
- EvHandler, EvHandlerState) ->
- commands(Tail, State#tunnel_state{commands_queue=[SetCookie|Queue]},
- EvHandler, EvHandlerState);
%% @todo What to do about IsFin?
commands([{send, _IsFin, Data}|Tail],
State=#tunnel_state{socket=Socket, transport=Transport},
@@ -604,17 +598,3 @@ outer_stream_ref(StreamRef) when is_list(StreamRef) ->
lists:last(StreamRef);
outer_stream_ref(StreamRef) ->
StreamRef.
-
-%% This function is called before returning commands.
-ret(CommandOrCommands, #tunnel_state{commands_queue=[]}) ->
- empty_commands_queue(CommandOrCommands);
-ret(Commands, #tunnel_state{commands_queue=Queue}) when is_list(Commands) ->
- lists:reverse(Queue, empty_commands_queue(Commands));
-ret(Command, #tunnel_state{commands_queue=Queue}) ->
- lists:reverse([empty_commands_queue(Command)|Queue]).
-
-empty_commands_queue([{state, State}|Tail]) -> [{state, State#tunnel_state{commands_queue=[]}}|Tail];
-empty_commands_queue([Command|Tail]) -> [Command|empty_commands_queue(Tail)];
-empty_commands_queue([]) -> [];
-empty_commands_queue({state, State}) -> {state, State#tunnel_state{commands_queue=[]}};
-empty_commands_queue(Command) -> Command.
diff --git a/src/gun_ws.erl b/src/gun_ws.erl
index cd81d65..f413f94 100644
--- a/src/gun_ws.erl
+++ b/src/gun_ws.erl
@@ -20,7 +20,7 @@
-export([has_keepalive/0]).
-export([default_keepalive/0]).
-export([init/4]).
--export([handle/4]).
+-export([handle/5]).
-export([update_flow/4]).
-export([closing/4]).
-export([close/4]).
@@ -101,6 +101,10 @@ init(ReplyTo, Socket, Transport, #{stream_ref := StreamRef, headers := Headers,
socket=Socket, transport=Transport, opts=Opts, extensions=Extensions,
flow=InitialFlow, handler=Handler, handler_state=HandlerState}}.
+handle(Data, State, CookieStore, EvHandler, EvHandlerState0) ->
+ {Commands, EvHandlerState} = handle(Data, State, EvHandler, EvHandlerState0),
+ {Commands, CookieStore, EvHandlerState}.
+
%% Do not handle anything if we received a close frame.
%% Initiate or terminate the closing state depending on whether we sent a close yet.
handle(_, State=#ws_state{in=close, out=close}, _, EvHandlerState) ->
diff --git a/test/rfc6265bis_SUITE.erl b/test/rfc6265bis_SUITE.erl
index 8beee58..b4b443d 100644
--- a/test/rfc6265bis_SUITE.erl
+++ b/test/rfc6265bis_SUITE.erl
@@ -158,9 +158,8 @@ do_informational_set_cookie(Config, Boolean) ->
Protocol = config(protocol, Config),
{ok, ConnPid} = gun:open("localhost", config(port, Config), #{
transport => config(transport, Config),
- protocols => [Protocol],
- cookie_store => gun_cookies_list:init(),
- cookie_ignore_informational => Boolean
+ protocols => [{Protocol, #{cookie_ignore_informational => Boolean}}],
+ cookie_store => gun_cookies_list:init()
}),
{ok, Protocol} = gun:await_up(ConnPid),
StreamRef1 = gun:get(ConnPid, "/informational"),
@@ -176,13 +175,46 @@ do_informational_set_cookie(Config, Boolean) ->
gun:close(ConnPid),
Res.
-set_cookie_connect(Config) ->
+set_cookie_connect_tcp(Config) ->
doc("Cookies may also be set in responses going through CONNECT tunnels."),
Transport = config(transport, Config),
Protocol = config(protocol, Config),
- {ok, ProxyPid, ProxyPort} = event_SUITE:do_proxy_start(Protocol, Transport),
+ {ok, ProxyPid, ProxyPort} = event_SUITE:do_proxy_start(Protocol, tcp),
{ok, ConnPid} = gun:open("localhost", ProxyPort, #{
+ transport => tcp,
+ protocols => [Protocol],
+ cookie_store => gun_cookies_list:init()
+ }),
+ {ok, Protocol} = gun:await_up(ConnPid),
+ tunnel_SUITE:do_handshake_completed(Protocol, ProxyPid),
+ StreamRef1 = gun:connect(ConnPid, #{
+ host => "localhost",
+ port => config(port, Config),
transport => Transport,
+ protocols => [Protocol]
+ }),
+ %% @todo _IsFin is 'fin' for HTTP and 'nofin' for HTTP/2...
+ {response, _IsFin, 200, _} = gun:await(ConnPid, StreamRef1),
+ {up, Protocol} = gun:await(ConnPid, StreamRef1),
+ StreamRef2 = gun:get(ConnPid, "/cookie-set?prefix", #{
+ <<"please-set-cookie">> => <<"a=b">>
+ }, #{tunnel => StreamRef1}),
+ {response, fin, 204, Headers2} = gun:await(ConnPid, StreamRef2),
+ ct:log("Headers2:~n~p", [Headers2]),
+ StreamRef3 = gun:get(ConnPid, "/cookie-echo", [], #{tunnel => StreamRef1}),
+ {response, nofin, 200, _} = gun:await(ConnPid, StreamRef3),
+ {ok, Body3} = gun:await_body(ConnPid, StreamRef3),
+ ct:log("Body3:~n~p", [Body3]),
+ [{<<"a">>, <<"b">>}] = cow_cookie:parse_cookie(Body3),
+ gun:close(ConnPid).
+
+set_cookie_connect_tls(Config) ->
+ doc("Cookies may also be set in responses going through CONNECT tunnels."),
+ Transport = config(transport, Config),
+ Protocol = config(protocol, Config),
+ {ok, ProxyPid, ProxyPort} = event_SUITE:do_proxy_start(Protocol, tls),
+ {ok, ConnPid} = gun:open("localhost", ProxyPort, #{
+ transport => tls,
protocols => [Protocol],
cookie_store => gun_cookies_list:init()
}),