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
@@ -1,16 +1,15 @@
1
- require 'fileutils'
2
- require 'tmpdir'
1
+ module Ftpd
2
+ module TempDir
3
3
 
4
- module TempDir
5
-
6
- def make
7
- Dir.mktmpdir.tap do |path|
8
- at_exit do
9
- FileUtils.rm_rf path
10
- Dir.rmdir path if File.exists?(path)
4
+ def make
5
+ Dir.mktmpdir.tap do |path|
6
+ at_exit do
7
+ FileUtils.rm_rf path
8
+ Dir.rmdir path if File.exists?(path)
9
+ end
11
10
  end
12
11
  end
13
- end
14
- module_function :make
12
+ module_function :make
15
13
 
14
+ end
16
15
  end
@@ -1,42 +1,48 @@
1
- require 'openssl'
2
- require 'ftpd/server'
3
-
4
1
  module Ftpd
5
2
  class TlsServer < Server
6
3
 
4
+ attr_accessor :tls
5
+ attr_accessor :certfile_path
6
+
7
7
  def initialize
8
- @ssl_context = make_ssl_context
9
8
  super
9
+ @tls = :off
10
+ if tls_enabled?
11
+ unless @certfile_path
12
+ raise ArgumentError, ":certfile required if tls enabled"
13
+ end
14
+ end
10
15
  end
11
16
 
12
17
  private
13
18
 
14
19
  def make_server_socket
15
- ssl_server_socket = OpenSSL::SSL::SSLServer.new(super, @ssl_context);
16
- ssl_server_socket.start_immediately = false
17
- ssl_server_socket
20
+ socket = super
21
+ if tls_enabled?
22
+ socket = OpenSSL::SSL::SSLServer.new(socket, ssl_context);
23
+ socket.start_immediately = false
24
+ end
25
+ socket
18
26
  end
19
27
 
20
28
  def accept
21
29
  socket = @server_socket.accept
22
- add_tls_methods_to_socket(socket)
30
+ if tls_enabled?
31
+ add_tls_methods_to_socket(socket)
32
+ end
23
33
  socket
24
34
  end
25
35
 
26
- def make_ssl_context
36
+ def ssl_context
27
37
  context = OpenSSL::SSL::SSLContext.new
28
- File.open(certfile_path) do |certfile|
38
+ File.open(@certfile_path) do |certfile|
29
39
  context.cert = OpenSSL::X509::Certificate.new(certfile)
30
40
  certfile.rewind
31
41
  context.key = OpenSSL::PKey::RSA.new(certfile)
32
42
  end
33
43
  context
34
44
  end
35
-
36
- def certfile_path
37
- File.expand_path('../../insecure-test-cert.pem',
38
- File.dirname(__FILE__))
39
- end
45
+ memoize :ssl_context
40
46
 
41
47
  def add_tls_methods_to_socket(socket)
42
48
  context = @ssl_context
@@ -53,5 +59,11 @@ module Ftpd
53
59
  end
54
60
  end
55
61
 
62
+ private
63
+
64
+ def tls_enabled?
65
+ @tls != :off
66
+ end
67
+
56
68
  end
57
69
  end
@@ -0,0 +1,44 @@
1
+ module Ftpd
2
+
3
+ # This module provides an easy interface to ExceptionTranslator.
4
+
5
+ module TranslateExceptions
6
+
7
+ include Memoizer
8
+
9
+ def self.included(includer)
10
+ includer.extend ClassMethods
11
+ end
12
+
13
+ module ClassMethods
14
+
15
+ # Cause the named method to translate exceptions.
16
+
17
+ def translate_exceptions(method_name)
18
+ original_method = instance_method(method_name)
19
+ define_method method_name do |*args|
20
+ exception_translator.translate_exceptions do
21
+ original_method.bind(self).call *args
22
+ end
23
+ end
24
+ end
25
+
26
+ end
27
+
28
+ # Add exception class e to the list of exceptions to be
29
+ # translated.
30
+
31
+ def translate_exception(e)
32
+ exception_translator.register_exception e
33
+ end
34
+
35
+ private
36
+
37
+ def exception_translator
38
+ ExceptionTranslator.new
39
+ end
40
+ memoize :exception_translator
41
+
42
+ end
43
+
44
+ end
@@ -1,7 +1,9 @@
1
1
  require 'cucumber/rake/task'
