Browse Source

Merge branch 'ipv6-literal' of git://github.com/yamt/cowboy

Loïc Hoguin 11 years ago
parent
commit
67410731e0
5 changed files with 131 additions and 40 deletions
  1. 10 7
      src/cowboy_client.erl
  2. 57 32
      src/cowboy_protocol.erl
  3. 1 1
      src/cowboy_spdy.erl
  4. 44 0
      test/http_SUITE.erl
  5. 19 0
      test/http_SUITE_data/http_req_attr.erl

+ 10 - 7
src/cowboy_client.erl

@@ -93,16 +93,19 @@ request(Method, URL, Headers, Body, Client=#client{
 	end,
 	VersionBin = atom_to_binary(Version, latin1),
 	%% @todo do keepalive too, allow override...
-	Headers2 = [
-		{<<"host">>, FullHost},
+	Headers2 = case lists:keyfind(<<"host">>, 1, Headers) of
+		false -> [{<<"host">>, FullHost}|Headers];
+		_ -> Headers
+	end,
+	Headers3 = [
 		{<<"user-agent">>, <<"Cow">>}
-	|Headers],
-	Headers3 = case iolist_size(Body) of
-		0 -> Headers2;
-		Length -> [{<<"content-length">>, integer_to_list(Length)}|Headers2]
+	|Headers2],
+	Headers4 = case iolist_size(Body) of
+		0 -> Headers3;
+		Length -> [{<<"content-length">>, integer_to_list(Length)}|Headers3]
 	end,
 	HeadersData = [[Name, <<": ">>, Value, <<"\r\n">>]
-		|| {Name, Value} <- Headers3],
+		|| {Name, Value} <- Headers4],
 	Data = [Method, <<" ">>, Path, <<" ">>, VersionBin, <<"\r\n">>,
 		HeadersData, <<"\r\n">>, Body],
 	raw_request(Data, Client2).

+ 57 - 32
src/cowboy_protocol.erl

@@ -54,7 +54,7 @@
 %% Internal.
 -export([init/4]).
 -export([parse_request/3]).
