ftpd 0.1.1 → 0.2.0

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.

@@ -5,18 +5,19 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "ftpd"
8
- s.version = "0.1.1"
8
+ s.version = "0.2.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-02-23"
13
- s.description = "ftpd is a pure Ruby FTP server library. It supports implicit and explicit TLS, and is suitable for use by a program such as a test fixture or small FTP daemon."
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."
14
14
  s.email = "wconrad@yagni.com"
15
15
  s.extra_rdoc_files = [
16
16
  "LICENSE.md",
17
17
  "README.md"
18
18
  ]
19
19
  s.files = [
20
+ "Changelog.md",
20
21
  "Gemfile",
21
22
  "Gemfile.lock",
22
23
  "LICENSE.md",
@@ -27,6 +28,7 @@ Gem::Specification.new do |s|
27
28
  "examples/hello_world.rb",
28
29
  "features/example/example.feature",
29
30
  "features/example/step_definitions/example_server.rb",
31
+ "features/ftp_server/allo.feature",
30
32
  "features/ftp_server/command_errors.feature",
31
33
  "features/ftp_server/concurrent_sessions.feature",
32
34
  "features/ftp_server/debug.feature",
@@ -49,6 +51,7 @@ Gem::Specification.new do |s|
49
51
  "features/ftp_server/step_definitions/debug.rb",
50
52
  "features/ftp_server/step_definitions/test_server.rb",
51
53
  "features/ftp_server/syntax_errors.feature",
54
+ "features/ftp_server/syst.feature",
52
55
  "features/ftp_server/type.feature",
53
56
  "features/step_definitions/client.rb",
54
57
  "features/step_definitions/client_and_server_files.rb",
@@ -57,8 +60,9 @@ Gem::Specification.new do |s|
57
60
  "features/step_definitions/connect.rb",
58
61
  "features/step_definitions/delete.rb",
59
62
  "features/step_definitions/directories.rb",
60
- "features/step_definitions/error.rb",
63
+ "features/step_definitions/error_replies.rb",
61
64
  "features/step_definitions/file_structure.rb",
65
+ "features/step_definitions/generic_send.rb",
62
66
  "features/step_definitions/get.rb",
63
67
  "features/step_definitions/invalid_commands.rb",
64
68
  "features/step_definitions/line_endings.rb",
@@ -72,6 +76,8 @@ Gem::Specification.new do |s|
72
76
  "features/step_definitions/quit.rb",
73
77
  "features/step_definitions/server_files.rb",
74
78
  "features/step_definitions/stop_server.rb",
79
+ "features/step_definitions/success_replies.rb",
80
+ "features/step_definitions/system.rb",
75
81
  "features/step_definitions/type.rb",
76
82
  "features/support/env.rb",
77
83
  "features/support/example_server.rb",
@@ -106,6 +112,7 @@ Gem::Specification.new do |s|
106
112
  "sandbox/em-server.rb",
107
113
  "spec/disk_file_system_spec.rb",
108
114
  "spec/exception_translator_spec.rb",
115
+ "spec/file_system_error_translator_spec.rb",
109
116
  "spec/spec_helper.rb",
110
117
  "spec/translate_exceptions_spec.rb"
111
118
  ]
@@ -1,136 +1,301 @@
1
1
  module Ftpd
2
2
 
3
- # An FTP file system mapped to a disk directory. This can serve as
4
- # a template for creating your own specialized driver.
5
- #
6
- # Some methods may raise FileSystemError; some may not. The
7
- # predicates (methods ending with a question mark) may not; other
8
- # methods may. FileSystemError is the _only_ exception which a file
9
- # system driver may raise.
10
-
11
3
  class DiskFileSystem
12
4
 
13
- include TranslateExceptions
5
+ # DiskFileSystem mixin for path expansion. Used by every command
6
+ # that accesses the disk file system.
14
7
 
15
- # Make a new instance to serve a directory. data_dir should be
16
- # fully qualified.
8
+ module PathExpansion
17
9
 
