fake_ftp 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|