# frozen_string_literal: true

module Msf
  module Ui
    module Console
      module CommandDispatcher
        module Session
          include Rex::Ui::Text::DispatcherShell::CommandDispatcher

          @@irb_opts = Rex::Parser::Arguments.new(
            %w[-h --help] => [false, 'Help menu.' ],
            '-e' => [true, 'Expression to evaluate.']
          )

          @@sessions_opts = Rex::Parser::Arguments.new(
            ['-h', '--help'] => [ false, 'Show this message' ],
            ['-i', '--interact'] => [ true, 'Interact with a provided session ID', '<id>' ]
          )

          def commands
            {
              '?' => 'Help menu',
              'background' => 'Backgrounds the current session',
              'bg' => 'Alias for background',
              'exit' => 'Terminate the session',
              'help' => 'Help menu',
              'irb' => 'Open an interactive Ruby shell on the current session',
              'pry' => 'Open the Pry debugger on the current session',
              'quit' => 'Terminate the session',
              'resource' => 'Run the commands stored in a file',
              'uuid' => 'Get the UUID for the current session',
              'sessions' => 'Quickly switch to another session'
            }
          end

          def cmd_background_help
            print_line('Usage: background')
            print_line
            print_line('Stop interacting with this session and return to the parent prompt')
            print_line
          end

          def cmd_background(*args)
            if args.include?('-h') || args.include?('--help')
              cmd_background_help
              return
            end
            print_status("Backgrounding session #{session.name}...")
            session.interacting = false
          end

          alias cmd_bg cmd_background
          alias cmd_bg_help cmd_background_help

          #
          # Terminates the session.
          #
          def cmd_exit(*args)
            print_status("Shutting down session: #{session.sid}")
            session.exit
          end

          alias cmd_quit cmd_exit

          def cmd_irb_help
            print_line('Usage: irb')
            print_line
            print_line('Open an interactive Ruby shell on the current session.')
            print @@irb_opts.usage
          end

          def cmd_irb_tabs(str, words)
            return [] if words.length > 1

            @@irb_opts.option_keys
          end

          #
          # Open an interactive Ruby shell on the current session
          #
          def cmd_irb(*args)
            expressions = []

            # Parse the command options
            @@irb_opts.parse(args) do |opt, _idx, val|
              case opt
              when '-e'
                expressions << val
              when '-h', '--help'
                return cmd_irb_help
              end
            end

            framework = session.framework

            if expressions.empty?
              print_status('Starting IRB shell...')
              print_status("You are in the session object\n")
              framework.history_manager.with_context(name: :irb) do
                Rex::Ui::Text::IrbShell.new(session).run
              end
            else
              # XXX: No vprint_status here
              if framework.datastore['VERBOSE']
                print_status("You are executing expressions in #{binding.receiver}")
              end

              expressions.each { |expression| eval(expression, binding) }
            end
          end

          def cmd_pry_help
            print_line 'Usage: pry'
            print_line
            print_line 'Open the Pry debugger on the current session.'
            print_line
          end

          #
          # Open the Pry debugger on the current session
          #
          def cmd_pry(*args)
            if args.include?('-h') || args.include?('--help')
              cmd_pry_help
              return
            end

            begin
              require 'pry'
            rescue LoadError
              print_error('Failed to load Pry, try "gem install pry"')
              return
            end

            print_status('Starting Pry shell...')
            print_status("You are in the session object\n")

            Pry.config.history_load = false
            session.framework.history_manager.with_context(history_file: Msf::Config.pry_history, name: :pry) do
              session.pry
            end
          end

          def cmd_sessions_help
            print_line('Usage: sessions [options] or sessions [id]')
            print_line
            print_line('Interact with a different session ID.')
            print(@@sessions_opts.usage)
            print_line
          end

          def cmd_sessions(*args)
            if args.empty?
              cmd_sessions_help
              return false
            end

            sid = nil

            if args.length == 1 && args[0] =~ /-?\d+/
              sid = args[0].to_i
            else
              @@sessions_opts.parse(args) do |opt, _idx, val|
                case opt
                when '-h', '--help'
                  cmd_sessions_help
                  return false
                when '-i', '--interact'
                  sid = val.to_i
                else
                  cmd_sessions_help
                  return false
                end
              end
            end

            if sid == 0 || sid.nil?
              cmd_sessions_help
              return false
            end

            if sid.to_s == session.name.to_s
              print_status("Session #{session.name} is already interactive.")
            else
              print_status("Backgrounding session #{session.name}...")
              # store the next session id so that it can be referenced as soon
              # as this session is no longer interacting
              session.next_session = sid
              session.interacting = false
            end
          end

          def cmd_resource_help
            print_line 'Usage: resource path1 [path2 ...]'
            print_line
            print_line 'Run the commands stored in the supplied files. (- for stdin, press CTRL+D to end input from stdin)'
            print_line 'Resource files may also contain ERB or Ruby code between <ruby></ruby> tags.'
            print_line
          end

          def cmd_resource(*args)
            if args.empty? || args.include?('-h') || args.include?('--help')
              cmd_resource_help
              return false
            end

            args.each do |res|
              good_res = nil
              if res == '-'
                good_res = res
              elsif ::File.exist?(res)
                good_res = res
              elsif [
                ::Msf::Config.script_directory + ::File::SEPARATOR + 'resource' + ::File::SEPARATOR + 'meterpreter',
                ::Msf::Config.user_script_directory + ::File::SEPARATOR + 'resource' + ::File::SEPARATOR + 'meterpreter'
              ].each do |dir|
                      res_path = dir + ::File::SEPARATOR + res
                      if ::File.exist?(res_path)
                        good_res = res_path
                        break
                      end
                    end
                # let's check to see if it's in the scripts/resource dir (like when tab completed)
              end
              unless good_res
                print_error("#{res} is not a valid resource file")
                next
              end

              session.console.load_resource(good_res)
            end
          end

          def cmd_resource_tabs(str, words)
            tabs = []
            # return tabs if words.length > 1
            if (str && str =~ (/^#{Regexp.escape(::File::SEPARATOR)}/))
              # then you are probably specifying a full path so let's just use normal file completion
              return tab_complete_filenames(str, words)
            elsif (!(words[1]) || !words[1].match(%r{^/}))
              # then let's start tab completion in the scripts/resource directories
              begin
                [
                  ::Msf::Config.script_directory + ::File::SEPARATOR + 'resource' + ::File::SEPARATOR + 'meterpreter',
                  ::Msf::Config.user_script_directory + ::File::SEPARATOR + 'resource' + ::File::SEPARATOR + 'meterpreter',
                  '.'
                ].each do |dir|
                  next if !::File.exist? dir

                  tabs += ::Dir.new(dir).find_all do |e|
                    path = dir + ::File::SEPARATOR + e
                    ::File.file?(path) and ::File.readable?(path)
                  end
                end
              rescue StandardError => e
                elog('Problem tab completing resource file names in the scripts/resource directories', error: e)
              end
            else
              tabs += tab_complete_filenames(str, words)
            end

            return tabs
          end
        end
      end
    end
  end
end
