# -*- ruby encoding: utf-8 -*- ## # An LDAP Dataset. Used primarily as an intermediate format for converting # to and from LDIF strings and Net::LDAP::Entry objects. class Net::LDAP::Dataset < Hash ## # Dataset object comments. attr_reader :comments def initialize(*args, &block) # :nodoc: super @comments = [] end ## # Outputs an LDAP Dataset as an array of strings representing LDIF # entries. def to_ldif ary = [] ary += @comments unless @comments.empty? keys.sort.each do |dn| ary << "dn: #{dn}" attributes = self[dn].keys.map { |attr| attr.to_s }.sort attributes.each do |attr| self[dn][attr.to_sym].each do |value| if attr == "userpassword" or value_is_binary?(value) value = [value].pack("m").chomp.gsub(/\n/m, "\n ") ary << "#{attr}:: #{value}" else ary << "#{attr}: #{value}" end end end ary << "" end block_given? and ary.each { |line| yield line} ary end ## # Outputs an LDAP Dataset as an LDIF string. def to_ldif_string to_ldif.join("\n") end ## # Convert the parsed LDIF objects to Net::LDAP::Entry objects. def to_entries ary = [] keys.each do |dn| entry = Net::LDAP::Entry.new(dn) self[dn].each do |attr, value| entry[attr] = value end ary << entry end ary end ## # This is an internal convenience method to determine if a value requires # base64-encoding before conversion to LDIF output. The standard approach # in most LDAP tools is to check whether the value is a password, or if # the first or last bytes are non-printable. Microsoft Active Directory, # on the other hand, sometimes sends values that are binary in the middle. # # In the worst cases, this could be a nasty performance killer, which is # why we handle the simplest cases first. Ideally, we would also test the # first/last byte, but it's a bit harder to do this in a way that's # compatible with both 1.8.6 and 1.8.7. def value_is_binary?(value) # :nodoc: value = value.to_s return true if value[0] == ?: or value[0] == ?< value.each_byte { |byte| return true if (byte < 32) || (byte > 126) } false end private :value_is_binary? class << self class ChompedIO # :nodoc: def initialize(io) @io = io end def gets s = @io.gets s.chomp if s end end ## # Creates a Dataset object from an Entry object. Used mostly to assist # with the conversion of def from_entry(entry) dataset = Net::LDAP::Dataset.new hash = { } entry.each_attribute do |attribute, value| next if attribute == :dn hash[attribute] = value end dataset[entry.dn] = hash dataset end ## # Reads an object that returns data line-wise (using #gets) and parses # LDIF data into a Dataset object. def read_ldif(io) ds = Net::LDAP::Dataset.new io = ChompedIO.new(io) line = io.gets dn = nil while line new_line = io.gets if new_line =~ /^[\s]+/ line << " " << $' else nextline = new_line if line =~ /^#/ ds.comments << line yield :comment, line if block_given? elsif line =~ /^dn:[\s]*/i dn = $' ds[dn] = Hash.new { |k,v| k[v] = [] } yield :dn, dn if block_given? elsif line.empty? dn = nil yield :end, nil if block_given? elsif line =~ /^([^:]+):([\:]?)[\s]*/ # $1 is the attribute name # $2 is a colon iff the attr-value is base-64 encoded # $' is the attr-value # Avoid the Base64 class because not all Ruby versions have it. attrvalue = ($2 == ":") ? $'.unpack('m').shift : $' ds[dn][$1.downcase.to_sym] << attrvalue yield :attr, [$1.downcase.to_sym, attrvalue] if block_given? end line = nextline end end ds end end end require 'net/ldap/entry' unless defined? Net::LDAP::Entry