From 77f7427b4162d518bd4c22806e5acdb40f4fa675 Mon Sep 17 00:00:00 2001 From: YAMAMOTO Takashi Date: Sat, 24 Aug 2013 02:13:44 +0900 Subject: cowboy_protocol: accept host using ipv6 literal --- src/cowboy_protocol.erl | 68 ++++++++++++++++++++++++++----------------------- src/cowboy_spdy.erl | 2 +- 2 files changed, 37 insertions(+), 33 deletions(-) diff --git a/src/cowboy_protocol.erl b/src/cowboy_protocol.erl index 40be2c0..887e3aa 100644 --- a/src/cowboy_protocol.erl +++ b/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. diff --git a/src/cowboy_spdy.erl b/src/cowboy_spdy.erl index cc4d867..32d65df 100644 --- a/src/cowboy_spdy.erl +++ b/src/cowboy_spdy.erl @@ -474,7 +474,7 @@ request_init(Parent, StreamID, Peer, #special_headers{method=Method, path=Path, version=Version, host=Host}) -> 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({Parent, StreamID}, ?MODULE, Peer, Method, Path2, Query, Version2, Headers, -- cgit v1.2.3 From 43b3c39a0c74f8dc98ad410d170a87ea5d5f1018 Mon Sep 17 00:00:00 2001 From: YAMAMOTO Takashi Date: Sun, 25 Aug 2013 01:34:42 +0900 Subject: add unit test for cowboy_protocol:parse_host/1 --- src/cowboy_protocol.erl | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/cowboy_protocol.erl b/src/cowboy_protocol.erl index 887e3aa..68a03b1 100644 --- a/src/cowboy_protocol.erl +++ b/src/cowboy_protocol.erl @@ -593,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. -- cgit v1.2.3 From 201c53cb9f8217d497fb7acae2fa7d64f04f4022 Mon Sep 17 00:00:00 2001 From: YAMAMOTO Takashi Date: Sun, 25 Aug 2013 02:11:49 +0900 Subject: make cowboy_client:request() allow to override Host header this will be used by tests --- src/cowboy_client.erl | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/cowboy_client.erl b/src/cowboy_client.erl index b5f96b3..10aaa9c 100644 --- a/src/cowboy_client.erl +++ b/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). -- cgit v1.2.3 From d2adbf3de66f15dc0b654c76ee7bee7bd9c8c778 Mon Sep 17 00:00:00 2001 From: YAMAMOTO Takashi Date: Sun, 25 Aug 2013 02:13:53 +0900 Subject: add some tests for Host header parser --- test/http_SUITE.erl | 44 ++++++++++++++++++++++++++++++++++ test/http_SUITE_data/http_req_attr.erl | 19 +++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 test/http_SUITE_data/http_req_attr.erl diff --git a/test/http_SUITE.erl b/test/http_SUITE.erl index 2135fdc..f784c50 100644 --- a/test/http_SUITE.erl +++ b/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">>, diff --git a/test/http_SUITE_data/http_req_attr.erl b/test/http_SUITE_data/http_req_attr.erl new file mode 100644 index 0000000..eb5e70e --- /dev/null +++ b/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. -- cgit v1.2.3