ftpd 2.0.1 → 2.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (141) hide show
  1. checksums.yaml +4 -4
  2. data/Changelog.md +6 -0
  3. data/Gemfile +2 -15
  4. data/Gemfile.lock +12 -51
  5. data/README.md +12 -0
  6. data/VERSION +1 -1
  7. data/bin/ftpdrb +17 -12
  8. data/examples/write_only.rb +61 -0
  9. data/ftpd.gemspec +59 -261
  10. data/lib/ftpd/session.rb +51 -51
  11. metadata +11 -153
  12. data/.travis.yml +0 -6
  13. data/features/example/eplf.feature +0 -14
  14. data/features/example/example.feature +0 -18
  15. data/features/example/read_only.feature +0 -63
  16. data/features/example/step_definitions/example_server.rb +0 -13
  17. data/features/ftp_server/abort.feature +0 -13
  18. data/features/ftp_server/allo.feature +0 -33
  19. data/features/ftp_server/append.feature +0 -94
  20. data/features/ftp_server/cdup.feature +0 -36
  21. data/features/ftp_server/command_errors.feature +0 -13
  22. data/features/ftp_server/concurrent_sessions.feature +0 -14
  23. data/features/ftp_server/delay_after_failed_login.feature +0 -23
  24. data/features/ftp_server/delete.feature +0 -60
  25. data/features/ftp_server/directory_navigation.feature +0 -59
  26. data/features/ftp_server/disconnect_after_failed_logins.feature +0 -25
  27. data/features/ftp_server/eprt.feature +0 -56
  28. data/features/ftp_server/epsv.feature +0 -37
  29. data/features/ftp_server/features.feature +0 -38
  30. data/features/ftp_server/file_structure.feature +0 -43
  31. data/features/ftp_server/get.feature +0 -80
  32. data/features/ftp_server/get_ipv6.feature +0 -46
  33. data/features/ftp_server/get_tls.feature +0 -23
  34. data/features/ftp_server/help.feature +0 -21
  35. data/features/ftp_server/implicit_tls.feature +0 -23
  36. data/features/ftp_server/invertability.feature +0 -15
  37. data/features/ftp_server/list.feature +0 -94
  38. data/features/ftp_server/list_tls.feature +0 -29
  39. data/features/ftp_server/logging.feature +0 -11
  40. data/features/ftp_server/login_auth_level_account.feature +0 -51
  41. data/features/ftp_server/login_auth_level_password.feature +0 -59
  42. data/features/ftp_server/login_auth_level_user.feature +0 -31
  43. data/features/ftp_server/max_connections.feature +0 -39
  44. data/features/ftp_server/mdtm.feature +0 -53
  45. data/features/ftp_server/mkdir.feature +0 -70
  46. data/features/ftp_server/mode.feature +0 -43
  47. data/features/ftp_server/name_list.feature +0 -77
  48. data/features/ftp_server/name_list_tls.feature +0 -30
  49. data/features/ftp_server/noop.feature +0 -17
  50. data/features/ftp_server/options.feature +0 -17
  51. data/features/ftp_server/pasv.feature +0 -30
  52. data/features/ftp_server/port.feature +0 -49
  53. data/features/ftp_server/put.feature +0 -79
  54. data/features/ftp_server/put_tls.feature +0 -23
  55. data/features/ftp_server/put_unique.feature +0 -56
  56. data/features/ftp_server/quit.feature +0 -23
  57. data/features/ftp_server/reinitialize.feature +0 -13
  58. data/features/ftp_server/rename.feature +0 -97
  59. data/features/ftp_server/rmdir.feature +0 -71
  60. data/features/ftp_server/site.feature +0 -13
  61. data/features/ftp_server/size.feature +0 -69
  62. data/features/ftp_server/status.feature +0 -18
  63. data/features/ftp_server/step_definitions/logging.rb +0 -10
  64. data/features/ftp_server/step_definitions/test_server.rb +0 -71
  65. data/features/ftp_server/structure_mount.feature +0 -13
  66. data/features/ftp_server/syntax_errors.feature +0 -18
  67. data/features/ftp_server/syst.feature +0 -18
  68. data/features/ftp_server/timeout.feature +0 -26
  69. data/features/ftp_server/type.feature +0 -59
  70. data/features/step_definitions/append.rb +0 -17
  71. data/features/step_definitions/client.rb +0 -27
  72. data/features/step_definitions/client_and_server_files.rb +0 -26
  73. data/features/step_definitions/client_files.rb +0 -16
  74. data/features/step_definitions/command.rb +0 -7
  75. data/features/step_definitions/connect.rb +0 -39
  76. data/features/step_definitions/delete.rb +0 -17
  77. data/features/step_definitions/directory_navigation.rb +0 -28
  78. data/features/step_definitions/error_replies.rb +0 -117
  79. data/features/step_definitions/features.rb +0 -23
  80. data/features/step_definitions/file_structure.rb +0 -18
  81. data/features/step_definitions/generic_send.rb +0 -11
  82. data/features/step_definitions/get.rb +0 -18
  83. data/features/step_definitions/help.rb +0 -20
  84. data/features/step_definitions/invalid_commands.rb +0 -13
  85. data/features/step_definitions/ipv6.rb +0 -11
  86. data/features/step_definitions/line_endings.rb +0 -9
  87. data/features/step_definitions/list.rb +0 -75
  88. data/features/step_definitions/login.rb +0 -84
  89. data/features/step_definitions/mkdir.rb +0 -11
  90. data/features/step_definitions/mode.rb +0 -17
  91. data/features/step_definitions/mtime.rb +0 -25
  92. data/features/step_definitions/noop.rb +0 -17
  93. data/features/step_definitions/options.rb +0 -11
  94. data/features/step_definitions/passive.rb +0 -10
  95. data/features/step_definitions/pending.rb +0 -5
  96. data/features/step_definitions/port.rb +0 -7
  97. data/features/step_definitions/put.rb +0 -31
  98. data/features/step_definitions/quit.rb +0 -17
  99. data/features/step_definitions/rename.rb +0 -13
  100. data/features/step_definitions/rmdir.rb +0 -11
  101. data/features/step_definitions/server_files.rb +0 -63
  102. data/features/step_definitions/server_title.rb +0 -14
  103. data/features/step_definitions/size.rb +0 -22
  104. data/features/step_definitions/status.rb +0 -11
  105. data/features/step_definitions/success_replies.rb +0 -9
  106. data/features/step_definitions/system.rb +0 -12
  107. data/features/step_definitions/timing.rb +0 -21
  108. data/features/step_definitions/type.rb +0 -17
  109. data/features/support/env.rb +0 -6
  110. data/features/support/example_server.rb +0 -69
  111. data/features/support/file_templates/ascii_unix +0 -4
  112. data/features/support/file_templates/ascii_windows +0 -4
  113. data/features/support/file_templates/binary +0 -0
  114. data/features/support/test_client.rb +0 -258
  115. data/features/support/test_file_templates.rb +0 -35
  116. data/features/support/test_server.rb +0 -304
  117. data/features/support/test_server_files.rb +0 -59
  118. data/rake_tasks/cucumber.rake +0 -9
  119. data/rake_tasks/default.rake +0 -1
  120. data/rake_tasks/jeweler.rake +0 -52
  121. data/rake_tasks/spec.rake +0 -3
  122. data/rake_tasks/test.rake +0 -2
  123. data/rake_tasks/yard.rake +0 -3
  124. data/spec/command_sequence_checker_spec.rb +0 -85
  125. data/spec/connection_throttle_spec.rb +0 -101
  126. data/spec/connection_tracker_spec.rb +0 -99
  127. data/spec/data_server_factory_spec.rb +0 -104
  128. data/spec/disk_file_system_spec.rb +0 -322
  129. data/spec/exception_translator_spec.rb +0 -38
  130. data/spec/file_info_spec.rb +0 -61
  131. data/spec/ftp_server_error_spec.rb +0 -15
  132. data/spec/list_format/eplf_spec.rb +0 -63
  133. data/spec/list_format/ls_spec.rb +0 -272
  134. data/spec/list_path_spec.rb +0 -23
  135. data/spec/null_logger_spec.rb +0 -26
  136. data/spec/protocols_spec.rb +0 -159
  137. data/spec/server_spec.rb +0 -83
  138. data/spec/spec_helper.rb +0 -17
  139. data/spec/telnet_spec.rb +0 -77
  140. data/spec/translate_exceptions_spec.rb +0 -42
  141. data/testlib/network.rb +0 -17
