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