fake_ftp 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.codeclimate.yml +16 -0
- data/.gitignore +1 -0
- data/.rspec +2 -0
- data/.rubocop.yml +27 -0
- data/.rubocop_todo.yml +7 -0
- data/.simplecov +6 -0
- data/.travis.yml +27 -2
- data/CHANGELOG.md +136 -0
- data/Gemfile +9 -7
- data/Guardfile +5 -4
- data/LICENSE.md +20 -0
- data/README.md +30 -63
- data/Rakefile +12 -7
- data/fake_ftp.gemspec +16 -17
- data/lib/fake_ftp.rb +3 -2
- data/lib/fake_ftp/file.rb +11 -5
- data/lib/fake_ftp/server.rb +138 -261
- data/lib/fake_ftp/server_commands.rb +6 -0
- data/lib/fake_ftp/server_commands/acct.rb +11 -0
- data/lib/fake_ftp/server_commands/cdup.rb +11 -0
- data/lib/fake_ftp/server_commands/cwd.rb +13 -0
- data/lib/fake_ftp/server_commands/dele.rb +25 -0
- data/lib/fake_ftp/server_commands/list.rb +39 -0
- data/lib/fake_ftp/server_commands/mdtm.rb +17 -0
- data/lib/fake_ftp/server_commands/mkd.rb +11 -0
- data/lib/fake_ftp/server_commands/nlst.rb +32 -0
- data/lib/fake_ftp/server_commands/pass.rb +11 -0
- data/lib/fake_ftp/server_commands/pasv.rb +15 -0
- data/lib/fake_ftp/server_commands/port.rb +23 -0
- data/lib/fake_ftp/server_commands/pwd.rb +11 -0
- data/lib/fake_ftp/server_commands/quit.rb +13 -0
- data/lib/fake_ftp/server_commands/retr.rb +32 -0
- data/lib/fake_ftp/server_commands/rnfr.rb +18 -0
- data/lib/fake_ftp/server_commands/rnto.rb +22 -0
- data/lib/fake_ftp/server_commands/site.rb +11 -0
- data/lib/fake_ftp/server_commands/size.rb +11 -0
- data/lib/fake_ftp/server_commands/stor.rb +30 -0
- data/lib/fake_ftp/server_commands/type.rb +18 -0
- data/lib/fake_ftp/server_commands/user.rb +12 -0
- data/lib/fake_ftp/server_commands/wat.rb +33 -0
- data/lib/fake_ftp/version.rb +3 -1
- data/spec/functional/server_spec.rb +459 -358
- data/spec/integration/server_spec.rb +39 -29
- data/spec/models/fake_ftp/file_spec.rb +22 -21
- data/spec/models/fake_ftp/server_spec.rb +33 -32
- data/spec/spec_helper.rb +98 -14
- metadata +32 -3
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FakeFtp
|
4
|
+
module ServerCommands
|
5
|
+
class Dele
|
6
|
+
def run(ctx, filename = '', *)
|
7
|
+
files_to_delete = ctx.store.values.select do |f|
|
8
|
+
if ctx.absolute?
|
9
|
+
ctx.abspath(::File.basename(filename)) == ctx.abspath(f.name)
|
10
|
+
else
|
11
|
+
::File.basename(filename) == f.name
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
return '550 Delete operation failed.' if files_to_delete.empty?
|
16
|
+
|
17
|
+
ctx.store.reject! do |_, f|
|
18
|
+
files_to_delete.include?(f)
|
19
|
+
end
|
20
|
+
|
21
|
+
'250 Delete operation successful.'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FakeFtp
|
4
|
+
module ServerCommands
|
5
|
+
class List
|
6
|
+
def run(ctx, *args)
|
7
|
+
if ctx.active? && ctx.command_state[:active_connection].nil?
|
8
|
+
ctx.respond_with('425 Ain\'t no data port!')
|
9
|
+
return
|
10
|
+
end
|
11
|
+
|
12
|
+
ctx.respond_with('150 Listing status ok, about to open data connection')
|
13
|
+
data_client = if ctx.active?
|
14
|
+
ctx.command_state[:active_connection]
|
15
|
+
else
|
16
|
+
ctx.data_server.accept
|
17
|
+
end
|
18
|
+
|
19
|
+
wildcards = ctx.build_wildcards(args)
|
20
|
+
statlines = ctx.matching_files(wildcards).map do |f|
|
21
|
+
%W[
|
22
|
+
-rw-r--r--
|
23
|
+
1
|
24
|
+
owner
|
25
|
+
group
|
26
|
+
#{f.bytes}
|
27
|
+
#{f.created.strftime('%b %d %H:%M')}
|
28
|
+
#{f.name}
|
29
|
+
].join("\t")
|
30
|
+
end
|
31
|
+
data_client.write(statlines.join("\n"))
|
32
|
+
data_client.close
|
33
|
+
ctx.command_state[:active_connection] = nil
|
34
|
+
|
35
|
+
'226 List information transferred'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FakeFtp
|
4
|
+
module ServerCommands
|
5
|
+
class Mdtm
|
6
|
+
def run(ctx, filename = '', *)
|
7
|
+
ctx.respond_with('501 No filename given') && return if filename.empty?
|
8
|
+
server_file = ctx.file(filename)
|
9
|
+
ctx.respond_with('550 File not found') && return if server_file.nil?
|
10
|
+
|
11
|
+
ctx.respond_with(
|
12
|
+
"213 #{server_file.last_modified_time.strftime('%Y%m%d%H%M%S')}"
|
13
|
+
)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FakeFtp
|
4
|
+
module ServerCommands
|
5
|
+
class Nlst
|
6
|
+
def run(ctx, *args)
|
7
|
+
if ctx.active? && ctx.command_state[:active_connection].nil?
|
8
|
+
ctx.respond_with('425 Ain\'t no data port!')
|
9
|
+
return
|
10
|
+
end
|
11
|
+
|
12
|
+
ctx.respond_with('150 Listing status ok, about to open data connection')
|
13
|
+
data_client = if ctx.active?
|
14
|
+
ctx.command_state[:active_connection]
|
15
|
+
else
|
16
|
+
ctx.data_server.accept
|
17
|
+
end
|
18
|
+
|
19
|
+
wildcards = ctx.build_wildcards(args)
|
20
|
+
matching = ctx.matching_files(wildcards).map do |f|
|
21
|
+
"#{f.name}\n"
|
22
|
+
end
|
23
|
+
|
24
|
+
data_client.write(matching.join)
|
25
|
+
data_client.close
|
26
|
+
ctx.command_state[:active_connection] = nil
|
27
|
+
|
28
|
+
'226 List information transferred'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FakeFtp
|
4
|
+
module ServerCommands
|
5
|
+
class Pasv
|
6
|
+
def run(ctx, *)
|
7
|
+
return '502 Aww hell no, use Active' if ctx.passive_port.nil?
|
8
|
+
ctx.mode = :passive
|
9
|
+
p1 = (ctx.passive_port / 256).to_i
|
10
|
+
p2 = ctx.passive_port % 256
|
11
|
+
"227 Entering Passive Mode (127,0,0,1,#{p1},#{p2})"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FakeFtp
|
4
|
+
module ServerCommands
|
5
|
+
class Port
|
6
|
+
def run(ctx, remote = '', *)
|
7
|
+
remote = remote.split(',')
|
8
|
+
remote_port = remote[4].to_i * 256 + remote[5].to_i
|
9
|
+
unless ctx.command_state[:active_connection].nil?
|
10
|
+
ctx.command_state[:active_connection].close
|
11
|
+
ctx.command_state[:active_connection] = nil
|
12
|
+
end
|
13
|
+
ctx.mode = :active
|
14
|
+
ctx.debug('_port active connection ->')
|
15
|
+
ctx.command_state[:active_connection] = ::TCPSocket.new(
|
16
|
+
'127.0.0.1', remote_port
|
17
|
+
)
|
18
|
+
ctx.debug('_port active connection <-')
|
19
|
+
'200 Okay'
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FakeFtp
|
4
|
+
module ServerCommands
|
5
|
+
class Retr
|
6
|
+
def run(ctx, filename = '', *)
|
7
|
+
ctx.respond_with('501 No filename given') if filename.empty?
|
8
|
+
|
9
|
+
f = ctx.file(filename.to_s)
|
10
|
+
return ctx.respond_with('550 File not found') if f.nil?
|
11
|
+
|
12
|
+
if ctx.active? && ctx.command_state[:active_connection].nil?
|
13
|
+
ctx.respond_with('425 Ain\'t no data port!')
|
14
|
+
return
|
15
|
+
end
|
16
|
+
|
17
|
+
ctx.respond_with('150 File status ok, about to open data connection')
|
18
|
+
data_client = if ctx.active?
|
19
|
+
ctx.command_state[:active_connection]
|
20
|
+
else
|
21
|
+
ctx.data_server.accept
|
22
|
+
end
|
23
|
+
|
24
|
+
data_client.write(f.data)
|
25
|
+
|
26
|
+
data_client.close
|
27
|
+
ctx.command_state[:active_connection] = nil
|
28
|
+
'226 File transferred'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FakeFtp
|
4
|
+
module ServerCommands
|
5
|
+
class Rnfr
|
6
|
+
def run(ctx, rename_from = '', *)
|
7
|
+
return '501 Send path name.' if rename_from.nil? || rename_from.empty?
|
8
|
+
|
9
|
+
ctx.command_state[:rename_from] = if ctx.absolute?
|
10
|
+
ctx.abspath(rename_from)
|
11
|
+
else
|
12
|
+
rename_from
|
13
|
+
end
|
14
|
+
'350 Send RNTO to complete rename.'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FakeFtp
|
4
|
+
module ServerCommands
|
5
|
+
class Rnto
|
6
|
+
def run(ctx, rename_to = '', *)
|
7
|
+
return '501 Send path name.' if rename_to.nil? || rename_to.empty?
|
8
|
+
return '503 Send RNFR first.' if ctx.command_state[:rename_from].nil?
|
9
|
+
|
10
|
+
f = ctx.file(ctx.command_state[:rename_from])
|
11
|
+
if f.nil?
|
12
|
+
ctx.command_state[:rename_from] = nil
|
13
|
+
return '550 File not found.'
|
14
|
+
end
|
15
|
+
|
16
|
+
f.name = rename_to
|
17
|
+
ctx.command_state[:rename_from] = nil
|
18
|
+
'250 Path renamed.'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FakeFtp
|
4
|
+
module ServerCommands
|
5
|
+
class Stor
|
6
|
+
def run(ctx, filename = '', *)
|
7
|
+
if ctx.active? && ctx.command_state[:active_connection].nil?
|
8
|
+
ctx.respond_with('425 Ain\'t no data port!')
|
9
|
+
return
|
10
|
+
end
|
11
|
+
|
12
|
+
ctx.respond_with('125 Do it!')
|
13
|
+
data_client = if ctx.active?
|
14
|
+
ctx.command_state[:active_connection]
|
15
|
+
else
|
16
|
+
ctx.data_server.accept
|
17
|
+
end
|
18
|
+
|
19
|
+
data = data_client.read(nil)
|
20
|
+
ctx.store[ctx.abspath(filename)] = FakeFtp::File.new(
|
21
|
+
filename.to_s, data, ctx.mode
|
22
|
+
)
|
23
|
+
|
24
|
+
data_client.close
|
25
|
+
ctx.command_state[:active_connection] = nil
|
26
|
+
'226 Did it!'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FakeFtp
|
4
|
+
module ServerCommands
|
5
|
+
class Type
|
6
|
+
def run(_, type = 'A', *)
|
7
|
+
case type.to_s
|
8
|
+
when 'A'
|
9
|
+
'200 Type set to A.'
|
10
|
+
when 'I'
|
11
|
+
'200 Type set to I.'
|
12
|
+
else
|
13
|
+
'504 We don\'t allow those'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module FakeFtp
|
4
|
+
module ServerCommands
|
5
|
+
class Wat
|
6
|
+
def run(ctx, *)
|
7
|
+
if ctx.active? && ctx.command_state[:active_connection].nil?
|
8
|
+
ctx.respond_with('425 Ain\'t no data port!')
|
9
|
+
return
|
10
|
+
end
|
11
|
+
|
12
|
+
data_client = if ctx.active?
|
13
|
+
ctx.command_state[:active_connection]
|
14
|
+
else
|
15
|
+
ctx.data_server.accept
|
16
|
+
end
|
17
|
+
data_client.write(invisible_bike)
|
18
|
+
data_client.close
|
19
|
+
ctx.command_state[:active_connection] = nil
|
20
|
+
'418 Pizza Party'
|
21
|
+
end
|
22
|
+
|
23
|
+
private def invisible_bike
|
24
|
+
::File.read(
|
25
|
+
::File.expand_path(
|
26
|
+
'../../../../spec/fixtures/invisible_bike.jpg',
|
27
|
+
__FILE__
|
28
|
+
)
|
29
|
+
)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|