18
- def initialize(data_dir)
19
- @data_dir = data_dir
20
- translate_exception SystemCallError
21
- end
10
+ # Set the data directory, the root of the disk file system.
11
+ # data_dir should be an absolute path.
22
12
 
23
- # Return true if the path is accessible to the user. This will be
24
- # called for put, get and directory lists, so the file or
25
- # directory named by the path may not exist.
13
+ def set_data_dir(data_dir)
14
+ @data_dir = data_dir
15
+ end
26
16
 
27
- def accessible?(ftp_path)
28
- # The server should never try to access a path outside of the
29
- # directory (such as '../foo'), but if it does, we'll catch it
30
- # here.
31
- expand_ftp_path(ftp_path).start_with?(@data_dir)
32
- end
17
+ # Expand an ftp_path to an absolute file system path.
18
+ #
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.
33
22
 
34
- # Return true if the file or directory path exists.
23
+ def expand_ftp_path(ftp_path)
24
+ File.expand_path(File.join(@data_dir, ftp_path))
25
+ end
35
26
 
36
- def exists?(ftp_path)
37
- File.exists?(expand_ftp_path(ftp_path))
38
27
  end
28
+ end
29
+
30
+ class DiskFileSystem
31
+
32
+ # DiskFileSystem mixin providing file attributes. These are used,
33
+ # alone or in combination, by nearly every command that accesses the
34
+ # disk file system.
35
+
36
+ module Accessors
37
+
38
+ # Return true if the path is accessible to the user. This will be
39
+ # called for put, get and directory lists, so the file or
40
+ # directory named by the path may not exist.
41
+ #
42
+ # Called for:
43
+ # * STOR
44
+ # * RETR
45
+ # * DELE
46
+ # * CWD
47
+
48
+ def accessible?(ftp_path)
49
+ # The server should never try to access a path outside of the
50
+ # directory (such as '../foo'), but if it does, we'll catch it
51
+ # here.
52
+ expand_ftp_path(ftp_path).start_with?(@data_dir)
53
+ end
54
+
55
+ # Return true if the file or directory path exists.
56
+ #
57
+ # Called for:
58
+ # * STOR (with directory)
59
+ # * RETR
60
+ # * DELE
61
+ # * CWD
62
+
63
+ def exists?(ftp_path)
64
+ File.exists?(expand_ftp_path(ftp_path))
65
+ end
66
+
67
+ # Return true if the path exists and is a directory.
68
+ #
69
+ # Called for:
70
+ # * CWD
39
71
 
40
- # Return true if the path exists and is a directory.
72
+ def directory?(ftp_path)
73
+ File.directory?(expand_ftp_path(ftp_path))
74
+ end
41
75
 
42
- def directory?(ftp_path)
43
- File.directory?(expand_ftp_path(ftp_path))
44
76
  end
77
+ end
78
+
79
+ class DiskFileSystem
80
+
81
+ # DiskFileSystem mixin providing file deletion
82
+
83
+ module Delete
84
+
85
+ include TranslateExceptions
45
86
 
46
- # Remove a file. Can raise FileSystemError.
87
+ # Remove a file. Can raise FileSystemError.
88
+ #
89
+ # Called for:
90
+ # * DELE
91
+ #
92
+ # If missing, then these commands are not supported.
93
+
94
+ def delete(ftp_path)
95
+ FileUtils.rm expand_ftp_path(ftp_path)
96
+ end
97
+ translate_exceptions :delete
47
98
 
48
- def delete(ftp_path)
49
- FileUtils.rm expand_ftp_path(ftp_path)
50
99
  end
51
- translate_exceptions :delete
100
+ end
101
+
102
+ class DiskFileSystem
103
+
104
+ # DiskFileSystem mixin providing file reading
52
105
 
53
- # Read a file into memory. Can raise FileSystemError.
106
+ module Read
107
+
108
+ include TranslateExceptions
109
+
110
+ # Read a file into memory. Can raise FileSystemError.
111
+ #
112
+ # Called for:
113
+ # * RETR
114
+ #
115
+ # If missing, then these commands are not supported.
116
+
117
+ def read(ftp_path)
118
+ File.open(expand_ftp_path(ftp_path), 'rb', &:read)
119
+ end
120
+ translate_exceptions :read
54
121
 
