diff --git a/lib/elixir/lib/file.ex b/lib/elixir/lib/file.ex index b33731ef8f..a1512756b0 100644 --- a/lib/elixir/lib/file.ex +++ b/lib/elixir/lib/file.ex @@ -670,15 +670,21 @@ defmodule File do File.cp_r "samples", "tmp" # Same as before, but asks the user how to proceed in case of conflicts - File.cp_r "samples", "tmp", fn(source, destination) -> + File.cp_r "samples", "tmp", fn source, destination -> IO.gets("Overwriting #{destination} by #{source}. Type y to confirm. ") == "y\n" end """ @spec cp_r(Path.t, Path.t, (Path.t, Path.t -> boolean)) :: {:ok, [binary]} | {:error, posix, binary} - def cp_r(source, destination, callback \\ fn(_, _) -> true end) when is_function(callback, 2) do - source = IO.chardata_to_string(source) - destination = IO.chardata_to_string(destination) + def cp_r(source, destination, callback \\ fn _, _ -> true end) when is_function(callback, 2) do + source = + source + |> IO.chardata_to_string() + |> assert_no_null_byte!("File.cp_r/3") + destination = + destination + |> IO.chardata_to_string() + |> assert_no_null_byte!("File.cp_r/3") case do_cp_r(source, destination, callback, []) do {:error, _, _} = error -> error @@ -947,7 +953,10 @@ defmodule File do """ @spec rm_rf(Path.t) :: {:ok, [binary]} | {:error, posix, binary} def rm_rf(path) do - do_rm_rf(IO.chardata_to_string(path), {:ok, []}) + path + |> IO.chardata_to_string() + |> assert_no_null_byte!("File.cp_r/3") + |> do_rm_rf({:ok, []}) end defp do_rm_rf(path, {:ok, _} = entry) do @@ -1454,6 +1463,15 @@ defmodule File do @read_ahead_size 64 * 1024 + defp assert_no_null_byte!(binary, operation) do + case :binary.match(binary, "\0") do + {_, _} -> + raise ArgumentError, "cannot execute #{operation} for path with null byte, got: #{inspect binary}" + :nomatch -> + binary + end + end + defp normalize_modes([:utf8 | rest], binary?) do [encoding: :utf8] ++ normalize_modes(rest, binary?) end diff --git a/lib/elixir/lib/path.ex b/lib/elixir/lib/path.ex index e62ca7c124..d08203e593 100644 --- a/lib/elixir/lib/path.ex +++ b/lib/elixir/lib/path.ex @@ -615,17 +615,19 @@ defmodule Path do def wildcard(glob, opts \\ []) do mod = if Keyword.get(opts, :match_dot), do: :file, else: Path.Wildcard glob - |> chardata_to_list() + |> chardata_to_list!() |> :filelib.wildcard(mod) |> Enum.map(&IO.chardata_to_string/1) end - # expand_dot the given path by expanding "..", "." and "~". - - defp chardata_to_list(chardata) do + defp chardata_to_list!(chardata) do case :unicode.characters_to_list(chardata) do result when is_list(result) -> - result + if 0 in result do + raise ArgumentError, "cannot execute Path.wildcard/2 for path with null byte, got: #{inspect chardata}" + else + result + end {:error, encoded, rest} -> raise UnicodeConversionError, encoded: encoded, rest: rest, kind: :invalid @@ -654,6 +656,8 @@ defmodule Path do end end + # expand_dot the given path by expanding "..", "." and "~". + defp expand_dot(<<"/", rest::binary>>), do: "/" <> do_expand_dot(rest) defp expand_dot(<>) when letter in ?a..?z, diff --git a/lib/elixir/test/elixir/file_test.exs b/lib/elixir/test/elixir/file_test.exs index 16642a188a..b3aa2e226a 100644 --- a/lib/elixir/test/elixir/file_test.exs +++ b/lib/elixir/test/elixir/file_test.exs @@ -460,6 +460,11 @@ defmodule FileTest do end end + test "cp_r raises on path with null byte" do + assert_raise ArgumentError, ~r/null byte/, fn -> File.cp_r("source", "foo\0bar") end + assert_raise ArgumentError, ~r/null byte/, fn -> File.cp_r("foo\0bar", "dest") end + end + test "cp_r with src file and dest file" do src = fixture_path("file.txt") dest = tmp_path("sample.txt") @@ -1113,6 +1118,10 @@ defmodule FileTest do refute File.exists?(fixture) end + test "rm_rf raises on path with null byte" do + assert_raise ArgumentError, ~r/null byte/, fn -> File.rm_rf("foo\0bar") end + end + test "rm_rf with symlink" do from = tmp_path("tmp/from") to = tmp_path("tmp/to") diff --git a/lib/elixir/test/elixir/path_test.exs b/lib/elixir/test/elixir/path_test.exs index 35bb4cd8af..a2472a2e2f 100644 --- a/lib/elixir/test/elixir/path_test.exs +++ b/lib/elixir/test/elixir/path_test.exs @@ -32,6 +32,10 @@ defmodule PathTest do File.rm_rf tmp_path("wildcard") end + test "wildcard/2 raises on null byte" do + assert_raise ArgumentError, ~r/null byte/, fn -> Path.wildcard("foo\0bar") end + end + if windows?() do describe "Windows" do test "relative/1" do