Revert "Replace Readline with Reline"

This commit is contained in:
Simon Janusz
2024-10-02 13:15:12 +01:00
committed by GitHub
parent c7d1e3411a
commit a31261ecf2
25 changed files with 298 additions and 128 deletions
@@ -92,6 +92,7 @@ class Metasploit::Framework::Command::Console < Metasploit::Framework::Command::
driver_options['ModulePath'] = options.modules.path
driver_options['Plugins'] = options.console.plugins
driver_options['Readline'] = options.console.readline
driver_options['RealReadline'] = options.console.real_readline
driver_options['Resource'] = options.console.resources
driver_options['XCommands'] = options.console.commands
@@ -16,6 +16,7 @@ class Metasploit::Framework::ParsedOptions::Console < Metasploit::Framework::Par
options.console.plugins = []
options.console.quiet = false
options.console.readline = true
options.console.real_readline = false
options.console.resources = []
options.console.subcommand = :run
}
@@ -53,11 +54,7 @@ class Metasploit::Framework::ParsedOptions::Console < Metasploit::Framework::Par
end
option_parser.on('-L', '--real-readline', 'Use the system Readline library instead of RbReadline') do
message = "The RealReadline option has been marked as deprecated, and is currently a noop.\n"
message << "Metasploit Framework now uses Reline exclusively as the input handling library.\n"
message << "If you require this functionality, please use the following link to tell us:\n"
message << ' https://github.com/rapid7/metasploit-framework/issues/19399'
warn message
options.console.real_readline = true
end
option_parser.on('-o', '--output FILE', 'Output to the specified file') do |file|
@@ -13,6 +13,7 @@ class Metasploit::Framework::ParsedOptions::RemoteDB < Metasploit::Framework::Pa
options.console.local_output = nil
options.console.plugins = []
options.console.quiet = false
options.console.real_readline = false
options.console.resources = []
options.console.subcommand = :run
}
+10 -3
View File
@@ -760,7 +760,7 @@ class Core
end
def cmd_history(*args)
length = ::Reline::HISTORY.length
length = Readline::HISTORY.length
if length < @history_limit
limit = length
@@ -780,7 +780,14 @@ class Core
limit = val.to_i
end
when '-c'
::Reline::HISTORY.clear
if Readline::HISTORY.respond_to?(:clear)
Readline::HISTORY.clear
elsif defined?(RbReadline)
RbReadline.clear_history
else
print_error('Could not clear history, skipping file')
return false
end
# Portable file truncation?
if File.writable?(Msf::Config.history_file)
@@ -801,7 +808,7 @@ class Core
(start..length-1).each do |pos|
cmd_num = (pos + 1).to_s
print_line "#{cmd_num.ljust(pad_len)} #{::Reline::HISTORY[pos]}"
print_line "#{cmd_num.ljust(pad_len)} #{Readline::HISTORY[pos]}"
end
end
@@ -131,7 +131,6 @@ class Msf::Ui::Console::CommandDispatcher::Developer
framework.history_manager.with_context(name: :irb) do
begin
reline_autocomplete = Reline.autocompletion
if active_module
print_status("You are in #{active_module.fullname}\n")
Rex::Ui::Text::IrbShell.new(active_module).run
@@ -141,8 +140,6 @@ class Msf::Ui::Console::CommandDispatcher::Developer
end
rescue
print_error("Error during IRB: #{$!}\n\n#{$@.join("\n")}")
ensure
Reline.autocompletion = reline_autocomplete if defined? reline_autocomplete
end
end
@@ -518,10 +515,6 @@ class Msf::Ui::Console::CommandDispatcher::Developer
private
def modified_files
# Temporary work-around until Open3 gets fixed on Windows 11:
# https://github.com/ruby/open3/issues/9
return [] if Rex::Compat.is_cygwin || Rex::Compat.is_windows
# Using an array avoids shelling out, so we avoid escaping/quoting
changed_files = %w[git diff --name-only]
begin
+53 -3
View File
@@ -53,6 +53,8 @@ class Driver < Msf::Ui::Driver
# @option opts [Boolean] 'AllowCommandPassthru' (true) Whether to allow
# unrecognized commands to be executed by the system shell
# @option opts [Boolean] 'Readline' (true) Whether to use the readline or not
# @option opts [Boolean] 'RealReadline' (false) Whether to use the system's
# readline library instead of RBReadline
# @option opts [String] 'HistFile' (Msf::Config.history_file) Path to a file
# where we can store command history
# @option opts [Array<String>] 'Resources' ([]) A list of resource files to
@@ -62,6 +64,8 @@ class Driver < Msf::Ui::Driver
# @option opts [Boolean] 'SkipDatabaseInit' (false) Whether to skip
# connecting to the database and running migrations
def initialize(prompt = DefaultPrompt, prompt_char = DefaultPromptChar, opts = {})
choose_readline(opts)
histfile = opts['HistFile'] || Msf::Config.history_file
begin
@@ -128,6 +132,14 @@ class Driver < Msf::Ui::Driver
# stack
enstack_dispatcher(CommandDispatcher::Core)
# Report readline error if there was one..
if !@rl_err.nil?
print_error("***")
print_error("* Unable to load readline: #{@rl_err}")
print_error("* Falling back to RbReadLine")
print_error("***")
end
# Load the other "core" command dispatchers
CommandDispatchers.each do |dispatcher_class|
dispatcher = enstack_dispatcher(dispatcher_class)
@@ -311,11 +323,11 @@ class Driver < Msf::Ui::Driver
# Saves the recent history to the specified file
#
def save_recent_history(path)
num = ::Reline::HISTORY.length - hist_last_saved - 1
num = Readline::HISTORY.length - hist_last_saved - 1
tmprc = ""
num.times { |x|
tmprc << ::Reline::HISTORY[hist_last_saved + x] + "\n"
tmprc << Readline::HISTORY[hist_last_saved + x] + "\n"
}
if tmprc.length > 0
@@ -327,7 +339,7 @@ class Driver < Msf::Ui::Driver
# Always update this, even if we didn't save anything. We do this
# so that we don't end up saving the "makerc" command itself.
self.hist_last_saved = ::Reline::HISTORY.length
self.hist_last_saved = Readline::HISTORY.length
end
#
@@ -690,6 +702,44 @@ protected
false
end
# Require the appropriate readline library based on the user's preference.
#
# @return [void]
def choose_readline(opts)
# Choose a readline library before calling the parent
@rl_err = nil
if opts['RealReadline']
# Remove the gem version from load path to be sure we're getting the
# stdlib readline.
gem_dir = Gem::Specification.find_all_by_name('rb-readline').first.gem_dir
rb_readline_path = File.join(gem_dir, "lib")
index = $LOAD_PATH.index(rb_readline_path)
# Bundler guarantees that the gem will be there, so it should be safe to
# assume we found it in the load path, but check to be on the safe side.
if index
$LOAD_PATH.delete_at(index)
end
end
begin
require 'readline'
rescue ::LoadError => e
if @rl_err.nil? && index
# Then this is the first time the require failed and we have an index
# for the gem version as a fallback.
@rl_err = e
# Put the gem back and see if that works
$LOAD_PATH.insert(index, rb_readline_path)
index = rb_readline_path = nil
retry
else
# Either we didn't have the gem to fall back on, or we failed twice.
# Nothing more we can do here.
raise e
end
end
end
end
end
@@ -53,18 +53,18 @@ module Msf
option_name = str.chop
option_value = ''
::Reline.completion_append_character = ' '
::Readline.completion_append_character = ' '
return tab_complete_option_values(mod, option_value, words, opt: option_name).map { |value| "#{str}#{value}" }
elsif str.include?('=')
str_split = str.split('=')
option_name = str_split[0].strip
option_value = str_split[1].strip
::Reline.completion_append_character = ' '
::Readline.completion_append_character = ' '
return tab_complete_option_values(mod, option_value, words, opt: option_name).map { |value| "#{option_name}=#{value}" }
end
::Reline.completion_append_character = ''
::Readline.completion_append_character = ''
tab_complete_option_names(mod, str, words).map { |name| "#{name}=" }
end
+2 -2
View File
@@ -220,13 +220,13 @@ module Msf
end
def self.history(driver)
end_pos = ::Reline::HISTORY.length - 1
end_pos = Readline::HISTORY.length - 1
start_pos = end_pos - COMMAND_HISTORY_TOTAL > driver.hist_last_saved ? end_pos - (COMMAND_HISTORY_TOTAL - 1) : driver.hist_last_saved
commands = ''
while start_pos <= end_pos
# Formats command position in history to 6 characters in length
commands += "#{'%-6.6s' % start_pos.to_s} #{::Reline::HISTORY[start_pos]}\n"
commands += "#{'%-6.6s' % start_pos.to_s} #{Readline::HISTORY[start_pos]}\n"
start_pos += 1
end
@@ -902,7 +902,7 @@ class Console::CommandDispatcher::Stdapi::Fs
def tab_complete_path(str, words, dir_only)
if client.platform == 'windows'
::Reline.completion_case_fold = true
::Readline.completion_case_fold = true
end
if client.commands.include?(COMMAND_ID_STDAPI_FS_LS)
expanded = str
@@ -915,7 +915,7 @@ class Console::CommandDispatcher::Stdapi::Fs
# This is annoying if we're recursively tab-traversing our way through subdirectories -
# we may want to continue traversing, but MSF will add a space, requiring us to back up to continue
# tab-completing our way through successive subdirectories.
::Reline.completion_append_character = nil
::Readline.completion_append_character = nil
end
results
else
@@ -939,6 +939,7 @@ class Console::CommandDispatcher::Stdapi::Fs
result
end
end
end
end
@@ -20,7 +20,7 @@ module InteractiveSqlClient
#
def _interact
while self.interacting
sql_input = reline_multiline
sql_input = _multiline_with_fallback
self.interacting = (sql_input[:status] != :exit)
if sql_input[:status] == :help
@@ -65,21 +65,35 @@ module InteractiveSqlClient
# noop
end
# Get multi-line input support provided by Reline.
def reline_multiline
# Try getting multi-line input support provided by Reline, fall back to Readline.
def _multiline_with_fallback
name = session.type
query = {}
history_file = Msf::Config.history_file_for_session_type(session_type: name, interactive: true)
return { status: :fail, errors: ["Unable to get history file for session type: #{name}"] } if history_file.nil?
framework.history_manager.with_context(history_file: history_file , name: name) do
# Multiline (Reline) and fallback (Readline) have separate history contexts as they are two different libraries.
framework.history_manager.with_context(history_file: history_file , name: name, input_library: :reline) do
query = _multiline
end
if query[:status] == :fail
framework.history_manager.with_context(history_file: history_file, name: name, input_library: :readline) do
query = _fallback
end
end
query
end
def _multiline
begin
require 'reline' unless defined?(::Reline)
rescue ::LoadError => e
elog('Failed to load Reline', e)
return { status: :fail, errors: [e] }
end
stop_words = %w[stop s exit e end quit q].freeze
help_words = %w[help h].freeze
@@ -138,6 +152,28 @@ module InteractiveSqlClient
{ status: :success, result: raw_query }
end
def _fallback
stop_words = %w[stop s exit e end quit q].freeze
line_buffer = []
while (line = ::Readline.readline(prompt = line_buffer.empty? ? 'SQL >> ' : 'SQL *> ', add_history = true))
return { status: :exit, result: nil } unless self.interacting
if stop_words.include? line.chomp.downcase
self.interacting = false
print_status 'Exiting Interactive mode.'
return { status: :exit, result: nil }
end
next if line.empty?
line_buffer.append line
break if line.end_with? ';'
end
{ status: :success, result: line_buffer.join(' ') }
end
attr_accessor :on_log_proc, :client_dispatcher
private
+10 -12
View File
@@ -2,7 +2,6 @@
require 'pp'
require 'rex/text/table'
require 'erb'
require 'readline'
module Rex
module Ui
@@ -312,7 +311,7 @@ module DispatcherShell
# This is annoying if we're recursively tab-traversing our way through subdirectories -
# we may want to continue traversing, but MSF will add a space, requiring us to back up to continue
# tab-completing our way through successive subdirectories.
::Reline.completion_append_character = nil
::Readline.completion_append_character = nil
end
if dirs.length == 0 && File.directory?(str)
@@ -407,16 +406,11 @@ module DispatcherShell
# routine, stores all completed words, and passes the partial
# word to the real tab completion function. This works around
# a design problem in the Readline module and depends on the
# Reline.basic_word_break_characters variable being set to \x00
# Readline.basic_word_break_characters variable being set to \x00
#
def tab_complete(str, opts: {})
# Compatibility with how Readline worked before Reline
if opts[:preposing]
str = "#{opts[:preposing]}#{str}"
end
::Reline.completion_append_character = ' '
::Reline.completion_case_fold = false
def tab_complete(str)
::Readline.completion_append_character = ' '
::Readline.completion_case_fold = false
# Check trailing whitespace so we can tell 'x' from 'x '
str_match = str.match(/[^\\]([\\]{2})*\s+$/)
@@ -430,7 +424,11 @@ module DispatcherShell
# Pop the last word and pass it to the real method
result = tab_complete_stub(str, split_str)
result&.uniq
if result
result.uniq
else
result
end
end
# Performs tab completion of a command, if supported
+52 -18
View File
@@ -8,7 +8,7 @@ begin
###
#
# This class implements standard input using Reline against
# This class implements standard input using readline against
# standard input. It supports tab completion.
#
###
@@ -18,12 +18,16 @@ begin
# Initializes the readline-aware Input instance for text.
#
def initialize(tab_complete_proc = nil)
self.extend(::Reline)
if(not Object.const_defined?('Readline'))
require 'readline'
end
self.extend(::Readline)
if tab_complete_proc
::Reline.basic_word_break_characters = ""
::Readline.basic_word_break_characters = ""
@rl_saved_proc = with_error_handling(tab_complete_proc)
::Reline.completion_proc = @rl_saved_proc
::Readline.completion_proc = @rl_saved_proc
end
end
@@ -31,8 +35,8 @@ begin
# Reattach the original completion proc
#
def reset_tab_completion(tab_complete_proc = nil)
::Reline.basic_word_break_characters = "\x00"
::Reline.completion_proc = tab_complete_proc ? with_error_handling(tab_complete_proc) : @rl_saved_proc
::Readline.basic_word_break_characters = "\x00"
::Readline.completion_proc = tab_complete_proc ? with_error_handling(tab_complete_proc) : @rl_saved_proc
end
@@ -40,7 +44,11 @@ begin
# Retrieve the line buffer
#
def line_buffer
::Reline.line_buffer
if defined? RbReadline
RbReadline.rl_line_buffer
else
::Readline.line_buffer
end
end
attr_accessor :prompt
@@ -89,7 +97,7 @@ begin
output.prompting
line = readline_with_output(prompt, true)
::Reline::HISTORY.pop if (line and line.empty?)
::Readline::HISTORY.pop if (line and line.empty?)
ensure
Thread.current.priority = orig || 0
end
@@ -124,8 +132,13 @@ begin
private
def readline_with_output(prompt, add_history=false)
# Output needs to be set so that colorization works for the prompt on Windows.
# rb-readlines's Readline.readline hardcodes the input and output to
# $stdin and $stdout, which means setting `Readline.input` or
# `Readline.ouput` has no effect when running `Readline.readline` with
# rb-readline, so need to reimplement
# []`Readline.readline`](https://github.com/luislavena/rb-readline/blob/ce4908dae45dbcae90a6e42e3710b8c3a1f2cd64/lib/readline.rb#L36-L58)
# for rb-readline to support setting input and output. Output needs to
# be set so that colorization works for the prompt on Windows.
self.prompt = prompt
# TODO: there are unhandled quirks in async output buffering that
@@ -140,17 +153,38 @@ begin
=end
reset_sequence = ""
::Reline.input = fd
::Reline.output = output
if defined? RbReadline
RbReadline.rl_instream = fd
RbReadline.rl_outstream = output
line = ::Reline.readline(reset_sequence + prompt, add_history)
begin
line = RbReadline.readline(reset_sequence + prompt)
rescue ::Exception => exception
RbReadline.rl_cleanup_after_signal()
RbReadline.rl_deprep_terminal()
# Don't add duplicate lines to history
if ::Reline::HISTORY.length > 1 && line == ::Reline::HISTORY[-2]
::Reline::HISTORY.pop
raise exception
end
if add_history && line && !line.start_with?(' ')
# Don't add duplicate lines to history
if ::Readline::HISTORY.empty? || line.strip != ::Readline::HISTORY[-1]
RbReadline.add_history(line.strip)
end
end
line.try(:dup)
else
# The line that's read is immediately added to history
line = ::Readline.readline(reset_sequence + prompt, true)
# Don't add duplicate lines to history
if ::Readline::HISTORY.length > 1 && line == ::Readline::HISTORY[-2]
::Readline::HISTORY.pop
end
line
end
line
end
private
+11 -8
View File
@@ -39,18 +39,21 @@ class IrbShell
# commands will work.
IRB.conf[:MAIN_CONTEXT] = irb.context
begin
old_sigint = trap("SIGINT") do
# Trap interrupt
old_sigint = trap("SIGINT") do
begin
irb.signal_handle
end
# Keep processing input until the cows come home...
catch(:IRB_EXIT) do
rescue RubyLex::TerminateLineInput
irb.eval_input
end
ensure
trap("SIGINT", old_sigint) if old_sigint
end
# Keep processing input until the cows come home...
catch(:IRB_EXIT) do
irb.eval_input
end
trap("SIGINT", old_sigint)
end
end
-1
View File
@@ -94,7 +94,6 @@ class Output::Stdio < Rex::Ui::Text::Output
msg
end
alias_method :write, :print_raw
alias_method :<<, :write
def supports_color?
case config[:color]
+6 -8
View File
@@ -66,10 +66,7 @@ module Shell
def init_tab_complete
if (self.input and self.input.supports_readline)
# Unless cont_flag because there's no tab complete for continuation lines
tab_complete_lambda = proc do |str, preposing = nil, postposing = nil|
next tab_complete(str, opts: { preposing: preposing, postposing: postposing }).map { |result| result[preposing.to_s.length..] } unless cont_flag
end
self.input = Input::Readline.new(tab_complete_lambda)
self.input = Input::Readline.new(lambda { |str| tab_complete(str) unless cont_flag })
self.input.output = self.output
end
end
@@ -117,8 +114,8 @@ module Shell
#
# Performs tab completion on the supplied string.
#
def tab_complete(str, opts: {})
tab_complete_proc(str, opts: opts) if tab_complete_proc
def tab_complete(str)
return tab_complete_proc(str) if (tab_complete_proc)
end
#
@@ -307,13 +304,13 @@ module Shell
begin
history_manager.with_context(history_file: histfile, name: name) do
self.hist_last_saved = ::Reline::HISTORY.length
self.hist_last_saved = Readline::HISTORY.length
yield
end
ensure
history_manager.flush
self.hist_last_saved = ::Reline::HISTORY.length
self.hist_last_saved = Readline::HISTORY.length
end
end
@@ -513,6 +510,7 @@ module Shell
attr_reader :cont_flag # :nodoc:
attr_accessor :name
private
def try_exec(command)
begin
%x{ #{ command } }
+61 -15
View File
@@ -1,7 +1,6 @@
# -*- coding: binary -*-
require 'singleton'
require 'reline'
module Rex
module Ui
@@ -25,11 +24,12 @@ class HistoryManager
#
# @param [String,nil] history_file The file to load and persist commands to
# @param [String] name Human readable history context name
# @param [Symbol] input_library The input library to provide context for. :reline, :readline
# @param [Proc] block
# @return [nil]
def with_context(history_file: nil, name: nil, &block)
def with_context(history_file: nil, name: nil, input_library: nil, &block)
# Default to Readline for backwards compatibility.
push_context(history_file: history_file, name: name)
push_context(history_file: history_file, name: name, input_library: input_library || :readline)
begin
block.call
@@ -73,9 +73,33 @@ class HistoryManager
@debug
end
def push_context(history_file: nil, name: nil)
# A wrapper around mapping the input library to its history; this way we can mock the return value of this method.
def map_library_to_history(input_library)
case input_library
when :readline
::Readline::HISTORY
when :reline
::Reline::HISTORY
else
$stderr.puts("Unknown input library: #{input_library}") if debug?
[]
end
end
def clear_library(input_library)
case input_library
when :readline
clear_readline
when :reline
clear_reline
else
$stderr.puts("Unknown input library: #{input_library}") if debug?
end
end
def push_context(history_file: nil, name: nil, input_library: nil)
$stderr.puts("Push context before\n#{JSON.pretty_generate(_contexts)}") if debug?
new_context = { history_file: history_file, name: name }
new_context = { history_file: history_file, name: name, input_library: input_library || :readline }
switch_context(new_context, @contexts.last)
@contexts.push(new_context)
@@ -95,18 +119,40 @@ class HistoryManager
nil
end
def clear_history
history.clear
def readline_available?
defined?(::Readline)
end
def reline_available?
begin
require 'reline'
defined?(::Reline)
rescue ::LoadError => _e
false
end
end
def clear_readline
return unless readline_available?
::Readline::HISTORY.length.times { ::Readline::HISTORY.pop }
end
def clear_reline
return unless reline_available?
::Reline::HISTORY.length.times { ::Reline::HISTORY.pop }
end
def load_history_file(context)
history_file = context[:history_file]
history = map_library_to_history(context[:input_library])
begin
File.open(history_file, 'rb') do |f|
clear_history
clear_library(context[:input_library])
f.each_line(chomp: true) do |line|
if history.last&.end_with?("\\")
if context[:input_library] == :reline && history.last&.end_with?("\\")
history.last.delete_suffix!("\\")
history.last << "\n" << line
else
@@ -121,6 +167,8 @@ class HistoryManager
def store_history_file(context)
history_file = context[:history_file]
history = map_library_to_history(context[:input_library])
history_diff = history.length < MAX_HISTORY ? history.length : MAX_HISTORY
cmds = []
@@ -140,10 +188,12 @@ class HistoryManager
if new_context && new_context[:history_file]
load_history_file(new_context)
else
clear_history
clear_readline
clear_reline
end
rescue SignalException => _e
clear_history
clear_readline
clear_reline
end
def write_history_file(history_file, cmds)
@@ -174,10 +224,6 @@ class HistoryManager
@write_queue << event
@remaining_work << event
end
def history
::Reline::HISTORY
end
end
end
+3 -2
View File
@@ -197,8 +197,6 @@ Gem::Specification.new do |spec|
# Library for exploit development helpers
spec.add_runtime_dependency 'rex-exploitation'
# Command line editing, history, and tab completion in msfconsole
spec.add_runtime_dependency 'reline'
# Legacy support for FILENAME_COMPLETION_PROC, not present in Reline
spec.add_runtime_dependency 'rb-readline'
# Needed by some modules
spec.add_runtime_dependency 'rubyzip'
@@ -250,6 +248,9 @@ Gem::Specification.new do |spec|
# to generate PNG files, not to parse untrusted PNG files.
spec.add_runtime_dependency 'chunky_png'
# Needed for multiline REPL support for interactive SQL sessions
spec.add_runtime_dependency 'reline'
# Standard libraries: https://www.ruby-lang.org/en/news/2023/12/25/ruby-3-3-0-released/
%w[
abbrev
+4 -4
View File
@@ -3,7 +3,7 @@
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'reline'
require 'readline'
class MetasploitModule < Msf::Post
include Msf::Post::File
@@ -104,9 +104,9 @@ class MetasploitModule < Msf::Post
def prompt_show
promptshell = "#{@vusername}@#{@vhostname}:#{pwd.strip}#{@vpromptchar} "
comp = proc { |s| LIST.grep(/^#{Regexp.escape(s)}/) }
Reline.completion_append_character = ' '
Reline.completion_proc = comp
input = Reline.readline(promptshell, true)
Readline.completion_append_character = ' '
Readline.completion_proc = comp
input = Readline.readline(promptshell, true)
return nil if input.nil?
input
-2
View File
@@ -6,8 +6,6 @@
#
require 'pathname'
require 'reline'
begin
# Silences warnings as they only serve to confuse end users
+4 -4
View File
@@ -212,7 +212,7 @@ RSpec.describe Msf::Ui::Debug do
end
it 'correctly retrieves and parses a command history shorter than the command total' do
stub_const('Reline::HISTORY', Array.new(4) { |i| "Command #{i + 1}" })
stub_const('Readline::HISTORY', Array.new(4) { |i| "Command #{i + 1}" })
driver = instance_double(
Msf::Ui::Console::Driver,
@@ -251,7 +251,7 @@ RSpec.describe Msf::Ui::Debug do
stub_const('Msf::Ui::Debug::COMMAND_HISTORY_TOTAL', 10)
stub_const('Reline::HISTORY', Array.new(10) { |i| "Command #{i + 1}" })
stub_const('Readline::HISTORY', Array.new(10) { |i| "Command #{i + 1}" })
history_output = <<~E_LOG
## %grnHistory%clr
@@ -288,7 +288,7 @@ RSpec.describe Msf::Ui::Debug do
)
stub_const('Msf::Ui::Debug::COMMAND_HISTORY_TOTAL', 10)
stub_const('Reline::HISTORY', Array.new(15) { |i| "Command #{i + 1}" })
stub_const('Readline::HISTORY', Array.new(15) { |i| "Command #{i + 1}" })
history_output = <<~E_LOG
## %grnHistory%clr
@@ -325,7 +325,7 @@ RSpec.describe Msf::Ui::Debug do
)
stub_const('Msf::Ui::Debug::COMMAND_HISTORY_TOTAL', 10)
stub_const('Reline::HISTORY', Array.new(15) { |i| "Command #{i + 1}" })
stub_const('Readline::HISTORY', Array.new(15) { |i| "Command #{i + 1}" })
history_output = <<~E_LOG
## %grnHistory%clr
@@ -1,5 +1,6 @@
require 'spec_helper'
require 'reline'
require 'readline'
RSpec.describe Msf::Ui::Console::CommandDispatcher::Core do
include_context 'Msf::DBManager'
@@ -1,5 +1,5 @@
require 'spec_helper'
require 'reline'
require 'readline'
RSpec.describe Rex::Ui::Text::DispatcherShell do
let(:prompt) { '%undmsf6%clr' }
@@ -7,6 +7,13 @@ RSpec.describe Rex::Ui::Text::Shell::HistoryManager do
include_context 'wait_for_expect'
subject { described_class.send(:new) }
let(:readline_available) { false }
let(:reline_available) { false }
before(:each) do
allow(subject).to receive(:readline_available?).and_return(readline_available)
allow(subject).to receive(:reline_available?).and_return(reline_available)
end
describe '#with_context' do
context 'when there is not an existing stack' do
@@ -23,7 +30,7 @@ RSpec.describe Rex::Ui::Text::Shell::HistoryManager do
(expect do |block|
subject.with_context(name: 'a') do
expected_contexts = [
{ history_file: nil, name: 'a' },
{ history_file: nil, input_library: :readline, name: 'a' },
]
expect(subject._contexts).to eq(expected_contexts)
block.to_proc.call
@@ -34,7 +41,7 @@ RSpec.describe Rex::Ui::Text::Shell::HistoryManager do
context 'when there is an existing stack' do
before(:each) do
subject.send(:push_context, history_file: nil, name: 'a')
subject.send(:push_context, history_file: nil, input_library: :readline, name: 'a')
end
it 'continues to have the previous existing stack' do
@@ -42,7 +49,7 @@ RSpec.describe Rex::Ui::Text::Shell::HistoryManager do
# noop
}
expected_contexts = [
{ history_file: nil, name: 'a' },
{ history_file: nil, input_library: :readline, name: 'a' },
]
expect(subject._contexts).to eq(expected_contexts)
end
@@ -51,8 +58,8 @@ RSpec.describe Rex::Ui::Text::Shell::HistoryManager do
(expect do |block|
subject.with_context(name: 'b') do
expected_contexts = [
{ history_file: nil, name: 'a' },
{ history_file: nil, name: 'b' },
{ history_file: nil, input_library: :readline, name: 'a' },
{ history_file: nil, input_library: :readline, name: 'b' },
]
expect(subject._contexts).to eq(expected_contexts)
block.to_proc.call
@@ -67,7 +74,7 @@ RSpec.describe Rex::Ui::Text::Shell::HistoryManager do
}
end.to raise_exception ArgumentError, 'Mock error'
expected_contexts = [
{ history_file: nil, name: 'a' },
{ history_file: nil, input_library: :readline, name: 'a' },
]
expect(subject._contexts).to eq(expected_contexts)
end
@@ -77,9 +84,9 @@ RSpec.describe Rex::Ui::Text::Shell::HistoryManager do
describe '#push_context' do
context 'when the stack is empty' do
it 'stores the history contexts' do
subject.send(:push_context, history_file: nil, name: 'a')
subject.send(:push_context, history_file: nil, input_library: :readline, name: 'a')
expected_contexts = [
{ history_file: nil, name: 'a' }
{ history_file: nil, input_library: :readline, name: 'a' }
]
expect(subject._contexts).to eq(expected_contexts)
end
@@ -88,12 +95,12 @@ RSpec.describe Rex::Ui::Text::Shell::HistoryManager do
context 'when multiple values are pushed' do
it 'stores the history contexts' do
subject.send(:push_context, history_file: nil, name: 'a')
subject.send(:push_context, history_file: nil, name: 'b')
subject.send(:push_context, history_file: nil, name: 'c')
subject.send(:push_context, history_file: nil, input_library: :readline, name: 'b')
subject.send(:push_context, history_file: nil, input_library: :reline, name: 'c')
expected_contexts = [
{ history_file: nil, name: 'a' },
{ history_file: nil, name: 'b' },
{ history_file: nil, name: 'c' },
{ history_file: nil, input_library: :readline, name: 'a' },
{ history_file: nil, input_library: :readline, name: 'b' },
{ history_file: nil, input_library: :reline, name: 'c' },
]
expect(subject._contexts).to eq(expected_contexts)
end
@@ -116,7 +123,7 @@ RSpec.describe Rex::Ui::Text::Shell::HistoryManager do
subject.send(:push_context, history_file: nil, name: 'b')
subject.send(:pop_context)
expected_contexts = [
{ history_file: nil, name: 'a' },
{ history_file: nil, input_library: :readline, name: 'a' },
]
expect(subject._contexts).to eq(expected_contexts)
end
@@ -143,14 +150,14 @@ RSpec.describe Rex::Ui::Text::Shell::HistoryManager do
context "when storing #{test[:history_size]} lines" do
it "correctly stores #{test[:expected_size]} lines" do
allow(subject).to receive(:store_history_file).and_call_original
allow(subject).to receive(:history).and_return(history_mock)
allow(subject).to receive(:map_library_to_history).and_return(history_mock)
test[:history_size].times do
# This imitates the user typing in a command and pressing the 'enter' key.
history_mock << history_choices.sample
end
context = { history_file: history_file.path, name: 'history' }
context = { input_library: :readline, history_file: history_file.path, name: 'history'}
subject.send(:store_history_file, context)
@@ -169,16 +176,15 @@ RSpec.describe Rex::Ui::Text::Shell::HistoryManager do
let(:history_file) { ::Tempfile.new('history') }
after(:each) do
history_file.close
history_file.unlink
subject._close
end
context 'when history file is not accessible' do
it 'the library history remains unchanged' do
allow(subject).to receive(:history).and_return(history_mock)
allow(subject).to receive(:map_library_to_history).and_return(history_mock)
history_file = ::File.join('does', 'not', 'exist', 'history')
context = { history_file: history_file, name: 'history' }
context = { input_library: :readline, history_file: history_file, name: 'history' }
subject.send(:load_history_file, context)
expect(history_mock).to eq(initial_history)
@@ -187,7 +193,7 @@ RSpec.describe Rex::Ui::Text::Shell::HistoryManager do
context 'when history file is accessible' do
it 'correctly loads the history' do
allow(subject).to receive(:history).and_return(history_mock)
allow(subject).to receive(:map_library_to_history).and_return(history_mock)
# Populate our own history file with random entries.
# Using this allows us to not have to worry about history files present/not present on disk.
@@ -198,7 +204,7 @@ RSpec.describe Rex::Ui::Text::Shell::HistoryManager do
history_file.puts new_history
history_file.rewind
context = { history_file: history_file.path, name: 'history' }
context = { input_library: :readline, history_file: history_file.path, name: 'history' }
subject.send(:load_history_file, context)
+1 -1
View File
@@ -31,7 +31,7 @@ require 'msfenv'
$:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB']
require 'rex'
require 'reline'
require 'readline'
require 'metasm'
#PowerPC, seems broken for now in metasm
+1 -1
View File
@@ -21,7 +21,7 @@ $:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB']
require 'msfenv'
require 'rex'
require 'reline'
require 'readline'
# Check to make sure nasm is installed and reachable through the user's PATH.
begin