001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied. See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 *
019 */
020 package org.apache.directory.shared.ldap.ldif;
021
022 import java.io.BufferedReader;
023 import java.io.StringReader;
024 import java.util.ArrayList;
025
026 import javax.naming.NamingException;
027 import javax.naming.directory.Attribute;
028 import javax.naming.directory.Attributes;
029 import javax.naming.directory.BasicAttributes;
030
031 import org.apache.directory.shared.ldap.util.StringTools;
032 import org.slf4j.Logger;
033 import org.slf4j.LoggerFactory;
034
035 /**
036 * <pre>
037 * <ldif-file> ::= "version:" <fill> <number> <seps> <dn-spec> <sep>
038 * <ldif-content-change>
039 *
040 * <ldif-content-change> ::=
041 * <number> <oid> <options-e> <value-spec> <sep> <attrval-specs-e>
042 * <ldif-attrval-record-e> |
043 * <alpha> <chars-e> <options-e> <value-spec> <sep> <attrval-specs-e>
044 * <ldif-attrval-record-e> |
045 * "control:" <fill> <number> <oid> <spaces-e> <criticality>
046 * <value-spec-e> <sep> <controls-e>
047 * "changetype:" <fill> <changerecord-type> <ldif-change-record-e> |
048 * "changetype:" <fill> <changerecord-type> <ldif-change-record-e>
049 *
050 * <ldif-attrval-record-e> ::= <seps> <dn-spec> <sep> <attributeType>
051 * <options-e> <value-spec> <sep> <attrval-specs-e>
052 * <ldif-attrval-record-e> | e
053 *
054 * <ldif-change-record-e> ::= <seps> <dn-spec> <sep> <controls-e>
055 * "changetype:" <fill> <changerecord-type> <ldif-change-record-e> | e
056 *
057 * <dn-spec> ::= "dn:" <fill> <safe-string> | "dn::" <fill> <base64-string>
058 *
059 * <controls-e> ::= "control:" <fill> <number> <oid> <spaces-e> <criticality>
060 * <value-spec-e> <sep> <controls-e> | e
061 *
062 * <criticality> ::= "true" | "false" | e
063 *
064 * <oid> ::= '.' <number> <oid> | e
065 *
066 * <attrval-specs-e> ::= <number> <oid> <options-e> <value-spec> <sep>
067 * <attrval-specs-e> |
068 * <alpha> <chars-e> <options-e> <value-spec> <sep> <attrval-specs-e> | e
069 *
070 * <value-spec-e> ::= <value-spec> | e
071 *
072 * <value-spec> ::= ':' <fill> <safe-string-e> |
073 * "::" <fill> <base64-chars> |
074 * ":<" <fill> <url>
075 *
076 * <attributeType> ::= <number> <oid> | <alpha> <chars-e>
077 *
078 * <options-e> ::= ';' <char> <chars-e> <options-e> |e
079 *
080 * <chars-e> ::= <char> <chars-e> | e
081 *
082 * <changerecord-type> ::= "add" <sep> <attributeType> <options-e> <value-spec>
083 * <sep> <attrval-specs-e> |
084 * "delete" <sep> |
085 * "modify" <sep> <mod-type> <fill> <attributeType> <options-e> <sep>
086 * <attrval-specs-e> <sep> '-' <sep> <mod-specs-e> |
087 * "moddn" <sep> <newrdn> <sep> "deleteoldrdn:" <fill> <0-1> <sep>
088 * <newsuperior-e> <sep> |
089 * "modrdn" <sep> <newrdn> <sep> "deleteoldrdn:" <fill> <0-1> <sep>
090 * <newsuperior-e> <sep>
091 *
092 * <newrdn> ::= ':' <fill> <safe-string> | "::" <fill> <base64-chars>
093 *
094 * <newsuperior-e> ::= "newsuperior" <newrdn> | e
095 *
096 * <mod-specs-e> ::= <mod-type> <fill> <attributeType> <options-e>
097 * <sep> <attrval-specs-e> <sep> '-' <sep> <mod-specs-e> | e
098 *
099 * <mod-type> ::= "add:" | "delete:" | "replace:"
100 *
101 * <url> ::= <a Uniform Resource Locator, as defined in [6]>
102 *
103 *
104 *
105 * LEXICAL
106 * -------
107 *
108 * <fill> ::= ' ' <fill> | e
109 * <char> ::= <alpha> | <digit> | '-'
110 * <number> ::= <digit> <digits>
111 * <0-1> ::= '0' | '1'
112 * <digits> ::= <digit> <digits> | e
113 * <digit> ::= '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
114 * <seps> ::= <sep> <seps-e>
115 * <seps-e> ::= <sep> <seps-e> | e
116 * <sep> ::= 0x0D 0x0A | 0x0A
117 * <spaces> ::= ' ' <spaces-e>
118 * <spaces-e> ::= ' ' <spaces-e> | e
119 * <safe-string-e> ::= <safe-string> | e
120 * <safe-string> ::= <safe-init-char> <safe-chars>
121 * <safe-init-char> ::= [0x01-0x09] | 0x0B | 0x0C | [0x0E-0x1F] | [0x21-0x39] | 0x3B | [0x3D-0x7F]
122 * <safe-chars> ::= <safe-char> <safe-chars> | e
123 * <safe-char> ::= [0x01-0x09] | 0x0B | 0x0C | [0x0E-0x7F]
124 * <base64-string> ::= <base64-char> <base64-chars>
125 * <base64-chars> ::= <base64-char> <base64-chars> | e
126 * <base64-char> ::= 0x2B | 0x2F | [0x30-0x39] | 0x3D | [0x41-9x5A] | [0x61-0x7A]
127 * <alpha> ::= [0x41-0x5A] | [0x61-0x7A]
128 *
129 * COMMENTS
130 * --------
131 * - The ldap-oid VN is not correct in the RFC-2849. It has been changed from 1*DIGIT 0*1("." 1*DIGIT) to
132 * DIGIT+ ("." DIGIT+)*
133 * - The mod-spec lacks a sep between *attrval-spec and "-".
134 * - The BASE64-UTF8-STRING should be BASE64-CHAR BASE64-STRING
135 * - The ValueSpec rule must accept multilines values. In this case, we have a LF followed by a
136 * single space before the continued value.
137 * </pre>
138 *
139 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
140 * @version $Rev$, $Date$
141 */
142 public class LdifAttributesReader extends LdifReader
143 {
144 /** A logger */
145 private static final Logger LOG = LoggerFactory.getLogger( LdifAttributesReader.class );
146
147 /**
148 * Constructors
149 */
150 public LdifAttributesReader()
151 {
152 lines = new ArrayList<String>();
153 position = new Position();
154 version = DEFAULT_VERSION;
155 }
156
157
158 /**
159 * Parse an AttributeType/AttributeValue
160 *
161 * @param attributes The entry where to store the value
162 * @param line The line to parse
163 * @param lowerLine The same line, lowercased
164 * @throws NamingException If anything goes wrong
165 */
166 private void parseAttribute( Attributes attributes, String line, String lowerLine ) throws NamingException
167 {
168 int colonIndex = line.indexOf( ':' );
169
170 String attributeType = lowerLine.substring( 0, colonIndex );
171
172 // We should *not* have a DN twice
173 if ( attributeType.equals( "dn" ) )
174 {
175 LOG.error( "An entry must not have two DNs" );
176 throw new NamingException( "A ldif entry should not have two DNs" );
177 }
178
179 Object attributeValue = parseValue( line, colonIndex );
180
181 // Update the entry
182 Attribute attribute = attributes.get( attributeType );
183
184 if ( attribute == null )
185 {
186 attributes.put( attributeType, attributeValue );
187 }
188 else
189 {
190 attribute.add( attributeValue );
191 }
192 }
193
194 /**
195 * Parse a ldif file. The following rules are processed :
196 *
197 * <ldif-file> ::= <ldif-attrval-record> <ldif-attrval-records> |
198 * <ldif-change-record> <ldif-change-records> <ldif-attrval-record> ::=
199 * <dn-spec> <sep> <attrval-spec> <attrval-specs> <ldif-change-record> ::=
200 * <dn-spec> <sep> <controls-e> <changerecord> <dn-spec> ::= "dn:" <fill>
201 * <distinguishedName> | "dn::" <fill> <base64-distinguishedName>
202 * <changerecord> ::= "changetype:" <fill> <change-op>
203 *
204 * @return The read entry
205 * @throws NamingException If the entry can't be read or is invalid
206 */
207 private Attributes parseAttributes() throws NamingException
208 {
209 if ( ( lines == null ) || ( lines.size() == 0 ) )
210 {
211 LOG.debug( "The entry is empty : end of ldif file" );
212 return null;
213 }
214
215 Attributes attributes = new BasicAttributes( true );
216
217 // Now, let's iterate through the other lines
218 for ( String line:lines )
219 {
220 // Each line could start either with an OID, an attribute type, with
221 // "control:" or with "changetype:"
222 String lowerLine = line.toLowerCase();
223
224 // We have three cases :
225 // 1) The first line after the DN is a "control:" -> this is an error
226 // 2) The first line after the DN is a "changeType:" -> this is an error
227 // 3) The first line after the DN is anything else
228 if ( lowerLine.startsWith( "control:" ) )
229 {
230 LOG.error( "We cannot have changes when reading a file which already contains entries" );
231 throw new NamingException( "No changes withing entries" );
232 }
233 else if ( lowerLine.startsWith( "changetype:" ) )
234 {
235 LOG.error( "We cannot have changes when reading a file which already contains entries" );
236 throw new NamingException( "No changes withing entries" );
237 }
238 else if ( line.indexOf( ':' ) > 0 )
239 {
240 parseAttribute( attributes, line, lowerLine );
241 }
242 else
243 {
244 // Invalid attribute Value
245 LOG.error( "Expecting an attribute type" );
246 throw new NamingException( "Bad attribute" );
247 }
248 }
249
250 LOG.debug( "Read an attributes : {}", attributes );
251
252 return attributes;
253 }
254
255
256 /**
257 * A method which parses a ldif string and returns a list of entries.
258 *
259 * @param ldif The ldif string
260 * @return A list of entries, or an empty List
261 * @throws NamingException
262 * If something went wrong
263 */
264 public Attributes parseAttributes( String ldif ) throws NamingException
265 {
266 lines = new ArrayList<String>();
267 position = new Position();
268
269 LOG.debug( "Starts parsing ldif buffer" );
270
271 if ( StringTools.isEmpty( ldif ) )
272 {
273 return new BasicAttributes( true );
274 }
275
276 StringReader strIn = new StringReader( ldif );
277 in = new BufferedReader( strIn );
278
279 try
280 {
281 readLines();
282
283 Attributes attributes = parseAttributes();
284
285 if ( LOG.isDebugEnabled() )
286 {
287 LOG.debug( "Parsed {} entries.", ( attributes == null ? 0 : 1 ) );
288 }
289
290 return attributes;
291 }
292 catch (NamingException ne)
293 {
294 LOG.error( "Cannot parse the ldif buffer : {}", ne.getMessage() );
295 throw new NamingException( "Error while parsing the ldif buffer" );
296 }
297 }
298 }