/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * */ package org.apache.directory.api.ldap.model.ldif; import java.io.BufferedReader; import java.io.IOException; import java.io.StringReader; import java.util.ArrayList; import javax.naming.directory.Attributes; import javax.naming.directory.BasicAttributes; import org.apache.directory.api.i18n.I18n; import org.apache.directory.api.ldap.model.entry.Attribute; import org.apache.directory.api.ldap.model.entry.DefaultEntry; import org.apache.directory.api.ldap.model.entry.Entry; import org.apache.directory.api.ldap.model.exception.LdapException; import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException; import org.apache.directory.api.ldap.model.schema.AttributeType; import org.apache.directory.api.ldap.model.schema.SchemaManager; import org.apache.directory.api.util.Strings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * <pre> * <ldif-file> ::= "version:" <fill> <number> <seps> <dn-spec> <sep> * <ldif-content-change> * * <ldif-content-change> ::= * <number> <oid> <options-e> <value-spec> <sep> <attrval-specs-e> * <ldif-attrval-record-e> | * <alpha> <chars-e> <options-e> <value-spec> <sep> <attrval-specs-e> * <ldif-attrval-record-e> | * "control:" <fill> <number> <oid> <spaces-e> <criticality> * <value-spec-e> <sep> <controls-e> * "changetype:" <fill> <changerecord-type> <ldif-change-record-e> | * "changetype:" <fill> <changerecord-type> <ldif-change-record-e> * * <ldif-attrval-record-e> ::= <seps> <dn-spec> <sep> <attributeType> * <options-e> <value-spec> <sep> <attrval-specs-e> * <ldif-attrval-record-e> | e * * <ldif-change-record-e> ::= <seps> <dn-spec> <sep> <controls-e> * "changetype:" <fill> <changerecord-type> <ldif-change-record-e> | e * * <dn-spec> ::= "dn:" <fill> <safe-string> | "dn::" <fill> <base64-string> * * <controls-e> ::= "control:" <fill> <number> <oid> <spaces-e> <criticality> * <value-spec-e> <sep> <controls-e> | e * * <criticality> ::= "true" | "false" | e * * <oid> ::= '.' <number> <oid> | e * * <attrval-specs-e> ::= <number> <oid> <options-e> <value-spec> <sep> * <attrval-specs-e> | * <alpha> <chars-e> <options-e> <value-spec> <sep> <attrval-specs-e> | e * * <value-spec-e> ::= <value-spec> | e * * <value-spec> ::= ':' <fill> <safe-string-e> | * "::" <fill> <base64-chars> | * ":<" <fill> <url> * * <attributeType> ::= <number> <oid> | <alpha> <chars-e> * * <options-e> ::= ';' <char> <chars-e> <options-e> |e * * <chars-e> ::= <char> <chars-e> | e * * <changerecord-type> ::= "add" <sep> <attributeType> <options-e> <value-spec> * <sep> <attrval-specs-e> | * "delete" <sep> | * "modify" <sep> <mod-type> <fill> <attributeType> <options-e> <sep> * <attrval-specs-e> <sep> '-' <sep> <mod-specs-e> | * "moddn" <sep> <newrdn> <sep> "deleteoldrdn:" <fill> <0-1> <sep> * <newsuperior-e> <sep> | * "modrdn" <sep> <newrdn> <sep> "deleteoldrdn:" <fill> <0-1> <sep> * <newsuperior-e> <sep> * * <newrdn> ::= ':' <fill> <safe-string> | "::" <fill> <base64-chars> * * <newsuperior-e> ::= "newsuperior" <newrdn> | e * * <mod-specs-e> ::= <mod-type> <fill> <attributeType> <options-e> * <sep> <attrval-specs-e> <sep> '-' <sep> <mod-specs-e> | e * * <mod-type> ::= "add:" | "delete:" | "replace:" * * <url> ::= <a Uniform Resource Locator, as defined in [6]> * * * * LEXICAL * ------- * * <fill> ::= ' ' <fill> | e * <char> ::= <alpha> | <digit> | '-' * <number> ::= <digit> <digits> * <0-1> ::= '0' | '1' * <digits> ::= <digit> <digits> | e * <digit> ::= '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' * <seps> ::= <sep> <seps-e> * <seps-e> ::= <sep> <seps-e> | e * <sep> ::= 0x0D 0x0A | 0x0A * <spaces> ::= ' ' <spaces-e> * <spaces-e> ::= ' ' <spaces-e> | e * <safe-string-e> ::= <safe-string> | e * <safe-string> ::= <safe-init-char> <safe-chars> * <safe-init-char> ::= [0x01-0x09] | 0x0B | 0x0C | [0x0E-0x1F] | [0x21-0x39] | 0x3B | [0x3D-0x7F] * <safe-chars> ::= <safe-char> <safe-chars> | e * <safe-char> ::= [0x01-0x09] | 0x0B | 0x0C | [0x0E-0x7F] * <base64-string> ::= <base64-char> <base64-chars> * <base64-chars> ::= <base64-char> <base64-chars> | e * <base64-char> ::= 0x2B | 0x2F | [0x30-0x39] | 0x3D | [0x41-9x5A] | [0x61-0x7A] * <alpha> ::= [0x41-0x5A] | [0x61-0x7A] * * COMMENTS * -------- * - The ldap-oid VN is not correct in the RFC-2849. It has been changed from 1*DIGIT 0*1("." 1*DIGIT) to * DIGIT+ ("." DIGIT+)* * - The mod-spec lacks a sep between *attrval-spec and "-". * - The BASE64-UTF8-STRING should be BASE64-CHAR BASE64-STRING * - The ValueSpec rule must accept multilines values. In this case, we have a LF followed by a * single space before the continued value. * </pre> * * @author <a href="mailto:[email protected]">Apache Directory Project</a> */ public class LdifAttributesReader extends LdifReader { /** A logger */ private static final Logger LOG = LoggerFactory.getLogger( LdifAttributesReader.class ); /** * Constructors */ public LdifAttributesReader() { lines = new ArrayList<String>(); position = 0; version = DEFAULT_VERSION; } /** * Parse an AttributeType/AttributeValue * * @param attributes The entry where to store the value * @param line The line to parse * @param lowerLine The same line, lowercased * @throws LdapLdifException If anything goes wrong */ private void parseAttribute( Attributes attributes, String line, String lowerLine ) throws LdapLdifException { int colonIndex = line.indexOf( ':' ); String attributeType = lowerLine.substring( 0, colonIndex ); // We should *not* have a Dn twice if ( "dn".equals( attributeType ) ) { LOG.error( I18n.err( I18n.ERR_13400_ENTRY_WITH_TWO_DNS ) ); throw new LdapLdifException( I18n.err( I18n.ERR_13439_LDIF_ENTRY_WITH_TWO_DNS ) ); } Object attributeValue = parseValue( attributeType, line, colonIndex ); // Update the entry javax.naming.directory.Attribute attribute = attributes.get( attributeType ); if ( attribute == null ) { attributes.put( attributeType, attributeValue ); } else { attribute.add( attributeValue ); } } /** * Parse an AttributeType/AttributeValue * * @param schemaManager The SchemaManager * @param entry The entry where to store the value * @param line The line to parse * @param lowerLine The same line, lowercased * @throws LdapLdifException If anything goes wrong */ private void parseEntryAttribute( SchemaManager schemaManager, Entry entry, String line, String lowerLine ) throws LdapLdifException { int colonIndex = line.indexOf( ':' ); String attributeName = lowerLine.substring( 0, colonIndex ); AttributeType attributeType = null; // We should *not* have a Dn twice if ( "dn".equals( attributeName ) ) { LOG.error( I18n.err( I18n.ERR_13400_ENTRY_WITH_TWO_DNS ) ); throw new LdapLdifException( I18n.err( I18n.ERR_13439_LDIF_ENTRY_WITH_TWO_DNS ) ); } if ( schemaManager != null ) { attributeType = schemaManager.getAttributeType( attributeName ); if ( attributeType == null ) { String msg = I18n.err( I18n.ERR_13475_UNKNOWN_ATTRIBUTETYPE, attributeName ); LOG.error( msg ); throw new LdapLdifException( msg ); } } Object attributeValue = parseValue( attributeName, line, colonIndex ); // Update the entry Attribute attribute; if ( schemaManager == null ) { attribute = entry.get( attributeName ); } else { attribute = entry.get( attributeType ); } if ( attribute == null ) { if ( schemaManager == null ) { if ( attributeValue instanceof String ) { entry.put( attributeName, ( String ) attributeValue ); } else { entry.put( attributeName, ( byte[] ) attributeValue ); } } else { try { if ( attributeValue instanceof String ) { entry.put( attributeName, attributeType, ( String ) attributeValue ); } else { entry.put( attributeName, attributeType, ( byte[] ) attributeValue ); } } catch ( LdapException le ) { throw new LdapLdifException( I18n.err( I18n.ERR_13460_BAD_ATTRIBUTE ), le ); } } } else { try { if ( attributeValue instanceof String ) { attribute.add( ( String ) attributeValue ); } else { attribute.add( ( byte[] ) attributeValue ); } } catch ( LdapInvalidAttributeValueException liave ) { throw new LdapLdifException( liave.getMessage(), liave ); } } } /** * Parse a ldif file. The following rules are processed : * * <ldif-file> ::= <ldif-attrval-record> <ldif-attrval-records> | * <ldif-change-record> <ldif-change-records> <ldif-attrval-record> ::= * <dn-spec> <sep> <attrval-spec> <attrval-specs> <ldif-change-record> ::= * <dn-spec> <sep> <controls-e> <changerecord> <dn-spec> ::= "dn:" <fill> * <distinguishedName> | "dn::" <fill> <base64-distinguishedName> * <changerecord> ::= "changetype:" <fill> <change-op> * * @param schemaManager The SchemaManager * @return The read entry * @throws LdapLdifException If the entry can't be read or is invalid */ private Entry parseEntry( SchemaManager schemaManager ) throws LdapLdifException { if ( ( lines == null ) || lines.isEmpty() ) { if ( LOG.isDebugEnabled() ) { LOG.debug( I18n.msg( I18n.MSG_13408_END_OF_LDIF ) ); } return null; } Entry entry = new DefaultEntry( schemaManager ); // Now, let's iterate through the other lines for ( String line : lines ) { // Each line could start either with an OID, an attribute type, with // "control:" or with "changetype:" String lowerLine = Strings.toLowerCaseAscii( line ); // We have three cases : // 1) The first line after the Dn is a "control:" -> this is an error // 2) The first line after the Dn is a "changeType:" -> this is an error // 3) The first line after the Dn is anything else if ( lowerLine.startsWith( "control:" ) ) { LOG.error( I18n.err( I18n.ERR_13401_CHANGE_NOT_ALLOWED ) ); throw new LdapLdifException( I18n.err( I18n.ERR_13440_NO_CHANGE ) ); } else if ( lowerLine.startsWith( "changetype:" ) ) { LOG.error( I18n.err( I18n.ERR_13401_CHANGE_NOT_ALLOWED ) ); throw new LdapLdifException( I18n.err( I18n.ERR_13440_NO_CHANGE ) ); } else if ( line.indexOf( ':' ) > 0 ) { parseEntryAttribute( schemaManager, entry, line, lowerLine ); } else { // Invalid attribute Value LOG.error( I18n.err( I18n.ERR_13402_EXPECTING_ATTRIBUTE_TYPE ) ); throw new LdapLdifException( I18n.err( I18n.ERR_13441_BAD_ATTRIBUTE ) ); } } if ( LOG.isDebugEnabled() ) { LOG.debug( I18n.msg( I18n.MSG_13405_READ_ATTR, entry ) ); } return entry; } /** * Parse a ldif file. The following rules are processed : * * <pre> * <ldif-file> ::= <ldif-attrval-record> <ldif-attrval-records> | * <ldif-change-record> <ldif-change-records> <ldif-attrval-record> ::= * <dn-spec> <sep> <attrval-spec> <attrval-specs> <ldif-change-record> ::= * <dn-spec> <sep> <controls-e> <changerecord> <dn-spec> ::= "dn:" <fill> * <distinguishedName> | "dn::" <fill> <base64-distinguishedName> * <changerecord> ::= "changetype:" <fill> <change-op> * </pre> * * @return The read entry * @throws LdapLdifException If the entry can't be read or is invalid */ private Attributes parseAttributes() throws LdapLdifException { if ( ( lines == null ) || lines.isEmpty() ) { if ( LOG.isDebugEnabled() ) { LOG.debug( I18n.msg( I18n.MSG_13408_END_OF_LDIF ) ); } return null; } Attributes attributes = new BasicAttributes( true ); // Now, let's iterate through the other lines for ( String line : lines ) { // Each line could start either with an OID, an attribute type, with // "control:" or with "changetype:" String lowerLine = Strings.toLowerCaseAscii( line ); // We have three cases : // 1) The first line after the Dn is a "control:" -> this is an error // 2) The first line after the Dn is a "changeType:" -> this is an error // 3) The first line after the Dn is anything else if ( lowerLine.startsWith( "control:" ) ) { LOG.error( I18n.err( I18n.ERR_13401_CHANGE_NOT_ALLOWED ) ); throw new LdapLdifException( I18n.err( I18n.ERR_13440_NO_CHANGE ) ); } else if ( lowerLine.startsWith( "changetype:" ) ) { LOG.error( I18n.err( I18n.ERR_13401_CHANGE_NOT_ALLOWED ) ); throw new LdapLdifException( I18n.err( I18n.ERR_13440_NO_CHANGE ) ); } else if ( line.indexOf( ':' ) > 0 ) { parseAttribute( attributes, line, lowerLine ); } else { // Invalid attribute Value LOG.error( I18n.err( I18n.ERR_13402_EXPECTING_ATTRIBUTE_TYPE ) ); throw new LdapLdifException( I18n.err( I18n.ERR_13441_BAD_ATTRIBUTE ) ); } } if ( LOG.isDebugEnabled() ) { LOG.debug( I18n.msg( I18n.MSG_13405_READ_ATTR, attributes ) ); } return attributes; } /** * A method which parses a ldif string and returns a list of Attributes. * * @param ldif The ldif string * @return A list of Attributes, or an empty List * @throws LdapLdifException If something went wrong */ public Attributes parseAttributes( String ldif ) throws LdapLdifException { lines = new ArrayList<String>(); position = 0; if ( LOG.isDebugEnabled() ) { LOG.debug( I18n.msg( I18n.MSG_13407_STARTS_PARSING_LDIF ) ); } if ( Strings.isEmpty( ldif ) ) { return new BasicAttributes( true ); } StringReader strIn = new StringReader( ldif ); reader = new BufferedReader( strIn ); try { readLines(); Attributes attributes = parseAttributes(); if ( LOG.isDebugEnabled() ) { if ( attributes == null ) { LOG.debug( I18n.msg( I18n.MSG_13401_PARSED_NO_ENTRY ) ); } else { LOG.debug( I18n.msg( I18n.MSG_13402_PARSED_ONE_ENTRY ) ); } } return attributes; } catch ( LdapLdifException ne ) { LOG.error( I18n.err( I18n.ERR_13403_CANNOT_PARSE_LDIF_BUFFER, ne.getLocalizedMessage() ) ); throw new LdapLdifException( I18n.err( I18n.ERR_13442_ERROR_PARSING_LDIF_BUFFER ), ne ); } finally { try { reader.close(); } catch ( IOException ioe ) { throw new LdapLdifException( I18n.err( I18n.ERR_13450_CANNOT_CLOSE_FILE ), ioe ); } } } /** * A method which parses a ldif string and returns an Entry. * * @param ldif The ldif string * @return An entry * @throws LdapLdifException If something went wrong */ public Entry parseEntry( String ldif ) throws LdapLdifException { lines = new ArrayList<String>(); position = 0; if ( LOG.isDebugEnabled() ) { LOG.debug( I18n.msg( I18n.MSG_13407_STARTS_PARSING_LDIF ) ); } if ( Strings.isEmpty( ldif ) ) { return new DefaultEntry(); } StringReader strIn = new StringReader( ldif ); reader = new BufferedReader( strIn ); try { readLines(); Entry entry = parseEntry( ( SchemaManager ) null ); if ( LOG.isDebugEnabled() ) { if ( entry == null ) { LOG.debug( I18n.msg( I18n.MSG_13401_PARSED_NO_ENTRY ) ); } else { LOG.debug( I18n.msg( I18n.MSG_13402_PARSED_ONE_ENTRY ) ); } } return entry; } catch ( LdapLdifException ne ) { LOG.error( I18n.err( I18n.ERR_13403_CANNOT_PARSE_LDIF_BUFFER, ne.getLocalizedMessage() ) ); throw new LdapLdifException( I18n.err( I18n.ERR_13442_ERROR_PARSING_LDIF_BUFFER ), ne ); } finally { try { reader.close(); } catch ( IOException ioe ) { throw new LdapLdifException( I18n.err( I18n.ERR_13450_CANNOT_CLOSE_FILE ), ioe ); } } } /** * A method which parses a ldif string and returns an Entry. * * @param schemaManager The SchemaManager * @param ldif The ldif string * @return An entry * @throws LdapLdifException If something went wrong */ public Entry parseEntry( SchemaManager schemaManager, String ldif ) throws LdapLdifException { lines = new ArrayList<String>(); position = 0; if ( LOG.isDebugEnabled() ) { LOG.debug( I18n.msg( I18n.MSG_13407_STARTS_PARSING_LDIF ) ); } if ( Strings.isEmpty( ldif ) ) { return new DefaultEntry( schemaManager ); } StringReader strIn = new StringReader( ldif ); reader = new BufferedReader( strIn ); try { readLines(); Entry entry = parseEntry( schemaManager ); if ( LOG.isDebugEnabled() ) { if ( entry == null ) { LOG.debug( I18n.msg( I18n.MSG_13401_PARSED_NO_ENTRY ) ); } else { LOG.debug( I18n.msg( I18n.MSG_13402_PARSED_ONE_ENTRY ) ); } } return entry; } catch ( LdapLdifException ne ) { LOG.error( I18n.err( I18n.ERR_13403_CANNOT_PARSE_LDIF_BUFFER, ne.getLocalizedMessage() ) ); throw new LdapLdifException( I18n.err( I18n.ERR_13442_ERROR_PARSING_LDIF_BUFFER ), ne ); } finally { try { reader.close(); } catch ( IOException ioe ) { throw new LdapLdifException( I18n.err( I18n.ERR_13450_CANNOT_CLOSE_FILE ), ioe ); } } } }