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