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
data/Gemfile CHANGED
@@ -1,9 +1,11 @@
1
1
  source 'http://rubygems.org'
2
2
 
3
+ gem 'memoizer', '~> 1.0.1'
4
+
3
5
  group :development do
4
6
  gem 'cucumber', '~> 1.2.1'
5
7
  gem 'double-bag-ftps', '~> 0.1.0'
6
8
  gem 'jeweler', '~> 1.8.4'
7
9
  gem 'rake', '~> 10.0.3'
8
- gem 'rspec', '~> 2.0.1'
10
+ gem 'rspec', '~> 2.12.0'
9
11
  end
@@ -7,7 +7,7 @@ GEM
7
7
  diff-lcs (>= 1.1.3)
8
8
  gherkin (~> 2.11.0)
9
9
  json (>= 1.4.6)
10
- diff-lcs (1.2.0)
10
+ diff-lcs (1.1.3)
11
11
  double-bag-ftps (0.1.0)
12
12
  gherkin (2.11.6)
13
13
  json (>= 1.7.6)
@@ -17,20 +17,19 @@ GEM
17
17
  git (>= 1.2.5)
18
18
  rake
19
19
  rdoc
20
- json (1.7.6)
20
+ json (1.7.7)
21
+ memoizer (1.0.1)
21
22
  rake (10.0.3)
22
- rdoc (3.12)
23
+ rdoc (3.12.1)
23
24
  json (~> 1.4)
24
- rspec (2.0.1)
25
- rspec-core (~> 2.0.1)
26
- rspec-expectations (~> 2.0.1)
27
- rspec-mocks (~> 2.0.1)
28
- rspec-core (2.0.1)
29
- rspec-expectations (2.0.1)
30
- diff-lcs (>= 1.1.2)
31
- rspec-mocks (2.0.1)
32
- rspec-core (~> 2.0.1)
33
- rspec-expectations (~> 2.0.1)
25
+ rspec (2.12.0)
26
+ rspec-core (~> 2.12.0)
27
+ rspec-expectations (~> 2.12.0)
28
+ rspec-mocks (~> 2.12.0)
29
+ rspec-core (2.12.2)
30
+ rspec-expectations (2.12.1)
31
+ diff-lcs (~> 1.1.3)
32
+ rspec-mocks (2.12.2)
34
33
 
35
34
  PLATFORMS
36
35
  ruby
@@ -39,5 +38,6 @@ DEPENDENCIES
39
38
  cucumber (~> 1.2.1)
40
39
  double-bag-ftps (~> 0.1.0)
41
40
  jeweler (~> 1.8.4)
41
+ memoizer (~> 1.0.1)
42
42
  rake (~> 10.0.3)
43
- rspec (~> 2.0.1)
43
+ rspec (~> 2.12.0)
data/README.md CHANGED
@@ -4,37 +4,79 @@ ftpd is a pure Ruby FTP server library. It supports implicit and
4
4
  explicit TLS, suitlble for use by a program such as a test fixture or
5
5
  FTP daemon.
6
6
 
7
- ## UNFINISHED
7
+ ## HELLO WORLD
8
8
 
9
- I created ftpd to support the test framework I wrote for Databill,
10
- LLC, which has given its kind permission to donate it to the
11
- community.
9
+ This is examples/hello_world.rb, a bare minimum FTP server. It allows
10
+ any user/password, and serves files in the diretory '/tmp/ftp'. It
11
+ binds to an ephemeral port on the local interface:
12
+
13
+ require 'ftpd'
14
+ require 'tmpdir'
15
+
16
+ class Driver
17
+
18
+ def initialize(temp_dir)
19
+ @temp_dir = temp_dir
20
+ end
12
21
 
13
- I've moved the code from Databill's source tree, but it's not ready
14
- for prime time yet. It needs pluggable authentication and file system
15
- drivers, refactoring, and the removal of bits of the Databill source
16
- tree which are temporarily included.
22
+ def authenticate(user, password)
23
+ true
24
+ end
25
+
26
+ def file_system(user)
27
+ Ftpd::DiskFileSystem.new(@temp_dir)
28
+ end
29
+
30
+ end
31
+
32
+ Dir.mktmpdir do |temp_dir|
33
+ driver = Driver.new(temp_dir)
34
+ server = Ftpd::FtpServer.new(driver)
35
+ server.start
36
+ puts "Server listening on port #{server.bound_port}"
37
+ gets
38
+ end
39
+
40
+ A more full-featured example that allows TLS and takes options is in
41
+ examples/example.rb
17
42
 
