Unquote basic auth username and password (#5)

Unquote username and password which were parsed by ibrowse_lib:parse_url/1 before inserting them in the basic auth header.

Previously if the user had characters like @ in their username or password, and they were percent-encoded, they were inserted encoded in the basic auth header which lead to authentication failure.
diff --git a/src/ibrowse_http_client.erl b/src/ibrowse_http_client.erl
index bee6a70..e3bc698 100644
--- a/src/ibrowse_http_client.erl
+++ b/src/ibrowse_http_client.erl
@@ -1066,7 +1066,9 @@
                                 [{"Authorization", ["Basic ", http_auth_basic(U, P)]} | Headers]
                         end;
                     _ ->
-                        [{"Authorization", ["Basic ", http_auth_basic(User, UPw)]} | Headers]
+                        User1 = ibrowse_lib:unquote(User),
+                        UPw1 = ibrowse_lib:unquote(UPw),
+                        [{"Authorization", ["Basic ", http_auth_basic(User1, UPw1)]} | Headers]
                 end,
     add_proxy_auth_headers(State, Headers_1).
 
diff --git a/src/ibrowse_lib.erl b/src/ibrowse_lib.erl
index 6c1883d..581ebfd 100644
--- a/src/ibrowse_lib.erl
+++ b/src/ibrowse_lib.erl
@@ -30,9 +30,14 @@
          get_value/3,
          parse_url/1,
          printable_date/0,
-         printable_date/1
+         unquote/1
         ]).
 
+-define(PERCENT, 37).  % $\%
+-define(IS_HEX(C), ((C >= $0 andalso C =< $9) orelse
+                    (C >= $a andalso C =< $f) orelse
+                    (C >= $A andalso C =< $F))).
+
 get_trace_status(Host, Port) ->
     ibrowse:get_config_value({trace, Host, Port}, false).
 
@@ -389,6 +394,29 @@
      $:,
      integer_to_list(MicroSecs div 1000)].
 
+%% @spec unquote(string() | binary()) -> string()
+%% @doc Unquote a URL encoded string.
+unquote(Binary) when is_binary(Binary) ->
+    unquote(binary_to_list(Binary));
+unquote(String) ->
+    qs_revdecode(lists:reverse(String)).
+
+qs_revdecode(S) ->
+    qs_revdecode(S, []).
+
+qs_revdecode([], Acc) ->
+    Acc;
+qs_revdecode([$+ | Rest], Acc) ->
+    qs_revdecode(Rest, [$\s | Acc]);
+qs_revdecode([Lo, Hi, ?PERCENT | Rest], Acc) when ?IS_HEX(Lo), ?IS_HEX(Hi) ->
+    qs_revdecode(Rest, [(unhexdigit(Lo) bor (unhexdigit(Hi) bsl 4)) | Acc]);
+qs_revdecode([C | Rest], Acc) ->
+    qs_revdecode(Rest, [C | Acc]).
+
+unhexdigit(C) when C >= $0, C =< $9 -> C - $0;
+unhexdigit(C) when C >= $a, C =< $f -> C - $a + 10;
+unhexdigit(C) when C >= $A, C =< $F -> C - $A + 10.
+
 do_trace(Fmt, Args) ->
     do_trace(get(my_trace_flag), Fmt, Args).
 
@@ -462,4 +490,15 @@
               ?assertMatch(Expected_result, parse_url(Url))
       end, Urls).
 
--endif.
+unquote_test() ->
+    ?assertEqual("foo bar",
+        unquote("foo+bar")),
+    ?assertEqual("foo bar",
+        unquote("foo%20bar")),
+    ?assertEqual("foo\r\n",
+        unquote("foo%0D%0A")),
+    ?assertEqual("foo\r\n",
+        unquote(<<"foo%0D%0A">>)),
+    ok.
+
+-endif.
\ No newline at end of file