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