ftpd 2.0.1 → 2.0.2

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