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
3 changes: 3 additions & 0 deletions config/dev.exs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import Config

alias ExFTP.Auth.PassthroughAuth
alias ExFTP.Storage.S3Connector

config :ex_aws,
s3: [
scheme: System.get_env("AWS_SCHEME", "http://"),
Expand Down
2 changes: 1 addition & 1 deletion lib/ex_ftp/application.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ defmodule ExFTP.Application do

@impl true
def start(_type, _args) do
port = Application.get_env(:ex_ftp, :ftp_port)
port = Application.get_env(:ex_ftp, :ftp_port, 4041)

children = [
{Cachex, [:auth_cache]},
Expand Down
4 changes: 3 additions & 1 deletion lib/ex_ftp/passive_socket.ex
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ defmodule ExFTP.PassiveSocket do

def close(pid) do
GenServer.call(pid, {:close}, :infinity)
rescue
_ -> :ok
end

# Server
Expand Down Expand Up @@ -96,7 +98,7 @@ defmodule ExFTP.PassiveSocket do
def handle_call({:close}, _from, %{socket: socket, write_socket: write_socket} = state) do
Logger.info("Closing PASV connection.")
write_socket && :gen_tcp.close(write_socket)
:gen_tcp.close(socket)
socket && :gen_tcp.close(socket)
{:stop, :normal, :ok, state}
end

Expand Down
56 changes: 33 additions & 23 deletions lib/ex_ftp/storage/s3_connector.ex
Original file line number Diff line number Diff line change
Expand Up @@ -230,11 +230,15 @@ defmodule ExFTP.Storage.S3Connector do
bucket = get_bucket(config, path)
key = get_prefix(config, bucket, path)

bucket
|> ExAws.S3.delete_object(key)
|> ExAws.request!()
if prefix_exists?(bucket, key) do
bucket
|> ExAws.S3.delete_object(key)
|> ExAws.request!()

{:ok, connector_state}
{:ok, connector_state}
else
{:error, "does not exist"}
end
end
end

Expand Down Expand Up @@ -302,12 +306,16 @@ defmodule ExFTP.Storage.S3Connector do
bucket = get_bucket(config, path)
prefix = get_prefix(config, bucket, path)

stream =
bucket
|> ExAws.S3.download_file(prefix, :memory, chunk_size: 5 * 1024 * 1024)
|> ExAws.stream!()
if prefix_exists?(bucket, prefix) do
stream =
bucket
|> ExAws.S3.download_file(prefix, :memory, chunk_size: 5 * 1024 * 1024)
|> ExAws.stream!()

{:ok, stream}
{:ok, stream}
else
{:error, "no such file"}
end
end
end

Expand All @@ -334,16 +342,12 @@ defmodule ExFTP.Storage.S3Connector do
prefix = get_prefix(config, bucket, path)

fn stream ->
try do
stream
|> chunk_stream(opts)
|> ExAws.S3.upload(bucket, prefix)
|> ExAws.request!()

{:ok, connector_state}
rescue
_ -> {:error, "Failed to transfer"}
end
stream
|> chunk_stream(opts)
|> ExAws.S3.upload(bucket, prefix, timeout: :infinity)
|> ExAws.request!()

{:ok, connector_state}
end
end
end
Expand Down Expand Up @@ -374,10 +378,16 @@ defmodule ExFTP.Storage.S3Connector do
with {:ok, config} <- validate_config(S3ConnectorConfig) do
path = Path.join(path, "")

contents = s3_get_prefix_contents(config, path, connector_state, :key)
contents =
s3_get_prefix_contents(config, path, connector_state, :key)
|> Enum.to_list()

if Enum.empty?(contents) do
{:error, "Could not get content info"}
if virtual_directory?(config, path, connector_state) do
{:ok, to_content_info(%{prefix: Path.basename(path)}, nil)}
else
{:error, "Cant get info"}
end
else
{:ok, contents |> Enum.take(1) |> hd()}
end
Expand Down Expand Up @@ -456,11 +466,11 @@ defmodule ExFTP.Storage.S3Connector do
}
end

defp to_content_info(%{key: filename, last_modified: last_mod_str, size: size}, parent_prefix) do
defp to_content_info(%{key: filename, last_modified: last_mod_str, size: size}, _parent_prefix) do
{:ok, modified_datetime, _} = DateTime.from_iso8601(last_mod_str)

%{
file_name: String.replace(filename, parent_prefix, ""),
file_name: Path.basename(filename),
modified_datetime: modified_datetime,
size: size,
access: :read_write,
Expand Down
10 changes: 0 additions & 10 deletions test/ex_ftp/util/digest_auth_util_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -63,14 +63,4 @@ defmodule ExFTP.DigestAuthUtilTest do
assert {:ok, %{status: 200}} = DigestAuthUtil.request(url, method, user, password)
end
end

describe "auth, sha512" do
test "workflow - auth , sha512" do
user = Faker.Internet.user_name()
password = Faker.Internet.slug()
url = "https://httpbin.org/digest-auth/auth/#{user}/#{password}/SHA-512"
method = :get
assert {:ok, %{status: 200}} = DigestAuthUtil.request(url, method, user, password)
end
end
end
65 changes: 58 additions & 7 deletions test/test_helper.exs
Original file line number Diff line number Diff line change
Expand Up @@ -111,14 +111,48 @@ defmodule ExFTP.StorageTester do
|> send_and_expect("PWD", [], 257, "\"/\" is the current directory")
end

def test_mkd_rmd(%{socket: socket}, dir_to_make) do
def test_mkd_rmd(%{socket: socket} = state, dir_to_make) do
# PWD
send_and_expect(socket, "PWD", [], 257, "\"/\" is the current directory")

# CWD tmp_dir
# MKD dir_to_make
send_and_expect(socket, "MKD", [dir_to_make], 257, "\"#{dir_to_make}\" directory created.")

# LIST -a an empty dir
%{socket: socket, pasv_socket: pasv_socket} = setup_pasv_connection(state)

socket
|> send_and_expect("CWD", [dir_to_make], 250, "Directory changed successfully.")
|> send_and_expect("LIST", ["-a"], 150)

assert {:ok, listing} = read_fully(pasv_socket)
expect_recv(socket, 226, "Directory send OK.")
assert String.trim(listing) != ""

# List an empty dir
%{socket: socket, pasv_socket: pasv_socket} = setup_pasv_connection(state)

socket
|> send_and_expect("CWD", [dir_to_make], 250, "Directory changed successfully.")
|> send_and_expect("LIST", [], 150)

assert {:ok, listing} = read_fully(pasv_socket)
expect_recv(socket, 226, "Directory send OK.")
assert String.trim(listing) == ""

# NLST an empty dir

%{socket: socket, pasv_socket: pasv_socket} = setup_pasv_connection(state)

socket
|> send_and_expect("CWD", [dir_to_make], 250, "Directory changed successfully.")
|> send_and_expect("NLST", [], 150)

assert {:ok, listing} = read_fully(pasv_socket)
expect_recv(socket, 226, "Directory send OK.")
assert String.trim(listing) == ""

# call it twice
send_and_expect(socket, "MKD", [dir_to_make], 521, "\"#{dir_to_make}\" directory already exists")

Expand Down Expand Up @@ -194,6 +228,14 @@ defmodule ExFTP.StorageTester do
refute byte_size(bytes) == 0
expect_recv(socket, 226, "Transfer complete.")
end)

# try to retrieve something that doesn't exist
file = "/does-not-exist"
%{pasv_socket: pasv_socket} = setup_pasv_connection(state)
send_and_expect(socket, "RETR", [file], 150)
assert {:ok, bytes} = read_fully(pasv_socket)
assert byte_size(bytes) == 0
expect_recv(socket, 451, "File not found.")
end

def test_size(state, w_dir) do
Expand All @@ -219,18 +261,21 @@ defmodule ExFTP.StorageTester do

Enum.each(files_to_store, fn file ->
%{pasv_socket: pasv_socket} = setup_pasv_connection(state)

send_and_expect(socket, "STOR", [file], 150)

File.cwd!()
|> Path.join(file)
|> File.stream!(5 * 1024 * 1024, [])
|> Enum.each(fn data ->
:ok = :gen_tcp.send(pasv_socket, data)
end)
path = Path.join(File.cwd!(), file)

data =
path
|> File.read!()

:ok = :gen_tcp.send(pasv_socket, data)

close_pasv(pasv_socket)

expect_recv(socket, 226, "Transfer Complete.")
:timer.sleep(400)
:timer.sleep(100)

send_and_expect(socket, "SIZE", [file], 213)
Expand All @@ -245,5 +290,11 @@ defmodule ExFTP.StorageTester do
|> send_and_expect("DELE", [file], 250)
|> send_and_expect("SIZE", [file], 550)
end)

# test delete a file that doesn't exist
file = "/does-not-exist"

socket
|> send_and_expect("DELE", [file], 550, "Failed to remove file.")
end
end
Loading