ftpd 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of ftpd might be problematic. Click here for more details.

Files changed (49) hide show
  1. data/Changelog.md +24 -3
  2. data/Gemfile +2 -1
  3. data/Gemfile.lock +9 -2
  4. data/README.md +20 -9
  5. data/VERSION +1 -1
  6. data/doc/rfc.md +277 -0
  7. data/features/ftp_server/cdup.feature +36 -0
  8. data/features/ftp_server/command_errors.feature +0 -4
  9. data/features/ftp_server/delete.feature +1 -1
  10. data/features/ftp_server/directory_navigation.feature +18 -6
  11. data/features/ftp_server/get.feature +1 -1
  12. data/features/ftp_server/get_tls.feature +2 -2
  13. data/features/ftp_server/implicit_tls.feature +18 -0
  14. data/features/ftp_server/list_tls.feature +2 -2
  15. data/features/ftp_server/mkdir.feature +70 -0
  16. data/features/ftp_server/name_list_tls.feature +2 -2
  17. data/features/ftp_server/put.feature +1 -1
  18. data/features/ftp_server/put_tls.feature +2 -2
  19. data/features/ftp_server/rename.feature +90 -0
  20. data/features/ftp_server/rmdir.feature +71 -0
  21. data/features/ftp_server/step_definitions/debug.rb +6 -6
  22. data/features/ftp_server/step_definitions/test_server.rb +3 -2
  23. data/features/step_definitions/connect.rb +4 -3
  24. data/features/step_definitions/{directories.rb → directory_navigation.rb} +4 -0
  25. data/features/step_definitions/error_replies.rb +5 -5
  26. data/features/step_definitions/login.rb +2 -2
  27. data/features/step_definitions/mkdir.rb +9 -0
  28. data/features/step_definitions/rename.rb +11 -0
  29. data/features/step_definitions/rmdir.rb +9 -0
  30. data/features/step_definitions/server_files.rb +9 -0
  31. data/features/support/test_client.rb +19 -5
  32. data/features/support/test_server.rb +28 -3
  33. data/features/support/test_server_files.rb +5 -0
  34. data/ftpd.gemspec +17 -6
  35. data/lib/ftpd.rb +1 -0
  36. data/lib/ftpd/disk_file_system.rb +97 -16
  37. data/lib/ftpd/error.rb +16 -0
  38. data/lib/ftpd/exception_translator.rb +1 -1
  39. data/lib/ftpd/exceptions.rb +10 -4
  40. data/lib/ftpd/file_system_error_translator.rb +8 -4
  41. data/lib/ftpd/ftp_server.rb +1 -0
  42. data/lib/ftpd/session.rb +98 -87
  43. data/rake_tasks/yard.rake +1 -0
  44. data/spec/disk_file_system_spec.rb +55 -8
  45. data/spec/exception_translator_spec.rb +1 -1
  46. data/spec/file_system_error_translator_spec.rb +20 -4
  47. data/spec/translate_exceptions_spec.rb +1 -1
  48. metadata +32 -7
  49. data/sandbox/em-server.rb +0 -37
@@ -20,3 +20,7 @@ end
20
20
  Then /^the current directory should be "(.*?)"$/ do |path|
21
21
  @client.pwd.should == path
22
22
  end
23
+
24
+ Then /^the XPWD directory should be "(.*?)"$/ do |path|
25
+ @client.xpwd.should == path
26
+ end
@@ -13,10 +13,6 @@ Then /^the server returns a "(.*?)" error$/ do |error_message|
13
13
  (@error || '').should include error_message
14
14
  end
15
15
 
16
- Then /^the server returns a no such file error$/ do
17
- step 'the server returns a "550 No such file or directory" error'
18
- end
19
-
20
16
  Then /^the server returns a not a directory error$/ do
21
17
  step 'the server returns a "550 Not a directory" error'
22
18
  end
@@ -90,5 +86,9 @@ Then /^the server returns an unimplemented command error$/ do
90
86
  end
91
87
 
92
88
  Then /^the server returns an action not taken error$/ do
93
- step 'the server returns a "450 Unable to do it" error'
89
+ step 'the server returns a "550 Unable to do it" error'
90
+ end
91
+
92
+ Then /^the server returns an already exists error$/ do
93
+ step 'the server returns a "550 Already exists" error'
94
94
  end
@@ -9,11 +9,11 @@ def login(user, password, client_name = nil)
9
9
  end
10
10
  end
11
11
 
