001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied. See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 *
019 */
020 package org.apache.directory.shared.ldap.ldif;
021
022 import java.io.UnsupportedEncodingException;
023
024 import javax.naming.NamingException;
025 import javax.naming.directory.Attributes;
026
027 import org.apache.directory.shared.ldap.entry.Entry;
028 import org.apache.directory.shared.ldap.entry.EntryAttribute;
029 import org.apache.directory.shared.ldap.entry.Modification;
030 import org.apache.directory.shared.ldap.entry.Value;
031 import org.apache.directory.shared.ldap.entry.client.DefaultClientAttribute;
032 import org.apache.directory.shared.ldap.name.LdapDN;
033 import org.apache.directory.shared.ldap.util.AttributeUtils;
034 import org.apache.directory.shared.ldap.util.Base64;
035 import org.apache.directory.shared.ldap.util.StringTools;
036
037
038
039 /**
040 * Some LDIF useful methods
041 *
042 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
043 * @version $Rev$, $Date$
044 */
045 public class LdifUtils
046 {
047 /** The array that will be used to match the first char.*/
048 private static boolean[] LDIF_SAFE_STARTING_CHAR_ALPHABET = new boolean[128];
049
050 /** The array that will be used to match the other chars.*/
051 private static boolean[] LDIF_SAFE_OTHER_CHARS_ALPHABET = new boolean[128];
052
053 /** The default length for a line in a ldif file */
054 private static final int DEFAULT_LINE_LENGTH = 80;
055
056 static
057 {
058 // Initialization of the array that will be used to match the first char.
059 for (int i = 0; i < 128; i++)
060 {
061 LDIF_SAFE_STARTING_CHAR_ALPHABET[i] = true;
062 }
063
064 LDIF_SAFE_STARTING_CHAR_ALPHABET[0] = false; // 0 (NUL)
065 LDIF_SAFE_STARTING_CHAR_ALPHABET[10] = false; // 10 (LF)
066 LDIF_SAFE_STARTING_CHAR_ALPHABET[13] = false; // 13 (CR)
067 LDIF_SAFE_STARTING_CHAR_ALPHABET[32] = false; // 32 (SPACE)
068 LDIF_SAFE_STARTING_CHAR_ALPHABET[58] = false; // 58 (:)
069 LDIF_SAFE_STARTING_CHAR_ALPHABET[60] = false; // 60 (>)
070
071 // Initialization of the array that will be used to match the other chars.
072 for (int i = 0; i < 128; i++)
073 {
074 LDIF_SAFE_OTHER_CHARS_ALPHABET[i] = true;
075 }
076
077 LDIF_SAFE_OTHER_CHARS_ALPHABET[0] = false; // 0 (NUL)
078 LDIF_SAFE_OTHER_CHARS_ALPHABET[10] = false; // 10 (LF)
079 LDIF_SAFE_OTHER_CHARS_ALPHABET[13] = false; // 13 (CR)
080 }
081
082
083 /**
084 * Checks if the input String contains only safe values, that is, the data
085 * does not need to be encoded for use with LDIF. The rules for checking safety
086 * are based on the rules for LDIF (LDAP Data Interchange Format) per RFC 2849.
087 * The data does not need to be encoded if all the following are true:
088 *
089 * The data cannot start with the following char values:
090 * 00 (NUL)
091 * 10 (LF)
092 * 13 (CR)
093 * 32 (SPACE)
094 * 58 (:)
095 * 60 (<)
096 * Any character with value greater than 127
097 *
098 * The data cannot contain any of the following char values:
099 * 00 (NUL)
100 * 10 (LF)
101 * 13 (CR)
102 * Any character with value greater than 127
103 *
104 * The data cannot end with a space.
105 *
106 * @param str the String to be checked
107 * @return true if encoding not required for LDIF
108 */
109 public static boolean isLDIFSafe( String str )
110 {
111 if ( StringTools.isEmpty( str ) )
112 {
113 // A null string is LDIF safe
114 return true;
115 }
116
117 // Checking the first char
118 char currentChar = str.charAt(0);
119
120 if ( ( currentChar > 127 ) || !LDIF_SAFE_STARTING_CHAR_ALPHABET[currentChar] )
121 {
122 return false;
123 }
124
125 // Checking the other chars
126 for (int i = 1; i < str.length(); i++)
127 {
128 currentChar = str.charAt(i);
129
130 if ( ( currentChar > 127 ) || !LDIF_SAFE_OTHER_CHARS_ALPHABET[currentChar] )
131 {
132 return false;
133 }
134 }
135
136 // The String cannot end with a space
137 return ( currentChar != ' ' );
138 }
139
140
141 /**
142 * Convert an Attributes as LDIF
143 * @param attrs the Attributes to convert
144 * @return the corresponding LDIF code as a String
145 * @throws NamingException If a naming exception is encountered.
146 */
147 public static String convertToLdif( Attributes attrs ) throws NamingException
148 {
149 return convertAttributesToLdif( AttributeUtils.toClientEntry( attrs, null ), DEFAULT_LINE_LENGTH );
150 }
151
152
153 /**
154 * Convert an Attributes as LDIF
155 * @param attrs the Attributes to convert
156 * @return the corresponding LDIF code as a String
157 * @throws NamingException If a naming exception is encountered.
158 */
159 public static String convertToLdif( Attributes attrs, int length ) throws NamingException
160 {
161 return convertAttributesToLdif( AttributeUtils.toClientEntry( attrs, null ), length );
162 }
163
164
165 /**
166 * Convert an Attributes as LDIF. The DN is written.
167 * @param attrs the Attributes to convert
168 * @return the corresponding LDIF code as a String
169 * @throws NamingException If a naming exception is encountered.
170 */
171 public static String convertToLdif( Attributes attrs, LdapDN dn, int length ) throws NamingException
172 {
173 return convertEntryToLdif( AttributeUtils.toClientEntry( attrs, dn ), length );
174 }
175
176
177 /**
178 * Convert an Attributes as LDIF. The DN is written.
179 * @param attrs the Attributes to convert
180 * @return the corresponding LDIF code as a String
181 * @throws NamingException If a naming exception is encountered.
182 */
183 public static String convertToLdif( Attributes attrs, LdapDN dn ) throws NamingException
184 {
185 return convertEntryToLdif( AttributeUtils.toClientEntry( attrs, dn ), DEFAULT_LINE_LENGTH );
186 }
187
188
189 /**
190 * Convert an Entry to LDIF
191 * @param entry the Entry to convert
192 * @return the corresponding LDIF code as a String
193 * @throws NamingException If a naming exception is encountered.
194 */
195 public static String convertEntryToLdif( Entry entry ) throws NamingException
196 {
197 return convertEntryToLdif( entry, DEFAULT_LINE_LENGTH );
198 }
199
200
201 /**
202 * Convert all the Entry's attributes to LDIF. The DN is not written
203 * @param entry the Entry to convert
204 * @return the corresponding LDIF code as a String
205 * @throws NamingException If a naming exception is encountered.
206 */
207 public static String convertAttributesToLdif( Entry entry ) throws NamingException
208 {
209 return convertAttributesToLdif( entry, DEFAULT_LINE_LENGTH );
210 }
211
212
213 /**
214 * Convert a LDIF String to an attributes.
215 *
216 * @param ldif The LDIF string containing an attribute value
217 * @return An Attributes instance
218 * @exception NamingException If the LDIF String cannot be converted to an Attributes
219 */
220 public static Attributes convertAttributesFromLdif( String ldif ) throws NamingException
221 {
222 LdifAttributesReader reader = new LdifAttributesReader();
223
224 return reader.parseAttributes( ldif );
225 }
226
227
228 /**
229 * Convert an Entry as LDIF
230 * @param entry the Entry to convert
231 * @param length the expected line length
232 * @return the corresponding LDIF code as a String
233 * @throws NamingException If a naming exception is encountered.
234 */
235 public static String convertEntryToLdif( Entry entry, int length ) throws NamingException
236 {
237 StringBuilder sb = new StringBuilder();
238
239 if ( entry.getDn() != null )
240 {
241 // First, dump the DN
242 if ( isLDIFSafe( entry.getDn().getUpName() ) )
243 {
244 sb.append( stripLineToNChars( "dn: " + entry.getDn().getUpName(), length ) );
245 }
246 else
247 {
248 sb.append( stripLineToNChars( "dn:: " + encodeBase64( entry.getDn().getUpName() ), length ) );
249 }
250
251 sb.append( '\n' );
252 }
253
254 // Then all the attributes
255 for ( EntryAttribute attribute:entry )
256 {
257 sb.append( convertToLdif( attribute, length ) );
258 }
259
260 return sb.toString();
261 }
262
263
264 /**
265 * Convert the Entry's attributes to LDIF. The DN is not written.
266 * @param entry the Entry to convert
267 * @param length the expected line length
268 * @return the corresponding LDIF code as a String
269 * @throws NamingException If a naming exception is encountered.
270 */
271 public static String convertAttributesToLdif( Entry entry, int length ) throws NamingException
272 {
273 StringBuilder sb = new StringBuilder();
274
275 // Then all the attributes
276 for ( EntryAttribute attribute:entry )
277 {
278 sb.append( convertToLdif( attribute, length ) );
279 }
280
281 return sb.toString();
282 }
283
284
285 /**
286 * Convert an LdifEntry to LDIF
287 * @param entry the LdifEntry to convert
288 * @return the corresponding LDIF as a String
289 * @throws NamingException If a naming exception is encountered.
290 */
291 public static String convertToLdif( LdifEntry entry ) throws NamingException
292 {
293 return convertToLdif( entry, DEFAULT_LINE_LENGTH );
294 }
295
296
297 /**
298 * Convert an LdifEntry to LDIF
299 * @param entry the LdifEntry to convert
300 * @param length The maximum line's length
301 * @return the corresponding LDIF as a String
302 * @throws NamingException If a naming exception is encountered.
303 */
304 public static String convertToLdif( LdifEntry entry, int length ) throws NamingException
305 {
306 StringBuilder sb = new StringBuilder();
307
308 // First, dump the DN
309 if ( isLDIFSafe( entry.getDn().getUpName() ) )
310 {
311 sb.append( stripLineToNChars( "dn: " + entry.getDn(), length ) );
312 }
313 else
314 {
315 sb.append( stripLineToNChars( "dn:: " + encodeBase64( entry.getDn().getUpName() ), length ) );
316 }
317
318 sb.append( '\n' );
319
320 // Dump the ChangeType
321 String changeType = entry.getChangeType().toString().toLowerCase();
322 sb.append( stripLineToNChars( "changetype: " + changeType, length ) );
323
324 sb.append( '\n' );
325
326 switch ( entry.getChangeType() )
327 {
328 case Delete :
329 if ( entry.getEntry() != null )
330 {
331 throw new NamingException( "Invalid Entry : a deleted entry should not contain attributes" );
332 }
333
334 break;
335
336 case Add :
337 if ( ( entry.getEntry() == null ) )
338 {
339 throw new NamingException( "Invalid Entry : a added or modified entry should contain attributes" );
340 }
341
342 // Now, iterate through all the attributes
343 for ( EntryAttribute attribute:entry.getEntry() )
344 {
345 sb.append( convertToLdif( attribute, length ) );
346 }
347
348 break;
349
350 case ModDn :
351 case ModRdn :
352 if ( entry.getEntry() != null )
353 {
354 throw new NamingException( "Invalid Entry : a modifyDN operation entry should not contain attributes" );
355 }
356
357
358 // Stores the new RDN
359 EntryAttribute newRdn = new DefaultClientAttribute( "newrdn", entry.getNewRdn() );
360 sb.append( convertToLdif( newRdn, length ) );
361
362 // Stores the deleteoldrdn flag
363 sb.append( "deleteoldrdn: " );
364
365 if ( entry.isDeleteOldRdn() )
366 {
367 sb.append( "1" );
368 }
369 else
370 {
371 sb.append( "0" );
372 }
373
374 sb.append( '\n' );
375
376 // Stores the optional newSuperior
377 if ( ! StringTools.isEmpty( entry.getNewSuperior() ) )
378 {
379 EntryAttribute newSuperior = new DefaultClientAttribute( "newsuperior", entry.getNewSuperior() );
380 sb.append( convertToLdif( newSuperior, length ) );
381 }
382
383 break;
384
385 case Modify :
386 for ( Modification modification:entry.getModificationItems() )
387 {
388 switch ( modification.getOperation() )
389 {
390 case ADD_ATTRIBUTE :
391 sb.append( "add: " );
392 break;
393
394 case REMOVE_ATTRIBUTE :
395 sb.append( "delete: " );
396 break;
397
398 case REPLACE_ATTRIBUTE :
399 sb.append( "replace: " );
400 break;
401
402 default :
403 break; // Do nothing
404
405 }
406
407 sb.append( modification.getAttribute().getId() );
408 sb.append( '\n' );
409
410 sb.append( convertToLdif( modification.getAttribute() ) );
411 sb.append( "-\n" );
412 }
413 break;
414
415 default :
416 break; // Do nothing
417
418 }
419
420 sb.append( '\n' );
421
422 return sb.toString();
423 }
424
425 /**
426 * Base64 encode a String
427 * @param str The string to encode
428 * @return the base 64 encoded string
429 */
430 private static String encodeBase64( String str )
431 {
432 char[] encoded =null;
433
434 try
435 {
436 // force encoding using UTF-8 charset, as required in RFC2849 note 7
437 encoded = Base64.encode( str.getBytes( "UTF-8" ) );
438 }
439 catch ( UnsupportedEncodingException e )
440 {
441 encoded = Base64.encode( str.getBytes() );
442 }
443
444 return new String( encoded );
445 }
446
447
448 /**
449 * Converts an EntryAttribute to LDIF
450 * @param attr the >EntryAttribute to convert
451 * @return the corresponding LDIF code as a String
452 * @throws NamingException If a naming exception is encountered.
453 */
454 public static String convertToLdif( EntryAttribute attr ) throws NamingException
455 {
456 return convertToLdif( attr, DEFAULT_LINE_LENGTH );
457 }
458
459
460 /**
461 * Converts an EntryAttribute as LDIF
462 * @param attr the EntryAttribute to convert
463 * @param length the expected line length
464 * @return the corresponding LDIF code as a String
465 * @throws NamingException If a naming exception is encountered.
466 */
467 public static String convertToLdif( EntryAttribute attr, int length ) throws NamingException
468 {
469 StringBuilder sb = new StringBuilder();
470
471 for ( Value<?> value:attr )
472 {
473 StringBuilder lineBuffer = new StringBuilder();
474
475 lineBuffer.append( attr.getId() );
476
477 // First, deal with null value (which is valid)
478 if ( value.isNull() )
479 {
480 lineBuffer.append( ':' );
481 }
482 else if ( value.isBinary() )
483 {
484 // It is binary, so we have to encode it using Base64 before adding it
485 char[] encoded = Base64.encode( value.getBytes() );
486
487 lineBuffer.append( ":: " + new String( encoded ) );
488 }
489 else if ( !value.isBinary() )
490 {
491 // It's a String but, we have to check if encoding isn't required
492 String str = value.getString();
493
494 if ( !LdifUtils.isLDIFSafe( str ) )
495 {
496 lineBuffer.append( ":: " + encodeBase64( str ) );
497 }
498 else
499 {
500 lineBuffer.append( ":" );
501
502 if ( str != null)
503 {
504 lineBuffer.append( " " ).append( str );
505 }
506 }
507 }
508
509 lineBuffer.append( "\n" );
510 sb.append( stripLineToNChars( lineBuffer.toString(), length ) );
511 }
512
513 return sb.toString();
514 }
515
516
517 /**
518 * Strips the String every n specified characters
519 * @param str the string to strip
520 * @param nbChars the number of characters
521 * @return the stripped String
522 */
523 public static String stripLineToNChars( String str, int nbChars)
524 {
525 int strLength = str.length();
526
527 if ( strLength <= nbChars )
528 {
529 return str;
530 }
531
532 if ( nbChars < 2 )
533 {
534 throw new IllegalArgumentException( "The length of each line must be at least 2 chars long" );
535 }
536
537 // We will first compute the new size of the LDIF result
538 // It's at least nbChars chars plus one for \n
539 int charsPerLine = nbChars - 1;
540
541 int remaining = ( strLength - nbChars ) % charsPerLine;
542
543 int nbLines = 1 + ( ( strLength - nbChars ) / charsPerLine ) +
544 ( remaining == 0 ? 0 : 1 );
545
546 int nbCharsTotal = strLength + nbLines + nbLines - 2;
547
548 char[] buffer = new char[ nbCharsTotal ];
549 char[] orig = str.toCharArray();
550
551 int posSrc = 0;
552 int posDst = 0;
553
554 System.arraycopy( orig, posSrc, buffer, posDst, nbChars );
555 posSrc += nbChars;
556 posDst += nbChars;
557
558 for ( int i = 0; i < nbLines - 2; i ++ )
559 {
560 buffer[posDst++] = '\n';
561 buffer[posDst++] = ' ';
562
563 System.arraycopy( orig, posSrc, buffer, posDst, charsPerLine );
564 posSrc += charsPerLine;
565 posDst += charsPerLine;
566 }
567
568 buffer[posDst++] = '\n';
569 buffer[posDst++] = ' ';
570 System.arraycopy( orig, posSrc, buffer, posDst, remaining == 0 ? charsPerLine : remaining );
571
572 return new String( buffer );
573 }
574 }
575