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.
- data/Gemfile +3 -1
- data/Gemfile.lock +14 -14
- data/README.md +71 -23
- data/Rakefile +9 -3
- data/VERSION +1 -1
- data/examples/example.rb +132 -53
- data/examples/hello_world.rb +32 -0
- data/features/example/example.feature +18 -0
- data/features/example/step_definitions/example_server.rb +3 -0
- data/features/{command_errors.feature → ftp_server/command_errors.feature} +3 -0
- data/features/ftp_server/concurrent_sessions.feature +14 -0
- data/features/ftp_server/debug.feature +15 -0
- data/features/{delete.feature → ftp_server/delete.feature} +23 -2
- data/features/{directory_navigation.feature → ftp_server/directory_navigation.feature} +17 -4
- data/features/ftp_server/file_structure.feature +43 -0
- data/features/{get.feature → ftp_server/get.feature} +21 -10
- data/features/ftp_server/get_tls.feature +18 -0
- data/features/{list.feature → ftp_server/list.feature} +24 -30
- data/features/ftp_server/list_tls.feature +21 -0
- data/features/{login.feature → ftp_server/login.feature} +4 -2
- data/features/ftp_server/mode.feature +43 -0
- data/features/{name_list.feature → ftp_server/name_list.feature} +25 -31
- data/features/ftp_server/name_list_tls.feature +22 -0
- data/features/{noop.feature → ftp_server/noop.feature} +3 -0
- data/features/{port.feature → ftp_server/port.feature} +3 -0
- data/features/{put.feature → ftp_server/put.feature} +19 -11
- data/features/ftp_server/put_tls.feature +18 -0
- data/features/{quit.feature → ftp_server/quit.feature} +3 -0
- data/features/ftp_server/step_definitions/debug.rb +8 -0
- data/features/ftp_server/step_definitions/test_server.rb +12 -0
- data/features/{syntax_errors.feature → ftp_server/syntax_errors.feature} +3 -0
- data/features/ftp_server/type.feature +56 -0
- data/features/step_definitions/error.rb +10 -3
- data/features/step_definitions/list.rb +21 -4
- data/features/step_definitions/login.rb +0 -2
- data/features/step_definitions/server_files.rb +4 -0
- data/features/step_definitions/stop_server.rb +3 -0
- data/features/support/example_server.rb +58 -0
- data/features/support/test_client.rb +1 -1
- data/features/support/test_server.rb +106 -24
- data/features/support/test_server_files.rb +30 -0
- data/ftpd.gemspec +56 -25
- data/lib/ftpd.rb +22 -4
- data/lib/ftpd/disk_file_system.rb +137 -0
- data/lib/ftpd/error.rb +9 -0
- data/lib/ftpd/exception_translator.rb +29 -0
- data/lib/ftpd/exceptions.rb +13 -0
- data/lib/ftpd/file_system_error_translator.rb +21 -0
- data/lib/ftpd/ftp_server.rb +8 -645
- data/lib/ftpd/insecure_certificate.rb +10 -0
- data/lib/ftpd/server.rb +15 -12
- data/lib/ftpd/session.rb +569 -0
- data/lib/ftpd/temp_dir.rb +10 -11
- data/lib/ftpd/tls_server.rb +27 -15
- data/lib/ftpd/translate_exceptions.rb +44 -0
- data/rake_tasks/cucumber.rake +4 -2
- data/rake_tasks/default.rake +1 -0
- data/rake_tasks/spec.rake +3 -0
- data/rake_tasks/test.rake +2 -0
- data/sandbox/em-server.rb +37 -0
- data/spec/disk_file_system_spec.rb +239 -0
- data/spec/exception_translator_spec.rb +35 -0
- data/spec/spec_helper.rb +5 -0
- data/spec/translate_exceptions_spec.rb +40 -0
- metadata +143 -115
- data/features/concurrent_sessions.feature +0 -11
- data/features/file_structure.feature +0 -40
- data/features/mode.feature +0 -40
- data/features/step_definitions/server.rb +0 -7
- 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
|
10
|
+
gem 'rspec', '~> 2.12.0'
|
9
11
|
end
|
data/Gemfile.lock
CHANGED
@@ -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.
|
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.
|
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
|
25
|
-
rspec-core (~> 2.0
|
26
|
-
rspec-expectations (~> 2.0
|
27
|
-
rspec-mocks (~> 2.0
|
28
|
-
rspec-core (2.
|
29
|
-
rspec-expectations (2.
|
30
|
-
diff-lcs (
|
31
|
-
rspec-mocks (2.
|
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
|
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
|
-
##
|
7
|
+
## HELLO WORLD
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
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
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
21
|
-
client used by the test doesn't work in active
|
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
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
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
|
-
|
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
|
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
|
-
|
6
|
-
|
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
|
-
|
13
|
+
$:.unshift(File.dirname(__FILE__) + '/lib')
|
14
|
+
Dir['rake_tasks/**/*.rake'].sort.each { |path| load path }
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.1.0
|
data/examples/example.rb
CHANGED
@@ -1,77 +1,156 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
unless $:.include?(File.dirname(__FILE__) + '/../lib')
|
4
|
-
|
4
|
+
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
5
5
|
end
|
6
6
|
|
7
7
|
require 'ftpd'
|
8
|
+
require 'optparse'
|
8
9
|
|
9
|
-
|
10
|
+
module Example
|
11
|
+
class Arguments
|
10
12
|
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
-
|
21
|
-
|
22
|
-
|
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
|
-
|
25
|
-
|
26
|
-
|
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
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
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
|
-
|
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
|
-
|
47
|
-
|
48
|
-
|
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
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
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
|
-
|
62
|
-
puts "Connection script written to #{command_path}"
|
78
|
+
|
63
79
|
end
|
80
|
+
end
|
81
|
+
|
82
|
+
module Example
|
83
|
+
class Main
|
64
84
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
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
|