# frozen_string_literal: true

require 'cgi'
require 'htmlentities'

module PWN
  module Plugins
    # This plugin was created to generate various characters for fuzzing
    module Char
      # Supported Method Parameters::
      # PWN::Plugins::Char.generate_by_range(
      #   from: 'required - integer to start from',
      #   to: 'required - integer to end UTF-8 generation'
      # )

      public_class_method def self.generate_by_range(opts = {})
        from = opts[:from].to_i
        to = opts[:to].to_i

        char_arr = []

        encoder_arr = list_encoders
        encoder_arr.delete('UTF-8')

        (from..to).each do |this_int|
          char_hash = {}

          this_bin = format('%08d', this_int.to_s(2))
          this_dec = this_int
          this_hex = format('%02x', this_int)
          # this_long_int = [this_int].pack('L>').unpack1('H*').scan(/../).map { |h| '\x' + h }.join
          this_long_int = [this_int].pack('L>').unpack1('H*').scan(/../).map { |h| "\\x#{h}" }.join
          this_oct = format('\%03d', this_int.to_s(8))
          # this_short_int = [this_int].pack('S>').unpack1('H*').scan(/../).map { |h| '\x' + h }.join
          this_short_int = [this_int].pack('S>').unpack1('H*').scan(/../).map { |h| "\\x#{h}" }.join
          this_utf8 = [this_int].pack('U*')

          begin
            # Begins breaking once this_int reaches 55296
            this_html_entity = HTMLEntities.new.encode(this_utf8)
            this_html_entity_dec = HTMLEntities.new.encode(this_utf8, :decimal)
            this_html_entity_hex = HTMLEntities.new.encode(this_utf8, :hexadecimal)
          rescue ArgumentError
            this_html_entity = "***max_int<#{this_int}"
            this_html_entity_dec = "***max_int<#{this_int}"
            thishtml_entity_hex = "***max_int<#{this_int}"
            next
          end

          this_url = CGI.escape(this_utf8)

          # To date Base 2 - Base 36 is supported:
          # (0..999).each {|base| begin; puts "#{base} => #{this_dec.to_s(base)}"; rescue; next; end }
          char_hash[:bin] = { char: this_bin, encoder: nil }
          char_hash[:dec] = { char: this_dec, encoder: nil }
          char_hash[:hex] = { char: this_hex, encoder: nil }
          char_hash[:html_entity] = { char: this_html_entity, encoder: nil }
          char_hash[:html_entity_dec] = { char: this_html_entity_dec, encoder: nil }
          char_hash[:html_entity_hex] = { char: this_html_entity_hex, encoder: nil }
          char_hash[:long_int] = { char: this_long_int, encoder: nil }
          char_hash[:oct] = { char: this_oct, encoder: nil }
          char_hash[:short_int] = { char: this_short_int, encoder: nil }
          char_hash[:url] = { char: this_url, encoder: nil }
          char_hash[:utf8] = { char: this_utf8, encoder: 'UTF-8' }

          encoder_arr.each do |encoder|
            this_encoder_key = encoder.downcase.tr('-', '_').to_sym
            begin
              char_hash[this_encoder_key] = {
                char: this_utf8.encode(encoder, 'UTF-8'),
                encoder: encoder
              }
            rescue Encoding::InvalidByteSequenceError
              char_hash[this_encoder_key] = {
                char: "***invalid_byte_seq@#{this_int}",
                encoder: encoder
              }
              next
            rescue Encoding::UndefinedConversionError
              char_hash[this_encoder_key] = {
                char: "***max_int<#{this_int}",
                encoder: encoder
              }
              next
            rescue Encoding::ConverterNotFoundError
              char_hash[this_encoder_key] = {
                char: '***convertor_not_found',
                encoder: encoder
              }
              next
            end
          end

          sorted_char_hash = char_hash.sort.to_h
          char_arr.push(sorted_char_hash)
        end

        char_arr
      rescue StandardError => e
        raise e
      end

      # Supported Method Parameters::
      # PWN::Plugins::Char.c0_controls_latin_basic

      public_class_method def self.c0_controls_latin_basic
        generate_by_range(from: 0, to: 127)
      rescue StandardError => e
        raise e
      end

      # Supported Method Parameters::
      # PWN::Plugins::Char.c1_controls_latin_supplement

      public_class_method def self.c1_controls_latin_supplement
        generate_by_range(from: 128, to: 255)
      rescue StandardError => e
        raise e
      end

      # Supported Method Parameters::
      # PWN::Plugins::Char.latin_extended_a

      public_class_method def self.latin_extended_a
        generate_by_range(from: 256, to: 383)
      rescue StandardError => e
        raise e
      end

      # Supported Method Parameters::
      # PWN::Plugins::Char.latin_extended_b

      public_class_method def self.latin_extended_b
        generate_by_range(from: 384, to: 591)
      rescue StandardError => e
        raise e
      end

      # Supported Method Parameters::
      # PWN::Plugins::Char.spacing_modifiers

      public_class_method def self.spacing_modifiers
        generate_by_range(from: 688, to: 767)
      rescue StandardError => e
        raise e
      end

      # Supported Method Parameters::
      # PWN::Plugins::Char.diacritical_marks

      public_class_method def self.diacritical_marks
        generate_by_range(from: 768, to: 879)
      rescue StandardError => e
        raise e
      end

      # Supported Method Parameters::
      # PWN::Plugins::Char.greek_coptic

      public_class_method def self.greek_coptic
        generate_by_range(from: 880, to: 1023)
      rescue StandardError => e
        raise e
      end

      # Supported Method Parameters::
      # PWN::Plugins::Char.cyrillic_basic

      public_class_method def self.cyrillic_basic
        generate_by_range(from: 1024, to: 1279)
      rescue StandardError => e
        raise e
      end

      # Supported Method Parameters::
      # PWN::Plugins::Char.cyrillic_supplement

      public_class_method def self.cyrillic_supplement
        generate_by_range(from: 1280, to: 1327)
      rescue StandardError => e
        raise e
      end

      # Supported Method Parameters::
      # PWN::Plugins::Char.punctuation

      public_class_method def self.punctuation
        generate_by_range(from: 8192, to: 8303)
      rescue StandardError => e
        raise e
      end

      # Supported Method Parameters::
      # PWN::Plugins::Char.currency_symbols

      public_class_method def self.currency_symbols
        generate_by_range(from: 8352, to: 8399)
      rescue StandardError => e
        raise e
      end

      # Supported Method Parameters::
      # PWN::Plugins::Char.letterlike_symbols

      public_class_method def self.letterlike_symbols
        generate_by_range(from: 8448, to: 8527)
      rescue StandardError => e
        raise e
      end

      # Supported Method Parameters::
      # PWN::Plugins::Char.arrows

      public_class_method def self.arrows
        generate_by_range(from: 8592, to: 8703)
      rescue StandardError => e
        raise e
      end

      # Supported Method Parameters::
      # PWN::Plugins::Char.math_operators

      public_class_method def self.math_operators
        generate_by_range(from: 8704, to: 8959)
      rescue StandardError => e
        raise e
      end

      # Supported Method Parameters::
      # PWN::Plugins::Char.box_drawings

      public_class_method def self.box_drawings
        generate_by_range(from: 9312, to: 9599)
      rescue StandardError => e
        raise e
      end

      # Supported Method Parameters::
      # PWN::Plugins::Char.block_elements

      public_class_method def self.block_elements
        generate_by_range(from: 9600, to: 9631)
      rescue StandardError => e
        raise e
      end

      # Supported Method Parameters::
      # PWN::Plugins::Char.geometric_shapes

      public_class_method def self.geometric_shapes
        generate_by_range(from: 9632, to: 9727)
      rescue StandardError => e
        raise e
      end

      # Supported Method Parameters::
      # PWN::Plugins::Char.misc_symbols

      public_class_method def self.misc_symbols
        generate_by_range(from: 9728, to: 9983)
      rescue StandardError => e
        raise e
      end

      # Supported Method Parameters::
      # PWN::Plugins::Char.dingbats

      public_class_method def self.dingbats
        generate_by_range(from: 9984, to: 10_175)
      rescue StandardError => e
        raise e
      end

      # Supported Method Parameters::
      # PWN::Plugins::Char.bubble_ip(
      #   ip: 'required - ip address to transform'
      # )

      public_class_method def self.bubble_ip(opts = {})
        ip = opts[:ip].to_s

        bubble_ip = ''
        ip_arr = ip.split('.')
        dot = "\u3002"
        ip_arr.each.with_index do |octet_str, this_index|
          octet_str.each_char do |digit_str|
            case digit_str.to_i
            when 0
              bubble_ip = "#{bubble_ip}\u24ea"
            when 1
              bubble_ip = "#{bubble_ip}\u2460"
            when 2
              bubble_ip = "#{bubble_ip}\u2461"
            when 3
              bubble_ip = "#{bubble_ip}\u2462"
            when 4
              bubble_ip = "#{bubble_ip}\u2463"
            when 5
              bubble_ip = "#{bubble_ip}\u2464"
            when 6
              bubble_ip = "#{bubble_ip}\u2465"
            when 7
              bubble_ip = "#{bubble_ip}\u2466"
            when 8
              bubble_ip = "#{bubble_ip}\u2467"
            when 9
              bubble_ip = "#{bubble_ip}\u2468"
            end
          end
          bubble_ip = "#{bubble_ip}#{dot}" if (this_index + 1) < ip_arr.length
        end

        bubble_ip
      rescue StandardError => e
        raise e
      end

      # Supported Method Parameters::
      # encoder_arr = PWN::Plugins::Char.list_encoders

      public_class_method def self.list_encoders
        encoder_arr = []

        Encoding.list.each do |encoder|
          encoder_arr.push(encoder.name)
        end

        encoder_arr.sort
      rescue StandardError => e
        raise e
      end

      # Supported Method Parameters::
      # PWN::Plugins::Char.generate_encoded_files(
      #   from: 'required - integer to start from',
      #   to: 'required - integer to end UTF-8 generation',
      #   output_dir: 'required - folder to create files'
      # )

      public_class_method def self.generate_encoded_files(opts = {})
        from = opts[:from].to_i
        to = opts[:to].to_i
        output_dir = opts[:output_dir] if Dir.exist?(opts[:output_dir])

        char_arr = generate_by_range(from: 0, to: 0).first
        char_keys = char_arr.keys
        char_keys.each do |char_key|
          encoder = char_arr[char_key][:encoder]
          this_file = "#{output_dir}/#{from}_#{to}_#{encoder}.txt"

          case char_key
          when :bin, :dec, :hex, :html_entity, :html_entity_dec, :html_entity_hex, :long_int, :oct, :short_int, :url
            file_instr = 'wb'
          else
            file_instr = "wb:#{encoder}"
          end

          File.open(this_file, file_instr) do |f|
            generate_by_range(from: from, to: to).each do |char_hash|
              case char_key
              when :bin, :dec, :hex, :html_entity, :html_entity_dec, :html_entity_hex, :long_int, :oct, :short_int, :url
                f.puts char_hash[char_key][:char]
              else
                f.puts char_hash[char_key][:char] unless char_hash[char_key][:char].nil? || char_hash[char_key][:char].encode('utf-8').include?('***')
              end
            end
          end

          if File.empty?(this_file)
            File.unlink(this_file)
          else
            print '.'
          end
        rescue StandardError => e
          puts "FILE GENERATION ATTEMPT OF: #{this_file} RESULTED THE FOLLOWING ERROR:"
          puts "#{e.class}: #{e.message}\n#{e.backtrace}\n\n\n"
          File.unlink(this_file) if File.empty?(this_file)
          next
        end
        print "\n"
      rescue StandardError => e
        raise e
      end

      # Author(s):: 0day Inc. <support@0dayinc.com>

      public_class_method def self.authors
        "AUTHOR(S):
          0day Inc. <support@0dayinc.com>
        "
      end

      # Display Usage for this Module

      public_class_method def self.help
        puts "USAGE:
          char_arr = #{self}.generate_by_range(
            from: 'required - integer to start from',
            to: 'required - integer to end char generation'
          )

          #{self}.c0_controls_latin_basic

          #{self}.c1_controls_latin_supplement

          #{self}.latin_extended_a

          #{self}.latin_extended_b

          #{self}.spacing_modifiers

          #{self}.diacritical_marks

          #{self}.greek_coptic

          #{self}.cyrillic_basic

          #{self}.cyrillic_supplement

          #{self}.punctuation

          #{self}.currency_symbols

          #{self}.letterlike_symbols

          #{self}.arrows

          #{self}.math_operators

          #{self}.box_drawings

          #{self}.block_elements

          #{self}.geometric_shapes

          #{self}.misc_symbols

          #{self}.dingbats

          #{self}.bubble_ip(
            ip: 'required - ip address to transform'
          )

          encoder_arr = #{self}.list_encoders

          #{self}.generate_encoded_files(
            from: 'required - integer to start from',
            to: 'required - integer to end UTF-8 generation',
            output_dir: 'required - folder to create files'
          )

          #{self}.authors
        "
      end
    end
  end
end
