# frozen_string_literal: true

require 'command_mapper/command'

module Masscan
  #
  # Provides an interface for invoking the `masscan` utility.
  #
  # ## Example
  #
  #     require 'masscan/command'
  #
  #     Masscan::Command.sudo do |masscan|
  #       masscan.output_format = :list
  #       masscan.output_file   = 'masscan.txt'
  #
  #       masscan.ips   = '192.168.1.1/24'
  #       masscan.ports = [20,21,22,23,25,80,110,443,512,522,8080,1080]
  #     end
  #
  # ## `masscan` options:
  #
  # * `--range` - `masscan.range`
  # * `-p` - `masscan.ports`
  # * `--banners` - `masscan.banners`
  # * `--rate` - `masscan.rate`
  # * `--conf` - `masscan.config_file`
  # * `--resume` - `masscan.resume`
  # * `--echo` - `masscan.echo`
  # * `--adapter` - `masscan.adapter`
  # * `--adapter-ip` - `masscan.adapter_ip`
  # * `--adapter-port` - `masscan.adapter_port`
  # * `--adapter-mac` - `masscan.adapter_mac`
  # * `--adapter-vlan` - `masscan.adapter_vlan`
  # * `--router-mac` - `masscan.router_mac`
  # * `--ping` - `masscan.ping`
  # * `--exclude` - `masscan.exclude`
  # * `--excludefile` - `masscan.exclude_file`
  # * `--includefile` - `masscan.include_file`
  # * `--append-output` - `masscan.append_output`
  # * `--iflist` - `masscan.list_interfaces`
  # * `--retries` - `masscan.retries`
  # * `--nmap` - `masscan.nmap_help`
  # * `--pcap-payloads` - `masscan.pcap_payloads`
  # * `--nmap-payloads` - `masscan.nmap_payloads`
  # * `--http-method` - `masscan.http_method`
  # * `--http-url` - `masscan.http_url`
  # * `--http-version` - `masscan.http_version`
  # * `--http-host` - `masscan.http_host`
  # * `--http-user-agent` - `masscan.http_user_agent`
  # * `--http-field` - `masscan.http_field`
  # * `--http-field-remove` - `masscan.http_field_remove`
  # * `--http-cookie` - `masscan.http_cookie`
  # * `--http-payload` - `masscan.http_payload`
  # * `--show` - `masscan.show`
  # * `--noshow` - `masscan.hide`
  # * `--pcap` - `masscan.pcap`
  # * `--packet-trace` - `masscan.packet_trace`
  # * `--pfring` - `masscan.pfring`
  # * `--resume-index` - `masscan.resume_index`
  # * `--resume-count` - `masscan.resume_count`
  # * `--shards` - `masscan.shards`
  # * `--rotate` - `masscan.rotate`
  # * `--rotate-offset` - `masscan.rotate_offset`
  # * `--rotate-size` - `masscan.rotate_size`
  # * `--rotate-dir` - `masscan.rotate_dir`
  # * `--seed` - `masscan.seed`
  # * `--regress` - `masscan.regress`
  # * `--ttl` - `masscan.ttl`
  # * `--wait` - `masscan.wait`
  # * `--offline` - `masscan.offline`
  # * `-sL` - `masscan.print_list`
  # * `--interactive` - `masscan.interactive`
  # * `--output-format` - `masscan.output_format`
  # * `--output-filename` - `masscan.output_file`
  # * `-oB` - `masscan.output_binary`
  # * `-oX` - `masscan.output_xml`
  # * `-oG` - `masscan.output_grepable`
  # * ` -oJ` - `masscan.output_json`
  # * `-oL` - `masscan.output_list`
  # * `--readscan` - `masscan.read_scan`
  # * `-V` - `masscan.version`
  # * `-h` - `masscan.help`
  #
  # @see https://github.com/robertdavidgraham/masscan/blob/master/doc/masscan.8.markdown
  #
  # @since 0.2.0
  #
  class Command < CommandMapper::Command

    class PortList < CommandMapper::Types::Num

      def validate(value)
        case value
        when Array
          value.each do |element|
            valid, message = validate(element)

            unless valid
              return [valid, message]
            end
          end

          return true
        when Range
          valid, message = super(value.begin)

          unless valid
            return [valid, message]
          end

          valid, message = super(value.end)

          unless valid
            return [valid, message]
          end

          return true
        else
          super(value)
        end
      end

      def format(value)
        case value
        when Array
          value.map(&method(:format)).join(',')
        when Range
          "#{value.begin}-#{value.end}"
        else
          super(value)
        end
      end

    end

    class Shards < CommandMapper::Types::Str

      def validate(value)
        case value
        when Array
          if value.length > 2
            return [false, "cannot contain more tha two elements (#{value.inspect})"]
          end

          return true
        else
          super(value)
        end
      end

      def format(value)
        case value
        when Array
          "#{value[0]}/#{value[1]}"
        else
          super(value)
        end
      end

    end

    command "masscan" do
      option '--range', name: :range, value: true, repeats: true
      option '-p', name: :ports, value: {type: PortList.new}
      option '--banners', name: :banners
      option '--rate',    name: :rate, value: {type: Num.new}
      option '--conf',    name: :config_file, value: {type: InputFile.new}
      option '--resume',  name: :resume, value: {type: InputFile.new}
      option '--echo',    name: :echo, value: true
      option '--adapter', name: :adapter, value: true
      option '--adapter-ip',   name: :adapter_ip, value: true
      option '--adapter-port', name: :adapter_port, value: {type: Num.new}
      option '--adapter-mac',  name: :adapter_mac, value: true
      option '--adapter-vlan', name: :adapter_vlan, value: true
      option '--router-mac', name: :router_mac, value: true
      option '--ping', name: :ping
      option '--exclude', name: :exclude, value: true, repeats: true
      option '--excludefile', name: :exclude_file, value: {type: InputFile.new}, repeats: true
      option '--includefile', name: :include_file, value: {type: InputFile.new}, repeats: true
      option '--append-output', name: :append_output
      option '--iflist', name: :list_interfaces
      option '--retries', name: :retries, value: {type: Num.new}
      option '--nmap', name: :nmap_help
      option '--pcap-payloads', name: :pcap_payloads, value: {type: InputFile.new}
      option '--nmap-payloads', name: :nmap_payloads, value: {type: InputFile.new}

      option '--http-method',     name: :http_method, value: true
      option '--http-url',        name: :http_url, value: true
      option '--http-version',    name: :http_version, value: true
      option '--http-host',       name: :http_host, value: true
      option '--http-user-agent', name: :http_user_agent, value: true

      option '--http-field', value: {type: KeyValue.new}, repeats: true

      option '--http-field-remove', name: :http_field_remove
      option '--http-cookie', name: :http_cookie
      option '--http-payload', name: :http_payload

      option '--show', name: :show
      option '--noshow', name: :hide
      option '--pcap', name: :pcap, value: true
      option '--packet-trace', name: :packet_trace
      option '--pfring', name: :pfring
      option '--resume-index', name: :resume_index
      option '--resume-count', name: :resume_count
      option '--shards', name: :shards, value: {type: Shards.new}
      option '--rotate', name: :rotate, value: true
      option '--rotate-offset', name: :rotate_offset, value: true
      option '--rotate-size',   name: :rotate_size, value: true
      option '--rotate-dir',    name: :rotate_dir, value: {type: InputDir.new}
      option '--seed', name: :seed, value: {type: Num.new}
      option '--regress', name: :regress
      option '--ttl', name: :ttl, value: {type: Num.new}
      option '--wait', name: :wait, value: {type: Num.new}
      option '--offline', name: :offline
      option '-sL', name: :print_list
      option '--interactive', name: :interactive
      option '--output-format', name: :output_format, value: {
                                                        type: Enum[
                                                          :xml,
                                                          :binary,
                                                          :grepable,
                                                          :list,
                                                          :JSON
                                                        ]
                                                      }
      option '--output-filename', name: :output_file, value: true
      option '-oB', name: :output_binary, value: true
      option '-oX', name: :output_xml, value: true
      option '-oG', name: :output_grepable, value: true
      option '-oJ', name: :output_json, value: true
      option '-oL', name: :output_list, value: true
      option '--readscan', name: :read_scan, value: {type: InputFile.new}
      option '-V', name: :version
      option '-h', name: :help

      argument :ips, repeats: true
    end

  end
end