--export([parse_host/2]).
+-export([parse_host/3]).
 -export([resume/6]).
 
 -type opts() :: [{compress, boolean()}
@@ -426,7 +426,7 @@ request(B, State=#state{transport=Transport}, M, P, Q, Version, Headers) ->
 			request(B, State, M, P, Q, Version, Headers,
 				<<>>, default_port(Transport:name()));
 		{_, RawHost} ->
-			case catch parse_host(RawHost, <<>>) of
+			case catch parse_host(RawHost, false, <<>>) of
 				{'EXIT', _} ->
 					error_terminate(400, State);
 				{Host, undefined} ->
@@ -443,39 +443,43 @@ default_port(ssl) -> 443;
 default_port(_) -> 80.
 
 %% Another hurtful block of code. :)
-parse_host(<<>>, Acc) ->
+parse_host(<< $[, Rest/bits >>, false, <<>>) ->
+	parse_host(Rest, true, << $[ >>);
+parse_host(<<>>, false, Acc) ->
 	{Acc, undefined};
-parse_host(<< $:, Rest/bits >>, Acc) ->
+parse_host(<< $:, Rest/bits >>, false, Acc) ->
 	{Acc, list_to_integer(binary_to_list(Rest))};
-parse_host(<< C, Rest/bits >>, Acc) ->
+parse_host(<< $], Rest/bits >>, true, Acc) ->
+	parse_host(Rest, false, << Acc/binary, $] >>);
+parse_host(<< C, Rest/bits >>, E, Acc) ->
 	case C of
-		$A -> parse_host(Rest, << Acc/binary, $a >>);
-		$B -> parse_host(Rest, << Acc/binary, $b >>);
-		$C -> parse_host(Rest, << Acc/binary, $c >>);
-		$D -> parse_host(Rest, << Acc/binary, $d >>);
-		$E -> parse_host(Rest, << Acc/binary, $e >>);
-		$F -> parse_host(Rest, << Acc/binary, $f >>);
-		$G -> parse_host(Rest, << Acc/binary, $g >>);
-		$H -> parse_host(Rest, << Acc/binary, $h >>);
-		$I -> parse_host(Rest, << Acc/binary, $i >>);
-		$J -> parse_host(Rest, << Acc/binary, $j >>);
-		$K -> parse_host(Rest, << Acc/binary, $k >>);
-		$L -> parse_host(Rest, << Acc/binary, $l >>);
-		$M -> parse_host(Rest, << Acc/binary, $m >>);
-		$N -> parse_host(Rest, << Acc/binary, $n >>);
-		$O -> parse_host(Rest, << Acc/binary, $o >>);
-		$P -> parse_host(Rest, << Acc/binary, $p >>);
-		$Q -> parse_host(Rest, << Acc/binary, $q >>);
-		$R -> parse_host(Rest, << Acc/binary, $r >>);
-		$S -> parse_host(Rest, << Acc/binary, $s >>);
-		$T -> parse_host(Rest, << Acc/binary, $t >>);
-		$U -> parse_host(Rest, << Acc/binary, $u >>);
-		$V -> parse_host(Rest, << Acc/binary, $v >>);
-		$W -> parse_host(Rest, << Acc/binary, $w >>);
-		$X -> parse_host(Rest, << Acc/binary, $x >>);
-		$Y -> parse_host(Rest, << Acc/binary, $y >>);
-		$Z -> parse_host(Rest, << Acc/binary, $z >>);
-		_ -> parse_host(Rest, << Acc/binary, C >>)
+		$A -> parse_host(Rest, E, << Acc/binary, $a >>);
+		$B -> parse_host(Rest, E, << Acc/binary, $b >>);
+		$C -> parse_host(Rest, E, << Acc/binary, $c >>);
+		$D -> parse_host(Rest, E, << Acc/binary, $d >>);
+		$E -> parse_host(Rest, E, << Acc/binary, $e >>);
+		$F -> parse_host(Rest, E, << Acc/binary, $f >>);
+		$G -> parse_host(Rest, E, << Acc/binary, $g >>);
+		$H -> parse_host(Rest, E, << Acc/binary, $h >>);
+		$I -> parse_host(Rest, E, << Acc/binary, $i >>);
+		$J -> parse_host(Rest, E, << Acc/binary, $j >>);
+		$K -> parse_host(Rest, E, << Acc/binary, $k >>);
+		$L -> parse_host(Rest, E, << Acc/binary, $l >>);
+		$M -> parse_host(Rest, E, << Acc/binary, $m >>);
+		$N -> parse_host(Rest, E, << Acc/binary, $n >>);
+		$O -> parse_host(Rest, E, << Acc/binary, $o >>);
+		$P -> parse_host(Rest, E, << Acc/binary, $p >>);
+		$Q -> parse_host(Rest, E, << Acc/binary, $q >>);
+		$R -> parse_host(Rest, E, << Acc/binary, $r >>);
+		$S -> parse_host(Rest, E, << Acc/binary, $s >>);
+		$T -> parse_host(Rest, E, << Acc/binary, $t >>);
+		$U -> parse_host(Rest, E, << Acc/binary, $u >>);
+		$V -> parse_host(Rest, E, << Acc/binary, $v >>);
+		$W -> parse_host(Rest, E, << Acc/binary, $w >>);
+		$X -> parse_host(Rest, E, << Acc/binary, $x >>);
+		$Y -> parse_host(Rest, E, << Acc/binary, $y >>);
+		$Z -> parse_host(Rest, E, << Acc/binary, $z >>);
+		_ -> parse_host(Rest, E, << Acc/binary, C >>)
 	end.
 
 %% End of request parsing.
@@ -589,3 +593,24 @@ error_terminate(Status, Req, State) ->
 terminate(#state{socket=Socket, transport=Transport}) ->
 	Transport:close(Socket),
 	ok.
+
+%% Tests.
+
+-ifdef(TEST).
+
+parse_host(RawHost) ->
+	parse_host(RawHost, false, <<>>).
+
+parse_host_test() ->
+	{<<"example.org">>, 8080} = parse_host(<<"example.org:8080">>),
+	{<<"example.org">>, undefined} = parse_host(<<"example.org">>),
+	{<<"192.0.2.1">>, 8080} = parse_host(<<"192.0.2.1:8080">>),
+	{<<"192.0.2.1">>, undefined} = parse_host(<<"192.0.2.1">>),
+	{<<"[2001:db8::1]">>, 8080} = parse_host(<<"[2001:db8::1]:8080">>),
+	{<<"[2001:db8::1]">>, undefined} = parse_host(<<"[2001:db8::1]">>),
+	{<<"[::ffff:192.0.2.1]">>, 8080} =
+		parse_host(<<"[::ffff:192.0.2.1]:8080">>),
+	{<<"[::ffff:192.0.2.1]">>, undefined} =
+		parse_host(<<"[::ffff:192.0.2.1]">>).
+
+-endif.

+ 1 - 1
src/cowboy_spdy.erl

@@ -346,7 +346,7 @@ data_from_file(Socket, Transport, StreamID, IoDevice) ->
 request_init(FakeSocket, Peer, OnRequest, OnResponse,
 		Env, Middlewares, Method, Host, Path, Version, Headers) ->
 	Version2 = parse_version(Version),
-	{Host2, Port} = cowboy_protocol:parse_host(Host, <<>>),
+	{Host2, Port} = cowboy_protocol:parse_host(Host, false, <<>>),
 	{Path2, Query} = parse_path(Path, <<>>),
 	Req = cowboy_req:new(FakeSocket, ?MODULE, Peer,
 		Method, Path2, Query, Version2, Headers,

+ 44 - 0
test/http_SUITE.erl

@@ -51,6 +51,7 @@
 -export([onresponse_capitalize/1]).
 -export([onresponse_crash/1]).
 -export([onresponse_reply/1]).
+-export([parse_host/1]).
 -export([pipeline/1]).
 -export([pipeline_long_polling/1]).
 -export([rest_bad_accept/1]).
@@ -103,6 +104,7 @@ all() ->
 		{group, onrequest},
 		{group, onresponse},
 		{group, onresponse_capitalize},
+		{group, parse_host},
 		{group, set_env}
 	].
 
@@ -184,6 +186,9 @@ groups() ->
 		{onresponse_capitalize, [parallel], [
 			onresponse_capitalize
 		]},
+		{parse_host, [], [
+			parse_host
+		]},
 		{set_env, [], [
 			set_env_dispatch
 		]}
@@ -297,6 +302,22 @@ init_per_group(onresponse_capitalize, Config) ->
 	{ok, Client} = cowboy_client:init([]),
 	[{scheme, <<"http">>}, {port, Port}, {opts, []},
 		{transport, Transport}, {client, Client}|Config];
+init_per_group(parse_host, Config) ->
+	Transport = ranch_tcp,
+	Dispatch = cowboy_router:compile([
+		{'_', [
+			{"/req_attr", http_req_attr, []}
+		]}
+	]),
+	{ok, _} = cowboy:start_http(http, 100, [{port, 0}], [
+		{env, [{dispatch, Dispatch}]},
+		{max_keepalive, 50},
+		{timeout, 500}
+	]),
+	Port = ranch:get_port(http),
+	{ok, Client} = cowboy_client:init([]),
+	[{scheme, <<"http">>}, {port, Port}, {opts, []},
+		{transport, Transport}, {client, Client}|Config];
 init_per_group(set_env, Config) ->
 	Transport = ranch_tcp,
 	{ok, _} = cowboy:start_http(set_env, 100, [{port, 0}], [
@@ -802,6 +823,29 @@ onresponse_hook(_, Headers, _, Req) ->
 		<<"777 Lucky">>, [{<<"x-hook">>, <<"onresponse">>}|Headers], Req),
 	Req2.
 
+parse_host(Config) ->
+	Tests = [
+		{<<"example.org\n8080">>, <<"example.org:8080">>},
+		{<<"example.org\n80">>, <<"example.org">>},
+		{<<"192.0.2.1\n8080">>, <<"192.0.2.1:8080">>},
+		{<<"192.0.2.1\n80">>, <<"192.0.2.1">>},
+		{<<"[2001:db8::1]\n8080">>, <<"[2001:db8::1]:8080">>},
+		{<<"[2001:db8::1]\n80">>, <<"[2001:db8::1]">>},
+		{<<"[::ffff:192.0.2.1]\n8080">>, <<"[::ffff:192.0.2.1]:8080">>},
+		{<<"[::ffff:192.0.2.1]\n80">>, <<"[::ffff:192.0.2.1]">>}
+	],
+	[begin
+		Client = ?config(client, Config),
+		{ok, Client2} = cowboy_client:request(<<"GET">>,
+			build_url("/req_attr?attr=host_and_port", Config),
+			[{<<"host">>, Host}],
+			Client),
+		{ok, 200, _, Client3} = cowboy_client:response(Client2),
+		{ok, Value, Client4} = cowboy_client:response_body(Client3),
+		{error, closed} = cowboy_client:response(Client4),
+        Value
+	end || {Value, Host} <- Tests].
+
 pipeline(Config) ->
 	Client = ?config(client, Config),
 	{ok, Client2} = cowboy_client:request(<<"GET">>,

+ 19 - 0
test/http_SUITE_data/http_req_attr.erl

@@ -0,0 +1,19 @@
+%% Feel free to use, reuse and abuse the code in this file.
+
+-module(http_req_attr).
+-behaviour(cowboy_http_handler).
+-export([init/3, handle/2, terminate/3]).
+
+init({_, http}, Req, _) ->
+	{Attr, Req2} = cowboy_req:qs_val(<<"attr">>, Req),
+	{ok, Req2, Attr}.
+
+handle(Req, <<"host_and_port">> = Attr) ->
+	{Host, Req2} = cowboy_req:host(Req),
+	{Port, Req3} = cowboy_req:port(Req2),
+	Value = [Host, "\n", integer_to_list(Port)],
+	{ok, Req4} = cowboy_req:reply(200, [], Value, Req3),
+	{ok, Req4, Attr}.
+
+terminate(_, _, _) ->
+	ok.