55
- def read(ftp_path)
56
- File.open(expand_ftp_path(ftp_path), 'rb', &:read)
57
122
  end
58
- translate_exceptions :read
123
+ end
124
+
125
+ class DiskFileSystem
59
126
 
60
- # Write a file to disk. Can raise FileSystemError.
127
+ # DiskFileSystem mixin providing file writing
61
128
 
62
- def write(ftp_path, contents)
63
- File.open(expand_ftp_path(ftp_path), 'wb') do |file|
64
- file.write contents
129
+ module Write
130
+
131
+ include TranslateExceptions
132
+
133
+ # Write a file to disk. Can raise FileSystemError.
134
+ #
135
+ # Called for:
136
+ # * STOR
137
+ #
138
+ # If missing, then these commands are not supported.
139
+
140
+ def write(ftp_path, contents)
141
+ File.open(expand_ftp_path(ftp_path), 'wb') do |file|
142
+ file.write contents
143
+ end
65
144
  end
145
+ translate_exceptions :write
146
+
66
147
  end
67
- translate_exceptions :write
68
-
69
- # Get a file list, long form. Can raise FileSystemError. This
70
- # returns a long-form directory listing. The FTP standard does
71
- # not specify the format of the listing, but many systems emit a
72
- # *nix style directory listing:
73
- #
74
- # -rw-r--r-- 1 wayne wayne 4 Feb 18 18:36 a
75
- # -rw-r--r-- 1 wayne wayne 8 Feb 18 18:36 b
76
- #
77
- # some emit a Windows style listing. Some emit EPLF (Easily
78
- # Parsed List Format):
79
- #
80
- # +i8388621.48594,m825718503,r,s280, djb.html
81
- # +i8388621.50690,m824255907,/, 514
82
- # +i8388621.48598,m824253270,r,s612, 514.html
83
- #
84
- # EPLF is a draft internet standard for the output of LIST:
85
- #
86
- # http://cr.yp.to/ftp/list/eplf.html
87
- #
88
- # Some FTP clients know how to parse EPLF; those clients will
89
- # display the EPLF in a more user-friendly format. Clients that
90
- # don't recognize EPLF will display it raw. The advantages of
91
- # EPLF are that it's easier for clients to parse, and the client
92
- # can display the LIST output in any format it likes.
93
- #
94
- # This class emits a *nix style listing. It does so by shelling
95
- # to the "ls" command, so it won't run on Windows at all.
96
-
97
- def list_long(ftp_path)
98
- ls(ftp_path, '-l')
148
+ end
149
+
150
+ class DiskFileSystem
151
+
152
+ # Ls interface used by List and NameList
153
+
154
+ module Ls
155
+
156
+ def ls(ftp_path, option)
157
+ path = expand_ftp_path(ftp_path)
158
+ dirname = File.dirname(path)
159
+ filename = File.basename(path)
160
+ command = [
161
+ 'ls',
162
+ option,
163
+ filename,
164
+ '2>&1',
165
+ ].compact.join(' ')
166
+ if File.exists?(dirname)
167
+ list = Dir.chdir(dirname) do
168
+ `#{command}`
169
+ end
170
+ else
171
+ list = ''
172
+ end
173
+ list = "" if $? != 0
174
+ list = list.gsub(/^total \d+\n/, '')
175
+ end
176
+
99
177
  end
100
178
 
