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