ftpd 0.0.1.pre → 0.1.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.

Files changed (70) hide show
  1. data/Gemfile +3 -1
  2. data/Gemfile.lock +14 -14
  3. data/README.md +71 -23
  4. data/Rakefile +9 -3
  5. data/VERSION +1 -1
  6. data/examples/example.rb +132 -53
  7. data/examples/hello_world.rb +32 -0
  8. data/features/example/example.feature +18 -0
  9. data/features/example/step_definitions/example_server.rb +3 -0
  10. data/features/{command_errors.feature → ftp_server/command_errors.feature} +3 -0
  11. data/features/ftp_server/concurrent_sessions.feature +14 -0
  12. data/features/ftp_server/debug.feature +15 -0
  13. data/features/{delete.feature → ftp_server/delete.feature} +23 -2
  14. data/features/{directory_navigation.feature → ftp_server/directory_navigation.feature} +17 -4
  15. data/features/ftp_server/file_structure.feature +43 -0
  16. data/features/{get.feature → ftp_server/get.feature} +21 -10
  17. data/features/ftp_server/get_tls.feature +18 -0
  18. data/features/{list.feature → ftp_server/list.feature} +24 -30
  19. data/features/ftp_server/list_tls.feature +21 -0
  20. data/features/{login.feature → ftp_server/login.feature} +4 -2
  21. data/features/ftp_server/mode.feature +43 -0
  22. data/features/{name_list.feature → ftp_server/name_list.feature} +25 -31
  23. data/features/ftp_server/name_list_tls.feature +22 -0
  24. data/features/{noop.feature → ftp_server/noop.feature} +3 -0
  25. data/features/{port.feature → ftp_server/port.feature} +3 -0
  26. data/features/{put.feature → ftp_server/put.feature} +19 -11
  27. data/features/ftp_server/put_tls.feature +18 -0
  28. data/features/{quit.feature → ftp_server/quit.feature} +3 -0
  29. data/features/ftp_server/step_definitions/debug.rb +8 -0
  30. data/features/ftp_server/step_definitions/test_server.rb +12 -0
  31. data/features/{syntax_errors.feature → ftp_server/syntax_errors.feature} +3 -0
  32. data/features/ftp_server/type.feature +56 -0
  33. data/features/step_definitions/error.rb +10 -3
  34. data/features/step_definitions/list.rb +21 -4
  35. data/features/step_definitions/login.rb +0 -2
  36. data/features/step_definitions/server_files.rb +4 -0
  37. data/features/step_definitions/stop_server.rb +3 -0
  38. data/features/support/example_server.rb +58 -0
  39. data/features/support/test_client.rb +1 -1
  40. data/features/support/test_server.rb +106 -24
  41. data/features/support/test_server_files.rb +30 -0
  42. data/ftpd.gemspec +56 -25
  43. data/lib/ftpd.rb +22 -4
  44. data/lib/ftpd/disk_file_system.rb +137 -0
  45. data/lib/ftpd/error.rb +9 -0
  46. data/lib/ftpd/exception_translator.rb +29 -0
  47. data/lib/ftpd/exceptions.rb +13 -0
  48. data/lib/ftpd/file_system_error_translator.rb +21 -0
  49. data/lib/ftpd/ftp_server.rb +8 -645
  50. data/lib/ftpd/insecure_certificate.rb +10 -0
  51. data/lib/ftpd/server.rb +15 -12
  52. data/lib/ftpd/session.rb +569 -0
  53. data/lib/ftpd/temp_dir.rb +10 -11
  54. data/lib/ftpd/tls_server.rb +27 -15
  55. data/lib/ftpd/translate_exceptions.rb +44 -0
  56. data/rake_tasks/cucumber.rake +4 -2
  57. data/rake_tasks/default.rake +1 -0
  58. data/rake_tasks/spec.rake +3 -0
  59. data/rake_tasks/test.rake +2 -0
  60. data/sandbox/em-server.rb +37 -0
  61. data/spec/disk_file_system_spec.rb +239 -0
  62. data/spec/exception_translator_spec.rb +35 -0
  63. data/spec/spec_helper.rb +5 -0
  64. data/spec/translate_exceptions_spec.rb +40 -0
  65. metadata +143 -115
  66. data/features/concurrent_sessions.feature +0 -11
  67. data/features/file_structure.feature +0 -40
  68. data/features/mode.feature +0 -40
  69. data/features/step_definitions/server.rb +0 -7
  70. data/features/type.feature +0 -53
