aboutsummaryrefslogtreecommitdiffstats
path: root/src/cow_http_hd.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/cow_http_hd.erl')
-rw-r--r--src/cow_http_hd.erl91
1 files changed, 91 insertions, 0 deletions
diff --git a/src/cow_http_hd.erl b/src/cow_http_hd.erl
index 848b8c9..bd0a258 100644
--- a/src/cow_http_hd.erl
+++ b/src/cow_http_hd.erl
@@ -32,6 +32,7 @@
-export([parse_etag/1]).
-export([parse_expect/1]).
-export([parse_expires/1]).
+-export([parse_host/1]).
-export([parse_if_match/1]).
-export([parse_if_modified_since/1]).
-export([parse_if_none_match/1]).
@@ -1846,6 +1847,96 @@ horse_parse_expires_invalid() ->
).
-endif.
+%% @doc Parse the Host header.
+%%
+%% We only seek to have legal characters and separate the
+%% host and port values. The number of segments in the host
+%% or the size of each segment is not checked.
+%%
+%% There is no way to distinguish IPv4 addresses from regular
+%% names until the last segment is reached therefore we do not
+%% differentiate them.
+%%
+%% The following valid hosts are currently rejected: IPv6
+%% addresses with a zone identifier; IPvFuture addresses;
+%% and percent-encoded addresses.
+
+-spec parse_host(binary()) -> {binary(), 0..65535 | undefined}.
+parse_host(<< $[, R/bits >>) ->
+ ipv6_address(R, << $[ >>);
+parse_host(Host) ->
+ reg_name(Host, <<>>).
+
+ipv6_address(<< $] >>, IP) -> {<< IP/binary, $] >>, undefined};
+ipv6_address(<< $], $:, Port/bits >>, IP) -> {<< IP/binary, $] >>, binary_to_integer(Port)};
+ipv6_address(<< C, R/bits >>, IP) when ?IS_HEX(C) orelse C =:= $: orelse C =:= $. ->
+ case C of
+ ?INLINE_LOWERCASE(ipv6_address, R, IP)
+ end.
+
+reg_name(<<>>, Name) -> {Name, undefined};
+reg_name(<< $:, Port/bits >>, Name) -> {Name, binary_to_integer(Port)};
+reg_name(<< C, R/bits >>, Name) when ?IS_URI_UNRESERVED(C) orelse ?IS_URI_SUB_DELIMS(C) ->
+ case C of
+ ?INLINE_LOWERCASE(reg_name, R, Name)
+ end.
+
+-ifdef(TEST).
+host_chars() -> "!$&'()*+,-.0123456789;=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~".
+host() -> vector(1, 255, elements(host_chars())).
+
+host_port() ->
+ ?LET({Host, Port},
+ {host(), oneof([undefined, int(1, 65535)])},
+ begin
+ HostBin = list_to_binary(Host),
+ {{?INLINE_LOWERCASE_BC(HostBin), Port},
+ case Port of
+ undefined -> HostBin;
+ _ -> << HostBin/binary, $:, (integer_to_binary(Port))/binary >>
+ end}
+ end).
+
+prop_parse_host() ->
+ ?FORALL({Res, Host}, host_port(), Res =:= parse_host(Host)).
+
+parse_host_test_() ->
+ Tests = [
+ {<<>>, {<<>>, undefined}},
+ {<<"www.example.org:8080">>, {<<"www.example.org">>, 8080}},
+ {<<"www.example.org">>, {<<"www.example.org">>, undefined}},
+ {<<"192.0.2.1:8080">>, {<<"192.0.2.1">>, 8080}},
+ {<<"192.0.2.1">>, {<<"192.0.2.1">>, undefined}},
+ {<<"[2001:db8::1]:8080">>, {<<"[2001:db8::1]">>, 8080}},
+ {<<"[2001:db8::1]">>, {<<"[2001:db8::1]">>, undefined}},
+ {<<"[::ffff:192.0.2.1]:8080">>, {<<"[::ffff:192.0.2.1]">>, 8080}},
+ {<<"[::ffff:192.0.2.1]">>, {<<"[::ffff:192.0.2.1]">>, undefined}}
+ ],
+ [{V, fun() -> R = parse_host(V) end} || {V, R} <- Tests].
+-endif.
+
+-ifdef(PERF).
+horse_parse_host_blue_example_org() ->
+ horse:repeat(200000,
+ parse_host(<<"blue.example.org:8080">>)
+ ).
+
+horse_parse_host_ipv4() ->
+ horse:repeat(200000,
+ parse_host(<<"192.0.2.1:8080">>)
+ ).
+
+horse_parse_host_ipv6() ->
+ horse:repeat(200000,
+ parse_host(<<"[2001:db8::1]:8080">>)
+ ).
+
+horse_parse_host_ipv6_v4() ->
+ horse:repeat(200000,
+ parse_host(<<"[::ffff:192.0.2.1]:8080">>)
+ ).
+-endif.
+
%% @doc Parse the If-Match header.
-spec parse_if_match(binary()) -> '*' | [etag()].