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 ( "dn".equals( attributeType ) ) 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( attributeType, 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 ( "dn".equals( attributeName ) ) 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( attributeName, line, colonIndex ); 240 241 // Update the entry 242 Attribute attribute; 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.isEmpty() ) 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.toLowerCaseAscii( 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 * <pre> 373 * <ldif-file> ::= <ldif-attrval-record> <ldif-attrval-records> | 374 * <ldif-change-record> <ldif-change-records> <ldif-attrval-record> ::= 375 * <dn-spec> <sep> <attrval-spec> <attrval-specs> <ldif-change-record> ::= 376 * <dn-spec> <sep> <controls-e> <changerecord> <dn-spec> ::= "dn:" <fill> 377 * <distinguishedName> | "dn::" <fill> <base64-distinguishedName> 378 * <changerecord> ::= "changetype:" <fill> <change-op> 379 * </pre> 380 * 381 * @return The read entry 382 * @throws LdapLdifException If the entry can't be read or is invalid 383 */ 384 private Attributes parseAttributes() throws LdapLdifException 385 { 386 if ( ( lines == null ) || lines.isEmpty() ) 387 { 388 LOG.debug( "The entry is empty : end of ldif file" ); 389 return null; 390 } 391 392 Attributes attributes = new BasicAttributes( true ); 393 394 // Now, let's iterate through the other lines 395 for ( String line : lines ) 396 { 397 // Each line could start either with an OID, an attribute type, with 398 // "control:" or with "changetype:" 399 String lowerLine = Strings.toLowerCaseAscii( line ); 400 401 // We have three cases : 402 // 1) The first line after the Dn is a "control:" -> this is an error 403 // 2) The first line after the Dn is a "changeType:" -> this is an error 404 // 3) The first line after the Dn is anything else 405 if ( lowerLine.startsWith( "control:" ) ) 406 { 407 LOG.error( I18n.err( I18n.ERR_12004_CHANGE_NOT_ALLOWED ) ); 408 throw new LdapLdifException( I18n.err( I18n.ERR_12005_NO_CHANGE ) ); 409 } 410 else if ( lowerLine.startsWith( "changetype:" ) ) 411 { 412 LOG.error( I18n.err( I18n.ERR_12004_CHANGE_NOT_ALLOWED ) ); 413 throw new LdapLdifException( I18n.err( I18n.ERR_12005_NO_CHANGE ) ); 414 } 415 else if ( line.indexOf( ':' ) > 0 ) 416 { 417 parseAttribute( attributes, line, lowerLine ); 418 } 419 else 420 { 421 // Invalid attribute Value 422 LOG.error( I18n.err( I18n.ERR_12006_EXPECTING_ATTRIBUTE_TYPE ) ); 423 throw new LdapLdifException( I18n.err( I18n.ERR_12007_BAD_ATTRIBUTE ) ); 424 } 425 } 426 427 LOG.debug( "Read an attributes : {}", attributes ); 428 429 return attributes; 430 } 431 432 433 /** 434 * A method which parses a ldif string and returns a list of Attributes. 435 * 436 * @param ldif The ldif string 437 * @return A list of Attributes, or an empty List 438 * @throws LdapLdifException If something went wrong 439 */ 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 public Entry parseEntry( String ldif ) throws LdapLdifException 502 { 503 lines = new ArrayList<String>(); 504 position = 0; 505 506 LOG.debug( "Starts parsing ldif buffer" ); 507 508 if ( Strings.isEmpty( ldif ) ) 509 { 510 return new DefaultEntry(); 511 } 512 513 StringReader strIn = new StringReader( ldif ); 514 reader = new BufferedReader( strIn ); 515 516 try 517 { 518 readLines(); 519 520 Entry entry = parseEntry( ( SchemaManager ) null ); 521 522 if ( LOG.isDebugEnabled() ) 523 { 524 if ( entry == null ) 525 { 526 LOG.debug( "Parsed no entry." ); 527 } 528 else 529 { 530 LOG.debug( "Parsed one entry." ); 531 } 532 533 } 534 535 return entry; 536 } 537 catch ( LdapLdifException ne ) 538 { 539 LOG.error( I18n.err( I18n.ERR_12008_CANNOT_PARSE_LDIF_BUFFER, ne.getLocalizedMessage() ) ); 540 throw new LdapLdifException( I18n.err( I18n.ERR_12009_ERROR_PARSING_LDIF_BUFFER ), ne ); 541 } 542 finally 543 { 544 try 545 { 546 reader.close(); 547 } 548 catch ( IOException ioe ) 549 { 550 throw new LdapLdifException( I18n.err( I18n.ERR_12024_CANNOT_CLOSE_FILE ), ioe ); 551 } 552 } 553 } 554 555 556 /** 557 * A method which parses a ldif string and returns an Entry. 558 * 559 * @param schemaManager The SchemaManager 560 * @param ldif The ldif string 561 * @return An entry 562 * @throws LdapLdifException If something went wrong 563 */ 564 public Entry parseEntry( SchemaManager schemaManager, String ldif ) throws LdapLdifException 565 { 566 lines = new ArrayList<String>(); 567 position = 0; 568 569 LOG.debug( "Starts parsing ldif buffer" ); 570 571 if ( Strings.isEmpty( ldif ) ) 572 { 573 return new DefaultEntry( schemaManager ); 574 } 575 576 StringReader strIn = new StringReader( ldif ); 577 reader = new BufferedReader( strIn ); 578 579 try 580 { 581 readLines(); 582 583 Entry entry = parseEntry( schemaManager ); 584 585 if ( LOG.isDebugEnabled() ) 586 { 587 if ( entry == null ) 588 { 589 LOG.debug( "Parsed no entry." ); 590 } 591 else 592 { 593 LOG.debug( "Parsed one entry." ); 594 } 595 596 } 597 598 return entry; 599 } 600 catch ( LdapLdifException ne ) 601 { 602 LOG.error( I18n.err( I18n.ERR_12008_CANNOT_PARSE_LDIF_BUFFER, ne.getLocalizedMessage() ) ); 603 throw new LdapLdifException( I18n.err( I18n.ERR_12009_ERROR_PARSING_LDIF_BUFFER ), ne ); 604 } 605 finally 606 { 607 try 608 { 609 reader.close(); 610 } 611 catch ( IOException ioe ) 612 { 613 throw new LdapLdifException( I18n.err( I18n.ERR_12024_CANNOT_CLOSE_FILE ), ioe ); 614 } 615 } 616 } 617}