diff options
Diffstat (limited to 'lib/stdlib/src')
| -rwxr-xr-x | lib/stdlib/src/uri_string.erl | 198 | 
1 files changed, 143 insertions, 55 deletions
| diff --git a/lib/stdlib/src/uri_string.erl b/lib/stdlib/src/uri_string.erl index 3656d561be..50e8a0bf5a 100755 --- a/lib/stdlib/src/uri_string.erl +++ b/lib/stdlib/src/uri_string.erl @@ -223,9 +223,9 @@  %%  -module(uri_string). -  -export([compose_query/1, create_uri_reference/2, dissect_query/1, normalize/1,           parse/1, recompose/1, resolve_uri_reference/2, transcode/2]). +-export([is_host/1, is_path/1]).  % suppress warnings  -export_type([uri_map/0, uri_string/0]).  -define(CHAR(Char), <<Char/utf8>>). @@ -383,31 +383,31 @@ parse_relative_part(?STRING_REST("//", Rest), URI) ->      try parse_userinfo(Rest, URI) of          {T, URI1} ->  	    {Userinfo, _} = split_binary(Rest, byte_size(Rest) - byte_size(T) - 1), -	    URI1#{userinfo => Userinfo} +	    URI1#{userinfo => decode_userinfo(Userinfo)}      catch          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 => remove_brackets(Host)} +	    URI1#{host => decode_host(remove_brackets(Host))}      end;  parse_relative_part(?STRING_REST($/, Rest), URI) ->      {T, URI1} = parse_segment(Rest, URI),  % path-absolute      {Path, _} = split_binary(Rest, byte_size(Rest) - byte_size_exl_head(T)), -    URI1#{path => ?STRING_REST($/, Path)}; +    URI1#{path => decode_path(?STRING_REST($/, Path))};  parse_relative_part(?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)), -    URI1#{query => ?STRING_REST($?, Query)}; +    URI1#{query => decode_query(?STRING_REST($?, Query))};  parse_relative_part(?STRING_REST($#, Rest), URI) ->      {T, URI1} = parse_fragment(Rest, URI),  % path-empty      {Fragment, _} = split_binary(Rest, byte_size(Rest) - byte_size(T)), -    URI1#{fragment => Fragment}; +    URI1#{fragment => decode_fragment(Fragment)};  parse_relative_part(?STRING_REST(Char, Rest), URI) ->      case is_segment_nz_nc(Char) of          true ->              {T, URI1} = parse_segment_nz_nc(Rest, URI),  % path-noscheme              {Path, _} = split_binary(Rest, byte_size(Rest) - byte_size_exl_head(T)), -            URI1#{path => ?STRING_REST(Char, Path)}; +            URI1#{path => decode_path(?STRING_REST(Char, Path))};          false -> throw(uri_parse_error)      end. @@ -491,11 +491,11 @@ parse_segment(?STRING_REST($/, Rest), URI) ->  parse_segment(?STRING_REST($?, Rest), URI) ->      {T, URI1} = parse_query(Rest, URI),  % ?query      {Query, _} = split_binary(Rest, byte_size(Rest) - byte_size_exl_head(T)), -    {Rest, URI1#{query => ?STRING_REST($?, Query)}}; +    {Rest, URI1#{query => decode_query(?STRING_REST($?, Query))}};  parse_segment(?STRING_REST($#, Rest), URI) ->      {T, URI1} = parse_fragment(Rest, URI),      {Fragment, _} = split_binary(Rest, byte_size(Rest) - byte_size(T)), -    {Rest, URI1#{fragment => Fragment}}; +    {Rest, URI1#{fragment => decode_fragment(Fragment)}};  parse_segment(?STRING_REST(Char, Rest), URI) ->      case is_pchar(Char) of          true -> parse_segment(Rest, URI); @@ -515,16 +515,16 @@ parse_segment([H|Rest], Acc, URI) when is_list(H) ->  parse_segment([$/|Rest], Acc, URI) ->      parse_segment(Rest, [$/|Acc], URI);  % segment  parse_segment([$?|Rest], Acc, URI) -> -    parse_query(Rest, [$?], URI#{path => lists:reverse(Acc)});  % ?query +    parse_query(Rest, [$?], URI#{path => decode_path(lists:reverse(Acc))});  % ?query  parse_segment([$#|Rest], Acc, URI) -> -    parse_fragment(Rest, [], URI#{path => lists:reverse(Acc)}); +    parse_fragment(Rest, [], URI#{path => decode_path(lists:reverse(Acc))});  parse_segment([Char|Rest], Acc, URI) ->      case is_pchar(Char) of          true -> parse_segment(Rest, [Char|Acc], URI);          false -> throw(uri_parse_error)      end;  parse_segment([], Acc, URI) -> -    URI#{path => lists:reverse(Acc)}. +    URI#{path => decode_path(lists:reverse(Acc))}.  %%-------------------------------------------------------------------------  %%    path-noscheme @@ -535,11 +535,11 @@ parse_segment_nz_nc(?STRING_REST($/, Rest), URI) ->  parse_segment_nz_nc(?STRING_REST($?, Rest), URI) ->      {T, URI1} = parse_query(Rest, URI),  % ?query      {Query, _} = split_binary(Rest, byte_size(Rest) - byte_size_exl_head(T)), -    {Rest, URI1#{query => ?STRING_REST($?, Query)}}; +    {Rest, URI1#{query => decode_query(?STRING_REST($?, Query))}};  parse_segment_nz_nc(?STRING_REST($#, Rest), URI) ->      {T, URI1} = parse_fragment(Rest, URI),      {Fragment, _} = split_binary(Rest, byte_size(Rest) - byte_size(T)), -    {Rest, URI1#{fragment => Fragment}}; +    {Rest, URI1#{fragment => decode_fragment(Fragment)}};  parse_segment_nz_nc(?STRING_REST(Char, Rest), URI) ->      case is_segment_nz_nc(Char) of          true -> parse_segment_nz_nc(Rest, URI); @@ -559,16 +559,16 @@ parse_segment_nz_nc([H|Rest], Acc, URI) when is_list(H) ->  parse_segment_nz_nc([$/|Rest], Acc, URI) ->      parse_segment(Rest, [$/|Acc], URI);  % segment  parse_segment_nz_nc([$?|Rest], Acc, URI) -> -    parse_query(Rest, [$?], URI#{path => lists:reverse(Acc)});  % ?query +    parse_query(Rest, [$?], URI#{path => decode_path(lists:reverse(Acc))});  % ?query  parse_segment_nz_nc([$#|Rest], Acc, URI) -> -    parse_fragment(Rest, [], URI#{path => lists:reverse(Acc)}); +    parse_fragment(Rest, [], URI#{path => decode_path(lists:reverse(Acc))});  parse_segment_nz_nc([Char|Rest], Acc, URI) ->      case is_segment_nz_nc(Char) of          true -> parse_segment_nz_nc(Rest, [Char|Acc], URI);          false -> throw(uri_parse_error)      end;  parse_segment_nz_nc([], Acc, URI) -> -    URI#{path => lists:reverse(Acc)}. +    URI#{path => decode_path(lists:reverse(Acc))}.  %% Check if char is pchar.  -spec is_pchar(char()) -> boolean(). @@ -664,31 +664,31 @@ parse_hier(?STRING_REST("//", Rest), URI) ->      try parse_userinfo(Rest, URI) of          {T, URI1} ->  	    {Userinfo, _} = split_binary(Rest, byte_size(Rest) - byte_size(T) - 1), -	    {Rest, URI1#{userinfo => Userinfo}} +	    {Rest, URI1#{userinfo => decode_userinfo(Userinfo)}}      catch          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 => remove_brackets(Host)}} +	    {Rest, URI1#{host => decode_host(remove_brackets(Host))}}      end;  parse_hier(?STRING_REST($/, Rest), URI) ->      {T, URI1} = parse_segment(Rest, URI),  % path-absolute      {Path, _} = split_binary(Rest, byte_size(Rest) - byte_size_exl_head(T)), -    {Rest, URI1#{path => ?STRING_REST($/, Path)}}; +    {Rest, URI1#{path => decode_path(?STRING_REST($/, Path))}};  parse_hier(?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)}}; +    {Rest, URI1#{query => decode_query(?STRING_REST($?, Query))}};  parse_hier(?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}}; +    {Rest, URI1#{fragment => decode_fragment(Fragment)}};  parse_hier(?STRING_REST(Char, Rest), URI) ->  % path-rootless      case is_pchar(Char) of          true ->  % segment_nz              {T, URI1} = parse_segment(Rest, URI),              {Path, _} = split_binary(Rest, byte_size(Rest) - byte_size_exl_head(T)), -            {Rest, URI1#{path => ?STRING_REST(Char, Path)}}; +            {Rest, URI1#{path => decode_path(?STRING_REST(Char, Path))}};          false -> throw(uri_parse_error)      end;  parse_hier(?STRING_EMPTY, URI) -> @@ -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 => remove_brackets(Host)}}; +    {Rest, URI1#{host => decode_host(remove_brackets(Host))}};  parse_userinfo(?STRING_REST(Char, Rest), URI) ->      case is_userinfo(Char) of          true -> parse_userinfo(Rest, URI); @@ -778,11 +778,11 @@ parse_userinfo([$@], _Acc, _URI) ->      %% URI cannot end in userinfo state      throw(uri_parse_error);  parse_userinfo([$@|Rest], Acc, URI) -> -    parse_host(Rest, [],  URI#{userinfo => lists:reverse(Acc)}); +    parse_host(Rest, [],  URI#{userinfo => decode_userinfo(lists:reverse(Acc))});  parse_userinfo([Char|Rest], Acc, URI) ->      case is_userinfo(Char) of          true -> parse_userinfo(Rest, [Char|Acc], URI); -        false -> throw(uri_parse_error) % URI#{userinfo => lists:reverse(Acc)} +        false -> throw(uri_parse_error)      end;  parse_userinfo([], _Acc, _URI) ->      %% URI cannot end in userinfo state @@ -843,17 +843,17 @@ parse_host(?STRING_REST($:, Rest), URI) ->  parse_host(?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)}}; +    {Rest, URI1#{path => decode_path(?STRING_REST($/, Path))}};  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)}}; +    {Rest, URI1#{query => decode_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}}; +    {Rest, URI1#{fragment => decode_fragment(Fragment)}};  parse_host(?STRING_REST(Char, Rest), URI) ->      case is_digit(Char) of          true -> parse_ipv4_bin(Rest, [Char], URI); @@ -871,13 +871,13 @@ parse_host([H|Rest], Acc, URI) when is_binary(H) ->  parse_host([H|Rest], Acc, URI) when is_list(H) ->      parse_host(H ++ Rest, Acc, URI);  parse_host([$:|Rest], Acc, URI) -> -    parse_port(Rest, [], URI#{host => lists:reverse(Acc)}); +    parse_port(Rest, [], URI#{host => decode_host(lists:reverse(Acc))});  parse_host([$/|Rest], Acc, URI) -> -    parse_segment(Rest, [$/], URI#{host => lists:reverse(Acc)});  % path-abempty +    parse_segment(Rest, [$/], URI#{host => decode_host(lists:reverse(Acc))});  % path-abempty  parse_host([$?|Rest], Acc, URI) -> -    parse_query(Rest, [$?], URI#{host => lists:reverse(Acc)});  % path-empty ?query +    parse_query(Rest, [$?], URI#{host => decode_host(lists:reverse(Acc))});  % path-empty ?query  parse_host([$#|Rest], Acc, URI) -> -    parse_fragment(Rest, [], URI#{host => lists:reverse(Acc)});  % path-empty +    parse_fragment(Rest, [], URI#{host => decode_host(lists:reverse(Acc))});  % path-empty  parse_host([$[|Rest], _Acc, URI) ->      parse_ipv6(Rest, [], URI);  parse_host([Char|Rest], Acc, URI) -> @@ -886,7 +886,7 @@ parse_host([Char|Rest], Acc, URI) ->          false -> parse_reg_name([Char|Rest], Acc, URI)      end;  parse_host([], Acc, URI) -> -    URI#{host => lists:reverse(Acc)}. +    URI#{host => decode_host(lists:reverse(Acc))}.  -spec parse_reg_name(binary(), uri_map()) -> {binary(), uri_map()}. @@ -898,15 +898,15 @@ parse_reg_name(?STRING_REST($:, Rest), URI) ->  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)}}; +    {Rest, URI1#{path => decode_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)}}; +    {Rest, URI1#{query => decode_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}}; +    {Rest, URI1#{fragment => decode_fragment(Fragment)}};  parse_reg_name(?STRING_REST(Char, Rest), URI) ->      case is_reg_name(Char) of          true -> parse_reg_name(Rest, URI); @@ -924,20 +924,20 @@ parse_reg_name([H|Rest], Acc, URI) when is_binary(H) ->  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_port(Rest, [], URI#{host => decode_host(lists:reverse(Acc))});  parse_reg_name([$/|Rest], Acc, URI) -> -    parse_segment(Rest, [$/], URI#{host => lists:reverse(Acc)});  % path-abempty +    parse_segment(Rest, [$/], URI#{host => decode_host(lists:reverse(Acc))});  % path-abempty  parse_reg_name([$?|Rest], Acc, URI) -> -    parse_query(Rest, [$?], URI#{host => lists:reverse(Acc)});  % path-empty ?query +    parse_query(Rest, [$?], URI#{host => decode_host(lists:reverse(Acc))});  % path-empty ?query  parse_reg_name([$#|Rest], Acc, URI) -> -    parse_fragment(Rest, [], URI#{host => lists:reverse(Acc)});  % path-empty +    parse_fragment(Rest, [], URI#{host => decode_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)}. +    URI#{host => decode_host(lists:reverse(Acc))}.  %% Check if char is allowed in reg-name  -spec is_reg_name(char()) -> boolean(). @@ -956,17 +956,17 @@ 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)}}; +    {Rest, URI1#{path => decode_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)}}; +    {Rest, URI1#{query => decode_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}}; +    {Rest, URI1#{fragment => decode_fragment(Fragment)}};  parse_ipv4_bin(?STRING_REST(Char, Rest), Acc, URI) ->      case is_ipv4(Char) of          true -> parse_ipv4_bin(Rest, [Char|Acc], URI); @@ -1062,15 +1062,15 @@ parse_ipv6_bin_end(?STRING_REST($:, Rest), URI) ->  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)}}; +    {Rest, URI1#{path => decode_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)}}; +    {Rest, URI1#{query => decode_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}}; +    {Rest, URI1#{fragment => decode_fragment(Fragment)}};  parse_ipv6_bin_end(?STRING_REST(Char, Rest), URI) ->      case is_ipv6(Char) of          true -> parse_ipv6_bin_end(Rest, URI); @@ -1120,15 +1120,15 @@ validate_ipv6_address(Addr) ->  parse_port(?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)}}; +    {Rest, URI1#{path => decode_path(?STRING_REST($/, Path))}};  parse_port(?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)}}; +    {Rest, URI1#{query => decode_query(?STRING_REST($?, Query))}};  parse_port(?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}}; +    {Rest, URI1#{fragment => decode_fragment(Fragment)}};  parse_port(?STRING_REST(Char, Rest), URI) ->      case is_digit(Char) of          true -> parse_port(Rest, URI); @@ -1180,7 +1180,7 @@ parse_port([], Acc, URI) ->  parse_query(?STRING_REST($#, Rest), URI) ->      {T, URI1} = parse_fragment(Rest, URI),      {Fragment, _} = split_binary(Rest, byte_size(Rest) - byte_size(T)), -    {Rest, URI1#{fragment => Fragment}}; +    {Rest, URI1#{fragment => decode_fragment(Fragment)}};  parse_query(?STRING_REST(Char, Rest), URI) ->      case is_query(Char) of          true -> parse_query(Rest, URI); @@ -1198,18 +1198,19 @@ parse_query([H|Rest], Acc, URI) when is_binary(H) ->  parse_query([H|Rest], Acc, URI) when is_list(H) ->      parse_query(H ++ Rest, Acc, URI);  parse_query([$#|Rest], Acc, URI) -> -    parse_fragment(Rest, [], URI#{query => lists:reverse(Acc)}); +    parse_fragment(Rest, [], URI#{query => decode_query(lists:reverse(Acc))});  parse_query([Char|Rest], Acc, URI) ->      case is_query(Char) of          true -> parse_query(Rest, [Char|Acc], URI);          false -> throw(uri_parse_error)      end;  parse_query([], Acc, URI) -> -    URI#{query => lists:reverse(Acc)}. +    URI#{query => decode_query(lists:reverse(Acc))}.  %% Check if char is allowed in query  -spec is_query(char()) -> boolean().  is_query($/) -> true; +is_query($?) -> true;  is_query(Char) -> is_pchar(Char). @@ -1245,7 +1246,7 @@ parse_fragment([Char|Rest], Acc, URI) ->          false -> throw(uri_parse_error)      end;  parse_fragment([], Acc, URI) -> -    URI#{fragment => lists:reverse(Acc)}. +    URI#{fragment => decode_fragment(lists:reverse(Acc))}.  %% Check if char is allowed in fragment  -spec is_fragment(char()) -> boolean(). @@ -1339,3 +1340,90 @@ remove_brackets(?STRING_REST($[,Addr)) ->      A1 = binary:replace(Addr, <<$[>>, <<>>),      binary:replace(A1, <<$]>>, <<>>);  remove_brackets(Addr) -> Addr. + + +%%------------------------------------------------------------------------- +%% [RFC 3986, Chapter 2.1.  Percent-Encoding] +%% +%% A percent-encoding mechanism is used to represent a data octet in a +%% component when that octet's corresponding character is outside the +%% allowed set or is being used as a delimiter of, or within, the +%% component.  A percent-encoded octet is encoded as a character +%% triplet, consisting of the percent character "%" followed by the two +%% hexadecimal digits representing that octet's numeric value.  For +%% example, "%20" is the percent-encoding for the binary octet +%% "00100000" (ABNF: %x20), which in US-ASCII corresponds to the space +%% character (SP).  Section 2.4 describes when percent-encoding and +%% decoding is applied. +%% +%%   pct-encoded = "%" HEXDIG HEXDIG +%%------------------------------------------------------------------------- +-spec decode_userinfo(list()|binary()) -> list() | binary(). +decode_userinfo(Cs) -> +    decode(Cs, fun is_userinfo/1, <<>>). + + +-spec decode_host(list()|binary()) -> list() | binary(). +decode_host(Cs) -> +    decode(Cs, fun is_host/1, <<>>). + +%% Check if char is allowed in host +-spec is_host(char()) -> boolean(). +is_host($:) -> true; +is_host(Char) -> is_unreserved(Char) orelse is_sub_delim(Char). + + +-spec decode_path(list()|binary()) -> list() | binary(). +decode_path(Cs) -> +    decode(Cs, fun is_path/1, <<>>). + +%% Check if char is allowed in path +-spec is_path(char()) -> boolean(). +is_path($/) -> true; + +is_path(Char) -> is_pchar(Char). + + +-spec decode_query(list()|binary()) -> list() | binary(). +decode_query(Cs) -> +    decode(Cs, fun is_query/1, <<>>). + +-spec decode_fragment(list()|binary()) -> list() | binary(). +decode_fragment(Cs) -> +    decode(Cs, fun is_host/1, <<>>). + + +-spec decode(list()|binary(), fun(), binary()) -> list() | binary(). +decode(<<$%,C0,C1,Cs/binary>>, Fun, Acc) -> +    case is_hex_digit(C0) andalso is_hex_digit(C1) of +        true -> +            B = hex2dec(C0)*16+hex2dec(C1), +            decode(Cs, Fun, <<Acc/binary, B>>); +        false -> throw(uri_parse_error) +    end; +decode(<<C,Cs/binary>>, Fun, Acc) -> +    case Fun(C) of +        true -> decode(Cs, Fun, <<Acc/binary, C>>); +        false -> throw(uri_parse_error) +    end; +decode(<<>>, _Fun, Acc) -> +    Acc; +decode([$%,C0,C1|Cs], Fun, Acc) -> +    case is_hex_digit(C0) andalso is_hex_digit(C1) of +        true -> +            B = hex2dec(C0)*16+hex2dec(C1), +            decode(Cs, Fun, <<Acc/binary, B>>); +        false -> throw(uri_parse_error) +    end; +decode([C|Cs], Fun, Acc) -> +    case Fun(C) of +        true -> decode(Cs, Fun, <<Acc/binary, C>>); +        false -> throw(uri_parse_error) +    end; +decode([], _Fun, Acc) -> +    unicode:characters_to_list(Acc). + + +hex2dec(X) when (X >= $0) andalso (X =< $9) -> X - $0; +hex2dec(X) when (X >= $A) andalso (X =< $F) -> X - $A + 10; +hex2dec(X) when (X >= $a) andalso (X =< $f) -> X - $a + 10. | 