101
- # Get a file list, short form. Can raise FileSystemError.
102
- #
103
- # This returns one filename per line, and nothing else
179
+ end
180
+
181
+ class DiskFileSystem
182
+
183
+ # DiskFileSystem mixin providing directory listing
184
+
185
+ module List
186
+
187
+ include TranslateExceptions
188
+
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:
193
+ #
194
+ # -rw-r--r-- 1 wayne wayne 4 Feb 18 18:36 a
195
+ # -rw-r--r-- 1 wayne wayne 8 Feb 18 18:36 b
196
+ #
197
+ # some emit a Windows style listing. Some emit EPLF (Easily
198
+ # Parsed List Format):
199
+ #
200
+ # +i8388621.48594,m825718503,r,s280, djb.html
201
+ # +i8388621.50690,m824255907,/, 514
202
+ # +i8388621.48598,m824253270,r,s612, 514.html
203
+ #
204
+ # EPLF is a draft internet standard for the output of LIST:
205
+ #
206
+ # http://cr.yp.to/ftp/list/eplf.html
207
+ #
208
+ # Some FTP clients know how to parse EPLF; those clients will
209
+ # display the EPLF in a more user-friendly format. Clients that
210
+ # don't recognize EPLF will display it raw. The advantages of
211
+ # EPLF are that it's easier for clients to parse, and the client
212
+ # can display the LIST output in any format it likes.
213
+ #
214
+ # This class emits a *nix style listing. It does so by shelling
215
+ # to the "ls" command, so it won't run on Windows at all.
216
+ #
217
+ # Called for:
218
+ # * LIST
219
+ #
220
+ # If missing, then these commands are not supported.
221
+
222
+ def list(ftp_path)
223
+ ls(ftp_path, '-l')
224
+ end
104
225
 
105
- def list_short(ftp_path)
106
- ls(ftp_path, '-1')
107
226
  end
227
+ end
108
228
 
109
- private
110
-
111
- def ls(ftp_path, option)
112
- path = expand_ftp_path(ftp_path)
113
- dirname = File.dirname(path)
114
- filename = File.basename(path)
115
- command = [
116
- 'ls',
117
- option,
118
- filename,
119
- '2>&1',
120
- ].compact.join(' ')
121
- if File.exists?(dirname)
122
- list = Dir.chdir(dirname) do
123
- `#{command}`
124
- end
125
- else
126
- list = ''
229
+ class DiskFileSystem
230
+
231
+ # DiskFileSystem mixin providing directory name listing
232
+
233
+ module NameList
234
+
235
+ include Ls
236
+
237
+ # Get a file list, short form. Can raise FileSystemError.
238
+ #
239
+ # This returns one filename per line, and nothing else
240
+ #
241
+ # Called for:
242
+ # * NLST
243
+ #
244
+ # If missing, then these commands are not supported.
245
+
246
+ def name_list(ftp_path)
247
+ ls(ftp_path, '-1')
127
248
  end
128
- list = "" if $? != 0
129
- list = list.gsub(/^total \d+\n/, '')
249
+
130
250
  end
251
+ end
252
+
253
+ class DiskFileSystem
254
+
255
+ # DiskFileSystem "omnibus" mixin, which pulls in mixins which are
256
+ # likely to be needed by any DiskFileSystem.
131
257
 
132
- def expand_ftp_path(ftp_path)
133
- File.expand_path(File.join(@data_dir, ftp_path))
258
+ module Base
259
+ include TranslateExceptions
260
+ include DiskFileSystem::Accessors
261
+ include DiskFileSystem::PathExpansion
262
+ end
263
+
264
+ end
265
+
266
+ # An FTP file system mapped to a disk directory. This can serve as
267
+ # a template for creating your own specialized driver.
268
+ #
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.
273
+ #
274
+ # The class is divided into modules that may be included piecemeal.
275
+ # 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.
278
+
279
+ class DiskFileSystem
280
+
281
+ include DiskFileSystem::Base
282
+
283
+ # Mixins that make available commands or groups of commands. Each
284
+ # can be safely left out with the only effect being to make One or
285
+ # more commands be unimplemented.
286
+
287
+ include DiskFileSystem::Delete
288
+ include DiskFileSystem::List
289
+ include DiskFileSystem::NameList
290
+ include DiskFileSystem::Read
291
+ include DiskFileSystem::Write
292
+
293
+ # Make a new instance to serve a directory. data_dir should be an
294
+ # absolute path.
295
+
296
+ def initialize(data_dir)
297
+ set_data_dir data_dir
298
+ translate_exception SystemCallError
134
299
  end
135
300
 
136
301
  end