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.name;
021
022
023 import java.util.List;
024
025 import org.apache.directory.shared.i18n.I18n;
026 import org.apache.directory.shared.ldap.entry.StringValue;
027 import org.apache.directory.shared.ldap.exception.LdapException;
028 import org.apache.directory.shared.ldap.exception.LdapInvalidDnException;
029 import org.apache.directory.shared.ldap.message.ResultCodeEnum;
030 import org.apache.directory.shared.ldap.util.Position;
031 import org.apache.directory.shared.ldap.util.StringTools;
032
033
034 /**
035 * A fast LDAP DN parser that handles only simple DNs. If the DN contains
036 * any special character an {@link TooComplexException} is thrown.
037 *
038 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
039 * @version $Rev: 664290 $, $Date: 2008-06-07 08:28:06 +0200 (Sa, 07 Jun 2008) $
040 */
041 public enum FastDnParser
042 {
043 INSTANCE;
044
045 /**
046 * Gets the name parser singleton instance.
047 *
048 * @return the name parser
049 */
050 public static FastDnParser getNameParser()
051 {
052 return INSTANCE;
053 }
054
055
056 /**
057 * Parses a DN from a String
058 *
059 * @param name The DN to parse
060 * @return A valid DN
061 * @throws LdapException If the DN was invalid
062 */
063 public DN parse( String name ) throws LdapException
064 {
065 DN dn = new DN();
066 parseDn( name, dn );
067 return dn;
068 }
069
070
071 /**
072 * Parses the given name string and fills the given DN object.
073 *
074 * @param name the name to parse
075 * @param dn the DN to fill
076 *
077 * @throws LdapInvalidDnException the invalid name exception
078 */
079 public void parseDn( String name, DN dn ) throws LdapInvalidDnException
080 {
081 parseDn(name, dn.rdns);
082 dn.setUpName( name );
083 dn.normalizeInternal();
084 }
085
086 void parseDn( String name, List<RDN> rdns ) throws LdapInvalidDnException
087 {
088 if ( ( name == null ) || ( name.trim().length() == 0 ) )
089 {
090 // We have an empty DN, just get out of the function.
091 return;
092 }
093
094 Position pos = new Position();
095 pos.start = 0;
096 pos.length = name.length();
097
098 while ( true )
099 {
100 RDN rdn = new RDN();
101 parseRdnInternal( name, pos, rdn );
102 rdns.add( rdn );
103
104 if ( !hasMoreChars( pos ) )
105 {
106 // end of line reached
107 break;
108 }
109 char c = nextChar( name, pos, true );
110 switch ( c )
111 {
112 case ',':
113 case ';':
114 // another RDN to parse
115 break;
116
117 default:
118 throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, I18n.err( I18n.ERR_04192, c, pos.start) );
119 }
120 }
121 }
122
123
124 /**
125 * Parses the given name string and fills the given Rdn object.
126 *
127 * @param name the name to parse
128 * @param rdn the RDN to fill
129 *
130 * @throws LdapInvalidDnException the invalid name exception
131 */
132 public void parseRdn( String name, RDN rdn ) throws LdapInvalidDnException
133 {
134 if ( name == null || name.length() == 0 )
135 {
136 throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, I18n.err( I18n.ERR_04193 ) );
137 }
138 if( rdn == null )
139 {
140 throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, I18n.err( I18n.ERR_04194 ) );
141 }
142
143 Position pos = new Position();
144 pos.start = 0;
145 pos.length = name.length();
146
147 parseRdnInternal( name, pos, rdn );
148 }
149
150
151 private void parseRdnInternal( String name, Position pos, RDN rdn ) throws LdapInvalidDnException
152 {
153 int rdnStart = pos.start;
154
155 // SPACE*
156 matchSpaces( name, pos );
157
158 // attributeType: ALPHA (ALPHA|DIGIT|HYPEN) | NUMERICOID
159 String type = matchAttributeType( name, pos );
160
161 // SPACE*
162 matchSpaces( name, pos );
163
164 // EQUALS
165 matchEquals( name, pos );
166
167 // SPACE*
168 matchSpaces( name, pos );
169
170 // here we only match "simple" values
171 // stops at \ + # " -> Too Complex Exception
172 String upValue = matchValue( name, pos );
173 String value = StringTools.trimRight( upValue );
174 // TODO: trim, normalize, etc
175
176 // SPACE*
177 matchSpaces( name, pos );
178
179 String upName = name.substring( rdnStart, pos.start );
180
181 AVA ava = new AVA( type, type, new StringValue( upValue ),
182 new StringValue( value ), upName );
183 rdn.addAttributeTypeAndValue( ava );
184
185 rdn.setUpName( upName );
186 rdn.normalize();
187 }
188
189
190 /**
191 * Matches and forgets optional spaces.
192 *
193 * @param name the name
194 * @param pos the pos
195 * @throws LdapInvalidDnException
196 */
197 private void matchSpaces( String name, Position pos ) throws LdapInvalidDnException
198 {
199 while ( hasMoreChars( pos ) )
200 {
201 char c = nextChar( name, pos, true );
202 if ( c != ' ' )
203 {
204 pos.start--;
205 break;
206 }
207 }
208 }
209
210
211 /**
212 * Matches attribute type.
213 *
214 * @param name the name
215 * @param pos the pos
216 *
217 * @return the matched attribute type
218 *
219 * @throws LdapInvalidDnException the invalid name exception
220 */
221 private String matchAttributeType( String name, Position pos ) throws LdapInvalidDnException
222 {
223 char c = nextChar( name, pos, false );
224 switch ( c )
225 {
226 case 'A':
227 case 'B':
228 case 'C':
229 case 'D':
230 case 'E':
231 case 'F':
232 case 'G':
233 case 'H':
234 case 'I':
235 case 'J':
236 case 'K':
237 case 'L':
238 case 'M':
239 case 'N':
240 case 'O':
241 case 'P':
242 case 'Q':
243 case 'R':
244 case 'S':
245 case 'T':
246 case 'U':
247 case 'V':
248 case 'W':
249 case 'X':
250 case 'Y':
251 case 'Z':
252 case 'a':
253 case 'b':
254 case 'c':
255 case 'd':
256 case 'e':
257 case 'f':
258 case 'g':
259 case 'h':
260 case 'i':
261 case 'j':
262 case 'k':
263 case 'l':
264 case 'm':
265 case 'n':
266 case 'o':
267 case 'p':
268 case 'q':
269 case 'r':
270 case 's':
271 case 't':
272 case 'u':
273 case 'v':
274 case 'w':
275 case 'x':
276 case 'y':
277 case 'z':
278 // descr
279 return matchAttributeTypeDescr( name, pos );
280
281 case '0':
282 case '1':
283 case '2':
284 case '3':
285 case '4':
286 case '5':
287 case '6':
288 case '7':
289 case '8':
290 case '9':
291 // numericoid
292 return matchAttributeTypeNumericOid( name, pos );
293
294 default:
295 // error
296 throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, I18n.err( I18n.ERR_04195, c, pos.start) );
297 }
298 }
299
300
301 /**
302 * Matches attribute type descr.
303 *
304 * @param name the name
305 * @param pos the pos
306 *
307 * @return the attribute type descr
308 *
309 * @throws LdapInvalidDnException the invalid name exception
310 */
311 private String matchAttributeTypeDescr( String name, Position pos ) throws LdapInvalidDnException
312 {
313 StringBuilder descr = new StringBuilder();
314 while ( hasMoreChars( pos ) )
315 {
316 char c = nextChar( name, pos, true );
317 switch ( c )
318 {
319 case 'A':
320 case 'B':
321 case 'C':
322 case 'D':
323 case 'E':
324 case 'F':
325 case 'G':
326 case 'H':
327 case 'I':
328 case 'J':
329 case 'K':
330 case 'L':
331 case 'M':
332 case 'N':
333 case 'O':
334 case 'P':
335 case 'Q':
336 case 'R':
337 case 'S':
338 case 'T':
339 case 'U':
340 case 'V':
341 case 'W':
342 case 'X':
343 case 'Y':
344 case 'Z':
345 case 'a':
346 case 'b':
347 case 'c':
348 case 'd':
349 case 'e':
350 case 'f':
351 case 'g':
352 case 'h':
353 case 'i':
354 case 'j':
355 case 'k':
356 case 'l':
357 case 'm':
358 case 'n':
359 case 'o':
360 case 'p':
361 case 'q':
362 case 'r':
363 case 's':
364 case 't':
365 case 'u':
366 case 'v':
367 case 'w':
368 case 'x':
369 case 'y':
370 case 'z':
371 case '0':
372 case '1':
373 case '2':
374 case '3':
375 case '4':
376 case '5':
377 case '6':
378 case '7':
379 case '8':
380 case '9':
381 case '-':
382 descr.append( c );
383 break;
384
385 case ' ':
386 case '=':
387 pos.start--;
388 return descr.toString();
389
390 case '.':
391 // occurs for RDNs of form "oid.1.2.3=test"
392 throw new TooComplexException();
393
394 default:
395 // error
396 throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, I18n.err( I18n.ERR_04196, c, pos.start ) );
397 }
398 }
399 return descr.toString();
400 }
401
402
403 /**
404 * Matches attribute type numeric OID.
405 *
406 * @param name the name
407 * @param pos the pos
408 *
409 * @return the attribute type OID
410 *
411 * @throws LdapInvalidDnException the invalid name exception
412 */
413 private String matchAttributeTypeNumericOid( String name, Position pos ) throws LdapInvalidDnException
414 {
415 StringBuilder numericOid = new StringBuilder();
416 int dotCount = 0;
417 while ( true )
418 {
419 char c = nextChar( name, pos, true );
420 switch ( c )
421 {
422 case '0':
423 // leading '0', no other digit may follow!
424 numericOid.append( c );
425 c = nextChar( name, pos, true );
426 switch ( c )
427 {
428 case '.':
429 numericOid.append( c );
430 dotCount++;
431 break;
432 case ' ':
433 case '=':
434 pos.start--;
435 break;
436 default:
437 throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, I18n.err( I18n.ERR_04197, c, pos.start ) );
438 }
439 break;
440
441 case '1':
442 case '2':
443 case '3':
444 case '4':
445 case '5':
446 case '6':
447 case '7':
448 case '8':
449 case '9':
450 numericOid.append( c );
451 boolean inInnerLoop = true;
452 while ( inInnerLoop )
453 {
454 c = nextChar( name, pos, true );
455 switch ( c )
456 {
457 case ' ':
458 case '=':
459 inInnerLoop = false;
460 pos.start--;
461 break;
462 case '.':
463 inInnerLoop = false;
464 dotCount++;
465 // no break!
466 case '0':
467 case '1':
468 case '2':
469 case '3':
470 case '4':
471 case '5':
472 case '6':
473 case '7':
474 case '8':
475 case '9':
476 numericOid.append( c );
477 break;
478 default:
479 throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, I18n.err( I18n.ERR_04197, c, pos.start ) );
480 }
481 }
482 break;
483 case ' ':
484 case '=':
485 pos.start--;
486 if ( dotCount > 0 )
487 {
488 return numericOid.toString();
489 }
490 else
491 {
492 throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, I18n.err( I18n.ERR_04198 ) );
493 }
494 default:
495 throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, I18n.err( I18n.ERR_04199, c, pos.start ) );
496 }
497 }
498 }
499
500
501 /**
502 * Matches the equals character.
503 *
504 * @param name the name
505 * @param pos the pos
506 *
507 * @throws LdapInvalidDnException the invalid name exception
508 */
509 private void matchEquals( String name, Position pos ) throws LdapInvalidDnException
510 {
511 char c = nextChar( name, pos, true );
512 if ( c != '=' )
513 {
514 throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, I18n.err( I18n.ERR_04200, c, pos.start ) );
515 }
516 }
517
518
519 /**
520 * Matches the assertion value. This method only handles simple values.
521 * If we find any special character (BACKSLASH, PLUS, SHARP or DQUOTE),
522 * a TooComplexException will be thrown.
523 *
524 * @param name the name
525 * @param pos the pos
526 *
527 * @return the string
528 *
529 * @throws LdapInvalidDnException the invalid name exception
530 */
531 private String matchValue( String name, Position pos ) throws LdapInvalidDnException
532 {
533 StringBuilder value = new StringBuilder();
534 int numTrailingSpaces = 0;
535 while ( true )
536 {
537 if ( !hasMoreChars( pos ) )
538 {
539 pos.start -= numTrailingSpaces;
540 return value.substring( 0, value.length() - numTrailingSpaces );
541 }
542 char c = nextChar( name, pos, true );
543 switch ( c )
544 {
545 case '\\':
546 case '+':
547 case '#':
548 case '"':
549 throw new TooComplexException();
550 case ',':
551 case ';':
552 pos.start--;
553 pos.start -= numTrailingSpaces;
554 return value.substring( 0, value.length() - numTrailingSpaces );
555 case ' ':
556 numTrailingSpaces++;
557 value.append( c );
558 break;
559 default:
560 numTrailingSpaces = 0;
561 value.append( c );
562 }
563 }
564 }
565
566
567 /**
568 * Gets the next character.
569 *
570 * @param name the name
571 * @param pos the pos
572 * @param increment true to increment the position
573 *
574 * @return the character
575 * @throws LdapInvalidDnException If no more characters are available
576 */
577 private char nextChar( String name, Position pos, boolean increment ) throws LdapInvalidDnException
578 {
579 if ( !hasMoreChars( pos ) )
580 {
581 throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, I18n.err( I18n.ERR_04201, pos.start ) );
582 }
583 char c = name.charAt( pos.start );
584 if ( increment )
585 {
586 pos.start++;
587 }
588 return c;
589 }
590
591
592 /**
593 * Checks if there are more characters.
594 *
595 * @param pos the pos
596 *
597 * @return true, if more characters are available
598 */
599 private boolean hasMoreChars( Position pos )
600 {
601 return pos.start < pos.length;
602 }
603 }