require 'ldap/server/result'

module LDAP
class Server

  class Operation

    # Return true if connection is not authenticated

    def anonymous?
      @connection.binddn.nil?
    end

    # Split dn string into its component parts, returning
    #  [ {attr=>val}, {attr=>val}, ... ]
    #
    # This is pretty horrible legacy stuff from X500; see RFC2253 for the
    # full gore. It's stupid that the LDAP protocol sends the DN in string
    # form, rather than in ASN1 form (as it does with search filters, for
    # example), even though the DN syntax is defined in terms of ASN1!
    #
    # Attribute names are downcased, but values are not. For any
    # case-insensitive attributes it's up to you to downcase them.
    #
    # Note that only v2 clients should add extra space around the comma.
    # This is accepted, and so is semicolon instead of comma, but the
    # full RFC1779 backwards-compatibility rules (e.g. quoted values)
    # are not implemented.
    #
    # I *think* these functions will work correctly with UTF8-encoded
    # characters, given that a multibyte UTF8 character does not contain
    # the bytes 00-7F and therefore we cannot confuse '\', '+' etc

    def self.split_dn(dn)
      # convert \\ to \5c, \+ to \2b etc
      dn.gsub!(/\\([ #,+"\\<>;])/) { |match| format "\\%02x", match[1].ord }

      # Now we know that \\ and \, do not exist, it's safe to split
      parts = dn.split(/\s*[,;]\s*/)

      parts.collect do |part|
        res = {}

        # Split each part into attr=val+attr=val
        avs = part.split(/\+/)

        avs.each do |av|
          # These should all be of form attr=value
          unless av =~ /^([^=]+)=(.*)$/
            raise LDAP::ResultError::ProtocolError, "Bad DN component: #{av}"
          end
          attr, val = $1.downcase, $2
          # Now we can decode those bits
          attr.gsub!(/\\([a-f0-9][a-f0-9])/i) { $1.hex.chr }
          val.gsub!(/\\([a-f0-9][a-f0-9])/i) { $1.hex.chr }
          res[attr] = val
        end
        res
      end
    end

    # Reverse of split_dn. Join [elements...]
    # where each element can be {attr=>val,...} or [[attr,val],...]
    # or just [attr,val]

    def self.join_dn(elements)
      dn = ""
      elements.each do |elem|
        av = ""
        elem = [elem] if elem[0].is_a?(String)
        elem.each do |attr,val|
          av << "+" unless av == ""

          av << attr << "=" <<
                     val.sub(/^([# ])/, '\\\\\\1').
                     sub(/( )$/, '\\\\\\1').
                     gsub(/([,+"\\<>;])/, '\\\\\\1')
        end
        dn << "," unless dn == ""
        dn << av
      end
      dn
    end

  end # class Operation

end # class Server
end # module LDAP