@@ -7,7 +7,7 @@ class TestClient
7
7
  include FileUtils
8
8
 
9
9
  def initialize(opts = {})
10
- @temp_dir = TempDir.make
10
+ @temp_dir = Ftpd::TempDir.make
11
11
  @ftp = make_ftp(opts)
12
12
  @templates = TestFileTemplates.new
13
13
  end
@@ -1,51 +1,133 @@
1
1
  require 'fileutils'
2
2
  require 'forwardable'
3
+ require 'tempfile'
4
+
5
+ require File.expand_path('test_server_files',
6
+ File.dirname(__FILE__))
7
+
8
+ class TestServer
9
+ class TestServerDriver
10
+
11
+ def initialize(temp_dir)
12
+ @temp_dir = temp_dir
13
+ end
14
+
15
+ USER = 'user'
16
+ PASSWORD = 'password'
17
+
18
+ def authenticate(user, password)
19
+ user == USER && password == PASSWORD
20
+ end
21
+
22
+ def file_system(user)
23
+ TestServerFileSystem.new(@temp_dir)
24
+ end
25
+
26
+ end
27
+ end
28
+
29
+ class TestServer
30
+ class TestServerFileSystem < Ftpd::DiskFileSystem
31
+
32
+ def accessible?(ftp_path)
33
+ return false if force_access_denied?(ftp_path)
34
+ return true if force_file_system_error?(ftp_path)
35
+ super
36
+ end
37
+
38
+ def exists?(ftp_path)
39
+ return true if force_file_system_error?(ftp_path)
40
+ super
41
+ end
42
+
43
+ def directory?(ftp_path)
44
+ return true if force_file_system_error?(ftp_path)
45
+ super
46
+ end
47
+
48
+ def delete(ftp_path)
49
+ force_file_system_error(ftp_path)
50
+ super
51
+ end
52
+
53
+ def read(ftp_path)
54
+ force_file_system_error(ftp_path)
55
+ super
56
+ end
57
+
58
+ def write(ftp_path, contents)
59
+ force_file_system_error(ftp_path)
60
+ super
61
+ end
62
+
63
+ private
64
+
65
+ def force_file_system_error(ftp_path)
66
+ if force_file_system_error?(ftp_path)
67
+ raise Ftpd::FileSystemError, 'Unable to do it'
68
+ end
69
+ end
70
+
71
+ def force_access_denied?(ftp_path)
72
+ ftp_path =~ /forbidden/
73
+ end
74
+
75
+ def force_file_system_error?(ftp_path)
76
+ ftp_path =~ /unable/
77
+ end
78
+
79
+ end
80
+ end
3
81
 
4
82
  class TestServer
5
83
 
6
- extend Forwardable
7
84
  include FileUtils
85
+ include TestServerFiles
86
+ include Ftpd::InsecureCertificate
8
87
 
9
- def initialize
10
- @temp_dir = TempDir.make
11
- @server = Ftpd::FtpServer.new(@temp_dir)
88
+ def initialize(opts = {})
89
+ tls = opts[:tls] || :off
90
+ @temp_dir = Ftpd::TempDir.make
91
+ @debug_file = Tempfile.new('ftp-server-debug-output')
92
+ @debug_file.close
93
+ driver = TestServerDriver.new(@temp_dir)
94
+ @server = Ftpd::FtpServer.new(driver)
95
+ @server.tls = tls
96
+ @server.certfile_path = insecure_certfile_path
97
+ @server.debug_path = @debug_file.path
98
+ @server.debug = opts[:debug]
12
99
  @templates = TestFileTemplates.new
100
+ @server.start
101
+ end
102
+
103
+ def wrote_debug_output?
104
+ File.size(@debug_file.path) > 0
13
105
  end
14
106
 
15
- def close
16
- @server.close
107
+ def stop
108
+ @server.stop
17
109
  end
18
110
 
19
111
  def host
20
112
  'localhost'
21
113
  end
22
114
 