12
- Given /^a successful connection( with TLS)?$/ do |with_tls|
12
+ Given /^a successful connection( with \w+ TLS)?$/ do |with_tls|
13
13
  step "the client connects#{with_tls}"
14
14
  end
15
15
 
16
- Given /^a successful login( with TLS)?$/ do |with_tls|
16
+ Given /^a successful login( with \w+ TLS)?$/ do |with_tls|
17
17
  step "a successful connection#{with_tls}"
18
18
  step 'the client logs in'
19
19
  end
@@ -0,0 +1,9 @@
1
+ When /^the client makes directory "(.*?)"$/ do |path|
2
+ capture_error do
3
+ step %Q(the client successfully makes directory "#{path}")
4
+ end
5
+ end
6
+
7
+ When /^the client successfully makes directory "(.*?)"$/ do |path|
8
+ mkdir_response = @client.mkdir path
9
+ end
@@ -0,0 +1,11 @@
1
+ When /^the client renames "(.*?)" to "(.*?)"$/ do
2
+ |from_path, to_path|
3
+ capture_error do
4
+ step %Q'the client successfully renames "#{from_path}" to "#{to_path}"'
5
+ end
6
+ end
7
+
8
+ When /^the client successfully renames "(.*?)" to "(.*?)"$/ do
9
+ |from_path, to_path|
10
+ @client.rename(from_path, to_path)
11
+ end
@@ -0,0 +1,9 @@
1
+ When /^the client removes directory "(.*?)"$/ do |path|
2
+ capture_error do
3
+ step %Q(the client successfully removes directory "#{path}")
4
+ end
5
+ end
6
+
7
+ When /^the client successfully removes directory "(.*?)"$/ do |path|
8
+ mkdir_response = @client.rmdir path
9
+ end
@@ -15,6 +15,15 @@ Then /^the server should( not)? have file "(.*?)"$/ do |neg, path|
15
15
  @server.has_file?(path).should send(matcher)
16
16
  end
17
17
 
18
+ Then /^the server should( not)? have directory "(.*?)"$/ do |neg, path|
19
+ matcher = if neg
20
+ :be_false
21
+ else
22
+ :be_true
23
+ end
24
+ @server.has_directory?(path).should send(matcher)
25
+ end
26
+
18
27
  Then /^the remote file "(.*?)" should have (unix|windows) line endings$/ do
19
28
  |remote_path, line_ending_type|
20
29
  line_ending_type(@server.file_contents(remote_path)).should ==
@@ -24,10 +24,13 @@ class TestClient
24
24
  :gettextfile,
25
25
  :login,
26
26
  :ls,
27
+ :mkdir,
27
28
  :nlst,
28
29
  :noop,
29
30
  :passive=,
30
31
  :pwd,
32
+ :rename,
33
+ :rmdir,
31
34
  :quit,
32
35
  :system
33
36
 
@@ -57,6 +60,11 @@ class TestClient
57
60
  File.open(temp_path(path), 'rb', &:read)
58
61
  end
59
62
 
63
+ def xpwd
64
+ response = raw('XPWD')
65
+ response[/"(.+)"/, 1]
66
+ end
67
+
60
68
  private
61
69
 
62
70
  RAW_METHOD_REGEX = /^send_(.*)$/
@@ -70,20 +78,26 @@ class TestClient
70
78
  end
71
79
 
72
80
  def make_ftp(opts)
73
- tls = opts[:tls]
74
- if tls
75
- make_tls_ftp
76
- else
81
+ tls_mode = opts[:tls] || :off
82
+ case tls_mode
83
+ when :off
77
84
  make_non_tls_ftp
85
+ when :implicit
86
+ make_tls_ftp(:implicit)
87
+ when :explicit
88
+ make_tls_ftp(:explicit)
89
+ else
90
+ raise "Unknown TLS mode: #{tls_mode}"
78
91
  end
79
92
  end
80
93
 
81
- def make_tls_ftp
94
+ def make_tls_ftp(ftps_mode)
82
95
  ftp = DoubleBagFTPS.new
83
96
  context_opts = {
84
97
  :verify_mode => OpenSSL::SSL::VERIFY_NONE
85
98
  }
86
99
  ftp.ssl_context = DoubleBagFTPS.create_ssl_context(context_opts)
100
+ ftp.ftps_mode = ftps_mode
87
101
  ftp
88
102
  end
89
103
 
@@ -15,16 +15,22 @@ class TestServer
15
15
 
16
16
  attr_accessor :delete
