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