Module: LDAP::LDIF

### Home Classes Methods

### In Files

- lib/ldap/ldif.rb

### Namespace

- CLASS LDAP::LDIF::Entry
- CLASS LDAP::LDIF::LDIFError
- CLASS LDAP::LDIF::Mod

### Methods

- ::mods_to_ldif
- ::parse_entry
- ::parse_file

### Class/Module Index

Quicksearch

- LDAP
- LDAP::Conn
- LDAP::Control
- LDAP::Entry
- LDAP::LDIF
- LDAP::LDIF::Entry
- LDAP::LDIF::LDIFError
- LDAP::LDIF::Mod
- LDAP::Mod
- LDAP::Record
- LDAP::Schema

No matching classes.

# LDAP::LDIF

This module provides the ability to process LDIF entries and files.

### Constants

LINE_LENGTH

### Public Class Methods

mods_to_ldif( dn, *mods ) click to toggle source

Given the DN, `dn`, convert a single LDAP::Mod or an array of LDAP::Mod objects, given in `mods`, to LDIF.

    # File lib/ldap/ldif.rb, line 493
    def LDIF.mods_to_ldif( dn, *mods )
      ldif = "dn: %s\nchangetype: modify\n" % dn
      plural = false

      mods.flatten.each do |mod|
        # TODO: Need to dynamically assemble this case statement to add
        # OpenLDAP's increment change type, etc.
        change_type = case mod.mod_op & ~LDAP_MOD_BVALUES
          when LDAP_MOD_ADD     then 'add'
          when LDAP_MOD_DELETE  then 'delete'
          when LDAP_MOD_REPLACE then 'replace'
        end

        ldif << "-\n" if plural
        ldif << LDIF.to_ldif( change_type, [mod.mod_type] )
        ldif << LDIF.to_ldif( mod.mod_type, mod.mod_vals )

        plural = true
      end

      LDIF::Mod.new( ldif )
    end

parse_entry( lines ) click to toggle source

