fake_ftp 0.0.9 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.travis.yml +5 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +28 -11
- data/Gemfile.travis +5 -0
- data/Guardfile +10 -0
- data/README.markdown +51 -53
- data/fake_ftp.gemspec +0 -5
- data/lib/fake_ftp/file.rb +6 -3
- data/lib/fake_ftp/server.rb +119 -29
- data/lib/fake_ftp/version.rb +1 -1
- data/spec/models/fake_ftp/file_spec.rb +36 -21
- data/spec/models/fake_ftp/server_spec.rb +327 -194
- metadata +12 -44
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ef1324d66ffeb222bc35a3d1fe172bf649fefa9e
|
4
|
+
data.tar.gz: 285616254bf9b2f5ad441bfc41d17943a1609681
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b20a024c9e598db59fac9751517b08cc70c5325f0ba55b96352d719a0227827a18a956ef2e30e85f9c544009c4214f7285bf3d69a0139e66bdd3e0bed207c711
|
7
|
+
data.tar.gz: ecd74f559bf7d48318846671268c5b3d96468c93a3ad6fff487f310014b47e413244e55f700e8881c54565c4dd066d2d0c0573322aba70eb57d75d33f3dc1d16
|
data/.travis.yml
ADDED
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,27 +1,44 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
fake_ftp (0.0
|
4
|
+
fake_ftp (0.1.0)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: http://rubygems.org/
|
8
8
|
specs:
|
9
|
-
|
9
|
+
coderay (1.0.9)
|
10
|
+
diff-lcs (1.2.4)
|
11
|
+
ffi (1.0.11)
|
12
|
+
guard (1.0.1)
|
13
|
+
ffi (>= 0.5.0)
|
14
|
+
thor (~> 0.14.6)
|
15
|
+
guard-rspec (0.6.0)
|
16
|
+
guard (>= 0.10.0)
|
17
|
+
method_source (0.8.2)
|
18
|
+
pry (0.9.12.2)
|
19
|
+
coderay (~> 1.0.5)
|
20
|
+
method_source (~> 0.8)
|
21
|
+
slop (~> 3.4)
|
22
|
+
pry-nav (0.2.3)
|
23
|
+
pry (~> 0.9.10)
|
10
24
|
rake (0.9.2.2)
|
11
|
-
rspec (2.
|
12
|
-
rspec-core (~> 2.
|
13
|
-
rspec-expectations (~> 2.
|
14
|
-
rspec-mocks (~> 2.
|
15
|
-
rspec-core (2.
|
16
|
-
rspec-expectations (2.
|
17
|
-
diff-lcs (
|
18
|
-
rspec-mocks (2.
|
25
|
+
rspec (2.14.1)
|
26
|
+
rspec-core (~> 2.14.0)
|
27
|
+
rspec-expectations (~> 2.14.0)
|
28
|
+
rspec-mocks (~> 2.14.0)
|
29
|
+
rspec-core (2.14.5)
|
30
|
+
rspec-expectations (2.14.3)
|
31
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
32
|
+
rspec-mocks (2.14.3)
|
33
|
+
slop (3.4.6)
|
34
|
+
thor (0.14.6)
|
19
35
|
|
20
36
|
PLATFORMS
|
21
37
|
ruby
|
22
38
|
|
23
39
|
DEPENDENCIES
|
24
|
-
bundler (>= 1.0.10)
|
25
40
|
fake_ftp!
|
41
|
+
guard-rspec
|
42
|
+
pry-nav
|
26
43
|
rake (>= 0.8.7)
|
27
44
|
rspec (> 2)
|
data/Gemfile.travis
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard 'rspec', :version => 2 do
|
5
|
+
watch(%r{^spec/.+_spec\.rb$})
|
6
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
7
|
+
watch('spec/spec_helper.rb') { "spec" }
|
8
|
+
watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
|
9
|
+
end
|
10
|
+
|
data/README.markdown
CHANGED
@@ -1,85 +1,83 @@
|
|
1
1
|
FakeFtp
|
2
2
|
=======
|
3
3
|
|
4
|
+
[![Build status](https://secure.travis-ci.org/livinginthepast/fake_ftp.png)](http://travis-ci.org/livinginthepast/fake_ftp)
|
5
|
+
|
4
6
|
This is a gem that allows you to test FTP implementations in ruby. It is a minimal single-client FTP server
|
5
7
|
that can be bound to any arbitrary port on localhost.
|
6
8
|
|
7
|
-
Why?
|
8
|
-
----
|
9
|
+
## Why?
|
9
10
|
|
10
11
|
We want to ensure that our code works, in a way that is agnostic to the implementation used (unlike with stubs or mocks).
|
11
12
|
|
12
|
-
How
|
13
|
-
---
|
13
|
+
## How
|
14
14
|
|
15
15
|
FakeFtp is a simple FTP server that fakes out enough of the protocol to get us by, allowing us to test that files get to
|
16
16
|
their intended destination rather than testing how our code does so.
|
17
17
|
|
18
|
-
Usage
|
19
|
-
-----
|
18
|
+
## Usage
|
20
19
|
|
21
20
|
To test passive upload:
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
21
|
+
``` ruby
|
22
|
+
require 'fake_ftp'
|
23
|
+
require 'net/ftp'
|
24
|
+
|
25
|
+
server = FakeFtp::Server.new(21212, 21213)
|
26
|
+
## 21212 is the control port, which is used by FTP for the primary connection
|
27
|
+
## 21213 is the data port, used in FTP passive mode to send file contents
|
28
|
+
server.start
|
29
|
+
|
30
|
+
ftp = Net::FTP.new
|
31
|
+
ftp.connect('127.0.0.1', 21212)
|
32
|
+
ftp.login('user', 'password')
|
33
|
+
ftp.passive = true
|
34
|
+
ftp.put('some_file.txt')
|
35
|
+
ftp.close
|
36
|
+
|
37
|
+
server.files.should include('some_file.txt')
|
38
|
+
server.file('some_file.txt').bytes.should == 25
|
39
|
+
server.file('some_file.txt').should be_passive
|
40
|
+
server.file('some_file.txt').should_not be_active
|
41
|
+
|
42
|
+
server.stop
|
43
|
+
```
|
44
44
|
|
45
45
|
To test active upload:
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
46
|
+
``` ruby
|
47
|
+
server = FakeFtp::Server.new(21212)
|
48
|
+
## 21212 is the control port, which is used by FTP for the primary connection
|
49
|
+
## 21213 is the data port, used in FTP passive mode to send file contents
|
50
|
+
server.start
|
51
|
+
|
52
|
+
ftp = Net::FTP.new
|
53
|
+
ftp.connect('127.0.0.1', 21212)
|
54
|
+
ftp.login('user', 'password')
|
55
|
+
ftp.passive = false
|
56
|
+
ftp.put('some_file.txt')
|
57
|
+
ftp.close
|
58
|
+
|
59
|
+
server.files.should include('some_file.txt')
|
60
|
+
server.file('some_file.txt').bytes.should == 25
|
61
|
+
server.file('some_file.txt').should be_active
|
62
|
+
server.file('some_file.txt').should_not be_passive
|
63
|
+
|
64
|
+
server.stop
|
65
|
+
```
|
65
66
|
|
66
67
|
Note that many FTP clients default to active, unless specified otherwise.
|
67
68
|
|
68
|
-
References
|
69
|
-
----------
|
69
|
+
## References
|
70
70
|
|
71
71
|
* http://rubyforge.org/projects/ftpd/ - a simple ftp daemon written by Chris Wanstrath
|
72
72
|
* http://ruby-doc.org/stdlib/libdoc/gserver/rdoc/index.html - a generic server in the Ruby standard library, by John W Small
|
73
73
|
|
74
|
-
Contributors
|
75
|
-
------------
|
74
|
+
## Contributors
|
76
75
|
|
77
76
|
* Eric Saxby
|
78
77
|
* Colin Shield
|
79
78
|
* liehann (https://github.com/liehann)
|
80
79
|
|
81
|
-
License
|
82
|
-
-------
|
80
|
+
## License
|
83
81
|
|
84
82
|
The MIT License
|
85
83
|
|
data/fake_ftp.gemspec
CHANGED
@@ -13,12 +13,7 @@ Gem::Specification.new do |s|
|
|
13
13
|
s.description = %q{Testing FTP? Use this!}
|
14
14
|
|
15
15
|
s.required_rubygems_version = ">= 1.3.6"
|
16
|
-
s.rubyforge_project = "fake_ftp"
|
17
16
|
|
18
|
-
s.add_development_dependency "bundler", '>=1.0.10'
|
19
|
-
s.add_development_dependency "rspec", '>2'
|
20
|
-
s.add_development_dependency "rake", '>=0.8.7'
|
21
|
-
|
22
17
|
s.files = `git ls-files`.split("\n")
|
23
18
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
24
19
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
data/lib/fake_ftp/file.rb
CHANGED
@@ -1,18 +1,21 @@
|
|
1
1
|
module FakeFtp
|
2
2
|
class File
|
3
|
-
attr_accessor :bytes, :name
|
3
|
+
attr_accessor :bytes, :name, :last_modified_time
|
4
4
|
attr_writer :type
|
5
5
|
attr_accessor :data
|
6
6
|
attr_reader :created
|
7
7
|
|
8
|
-
def initialize(name = nil, data = nil, type = nil)
|
8
|
+
def initialize(name = nil, data = nil, type = nil, last_modified_time = Time.now)
|
9
9
|
@created = Time.now
|
10
10
|
@name = name
|
11
11
|
@data = data
|
12
|
+
# FIXME this is far too ambiguous. args should not mean different
|
13
|
+
# things in different contexts.
|
12
14
|
data_is_bytes = (data.nil? || Integer === data)
|
13
|
-
@bytes = data_is_bytes ? data : data.length
|
15
|
+
@bytes = data_is_bytes ? data : data.to_s.length
|
14
16
|
@data = data_is_bytes ? nil : data
|
15
17
|
@type = type
|
18
|
+
@last_modified_time = last_modified_time.utc
|
16
19
|
end
|
17
20
|
|
18
21
|
def data=(data)
|
data/lib/fake_ftp/server.rb
CHANGED
@@ -6,9 +6,29 @@ module FakeFtp
|
|
6
6
|
class Server
|
7
7
|
|
8
8
|
attr_accessor :port, :passive_port
|
9
|
-
attr_reader :mode
|
10
|
-
|
11
|
-
CMDS = %w
|
9
|
+
attr_reader :mode, :path
|
10
|
+
|
11
|
+
CMDS = %w(
|
12
|
+
acct
|
13
|
+
cwd
|
14
|
+
cdup
|
15
|
+
dele
|
16
|
+
list
|
17
|
+
mdtm
|
18
|
+
mkd
|
19
|
+
nlst
|
20
|
+
pass
|
21
|
+
pasv
|
22
|
+
port
|
23
|
+
pwd
|
24
|
+
quit
|
25
|
+
stor
|
26
|
+
retr
|
27
|
+
rnfr
|
28
|
+
rnto
|
29
|
+
type
|
30
|
+
user
|
31
|
+
)
|
12
32
|
LNBK = "\r\n"
|
13
33
|
|
14
34
|
def initialize(control_port = 21, data_port = nil, options = {})
|
@@ -20,6 +40,7 @@ module FakeFtp
|
|
20
40
|
@options = options
|
21
41
|
@files = []
|
22
42
|
@mode = :active
|
43
|
+
@path = "/pub"
|
23
44
|
end
|
24
45
|
|
25
46
|
def files
|
@@ -34,8 +55,8 @@ module FakeFtp
|
|
34
55
|
@files.clear
|
35
56
|
end
|
36
57
|
|
37
|
-
def add_file(filename, data)
|
38
|
-
@files << FakeFtp::File.new(::File.basename(filename.to_s), data, @mode)
|
58
|
+
def add_file(filename, data, last_modified_time = Time.now)
|
59
|
+
@files << FakeFtp::File.new(::File.basename(filename.to_s), data, @mode, last_modified_time)
|
39
60
|
end
|
40
61
|
|
41
62
|
def start
|
@@ -43,18 +64,25 @@ module FakeFtp
|
|
43
64
|
@server = ::TCPServer.new('127.0.0.1', port)
|
44
65
|
@thread = Thread.new do
|
45
66
|
while @started
|
46
|
-
@client = @server.accept
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
67
|
+
@client = @server.accept rescue nil
|
68
|
+
if @client
|
69
|
+
respond_with('220 Can has FTP?')
|
70
|
+
@connection = Thread.new(@client) do |socket|
|
71
|
+
while @started && !socket.nil? && !socket.closed?
|
72
|
+
input = socket.gets rescue nil
|
73
|
+
respond_with parse(input) if input
|
74
|
+
end
|
75
|
+
unless @client.nil?
|
76
|
+
@client.close unless @client.closed?
|
77
|
+
@client = nil
|
78
|
+
end
|
51
79
|
end
|
52
|
-
@client.close
|
53
|
-
@client = nil
|
54
80
|
end
|
55
81
|
end
|
56
|
-
@server.
|
57
|
-
|
82
|
+
unless @server.nil?
|
83
|
+
@server.close unless @server.closed?
|
84
|
+
@server = nil
|
85
|
+
end
|
58
86
|
end
|
59
87
|
|
60
88
|
if passive_port
|
@@ -88,13 +116,14 @@ module FakeFtp
|
|
88
116
|
contents = request.split
|
89
117
|
message = contents[1..contents.length]
|
90
118
|
case command
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
119
|
+
when *CMDS
|
120
|
+
__send__ "_#{command}", *message
|
121
|
+
else
|
122
|
+
'500 Unknown command'
|
95
123
|
end
|
96
124
|
end
|
97
125
|
|
126
|
+
|
98
127
|
## FTP commands
|
99
128
|
#
|
100
129
|
# Methods are prefixed with an underscore to avoid conflicts with internal server
|
@@ -105,25 +134,51 @@ module FakeFtp
|
|
105
134
|
end
|
106
135
|
|
107
136
|
def _cwd(*args)
|
137
|
+
@path = args[0]
|
138
|
+
@path = "/#{path}" if path[0].chr != "/"
|
139
|
+
'250 OK!'
|
140
|
+
end
|
141
|
+
|
142
|
+
def _cdup(*args)
|
108
143
|
'250 OK!'
|
109
144
|
end
|
110
|
-
alias :_cdup :_cwd
|
111
145
|
|
112
146
|
def _list(*args)
|
147
|
+
wildcards = []
|
148
|
+
args.each do |arg|
|
149
|
+
next unless arg.include? '*'
|
150
|
+
wildcards << arg.gsub('*', '.*')
|
151
|
+
end
|
152
|
+
|
113
153
|
respond_with('425 Ain\'t no data port!') && return if active? && @active_connection.nil?
|
114
154
|
|
115
155
|
respond_with('150 Listing status ok, about to open data connection')
|
116
156
|
data_client = active? ? @active_connection : @data_server.accept
|
117
157
|
|
118
|
-
|
158
|
+
files = @files
|
159
|
+
if not wildcards.empty?
|
160
|
+
files = files.select do |f|
|
161
|
+
wildcards.any? { |wildcard| f.name =~ /#{wildcard}/ }
|
162
|
+
end
|
163
|
+
end
|
164
|
+
files = files.map do |f|
|
119
165
|
"-rw-r--r--\t1\towner\tgroup\t#{f.bytes}\t#{f.created.strftime('%b %d %H:%M')}\t#{f.name}"
|
120
|
-
end
|
166
|
+
end
|
167
|
+
data_client.write(files.join("\n"))
|
121
168
|
data_client.close
|
122
169
|
@active_connection = nil
|
123
170
|
|
124
171
|
'226 List information transferred'
|
125
172
|
end
|
126
173
|
|
174
|
+
def _mdtm(filename = '', local = false)
|
175
|
+
respond_with('501 No filename given') && return if filename.empty?
|
176
|
+
server_file = file(filename)
|
177
|
+
respond_with('550 File not found') && return if server_file.nil?
|
178
|
+
|
179
|
+
respond_with("213 #{server_file.last_modified_time.strftime("%Y%m%d%H%M%S")}")
|
180
|
+
end
|
181
|
+
|
127
182
|
def _nlst(*args)
|
128
183
|
respond_with('425 Ain\'t no data port!') && return if active? && @active_connection.nil?
|
129
184
|
|
@@ -165,7 +220,7 @@ module FakeFtp
|
|
165
220
|
end
|
166
221
|
|
167
222
|
def _pwd(*args)
|
168
|
-
"257 \"
|
223
|
+
"257 \"#{path}\" is current directory"
|
169
224
|
end
|
170
225
|
|
171
226
|
def _quit(*args)
|
@@ -192,13 +247,35 @@ module FakeFtp
|
|
192
247
|
'226 File transferred'
|
193
248
|
end
|
194
249
|
|
250
|
+
def _rnfr(rename_from='')
|
251
|
+
return '501 Send path name.' if rename_from.nil? || rename_from.size < 1
|
252
|
+
|
253
|
+
@rename_from = rename_from
|
254
|
+
'350 Send RNTO to complete rename.'
|
255
|
+
end
|
256
|
+
|
257
|
+
def _rnto(rename_to='')
|
258
|
+
return '501 Send path name.' if rename_to.nil? || rename_to.size < 1
|
259
|
+
|
260
|
+
return '503 Send RNFR first.' unless @rename_from
|
261
|
+
|
262
|
+
if file = file(@rename_from)
|
263
|
+
file.name = rename_to
|
264
|
+
@rename_from = nil
|
265
|
+
'250 Path renamed.'
|
266
|
+
else
|
267
|
+
@rename_from = nil
|
268
|
+
'550 File not found.'
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
195
272
|
def _stor(filename = '')
|
196
273
|
respond_with('425 Ain\'t no data port!') && return if active? && @active_connection.nil?
|
197
274
|
|
198
275
|
respond_with('125 Do it!')
|
199
276
|
data_client = active? ? @active_connection : @data_server.accept
|
200
277
|
|
201
|
-
data = data_client.
|
278
|
+
data = data_client.read(nil).chomp
|
202
279
|
file = FakeFtp::File.new(::File.basename(filename.to_s), data, @mode)
|
203
280
|
@files << file
|
204
281
|
|
@@ -207,14 +284,23 @@ module FakeFtp
|
|
207
284
|
'226 Did it!'
|
208
285
|
end
|
209
286
|
|
287
|
+
def _dele(filename = '')
|
288
|
+
files_to_delete = @files.select{ |file| file.name == filename }
|
289
|
+
return '550 Delete operation failed.' if files_to_delete.count == 0
|
290
|
+
|
291
|
+
@files = @files - files_to_delete
|
292
|
+
|
293
|
+
'250 Delete operation successful.'
|
294
|
+
end
|
295
|
+
|
210
296
|
def _type(type = 'A')
|
211
297
|
case type.to_s
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
298
|
+
when 'A'
|
299
|
+
'200 Type set to A.'
|
300
|
+
when 'I'
|
301
|
+
'200 Type set to I.'
|
302
|
+
else
|
303
|
+
'504 We don\'t allow those'
|
218
304
|
end
|
219
305
|
end
|
220
306
|
|
@@ -222,6 +308,10 @@ module FakeFtp
|
|
222
308
|
(name.to_s == 'anonymous') ? '230 logged in' : '331 send your password'
|
223
309
|
end
|
224
310
|
|
311
|
+
def _mkd(directory)
|
312
|
+
"257 OK!"
|
313
|
+
end
|
314
|
+
|
225
315
|
def active?
|
226
316
|
@mode == :active
|
227
317
|
end
|