require 'xmlrpc/server'
require 'puppet/network/authorization'
require 'puppet/network/xmlrpc/processor'

module Puppet::Network::XMLRPC
  class ServletError < RuntimeError; end
  class WEBrickServlet < ::XMLRPC::WEBrickServlet
    include Puppet::Network::XMLRPCProcessor

    # This is a hackish way to avoid an auth message every time we have a
    # normal operation
    def self.log(msg)
      @logs ||= {}
      if @logs.include?(msg)
        @logs[msg] += 1
      else
        Puppet.info msg
        @logs[msg] = 1
      end
    end

    # Accept a list of handlers and register them all.
    def initialize(handlers)
      # the servlet base class does not consume any arguments
      # and its BasicServer base class only accepts a 'class_delim'
      # option which won't change in Puppet at all
      # thus, we don't need to pass any args to our base class,
      # and we can consume them all ourselves
      super()

      setup_processor

      # Set up each of the passed handlers.
      handlers.each do |handler|
        add_handler(handler.class.interface, handler)
      end
    end

    # Handle the actual request.  We can't use the super() method, because
    # we need to pass a ClientRequest object to process so we can do
    # authorization.  It's the only way to stay thread-safe.
    def service(request, response)
      if @valid_ip
        raise WEBrick::HTTPStatus::Forbidden unless @valid_ip.any? { |ip| request.peeraddr[3] =~ ip }
      end

      if request.request_method != "POST"
        raise WEBrick::HTTPStatus::MethodNotAllowed,
          "unsupported method `#{request.request_method}'."
      end

      raise WEBrick::HTTPStatus::BadRequest if parse_content_type(request['Content-type']).first != "text/xml"

      length = (request['Content-length'] || 0).to_i

      raise WEBrick::HTTPStatus::LengthRequired unless length > 0

      data = request.body

      raise WEBrick::HTTPStatus::BadRequest if data.nil? or data.size != length

      resp = process(data, client_request(request))
      raise WEBrick::HTTPStatus::InternalServerError if resp.nil? or resp.size <= 0

      response.status = 200
      response['Content-Length'] = resp.size
      response['Content-Type']   = "text/xml; charset=utf-8"
      response.body = resp
    end

    private

    # Generate a ClientRequest object for later validation.
    def client_request(request)
      if peer = request.peeraddr
        client = peer[2]
        clientip = peer[3]
      else

              raise ::XMLRPC::FaultException.new(
                
          ERR_UNCAUGHT_EXCEPTION,
        
          "Could not retrieve client information"
        )
      end

      # If they have a certificate (which will almost always be true)
      # then we get the hostname from the cert, instead of via IP
      # info
      valid = false
      if cert = request.client_cert
        nameary = cert.subject.to_a.find { |ary|
          ary[0] == "CN"
        }

        if nameary.nil?
          Puppet.warning "Could not retrieve server name from cert"
        else
          unless client == nameary[1]
            Puppet.debug "Overriding #{client} with cert name #{nameary[1]}"
            client = nameary[1]
          end
          valid = true
        end
      end

      info = Puppet::Network::ClientRequest.new(client, clientip, valid)

      info
    end
  end
end

