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.name;
21  
22  
23  import java.util.List;
24  
25  import org.apache.directory.api.i18n.I18n;
26  import org.apache.directory.api.ldap.model.entry.StringValue;
27  import org.apache.directory.api.ldap.model.exception.LdapException;
28  import org.apache.directory.api.ldap.model.exception.LdapInvalidDnException;
29  import org.apache.directory.api.ldap.model.message.ResultCodeEnum;
30  import org.apache.directory.api.util.Position;
31  
32  
33  /**
34   * A fast LDAP Dn parser that handles only simple DNs. If the Dn contains
35   * any special character an {@link TooComplexException} is thrown.
36   *
37   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
38   */
39  /* No protection*/enum FastDnParser
40  {
41      INSTANCE;
42  
43      /**
44       * Parses a Dn from a String
45       *
46       * @param name The Dn to parse
47       * @return A valid Dn
48       * @throws org.apache.directory.api.ldap.model.exception.LdapException If the Dn was invalid
49       */
50      /* No protection*/static Dn parse( String name ) throws LdapException
51      {
52          Dn dn = new Dn();
53          parseDn( name, dn );
54          return dn;
55      }
56  
57  
58      /**
59       * Parses the given name string and fills the given Dn object.
60       * 
61       * @param name the name to parse
62       * @param dn the Dn to fill
63       * 
64       * @throws LdapInvalidDnException the invalid name exception
65       */
66      /* No protection*/static void parseDn( String name, Dn dn ) throws LdapInvalidDnException
67      {
68          parseDn( name, dn.rdns );
69          dn.setUpName( name );
70          dn.apply( null );
71      }
72  
73  
74      /* No protection*/static void parseDn( String name, List<Rdn> rdns ) throws LdapInvalidDnException
75      {
76          if ( ( name == null ) || ( name.trim().length() == 0 ) )
77          {
78              // We have an empty Dn, just get out of the function.
79              return;
80          }
81  
82          Position pos = new Position();
83          char[] chars = name.toCharArray();
84  
85          pos.start = 0;
86          pos.length = chars.length;
87  
88          while ( true )
89          {
90              Rdn rdn = new Rdn();
91              parseRdnInternal( name, pos, rdn );
92              rdns.add( rdn );
93  
94              if ( !hasMoreChars( pos ) )
95              {
96                  // end of line reached
97                  break;
98              }
99  
100             char c = nextChar( chars, pos, true );
101 
102             switch ( c )
103             {
104                 case ',':
105                 case ';':
106                     // another Rdn to parse
107                     break;
108 
109                 default:
110                     throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, I18n.err( I18n.ERR_04192, c,
111                         pos.start ) );
112             }
113         }
114     }
115 
116 
117     /**
118      * Parses the given name string and fills the given Rdn object.
119      * 
120      * @param name the name to parse
121      * @param rdn the Rdn to fill
122      * 
123      * @throws LdapInvalidDnException the invalid name exception
124      */
125     /* No protection*/static void parseRdn( String name, Rdn rdn ) throws LdapInvalidDnException
126     {
127         if ( name == null || name.length() == 0 )
128         {
129             throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, I18n.err( I18n.ERR_04193 ) );
130         }
131 
132         if ( rdn == null )
133         {
134             throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, I18n.err( I18n.ERR_04194 ) );
135         }
136 
137         Position pos = new Position();
138         pos.start = 0;
139         pos.length = name.length();
140 
141         parseRdnInternal( name, pos, rdn );
142     }
143 
144 
145     private static void parseRdnInternal( String name, Position pos, Rdn rdn ) throws LdapInvalidDnException
146     {
147         int rdnStart = pos.start;
148         char[] chars = name.toCharArray();
149 
150         // SPACE*
151         matchSpaces( chars, pos );
152 
153         // attributeType: ALPHA (ALPHA|DIGIT|HYPEN) | NUMERICOID
154         String type = matchAttributeType( chars, pos );
155 
156         // SPACE*
157         matchSpaces( chars, pos );
158 
159         // EQUALS
160         matchEquals( chars, pos );
161 
162         // SPACE*
163         matchSpaces( chars, pos );
164 
165         // here we only match "simple" values
166         // stops at \ + # " -> Too Complex Exception
167         String upValue = matchValue( chars, pos );
168         // TODO: trim, normalize, etc
169 
170         // SPACE*
171         matchSpaces( chars, pos );
172 
173         String upName = name.substring( rdnStart, pos.start );
174 
175         Ava ava = new Ava( type, type, new StringValue( upValue ), upName );
176         rdn.addAVA( null, ava );
177 
178         rdn.setUpName( upName );
179         rdn.normalize();
180     }
181 
182 
183     /**
184      * Matches and forgets optional spaces.
185      * 
186      * @param name the name
187      * @param pos the pos
188      * @throws LdapInvalidDnException 
189      */
190     private static void matchSpaces( char[] name, Position pos ) throws LdapInvalidDnException
191     {
192         while ( hasMoreChars( pos ) )
193         {
194             char c = nextChar( name, pos, true );
195 
196             if ( c != ' ' )
197             {
198                 pos.start--;
199                 break;
200             }
201         }
202     }
203 
204 
205     /**
206      * Matches attribute type.
207      * 
208      * @param name the name
209      * @param pos the pos
210      * 
211      * @return the matched attribute type
212      * 
213      * @throws LdapInvalidDnException the invalid name exception
214      */
215     private static String matchAttributeType( char[] name, Position pos ) throws LdapInvalidDnException
216     {
217         char c = nextChar( name, pos, false );
218 
219         switch ( c )
220         {
221             case 'A':
222             case 'B':
223             case 'C':
224             case 'D':
225             case 'E':
226             case 'F':
227             case 'G':
228             case 'H':
229             case 'I':
230             case 'J':
231             case 'K':
232             case 'L':
233             case 'M':
234             case 'N':
235             case 'O':
236             case 'P':
237             case 'Q':
238             case 'R':
239             case 'S':
240             case 'T':
241             case 'U':
242             case 'V':
243             case 'W':
244             case 'X':
245             case 'Y':
246             case 'Z':
247             case 'a':
248             case 'b':
249             case 'c':
250             case 'd':
251             case 'e':
252             case 'f':
253             case 'g':
254             case 'h':
255             case 'i':
256             case 'j':
257             case 'k':
258             case 'l':
259             case 'm':
260             case 'n':
261             case 'o':
262             case 'p':
263             case 'q':
264             case 'r':
265             case 's':
266             case 't':
267             case 'u':
268             case 'v':
269             case 'w':
270             case 'x':
271             case 'y':
272             case 'z':
273                 // descr
274                 return matchAttributeTypeDescr( name, pos );
275 
276             case '0':
277             case '1':
278             case '2':
279             case '3':
280             case '4':
281             case '5':
282             case '6':
283             case '7':
284             case '8':
285             case '9':
286                 // numericoid
287                 return matchAttributeTypeNumericOid( name, pos );
288 
289             default:
290                 // error
291                 throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, I18n.err( I18n.ERR_04195, c,
292                     pos.start ) );
293         }
294     }
295 
296 
297     /**
298      * Matches attribute type descr.
299      * 
300      * @param name the name
301      * @param pos the pos
302      * 
303      * @return the attribute type descr
304      * 
305      * @throws LdapInvalidDnException the invalid name exception
306      */
307     private static String matchAttributeTypeDescr( char[] name, Position pos ) throws LdapInvalidDnException
308     {
309         int start = pos.start;
310 
311         while ( hasMoreChars( pos ) )
312         {
313             char c = nextChar( name, pos, true );
314 
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                 case '_':
381                     // Violation of the RFC, just because those idiots at Microsoft decided to support it...
382                     break;
383 
384                 case ' ':
385                 case '=':
386                     pos.start--;
387                     return new String( name, start, pos.start - start );
388 
389                 case '.':
390                     // occurs for RDNs of form "oid.1.2.3=test"
391                     throw TooComplexDnException.INSTANCE;
392 
393                 default:
394                     // error
395                     throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, I18n.err( I18n.ERR_04196, c,
396                         pos.start ) );
397             }
398         }
399 
400         return new String( name, start, pos.start - start );
401     }
402 
403 
404     /**
405      * Matches attribute type numeric OID.
406      * 
407      * @param name the name
408      * @param pos the pos
409      * 
410      * @return the attribute type OID
411      * 
412      * @throws org.apache.directory.api.ldap.model.exception.LdapInvalidDnException the invalid name exception
413      */
414     private static String matchAttributeTypeNumericOid( char[] name, Position pos ) throws LdapInvalidDnException
415     {
416         int dotCount = 0;
417         int start = pos.start;
418 
419         while ( true )
420         {
421             char c = nextChar( name, pos, true );
422 
423             switch ( c )
424             {
425                 case '0':
426                     // leading '0', no other digit may follow!
427                     c = nextChar( name, pos, true );
428 
429                     switch ( c )
430                     {
431                         case '.':
432                             dotCount++;
433                             break;
434 
435                         case ' ':
436                         case '=':
437                             pos.start--;
438                             break;
439 
440                         default:
441                             throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, I18n.err(
442                                 I18n.ERR_04197, c, pos.start ) );
443                     }
444 
445                     break;
446 
447                 case '1':
448                 case '2':
449                 case '3':
450                 case '4':
451                 case '5':
452                 case '6':
453                 case '7':
454                 case '8':
455                 case '9':
456                     boolean inInnerLoop = true;
457 
458                     while ( inInnerLoop )
459                     {
460                         c = nextChar( name, pos, true );
461 
462                         switch ( c )
463                         {
464                             case ' ':
465                             case '=':
466                                 inInnerLoop = false;
467                                 pos.start--;
468                                 break;
469 
470                             case '.':
471                                 inInnerLoop = false;
472                                 dotCount++;
473                                 // no break!
474                             case '0':
475                             case '1':
476                             case '2':
477                             case '3':
478                             case '4':
479                             case '5':
480                             case '6':
481                             case '7':
482                             case '8':
483                             case '9':
484                                 break;
485 
486                             default:
487                                 throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, I18n.err(
488                                     I18n.ERR_04197, c, pos.start ) );
489                         }
490                     }
491 
492                     break;
493 
494                 case ' ':
495                 case '=':
496                     pos.start--;
497 
498                     if ( dotCount > 0 )
499                     {
500                         return new String( name, start, pos.start - start );
501                     }
502                     else
503                     {
504                         throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, I18n.err( I18n.ERR_04198 ) );
505                     }
506 
507                 default:
508                     throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, I18n.err( I18n.ERR_04199, c,
509                         pos.start ) );
510             }
511         }
512     }
513 
514 
515     /**
516      * Matches the equals character.
517      * 
518      * @param name the name
519      * @param pos the pos
520      * 
521      * @throws LdapInvalidDnException the invalid name exception
522      */
523     private static void matchEquals( char[] name, Position pos ) throws LdapInvalidDnException
524     {
525         char c = nextChar( name, pos, true );
526 
527         if ( c != '=' )
528         {
529             throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, I18n.err( I18n.ERR_04200, c, pos.start ) );
530         }
531     }
532 
533 
534     /**
535      * Matches the assertion value. This method only handles simple values.
536      * If we find any special character (BACKSLASH, PLUS, SHARP or DQUOTE),
537      * a TooComplexException will be thrown.
538      * 
539      * @param name the name
540      * @param pos the pos
541      * 
542      * @return the string
543      * 
544      * @throws LdapInvalidDnException the invalid name exception
545      */
546     private static String matchValue( char[] name, Position pos ) throws LdapInvalidDnException
547     {
548         //StringBuilder value = new StringBuilder();
549         int start = pos.start;
550         int numTrailingSpaces = 0;
551 
552         while ( true )
553         {
554             if ( !hasMoreChars( pos ) )
555             {
556                 pos.start -= numTrailingSpaces;
557                 return new String( name, start, pos.start - start );
558             }
559 
560             char c = nextChar( name, pos, true );
561 
562             switch ( c )
563             {
564                 case '\\':
565                 case '+':
566                 case '#':
567                 case '"':
568                     throw TooComplexDnException.INSTANCE;
569 
570                 case ',':
571                 case ';':
572                     pos.start--;
573                     pos.start -= numTrailingSpaces;
574                     return new String( name, start, pos.start - start );
575 
576                 case ' ':
577                     numTrailingSpaces++;
578                     break;
579 
580                 default:
581                     numTrailingSpaces = 0;
582             }
583         }
584     }
585 
586 
587     /**
588      * Gets the next character.
589      * 
590      * @param name the name
591      * @param pos the pos
592      * @param increment true to increment the position
593      * 
594      * @return the character
595      * @throws LdapInvalidDnException If no more characters are available
596      */
597     private static char nextChar( char[] name, Position pos, boolean increment ) throws LdapInvalidDnException
598     {
599         if ( !hasMoreChars( pos ) )
600         {
601             throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, I18n.err( I18n.ERR_04201, pos.start ) );
602         }
603 
604         char c = name[pos.start];
605 
606         if ( increment )
607         {
608             pos.start++;
609         }
610 
611         return c;
612     }
613 
614 
615     /**
616      * Checks if there are more characters.
617      * 
618      * @param pos the pos
619      * 
620      * @return true, if more characters are available
621      */
622     private static boolean hasMoreChars( Position pos )
623     {
624         return pos.start < pos.length;
625     }
626 }