23
- def add_file(path)
24
- full_path = temp_path(path)
25
- mkdir_p File.dirname(full_path)
26
- File.open(full_path, 'wb') do |file|
27
- file.puts @templates[File.basename(full_path)]
28
- end
115
+ def user
116
+ TestServerDriver::USER
29
117
  end
30
118
 
31
- def has_file?(path)
32
- full_path = temp_path(path)
33
- File.exists?(full_path)
119
+ def password
120
+ TestServerDriver::PASSWORD
34
121
  end
35
122
 
36
- def file_contents(path)
37
- full_path = temp_path(path)
38
- File.open(full_path, 'rb', &:read)
123
+ def port
124
+ @server.bound_port
39
125
  end
40
126
 
41
- def_delegator :@server, :password
42
- def_delegator :@server, :port
43
- def_delegator :@server, :user
44
-
45
127
  private
46
128
 
47
- def temp_path(path)
48
- File.expand_path(path, @temp_dir)
129
+ def temp_dir
130
+ @temp_dir
49
131
  end
50
132
 
51
133
  end
@@ -0,0 +1,30 @@
1
+ module TestServerFiles
2
+
3
+ def add_file(path)
4
+ full_path = temp_path(path)
5
+ mkdir_p File.dirname(full_path)
6
+ File.open(full_path, 'wb') do |file|
7
+ file.puts @templates[File.basename(full_path)]
8
+ end
9
+ end
10
+
11
+ def add_directory(path)
12
+ full_path = temp_path(path)
13
+ mkdir_p full_path
14
+ end
15
+
16
+ def has_file?(path)
17
+ full_path = temp_path(path)
18
+ File.exists?(full_path)
19
+ end
20
+
21
+ def file_contents(path)
22
+ full_path = temp_path(path)
23
+ File.open(full_path, 'rb', &:read)
24
+ end
25
+
26
+ def temp_path(path)
27
+ File.expand_path(path, temp_dir)
28
+ end
29
+
30
+ end
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "ftpd"
8
- s.version = "0.0.1.pre"
8
+ s.version = "0.1.0"
9
9
 
10
- s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
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-11"
12
+ s.date = "2013-02-22"
13
13
  s.description = "ftpd is a pure Ruby FTP server library. It supports implicit and explicit TLS, suitlble for use by a program such as a test fixture or FTP daemon."
14
14
  s.email = "wconrad@yagni.com"
