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