Skip to content

Commit 72d2f38

Browse files
author
José Valim
committed
Merge pull request #2402 from lexmag/encode-www-form
Introduce `URI.encode(decode)_www_form/2` functions
2 parents dc9a47d + b3cc527 commit 72d2f38

File tree

2 files changed

+72
-19
lines changed

2 files changed

+72
-19
lines changed

lib/elixir/lib/uri.ex

Lines changed: 55 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,10 @@ defmodule URI do
126126

127127
current =
128128
case :binary.split(first, "=") do
129-
[ key, value ] -> {decode(key), decode(value)}
130-
[ key ] -> {decode(key), nil}
129+
[key, value] ->
130+
{decode_www_form(key), decode_www_form(value)}
131+
[key] ->
132+
{decode_www_form(key), nil}
131133
end
132134

133135
{current, next}
@@ -142,7 +144,8 @@ defmodule URI do
142144
end
143145

144146
defp pair({k, v}) do
145-
encode(to_string(k)) <> "=" <> encode(to_string(v))
147+
encode_www_form(to_string(k)) <>
148+
"=" <> encode_www_form(to_string(v))
146149
end
147150

148151
@doc """
@@ -154,7 +157,6 @@ defmodule URI do
154157
c in ':/?#[]@!$&\'()*+,;='
155158
end
156159

157-
#
158160
@doc """
159161
Checks if the character is a "unreserved" character in a URI.
160162
@@ -191,6 +193,24 @@ defmodule URI do
191193
for <<c <- str>>, into: "", do: percent(c, predicate)
192194
end
193195

196+
@doc """
197+
Encode a string as "x-www-urlencoded".
198+
199+
## Example
200+
201+
iex> URI.encode_www_form("put: it+й")
202+
"put%3A+it%2B%D0%B9"
203+
204+
"""
205+
def encode_www_form(str) do
206+
for <<c <- str>>, into: "" do
207+
case percent(c, &char_unreserved?/1) do
208+
"%20" -> "+"
209+
pct -> pct
210+
end
211+
end
212+
end
213+
194214
defp percent(c, predicate) do
195215
if predicate.(c) do
196216
<<c>>
@@ -212,26 +232,45 @@ defmodule URI do
212232
213233
"""
214234
def decode(uri) do
215-
decode(uri, uri)
235+
unpercent(uri)
236+
catch
237+
:malformed_uri ->
238+
raise ArgumentError, "malformed URI #{inspect uri}"
216239
end
217240

218-
def decode(<<?%, hex1, hex2, tail :: binary >>, uri) do
219-
<<bsl(hex_to_dec(hex1, uri), 4) + hex_to_dec(hex2, uri)>> <> decode(tail, uri)
220-
end
241+
@doc """
242+
Decode a string as "x-www-urlencoded".
243+
244+
## Examples
245+
246+
iex> URI.decode_www_form("%3Call+in%2F")
247+
"<all in/"
221248
222-
def decode(<<head, tail :: binary >>, uri) do
223-
<<head>> <> decode(tail, uri)
249+
"""
250+
def decode_www_form(str) do
251+
String.split(str, "+") |> Enum.map_join(" ", &unpercent/1)
252+
catch
253+
:malformed_uri ->
254+
raise ArgumentError, "malformed URI #{inspect str}"
224255
end
225256

226-
def decode(<<>>, _uri), do: <<>>
257+
defp unpercent(<<?%, hex_1, hex_2, tail :: binary>>) do
258+
<<bsl(hex_to_dec(hex_1), 4) + hex_to_dec(hex_2)>> <> unpercent(tail)
259+
end
260+
defp unpercent(<<?%, _>>), do: throw(:malformed_uri)
261+
defp unpercent(<<?%>>), do: throw(:malformed_uri)
227262

228-
defp hex_to_dec(n, _uri) when n in ?A..?F, do: n - ?A + 10
229-
defp hex_to_dec(n, _uri) when n in ?a..?f, do: n - ?a + 10
230-
defp hex_to_dec(n, _uri) when n in ?0..?9, do: n - ?0
231-
defp hex_to_dec(_n, uri) do
232-
raise ArgumentError, "malformed URI #{inspect uri}"
263+
defp unpercent(<<head, tail :: binary>>) do
264+
<<head>> <> unpercent(tail)
233265
end
234266

267+
defp unpercent(<<>>), do: <<>>
268+
269+
defp hex_to_dec(n) when n in ?A..?F, do: n - ?A + 10
270+
defp hex_to_dec(n) when n in ?a..?f, do: n - ?a + 10
271+
defp hex_to_dec(n) when n in ?0..?9, do: n - ?0
272+
defp hex_to_dec(_n), do: throw(:malformed_uri)
273+
235274
@doc """
236275
Parses a URI into components.
237276

lib/elixir/test/elixir/uri_test.exs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,16 @@ defmodule URITest do
99
"%0D%0A%26%3C%25%3E%22%20%E3%82%86"
1010
end
1111

12+
test :encode_www_form do
13+
assert URI.encode_www_form("4test ~1.x") == "4test+~1.x"
14+
assert URI.encode_www_form("poll:146%") == "poll%3A146%25"
15+
assert URI.encode_www_form("/\n+/ゆ") == "%2F%0A%2B%2F%E3%82%86"
16+
end
17+
1218
test :encode_query do
1319
assert URI.encode_query([{:foo, :bar}, {:baz, :quux}]) == "foo=bar&baz=quux"
1420
assert URI.encode_query([{"foo", "bar"}, {"baz", "quux"}]) == "foo=bar&baz=quux"
15-
assert URI.encode_query([{"foo", :bar}]) == "foo=bar"
21+
assert URI.encode_query([{"foo z", :bar}]) == "foo+z=bar"
1622

1723
assert_raise ArgumentError, fn ->
1824
URI.encode_query([{"foo", 'bar'}])
@@ -23,7 +29,7 @@ defmodule URITest do
2329
assert URI.decode_query("", []) == []
2430
assert URI.decode_query("", %{}) == %{}
2531

26-
assert URI.decode_query("q=search%20query&cookie=ab%26cd&block%20buster=") ==
32+
assert URI.decode_query("q=search%20query&cookie=ab%26cd&block+buster=") ==
2733
%{"block buster" => "", "cookie" => "ab&cd", "q" => "search query"}
2834

2935
assert URI.decode_query("something=weird%3Dhappening") ==
@@ -40,7 +46,7 @@ defmodule URITest do
4046
test :decoder do
4147
decoder = URI.query_decoder("q=search%20query&cookie=ab%26cd&block%20buster=")
4248
expected = [{"q", "search query"}, {"cookie", "ab&cd"}, {"block buster", ""}]
43-
assert Enum.map(decoder, fn(x) -> x end) == expected
49+
assert Enum.map(decoder, &(&1)) == expected
4450
end
4551

4652
test :decode do
@@ -51,6 +57,14 @@ defmodule URITest do
5157
assert_raise ArgumentError, ~R/malformed URI/, fn ->
5258
URI.decode("% invalid")
5359
end
60+
assert_raise ArgumentError, ~R/malformed URI/, fn ->
61+
URI.decode("invalid%")
62+
end
63+
end
64+
65+
test :decode_www_form do
66+
assert URI.decode_www_form("%3Eval+ue%2B") == ">val ue+"
67+
assert URI.decode_www_form("%E3%82%86+") == "ゆ "
5468
end
5569

5670
test :parse_http do

0 commit comments

Comments
 (0)