Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 29 additions & 9 deletions lib/elixir/lib/uri.ex
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,8 @@ defmodule URI do
end

defp encode_kv_pair({key, value}, :rfc3986) do
encode(Kernel.to_string(key), &char_unreserved?/1) <>
"=" <> encode(Kernel.to_string(value), &char_unreserved?/1)
encode_unreserved(Kernel.to_string(key), :percent) <>
"=" <> encode_unreserved(Kernel.to_string(value), :percent)
end

defp encode_kv_pair({key, value}, :www_form) do
Expand Down Expand Up @@ -340,6 +340,10 @@ defmodule URI do
character in @reserved_characters
end

defguardp unreserved_char?(character)
when character in ?0..?9 or character in ?a..?z or character in ?A..?Z or
character in ~c"~_-."

@doc """
Checks if `character` is an unreserved one in a URI.

Expand All @@ -357,7 +361,7 @@ defmodule URI do
"""
@spec char_unreserved?(byte) :: boolean
def char_unreserved?(character) do
character in ?0..?9 or character in ?a..?z or character in ?A..?Z or character in ~c"~_-."
unreserved_char?(character)
end

@doc """
Expand Down Expand Up @@ -432,15 +436,31 @@ defmodule URI do

"""
@spec encode_www_form(binary) :: binary
def encode_www_form(<<>>), do: ""

def encode_www_form(string) when is_binary(string) do
for <<byte <- string>>, into: "" do
case percent(byte, &char_unreserved?/1) do
"%20" -> "+"
percent -> percent
end
end
encode_unreserved(string, "", :www_form)
end

# Matching upfront keeps the empty case from allocating the accumulator.
defp encode_unreserved(<<>>, _mode), do: ""

defp encode_unreserved(string, mode), do: encode_unreserved(string, "", mode)

defp encode_unreserved(<<?\s, rest::binary>>, acc, :www_form) do
encode_unreserved(rest, <<acc::binary, ?+>>, :www_form)
end

defp encode_unreserved(<<byte, rest::binary>>, acc, mode) when unreserved_char?(byte) do
encode_unreserved(rest, <<acc::binary, byte>>, mode)
end

defp encode_unreserved(<<byte, rest::binary>>, acc, mode) do
encode_unreserved(rest, <<acc::binary, ?%, hex(bsr(byte, 4)), hex(band(byte, 15))>>, mode)
end

defp encode_unreserved(<<>>, acc, _mode), do: acc

defp percent(char, predicate) do
if predicate.(char) do
<<char>>
Expand Down
30 changes: 30 additions & 0 deletions lib/elixir/test/elixir/uri_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,34 @@ defmodule URITest do
end

test "encode_www_form/1" do
assert URI.encode_www_form("") == ""
assert URI.encode_www_form("4test ~1.x") == "4test+~1.x"
assert URI.encode_www_form("poll:146%") == "poll%3A146%25"
assert URI.encode_www_form("/\n+/ゆ") == "%2F%0A%2B%2F%E3%82%86"

for byte <- 0..255, input <- [<<byte>>, <<?a, byte, ?z>>] do
assert URI.encode_www_form(input) == www_form_reference(input)
end
end

defp www_form_reference(input) do
for <<byte <- input>>, into: "" do
cond do
byte == ?\s -> "+"
unreserved_reference?(byte) -> <<byte>>
true -> "%" <> Base.encode16(<<byte>>)
end
end
end

defp rfc3986_reference(input) do
for <<byte <- input>>, into: "" do
if unreserved_reference?(byte), do: <<byte>>, else: "%" <> Base.encode16(<<byte>>)
end
end

defp unreserved_reference?(byte) do
byte in ?0..?9 or byte in ?a..?z or byte in ?A..?Z or byte in ~c"~_-."
end

test "encode_query/1,2" do
Expand All @@ -39,6 +64,11 @@ defmodule URITest do
assert URI.encode_query([{"foo[]", "+=/?&# Ñ"}], :www_form) ==
"foo%5B%5D=%2B%3D%2F%3F%26%23+%C3%91"

for byte <- 0..255, input <- [<<byte>>, <<?a, byte, ?z>>] do
expected = rfc3986_reference(input)
assert URI.encode_query([{input, input}], :rfc3986) == expected <> "=" <> expected
end

assert_raise ArgumentError, fn ->
URI.encode_query([{"foo", ~c"bar"}])
end
Expand Down
Loading