class FakeFtp::Server

Attributes

client[RW]
command_state[RW]
data_server[RW]
options[R]
passive_port[RW]
path[RW]
port[RW]
store[RW]
workdir[RW]

Public Class Methods

new(control_port = 21, data_port = nil, options = {}) click to toggle source
# File lib/fake_ftp/server.rb, line 14
def initialize(control_port = 21, data_port = nil, options = {})
  @port = control_port
  @passive_port = data_port
  @store = {}
  @workdir = '/pub'
  @options = options
  @command_state = {}

  @connection = nil
  @data_server = nil
  @server = nil
  @client = nil

  raise Errno::EADDRINUSE, port.to_s if !control_port.zero? && running?

  if passive_port && !passive_port.zero? && running?(passive_port)
    raise Errno::EADDRINUSE, passive_port.to_s
  end

  self.mode = options.fetch(:mode, :active)
  self.absolute = options.fetch(:absolute, false)
end

Public Instance Methods

absolute=(value) click to toggle source
# File lib/fake_ftp/server.rb, line 146
def absolute=(value)
  unless [true, false].include?(value)
    raise ArgumentError, "invalid absolute #{value}"
  end
  options[:absolute] = value
end
absolute?() click to toggle source
# File lib/fake_ftp/server.rb, line 142
def absolute?
  options[:absolute]
end
abspath(filename) click to toggle source
# File lib/fake_ftp/server.rb, line 156
def abspath(filename)
  return filename if filename.start_with?('/')
  [@workdir.to_s, filename].join('/').gsub('//', '/')
end
active?() click to toggle source
# File lib/fake_ftp/server.rb, line 195
def active?
  options[:mode] == :active
end
add_file(filename, data, last_modified_time = Time.now) click to toggle source
# File lib/fake_ftp/server.rb, line 61
def add_file(filename, data, last_modified_time = Time.now)
  @store[abspath(filename)] = FakeFtp::File.new(
    filename.to_s, data, options[:mode], last_modified_time
  )
end
build_wildcards(args) click to toggle source
# File lib/fake_ftp/server.rb, line 216
def build_wildcards(args)
  wildcards = []
  args.each do |arg|
    next unless arg.include? '*'
    wildcards << arg.gsub('*', '.*')
  end
  wildcards
end
debug(msg) click to toggle source
# File lib/fake_ftp/server.rb, line 235
def debug(msg)
  return unless options[:debug]
  $stderr.puts("DEBUG:fake_ftp:#{msg}")
end
file(name) click to toggle source
# File lib/fake_ftp/server.rb, line 47
def file(name)
  @store.values.detect do |f|
    if absolute?
      abspath(f.name) == name
    else
      f.name == name
    end
  end
end
files() click to toggle source
# File lib/fake_ftp/server.rb, line 37
def files
  @store.values.map do |f|
    if absolute?
      abspath(f.name)
    else
      f.name
    end
  end
end
is_running?(tcp_port = nil)
Alias for: running?
matching_files(wildcards) click to toggle source
# File lib/fake_ftp/server.rb, line 225
def matching_files(wildcards)
  if !wildcards.empty?
    @store.values.select do |f|
      wildcards.any? { |wildcard| f.name =~ /#{wildcard}/ }
    end
  else
    @store.values
  end
end
mode() click to toggle source
# File lib/fake_ftp/server.rb, line 138
def mode
  options[:mode]
end
mode=(value) click to toggle source
# File lib/fake_ftp/server.rb, line 131
def mode=(value)
  unless %i[active passive].include?(value)
    raise ArgumentError, "invalid mode #{value.inspect}"
  end
  options[:mode] = value
end
reset() click to toggle source
# File lib/fake_ftp/server.rb, line 57
def reset
  @store.clear
end
respond_with(stuff) click to toggle source
# File lib/fake_ftp/server.rb, line 161
def respond_with(stuff)
  return if stuff.nil? || @client.nil? || @client.closed?
  debug("server client raw: -> #{stuff.inspect}")
  @client.print(stuff + "\r\n")
end
running?(tcp_port = nil) click to toggle source
# File lib/fake_ftp/server.rb, line 124
def running?(tcp_port = nil)
  return port_is_open?(port) if tcp_port.nil?
  port_is_open?(tcp_port)
end
Also aliased as: is_running?
start() click to toggle source
# File lib/fake_ftp/server.rb, line 67
def start
  @started = true
  @server = ::TCPServer.new('127.0.0.1', port)
  @port = @server.addr[1]
  @thread = Thread.new do
    while @started
      debug('enter client loop')
      @client = begin
                  @server.accept
                rescue => e
                  debug("error on accept: #{e}")
                  nil
                end
      next unless @client
      respond_with('220 Can has FTP?')
      @connection = Thread.new(@client) do |socket|
        debug('enter request thread')
        while @started && !socket.nil? && !socket.closed?
          input = begin
                    socket.gets
                  rescue
                    debug("error on socket.gets: #{e}")
                    nil
                  end
          if input
            debug("server client raw: <- #{input.inspect}")
            respond_with(handle_request(input))
          end
        end
        unless @client.nil?
          @client.close unless @client.closed?
          @client = nil
        end
        debug('leave request thread')
      end
      debug('leave client loop')
    end
    unless @server.nil?
      @server.close unless @server.closed?
      @server = nil
    end
  end

  return unless passive_port
  @data_server = ::TCPServer.new('127.0.0.1', passive_port)
  @passive_port = @data_server.addr[1]
end
stop() click to toggle source
# File lib/fake_ftp/server.rb, line 115
def stop
  @started = false
  @client&.close
  @server&.close
  @server = nil
  @data_server&.close
  @data_server = nil
end

Private Instance Methods

handle_request(request) click to toggle source
# File lib/fake_ftp/server.rb, line 167
        def handle_request(request)
  return if request.nil?
  debug("raw request: #{request.inspect}")
  command = request[0, 4].downcase.strip
  contents = request.split
  message = contents[1..contents.length]

  inst = load_command_instance(command)
  return "500 Unknown command #{command.inspect}" if inst.nil?
  debug(
    "running command #{command.inspect} " \
    "#{inst.class.name}#run(*#{message.inspect})"
  )
  inst.run(*([self] + message))
end
load_command_instance(command) click to toggle source
# File lib/fake_ftp/server.rb, line 183
        def load_command_instance(command)
  require "fake_ftp/server_commands/#{command}"
  FakeFtp::ServerCommands.constants.each do |const_name|
    next unless const_name.to_s.downcase == command
    return FakeFtp::ServerCommands.const_get(const_name).new
  end
  nil
rescue LoadError => e
  debug("failed to require #{command.inspect} class: #{e}")
  nil
end
port_is_open?(port) click to toggle source
# File lib/fake_ftp/server.rb, line 199
        def port_is_open?(port)
  begin
    Timeout.timeout(1) do
      begin
        TCPSocket.new('127.0.0.1', port).close
        return true
      rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
        return false
      end
    end
  rescue Timeout::Error => e
    debug("timeout while checking port #{port}: #{e}")
  end

  false
end