#
#   Copyright 2012 Wade Alcorn wade@bindshell.net
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
#   You may obtain a copy of the License at
#
#       http://www.apache.org/licenses/LICENSE-2.0
#
#   Unless required by applicable law or agreed to in writing, software
#   distributed under the License is distributed on an "AS IS" BASIS,
#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#   See the License for the specific language governing permissions and
#   limitations under the License.
#

module BeEF
  module Core

    class Server

      include Singleton

      # @note Grabs the version of beef the framework is deployed on
      VERSION = BeEF::Core::Configuration.instance.get('beef.version')

      attr_reader :root_dir, :url, :configuration, :command_urls, :mounts, :semaphore

      def initialize
        @configuration = BeEF::Core::Configuration.instance
        beef_host = @configuration.get("beef.http.public") || @configuration.get("beef.http.host")
        @url = "http://#{beef_host}:#{@configuration.get("beef.http.port")}"
        @root_dir = File.expand_path('../../../', __FILE__)
        @command_urls = {}
        @mounts = {}
        @rack_app
        @semaphore = Mutex.new
      end

      def to_h
        {
            'beef_version' => VERSION,
            'beef_url' => @url,
            'beef_root_dir' => @root_dir,
            'beef_host' => @configuration.get('beef.http.host'),
            'beef_port' => @configuration.get('beef.http.port'),
            'beef_dns' => @configuration.get('beef.http.dns'),
            'beef_hook' => @configuration.get('beef.http.hook_file')
        }
      end

      # Mounts a handler, can either be a hard or soft mount
      # @param [String] url The url to mount
      # @param [Class] http_handler_class Class to call once mount is triggered
      # @param args Arguments to pass to the http handler class
      def mount(url, http_handler_class, args = nil)
        # argument type checking
        raise Exception::TypeError, '"url" needs to be a string' if not url.string?

        if args == nil
          mounts[url] = http_handler_class
        else
          mounts[url] = http_handler_class, *args
        end
        print_debug("Server: mounted handler '#{url}'")
      end

      # Unmounts handler
      # @param [String] url URL to unmount.
      def unmount(url)
        raise Exception::TypeError, '"url" needs to be a string' if not url.string?
        @mounts.delete(url)
      end

      # Reload the URL map (used by the NetworkStack AssetHandler to mount new URLs at runtime)
      def remap
        @rack_app.remap(@mounts)
      end

      # Prepares the BeEF http server.
      def prepare
        # Create http handler for the javascript hook file
        self.mount("#{@configuration.get("beef.http.hook_file")}", BeEF::Core::Handlers::HookedBrowsers.new)

        # Dynamically get the list of all the http handlers using the API and register them
        BeEF::API::Registrar.instance.fire(BeEF::API::Server, 'mount_handler', self)

        # Rack mount points
        @rack_app = Rack::URLMap.new(@mounts)

        if not @http_server

          # Set the logging level of Thin to match the config 
          Thin::Logging.silent = true
          if @configuration.get('beef.http.debug') == true
            Thin::Logging.silent = false
            Thin::Logging.debug = true
          end

          # Create the BeEF http server
          @http_server = Thin::Server.new(
              @configuration.get('beef.http.host'),
              @configuration.get('beef.http.port'),
              @rack_app)
        end
      end

      # Starts the BeEF http server
      def start
        begin
          @http_server.start # starts the web server
        rescue RuntimeError => e
          if e.message =~ /no acceptor/ # the port is in use
            print_error "Another process is already listening on port #{@configuration.get('beef.http.port')}."
            print_error "Is BeEF already running? Exiting..."
            exit 127
          else
            raise
          end
        end
      end

    end
  end
end
