| /* |
| * 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.IOException; |
| |
| import javax.naming.directory.Attributes; |
| |
| import org.apache.directory.api.i18n.I18n; |
| import org.apache.directory.api.ldap.model.entry.Attribute; |
| import org.apache.directory.api.ldap.model.entry.AttributeUtils; |
| import org.apache.directory.api.ldap.model.entry.DefaultAttribute; |
| import org.apache.directory.api.ldap.model.entry.Entry; |
| import org.apache.directory.api.ldap.model.entry.Modification; |
| import org.apache.directory.api.ldap.model.entry.Value; |
| 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.message.ResultCodeEnum; |
| import org.apache.directory.api.ldap.model.name.Dn; |
| import org.apache.directory.api.util.Base64; |
| import org.apache.directory.api.util.Strings; |
| |
| |
| /** |
| * Some LDIF helper methods. |
| * |
| * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> |
| */ |
| public final class LdifUtils |
| { |
| /** The array that will be used to match the first char.*/ |
| private static final boolean[] LDIF_SAFE_STARTING_CHAR_ALPHABET = new boolean[128]; |
| |
| /** The array that will be used to match the other chars.*/ |
| private static final boolean[] LDIF_SAFE_OTHER_CHARS_ALPHABET = new boolean[128]; |
| |
| /** The default length for a line in a ldif file */ |
| private static final int DEFAULT_LINE_LENGTH = 80; |
| |
| /** The file separator */ |
| private static final String LINE_SEPARATOR = System.getProperty( "line.separator" ); |
| |
| static |
| { |
| // Initialization of the array that will be used to match the first char. |
| for ( int i = 0; i < 128; i++ ) |
| { |
| LDIF_SAFE_STARTING_CHAR_ALPHABET[i] = true; |
| } |
| |
| // 0 (NUL) |
| LDIF_SAFE_STARTING_CHAR_ALPHABET[0] = false; |
| // 10 (LF) |
| LDIF_SAFE_STARTING_CHAR_ALPHABET[10] = false; |
| // 13 (CR) |
| LDIF_SAFE_STARTING_CHAR_ALPHABET[13] = false; |
| // 32 (SPACE) |
| LDIF_SAFE_STARTING_CHAR_ALPHABET[32] = false; |
| // 58 (:) |
| LDIF_SAFE_STARTING_CHAR_ALPHABET[58] = false; |
| // 60 (>) |
| LDIF_SAFE_STARTING_CHAR_ALPHABET[60] = false; |
| |
| // Initialization of the array that will be used to match the other chars. |
| for ( int i = 0; i < 128; i++ ) |
| { |
| LDIF_SAFE_OTHER_CHARS_ALPHABET[i] = true; |
| } |
| |
| // 0 (NUL) |
| LDIF_SAFE_OTHER_CHARS_ALPHABET[0] = false; |
| // 10 (LF) |
| LDIF_SAFE_OTHER_CHARS_ALPHABET[10] = false; |
| // 13 (CR) |
| LDIF_SAFE_OTHER_CHARS_ALPHABET[13] = false; |
| } |
| |
| |
| /** |
| * Private constructor. |
| */ |
| private LdifUtils() |
| { |
| } |
| |
| |
| /** |
| * Checks if the input String contains only safe values, that is, the data |
| * does not need to be encoded for use with LDIF. The rules for checking safety |
| * are based on the rules for LDIF (LDAP Data Interchange Format) per RFC 2849. |
| * The data does not need to be encoded if all the following are true: |
| * |
| * The data cannot start with the following char values: |
| * <ul> |
| * <li>00 (NUL)</li> |
| * <li>10 (LF)</li> |
| * <li>13 (CR)</li> |
| * <li>32 (SPACE)</li> |
| * <li>58 (:)</li> |
| * <li>60 (<)</li> |
| * <li>Any character with value greater than 127</li> |
| * </ul> |
| * |
| * The data cannot contain any of the following char values: |
| * <ul> |
| * <li>00 (NUL)</li> |
| * <li>10 (LF)</li> |
| * <li>13 (CR)</li> |
| * <li>Any character with value greater than 127</li> |
| * </ul> |
| * |
| * The data cannot end with a space. |
| * |
| * @param str the String to be checked |
| * @return true if encoding not required for LDIF |
| */ |
| public static boolean isLDIFSafe( String str ) |
| { |
| if ( Strings.isEmpty( str ) ) |
| { |
| // A null string is LDIF safe |
| return true; |
| } |
| |
| // Checking the first char |
| char currentChar = str.charAt( 0 ); |
| |
| if ( ( currentChar > 127 ) || !LDIF_SAFE_STARTING_CHAR_ALPHABET[currentChar] ) |
| { |
| return false; |
| } |
| |
| // Checking the other chars |
| for ( int i = 1; i < str.length(); i++ ) |
| { |
| currentChar = str.charAt( i ); |
| |
| if ( ( currentChar > 127 ) || !LDIF_SAFE_OTHER_CHARS_ALPHABET[currentChar] ) |
| { |
| return false; |
| } |
| } |
| |
| // The String cannot end with a space |
| return ( currentChar != ' ' ); |
| } |
| |
| |
| /** |
| * Convert an Attributes as LDIF |
| * |
| * @param attrs the Attributes to convert |
| * @return the corresponding LDIF code as a String |
| * @throws LdapException If a naming exception is encountered. |
| */ |
| public static String convertToLdif( Attributes attrs ) throws LdapException |
| { |
| return convertAttributesToLdif( AttributeUtils.toEntry( attrs, null ), DEFAULT_LINE_LENGTH ); |
| } |
| |
| |
| /** |
| * Convert an Attributes as LDIF |
| * |
| * @param attrs the Attributes to convert |
| * @param length The ldif line length |
| * @return the corresponding LDIF code as a String |
| * @throws LdapException If a naming exception is encountered. |
| */ |
| public static String convertToLdif( Attributes attrs, int length ) throws LdapException |
| { |
| return convertAttributesToLdif( AttributeUtils.toEntry( attrs, null ), length ); |
| } |
| |
| |
| /** |
| * Convert an Attributes as LDIF. The Dn is written. |
| * |
| * @param attrs the Attributes to convert |
| * @param dn The Dn for this entry |
| * @param length The ldif line length |
| * @return the corresponding LDIF code as a String |
| * @throws LdapException If a naming exception is encountered. |
| */ |
| public static String convertToLdif( Attributes attrs, Dn dn, int length ) throws LdapException |
| { |
| return convertToLdif( AttributeUtils.toEntry( attrs, dn ), length ); |
| } |
| |
| |
| /** |
| * Convert an Attributes as LDIF. The Dn is written. |
| * |
| * @param attrs the Attributes to convert |
| * @param dn The Dn for this entry |
| * @return the corresponding LDIF code as a String |
| * @throws LdapException If a naming exception is encountered. |
| */ |
| public static String convertToLdif( Attributes attrs, Dn dn ) throws LdapException |
| { |
| return convertToLdif( AttributeUtils.toEntry( attrs, dn ), DEFAULT_LINE_LENGTH ); |
| } |
| |
| |
| /** |
| * Convert an Entry to LDIF |
| * |
| * @param entry the Entry to convert |
| * @return the corresponding LDIF code as a String |
| * @throws LdapException If a naming exception is encountered. |
| */ |
| public static String convertToLdif( Entry entry ) throws LdapException |
| { |
| return convertToLdif( entry, DEFAULT_LINE_LENGTH ); |
| } |
| |
| |
| /** |
| * Convert an Entry to LDIF including a version number at the top |
| * |
| * @param entry the Entry to convert |
| * @param includeVersionInfo flag to tell whether to include version number or not |
| * @return the corresponding LDIF code as a String |
| * @throws org.apache.directory.api.ldap.model.exception.LdapException If a naming exception is encountered. |
| */ |
| public static String convertToLdif( Entry entry, boolean includeVersionInfo ) throws LdapException |
| { |
| String ldif = convertToLdif( entry, DEFAULT_LINE_LENGTH ); |
| |
| if ( includeVersionInfo ) |
| { |
| ldif = "version: 1" + LINE_SEPARATOR + ldif; |
| } |
| |
| return ldif; |
| } |
| |
| |
| /** |
| * Convert all the Entry's attributes to LDIF. The Dn is not written |
| * |
| * @param entry the Entry to convert |
| * @return the corresponding LDIF code as a String |
| * @throws LdapException If a naming exception is encountered. |
| */ |
| public static String convertAttributesToLdif( Entry entry ) throws LdapException |
| { |
| return convertAttributesToLdif( entry, DEFAULT_LINE_LENGTH ); |
| } |
| |
| |
| /** |
| * Convert a LDIF String to a JNDI attributes. |
| * |
| * @param ldif The LDIF string containing an attribute value |
| * @return An Attributes instance |
| * @exception LdapLdifException If the LDIF String cannot be converted to an Attributes |
| */ |
| public static Attributes getJndiAttributesFromLdif( String ldif ) throws LdapLdifException |
| { |
| LdifAttributesReader reader = new LdifAttributesReader(); |
| |
| try |
| { |
| Attributes attributes = AttributeUtils.toAttributes( reader.parseEntry( ldif ) ); |
| |
| reader.close(); |
| |
| return attributes; |
| } |
| catch ( IOException ioe ) |
| { |
| throw new LdapLdifException( ioe.getMessage() ); |
| } |
| } |
| |
| |
| /** |
| * Convert an Entry as LDIF |
| * |
| * @param entry the Entry to convert |
| * @param length the expected line length |
| * @return the corresponding LDIF code as a String |
| * @throws LdapException If a naming exception is encountered. |
| */ |
| public static String convertToLdif( Entry entry, int length ) throws LdapException |
| { |
| StringBuilder sb = new StringBuilder(); |
| |
| if ( entry.getDn() != null ) |
| { |
| // First, dump the Dn |
| if ( isLDIFSafe( entry.getDn().getName() ) ) |
| { |
| sb.append( stripLineToNChars( "dn: " + entry.getDn().getName(), length ) ); |
| } |
| else |
| { |
| sb.append( stripLineToNChars( "dn:: " + encodeBase64( entry.getDn().getName() ), length ) ); |
| } |
| |
| sb.append( '\n' ); |
| } |
| |
| // Then all the attributes |
| for ( Attribute attribute : entry ) |
| { |
| sb.append( convertToLdif( attribute, length ) ); |
| } |
| |
| return sb.toString(); |
| } |
| |
| |
| /** |
| * Convert the Entry's attributes to LDIF. The Dn is not written. |
| * |
| * @param entry the Entry to convert |
| * @param length the expected line length |
| * @return the corresponding LDIF code as a String |
| * @throws LdapException If a naming exception is encountered. |
| */ |
| public static String convertAttributesToLdif( Entry entry, int length ) throws LdapException |
| { |
| StringBuilder sb = new StringBuilder(); |
| |
| // Then all the attributes |
| for ( Attribute attribute : entry ) |
| { |
| sb.append( convertToLdif( attribute, length ) ); |
| } |
| |
| return sb.toString(); |
| } |
| |
| |
| /** |
| * Convert an LdifEntry to LDIF |
| * |
| * @param entry the LdifEntry to convert |
| * @return the corresponding LDIF as a String |
| * @throws LdapException If a naming exception is encountered. |
| */ |
| public static String convertToLdif( LdifEntry entry ) throws LdapException |
| { |
| return convertToLdif( entry, DEFAULT_LINE_LENGTH ); |
| } |
| |
| |
| /** |
| * Convert an LdifEntry to LDIF |
| * |
| * @param entry the LdifEntry to convert |
| * @param length The maximum line's length |
| * @return the corresponding LDIF as a String |
| * @throws LdapException If a naming exception is encountered. |
| */ |
| public static String convertToLdif( LdifEntry entry, int length ) throws LdapException |
| { |
| StringBuilder sb = new StringBuilder(); |
| |
| // First, dump the Dn |
| if ( isLDIFSafe( entry.getDn().getName() ) ) |
| { |
| sb.append( stripLineToNChars( "dn: " + entry.getDn(), length ) ); |
| } |
| else |
| { |
| sb.append( stripLineToNChars( "dn:: " + encodeBase64( entry.getDn().getName() ), length ) ); |
| } |
| |
| sb.append( '\n' ); |
| |
| // Dump the ChangeType |
| String changeType = Strings.toLowerCase( entry.getChangeType().toString() ); |
| |
| if ( entry.getChangeType() != ChangeType.None ) |
| { |
| // First dump the controls if any |
| if ( entry.hasControls() ) |
| { |
| for ( LdifControl control : entry.getControls().values() ) |
| { |
| StringBuilder controlStr = new StringBuilder(); |
| |
| controlStr.append( "control: " ).append( control.getOid() ); |
| controlStr.append( " " ).append( control.isCritical() ); |
| |
| if ( control.hasValue() ) |
| { |
| controlStr.append( "::" ).append( Base64.encode( control.getValue() ) ); |
| } |
| |
| sb.append( stripLineToNChars( controlStr.toString(), length ) ); |
| sb.append( '\n' ); |
| } |
| } |
| |
| sb.append( stripLineToNChars( "changetype: " + changeType, length ) ); |
| sb.append( '\n' ); |
| } |
| |
| switch ( entry.getChangeType() ) |
| { |
| case None: |
| if ( entry.hasControls() ) |
| { |
| sb.append( stripLineToNChars( "changetype: " + ChangeType.Add, length ) ); |
| } |
| |
| // Fallthrough |
| |
| case Add: |
| if ( ( entry.getEntry() == null ) ) |
| { |
| throw new LdapException( I18n.err( I18n.ERR_12082 ) ); |
| } |
| |
| // Now, iterate through all the attributes |
| for ( Attribute attribute : entry.getEntry() ) |
| { |
| sb.append( convertToLdif( attribute, length ) ); |
| } |
| |
| break; |
| |
| case Delete: |
| if ( entry.getEntry() != null ) |
| { |
| throw new LdapException( I18n.err( I18n.ERR_12081 ) ); |
| } |
| |
| break; |
| |
| case ModDn: |
| case ModRdn: |
| if ( entry.getEntry() != null ) |
| { |
| throw new LdapException( I18n.err( I18n.ERR_12083 ) ); |
| } |
| |
| // Stores the new Rdn |
| Attribute newRdn = new DefaultAttribute( "newrdn", entry.getNewRdn() ); |
| sb.append( convertToLdif( newRdn, length ) ); |
| |
| // Stores the deleteoldrdn flag |
| sb.append( "deleteoldrdn: " ); |
| |
| if ( entry.isDeleteOldRdn() ) |
| { |
| sb.append( "1" ); |
| } |
| else |
| { |
| sb.append( "0" ); |
| } |
| |
| sb.append( '\n' ); |
| |
| // Stores the optional newSuperior |
| if ( !Strings.isEmpty( entry.getNewSuperior() ) ) |
| { |
| Attribute newSuperior = new DefaultAttribute( "newsuperior", entry.getNewSuperior() ); |
| sb.append( convertToLdif( newSuperior, length ) ); |
| } |
| |
| break; |
| |
| case Modify: |
| boolean isFirst = true; |
| |
| for ( Modification modification : entry.getModifications() ) |
| { |
| |
| if ( isFirst ) |
| { |
| isFirst = false; |
| } |
| else |
| { |
| sb.append( "-\n" ); |
| } |
| |
| switch ( modification.getOperation() ) |
| { |
| case ADD_ATTRIBUTE: |
| sb.append( "add: " ); |
| break; |
| |
| case REMOVE_ATTRIBUTE: |
| sb.append( "delete: " ); |
| break; |
| |
| case REPLACE_ATTRIBUTE: |
| sb.append( "replace: " ); |
| break; |
| |
| default: |
| throw new IllegalArgumentException( "Unexpected ModificationOperation: " |
| + modification.getOperation() ); |
| } |
| |
| sb.append( modification.getAttribute().getUpId() ); |
| sb.append( '\n' ); |
| |
| sb.append( convertToLdif( modification.getAttribute(), length ) ); |
| } |
| |
| sb.append( '-' ); |
| break; |
| |
| default: |
| throw new IllegalArgumentException( "Unexpected ChangeType: " + entry.getChangeType() ); |
| } |
| |
| sb.append( '\n' ); |
| |
| return sb.toString(); |
| } |
| |
| |
| /** |
| * Base64 encode a String |
| * |
| * @param str The string to encode |
| * @return the base 64 encoded string |
| */ |
| private static String encodeBase64( String str ) |
| { |
| // force encoding using UTF-8 charset, as required in RFC2849 note 7 |
| return new String( Base64.encode( Strings.getBytesUtf8( str ) ) ); |
| } |
| |
| |
| /** |
| * Converts an EntryAttribute to LDIF |
| * |
| * @param attr the >EntryAttribute to convert |
| * @return the corresponding LDIF code as a String |
| * @throws LdapException If a naming exception is encountered. |
| */ |
| public static String convertToLdif( Attribute attr ) throws LdapException |
| { |
| return convertToLdif( attr, DEFAULT_LINE_LENGTH ); |
| } |
| |
| |
| /** |
| * Converts an EntryAttribute as LDIF |
| * |
| * @param attr the EntryAttribute to convert |
| * @param length the expected line length |
| * @return the corresponding LDIF code as a String |
| * @throws LdapException If a naming exception is encountered. |
| */ |
| public static String convertToLdif( Attribute attr, int length ) throws LdapException |
| { |
| StringBuilder sb = new StringBuilder(); |
| |
| if ( attr.size() == 0 ) |
| { |
| // Special case : we don't have any value |
| return ""; |
| } |
| |
| for ( Value<?> value : attr ) |
| { |
| StringBuilder lineBuffer = new StringBuilder(); |
| |
| lineBuffer.append( attr.getUpId() ); |
| |
| // First, deal with null value (which is valid) |
| if ( value.isNull() ) |
| { |
| lineBuffer.append( ':' ); |
| } |
| else if ( value.isHumanReadable() ) |
| { |
| // It's a String but, we have to check if encoding isn't required |
| String str = value.getString(); |
| |
| if ( !LdifUtils.isLDIFSafe( str ) ) |
| { |
| lineBuffer.append( ":: " ).append( encodeBase64( str ) ); |
| } |
| else |
| { |
| lineBuffer.append( ':' ); |
| |
| if ( str != null ) |
| { |
| lineBuffer.append( ' ' ).append( str ); |
| } |
| } |
| } |
| else |
| { |
| // It is binary, so we have to encode it using Base64 before adding it |
| char[] encoded = Base64.encode( value.getBytes() ); |
| |
| lineBuffer.append( ":: " + new String( encoded ) ); |
| } |
| |
| lineBuffer.append( '\n' ); |
| sb.append( stripLineToNChars( lineBuffer.toString(), length ) ); |
| } |
| |
| return sb.toString(); |
| } |
| |
| |
| /** |
| * Strips the String every n specified characters |
| * |
| * @param str the string to strip |
| * @param nbChars the number of characters |
| * @return the stripped String |
| */ |
| public static String stripLineToNChars( String str, int nbChars ) |
| { |
| int strLength = str.length(); |
| |
| if ( strLength <= nbChars ) |
| { |
| return str; |
| } |
| |
| if ( nbChars < 2 ) |
| { |
| throw new IllegalArgumentException( I18n.err( I18n.ERR_12084 ) ); |
| } |
| |
| // We will first compute the new size of the LDIF result |
| // It's at least nbChars chars plus one for \n |
| int charsPerLine = nbChars - 1; |
| |
| int remaining = ( strLength - nbChars ) % charsPerLine; |
| |
| int nbLines = 1 + ( ( strLength - nbChars ) / charsPerLine ) + ( remaining == 0 ? 0 : 1 ); |
| |
| int nbCharsTotal = strLength + nbLines + nbLines - 2; |
| |
| char[] buffer = new char[nbCharsTotal]; |
| char[] orig = str.toCharArray(); |
| |
| int posSrc = 0; |
| int posDst = 0; |
| |
| System.arraycopy( orig, posSrc, buffer, posDst, nbChars ); |
| posSrc += nbChars; |
| posDst += nbChars; |
| |
| for ( int i = 0; i < nbLines - 2; i++ ) |
| { |
| buffer[posDst++] = '\n'; |
| buffer[posDst++] = ' '; |
| |
| System.arraycopy( orig, posSrc, buffer, posDst, charsPerLine ); |
| posSrc += charsPerLine; |
| posDst += charsPerLine; |
| } |
| |
| buffer[posDst++] = '\n'; |
| buffer[posDst++] = ' '; |
| System.arraycopy( orig, posSrc, buffer, posDst, remaining == 0 ? charsPerLine : remaining ); |
| |
| return new String( buffer ); |
| } |
| |
| |
| /** |
| * Build a new Attributes instance from a LDIF list of lines. The values can be |
| * either a complete Ava, or a couple of AttributeType ID and a value (a String or |
| * a byte[]). The following sample shows the three cases : |
| * |
| * <pre> |
| * Attribute attr = AttributeUtils.createAttributes( |
| * "objectclass: top", |
| * "cn", "My name", |
| * "jpegPhoto", new byte[]{0x01, 0x02} ); |
| * </pre> |
| * |
| * @param avas The AttributeType and Values, using a ldif format, or a couple of |
| * Attribute ID/Value |
| * @return An Attributes instance |
| * @throws LdapException If the data are invalid |
| */ |
| public static Attributes createJndiAttributes( Object... avas ) throws LdapException |
| { |
| StringBuilder sb = new StringBuilder(); |
| int pos = 0; |
| boolean valueExpected = false; |
| |
| for ( Object ava : avas ) |
| { |
| if ( !valueExpected ) |
| { |
| if ( !( ava instanceof String ) ) |
| { |
| throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, I18n.err( |
| I18n.ERR_12085, ( pos + 1 ) ) ); |
| } |
| |
| String attribute = ( String ) ava; |
| sb.append( attribute ); |
| |
| if ( attribute.indexOf( ':' ) != -1 ) |
| { |
| sb.append( '\n' ); |
| } |
| else |
| { |
| valueExpected = true; |
| } |
| } |
| else |
| { |
| if ( ava instanceof String ) |
| { |
| sb.append( ": " ).append( ( String ) ava ).append( '\n' ); |
| } |
| else if ( ava instanceof byte[] ) |
| { |
| sb.append( ":: " ); |
| sb.append( new String( Base64.encode( ( byte[] ) ava ) ) ); |
| sb.append( '\n' ); |
| } |
| else |
| { |
| throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, I18n.err( |
| I18n.ERR_12086, ( pos + 1 ) ) ); |
| } |
| |
| valueExpected = false; |
| } |
| } |
| |
| if ( valueExpected ) |
| { |
| throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, I18n |
| .err( I18n.ERR_12087 ) ); |
| } |
| |
| LdifAttributesReader reader = new LdifAttributesReader(); |
| Attributes attributes = AttributeUtils.toAttributes( reader.parseEntry( sb.toString() ) ); |
| |
| try |
| { |
| reader.close(); |
| } |
| catch ( IOException e ) |
| { |
| e.printStackTrace(); |
| } |
| |
| return attributes; |
| } |
| } |