2
2
 
3
- Cucumber::Rake::Task.new(:features) do |t|
3
+ Cucumber::Rake::Task.new 'test:features' do |t|
4
4
  t.fork = true
5
+ t.cucumber_opts = '--format progress'
5
6
  end
6
7
 
7
- task :cucumber => [:features]
8
+ task 'test:cucumber' => ['test:features']
9
+ task 'cucumber' => ['test:features']
@@ -0,0 +1 @@
1
+ task :default => [:test]
@@ -0,0 +1,3 @@
1
+ require 'rspec/core/rake_task'
2
+ RSpec::Core::RakeTask.new 'test:spec'
3
+ task :spec => ['test:spec']
@@ -0,0 +1,2 @@
1
+ desc 'Run all tests'
2
+ task :test => ['test:spec', 'test:cucumber']
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'eventmachine'
4
+
5
+ module EchoServer
6
+
7
+ def post_init
8
+ p 'post_init'
9
+ end
10
+
11
+ def connection_completed
12
+ p 'connection_completed'
13
+ end
14
+
15
+ def receive_data data
16
+ send_data ">>>you sent: #{data}"
17
+ close_connection if data =~ /quit/i
18
+ end
19
+
20
+ def unbind
21
+ p 'unbind'
22
+ end
23
+ end
24
+
25
+ def get_port_for_fd(fd)
26
+ sockname = EM.get_sockname(fd)
27
+ port, host = Socket.unpack_sockaddr_in(sockname)
28
+ port
29
+ end
30
+
31
+ # Note that this will block current thread.
32
+ EventMachine.run {
33
+ fd = EventMachine.start_server "127.0.0.1", 0, EchoServer do |e|
34
+ p e
35
+ end
36
+ p get_port_for_fd(fd)
37
+ }
@@ -0,0 +1,239 @@
1
+ require File.expand_path('spec_helper', File.dirname(__FILE__))
2
+
3
+ module Ftpd
4
+ describe DiskFileSystem do
5
+
6
+ let(:data_dir) {Ftpd::TempDir.make}
7
+ let(:disk_file_system) {DiskFileSystem.new(data_dir)}
8
+ let(:missing_file_error) do
9
+ [Ftpd::FileSystemError, /No such file or directory/]
10
+ end
11
+ let(:is_a_directory_error) do
12
+ [Ftpd::FileSystemError, /Is a directory/]
13
+ end
14
+ let(:missing_path) {'missing_path'}
15
+
16
+ def data_path(path)
17
+ File.join(data_dir, path)
18
+ end
19
+
20
+ def make_directory(path)
21
+ Dir.mkdir data_path(path)
22
+ end
23
+
24
+ def write_file(path)
25
+ File.open(data_path(path), 'wb') do |file|
26
+ file.write canned_contents(path)
27
+ end
28
+ end
29
+
30
+ def read_file(path)
31
+ File.open(data_path(path), 'rb', &:read)
32
+ end
33
+
34
+ def exists?(path)
35
+ File.exists?(data_path(path))
36
+ end
37
+
38
+ def canned_contents(path)
39
+ "Contents of #{path}"
40
+ end
41
+
42
+ before(:each) do
43
+ write_file 'file'
44
+ make_directory 'dir'
45
+ write_file 'dir/file_in_dir'
46
+ make_directory 'unwritable_dir'
47
+ write_file 'unwritable_dir/file'
48
+ end
49
+
50
+ describe '#accessible?' do
51
+
52
+ context '(within tree)' do
53
+ specify do
54
+ disk_file_system.accessible?('file').should be_true
55
+ end
56
+ end
57
+
58
+ context '(outside tree)' do
59
+ specify do
60
+ disk_file_system.accessible?('../outside').should be_false
61
+ end
62
+ end
63
+
64
+ end
65
+
66
+ describe '#exists?' do
67
+
68
+ context '(exists)' do
69
+ specify do
70
+ disk_file_system.exists?('file').should be_true
71
+ end
72
+ end
73
+
74
+ context '(does not exist)' do
75
+ specify do
76
+ disk_file_system.exists?('missing').should be_false
77
+ end
78
+ end
79
+
80
+ end
81
+
82
+ describe '#directory?' do
83
+
84
+ context '(directory)' do
85
+ specify do
86
+ disk_file_system.directory?('file').should be_false
87
+ end
88
+ end
89
+
90
+ context '(file)' do
91
+ specify do
92
+ disk_file_system.directory?('dir').should be_true
93
+ end
94
+ end
95
+
96
+ end
97
+
98
+ describe '#delete' do
99
+
100
+ context '(normal)' do
101
+ specify do
102
+ disk_file_system.delete('file')
103
+ exists?('file').should be_false
104
+ end
105
+ end
106
+
107
+ context '(file system error)' do
108
+ specify do
109
+ expect {
110
+ disk_file_system.delete(missing_path)
111
+ }.to raise_error *missing_file_error
112
+ end
113
+ end
114
+
115
+ end
116
+
117
+ describe '#read' do
118
+
119
+ context '(normal)' do
120
+ let(:path) {'file'}
121
+ specify do
122
+ disk_file_system.read(path).should == canned_contents(path)
123
+ end
124
+ end
125
+
126
+ context '(file system error)' do
127
+ specify do
128
+ expect {
129
+ disk_file_system.read(missing_path)
130
+ }.to raise_error *missing_file_error
131
+ end
132
+ end
133
+
134
+ end
135
+
136
+ describe '#write' do
137
+
138
+ let(:contents) {'file contents'}
139
+
140
+ context '(normal)' do
141
+ let(:path) {'file_path'}
142
+ specify do
143
+ disk_file_system.write(path, contents)
144
+ read_file(path).should == contents
145
+ end
146
+ end
147
+
148
+ context '(file system error)' do
149
+ specify do
150
+ expect {
151
+ disk_file_system.write('dir', contents)
152
+ }.to raise_error *is_a_directory_error
153
+ end
154
+ end
155
+
156
+ end
157
+
158
+ describe '#list_short' do
159
+
160
+ subject do
161
+ disk_file_system.list_short(path)
162
+ end
163
+
164
+ shared_examples 'returns short list of root' do
165
+ it {should =~ /^dir$/}
166
+ it {should =~ /^file$/}
167
+ it {should =~ /^unwritable_dir$/}
168
+ its('lines.to_a.size') {should == 3}
169
+ end
170
+
171
+ context '(root)' do
172
+ let(:path) {'/'}
173
+ it_behaves_like 'returns short list of root'
174
+ end
175
+
176
+ context '(empty path)' do
177
+ let(:path) {''}
178
+ it_behaves_like 'returns short list of root'
179
+ end
180
+
181
+ context '(specific file)' do
182
+ let(:path) {'/file'}
183
+ it {should == "file\n"}
184
+ end
185
+
186
+ context '(specific directory)' do
187
+ let(:path) {'/dir'}
188
+ it {should == "file_in_dir\n"}
189
+ end
190
+
191
+ context '(missing directory)' do
192
+ let(:path) {'/missing/file'}
193
+ it {should be_empty}
194
+ end
195
+
196
+ end
197
+
198
+ describe '#list_long' do
199
+
200
+ subject do
201
+ disk_file_system.list_long(path)
202
+ end
203
+
204
+ shared_examples 'returns long list of root' do
205
+ it {should =~ /^d.*dir$/}
206
+ it {should =~ /^-.*file$/}
207
+ it {should =~ /^d.*unwritable_dir$/}
208
+ its('lines.to_a.size') {should == 3}
209
+ end
210
+
211
+ context '(root)' do
212
+ let(:path) {'/'}
213
+ it_behaves_like 'returns long list of root'
214
+ end
215
+
216
+ context '(empty path)' do
217
+ let(:path) {''}
218
+ it_behaves_like 'returns long list of root'
219
+ end
220
+
221
+ context '(specific file)' do
222
+ let(:path) {'/file'}
223
+ it {should =~ /^-.*file$/}
224
+ end
225
+
226
+ context '(specific directory)' do
227
+ let(:path) {'/dir'}
228
+ it {should =~ /^-.*file_in_dir\n/}
229
+ end
230
+
231
+ context '(missing directory)' do
232
+ let(:path) {'/missing/file'}
233
+ it {should be_empty}
234
+ end
235
+
236
+ end
237
+
238
+ end
239
+ end