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