Skip to content

Commit be84c5f

Browse files
GriffinMBjosevalim
authored andcommitted
Whitelist accepted URI schemes (phoenixframework#172)
1 parent 4f562e2 commit be84c5f

File tree

2 files changed

+68
-0
lines changed

2 files changed

+68
-0
lines changed

lib/phoenix_html/link.ex

+39
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ defmodule Phoenix.HTML.Link do
6666
config :phoenix_html, csrf_token_generator: {MyGenerator, :get_token, []}
6767
6868
"""
69+
@valid_uri_schemes ["http:", "https:", "ftp:", "ftps:", "mailto:",
70+
"news:", "irc:", "gopher:", "nntp:", "feed:",
71+
"telnet:", "mms:", "rtsp:", "svn:", "tel:", "fax:",
72+
"xmpp:"]
73+
6974
def link(text, opts)
7075

7176
def link(opts, do: contents) when is_list(opts) do
@@ -78,6 +83,9 @@ defmodule Phoenix.HTML.Link do
7883

7984
def link(text, opts) do
8085
{to, opts} = pop_required_option!(opts, :to, "expected non-nil value for :to in link/2")
86+
87+
to = valid_destination!(to, "link/2")
88+
8189
{method, opts} = Keyword.pop(opts, :method, :get)
8290

8391
if method == :get do
@@ -129,6 +137,8 @@ defmodule Phoenix.HTML.Link do
129137
{to, opts} = pop_required_option!(opts, :to, "option :to is required in button/2")
130138
{method, opts} = Keyword.pop(opts, :method, :post)
131139

140+
to = valid_destination!(to, "button/2")
141+
132142
if method == :get do
133143
opts = skip_csrf(opts)
134144
content_tag(:button, text, [data: [method: method, to: to]] ++ opts)
@@ -165,4 +175,33 @@ defmodule Phoenix.HTML.Link do
165175

166176
{value, opts}
167177
end
178+
179+
defp valid_destination!(to, calling_func) do
180+
if invalid_destination?(to) do
181+
raise ArgumentError, """
182+
unsupported scheme given to #{calling_func}. In case you want to link to an
183+
unknown or unsafe scheme, such as javascript, use a tuple: {:javascript, rest}
184+
"""
185+
end
186+
187+
valid_destination!(to)
188+
end
189+
defp valid_destination!({:safe, to}) do
190+
{:safe, valid_destination!(to)}
191+
end
192+
defp valid_destination!({other, to}) when is_atom(other) do
193+
[Atom.to_string(other), ?:, to]
194+
end
195+
defp valid_destination!(to), do: to
196+
197+
for scheme <- @valid_uri_schemes do
198+
defp invalid_destination?(unquote(scheme) <> _), do: false
199+
end
200+
defp invalid_destination?({scheme, _}) when is_atom(scheme) do
201+
false
202+
end
203+
defp invalid_destination?(to) when is_binary(to) do
204+
String.contains?(to, ":")
205+
end
206+
defp invalid_destination?(_), do: true
168207
end

test/phoenix_html/link_test.exs

+29
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,17 @@ defmodule Phoenix.HTML.LinkTest do
3131
assert safe_to_string(link(to: "/hello", do: "world")) == ~s[<a href="/hello">world</a>]
3232
end
3333

34+
test "link with scheme tuple" do
35+
assert safe_to_string(link("foo", to: {:javascript, "alert(1)"})) ==
36+
~s[<a href="javascript:alert(1)">foo</a>]
37+
38+
assert safe_to_string(link("foo", to: {:javascript, {:safe, "alert(1)"}})) ==
39+
~s[<a href="javascript:alert(1)">foo</a>]
40+
41+
assert safe_to_string(link("foo", to: {:safe, {:javascript, "alert(1)"}})) ==
42+
~s[<a href="javascript:alert(1)">foo</a>]
43+
end
44+
3445
test "link with invalid args" do
3546
msg = "expected non-nil value for :to in link/2"
3647
assert_raise ArgumentError, msg, fn ->
@@ -46,6 +57,14 @@ defmodule Phoenix.HTML.LinkTest do
4657
assert_raise ArgumentError, msg, fn ->
4758
link(to: "/hello-world")
4859
end
60+
61+
msg = """
62+
unsupported scheme given to link/2. In case you want to link to an
63+
unknown or unsafe scheme, such as javascript, use a tuple: {:javascript, rest}
64+
"""
65+
assert_raise ArgumentError, msg, fn ->
66+
link("foo", to: "javascript:alert(1)")
67+
end
4968
end
5069

5170
test "button with post (default)" do
@@ -84,4 +103,14 @@ defmodule Phoenix.HTML.LinkTest do
84103
assert safe_to_string(button("hello", to: "/world", class: "btn rounded", id: "btn")) ==
85104
~s[<button class="btn rounded" data-csrf="#{csrf_token}" data-method="post" data-to="/world" id="btn">hello</button>]
86105
end
106+
107+
test "button with invalid args" do
108+
msg = """
109+
unsupported scheme given to button/2. In case you want to link to an
110+
unknown or unsafe scheme, such as javascript, use a tuple: {:javascript, rest}
111+
"""
112+
assert_raise ArgumentError, msg, fn ->
113+
button("foo", to: "javascript:alert(1)", method: :get)
114+
end
115+
end
87116
end

0 commit comments

Comments
 (0)