001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License.
018 *
019 */
020package org.apache.directory.api.ldap.model.ldif;
021
022
023import java.io.BufferedReader;
024import java.io.Closeable;
025import java.io.DataInputStream;
026import java.io.File;
027import java.io.FileNotFoundException;
028import java.io.IOException;
029import java.io.InputStream;
030import java.io.InputStreamReader;
031import java.io.Reader;
032import java.io.StringReader;
033import java.net.MalformedURLException;
034import java.net.URL;
035import java.nio.charset.Charset;
036import java.nio.file.Files;
037import java.nio.file.Paths;
038import java.util.ArrayList;
039import java.util.Iterator;
040import java.util.List;
041import java.util.NoSuchElementException;
042
043import org.apache.directory.api.asn1.util.Oid;
044import org.apache.directory.api.i18n.I18n;
045import org.apache.directory.api.ldap.model.constants.SchemaConstants;
046import org.apache.directory.api.ldap.model.entry.Attribute;
047import org.apache.directory.api.ldap.model.entry.DefaultAttribute;
048import org.apache.directory.api.ldap.model.entry.ModificationOperation;
049import org.apache.directory.api.ldap.model.exception.LdapException;
050import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException;
051import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException;
052import org.apache.directory.api.ldap.model.message.Control;
053import org.apache.directory.api.ldap.model.name.Ava;
054import org.apache.directory.api.ldap.model.name.Dn;
055import org.apache.directory.api.ldap.model.name.Rdn;
056import org.apache.directory.api.ldap.model.schema.AttributeType;
057import org.apache.directory.api.ldap.model.schema.MutableAttributeType;
058import org.apache.directory.api.ldap.model.schema.SchemaManager;
059import org.apache.directory.api.util.Base64;
060import org.apache.directory.api.util.Chars;
061import org.apache.directory.api.util.Strings;
062import org.apache.directory.api.util.exception.NotImplementedException;
063import org.slf4j.Logger;
064import org.slf4j.LoggerFactory;
065
066
067/**
068 * <pre>
069 *  &lt;ldif-file&gt; ::= &quot;version:&quot; &lt;fill&gt; &lt;number&gt; &lt;seps&gt; &lt;dn-spec&gt; &lt;sep&gt;
070 *  &lt;ldif-content-change&gt;
071 *
072 *  &lt;ldif-content-change&gt; ::=
073 *    &lt;number&gt; &lt;oid&gt; &lt;options-e&gt; &lt;value-spec&gt; &lt;sep&gt;
074 *    &lt;attrval-specs-e&gt; &lt;ldif-attrval-record-e&gt; |
075 *    &lt;alpha&gt; &lt;chars-e&gt; &lt;options-e&gt; &lt;value-spec&gt; &lt;sep&gt;
076 *    &lt;attrval-specs-e&gt; &lt;ldif-attrval-record-e&gt; |
077 *    &quot;control:&quot; &lt;fill&gt; &lt;number&gt; &lt;oid&gt; &lt;spaces-e&gt;
078 *    &lt;criticality&gt; &lt;value-spec-e&gt; &lt;sep&gt; &lt;controls-e&gt;
079 *        &quot;changetype:&quot; &lt;fill&gt; &lt;changerecord-type&gt; &lt;ldif-change-record-e&gt; |
080 *    &quot;changetype:&quot; &lt;fill&gt; &lt;changerecord-type&gt; &lt;ldif-change-record-e&gt;
081 *
082 *  &lt;ldif-attrval-record-e&gt; ::= &lt;seps&gt; &lt;dn-spec&gt; &lt;sep&gt; &lt;attributeType&gt;
083 *    &lt;options-e&gt; &lt;value-spec&gt; &lt;sep&gt; &lt;attrval-specs-e&gt;
084 *    &lt;ldif-attrval-record-e&gt; | e
085 *
086 *  &lt;ldif-change-record-e&gt; ::= &lt;seps&gt; &lt;dn-spec&gt; &lt;sep&gt; &lt;controls-e&gt;
087 *    &quot;changetype:&quot; &lt;fill&gt; &lt;changerecord-type&gt; &lt;ldif-change-record-e&gt; | e
088 *
089 *  &lt;dn-spec&gt; ::= &quot;dn:&quot; &lt;fill&gt; &lt;safe-string&gt; | &quot;dn::&quot; &lt;fill&gt; &lt;base64-string&gt;
090 *
091 *  &lt;controls-e&gt; ::= &quot;control:&quot; &lt;fill&gt; &lt;number&gt; &lt;oid&gt; &lt;spaces-e&gt; &lt;criticality&gt;
092 *    &lt;value-spec-e&gt; &lt;sep&gt; &lt;controls-e&gt; | e
093 *
094 *  &lt;criticality&gt; ::= &quot;true&quot; | &quot;false&quot; | e
095 *
096 *  &lt;oid&gt; ::= '.' &lt;number&gt; &lt;oid&gt; | e
097 *
098 *  &lt;attrval-specs-e&gt; ::= &lt;number&gt; &lt;oid&gt; &lt;options-e&gt; &lt;value-spec&gt;
099 *  &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 */
174public 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}