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.
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