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 import javax.naming.directory.InvalidAttributeValueException;
027
028 import org.apache.directory.shared.i18n.I18n;
029 import org.apache.directory.shared.ldap.entry.Entry;
030 import org.apache.directory.shared.ldap.entry.EntryAttribute;
031 import org.apache.directory.shared.ldap.entry.Modification;
032 import org.apache.directory.shared.ldap.entry.Value;
033 import org.apache.directory.shared.ldap.entry.client.DefaultClientAttribute;
034 import org.apache.directory.shared.ldap.name.DN;
035 import org.apache.directory.shared.ldap.util.AttributeUtils;
036 import org.apache.directory.shared.ldap.util.Base64;
037 import org.apache.directory.shared.ldap.util.StringTools;
038
039
040
041 /**
042 * Some LDIF useful methods
043 *
044 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
045 * @version $Rev$, $Date$
046 */
047 public class LdifUtils
048 {
049 /** The array that will be used to match the first char.*/
050 private static boolean[] LDIF_SAFE_STARTING_CHAR_ALPHABET = new boolean[128];
051
052 /** The array that will be used to match the other chars.*/
053 private static boolean[] LDIF_SAFE_OTHER_CHARS_ALPHABET = new boolean[128];
054
055 /** The default length for a line in a ldif file */
056 private static final int DEFAULT_LINE_LENGTH = 80;
057
058 static
059 {
060 // Initialization of the array that will be used to match the first char.
061 for (int i = 0; i < 128; i++)
062 {
063 LDIF_SAFE_STARTING_CHAR_ALPHABET[i] = true;
064 }
065
066 LDIF_SAFE_STARTING_CHAR_ALPHABET[0] = false; // 0 (NUL)
067 LDIF_SAFE_STARTING_CHAR_ALPHABET[10] = false; // 10 (LF)
068 LDIF_SAFE_STARTING_CHAR_ALPHABET[13] = false; // 13 (CR)
069 LDIF_SAFE_STARTING_CHAR_ALPHABET[32] = false; // 32 (SPACE)
070 LDIF_SAFE_STARTING_CHAR_ALPHABET[58] = false; // 58 (:)
071 LDIF_SAFE_STARTING_CHAR_ALPHABET[60] = false; // 60 (>)
072
073 // Initialization of the array that will be used to match the other chars.
074 for (int i = 0; i < 128; i++)
075 {
076 LDIF_SAFE_OTHER_CHARS_ALPHABET[i] = true;
077 }
078
079 LDIF_SAFE_OTHER_CHARS_ALPHABET[0] = false; // 0 (NUL)
080 LDIF_SAFE_OTHER_CHARS_ALPHABET[10] = false; // 10 (LF)
081 LDIF_SAFE_OTHER_CHARS_ALPHABET[13] = false; // 13 (CR)
082 }
083
084
085 /**
086 * Checks if the input String contains only safe values, that is, the data
087 * does not need to be encoded for use with LDIF. The rules for checking safety
088 * are based on the rules for LDIF (LDAP Data Interchange Format) per RFC 2849.
089 * The data does not need to be encoded if all the following are true:
090 *
091 * The data cannot start with the following char values:
092 * 00 (NUL)
093 * 10 (LF)
094 * 13 (CR)
095 * 32 (SPACE)
096 * 58 (:)
097 * 60 (<)
098 * Any character with value greater than 127
099 *
100 * The data cannot contain any of the following char values:
101 * 00 (NUL)
102 * 10 (LF)
103 * 13 (CR)
104 * Any character with value greater than 127
105 *
106 * The data cannot end with a space.
107 *
108 * @param str the String to be checked
109 * @return true if encoding not required for LDIF
110 */
111 public static boolean isLDIFSafe( String str )
112 {
113 if ( StringTools.isEmpty( str ) )
114 {
115 // A null string is LDIF safe
116 return true;
117 }
118
119 // Checking the first char
120 char currentChar = str.charAt(0);
121
122 if ( ( currentChar > 127 ) || !LDIF_SAFE_STARTING_CHAR_ALPHABET[currentChar] )
123 {
124 return false;
125 }
126
127 // Checking the other chars
128 for (int i = 1; i < str.length(); i++)
129 {
130 currentChar = str.charAt(i);
131
132 if ( ( currentChar > 127 ) || !LDIF_SAFE_OTHER_CHARS_ALPHABET[currentChar] )
133 {
134 return false;
135 }
136 }
137
138 // The String cannot end with a space
139 return ( currentChar != ' ' );
140 }
141
142
143 /**
144 * Convert an Attributes as LDIF
145 * @param attrs the Attributes to convert
146 * @return the corresponding LDIF code as a String
147 * @throws NamingException If a naming exception is encountered.
148 */
149 public static String convertToLdif( Attributes attrs ) throws NamingException
150 {
151 return convertAttributesToLdif( AttributeUtils.toClientEntry( attrs, null ), DEFAULT_LINE_LENGTH );
152 }
153
154
155 /**
156 * Convert an Attributes as LDIF
157 * @param attrs the Attributes to convert
158 * @return the corresponding LDIF code as a String
159 * @throws NamingException If a naming exception is encountered.
160 */
161 public static String convertToLdif( Attributes attrs, int length ) throws NamingException
162 {
163 return convertAttributesToLdif( AttributeUtils.toClientEntry( attrs, null ), length );
164 }
165
166
167 /**
168 * Convert an Attributes as LDIF. The DN is written.
169 * @param attrs the Attributes to convert
170 * @return the corresponding LDIF code as a String
171 * @throws NamingException If a naming exception is encountered.
172 */
173 public static String convertToLdif( Attributes attrs, DN dn, int length ) throws NamingException
174 {
175 return convertEntryToLdif( AttributeUtils.toClientEntry( attrs, dn ), length );
176 }
177
178
179 /**
180 * Convert an Attributes as LDIF. The DN is written.
181 * @param attrs the Attributes to convert
182 * @return the corresponding LDIF code as a String
183 * @throws NamingException If a naming exception is encountered.
184 */
185 public static String convertToLdif( Attributes attrs, DN dn ) throws NamingException
186 {
187 return convertEntryToLdif( AttributeUtils.toClientEntry( attrs, dn ), DEFAULT_LINE_LENGTH );
188 }
189
190
191 /**
192 * Convert an Entry to LDIF
193 * @param entry the Entry to convert
194 * @return the corresponding LDIF code as a String
195 * @throws NamingException If a naming exception is encountered.
196 */
197 public static String convertEntryToLdif( Entry entry ) throws NamingException
198 {
199 return convertEntryToLdif( entry, DEFAULT_LINE_LENGTH );
200 }
201
202
203 /**
204 * Convert all the Entry's attributes to LDIF. The DN is not written
205 * @param entry the Entry to convert
206 * @return the corresponding LDIF code as a String
207 * @throws NamingException If a naming exception is encountered.
208 */
209 public static String convertAttributesToLdif( Entry entry ) throws NamingException
210 {
211 return convertAttributesToLdif( entry, DEFAULT_LINE_LENGTH );
212 }
213
214
215 /**
216 * Convert a LDIF String to an attributes.
217 *
218 * @param ldif The LDIF string containing an attribute value
219 * @return An Attributes instance
220 * @exception NamingException If the LDIF String cannot be converted to an Attributes
221 */
222 public static Attributes convertAttributesFromLdif( String ldif ) throws NamingException
223 {
224 LdifAttributesReader reader = new LdifAttributesReader();
225
226 return reader.parseAttributes( ldif );
227 }
228
229
230 /**
231 * Convert an Entry as LDIF
232 * @param entry the Entry to convert
233 * @param length the expected line length
234 * @return the corresponding LDIF code as a String
235 * @throws NamingException If a naming exception is encountered.
236 */
237 public static String convertEntryToLdif( Entry entry, int length ) throws NamingException
238 {
239 StringBuilder sb = new StringBuilder();
240
241 if ( entry.getDn() != null )
242 {
243 // First, dump the DN
244 if ( isLDIFSafe( entry.getDn().getName() ) )
245 {
246 sb.append( stripLineToNChars( "dn: " + entry.getDn().getName(), length ) );
247 }
248 else
249 {
250 sb.append( stripLineToNChars( "dn:: " + encodeBase64( entry.getDn().getName() ), length ) );
251 }
252
253 sb.append( '\n' );
254 }
255
256 // Then all the attributes
257 for ( EntryAttribute attribute:entry )
258 {
259 sb.append( convertToLdif( attribute, length ) );
260 }
261
262 return sb.toString();
263 }
264
265
266 /**
267 * Convert the Entry's attributes to LDIF. The DN is not written.
268 * @param entry the Entry to convert
269 * @param length the expected line length
270 * @return the corresponding LDIF code as a String
271 * @throws NamingException If a naming exception is encountered.
272 */
273 public static String convertAttributesToLdif( Entry entry, int length ) throws NamingException
274 {
275 StringBuilder sb = new StringBuilder();
276
277 // Then all the attributes
278 for ( EntryAttribute attribute:entry )
279 {
280 sb.append( convertToLdif( attribute, length ) );
281 }
282
283 return sb.toString();
284 }
285
286
287 /**
288 * Convert an LdifEntry to LDIF
289 * @param entry the LdifEntry to convert
290 * @return the corresponding LDIF as a String
291 * @throws NamingException If a naming exception is encountered.
292 */
293 public static String convertToLdif( LdifEntry entry ) throws NamingException
294 {
295 return convertToLdif( entry, DEFAULT_LINE_LENGTH );
296 }
297
298
299 /**
300 * Convert an LdifEntry to LDIF
301 * @param entry the LdifEntry to convert
302 * @param length The maximum line's length
303 * @return the corresponding LDIF as a String
304 * @throws NamingException If a naming exception is encountered.
305 */
306 public static String convertToLdif( LdifEntry entry, int length ) throws NamingException
307 {
308 StringBuilder sb = new StringBuilder();
309
310 // First, dump the DN
311 if ( isLDIFSafe( entry.getDn().getName() ) )
312 {
313 sb.append( stripLineToNChars( "dn: " + entry.getDn(), length ) );
314 }
315 else
316 {
317 sb.append( stripLineToNChars( "dn:: " + encodeBase64( entry.getDn().getName() ), length ) );
318 }
319
320 sb.append( '\n' );
321
322 // Dump the ChangeType
323 String changeType = entry.getChangeType().toString().toLowerCase();
324
325 if ( entry.getChangeType() != ChangeType.Modify )
326 {
327 sb.append( stripLineToNChars( "changetype: " + changeType, length ) );
328 sb.append( '\n' );
329 }
330
331 switch ( entry.getChangeType() )
332 {
333 case Delete :
334 if ( entry.getEntry() != null )
335 {
336 throw new NamingException( I18n.err( I18n.ERR_12081 ) );
337 }
338
339 break;
340
341 case Add :
342 if ( ( entry.getEntry() == null ) )
343 {
344 throw new NamingException( I18n.err( I18n.ERR_12082 ) );
345 }
346
347 // Now, iterate through all the attributes
348 for ( EntryAttribute attribute:entry.getEntry() )
349 {
350 sb.append( convertToLdif( attribute, length ) );
351 }
352
353 break;
354
355 case ModDn :
356 case ModRdn :
357 if ( entry.getEntry() != null )
358 {
359 throw new NamingException( I18n.err( I18n.ERR_12083 ) );
360 }
361
362
363 // Stores the new RDN
364 EntryAttribute newRdn = new DefaultClientAttribute( "newrdn", entry.getNewRdn() );
365 sb.append( convertToLdif( newRdn, length ) );
366
367 // Stores the deleteoldrdn flag
368 sb.append( "deleteoldrdn: " );
369
370 if ( entry.isDeleteOldRdn() )
371 {
372 sb.append( "1" );
373 }
374 else
375 {
376 sb.append( "0" );
377 }
378
379 sb.append( '\n' );
380
381 // Stores the optional newSuperior
382 if ( ! StringTools.isEmpty( entry.getNewSuperior() ) )
383 {
384 EntryAttribute newSuperior = new DefaultClientAttribute( "newsuperior", entry.getNewSuperior() );
385 sb.append( convertToLdif( newSuperior, length ) );
386 }
387
388 break;
389
390 case Modify :
391 for ( Modification modification:entry.getModificationItems() )
392 {
393 switch ( modification.getOperation() )
394 {
395 case ADD_ATTRIBUTE :
396 sb.append( "add: " );
397 break;
398
399 case REMOVE_ATTRIBUTE :
400 sb.append( "delete: " );
401 break;
402
403 case REPLACE_ATTRIBUTE :
404 sb.append( "replace: " );
405 break;
406
407 default :
408 break; // Do nothing
409
410 }
411
412 sb.append( modification.getAttribute().getId() );
413 sb.append( '\n' );
414
415 sb.append( convertToLdif( modification.getAttribute() ) );
416 sb.append( "-\n" );
417 }
418 break;
419
420 default :
421 break; // Do nothing
422
423 }
424
425 sb.append( '\n' );
426
427 return sb.toString();
428 }
429
430 /**
431 * Base64 encode a String
432 * @param str The string to encode
433 * @return the base 64 encoded string
434 */
435 private static String encodeBase64( String str )
436 {
437 char[] encoded =null;
438
439 try
440 {
441 // force encoding using UTF-8 charset, as required in RFC2849 note 7
442 encoded = Base64.encode( str.getBytes( "UTF-8" ) );
443 }
444 catch ( UnsupportedEncodingException e )
445 {
446 encoded = Base64.encode( str.getBytes() );
447 }
448
449 return new String( encoded );
450 }
451
452
453 /**
454 * Converts an EntryAttribute to LDIF
455 * @param attr the >EntryAttribute to convert
456 * @return the corresponding LDIF code as a String
457 * @throws NamingException If a naming exception is encountered.
458 */
459 public static String convertToLdif( EntryAttribute attr ) throws NamingException
460 {
461 return convertToLdif( attr, DEFAULT_LINE_LENGTH );
462 }
463
464
465 /**
466 * Converts an EntryAttribute as LDIF
467 * @param attr the EntryAttribute to convert
468 * @param length the expected line length
469 * @return the corresponding LDIF code as a String
470 * @throws NamingException If a naming exception is encountered.
471 */
472 public static String convertToLdif( EntryAttribute attr, int length ) throws NamingException
473 {
474 StringBuilder sb = new StringBuilder();
475
476 for ( Value<?> value:attr )
477 {
478 StringBuilder lineBuffer = new StringBuilder();
479
480 lineBuffer.append( attr.getId() );
481
482 // First, deal with null value (which is valid)
483 if ( value.isNull() )
484 {
485 lineBuffer.append( ':' );
486 }
487 else if ( value.isBinary() )
488 {
489 // It is binary, so we have to encode it using Base64 before adding it
490 char[] encoded = Base64.encode( value.getBytes() );
491
492 lineBuffer.append( ":: " + new String( encoded ) );
493 }
494 else if ( !value.isBinary() )
495 {
496 // It's a String but, we have to check if encoding isn't required
497 String str = value.getString();
498
499 if ( !LdifUtils.isLDIFSafe( str ) )
500 {
501 lineBuffer.append( ":: " + encodeBase64( str ) );
502 }
503 else
504 {
505 lineBuffer.append( ":" );
506
507 if ( str != null)
508 {
509 lineBuffer.append( " " ).append( str );
510 }
511 }
512 }
513
514 lineBuffer.append( "\n" );
515 sb.append( stripLineToNChars( lineBuffer.toString(), length ) );
516 }
517
518 return sb.toString();
519 }
520
521
522 /**
523 * Strips the String every n specified characters
524 * @param str the string to strip
525 * @param nbChars the number of characters
526 * @return the stripped String
527 */
528 public static String stripLineToNChars( String str, int nbChars)
529 {
530 int strLength = str.length();
531
532 if ( strLength <= nbChars )
533 {
534 return str;
535 }
536
537 if ( nbChars < 2 )
538 {
539 throw new IllegalArgumentException( I18n.err( I18n.ERR_12084 ) );
540 }
541
542 // We will first compute the new size of the LDIF result
543 // It's at least nbChars chars plus one for \n
544 int charsPerLine = nbChars - 1;
545
546 int remaining = ( strLength - nbChars ) % charsPerLine;
547
548 int nbLines = 1 + ( ( strLength - nbChars ) / charsPerLine ) +
549 ( remaining == 0 ? 0 : 1 );
550
551 int nbCharsTotal = strLength + nbLines + nbLines - 2;
552
553 char[] buffer = new char[ nbCharsTotal ];
554 char[] orig = str.toCharArray();
555
556 int posSrc = 0;
557 int posDst = 0;
558
559 System.arraycopy( orig, posSrc, buffer, posDst, nbChars );
560 posSrc += nbChars;
561 posDst += nbChars;
562
563 for ( int i = 0; i < nbLines - 2; i ++ )
564 {
565 buffer[posDst++] = '\n';
566 buffer[posDst++] = ' ';
567
568 System.arraycopy( orig, posSrc, buffer, posDst, charsPerLine );
569 posSrc += charsPerLine;
570 posDst += charsPerLine;
571 }
572
573 buffer[posDst++] = '\n';
574 buffer[posDst++] = ' ';
575 System.arraycopy( orig, posSrc, buffer, posDst, remaining == 0 ? charsPerLine : remaining );
576
577 return new String( buffer );
578 }
579
580
581 /**
582 * Build a new Attributes instance from a LDIF list of lines. The values can be
583 * either a complete AVA, or a couple of AttributeType ID and a value (a String or
584 * a byte[]). The following sample shows the three cases :
585 *
586 * <pre>
587 * Attribute attr = AttributeUtils.createAttributes(
588 * "objectclass: top",
589 * "cn", "My name",
590 * "jpegPhoto", new byte[]{0x01, 0x02} );
591 * </pre>
592 *
593 * @param avas The AttributeType and Values, using a ldif format, or a couple of
594 * Attribute ID/Value
595 * @return An Attributes instance
596 * @throws NamingException If the data are invalid
597 */
598 public static Attributes createAttributes( Object... avas ) throws NamingException
599 {
600 StringBuilder sb = new StringBuilder();
601 int pos = 0;
602 boolean valueExpected = false;
603
604 for ( Object ava : avas)
605 {
606 if ( !valueExpected )
607 {
608 if ( !(ava instanceof String) )
609 {
610 throw new InvalidAttributeValueException( I18n.err( I18n.ERR_12085, (pos+1) ) );
611 }
612
613 String attribute = (String)ava;
614 sb.append( attribute );
615
616 if ( attribute.indexOf( ':' ) != -1 )
617 {
618 sb.append( '\n' );
619 }
620 else
621 {
622 valueExpected = true;
623 }
624 }
625 else
626 {
627 if ( ava instanceof String )
628 {
629 sb.append( ": " ).append( (String)ava ).append( '\n' );
630 }
631 else if ( ava instanceof byte[] )
632 {
633 sb.append( ":: " );
634 sb.append( new String( Base64.encode( (byte[] )ava ) ) );
635 sb.append( '\n' );
636 }
637 else
638 {
639 throw new InvalidAttributeValueException( I18n.err( I18n.ERR_12086, (pos+1) ) );
640 }
641
642 valueExpected = false;
643 }
644 }
645
646 if ( valueExpected )
647 {
648 throw new InvalidAttributeValueException( I18n.err( I18n.ERR_12087 ) );
649 }
650
651 LdifAttributesReader reader = new LdifAttributesReader();
652 Attributes attributes = reader.parseAttributes( sb.toString() );
653
654 return attributes;
655 }
656 }
657