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