18
43
  ## LIMITATIONS
19
44
 
20
- TLS is only supported in passive mode, not active. Either the FTPS
21
- client used by the test doesn't work in active mode, or this server
22
- doesn't work in FTPS active mode (or both).
45
+ TLS is only supported in passive mode, not active, but I don't know
46
+ why. Either the FTPS client used by the test doesn't work in active
47
+ mode, or this server doesn't work in FTPS active mode (or both).
48
+
49
+ The DiskFileSystem class only works in Linux. This is because it
50
+ shells out to the "ls" command. This affects the example, which uses
51
+ the DiskFileSystem.
23
52
 
24
53
  ## DEVELOPMENT
25
54
 
26
55
  ### TESTS
27
56
 
28
- To run the cucumber tests:
57
+ To run the cucumber (functional) tests:
29
58
 
30
- $ rake features
59
+ $ rake test:features
60
+
61
+ To run the rspec (unit) tests:
62
+
63
+ $ rake test:spec
64
+
65
+ To run all tests:
66
+
67
+ $ rake test
68
+
69
+ or just:
70
+
71
+ $ rake
31
72
 
32
73
  To run the stand-alone example:
33
74
 
34
75
  $ examples/example.rb
35
76
 
36
77
  The example prints its port, username and password to the console.
37
- You can connect to the stand-alone example with any FTP client.
78
+ You can connect to the stand-alone example with any FTP client. This
79
+ is useful when testing how the server responds to a given FTP client.
38
80
 
39
81
  ## REFERENCES
40
82
 
@@ -45,24 +87,30 @@ Copyright (c) 2008 James Healy)
45
87
  There are a range of RFCs that together specify the FTP protocol. In
46
88
  chronological order, the more useful ones are:
47
89
 
48
- http://tools.ietf.org/rfc/rfc959.txt
49
- http://tools.ietf.org/rfc/rfc1123.txt
50
- http://tools.ietf.org/rfc/rfc2228.txt
51
- http://tools.ietf.org/rfc/rfc2389.txt
52
- http://tools.ietf.org/rfc/rfc2428.txt
53
- http://tools.ietf.org/rfc/rfc3659.txt
54
- http://tools.ietf.org/rfc/rfc4217.txt
90
+ * <http://tools.ietf.org/rfc/rfc959.txt>
91
+ * <http://tools.ietf.org/rfc/rfc1123.txt>
92
+ * <http://tools.ietf.org/rfc/rfc2228.txt>
93
+ * <http://tools.ietf.org/rfc/rfc2389.txt>
94
+ * <http://tools.ietf.org/rfc/rfc2428.txt>
95
+ * <http://tools.ietf.org/rfc/rfc3659.txt>
96
+ * <http://tools.ietf.org/rfc/rfc4217.txt>
55
97
 
56
98
  For an english summary that's somewhat more legible than the RFCs, and
57
99
  provides some commentary on what features are actually useful or
58
100
  relevant 24 years after RFC959 was published:
59
101
 
60
- http://cr.yp.to/ftp.html
102
+ * <http://cr.yp.to/ftp.html>
61
103
 
62
104
  For a history lesson, check out Appendix III of RCF959. It lists the
63
105
  preceding (obsolete) RFC documents that relate to file transfers,
64
106
  including the ye old RFC114 from 1971, "A File Transfer Protocol"
65
107
 
108
+ ## ORIGIN
109
+
110
+ I created ftpd to support the test framework I wrote for Databill,
111
+ LLC, which has given its kind permission to donate it to the
112
+ community.
113
+
66
114
  ## WHOAMI
67
115
 
68
116
  Wayne Conrad <wconrad@yagni.com>
@@ -70,4 +118,4 @@ Wayne Conrad <wconrad@yagni.com>
70
118
  ## CREDITS
71
119
 
72
120
  Thanks to Databill, LLC, which supported the creation of this library,
