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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +16 -0
  3. data/.gitignore +1 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +27 -0
  6. data/.rubocop_todo.yml +7 -0
  7. data/.simplecov +6 -0
  8. data/.travis.yml +27 -2
  9. data/CHANGELOG.md +136 -0
  10. data/Gemfile +9 -7
  11. data/Guardfile +5 -4
  12. data/LICENSE.md +20 -0
  13. data/README.md +30 -63
  14. data/Rakefile +12 -7
  15. data/fake_ftp.gemspec +16 -17
  16. data/lib/fake_ftp.rb +3 -2
  17. data/lib/fake_ftp/file.rb +11 -5
  18. data/lib/fake_ftp/server.rb +138 -261
  19. data/lib/fake_ftp/server_commands.rb +6 -0
  20. data/lib/fake_ftp/server_commands/acct.rb +11 -0
  21. data/lib/fake_ftp/server_commands/cdup.rb +11 -0
  22. data/lib/fake_ftp/server_commands/cwd.rb +13 -0
  23. data/lib/fake_ftp/server_commands/dele.rb +25 -0
  24. data/lib/fake_ftp/server_commands/list.rb +39 -0
  25. data/lib/fake_ftp/server_commands/mdtm.rb +17 -0
  26. data/lib/fake_ftp/server_commands/mkd.rb +11 -0
  27. data/lib/fake_ftp/server_commands/nlst.rb +32 -0
  28. data/lib/fake_ftp/server_commands/pass.rb +11 -0
  29. data/lib/fake_ftp/server_commands/pasv.rb +15 -0
  30. data/lib/fake_ftp/server_commands/port.rb +23 -0
  31. data/lib/fake_ftp/server_commands/pwd.rb +11 -0
  32. data/lib/fake_ftp/server_commands/quit.rb +13 -0
  33. data/lib/fake_ftp/server_commands/retr.rb +32 -0
  34. data/lib/fake_ftp/server_commands/rnfr.rb +18 -0
  35. data/lib/fake_ftp/server_commands/rnto.rb +22 -0
  36. data/lib/fake_ftp/server_commands/site.rb +11 -0
  37. data/lib/fake_ftp/server_commands/size.rb +11 -0
  38. data/lib/fake_ftp/server_commands/stor.rb +30 -0
  39. data/lib/fake_ftp/server_commands/type.rb +18 -0
  40. data/lib/fake_ftp/server_commands/user.rb +12 -0
  41. data/lib/fake_ftp/server_commands/wat.rb +33 -0
  42. data/lib/fake_ftp/version.rb +3 -1
  43. data/spec/functional/server_spec.rb +459 -358
  44. data/spec/integration/server_spec.rb +39 -29
  45. data/spec/models/fake_ftp/file_spec.rb +22 -21
  46. data/spec/models/fake_ftp/server_spec.rb +33 -32
  47. data/spec/spec_helper.rb +98 -14
  48. metadata +32 -3
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FakeFtp
4
+ module ServerCommands
5
+ end
6
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FakeFtp
4
+ module ServerCommands
5
+ class Acct
6
+ def run(*)
7
+ '230 WHATEVER!'
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FakeFtp
4
+ module ServerCommands
5
+ class Cdup
6
+ def run(*)
7
+ '250 OK!'
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FakeFtp
4
+ module ServerCommands
5
+ class Cwd
6
+ def run(ctx, wd, *)
7
+ wd = "/#{wd}" unless wd.start_with?('/')
8
+ ctx.workdir = wd
9
+ '250 OK!'
10
+ end
11
+ end
12
+ end
13
+ end
@@ -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,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FakeFtp
4
+ module ServerCommands
5
+ class Mkd
6
+ def run(*)
7
+ '257 OK!'
8
+ end
9
+ end
10
+ end
11
+ 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,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FakeFtp
4
+ module ServerCommands
5
+ class Pass
6
+ def run(*)
7
+ '230 logged in'
8
+ end
9
+ end
10
+ end
11
+ 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,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FakeFtp
4
+ module ServerCommands
5
+ class Pwd
6
+ def run(ctx, *)
7
+ "257 \"#{ctx.workdir}\" is current directory"
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FakeFtp
4
+ module ServerCommands
5
+ class Quit
6
+ def run(ctx, *)
7
+ ctx.respond_with '221 OMG bye!'
8
+ ctx.client&.close
9
+ ctx.client = nil
10
+ end
11
+ end
12
+ end
13
+ 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,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FakeFtp
4
+ module ServerCommands
5
+ class Site
6
+ def run(_, command, *)
7
+ "200 #{command}"
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FakeFtp
4
+ module ServerCommands
5
+ class Size
6
+ def run(ctx, filename, *)
7
+ ctx.respond_with("213 #{ctx.file(filename).bytes}")
8
+ end
9
+ end
10
+ end
11
+ 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,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FakeFtp
4
+ module ServerCommands
5
+ class User
6
+ def run(_, name, *)
7
+ return '230 logged in' if name.to_s == 'anonymous'
8
+ '331 send your password'
9
+ end
10
+ end
11
+ end
12
+ 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