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