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.
- checksums.yaml +4 -4
- data/Changelog.md +6 -0
- data/Gemfile +2 -15
- data/Gemfile.lock +12 -51
- data/README.md +12 -0
- data/VERSION +1 -1
- data/bin/ftpdrb +17 -12
- data/examples/write_only.rb +61 -0
- data/ftpd.gemspec +59 -261
- data/lib/ftpd/session.rb +51 -51
- metadata +11 -153
- data/.travis.yml +0 -6
- data/features/example/eplf.feature +0 -14
- data/features/example/example.feature +0 -18
- data/features/example/read_only.feature +0 -63
- data/features/example/step_definitions/example_server.rb +0 -13
- data/features/ftp_server/abort.feature +0 -13
- data/features/ftp_server/allo.feature +0 -33
- data/features/ftp_server/append.feature +0 -94
- data/features/ftp_server/cdup.feature +0 -36
- data/features/ftp_server/command_errors.feature +0 -13
- data/features/ftp_server/concurrent_sessions.feature +0 -14
- data/features/ftp_server/delay_after_failed_login.feature +0 -23
- data/features/ftp_server/delete.feature +0 -60
- data/features/ftp_server/directory_navigation.feature +0 -59
- data/features/ftp_server/disconnect_after_failed_logins.feature +0 -25
- data/features/ftp_server/eprt.feature +0 -56
- data/features/ftp_server/epsv.feature +0 -37
- data/features/ftp_server/features.feature +0 -38
- data/features/ftp_server/file_structure.feature +0 -43
- data/features/ftp_server/get.feature +0 -80
- data/features/ftp_server/get_ipv6.feature +0 -46
- data/features/ftp_server/get_tls.feature +0 -23
- data/features/ftp_server/help.feature +0 -21
- data/features/ftp_server/implicit_tls.feature +0 -23
- data/features/ftp_server/invertability.feature +0 -15
- data/features/ftp_server/list.feature +0 -94
- data/features/ftp_server/list_tls.feature +0 -29
- data/features/ftp_server/logging.feature +0 -11
- data/features/ftp_server/login_auth_level_account.feature +0 -51
- data/features/ftp_server/login_auth_level_password.feature +0 -59
- data/features/ftp_server/login_auth_level_user.feature +0 -31
- data/features/ftp_server/max_connections.feature +0 -39
- data/features/ftp_server/mdtm.feature +0 -53
- data/features/ftp_server/mkdir.feature +0 -70
- data/features/ftp_server/mode.feature +0 -43
- data/features/ftp_server/name_list.feature +0 -77
- data/features/ftp_server/name_list_tls.feature +0 -30
- data/features/ftp_server/noop.feature +0 -17
- data/features/ftp_server/options.feature +0 -17
- data/features/ftp_server/pasv.feature +0 -30
- data/features/ftp_server/port.feature +0 -49
- data/features/ftp_server/put.feature +0 -79
- data/features/ftp_server/put_tls.feature +0 -23
- data/features/ftp_server/put_unique.feature +0 -56
- data/features/ftp_server/quit.feature +0 -23
- data/features/ftp_server/reinitialize.feature +0 -13
- data/features/ftp_server/rename.feature +0 -97
- data/features/ftp_server/rmdir.feature +0 -71
- data/features/ftp_server/site.feature +0 -13
- data/features/ftp_server/size.feature +0 -69
- data/features/ftp_server/status.feature +0 -18
- data/features/ftp_server/step_definitions/logging.rb +0 -10
- data/features/ftp_server/step_definitions/test_server.rb +0 -71
- data/features/ftp_server/structure_mount.feature +0 -13
- data/features/ftp_server/syntax_errors.feature +0 -18
- data/features/ftp_server/syst.feature +0 -18
- data/features/ftp_server/timeout.feature +0 -26
- data/features/ftp_server/type.feature +0 -59
- data/features/step_definitions/append.rb +0 -17
- data/features/step_definitions/client.rb +0 -27
- data/features/step_definitions/client_and_server_files.rb +0 -26
- data/features/step_definitions/client_files.rb +0 -16
- data/features/step_definitions/command.rb +0 -7
- data/features/step_definitions/connect.rb +0 -39
- data/features/step_definitions/delete.rb +0 -17
- data/features/step_definitions/directory_navigation.rb +0 -28
- data/features/step_definitions/error_replies.rb +0 -117
- data/features/step_definitions/features.rb +0 -23
- data/features/step_definitions/file_structure.rb +0 -18
- data/features/step_definitions/generic_send.rb +0 -11
- data/features/step_definitions/get.rb +0 -18
- data/features/step_definitions/help.rb +0 -20
- data/features/step_definitions/invalid_commands.rb +0 -13
- data/features/step_definitions/ipv6.rb +0 -11
- data/features/step_definitions/line_endings.rb +0 -9
- data/features/step_definitions/list.rb +0 -75
- data/features/step_definitions/login.rb +0 -84
- data/features/step_definitions/mkdir.rb +0 -11
- data/features/step_definitions/mode.rb +0 -17
- data/features/step_definitions/mtime.rb +0 -25
- data/features/step_definitions/noop.rb +0 -17
- data/features/step_definitions/options.rb +0 -11
- data/features/step_definitions/passive.rb +0 -10
- data/features/step_definitions/pending.rb +0 -5
- data/features/step_definitions/port.rb +0 -7
- data/features/step_definitions/put.rb +0 -31
- data/features/step_definitions/quit.rb +0 -17
- data/features/step_definitions/rename.rb +0 -13
- data/features/step_definitions/rmdir.rb +0 -11
- data/features/step_definitions/server_files.rb +0 -63
- data/features/step_definitions/server_title.rb +0 -14
- data/features/step_definitions/size.rb +0 -22
- data/features/step_definitions/status.rb +0 -11
- data/features/step_definitions/success_replies.rb +0 -9
- data/features/step_definitions/system.rb +0 -12
- data/features/step_definitions/timing.rb +0 -21
- data/features/step_definitions/type.rb +0 -17
- data/features/support/env.rb +0 -6
- data/features/support/example_server.rb +0 -69
- data/features/support/file_templates/ascii_unix +0 -4
- data/features/support/file_templates/ascii_windows +0 -4
- data/features/support/file_templates/binary +0 -0
- data/features/support/test_client.rb +0 -258
- data/features/support/test_file_templates.rb +0 -35
- data/features/support/test_server.rb +0 -304
- data/features/support/test_server_files.rb +0 -59
- data/rake_tasks/cucumber.rake +0 -9
- data/rake_tasks/default.rake +0 -1
- data/rake_tasks/jeweler.rake +0 -52
- data/rake_tasks/spec.rake +0 -3
- data/rake_tasks/test.rake +0 -2
- data/rake_tasks/yard.rake +0 -3
- data/spec/command_sequence_checker_spec.rb +0 -85
- data/spec/connection_throttle_spec.rb +0 -101
- data/spec/connection_tracker_spec.rb +0 -99
- data/spec/data_server_factory_spec.rb +0 -104
- data/spec/disk_file_system_spec.rb +0 -322
- data/spec/exception_translator_spec.rb +0 -38
- data/spec/file_info_spec.rb +0 -61
- data/spec/ftp_server_error_spec.rb +0 -15
- data/spec/list_format/eplf_spec.rb +0 -63
- data/spec/list_format/ls_spec.rb +0 -272
- data/spec/list_path_spec.rb +0 -23
- data/spec/null_logger_spec.rb +0 -26
- data/spec/protocols_spec.rb +0 -159
- data/spec/server_spec.rb +0 -83
- data/spec/spec_helper.rb +0 -17
- data/spec/telnet_spec.rb +0 -77
- data/spec/translate_exceptions_spec.rb +0 -42
- 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
|
data/rake_tasks/cucumber.rake
DELETED
data/rake_tasks/default.rake
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
task :default => [:test]
|
data/rake_tasks/jeweler.rake
DELETED
|
@@ -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
|
data/rake_tasks/spec.rake
DELETED
data/rake_tasks/test.rake
DELETED
data/rake_tasks/yard.rake
DELETED
|
@@ -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
|