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