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 */ 020 package org.apache.directory.shared.ldap.ldif; 021 022 import java.io.BufferedReader; 023 import java.io.IOException; 024 import java.io.StringReader; 025 import java.util.ArrayList; 026 027 import javax.naming.directory.Attribute; 028 import javax.naming.directory.Attributes; 029 import javax.naming.directory.BasicAttributes; 030 031 import org.apache.directory.shared.i18n.I18n; 032 import org.apache.directory.shared.ldap.entry.Entry; 033 import org.apache.directory.shared.ldap.entry.EntryAttribute; 034 import org.apache.directory.shared.ldap.entry.client.DefaultClientEntry; 035 import org.apache.directory.shared.ldap.util.StringTools; 036 import org.slf4j.Logger; 037 import org.slf4j.LoggerFactory; 038 039 /** 040 * <pre> 041 * <ldif-file> ::= "version:" <fill> <number> <seps> <dn-spec> <sep> 042 * <ldif-content-change> 043 * 044 * <ldif-content-change> ::= 045 * <number> <oid> <options-e> <value-spec> <sep> <attrval-specs-e> 046 * <ldif-attrval-record-e> | 047 * <alpha> <chars-e> <options-e> <value-spec> <sep> <attrval-specs-e> 048 * <ldif-attrval-record-e> | 049 * "control:" <fill> <number> <oid> <spaces-e> <criticality> 050 * <value-spec-e> <sep> <controls-e> 051 * "changetype:" <fill> <changerecord-type> <ldif-change-record-e> | 052 * "changetype:" <fill> <changerecord-type> <ldif-change-record-e> 053 * 054 * <ldif-attrval-record-e> ::= <seps> <dn-spec> <sep> <attributeType> 055 * <options-e> <value-spec> <sep> <attrval-specs-e> 056 * <ldif-attrval-record-e> | e 057 * 058 * <ldif-change-record-e> ::= <seps> <dn-spec> <sep> <controls-e> 059 * "changetype:" <fill> <changerecord-type> <ldif-change-record-e> | e 060 * 061 * <dn-spec> ::= "dn:" <fill> <safe-string> | "dn::" <fill> <base64-string> 062 * 063 * <controls-e> ::= "control:" <fill> <number> <oid> <spaces-e> <criticality> 064 * <value-spec-e> <sep> <controls-e> | e 065 * 066 * <criticality> ::= "true" | "false" | e 067 * 068 * <oid> ::= '.' <number> <oid> | e 069 * 070 * <attrval-specs-e> ::= <number> <oid> <options-e> <value-spec> <sep> 071 * <attrval-specs-e> | 072 * <alpha> <chars-e> <options-e> <value-spec> <sep> <attrval-specs-e> | e 073 * 074 * <value-spec-e> ::= <value-spec> | e 075 * 076 * <value-spec> ::= ':' <fill> <safe-string-e> | 077 * "::" <fill> <base64-chars> | 078 * ":<" <fill> <url> 079 * 080 * <attributeType> ::= <number> <oid> | <alpha> <chars-e> 081 * 082 * <options-e> ::= ';' <char> <chars-e> <options-e> |e 083 * 084 * <chars-e> ::= <char> <chars-e> | e 085 * 086 * <changerecord-type> ::= "add" <sep> <attributeType> <options-e> <value-spec> 087 * <sep> <attrval-specs-e> | 088 * "delete" <sep> | 089 * "modify" <sep> <mod-type> <fill> <attributeType> <options-e> <sep> 090 * <attrval-specs-e> <sep> '-' <sep> <mod-specs-e> | 091 * "moddn" <sep> <newrdn> <sep> "deleteoldrdn:" <fill> <0-1> <sep> 092 * <newsuperior-e> <sep> | 093 * "modrdn" <sep> <newrdn> <sep> "deleteoldrdn:" <fill> <0-1> <sep> 094 * <newsuperior-e> <sep> 095 * 096 * <newrdn> ::= ':' <fill> <safe-string> | "::" <fill> <base64-chars> 097 * 098 * <newsuperior-e> ::= "newsuperior" <newrdn> | e 099 * 100 * <mod-specs-e> ::= <mod-type> <fill> <attributeType> <options-e> 101 * <sep> <attrval-specs-e> <sep> '-' <sep> <mod-specs-e> | e 102 * 103 * <mod-type> ::= "add:" | "delete:" | "replace:" 104 * 105 * <url> ::= <a Uniform Resource Locator, as defined in [6]> 106 * 107 * 108 * 109 * LEXICAL 110 * ------- 111 * 112 * <fill> ::= ' ' <fill> | e 113 * <char> ::= <alpha> | <digit> | '-' 114 * <number> ::= <digit> <digits> 115 * <0-1> ::= '0' | '1' 116 * <digits> ::= <digit> <digits> | e 117 * <digit> ::= '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' 118 * <seps> ::= <sep> <seps-e> 119 * <seps-e> ::= <sep> <seps-e> | e 120 * <sep> ::= 0x0D 0x0A | 0x0A 121 * <spaces> ::= ' ' <spaces-e> 122 * <spaces-e> ::= ' ' <spaces-e> | e 123 * <safe-string-e> ::= <safe-string> | e 124 * <safe-string> ::= <safe-init-char> <safe-chars> 125 * <safe-init-char> ::= [0x01-0x09] | 0x0B | 0x0C | [0x0E-0x1F] | [0x21-0x39] | 0x3B | [0x3D-0x7F] 126 * <safe-chars> ::= <safe-char> <safe-chars> | e 127 * <safe-char> ::= [0x01-0x09] | 0x0B | 0x0C | [0x0E-0x7F] 128 * <base64-string> ::= <base64-char> <base64-chars> 129 * <base64-chars> ::= <base64-char> <base64-chars> | e 130 * <base64-char> ::= 0x2B | 0x2F | [0x30-0x39] | 0x3D | [0x41-9x5A] | [0x61-0x7A] 131 * <alpha> ::= [0x41-0x5A] | [0x61-0x7A] 132 * 133 * COMMENTS 134 * -------- 135 * - The ldap-oid VN is not correct in the RFC-2849. It has been changed from 1*DIGIT 0*1("." 1*DIGIT) to 136 * DIGIT+ ("." DIGIT+)* 137 * - The mod-spec lacks a sep between *attrval-spec and "-". 138 * - The BASE64-UTF8-STRING should be BASE64-CHAR BASE64-STRING 139 * - The ValueSpec rule must accept multilines values. In this case, we have a LF followed by a 140 * single space before the continued value. 141 * </pre> 142 * 143 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 144 * @version $Rev$, $Date$ 145 */ 146 public class LdifAttributesReader extends LdifReader 147 { 148 /** A logger */ 149 private static final Logger LOG = LoggerFactory.getLogger( LdifAttributesReader.class ); 150 151 /** 152 * Constructors 153 */ 154 public LdifAttributesReader() 155 { 156 lines = new ArrayList<String>(); 157 position = new Position(); 158 version = DEFAULT_VERSION; 159 } 160 161 162 /** 163 * Parse an AttributeType/AttributeValue 164 * 165 * @param attributes The entry where to store the value 166 * @param line The line to parse 167 * @param lowerLine The same line, lowercased 168 * @throws NamingException If anything goes wrong 169 */ 170 private void parseAttribute( Attributes attributes, String line, String lowerLine ) throws LdapLdifException 171 { 172 int colonIndex = line.indexOf( ':' ); 173 174 String attributeType = lowerLine.substring( 0, colonIndex ); 175 176 // We should *not* have a DN twice 177 if ( attributeType.equals( "dn" ) ) 178 { 179 LOG.error( I18n.err( I18n.ERR_12002 ) ); 180 throw new LdapLdifException( I18n.err( I18n.ERR_12003 ) ); 181 } 182 183 Object attributeValue = parseValue( line, colonIndex ); 184 185 // Update the entry 186 Attribute attribute = attributes.get( attributeType ); 187 188 if ( attribute == null ) 189 { 190 attributes.put( attributeType, attributeValue ); 191 } 192 else 193 { 194 attribute.add( attributeValue ); 195 } 196 } 197 198 199 200 201 /** 202 * Parse an AttributeType/AttributeValue 203 * 204 * @param attributes The entry where to store the value 205 * @param line The line to parse 206 * @param lowerLine The same line, lowercased 207 * @throws NamingException If anything goes wrong 208 */ 209 private void parseEntryAttribute( Entry entry, String line, String lowerLine ) throws LdapLdifException 210 { 211 int colonIndex = line.indexOf( ':' ); 212 213 String attributeType = lowerLine.substring( 0, colonIndex ); 214 215 // We should *not* have a DN twice 216 if ( attributeType.equals( "dn" ) ) 217 { 218 LOG.error( I18n.err( I18n.ERR_12002 ) ); 219 throw new LdapLdifException( I18n.err( I18n.ERR_12003 ) ); 220 } 221 222 Object attributeValue = parseValue( line, colonIndex ); 223 224 // Update the entry 225 EntryAttribute attribute = entry.get( attributeType ); 226 227 if ( attribute == null ) 228 { 229 if ( attributeValue instanceof String ) 230 { 231 entry.put( attributeType, (String)attributeValue ); 232 } 233 else 234 { 235 entry.put( attributeType, (byte[])attributeValue ); 236 } 237 } 238 else 239 { 240 if ( attributeValue instanceof String ) 241 { 242 attribute.add( (String)attributeValue ); 243 } 244 else 245 { 246 attribute.add( (byte[])attributeValue ); 247 } 248 } 249 } 250 251 252 /** 253 * Parse a ldif file. The following rules are processed : 254 * 255 * <ldif-file> ::= <ldif-attrval-record> <ldif-attrval-records> | 256 * <ldif-change-record> <ldif-change-records> <ldif-attrval-record> ::= 257 * <dn-spec> <sep> <attrval-spec> <attrval-specs> <ldif-change-record> ::= 258 * <dn-spec> <sep> <controls-e> <changerecord> <dn-spec> ::= "dn:" <fill> 259 * <distinguishedName> | "dn::" <fill> <base64-distinguishedName> 260 * <changerecord> ::= "changetype:" <fill> <change-op> 261 * 262 * @return The read entry 263 * @throws LdapLdifException If the entry can't be read or is invalid 264 */ 265 private Entry parseEntry() throws LdapLdifException 266 { 267 if ( ( lines == null ) || ( lines.size() == 0 ) ) 268 { 269 LOG.debug( "The entry is empty : end of ldif file" ); 270 return null; 271 } 272 273 Entry entry = new DefaultClientEntry(); 274 275 // Now, let's iterate through the other lines 276 for ( String line:lines ) 277 { 278 // Each line could start either with an OID, an attribute type, with 279 // "control:" or with "changetype:" 280 String lowerLine = line.toLowerCase(); 281 282 // We have three cases : 283 // 1) The first line after the DN is a "control:" -> this is an error 284 // 2) The first line after the DN is a "changeType:" -> this is an error 285 // 3) The first line after the DN is anything else 286 if ( lowerLine.startsWith( "control:" ) ) 287 { 288 LOG.error( I18n.err( I18n.ERR_12004 ) ); 289 throw new LdapLdifException( I18n.err( I18n.ERR_12005 ) ); 290 } 291 else if ( lowerLine.startsWith( "changetype:" ) ) 292 { 293 LOG.error( I18n.err( I18n.ERR_12004 ) ); 294 throw new LdapLdifException( I18n.err( I18n.ERR_12005 ) ); 295 } 296 else if ( line.indexOf( ':' ) > 0 ) 297 { 298 parseEntryAttribute( entry, line, lowerLine ); 299 } 300 else 301 { 302 // Invalid attribute Value 303 LOG.error( I18n.err( I18n.ERR_12006 ) ); 304 throw new LdapLdifException( I18n.err( I18n.ERR_12007 ) ); 305 } 306 } 307 308 LOG.debug( "Read an attributes : {}", entry ); 309 310 return entry; 311 } 312 313 314 /** 315 * Parse a ldif file. The following rules are processed : 316 * 317 * <ldif-file> ::= <ldif-attrval-record> <ldif-attrval-records> | 318 * <ldif-change-record> <ldif-change-records> <ldif-attrval-record> ::= 319 * <dn-spec> <sep> <attrval-spec> <attrval-specs> <ldif-change-record> ::= 320 * <dn-spec> <sep> <controls-e> <changerecord> <dn-spec> ::= "dn:" <fill> 321 * <distinguishedName> | "dn::" <fill> <base64-distinguishedName> 322 * <changerecord> ::= "changetype:" <fill> <change-op> 323 * 324 * @return The read entry 325 * @throws NamingException If the entry can't be read or is invalid 326 */ 327 private Attributes parseAttributes() throws LdapLdifException 328 { 329 if ( ( lines == null ) || ( lines.size() == 0 ) ) 330 { 331 LOG.debug( "The entry is empty : end of ldif file" ); 332 return null; 333 } 334 335 Attributes attributes = new BasicAttributes( true ); 336 337 // Now, let's iterate through the other lines 338 for ( String line:lines ) 339 { 340 // Each line could start either with an OID, an attribute type, with 341 // "control:" or with "changetype:" 342 String lowerLine = line.toLowerCase(); 343 344 // We have three cases : 345 // 1) The first line after the DN is a "control:" -> this is an error 346 // 2) The first line after the DN is a "changeType:" -> this is an error 347 // 3) The first line after the DN is anything else 348 if ( lowerLine.startsWith( "control:" ) ) 349 { 350 LOG.error( I18n.err( I18n.ERR_12004 ) ); 351 throw new LdapLdifException( I18n.err( I18n.ERR_12005 ) ); 352 } 353 else if ( lowerLine.startsWith( "changetype:" ) ) 354 { 355 LOG.error( I18n.err( I18n.ERR_12004 ) ); 356 throw new LdapLdifException( I18n.err( I18n.ERR_12005 ) ); 357 } 358 else if ( line.indexOf( ':' ) > 0 ) 359 { 360 parseAttribute( attributes, line, lowerLine ); 361 } 362 else 363 { 364 // Invalid attribute Value 365 LOG.error( I18n.err( I18n.ERR_12006 ) ); 366 throw new LdapLdifException( I18n.err( I18n.ERR_12007 ) ); 367 } 368 } 369 370 LOG.debug( "Read an attributes : {}", attributes ); 371 372 return attributes; 373 } 374 375 376 /** 377 * A method which parses a ldif string and returns a list of Attributes. 378 * 379 * @param ldif The ldif string 380 * @return A list of Attributes, or an empty List 381 * @throws LdapLdifException If something went wrong 382 */ 383 public Attributes parseAttributes( String ldif ) throws LdapLdifException 384 { 385 lines = new ArrayList<String>(); 386 position = new Position(); 387 388 LOG.debug( "Starts parsing ldif buffer" ); 389 390 if ( StringTools.isEmpty( ldif ) ) 391 { 392 return new BasicAttributes( true ); 393 } 394 395 StringReader strIn = new StringReader( ldif ); 396 reader = new BufferedReader( strIn ); 397 398 try 399 { 400 readLines(); 401 402 Attributes attributes = parseAttributes(); 403 404 if ( LOG.isDebugEnabled() ) 405 { 406 LOG.debug( "Parsed {} entries.", ( attributes == null ? 0 : 1 ) ); 407 } 408 409 return attributes; 410 } 411 catch (LdapLdifException ne) 412 { 413 LOG.error( I18n.err( I18n.ERR_12008, ne.getLocalizedMessage() ) ); 414 throw new LdapLdifException( I18n.err( I18n.ERR_12009 ) ); 415 } 416 finally 417 { 418 try 419 { 420 reader.close(); 421 } 422 catch ( IOException ioe ) 423 { 424 // Do nothing 425 } 426 } 427 } 428 429 430 /** 431 * A method which parses a ldif string and returns a list of Entry. 432 * 433 * @param ldif The ldif string 434 * @return A list of Entry, or an empty List 435 * @throws LdapLdifException If something went wrong 436 */ 437 public Entry parseEntry( String ldif ) throws LdapLdifException 438 { 439 lines = new ArrayList<String>(); 440 position = new Position(); 441 442 LOG.debug( "Starts parsing ldif buffer" ); 443 444 if ( StringTools.isEmpty( ldif ) ) 445 { 446 return new DefaultClientEntry(); 447 } 448 449 StringReader strIn = new StringReader( ldif ); 450 reader = new BufferedReader( strIn ); 451 452 try 453 { 454 readLines(); 455 456 Entry entry = parseEntry(); 457 458 if ( LOG.isDebugEnabled() ) 459 { 460 LOG.debug( "Parsed {} entries.", ( entry == null ? 0 : 1 ) ); 461 } 462 463 return entry; 464 } 465 catch (LdapLdifException ne) 466 { 467 LOG.error( I18n.err( I18n.ERR_12008, ne.getLocalizedMessage() ) ); 468 throw new LdapLdifException( I18n.err( I18n.ERR_12009 ) ); 469 } 470 finally 471 { 472 try 473 { 474 reader.close(); 475 } 476 catch ( IOException ioe ) 477 { 478 // Do nothing 479 } 480 } 481 } 482 }