73
- and granted permission to donate it ot the community.
121
+ and granted permission to donate it to the community.
data/Rakefile CHANGED
@@ -2,7 +2,13 @@
2
2
  require 'rubygems'
3
3
  require 'bundler'
4
4
 
5
- $:.unshift(File.dirname(__FILE__) + '/lib')
6
- Dir['rake_tasks/**/*.rake'].each { |path| load path }
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts 'Run `bundle install` to install missing gems'
10
+ exit e.status_code
11
+ end
7
12
 
8
- task :default => [:cucumber]
13
+ $:.unshift(File.dirname(__FILE__) + '/lib')
14
+ Dir['rake_tasks/**/*.rake'].sort.each { |path| load path }
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.1.pre
1
+ 0.1.0
@@ -1,77 +1,156 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  unless $:.include?(File.dirname(__FILE__) + '/../lib')
4
- $:.unshift(File.dirname(__FILE__) + '/../lib')
4
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
5
5
  end
6
6
 
7
7
  require 'ftpd'
8
+ require 'optparse'
8
9
 
9
- class Example
10
+ module Example
11
+ class Arguments
10
12
 
11
- def initialize
12
- @data_dir = TempDir.make
13
- create_files
14
- @server = Ftpd::FtpServer.new(@data_dir)
15
- set_credentials
16
- display_connection_info
17
- create_connection_script
18
- end
13
+ attr_reader :interface
14
+ attr_reader :port
15
+ attr_reader :tls
19
16
 
20
- def run
21
- wait_until_stopped
22
- end
17
+ def initialize(argv)
18
+ @interface = 'localhost'
19
+ @tls = :explicit
20
+ @port = 0
21
+ op = option_parser
22
+ op.parse!(argv)
23
+ rescue OptionParser::ParseError => e
24
+ $stderr.puts e
25
+ exit(1)
26
+ end
23
27
 
24
- private
25
-
26
- HOST = 'localhost'
28
+ private
29
+
30
+ def option_parser
31
+ op = OptionParser.new do |op|
32
+ op.on('-p', '--port N', Integer, 'Bind to a specific port') do |t|
33
+ @port = t
34
+ end
35
+ op.on('-i', '--interface IP', 'Bind to a specific interface') do |t|
36
+ @interface = t
37
+ end
38
+ op.on('--tls [TYPE]', [:off, :explicit, :implicit],
39
+ 'Select TLS support (off, explicit, implicit)') do |t|
40
+ @tls = t
41
+ end
42
+ end
43
+ end
27
44
 
28
- def create_files
29
- create_file 'README',
30
- "Temporary directory created by ftpd sample program\n"
31
45
  end
46
+ end
47
+
48
+ module Example
49
+
50
+ # The FTP server requires and instance of a _driver_ which can
51
+ # authenticate users and create a file system drivers for a given
52
+ # user. You can use this as a template for creating your own
53
+ # driver. There's no need to use this class (or any other) as a
54
+ # base class.
55
+
56
+ class Driver
32
57
 
33
- def create_file(path, contents)
34
- full_path = File.expand_path(path, @data_dir)
35
- FileUtils.mkdir_p File.dirname(full_path)
36
- File.open(full_path, 'w') do |file|
37
- file.write contents
58
+ attr_reader :expected_user
59
+ attr_reader :expected_password
60
+
61
+ def initialize(data_dir)
62
+ @data_dir = data_dir
63
+ @expected_user = ENV['LOGNAME']
64
+ @expected_password = ''
38
65
  end
39
- end
40
66
 
41
- def set_credentials
42
- @server.user = ENV['LOGNAME']
43
- @server.password = ''
44
- end
67
+ # Return true if the user/password should be allowed to log in.
45
68
 
46
- def display_connection_info
47
- puts "Host: #{HOST}"
48
- puts "Port: #{@server.port}"
49
- puts "User: #{@server.user}"
50
- puts "Pass: #{@server.password}"
51
- puts "Directory: #{@data_dir}"
52
- puts "URI: ftp://#{HOST}:#{@server.port}"
53
- end
69
+ def authenticate(user, password)
70
+ user == @expected_user && password == @expected_password
71
+ end
54
72
 
