Path: blob/master/lib/rex/ui/text/input/readline.rb
19592 views
# -*- coding: binary -*-12module Rex3module Ui4module Text56begin78###9#10# This class implements standard input using readline against11# standard input. It supports tab completion.12#13###14class Input::Readline < Rex::Ui::Text::Input1516#17# Initializes the readline-aware Input instance for text.18#19def initialize(tab_complete_proc = nil)20if(not Object.const_defined?('Readline'))21require 'readline'22end2324self.extend(::Readline)2526if tab_complete_proc27::Readline.basic_word_break_characters = ""28@rl_saved_proc = with_error_handling(tab_complete_proc)29::Readline.completion_proc = @rl_saved_proc30end31end3233#34# Reattach the original completion proc35#36def reset_tab_completion(tab_complete_proc = nil)37::Readline.basic_word_break_characters = "\x00"38::Readline.completion_proc = tab_complete_proc ? with_error_handling(tab_complete_proc) : @rl_saved_proc39end404142#43# Retrieve the line buffer44#45def line_buffer46if defined? RbReadline47RbReadline.rl_line_buffer48else49::Readline.line_buffer50end51end5253attr_accessor :prompt5455#56# Whether or not the input medium supports readline.57#58def supports_readline59true60end6162#63# Calls sysread on the standard input handle.64#65def sysread(len = 1)66begin67self.fd.sysread(len)68rescue ::Errno::EINTR69retry70end71end7273#74# Read a line from stdin75#76def gets()77begin78self.fd.gets()79rescue ::Errno::EINTR80retry81end82end8384#85# Stick readline into a low-priority thread so that the scheduler doesn't slow86# down other background threads. This is important when there are many active87# background jobs, such as when the user is running Karmetasploit88#89def pgets9091line = nil92orig = Thread.current.priority9394begin95Thread.current.priority = -209697output.prompting98line = readline_with_output(prompt, true)99::Readline::HISTORY.pop if (line and line.empty?)100ensure101Thread.current.priority = orig || 0102output.prompting(false)103end104105line106end107108#109# Returns the output pipe handle110#111def fd112$stdin113end114115#116# Indicates that this input medium as a shell builtin, no need117# to extend.118#119def intrinsic_shell?120true121end122123#124# The prompt that is to be displayed.125#126attr_accessor :prompt127#128# The output handle to use when displaying the prompt.129#130attr_accessor :output131132private133134def readline_with_output(prompt, add_history=false)135# rb-readlines's Readline.readline hardcodes the input and output to136# $stdin and $stdout, which means setting `Readline.input` or137# `Readline.ouput` has no effect when running `Readline.readline` with138# rb-readline, so need to reimplement139# []`Readline.readline`](https://github.com/luislavena/rb-readline/blob/ce4908dae45dbcae90a6e42e3710b8c3a1f2cd64/lib/readline.rb#L36-L58)140# for rb-readline to support setting input and output. Output needs to141# be set so that colorization works for the prompt on Windows.142self.prompt = prompt143144# TODO: there are unhandled quirks in async output buffering that145# we have not solved yet, for instance when loading meterpreter146# extensions, supporting Windows, printing output from commands, etc.147# Remove this guard when issues are resolved.148=begin149reset_sequence = "\n\001\r\033[K\002"150if (/mingw/ =~ RUBY_PLATFORM)151reset_sequence = ""152end153=end154reset_sequence = ""155156if defined? RbReadline157RbReadline.rl_instream = fd158RbReadline.rl_outstream = output159160begin161line = RbReadline.readline(reset_sequence + prompt)162rescue ::Exception => exception163RbReadline.rl_cleanup_after_signal()164RbReadline.rl_deprep_terminal()165166raise exception167end168169if add_history && line && !line.start_with?(' ')170# Don't add duplicate lines to history171if ::Readline::HISTORY.empty? || line.strip != ::Readline::HISTORY[-1]172RbReadline.add_history(line.strip)173end174end175176line.try(:dup)177else178# The line that's read is immediately added to history179line = ::Readline.readline(reset_sequence + prompt, true)180181# Don't add duplicate lines to history182if ::Readline::HISTORY.length > 1 && line == ::Readline::HISTORY[-2]183::Readline::HISTORY.pop184end185186line187end188end189190private191192def with_error_handling(proc)193proc do |*args|194proc.call(*args)195rescue StandardError => e196elog("tab_complete_proc has failed with args #{args}", error: e)197[]198end199end200201end202rescue LoadError203end204205end206end207end208209210