View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   *
19   */
20  package org.apache.directory.api.ldap.model.ldif;
21  
22  
23  import java.io.BufferedReader;
24  import java.io.Closeable;
25  import java.io.DataInputStream;
26  import java.io.File;
27  import java.io.FileNotFoundException;
28  import java.io.IOException;
29  import java.io.InputStream;
30  import java.io.InputStreamReader;
31  import java.io.Reader;
32  import java.io.StringReader;
33  import java.net.MalformedURLException;
34  import java.net.URL;
35  import java.nio.charset.Charset;
36  import java.nio.file.Files;
37  import java.nio.file.Paths;
38  import java.util.ArrayList;
39  import java.util.Iterator;
40  import java.util.List;
41  import java.util.NoSuchElementException;
42  
43  import org.apache.directory.api.asn1.util.Oid;
44  import org.apache.directory.api.i18n.I18n;
45  import org.apache.directory.api.ldap.model.constants.SchemaConstants;
46  import org.apache.directory.api.ldap.model.entry.Attribute;
47  import org.apache.directory.api.ldap.model.entry.DefaultAttribute;
48  import org.apache.directory.api.ldap.model.entry.ModificationOperation;
49  import org.apache.directory.api.ldap.model.exception.LdapException;
50  import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException;
51  import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException;
52  import org.apache.directory.api.ldap.model.message.Control;
53  import org.apache.directory.api.ldap.model.name.Ava;
54  import org.apache.directory.api.ldap.model.name.Dn;
55  import org.apache.directory.api.ldap.model.name.Rdn;
56  import org.apache.directory.api.ldap.model.schema.AttributeType;
57  import org.apache.directory.api.ldap.model.schema.MutableAttributeType;
58  import org.apache.directory.api.ldap.model.schema.SchemaManager;
59  import org.apache.directory.api.util.Base64;
60  import org.apache.directory.api.util.Chars;
61  import org.apache.directory.api.util.Strings;
62  import org.apache.directory.api.util.exception.NotImplementedException;
63  import org.slf4j.Logger;
64  import org.slf4j.LoggerFactory;
65  
66  
67  /**
68   * <pre>
69   *  &lt;ldif-file&gt; ::= &quot;version:&quot; &lt;fill&gt; &lt;number&gt; &lt;seps&gt; &lt;dn-spec&gt; &lt;sep&gt;
70   *  &lt;ldif-content-change&gt;
71   *
72   *  &lt;ldif-content-change&gt; ::=
73   *    &lt;number&gt; &lt;oid&gt; &lt;options-e&gt; &lt;value-spec&gt; &lt;sep&gt;
74   *    &lt;attrval-specs-e&gt; &lt;ldif-attrval-record-e&gt; |
75   *    &lt;alpha&gt; &lt;chars-e&gt; &lt;options-e&gt; &lt;value-spec&gt; &lt;sep&gt;
76   *    &lt;attrval-specs-e&gt; &lt;ldif-attrval-record-e&gt; |
77   *    &quot;control:&quot; &lt;fill&gt; &lt;number&gt; &lt;oid&gt; &lt;spaces-e&gt;
78   *    &lt;criticality&gt; &lt;value-spec-e&gt; &lt;sep&gt; &lt;controls-e&gt;
79   *        &quot;changetype:&quot; &lt;fill&gt; &lt;changerecord-type&gt; &lt;ldif-change-record-e&gt; |
80   *    &quot;changetype:&quot; &lt;fill&gt; &lt;changerecord-type&gt; &lt;ldif-change-record-e&gt;
81   *
82   *  &lt;ldif-attrval-record-e&gt; ::= &lt;seps&gt; &lt;dn-spec&gt; &lt;sep&gt; &lt;attributeType&gt;
83   *    &lt;options-e&gt; &lt;value-spec&gt; &lt;sep&gt; &lt;attrval-specs-e&gt;
84   *    &lt;ldif-attrval-record-e&gt; | e
85   *
86   *  &lt;ldif-change-record-e&gt; ::= &lt;seps&gt; &lt;dn-spec&gt; &lt;sep&gt; &lt;controls-e&gt;
87   *    &quot;changetype:&quot; &lt;fill&gt; &lt;changerecord-type&gt; &lt;ldif-change-record-e&gt; | e
88   *
89   *  &lt;dn-spec&gt; ::= &quot;dn:&quot; &lt;fill&gt; &lt;safe-string&gt; | &quot;dn::&quot; &lt;fill&gt; &lt;base64-string&gt;
90   *
91   *  &lt;controls-e&gt; ::= &quot;control:&quot; &lt;fill&gt; &lt;number&gt; &lt;oid&gt; &lt;spaces-e&gt; &lt;criticality&gt;
92   *    &lt;value-spec-e&gt; &lt;sep&gt; &lt;controls-e&gt; | e
93   *
94   *  &lt;criticality&gt; ::= &quot;true&quot; | &quot;false&quot; | e
95   *
96   *  &lt;oid&gt; ::= '.' &lt;number&gt; &lt;oid&gt; | e
97   *
98   *  &lt;attrval-specs-e&gt; ::= &lt;number&gt; &lt;oid&gt; &lt;options-e&gt; &lt;value-spec&gt;
99   *  &lt;sep&gt; &lt;attrval-specs-e&gt; |
100  *    &lt;alpha&gt; &lt;chars-e&gt; &lt;options-e&gt; &lt;value-spec&gt; &lt;sep&gt; &lt;attrval-specs-e&gt; | e
101  *
102  *  &lt;value-spec-e&gt; ::= &lt;value-spec&gt; | e
103  *
104  *  &lt;value-spec&gt; ::= ':' &lt;fill&gt; &lt;safe-string-e&gt; |
105  *    &quot;::&quot; &lt;fill&gt; &lt;base64-chars&gt; |
106  *    &quot;:&lt;&quot; &lt;fill&gt; &lt;url&gt;
107  *
108  *  &lt;attributeType&gt; ::= &lt;number&gt; &lt;oid&gt; | &lt;alpha&gt; &lt;chars-e&gt;
109  *
110  *  &lt;options-e&gt; ::= ';' &lt;char&gt; &lt;chars-e&gt; &lt;options-e&gt; |e
111  *
112  *  &lt;chars-e&gt; ::= &lt;char&gt; &lt;chars-e&gt; |  e
113  *
114  *  &lt;changerecord-type&gt; ::= &quot;add&quot; &lt;sep&gt; &lt;attributeType&gt;
115  *  &lt;options-e&gt; &lt;value-spec&gt; &lt;sep&gt; &lt;attrval-specs-e&gt; |
116  *    &quot;delete&quot; &lt;sep&gt; |
117  *    &quot;modify&quot; &lt;sep&gt; &lt;mod-type&gt; &lt;fill&gt; &lt;attributeType&gt;
118  *    &lt;options-e&gt; &lt;sep&gt; &lt;attrval-specs-e&gt; &lt;sep&gt; '-' &lt;sep&gt; &lt;mod-specs-e&gt; |
119  *    &quot;moddn&quot; &lt;sep&gt; &lt;newrdn&gt; &lt;sep&gt; &quot;deleteoldrdn:&quot;
120  *    &lt;fill&gt; &lt;0-1&gt; &lt;sep&gt; &lt;newsuperior-e&gt; &lt;sep&gt; |
121  *    &quot;modrdn&quot; &lt;sep&gt; &lt;newrdn&gt; &lt;sep&gt; &quot;deleteoldrdn:&quot;
122  *    &lt;fill&gt; &lt;0-1&gt; &lt;sep&gt; &lt;newsuperior-e&gt; &lt;sep&gt;
123  *
124  *  &lt;newrdn&gt; ::= ':' &lt;fill&gt; &lt;safe-string&gt; | &quot;::&quot; &lt;fill&gt; &lt;base64-chars&gt;
125  *
126  *  &lt;newsuperior-e&gt; ::= &quot;newsuperior&quot; &lt;newrdn&gt; | e
127  *
128  *  &lt;mod-specs-e&gt; ::= &lt;mod-type&gt; &lt;fill&gt; &lt;attributeType&gt; &lt;options-e&gt;
129  *    &lt;sep&gt; &lt;attrval-specs-e&gt; &lt;sep&gt; '-' &lt;sep&gt; &lt;mod-specs-e&gt; | e
130  *
131  *  &lt;mod-type&gt; ::= &quot;add:&quot; | &quot;delete:&quot; | &quot;replace:&quot;
132  *
133  *  &lt;url&gt; ::= &lt;a Uniform Resource Locator, as defined in [6]&gt;
134  *
135  *
136  *
137  *  LEXICAL
138  *  -------
139  *
140  *  &lt;fill&gt;           ::= ' ' &lt;fill&gt; | e
141  *  &lt;char&gt;           ::= &lt;alpha&gt; | &lt;digit&gt; | '-'
142  *  &lt;number&gt;         ::= &lt;digit&gt; &lt;digits&gt;
143  *  &lt;0-1&gt;            ::= '0' | '1'
144  *  &lt;digits&gt;         ::= &lt;digit&gt; &lt;digits&gt; | e
145  *  &lt;digit&gt;          ::= '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
146  *  &lt;seps&gt;           ::= &lt;sep&gt; &lt;seps-e&gt;
147  *  &lt;seps-e&gt;         ::= &lt;sep&gt; &lt;seps-e&gt; | e
148  *  &lt;sep&gt;            ::= 0x0D 0x0A | 0x0A
149  *  &lt;spaces&gt;         ::= ' ' &lt;spaces-e&gt;
150  *  &lt;spaces-e&gt;       ::= ' ' &lt;spaces-e&gt; | e
151  *  &lt;safe-string-e&gt;  ::= &lt;safe-string&gt; | e
152  *  &lt;safe-string&gt;    ::= &lt;safe-init-char&gt; &lt;safe-chars&gt;
153  *  &lt;safe-init-char&gt; ::= [0x01-0x09] | 0x0B | 0x0C | [0x0E-0x1F] | [0x21-0x39] | 0x3B | [0x3D-0x7F]
154  *  &lt;safe-chars&gt;     ::= &lt;safe-char&gt; &lt;safe-chars&gt; | e
155  *  &lt;safe-char&gt;      ::= [0x01-0x09] | 0x0B | 0x0C | [0x0E-0x7F]
156  *  &lt;base64-string&gt;  ::= &lt;base64-char&gt; &lt;base64-chars&gt;
157  *  &lt;base64-chars&gt;   ::= &lt;base64-char&gt; &lt;base64-chars&gt; | e
158  *  &lt;base64-char&gt;    ::= 0x2B | 0x2F | [0x30-0x39] | 0x3D | [0x41-9x5A] | [0x61-0x7A]
159  *  &lt;alpha&gt;          ::= [0x41-0x5A] | [0x61-0x7A]
160  *
161  *  COMMENTS
162  *  --------
163  *  - The ldap-oid VN is not correct in the RFC-2849. It has been changed from 1*DIGIT 0*1(&quot;.&quot; 1*DIGIT) to
164  *  DIGIT+ (&quot;.&quot; DIGIT+)*
165  *  - The mod-spec lacks a sep between *attrval-spec and &quot;-&quot;.
166  *  - The BASE64-UTF8-STRING should be BASE64-CHAR BASE64-STRING
167  *  - The ValueSpec rule must accept multilines values. In this case, we have a LF followed by a
168  *  single space before the continued value.
169  * </pre>
170  * The relaxed mode is used when a SchemaManager is injected.
171  * 
172  * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
173  */
174 public class LdifReader implements Iterable<LdifEntry>, Closeable
175 {
176     /** A logger */
177     private static final Logger LOG = LoggerFactory.getLogger( LdifReader.class );
178 
179     /** A list of read lines */
180     protected List<String> lines;
181 
182     /** The current position */
183     protected int position;
184 
185     /** The ldif file version default value */
186     protected static final int DEFAULT_VERSION = 1;
187 
188     /** The ldif version */
189     protected int version;
190 
191     /** Type of element read : ENTRY */
192     protected static final int LDIF_ENTRY = 0;
193 
194     /** Type of element read : CHANGE */
195     protected static final int CHANGE = 1;
196 
197     /** Type of element read : UNKNOWN */
198     protected static final int UNKNOWN = 2;
199 
200     /** Size limit for file contained values */
201     protected long sizeLimit = SIZE_LIMIT_DEFAULT;
202 
203     /** The default size limit : 1Mo */
204     protected static final long SIZE_LIMIT_DEFAULT = 1024000;
205 
206     /** State values for the modify operation : MOD_SPEC */
207     protected static final int MOD_SPEC = 0;
208 
209     /** State values for the modify operation : ATTRVAL_SPEC */
210     protected static final int ATTRVAL_SPEC = 1;
211 
212     /** State values for the modify operation : ATTRVAL_SPEC_OR_SEP */
213     protected static final int ATTRVAL_SPEC_OR_SEP = 2;
214 
215     /** Iterator prefetched entry */
216     protected LdifEntry prefetched;
217 
218     /** The ldif Reader */
219     protected Reader reader;
220 
221     /** A flag set if the ldif contains entries */
222     protected boolean containsEntries;
223 
224     /** A flag set if the ldif contains changes */
225     protected boolean containsChanges;
226 
227     /** The SchemaManager instance, if any */
228     protected SchemaManager schemaManager;
229 
230     /**
231      * An Exception to handle error message, has Iterator.next() can't throw
232      * exceptions
233      */
234     protected Exception error;
235 
236     /** total length of an LDIF entry including the comments */
237     protected int entryLen = 0;
238 
239     /** the parsed entry's starting position */
240     protected long entryOffset = 0;
241 
242     /** the current offset of the reader */
243     protected long offset = 0;
244 
245     /** the numer of the current line being parsed by the reader */
246     protected int lineNumber;
247 
248     /** flag to turn on/off of the DN validation. By default DNs are validated after parsing */
249     protected boolean validateDn = true;
250     
251     /** A counter used to create facked OIDs */
252     private int oidCounter = 0;
253 
254 
255     /**
256      * Constructors
257      */
258     public LdifReader()
259     {
260         lines = new ArrayList<>();
261         position = 0;
262         version = DEFAULT_VERSION;
263     }
264 
265 
266     /**
267      * Creates a Schema aware reader
268      * 
269      * @param schemaManager The SchemaManager
270      */
271     public LdifReader( SchemaManager schemaManager )
272     {
273         lines = new ArrayList<>();
274         position = 0;
275         version = DEFAULT_VERSION;
276         this.schemaManager = schemaManager;
277     }
278 
279 
280     /**
281      * A constructor which takes a file name. Default charset is used.
282      *
283      * @param ldifFileName A file name containing ldif formated input
284      * @throws LdapLdifException If the file cannot be processed or if the format is incorrect
285      */
286     public LdifReader( String ldifFileName ) throws LdapLdifException
287     {
288         this( new File( ldifFileName ) );
289     }
290 
291 
292     /**
293      * A constructor which takes a Reader.
294      *
295      * @param in A Reader containing ldif formated input
296      * @throws LdapException If the file cannot be processed or if the format is incorrect
297      */
298     public LdifReader( Reader in ) throws LdapException
299     {
300         initReader( new BufferedReader( in ) );
301     }
302 
303 
304     /**
305      * A constructor which takes an InputStream. Default charset is used.
306      *
307      * @param in An InputStream containing ldif formated input
308      * @throws LdapException If the file cannot be processed or if the format is incorrect
309      */
310     public LdifReader( InputStream in ) throws LdapException
311     {
312         initReader( new BufferedReader( new InputStreamReader( in, Charset.defaultCharset() ) ) );
313     }
314 
315 
316     /**
317      * A constructor which takes a File. Default charset is used.
318      *
319      * @param file A File containing ldif formated input
320      * @throws LdapLdifException If the file cannot be processed or if the format is incorrect
321      */
322     public LdifReader( File file ) throws LdapLdifException
323     {
324         this( file, null );
325     }
326 
327 
328     /**
329      * A constructor which takes a File and a SchemaManager. Default charset is used.
330      *
331      * @param file A File containing ldif formated input
332      * @param schemaManager The SchemaManager instance to use
333      * @throws LdapLdifException If the file cannot be processed or if the format is incorrect
334      */
335     public LdifReader( File file, SchemaManager schemaManager ) throws LdapLdifException
336     {
337         if ( !file.exists() )
338         {
339             String msg = I18n.err( I18n.ERR_12010_CANNOT_FIND_FILE, file.getAbsoluteFile() );
340             LOG.error( msg );
341             throw new LdapLdifException( msg );
342         }
343 
344         if ( !file.canRead() )
345         {
346             String msg = I18n.err( I18n.ERR_12011_CANNOT_READ_FILE, file.getName() );
347             LOG.error( msg );
348             throw new LdapLdifException( msg );
349         }
350 
351         this.schemaManager = schemaManager;
352 
353         try
354         {
355             InputStream is = Files.newInputStream( Paths.get( file.getPath() ) );
356             initReader(
357                 new BufferedReader( new InputStreamReader( is, Charset.defaultCharset() ) ) );
358         }
359         catch ( FileNotFoundException fnfe )
360         {
361             String msg = I18n.err( I18n.ERR_12010_CANNOT_FIND_FILE, file.getAbsoluteFile() );
362             LOG.error( msg );
363             throw new LdapLdifException( msg, fnfe );
364         }
365         catch ( LdapInvalidDnException lide )
366         {
367             throw new LdapLdifException( lide.getMessage(), lide );
368         }
369         catch ( IOException ioe )
370         {
371             throw new LdapLdifException( ioe.getMessage(), ioe );
372         }
373         catch ( LdapException le )
374         {
375             throw new LdapLdifException( le.getMessage(), le );
376         }
377     }
378 
379 
380     /**
381      * Store the reader and intialize the LdifReader
382      */
383     private void initReader( BufferedReader reader ) throws LdapException
384     {
385         this.reader = reader;
386         init();
387     }
388 
389 
390     /**
391      * Initialize the LdifReader
392      * 
393      * @throws LdapException If the initialization failed
394      */
395     public void init() throws LdapException
396     {
397         lines = new ArrayList<>();
398         position = 0;
399         version = DEFAULT_VERSION;
400         containsChanges = false;
401         containsEntries = false;
402 
403         // First get the version - if any -
404         version = parseVersion();
405         prefetched = parseEntry();
406     }
407 
408 
409     /**
410      * @return The ldif file version
411      */
412     public int getVersion()
413     {
414         return version;
415     }
416 
417 
418     /**
419      * @return The maximum size of a file which is used into an attribute value.
420      */
421     public long getSizeLimit()
422     {
423         return sizeLimit;
424     }
425 
426 
427     /**
428      * Set the maximum file size that can be accepted for an attribute value
429      *
430      * @param sizeLimit The size in bytes
431      */
432     public void setSizeLimit( long sizeLimit )
433     {
434         this.sizeLimit = sizeLimit;
435     }
436 
437 
438     // <fill> ::= ' ' <fill> | e
439     private void parseFill( char[] document )
440     {
441         while ( Chars.isCharASCII( document, position, ' ' ) )
442         {
443             position++;
444         }
445     }
446 
447 
448     /**
449      * Parse a number following the rules :
450      *
451      * &lt;number&gt; ::= &lt;digit&gt; &lt;digits&gt; &lt;digits&gt; ::= &lt;digit&gt; &lt;digits&gt; | e &lt;digit&gt;
452      * ::= '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
453      *
454      * Check that the number is in the interval
455      *
456      * @param document The document containing the number to parse
457      * @return a String representing the parsed number
458      */
459     private String parseNumber( char[] document )
460     {
461         int initPos = position;
462 
463         while ( true )
464         {
465             if ( Chars.isDigit( document, position ) )
466             {
467                 position++;
468             }
469             else
470             {
471                 break;
472             }
473         }
474 
475         if ( position == initPos )
476         {
477             return null;
478         }
479         else
480         {
481             return new String( document, initPos, position - initPos );
482         }
483     }
484 
485 
486     /**
487      * Parse the changeType
488      *
489      * @param line The line which contains the changeType
490      * @return The operation.
491      */
492     protected ChangeType parseChangeType( String line )
493     {
494         ChangeType operation = ChangeType.Add;
495 
496         String modOp = Strings.trim( line.substring( "changetype:".length() ) );
497 
498         if ( "add".equalsIgnoreCase( modOp ) )
499         {
500             operation = ChangeType.Add;
501         }
502         else if ( "delete".equalsIgnoreCase( modOp ) )
503         {
504             operation = ChangeType.Delete;
505         }
506         else if ( "modify".equalsIgnoreCase( modOp ) )
507         {
508             operation = ChangeType.Modify;
509         }
510         else if ( "moddn".equalsIgnoreCase( modOp ) )
511         {
512             operation = ChangeType.ModDn;
513         }
514         else if ( "modrdn".equalsIgnoreCase( modOp ) )
515         {
516             operation = ChangeType.ModRdn;
517         }
518 
519         return operation;
520     }
521 
522 
523     /**
524      * Parse the Dn of an entry
525      *
526      * @param line The line to parse
527      * @return A Dn
528      * @throws LdapLdifException If the Dn is invalid
529      */
530     protected String parseDn( String line ) throws LdapLdifException
531     {
532         String dn;
533 
534         String lowerLine = Strings.toLowerCaseAscii( line );
535 
536         if ( lowerLine.startsWith( "dn:" ) || lowerLine.startsWith( "Dn:" ) )
537         {
538             // Ok, we have a Dn. Is it base 64 encoded ?
539             int length = line.length();
540 
541             if ( length == 3 )
542             {
543                 // The Dn is empty : it's a rootDSE
544                 dn = "";
545             }
546             else if ( line.charAt( 3 ) == ':' )
547             {
548                 if ( length > 4 )
549                 {
550                     // This is a base 64 encoded Dn.
551                     String trimmedLine = line.substring( 4 ).trim();
552 
553                     dn = Strings.utf8ToString( Base64.decode( trimmedLine.toCharArray() ) );
554                 }
555                 else
556                 {
557                     // The Dn is empty : error
558                     LOG.error( I18n.err( I18n.ERR_12012_EMPTY_DN_NOT_ALLOWED, lineNumber ) );
559                     throw new LdapLdifException( I18n.err( I18n.ERR_12013_NO_DN ) );
560                 }
561             }
562             else
563             {
564                 dn = line.substring( 3 ).trim();
565             }
566         }
567         else
568         {
569             LOG.error( I18n.err( I18n.ERR_12016_DN_EXPECTED, lineNumber ) );
570             throw new LdapLdifException( I18n.err( I18n.ERR_12013_NO_DN ) );
571         }
572 
573         // Check that the Dn is valid. If not, an exception will be thrown
574         if ( validateDn && !Dn.isValid( dn ) )
575         {
576             String message = I18n.err( I18n.ERR_12017_INVALID_DN, dn, lineNumber );
577             LOG.error( message );
578             throw new LdapLdifException( message );
579         }
580 
581         return dn;
582     }
583 
584 
585     /**
586      * Parse the value part.
587      *
588      * @param line The line which contains the value
589      * @param pos The starting position in the line
590      * @return A String or a byte[], depending of the kind of value we get
591      */
592     protected static Object parseSimpleValue( String line, int pos )
593     {
594         if ( line.length() > pos + 1 )
595         {
596             char c = line.charAt( pos + 1 );
597 
598             if ( c == ':' )
599             {
600                 String value = Strings.trim( line.substring( pos + 2 ) );
601 
602                 return Base64.decode( value.toCharArray() );
603             }
604             else
605             {
606                 return Strings.trim( line.substring( pos + 1 ) );
607             }
608         }
609         else
610         {
611             return null;
612         }
613     }
614 
615     
616     private Object getValue( String attributeName, byte[] value )
617     {
618         if ( schemaManager != null )
619         {
620             AttributeType attributeType = schemaManager.getAttributeType( attributeName );
621             
622             if ( attributeType != null )
623             {
624                 if ( attributeType.getSyntax().isHumanReadable() )
625                 {
626                     return Strings.utf8ToString( value );
627                 }
628                 else
629                 {
630                     return value;
631                 }
632             }
633             else
634             {
635                 return value;
636             }
637         }
638         else
639         {
640             return value;
641         }
642     }
643     
644 
645     /**
646      * Parse the value part.
647      *
648      * @param attributeName The attribute name
649      * @param line The line which contains the value
650      * @param pos The starting position in the line
651      * @return A String or a byte[], depending of the kind of value we get
652      * @throws LdapLdifException If something went wrong
653      */
654     protected Object parseValue( String attributeName, String line, int pos ) throws LdapLdifException
655     {
656         if ( line.length() > pos + 1 )
657         {
658             char c = line.charAt( pos + 1 );
659 
660             if ( c == ':' )
661             {
662                 String value = Strings.trim( line.substring( pos + 2 ) );
663 
664                 byte[] decoded = Base64.decode( value.toCharArray() );
665                 
666                 return getValue( attributeName, decoded );
667             }
668             else if ( c == '<' )
669             {
670                 String urlName = Strings.trim( line.substring( pos + 2 ) );
671 
672                 try
673                 {
674                     URL url = new URL( urlName );
675 
676                     if ( "file".equals( url.getProtocol() ) )
677                     {
678                         String fileName = url.getFile();
679 
680                         File file = new File( fileName );
681 
682                         if ( !file.exists() )
683                         {
684                             LOG.error( I18n.err( I18n.ERR_12018_FILE_NOT_FOUND, fileName, lineNumber ) );
685                             throw new LdapLdifException( I18n.err( I18n.ERR_12019_BAD_URL_FILE_NOT_FOUND ) );
686                         }
687                         else
688                         {
689                             long length = file.length();
690 
691                             if ( length > sizeLimit )
692                             {
693                                 String message = I18n.err( I18n.ERR_12020_FILE_TOO_BIG, fileName, lineNumber );
694                                 LOG.error( message );
695                                 throw new LdapLdifException( message );
696                             }
697                             else
698                             {
699                                 byte[] data = new byte[( int ) length];
700                                 
701                                 try ( DataInputStream inf = new DataInputStream( 
702                                     Files.newInputStream( Paths.get( fileName ) ) ) )
703                                 {
704                                     inf.readFully( data );
705 
706                                     return getValue( attributeName, data );
707                                 }
708                                 catch ( FileNotFoundException fnfe )
709                                 {
710                                     // We can't reach this point, the file
711                                     // existence has already been
712                                     // checked
713                                     LOG.error( I18n.err( I18n.ERR_12018_FILE_NOT_FOUND, fileName, lineNumber ) );
714                                     throw new LdapLdifException( I18n.err( I18n.ERR_12019_BAD_URL_FILE_NOT_FOUND ),
715                                         fnfe );
716                                 }
717                                 catch ( IOException ioe )
718                                 {
719                                     LOG.error( I18n.err( I18n.ERR_12022_ERROR_READING_FILE, fileName, lineNumber ) );
720                                     throw new LdapLdifException( I18n.err( I18n.ERR_12023_ERROR_READING_BAD_URL ), ioe );
721                                 }
722                             }
723                         }
724                     }
725                     else
726                     {
727                         LOG.error( I18n.err( I18n.ERR_12025_BAD_PROTOCOL ) );
728                         throw new LdapLdifException( I18n.err( I18n.ERR_12026_UNSUPPORTED_PROTOCOL, lineNumber ) );
729                     }
730                 }
731                 catch ( MalformedURLException mue )
732                 {
733                     String message = I18n.err( I18n.ERR_12027_BAD_URL, urlName, lineNumber );
734                     LOG.error( message );
735                     throw new LdapLdifException( message, mue );
736                 }
737             }
738             else
739             {
740                 String value = Strings.trimLeft( line.substring( pos + 1 ) );
741                 int end = value.length();
742 
743                 for ( int i = value.length() - 1; i > 0; i-- )
744                 {
745                     char cc = value.charAt( i );
746 
747                     if ( cc == ' ' )
748                     {
749                         if ( value.charAt( i - 1 ) == '\\' )
750                         {
751                             // Escaped space : do nothing
752                             break;
753                         }
754                         else
755                         {
756                             end = i;
757                         }
758                     }
759                     else
760                     {
761                         break;
762                     }
763                 }
764 
765                 String result = null;
766 
767                 result = value.substring( 0, end );
768 
769                 return result;
770             }
771         }
772         else
773         {
774             return null;
775         }
776     }
777 
778 
779     /**
780      * Parse a control. The grammar is :
781      * <pre>
782      * &lt;control&gt; ::= "control:" &lt;fill&gt; &lt;ldap-oid&gt; &lt;critical-e&gt; &lt;value-spec-e&gt; &lt;sep&gt;
783      * &lt;critical-e&gt; ::= &lt;spaces&gt; &lt;boolean&gt; | e
784      * &lt;boolean&gt; ::= "true" | "false"
785      * &lt;value-spec-e&gt; ::= &lt;value-spec&gt; | e
786      * &lt;value-spec&gt; ::= ":" &lt;fill&gt; &lt;SAFE-STRING-e&gt; | "::" &lt;fill&gt; &lt;BASE64-STRING&gt; | ":<" &lt;fill&gt; &lt;url&gt;
787      * </pre>
788      *
789      * It can be read as :
790      * <pre>
791      * "control:" &lt;fill&gt; &lt;ldap-oid&gt; [ " "+ ( "true" |
792      * "false") ] [ ":" &lt;fill&gt; &lt;SAFE-STRING-e&gt; | "::" &lt;fill&gt; &lt;BASE64-STRING&gt; | ":<"
793      * &lt;fill&gt; &lt;url&gt; ]
794      * </pre>
795      *
796      * @param line The line containing the control
797      * @return A control
798      * @exception LdapLdifException If the control has no OID or if the OID is incorrect,
799      * of if the criticality is not set when it's mandatory.
800      */
801     private Control parseControl( String line ) throws LdapLdifException
802     {
803         String lowerLine = Strings.toLowerCaseAscii( line ).trim();
804         char[] controlValue = line.trim().toCharArray();
805         int pos = 0;
806         int length = controlValue.length;
807 
808         // Get the <ldap-oid>
809         if ( pos > length )
810         {
811             // No OID : error !
812             LOG.error( I18n.err( I18n.ERR_12029_CONTROL_WITHOUT_OID, lineNumber ) );
813             throw new LdapLdifException( I18n.err( I18n.ERR_12029_CONTROL_WITHOUT_OID ) );
814         }
815 
816         int initPos = pos;
817 
818         while ( Chars.isCharASCII( controlValue, pos, '.' ) || Chars.isDigit( controlValue, pos ) )
819         {
820             pos++;
821         }
822 
823         if ( pos == initPos )
824         {
825             // Not a valid OID !
826             LOG.error( I18n.err( I18n.ERR_12029_CONTROL_WITHOUT_OID, lineNumber ) );
827             throw new LdapLdifException( I18n.err( I18n.ERR_12029_CONTROL_WITHOUT_OID ) );
828         }
829 
830         // Create and check the OID
831         String oidString = lowerLine.substring( 0, pos );
832 
833         if ( !Oid.isOid( oidString ) )
834         {
835             String message = I18n.err( I18n.ERR_12031_INVALID_OID, oidString, lineNumber );
836             LOG.error( message );
837             throw new LdapLdifException( message );
838         }
839 
840         LdifControl control = new LdifControl( oidString );
841 
842         // Get the criticality, if any
843         // Skip the <fill>
844         while ( Chars.isCharASCII( controlValue, pos, ' ' ) )
845         {
846             pos++;
847         }
848 
849         // Check if we have a "true" or a "false"
850         int criticalPos = lowerLine.indexOf( ':' );
851 
852         int criticalLength;
853 
854         if ( criticalPos == -1 )
855         {
856             criticalLength = length - pos;
857         }
858         else
859         {
860             criticalLength = criticalPos - pos;
861         }
862 
863         if ( ( criticalLength == 4 ) && ( "true".equalsIgnoreCase( lowerLine.substring( pos, pos + 4 ) ) ) )
864         {
865             control.setCritical( true );
866         }
867         else if ( ( criticalLength == 5 ) && ( "false".equalsIgnoreCase( lowerLine.substring( pos, pos + 5 ) ) ) )
868         {
869             control.setCritical( false );
870         }
871         else if ( criticalLength != 0 )
872         {
873             // If we have a criticality, it should be either "true" or "false",
874             // nothing else
875             LOG.error( I18n.err( I18n.ERR_12033_INVALID_CRITICALITY, lineNumber ) );
876             throw new LdapLdifException( I18n.err( I18n.ERR_12033_INVALID_CRITICALITY ) );
877         }
878 
879         if ( criticalPos > 0 )
880         {
881             // We have a value. It can be a normal value, a base64 encoded value
882             // or a file contained value
883             if ( Chars.isCharASCII( controlValue, criticalPos + 1, ':' ) )
884             {
885                 // Base 64 encoded value
886 
887                 // Skip the <fill>
888                 pos = criticalPos + 2;
889 
890                 while ( Chars.isCharASCII( controlValue, pos, ' ' ) )
891                 {
892                     pos++;
893                 }
894 
895                 byte[] value = Base64.decode( line.substring( pos ).toCharArray() );
896                 control.setValue( value );
897             }
898             else if ( Chars.isCharASCII( controlValue, criticalPos + 1, '<' ) )
899             {
900                 // File contained value
901                 throw new NotImplementedException( "See DIRSERVER-1547" );
902             }
903             else
904             {
905                 // Skip the <fill>
906                 pos = criticalPos + 1;
907 
908                 while ( Chars.isCharASCII( controlValue, pos, ' ' ) )
909                 {
910                     pos++;
911                 }
912 
913                 // Standard value
914                 byte[] value = new byte[length - pos];
915 
916                 for ( int i = 0; i < length - pos; i++ )
917                 {
918                     value[i] = ( byte ) controlValue[i + pos];
919                 }
920 
921                 control.setValue( value );
922             }
923         }
924 
925         return control;
926     }
927 
928 
929     /**
930      * Parse an AttributeType/AttributeValue
931      *
932      * @param line The line to parse
933      * @return the parsed Attribute
934      */
935     public static Attribute parseAttributeValue( String line )
936     {
937         int colonIndex = line.indexOf( ':' );
938 
939         if ( colonIndex != -1 )
940         {
941             String attributeType = line.substring( 0, colonIndex );
942             Object attributeValue = parseSimpleValue( line, colonIndex );
943 
944             // Create an attribute
945             if ( attributeValue instanceof String )
946             {
947                 return new DefaultAttribute( attributeType, ( String ) attributeValue );
948             }
949             else
950             {
951                 return new DefaultAttribute( attributeType, ( byte[] ) attributeValue );
952             }
953         }
954         else
955         {
956             return null;
957         }
958     }
959 
960 
961     /**
962      * Parse an AttributeType/AttributeValue
963      *
964      * @param entry The entry where to store the value
965      * @param line The line to parse
966      * @param lowerLine The same line, lowercased
967      * @throws LdapException If anything goes wrong
968      */
969     public void parseAttributeValue( LdifEntry entry, String line, String lowerLine ) throws LdapException
970     {
971         int colonIndex = line.indexOf( ':' );
972 
973         String attributeType = lowerLine.substring( 0, colonIndex );
974 
975         // We should *not* have a Dn twice
976         if ( "dn".equals( attributeType ) )
977         {
978             LOG.error( I18n.err( I18n.ERR_12002_ENTRY_WITH_TWO_DNS, lineNumber ) );
979             throw new LdapLdifException( I18n.err( I18n.ERR_12003_LDIF_ENTRY_WITH_TWO_DNS ) );
980         }
981 
982         Object attributeValue = parseValue( attributeType, line, colonIndex );
983 
984         if ( schemaManager != null )
985         {
986             AttributeType at = schemaManager.getAttributeType( attributeType );
987 
988             if ( at != null )
989             {
990                 if ( at.getSyntax().isHumanReadable() )
991                 {
992                     if ( attributeValue instanceof byte[] )
993                     {
994                         attributeValue = Strings.utf8ToString( ( byte[] ) attributeValue );
995                     }
996                 }
997                 else
998                 {
999                     if ( attributeValue instanceof String )
1000                     {
1001                         attributeValue = Strings.getBytesUtf8( ( String ) attributeValue );
1002                     }
1003                 }
1004             }
1005         }
1006 
1007         // Update the entry
1008         try
1009         {
1010             entry.addAttribute( attributeType, attributeValue );
1011         }
1012         catch ( Exception e )
1013         {
1014             // The attribute does not exist already, create a fake one 
1015             if ( ( schemaManager != null ) && schemaManager.isRelaxed() )
1016             {
1017                 MutableAttributeType newAttributeType = new MutableAttributeType( "1.3.6.1.4.1.18060.0.9999." + oidCounter++ );
1018                 newAttributeType.setNames( attributeType );
1019                 newAttributeType.setSyntax( schemaManager.getLdapSyntaxRegistry().get( SchemaConstants.DIRECTORY_STRING_SYNTAX ) );
1020                 schemaManager.add( newAttributeType );
1021                 entry.addAttribute( attributeType, attributeValue );
1022             }
1023         }
1024     }
1025 
1026 
1027     /**
1028      * Parse a ModRDN operation
1029      *
1030      * @param entry The entry to update
1031      * @param iter The lines iterator
1032      * @throws LdapLdifException If anything goes wrong
1033      */
1034     private void parseModRdn( LdifEntry entry, Iterator<String> iter ) throws LdapLdifException
1035     {
1036         // We must have two lines : one starting with "newrdn:" or "newrdn::",
1037         // and the second starting with "deleteoldrdn:"
1038         if ( iter.hasNext() )
1039         {
1040             String line = iter.next();
1041             String lowerLine = Strings.toLowerCaseAscii( line );
1042 
1043             if ( lowerLine.startsWith( "newrdn::" ) || lowerLine.startsWith( "newrdn:" ) )
1044             {
1045                 int colonIndex = line.indexOf( ':' );
1046                 Object attributeValue = parseValue( null, line, colonIndex );
1047 
1048                 if ( attributeValue instanceof String )
1049                 {
1050                     entry.setNewRdn( ( String ) attributeValue );
1051                 }
1052                 else
1053                 {
1054                     entry.setNewRdn( Strings.utf8ToString( ( byte[] ) attributeValue ) );
1055                 }
1056             }
1057             else
1058             {
1059                 LOG.error( I18n.err( I18n.ERR_12035_BAD_MODRDN_OPERATION, lineNumber ) );
1060                 throw new LdapLdifException( I18n.err( I18n.ERR_12035_BAD_MODRDN_OPERATION ) );
1061             }
1062         }
1063         else
1064         {
1065             LOG.error( I18n.err( I18n.ERR_12035_BAD_MODRDN_OPERATION, lineNumber ) );
1066             throw new LdapLdifException( I18n.err( I18n.ERR_12035_BAD_MODRDN_OPERATION ) );
1067         }
1068 
1069         if ( iter.hasNext() )
1070         {
1071             String line = iter.next();
1072             String lowerLine = Strings.toLowerCaseAscii( line );
1073 
1074             if ( lowerLine.startsWith( "deleteoldrdn:" ) )
1075             {
1076                 int colonIndex = line.indexOf( ':' );
1077                 Object attributeValue = parseValue( null, line, colonIndex );
1078                 entry.setDeleteOldRdn( "1".equals( attributeValue ) );
1079             }
1080             else
1081             {
1082                 LOG.error( I18n.err( I18n.ERR_12038_NO_DELETEOLDRDN, lineNumber ) );
1083                 throw new LdapLdifException( I18n.err( I18n.ERR_12038_NO_DELETEOLDRDN ) );
1084             }
1085         }
1086         else
1087         {
1088             LOG.error( I18n.err( I18n.ERR_12038_NO_DELETEOLDRDN, lineNumber ) );
1089             throw new LdapLdifException( I18n.err( I18n.ERR_12038_NO_DELETEOLDRDN ) );
1090         }
1091     }
1092 
1093 
1094     /**
1095      * Parse a modify change type.
1096      *
1097      * The grammar is :
1098      * <pre>
1099      * &lt;changerecord&gt; ::= "changetype:" FILL "modify" SEP &lt;mod-spec&gt; &lt;mod-specs-e&gt;
1100      * &lt;mod-spec&gt; ::= "add:" &lt;mod-val&gt; | "delete:" &lt;mod-val-del&gt; | "replace:" &lt;mod-val&gt;
1101      * &lt;mod-specs-e&gt; ::= &lt;mod-spec&gt;
1102      * &lt;mod-specs-e&gt; | e
1103      * &lt;mod-val&gt; ::= FILL ATTRIBUTE-DESCRIPTION SEP ATTRVAL-SPEC &lt;attrval-specs-e&gt; "-" SEP
1104      * &lt;mod-val-del&gt; ::= FILL ATTRIBUTE-DESCRIPTION SEP &lt;attrval-specs-e&gt; "-" SEP
1105      * &lt;attrval-specs-e&gt; ::= ATTRVAL-SPEC &lt;attrval-specs&gt; | e
1106      * </pre>
1107      *
1108      * @param entry The entry to feed
1109      * @param iter The lines
1110      * @exception LdapLdifException If the modify operation is invalid
1111      */
1112     private void parseModify( LdifEntry entry, Iterator<String> iter ) throws LdapLdifException
1113     {
1114         int state = MOD_SPEC;
1115         String modified = null;
1116         ModificationOperation modificationType = ModificationOperation.ADD_ATTRIBUTE;
1117         Attribute attribute = null;
1118 
1119         // The following flag is used to deal with empty modifications
1120         boolean isEmptyValue = true;
1121 
1122         while ( iter.hasNext() )
1123         {
1124             String line = iter.next();
1125             String lowerLine = Strings.toLowerCaseAscii( line );
1126 
1127             if ( lowerLine.startsWith( "-" ) )
1128             {
1129                 if ( ( state != ATTRVAL_SPEC_OR_SEP ) && ( state != ATTRVAL_SPEC ) )
1130                 {
1131                     LOG.error( I18n.err( I18n.ERR_12040_BAD_MODIFY_SEPARATOR, lineNumber ) );
1132                     throw new LdapLdifException( I18n.err( I18n.ERR_12040_BAD_MODIFY_SEPARATOR ) );
1133                 }
1134                 else
1135                 {
1136                     if ( isEmptyValue )
1137                     {
1138                         if ( state == ATTRVAL_SPEC_OR_SEP )
1139                         {
1140                             entry.addModification( modificationType, modified );
1141                         }
1142                         else
1143                         {
1144                             // Update the entry with a null value
1145                             entry.addModification( modificationType, modified, null );
1146                         }
1147                     }
1148                     else
1149                     {
1150                         // Update the entry with the attribute
1151                         entry.addModification( modificationType, attribute );
1152                     }
1153 
1154                     state = MOD_SPEC;
1155                     isEmptyValue = true;
1156                 }
1157             }
1158             else if ( lowerLine.startsWith( "add:" ) )
1159             {
1160                 if ( ( state != MOD_SPEC ) && ( state != ATTRVAL_SPEC ) )
1161                 {
1162                     LOG.error( I18n.err( I18n.ERR_12042_BAD_MODIFY_SEPARATOR_2, lineNumber ) );
1163                     throw new LdapLdifException( I18n.err( I18n.ERR_12042_BAD_MODIFY_SEPARATOR_2 ) );
1164                 }
1165 
1166                 modified = Strings.trim( line.substring( "add:".length() ) );
1167                 modificationType = ModificationOperation.ADD_ATTRIBUTE;
1168                 attribute = new DefaultAttribute( modified );
1169 
1170                 state = ATTRVAL_SPEC;
1171             }
1172             else if ( lowerLine.startsWith( "delete:" ) )
1173             {
1174                 if ( ( state != MOD_SPEC ) && ( state != ATTRVAL_SPEC ) )
1175                 {
1176                     LOG.error( I18n.err( I18n.ERR_12042_BAD_MODIFY_SEPARATOR_2, lineNumber ) );
1177                     throw new LdapLdifException( I18n.err( I18n.ERR_12042_BAD_MODIFY_SEPARATOR_2 ) );
1178                 }
1179 
1180                 modified = Strings.trim( line.substring( "delete:".length() ) );
1181                 modificationType = ModificationOperation.REMOVE_ATTRIBUTE;
1182                 attribute = new DefaultAttribute( modified );
1183                 isEmptyValue = false;
1184 
1185                 state = ATTRVAL_SPEC_OR_SEP;
1186             }
1187             else if ( lowerLine.startsWith( "replace:" ) )
1188             {
1189                 if ( ( state != MOD_SPEC ) && ( state != ATTRVAL_SPEC ) )
1190                 {
1191                     LOG.error( I18n.err( I18n.ERR_12042_BAD_MODIFY_SEPARATOR_2, lineNumber ) );
1192                     throw new LdapLdifException( I18n.err( I18n.ERR_12042_BAD_MODIFY_SEPARATOR_2 ) );
1193                 }
1194 
1195                 modified = Strings.trim( line.substring( "replace:".length() ) );
1196                 modificationType = ModificationOperation.REPLACE_ATTRIBUTE;
1197                 
1198                 if ( schemaManager != null )
1199                 {
1200                     AttributeType attributeType = schemaManager.getAttributeType( modified );
1201                     attribute = new DefaultAttribute( modified, attributeType );
1202                 }
1203                 else
1204                 {
1205                     attribute = new DefaultAttribute( modified );
1206                 }
1207 
1208                 state = ATTRVAL_SPEC_OR_SEP;
1209             }
1210             else
1211             {
1212                 if ( ( state != ATTRVAL_SPEC ) && ( state != ATTRVAL_SPEC_OR_SEP ) )
1213                 {
1214                     LOG.error( I18n.err( I18n.ERR_12040_BAD_MODIFY_SEPARATOR, lineNumber ) );
1215                     throw new LdapLdifException( I18n.err( I18n.ERR_12040_BAD_MODIFY_SEPARATOR ) );
1216                 }
1217 
1218                 // A standard AttributeType/AttributeValue pair
1219                 int colonIndex = line.indexOf( ':' );
1220 
1221                 String attributeType = line.substring( 0, colonIndex );
1222 
1223                 if ( !attributeType.equalsIgnoreCase( modified ) )
1224                 {
1225                     LOG.error( I18n.err( I18n.ERR_12044, lineNumber ) );
1226                     throw new LdapLdifException( I18n.err( I18n.ERR_12045 ) );
1227                 }
1228 
1229                 // We should *not* have a Dn twice
1230                 if ( "dn".equalsIgnoreCase( attributeType ) )
1231                 {
1232                     LOG.error( I18n.err( I18n.ERR_12002_ENTRY_WITH_TWO_DNS, lineNumber ) );
1233                     throw new LdapLdifException( I18n.err( I18n.ERR_12003_LDIF_ENTRY_WITH_TWO_DNS ) );
1234                 }
1235 
1236                 Object attributeValue = parseValue( attributeType, line, colonIndex );
1237 
1238                 try
1239                 {
1240                     if ( attributeValue instanceof String )
1241                     {
1242                         attribute.add( ( String ) attributeValue );
1243                     }
1244                     else
1245                     {
1246                         attribute.add( ( byte[] ) attributeValue );
1247                     }
1248                 }
1249                 catch ( LdapInvalidAttributeValueException liave )
1250                 {
1251                     throw new LdapLdifException( liave.getMessage(), liave );
1252                 }
1253 
1254                 isEmptyValue = false;
1255 
1256                 state = ATTRVAL_SPEC_OR_SEP;
1257             }
1258         }
1259 
1260         if ( state != MOD_SPEC )
1261         {
1262             LOG.error( I18n.err( I18n.ERR_12042_BAD_MODIFY_SEPARATOR_2, lineNumber ) );
1263             throw new LdapLdifException( I18n.err( I18n.ERR_12042_BAD_MODIFY_SEPARATOR_2, lineNumber ) );
1264         }
1265     }
1266 
1267 
1268     /**
1269      * Parse a change operation. We have to handle different cases depending on
1270      * the operation.
1271      * <ul>
1272      * <li>1) Delete : there should *not* be any line after the "changetype: delete" </li>
1273      * <li>2) Add : we must have a list of AttributeType : AttributeValue elements </li>
1274      * <li>3) ModDN : we must have two following lines: a "newrdn:" and a "deleteoldrdn:" </li>
1275      * <li>4) ModRDN : the very same, but a "newsuperior:" line is expected </li>
1276      * <li>5) Modify</li>
1277      * </ul>
1278      *
1279      * The grammar is :
1280      * <pre>
1281      * &lt;changerecord&gt; ::= "changetype:" FILL "add" SEP &lt;attrval-spec&gt; &lt;attrval-specs-e&gt; |
1282      *     "changetype:" FILL "delete" |
1283      *     "changetype:" FILL "modrdn" SEP &lt;newrdn&gt; SEP &lt;deleteoldrdn&gt; SEP |
1284      *     // To be checked
1285      *     "changetype:" FILL "moddn" SEP &lt;newrdn&gt; SEP &lt;deleteoldrdn&gt; SEP &lt;newsuperior&gt; SEP |
1286      *     "changetype:" FILL "modify" SEP &lt;mod-spec&gt; &lt;mod-specs-e&gt;
1287      * &lt;newrdn&gt; ::= "newrdn:" FILL Rdn | "newrdn::" FILL BASE64-Rdn
1288      * &lt;deleteoldrdn&gt; ::= "deleteoldrdn:" FILL "0" | "deleteoldrdn:" FILL "1"
1289      * &lt;newsuperior&gt; ::= "newsuperior:" FILL Dn | "newsuperior::" FILL BASE64-Dn
1290      * &lt;mod-specs-e&gt; ::= &lt;mod-spec&gt; &lt;mod-specs-e&gt; | e
1291      * &lt;mod-spec&gt; ::= "add:" &lt;mod-val&gt; | "delete:" &lt;mod-val&gt; | "replace:" &lt;mod-val&gt;
1292      * &lt;mod-val&gt; ::= FILL ATTRIBUTE-DESCRIPTION SEP ATTRVAL-SPEC &lt;attrval-specs-e&gt; "-" SEP
1293      * &lt;attrval-specs-e&gt; ::= ATTRVAL-SPEC &lt;attrval-specs&gt; | e
1294      * </pre>
1295      *
1296      * @param entry The entry to feed
1297      * @param iter The lines iterator
1298      * @param operation The change operation (add, modify, delete, moddn or modrdn)
1299      * @exception LdapException If the change operation is invalid
1300      */
1301     private void parseChange( LdifEntry entry, Iterator<String> iter, ChangeType operation ) throws LdapException
1302     {
1303         // The changetype and operation has already been parsed.
1304         entry.setChangeType( operation );
1305 
1306         switch ( operation )
1307         {
1308             case Delete:
1309                 // The change type will tell that it's a delete operation,
1310                 // the dn is used as a key.
1311                 return;
1312 
1313             case Add:
1314                 // We will iterate through all attribute/value pairs
1315                 while ( iter.hasNext() )
1316                 {
1317                     String line = iter.next();
1318                     String lowerLine = Strings.toLowerCaseAscii( line );
1319                     parseAttributeValue( entry, line, lowerLine );
1320                 }
1321 
1322                 return;
1323 
1324             case Modify:
1325                 parseModify( entry, iter );
1326                 return;
1327 
1328             case ModDn:
1329                 // They are supposed to have the same syntax :
1330                 // No break !
1331             case ModRdn:
1332                 // First, parse the modrdn part
1333                 parseModRdn( entry, iter );
1334 
1335                 // The next line should be the new superior, if we have one
1336                 if ( iter.hasNext() )
1337                 {
1338                     String line = iter.next();
1339                     String lowerLine = Strings.toLowerCaseAscii( line );
1340 
1341                     if ( lowerLine.startsWith( "newsuperior:" ) )
1342                     {
1343                         int colonIndex = line.indexOf( ':' );
1344                         Object attributeValue = parseValue( null, line, colonIndex );
1345 
1346                         if ( attributeValue instanceof String )
1347                         {
1348                             entry.setNewSuperior( ( String ) attributeValue );
1349                         }
1350                         else
1351                         {
1352                             entry.setNewSuperior( Strings.utf8ToString( ( byte[] ) attributeValue ) );
1353                         }
1354                     }
1355                     else
1356                     {
1357                         if ( operation == ChangeType.ModDn )
1358                         {
1359                             LOG.error( I18n.err( I18n.ERR_12046, lineNumber ) );
1360                             throw new LdapLdifException( I18n.err( I18n.ERR_12047 ) );
1361                         }
1362                     }
1363                 }
1364 
1365                 return;
1366 
1367             default:
1368                 // This is an error
1369                 LOG.error( I18n.err( I18n.ERR_12048, lineNumber ) );
1370                 throw new LdapLdifException( I18n.err( I18n.ERR_12049 ) );
1371         }
1372     }
1373 
1374 
1375     /**
1376      * Parse a ldif file. The following rules are processed :
1377      * <pre>
1378      * &lt;ldif-file&gt; ::= &lt;ldif-attrval-record&gt; &lt;ldif-attrval-records&gt; |
1379      *     &lt;ldif-change-record&gt; &lt;ldif-change-records&gt;
1380      * &lt;ldif-attrval-record&gt; ::= &lt;dn-spec&gt; &lt;sep&gt; &lt;attrval-spec&gt; &lt;attrval-specs&gt;
1381      * &lt;ldif-change-record&gt; ::= &lt;dn-spec&gt; &lt;sep&gt; &lt;controls-e&gt; &lt;changerecord&gt;
1382      * &lt;dn-spec&gt; ::= "dn:" &lt;fill&gt; &lt;distinguishedName&gt; | "dn::" &lt;fill&gt; &lt;base64-distinguishedName&gt;
1383      * &lt;changerecord&gt; ::= "changetype:" &lt;fill&gt; &lt;change-op&gt;
1384      * </pre>
1385      *
1386      * @return the parsed ldifEntry
1387      * @exception LdapException If the ldif file does not contain a valid entry
1388      */
1389     protected LdifEntry parseEntry() throws LdapException
1390     {
1391         if ( ( lines == null ) || lines.isEmpty() )
1392         {
1393             LOG.debug( "The entry is empty : end of ldif file" );
1394             return null;
1395         }
1396 
1397         // The entry must start with a dn: or a dn::
1398         String line = lines.get( 0 );
1399 
1400         lineNumber -= ( lines.size() - 1 );
1401 
1402         String name = parseDn( line );
1403 
1404         Dn dn = null;
1405         
1406         try
1407         {
1408             dn = new Dn( schemaManager, name );
1409         }
1410         catch ( LdapInvalidDnException lide )
1411         {
1412             // Deal with the RDN whihc is not in the schema
1413             // First parse the DN without the schema
1414             dn = new Dn( name );
1415             
1416             Rdn rdn = dn.getRdn();
1417             
1418             // Process each Ava
1419             for ( Ava ava : rdn )
1420             {
1421                 if ( ( schemaManager != null ) && ( schemaManager.getAttributeType( ava.getType() ) == null ) 
1422                     && schemaManager.isRelaxed() )
1423                 {
1424                     // Not found : create a new one
1425                     MutableAttributeType newAttributeType = new MutableAttributeType( "1.3.6.1.4.1.18060.0.9999." + oidCounter++ );
1426                     newAttributeType.setNames( ava.getType() );
1427                     newAttributeType.setSyntax( schemaManager.getLdapSyntaxRegistry().get( SchemaConstants.DIRECTORY_STRING_SYNTAX ) );
1428                     schemaManager.add( newAttributeType );
1429                 }
1430             }
1431             
1432             dn = new Dn( schemaManager, name );
1433         }
1434 
1435         // Ok, we have found a Dn
1436         LdifEntry entry = createLdifEntry( schemaManager );
1437         entry.setLengthBeforeParsing( entryLen );
1438         entry.setOffset( entryOffset );
1439 
1440         entry.setDn( dn );
1441 
1442         // We remove this dn from the lines
1443         lines.remove( 0 );
1444 
1445         // Now, let's iterate through the other lines
1446         Iterator<String> iter = lines.iterator();
1447 
1448         // This flag is used to distinguish between an entry and a change
1449         int type = LDIF_ENTRY;
1450 
1451         // The following boolean is used to check that a control is *not*
1452         // found elswhere than just after the dn
1453         boolean controlSeen = false;
1454 
1455         // We use this boolean to check that we do not have AttributeValues
1456         // after a change operation
1457         boolean changeTypeSeen = false;
1458 
1459         ChangeType operation = ChangeType.Add;
1460         String lowerLine;
1461         Control control;
1462 
1463         while ( iter.hasNext() )
1464         {
1465             lineNumber++;
1466 
1467             // Each line could start either with an OID, an attribute type, with
1468             // "control:" or with "changetype:"
1469             line = iter.next();
1470             lowerLine = Strings.toLowerCaseAscii( line );
1471 
1472             // We have three cases :
1473             // 1) The first line after the Dn is a "control:"
1474             // 2) The first line after the Dn is a "changeType:"
1475             // 3) The first line after the Dn is anything else
1476             if ( lowerLine.startsWith( "control:" ) )
1477             {
1478                 if ( containsEntries )
1479                 {
1480                     LOG.error( I18n.err( I18n.ERR_12004_CHANGE_NOT_ALLOWED, lineNumber ) );
1481                     throw new LdapLdifException( I18n.err( I18n.ERR_12005_NO_CHANGE ) );
1482                 }
1483 
1484                 containsChanges = true;
1485 
1486                 if ( controlSeen )
1487                 {
1488                     LOG.error( I18n.err( I18n.ERR_12050, lineNumber ) );
1489                     throw new LdapLdifException( I18n.err( I18n.ERR_12051 ) );
1490                 }
1491 
1492                 // Parse the control
1493                 control = parseControl( line.substring( "control:".length() ) );
1494                 entry.addControl( control );
1495             }
1496             else if ( lowerLine.startsWith( "changetype:" ) )
1497             {
1498                 if ( containsEntries )
1499                 {
1500                     LOG.error( I18n.err( I18n.ERR_12004_CHANGE_NOT_ALLOWED, lineNumber ) );
1501                     throw new LdapLdifException( I18n.err( I18n.ERR_12005_NO_CHANGE ) );
1502                 }
1503 
1504                 containsChanges = true;
1505 
1506                 if ( changeTypeSeen )
1507                 {
1508                     LOG.error( I18n.err( I18n.ERR_12052, lineNumber ) );
1509                     throw new LdapLdifException( I18n.err( I18n.ERR_12053 ) );
1510                 }
1511 
1512                 // A change request
1513                 type = CHANGE;
1514                 controlSeen = true;
1515 
1516                 operation = parseChangeType( line );
1517 
1518                 // Parse the change operation in a separate function
1519                 parseChange( entry, iter, operation );
1520                 changeTypeSeen = true;
1521             }
1522             else if ( line.indexOf( ':' ) > 0 )
1523             {
1524                 if ( containsChanges )
1525                 {
1526                     LOG.error( I18n.err( I18n.ERR_12004_CHANGE_NOT_ALLOWED, lineNumber ) );
1527                     throw new LdapLdifException( I18n.err( I18n.ERR_12005_NO_CHANGE ) );
1528                 }
1529 
1530                 containsEntries = true;
1531 
1532                 if ( controlSeen || changeTypeSeen )
1533                 {
1534                     LOG.error( I18n.err( I18n.ERR_12054, lineNumber ) );
1535                     throw new LdapLdifException( I18n.err( I18n.ERR_12055 ) );
1536                 }
1537 
1538                 parseAttributeValue( entry, line, lowerLine );
1539                 type = LDIF_ENTRY;
1540             }
1541             else
1542             {
1543                 // Invalid attribute Value
1544                 LOG.error( I18n.err( I18n.ERR_12056, lineNumber ) );
1545                 throw new LdapLdifException( I18n.err( I18n.ERR_12057_BAD_ATTRIBUTE ) );
1546             }
1547         }
1548 
1549         if ( type == LDIF_ENTRY )
1550         {
1551             LOG.debug( "Read an entry : {}", entry );
1552         }
1553         else if ( type == CHANGE )
1554         {
1555             entry.setChangeType( operation );
1556             LOG.debug( "Read a modification : {}", entry );
1557         }
1558         else
1559         {
1560             LOG.error( I18n.err( I18n.ERR_12058_UNKNOWN_ENTRY_TYPE, lineNumber ) );
1561             throw new LdapLdifException( I18n.err( I18n.ERR_12059_UNKNOWN_ENTRY ) );
1562         }
1563 
1564         return entry;
1565     }
1566 
1567 
1568     /**
1569      * Parse the version from the ldif input.
1570      *
1571      * @return A number representing the version (default to 1)
1572      * @throws LdapLdifException If the version is incorrect or if the input is incorrect
1573      */
1574     protected int parseVersion() throws LdapLdifException
1575     {
1576         int ver = DEFAULT_VERSION;
1577 
1578         // First, read a list of lines
1579         readLines();
1580 
1581         if ( lines.isEmpty() )
1582         {
1583             LOG.warn( "The ldif file is empty" );
1584             return ver;
1585         }
1586 
1587         // get the first line
1588         String line = lines.get( 0 );
1589 
1590         // <ldif-file> ::= "version:" <fill> <number>
1591         char[] document = line.toCharArray();
1592         String versionNumber;
1593 
1594         if ( line.startsWith( "version:" ) )
1595         {
1596             position += "version:".length();
1597             parseFill( document );
1598 
1599             // Version number. Must be '1' in this version
1600             versionNumber = parseNumber( document );
1601 
1602             // We should not have any other chars after the number
1603             if ( position != document.length )
1604             {
1605                 LOG.error( I18n.err( I18n.ERR_12060_VERSION_NOT_A_NUMBER, lineNumber ) );
1606                 throw new LdapLdifException( I18n.err( I18n.ERR_12061_LDIF_PARSING_ERROR ) );
1607             }
1608 
1609             try
1610             {
1611                 ver = Integer.parseInt( versionNumber );
1612             }
1613             catch ( NumberFormatException nfe )
1614             {
1615                 LOG.error( I18n.err( I18n.ERR_12060_VERSION_NOT_A_NUMBER, lineNumber ) );
1616                 throw new LdapLdifException( I18n.err( I18n.ERR_12061_LDIF_PARSING_ERROR ), nfe );
1617             }
1618 
1619             LOG.debug( "Ldif version : {}", versionNumber );
1620 
1621             // We have found the version, just discard the line from the list
1622             lines.remove( 0 );
1623 
1624             // and read the next lines if the current buffer is empty
1625             if ( lines.isEmpty() )
1626             {
1627                 // include the version line as part of the first entry
1628                 int tmpEntryLen = entryLen;
1629 
1630                 readLines();
1631 
1632                 entryLen += tmpEntryLen;
1633             }
1634         }
1635         else
1636         {
1637             LOG.info( "No version information : assuming version: 1" );
1638         }
1639 
1640         return ver;
1641     }
1642 
1643 
1644     /**
1645      * gets a line from the underlying data store
1646      *
1647      * @return a line of characters or null if EOF reached
1648      * @throws IOException on read failure
1649      */
1650     protected String getLine() throws IOException
1651     {
1652         return ( ( BufferedReader ) reader ).readLine();
1653     }
1654 
1655 
1656     /**
1657      * Reads an entry in a ldif buffer, and returns the resulting lines, without
1658      * comments, and unfolded.
1659      *
1660      * The lines represent *one* entry.
1661      *
1662      * @throws LdapLdifException If something went wrong
1663      */
1664     protected void readLines() throws LdapLdifException
1665     {
1666         String line;
1667         boolean insideComment = true;
1668         boolean isFirstLine = true;
1669 
1670         lines.clear();
1671         entryLen = 0;
1672         entryOffset = offset;
1673 
1674         StringBuilder sb = new StringBuilder();
1675 
1676         try
1677         {
1678             while ( ( line = getLine() ) != null )
1679             {
1680                 lineNumber++;
1681 
1682                 if ( line.length() == 0 )
1683                 {
1684                     if ( isFirstLine )
1685                     {
1686                         continue;
1687                     }
1688                     else
1689                     {
1690                         // The line is empty, we have read an entry
1691                         insideComment = false;
1692                         offset++;
1693                         break;
1694                     }
1695                 }
1696 
1697                 // We will read the first line which is not a comment
1698                 switch ( line.charAt( 0 ) )
1699                 {
1700                     case '#':
1701                         insideComment = true;
1702                         break;
1703 
1704                     case ' ':
1705                         isFirstLine = false;
1706 
1707                         if ( insideComment )
1708                         {
1709                             continue;
1710                         }
1711                         else if ( sb.length() == 0 )
1712                         {
1713                             LOG.error( I18n.err( I18n.ERR_12062_EMPTY_CONTINUATION_LINE, lineNumber ) );
1714                             throw new LdapLdifException( I18n.err( I18n.ERR_12061_LDIF_PARSING_ERROR ) );
1715                         }
1716                         else
1717                         {
1718                             sb.append( line.substring( 1 ) );
1719                         }
1720 
1721                         insideComment = false;
1722                         break;
1723 
1724                     default:
1725                         isFirstLine = false;
1726 
1727                         // We have found a new entry
1728                         // First, stores the previous one if any.
1729                         if ( sb.length() != 0 )
1730                         {
1731                             lines.add( sb.toString() );
1732                         }
1733 
1734                         sb = new StringBuilder( line );
1735                         insideComment = false;
1736                         break;
1737                 }
1738 
1739                 byte[] data = Strings.getBytesUtf8( line );
1740                 // FIXME might fail on windows in the new line issue, yet to check
1741                 offset += ( data.length + 1 );
1742                 entryLen += ( data.length + 1 );
1743             }
1744         }
1745         catch ( IOException ioe )
1746         {
1747             throw new LdapLdifException( I18n.err( I18n.ERR_12063_ERROR_WHILE_READING_LDIF_LINE ), ioe );
1748         }
1749 
1750         // Stores the current line if necessary.
1751         if ( sb.length() != 0 )
1752         {
1753             lines.add( sb.toString() );
1754         }
1755     }
1756 
1757 
1758     /**
1759      * Parse a ldif file (using the default encoding).
1760      *
1761      * @param fileName The ldif file
1762      * @return A list of entries
1763      * @throws LdapLdifException If the parsing fails
1764      */
1765     public List<LdifEntry> parseLdifFile( String fileName ) throws LdapLdifException
1766     {
1767         return parseLdifFile( fileName, Strings.getDefaultCharsetName() );
1768     }
1769 
1770 
1771     /**
1772      * Parse a ldif file, decoding it using the given charset encoding
1773      *
1774      * @param fileName The ldif file
1775      * @param encoding The charset encoding to use
1776      * @return A list of entries
1777      * @throws LdapLdifException If the parsing fails
1778      */
1779     public List<LdifEntry> parseLdifFile( String fileName, String encoding ) throws LdapLdifException
1780     {
1781         if ( Strings.isEmpty( fileName ) )
1782         {
1783             LOG.error( I18n.err( I18n.ERR_12064_EMPTY_FILE_NAME ) );
1784             throw new LdapLdifException( I18n.err( I18n.ERR_12064_EMPTY_FILE_NAME ) );
1785         }
1786 
1787         File file = new File( fileName );
1788 
1789         if ( !file.exists() )
1790         {
1791             LOG.error( I18n.err( I18n.ERR_12066, fileName ) );
1792             throw new LdapLdifException( I18n.err( I18n.ERR_12067, fileName ) );
1793         }
1794 
1795         // Open the file and then get a channel from the stream
1796         try ( 
1797             InputStream is = Files.newInputStream( Paths.get( fileName ) );
1798             BufferedReader bufferReader = new BufferedReader(
1799                 new InputStreamReader( is, Charset.forName( encoding ) ) ) )
1800         {
1801             return parseLdif( bufferReader );
1802         }
1803         catch ( FileNotFoundException fnfe )
1804         {
1805             LOG.error( I18n.err( I18n.ERR_12068, fileName ) );
1806             throw new LdapLdifException( I18n.err( I18n.ERR_12067, fileName ), fnfe );
1807         }
1808         catch ( LdapException le )
1809         {
1810             throw new LdapLdifException( le.getMessage(), le );
1811         }
1812         catch ( IOException ioe )
1813         {
1814             // Nothing to do
1815             throw new LdapLdifException( ioe.getMessage(), ioe );
1816         }
1817     }
1818 
1819 
1820     /**
1821      * A method which parses a ldif string and returns a list of entries.
1822      *
1823      * @param ldif The ldif string
1824      * @return A list of entries, or an empty List
1825      * @throws LdapLdifException If something went wrong
1826      */
1827     public List<LdifEntry> parseLdif( String ldif ) throws LdapLdifException
1828     {
1829         LOG.debug( "Starts parsing ldif buffer" );
1830 
1831         if ( Strings.isEmpty( ldif ) )
1832         {
1833             return new ArrayList<>();
1834         }
1835 
1836         BufferedReader bufferReader = new BufferedReader( new StringReader( ldif ) );
1837 
1838         try
1839         {
1840             List<LdifEntry> entries = parseLdif( bufferReader );
1841 
1842             if ( LOG.isDebugEnabled() )
1843             {
1844                 LOG.debug( "Parsed {} entries.", Integer.valueOf( entries.size() ) );
1845             }
1846 
1847             return entries;
1848         }
1849         catch ( LdapLdifException ne )
1850         {
1851             LOG.error( I18n.err( I18n.ERR_12069, ne.getLocalizedMessage() ) );
1852             throw new LdapLdifException( I18n.err( I18n.ERR_12070 ), ne );
1853         }
1854         catch ( LdapException le )
1855         {
1856             throw new LdapLdifException( le.getMessage(), le );
1857         }
1858         finally
1859         {
1860             // Close the reader
1861             try
1862             {
1863                 bufferReader.close();
1864             }
1865             catch ( IOException ioe )
1866             {
1867                 throw new LdapLdifException( I18n.err( I18n.ERR_12024_CANNOT_CLOSE_FILE ), ioe );
1868             }
1869 
1870         }
1871     }
1872 
1873 
1874     // ------------------------------------------------------------------------
1875     // Iterator Methods
1876     // ------------------------------------------------------------------------
1877     /**
1878      * Gets the next LDIF on the channel.
1879      *
1880      * @return the next LDIF as a String.
1881      */
1882     private LdifEntry nextInternal()
1883     {
1884         try
1885         {
1886             LOG.debug( "next(): -- called" );
1887 
1888             LdifEntry entry = prefetched;
1889             readLines();
1890 
1891             try
1892             {
1893                 prefetched = parseEntry();
1894             }
1895             catch ( LdapLdifException ne )
1896             {
1897                 error = ne;
1898                 throw new NoSuchElementException( ne.getMessage() );
1899             }
1900             catch ( LdapException le )
1901             {
1902                 throw new NoSuchElementException( le.getMessage() );
1903             }
1904 
1905             LOG.debug( "next(): -- returning ldif {}\n", entry );
1906 
1907             return entry;
1908         }
1909         catch ( LdapLdifException ne )
1910         {
1911             LOG.error( I18n.err( I18n.ERR_12071 ) );
1912             error = ne;
1913             return null;
1914         }
1915     }
1916 
1917 
1918     /**
1919      * Gets the next LDIF on the channel.
1920      *
1921      * @return the next LDIF as a String.
1922      */
1923     public LdifEntry next()
1924     {
1925         return nextInternal();
1926     }
1927 
1928 
1929     /**
1930      * Gets the current entry, but don't move forward.
1931      *
1932      * @return the pre-fetched entry 
1933      */
1934     public LdifEntry fetch()
1935     {
1936         return prefetched;
1937     }
1938 
1939 
1940     /**
1941      * Tests to see if another LDIF is on the input channel.
1942      *
1943      * @return true if another LDIF is available false otherwise.
1944      */
1945     private boolean hasNextInternal()
1946     {
1947         return null != prefetched;
1948     }
1949 
1950 
1951     /**
1952      * Tests to see if another LDIF is on the input channel.
1953      *
1954      * @return true if another LDIF is available false otherwise.
1955      */
1956     public boolean hasNext()
1957     {
1958         if ( prefetched != null )
1959         {
1960             LOG.debug( "hasNext(): -- returning true" );
1961         }
1962         else
1963         {
1964             LOG.debug( "hasNext(): -- returning false" );
1965         }
1966 
1967         return hasNextInternal();
1968     }
1969 
1970 
1971     /**
1972      * Always throws UnsupportedOperationException!
1973      *
1974      * @see java.util.Iterator#remove()
1975      */
1976     private void removeInternal()
1977     {
1978         throw new UnsupportedOperationException();
1979     }
1980 
1981 
1982     /**
1983      * Always throws UnsupportedOperationException!
1984      *
1985      * @see java.util.Iterator#remove()
1986      */
1987     public void remove()
1988     {
1989         removeInternal();
1990     }
1991 
1992 
1993     /**
1994      * @return An iterator on the file
1995      */
1996     @Override
1997     public Iterator<LdifEntry> iterator()
1998     {
1999         return new Iterator<LdifEntry>()
2000         {
2001             @Override
2002             public boolean hasNext()
2003             {
2004                 return hasNextInternal();
2005             }
2006 
2007 
2008             @Override
2009             public LdifEntry next()
2010             {
2011                 try
2012                 {
2013                     return nextInternal();
2014                 }
2015                 catch ( NoSuchElementException nse )
2016                 {
2017                     LOG.error( nse.getMessage() );
2018                     return null;
2019                 }
2020             }
2021 
2022 
2023             @Override
2024             public void remove()
2025             {
2026                 throw new UnsupportedOperationException();
2027             }
2028         };
2029     }
2030 
2031 
2032     /**
2033      * @return True if an error occurred during parsing
2034      */
2035     public boolean hasError()
2036     {
2037         return error != null;
2038     }
2039 
2040 
2041     /**
2042      * @return The exception that occurs during an entry parsing
2043      */
2044     public Exception getError()
2045     {
2046         return error;
2047     }
2048 
2049 
2050     /**
2051      * The main entry point of the LdifParser. It reads a buffer and returns a
2052      * List of entries.
2053      *
2054      * @param reader The buffer being processed
2055      * @return A list of entries
2056      * @throws LdapException If something went wrong
2057      */
2058     public List<LdifEntry> parseLdif( BufferedReader reader ) throws LdapException
2059     {
2060         // Create a list that will contain the read entries
2061         List<LdifEntry> entries = new ArrayList<>();
2062 
2063         this.reader = reader;
2064 
2065         // First get the version - if any -
2066         version = parseVersion();
2067         prefetched = parseEntry();
2068 
2069         // When done, get the entries one by one.
2070         for ( LdifEntry entry : this )
2071         {
2072             if ( entry != null )
2073             {
2074                 entries.add( entry );
2075             }
2076             else
2077             {
2078                 throw new LdapLdifException( I18n.err( I18n.ERR_12072, error.getLocalizedMessage() ) );
2079             }
2080         }
2081 
2082         return entries;
2083     }
2084 
2085 
2086     /**
2087      * @return True if the ldif file contains entries, fals if it contains changes
2088      */
2089     public boolean containsEntries()
2090     {
2091         return containsEntries;
2092     }
2093 
2094 
2095     /**
2096      * @return the current line that is being processed by the reader
2097      */
2098     public int getLineNumber()
2099     {
2100         return lineNumber;
2101     }
2102 
2103 
2104     /**
2105      * Creates a schema aware LdifEntry
2106      * 
2107      * @param schemaManager The SchemaManager
2108      * @return an LdifEntry that is schema aware
2109      */
2110     protected LdifEntry createLdifEntry( SchemaManager schemaManager )
2111     {
2112         if ( schemaManager != null )
2113         {
2114             return new LdifEntry( schemaManager );
2115         }
2116         else
2117         {
2118             return new LdifEntry();
2119         }
2120     }
2121 
2122 
2123     /**
2124      * @return true if the DN validation is turned on
2125      */
2126     public boolean isValidateDn()
2127     {
2128         return validateDn;
2129     }
2130 
2131 
2132     /**
2133      * Turns on/off the DN validation
2134      * 
2135      * @param validateDn the boolean flag
2136      */
2137     public void setValidateDn( boolean validateDn )
2138     {
2139         this.validateDn = validateDn;
2140     }
2141 
2142 
2143     /**
2144      * @param schemaManager the schemaManager to set
2145      */
2146     public void setSchemaManager( SchemaManager schemaManager )
2147     {
2148         this.schemaManager = schemaManager;
2149     }
2150 
2151 
2152     /**
2153      * {@inheritDoc}
2154      */
2155     @Override
2156     public void close() throws IOException
2157     {
2158         if ( reader != null )
2159         {
2160             position = 0;
2161             reader.close();
2162             containsEntries = false;
2163             containsChanges = false;
2164             offset = 0;
2165             entryOffset = 0;
2166             lineNumber = 0;
2167         }
2168     }
2169 }