17
17
  attr_accessor :list
18
+ attr_accessor :mkdir
18
19
  attr_accessor :name_list
19
20
  attr_accessor :read
21
+ attr_accessor :rename
22
+ attr_accessor :rmdir
20
23
  attr_accessor :write
21
24
 
22
25
  def initialize(temp_dir)
23
26
  @temp_dir = temp_dir
24
27
  @delete = true
25
28
  @list = true
29
+ @mkdir = true
26
30
  @name_list = true
27
31
  @read = true
32
+ @rename = true
33
+ @rmdir = true
28
34
  @write = true
29
35
  end
30
36
 
@@ -36,8 +42,11 @@ class TestServer
36
42
  TestServerFileSystem.new(@temp_dir,
37
43
  :delete => @delete,
38
44
  :list => @list,
45
+ :mkdir => @mkdir,
39
46
  :name_list => @name_list,
40
47
  :read => @read,
48
+ :rename => @rename,
49
+ :rmdir => @rmdir,
41
50
  :write => @write)
42
51
  end
43
52
 
@@ -88,7 +97,7 @@ class TestServer
88
97
  define_method method_name do |*args|
89
98
  ftp_path = args.first
90
99
  if force_file_system_error?(ftp_path)
91
- raise Ftpd::FileSystemError, 'Unable to do it'
100
+ raise Ftpd::PermanentFileSystemError, 'Unable to do it'
92
101
  end
93
102
  original_method.bind(self).call *args
94
103
  end
@@ -142,11 +151,24 @@ class TestServer
142
151
  include Ftpd::DiskFileSystem::NameList
143
152
  end
144
153
 
154
+ if opts[:mkdir]
155
+ include Ftpd::DiskFileSystem::Mkdir
156
+ end
157
+
145
158
  if opts[:read]
146
159
  include Ftpd::DiskFileSystem::Read
147
160
  raise_on_file_system_error :read
148
161
  end
149
162
 
163
+ if opts[:rename]
164
+ include Ftpd::DiskFileSystem::Rename
165
+ raise_on_file_system_error :rename
166
+ end
167
+
168
+ if opts[:rmdir]
169
+ include Ftpd::DiskFileSystem::Rmdir
170
+ end
171
+
150
172
  if opts[:write]
151
173
  include Ftpd::DiskFileSystem::Write
152
174
  raise_on_file_system_error :write
@@ -195,8 +217,11 @@ class TestServer
195
217
 
196
218
  def_delegator :@driver, :'delete='
197
219
  def_delegator :@driver, :'list='
220
+ def_delegator :@driver, :'mkdir='
198
221
  def_delegator :@driver, :'name_list='
222
+ def_delegator :@driver, :'rmdir='
199
223
  def_delegator :@driver, :'read='
224
+ def_delegator :@driver, :'rename='
200
225
  def_delegator :@driver, :'write='
201
226
 
202
227
  def start
@@ -207,8 +232,8 @@ class TestServer
207
232
  @server.stop
208
233
  end
209
234
 
210
- def wrote_debug_output?
211
- File.size(@debug_file.path) > 0
235
+ def debug_output
236
+ IO.read(@debug_file.path)
212
237
  end
213
238
 
214
239
  def host
@@ -18,6 +18,11 @@ module TestServerFiles
18
18
  File.exists?(full_path)
19
19
  end
20
20
 
21
+ def has_directory?(path)
22
+ full_path = temp_path(path)
23
+ File.directory?(full_path)
24
+ end
25
+
21
26
  def file_contents(path)
22
27
  full_path = temp_path(path)
23
28
  File.open(full_path, 'rb', &:read)
