fake_ftp 0.0.9 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
[](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
|