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