55
- def create_connection_script
56
- command_path = '/tmp/connect-to-example-ftp-server.sh'
57
- File.open(command_path, 'w') do |file|
58
- file.puts "#!/bin/bash"
59
- file.puts "ftp $FTP_ARGS #{HOST} #{@server.port}"
73
+ # Return the file system to use for a user.
74
+
75
+ def file_system(user)
76
+ Ftpd::DiskFileSystem.new(@data_dir)
60
77
  end
61
- system("chmod +x #{command_path}")
62
- puts "Connection script written to #{command_path}"
78
+
63
79
  end
80
+ end
81
+
82
+ module Example
83
+ class Main
64
84
 
65
- def wait_until_stopped
66
- puts "FTP server started. Press ENTER or c-C to stop it"
67
- $stdout.flush
68
- begin
69
- gets
70
- rescue Interrupt
71
- puts "Interrupt"
85
+ include Ftpd::InsecureCertificate
86
+
87
+ def initialize(argv)
88
+ @args = Arguments.new(argv)
89
+ @data_dir = Ftpd::TempDir.make
90
+ create_files
91
+ @driver = Driver.new(@data_dir)
92
+ @server = Ftpd::FtpServer.new(@driver)
93
+ @server.interface = @args.interface
94
+ @server.port = @args.port
95
+ @server.tls = @args.tls
96
+ @server.certfile_path = insecure_certfile_path
97
+ @server.start
98
+ display_connection_info
99
+ create_connection_script
72
100
  end
73
- end
74
101
 
102
+ def run
103
+ wait_until_stopped
104
+ end
105
+
106
+ private
107
+
108
+ HOST = 'localhost'
109
+
110
+ def create_files
111
+ create_file 'README',
112
+ "Temporary directory created by ftpd sample program\n"
113
+ end
114
+
115
+ def create_file(path, contents)
116
+ full_path = File.expand_path(path, @data_dir)
117
+ FileUtils.mkdir_p File.dirname(full_path)
118
+ File.open(full_path, 'w') do |file|
119
+ file.write contents
120
+ end
121
+ end
122
+
123
+ def display_connection_info
124
+ puts "Interface: #{@server.interface}"
125
+ puts "Port: #{@server.bound_port}"
126
+ puts "User: #{@driver.expected_user}"
127
+ puts "Pass: #{@driver.expected_password}"
128
+ puts "TLS: #{@args.tls}"
129
+ puts "Directory: #{@data_dir}"
130
+ puts "URI: ftp://#{HOST}:#{@server.bound_port}"
131
+ end
132
+
133
+ def create_connection_script
134
+ command_path = '/tmp/connect-to-example-ftp-server.sh'
135
+ File.open(command_path, 'w') do |file|
136
+ file.puts "#!/bin/bash"
137
+ file.puts "ftp $FTP_ARGS #{HOST} #{@server.bound_port}"
138
+ end
139
+ system("chmod +x #{command_path}")
140
+ puts "Connection script written to #{command_path}"
141
+ end
142
+
143
+ def wait_until_stopped
144
+ puts "FTP server started. Press ENTER or c-C to stop it"
145
+ $stdout.flush
146
+ begin
147
+ gets
148
+ rescue Interrupt
149
+ puts "Interrupt"
150
+ end
151
+ end
152
+
153
+ end
75
154
  end
76
155
 
77
- Example.new.run if $0 == __FILE__
156
+ Example::Main.new(ARGV).run if $0 == __FILE__
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ unless $:.include?(File.dirname(__FILE__) + '/../lib')
4
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
5
+ end
6
+
7
+ require 'ftpd'
8
+ require 'tmpdir'
9
+
10
+ class Driver
11
+
12
+ def initialize(temp_dir)
13
+ @temp_dir = temp_dir
14
+ end
15
+
16
+ def authenticate(user, password)
17
+ true
18
+ end
19
+
20
+ def file_system(user)
21
+ Ftpd::DiskFileSystem.new(@temp_dir)
22
+ end
23
+
24
+ end
25
+
26
+ Dir.mktmpdir do |temp_dir|
27
+ driver = Driver.new(temp_dir)
28
+ server = Ftpd::FtpServer.new(driver)
29
+ server.start
30
+ puts "Server listening on port #{server.bound_port}"
31
+ gets
32
+ end