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