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