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