Parse the LDIF entry contained in `lines` and return an LDAP::Record object. `lines` should be an object that responds to each, such as a string or an array of lines, separated by n characters.

    # File lib/ldap/ldif.rb, line 174
    def LDIF.parse_entry( lines )
      header = true
      comment = false
      change_type = nil
      sep = nil
      attr = nil
      bvalues = []
      controls = nil
      hash = {}
      mods = {}
      mod_type = nil

      lines.each do |line|
        # Skip (continued) comments.
        if line =~ /^#/ || ( comment && line[0..0] == ' ' )
          comment = true
          next
        end

        # Skip blank lines.
        next if line =~ /^$/

        # Reset mod type if this entry has more than one mod to make.
        # A '-' continuation is only valid if we've already had a
        # 'changetype: modify' line.
        if line =~ /^-$/ && change_type == LDAP_MOD_REPLACE
          next
        end

        line.chomp!

        # N.B. Attributes and values can be separated by one or two colons,
        # or one colon and a '<'. Either of these is then followed by zero
        # or one spaces.
        if md = line.match( /^[^ ].*?((:[:<]?) ?)/ )

          # If previous value was Base64-encoded and is not continued,
          # we need to decode it now.
          if sep == '::'
            if mod_type
              mods[mod_type][attr][-1] =
                base64_decode( mods[mod_type][attr][-1] )
              bvalues << attr if unsafe_char?( mods[mod_type][attr][-1] )
            else
              hash[attr][-1] = base64_decode( hash[attr][-1] )
              bvalues << attr if unsafe_char?( hash[attr][-1] )
            end
          end

          # Found a attr/value line.
          attr, val = line.split( md[1], 2 )
          attr.downcase!

          # Attribute must be ldap-oid / (ALPHA *(attr-type-chars))
          if attr !~ /^(?:(?:\d+\.)*\d+|[[:alnum:]-]+)(?:;[[:alnum:]-]+)*$/
            raise LDIFError, "Invalid attribute: #{attr}"
          end

          if attr == 'dn'
            header = false
            change_type = nil
            controls = []
          end
          sep = md[2]

          val = read_file( val ) if sep == ':<'

          case attr
          when 'version'
            # Check the LDIF version.
            if header
              if val != '1'
                raise LDIFError, "Unsupported LDIF version: #{val}"
              else
                header = false
                next
              end
            end

          when 'changetype'
            change_type = case val
              when 'add'       then LDAP_MOD_ADD
              when 'delete'    then LDAP_MOD_DELETE
              when 'modify'    then LDAP_MOD_REPLACE
              when /^modr?dn$/ then :MODRDN
            end

            raise LDIFError, "Invalid change type: #{attr}" unless change_type

          when 'add', 'delete', 'replace'
            unless change_type == LDAP_MOD_REPLACE
              raise LDIFError, "Cannot #{attr} here."
            end

            mod_type = case attr
              when 'add'     then LDAP_MOD_ADD
              when 'delete'  then LDAP_MOD_DELETE
              when 'replace' then LDAP_MOD_REPLACE
            end

            # In this case val is actually an attribute and should be lowercased.
            mods[mod_type] ||= {}
            mods[mod_type][val.downcase] ||= []

          when 'control'
            oid, criticality = val.split( / /, 2 )

            unless oid =~ /(?:\d+\.)*\d+/
              raise LDIFError, "Bad control OID: #{oid}"
            end

            if criticality
              md = criticality.match( /(:[:<]?) ?/ )
              ctl_sep = md[1] if md
              criticality, value = criticality.split( /:[:<]? ?/, 2 )

              if criticality !~ /^(?:true|false)$/
                raise LDIFError, "Bad control criticality: #{criticality}"
              end

              # Convert 'true' or 'false'. to_boolean would be nice. :-)
              criticality = eval( criticality )
            end

            if value
              value = base64_decode( value ) if ctl_sep == '::'
              value = read_file( value ) if ctl_sep == ':<'
              value = Control.encode( value )
            end

            controls << Control.new( oid, value, criticality )

          else
            # Convert modrdn's deleteoldrdn from '1' to true, anything else
            # to false. Should probably raise an exception if not '0' or '1'.
            #
            if change_type == :MODRDN && attr == 'deleteoldrdn'
              val = val == '1' ? true : false
            end

            if change_type == LDAP_MOD_REPLACE
              mods[mod_type][attr] << val
            else
              hash[attr] ||= []
              hash[attr] << val
            end

            comment = false

            # Make a note of this attribute if value is binary.
            bvalues << attr if unsafe_char?( val )

          end

        else
          # Check last line's separator: if not a binary value, the
          # continuation line must be indented. If a comment makes it this
          # far, that's also an error.
          #
          if sep == ':' && line[0..0] != ' ' || comment
            raise LDIFError, "Improperly continued line: #{line}"
          end

          # OK; this is a valid continuation line.

          # Append line except for initial space.
          line[0] = '' if line[0..0] == ' '

          if change_type == LDAP_MOD_REPLACE
            # Append to last value of current mod type.
            mods[mod_type][attr][-1] << line
          else
            # Append to last value.
            hash[attr][-1] << line
          end
        end

      end

      # If last value in LDIF entry was Base64-encoded, we need to decode
      # it now.
      if sep == '::'
        if mod_type
          mods[mod_type][attr][-1] =
            base64_decode( mods[mod_type][attr][-1] )
          bvalues << attr if unsafe_char?( mods[mod_type][attr][-1] )
        else
          hash[attr][-1] = base64_decode( hash[attr][-1] )
          bvalues << attr if unsafe_char?( hash[attr][-1] )
        end
      end

      # Remove and remember DN.
      dn = hash.delete( 'dn' )[0]

      # This doesn't really matter, but let's be anal about it, because it's
      # not an attribute and doesn't belong here.
      bvalues.delete( 'dn' )

      # If there's no change type, it's just plain LDIF data, so we'll treat
      # it like an addition.
      change_type ||= LDAP_MOD_ADD

      case change_type
      when LDAP_MOD_ADD
        mods[LDAP_MOD_ADD] = []

        hash.each do |attr_local, val|
          if bvalues.include?( attr_local )
            ct = LDAP_MOD_ADD | LDAP_MOD_BVALUES
          else
            ct = LDAP_MOD_ADD
          end

          mods[LDAP_MOD_ADD] << LDAP.mod( ct, attr_local, val )
        end

      when LDAP_MOD_DELETE
        # Nothing to do.

      when LDAP_MOD_REPLACE
        raise LDIFError, "mods should not be empty" if mods == {}

        new_mods = {}

        mods.each do |mod_type_local,attrs|
          attrs.each_key do |attr_local|
            if bvalues.include?( attr_local )
              mt = mod_type_local | LDAP_MOD_BVALUES
            else
              mt = mod_type_local
            end

            new_mods[mt] ||= {}
            new_mods[mt][attr_local] = mods[mod_type_local][attr_local]
          end
        end

        mods = new_mods

      when :MODRDN
        # Nothing to do.

      end

      Record.new( dn, change_type, hash, mods, controls )
    end

parse_file( file, sort=false ) click to toggle source

Open and parse a file containing LDIF entries. `file` should be a string containing the path to the file. If `sort` is true, the resulting array of LDAP::Record objects will be sorted on DN length, which can be useful to avoid a later attempt to process an entry whose parent does not yet exist. This can easily happen if your LDIF file is unordered, which is likely if it was produced with a tool such as slapcat(8).

If a block is given, each LDAP::Record object will be yielded to the block and nil will be returned instead of the array. This is much less memory-intensive when parsing a large LDIF file.

    # File lib/ldap/ldif.rb, line 434
    def LDIF.parse_file( file, sort=false ) # :yield: record

      File.open( file ) do |f|
        entries = []
        entry = false
        header = true
        version = false

        while line = f.gets

          if line =~ /^dn:/
            header = false

            if entry && ! version
              if block_given?
                yield parse_entry( entry )
              else
                entries << parse_entry( entry )
              end
            end

            if version
              entry << line
              version = false
            else
              entry = [ line ]
            end

            next
          end

          if header && line.downcase =~ /^version/
            entry = [ line ]
            version = true
            next
          end

          entry << line
        end

        if block_given?
          yield parse_entry( entry )
          nil
        else
          entries << parse_entry( entry )

          # Sort entries if sorting has been requested.
          entries.sort! { |x,y| x.dn.length <=> y.dn.length } if sort
          entries
        end

      end

    end

[Validate]

Generated with the Darkfish Rdoc Generator 2.