lib/fake_ftp/server.rb in fake_ftp-0.0.9 vs lib/fake_ftp/server.rb in fake_ftp-0.1.0

- old
+ new

@@ -4,13 +4,33 @@ module FakeFtp class Server attr_accessor :port, :passive_port - attr_reader :mode + attr_reader :mode, :path - CMDS = %w[acct cwd cdup list nlst pass pasv port pwd quit stor retr type user] + CMDS = %w( + acct + cwd + cdup + dele + list + mdtm + mkd + nlst + pass + pasv + port + pwd + quit + stor + retr + rnfr + rnto + type + user + ) LNBK = "\r\n" def initialize(control_port = 21, data_port = nil, options = {}) self.port = control_port self.passive_port = data_port @@ -18,10 +38,11 @@ raise(Errno::EADDRINUSE, "#{passive_port}") if passive_port && is_running?(passive_port) @connection = nil @options = options @files = [] @mode = :active + @path = "/pub" end def files @files.map(&:name) end @@ -32,31 +53,38 @@ def reset @files.clear end - def add_file(filename, data) - @files << FakeFtp::File.new(::File.basename(filename.to_s), data, @mode) + def add_file(filename, data, last_modified_time = Time.now) + @files << FakeFtp::File.new(::File.basename(filename.to_s), data, @mode, last_modified_time) end def start @started = true @server = ::TCPServer.new('127.0.0.1', port) @thread = Thread.new do while @started - @client = @server.accept - respond_with('220 Can has FTP?') - @connection = Thread.new(@client) do |socket| - while @started && !socket.nil? && !socket.closed? - respond_with parse(socket.gets) + @client = @server.accept rescue nil + if @client + respond_with('220 Can has FTP?') + @connection = Thread.new(@client) do |socket| + while @started && !socket.nil? && !socket.closed? + input = socket.gets rescue nil + respond_with parse(input) if input + end + unless @client.nil? + @client.close unless @client.closed? + @client = nil + end end - @client.close - @client = nil end end - @server.close - @server = nil + unless @server.nil? + @server.close unless @server.closed? + @server = nil + end end if passive_port @data_server = ::TCPServer.new('127.0.0.1', passive_port) end @@ -86,46 +114,73 @@ puts request if @options[:debug] command = request[0, 4].downcase.strip contents = request.split message = contents[1..contents.length] case command - when *CMDS - __send__ "_#{command}", *message - else - '500 Unknown command' + when *CMDS + __send__ "_#{command}", *message + else + '500 Unknown command' end end + ## FTP commands # # Methods are prefixed with an underscore to avoid conflicts with internal server # methods. Methods map 1:1 to FTP command words. # def _acct(*args) '230 WHATEVER!' end def _cwd(*args) + @path = args[0] + @path = "/#{path}" if path[0].chr != "/" '250 OK!' end - alias :_cdup :_cwd + def _cdup(*args) + '250 OK!' + end + def _list(*args) + wildcards = [] + args.each do |arg| + next unless arg.include? '*' + wildcards << arg.gsub('*', '.*') + end + respond_with('425 Ain\'t no data port!') && return if active? && @active_connection.nil? respond_with('150 Listing status ok, about to open data connection') data_client = active? ? @active_connection : @data_server.accept - data_client.write(@files.map do |f| + files = @files + if not wildcards.empty? + files = files.select do |f| + wildcards.any? { |wildcard| f.name =~ /#{wildcard}/ } + end + end + files = files.map do |f| "-rw-r--r--\t1\towner\tgroup\t#{f.bytes}\t#{f.created.strftime('%b %d %H:%M')}\t#{f.name}" - end.join("\n")) + end + data_client.write(files.join("\n")) data_client.close @active_connection = nil '226 List information transferred' end + def _mdtm(filename = '', local = false) + respond_with('501 No filename given') && return if filename.empty? + server_file = file(filename) + respond_with('550 File not found') && return if server_file.nil? + + respond_with("213 #{server_file.last_modified_time.strftime("%Y%m%d%H%M%S")}") + end + def _nlst(*args) respond_with('425 Ain\'t no data port!') && return if active? && @active_connection.nil? respond_with('150 Listing status ok, about to open data connection') data_client = active? ? @active_connection : @data_server.accept @@ -163,11 +218,11 @@ @active_connection = ::TCPSocket.open('127.0.0.1', remote_port) '200 Okay' end def _pwd(*args) - "257 \"/pub\" is current directory" + "257 \"#{path}\" is current directory" end def _quit(*args) respond_with '221 OMG bye!' @client.close if @client @@ -190,37 +245,72 @@ data_client.close @active_connection = nil '226 File transferred' end + def _rnfr(rename_from='') + return '501 Send path name.' if rename_from.nil? || rename_from.size < 1 + + @rename_from = rename_from + '350 Send RNTO to complete rename.' + end + + def _rnto(rename_to='') + return '501 Send path name.' if rename_to.nil? || rename_to.size < 1 + + return '503 Send RNFR first.' unless @rename_from + + if file = file(@rename_from) + file.name = rename_to + @rename_from = nil + '250 Path renamed.' + else + @rename_from = nil + '550 File not found.' + end + end + def _stor(filename = '') respond_with('425 Ain\'t no data port!') && return if active? && @active_connection.nil? respond_with('125 Do it!') data_client = active? ? @active_connection : @data_server.accept - data = data_client.recv(1024) + data = data_client.read(nil).chomp file = FakeFtp::File.new(::File.basename(filename.to_s), data, @mode) @files << file data_client.close @active_connection = nil '226 Did it!' end + def _dele(filename = '') + files_to_delete = @files.select{ |file| file.name == filename } + return '550 Delete operation failed.' if files_to_delete.count == 0 + + @files = @files - files_to_delete + + '250 Delete operation successful.' + end + def _type(type = 'A') case type.to_s - when 'A' - '200 Type set to A.' - when 'I' - '200 Type set to I.' - else - '504 We don\'t allow those' + when 'A' + '200 Type set to A.' + when 'I' + '200 Type set to I.' + else + '504 We don\'t allow those' end end def _user(name = '') (name.to_s == 'anonymous') ? '230 logged in' : '331 send your password' + end + + def _mkd(directory) + "257 OK!" end def active? @mode == :active end