diff --git a/bootstrap b/bootstrap index 0ea87060..0c74dcbf 100755 --- a/bootstrap +++ b/bootstrap @@ -156,7 +156,7 @@ set_httpc_options(_, []) -> ok; set_httpc_options(Scheme, Proxy) -> - {ok, {_, UserInfo, Host, Port, _, _}} = http_uri:parse(Proxy), + #{userinfo := UserInfo, host := Host, port := Port} = rebar_uri:parse(Proxy), httpc:set_options([{Scheme, {{Host, Port}, []}}], rebar), proxy_ipfamily(Host, inet:gethostbyname(Host)), set_proxy_auth(UserInfo). @@ -724,7 +724,7 @@ set_proxy_auth(UserInfo) -> [Username, Password] = re:split(UserInfo, ":", [{return, list}, {parts,2}, unicode]), %% password may contain url encoded characters, need to decode them first - put(proxy_auth, [{proxy_auth, {Username, http_uri:decode(Password)}}]). + put(proxy_auth, [{proxy_auth, {Username, rebar_uri:percent_decode(Password)}}]). get_proxy_auth() -> case get(proxy_auth) of diff --git a/src/rebar_uri.erl b/src/rebar_uri.erl index 428e4870..a928ef16 100644 --- a/src/rebar_uri.erl +++ b/src/rebar_uri.erl @@ -3,9 +3,22 @@ -export([ parse/1, parse/2, scheme_defaults/0, - append_path/2 + append_path/2, percent_decode/1 ]). +-type error() :: {error, atom(), term()}. + +-define(DEC2HEX(X), + if ((X) >= 0) andalso ((X) =< 9) -> (X) + $0; + ((X) >= 10) andalso ((X) =< 15) -> (X) + $A - 10 + end). + +-define(HEX2DEC(X), + if ((X) >= $0) andalso ((X) =< $9) -> (X) - $0; + ((X) >= $A) andalso ((X) =< $F) -> (X) - $A + 10; + ((X) >= $a) andalso ((X) =< $f) -> (X) - $a + 10 + end). + -ifdef(OTP_RELEASE). -spec parse(URIString) -> URIMap when URIString :: uri_string:uri_string(), @@ -87,6 +100,33 @@ append_path(Url, ExtraPath) -> end. -endif. +%% Taken from OTP 23.2 +-spec percent_decode(URI) -> Result when + URI :: uri_string:uri_string() | uri_string:uri_map(), + Result :: uri_string:uri_string() | + uri_string:uri_map() | + {error, {invalid, {atom(), {term(), term()}}}}. +percent_decode(URIMap) when is_map(URIMap)-> + Fun = fun (K,V) when K =:= userinfo; K =:= host; K =:= path; + K =:= query; K =:= fragment -> + case raw_decode(V) of + {error, Reason, Input} -> + throw({error, {invalid, {K, {Reason, Input}}}}); + Else -> + Else + end; + %% Handle port and scheme + (_,V) -> + V + end, + try maps:map(Fun, URIMap) + catch throw:Return -> + Return + end; +percent_decode(URI) when is_list(URI) orelse + is_binary(URI) -> + raw_decode(URI). + %% OTP 21+ -ifdef(OTP_RELEASE). scheme_defaults() -> @@ -129,3 +169,44 @@ maybe_port(Url, Host, Port, PathQ) -> false -> Host ++ PathQ % port was implicit end. -endif. + +-spec raw_decode(list()|binary()) -> list() | binary() | error(). +raw_decode(Cs) -> + raw_decode(Cs, <<>>). + +raw_decode(L, Acc) when is_list(L) -> + try + B0 = unicode:characters_to_binary(L), + B1 = raw_decode(B0, Acc), + unicode:characters_to_list(B1) + catch + throw:{error, Atom, RestData} -> + {error, Atom, RestData} + end; +raw_decode(<<$%,C0,C1,Cs/binary>>, Acc) -> + case is_hex_digit(C0) andalso is_hex_digit(C1) of + true -> + B = ?HEX2DEC(C0)*16+?HEX2DEC(C1), + raw_decode(Cs, <>); + false -> + throw({error,invalid_percent_encoding,<<$%,C0,C1>>}) + end; +raw_decode(<>, Acc) -> + raw_decode(Cs, <>); +raw_decode(<<>>, Acc) -> + check_utf8(Acc). + +-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 Cs if it is utf8 encoded. +check_utf8(Cs) -> + case unicode:characters_to_list(Cs) of + {incomplete,_,_} -> + throw({error,invalid_utf8,Cs}); + {error,_,_} -> + throw({error,invalid_utf8,Cs}); + _ -> Cs + end.