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 * @throws LdapException If a naming exception is encountered. 229 */ 230 public static String convertToLdif( Entry entry ) throws LdapException 231 { 232 return convertToLdif( entry, DEFAULT_LINE_LENGTH ); 233 } 234 235 236 /** 237 * Convert an Entry to LDIF including a version number at the top 238 * 239 * @param entry the Entry to convert 240 * @param includeVersionInfo flag to tell whether to include version number or not 241 * @return the corresponding LDIF code as a String 242 * @throws org.apache.directory.api.ldap.model.exception.LdapException If a naming exception is encountered. 243 */ 244 public static String convertToLdif( Entry entry, boolean includeVersionInfo ) throws LdapException 245 { 246 String ldif = convertToLdif( entry, DEFAULT_LINE_LENGTH ); 247 248 if ( includeVersionInfo ) 249 { 250 ldif = "version: 1" + LINE_SEPARATOR + ldif; 251 } 252 253 return ldif; 254 } 255 256 257 /** 258 * Convert all the Entry's attributes to LDIF. The Dn is not written 259 * 260 * @param entry the Entry to convert 261 * @return the corresponding LDIF code as a String 262 * @throws LdapException If a naming exception is encountered. 263 */ 264 public static String convertAttributesToLdif( Entry entry ) throws LdapException 265 { 266 return convertAttributesToLdif( entry, DEFAULT_LINE_LENGTH ); 267 } 268 269 270 /** 271 * Convert a LDIF String to a JNDI attributes. 272 * 273 * @param ldif The LDIF string containing an attribute value 274 * @return An Attributes instance 275 * @exception LdapLdifException If the LDIF String cannot be converted to an Attributes 276 */ 277 public static Attributes getJndiAttributesFromLdif( String ldif ) throws LdapLdifException 278 { 279 try ( LdifAttributesReader reader = new LdifAttributesReader() ) 280 { 281 Attributes attributes = AttributeUtils.toAttributes( reader.parseEntry( ldif ) ); 282 283 reader.close(); 284 285 return attributes; 286 } 287 catch ( IOException ioe ) 288 { 289 throw new LdapLdifException( ioe.getMessage() ); 290 } 291 } 292 293 294 /** 295 * Convert an Entry as LDIF 296 * 297 * @param entry the Entry to convert 298 * @param length the expected line length 299 * @return the corresponding LDIF code as a String 300 * @throws LdapException If a naming exception is encountered. 301 */ 302 public static String convertToLdif( Entry entry, int length ) throws LdapException 303 { 304 StringBuilder sb = new StringBuilder(); 305 306 if ( entry.getDn() != null ) 307 { 308 // First, dump the Dn 309 if ( isLDIFSafe( entry.getDn().getName() ) ) 310 { 311 sb.append( stripLineToNChars( "dn: " + entry.getDn().getName(), length ) ); 312 } 313 else 314 { 315 sb.append( stripLineToNChars( "dn:: " + encodeBase64( entry.getDn().getName() ), length ) ); 316 } 317 318 sb.append( '\n' ); 319 } 320 321 // Then all the attributes 322 for ( Attribute attribute : entry ) 323 { 324 sb.append( convertToLdif( attribute, length ) ); 325 } 326 327 return sb.toString(); 328 } 329 330 331 /** 332 * Convert the Entry's attributes to LDIF. The Dn is not written. 333 * 334 * @param entry the Entry to convert 335 * @param length the expected line length 336 * @return the corresponding LDIF code as a String 337 * @throws LdapException If a naming exception is encountered. 338 */ 339 public static String convertAttributesToLdif( Entry entry, int length ) throws LdapException 340 { 341 StringBuilder sb = new StringBuilder(); 342 343 // Then all the attributes 344 for ( Attribute attribute : entry ) 345 { 346 sb.append( convertToLdif( attribute, length ) ); 347 } 348 349 return sb.toString(); 350 } 351 352 353 /** 354 * Convert an LdifEntry to LDIF 355 * 356 * @param entry the LdifEntry to convert 357 * @return the corresponding LDIF as a String 358 * @throws LdapException If a naming exception is encountered. 359 */ 360 public static String convertToLdif( LdifEntry entry ) throws LdapException 361 { 362 return convertToLdif( entry, DEFAULT_LINE_LENGTH ); 363 } 364 365 366 /** 367 * Convert an LdifEntry to LDIF 368 * 369 * @param entry the LdifEntry to convert 370 * @param length The maximum line's length 371 * @return the corresponding LDIF as a String 372 * @throws LdapException If a naming exception is encountered. 373 */ 374 public static String convertToLdif( LdifEntry entry, int length ) throws LdapException 375 { 376 StringBuilder sb = new StringBuilder(); 377 378 // First, dump the Dn 379 if ( isLDIFSafe( entry.getDn().getName() ) ) 380 { 381 sb.append( stripLineToNChars( "dn: " + entry.getDn(), length ) ); 382 } 383 else 384 { 385 sb.append( stripLineToNChars( "dn:: " + encodeBase64( entry.getDn().getName() ), length ) ); 386 } 387 388 sb.append( '\n' ); 389 390 // Dump the ChangeType 391 String changeType = Strings.toLowerCaseAscii( entry.getChangeType().toString() ); 392 393 if ( entry.getChangeType() != ChangeType.None ) 394 { 395 // First dump the controls if any 396 if ( entry.hasControls() ) 397 { 398 for ( LdifControl control : entry.getControls().values() ) 399 { 400 StringBuilder controlStr = new StringBuilder(); 401 402 controlStr.append( "control: " ).append( control.getOid() ); 403 controlStr.append( " " ).append( control.isCritical() ); 404 405 if ( control.hasValue() ) 406 { 407 controlStr.append( "::" ).append( Base64.encode( control.getValue() ) ); 408 } 409 410 sb.append( stripLineToNChars( controlStr.toString(), length ) ); 411 sb.append( '\n' ); 412 } 413 } 414 415 sb.append( stripLineToNChars( "changetype: " + changeType, length ) ); 416 sb.append( '\n' ); 417 } 418 419 switch ( entry.getChangeType() ) 420 { 421 case None: 422 if ( entry.hasControls() ) 423 { 424 sb.append( stripLineToNChars( "changetype: " + ChangeType.Add, length ) ); 425 } 426 427 // Fallthrough 428 429 case Add: 430 if ( entry.getEntry() == null ) 431 { 432 throw new LdapException( I18n.err( I18n.ERR_12082 ) ); 433 } 434 435 // Now, iterate through all the attributes 436 for ( Attribute attribute : entry.getEntry() ) 437 { 438 sb.append( convertToLdif( attribute, length ) ); 439 } 440 441 break; 442 443 case Delete: 444 if ( entry.getEntry() != null ) 445 { 446 throw new LdapException( I18n.err( I18n.ERR_12081 ) ); 447 } 448 449 break; 450 451 case ModDn: 452 case ModRdn: 453 if ( entry.getEntry() != null ) 454 { 455 throw new LdapException( I18n.err( I18n.ERR_12083 ) ); 456 } 457 458 // Stores the new Rdn 459 Attribute newRdn = new DefaultAttribute( "newrdn", entry.getNewRdn() ); 460 sb.append( convertToLdif( newRdn, length ) ); 461 462 // Stores the deleteoldrdn flag 463 sb.append( "deleteoldrdn: " ); 464 465 if ( entry.isDeleteOldRdn() ) 466 { 467 sb.append( "1" ); 468 } 469 else 470 { 471 sb.append( "0" ); 472 } 473 474 sb.append( '\n' ); 475 476 // Stores the optional newSuperior 477 if ( !Strings.isEmpty( entry.getNewSuperior() ) ) 478 { 479 Attribute newSuperior = new DefaultAttribute( "newsuperior", entry.getNewSuperior() ); 480 sb.append( convertToLdif( newSuperior, length ) ); 481 } 482 483 break; 484 485 case Modify: 486 boolean isFirst = true; 487 488 for ( Modification modification : entry.getModifications() ) 489 { 490 491 if ( isFirst ) 492 { 493 isFirst = false; 494 } 495 else 496 { 497 sb.append( "-\n" ); 498 } 499 500 switch ( modification.getOperation() ) 501 { 502 case ADD_ATTRIBUTE: 503 sb.append( "add: " ); 504 break; 505 506 case REMOVE_ATTRIBUTE: 507 sb.append( "delete: " ); 508 break; 509 510 case REPLACE_ATTRIBUTE: 511 sb.append( "replace: " ); 512 break; 513 514 default: 515 throw new IllegalArgumentException( "Unexpected ModificationOperation: " 516 + modification.getOperation() ); 517 } 518 519 sb.append( modification.getAttribute().getUpId() ); 520 sb.append( '\n' ); 521 522 sb.append( convertToLdif( modification.getAttribute(), length ) ); 523 } 524 525 sb.append( '-' ); 526 break; 527 528 default: 529 throw new IllegalArgumentException( "Unexpected ChangeType: " + entry.getChangeType() ); 530 } 531 532 sb.append( '\n' ); 533 534 return sb.toString(); 535 } 536 537 538 /** 539 * Base64 encode a String 540 * 541 * @param str The string to encode 542 * @return the base 64 encoded string 543 */ 544 private static String encodeBase64( String str ) 545 { 546 // force encoding using UTF-8 charset, as required in RFC2849 note 7 547 return new String( Base64.encode( Strings.getBytesUtf8( str ) ) ); 548 } 549 550 551 /** 552 * Converts an EntryAttribute to LDIF 553 * 554 * @param attr the EntryAttribute to convert 555 * @return the corresponding LDIF code as a String 556 * @throws LdapException If a naming exception is encountered. 557 */ 558 public static String convertToLdif( Attribute attr ) throws LdapException 559 { 560 return convertToLdif( attr, DEFAULT_LINE_LENGTH ); 561 } 562 563 564 /** 565 * Converts an EntryAttribute as LDIF 566 * 567 * @param attr the EntryAttribute to convert 568 * @param length the expected line length 569 * @return the corresponding LDIF code as a String 570 * @throws LdapException If a naming exception is encountered. 571 */ 572 public static String convertToLdif( Attribute attr, int length ) throws LdapException 573 { 574 StringBuilder sb = new StringBuilder(); 575 576 if ( attr.size() == 0 ) 577 { 578 // Special case : we don't have any value 579 return ""; 580 } 581 582 for ( Value<?> value : attr ) 583 { 584 StringBuilder lineBuffer = new StringBuilder(); 585 586 lineBuffer.append( attr.getUpId() ); 587 588 // First, deal with null value (which is valid) 589 if ( value.isNull() ) 590 { 591 lineBuffer.append( ':' ); 592 } 593 else if ( value.isHumanReadable() ) 594 { 595 // It's a String but, we have to check if encoding isn't required 596 String str = value.getString(); 597 598 if ( !LdifUtils.isLDIFSafe( str ) ) 599 { 600 lineBuffer.append( ":: " ).append( encodeBase64( str ) ); 601 } 602 else 603 { 604 lineBuffer.append( ':' ); 605 606 if ( str != null ) 607 { 608 lineBuffer.append( ' ' ).append( str ); 609 } 610 } 611 } 612 else 613 { 614 // It is binary, so we have to encode it using Base64 before adding it 615 char[] encoded = Base64.encode( value.getBytes() ); 616 617 lineBuffer.append( ":: " + new String( encoded ) ); 618 } 619 620 lineBuffer.append( '\n' ); 621 sb.append( stripLineToNChars( lineBuffer.toString(), length ) ); 622 } 623 624 return sb.toString(); 625 } 626 627 628 /** 629 * Strips the String every n specified characters 630 * 631 * @param str the string to strip 632 * @param nbChars the number of characters 633 * @return the stripped String 634 */ 635 public static String stripLineToNChars( String str, int nbChars ) 636 { 637 int strLength = str.length(); 638 639 if ( strLength <= nbChars ) 640 { 641 return str; 642 } 643 644 if ( nbChars < 2 ) 645 { 646 throw new IllegalArgumentException( I18n.err( I18n.ERR_12084 ) ); 647 } 648 649 // We will first compute the new size of the LDIF result 650 // It's at least nbChars chars plus one for \n 651 int charsPerLine = nbChars - 1; 652 653 int remaining = ( strLength - nbChars ) % charsPerLine; 654 655 int nbLines = 1 + ( ( strLength - nbChars ) / charsPerLine ) + ( remaining == 0 ? 0 : 1 ); 656 657 int nbCharsTotal = strLength + nbLines + nbLines - 2; 658 659 char[] buffer = new char[nbCharsTotal]; 660 char[] orig = str.toCharArray(); 661 662 int posSrc = 0; 663 int posDst = 0; 664 665 System.arraycopy( orig, posSrc, buffer, posDst, nbChars ); 666 posSrc += nbChars; 667 posDst += nbChars; 668 669 for ( int i = 0; i < nbLines - 2; i++ ) 670 { 671 buffer[posDst++] = '\n'; 672 buffer[posDst++] = ' '; 673 674 System.arraycopy( orig, posSrc, buffer, posDst, charsPerLine ); 675 posSrc += charsPerLine; 676 posDst += charsPerLine; 677 } 678 679 buffer[posDst++] = '\n'; 680 buffer[posDst++] = ' '; 681 System.arraycopy( orig, posSrc, buffer, posDst, remaining == 0 ? charsPerLine : remaining ); 682 683 return new String( buffer ); 684 } 685 686 687 /** 688 * Build a new Attributes instance from a LDIF list of lines. The values can be 689 * either a complete Ava, or a couple of AttributeType ID and a value (a String or 690 * a byte[]). The following sample shows the three cases : 691 * 692 * <pre> 693 * Attribute attr = AttributeUtils.createAttributes( 694 * "objectclass: top", 695 * "cn", "My name", 696 * "jpegPhoto", new byte[]{0x01, 0x02} ); 697 * </pre> 698 * 699 * @param avas The AttributeType and Values, using a ldif format, or a couple of 700 * Attribute ID/Value 701 * @return An Attributes instance 702 * @throws LdapException If the data are invalid 703 */ 704 public static Attributes createJndiAttributes( Object... avas ) throws LdapException 705 { 706 StringBuilder sb = new StringBuilder(); 707 int pos = 0; 708 boolean valueExpected = false; 709 710 for ( Object ava : avas ) 711 { 712 if ( !valueExpected ) 713 { 714 if ( !( ava instanceof String ) ) 715 { 716 throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, I18n.err( 717 I18n.ERR_12085, pos + 1 ) ); 718 } 719 720 String attribute = ( String ) ava; 721 sb.append( attribute ); 722 723 if ( attribute.indexOf( ':' ) != -1 ) 724 { 725 sb.append( '\n' ); 726 } 727 else 728 { 729 valueExpected = true; 730 } 731 } 732 else 733 { 734 if ( ava instanceof String ) 735 { 736 sb.append( ": " ).append( ( String ) ava ).append( '\n' ); 737 } 738 else if ( ava instanceof byte[] ) 739 { 740 sb.append( ":: " ); 741 sb.append( new String( Base64.encode( ( byte[] ) ava ) ) ); 742 sb.append( '\n' ); 743 } 744 else 745 { 746 throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, I18n.err( 747 I18n.ERR_12086, pos + 1 ) ); 748 } 749 750 valueExpected = false; 751 } 752 } 753 754 if ( valueExpected ) 755 { 756 throw new LdapInvalidAttributeValueException( ResultCodeEnum.INVALID_ATTRIBUTE_SYNTAX, I18n 757 .err( I18n.ERR_12087 ) ); 758 } 759 760 LdifAttributesReader reader = new LdifAttributesReader(); 761 Attributes attributes = AttributeUtils.toAttributes( reader.parseEntry( sb.toString() ) ); 762 763 try 764 { 765 reader.close(); 766 } 767 catch ( IOException e ) 768 { 769 e.printStackTrace(); 770 } 771 772 return attributes; 773 } 774}