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