ftpd 0.3.2 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. data/.yardopts +1 -0
  2. data/Changelog.md +25 -1
  3. data/README.md +91 -10
  4. data/VERSION +1 -1
  5. data/doc/rfc-compliance.md +20 -20
  6. data/examples/example.rb +69 -11
  7. data/features/example/read_only.feature +63 -0
  8. data/features/ftp_server/append.feature +94 -0
  9. data/features/ftp_server/command_errors.feature +0 -2
  10. data/features/ftp_server/concurrent_sessions.feature +1 -1
  11. data/features/ftp_server/debug.feature +4 -2
  12. data/features/ftp_server/delete.feature +1 -1
  13. data/features/ftp_server/get.feature +1 -1
  14. data/features/ftp_server/get_tls.feature +2 -1
  15. data/features/ftp_server/implicit_tls.feature +2 -1
  16. data/features/ftp_server/invertability.feature +15 -0
  17. data/features/ftp_server/list.feature +1 -1
  18. data/features/ftp_server/list_tls.feature +2 -1
  19. data/features/ftp_server/login_auth_level_account.feature +51 -0
  20. data/features/ftp_server/{login.feature → login_auth_level_password.feature} +4 -8
  21. data/features/ftp_server/login_auth_level_user.feature +31 -0
  22. data/features/ftp_server/mkdir.feature +1 -1
  23. data/features/ftp_server/name_list.feature +1 -1
  24. data/features/ftp_server/name_list_tls.feature +2 -1
  25. data/features/ftp_server/put.feature +1 -1
  26. data/features/ftp_server/put_tls.feature +2 -1
  27. data/features/ftp_server/put_unique.feature +1 -1
  28. data/features/ftp_server/rename.feature +1 -1
  29. data/features/ftp_server/rmdir.feature +1 -1
  30. data/features/ftp_server/step_definitions/debug.rb +1 -1
  31. data/features/ftp_server/step_definitions/test_server.rb +16 -15
  32. data/features/ftp_server/type.feature +11 -8
  33. data/features/step_definitions/append.rb +15 -0
  34. data/features/step_definitions/client_and_server_files.rb +2 -2
  35. data/features/step_definitions/client_files.rb +5 -0
  36. data/features/step_definitions/connect.rb +1 -1
  37. data/features/step_definitions/error_replies.rb +0 -4
  38. data/features/step_definitions/login.rb +30 -20
  39. data/features/step_definitions/server_files.rb +20 -7
  40. data/features/support/example_server.rb +10 -2
  41. data/features/support/test_client.rb +18 -0
  42. data/features/support/test_file_templates.rb +1 -1
  43. data/features/support/test_server.rb +25 -4
  44. data/ftpd.gemspec +11 -3
  45. data/lib/ftpd.rb +2 -0
  46. data/lib/ftpd/auth_levels.rb +9 -0
  47. data/lib/ftpd/disk_file_system.rb +48 -19
  48. data/lib/ftpd/ftp_server.rb +13 -10
  49. data/lib/ftpd/read_only_disk_file_system.rb +22 -0
  50. data/lib/ftpd/session.rb +62 -29
  51. data/spec/disk_file_system_spec.rb +30 -0
  52. 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: Type ASCII
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 format not implemented error
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 format error
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 = @server.file_contents(remote_path)
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 = @server.file_contents(remote_path)
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(@server.host, @server.port)
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(user, password, client_name = nil)
6
+ def login(tokens, client_name = nil)
7
7
  capture_error do
8
- client.login user, password
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 a bad password'
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 a bad password$/ do
36
- login @server.user, 'the-wrong-password'
37
- end
38
-
39
- When /^the client logs in with a bad user$/ do
40
- login 'the-wrong-user', @server.password
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
- [@server.password]
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
- [@server.user]
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
- @server.add_directory remote_path
2
+ server.add_directory remote_path
3
3
  end
4
4
 
5
5
  Given /^the server has file "(.*?)"$/ do |remote_path|
6
- @server.add_file remote_path
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
- @server.has_file?(path).should send(matcher)
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
- @server.has_directory?(path).should send(matcher)
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(@server.file_contents(remote_path)).should ==
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
- @server.has_file_with_contents_of?(path).should be_true
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
- @server.files_named_like(name).size.should == count.to_i
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_(.*)$/
@@ -15,7 +15,7 @@ class TestFileTemplates
15
15
  end
16
16
 
17
17
  def read_template(filename)
18
- File.open(template_path(filename), 'r', &:read)
18
+ File.open(template_path(filename), 'rb', &:read)
19
19
  end
20
20
 
21
21
  def template_path(filename)
@@ -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 && password == PASSWORD
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(opts = {})
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.3.2"
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-05"
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/login.feature",
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",