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