#
# Copyright (c) 2006-2024 Wade Alcorn - wade@bindshell.net
# Browser Exploitation Framework (BeEF) - https://beefproject.com
# See the file 'doc/COPYING' for copying permission
#
module BeEF
  module Core
    module AutorunEngine
      class Parser
        include Singleton

        def initialize
          @config = BeEF::Core::Configuration.instance
        end

        BROWSER = %w[FF C IE S O ALL]
        OS = %w[Linux Windows OSX Android iOS BlackBerry ALL]
        VERSION = ['<', '<=', '==', '>=', '>', 'ALL', 'Vista', 'XP']
        CHAIN_MODE = %w[sequential nested-forward]
        MAX_VER_LEN = 15

        def parse(name, author, browser, browser_version, os, os_version, modules, execution_order, execution_delay, chain_mode)
          raise ArgumentError, "Invalid rule name: #{name}" unless BeEF::Filters.is_non_empty_string?(name)
          raise ArgumentError, "Invalid author name: #{author}" unless BeEF::Filters.is_non_empty_string?(author)
          raise ArgumentError, "Invalid chain_mode definition: #{chain_mode}" unless CHAIN_MODE.include?(chain_mode)
          raise ArgumentError, "Invalid os definition: #{os}" unless OS.include?(os)

          unless modules.size == execution_delay.size
            raise ArgumentError, "Number of execution_delay values (#{execution_delay.size}) must be consistent with number of modules (#{modules.size})"
          end
          execution_delay.each { |delay| raise TypeError, "Invalid execution_delay value: #{delay}. Values must be Integers." unless delay.is_a?(Integer) }

          unless modules.size == execution_order.size
            raise ArgumentError, "Number of execution_order values (#{execution_order.size}) must be consistent with number of modules (#{modules.size})"
          end
          execution_order.each { |order| raise TypeError, "Invalid execution_order value: #{order}. Values must be Integers." unless order.is_a?(Integer) }

          # if multiple browsers were specified, check each browser
          if browser.is_a?(Array)
            browser.each do |b|
              raise ArgumentError, "Invalid browser definition: #{browser}" unless BROWSER.include?(b)
            end
          # else, if only one browser was specified, check browser and browser version
          else
            raise ArgumentError, "Invalid browser definition: #{browser}" unless BROWSER.include?(browser)

            if browser_version != 'ALL' && !(VERSION.include?(browser_version[0, 2].gsub(/\s+/, '')) &&
                  BeEF::Filters.is_valid_browserversion?(browser_version[2..-1].gsub(/\s+/, '')) && browser_version.length < MAX_VER_LEN)
              raise ArgumentError, "Invalid browser_version definition: #{browser_version}"
            end
          end

          if os_version != 'ALL' && !(VERSION.include?(os_version[0, 2].gsub(/\s+/, '')) &&
                  BeEF::Filters.is_valid_osversion?(os_version[2..-1].gsub(/\s+/, '')) && os_version.length < MAX_VER_LEN)
            return ArgumentError, "Invalid os_version definition: #{os_version}"
          end

          # check if module names, conditions and options are ok
          modules.each do |cmd_mod|
            mod = BeEF::Core::Models::CommandModule.where(name: cmd_mod['name']).first

            raise "The specified module name (#{cmd_mod['name']}) does not exist" if mod.nil?

            modk = BeEF::Module.get_key_by_database_id(mod.id)
            mod_options = BeEF::Module.get_options(modk)

            opt_count = 0
            mod_options.each do |opt|
              if opt['name'] != cmd_mod['options'].keys[opt_count]
                raise ArgumentError, "The specified option (#{cmd_mod['options'].keys[opt_count]}) for module (#{cmd_mod['name']}) was not specified"
              end

              opt_count += 1
            end
          end

          true
        end
      end
    end
  end
end
