Add percent_encode/2 and plus_encode/2

This commit is contained in:
Gavin M. Roy 2016-10-27 12:02:26 -04:00
parent 074dffa0dd
commit 2850835414
4 changed files with 87 additions and 5 deletions

View file

@ -19,8 +19,10 @@ Function | Description
`parse/2` | Parse a URI, returning the result as either a `uri()` or `url()`.
`percent_decode/1` | Decode a percent encoded string value.
`percent_encode/1` | Percent encode a string value.
`percent_encode/2` | Percent encode a string value, explicitly stating the desired case for hexidecimal values. Pass `uppercase` to the second value to have hex values returned as `%2F` instead of `%2f`.
`plus_decode/1` | Decode a percent encoded string value that uses pluses for spaces.
`plus_encode/1` | Percent encode a string value similar to `percent_encode/1`, but encodes spaces with a plus (`+`) instead of `%20`.
`plus_encode/2` | Percent encode a string value similar to `percent_encode/1`, but encodes spaces with a plus (`+`) instead of `%20`, explicitly stating the desired case for hexidecimal values. Pass `uppercase` to the second value to have hex values returned as `%2F` instead of `%2f`.
### Types

View file

@ -1,6 +1,6 @@
{application, urilib, [
{description, "A RFC-3986 URI Library for parsing and building URIs"},
{vsn,"0.1.1"},
{vsn,"0.2.0"},
{licenses, ["BSD"]},
{links, [{"Github", "https://github.com/gmr/urilib"}]},
{maintainers, ["Gavin M. Roy"]},
@ -11,6 +11,6 @@
inets,
edoc
]},
{mod, {urilib, []}},
{modules, []},
{env, []}
]}.

View file

