Skip to content

Commit 9af5fbb

Browse files
authored
[3] add a dns_module option that allows overriding the DNS resolver (useful for testing) (#6)
1 parent 9ee61ab commit 9af5fbb

File tree

5 files changed

+24
-11
lines changed

5 files changed

+24
-11
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ following options:
9090

9191
* `:schemes` - List of allowed URL schemes. Defaults to `["http, "https"]`.
9292

93+
* `:dns_module` - Any module that implements DNSResolver. Defaults to DNS from the `dns` package.
9394

9495
These options can be passed to the function directly or set globally in your `config.exs`
9596
file:

lib/dns_resolver.ex

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
defmodule DNSResolver do
2+
@callback resolve(String.t) :: {atom, list}
3+
end

lib/safeurl.ex

+5-4
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ defmodule SafeURL do
133133
def allowed?(url, opts \\ []) do
134134
uri = URI.parse(url)
135135
opts = build_options(opts)
136-
address = resolve_address(uri.host)
136+
address = resolve_address(uri.host, opts.dns_module)
137137

138138
cond do
139139
uri.scheme not in opts.schemes ->
@@ -230,6 +230,7 @@ defmodule SafeURL do
230230
schemes = get_option(opts, :schemes)
231231
allowlist = get_option(opts, :allowlist)
232232
blocklist = get_option(opts, :blocklist)
233+
dns_module = get_option(opts, :dns_module)
233234

234235
blocklist =
235236
if get_option(opts, :block_reserved) do
@@ -238,7 +239,7 @@ defmodule SafeURL do
238239
blocklist
239240
end
240241

241-
%{schemes: schemes, allowlist: allowlist, blocklist: blocklist}
242+
%{schemes: schemes, allowlist: allowlist, blocklist: blocklist, dns_module: dns_module}
242243
end
243244

244245

@@ -254,7 +255,7 @@ defmodule SafeURL do
254255

255256

256257
# Resolve hostname in DNS to an IP address (if not already an IP)
257-
defp resolve_address(hostname) do
258+
defp resolve_address(hostname, dns_module) do
258259
hostname
259260
|> to_charlist()
260261
|> :inet.parse_address()
@@ -264,7 +265,7 @@ defmodule SafeURL do
264265

265266
{:error, :einval} ->
266267
# TODO: safely handle multiple IPs/round-robin DNS
267-
case DNS.resolve(hostname) do
268+
case dns_module.resolve(hostname) do
268269
{:ok, ips} -> List.first(ips)
269270
{:error, _reason} -> nil
270271
end

mix.exs

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ defmodule SafeURL.MixProject do
4949
block_reserved: true,
5050
blocklist: [],
5151
allowlist: [],
52+
dns_module: DNS,
5253
]
5354
end
5455

test/safeurl_test.exs

+14-7
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1+
defmodule TestDNSResolver do
2+
@behaviour DNSResolver
3+
4+
@impl DNSResolver
5+
def resolve(_domain), do: {:ok, [{192, 0, 78, 24}]}
6+
end
7+
18
defmodule SafeURLTest do
29
use ExUnit.Case
310

@@ -10,12 +17,12 @@ defmodule SafeURLTest do
1017

1118
describe "#allowed?" do
1219
test "returns true for only allowed schemes" do
13-
assert SafeURL.allowed?("http://includesecurity.com")
14-
assert SafeURL.allowed?("https://includesecurity.com")
15-
refute SafeURL.allowed?("ftp://includesecurity.com")
20+
assert SafeURL.allowed?("http://includesecurity.com", dns_module: TestDNSResolver)
21+
assert SafeURL.allowed?("https://includesecurity.com", dns_module: TestDNSResolver)
22+
refute SafeURL.allowed?("ftp://includesecurity.com", dns_module: TestDNSResolver)
1623

17-
assert SafeURL.allowed?("ftp://includesecurity.com", schemes: ~w[ftp])
18-
refute SafeURL.allowed?("http://includesecurity.com", schemes: ~w[ftp])
24+
assert SafeURL.allowed?("ftp://includesecurity.com", schemes: ~w[ftp], dns_module: TestDNSResolver)
25+
refute SafeURL.allowed?("http://includesecurity.com", schemes: ~w[ftp], dns_module: TestDNSResolver)
1926
end
2027

2128
test "returns false for reserved ranges" do
@@ -37,7 +44,7 @@ defmodule SafeURLTest do
3744
end
3845

3946
test "blocking custom IP ranges" do
40-
opts = [blocklist: ["5.5.0.0/16", "100.0.0.0/24"]]
47+
opts = [blocklist: ["5.5.0.0/16", "100.0.0.0/24"], dns_module: TestDNSResolver]
4148

4249
assert SafeURL.allowed?("http://includesecurity.com", opts)
4350
assert SafeURL.allowed?("http://3.3.3.3", opts)
@@ -46,7 +53,7 @@ defmodule SafeURLTest do
4653
end
4754

4855
test "only allows IPs in the allowlist when present" do
49-
opts = [allowlist: ["10.0.0.0/24"]]
56+
opts = [allowlist: ["10.0.0.0/24"], dns_module: TestDNSResolver]
5057

5158
assert SafeURL.allowed?("http://10.0.0.1/", opts)
5259
refute SafeURL.allowed?("http://72.254.45.178", opts)

0 commit comments

Comments
 (0)