ftpd 0.3.2 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.yardopts +1 -0
- data/Changelog.md +25 -1
- data/README.md +91 -10
- data/VERSION +1 -1
- data/doc/rfc-compliance.md +20 -20
- data/examples/example.rb +69 -11
- data/features/example/read_only.feature +63 -0
- data/features/ftp_server/append.feature +94 -0
- data/features/ftp_server/command_errors.feature +0 -2
- data/features/ftp_server/concurrent_sessions.feature +1 -1
- data/features/ftp_server/debug.feature +4 -2
- data/features/ftp_server/delete.feature +1 -1
- data/features/ftp_server/get.feature +1 -1
- data/features/ftp_server/get_tls.feature +2 -1
- data/features/ftp_server/implicit_tls.feature +2 -1
- data/features/ftp_server/invertability.feature +15 -0
- data/features/ftp_server/list.feature +1 -1
- data/features/ftp_server/list_tls.feature +2 -1
- data/features/ftp_server/login_auth_level_account.feature +51 -0
- data/features/ftp_server/{login.feature → login_auth_level_password.feature} +4 -8
- data/features/ftp_server/login_auth_level_user.feature +31 -0
- data/features/ftp_server/mkdir.feature +1 -1
- data/features/ftp_server/name_list.feature +1 -1
- data/features/ftp_server/name_list_tls.feature +2 -1
- data/features/ftp_server/put.feature +1 -1
- data/features/ftp_server/put_tls.feature +2 -1
- data/features/ftp_server/put_unique.feature +1 -1
- data/features/ftp_server/rename.feature +1 -1
- data/features/ftp_server/rmdir.feature +1 -1
- data/features/ftp_server/step_definitions/debug.rb +1 -1
- data/features/ftp_server/step_definitions/test_server.rb +16 -15
- data/features/ftp_server/type.feature +11 -8
- data/features/step_definitions/append.rb +15 -0
- data/features/step_definitions/client_and_server_files.rb +2 -2
- data/features/step_definitions/client_files.rb +5 -0
- data/features/step_definitions/connect.rb +1 -1
- data/features/step_definitions/error_replies.rb +0 -4
- data/features/step_definitions/login.rb +30 -20
- data/features/step_definitions/server_files.rb +20 -7
- data/features/support/example_server.rb +10 -2
- data/features/support/test_client.rb +18 -0
- data/features/support/test_file_templates.rb +1 -1
- data/features/support/test_server.rb +25 -4
- data/ftpd.gemspec +11 -3
- data/lib/ftpd.rb +2 -0
- data/lib/ftpd/auth_levels.rb +9 -0
- data/lib/ftpd/disk_file_system.rb +48 -19
- data/lib/ftpd/ftp_server.rb +13 -10
- data/lib/ftpd/read_only_disk_file_system.rb +22 -0
- data/lib/ftpd/session.rb +62 -29
- data/spec/disk_file_system_spec.rb +30 -0
- metadata +12 -4
@@ -7,10 +7,18 @@ Feature: Representation Type
|
|
7
7
|
Background:
|
8
8
|
Given the test server is started
|
9
9
|
|
10
|
-
Scenario:
|
10
|
+
Scenario: ASCII/default
|
11
11
|
Given a successful login
|
12
12
|
Then the client successfully sets type "A"
|
13
13
|
|
14
|
+
Scenario: ASCII/Non-print
|
15
|
+
Given a successful login
|
16
|
+
Then the client successfully sets type "A N"
|
17
|
+
|
18
|
+
Scenario: ASCII/Telnet
|
19
|
+
Given a successful login
|
20
|
+
When the client successfully sets type "A T"
|
21
|
+
|
14
22
|
Scenario: Type IMAGE
|
15
23
|
Given a successful login
|
16
24
|
Then the client successfully sets type "I"
|
@@ -30,20 +38,15 @@ Feature: Representation Type
|
|
30
38
|
When the client sets type "*"
|
31
39
|
Then the server returns an invalid type error
|
32
40
|
|
33
|
-
Scenario: Format Telnet
|
34
|
-
Given a successful login
|
35
|
-
When the client sets type "A T"
|
36
|
-
Then the server returns a format not implemented error
|
37
|
-
|
38
41
|
Scenario: Format Carriage Control
|
39
42
|
Given a successful login
|
40
43
|
When the client sets type "A C"
|
41
|
-
Then the server returns a
|
44
|
+
Then the server returns a type not implemented error
|
42
45
|
|
43
46
|
Scenario: Invalid Format
|
44
47
|
Given a successful login
|
45
48
|
When the client sets type "A *"
|
46
|
-
Then the server returns an invalid
|
49
|
+
Then the server returns an invalid type error
|
47
50
|
|
48
51
|
Scenario: Not logged in
|
49
52
|
Given a successful connection
|
@@ -0,0 +1,15 @@
|
|
1
|
+
When /^the client appends (.*)$/ do |args|
|
2
|
+
capture_error do
|
3
|
+
step "the client successfully appends #{args}"
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
When /^the client successfully appends text "(.*?)" onto "(.*?)"$/ do
|
8
|
+
|local_path, remote_path|
|
9
|
+
@client.append_text local_path, remote_path
|
10
|
+
end
|
11
|
+
|
12
|
+
When /^the client successfully appends binary "(.*?)" onto "(.*?)"$/ do
|
13
|
+
|local_path, remote_path|
|
14
|
+
@client.append_binary local_path, remote_path
|
15
|
+
end
|
@@ -6,7 +6,7 @@ end
|
|
6
6
|
Then /^the remote file "(.*?)" should( exactly)? match the local file$/ do
|
7
7
|
|remote_path, exactly|
|
8
8
|
local_path = File.basename(remote_path)
|
9
|
-
remote_contents =
|
9
|
+
remote_contents = server.file_contents(remote_path)
|
10
10
|
local_contents = @client.file_contents(local_path)
|
11
11
|
remote_contents = unix_line_endings(exactly, remote_contents)
|
12
12
|
local_contents = unix_line_endings(exactly, local_contents)
|
@@ -16,7 +16,7 @@ end
|
|
16
16
|
Then /^the local file "(.*?)" should( exactly)? match the remote file$/ do
|
17
17
|
|local_path, exactly|
|
18
18
|
remote_path = local_path
|
19
|
-
remote_contents =
|
19
|
+
remote_contents = server.file_contents(remote_path)
|
20
20
|
local_contents = @client.file_contents(local_path)
|
21
21
|
remote_contents = unix_line_endings(exactly, remote_contents)
|
22
22
|
local_contents = unix_line_endings(exactly, local_contents)
|
@@ -7,3 +7,8 @@ Then /^the local file "(.*?)" should have (unix|windows) line endings$/ do
|
|
7
7
|
line_ending_type(@client.file_contents(local_path)).should ==
|
8
8
|
line_ending_type.to_sym
|
9
9
|
end
|
10
|
+
|
11
|
+
Then /^the local file "(.*?)" should match its template$/ do |local_path|
|
12
|
+
@client.template(local_path).should ==
|
13
|
+
@client.file_contents(local_path)
|
14
|
+
end
|
@@ -5,7 +5,7 @@ When /^the( \w+)? client connects(?: with (\w+) TLS)?$/ do
|
|
5
5
|
|client_name, tls_mode|
|
6
6
|
tls_mode ||= :off
|
7
7
|
client = TestClient.new(:tls => tls_mode.to_sym)
|
8
|
-
client.connect(
|
8
|
+
client.connect(server.host, server.port)
|
9
9
|
set_client client_name, client
|
10
10
|
end
|
11
11
|
|
@@ -73,10 +73,6 @@ Then /^the server returns a format not implemented error$/ do
|
|
73
73
|
step 'the server returns a "504 Format not implemented" error'
|
74
74
|
end
|
75
75
|
|
76
|
-
Then /^the server returns an invalid format error$/ do
|
77
|
-
step 'the server returns a "504 Invalid format code" error'
|
78
|
-
end
|
79
|
-
|
80
76
|
Then /^the server returns a command unrecognized error$/ do
|
81
77
|
step 'the server returns a "500 Syntax error, command unrecognized" error'
|
82
78
|
end
|
@@ -3,9 +3,9 @@ def logged_in?
|
|
3
3
|
@error.nil?
|
4
4
|
end
|
5
5
|
|
6
|
-
def login(
|
6
|
+
def login(tokens, client_name = nil)
|
7
7
|
capture_error do
|
8
|
-
client.login
|
8
|
+
client(client_name).login *tokens
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
@@ -18,26 +18,31 @@ Given /^a successful login( with \w+ TLS)?$/ do |with_tls|
|
|
18
18
|
step 'the client logs in'
|
19
19
|
end
|
20
20
|
|
21
|
-
Given /^the( \w+)? client connects and logs in$/ do |client_name|
|
22
|
-
step "the#{client_name} client connects"
|
23
|
-
step "the#{client_name} client logs in"
|
24
|
-
end
|
25
|
-
|
26
21
|
Given /^a failed login$/ do
|
27
22
|
step 'the client connects'
|
28
|
-
step 'the client logs in with
|
29
|
-
end
|
30
|
-
|
31
|
-
When /^the( \w+)? client logs in$/ do |client_name|
|
32
|
-
login @server.user, @server.password, client_name
|
23
|
+
step 'the client logs in with bad user'
|
33
24
|
end
|
34
25
|
|
35
|
-
When /^the client logs in with
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
26
|
+
When /^the(?: (\w+))? client logs in(?: with bad (\w+))?$/ do
|
27
|
+
|client_name, bad|
|
28
|
+
tokens = [
|
29
|
+
if bad == 'user'
|
30
|
+
'bad_user'
|
31
|
+
else
|
32
|
+
@server.user
|
33
|
+
end,
|
34
|
+
if bad == 'password'
|
35
|
+
'bad_password'
|
36
|
+
else
|
37
|
+
@server.password
|
38
|
+
end,
|
39
|
+
if bad == 'account'
|
40
|
+
'bad_account'
|
41
|
+
else
|
42
|
+
@server.account
|
43
|
+
end,
|
44
|
+
][0..server.auth_level]
|
45
|
+
login(tokens, client_name)
|
41
46
|
end
|
42
47
|
|
43
48
|
Then /^the client should( not)? be logged in$/ do |neg|
|
@@ -54,7 +59,7 @@ When /^the client sends a password( with no parameter)?$/ do |no_param|
|
|
54
59
|
args = if no_param
|
55
60
|
[]
|
56
61
|
else
|
57
|
-
[
|
62
|
+
[server.password]
|
58
63
|
end
|
59
64
|
@client.raw 'PASS', *args
|
60
65
|
end
|
@@ -65,8 +70,13 @@ When /^the client sends a user( with no parameter)?$/ do |no_param|
|
|
65
70
|
args = if no_param
|
66
71
|
[]
|
67
72
|
else
|
68
|
-
[
|
73
|
+
[server.user]
|
69
74
|
end
|
70
75
|
@client.raw 'USER', *args
|
71
76
|
end
|
72
77
|
end
|
78
|
+
|
79
|
+
Given /^the (\w+) client connects and logs in$/ do |client_name|
|
80
|
+
step "the #{client_name} client connects"
|
81
|
+
step "the #{client_name} client logs in"
|
82
|
+
end
|
@@ -1,9 +1,9 @@
|
|
1
1
|
Given /^the server has directory "(.*?)"$/ do |remote_path|
|
2
|
-
|
2
|
+
server.add_directory remote_path
|
3
3
|
end
|
4
4
|
|
5
5
|
Given /^the server has file "(.*?)"$/ do |remote_path|
|
6
|
-
|
6
|
+
server.add_file remote_path
|
7
7
|
end
|
8
8
|
|
9
9
|
Then /^the server should( not)? have file "(.*?)"$/ do |neg, path|
|
@@ -12,7 +12,7 @@ Then /^the server should( not)? have file "(.*?)"$/ do |neg, path|
|
|
12
12
|
else
|
13
13
|
:be_true
|
14
14
|
end
|
15
|
-
|
15
|
+
server.has_file?(path).should send(matcher)
|
16
16
|
end
|
17
17
|
|
18
18
|
Then /^the server should( not)? have directory "(.*?)"$/ do |neg, path|
|
@@ -21,21 +21,34 @@ Then /^the server should( not)? have directory "(.*?)"$/ do |neg, path|
|
|
21
21
|
else
|
22
22
|
:be_true
|
23
23
|
end
|
24
|
-
|
24
|
+
server.has_directory?(path).should send(matcher)
|
25
25
|
end
|
26
26
|
|
27
27
|
Then /^the remote file "(.*?)" should have (unix|windows) line endings$/ do
|
28
28
|
|remote_path, line_ending_type|
|
29
|
-
line_ending_type(
|
29
|
+
line_ending_type(server.file_contents(remote_path)).should ==
|
30
30
|
line_ending_type.to_sym
|
31
31
|
end
|
32
32
|
|
33
33
|
Then /^the server should have a file with the contents of "(.*?)"$/ do
|
34
34
|
|path|
|
35
|
-
|
35
|
+
server.has_file_with_contents_of?(path).should be_true
|
36
36
|
end
|
37
37
|
|
38
38
|
Then /^the server should have (\d+) files? with "(.*?)" in the name$/ do
|
39
39
|
|count, name|
|
40
|
-
|
40
|
+
server.files_named_like(name).size.should == count.to_i
|
41
|
+
end
|
42
|
+
|
43
|
+
Then /^the remote file "(.*?)" should match "(\w+)" \+ "(\w+)"$/ do
|
44
|
+
|remote_path, template1, template2|
|
45
|
+
expected = @server.template(template1) + @server.template(template2)
|
46
|
+
actual = @server.file_contents(remote_path)
|
47
|
+
actual.should == expected
|
48
|
+
end
|
49
|
+
|
50
|
+
Then /^the remote file "(.*?)" should match "(\w+)"$/ do |remote_path, template|
|
51
|
+
expected = @server.template(template)
|
52
|
+
actual = @server.file_contents(remote_path)
|
53
|
+
actual.should == expected
|
41
54
|
end
|
@@ -32,11 +32,19 @@ class ExampleServer
|
|
32
32
|
end
|
33
33
|
|
34
34
|
def user
|
35
|
-
@output[/User: (.*)$/, 1]
|
35
|
+
@output[/User: "(.*)"$/, 1]
|
36
36
|
end
|
37
37
|
|
38
38
|
def password
|
39
|
-
@output[/Pass: (.*)$/, 1]
|
39
|
+
@output[/Pass: "(.*)"$/, 1]
|
40
|
+
end
|
41
|
+
|
42
|
+
def account
|
43
|
+
@output[/Account: "(.*)"$/, 1]
|
44
|
+
end
|
45
|
+
|
46
|
+
def auth_level
|
47
|
+
Ftpd::AUTH_PASSWORD
|
40
48
|
end
|
41
49
|
|
42
50
|
private
|
@@ -57,6 +57,10 @@ class TestClient
|
|
57
57
|
end
|
58
58
|
end
|
59
59
|
|
60
|
+
def template(path)
|
61
|
+
@templates[File.basename(path)]
|
62
|
+
end
|
63
|
+
|
60
64
|
def file_contents(path)
|
61
65
|
File.open(temp_path(path), 'rb', &:read)
|
62
66
|
end
|
@@ -73,6 +77,20 @@ class TestClient
|
|
73
77
|
end
|
74
78
|
end
|
75
79
|
|
80
|
+
def append_binary(local_path, remote_path)
|
81
|
+
command = ['APPE', remote_path].compact.join(' ')
|
82
|
+
File.open(temp_path(local_path), 'rb') do |file|
|
83
|
+
@ftp.storbinary command, file, Net::FTP::DEFAULT_BLOCKSIZE
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def append_text(local_path, remote_path)
|
88
|
+
command = ['APPE', remote_path].compact.join(' ')
|
89
|
+
File.open(temp_path(local_path), 'rb') do |file|
|
90
|
+
@ftp.storlines command, file
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
76
94
|
private
|
77
95
|
|
78
96
|
RAW_METHOD_REGEX = /^send_(.*)$/
|
@@ -12,7 +12,9 @@ class TestServer
|
|
12
12
|
|
13
13
|
USER = 'user'
|
14
14
|
PASSWORD = 'password'
|
15
|
+
ACCOUNT = 'account'
|
15
16
|
|
17
|
+
attr_accessor :append
|
16
18
|
attr_accessor :delete
|
17
19
|
attr_accessor :list
|
18
20
|
attr_accessor :mkdir
|
@@ -23,6 +25,7 @@ class TestServer
|
|
23
25
|
|
24
26
|
def initialize(temp_dir)
|
25
27
|
@temp_dir = temp_dir
|
28
|
+
@append = true
|
26
29
|
@delete = true
|
27
30
|
@list = true
|
28
31
|
@mkdir = true
|
@@ -32,12 +35,15 @@ class TestServer
|
|
32
35
|
@write = true
|
33
36
|
end
|
34
37
|
|
35
|
-
def authenticate(user, password)
|
36
|
-
user == USER &&
|
38
|
+
def authenticate(user, password, account)
|
39
|
+
user == USER &&
|
40
|
+
(password.nil? || password == PASSWORD) &&
|
41
|
+
(account.nil? || account == ACCOUNT)
|
37
42
|
end
|
38
43
|
|
39
44
|
def file_system(user)
|
40
45
|
TestServerFileSystem.new(@temp_dir,
|
46
|
+
:append => @append,
|
41
47
|
:delete => @delete,
|
42
48
|
:list => @list,
|
43
49
|
:mkdir => @mkdir,
|
@@ -135,6 +141,11 @@ class TestServer
|
|
135
141
|
|
136
142
|
include Ftpd::DiskFileSystem::Base
|
137
143
|
|
144
|
+
if opts[:append]
|
145
|
+
include Ftpd::DiskFileSystem::Append
|
146
|
+
raise_on_file_system_error :append
|
147
|
+
end
|
148
|
+
|
138
149
|
if opts[:delete]
|
139
150
|
include Ftpd::DiskFileSystem::Delete
|
140
151
|
raise_on_file_system_error :delete
|
@@ -191,8 +202,7 @@ class TestServer
|
|
191
202
|
include Ftpd::InsecureCertificate
|
192
203
|
include TestServerFiles
|
193
204
|
|
194
|
-
def initialize
|
195
|
-
tls = opts[:tls] || :off
|
205
|
+
def initialize
|
196
206
|
@temp_dir = Ftpd::TempDir.make
|
197
207
|
@debug_file = Tempfile.new('ftp-server-debug-output')
|
198
208
|
@debug_file.close
|
@@ -205,9 +215,12 @@ class TestServer
|
|
205
215
|
self.tls = :off
|
206
216
|
end
|
207
217
|
|
218
|
+
def_delegator :@server, :'auth_level'
|
219
|
+
def_delegator :@server, :'auth_level='
|
208
220
|
def_delegator :@server, :'debug='
|
209
221
|
def_delegator :@server, :'tls='
|
210
222
|
|
223
|
+
def_delegator :@driver, :'append='
|
211
224
|
def_delegator :@driver, :'delete='
|
212
225
|
def_delegator :@driver, :'list='
|
213
226
|
def_delegator :@driver, :'mkdir='
|
@@ -240,10 +253,18 @@ class TestServer
|
|
240
253
|
TestServerDriver::PASSWORD
|
241
254
|
end
|
242
255
|
|
256
|
+
def account
|
257
|
+
TestServerDriver::ACCOUNT
|
258
|
+
end
|
259
|
+
|
243
260
|
def port
|
244
261
|
@server.bound_port
|
245
262
|
end
|
246
263
|
|
264
|
+
def template(path)
|
265
|
+
@templates[File.basename(path)]
|
266
|
+
end
|
267
|
+
|
247
268
|
private
|
248
269
|
|
249
270
|
def temp_dir
|
data/ftpd.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "ftpd"
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.4.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Wayne Conrad"]
|
12
|
-
s.date = "2013-03-
|
12
|
+
s.date = "2013-03-06"
|
13
13
|
s.description = "ftpd is a pure Ruby FTP server library. It supports implicit and explicit TLS, passive and active mode, and most of the commands specified in RFC 969. It an be used as part of a test fixture or embedded in a program."
|
14
14
|
s.email = "wconrad@yagni.com"
|
15
15
|
s.extra_rdoc_files = [
|
@@ -31,8 +31,10 @@ Gem::Specification.new do |s|
|
|
31
31
|
"examples/hello_world.rb",
|
32
32
|
"features/example/eplf.feature",
|
33
33
|
"features/example/example.feature",
|
34
|
+
"features/example/read_only.feature",
|
34
35
|
"features/example/step_definitions/example_server.rb",
|
35
36
|
"features/ftp_server/allo.feature",
|
37
|
+
"features/ftp_server/append.feature",
|
36
38
|
"features/ftp_server/cdup.feature",
|
37
39
|
"features/ftp_server/command_errors.feature",
|
38
40
|
"features/ftp_server/concurrent_sessions.feature",
|
@@ -44,9 +46,12 @@ Gem::Specification.new do |s|
|
|
44
46
|
"features/ftp_server/get_tls.feature",
|
45
47
|
"features/ftp_server/help.feature",
|
46
48
|
"features/ftp_server/implicit_tls.feature",
|
49
|
+
"features/ftp_server/invertability.feature",
|
47
50
|
"features/ftp_server/list.feature",
|
48
51
|
"features/ftp_server/list_tls.feature",
|
49
|
-
"features/ftp_server/
|
52
|
+
"features/ftp_server/login_auth_level_account.feature",
|
53
|
+
"features/ftp_server/login_auth_level_password.feature",
|
54
|
+
"features/ftp_server/login_auth_level_user.feature",
|
50
55
|
"features/ftp_server/mkdir.feature",
|
51
56
|
"features/ftp_server/mode.feature",
|
52
57
|
"features/ftp_server/name_list.feature",
|
@@ -64,6 +69,7 @@ Gem::Specification.new do |s|
|
|
64
69
|
"features/ftp_server/syntax_errors.feature",
|
65
70
|
"features/ftp_server/syst.feature",
|
66
71
|
"features/ftp_server/type.feature",
|
72
|
+
"features/step_definitions/append.rb",
|
67
73
|
"features/step_definitions/client.rb",
|
68
74
|
"features/step_definitions/client_and_server_files.rb",
|
69
75
|
"features/step_definitions/client_files.rb",
|
@@ -107,6 +113,7 @@ Gem::Specification.new do |s|
|
|
107
113
|
"ftpd.gemspec",
|
108
114
|
"insecure-test-cert.pem",
|
109
115
|
"lib/ftpd.rb",
|
116
|
+
"lib/ftpd/auth_levels.rb",
|
110
117
|
"lib/ftpd/command_sequence_checker.rb",
|
111
118
|
"lib/ftpd/disk_file_system.rb",
|
112
119
|
"lib/ftpd/error.rb",
|
@@ -118,6 +125,7 @@ Gem::Specification.new do |s|
|
|
118
125
|
"lib/ftpd/insecure_certificate.rb",
|
119
126
|
"lib/ftpd/list_format/eplf.rb",
|
120
127
|
"lib/ftpd/list_format/ls.rb",
|
128
|
+
"lib/ftpd/read_only_disk_file_system.rb",
|
121
129
|
"lib/ftpd/server.rb",
|
122
130
|
"lib/ftpd/session.rb",
|
123
131
|
"lib/ftpd/temp_dir.rb",
|