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.shared.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.shared.i18n.I18n; 032import org.apache.directory.shared.ldap.model.entry.Attribute; 033import org.apache.directory.shared.ldap.model.entry.DefaultEntry; 034import org.apache.directory.shared.ldap.model.entry.Entry; 035import org.apache.directory.shared.ldap.model.exception.LdapException; 036import org.apache.directory.shared.ldap.model.exception.LdapInvalidAttributeValueException; 037import org.apache.directory.shared.ldap.model.schema.AttributeType; 038import org.apache.directory.shared.ldap.model.schema.SchemaManager; 039import org.apache.directory.shared.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 = line.toLowerCase(); 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 = line.toLowerCase(); 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 // This will suppress PMD.EmptyCatchBlock warnings in this method 439 @SuppressWarnings("PMD.EmptyCatchBlock") 440 public Attributes parseAttributes( String ldif ) throws LdapLdifException 441 { 442 lines = new ArrayList<String>(); 443 position = 0; 444 445 LOG.debug( "Starts parsing ldif buffer" ); 446 447 if ( Strings.isEmpty(ldif) ) 448 { 449 return new BasicAttributes( true ); 450 } 451 452 StringReader strIn = new StringReader( ldif ); 453 reader = new BufferedReader( strIn ); 454 455 try 456 { 457 readLines(); 458 459 Attributes attributes = parseAttributes(); 460 461 if ( LOG.isDebugEnabled() ) 462 { 463 if ( attributes == null ) 464 { 465 LOG.debug( "Parsed no entry." ); 466 } 467 else 468 { 469 LOG.debug( "Parsed one entry." ); 470 } 471 } 472 473 return attributes; 474 } 475 catch ( LdapLdifException ne ) 476 { 477 LOG.error( I18n.err( I18n.ERR_12008_CANNOT_PARSE_LDIF_BUFFER, ne.getLocalizedMessage() ) ); 478 throw new LdapLdifException( I18n.err( I18n.ERR_12009_ERROR_PARSING_LDIF_BUFFER ), ne ); 479 } 480 finally 481 { 482 try 483 { 484 reader.close(); 485 } 486 catch ( IOException ioe ) 487 { 488 throw new LdapLdifException( I18n.err( I18n.ERR_12024_CANNOT_CLOSE_FILE ), ioe ); 489 } 490 } 491 } 492 493 494 /** 495 * A method which parses a ldif string and returns an Entry. 496 * 497 * @param ldif The ldif string 498 * @return An entry 499 * @throws LdapLdifException If something went wrong 500 */ 501 // This will suppress PMD.EmptyCatchBlock warnings in this method 502 @SuppressWarnings("PMD.EmptyCatchBlock") 503 public Entry parseEntry( String ldif ) throws LdapLdifException 504 { 505 lines = new ArrayList<String>(); 506 position = 0; 507 508 LOG.debug( "Starts parsing ldif buffer" ); 509 510 if ( Strings.isEmpty(ldif) ) 511 { 512 return new DefaultEntry(); 513 } 514 515 StringReader strIn = new StringReader( ldif ); 516 reader = new BufferedReader( strIn ); 517 518 try 519 { 520 readLines(); 521 522 Entry entry = parseEntry( ( SchemaManager ) null ); 523 524 if ( LOG.isDebugEnabled() ) 525 { 526 if ( entry == null ) 527 { 528 LOG.debug( "Parsed no entry." ); 529 } 530 else 531 { 532 LOG.debug( "Parsed one entry." ); 533 } 534 535 } 536 537 return entry; 538 } 539 catch ( LdapLdifException ne ) 540 { 541 LOG.error( I18n.err( I18n.ERR_12008_CANNOT_PARSE_LDIF_BUFFER, ne.getLocalizedMessage() ) ); 542 throw new LdapLdifException( I18n.err( I18n.ERR_12009_ERROR_PARSING_LDIF_BUFFER ), ne ); 543 } 544 finally 545 { 546 try 547 { 548 reader.close(); 549 } 550 catch ( IOException ioe ) 551 { 552 throw new LdapLdifException( I18n.err( I18n.ERR_12024_CANNOT_CLOSE_FILE ), ioe ); 553 } 554 } 555 } 556 557 558 /** 559 * A method which parses a ldif string and returns an Entry. 560 * 561 * @param schemaManager The SchemaManager 562 * @param ldif The ldif string 563 * @return An entry 564 * @throws LdapLdifException If something went wrong 565 */ 566 // This will suppress PMD.EmptyCatchBlock warnings in this method 567 @SuppressWarnings("PMD.EmptyCatchBlock") 568 public Entry parseEntry( SchemaManager schemaManager, String ldif ) throws LdapLdifException 569 { 570 lines = new ArrayList<String>(); 571 position = 0; 572 573 LOG.debug( "Starts parsing ldif buffer" ); 574 575 if ( Strings.isEmpty(ldif) ) 576 { 577 return new DefaultEntry( schemaManager ); 578 } 579 580 StringReader strIn = new StringReader( ldif ); 581 reader = new BufferedReader( strIn ); 582 583 try 584 { 585 readLines(); 586 587 Entry entry = parseEntry( schemaManager ); 588 589 if ( LOG.isDebugEnabled() ) 590 { 591 if ( entry == null ) 592 { 593 LOG.debug( "Parsed no entry." ); 594 } 595 else 596 { 597 LOG.debug( "Parsed one entry." ); 598 } 599 600 } 601 602 return entry; 603 } 604 catch ( LdapLdifException ne ) 605 { 606 LOG.error( I18n.err( I18n.ERR_12008_CANNOT_PARSE_LDIF_BUFFER, ne.getLocalizedMessage() ) ); 607 throw new LdapLdifException( I18n.err( I18n.ERR_12009_ERROR_PARSING_LDIF_BUFFER ), ne ); 608 } 609 finally 610 { 611 try 612 { 613 reader.close(); 614 } 615 catch ( IOException ioe ) 616 { 617 throw new LdapLdifException( I18n.err( I18n.ERR_12024_CANNOT_CLOSE_FILE ), ioe ); 618 } 619 } 620 } 621}