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.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 lines = new ArrayList<String>(); 162 position = 0; 163 version = DEFAULT_VERSION; 164 } 165 166 167 /** 168 * Parse an AttributeType/AttributeValue 169 * 170 * @param attributes The entry where to store the value 171 * @param line The line to parse 172 * @param lowerLine The same line, lowercased 173 * @throws LdapLdifException If anything goes wrong 174 */ 175 private void parseAttribute( Attributes attributes, String line, String lowerLine ) throws LdapLdifException 176 { 177 int colonIndex = line.indexOf( ':' ); 178 179 String attributeType = lowerLine.substring( 0, colonIndex ); 180 181 // We should *not* have a Dn twice 182 if ( attributeType.equals( "dn" ) ) 183 { 184 LOG.error( I18n.err( I18n.ERR_12002_ENTRY_WITH_TWO_DNS ) ); 185 throw new LdapLdifException( I18n.err( I18n.ERR_12003_LDIF_ENTRY_WITH_TWO_DNS ) ); 186 } 187 188 Object attributeValue = parseValue( line, colonIndex ); 189 190 // Update the entry 191 javax.naming.directory.Attribute attribute = attributes.get( attributeType ); 192 193 if ( attribute == null ) 194 { 195 attributes.put( attributeType, attributeValue ); 196 } 197 else 198 { 199 attribute.add( attributeValue ); 200 } 201 } 202 203 204 /** 205 * Parse an AttributeType/AttributeValue 206 * 207 * @param schemaManager The SchemaManager 208 * @param entry The entry where to store the value 209 * @param line The line to parse 210 * @param lowerLine The same line, lowercased 211 * @throws LdapLdifException If anything goes wrong 212 */ 213 private void parseEntryAttribute( SchemaManager schemaManager, Entry entry, String line, String lowerLine ) 214 throws LdapLdifException 215 { 216 int colonIndex = line.indexOf( ':' ); 217 218 String attributeName = lowerLine.substring( 0, colonIndex ); 219 AttributeType attributeType = null; 220 221 // We should *not* have a Dn twice 222 if ( attributeName.equals( "dn" ) ) 223 { 224 LOG.error( I18n.err( I18n.ERR_12002_ENTRY_WITH_TWO_DNS ) ); 225 throw new LdapLdifException( I18n.err( I18n.ERR_12003_LDIF_ENTRY_WITH_TWO_DNS ) ); 226 } 227 228 if ( schemaManager != null ) 229 { 230 attributeType = schemaManager.getAttributeType( attributeName ); 231 232 if ( attributeType == null ) 233 { 234 LOG.error( "" ); 235 throw new LdapLdifException( "" ); 236 } 237 } 238 239 Object attributeValue = parseValue( line, colonIndex ); 240 241 // Update the entry 242 Attribute attribute = null; 243 244 if ( schemaManager == null ) 245 { 246 attribute = entry.get( attributeName ); 247 } 248 else 249 { 250 attribute = entry.get( attributeType ); 251 } 252 253 if ( attribute == null ) 254 { 255 if ( schemaManager == null ) 256 { 257 if ( attributeValue instanceof String ) 258 { 259 entry.put( attributeName, ( String ) attributeValue ); 260 } 261 else 262 { 263 entry.put( attributeName, ( byte[] ) attributeValue ); 264 } 265 } 266 else 267 { 268 try 269 { 270 if ( attributeValue instanceof String ) 271 { 272 entry.put( attributeName, attributeType, ( String ) attributeValue ); 273 } 274 else 275 { 276 entry.put( attributeName, attributeType, ( byte[] ) attributeValue ); 277 } 278 } 279 catch ( LdapException le ) 280 { 281 throw new LdapLdifException( I18n.err( I18n.ERR_12057_BAD_ATTRIBUTE ), le ); 282 } 283 } 284 } 285 else 286 { 287 try 288 { 289 if ( attributeValue instanceof String ) 290 { 291 attribute.add( ( String ) attributeValue ); 292 } 293 else 294 { 295 attribute.add( ( byte[] ) attributeValue ); 296 } 297 } 298 catch ( LdapInvalidAttributeValueException liave ) 299 { 300 throw new LdapLdifException( liave.getMessage(), liave ); 301 } 302 } 303 } 304 305 306 /** 307 * Parse a ldif file. The following rules are processed : 308 * 309 * <ldif-file> ::= <ldif-attrval-record> <ldif-attrval-records> | 310 * <ldif-change-record> <ldif-change-records> <ldif-attrval-record> ::= 311 * <dn-spec> <sep> <attrval-spec> <attrval-specs> <ldif-change-record> ::= 312 * <dn-spec> <sep> <controls-e> <changerecord> <dn-spec> ::= "dn:" <fill> 313 * <distinguishedName> | "dn::" <fill> <base64-distinguishedName> 314 * <changerecord> ::= "changetype:" <fill> <change-op> 315 * 316 * @param schemaManager The SchemaManager 317 * @return The read entry 318 * @throws LdapLdifException If the entry can't be read or is invalid 319 */ 320 private Entry parseEntry( SchemaManager schemaManager ) throws LdapLdifException 321 { 322 if ( ( lines == null ) || ( lines.size() == 0 ) ) 323 { 324 LOG.debug( "The entry is empty : end of ldif file" ); 325 return null; 326 } 327 328 Entry entry = new DefaultEntry( schemaManager ); 329 330 // Now, let's iterate through the other lines 331 for ( String line : lines ) 332 { 333 // Each line could start either with an OID, an attribute type, with 334 // "control:" or with "changetype:" 335 String lowerLine = Strings.toLowerCase( line ); 336 337 // We have three cases : 338 // 1) The first line after the Dn is a "control:" -> this is an error 339 // 2) The first line after the Dn is a "changeType:" -> this is an error 340 // 3) The first line after the Dn is anything else 341 if ( lowerLine.startsWith( "control:" ) ) 342 { 343 LOG.error( I18n.err( I18n.ERR_12004_CHANGE_NOT_ALLOWED ) ); 344 throw new LdapLdifException( I18n.err( I18n.ERR_12005_NO_CHANGE ) ); 345 } 346 else if ( lowerLine.startsWith( "changetype:" ) ) 347 { 348 LOG.error( I18n.err( I18n.ERR_12004_CHANGE_NOT_ALLOWED ) ); 349 throw new LdapLdifException( I18n.err( I18n.ERR_12005_NO_CHANGE ) ); 350 } 351 else if ( line.indexOf( ':' ) > 0 ) 352 { 353 parseEntryAttribute( schemaManager, entry, line, lowerLine ); 354 } 355 else 356 { 357 // Invalid attribute Value 358 LOG.error( I18n.err( I18n.ERR_12006_EXPECTING_ATTRIBUTE_TYPE ) ); 359 throw new LdapLdifException( I18n.err( I18n.ERR_12007_BAD_ATTRIBUTE ) ); 360 } 361 } 362 363 LOG.debug( "Read an attributes : {}", entry ); 364 365 return entry; 366 } 367 368 369 /** 370 * Parse a ldif file. The following rules are processed : 371 * 372 * <ldif-file> ::= <ldif-attrval-record> <ldif-attrval-records> | 373 * <ldif-change-record> <ldif-change-records> <ldif-attrval-record> ::= 374 * <dn-spec> <sep> <attrval-spec> <attrval-specs> <ldif-change-record> ::= 375 * <dn-spec> <sep> <controls-e> <changerecord> <dn-spec> ::= "dn:" <fill> 376 * <distinguishedName> | "dn::" <fill> <base64-distinguishedName> 377 * <changerecord> ::= "changetype:" <fill> <change-op> 378 * 379 * @return The read entry 380 * @throws LdapLdifException If the entry can't be read or is invalid 381 */ 382 private Attributes parseAttributes() throws LdapLdifException 383 { 384 if ( ( lines == null ) || ( lines.size() == 0 ) ) 385 { 386 LOG.debug( "The entry is empty : end of ldif file" ); 387 return null; 388 } 389 390 Attributes attributes = new BasicAttributes( true ); 391 392 // Now, let's iterate through the other lines 393 for ( String line : lines ) 394 { 395 // Each line could start either with an OID, an attribute type, with 396 // "control:" or with "changetype:" 397 String lowerLine = Strings.toLowerCase( line ); 398 399 // We have three cases : 400 // 1) The first line after the Dn is a "control:" -> this is an error 401 // 2) The first line after the Dn is a "changeType:" -> this is an error 402 // 3) The first line after the Dn is anything else 403 if ( lowerLine.startsWith( "control:" ) ) 404 { 405 LOG.error( I18n.err( I18n.ERR_12004_CHANGE_NOT_ALLOWED ) ); 406 throw new LdapLdifException( I18n.err( I18n.ERR_12005_NO_CHANGE ) ); 407 } 408 else if ( lowerLine.startsWith( "changetype:" ) ) 409 { 410 LOG.error( I18n.err( I18n.ERR_12004_CHANGE_NOT_ALLOWED ) ); 411 throw new LdapLdifException( I18n.err( I18n.ERR_12005_NO_CHANGE ) ); 412 } 413 else if ( line.indexOf( ':' ) > 0 ) 414 { 415 parseAttribute( attributes, line, lowerLine ); 416 } 417 else 418 { 419 // Invalid attribute Value 420 LOG.error( I18n.err( I18n.ERR_12006_EXPECTING_ATTRIBUTE_TYPE ) ); 421 throw new LdapLdifException( I18n.err( I18n.ERR_12007_BAD_ATTRIBUTE ) ); 422 } 423 } 424 425 LOG.debug( "Read an attributes : {}", attributes ); 426 427 return attributes; 428 } 429 430 431 /** 432 * A method which parses a ldif string and returns a list of Attributes. 433 * 434 * @param ldif The ldif string 435 * @return A list of Attributes, or an empty List 436 * @throws LdapLdifException If something went wrong 437 */ 438 public Attributes parseAttributes( String ldif ) throws LdapLdifException 439 { 440 lines = new ArrayList<String>(); 441 position = 0; 442 443 LOG.debug( "Starts parsing ldif buffer" ); 444 445 if ( Strings.isEmpty( ldif ) ) 446 { 447 return new BasicAttributes( true ); 448 } 449 450 StringReader strIn = new StringReader( ldif ); 451 reader = new BufferedReader( strIn ); 452 453 try 454 { 455 readLines(); 456 457 Attributes attributes = parseAttributes(); 458 459 if ( LOG.isDebugEnabled() ) 460 { 461 if ( attributes == null ) 462 { 463 LOG.debug( "Parsed no entry." ); 464 } 465 else 466 { 467 LOG.debug( "Parsed one entry." ); 468 } 469 } 470 471 return attributes; 472 } 473 catch ( LdapLdifException ne ) 474 { 475 LOG.error( I18n.err( I18n.ERR_12008_CANNOT_PARSE_LDIF_BUFFER, ne.getLocalizedMessage() ) ); 476 throw new LdapLdifException( I18n.err( I18n.ERR_12009_ERROR_PARSING_LDIF_BUFFER ), ne ); 477 } 478 finally 479 { 480 try 481 { 482 reader.close(); 483 } 484 catch ( IOException ioe ) 485 { 486 throw new LdapLdifException( I18n.err( I18n.ERR_12024_CANNOT_CLOSE_FILE ), ioe ); 487 } 488 } 489 } 490 491 492 /** 493 * A method which parses a ldif string and returns an Entry. 494 * 495 * @param ldif The ldif string 496 * @return An entry 497 * @throws LdapLdifException If something went wrong 498 */ 499 public Entry parseEntry( String ldif ) throws LdapLdifException 500 { 501 lines = new ArrayList<String>(); 502 position = 0; 503 504 LOG.debug( "Starts parsing ldif buffer" ); 505 506 if ( Strings.isEmpty( ldif ) ) 507 { 508 return new DefaultEntry(); 509 } 510 511 StringReader strIn = new StringReader( ldif ); 512 reader = new BufferedReader( strIn ); 513 514 try 515 { 516 readLines(); 517 518 Entry entry = parseEntry( ( SchemaManager ) null ); 519 520 if ( LOG.isDebugEnabled() ) 521 { 522 if ( entry == null ) 523 { 524 LOG.debug( "Parsed no entry." ); 525 } 526 else 527 { 528 LOG.debug( "Parsed one entry." ); 529 } 530 531 } 532 533 return entry; 534 } 535 catch ( LdapLdifException ne ) 536 { 537 LOG.error( I18n.err( I18n.ERR_12008_CANNOT_PARSE_LDIF_BUFFER, ne.getLocalizedMessage() ) ); 538 throw new LdapLdifException( I18n.err( I18n.ERR_12009_ERROR_PARSING_LDIF_BUFFER ), ne ); 539 } 540 finally 541 { 542 try 543 { 544 reader.close(); 545 } 546 catch ( IOException ioe ) 547 { 548 throw new LdapLdifException( I18n.err( I18n.ERR_12024_CANNOT_CLOSE_FILE ), ioe ); 549 } 550 } 551 } 552 553 554 /** 555 * A method which parses a ldif string and returns an Entry. 556 * 557 * @param schemaManager The SchemaManager 558 * @param ldif The ldif string 559 * @return An entry 560 * @throws LdapLdifException If something went wrong 561 */ 562 public Entry parseEntry( SchemaManager schemaManager, String ldif ) throws LdapLdifException 563 { 564 lines = new ArrayList<String>(); 565 position = 0; 566 567 LOG.debug( "Starts parsing ldif buffer" ); 568 569 if ( Strings.isEmpty( ldif ) ) 570 { 571 return new DefaultEntry( schemaManager ); 572 } 573 574 StringReader strIn = new StringReader( ldif ); 575 reader = new BufferedReader( strIn ); 576 577 try 578 { 579 readLines(); 580 581 Entry entry = parseEntry( schemaManager ); 582 583 if ( LOG.isDebugEnabled() ) 584 { 585 if ( entry == null ) 586 { 587 LOG.debug( "Parsed no entry." ); 588 } 589 else 590 { 591 LOG.debug( "Parsed one entry." ); 592 } 593 594 } 595 596 return entry; 597 } 598 catch ( LdapLdifException ne ) 599 { 600 LOG.error( I18n.err( I18n.ERR_12008_CANNOT_PARSE_LDIF_BUFFER, ne.getLocalizedMessage() ) ); 601 throw new LdapLdifException( I18n.err( I18n.ERR_12009_ERROR_PARSING_LDIF_BUFFER ), ne ); 602 } 603 finally 604 { 605 try 606 { 607 reader.close(); 608 } 609 catch ( IOException ioe ) 610 { 611 throw new LdapLdifException( I18n.err( I18n.ERR_12024_CANNOT_CLOSE_FILE ), ioe ); 612 } 613 } 614 } 615}