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