Merge pull request #234 from noahshaw11/add-unquote-path-for-separate-+-encoding

Add unquote_path/1 for separate + encoding
diff --git a/src/mochiweb_util.erl b/src/mochiweb_util.erl
index 5af2b24..7102dd3 100644
--- a/src/mochiweb_util.erl
+++ b/src/mochiweb_util.erl
@@ -5,7 +5,7 @@
 
 -module(mochiweb_util).
 -author('bob@mochimedia.com').
--export([join/2, quote_plus/1, urlencode/1, parse_qs/1, unquote/1]).
+-export([join/2, quote_plus/1, urlencode/1, parse_qs/1, unquote/1, unquote_path/1]).
 -export([path_split/1]).
 -export([urlsplit/1, urlsplit_path/1, urlunsplit/1, urlunsplit_path/1]).
 -export([guess_mime/1, parse_header/1]).
@@ -34,6 +34,8 @@
 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.
+unhexdigit(Hi, Lo) ->
+    unhexdigit(Lo) bor (unhexdigit(Hi) bsl 4).
 
 %% @spec partition(String, Sep) -> {String, [], []} | {Prefix, Sep, Postfix}
 %% @doc Inspired by Python 2.5's str.partition:
@@ -256,10 +258,27 @@
 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(Rest, [(unhexdigit(Hi, Lo)) | Acc]);
 qs_revdecode([C | Rest], Acc) ->
     qs_revdecode(Rest, [C | Acc]).
 
+%% @spec unquote_path(string() | binary()) -> string()
+%% @doc Unquote a URL encoded string, does not encode + into space.
+unquote_path(Binary) when is_binary(Binary) ->
+    unquote_path(binary_to_list(Binary));
+unquote_path(String) ->
+    qs_revdecode_path(lists:reverse(String)).
+
+qs_revdecode_path(S) ->
+    qs_revdecode_path(S, []).
+
+qs_revdecode_path([], Acc) ->
+    Acc;
+qs_revdecode_path([Lo, Hi, ?PERCENT | Rest], Acc) when ?IS_HEX(Lo), ?IS_HEX(Hi) ->
+    qs_revdecode_path(Rest, [(unhexdigit(Hi, Lo)) | Acc]);
+qs_revdecode_path([C | Rest], Acc) ->
+    qs_revdecode_path(Rest, [C | Acc]).
+
 %% @spec urlsplit(Url) -> {Scheme, Netloc, Path, Query, Fragment}
 %% @doc Return a 5-tuple, does not expand % escapes. Only supports HTTP style
 %%      URLs.
diff --git a/test/mochiweb_util_tests.erl b/test/mochiweb_util_tests.erl
new file mode 100644
index 0000000..2094513
--- /dev/null
+++ b/test/mochiweb_util_tests.erl
@@ -0,0 +1,17 @@
+-module(mochiweb_util_tests).
+
+-ifdef(TEST).
+
+-include_lib("eunit/include/eunit.hrl").
+
+unquote_test() ->
+    Str = "/test/path/dwabble%20wibble+quux?qs=2",
+    ?assertEqual("/test/path/dwabble wibble quux?qs=2",
+        mochiweb_util:unquote(Str)).
+
+unquote_path_test() ->
+    Str = "/test/path/dwabble%20wibble+quux?qs=2",
+    ?assertEqual("/test/path/dwabble wibble+quux?qs=2",
+        mochiweb_util:unquote_path(Str)).
+
+-endif.