@@ -5,12 +5,12 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "ftpd"
8
- s.version = "0.2.0"
8
+ s.version = "0.2.1"
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-02-24"
13
- s.description = "ftpd is a pure Ruby FTP server library. It supports implicit and explicit TLS, and can be used as part of a test fixture or to embed in another program."
12
+ s.date = "2013-02-28"
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 = [
16
16
  "LICENSE.md",
@@ -24,11 +24,13 @@ Gem::Specification.new do |s|
24
24
  "README.md",
25
25
  "Rakefile",
26
26
  "VERSION",
27
+ "doc/rfc.md",
27
28
  "examples/example.rb",
28
29
  "examples/hello_world.rb",
29
30
  "features/example/example.feature",
30
31
  "features/example/step_definitions/example_server.rb",
31
32
  "features/ftp_server/allo.feature",
33
+ "features/ftp_server/cdup.feature",
32
34
  "features/ftp_server/command_errors.feature",
33
35
  "features/ftp_server/concurrent_sessions.feature",
34
36
  "features/ftp_server/debug.feature",
@@ -37,9 +39,11 @@ Gem::Specification.new do |s|
37
39
  "features/ftp_server/file_structure.feature",
38
40
  "features/ftp_server/get.feature",
39
41
  "features/ftp_server/get_tls.feature",
42
+ "features/ftp_server/implicit_tls.feature",
40
43
  "features/ftp_server/list.feature",
41
44
  "features/ftp_server/list_tls.feature",
42
45
  "features/ftp_server/login.feature",
46
+ "features/ftp_server/mkdir.feature",
43
47
  "features/ftp_server/mode.feature",
44
48
  "features/ftp_server/name_list.feature",
45
49
  "features/ftp_server/name_list_tls.feature",
@@ -48,6 +52,8 @@ Gem::Specification.new do |s|
48
52
  "features/ftp_server/put.feature",
49
53
  "features/ftp_server/put_tls.feature",
50
54
  "features/ftp_server/quit.feature",
55
+ "features/ftp_server/rename.feature",
56
+ "features/ftp_server/rmdir.feature",
51
57
  "features/ftp_server/step_definitions/debug.rb",
52
58
  "features/ftp_server/step_definitions/test_server.rb",
53
59
  "features/ftp_server/syntax_errors.feature",
@@ -59,7 +65,7 @@ Gem::Specification.new do |s|
59
65
  "features/step_definitions/command.rb",
60
66
  "features/step_definitions/connect.rb",
61
67
  "features/step_definitions/delete.rb",
62
- "features/step_definitions/directories.rb",
68
+ "features/step_definitions/directory_navigation.rb",
63
69
  "features/step_definitions/error_replies.rb",
64
70
  "features/step_definitions/file_structure.rb",
65
71
  "features/step_definitions/generic_send.rb",
@@ -68,12 +74,15 @@ Gem::Specification.new do |s|
68
74
  "features/step_definitions/line_endings.rb",
69
75
  "features/step_definitions/list.rb",
70
76
  "features/step_definitions/login.rb",
77
+ "features/step_definitions/mkdir.rb",
71
78
  "features/step_definitions/mode.rb",
72
79
  "features/step_definitions/noop.rb",
73
80
  "features/step_definitions/passive.rb",
74
81
  "features/step_definitions/port.rb",
75
82
  "features/step_definitions/put.rb",
76
83
  "features/step_definitions/quit.rb",
84
+ "features/step_definitions/rename.rb",
85
+ "features/step_definitions/rmdir.rb",
77
86
  "features/step_definitions/server_files.rb",
78
87
  "features/step_definitions/stop_server.rb",
79
88
  "features/step_definitions/success_replies.rb",
@@ -109,7 +118,6 @@ Gem::Specification.new do |s|
109
118
  "rake_tasks/spec.rake",
110
119
  "rake_tasks/test.rake",
111
120
  "rake_tasks/yard.rake",
112
- "sandbox/em-server.rb",
113
121
  "spec/disk_file_system_spec.rb",
114
122
  "spec/exception_translator_spec.rb",
115
123
  "spec/file_system_error_translator_spec.rb",
@@ -119,7 +127,7 @@ Gem::Specification.new do |s|
119
127
  s.homepage = "http://github.com/wconrad/ftpd"
120
128
  s.licenses = ["MIT"]
121
129
  s.require_paths = ["lib"]
122
- s.rubygems_version = "1.8.25"
130
+ s.rubygems_version = "1.8.24"
123
131
  s.summary = "Pure Ruby FTP server library"
124
132
 
125
133
  if s.respond_to? :specification_version then
@@ -131,6 +139,7 @@ Gem::Specification.new do |s|
131
139
  s.add_development_dependency(%q<double-bag-ftps>, [">= 0"])
132
140
  s.add_development_dependency(%q<jeweler>, [">= 0"])
133
141
  s.add_development_dependency(%q<rake>, [">= 0"])
142
+ s.add_development_dependency(%q<redcarpet>, [">= 0"])
134
143
  s.add_development_dependency(%q<rspec>, [">= 0"])
135
144
  s.add_development_dependency(%q<yard>, [">= 0"])
136
145
  else
@@ -139,6 +148,7 @@ Gem::Specification.new do |s|
139
148
  s.add_dependency(%q<double-bag-ftps>, [">= 0"])
140
149
  s.add_dependency(%q<jeweler>, [">= 0"])
141
150
  s.add_dependency(%q<rake>, [">= 0"])
151
+ s.add_dependency(%q<redcarpet>, [">= 0"])
142
152
  s.add_dependency(%q<rspec>, [">= 0"])
143
153
  s.add_dependency(%q<yard>, [">= 0"])
144
154
  end
@@ -148,6 +158,7 @@ Gem::Specification.new do |s|
148
158
  s.add_dependency(%q<double-bag-ftps>, [">= 0"])
149
159
  s.add_dependency(%q<jeweler>, [">= 0"])
150
160
  s.add_dependency(%q<rake>, [">= 0"])
161
+ s.add_dependency(%q<redcarpet>, [">= 0"])
151
162
  s.add_dependency(%q<rspec>, [">= 0"])
152
163
  s.add_dependency(%q<yard>, [">= 0"])
153
164
  end
@@ -10,6 +10,7 @@ module Ftpd
10
10
  autoload :Error, 'ftpd/error'
11
11
  autoload :ExceptionTranslator, 'ftpd/exception_translator'
12
12
  autoload :FileSystemErrorTranslator, 'ftpd/file_system_error_translator'
13
+ autoload :FileSystemMethodMissing, 'ftpd/file_system_method_missing'
13
14
  autoload :FtpServer, 'ftpd/ftp_server'
14
15
  autoload :InsecureCertificate, 'ftpd/insecure_certificate'
15
16
  autoload :Server, 'ftpd/server'
@@ -17,8 +17,8 @@ module Ftpd
17
17
  # Expand an ftp_path to an absolute file system path.
18
18
  #
19
19
  # ftp_path is an absolute path relative to the FTP file system.
20
- # @data_dir is an absolute path relative to the disk file system.
21
- # The return value is an absolute path relative to the disk file system.
20
+ # The return value is an absolute path relative to the disk file
21
+ # system.
22
22
 
23
23
  def expand_ftp_path(ftp_path)
24
24
  File.expand_path(File.join(@data_dir, ftp_path))
@@ -44,6 +44,8 @@ module Ftpd
44
44
  # * RETR
45
45
  # * DELE
46
46
  # * CWD
47
+ # * MKD
48
+ # * RMD
47
49
 
48
50
  def accessible?(ftp_path)
49
51
  # The server should never try to access a path outside of the
@@ -59,6 +61,7 @@ module Ftpd
59
61
  # * RETR
60
62
  # * DELE
61
63
  # * CWD
64
+ # * MKD
62
65
 
63
66
  def exists?(ftp_path)
64
67
  File.exists?(expand_ftp_path(ftp_path))
@@ -68,6 +71,7 @@ module Ftpd
68
71
  #
69
72
  # Called for:
70
73
  # * CWD
74
+ # * MKD
71
75
 
72
76
  def directory?(ftp_path)
73
77
  File.directory?(expand_ftp_path(ftp_path))
@@ -84,7 +88,7 @@ module Ftpd
84
88
 
85
89
  include TranslateExceptions
86
90
 
87
- # Remove a file. Can raise FileSystemError.
91
+ # Remove a file.
88
92
  #
89
93
  # Called for:
90
94
  # * DELE
@@ -107,7 +111,7 @@ module Ftpd
107
111
 
108
112
  include TranslateExceptions
109
113
 
110
- # Read a file into memory. Can raise FileSystemError.
114
+ # Read a file into memory.
111
115
  #
112
116
  # Called for:
113
117
  # * RETR
@@ -130,7 +134,7 @@ module Ftpd
130
134
 
131
135
  include TranslateExceptions
132
136
 
133
- # Write a file to disk. Can raise FileSystemError.
137
+ # Write a file to disk.
134
138
  #
135
139
  # Called for:
136
140
  # * STOR
@@ -147,6 +151,54 @@ module Ftpd
147
151
  end
148
152
  end
149
153
 
154
+ class DiskFileSystem
155
+
156
+ # DiskFileSystem mixing providing mkdir
157
+
158
+ module Mkdir
159
+
160
+ include TranslateExceptions
161
+
162
+ # Create a directory.
163
+ #
164
+ # Called for:
165
+ # * MKD
166
+ #
167
+ # If missing, then these commands are not supported.
168
+
169
+ def mkdir(ftp_path)
170
+ Dir.mkdir expand_ftp_path(ftp_path)
171
+ end
172
+ translate_exceptions :mkdir
173
+
174
+ end
175
+
176
+ end
177
+
178
+ class DiskFileSystem
179
+
180
+ # DiskFileSystem mixing providing mkdir
181
+
182
+ module Rmdir
183
+
184
+ include TranslateExceptions
185
+
186
+ # Remove a directory.
187
+ #
188
+ # Called for:
189
+ # * RMD
190
+ #
191
+ # If missing, then these commands are not supported.
192
+
193
+ def rmdir(ftp_path)
194
+ Dir.rmdir expand_ftp_path(ftp_path)
195
+ end
196
+ translate_exceptions :rmdir
197
+
198
+ end
199
+
200
+ end
201
+
150
202
  class DiskFileSystem
151
203
 
152
204
  # Ls interface used by List and NameList
@@ -186,10 +238,10 @@ module Ftpd
186
238
 
187
239
  include TranslateExceptions
188
240
 
189
- # Get a file list, long form. Can raise FileSystemError. This
190
- # returns a long-form directory listing. The FTP standard does
191
- # not specify the format of the listing, but many systems emit a
192
- # *nix style directory listing:
241
+ # Get a file list, long form. This returns a long-form
242
+ # directory listing. The FTP standard does not specify the
243
+ # format of the listing, but many systems emit a *nix style
244
+ # directory listing:
193
245
  #
194
246
  # -rw-r--r-- 1 wayne wayne 4 Feb 18 18:36 a
195
247
  # -rw-r--r-- 1 wayne wayne 8 Feb 18 18:36 b
@@ -234,7 +286,7 @@ module Ftpd
234
286
 
235
287
  include Ls
236
288
 
237
- # Get a file list, short form. Can raise FileSystemError.
289
+ # Get a file list, short form.
238
290
  #
239
291
  # This returns one filename per line, and nothing else
240
292
  #
@@ -250,6 +302,30 @@ module Ftpd
250
302
  end
251
303
  end
252
304
 
305
+ class DiskFileSystem
306
+
307
+ # DiskFileSystem mixin providing file/directory rename/move
308
+
309
+ module Rename
310
+
311
+ include TranslateExceptions
312
+
313
+ # Rename or move a file or directory
314
+ #
315
+ # Called for:
316
+ # * RNTO
317
+ #
318
+ # If missing, then these commands are not supported.
319
+
320
+ def rename(from_ftp_path, to_ftp_path)
321
+ FileUtils.mv(expand_ftp_path(from_ftp_path),
322
+ expand_ftp_path(to_ftp_path))
323
+ end
324
+ translate_exceptions :rename
325
+
326
+ end
327
+ end
328
+
253
329
  class DiskFileSystem
254
330
 
255
331
  # DiskFileSystem "omnibus" mixin, which pulls in mixins which are
@@ -266,15 +342,17 @@ module Ftpd
266
342
  # An FTP file system mapped to a disk directory. This can serve as
267
343
  # a template for creating your own specialized driver.
268
344
  #
269
- # Some methods may raise FileSystemError; some may not. The
270
- # predicates (methods ending with a question mark) may not; other
271
- # methods may. FileSystemError is the _only_ exception which a file
272
- # system driver may raise.
345
+ # Any method may raise a PermanentFileSystemError (e.g. "file not
346
+ # found") or TransientFileSystemError (e.g. "file busy"). A
347
+ # PermanentFileSystemError will cause a "550" error response to be
348
+ # sent; a TransientFileSystemError will cause a "450" error response
349
+ # to be sent.
273
350
  #
274
351
  # The class is divided into modules that may be included piecemeal.
275
352
  # By including some mixins and not others, you can compose a disk
276
- # file system driver "a la cart." This is useful if you want an FTP
277
- # server that, for example, allows reading but not writing files.
353
+ # file system driver "a la carte." This is useful if you want an
354
+ # FTP server that, for example, allows reading but not writing
355
+ # files.
278
356
 
279
357
  class DiskFileSystem
280
358
 
@@ -286,8 +364,11 @@ module Ftpd
286
364
 
287
365
  include DiskFileSystem::Delete
288
366
  include DiskFileSystem::List
367
+ include DiskFileSystem::Mkdir
289
368
  include DiskFileSystem::NameList
290
369
  include DiskFileSystem::Read
370
+ include DiskFileSystem::Rename
371
+ include DiskFileSystem::Rmdir
291
372
  include DiskFileSystem::Write
292
373
 
293
374
  # Make a new instance to serve a directory. data_dir should be an