ftpd 0.17.0 → 1.0.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.
- checksums.yaml +13 -5
- data/Changelog.md +7 -0
- data/Gemfile +9 -8
- data/Gemfile.lock +66 -55
- data/README.md +4 -2
- data/VERSION +1 -1
- data/features/ftp_server/step_definitions/logging.rb +6 -6
- data/features/step_definitions/client_and_server_files.rb +2 -2
- data/features/step_definitions/client_files.rb +4 -4
- data/features/step_definitions/connect.rb +2 -2
- data/features/step_definitions/directory_navigation.rb +2 -2
- data/features/step_definitions/error_replies.rb +2 -2
- data/features/step_definitions/features.rb +2 -2
- data/features/step_definitions/help.rb +4 -4
- data/features/step_definitions/list.rb +9 -9
- data/features/step_definitions/login.rb +3 -3
- data/features/step_definitions/mtime.rb +1 -1
- data/features/step_definitions/server_files.rb +11 -11
- data/features/step_definitions/server_title.rb +2 -2
- data/features/step_definitions/size.rb +1 -1
- data/features/step_definitions/success_replies.rb +1 -1
- data/features/step_definitions/timing.rb +2 -2
- data/ftpd.gemspec +30 -27
- data/spec/connection_throttle_spec.rb +14 -11
- data/spec/connection_tracker_spec.rb +23 -17
- data/spec/disk_file_system_spec.rb +15 -15
- data/spec/exception_translator_spec.rb +1 -0
- data/spec/file_info_spec.rb +4 -4
- data/spec/list_format/eplf_spec.rb +3 -4
- data/spec/list_format/ls_spec.rb +3 -3
- data/spec/null_logger_spec.rb +1 -1
- data/spec/protocols_spec.rb +8 -8
- data/spec/spec_helper.rb +1 -0
- metadata +54 -40
@@ -4,9 +4,9 @@ Then /^the server returns its title$/ do
|
|
4
4
|
end
|
5
5
|
|
6
6
|
Then /^the server returns its name$/ do
|
7
|
-
@response.
|
7
|
+
expect(@response).to include @server.server_name
|
8
8
|
end
|
9
9
|
|
10
10
|
Then /^the server returns its version$/ do
|
11
|
-
@response.
|
11
|
+
expect(@response).to match /\b\d+\.\d+\.\d+\b/
|
12
12
|
end
|
@@ -9,11 +9,11 @@ end
|
|
9
9
|
Then /^it should take at least (\S+) seconds$/ do |s|
|
10
10
|
min_elapsed_time = s.to_f
|
11
11
|
elapsed_time = Time.now - @start_time
|
12
|
-
elapsed_time.
|
12
|
+
expect(elapsed_time).to be >= min_elapsed_time
|
13
13
|
end
|
14
14
|
|
15
15
|
Then /^it should take less than (\S+) seconds$/ do |s|
|
16
16
|
max_elapsed_time = s.to_f
|
17
17
|
elapsed_time = Time.now - @start_time
|
18
|
-
elapsed_time.
|
18
|
+
expect(elapsed_time).to be < max_elapsed_time
|
19
19
|
end
|
data/ftpd.gemspec
CHANGED
@@ -2,16 +2,16 @@
|
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
3
3
|
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
|
-
# stub: ftpd 0.
|
5
|
+
# stub: ftpd 1.0.0 ruby lib
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "ftpd"
|
9
|
-
s.version = "0.
|
9
|
+
s.version = "1.0.0"
|
10
10
|
|
11
11
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
12
12
|
s.require_paths = ["lib"]
|
13
13
|
s.authors = ["Wayne Conrad"]
|
14
|
-
s.date = "2014-
|
14
|
+
s.date = "2014-09-05"
|
15
15
|
s.description = "ftpd is a pure Ruby FTP server library. It supports implicit and explicit TLS, IPV6, passive and active mode, and is unconditionally compliant per RFC-1123. It can be used as part of a test fixture or embedded in a program."
|
16
16
|
s.email = "wconrad@yagni.com"
|
17
17
|
s.extra_rdoc_files = [
|
@@ -245,35 +245,38 @@ Gem::Specification.new do |s|
|
|
245
245
|
|
246
246
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
247
247
|
s.add_runtime_dependency(%q<memoizer>, ["~> 1.0"])
|
248
|
-
s.add_development_dependency(%q<cucumber>, ["
|
249
|
-
s.add_development_dependency(%q<double-bag-ftps>, ["
|
250
|
-
s.add_development_dependency(%q<jeweler>, ["
|
251
|
-
s.add_development_dependency(%q<rake>, ["
|
252
|
-
s.add_development_dependency(%q<redcarpet>, ["
|
253
|
-
s.add_development_dependency(%q<rspec>, ["
|
254
|
-
s.add_development_dependency(%q<
|
255
|
-
s.add_development_dependency(%q<
|
248
|
+
s.add_development_dependency(%q<cucumber>, ["~> 1.3.16"])
|
249
|
+
s.add_development_dependency(%q<double-bag-ftps>, ["~> 0.1.2"])
|
250
|
+
s.add_development_dependency(%q<jeweler>, ["~> 2.0.1"])
|
251
|
+
s.add_development_dependency(%q<rake>, ["~> 10.3.2"])
|
252
|
+
s.add_development_dependency(%q<redcarpet>, ["~> 3.1.2"])
|
253
|
+
s.add_development_dependency(%q<rspec>, ["~> 3.1.0"])
|
254
|
+
s.add_development_dependency(%q<rspec-its>, ["~> 1.0.1"])
|
255
|
+
s.add_development_dependency(%q<timecop>, ["~> 0.7.1"])
|
256
|
+
s.add_development_dependency(%q<yard>, ["~> 0.8.7.4"])
|
256
257
|
else
|
257
258
|
s.add_dependency(%q<memoizer>, ["~> 1.0"])
|
258
|
-
s.add_dependency(%q<cucumber>, ["
|
259
|
-
s.add_dependency(%q<double-bag-ftps>, ["
|
260
|
-
s.add_dependency(%q<jeweler>, ["
|
261
|
-
s.add_dependency(%q<rake>, ["
|
262
|
-
s.add_dependency(%q<redcarpet>, ["
|
263
|
-
s.add_dependency(%q<rspec>, ["
|
264
|
-
s.add_dependency(%q<
|
265
|
-
s.add_dependency(%q<
|
259
|
+
s.add_dependency(%q<cucumber>, ["~> 1.3.16"])
|
260
|
+
s.add_dependency(%q<double-bag-ftps>, ["~> 0.1.2"])
|
261
|
+
s.add_dependency(%q<jeweler>, ["~> 2.0.1"])
|
262
|
+
s.add_dependency(%q<rake>, ["~> 10.3.2"])
|
263
|
+
s.add_dependency(%q<redcarpet>, ["~> 3.1.2"])
|
264
|
+
s.add_dependency(%q<rspec>, ["~> 3.1.0"])
|
265
|
+
s.add_dependency(%q<rspec-its>, ["~> 1.0.1"])
|
266
|
+
s.add_dependency(%q<timecop>, ["~> 0.7.1"])
|
267
|
+
s.add_dependency(%q<yard>, ["~> 0.8.7.4"])
|
266
268
|
end
|
267
269
|
else
|
268
270
|
s.add_dependency(%q<memoizer>, ["~> 1.0"])
|
269
|
-
s.add_dependency(%q<cucumber>, ["
|
270
|
-
s.add_dependency(%q<double-bag-ftps>, ["
|
271
|
-
s.add_dependency(%q<jeweler>, ["
|
272
|
-
s.add_dependency(%q<rake>, ["
|
273
|
-
s.add_dependency(%q<redcarpet>, ["
|
274
|
-
s.add_dependency(%q<rspec>, ["
|
275
|
-
s.add_dependency(%q<
|
276
|
-
s.add_dependency(%q<
|
271
|
+
s.add_dependency(%q<cucumber>, ["~> 1.3.16"])
|
272
|
+
s.add_dependency(%q<double-bag-ftps>, ["~> 0.1.2"])
|
273
|
+
s.add_dependency(%q<jeweler>, ["~> 2.0.1"])
|
274
|
+
s.add_dependency(%q<rake>, ["~> 10.3.2"])
|
275
|
+
s.add_dependency(%q<redcarpet>, ["~> 3.1.2"])
|
276
|
+
s.add_dependency(%q<rspec>, ["~> 3.1.0"])
|
277
|
+
s.add_dependency(%q<rspec-its>, ["~> 1.0.1"])
|
278
|
+
s.add_dependency(%q<timecop>, ["~> 0.7.1"])
|
279
|
+
s.add_dependency(%q<yard>, ["~> 0.8.7.4"])
|
277
280
|
end
|
278
281
|
end
|
279
282
|
|
@@ -15,13 +15,16 @@ module Ftpd
|
|
15
15
|
end
|
16
16
|
|
17
17
|
before(:each) do
|
18
|
-
connection_tracker.
|
19
|
-
|
18
|
+
allow(connection_tracker).to receive(:connections)
|
19
|
+
.and_return(connections)
|
20
|
+
allow(connection_tracker).to receive(:connections_for)
|
21
|
+
.with(socket)
|
22
|
+
.and_return(connections_for_socket)
|
20
23
|
end
|
21
24
|
|
22
25
|
it 'should have defaults' do
|
23
|
-
connection_throttle.max_connections.
|
24
|
-
connection_throttle.max_connections_per_ip.
|
26
|
+
expect(connection_throttle.max_connections).to be_nil
|
27
|
+
expect(connection_throttle.max_connections_per_ip).to be_nil
|
25
28
|
end
|
26
29
|
|
27
30
|
describe '#allow?' do
|
@@ -37,17 +40,17 @@ module Ftpd
|
|
37
40
|
|
38
41
|
context 'almost at maximum connections' do
|
39
42
|
let(:connections) {max_connections - 1}
|
40
|
-
specify {connection_throttle.allow?(socket).
|
43
|
+
specify {expect(connection_throttle.allow?(socket)).to be_truthy}
|
41
44
|
end
|
42
45
|
|
43
46
|
context 'at maximum connections' do
|
44
47
|
let(:connections) {max_connections}
|
45
|
-
specify {connection_throttle.allow?(socket).
|
48
|
+
specify {expect(connection_throttle.allow?(socket)).to be_falsey}
|
46
49
|
end
|
47
50
|
|
48
51
|
context 'above maximum connections' do
|
49
52
|
let(:connections) {max_connections + 1}
|
50
|
-
specify {connection_throttle.allow?(socket).
|
53
|
+
specify {expect(connection_throttle.allow?(socket)).to be_falsey}
|
51
54
|
end
|
52
55
|
|
53
56
|
end
|
@@ -63,17 +66,17 @@ module Ftpd
|
|
63
66
|
|
64
67
|
context 'almost at maximum connections for ip' do
|
65
68
|
let(:connections_for_socket) {max_connections_per_ip - 1}
|
66
|
-
specify {connection_throttle.allow?(socket).
|
69
|
+
specify {expect(connection_throttle.allow?(socket)).to be_truthy}
|
67
70
|
end
|
68
71
|
|
69
72
|
context 'at maximum connections for ip' do
|
70
73
|
let(:connections_for_socket) {max_connections_per_ip}
|
71
|
-
specify {connection_throttle.allow?(socket).
|
74
|
+
specify {expect(connection_throttle.allow?(socket)).to be_falsey}
|
72
75
|
end
|
73
76
|
|
74
77
|
context 'above maximum connections for ip' do
|
75
78
|
let(:connections_for_socket) {max_connections_per_ip + 1}
|
76
|
-
specify {connection_throttle.allow?(socket).
|
79
|
+
specify {expect(connection_throttle.allow?(socket)).to be_falsey}
|
77
80
|
end
|
78
81
|
|
79
82
|
end
|
@@ -86,7 +89,7 @@ module Ftpd
|
|
86
89
|
|
87
90
|
it 'should send a "too many connections" message' do
|
88
91
|
connection_throttle.deny socket
|
89
|
-
socket.string.
|
92
|
+
expect(socket.string).to eq "421 Too many connections\r\n"
|
90
93
|
end
|
91
94
|
|
92
95
|
end
|
@@ -13,7 +13,7 @@ module Ftpd
|
|
13
13
|
def socket_bound_to(source_ip)
|
14
14
|
socket = double TCPSocket
|
15
15
|
peeraddr = Socket.pack_sockaddr_in(0, source_ip)
|
16
|
-
socket.
|
16
|
+
allow(socket).to receive(:getpeername) {peeraddr}
|
17
17
|
socket
|
18
18
|
end
|
19
19
|
|
@@ -23,6 +23,11 @@ module Ftpd
|
|
23
23
|
|
24
24
|
class Connector
|
25
25
|
|
26
|
+
# Enable the rspec expect syntax in this class.
|
27
|
+
# This uses an internal API of rspec-mock.
|
28
|
+
# See: http://stackoverflow.com/q/25692786/238886
|
29
|
+
RSpec::Mocks::Syntax.enable_expect self
|
30
|
+
|
26
31
|
def initialize(connection_tracker)
|
27
32
|
@connection_tracker = connection_tracker
|
28
33
|
@tracked = Queue.new
|
@@ -39,7 +44,8 @@ module Ftpd
|
|
39
44
|
@tracked.enq :go
|
40
45
|
command = @end_session.deq
|
41
46
|
if command == :close
|
42
|
-
socket.
|
47
|
+
allow(socket).to receive(:getpeername)
|
48
|
+
.and_raise(RuntimeError, "Socket closed")
|
43
49
|
end
|
44
50
|
end
|
45
51
|
@session_ended.enq :go
|
@@ -67,11 +73,11 @@ module Ftpd
|
|
67
73
|
context '(session ends normally)' do
|
68
74
|
|
69
75
|
it 'should track the total number of connection' do
|
70
|
-
connection_tracker.connections.
|
76
|
+
expect(connection_tracker.connections).to eq 0
|
71
77
|
connector.start_session socket
|
72
|
-
connection_tracker.connections.
|
78
|
+
expect(connection_tracker.connections).to eq 1
|
73
79
|
connector.end_session
|
74
|
-
connection_tracker.connections.
|
80
|
+
expect(connection_tracker.connections).to eq 0
|
75
81
|
end
|
76
82
|
|
77
83
|
end
|
@@ -79,11 +85,11 @@ module Ftpd
|
|
79
85
|
context '(socket disconnected during session)' do
|
80
86
|
|
81
87
|
it 'should track the total number of connection' do
|
82
|
-
connection_tracker.connections.
|
88
|
+
expect(connection_tracker.connections).to eq 0
|
83
89
|
connector.start_session socket
|
84
|
-
connection_tracker.connections.
|
90
|
+
expect(connection_tracker.connections).to eq 1
|
85
91
|
connector.end_session :close
|
86
|
-
connection_tracker.connections.
|
92
|
+
expect(connection_tracker.connections).to eq 0
|
87
93
|
end
|
88
94
|
|
89
95
|
end
|
@@ -95,14 +101,14 @@ module Ftpd
|
|
95
101
|
it 'should track the number of connections for an ip' do
|
96
102
|
socket1 = socket_bound_to('127.0.0.1')
|
97
103
|
socket2 = socket_bound_to('127.0.0.2')
|
98
|
-
connection_tracker.connections_for(socket1).
|
99
|
-
connection_tracker.connections_for(socket2).
|
104
|
+
expect(connection_tracker.connections_for(socket1)).to eq 0
|
105
|
+
expect(connection_tracker.connections_for(socket2)).to eq 0
|
100
106
|
connector.start_session socket1
|
101
|
-
connection_tracker.connections_for(socket1).
|
102
|
-
connection_tracker.connections_for(socket2).
|
107
|
+
expect(connection_tracker.connections_for(socket1)).to eq 1
|
108
|
+
expect(connection_tracker.connections_for(socket2)).to eq 0
|
103
109
|
connector.end_session
|
104
|
-
connection_tracker.connections_for(socket1).
|
105
|
-
connection_tracker.connections_for(socket2).
|
110
|
+
expect(connection_tracker.connections_for(socket1)).to eq 0
|
111
|
+
expect(connection_tracker.connections_for(socket2)).to eq 0
|
106
112
|
end
|
107
113
|
|
108
114
|
end
|
@@ -112,11 +118,11 @@ module Ftpd
|
|
112
118
|
let(:socket) {socket_bound_to('127.0.0.1')}
|
113
119
|
|
114
120
|
it 'should forget about an IP that has no connection' do
|
115
|
-
connection_tracker.known_ip_count.
|
121
|
+
expect(connection_tracker.known_ip_count).to eq 0
|
116
122
|
connector.start_session socket
|
117
|
-
connection_tracker.known_ip_count.
|
123
|
+
expect(connection_tracker.known_ip_count).to eq 1
|
118
124
|
connector.end_session
|
119
|
-
connection_tracker.known_ip_count.
|
125
|
+
expect(connection_tracker.known_ip_count).to eq 0
|
120
126
|
end
|
121
127
|
|
122
128
|
end
|
@@ -60,13 +60,13 @@ module Ftpd
|
|
60
60
|
|
61
61
|
context '(within tree)' do
|
62
62
|
specify do
|
63
|
-
disk_file_system.accessible?('file').
|
63
|
+
expect(disk_file_system.accessible?('file')).to be_truthy
|
64
64
|
end
|
65
65
|
end
|
66
66
|
|
67
67
|
context '(outside tree)' do
|
68
68
|
specify do
|
69
|
-
disk_file_system.accessible?('../outside').
|
69
|
+
expect(disk_file_system.accessible?('../outside')).to be_falsey
|
70
70
|
end
|
71
71
|
end
|
72
72
|
|
@@ -76,13 +76,13 @@ module Ftpd
|
|
76
76
|
|
77
77
|
context '(exists)' do
|
78
78
|
specify do
|
79
|
-
disk_file_system.exists?('file').
|
79
|
+
expect(disk_file_system.exists?('file')).to be_truthy
|
80
80
|
end
|
81
81
|
end
|
82
82
|
|
83
83
|
context '(does not exist)' do
|
84
84
|
specify do
|
85
|
-
disk_file_system.exists?('missing').
|
85
|
+
expect(disk_file_system.exists?('missing')).to be_falsey
|
86
86
|
end
|
87
87
|
end
|
88
88
|
|
@@ -92,13 +92,13 @@ module Ftpd
|
|
92
92
|
|
93
93
|
context '(directory)' do
|
94
94
|
specify do
|
95
|
-
disk_file_system.directory?('file').
|
95
|
+
expect(disk_file_system.directory?('file')).to be_falsey
|
96
96
|
end
|
97
97
|
end
|
98
98
|
|
99
99
|
context '(file)' do
|
100
100
|
specify do
|
101
|
-
disk_file_system.directory?('dir').
|
101
|
+
expect(disk_file_system.directory?('dir')).to be_truthy
|
102
102
|
end
|
103
103
|
end
|
104
104
|
|
@@ -109,7 +109,7 @@ module Ftpd
|
|
109
109
|
context '(success)' do
|
110
110
|
specify do
|
111
111
|
disk_file_system.delete('file')
|
112
|
-
exists?('file').
|
112
|
+
expect(exists?('file')).to be_falsey
|
113
113
|
end
|
114
114
|
end
|
115
115
|
|
@@ -129,8 +129,8 @@ module Ftpd
|
|
129
129
|
let(:path) {'file'}
|
130
130
|
specify do
|
131
131
|
disk_file_system.read(path) do |file|
|
132
|
-
file.
|
133
|
-
file.read.
|
132
|
+
expect(file).to be_a(IO)
|
133
|
+
expect(file.read).to eq canned_contents(path)
|
134
134
|
end
|
135
135
|
end
|
136
136
|
end
|
@@ -154,7 +154,7 @@ module Ftpd
|
|
154
154
|
let(:path) {'file_path'}
|
155
155
|
specify do
|
156
156
|
disk_file_system.write(path, stream)
|
157
|
-
read_file(path).
|
157
|
+
expect(read_file(path)).to eq contents
|
158
158
|
end
|
159
159
|
end
|
160
160
|
|
@@ -177,7 +177,7 @@ module Ftpd
|
|
177
177
|
let(:path) {'file_path'}
|
178
178
|
specify do
|
179
179
|
disk_file_system.append(path, stream)
|
180
|
-
read_file(path).
|
180
|
+
expect(read_file(path)).to eq contents
|
181
181
|
end
|
182
182
|
end
|
183
183
|
|
@@ -185,7 +185,7 @@ module Ftpd
|
|
185
185
|
let(:path) {'file'}
|
186
186
|
specify do
|
187
187
|
disk_file_system.append(path, stream)
|
188
|
-
read_file(path).
|
188
|
+
expect(read_file(path)).to eq canned_contents(path) + contents
|
189
189
|
end
|
190
190
|
end
|
191
191
|
|
@@ -205,7 +205,7 @@ module Ftpd
|
|
205
205
|
let(:path) {'another_subdir'}
|
206
206
|
specify do
|
207
207
|
disk_file_system.mkdir(path)
|
208
|
-
directory?(path).
|
208
|
+
expect(directory?(path)).to be_truthy
|
209
209
|
end
|
210
210
|
end
|
211
211
|
|
@@ -227,8 +227,8 @@ module Ftpd
|
|
227
227
|
context '(success)' do
|
228
228
|
specify do
|
229
229
|
disk_file_system.rename(from_path, to_path)
|
230
|
-
exists?(from_path).
|
231
|
-
exists?(to_path).
|
230
|
+
expect(exists?(from_path)).to be_falsey
|
231
|
+
expect(exists?(to_path)).to be_truthy
|
232
232
|
end
|
233
233
|
end
|
234
234
|
|
data/spec/file_info_spec.rb
CHANGED
@@ -29,12 +29,12 @@ module Ftpd
|
|
29
29
|
|
30
30
|
context '(file)' do
|
31
31
|
let(:ftype) {'file'}
|
32
|
-
its(:file?) {should
|
32
|
+
its(:file?) {should be_truthy}
|
33
33
|
end
|
34
34
|
|
35
35
|
context '(directory)' do
|
36
36
|
let(:ftype) {'directory'}
|
37
|
-
its(:file?) {should
|
37
|
+
its(:file?) {should be_falsey}
|
38
38
|
end
|
39
39
|
|
40
40
|
end
|
@@ -45,12 +45,12 @@ module Ftpd
|
|
45
45
|
|
46
46
|
context '(file)' do
|
47
47
|
let(:ftype) {'file'}
|
48
|
-
its(:directory?) {should
|
48
|
+
its(:directory?) {should be_falsey}
|
49
49
|
end
|
50
50
|
|
51
51
|
context '(directory)' do
|
52
52
|
let(:ftype) {'directory'}
|
53
|
-
its(:directory?) {should
|
53
|
+
its(:directory?) {should be_truthy}
|
54
54
|
end
|
55
55
|
|
56
56
|
end
|
@@ -16,7 +16,7 @@ module Ftpd
|
|
16
16
|
subject(:formatter) {Eplf.new(file_info)}
|
17
17
|
|
18
18
|
it 'should produce EPLF format' do
|
19
|
-
formatter.to_s.
|
19
|
+
expect(formatter.to_s).to eq "+r,s1234,m1362299880\tfoo"
|
20
20
|
end
|
21
21
|
|
22
22
|
end
|
@@ -33,7 +33,7 @@ module Ftpd
|
|
33
33
|
subject(:formatter) {Eplf.new(file_info)}
|
34
34
|
|
35
35
|
it 'should produce EPLF format' do
|
36
|
-
formatter.to_s.
|
36
|
+
expect(formatter.to_s).to eq "+/,m1362299880\tfoo"
|
37
37
|
end
|
38
38
|
|
39
39
|
end
|
@@ -51,8 +51,7 @@ module Ftpd
|
|
51
51
|
subject(:formatter) {Eplf.new(file_info)}
|
52
52
|
|
53
53
|
it 'should produce EPLF format' do
|
54
|
-
formatter.to_s.
|
55
|
-
"+r,s1234,m1362299880,i1234.5678\tfoo"
|
54
|
+
expect(formatter.to_s).to eq "+r,s1234,m1362299880,i1234.5678\tfoo"
|
56
55
|
end
|
57
56
|
|
58
57
|
end
|