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.IOException; 025import java.io.StringReader; 026import java.util.ArrayList; 027 028import javax.naming.directory.Attributes; 029import javax.naming.directory.BasicAttributes; 030 031import org.apache.directory.api.i18n.I18n; 032import org.apache.directory.api.ldap.model.entry.Attribute; 033import org.apache.directory.api.ldap.model.entry.DefaultEntry; 034import org.apache.directory.api.ldap.model.entry.Entry; 035import org.apache.directory.api.ldap.model.exception.LdapException; 036import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException; 037import org.apache.directory.api.ldap.model.schema.AttributeType; 038import org.apache.directory.api.ldap.model.schema.SchemaManager; 039import org.apache.directory.api.util.Strings; 040import org.slf4j.Logger; 041import org.slf4j.LoggerFactory; 042 043 044/** 045 * <pre> 046 * <ldif-file> ::= "version:" <fill> <number> <seps> <dn-spec> <sep> 047 * <ldif-content-change> 048 * 049 * <ldif-content-change> ::= 050 * <number> <oid> <options-e> <value-spec> <sep> <attrval-specs-e> 051 * <ldif-attrval-record-e> | 052 * <alpha> <chars-e> <options-e> <value-spec> <sep> <attrval-specs-e> 053 * <ldif-attrval-record-e> | 054 * "control:" <fill> <number> <oid> <spaces-e> <criticality> 055 * <value-spec-e> <sep> <controls-e> 056 * "changetype:" <fill> <changerecord-type> <ldif-change-record-e> | 057 * "changetype:" <fill> <changerecord-type> <ldif-change-record-e> 058 * 059 * <ldif-attrval-record-e> ::= <seps> <dn-spec> <sep> <attributeType> 060 * <options-e> <value-spec> <sep> <attrval-specs-e> 061 * <ldif-attrval-record-e> | e 062 * 063 * <ldif-change-record-e> ::= <seps> <dn-spec> <sep> <controls-e> 064 * "changetype:" <fill> <changerecord-type> <ldif-change-record-e> | e 065 * 066 * <dn-spec> ::= "dn:" <fill> <safe-string> | "dn::" <fill> <base64-string> 067 * 068 * <controls-e> ::= "control:" <fill> <number> <oid> <spaces-e> <criticality> 069 * <value-spec-e> <sep> <controls-e> | e 070 * 071 * <criticality> ::= "true" | "false" | e 072 * 073 * <oid> ::= '.' <number> <oid> | e 074 * 075 * <attrval-specs-e> ::= <number> <oid> <options-e> <value-spec> <sep> 076 * <attrval-specs-e> | 077 * <alpha> <chars-e> <options-e> <value-spec> <sep> <attrval-specs-e> | e 078 * 079 * <value-spec-e> ::= <value-spec> | e 080 * 081 * <value-spec> ::= ':' <fill> <safe-string-e> | 082 * "::" <fill> <base64-chars> | 083 * ":<" <fill> <url> 084 * 085 * <attributeType> ::= <number> <oid> | <alpha> <chars-e> 086 * 087 * <options-e> ::= ';' <char> <chars-e> <options-e> |e 088 * 089 * <chars-e> ::= <char> <chars-e> | e 090 * 091 * <changerecord-type> ::= "add" <sep> <attributeType> <options-e> <value-spec> 092 * <sep> <attrval-specs-e> | 093 * "delete" <sep> | 094 * "modify" <sep> <mod-type> <fill> <attributeType> <options-e> <sep> 095 * <attrval-specs-e> <sep> '-' <sep> <mod-specs-e> | 096 * "moddn" <sep> <newrdn> <sep> "deleteoldrdn:" <fill> <0-1> <sep> 097 * <newsuperior-e> <sep> | 098 * "modrdn" <sep> <newrdn> <sep> "deleteoldrdn:" <fill> <0-1> <sep> 099 * <newsuperior-e> <sep> 100 * 101 * <newrdn> ::= ':' <fill> <safe-string> | "::" <fill> <base64-chars> 102 * 103 * <newsuperior-e> ::= "newsuperior" <newrdn> | e 104 * 105 * <mod-specs-e> ::= <mod-type> <fill> <attributeType> <options-e> 106 * <sep> <attrval-specs-e> <sep> '-' <sep> <mod-specs-e> | e 107 * 108 * <mod-type> ::= "add:" | "delete:" | "replace:" 109 * 110 * <url> ::= <a Uniform Resource Locator, as defined in [6]> 111 * 112 * 113 * 114 * LEXICAL 115 * ------- 116 * 117 * <fill> ::= ' ' <fill> | e 118 * <char> ::= <alpha> | <digit> | '-' 119 * <number> ::= <digit> <digits> 120 * <0-1> ::= '0' | '1' 121 * <digits> ::= <digit> <digits> | e 122 * <digit> ::= '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' 123 * <seps> ::= <sep> <seps-e> 124 * <seps-e> ::= <sep> <seps-e> | e 125 * <sep> ::= 0x0D 0x0A | 0x0A 126 * <spaces> ::= ' ' <spaces-e> 127 * <spaces-e> ::= ' ' <spaces-e> | e 128 * <safe-string-e> ::= <safe-string> | e 129 * <safe-string> ::= <safe-init-char> <safe-chars> 130 * <safe-init-char> ::= [0x01-0x09] | 0x0B | 0x0C | [0x0E-0x1F] | [0x21-0x39] | 0x3B | [0x3D-0x7F] 131 * <safe-chars> ::= <safe-char> <safe-chars> | e 132 * <safe-char> ::= [0x01-0x09] | 0x0B | 0x0C | [0x0E-0x7F] 133 * <base64-string> ::= <base64-char> <base64-chars> 134 * <base64-chars> ::= <base64-char> <base64-chars> | e 135 * <base64-char> ::= 0x2B | 0x2F | [0x30-0x39] | 0x3D | [0x41-9x5A] | [0x61-0x7A] 136 * <alpha> ::= [0x41-0x5A] | [0x61-0x7A] 137 * 138 * COMMENTS 139 * -------- 140 * - The ldap-oid VN is not correct in the RFC-2849. It has been changed from 1*DIGIT 0*1("." 1*DIGIT) to 141 * DIGIT+ ("." DIGIT+)* 142 * - The mod-spec lacks a sep between *attrval-spec and "-". 143 * - The BASE64-UTF8-STRING should be BASE64-CHAR BASE64-STRING 144 * - The ValueSpec rule must accept multilines values. In this case, we have a LF followed by a 145 * single space before the continued value. 146 * </pre> 147 * 148 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 149 */ 150public class LdifAttributesReader extends LdifReader 151{ 152 /** A logger */ 153 private static final Logger LOG = LoggerFactory.getLogger( LdifAttributesReader.class ); 154 155 156 /** 157 * Constructors 158 */ 159 public LdifAttributesReader() 160 { 161 super(); 162 lines = new ArrayList<String>(); 163 position = 0; 164 version = DEFAULT_VERSION; 165 } 166 167 168 /** 169 * Parse an AttributeType/AttributeValue 170 * 171 * @param attributes The entry where to store the value 172 * @param line The line to parse 173 * @param lowerLine The same line, lowercased 174 * @throws LdapLdifException If anything goes wrong 175 */ 176 private void parseAttribute( Attributes attributes, String line, String lowerLine ) throws LdapLdifException 177 { 178 int colonIndex = line.indexOf( ':' ); 179 180 String attributeType = lowerLine.substring( 0, colonIndex ); 181 182 // We should *not* have a Dn twice 183 if ( "dn".equals( attributeType ) ) 184 { 185 LOG.error( I18n.err( I18n.ERR_13400_ENTRY_WITH_TWO_DNS ) ); 186 throw new LdapLdifException( I18n.err( I18n.ERR_13439_LDIF_ENTRY_WITH_TWO_DNS ) ); 187 } 188 189 Object attributeValue = parseValue( attributeType, line, colonIndex ); 190 191 // Update the entry 192 javax.naming.directory.Attribute attribute = attributes.get( attributeType ); 193 194 if ( attribute == null ) 195 { 196 attributes.put( attributeType, attributeValue ); 197 } 198 else 199 { 200 attribute.add( attributeValue ); 201 } 202 } 203 204 205 /** 206 * Parse an AttributeType/AttributeValue 207 * 208 * @param schemaManager The SchemaManager 209 * @param entry The entry where to store the value 210 * @param line The line to parse 211 * @param lowerLine The same line, lowercased 212 * @throws LdapLdifException If anything goes wrong 213 */ 214 private void parseEntryAttribute( SchemaManager schemaManager, Entry entry, String line, String lowerLine ) 215 throws LdapLdifException 216 { 217 int colonIndex = line.indexOf( ':' ); 218 219 String attributeName = lowerLine.substring( 0, colonIndex ); 220 AttributeType attributeType = null; 221 222 // We should *not* have a Dn twice 223 if ( "dn".equals( attributeName ) ) 224 { 225 LOG.error( I18n.err( I18n.ERR_13400_ENTRY_WITH_TWO_DNS ) ); 226 throw new LdapLdifException( I18n.err( I18n.ERR_13439_LDIF_ENTRY_WITH_TWO_DNS ) ); 227 } 228 229 if ( schemaManager != null ) 230 { 231 attributeType = schemaManager.getAttributeType( attributeName ); 232 233 if ( attributeType == null ) 234 { 235 String msg = I18n.err( I18n.ERR_13475_UNKNOWN_ATTRIBUTETYPE, attributeName ); 236 LOG.error( msg ); 237 throw new LdapLdifException( msg ); 238 } 239 } 240 241 Object attributeValue = parseValue( attributeName, line, colonIndex ); 242 243 // Update the entry 244 Attribute attribute; 245 246 if ( schemaManager == null ) 247 { 248 attribute = entry.get( attributeName ); 249 } 250 else 251 { 252 attribute = entry.get( attributeType ); 253 } 254 255 if ( attribute == null ) 256 { 257 if ( schemaManager == null ) 258 { 259 if ( attributeValue instanceof String ) 260 { 261 entry.put( attributeName, ( String ) attributeValue ); 262 } 263 else 264 { 265 entry.put( attributeName, ( byte[] ) attributeValue ); 266 } 267 } 268 else 269 { 270 try 271 { 272 if ( attributeValue instanceof String ) 273 { 274 entry.put( attributeName, attributeType, ( String ) attributeValue ); 275 } 276 else 277 { 278 entry.put( attributeName, attributeType, ( byte[] ) attributeValue ); 279 } 280 } 281 catch ( LdapException le ) 282 { 283 throw new LdapLdifException( I18n.err( I18n.ERR_13460_BAD_ATTRIBUTE ), le ); 284 } 285 } 286 } 287 else 288 { 289 try 290 { 291 if ( attributeValue instanceof String ) 292 { 293 attribute.add( ( String ) attributeValue ); 294 } 295 else 296 { 297 attribute.add( ( byte[] ) attributeValue ); 298 } 299 } 300 catch ( LdapInvalidAttributeValueException liave ) 301 { 302 throw new LdapLdifException( liave.getMessage(), liave ); 303 } 304 } 305 } 306 307 308 /** 309 * Parse a ldif file. The following rules are processed : 310 * 311 * <ldif-file> ::= <ldif-attrval-record> <ldif-attrval-records> | 312 * <ldif-change-record> <ldif-change-records> <ldif-attrval-record> ::= 313 * <dn-spec> <sep> <attrval-spec> <attrval-specs> <ldif-change-record> ::= 314 * <dn-spec> <sep> <controls-e> <changerecord> <dn-spec> ::= "dn:" <fill> 315 * <distinguishedName> | "dn::" <fill> <base64-distinguishedName> 316 * <changerecord> ::= "changetype:" <fill> <change-op> 317 * 318 * @param schemaManager The SchemaManager 319 * @return The read entry 320 * @throws LdapLdifException If the entry can't be read or is invalid 321 */ 322 private Entry parseEntry( SchemaManager schemaManager ) throws LdapLdifException 323 { 324 if ( ( lines == null ) || lines.isEmpty() ) 325 { 326 if ( LOG.isDebugEnabled() ) 327 { 328 LOG.debug( I18n.msg( I18n.MSG_13408_END_OF_LDIF ) ); 329 } 330 331 return null; 332 } 333 334 Entry entry = new DefaultEntry( schemaManager ); 335 336 // Now, let's iterate through the other lines 337 for ( String line : lines ) 338 { 339 // Each line could start either with an OID, an attribute type, with 340 // "control:" or with "changetype:" 341 String lowerLine = Strings.toLowerCaseAscii( line ); 342 343 // We have three cases : 344 // 1) The first line after the Dn is a "control:" -> this is an error 345 // 2) The first line after the Dn is a "changeType:" -> this is an error 346 // 3) The first line after the Dn is anything else 347 if ( lowerLine.startsWith( "control:" ) ) 348 { 349 LOG.error( I18n.err( I18n.ERR_13401_CHANGE_NOT_ALLOWED ) ); 350 throw new LdapLdifException( I18n.err( I18n.ERR_13440_NO_CHANGE ) ); 351 } 352 else if ( lowerLine.startsWith( "changetype:" ) ) 353 { 354 LOG.error( I18n.err( I18n.ERR_13401_CHANGE_NOT_ALLOWED ) ); 355 throw new LdapLdifException( I18n.err( I18n.ERR_13440_NO_CHANGE ) ); 356 } 357 else if ( line.indexOf( ':' ) > 0 ) 358 { 359 parseEntryAttribute( schemaManager, entry, line, lowerLine ); 360 } 361 else 362 { 363 // Invalid attribute Value 364 LOG.error( I18n.err( I18n.ERR_13402_EXPECTING_ATTRIBUTE_TYPE ) ); 365 throw new LdapLdifException( I18n.err( I18n.ERR_13441_BAD_ATTRIBUTE ) ); 366 } 367 } 368 369 if ( LOG.isDebugEnabled() ) 370 { 371 LOG.debug( I18n.msg( I18n.MSG_13405_READ_ATTR, entry ) ); 372 } 373 374 return entry; 375 } 376 377 378 /** 379 * Parse a ldif file. The following rules are processed : 380 * 381 * <pre> 382 * <ldif-file> ::= <ldif-attrval-record> <ldif-attrval-records> | 383 * <ldif-change-record> <ldif-change-records> <ldif-attrval-record> ::= 384 * <dn-spec> <sep> <attrval-spec> <attrval-specs> <ldif-change-record> ::= 385 * <dn-spec> <sep> <controls-e> <changerecord> <dn-spec> ::= "dn:" <fill> 386 * <distinguishedName> | "dn::" <fill> <base64-distinguishedName> 387 * <changerecord> ::= "changetype:" <fill> <change-op> 388 * </pre> 389 * 390 * @return The read entry 391 * @throws LdapLdifException If the entry can't be read or is invalid 392 */ 393 private Attributes parseAttributes() throws LdapLdifException 394 { 395 if ( ( lines == null ) || lines.isEmpty() ) 396 { 397 if ( LOG.isDebugEnabled() ) 398 { 399 LOG.debug( I18n.msg( I18n.MSG_13408_END_OF_LDIF ) ); 400 } 401 402 return null; 403 } 404 405 Attributes attributes = new BasicAttributes( true ); 406 407 // Now, let's iterate through the other lines 408 for ( String line : lines ) 409 { 410 // Each line could start either with an OID, an attribute type, with 411 // "control:" or with "changetype:" 412 String lowerLine = Strings.toLowerCaseAscii( line ); 413 414 // We have three cases : 415 // 1) The first line after the Dn is a "control:" -> this is an error 416 // 2) The first line after the Dn is a "changeType:" -> this is an error 417 // 3) The first line after the Dn is anything else 418 if ( lowerLine.startsWith( "control:" ) ) 419 { 420 LOG.error( I18n.err( I18n.ERR_13401_CHANGE_NOT_ALLOWED ) ); 421 throw new LdapLdifException( I18n.err( I18n.ERR_13440_NO_CHANGE ) ); 422 } 423 else if ( lowerLine.startsWith( "changetype:" ) ) 424 { 425 LOG.error( I18n.err( I18n.ERR_13401_CHANGE_NOT_ALLOWED ) ); 426 throw new LdapLdifException( I18n.err( I18n.ERR_13440_NO_CHANGE ) ); 427 } 428 else if ( line.indexOf( ':' ) > 0 ) 429 { 430 parseAttribute( attributes, line, lowerLine ); 431 } 432 else 433 { 434 // Invalid attribute Value 435 LOG.error( I18n.err( I18n.ERR_13402_EXPECTING_ATTRIBUTE_TYPE ) ); 436 throw new LdapLdifException( I18n.err( I18n.ERR_13441_BAD_ATTRIBUTE ) ); 437 } 438 } 439 440 if ( LOG.isDebugEnabled() ) 441 { 442 LOG.debug( I18n.msg( I18n.MSG_13405_READ_ATTR, attributes ) ); 443 } 444 445 return attributes; 446 } 447 448 449 /** 450 * A method which parses a ldif string and returns a list of Attributes. 451 * 452 * @param ldif The ldif string 453 * @return A list of Attributes, or an empty List 454 * @throws LdapLdifException If something went wrong 455 */ 456 public Attributes parseAttributes( String ldif ) throws LdapLdifException 457 { 458 lines = new ArrayList<String>(); 459 position = 0; 460 461 if ( LOG.isDebugEnabled() ) 462 { 463 LOG.debug( I18n.msg( I18n.MSG_13407_STARTS_PARSING_LDIF ) ); 464 } 465 466 if ( Strings.isEmpty( ldif ) ) 467 { 468 return new BasicAttributes( true ); 469 } 470 471 StringReader strIn = new StringReader( ldif ); 472 reader = new BufferedReader( strIn ); 473 474 try 475 { 476 readLines(); 477 478 Attributes attributes = parseAttributes(); 479 480 if ( LOG.isDebugEnabled() ) 481 { 482 if ( attributes == null ) 483 { 484 LOG.debug( I18n.msg( I18n.MSG_13401_PARSED_NO_ENTRY ) ); 485 } 486 else 487 { 488 LOG.debug( I18n.msg( I18n.MSG_13402_PARSED_ONE_ENTRY ) ); 489 } 490 } 491 492 return attributes; 493 } 494 catch ( LdapLdifException ne ) 495 { 496 LOG.error( I18n.err( I18n.ERR_13403_CANNOT_PARSE_LDIF_BUFFER, ne.getLocalizedMessage() ) ); 497 throw new LdapLdifException( I18n.err( I18n.ERR_13442_ERROR_PARSING_LDIF_BUFFER ), ne ); 498 } 499 finally 500 { 501 try 502 { 503 reader.close(); 504 } 505 catch ( IOException ioe ) 506 { 507 throw new LdapLdifException( I18n.err( I18n.ERR_13450_CANNOT_CLOSE_FILE ), ioe ); 508 } 509 } 510 } 511 512 513 /** 514 * A method which parses a ldif string and returns an Entry. 515 * 516 * @param ldif The ldif string 517 * @return An entry 518 * @throws LdapLdifException If something went wrong 519 */ 520 public Entry parseEntry( String ldif ) throws LdapLdifException 521 { 522 lines = new ArrayList<String>(); 523 position = 0; 524 525 if ( LOG.isDebugEnabled() ) 526 { 527 LOG.debug( I18n.msg( I18n.MSG_13407_STARTS_PARSING_LDIF ) ); 528 } 529 530 if ( Strings.isEmpty( ldif ) ) 531 { 532 return new DefaultEntry(); 533 } 534 535 StringReader strIn = new StringReader( ldif ); 536 reader = new BufferedReader( strIn ); 537 538 try 539 { 540 readLines(); 541 542 Entry entry = parseEntry( ( SchemaManager ) null ); 543 544 if ( LOG.isDebugEnabled() ) 545 { 546 if ( entry == null ) 547 { 548 LOG.debug( I18n.msg( I18n.MSG_13401_PARSED_NO_ENTRY ) ); 549 } 550 else 551 { 552 LOG.debug( I18n.msg( I18n.MSG_13402_PARSED_ONE_ENTRY ) ); 553 } 554 } 555 556 return entry; 557 } 558 catch ( LdapLdifException ne ) 559 { 560 LOG.error( I18n.err( I18n.ERR_13403_CANNOT_PARSE_LDIF_BUFFER, ne.getLocalizedMessage() ) ); 561 throw new LdapLdifException( I18n.err( I18n.ERR_13442_ERROR_PARSING_LDIF_BUFFER ), ne ); 562 } 563 finally 564 { 565 try 566 { 567 reader.close(); 568 } 569 catch ( IOException ioe ) 570 { 571 throw new LdapLdifException( I18n.err( I18n.ERR_13450_CANNOT_CLOSE_FILE ), ioe ); 572 } 573 } 574 } 575 576 577 /** 578 * A method which parses a ldif string and returns an Entry. 579 * 580 * @param schemaManager The SchemaManager 581 * @param ldif The ldif string 582 * @return An entry 583 * @throws LdapLdifException If something went wrong 584 */ 585 public Entry parseEntry( SchemaManager schemaManager, String ldif ) throws LdapLdifException 586 { 587 lines = new ArrayList<String>(); 588 position = 0; 589 590 if ( LOG.isDebugEnabled() ) 591 { 592 LOG.debug( I18n.msg( I18n.MSG_13407_STARTS_PARSING_LDIF ) ); 593 } 594 595 if ( Strings.isEmpty( ldif ) ) 596 { 597 return new DefaultEntry( schemaManager ); 598 } 599 600 StringReader strIn = new StringReader( ldif ); 601 reader = new BufferedReader( strIn ); 602 603 try 604 { 605 readLines(); 606 607 Entry entry = parseEntry( schemaManager ); 608 609 if ( LOG.isDebugEnabled() ) 610 { 611 if ( entry == null ) 612 { 613 LOG.debug( I18n.msg( I18n.MSG_13401_PARSED_NO_ENTRY ) ); 614 } 615 else 616 { 617 LOG.debug( I18n.msg( I18n.MSG_13402_PARSED_ONE_ENTRY ) ); 618 } 619 620 } 621 622 return entry; 623 } 624 catch ( LdapLdifException ne ) 625 { 626 LOG.error( I18n.err( I18n.ERR_13403_CANNOT_PARSE_LDIF_BUFFER, ne.getLocalizedMessage() ) ); 627 throw new LdapLdifException( I18n.err( I18n.ERR_13442_ERROR_PARSING_LDIF_BUFFER ), ne ); 628 } 629 finally 630 { 631 try 632 { 633 reader.close(); 634 } 635 catch ( IOException ioe ) 636 { 637 throw new LdapLdifException( I18n.err( I18n.ERR_13450_CANNOT_CLOSE_FILE ), ioe ); 638 } 639 } 640 } 641}