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