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 }