module Wpxf::Cli::AutoComplete

Functionality for configuring auto-complete functionality in Readline lib.

Attributes

autocomplete_list[RW]

Public Instance Methods

auto_complete_proc(input, list) click to toggle source

Process the current CLI input buffer to determine auto-complete options. @param input [String] the current input buffer. @param list [Array] the array of auto-complete options. @return [Array, nil] an array of possible commands.

# File lib/wpxf/cli/auto_complete.rb, line 68
def auto_complete_proc(input, list)
  res = nil

  # Nothing on this level, so return previous level.
  return res if list.keys.empty?

  # Enumerate each cmd/arg on this level, if there's a match, descend
  # into the next level and update the return value if anything is
  # returned and repeat.
  list.each do |k, v|
    next unless input =~ /^#{k}\s+/i
    res = list.keys
    trimmed_input = input.gsub(/^(#{k}\s+)(.+)/i, '\2')

    # If there wasn't another level of input, return the list from
    # the next level as the suggestions. For example, if `input` is
    # "show " (emphasis on trailing space), then return
    # ["show options", "show exploits"].
    if input.eql?(trimmed_input) && !v.keys.empty?
      res = v.keys.map { |r| input + r }
    else
      # If there was another level of input (e.g. "show o"), descend
      # into that level to find the partial matches (such as "show options").
      descended_res = auto_complete_proc(trimmed_input, v)
      if descended_res
        res = descended_res.grep(/^#{Regexp.escape(trimmed_input)}/)

        # If we have descended, we'll need to prepend the input that
        # we trimmed back into the returned results as to not overwrite
        # the previous levels in STDIN.
        res = res.map { |r| input.gsub(/^(#{k}\s+)(.+)/i, '\1') + r }
      else
        res = res.grep(/^#{Regexp.escape(input)}/)
      end

      break
    end
  end

  # If no full matches were found, check if there are partial matches
  # on the current level, and if so, return the current level as the
  # list of possible values.
  unless res
    grep_res = list.keys.grep(/^#{Regexp.escape(input)}/)
    res = grep_res if grep_res && !grep_res.empty?
  end

  res
end
build_cmd_list() click to toggle source
# File lib/wpxf/cli/auto_complete.rb, line 51
def build_cmd_list
  cmds = {}
  permitted_commands.each { |c| cmds[c] = {} }
  Wpxf::Models::Module.each { |m| cmds['use'][m.path] = {} }
  cmds['show'] = {
    'options' => {},
    'advanced' => {},
    'exploits' => {},
    'auxiliary' => {}
  }
  cmds
end
build_opts_hash() click to toggle source
# File lib/wpxf/cli/auto_complete.rb, line 20
def build_opts_hash
  opts_hash = {}
  return opts_hash unless context

  mod = context.module
  opts = mod.options.map(&:name)
  opts += mod.payload.options.map(&:name) if mod.payload
  opts.each do |o|
    opts_hash[o] = {}
  end

  opts_hash
end
build_payload_names_hash() click to toggle source
# File lib/wpxf/cli/auto_complete.rb, line 34
def build_payload_names_hash
  opts_hash = {}
  return opts_hash unless context&.module&.exploit_module?

  opts_hash['payload'] = {}
  Wpxf::Payloads.payload_list.each { |p| opts_hash['payload'][p[:name]] = {} }
  opts_hash
end
readline_completion_proc(input) click to toggle source
# File lib/wpxf/cli/auto_complete.rb, line 14
def readline_completion_proc(input)
  res = auto_complete_proc(input, autocomplete_list)
  return [] unless res
  res
end
refresh_autocomplete_options() click to toggle source
# File lib/wpxf/cli/auto_complete.rb, line 43
def refresh_autocomplete_options
  opts_hash = build_opts_hash.merge(build_payload_names_hash)

  %w[set unset gset gunset setg unsetg].each do |key|
    autocomplete_list[key] = opts_hash
  end
end
setup_auto_complete() click to toggle source
# File lib/wpxf/cli/auto_complete.rb, line 7
def setup_auto_complete
  self.autocomplete_list = build_cmd_list
  Readline.completer_word_break_characters = ''
  Readline.completion_append_character = ' '
  Readline.completion_proc = method(:readline_completion_proc)
end