@ -11,8 +11,10 @@
parse/2,
percent_decode/1,
percent_encode/1,
percent_encode/2,
plus_decode/1,
plus_encode/1]).
plus_encode/1,
plus_encode/2]).
-export_type([scheme/0,
host/0,
@ -32,6 +34,7 @@
-compile(export_all).
-endif.
-type hexcase() :: uppercase | lowercase.
-type scheme() :: http | https | atom().
-type host() :: string().
-type tcp_port() :: integer().
@ -45,7 +48,6 @@
-type uri() :: {scheme(), authority(), path(), query(), fragment()}.
-type url() :: {scheme(), username(), password(), host(), tcp_port(), path(), query(), fragment()}.
-spec build(Value :: uri() | url()) -> string().
%% @doc Build a URI
%% @end
@ -101,12 +103,27 @@ parse(Value, url) ->
-spec percent_encode(string()) -> string().
%% @doc Percent encode a string value.
%% @doc Percent encode a string value. Note that this will return hexidecimal
%% values in lowercase. If you need uppercase values, invoke percent_encode/2
%% with the second parameter as the value ``upercase``.
%% @end
percent_encode(Value) ->
edoc_lib:escape_uri(Value).
-spec percent_encode(string(), hexcase()) -> string().
%% @doc Percent encode a string value.
%%
%% When lowercase is passed, hexidecimal strings with A-F values in them are returned
%% as lowercase. Likewise, the uppercase value will encode hexidecimal strings as
%% uppercase values.
%% @end
percent_encode(Value, lowercase) ->
percent_encode(Value);
percent_encode(Value, uppercase) ->
hex_to_upper(percent_encode(Value)).
-spec percent_decode(string()) -> string().
%% @doc Decode a percent encoded string value.
%% @end
@ -124,6 +141,21 @@ plus_encode(Value) ->
string:join([edoc_lib:escape_uri(V) || V <- string:tokens(Value, " ")], "+").
-spec plus_encode(string(), hexcase()) -> string().
%% @doc Percent encode a string value similar to encode/1, but encodes spaces with a
%% plus (`+') instead of `%20'. This function can be used for encoding query arguments.
%% When lowercase is passed, hexidecimal strings with A-F values in them are returned
%% as lowercase. Likewise, the uppercase value will encode hexidecimal strings as
%% uppercase values.
%%
%% Note: The use of plus for space is defined in RFC-1630 but does not appear in RFC-3986.
%% @end
plus_encode(Value, lowercase) ->
plus_encode(Value);
plus_encode(Value, uppercase) ->
hex_to_upper(plus_encode(Value)).
-spec plus_decode(string()) -> string().
%% @doc Decode a percent encoded string value that uses pluses for spaces.
%%
@ -284,3 +316,21 @@ url_maybe_add_fragment(Value, URL) ->
_ -> edoc_lib:escape_uri(Value)
end,
string:join([URL, Fragment], "#").
-spec hex_to_upper(string()) -> string().
%% @private
hex_to_upper(Value) ->
hex_to_upper(Value, [], []).
hex_to_upper([], [], Value) ->
Value;
hex_to_upper([], Hex, Value) ->
lists:append(Value, string:to_upper(Hex));
hex_to_upper([37|T], [], Value) ->
hex_to_upper(T, [37], Value);
hex_to_upper([H|T], [], Value) ->
hex_to_upper(T, [], lists:append(Value, [H]));
hex_to_upper(Remaining, Hex, Value) when length(Hex) == 3 ->
hex_to_upper(Remaining, [], lists:append(Value, string:to_upper(Hex)));
hex_to_upper([H|T], Hex, Value) ->
hex_to_upper(T, lists:append(Hex, [H]), Value).

View file

@ -2,6 +2,11 @@
-include_lib("eunit/include/eunit.hrl").
hex_to_upper_test() ->
Value = "%2f%c0%88this+is+%c3%a4n+%c3%aaxample+value+woot%c0%88%2f",
Expect = "%2F%C0%88this+is+%C3%A4n+%C3%AAxample+value+woot%C0%88%2F",
?assertEqual(Expect, urilib:hex_to_upper(Value)).
build_variation1_test() ->
Params = {amqp, {{"guest", "password"}, "rabbitmq", 5672}, "/%2f", [{"heartbeat", "5"}], undefined},
Expect = "amqp://guest:password@rabbitmq:5672/%2f?heartbeat=5",
@ -147,7 +152,32 @@ percent_encode_unicode_test() ->
Expect = "foo%2fbar%c0%88baz",
?assertEqual(Expect, urilib:percent_encode(Value)).
percent_encode_lowercase_unicode_test() ->
Value = "/✈this is än êxample value woot✈/",
Expect = "%2f%c0%88this%20is%20%c3%a4n%20%c3%aaxample%20value%20woot%c0%88%2f",
?assertEqual(Expect, urilib:percent_encode(Value, lowercase)).
percent_encode_uppercase_unicode_test() ->
Value = "/✈this is än êxample value woot✈/",
Expect = "%2F%C0%88this%20is%20%C3%A4n%20%C3%AAxample%20value%20woot%C0%88%2F",
?assertEqual(Expect, urilib:percent_encode(Value, uppercase)).
percent_encode_uppercase_perent_test() ->
Value = "/✈this is än êxample value woot with 30% off✈/",
Expect = "%2F%C0%88this+is+%C3%A4n+%C3%AAxample+value+woot+with+30%25+off%C0%88%2F",
?assertEqual(Expect, urilib:plus_encode(Value, uppercase)).
plus_encode_test() ->
Value = "foo/bar baz",
Expect = "foo%2fbar+baz",
?assertEqual(Expect, urilib:plus_encode(Value)).
plus_encode_lowercase_test() ->
Value = "/✈this is än êxample value woot✈/",
Expect = "%2f%c0%88this+is+%c3%a4n+%c3%aaxample+value+woot%c0%88%2f",
?assertEqual(Expect, urilib:plus_encode(Value, lowercase)).
plus_encode_uppercase_test() ->
Value = "/✈this is än êxample value woot✈/",
Expect = "%2F%C0%88this+is+%C3%A4n+%C3%AAxample+value+woot%C0%88%2F",
?assertEqual(Expect, urilib:plus_encode(Value, uppercase)).