15
15
  s.extra_rdoc_files = [
@@ -24,20 +24,32 @@ Gem::Specification.new do |s|
24
24
  "Rakefile",
25
25
  "VERSION",
26
26
  "examples/example.rb",
27
- "features/command_errors.feature",
28
- "features/concurrent_sessions.feature",
29
- "features/delete.feature",
30
- "features/directory_navigation.feature",
31
- "features/file_structure.feature",
32
- "features/get.feature",
33
- "features/list.feature",
34
- "features/login.feature",
35
- "features/mode.feature",
36
- "features/name_list.feature",
37
- "features/noop.feature",
38
- "features/port.feature",
39
- "features/put.feature",
40
- "features/quit.feature",
27
+ "examples/hello_world.rb",
28
+ "features/example/example.feature",
29
+ "features/example/step_definitions/example_server.rb",
30
+ "features/ftp_server/command_errors.feature",
31
+ "features/ftp_server/concurrent_sessions.feature",
32
+ "features/ftp_server/debug.feature",
33
+ "features/ftp_server/delete.feature",
34
+ "features/ftp_server/directory_navigation.feature",
35
+ "features/ftp_server/file_structure.feature",
36
+ "features/ftp_server/get.feature",
37
+ "features/ftp_server/get_tls.feature",
38
+ "features/ftp_server/list.feature",
39
+ "features/ftp_server/list_tls.feature",
40
+ "features/ftp_server/login.feature",
41
+ "features/ftp_server/mode.feature",
42
+ "features/ftp_server/name_list.feature",
43
+ "features/ftp_server/name_list_tls.feature",
44
+ "features/ftp_server/noop.feature",
45
+ "features/ftp_server/port.feature",
46
+ "features/ftp_server/put.feature",
47
+ "features/ftp_server/put_tls.feature",
48
+ "features/ftp_server/quit.feature",
49
+ "features/ftp_server/step_definitions/debug.rb",
50
+ "features/ftp_server/step_definitions/test_server.rb",
51
+ "features/ftp_server/syntax_errors.feature",
52
+ "features/ftp_server/type.feature",
41
53
  "features/step_definitions/client.rb",
42
54
  "features/step_definitions/client_and_server_files.rb",
43
55
  "features/step_definitions/client_files.rb",
@@ -58,56 +70,75 @@ Gem::Specification.new do |s|
58
70
  "features/step_definitions/port.rb",
59
71
  "features/step_definitions/put.rb",
60
72
  "features/step_definitions/quit.rb",
61
- "features/step_definitions/server.rb",
62
73
  "features/step_definitions/server_files.rb",
74
+ "features/step_definitions/stop_server.rb",
63
75
  "features/step_definitions/type.rb",
64
76
  "features/support/env.rb",
77
+ "features/support/example_server.rb",
65
78
  "features/support/file_templates/ascii_unix",
66
79
  "features/support/file_templates/ascii_windows",
67
80
  "features/support/file_templates/binary",
68
81
  "features/support/test_client.rb",
69
82
  "features/support/test_file_templates.rb",
70
83
  "features/support/test_server.rb",
71
- "features/syntax_errors.feature",
72
- "features/type.feature",
84
+ "features/support/test_server_files.rb",
73
85
  "ftpd.gemspec",
74
86
  "insecure-test-cert.pem",
75
87
  "lib/ftpd.rb",
88
+ "lib/ftpd/disk_file_system.rb",
89
+ "lib/ftpd/error.rb",
90
+ "lib/ftpd/exception_translator.rb",
91
+ "lib/ftpd/exceptions.rb",
92
+ "lib/ftpd/file_system_error_translator.rb",
76
93
  "lib/ftpd/ftp_server.rb",
94
+ "lib/ftpd/insecure_certificate.rb",
77
95
  "lib/ftpd/server.rb",
96
+ "lib/ftpd/session.rb",
78
97
  "lib/ftpd/temp_dir.rb",
79
98
  "lib/ftpd/tls_server.rb",
99
+ "lib/ftpd/translate_exceptions.rb",
80
100
  "rake_tasks/cucumber.rake",
81
- "rake_tasks/jeweler.rake"
101
+ "rake_tasks/default.rake",
102
+ "rake_tasks/jeweler.rake",
103
+ "rake_tasks/spec.rake",
104
+ "rake_tasks/test.rake",
105
+ "sandbox/em-server.rb",
106
+ "spec/disk_file_system_spec.rb",
107
+ "spec/exception_translator_spec.rb",
108
+ "spec/spec_helper.rb",
109
+ "spec/translate_exceptions_spec.rb"
82
110
  ]
83
111
  s.homepage = "http://github.com/wconrad/ftpd"
84
112
  s.licenses = ["MIT"]
85
113
  s.require_paths = ["lib"]
86
- s.rubygems_version = "1.8.17"
114
+ s.rubygems_version = "1.8.24"
87
115
  s.summary = "Pure Ruby FTP server library"
88
116
 
89
117
  if s.respond_to? :specification_version then
90
118
  s.specification_version = 3
91
119
 
92
120
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
121
+ s.add_runtime_dependency(%q<memoizer>, ["~> 1.0.1"])
93
122
  s.add_development_dependency(%q<cucumber>, ["~> 1.2.1"])
94
123
  s.add_development_dependency(%q<double-bag-ftps>, ["~> 0.1.0"])
95
124
  s.add_development_dependency(%q<jeweler>, ["~> 1.8.4"])
96
125
  s.add_development_dependency(%q<rake>, ["~> 10.0.3"])
97
- s.add_development_dependency(%q<rspec>, ["~> 2.0.1"])
126
+ s.add_development_dependency(%q<rspec>, ["~> 2.12.0"])
98
127
  else
128
+ s.add_dependency(%q<memoizer>, ["~> 1.0.1"])
99
129
  s.add_dependency(%q<cucumber>, ["~> 1.2.1"])
100
130
  s.add_dependency(%q<double-bag-ftps>, ["~> 0.1.0"])
101
131
  s.add_dependency(%q<jeweler>, ["~> 1.8.4"])
102
132
  s.add_dependency(%q<rake>, ["~> 10.0.3"])
