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 023 import java.io.BufferedReader; 024 import java.io.Closeable; 025 import java.io.DataInputStream; 026 import java.io.File; 027 import java.io.FileInputStream; 028 import java.io.FileNotFoundException; 029 import java.io.FileReader; 030 import java.io.IOException; 031 import java.io.InputStream; 032 import java.io.InputStreamReader; 033 import java.io.Reader; 034 import java.io.StringReader; 035 import java.io.UnsupportedEncodingException; 036 import java.net.MalformedURLException; 037 import java.net.URL; 038 import java.nio.charset.Charset; 039 import java.util.ArrayList; 040 import java.util.Iterator; 041 import java.util.List; 042 import java.util.NoSuchElementException; 043 044 import org.apache.directory.shared.asn1.primitives.OID; 045 import org.apache.directory.shared.i18n.I18n; 046 import org.apache.directory.shared.ldap.entry.EntryAttribute; 047 import org.apache.directory.shared.ldap.entry.ModificationOperation; 048 import org.apache.directory.shared.ldap.entry.client.DefaultClientAttribute; 049 import org.apache.directory.shared.ldap.exception.LdapException; 050 import org.apache.directory.shared.ldap.exception.LdapInvalidDnException; 051 import org.apache.directory.shared.ldap.message.control.Control; 052 import org.apache.directory.shared.ldap.name.DN; 053 import org.apache.directory.shared.ldap.name.DnParser; 054 import org.apache.directory.shared.ldap.name.RDN; 055 import org.apache.directory.shared.ldap.util.Base64; 056 import org.apache.directory.shared.ldap.util.StringTools; 057 import org.slf4j.Logger; 058 import org.slf4j.LoggerFactory; 059 060 061 /** 062 * <pre> 063 * <ldif-file> ::= "version:" <fill> <number> <seps> <dn-spec> <sep> 064 * <ldif-content-change> 065 * 066 * <ldif-content-change> ::= 067 * <number> <oid> <options-e> <value-spec> <sep> 068 * <attrval-specs-e> <ldif-attrval-record-e> | 069 * <alpha> <chars-e> <options-e> <value-spec> <sep> 070 * <attrval-specs-e> <ldif-attrval-record-e> | 071 * "control:" <fill> <number> <oid> <spaces-e> 072 * <criticality> <value-spec-e> <sep> <controls-e> 073 * "changetype:" <fill> <changerecord-type> <ldif-change-record-e> | 074 * "changetype:" <fill> <changerecord-type> <ldif-change-record-e> 075 * 076 * <ldif-attrval-record-e> ::= <seps> <dn-spec> <sep> <attributeType> 077 * <options-e> <value-spec> <sep> <attrval-specs-e> 078 * <ldif-attrval-record-e> | e 079 * 080 * <ldif-change-record-e> ::= <seps> <dn-spec> <sep> <controls-e> 081 * "changetype:" <fill> <changerecord-type> <ldif-change-record-e> | e 082 * 083 * <dn-spec> ::= "dn:" <fill> <safe-string> | "dn::" <fill> <base64-string> 084 * 085 * <controls-e> ::= "control:" <fill> <number> <oid> <spaces-e> <criticality> 086 * <value-spec-e> <sep> <controls-e> | e 087 * 088 * <criticality> ::= "true" | "false" | e 089 * 090 * <oid> ::= '.' <number> <oid> | e 091 * 092 * <attrval-specs-e> ::= <number> <oid> <options-e> <value-spec> 093 * <sep> <attrval-specs-e> | 094 * <alpha> <chars-e> <options-e> <value-spec> <sep> <attrval-specs-e> | e 095 * 096 * <value-spec-e> ::= <value-spec> | e 097 * 098 * <value-spec> ::= ':' <fill> <safe-string-e> | 099 * "::" <fill> <base64-chars> | 100 * ":<" <fill> <url> 101 * 102 * <attributeType> ::= <number> <oid> | <alpha> <chars-e> 103 * 104 * <options-e> ::= ';' <char> <chars-e> <options-e> |e 105 * 106 * <chars-e> ::= <char> <chars-e> | e 107 * 108 * <changerecord-type> ::= "add" <sep> <attributeType> 109 * <options-e> <value-spec> <sep> <attrval-specs-e> | 110 * "delete" <sep> | 111 * "modify" <sep> <mod-type> <fill> <attributeType> 112 * <options-e> <sep> <attrval-specs-e> <sep> '-' <sep> <mod-specs-e> | 113 * "moddn" <sep> <newrdn> <sep> "deleteoldrdn:" 114 * <fill> <0-1> <sep> <newsuperior-e> <sep> | 115 * "modrdn" <sep> <newrdn> <sep> "deleteoldrdn:" 116 * <fill> <0-1> <sep> <newsuperior-e> <sep> 117 * 118 * <newrdn> ::= ':' <fill> <safe-string> | "::" <fill> <base64-chars> 119 * 120 * <newsuperior-e> ::= "newsuperior" <newrdn> | e 121 * 122 * <mod-specs-e> ::= <mod-type> <fill> <attributeType> <options-e> 123 * <sep> <attrval-specs-e> <sep> '-' <sep> <mod-specs-e> | e 124 * 125 * <mod-type> ::= "add:" | "delete:" | "replace:" 126 * 127 * <url> ::= <a Uniform Resource Locator, as defined in [6]> 128 * 129 * 130 * 131 * LEXICAL 132 * ------- 133 * 134 * <fill> ::= ' ' <fill> | e 135 * <char> ::= <alpha> | <digit> | '-' 136 * <number> ::= <digit> <digits> 137 * <0-1> ::= '0' | '1' 138 * <digits> ::= <digit> <digits> | e 139 * <digit> ::= '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' 140 * <seps> ::= <sep> <seps-e> 141 * <seps-e> ::= <sep> <seps-e> | e 142 * <sep> ::= 0x0D 0x0A | 0x0A 143 * <spaces> ::= ' ' <spaces-e> 144 * <spaces-e> ::= ' ' <spaces-e> | e 145 * <safe-string-e> ::= <safe-string> | e 146 * <safe-string> ::= <safe-init-char> <safe-chars> 147 * <safe-init-char> ::= [0x01-0x09] | 0x0B | 0x0C | [0x0E-0x1F] | [0x21-0x39] | 0x3B | [0x3D-0x7F] 148 * <safe-chars> ::= <safe-char> <safe-chars> | e 149 * <safe-char> ::= [0x01-0x09] | 0x0B | 0x0C | [0x0E-0x7F] 150 * <base64-string> ::= <base64-char> <base64-chars> 151 * <base64-chars> ::= <base64-char> <base64-chars> | e 152 * <base64-char> ::= 0x2B | 0x2F | [0x30-0x39] | 0x3D | [0x41-9x5A] | [0x61-0x7A] 153 * <alpha> ::= [0x41-0x5A] | [0x61-0x7A] 154 * 155 * COMMENTS 156 * -------- 157 * - The ldap-oid VN is not correct in the RFC-2849. It has been changed from 1*DIGIT 0*1("." 1*DIGIT) to 158 * DIGIT+ ("." DIGIT+)* 159 * - The mod-spec lacks a sep between *attrval-spec and "-". 160 * - The BASE64-UTF8-STRING should be BASE64-CHAR BASE64-STRING 161 * - The ValueSpec rule must accept multilines values. In this case, we have a LF followed by a 162 * single space before the continued value. 163 * </pre> 164 * 165 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 166 * @version $Rev$, $Date$ 167 */ 168 public class LdifReader implements Iterable<LdifEntry>, Closeable 169 { 170 /** A logger */ 171 private static final Logger LOG = LoggerFactory.getLogger( LdifReader.class ); 172 173 /** 174 * A private class to track the current position in a line 175 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 176 * @version $Rev$, $Date$ 177 */ 178 public class Position 179 { 180 /** The current position */ 181 private int pos; 182 183 184 /** 185 * Creates a new instance of Position. 186 */ 187 public Position() 188 { 189 pos = 0; 190 } 191 192 193 /** 194 * Increment the current position by one 195 * 196 */ 197 public void inc() 198 { 199 pos++; 200 } 201 202 203 /** 204 * Increment the current position by the given value 205 * 206 * @param val The value to add to the current position 207 */ 208 public void inc( int val ) 209 { 210 pos += val; 211 } 212 } 213 214 /** A list of read lines */ 215 protected List<String> lines; 216 217 /** The current position */ 218 protected Position position; 219 220 /** The ldif file version default value */ 221 protected static final int DEFAULT_VERSION = 1; 222 223 /** The ldif version */ 224 protected int version; 225 226 /** Type of element read */ 227 protected static final int LDIF_ENTRY = 0; 228 229 protected static final int CHANGE = 1; 230 231 protected static final int UNKNOWN = 2; 232 233 /** Size limit for file contained values */ 234 protected long sizeLimit = SIZE_LIMIT_DEFAULT; 235 236 /** The default size limit : 1Mo */ 237 protected static final long SIZE_LIMIT_DEFAULT = 1024000; 238 239 /** State values for the modify operation */ 240 protected static final int MOD_SPEC = 0; 241 242 protected static final int ATTRVAL_SPEC = 1; 243 244 protected static final int ATTRVAL_SPEC_OR_SEP = 2; 245 246 /** Iterator prefetched entry */ 247 protected LdifEntry prefetched; 248 249 /** The ldif Reader */ 250 protected Reader reader; 251 252 /** A flag set if the ldif contains entries */ 253 protected boolean containsEntries; 254 255 /** A flag set if the ldif contains changes */ 256 protected boolean containsChanges; 257 258 /** 259 * An Exception to handle error message, has Iterator.next() can't throw 260 * exceptions 261 */ 262 protected Exception error; 263 264 265 /** 266 * Constructors 267 */ 268 public LdifReader() 269 { 270 lines = new ArrayList<String>(); 271 position = new Position(); 272 version = DEFAULT_VERSION; 273 } 274 275 276 private void init( BufferedReader reader ) throws LdapLdifException, LdapException 277 { 278 this.reader = reader; 279 lines = new ArrayList<String>(); 280 position = new Position(); 281 version = DEFAULT_VERSION; 282 containsChanges = false; 283 containsEntries = false; 284 285 // First get the version - if any - 286 version = parseVersion(); 287 prefetched = parseEntry(); 288 } 289 290 291 /** 292 * A constructor which takes a file name 293 * 294 * @param ldifFileName A file name containing ldif formated input 295 * @throws LdapLdifException 296 * If the file cannot be processed or if the format is incorrect 297 */ 298 public LdifReader( String ldifFileName ) throws LdapLdifException 299 { 300 File file = new File( ldifFileName ); 301 302 if ( !file.exists() ) 303 { 304 LOG.error( I18n.err( I18n.ERR_12010, file.getAbsoluteFile() ) ); 305 throw new LdapLdifException( I18n.err( I18n.ERR_12010, file.getAbsoluteFile() ) ); 306 } 307 308 if ( !file.canRead() ) 309 { 310 LOG.error( I18n.err( I18n.ERR_12011, file.getName() ) ); 311 throw new LdapLdifException( I18n.err( I18n.ERR_12011, file.getName() ) ); 312 } 313 314 try 315 { 316 init( new BufferedReader( new FileReader( file ) ) ); 317 } 318 catch ( FileNotFoundException fnfe ) 319 { 320 LOG.error( I18n.err( I18n.ERR_12010, file.getAbsoluteFile() ) ); 321 throw new LdapLdifException( I18n.err( I18n.ERR_12010, file.getAbsoluteFile() ) ); 322 } 323 catch ( LdapInvalidDnException lide ) 324 { 325 throw new LdapLdifException( lide.getMessage() ); 326 } 327 catch ( LdapException le ) 328 { 329 throw new LdapLdifException( le.getMessage() ); 330 } 331 } 332 333 334 /** 335 * A constructor which takes a Reader 336 * 337 * @param in 338 * A Reader containing ldif formated input 339 * @throws LdapLdifException 340 * If the file cannot be processed or if the format is incorrect 341 * @throws LdapException 342 */ 343 public LdifReader( Reader in ) throws LdapLdifException, LdapException 344 { 345 init( new BufferedReader( in ) ); 346 } 347 348 349 /** 350 * A constructor which takes an InputStream 351 * 352 * @param in 353 * An InputStream containing ldif formated input 354 * @throws LdapLdifException 355 * If the file cannot be processed or if the format is incorrect 356 * @throws LdapException 357 */ 358 public LdifReader( InputStream in ) throws LdapLdifException, LdapException 359 { 360 init( new BufferedReader( new InputStreamReader( in ) ) ); 361 } 362 363 364 /** 365 * A constructor which takes a File 366 * 367 * @param in 368 * A File containing ldif formated input 369 * @throws LdapLdifException 370 * If the file cannot be processed or if the format is incorrect 371 */ 372 public LdifReader( File file ) throws LdapLdifException 373 { 374 if ( !file.exists() ) 375 { 376 LOG.error( I18n.err( I18n.ERR_12010, file.getAbsoluteFile() ) ); 377 throw new LdapLdifException( I18n.err( I18n.ERR_12010, file.getAbsoluteFile() ) ); 378 } 379 380 if ( !file.canRead() ) 381 { 382 LOG.error( I18n.err( I18n.ERR_12011, file.getName() ) ); 383 throw new LdapLdifException( I18n.err( I18n.ERR_12011, file.getName() ) ); 384 } 385 386 try 387 { 388 init( new BufferedReader( new FileReader( file ) ) ); 389 } 390 catch ( FileNotFoundException fnfe ) 391 { 392 LOG.error( I18n.err( I18n.ERR_12010, file.getAbsoluteFile() ) ); 393 throw new LdapLdifException( I18n.err( I18n.ERR_12010, file.getAbsoluteFile() ) ); 394 } 395 catch ( LdapInvalidDnException lide ) 396 { 397 throw new LdapLdifException( lide.getMessage() ); 398 } 399 catch ( LdapException le ) 400 { 401 throw new LdapLdifException( le.getMessage() ); 402 } 403 } 404 405 406 /** 407 * @return The ldif file version 408 */ 409 public int getVersion() 410 { 411 return version; 412 } 413 414 415 /** 416 * @return The maximum size of a file which is used into an attribute value. 417 */ 418 public long getSizeLimit() 419 { 420 return sizeLimit; 421 } 422 423 424 /** 425 * Set the maximum file size that can be accepted for an attribute value 426 * 427 * @param sizeLimit 428 * The size in bytes 429 */ 430 public void setSizeLimit( long sizeLimit ) 431 { 432 this.sizeLimit = sizeLimit; 433 } 434 435 436 // <fill> ::= ' ' <fill> | ????????? 437 private static void parseFill( char[] document, Position position ) 438 { 439 440 while ( StringTools.isCharASCII( document, position.pos, ' ' ) ) 441 { 442 position.inc(); 443 } 444 } 445 446 447 /** 448 * Parse a number following the rules : 449 * 450 * <number> ::= <digit> <digits> <digits> ::= <digit> <digits> | e <digit> 451 * ::= '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' 452 * 453 * Check that the number is in the interval 454 * 455 * @param document The document containing the number to parse 456 * @param position The current position in the document 457 * @return a String representing the parsed number 458 */ 459 private static String parseNumber( char[] document, Position position ) 460 { 461 int initPos = position.pos; 462 463 while ( true ) 464 { 465 if ( StringTools.isDigit( document, position.pos ) ) 466 { 467 position.inc(); 468 } 469 else 470 { 471 break; 472 } 473 } 474 475 if ( position.pos == initPos ) 476 { 477 return null; 478 } 479 else 480 { 481 return new String( document, initPos, position.pos - initPos ); 482 } 483 } 484 485 486 /** 487 * Parse the changeType 488 * 489 * @param line 490 * The line which contains the changeType 491 * @return The operation. 492 */ 493 private ChangeType parseChangeType( String line ) 494 { 495 ChangeType operation = ChangeType.Add; 496 497 String modOp = StringTools.trim( line.substring( "changetype:".length() + 1 ) ); 498 499 if ( "add".equalsIgnoreCase( modOp ) ) 500 { 501 operation = ChangeType.Add; 502 } 503 else if ( "delete".equalsIgnoreCase( modOp ) ) 504 { 505 operation = ChangeType.Delete; 506 } 507 else if ( "modify".equalsIgnoreCase( modOp ) ) 508 { 509 operation = ChangeType.Modify; 510 } 511 else if ( "moddn".equalsIgnoreCase( modOp ) ) 512 { 513 operation = ChangeType.ModDn; 514 } 515 else if ( "modrdn".equalsIgnoreCase( modOp ) ) 516 { 517 operation = ChangeType.ModRdn; 518 } 519 520 return operation; 521 } 522 523 524 /** 525 * Parse the DN of an entry 526 * 527 * @param line 528 * The line to parse 529 * @return A DN 530 * @throws LdapLdifException 531 * If the DN is invalid 532 */ 533 private String parseDn( String line ) throws LdapLdifException 534 { 535 String dn = null; 536 537 String lowerLine = line.toLowerCase(); 538 539 if ( lowerLine.startsWith( "dn:" ) || lowerLine.startsWith( "DN:" ) ) 540 { 541 // Ok, we have a DN. Is it base 64 encoded ? 542 int length = line.length(); 543 544 if ( length == 3 ) 545 { 546 // The DN is empty : error 547 LOG.error( I18n.err( I18n.ERR_12012 ) ); 548 throw new LdapLdifException( I18n.err( I18n.ERR_12013 ) ); 549 } 550 else if ( line.charAt( 3 ) == ':' ) 551 { 552 if ( length > 4 ) 553 { 554 // This is a base 64 encoded DN. 555 String trimmedLine = line.substring( 4 ).trim(); 556 557 try 558 { 559 dn = new String( Base64.decode( trimmedLine.toCharArray() ), "UTF-8" ); 560 } 561 catch ( UnsupportedEncodingException uee ) 562 { 563 // The DN is not base 64 encoded 564 LOG.error( I18n.err( I18n.ERR_12014 ) ); 565 throw new LdapLdifException( I18n.err( I18n.ERR_12015 ) ); 566 } 567 } 568 else 569 { 570 // The DN is empty : error 571 LOG.error( I18n.err( I18n.ERR_12012 ) ); 572 throw new LdapLdifException( I18n.err( I18n.ERR_12013 ) ); 573 } 574 } 575 else 576 { 577 dn = line.substring( 3 ).trim(); 578 } 579 } 580 else 581 { 582 LOG.error( I18n.err( I18n.ERR_12016 ) ); 583 throw new LdapLdifException( I18n.err( I18n.ERR_12013 ) ); 584 } 585 586 // Check that the DN is valid. If not, an exception will be thrown 587 try 588 { 589 DnParser.parseInternal( dn, new ArrayList<RDN>() ); 590 } 591 catch ( LdapInvalidDnException ine ) 592 { 593 LOG.error( I18n.err( I18n.ERR_12017, dn ) ); 594 throw new LdapLdifException( ine.getMessage() ); 595 } 596 597 return dn; 598 } 599 600 601 /** 602 * Parse the value part. 603 * 604 * @param line 605 * The line which contains the value 606 * @param pos 607 * The starting position in the line 608 * @return A String or a byte[], depending of the kind of value we get 609 */ 610 protected static Object parseSimpleValue( String line, int pos ) 611 { 612 if ( line.length() > pos + 1 ) 613 { 614 char c = line.charAt( pos + 1 ); 615 616 if ( c == ':' ) 617 { 618 String value = StringTools.trim( line.substring( pos + 2 ) ); 619 620 return Base64.decode( value.toCharArray() ); 621 } 622 else 623 { 624 return StringTools.trim( line.substring( pos + 1 ) ); 625 } 626 } 627 else 628 { 629 return null; 630 } 631 } 632 633 634 /** 635 * Parse the value part. 636 * 637 * @param line The line which contains the value 638 * @param pos The starting position in the line 639 * @return A String or a byte[], depending of the kind of value we get 640 * @throws LdapLdifException 641 * If something went wrong 642 */ 643 protected Object parseValue( String line, int pos ) throws LdapLdifException 644 { 645 if ( line.length() > pos + 1 ) 646 { 647 char c = line.charAt( pos + 1 ); 648 649 if ( c == ':' ) 650 { 651 String value = StringTools.trim( line.substring( pos + 2 ) ); 652 653 return Base64.decode( value.toCharArray() ); 654 } 655 else if ( c == '<' ) 656 { 657 String urlName = StringTools.trim( line.substring( pos + 2 ) ); 658 659 try 660 { 661 URL url = new URL( urlName ); 662 663 if ( "file".equals( url.getProtocol() ) ) 664 { 665 String fileName = url.getFile(); 666 667 File file = new File( fileName ); 668 669 if ( !file.exists() ) 670 { 671 LOG.error( I18n.err( I18n.ERR_12018, fileName ) ); 672 throw new LdapLdifException( I18n.err( I18n.ERR_12019 ) ); 673 } 674 else 675 { 676 long length = file.length(); 677 678 if ( length > sizeLimit ) 679 { 680 LOG.error( I18n.err( I18n.ERR_12020, fileName ) ); 681 throw new LdapLdifException( I18n.err( I18n.ERR_12021 ) ); 682 } 683 else 684 { 685 byte[] data = new byte[( int ) length]; 686 DataInputStream inf = null; 687 688 try 689 { 690 inf = new DataInputStream( new FileInputStream( file ) ); 691 inf.read( data ); 692 693 return data; 694 } 695 catch ( FileNotFoundException fnfe ) 696 { 697 // We can't reach this point, the file 698 // existence has already been 699 // checked 700 LOG.error( I18n.err( I18n.ERR_12018, fileName ) ); 701 throw new LdapLdifException( I18n.err( I18n.ERR_12019 ) ); 702 } 703 catch ( IOException ioe ) 704 { 705 LOG.error( I18n.err( I18n.ERR_12022, fileName ) ); 706 throw new LdapLdifException( I18n.err( I18n.ERR_12023 ) ); 707 } 708 finally 709 { 710 try 711 { 712 inf.close(); 713 } 714 catch ( IOException ioe ) 715 { 716 LOG.error( I18n.err( I18n.ERR_12024, ioe.getMessage() ) ); 717 // Just do nothing ... 718 } 719 } 720 } 721 } 722 } 723 else 724 { 725 LOG.error( I18n.err( I18n.ERR_12025 ) ); 726 throw new LdapLdifException( I18n.err( I18n.ERR_12026 ) ); 727 } 728 } 729 catch ( MalformedURLException mue ) 730 { 731 LOG.error( I18n.err( I18n.ERR_12027, urlName ) ); 732 throw new LdapLdifException( I18n.err( I18n.ERR_12028 ) ); 733 } 734 } 735 else 736 { 737 return StringTools.trim( line.substring( pos + 1 ) ); 738 } 739 } 740 else 741 { 742 return null; 743 } 744 } 745 746 747 /** 748 * Parse a control. The grammar is : <control> ::= "control:" <fill> 749 * <ldap-oid> <critical-e> <value-spec-e> <sep> <critical-e> ::= <spaces> 750 * <boolean> | e <boolean> ::= "true" | "false" <value-spec-e> ::= 751 * <value-spec> | e <value-spec> ::= ":" <fill> <SAFE-STRING-e> | "::" 752 * <fill> <BASE64-STRING> | ":<" <fill> <url> 753 * 754 * It can be read as : "control:" <fill> <ldap-oid> [ " "+ ( "true" | 755 * "false") ] [ ":" <fill> <SAFE-STRING-e> | "::" <fill> <BASE64-STRING> | ":<" 756 * <fill> <url> ] 757 * 758 * @param line The line containing the control 759 * @return A control 760 * @exception LdapLdifException If the control has no OID or if the OID is incorrect, 761 * of if the criticality is not set when it's mandatory. 762 */ 763 private Control parseControl( String line ) throws LdapLdifException 764 { 765 String lowerLine = line.toLowerCase().trim(); 766 char[] controlValue = line.trim().toCharArray(); 767 int pos = 0; 768 int length = controlValue.length; 769 770 // Get the <ldap-oid> 771 if ( pos > length ) 772 { 773 // No OID : error ! 774 LOG.error( I18n.err( I18n.ERR_12029 ) ); 775 throw new LdapLdifException( I18n.err( I18n.ERR_12030 ) ); 776 } 777 778 int initPos = pos; 779 780 while ( StringTools.isCharASCII( controlValue, pos, '.' ) || StringTools.isDigit( controlValue, pos ) ) 781 { 782 pos++; 783 } 784 785 if ( pos == initPos ) 786 { 787 // Not a valid OID ! 788 LOG.error( I18n.err( I18n.ERR_12029 ) ); 789 throw new LdapLdifException( I18n.err( I18n.ERR_12030 ) ); 790 } 791 792 // Create and check the OID 793 String oidString = lowerLine.substring( 0, pos ); 794 795 if ( !OID.isOID( oidString ) ) 796 { 797 LOG.error( I18n.err( I18n.ERR_12031, oidString ) ); 798 throw new LdapLdifException( I18n.err( I18n.ERR_12032 ) ); 799 } 800 801 LdifControl control = new LdifControl( oidString ); 802 803 // Get the criticality, if any 804 // Skip the <fill> 805 while ( StringTools.isCharASCII( controlValue, pos, ' ' ) ) 806 { 807 pos++; 808 } 809 810 // Check if we have a "true" or a "false" 811 int criticalPos = lowerLine.indexOf( ':' ); 812 813 int criticalLength = 0; 814 815 if ( criticalPos == -1 ) 816 { 817 criticalLength = length - pos; 818 } 819 else 820 { 821 criticalLength = criticalPos - pos; 822 } 823 824 if ( ( criticalLength == 4 ) && ( "true".equalsIgnoreCase( lowerLine.substring( pos, pos + 4 ) ) ) ) 825 { 826 control.setCritical( true ); 827 } 828 else if ( ( criticalLength == 5 ) && ( "false".equalsIgnoreCase( lowerLine.substring( pos, pos + 5 ) ) ) ) 829 { 830 control.setCritical( false ); 831 } 832 else if ( criticalLength != 0 ) 833 { 834 // If we have a criticality, it should be either "true" or "false", 835 // nothing else 836 LOG.error( I18n.err( I18n.ERR_12033 ) ); 837 throw new LdapLdifException( I18n.err( I18n.ERR_12034 ) ); 838 } 839 840 if ( criticalPos > 0 ) 841 { 842 // We have a value. It can be a normal value, a base64 encoded value 843 // or a file contained value 844 if ( StringTools.isCharASCII( controlValue, criticalPos + 1, ':' ) ) 845 { 846 // Base 64 encoded value 847 byte[] value = Base64.decode( line.substring( criticalPos + 2 ).toCharArray() ); 848 control.setValue( value ); 849 } 850 else if ( StringTools.isCharASCII( controlValue, criticalPos + 1, '<' ) ) 851 { 852 // File contained value 853 } 854 else 855 { 856 // Standard value 857 byte[] value = new byte[length - criticalPos - 1]; 858 859 for ( int i = 0; i < length - criticalPos - 1; i++ ) 860 { 861 value[i] = ( byte ) controlValue[i + criticalPos + 1]; 862 } 863 864 control.setValue( value ); 865 } 866 } 867 868 return control; 869 } 870 871 872 /** 873 * Parse an AttributeType/AttributeValue 874 * 875 * @param line The line to parse 876 * @return the parsed Attribute 877 */ 878 public static EntryAttribute parseAttributeValue( String line ) 879 { 880 int colonIndex = line.indexOf( ':' ); 881 882 if ( colonIndex != -1 ) 883 { 884 String attributeType = line.toLowerCase().substring( 0, colonIndex ); 885 Object attributeValue = parseSimpleValue( line, colonIndex ); 886 887 // Create an attribute 888 if ( attributeValue instanceof String ) 889 { 890 return new DefaultClientAttribute( attributeType, (String)attributeValue ); 891 } 892 else 893 { 894 return new DefaultClientAttribute( attributeType, (byte[])attributeValue ); 895 } 896 } 897 else 898 { 899 return null; 900 } 901 } 902 903 904 /** 905 * Parse an AttributeType/AttributeValue 906 * 907 * @param entry The entry where to store the value 908 * @param line The line to parse 909 * @param lowerLine The same line, lowercased 910 * @throws LdapLdifException If anything goes wrong 911 */ 912 public void parseAttributeValue( LdifEntry entry, String line, String lowerLine ) throws LdapLdifException, LdapException 913 { 914 int colonIndex = line.indexOf( ':' ); 915 916 String attributeType = lowerLine.substring( 0, colonIndex ); 917 918 // We should *not* have a DN twice 919 if ( attributeType.equals( "dn" ) ) 920 { 921 LOG.error( I18n.err( I18n.ERR_12002 ) ); 922 throw new LdapLdifException( I18n.err( I18n.ERR_12003 ) ); 923 } 924 925 Object attributeValue = parseValue( line, colonIndex ); 926 927 // Update the entry 928 entry.addAttribute( attributeType, attributeValue ); 929 } 930 931 932 /** 933 * Parse a ModRDN operation 934 * 935 * @param entry 936 * The entry to update 937 * @param iter 938 * The lines iterator 939 * @throws LdapLdifException 940 * If anything goes wrong 941 */ 942 private void parseModRdn( LdifEntry entry, Iterator<String> iter ) throws LdapLdifException 943 { 944 // We must have two lines : one starting with "newrdn:" or "newrdn::", 945 // and the second starting with "deleteoldrdn:" 946 if ( iter.hasNext() ) 947 { 948 String line = iter.next(); 949 String lowerLine = line.toLowerCase(); 950 951 if ( lowerLine.startsWith( "newrdn::" ) || lowerLine.startsWith( "newrdn:" ) ) 952 { 953 int colonIndex = line.indexOf( ':' ); 954 Object attributeValue = parseValue( line, colonIndex ); 955 entry.setNewRdn( attributeValue instanceof String ? ( String ) attributeValue : StringTools 956 .utf8ToString( ( byte[] ) attributeValue ) ); 957 } 958 else 959 { 960 LOG.error( I18n.err( I18n.ERR_12035 ) ); 961 throw new LdapLdifException( I18n.err( I18n.ERR_12036 ) ); 962 } 963 964 } 965 else 966 { 967 LOG.error( I18n.err( I18n.ERR_12035 ) ); 968 throw new LdapLdifException( I18n.err( I18n.ERR_12037 ) ); 969 } 970 971 if ( iter.hasNext() ) 972 { 973 String line = iter.next(); 974 String lowerLine = line.toLowerCase(); 975 976 if ( lowerLine.startsWith( "deleteoldrdn:" ) ) 977 { 978 int colonIndex = line.indexOf( ':' ); 979 Object attributeValue = parseValue( line, colonIndex ); 980 entry.setDeleteOldRdn( "1".equals( attributeValue ) ); 981 } 982 else 983 { 984 LOG.error( I18n.err( I18n.ERR_12038 ) ); 985 throw new LdapLdifException( I18n.err( I18n.ERR_12039 ) ); 986 } 987 } 988 else 989 { 990 LOG.error( I18n.err( I18n.ERR_12038 ) ); 991 throw new LdapLdifException( I18n.err( I18n.ERR_12039 ) ); 992 } 993 994 return; 995 } 996 997 998 /** 999 * Parse a modify change type. 1000 * 1001 * The grammar is : <changerecord> ::= "changetype:" FILL "modify" SEP 1002 * <mod-spec> <mod-specs-e> <mod-spec> ::= "add:" <mod-val> | "delete:" 1003 * <mod-val-del> | "replace:" <mod-val> <mod-specs-e> ::= <mod-spec> 1004 * <mod-specs-e> | e <mod-val> ::= FILL ATTRIBUTE-DESCRIPTION SEP 1005 * ATTRVAL-SPEC <attrval-specs-e> "-" SEP <mod-val-del> ::= FILL 1006 * ATTRIBUTE-DESCRIPTION SEP <attrval-specs-e> "-" SEP <attrval-specs-e> ::= 1007 * ATTRVAL-SPEC <attrval-specs> | e * 1008 * 1009 * @param entry The entry to feed 1010 * @param iter The lines 1011 * @exception LdapLdifException If the modify operation is invalid 1012 */ 1013 private void parseModify( LdifEntry entry, Iterator<String> iter ) throws LdapLdifException 1014 { 1015 int state = MOD_SPEC; 1016 String modified = null; 1017 ModificationOperation modificationType = ModificationOperation.ADD_ATTRIBUTE; 1018 EntryAttribute attribute = null; 1019 1020 // The following flag is used to deal with empty modifications 1021 boolean isEmptyValue = true; 1022 1023 while ( iter.hasNext() ) 1024 { 1025 String line = iter.next(); 1026 String lowerLine = line.toLowerCase(); 1027 1028 if ( lowerLine.startsWith( "-" ) ) 1029 { 1030 if ( state != ATTRVAL_SPEC_OR_SEP ) 1031 { 1032 LOG.error( I18n.err( I18n.ERR_12040 ) ); 1033 throw new LdapLdifException( I18n.err( I18n.ERR_12041 ) ); 1034 } 1035 else 1036 { 1037 if ( isEmptyValue ) 1038 { 1039 // Update the entry 1040 entry.addModificationItem( modificationType, modified, null ); 1041 } 1042 else 1043 { 1044 // Update the entry with the attribute 1045 entry.addModificationItem( modificationType, attribute ); 1046 } 1047 1048 state = MOD_SPEC; 1049 isEmptyValue = true; 1050 continue; 1051 } 1052 } 1053 else if ( lowerLine.startsWith( "add:" ) ) 1054 { 1055 if ( ( state != MOD_SPEC ) && ( state != ATTRVAL_SPEC ) ) 1056 { 1057 LOG.error( I18n.err( I18n.ERR_12042 ) ); 1058 throw new LdapLdifException( I18n.err( I18n.ERR_12043 ) ); 1059 } 1060 1061 modified = StringTools.trim( line.substring( "add:".length() ) ); 1062 modificationType = ModificationOperation.ADD_ATTRIBUTE; 1063 attribute = new DefaultClientAttribute( modified ); 1064 1065 state = ATTRVAL_SPEC; 1066 } 1067 else if ( lowerLine.startsWith( "delete:" ) ) 1068 { 1069 if ( ( state != MOD_SPEC ) && ( state != ATTRVAL_SPEC ) ) 1070 { 1071 LOG.error( I18n.err( I18n.ERR_12042 ) ); 1072 throw new LdapLdifException( I18n.err( I18n.ERR_12043 ) ); 1073 } 1074 1075 modified = StringTools.trim( line.substring( "delete:".length() ) ); 1076 modificationType = ModificationOperation.REMOVE_ATTRIBUTE; 1077 attribute = new DefaultClientAttribute( modified ); 1078 1079 state = ATTRVAL_SPEC_OR_SEP; 1080 } 1081 else if ( lowerLine.startsWith( "replace:" ) ) 1082 { 1083 if ( ( state != MOD_SPEC ) && ( state != ATTRVAL_SPEC ) ) 1084 { 1085 LOG.error( I18n.err( I18n.ERR_12042 ) ); 1086 throw new LdapLdifException( I18n.err( I18n.ERR_12043 ) ); 1087 } 1088 1089 modified = StringTools.trim( line.substring( "replace:".length() ) ); 1090 modificationType = ModificationOperation.REPLACE_ATTRIBUTE; 1091 attribute = new DefaultClientAttribute( modified ); 1092 1093 state = ATTRVAL_SPEC_OR_SEP; 1094 } 1095 else 1096 { 1097 if ( ( state != ATTRVAL_SPEC ) && ( state != ATTRVAL_SPEC_OR_SEP ) ) 1098 { 1099 LOG.error( I18n.err( I18n.ERR_12040 ) ); 1100 throw new LdapLdifException( I18n.err( I18n.ERR_12043 ) ); 1101 } 1102 1103 // A standard AttributeType/AttributeValue pair 1104 int colonIndex = line.indexOf( ':' ); 1105 1106 String attributeType = line.substring( 0, colonIndex ); 1107 1108 if ( !attributeType.equalsIgnoreCase( modified ) ) 1109 { 1110 LOG.error( I18n.err( I18n.ERR_12044 ) ); 1111 throw new LdapLdifException( I18n.err( I18n.ERR_12045 ) ); 1112 } 1113 1114 // We should *not* have a DN twice 1115 if ( attributeType.equalsIgnoreCase( "dn" ) ) 1116 { 1117 LOG.error( I18n.err( I18n.ERR_12002 ) ); 1118 throw new LdapLdifException( I18n.err( I18n.ERR_12003 ) ); 1119 } 1120 1121 Object attributeValue = parseValue( line, colonIndex ); 1122 1123 if ( attributeValue instanceof String ) 1124 { 1125 attribute.add( ( String ) attributeValue ); 1126 } 1127 else 1128 { 1129 attribute.add( ( byte[] ) attributeValue ); 1130 } 1131 1132 isEmptyValue = false; 1133 1134 state = ATTRVAL_SPEC_OR_SEP; 1135 } 1136 } 1137 } 1138 1139 1140 /** 1141 * Parse a change operation. We have to handle different cases depending on 1142 * the operation. 1) Delete : there should *not* be any line after the 1143 * "changetype: delete" 2) Add : we must have a list of AttributeType : 1144 * AttributeValue elements 3) ModDN : we must have two following lines: a 1145 * "newrdn:" and a "deleteoldrdn:" 4) ModRDN : the very same, but a 1146 * "newsuperior:" line is expected 5) Modify : 1147 * 1148 * The grammar is : <changerecord> ::= "changetype:" FILL "add" SEP 1149 * <attrval-spec> <attrval-specs-e> | "changetype:" FILL "delete" | 1150 * "changetype:" FILL "modrdn" SEP <newrdn> SEP <deleteoldrdn> SEP | // To 1151 * be checked "changetype:" FILL "moddn" SEP <newrdn> SEP <deleteoldrdn> SEP 1152 * <newsuperior> SEP | "changetype:" FILL "modify" SEP <mod-spec> 1153 * <mod-specs-e> <newrdn> ::= "newrdn:" FILL RDN | "newrdn::" FILL 1154 * BASE64-RDN <deleteoldrdn> ::= "deleteoldrdn:" FILL "0" | "deleteoldrdn:" 1155 * FILL "1" <newsuperior> ::= "newsuperior:" FILL DN | "newsuperior::" FILL 1156 * BASE64-DN <mod-specs-e> ::= <mod-spec> <mod-specs-e> | e <mod-spec> ::= 1157 * "add:" <mod-val> | "delete:" <mod-val> | "replace:" <mod-val> <mod-val> 1158 * ::= FILL ATTRIBUTE-DESCRIPTION SEP ATTRVAL-SPEC <attrval-specs-e> "-" SEP 1159 * <attrval-specs-e> ::= ATTRVAL-SPEC <attrval-specs> | e 1160 * 1161 * @param entry The entry to feed 1162 * @param iter The lines iterator 1163 * @param operation The change operation (add, modify, delete, moddn or modrdn) 1164 * @exception LdapLdifException If the change operation is invalid 1165 */ 1166 private void parseChange( LdifEntry entry, Iterator<String> iter, ChangeType operation ) throws LdapLdifException, LdapException 1167 { 1168 // The changetype and operation has already been parsed. 1169 entry.setChangeType( operation ); 1170 1171 switch ( operation.getChangeType() ) 1172 { 1173 case ChangeType.DELETE_ORDINAL: 1174 // The change type will tell that it's a delete operation, 1175 // the dn is used as a key. 1176 return; 1177 1178 case ChangeType.ADD_ORDINAL: 1179 // We will iterate through all attribute/value pairs 1180 while ( iter.hasNext() ) 1181 { 1182 String line = iter.next(); 1183 String lowerLine = line.toLowerCase(); 1184 parseAttributeValue( entry, line, lowerLine ); 1185 } 1186 1187 return; 1188 1189 case ChangeType.MODIFY_ORDINAL: 1190 parseModify( entry, iter ); 1191 return; 1192 1193 case ChangeType.MODRDN_ORDINAL:// They are supposed to have the same syntax ??? 1194 case ChangeType.MODDN_ORDINAL: 1195 // First, parse the modrdn part 1196 parseModRdn( entry, iter ); 1197 1198 // The next line should be the new superior 1199 if ( iter.hasNext() ) 1200 { 1201 String line = iter.next(); 1202 String lowerLine = line.toLowerCase(); 1203 1204 if ( lowerLine.startsWith( "newsuperior:" ) ) 1205 { 1206 int colonIndex = line.indexOf( ':' ); 1207 Object attributeValue = parseValue( line, colonIndex ); 1208 entry.setNewSuperior( attributeValue instanceof String ? ( String ) attributeValue 1209 : StringTools.utf8ToString( ( byte[] ) attributeValue ) ); 1210 } 1211 else 1212 { 1213 if ( operation == ChangeType.ModDn ) 1214 { 1215 LOG.error( I18n.err( I18n.ERR_12046 ) ); 1216 throw new LdapLdifException( I18n.err( I18n.ERR_12047 ) ); 1217 } 1218 } 1219 } 1220 else 1221 { 1222 if ( operation == ChangeType.ModDn ) 1223 { 1224 LOG.error( I18n.err( I18n.ERR_12046 ) ); 1225 throw new LdapLdifException( I18n.err( I18n.ERR_12047 ) ); 1226 } 1227 } 1228 1229 return; 1230 1231 default: 1232 // This is an error 1233 LOG.error( I18n.err( I18n.ERR_12048 ) ); 1234 throw new LdapLdifException( I18n.err( I18n.ERR_12049 ) ); 1235 } 1236 } 1237 1238 1239 /** 1240 * Parse a ldif file. The following rules are processed : 1241 * 1242 * <ldif-file> ::= <ldif-attrval-record> <ldif-attrval-records> | 1243 * <ldif-change-record> <ldif-change-records> <ldif-attrval-record> ::= 1244 * <dn-spec> <sep> <attrval-spec> <attrval-specs> <ldif-change-record> ::= 1245 * <dn-spec> <sep> <controls-e> <changerecord> <dn-spec> ::= "dn:" <fill> 1246 * <distinguishedName> | "dn::" <fill> <base64-distinguishedName> 1247 * <changerecord> ::= "changetype:" <fill> <change-op> 1248 * 1249 * @return the parsed ldifEntry 1250 * @exception LdapLdifException If the ldif file does not contain a valid entry 1251 * @throws LdapException 1252 */ 1253 private LdifEntry parseEntry() throws LdapLdifException, LdapException 1254 { 1255 if ( ( lines == null ) || ( lines.size() == 0 ) ) 1256 { 1257 LOG.debug( "The entry is empty : end of ldif file" ); 1258 return null; 1259 } 1260 1261 // The entry must start with a dn: or a dn:: 1262 String line = lines.get( 0 ); 1263 1264 String name = parseDn( line ); 1265 1266 DN dn = new DN( name ); 1267 1268 // Ok, we have found a DN 1269 LdifEntry entry = new LdifEntry(); 1270 entry.setDn( dn ); 1271 1272 // We remove this dn from the lines 1273 lines.remove( 0 ); 1274 1275 // Now, let's iterate through the other lines 1276 Iterator<String> iter = lines.iterator(); 1277 1278 // This flag is used to distinguish between an entry and a change 1279 int type = UNKNOWN; 1280 1281 // The following boolean is used to check that a control is *not* 1282 // found elswhere than just after the dn 1283 boolean controlSeen = false; 1284 1285 // We use this boolean to check that we do not have AttributeValues 1286 // after a change operation 1287 boolean changeTypeSeen = false; 1288 1289 ChangeType operation = ChangeType.Add; 1290 String lowerLine = null; 1291 Control control = null; 1292 1293 while ( iter.hasNext() ) 1294 { 1295 // Each line could start either with an OID, an attribute type, with 1296 // "control:" or with "changetype:" 1297 line = iter.next(); 1298 lowerLine = line.toLowerCase(); 1299 1300 // We have three cases : 1301 // 1) The first line after the DN is a "control:" 1302 // 2) The first line after the DN is a "changeType:" 1303 // 3) The first line after the DN is anything else 1304 if ( lowerLine.startsWith( "control:" ) ) 1305 { 1306 if ( containsEntries ) 1307 { 1308 LOG.error( I18n.err( I18n.ERR_12004 ) ); 1309 throw new LdapLdifException( I18n.err( I18n.ERR_12005 ) ); 1310 } 1311 1312 containsChanges = true; 1313 1314 if ( controlSeen ) 1315 { 1316 LOG.error( I18n.err( I18n.ERR_12050 ) ); 1317 throw new LdapLdifException( I18n.err( I18n.ERR_12051 ) ); 1318 } 1319 1320 // Parse the control 1321 control = parseControl( line.substring( "control:".length() ) ); 1322 entry.setControl( control ); 1323 } 1324 else if ( lowerLine.startsWith( "changetype:" ) ) 1325 { 1326 if ( containsEntries ) 1327 { 1328 LOG.error( I18n.err( I18n.ERR_12004 ) ); 1329 throw new LdapLdifException( I18n.err( I18n.ERR_12005 ) ); 1330 } 1331 1332 containsChanges = true; 1333 1334 if ( changeTypeSeen ) 1335 { 1336 LOG.error( I18n.err( I18n.ERR_12052 ) ); 1337 throw new LdapLdifException( I18n.err( I18n.ERR_12053 ) ); 1338 } 1339 1340 // A change request 1341 type = CHANGE; 1342 controlSeen = true; 1343 1344 operation = parseChangeType( line ); 1345 1346 // Parse the change operation in a separate function 1347 parseChange( entry, iter, operation ); 1348 changeTypeSeen = true; 1349 } 1350 else if ( line.indexOf( ':' ) > 0 ) 1351 { 1352 if ( containsChanges ) 1353 { 1354 LOG.error( I18n.err( I18n.ERR_12004 ) ); 1355 throw new LdapLdifException( I18n.err( I18n.ERR_12005 ) ); 1356 } 1357 1358 containsEntries = true; 1359 1360 if ( controlSeen || changeTypeSeen ) 1361 { 1362 LOG.error( I18n.err( I18n.ERR_12054 ) ); 1363 throw new LdapLdifException( I18n.err( I18n.ERR_12055 ) ); 1364 } 1365 1366 parseAttributeValue( entry, line, lowerLine ); 1367 type = LDIF_ENTRY; 1368 } 1369 else 1370 { 1371 // Invalid attribute Value 1372 LOG.error( I18n.err( I18n.ERR_12056 ) ); 1373 throw new LdapLdifException( I18n.err( I18n.ERR_12057 ) ); 1374 } 1375 } 1376 1377 if ( type == LDIF_ENTRY ) 1378 { 1379 LOG.debug( "Read an entry : {}", entry ); 1380 } 1381 else if ( type == CHANGE ) 1382 { 1383 entry.setChangeType( operation ); 1384 LOG.debug( "Read a modification : {}", entry ); 1385 } 1386 else 1387 { 1388 LOG.error( I18n.err( I18n.ERR_12058 ) ); 1389 throw new LdapLdifException( I18n.err( I18n.ERR_12059 ) ); 1390 } 1391 1392 return entry; 1393 } 1394 1395 1396 /** 1397 * Parse the version from the ldif input. 1398 * 1399 * @return A number representing the version (default to 1) 1400 * @throws LdapLdifException 1401 * If the version is incorrect 1402 * @throws LdapLdifException 1403 * If the input is incorrect 1404 */ 1405 private int parseVersion() throws LdapLdifException 1406 { 1407 int ver = DEFAULT_VERSION; 1408 1409 // First, read a list of lines 1410 readLines(); 1411 1412 if ( lines.size() == 0 ) 1413 { 1414 LOG.warn( "The ldif file is empty" ); 1415 return ver; 1416 } 1417 1418 // get the first line 1419 String line = lines.get( 0 ); 1420 1421 // <ldif-file> ::= "version:" <fill> <number> 1422 char[] document = line.toCharArray(); 1423 String versionNumber = null; 1424 1425 if ( line.startsWith( "version:" ) ) 1426 { 1427 position.inc( "version:".length() ); 1428 parseFill( document, position ); 1429 1430 // Version number. Must be '1' in this version 1431 versionNumber = parseNumber( document, position ); 1432 1433 // We should not have any other chars after the number 1434 if ( position.pos != document.length ) 1435 { 1436 LOG.error( I18n.err( I18n.ERR_12060 ) ); 1437 throw new LdapLdifException( I18n.err( I18n.ERR_12061 ) ); 1438 } 1439 1440 try 1441 { 1442 ver = Integer.parseInt( versionNumber ); 1443 } 1444 catch ( NumberFormatException nfe ) 1445 { 1446 LOG.error( I18n.err( I18n.ERR_12060 ) ); 1447 throw new LdapLdifException( I18n.err( I18n.ERR_12061 ) ); 1448 } 1449 1450 LOG.debug( "Ldif version : {}", versionNumber ); 1451 1452 // We have found the version, just discard the line from the list 1453 lines.remove( 0 ); 1454 1455 // and read the next lines if the current buffer is empty 1456 if ( lines.size() == 0 ) 1457 { 1458 readLines(); 1459 } 1460 } 1461 else 1462 { 1463 LOG.warn( "No version information : assuming version: 1" ); 1464 } 1465 1466 return ver; 1467 } 1468 1469 1470 /** 1471 * Reads an entry in a ldif buffer, and returns the resulting lines, without 1472 * comments, and unfolded. 1473 * 1474 * The lines represent *one* entry. 1475 * 1476 * @throws LdapLdifException If something went wrong 1477 */ 1478 protected void readLines() throws LdapLdifException 1479 { 1480 String line = null; 1481 boolean insideComment = true; 1482 boolean isFirstLine = true; 1483 1484 lines.clear(); 1485 StringBuffer sb = new StringBuffer(); 1486 1487 try 1488 { 1489 while ( ( line = ( ( BufferedReader ) reader ).readLine() ) != null ) 1490 { 1491 if ( line.length() == 0 ) 1492 { 1493 if ( isFirstLine ) 1494 { 1495 continue; 1496 } 1497 else 1498 { 1499 // The line is empty, we have read an entry 1500 insideComment = false; 1501 break; 1502 } 1503 } 1504 1505 // We will read the first line which is not a comment 1506 switch ( line.charAt( 0 ) ) 1507 { 1508 case '#': 1509 insideComment = true; 1510 break; 1511 1512 case ' ': 1513 isFirstLine = false; 1514 1515 if ( insideComment ) 1516 { 1517 continue; 1518 } 1519 else if ( sb.length() == 0 ) 1520 { 1521 LOG.error( I18n.err( I18n.ERR_12062 ) ); 1522 throw new LdapLdifException( I18n.err( I18n.ERR_12061 ) ); 1523 } 1524 else 1525 { 1526 sb.append( line.substring( 1 ) ); 1527 } 1528 1529 insideComment = false; 1530 break; 1531 1532 default: 1533 isFirstLine = false; 1534 1535 // We have found a new entry 1536 // First, stores the previous one if any. 1537 if ( sb.length() != 0 ) 1538 { 1539 lines.add( sb.toString() ); 1540 } 1541 1542 sb = new StringBuffer( line ); 1543 insideComment = false; 1544 break; 1545 } 1546 } 1547 } 1548 catch ( IOException ioe ) 1549 { 1550 throw new LdapLdifException( I18n.err( I18n.ERR_12063 ) ); 1551 } 1552 1553 // Stores the current line if necessary. 1554 if ( sb.length() != 0 ) 1555 { 1556 lines.add( sb.toString() ); 1557 } 1558 1559 return; 1560 } 1561 1562 1563 /** 1564 * Parse a ldif file (using the default encoding). 1565 * 1566 * @param fileName 1567 * The ldif file 1568 * @return A list of entries 1569 * @throws LdapLdifException 1570 * If the parsing fails 1571 */ 1572 public List<LdifEntry> parseLdifFile( String fileName ) throws LdapLdifException 1573 { 1574 return parseLdifFile( fileName, Charset.forName( StringTools.getDefaultCharsetName() ).toString() ); 1575 } 1576 1577 1578 /** 1579 * Parse a ldif file, decoding it using the given charset encoding 1580 * 1581 * @param fileName 1582 * The ldif file 1583 * @param encoding 1584 * The charset encoding to use 1585 * @return A list of entries 1586 * @throws LdapLdifException 1587 * If the parsing fails 1588 */ 1589 public List<LdifEntry> parseLdifFile( String fileName, String encoding ) throws LdapLdifException 1590 { 1591 if ( StringTools.isEmpty( fileName ) ) 1592 { 1593 LOG.error( I18n.err( I18n.ERR_12064 ) ); 1594 throw new LdapLdifException( I18n.err( I18n.ERR_12065 ) ); 1595 } 1596 1597 File file = new File( fileName ); 1598 1599 if ( !file.exists() ) 1600 { 1601 LOG.error( I18n.err( I18n.ERR_12066, fileName ) ); 1602 throw new LdapLdifException( I18n.err( I18n.ERR_12067, fileName ) ); 1603 } 1604 1605 BufferedReader reader = null; 1606 1607 // Open the file and then get a channel from the stream 1608 try 1609 { 1610 reader = new BufferedReader( 1611 new InputStreamReader( 1612 new FileInputStream( file ), Charset.forName( encoding ) ) ); 1613 1614 return parseLdif( reader ); 1615 } 1616 catch ( FileNotFoundException fnfe ) 1617 { 1618 LOG.error( I18n.err( I18n.ERR_12068, fileName ) ); 1619 throw new LdapLdifException( I18n.err( I18n.ERR_12067, fileName ) ); 1620 } 1621 catch ( LdapException le ) 1622 { 1623 le.printStackTrace(); 1624 throw new LdapLdifException( le.getMessage() ); 1625 } 1626 finally 1627 { 1628 // close the reader 1629 try 1630 { 1631 if ( reader != null ) 1632 { 1633 reader.close(); 1634 } 1635 } 1636 catch ( IOException ioe ) 1637 { 1638 // Nothing to do 1639 } 1640 } 1641 } 1642 1643 1644 /** 1645 * A method which parses a ldif string and returns a list of entries. 1646 * 1647 * @param ldif 1648 * The ldif string 1649 * @return A list of entries, or an empty List 1650 * @throws LdapLdifException 1651 * If something went wrong 1652 */ 1653 public List<LdifEntry> parseLdif( String ldif ) throws LdapLdifException 1654 { 1655 LOG.debug( "Starts parsing ldif buffer" ); 1656 1657 if ( StringTools.isEmpty( ldif ) ) 1658 { 1659 return new ArrayList<LdifEntry>(); 1660 } 1661 1662 BufferedReader reader = new BufferedReader( 1663 new StringReader( ldif ) ); 1664 1665 try 1666 { 1667 List<LdifEntry> entries = parseLdif( reader ); 1668 1669 if ( LOG.isDebugEnabled() ) 1670 { 1671 LOG.debug( "Parsed {} entries.", ( entries == null ? Integer.valueOf( 0 ) : Integer.valueOf( entries 1672 .size() ) ) ); 1673 } 1674 1675 return entries; 1676 } 1677 catch ( LdapLdifException ne ) 1678 { 1679 LOG.error( I18n.err( I18n.ERR_12069, ne.getLocalizedMessage() ) ); 1680 throw new LdapLdifException( I18n.err( I18n.ERR_12070 ) ); 1681 } 1682 catch ( LdapException le ) 1683 { 1684 throw new LdapLdifException( le.getMessage() ); 1685 } 1686 finally 1687 { 1688 // Close the reader 1689 try 1690 { 1691 if ( reader != null ) 1692 { 1693 reader.close(); 1694 } 1695 } 1696 catch ( IOException ioe ) 1697 { 1698 // Nothing to do 1699 } 1700 1701 } 1702 } 1703 1704 1705 // ------------------------------------------------------------------------ 1706 // Iterator Methods 1707 // ------------------------------------------------------------------------ 1708 1709 /** 1710 * Gets the next LDIF on the channel. 1711 * 1712 * @return the next LDIF as a String. 1713 * @exception NoSuchElementException If we can't read the next entry 1714 */ 1715 private LdifEntry nextInternal() 1716 { 1717 try 1718 { 1719 LOG.debug( "next(): -- called" ); 1720 1721 LdifEntry entry = prefetched; 1722 readLines(); 1723 1724 try 1725 { 1726 prefetched = parseEntry(); 1727 } 1728 catch ( LdapLdifException ne ) 1729 { 1730 error = ne; 1731 throw new NoSuchElementException( ne.getMessage() ); 1732 } 1733 catch ( LdapException le ) 1734 { 1735 throw new NoSuchElementException( le.getMessage() ); 1736 } 1737 1738 LOG.debug( "next(): -- returning ldif {}\n", entry ); 1739 1740 return entry; 1741 } 1742 catch ( LdapLdifException ne ) 1743 { 1744 LOG.error( I18n.err( I18n.ERR_12071 ) ); 1745 error = ne; 1746 return null; 1747 } 1748 } 1749 1750 1751 /** 1752 * Gets the next LDIF on the channel. 1753 * 1754 * @return the next LDIF as a String. 1755 * @exception NoSuchElementException If we can't read the next entry 1756 */ 1757 public LdifEntry next() 1758 { 1759 return nextInternal(); 1760 } 1761 1762 1763 /** 1764 * Tests to see if another LDIF is on the input channel. 1765 * 1766 * @return true if another LDIF is available false otherwise. 1767 */ 1768 private boolean hasNextInternal() 1769 { 1770 return null != prefetched; 1771 } 1772 1773 1774 /** 1775 * Tests to see if another LDIF is on the input channel. 1776 * 1777 * @return true if another LDIF is available false otherwise. 1778 */ 1779 public boolean hasNext() 1780 { 1781 LOG.debug( "hasNext(): -- returning {}", ( prefetched != null ) ? Boolean.TRUE : Boolean.FALSE ); 1782 1783 return hasNextInternal(); 1784 } 1785 1786 1787 /** 1788 * Always throws UnsupportedOperationException! 1789 * 1790 * @see java.util.Iterator#remove() 1791 */ 1792 private void removeInternal() 1793 { 1794 throw new UnsupportedOperationException(); 1795 } 1796 1797 1798 /** 1799 * Always throws UnsupportedOperationException! 1800 * 1801 * @see java.util.Iterator#remove() 1802 */ 1803 public void remove() 1804 { 1805 removeInternal(); 1806 } 1807 1808 1809 /** 1810 * @return An iterator on the file 1811 */ 1812 public Iterator<LdifEntry> iterator() 1813 { 1814 return new Iterator<LdifEntry>() 1815 { 1816 public boolean hasNext() 1817 { 1818 return hasNextInternal(); 1819 } 1820 1821 1822 public LdifEntry next() 1823 { 1824 return nextInternal(); 1825 } 1826 1827 1828 public void remove() 1829 { 1830 throw new UnsupportedOperationException(); 1831 } 1832 }; 1833 } 1834 1835 1836 /** 1837 * @return True if an error occured during parsing 1838 */ 1839 public boolean hasError() 1840 { 1841 return error != null; 1842 } 1843 1844 1845 /** 1846 * @return The exception that occurs during an entry parsing 1847 */ 1848 public Exception getError() 1849 { 1850 return error; 1851 } 1852 1853 1854 /** 1855 * The main entry point of the LdifParser. It reads a buffer and returns a 1856 * List of entries. 1857 * 1858 * @param inf 1859 * The buffer being processed 1860 * @return A list of entries 1861 * @throws LdapLdifException 1862 * If something went wrong 1863 * @throws LdapException 1864 */ 1865 public List<LdifEntry> parseLdif( BufferedReader reader ) throws LdapLdifException, LdapException 1866 { 1867 // Create a list that will contain the read entries 1868 List<LdifEntry> entries = new ArrayList<LdifEntry>(); 1869 1870 this.reader = reader; 1871 1872 // First get the version - if any - 1873 version = parseVersion(); 1874 prefetched = parseEntry(); 1875 1876 // When done, get the entries one by one. 1877 try 1878 { 1879 for ( LdifEntry entry : this ) 1880 { 1881 if ( entry != null ) 1882 { 1883 entries.add( entry ); 1884 } 1885 } 1886 } 1887 catch ( NoSuchElementException nsee ) 1888 { 1889 throw new LdapLdifException( I18n.err( I18n.ERR_12072, error.getLocalizedMessage() ) ); 1890 } 1891 1892 return entries; 1893 } 1894 1895 1896 /** 1897 * @return True if the ldif file contains entries, fals if it contains 1898 * changes 1899 */ 1900 public boolean containsEntries() 1901 { 1902 return containsEntries; 1903 } 1904 1905 1906 /** 1907 * {@inheritDoc} 1908 */ 1909 public void close() throws IOException 1910 { 1911 if ( reader != null ) 1912 { 1913 position = new Position(); 1914 reader.close(); 1915 containsEntries = false; 1916 containsChanges = false; 1917 } 1918 } 1919 } 1920 1921