|  | 
|  | 1 | +# LDAP DN support classes | 
|  | 2 | +# | 
|  | 3 | + | 
|  | 4 | +## | 
|  | 5 | +# Objects of this class represent an LDAP DN. | 
|  | 6 | +# | 
|  | 7 | +# In LDAP-land, a DN ("Distinguished Name") is a unique identifier for an | 
|  | 8 | +# entry within an LDAP directory. It is made up of a number of other | 
|  | 9 | +# attributes strung together, to identify the entry in the tree. | 
|  | 10 | +# | 
|  | 11 | +# Each attribute that makes up a DN needs to have its value escaped so that | 
|  | 12 | +# the DN is valid. This class helps take care of that. | 
|  | 13 | +# | 
|  | 14 | +# A fully escaped DN needs to be unescaped when analysing its contents. This | 
|  | 15 | +# class also helps take care of that. | 
|  | 16 | +class Net::LDAP::DN | 
|  | 17 | +  ## | 
|  | 18 | +  # Initialize a DN, escaping as required. Pass in attributes in name/value | 
|  | 19 | +  # pairs. If there is a left over argument, it will be appended to the dn | 
|  | 20 | +  # without escaping (useful for a base string). | 
|  | 21 | +  #  | 
|  | 22 | +  # Most uses of this class will be to escape a DN, rather than to parse it, | 
|  | 23 | +  # so storing the dn as an escaped String and parsing parts as required with | 
|  | 24 | +  # a state machine seems sensible. | 
|  | 25 | +  def initialize(*args) | 
|  | 26 | +    buffer = StringIO.new | 
|  | 27 | + | 
|  | 28 | +    args.each_index do |index| | 
|  | 29 | +      buffer << "=" if index % 2 == 1 | 
|  | 30 | +      buffer << "," if index % 2 == 0 && index != 0 | 
|  | 31 | + | 
|  | 32 | +      if index < args.length - 1 || index % 2 == 1 | 
|  | 33 | +        buffer << Net::LDAP::DN.escape(args[index]) | 
|  | 34 | +      else | 
|  | 35 | +        buffer << args[index] | 
|  | 36 | +      end | 
|  | 37 | +    end | 
|  | 38 | + | 
|  | 39 | +    @dn = buffer.string | 
|  | 40 | +  end | 
|  | 41 | + | 
|  | 42 | +  ## | 
|  | 43 | +  # Parse a DN into key value pairs using ASN from | 
|  | 44 | +  # http://tools.ietf.org/html/rfc2253 section 3. | 
|  | 45 | +  # | 
|  | 46 | +  def each_pair | 
|  | 47 | +    state = :key | 
|  | 48 | +    key = StringIO.new | 
|  | 49 | +    value = StringIO.new | 
|  | 50 | +    hex_buffer = "" | 
|  | 51 | + | 
|  | 52 | +    @dn.each_char do |char| | 
|  | 53 | +      case state | 
|  | 54 | + | 
|  | 55 | +        when :key then case char | 
|  | 56 | +          when 'a'..'z','A'..'Z' then | 
|  | 57 | +            state = :key_normal | 
|  | 58 | +            key << char | 
|  | 59 | +          when '0'..'9' then | 
|  | 60 | +            state = :key_oid | 
|  | 61 | +            key << char | 
|  | 62 | +          when ' ' then state = :key | 
|  | 63 | +          else raise "DN badly formed" | 
|  | 64 | +        end | 
|  | 65 | +        when :key_normal then case char | 
|  | 66 | +          when '=' then state = :value | 
|  | 67 | +          when 'a'..'z','A'..'Z','0'..'9','-',' ' then key << char | 
|  | 68 | +          else raise "DN badly formed" | 
|  | 69 | +        end | 
|  | 70 | +        when :key_oid then case char | 
|  | 71 | +          when '=' then state = :value | 
|  | 72 | +          when '0'..'9','.',' ' then key << char | 
|  | 73 | +          else raise "DN badly formed" | 
|  | 74 | +        end | 
|  | 75 | + | 
|  | 76 | +        when :value then case char | 
|  | 77 | +          when '\\' then state = :value_normal_escape | 
|  | 78 | +          when '"' then state = :value_quoted | 
|  | 79 | +          when ' ' then state = :value | 
|  | 80 | +          when '#' then | 
|  | 81 | +            state = :value_hexstring | 
|  | 82 | +            value << char | 
|  | 83 | +          when ',' then | 
|  | 84 | +            state = :key | 
|  | 85 | +            yield key.string.strip, value.string.rstrip | 
|  | 86 | +            key = StringIO.new | 
|  | 87 | +            value = StringIO.new; | 
|  | 88 | +          else | 
|  | 89 | +            state = :value_normal | 
|  | 90 | +            value << char | 
|  | 91 | +        end | 
|  | 92 | + | 
|  | 93 | +        when :value_normal then case char | 
|  | 94 | +          when '\\' then state = :value_normal_escape | 
|  | 95 | +          when ',' then | 
|  | 96 | +            state = :key | 
|  | 97 | +            yield key.string.strip, value.string.rstrip | 
|  | 98 | +            key = StringIO.new | 
|  | 99 | +            value = StringIO.new; | 
|  | 100 | +          else value << char | 
|  | 101 | +        end | 
|  | 102 | +        when :value_normal_escape then case char | 
|  | 103 | +          when '0'..'9', 'a'..'f', 'A'..'F' then | 
|  | 104 | +            state = :value_normal_escape_hex | 
|  | 105 | +            hex_buffer = char | 
|  | 106 | +          else state = :value_normal; value << char | 
|  | 107 | +        end | 
|  | 108 | +        when :value_normal_escape_hex then case char | 
|  | 109 | +          when '0'..'9', 'a'..'f', 'A'..'F' then | 
|  | 110 | +            state = :value_normal | 
|  | 111 | +            value << "#{hex_buffer}#{char}".to_i(16).chr | 
|  | 112 | +          else raise "DN badly formed" | 
|  | 113 | +        end | 
|  | 114 | + | 
|  | 115 | +        when :value_quoted then case char | 
|  | 116 | +          when '\\' then state = :value_quoted_escape | 
|  | 117 | +          when '"' then state = :value_end | 
|  | 118 | +          else value << char | 
|  | 119 | +        end | 
|  | 120 | +        when :value_quoted_escape then case char | 
|  | 121 | +          when '0'..'9', 'a'..'f', 'A'..'F' then | 
|  | 122 | +            state = :value_quoted_escape_hex | 
|  | 123 | +            hex_buffer = char | 
|  | 124 | +          else state = :value_quoted; value << char | 
|  | 125 | +        end | 
|  | 126 | +        when :value_quoted_escape_hex then case char | 
|  | 127 | +          when '0'..'9', 'a'..'f', 'A'..'F' then | 
|  | 128 | +            state = :value_quoted | 
|  | 129 | +            value << "#{hex_buffer}#{char}".to_i(16).chr | 
|  | 130 | +          else raise "DN badly formed" | 
|  | 131 | +        end | 
|  | 132 | + | 
|  | 133 | +        when :value_hexstring then case char | 
|  | 134 | +          when '0'..'9', 'a'..'f', 'A'..'F' then | 
|  | 135 | +            state = :value_hexstring_hex | 
|  | 136 | +            value << char | 
|  | 137 | +          when ' ' then state = :value_end | 
|  | 138 | +          when ',' then | 
|  | 139 | +            state = :key | 
|  | 140 | +            yield key.string.strip, value.string.rstrip | 
|  | 141 | +            key = StringIO.new | 
|  | 142 | +            value = StringIO.new; | 
|  | 143 | +          else raise "DN badly formed" | 
|  | 144 | +        end | 
|  | 145 | +        when :value_hexstring_hex then case char | 
|  | 146 | +          when '0'..'9', 'a'..'f', 'A'..'F' then | 
|  | 147 | +            state = :value_hexstring | 
|  | 148 | +            value << char | 
|  | 149 | +          else raise "DN badly formed" | 
|  | 150 | +        end | 
|  | 151 | + | 
|  | 152 | +        when :value_end then case char | 
|  | 153 | +          when ' ' then state = :value_end | 
|  | 154 | +          when ',' then | 
|  | 155 | +            state = :key | 
|  | 156 | +            yield key.string.strip, value.string.rstrip | 
|  | 157 | +            key = StringIO.new | 
|  | 158 | +            value = StringIO.new; | 
|  | 159 | +          else raise "DN badly formed" | 
|  | 160 | +        end | 
|  | 161 | + | 
|  | 162 | +        else raise "Fell out of state machine" | 
|  | 163 | +      end | 
|  | 164 | +    end | 
|  | 165 | +     | 
|  | 166 | +    # Last pair | 
|  | 167 | +    if [:value, :value_normal, :value_hexstring, :value_end].include? state | 
|  | 168 | +      yield key.string.strip, value.string.rstrip | 
|  | 169 | +    else | 
|  | 170 | +      raise "DN badly formed" | 
|  | 171 | +    end | 
|  | 172 | +  end | 
|  | 173 | + | 
|  | 174 | +  ## | 
|  | 175 | +  # Returns the DN as an array in the form expected by the constructor. | 
|  | 176 | +  def to_a | 
|  | 177 | +    a = [] | 
|  | 178 | +    self.each_pair { |key, value| a << key << value } | 
|  | 179 | +    a | 
|  | 180 | +  end | 
|  | 181 | + | 
|  | 182 | +  ## | 
|  | 183 | +  # Return the DN as an escaped string. | 
|  | 184 | +  def to_s | 
|  | 185 | +    @dn | 
|  | 186 | +  end | 
|  | 187 | + | 
|  | 188 | +  # http://tools.ietf.org/html/rfc2253 section 2.4 lists these exceptions | 
|  | 189 | +  # for dn values. All of the following must be escaped in any normal | 
|  | 190 | +  # string using a single backslash ('\') as escape. | 
|  | 191 | +  # | 
|  | 192 | +  ESCAPES = { | 
|  | 193 | +    ','  => ',', | 
|  | 194 | +    '+'  => '+', | 
|  | 195 | +    '"'  => '"', | 
|  | 196 | +    '\\' => '\\', | 
|  | 197 | +    '<' => '<', | 
|  | 198 | +    '>' => '>', | 
|  | 199 | +    ';' => ';', | 
|  | 200 | +  } | 
|  | 201 | +  # Compiled character class regexp using the keys from the above hash, and | 
|  | 202 | +  # checking for a space or # at the start, or space at the end, of the | 
|  | 203 | +  # string. | 
|  | 204 | +  ESCAPE_RE = Regexp.new( | 
|  | 205 | +    "(^ |^#| $|[" +  | 
|  | 206 | +    ESCAPES.keys.map { |e| Regexp.escape(e) }.join +  | 
|  | 207 | +    "])") | 
|  | 208 | + | 
|  | 209 | +  ## | 
|  | 210 | +  # Escape a string for use in a DN value | 
|  | 211 | +  def self.escape(string) | 
|  | 212 | +    string.gsub(ESCAPE_RE) { |char| "\\" + ESCAPES[char] } | 
|  | 213 | +  end | 
|  | 214 | + | 
|  | 215 | +  ## | 
|  | 216 | +  # Proxy all other requests to the string object, because a DN is mainly | 
|  | 217 | +  # used within the library as a string | 
|  | 218 | +  def method_missing(method, *args, &block) | 
|  | 219 | +    @dn.send(method, *args, &block) | 
|  | 220 | +  end | 
|  | 221 | +end | 
0 commit comments