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