@@ -1,59 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module TestServerFiles
4
-
5
- def add_file(path)
6
- full_path = temp_path(path)
7
- mkdir_p File.dirname(full_path)
8
- File.open(full_path, 'wb') do |file|
9
- file.write @templates[File.basename(full_path)]
10
- end
11
- end
12
-
13
- def set_mtime(path, mtime)
14
- full_path = temp_path(path)
15
- File.utime(File.atime(full_path), mtime, full_path)
16
- end
17
-
18
- def add_directory(path)
19
- full_path = temp_path(path)
20
- mkdir_p full_path
21
- end
22
-
23
- def has_file?(path)
24
- full_path = temp_path(path)
25
- File.exists?(full_path)
26
- end
27
-
28
- def has_file_with_contents_of?(path)
29
- expected_contents = @templates[File.basename(path)]
30
- all_paths.any? do |path|
31
- File.open(path, 'rb', &:read) == expected_contents
32
- end
33
- end
34
-
35
- def files_named_like(name)
36
- all_paths.select do |path|
37
- path.include?(name)
38
- end
39
- end
40
-
41
- def has_directory?(path)
42
- full_path = temp_path(path)
43
- File.directory?(full_path)
44
- end
45
-
46
- def file_contents(path)
47
- full_path = temp_path(path)
48
- File.open(full_path, 'rb', &:read)
49
- end
50
-
51
- def temp_path(path)
52
- File.expand_path(path, temp_dir)
53
- end
54
-
55
- def all_paths
56
- Dir[temp_path('**/*')]
57
- end
58
-
59
- end
@@ -1,9 +0,0 @@
1
- require 'cucumber/rake/task'
2
-
3
- Cucumber::Rake::Task.new 'test:features' do |t|
4
- t.fork = true
5
- t.cucumber_opts = '--format progress'
6
- end
7
-
8
- task 'test:cucumber' => ['test:features']
9
- task 'cucumber' => ['test:features']
@@ -1 +0,0 @@
1
- task :default => [:test]
@@ -1,52 +0,0 @@
1
- # encoding: utf-8
2
-
3
- require 'jeweler'
4
-
5
- README_PATH = File.expand_path('../README.md', File.dirname(__FILE__))
6
-
7
- def remove_markdown_link(description)
8
- regex = %r{
9
- \[
10
- ([^\]]+)
11
- \]
12
- (
13
- \[\d+\] |
14
- \([^)]+\)
15
- )
16
- }x
17
- description = description.gsub(regex, '\1')
18
- end
19
-
20
- def remove_badges(description)
21
- description.gsub(/^\[!.*\n/, '')
22
- end
23
-
24
- def join_lines(description)
25
- description.gsub(/\n/, ' ').strip
26
- end
27
-
28
- def extract_description_from_readme
29
- readme = File.open(README_PATH, 'r', &:read)
30
- description = readme[/^# FTPD.*\n+((?:.*\n)+?)\n*##/i, 1]
31
- unless description
32
- raise 'Unable to extract description from readme'
33
- end
34
- description = remove_badges(description)
35
- description = remove_markdown_link(description)
36
- description = join_lines(description)
37
- description
38
- end
39
-
40
- Jeweler::Tasks.new do |gem|
41
- # gem is a Gem::Specification... see
42
- # http://docs.rubygems.org/read/chapter/20 for more options
43
- gem.name = 'ftpd'
44
- gem.homepage = 'http://github.com/wconrad/ftpd'
45
- gem.license = 'MIT'
46
- gem.summary = %Q{Pure Ruby FTP server library}
47
- gem.description = extract_description_from_readme
48
- gem.email = 'wconrad@yagni.com'
49
- gem.authors = ['Wayne Conrad']
50
- # dependencies defined in Gemfile
51
- end
52
- Jeweler::RubygemsDotOrgTasks.new
@@ -1,3 +0,0 @@
1
- require 'rspec/core/rake_task'
2
- RSpec::Core::RakeTask.new 'test:spec'
3
- task :spec => ['test:spec']
@@ -1,2 +0,0 @@
1
- desc 'Run all tests'
2
- task :test => ['test:spec', 'test:cucumber']
@@ -1,3 +0,0 @@
1
- require 'yard'
2
- YARD::Rake::YardocTask.new do |t|
3
- end
@@ -1,85 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require File.expand_path('spec_helper', File.dirname(__FILE__))
4
-
5
- module Ftpd
6
- describe CommandSequenceChecker do
7
-
8
- let(:sequence_error_verification) do
9
- lambda {|e| e.code == 503 && e.message == "Bad sequence of commands"}
10
- end
11
- subject(:checker) {CommandSequenceChecker.new}
12
-
13
- context 'initial' do
14
-
15
- it 'accepts any command' do
16
- checker.check 'NOOP'
17
- end
18
-
19
- end
20
-
21
- context 'when a specific command is expected' do
22
-
23
- before(:each) {checker.expect 'PASS'}
24
-
25
- it 'accepts that command' do
26
- checker.check 'PASS'
27
- end
28
-
29
- it 'rejects any other command' do
30
- expect {
31
- checker.check 'NOOP'
32
- }.to raise_error(FtpServerError, &sequence_error_verification)
33
- end
34
-
35
- end
36
-
37
- context 'after the expected command has arrived' do
38
-
39
- before(:each) do
40
- checker.expect 'PASS'
41
- checker.check 'PASS'
42
- end
43
-
44
- it 'accepts any other command' do
45
- checker.check 'NOOP'
46
- end
47
-
48
- end
49
-
50
- context 'after a command is rejected' do
51
-
52
- before(:each) do
53
- checker.expect 'PASS'
54
- expect {
55
- checker.check 'NOOP'
56
- }.to raise_error(FtpServerError, &sequence_error_verification)
57
- end
58
-
59
- it 'accepts any other command' do
60
- checker.check 'NOOP'
61
- end
62
-
63
- end
64
-
65
- context 'when a command must be expected' do
66
-
67
- before(:each) do
68
- checker.must_expect 'PASS'
69
- end
70
-
71
- it 'rejects that command if not expected' do
72
- expect {
73
- checker.check 'PASS'
74
- }.to raise_error(FtpServerError, &sequence_error_verification)
75
- end
76
-
77
- it 'accepts that command when it is accepted' do
78
- checker.expect 'PASS'
79
- checker.check 'PASS'
80
- end
81
-
82
- end
83
-
84
- end
85
- end
@@ -1,101 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require File.expand_path('spec_helper', File.dirname(__FILE__))
4
-
5
- module Ftpd
6
-
7
- describe ConnectionThrottle do
8
-
9
- let(:socket) {double TCPSocket}
10
- let(:connections) {0}
11
- let(:connections_for_socket) {0}
12
- let(:connection_tracker) do
13
- double ConnectionTracker, :connections => connections
14
- end
15
- subject(:connection_throttle) do
16
- ConnectionThrottle.new(connection_tracker)
17
- end
18
-
19
- before(:each) do
20
- allow(connection_tracker).to receive(:connections)
21
- .and_return(connections)
22
- allow(connection_tracker).to receive(:connections_for)
23
- .with(socket)
24
- .and_return(connections_for_socket)
25
- end
26
-
27
- it 'should have defaults' do
28
- expect(connection_throttle.max_connections).to be_nil
29
- expect(connection_throttle.max_connections_per_ip).to be_nil
30
- end
31
-
32
- describe '#allow?' do
33
-
34
- context '(total connections)' do
35
-
36
- let(:max_connections) {50}
37
-
38
- before(:each) do
39
- connection_throttle.max_connections = max_connections
40
- connection_throttle.max_connections_per_ip = 2 * max_connections
41
- end
42
-
43
- context 'almost at maximum connections' do
44
- let(:connections) {max_connections - 1}
45
- specify {expect(connection_throttle.allow?(socket)).to be_truthy}
46
- end
47
-
48
- context 'at maximum connections' do
49
- let(:connections) {max_connections}
50
- specify {expect(connection_throttle.allow?(socket)).to be_falsey}
51
- end
52
-
53
- context 'above maximum connections' do
54
- let(:connections) {max_connections + 1}
55
- specify {expect(connection_throttle.allow?(socket)).to be_falsey}
56
- end
57
-
58
- end
59
-
60
- context '(per ip)' do
61
-
62
- let(:max_connections_per_ip) {5}
63
-
64
- before(:each) do
65
- connection_throttle.max_connections = 2 * max_connections_per_ip
66
- connection_throttle.max_connections_per_ip = max_connections_per_ip
67
- end
68
-
69
- context 'almost at maximum connections for ip' do
70
- let(:connections_for_socket) {max_connections_per_ip - 1}
71
- specify {expect(connection_throttle.allow?(socket)).to be_truthy}
72
- end
73
-
74
- context 'at maximum connections for ip' do
75
- let(:connections_for_socket) {max_connections_per_ip}
76
- specify {expect(connection_throttle.allow?(socket)).to be_falsey}
77
- end
78
-
79
- context 'above maximum connections for ip' do
80
- let(:connections_for_socket) {max_connections_per_ip + 1}
81
- specify {expect(connection_throttle.allow?(socket)).to be_falsey}
82
- end
83
-
84
- end
85
-
86
- end
87
-
88
- describe '#deny' do
89
-
90
- let(:socket) {StringIO.new}
91
-
92
- it 'should send a "too many connections" message' do
93
- connection_throttle.deny socket
94
- expect(socket.string).to eq "421 Too many connections\r\n"
95
- end
96
-
97
- end
98
-
99
- end
100
-
101
- end
@@ -1,99 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require File.expand_path('spec_helper', File.dirname(__FILE__))
4
-
5
- module Ftpd
6
-
7
- describe ConnectionTracker do
8
-
9
- before(:all) do
10
- Thread.abort_on_exception = true
11
- end
12
-
13
- # Create a mock socket with the given peer address
14
-
15
- def socket_bound_to(source_ip)
16
- socket = double TCPSocket
17
- peeraddr = Socket.pack_sockaddr_in(0, source_ip)
18
- allow(socket).to receive(:getpeername) {peeraddr}
19
- socket
20
- end
21
-
22
- subject(:connection_tracker) {ConnectionTracker.new}
23
-
24
- describe '#connections' do
25
-
26
- let(:socket) {socket_bound_to('127.0.0.1')}
27
-
28
- context '(session ends normally)' do
29
-
30
- it 'should track the total number of connection' do
31
- expect(connection_tracker.connections).to eq 0
32
- connection_tracker.start_track socket
33
- expect(connection_tracker.connections).to eq 1
34
- connection_tracker.stop_track socket
35
- expect(connection_tracker.connections).to eq 0
36
- end
37
-
38
- end
39
-
40
- end
41
-
42
- describe '#connections_for' do
43
-
44
- it 'should track the number of connections for an ip' do
45
- socket1 = socket_bound_to('127.0.0.1')
46
- socket2 = socket_bound_to('127.0.0.2')
47
- expect(connection_tracker.connections_for(socket1)).to eq 0
48
- expect(connection_tracker.connections_for(socket2)).to eq 0
49
- connection_tracker.start_track socket1
50
- expect(connection_tracker.connections_for(socket1)).to eq 1
51
- expect(connection_tracker.connections_for(socket2)).to eq 0
52
- connection_tracker.stop_track socket1
53
- expect(connection_tracker.connections_for(socket1)).to eq 0
54
- expect(connection_tracker.connections_for(socket2)).to eq 0
55
- end
56
-
57
- end
58
-
59
- describe '#known_ip_count' do
60
-
61
- let(:socket) {socket_bound_to('127.0.0.1')}
62
-
63
- it 'should forget about an IP that has no connection' do
64
- expect(connection_tracker.known_ip_count).to eq 0
65
- connection_tracker.start_track socket
66
- expect(connection_tracker.known_ip_count).to eq 1
67
- connection_tracker.stop_track socket
68
- expect(connection_tracker.known_ip_count).to eq 0
69
- end
70
-
71
- end
72
-
73
- describe '#track' do
74
-
75
- let(:socket) {socket_bound_to('127.0.0.1')}
76
-
77
- context '(session ends normally)' do
78
- specify do
79
- expect(connection_tracker.connections_for(socket)).to eq 0
80
- connection_tracker.track(socket) do
81
- expect(connection_tracker.connections_for(socket)).to eq 1
82
- end
83
- expect(connection_tracker.connections_for(socket)).to eq 0
84
- end
85
- end
86
-
87
- context '(session ends with exception)' do
88
- specify do
89
- expect(connection_tracker.connections_for(socket)).to eq 0
90
- connection_tracker.track(socket) { raise } rescue
91
- expect(connection_tracker.connections_for(socket)).to eq 0
92
- end
93
- end
94
-
95
- end
96
-
97
- end
98
-
99
- end
@@ -1,104 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require File.expand_path('spec_helper', File.dirname(__FILE__))
4
-
5
- module Ftpd
6
- describe DataServerFactory do
7
-
8
- it "creates a socket bound to 127.0.0.1" do
9
- factory = DataServerFactory.make("127.0.0.1", nil)
10
- tcp_server = factory.make_tcp_server
11
- expect(tcp_server.addr[3]).to eq "127.0.0.1"
12
- end
13
-
14
- it "creates a socket bound to 127.0.0.2" do
15
- factory = DataServerFactory.make("127.0.0.2", nil)
16
- tcp_server = factory.make_tcp_server
17
- expect(tcp_server.addr[3]).to eq "127.0.0.2"
18
- end
19
-
20
- context "with no port range" do
21
-
22
- it "creates a socket bound to an ephemeral port" do
23
- interface = "0.0.0.0"
24
- factory = DataServerFactory.make(interface, nil)
25
- ports = (1..10).map do
26
- tcp_server = factory.make_tcp_server
27
- begin
28
- tcp_server.addr[1]
29
- ensure
30
- tcp_server.close
31
- end
32
- end
33
- expect(ports.uniq.size).to be > 1
34
- ports.each do |port|
35
- expect(port).to be_between(1024, 65535)
36
- end
37
- end
38
-
39
- end
40
-
41
- context "with a port range" do
42
-
43
- let(:interface) { "127.0.0.1" }
44
-
45
- def get_unused_port
46
- server = TCPServer.new(interface, 0)
47
- port = server.addr[1]
48
- server.close
49
- port
50
- end
51
-
52
- def use_port(port)
53
- server = TCPServer.new(interface, port)
54
- begin
55
- yield
56
- ensure
57
- server.close
58
- end
59
- end
60
-
61
- it "creates a socket bound to an ephemeral port" do
62
- ports = (1..10).map { get_unused_port }
63
- factory = DataServerFactory.make(interface, ports)
64
- 10.times do
65
- tcp_server = factory.make_tcp_server
66
- begin
67
- port = tcp_server.addr[1]
68
- expect(ports).to include(port)
69
- ensure
70
- tcp_server.close
71
- end
72
- end
73
- end
74
-
75
- it "skips a port that is already in use" do
76
- ports = (1..2).map { get_unused_port }
77
- use_port(ports[0]) do
78
- factory = DataServerFactory.make(interface, ports)
79
- 10.times do
80
- tcp_server = factory.make_tcp_server
81
- begin
82
- port = tcp_server.addr[1]
83
- expect(port).to eq ports[1]
84
- ensure
85
- tcp_server.close
86
- end
87
- end
88
- end
89
- end
90
-
91
- it "uses a random ephemeral port when all configured ports are in use" do
92
- ports = [ get_unused_port ]
93
- use_port(ports[0]) do
94
- factory = DataServerFactory.make(interface, ports)
95
- tcp_server = factory.make_tcp_server
96
- port = tcp_server.addr[1]
97
- expect(port).to_not eq ports[0]
98
- end
99
- end
100
-
101
- end
102
-
103
- end
104
- end