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