diff options
Diffstat (limited to 'lib')
-rwxr-xr-x | lib/stdlib/src/uri_string.erl | 246 | ||||
-rw-r--r-- | lib/stdlib/test/uri_string_SUITE.erl | 74 |
2 files changed, 296 insertions, 24 deletions
diff --git a/lib/stdlib/src/uri_string.erl b/lib/stdlib/src/uri_string.erl index 619da24cbc..3656d561be 100755 --- a/lib/stdlib/src/uri_string.erl +++ b/lib/stdlib/src/uri_string.erl @@ -388,7 +388,7 @@ parse_relative_part(?STRING_REST("//", Rest), URI) -> throw:uri_parse_error -> {T, URI1} = parse_host(Rest, URI), {Host, _} = split_binary(Rest, byte_size_exl_single_slash(Rest) - byte_size_exl_head(T)), - URI1#{host => Host} + URI1#{host => remove_brackets(Host)} end; parse_relative_part(?STRING_REST($/, Rest), URI) -> {T, URI1} = parse_segment(Rest, URI), % path-absolute @@ -669,7 +669,7 @@ parse_hier(?STRING_REST("//", Rest), URI) -> throw:uri_parse_error -> {T, URI1} = parse_host(Rest, URI), {Host, _} = split_binary(Rest, byte_size_exl_single_slash(Rest) - byte_size_exl_head(T)), - {Rest, URI1#{host => Host}} + {Rest, URI1#{host => remove_brackets(Host)}} end; parse_hier(?STRING_REST($/, Rest), URI) -> {T, URI1} = parse_segment(Rest, URI), % path-absolute @@ -756,7 +756,7 @@ parse_userinfo(?CHAR($@), _URI) -> parse_userinfo(?STRING_REST($@, Rest), URI) -> {T, URI1} = parse_host(Rest, URI), {Host, _} = split_binary(Rest, byte_size(Rest) - byte_size_exl_head(T)), - {Rest, URI1#{host => Host}}; + {Rest, URI1#{host => remove_brackets(Host)}}; parse_userinfo(?STRING_REST(Char, Rest), URI) -> case is_userinfo(Char) of true -> parse_userinfo(Rest, URI); @@ -834,7 +834,6 @@ is_userinfo(Char) -> is_unreserved(Char) orelse is_sub_delim(Char). %% %% reg-name = *( unreserved / pct-encoded / sub-delims ) %%------------------------------------------------------------------------- -%% TODO: implement parsing of IPv4/IPv6 addresses -spec parse_host(binary(), uri_map()) -> {binary(), uri_map()}. parse_host(?STRING_REST($:, Rest), URI) -> {T, URI1} = parse_port(Rest, URI), @@ -849,14 +848,16 @@ parse_host(?STRING_REST($?, Rest), URI) -> {T, URI1} = parse_query(Rest, URI), % path-empty ?query {Query, _} = split_binary(Rest, byte_size(Rest) - byte_size_exl_head(T)), {Rest, URI1#{query => ?STRING_REST($?, Query)}}; +parse_host(?STRING_REST($[, Rest), URI) -> + parse_ipv6_bin(Rest, [], URI); parse_host(?STRING_REST($#, Rest), URI) -> {T, URI1} = parse_fragment(Rest, URI), % path-empty {Fragment, _} = split_binary(Rest, byte_size(Rest) - byte_size(T)), {Rest, URI1#{fragment => Fragment}}; parse_host(?STRING_REST(Char, Rest), URI) -> - case is_reg_name(Char) of - true -> parse_host(Rest, URI); - false -> throw(uri_parse_error) + case is_digit(Char) of + true -> parse_ipv4_bin(Rest, [Char], URI); + false -> parse_reg_name(?STRING_REST(Char, Rest), URI) end; parse_host(?STRING_EMPTY, URI) -> {?STRING_EMPTY, URI}. @@ -877,12 +878,65 @@ parse_host([$?|Rest], Acc, URI) -> parse_query(Rest, [$?], URI#{host => lists:reverse(Acc)}); % path-empty ?query parse_host([$#|Rest], Acc, URI) -> parse_fragment(Rest, [], URI#{host => lists:reverse(Acc)}); % path-empty +parse_host([$[|Rest], _Acc, URI) -> + parse_ipv6(Rest, [], URI); parse_host([Char|Rest], Acc, URI) -> + case is_digit(Char) of + true -> parse_ipv4(Rest, [Char|Acc], URI); + false -> parse_reg_name([Char|Rest], Acc, URI) + end; +parse_host([], Acc, URI) -> + URI#{host => lists:reverse(Acc)}. + + +-spec parse_reg_name(binary(), uri_map()) -> {binary(), uri_map()}. +parse_reg_name(?STRING_REST($:, Rest), URI) -> + {T, URI1} = parse_port(Rest, URI), + {H, _} = split_binary(Rest, byte_size(Rest) - byte_size_exl_head(T)), + Port = binary_to_integer(H), + {Rest, URI1#{port => Port}}; +parse_reg_name(?STRING_REST($/, Rest), URI) -> + {T, URI1} = parse_segment(Rest, URI), % path-abempty + {Path, _} = split_binary(Rest, byte_size(Rest) - byte_size_exl_head(T)), + {Rest, URI1#{path => ?STRING_REST($/, Path)}}; +parse_reg_name(?STRING_REST($?, Rest), URI) -> + {T, URI1} = parse_query(Rest, URI), % path-empty ?query + {Query, _} = split_binary(Rest, byte_size(Rest) - byte_size_exl_head(T)), + {Rest, URI1#{query => ?STRING_REST($?, Query)}}; +parse_reg_name(?STRING_REST($#, Rest), URI) -> + {T, URI1} = parse_fragment(Rest, URI), % path-empty + {Fragment, _} = split_binary(Rest, byte_size(Rest) - byte_size(T)), + {Rest, URI1#{fragment => Fragment}}; +parse_reg_name(?STRING_REST(Char, Rest), URI) -> case is_reg_name(Char) of - true -> parse_host(Rest, [Char|Acc], URI); + true -> parse_reg_name(Rest, URI); false -> throw(uri_parse_error) end; -parse_host([], Acc, URI) -> +parse_reg_name(?STRING_EMPTY, URI) -> + {?STRING_EMPTY, URI}. + +-spec parse_reg_name(iolist(), list(), uri_map()) -> uri_map(). +parse_reg_name(?STRING(Str), Acc, URI) when is_list(Acc) -> + parse_reg_name(unicode:characters_to_list(Str), Acc, URI); +parse_reg_name([H|Rest], Acc, URI) when is_binary(H) -> + parse_reg_name(unicode:characters_to_list(H, utf8) ++ Rest, + Acc, URI); +parse_reg_name([H|Rest], Acc, URI) when is_list(H) -> + parse_reg_name(H ++ Rest, Acc, URI); +parse_reg_name([$:|Rest], Acc, URI) -> + parse_port(Rest, [], URI#{host => lists:reverse(Acc)}); +parse_reg_name([$/|Rest], Acc, URI) -> + parse_segment(Rest, [$/], URI#{host => lists:reverse(Acc)}); % path-abempty +parse_reg_name([$?|Rest], Acc, URI) -> + parse_query(Rest, [$?], URI#{host => lists:reverse(Acc)}); % path-empty ?query +parse_reg_name([$#|Rest], Acc, URI) -> + parse_fragment(Rest, [], URI#{host => lists:reverse(Acc)}); % path-empty +parse_reg_name([Char|Rest], Acc, URI) -> + case is_reg_name(Char) of + true -> parse_reg_name(Rest, [Char|Acc], URI); + false -> throw(uri_parse_error) + end; +parse_reg_name([], Acc, URI) -> URI#{host => lists:reverse(Acc)}. %% Check if char is allowed in reg-name @@ -891,6 +945,168 @@ is_reg_name($%) -> true; is_reg_name(Char) -> is_unreserved(Char) orelse is_sub_delim(Char). +-spec parse_ipv4_bin(binary(), list(), uri_map()) -> {binary(), uri_map()}. +parse_ipv4_bin(?STRING_REST($:, Rest), Acc, URI) -> + _ = validate_ipv4_address(lists:reverse(Acc)), + {T, URI1} = parse_port(Rest, URI), + {H, _} = split_binary(Rest, byte_size(Rest) - byte_size_exl_head(T)), + Port = binary_to_integer(H), + {Rest, URI1#{port => Port}}; +parse_ipv4_bin(?STRING_REST($/, Rest), Acc, URI) -> + _ = validate_ipv4_address(lists:reverse(Acc)), + {T, URI1} = parse_segment(Rest, URI), % path-abempty + {Path, _} = split_binary(Rest, byte_size(Rest) - byte_size_exl_head(T)), + {Rest, URI1#{path => ?STRING_REST($/, Path)}}; +parse_ipv4_bin(?STRING_REST($?, Rest), Acc, URI) -> + _ = validate_ipv4_address(lists:reverse(Acc)), + {T, URI1} = parse_query(Rest, URI), % path-empty ?query + {Query, _} = split_binary(Rest, byte_size(Rest) - byte_size_exl_head(T)), + {Rest, URI1#{query => ?STRING_REST($?, Query)}}; +parse_ipv4_bin(?STRING_REST($#, Rest), Acc, URI) -> + _ = validate_ipv4_address(lists:reverse(Acc)), + {T, URI1} = parse_fragment(Rest, URI), % path-empty + {Fragment, _} = split_binary(Rest, byte_size(Rest) - byte_size(T)), + {Rest, URI1#{fragment => Fragment}}; +parse_ipv4_bin(?STRING_REST(Char, Rest), Acc, URI) -> + case is_ipv4(Char) of + true -> parse_ipv4_bin(Rest, [Char|Acc], URI); + false -> throw(uri_parse_error) + end; +parse_ipv4_bin(?STRING_EMPTY, Acc, URI) -> + _ = validate_ipv4_address(lists:reverse(Acc)), + {?STRING_EMPTY, URI}. + +-spec parse_ipv4(iolist(), list(), uri_map()) -> uri_map(). +parse_ipv4(?STRING(Str), Acc, URI) when is_list(Acc) -> + parse_ipv4(unicode:characters_to_list(Str), Acc, URI); +parse_ipv4([H|Rest], Acc, URI) when is_binary(H) -> + parse_ipv4(unicode:characters_to_list(H, utf8) ++ Rest, + Acc, URI); +parse_ipv4([H|Rest], Acc, URI) when is_list(H) -> + parse_ipv4(H ++ Rest, Acc, URI); +parse_ipv4([$:|Rest], Acc, URI) -> + parse_port(Rest, [], URI#{host => validate_ipv4_address(lists:reverse(Acc))}); +parse_ipv4([$/|Rest], Acc, URI) -> + parse_segment(Rest, [$/], URI#{host => validate_ipv4_address(lists:reverse(Acc))}); % path-abempty +parse_ipv4([$?|Rest], Acc, URI) -> + parse_query(Rest, [$?], URI#{host => validate_ipv4_address(lists:reverse(Acc))}); % path-empty ?query +parse_ipv4([$#|Rest], Acc, URI) -> + parse_fragment(Rest, [], URI#{host => validate_ipv4_address(lists:reverse(Acc))}); % path-empty +parse_ipv4([Char|Rest], Acc, URI) -> + case is_ipv4(Char) of + true -> parse_ipv4(Rest, [Char|Acc], URI); + false -> throw(uri_parse_error) + end; +parse_ipv4([], Acc, URI) -> + URI#{host => validate_ipv4_address(lists:reverse(Acc))}. + +%% Check if char is allowed in IPv4 addresses +-spec is_ipv4(char()) -> boolean(). +is_ipv4($.) -> true; +is_ipv4(Char) -> is_digit(Char). + +-spec validate_ipv4_address(list()) -> list(). +validate_ipv4_address(Addr) -> + case inet:parse_ipv4strict_address(Addr) of + {ok, _} -> Addr; + {error, _} -> throw(uri_parse_error) + end. + + +-spec parse_ipv6_bin(binary(), list(), uri_map()) -> {binary(), uri_map()}. +parse_ipv6_bin(?STRING_REST($], Rest), Acc, URI) -> + _ = validate_ipv6_address(lists:reverse(Acc)), + parse_ipv6_bin_end(Rest, URI); +parse_ipv6_bin(?STRING_REST(Char, Rest), Acc, URI) -> + case is_ipv6(Char) of + true -> parse_ipv6_bin(Rest, [Char|Acc], URI); + false -> throw(uri_parse_error) + end; +parse_ipv6_bin(?STRING_EMPTY, _Acc, _URI) -> + throw(uri_parse_error). + +-spec parse_ipv6(iolist(), list(), uri_map()) -> uri_map(). +parse_ipv6(?STRING(Str), Acc, URI) when is_list(Acc) -> + parse_ipv6(unicode:characters_to_list(Str), Acc, URI); +parse_ipv6([H|Rest], Acc, URI) when is_binary(H) -> + parse_ipv6(unicode:characters_to_list(H, utf8) ++ Rest, + Acc, URI); +parse_ipv6([H|Rest], Acc, URI) when is_list(H) -> + parse_ipv6(H ++ Rest, Acc, URI); +parse_ipv6([$]|Rest], Acc, URI) -> + parse_ipv6_end(Rest, [], URI#{host => validate_ipv6_address(lists:reverse(Acc))}); +parse_ipv6([Char|Rest], Acc, URI) -> + case is_ipv6(Char) of + true -> parse_ipv6(Rest, [Char|Acc], URI); + false -> + io:format("# DEBUG Char: >>~c<<~n", [Char]), + io:format("# DEBUG Rest: >>~s<<~n", [Rest]), + throw(uri_parse_error) + end; +parse_ipv6([], _Acc, _URI) -> + throw(uri_parse_error). + +%% Check if char is allowed in IPv6 addresses +-spec is_ipv6(char()) -> boolean(). +is_ipv6($:) -> true; +is_ipv6($.) -> true; +is_ipv6(Char) -> is_hex_digit(Char). + + +-spec parse_ipv6_bin_end(binary(), uri_map()) -> {binary(), uri_map()}. +parse_ipv6_bin_end(?STRING_REST($:, Rest), URI) -> + {T, URI1} = parse_port(Rest, URI), + {H, _} = split_binary(Rest, byte_size(Rest) - byte_size_exl_head(T)), + Port = binary_to_integer(H), + {Rest, URI1#{port => Port}}; +parse_ipv6_bin_end(?STRING_REST($/, Rest), URI) -> + {T, URI1} = parse_segment(Rest, URI), % path-abempty + {Path, _} = split_binary(Rest, byte_size(Rest) - byte_size_exl_head(T)), + {Rest, URI1#{path => ?STRING_REST($/, Path)}}; +parse_ipv6_bin_end(?STRING_REST($?, Rest), URI) -> + {T, URI1} = parse_query(Rest, URI), % path-empty ?query + {Query, _} = split_binary(Rest, byte_size(Rest) - byte_size_exl_head(T)), + {Rest, URI1#{query => ?STRING_REST($?, Query)}}; +parse_ipv6_bin_end(?STRING_REST($#, Rest), URI) -> + {T, URI1} = parse_fragment(Rest, URI), % path-empty + {Fragment, _} = split_binary(Rest, byte_size(Rest) - byte_size(T)), + {Rest, URI1#{fragment => Fragment}}; +parse_ipv6_bin_end(?STRING_REST(Char, Rest), URI) -> + case is_ipv6(Char) of + true -> parse_ipv6_bin_end(Rest, URI); + false -> throw(uri_parse_error) + end; +parse_ipv6_bin_end(?STRING_EMPTY, URI) -> + {?STRING_EMPTY, URI}. + +-spec parse_ipv6_end(iolist(), list(), uri_map()) -> uri_map(). +parse_ipv6_end(?STRING(Str), Acc, URI) when is_list(Acc) -> + parse_ipv6_end(unicode:characters_to_list(Str), Acc, URI); +parse_ipv6_end([H|Rest], Acc, URI) when is_binary(H) -> + parse_ipv6_end(unicode:characters_to_list(H, utf8) ++ Rest, + Acc, URI); +parse_ipv6_end([H|Rest], Acc, URI) when is_list(H) -> + parse_ipv6_end(H ++ Rest, Acc, URI); +parse_ipv6_end([$:|Rest], _Acc, URI) -> + parse_port(Rest, [], URI); +parse_ipv6_end([$/|Rest], _Acc, URI) -> + parse_segment(Rest, [$/], URI); % path-abempty +parse_ipv6_end([$?|Rest], _Acc, URI) -> + parse_query(Rest, [$?], URI); % path-empty ?query +parse_ipv6_end([$#|Rest], _Acc, URI) -> + parse_fragment(Rest, [], URI); % path-empty +parse_ipv6_end([], _Acc, URI) -> + URI. + + +-spec validate_ipv6_address(list()) -> list(). +validate_ipv6_address(Addr) -> + case inet:parse_ipv6strict_address(Addr) of + {ok, _} -> Addr; + {error, _} -> throw(uri_parse_error) + end. + + %%------------------------------------------------------------------------- %% [RFC 3986, Chapter 3.2.2. Port] %% @@ -1106,8 +1322,20 @@ is_digit(C) when $0 =< C, C =< $9 -> true; is_digit(_) -> false. +-spec is_hex_digit(char()) -> boolean(). +is_hex_digit(C) + when $0 =< C, C =< $9;$a =< C, C =< $f;$A =< C, C =< $F -> true; +is_hex_digit(_) -> false. + %% Returns the size of a binary exluding the first element. %% Used in calls to split_binary(). -spec byte_size_exl_head(binary()) -> number(). byte_size_exl_head(<<>>) -> 0; byte_size_exl_head(Binary) -> byte_size(Binary) + 1. + +% Remove brackets from binary +-spec remove_brackets(binary()) -> binary(). +remove_brackets(?STRING_REST($[,Addr)) -> + A1 = binary:replace(Addr, <<$[>>, <<>>), + binary:replace(A1, <<$]>>, <<>>); +remove_brackets(Addr) -> Addr. diff --git a/lib/stdlib/test/uri_string_SUITE.erl b/lib/stdlib/test/uri_string_SUITE.erl index 189941de03..9b8e52f0b2 100644 --- a/lib/stdlib/test/uri_string_SUITE.erl +++ b/lib/stdlib/test/uri_string_SUITE.erl @@ -22,9 +22,12 @@ -include_lib("common_test/include/ct.hrl"). -export([all/0, suite/0,groups/0, - parse_binary_fragment/1, parse_binary_host/1, parse_binary_path/1, parse_binary_port/1, + parse_binary_fragment/1, parse_binary_host/1, parse_binary_host_ipv4/1, + parse_binary_host_ipv6/1, + parse_binary_path/1, parse_binary_port/1, parse_binary_query/1, parse_binary_scheme/1, parse_binary_userinfo/1, - parse_fragment/1, parse_host/1, parse_path/1, parse_port/1, + parse_fragment/1, parse_host/1, parse_host_ipv4/1, parse_host_ipv6/1, + parse_path/1, parse_port/1, parse_query/1, parse_scheme/1, parse_userinfo/1, parse_list/1, parse_binary/1, parse_mixed/1, parse_relative/1 ]). @@ -37,6 +40,8 @@ all() -> parse_binary_scheme, parse_binary_userinfo, parse_binary_host, + parse_binary_host_ipv4, + parse_binary_host_ipv6, parse_binary_port, parse_binary_path, parse_binary_query, @@ -44,6 +49,8 @@ all() -> parse_scheme, parse_userinfo, parse_host, + parse_host_ipv4, + parse_host_ipv6, parse_port, parse_path, parse_query, @@ -57,17 +64,6 @@ all() -> groups() -> []. - -%% TODO: Negative tests -%% uri_string:parse(<<"?name=ferret">>). -%% uri_string:parse("//user@") -%% uri_string:parse("foo://user@") -%% uri_string:parse(":600"). -%% -%% uri_string:parse("//:8042x"). -%% -%% io:format("# DEBUG T: >>~s<<~n", [T]), - parse_binary_scheme(_Config) -> #{} = uri_string:parse(<<>>), #{path := <<"foo">>} = uri_string:parse(<<"foo">>), @@ -95,7 +91,9 @@ parse_binary_userinfo(_Config) -> #{scheme := <<"foo">>, userinfo := <<"user">>, host := <<"localhost">>} = uri_string:parse(<<"foo://user@localhost">>), #{scheme := <<"foo">>, userinfo := <<"user:password">>, host := <<"localhost">>} = - uri_string:parse(<<"foo://user:password@localhost">>). + uri_string:parse(<<"foo://user:password@localhost">>), + uri_parse_error =(catch uri_string:parse("//user@")), + uri_parse_error = (catch uri_string:parse("foo://user@")). parse_binary_host(_Config) -> #{host := <<"hostname">>} = uri_string:parse(<<"//hostname">>), @@ -103,6 +101,30 @@ parse_binary_host(_Config) -> #{host := <<"hostname">>,scheme := <<"foo">>, userinfo := <<"user">>} = uri_string:parse(<<"foo://user@hostname">>). +parse_binary_host_ipv4(_Config) -> + #{host := <<"127.0.0.1">>} = uri_string:parse(<<"//127.0.0.1">>), + #{host := <<"127.0.0.1">>, path := <<"/over/there">>} = + uri_string:parse(<<"//127.0.0.1/over/there">>), + #{host := <<"127.0.0.1">>, query := <<"?name=ferret">>} = + uri_string:parse(<<"//127.0.0.1?name=ferret">>), + #{host := <<"127.0.0.1">>, fragment := <<"nose">>} = uri_string:parse(<<"//127.0.0.1#nose">>), + uri_parse_error = (catch uri_string:parse(<<"//127.0.0.x">>)), + uri_parse_error = (catch uri_string:parse(<<"//1227.0.0.1">>)). + +parse_binary_host_ipv6(_Config) -> + #{host := <<"::127.0.0.1">>} = uri_string:parse(<<"//[::127.0.0.1]">>), + #{host := <<"2001:0db8:0000:0000:0000:0000:1428:07ab">>} = + uri_string:parse(<<"//[2001:0db8:0000:0000:0000:0000:1428:07ab]">>), + #{host := <<"::127.0.0.1">>, path := <<"/over/there">>} = + uri_string:parse(<<"//[::127.0.0.1]/over/there">>), + #{host := <<"::127.0.0.1">>, query := <<"?name=ferret">>} = + uri_string:parse(<<"//[::127.0.0.1]?name=ferret">>), + #{host := <<"::127.0.0.1">>, fragment := <<"nose">>} = + uri_string:parse(<<"//[::127.0.0.1]#nose">>), + uri_parse_error = (catch uri_string:parse(<<"//[::127.0.0.x]">>)), + uri_parse_error = (catch uri_string:parse(<<"//[::1227.0.0.1]">>)), + uri_parse_error = (catch uri_string:parse(<<"//[2001:0db8:0000:0000:0000:0000:1428:G7ab]">>)). + parse_binary_port(_Config) -> #{path:= <<"/:8042">>} = uri_string:parse(<<"/:8042">>), @@ -115,7 +137,9 @@ parse_binary_port(_Config) -> #{scheme := <<"foo">>, host := <<>>, port := 8042} = uri_string:parse(<<"foo://:8042">>), #{scheme := <<"foo">>, host := <<"example.com">>, port := 8042} = - uri_string:parse(<<"foo://example.com:8042">>). + uri_string:parse(<<"foo://example.com:8042">>), + uri_parse_error = (catch uri_string:parse(":600")), + uri_parse_error = (catch uri_string:parse("//:8042x")). parse_binary_path(_Config) -> #{path := <<"over/there">>} = uri_string:parse(<<"over/there">>), @@ -214,6 +238,26 @@ parse_host(_Config) -> #{host := "hostname",scheme := "foo", userinfo := "user"} = uri_string:parse("foo://user@hostname"). +parse_host_ipv4(_Config) -> + #{host := "127.0.0.1"} = uri_string:parse("//127.0.0.1"), + #{host := "2001:0db8:0000:0000:0000:0000:1428:07ab"} = + uri_string:parse("//[2001:0db8:0000:0000:0000:0000:1428:07ab]"), + #{host := "127.0.0.1", path := "/over/there"} = uri_string:parse("//127.0.0.1/over/there"), + #{host := "127.0.0.1", query := "?name=ferret"} = uri_string:parse("//127.0.0.1?name=ferret"), + #{host := "127.0.0.1", fragment := "nose"} = uri_string:parse("//127.0.0.1#nose"), + uri_parse_error = (catch uri_string:parse("//127.0.0.x")), + uri_parse_error = (catch uri_string:parse("//1227.0.0.1")). + +parse_host_ipv6(_Config) -> + #{host := "::127.0.0.1"} = uri_string:parse("//[::127.0.0.1]"), + #{host := "::127.0.0.1", path := "/over/there"} = uri_string:parse("//[::127.0.0.1]/over/there"), + #{host := "::127.0.0.1", query := "?name=ferret"} = + uri_string:parse("//[::127.0.0.1]?name=ferret"), + #{host := "::127.0.0.1", fragment := "nose"} = uri_string:parse("//[::127.0.0.1]#nose"), + uri_parse_error = (catch uri_string:parse("//[::127.0.0.x]")), + uri_parse_error = (catch uri_string:parse("//[::1227.0.0.1]")), + uri_parse_error = (catch uri_string:parse("//[2001:0db8:0000:0000:0000:0000:1428:G7ab]")). + parse_port(_Config) -> #{path:= "/:8042"} = uri_string:parse("/:8042"), |