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