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