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.
@@ -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
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
4
+ gemfile:
5
+ - Gemfile.travis
data/Gemfile CHANGED
@@ -2,3 +2,11 @@ source "http://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in fake_ftp.gemspec
4
4
  gemspec
5
+
6
+ group :test do
7
+ gem "rspec", '>2'
8
+ gem "rake", '>=0.8.7'
9
+
10
+ gem "guard-rspec"
11
+ gem 'pry-nav'
12
+ end
@@ -1,27 +1,44 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- fake_ftp (0.0.8)
4
+ fake_ftp (0.1.0)
5
5
 
6
6
  GEM
7
7
  remote: http://rubygems.org/
8
8
  specs:
9
- diff-lcs (1.1.3)
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.7.0)
12
- rspec-core (~> 2.7.0)
13
- rspec-expectations (~> 2.7.0)
14
- rspec-mocks (~> 2.7.0)
15
- rspec-core (2.7.1)
16
- rspec-expectations (2.7.0)
17
- diff-lcs (~> 1.1.2)
18
- rspec-mocks (2.7.0)
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)
@@ -0,0 +1,5 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in fake_ftp.gemspec
4
+ gemspec
5
+
@@ -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
+
@@ -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
- require 'fake_ftp'
24
- require 'net/ftp'
25
-
26
- server = FakeFtp::Server.new(21212, 21213)
27
- ## 21212 is the control port, which is used by FTP for the primary connection
28
- ## 21213 is the data port, used in FTP passive mode to send file contents
29
- server.start
30
-
31
- ftp = Net::FTP.new
32
- ftp.connect('127.0.0.1', 21212)
33
- ftp.login('user', 'password')
34
- ftp.passive = true
35
- ftp.put('some_file.txt')
36
- ftp.close
37
-
38
- server.files.should include('some_file.txt')
39
- server.file('some_file.txt').bytes.should == 25
40
- server.file('some_file.txt').should be_passive
41
- server.file('some_file.txt').should_not be_active
42
-
43
- server.stop
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
- 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
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
 
@@ -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) }
@@ -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)
@@ -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[acct cwd cdup list nlst pass pasv port pwd quit stor retr type user]
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
- respond_with('220 Can has FTP?')
48
- @connection = Thread.new(@client) do |socket|
49
- while @started && !socket.nil? && !socket.closed?
50
- respond_with parse(socket.gets)
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.close
57
- @server = nil
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
- when *CMDS
92
- __send__ "_#{command}", *message
93
- else
94
- '500 Unknown command'
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
- data_client.write(@files.map do |f|
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.join("\n"))
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 \"/pub\" is current directory"
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.recv(1024)
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
- when 'A'
213
- '200 Type set to A.'
214
- when 'I'
215
- '200 Type set to I.'
216
- else
217
- '504 We don\'t allow those'
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