103
- s.add_dependency(%q<rspec>, ["~> 2.0.1"])
133
+ s.add_dependency(%q<rspec>, ["~> 2.12.0"])
104
134
  end
105
135
  else
136
+ s.add_dependency(%q<memoizer>, ["~> 1.0.1"])
106
137
  s.add_dependency(%q<cucumber>, ["~> 1.2.1"])
107
138
  s.add_dependency(%q<double-bag-ftps>, ["~> 0.1.0"])
108
139
  s.add_dependency(%q<jeweler>, ["~> 1.8.4"])
109
140
  s.add_dependency(%q<rake>, ["~> 10.0.3"])
110
- s.add_dependency(%q<rspec>, ["~> 2.0.1"])
141
+ s.add_dependency(%q<rspec>, ["~> 2.12.0"])
111
142
  end
112
143
  end
113
144
 
@@ -1,4 +1,22 @@
1
- require 'ftpd/ftp_server'
2
- require 'ftpd/server'
3
- require 'ftpd/temp_dir'
4
- require 'ftpd/tls_server'
1
+ require 'fileutils'
2
+ require 'memoizer'
3
+ require 'openssl'
4
+ require 'pathname'
5
+ require 'socket'
6
+ require 'tmpdir'
7
+
8
+ module Ftpd
9
+ autoload :DiskFileSystem, 'ftpd/disk_file_system'
10
+ autoload :Error, 'ftpd/error'
11
+ autoload :ExceptionTranslator, 'ftpd/exception_translator'
12
+ autoload :FileSystemErrorTranslator, 'ftpd/file_system_error_translator'
13
+ autoload :FtpServer, 'ftpd/ftp_server'
14
+ autoload :InsecureCertificate, 'ftpd/insecure_certificate'
15
+ autoload :Server, 'ftpd/server'
16
+ autoload :Session, 'ftpd/session'
17
+ autoload :TempDir, 'ftpd/temp_dir'
18
+ autoload :TlsServer, 'ftpd/tls_server'
19
+ autoload :TranslateExceptions, 'ftpd/translate_exceptions'
20
+ end
21
+
22
+ require 'ftpd/exceptions'
@@ -0,0 +1,137 @@
1
+ module Ftpd
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
+ class DiskFileSystem
12
+
13
+ include TranslateExceptions
14
+
15
+ # Make a new instance to serve a directory. data_dir should be
16
+ # fully qualified.
17
+
18
+ def initialize(data_dir)
19
+ @data_dir = data_dir
20
+ translate_exception SystemCallError
21
+ end
22
+
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.
26
+
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
33
+
34
+ # Return true if the file or directory path exists.
35
+
36
+ def exists?(ftp_path)
37
+ File.exists?(expand_ftp_path(ftp_path))
38
+ end
39
+
40
+ # Return true if the path exists and is a directory.
41
+
42
+ def directory?(ftp_path)
43
+ File.directory?(expand_ftp_path(ftp_path))
44
+ end
45
+
46
+ # Remove a file. Can raise FileSystemError.
47
+
48
+ def delete(ftp_path)
49
+ FileUtils.rm expand_ftp_path(ftp_path)
50
+ end
51
+ translate_exceptions :delete
52
+
53
+ # Read a file into memory. Can raise FileSystemError.
54
+
55
+ def read(ftp_path)
56
+ File.open(expand_ftp_path(ftp_path), 'rb', &:read)
57
+ end
58
+ translate_exceptions :read
59
+
60
+ # Write a file to disk. Can raise FileSystemError.
61
+
62
+ def write(ftp_path, contents)
63
+ File.open(expand_ftp_path(ftp_path), 'wb') do |file|
64
+ file.write contents
65
+ end
66
+ 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')
99
+ end
100
+
101
+ # Get a file list, short form. Can raise FileSystemError.
102
+ #
103
+ # This returns one filename per line, and nothing else
104
+
105
+ def list_short(ftp_path)
106
+ ls(ftp_path, '-1')
107
+ end
108
+
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 = ''
127
+ end
128
+ list = "" if $? != 0
129
+ list = list.gsub(/^total \d+\n/, '')
130
+ end
131
+
132
+ def expand_ftp_path(ftp_path)
133
+ File.expand_path(File.join(@data_dir, ftp_path))
134
+ end
135
+
136
+ end
137
+ end