1%% Copyright (c) 2014-2018, 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
15-module(gun_http).
16
17-export([check_options/1]).
18-export([name/0]).
19-export([init/4]).
20-export([handle/2]).
21-export([close/1]).
22-export([keepalive/1]).
23-export([request/8]).
24-export([request/9]).
25-export([data/5]).
26-export([connect/5]).
27-export([cancel/3]).
28-export([down/1]).
29-export([ws_upgrade/7]).
30
31-type io() :: head | {body, non_neg_integer()} | body_close | body_chunked | body_trailer.
32
33%% @todo Make that a record.
34-type connect_info() :: {connect, reference(), gun:connect_destination()}.
35
36%% @todo Make that a record.
37-type websocket_info() :: {websocket, reference(), binary(), [binary()], gun:ws_opts()}. %% key, extensions, options
38
39-record(stream, {
40	ref :: reference() | connect_info() | websocket_info(),
41	reply_to :: pid(),
42	method :: binary(),
43	is_alive :: boolean(),
44	handler_state :: undefined | gun_content_handler:state()
45}).
46
47-record(http_state, {
48	owner :: pid(),
49	socket :: inet:socket() | ssl:sslsocket(),
50	transport :: module(),
51	version = 'HTTP/1.1' :: cow_http:version(),
52	content_handlers :: gun_content_handler:opt(),
53	connection = keepalive :: keepalive | close,
54	buffer = <<>> :: binary(),
55	streams = [] :: [#stream{}],
56	in = head :: io(),
57	in_state = {0, 0} :: {non_neg_integer(), non_neg_integer()},
58	out = head :: io(),
59	transform_header_name :: fun((binary()) -> binary())
60}).
61
62check_options(Opts) ->
63	do_check_options(maps:to_list(Opts)).
64
65do_check_options([]) ->
66	ok;
67do_check_options([Opt={content_handlers, Handlers}|Opts]) ->
68	case gun_content_handler:check_option(Handlers) of
69		ok -> do_check_options(Opts);
70		error -> {error, {options, {http, Opt}}}
71	end;
72do_check_options([{keepalive, infinity}|Opts]) ->
73	do_check_options(Opts);
74do_check_options([{keepalive, K}|Opts]) when is_integer(K), K > 0 ->
75	do_check_options(Opts);
76do_check_options([{transform_header_name, F}|Opts]) when is_function(F) ->
77	do_check_options(Opts);
78do_check_options([{version, V}|Opts]) when V =:= 'HTTP/1.1'; V =:= 'HTTP/1.0' ->
79	do_check_options(Opts);
80do_check_options([Opt|_]) ->
81	{error, {options, {http, Opt}}}.
82
83name() -> http.
84
85init(Owner, Socket, Transport, Opts) ->
86	Version = maps:get(version, Opts, 'HTTP/1.1'),
87	Handlers = maps:get(content_handlers, Opts, [gun_data_h]),
88	TransformHeaderName = maps:get(transform_header_name, Opts, fun (N) -> N end),
89	#http_state{owner=Owner, socket=Socket, transport=Transport, version=Version,
90		content_handlers=Handlers, transform_header_name=TransformHeaderName}.
91
92%% Stop looping when we got no more data.
93handle(<<>>, State) ->
94	{state, State};
95%% Close when server responds and we don't have any open streams.
96handle(_, #http_state{streams=[]}) ->
97	close;
98%% Wait for the full response headers before trying to parse them.
99handle(Data, State=#http_state{in=head, buffer=Buffer}) ->
100	Data2 = << Buffer/binary, Data/binary >>,
101	case binary:match(Data2, <<"\r\n\r\n">>) of
102		nomatch -> {state, State#http_state{buffer=Data2}};
103		{_, _} -> handle_head(Data2, State#http_state{buffer= <<>>})
104	end;
105%% Everything sent to the socket until it closes is part of the response body.
106handle(Data, State=#http_state{in=body_close}) ->
107	{state, send_data_if_alive(Data, State, nofin)};
108%% Chunked transfer-encoding may contain both data and trailers.
109handle(Data, State=#http_state{in=body_chunked, in_state=InState,
110		buffer=Buffer, connection=Conn}) ->
111	Buffer2 = << Buffer/binary, Data/binary >>,
112	case cow_http_te:stream_chunked(Buffer2, InState) of
113		more ->
114			{state, State#http_state{buffer=Buffer2}};
115		{more, Data2, InState2} ->
116			{state, send_data_if_alive(Data2,
117				State#http_state{buffer= <<>>, in_state=InState2},
118				nofin)};
119		{more, Data2, Length, InState2} when is_integer(Length) ->
120			%% @todo See if we can recv faster than one message at a time.
121			{state, send_data_if_alive(Data2,
122				State#http_state{buffer= <<>>, in_state=InState2},
123				nofin)};
124		{more, Data2, Rest, InState2} ->
125			%% @todo See if we can recv faster than one message at a time.
126			{state, send_data_if_alive(Data2,
127				State#http_state{buffer=Rest, in_state=InState2},
128				nofin)};
129		{done, HasTrailers, Rest} ->
130			IsFin = case HasTrailers of
131				trailers -> nofin;
132				no_trailers -> fin
133			end,
134			%% I suppose it doesn't hurt to append an empty binary.
135			State1 = send_data_if_alive(<<>>, State, IsFin),
136			case {HasTrailers, Conn} of
137				{trailers, _} ->
138					handle(Rest, State1#http_state{buffer = <<>>, in=body_trailer});
139				{no_trailers, keepalive} ->
140					handle(Rest, end_stream(State1#http_state{buffer= <<>>}));
141				{no_trailers, close} ->
142					close
143			end;
144		{done, Data2, HasTrailers, Rest} ->
145			IsFin = case HasTrailers of
146				trailers -> nofin;
147				no_trailers -> fin
148			end,
149			State1 = send_data_if_alive(Data2, State, IsFin),
150			case {HasTrailers, Conn} of
151				{trailers, _} ->
152					handle(Rest, State1#http_state{buffer = <<>>, in=body_trailer});
153				{no_trailers, keepalive} ->
154					handle(Rest, end_stream(State1#http_state{buffer= <<>>}));
155				{no_trailers, close} ->
156					close
157			end
158	end;
159handle(Data, State=#http_state{in=body_trailer, buffer=Buffer, connection=Conn,
160		streams=[#stream{ref=StreamRef, reply_to=ReplyTo}|_]}) ->
161	Data2 = << Buffer/binary, Data/binary >>,
162	case binary:match(Data2, <<"\r\n\r\n">>) of
163		nomatch -> {state, State#http_state{buffer=Data2}};
164		{_, _} ->
165			{Trailers, Rest} = cow_http:parse_headers(Data2),
166			%% @todo We probably want to pass this to gun_content_handler?
167			ReplyTo ! {gun_trailers, self(), stream_ref(StreamRef), Trailers},
168			case Conn of
169				keepalive ->
170					handle(Rest, end_stream(State#http_state{buffer= <<>>}));
171				close ->
172					close
173			end
174	end;
175%% We know the length of the rest of the body.
176handle(Data, State=#http_state{in={body, Length}, connection=Conn}) ->
177	DataSize = byte_size(Data),
178	if
179		%% More data coming.
180		DataSize < Length ->
181			{state, send_data_if_alive(Data,
182				State#http_state{in={body, Length - DataSize}},
183				nofin)};
184		%% Stream finished, no rest.
185		DataSize =:= Length ->
186			State1 = send_data_if_alive(Data, State, fin),
187			case Conn of
188				keepalive -> {state, end_stream(State1)};
189				close -> close
190			end;
191		%% Stream finished, rest.
192		true ->
193			<< Body:Length/binary, Rest/bits >> = Data,
194			State1 = send_data_if_alive(Body, State, fin),
195			case Conn of
196				keepalive -> handle(Rest, end_stream(State1));
197				close -> close
198			end
199	end.
200
201handle_head(Data, State=#http_state{socket=Socket, version=ClientVersion,
202		content_handlers=Handlers0, connection=Conn,
203		streams=[Stream=#stream{ref=StreamRef, reply_to=ReplyTo,
204			method=Method, is_alive=IsAlive}|Tail]}) ->
205	{Version, Status, _, Rest} = cow_http:parse_status_line(Data),
206	{Headers, Rest2} = cow_http:parse_headers(Rest),
207	case {Status, StreamRef} of
208		{101, {websocket, RealStreamRef, WsKey, WsExtensions, WsOpts}} ->
209			ws_handshake(Rest2, State, RealStreamRef, Headers, WsKey, WsExtensions, WsOpts);
210		{_, {connect, RealStreamRef, Destination}} when Status >= 200, Status < 300 ->
211			case IsAlive of
212				false ->
213					ok;
214				true ->
215					ReplyTo ! {gun_response, self(), RealStreamRef,
216						fin, Status, Headers},
217					ok
218			end,
219			%% We expect there to be no additional data after the CONNECT response.
220			<<>> = Rest2,
221			State2 = end_stream(State#http_state{streams=[Stream|Tail]}),
222			NewHost = maps:get(host, Destination),
223			NewPort = maps:get(port, Destination),
224			case Destination of
225				#{transport := tls} ->
226					TLSOpts = maps:get(tls_opts, Destination, []),
227					TLSTimeout = maps:get(tls_handshake_timeout, Destination, infinity),
228					case gun_tls:connect(Socket, TLSOpts, TLSTimeout) of
229						{ok, TLSSocket} ->
230							case ssl:negotiated_protocol(TLSSocket) of
231								{ok, <<"h2">>} ->
232									[{origin, <<"https">>, NewHost, NewPort, connect},
233										{switch_transport, gun_tls, TLSSocket},
234										{switch_protocol, gun_http2, State2}];
235								_ ->
236									[{state, State2#http_state{socket=TLSSocket, transport=gun_tls}},
237										{origin, <<"https">>, NewHost, NewPort, connect},
238										{switch_transport, gun_tls, TLSSocket}]
239							end;
240						Error ->
241							Error
242					end;
243				_ ->
244					case maps:get(protocols, Destination, [http]) of
245						[http] ->
246							[{state, State2},
247								{origin, <<"http">>, NewHost, NewPort, connect}];
248						[http2] ->
249							[{origin, <<"http">>, NewHost, NewPort, connect},
250								{switch_protocol, gun_http2, State2}]
251					end
252			end;
253		{_, _} when Status >= 100, Status =< 199 ->
254			ReplyTo ! {gun_inform, self(), stream_ref(StreamRef), Status, Headers},
255			handle(Rest2, State);
256		_ ->
257			In = response_io_from_headers(Method, Version, Status, Headers),
258			IsFin = case In of head -> fin; _ -> nofin end,
259			Handlers = case IsAlive of
260				false ->
261					undefined;
262				true ->
263					ReplyTo ! {gun_response, self(), stream_ref(StreamRef),
264						IsFin, Status, Headers},
265					case IsFin of
266						fin -> undefined;
267						nofin ->
268							gun_content_handler:init(ReplyTo, StreamRef,
269								Status, Headers, Handlers0)
270					end
271			end,
272			Conn2 = if
273				Conn =:= close -> close;
274				Version =:= 'HTTP/1.0' -> close;
275				ClientVersion =:= 'HTTP/1.0' -> close;
276				true -> conn_from_headers(Version, Headers)
277			end,
278			%% We always reset in_state even if not chunked.
279			if
280				IsFin =:= fin, Conn2 =:= close ->
281					close;
282				IsFin =:= fin ->
283					handle(Rest2, end_stream(State#http_state{in=In,
284						in_state={0, 0}, connection=Conn2,
285						streams=[Stream#stream{handler_state=Handlers}|Tail]}));
286				true ->
287					handle(Rest2, State#http_state{in=In,
288						in_state={0, 0}, connection=Conn2,
289						streams=[Stream#stream{handler_state=Handlers}|Tail]})
290			end
291	end.
292
293stream_ref({connect, StreamRef, _}) -> StreamRef;
294stream_ref({websocket, StreamRef, _, _, _}) -> StreamRef;
295stream_ref(StreamRef) -> StreamRef.
296
297send_data_if_alive(<<>>, State, nofin) ->
298	State;
299%% @todo What if we receive data when the HEAD method was used?
300send_data_if_alive(Data, State=#http_state{streams=[Stream=#stream{
301		is_alive=true, handler_state=Handlers0}|Tail]}, IsFin) ->
302	Handlers = gun_content_handler:handle(IsFin, Data, Handlers0),
303	State#http_state{streams=[Stream#stream{handler_state=Handlers}|Tail]};
304send_data_if_alive(_, State, _) ->
305	State.
306
307close(State=#http_state{in=body_close, streams=[_|Tail]}) ->
308	_ = send_data_if_alive(<<>>, State, fin),
309	close_streams(Tail);
310close(#http_state{streams=Streams}) ->
311	close_streams(Streams).
312
313close_streams([]) ->
314	ok;
315close_streams([#stream{is_alive=false}|Tail]) ->
316	close_streams(Tail);
317close_streams([#stream{ref=StreamRef, reply_to=ReplyTo}|Tail]) ->
318	ReplyTo ! {gun_error, self(), StreamRef, {closed,
319		"The connection was lost."}},
320	close_streams(Tail).
321
322%% We don't send a keep-alive when a CONNECT request was initiated.
323keepalive(State=#http_state{streams=[#stream{ref={connect, _, _}}]}) ->
324	State;
325%% We can only keep-alive by sending an empty line in-between streams.
326keepalive(State=#http_state{socket=Socket, transport=Transport, out=head}) ->
327	Transport:send(Socket, <<"\r\n">>),
328	State;
329keepalive(State) ->
330	State.
331
332request(State=#http_state{socket=Socket, transport=Transport, version=Version,
333		out=head}, StreamRef, ReplyTo, Method, Host, Port, Path, Headers) ->
334	Host2 = case Host of
335		{local, _SocketPath} -> <<>>;
336		Tuple when is_tuple(Tuple) -> inet:ntoa(Tuple);
337		_ -> Host
338	end,
339	Headers2 = lists:keydelete(<<"transfer-encoding">>, 1, Headers),
340	Headers3 = case lists:keymember(<<"host">>, 1, Headers) of
341		false -> [{<<"host">>, [Host2, $:, integer_to_binary(Port)]}|Headers2];
342		true -> Headers2
343	end,
344	%% We use Headers2 because this is the smallest list.
345	Conn = conn_from_headers(Version, Headers2),
346	Out = request_io_from_headers(Headers2),
347	Headers4 = case Out of
348		body_chunked when Version =:= 'HTTP/1.0' -> Headers3;
349		body_chunked -> [{<<"transfer-encoding">>, <<"chunked">>}|Headers3];
350		_ -> Headers3
351	end,
352	Headers5 = transform_header_names(State, Headers4),
353	Transport:send(Socket, cow_http:request(Method, Path, Version, Headers5)),
354	new_stream(State#http_state{connection=Conn, out=Out}, StreamRef, ReplyTo, Method).
355
356request(State=#http_state{socket=Socket, transport=Transport, version=Version,
357		out=head}, StreamRef, ReplyTo, Method, Host, Port, Path, Headers, Body) ->
358	Host2 = case Host of
359		{local, _SocketPath} -> <<>>;
360		Tuple when is_tuple(Tuple) -> inet:ntoa(Tuple);
361		_ -> Host
362	end,
363	Headers2 = lists:keydelete(<<"content-length">>, 1,
364		lists:keydelete(<<"transfer-encoding">>, 1, Headers)),
365	Headers3 = case lists:keymember(<<"host">>, 1, Headers) of
366		false -> [{<<"host">>, [Host2, $:, integer_to_binary(Port)]}|Headers2];
367		true -> Headers2
368	end,
369	Headers4 = transform_header_names(State, Headers3),
370	%% We use Headers2 because this is the smallest list.
371	Conn = conn_from_headers(Version, Headers2),
372	Transport:send(Socket, [
373		cow_http:request(Method, Path, Version, [
374			{<<"content-length">>, integer_to_binary(iolist_size(Body))}
375		|Headers4]),
376		Body]),
377	new_stream(State#http_state{connection=Conn}, StreamRef, ReplyTo, Method).
378
379transform_header_names(#http_state{transform_header_name = Fun}, Headers) ->
380	lists:keymap(Fun, 1, Headers).
381
382%% We are expecting a new stream.
383data(State=#http_state{out=head}, StreamRef, ReplyTo, _, _) ->
384	error_stream_closed(State, StreamRef, ReplyTo);
385%% There are no active streams.
386data(State=#http_state{streams=[]}, StreamRef, ReplyTo, _, _) ->
387	error_stream_not_found(State, StreamRef, ReplyTo);
388%% We can only send data on the last created stream.
389data(State=#http_state{socket=Socket, transport=Transport, version=Version,
390		out=Out, streams=Streams}, StreamRef, ReplyTo, IsFin, Data) ->
391	case lists:last(Streams) of
392		#stream{ref=StreamRef, is_alive=true} ->
393			DataLength = iolist_size(Data),
394			case Out of
395				body_chunked when Version =:= 'HTTP/1.1', IsFin =:= fin ->
396					case Data of
397						<<>> ->
398							Transport:send(Socket, cow_http_te:last_chunk());
399						_ ->
400							Transport:send(Socket, [
401								cow_http_te:chunk(Data),
402								cow_http_te:last_chunk()
403							])
404					end,
405					State#http_state{out=head};
406				body_chunked when Version =:= 'HTTP/1.1' ->
407					Transport:send(Socket, cow_http_te:chunk(Data)),
408					State;
409				{body, Length} when DataLength =< Length ->
410					Transport:send(Socket, Data),
411					Length2 = Length - DataLength,
412					if
413						Length2 =:= 0, IsFin =:= fin ->
414							State#http_state{out=head};
415						Length2 > 0, IsFin =:= nofin ->
416							State#http_state{out={body, Length2}}
417					end;
418				body_chunked -> %% HTTP/1.0
419					Transport:send(Socket, Data),
420					State
421			end;
422		_ ->
423			error_stream_not_found(State, StreamRef, ReplyTo)
424	end.
425
426connect(State=#http_state{streams=Streams}, StreamRef, ReplyTo, _, _) when Streams =/= [] ->
427	ReplyTo ! {gun_error, self(), StreamRef, {badstate,
428		"CONNECT can only be used with HTTP/1.1 when no other streams are active."}},
429	State;
430connect(State=#http_state{socket=Socket, transport=Transport, version=Version},
431		StreamRef, ReplyTo, Destination=#{host := Host0}, Headers0) ->
432	Host = case Host0 of
433		Tuple when is_tuple(Tuple) -> inet:ntoa(Tuple);
434		_ -> Host0
435	end,
436	Port = maps:get(port, Destination, 1080),
437	Authority = [Host, $:, integer_to_binary(Port)],
438	Headers1 = lists:keydelete(<<"content-length">>, 1,
439		lists:keydelete(<<"transfer-encoding">>, 1, Headers0)),
440	Headers2 = case lists:keymember(<<"host">>, 1, Headers1) of
441		false -> [{<<"host">>, Authority}|Headers1];
442		true -> Headers1
443	end,
444	HasProxyAuthorization = lists:keymember(<<"proxy-authorization">>, 1, Headers2),
445	Headers3 = case {HasProxyAuthorization, Destination} of
446		{false, #{username := UserID, password := Password}} ->
447			[{<<"proxy-authorization">>, [
448					<<"Basic ">>,
449					base64:encode(iolist_to_binary([UserID, $:, Password]))]}
450				|Headers2];
451		_ ->
452			Headers2
453	end,
454	Headers = transform_header_names(State, Headers3),
455	Transport:send(Socket, [
456		cow_http:request(<<"CONNECT">>, Authority, Version, Headers)
457	]),
458	new_stream(State, {connect, StreamRef, Destination}, ReplyTo, <<"CONNECT">>).
459
460%% We can't cancel anything, we can just stop forwarding messages to the owner.
461cancel(State, StreamRef, ReplyTo) ->
462	case is_stream(State, StreamRef) of
463		true ->
464			cancel_stream(State, StreamRef);
465		false ->
466			error_stream_not_found(State, StreamRef, ReplyTo)
467	end.
468
469%% HTTP does not provide any way to figure out what streams are unprocessed.
470down(#http_state{streams=Streams}) ->
471	KilledStreams = [case Ref of
472		{connect, Ref2, _} -> Ref2;
473		{websocket, Ref2, _, _, _} -> Ref2;
474		_ -> Ref
475	end || #stream{ref=Ref} <- Streams],
476	{KilledStreams, []}.
477
478error_stream_closed(State, StreamRef, ReplyTo) ->
479	ReplyTo ! {gun_error, self(), StreamRef, {badstate,
480		"The stream has already been closed."}},
481	State.
482
483error_stream_not_found(State, StreamRef, ReplyTo) ->
484	ReplyTo ! {gun_error, self(), StreamRef, {badstate,
485		"The stream cannot be found."}},
486	State.
487
488%% Headers information retrieval.
489
490conn_from_headers(Version, Headers) ->
491	case lists:keyfind(<<"connection">>, 1, Headers) of
492		false when Version =:= 'HTTP/1.0' ->
493			close;
494		false ->
495			keepalive;
496		{_, ConnHd} ->
497			ConnList = cow_http_hd:parse_connection(ConnHd),
498			case lists:member(<<"keep-alive">>, ConnList) of
499				true -> keepalive;
500				false -> close
501			end
502	end.
503
504request_io_from_headers(Headers) ->
505	case lists:keyfind(<<"content-length">>, 1, Headers) of
506		{_, <<"0">>} ->
507			head;
508		{_, Length} ->
509			{body, cow_http_hd:parse_content_length(Length)};
510		_ ->
511			case lists:keymember(<<"content-type">>, 1, Headers) of
512				true -> body_chunked;
513				false -> head
514			end
515	end.
516
517response_io_from_headers(<<"HEAD">>, _, _, _) ->
518	head;
519response_io_from_headers(_, _, Status, _) when (Status =:= 204) or (Status =:= 304) ->
520	head;
521response_io_from_headers(_, Version, _Status, Headers) ->
522	case lists:keyfind(<<"transfer-encoding">>, 1, Headers) of
523		{_, TE} when Version =:= 'HTTP/1.1' ->
524			case cow_http_hd:parse_transfer_encoding(TE) of
525				[<<"chunked">>] -> body_chunked;
526				[<<"identity">>] -> body_close
527			end;
528		_ ->
529			case lists:keyfind(<<"content-length">>, 1, Headers) of
530				{_, <<"0">>} ->
531					head;
532				{_, Length} ->
533					{body, cow_http_hd:parse_content_length(Length)};
534				_ ->
535					body_close
536			end
537	end.
538
539%% Streams.
540
541new_stream(State=#http_state{streams=Streams}, StreamRef, ReplyTo, Method) ->
542	State#http_state{streams=Streams
543		++ [#stream{ref=StreamRef, reply_to=ReplyTo,
544			method=iolist_to_binary(Method), is_alive=true}]}.
545
546is_stream(#http_state{streams=Streams}, StreamRef) ->
547	lists:keymember(StreamRef, #stream.ref, Streams).
548
549cancel_stream(State=#http_state{streams=Streams}, StreamRef) ->
550	Streams2 = [case Ref of
551		StreamRef ->
552			Tuple#stream{is_alive=false};
553		_ ->
554			Tuple
555	end || Tuple = #stream{ref=Ref} <- Streams],
556	State#http_state{streams=Streams2}.
557
558end_stream(State=#http_state{streams=[_|Tail]}) ->
559	State#http_state{in=head, streams=Tail}.
560
561%% Websocket upgrade.
562
563%% Ensure version is 1.1.
564ws_upgrade(#http_state{version='HTTP/1.0'}, _, _, _, _, _, _) ->
565	error; %% @todo
566ws_upgrade(State=#http_state{socket=Socket, transport=Transport, owner=Owner, out=head},
567		StreamRef, Host, Port, Path, Headers0, WsOpts) ->
568	{Headers1, GunExtensions} = case maps:get(compress, WsOpts, false) of
569		true -> {[{<<"sec-websocket-extensions">>,
570				<<"permessage-deflate; client_max_window_bits; server_max_window_bits=15">>}
571			|Headers0],
572			[<<"permessage-deflate">>]};
573		false -> {Headers0, []}
574	end,
575	Headers2 = case maps:get(protocols, WsOpts, []) of
576		[] -> Headers1;
577		ProtoOpt ->
578			<< _, _, Proto/bits >> = iolist_to_binary([[<<", ">>, P] || {P, _} <- ProtoOpt]),
579			[{<<"sec-websocket-protocol">>, Proto}|Headers1]
580	end,
581	Key = cow_ws:key(),
582	Headers3 = [
583		{<<"connection">>, <<"upgrade">>},
584		{<<"upgrade">>, <<"websocket">>},
585		{<<"sec-websocket-version">>, <<"13">>},
586		{<<"sec-websocket-key">>, Key}
587		|Headers2
588	],
589	IsSecure = Transport =:= gun_tls,
590	Headers = case lists:keymember(<<"host">>, 1, Headers0) of
591		true -> Headers3;
592		false when Port =:= 80, not IsSecure -> [{<<"host">>, Host}|Headers3];
593		false when Port =:= 443, IsSecure -> [{<<"host">>, Host}|Headers3];
594		false -> [{<<"host">>, [Host, $:, integer_to_binary(Port)]}|Headers3]
595	end,
596	Transport:send(Socket, cow_http:request(<<"GET">>, Path, 'HTTP/1.1', Headers)),
597	new_stream(State#http_state{connection=keepalive, out=head},
598		{websocket, StreamRef, Key, GunExtensions, WsOpts}, Owner, <<"GET">>).
599
600ws_handshake(Buffer, State, StreamRef, Headers, Key, GunExtensions, Opts) ->
601	%% @todo check upgrade, connection
602	case lists:keyfind(<<"sec-websocket-accept">>, 1, Headers) of
603		false ->
604			close;
605		{_, Accept} ->
606			case cow_ws:encode_key(Key) of
607				Accept ->
608					ws_handshake_extensions(Buffer, State, StreamRef,
609						Headers, GunExtensions, Opts);
610				_ ->
611					close
612			end
613	end.
614
615ws_handshake_extensions(Buffer, State, StreamRef, Headers, GunExtensions, Opts) ->
616	case lists:keyfind(<<"sec-websocket-extensions">>, 1, Headers) of
617		false ->
618			ws_handshake_protocols(Buffer, State, StreamRef, Headers, #{}, Opts);
619		{_, ExtHd} ->
620			case ws_validate_extensions(cow_http_hd:parse_sec_websocket_extensions(ExtHd), GunExtensions, #{}, Opts) of
621				close ->
622					close;
623				Extensions ->
624					ws_handshake_protocols(Buffer, State, StreamRef, Headers, Extensions, Opts)
625			end
626	end.
627
628ws_validate_extensions([], _, Acc, _) ->
629	Acc;
630ws_validate_extensions([{Name = <<"permessage-deflate">>, Params}|Tail], GunExts, Acc, Opts) ->
631	case lists:member(Name, GunExts) of
632		true ->
633			case cow_ws:validate_permessage_deflate(Params, Acc, Opts) of
634				{ok, Acc2} -> ws_validate_extensions(Tail, GunExts, Acc2, Opts);
635				error -> close
636			end;
637		%% Fail the connection if extension was not requested.
638		false ->
639			close
640	end;
641%% Fail the connection on unknown extension.
642ws_validate_extensions(_, _, _, _) ->
643	close.
644
645%% @todo Validate protocols.
646ws_handshake_protocols(Buffer, State, StreamRef, Headers, Extensions, Opts) ->
647	case lists:keyfind(<<"sec-websocket-protocol">>, 1, Headers) of
648		false ->
649			ws_handshake_end(Buffer, State, StreamRef, Headers, Extensions,
650				maps:get(default_protocol, Opts, gun_ws_h), Opts);
651		{_, Proto} ->
652			ProtoOpt = maps:get(protocols, Opts, []),
653			case lists:keyfind(Proto, 1, ProtoOpt) of
654				{_, Handler} ->
655					ws_handshake_end(Buffer, State, StreamRef,
656						Headers, Extensions, Handler, Opts);
657				false ->
658					close
659			end
660	end.
661
662ws_handshake_end(Buffer, #http_state{owner=Owner, socket=Socket, transport=Transport},
663		StreamRef, Headers, Extensions, Handler, Opts) ->
664	%% Send ourselves the remaining buffer, if any.
665	_ = case Buffer of
666		<<>> ->
667			ok;
668		_ ->
669			{OK, _, _} = Transport:messages(),
670			self() ! {OK, Socket, Buffer}
671	end,
672	gun_ws:init(Owner, Socket, Transport, StreamRef, Headers, Extensions, Handler, Opts).
673