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          char[] chars = name.toCharArray();
85              
86          pos.start = 0;
87          pos.length = chars.length;
88  
89          while ( true )
90          {
91              Rdn rdn = new Rdn();
92              parseRdnInternal( name, pos, rdn );
93              rdns.add( rdn );
94  
95              if ( !hasMoreChars( pos ) )
96              {
97                  // end of line reached
98                  break;
99              }
100             
101             char c = nextChar( chars, pos, true );
102             
103             switch ( c )
104             {
105                 case ',':
106                 case ';':
107                     // another Rdn to parse
108                     break;
109 
110                 default:
111                     throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, I18n.err( I18n.ERR_04192, c,
112                         pos.start ) );
113             }
114         }
115     }
116 
117 
118     /**
119      * Parses the given name string and fills the given Rdn object.
120      * 
121      * @param name the name to parse
122      * @param rdn the Rdn to fill
123      * 
124      * @throws LdapInvalidDnException the invalid name exception
125      */
126     /* No protection*/static void parseRdn( String name, Rdn rdn ) throws LdapInvalidDnException
127     {
128         if ( name == null || name.length() == 0 )
129         {
130             throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, I18n.err( I18n.ERR_04193 ) );
131         }
132         
133         if ( rdn == null )
134         {
135             throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, I18n.err( I18n.ERR_04194 ) );
136         }
137 
138         Position pos = new Position();
139         pos.start = 0;
140         pos.length = name.length();
141 
142         parseRdnInternal( name, pos, rdn );
143     }
144 
145 
146     private static void parseRdnInternal( String name, Position pos, Rdn rdn ) throws LdapInvalidDnException
147     {
148         int rdnStart = pos.start;
149         char[] chars = name.toCharArray();
150 
151         // SPACE*
152         matchSpaces( chars, pos );
153 
154         // attributeType: ALPHA (ALPHA|DIGIT|HYPEN) | NUMERICOID
155         String type = matchAttributeType( chars, pos );
156 
157         // SPACE*
158         matchSpaces( chars, pos );
159 
160         // EQUALS
161         matchEquals( chars, pos );
162 
163         // SPACE*
164         matchSpaces( chars, pos );
165 
166         // here we only match "simple" values
167         // stops at \ + # " -> Too Complex Exception
168         String upValue = matchValue( chars, pos );
169         String value = Strings.trimRight( upValue );
170         // TODO: trim, normalize, etc
171 
172         // SPACE*
173         matchSpaces( chars, pos );
174 
175         String upName = name.substring( rdnStart, pos.start );
176 
177         Ava ava = new Ava( type, type, new StringValue( upValue ),
178             new StringValue( value ), upName );
179         rdn.addAVA( null, ava );
180 
181         rdn.setUpName( upName );
182         rdn.normalize();
183     }
184 
185 
186     /**
187      * Matches and forgets optional spaces.
188      * 
189      * @param name the name
190      * @param pos the pos
191      * @throws LdapInvalidDnException 
192      */
193     private static void matchSpaces( char[] name, Position pos ) throws LdapInvalidDnException
194     {
195         while ( hasMoreChars( pos ) )
196         {
197             char c = nextChar( name, pos, true );
198             
199             if ( c != ' ' )
200             {
201                 pos.start--;
202                 break;
203             }
204         }
205     }
206 
207 
208     /**
209      * Matches attribute type.
210      * 
211      * @param name the name
212      * @param pos the pos
213      * 
214      * @return the matched attribute type
215      * 
216      * @throws LdapInvalidDnException the invalid name exception
217      */
218     private static String matchAttributeType( char[] name, Position pos ) throws LdapInvalidDnException
219     {
220         char c = nextChar( name, pos, false );
221         
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 LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, I18n.err( I18n.ERR_04195, c,
295                     pos.start ) );
296         }
297     }
298 
299 
300     /**
301      * Matches attribute type descr.
302      * 
303      * @param name the name
304      * @param pos the pos
305      * 
306      * @return the attribute type descr
307      * 
308      * @throws LdapInvalidDnException the invalid name exception
309      */
310     private static String matchAttributeTypeDescr( char[] name, Position pos ) throws LdapInvalidDnException
311     {
312         int start = pos.start;
313         
314         while ( hasMoreChars( pos ) )
315         {
316             char c = nextChar( name, pos, true );
317             
318             switch ( c )
319             {
320                 case 'A':
321                 case 'B':
322                 case 'C':
323                 case 'D':
324                 case 'E':
325                 case 'F':
326                 case 'G':
327                 case 'H':
328                 case 'I':
329                 case 'J':
330                 case 'K':
331                 case 'L':
332                 case 'M':
333                 case 'N':
334                 case 'O':
335                 case 'P':
336                 case 'Q':
337                 case 'R':
338                 case 'S':
339                 case 'T':
340                 case 'U':
341                 case 'V':
342                 case 'W':
343                 case 'X':
344                 case 'Y':
345                 case 'Z':
346                 case 'a':
347                 case 'b':
348                 case 'c':
349                 case 'd':
350                 case 'e':
351                 case 'f':
352                 case 'g':
353                 case 'h':
354                 case 'i':
355                 case 'j':
356                 case 'k':
357                 case 'l':
358                 case 'm':
359                 case 'n':
360                 case 'o':
361                 case 'p':
362                 case 'q':
363                 case 'r':
364                 case 's':
365                 case 't':
366                 case 'u':
367                 case 'v':
368                 case 'w':
369                 case 'x':
370                 case 'y':
371                 case 'z':
372                 case '0':
373                 case '1':
374                 case '2':
375                 case '3':
376                 case '4':
377                 case '5':
378                 case '6':
379                 case '7':
380                 case '8':
381                 case '9':
382                 case '-':
383                 case '_': // Violation of the RFC, just because those idiots at Microsoft decided to support it...
384                     break;
385 
386                 case ' ':
387                 case '=':
388                     pos.start--;
389                     return new String( name, start, pos.start - start );
390 
391                 case '.':
392                     // occurs for RDNs of form "oid.1.2.3=test"
393                     throw TooComplexDnException.INSTANCE;
394 
395                 default:
396                     // error
397                     throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, I18n.err( I18n.ERR_04196, c,
398                         pos.start ) );
399             }
400         }
401         
402         return new String( name, start, pos.start - start );
403     }
404 
405 
406     /**
407      * Matches attribute type numeric OID.
408      * 
409      * @param name the name
410      * @param pos the pos
411      * 
412      * @return the attribute type OID
413      * 
414      * @throws org.apache.directory.api.ldap.model.exception.LdapInvalidDnException the invalid name exception
415      */
416     private static String matchAttributeTypeNumericOid( char[] name, Position pos ) throws LdapInvalidDnException
417     {
418         int dotCount = 0;
419         int start = pos.start;
420         
421         while ( true )
422         {
423             char c = nextChar( name, pos, true );
424             
425             switch ( c )
426             {
427                 case '0':
428                     // leading '0', no other digit may follow!
429                     c = nextChar( name, pos, true );
430                     
431                     switch ( c )
432                     {
433                         case '.':
434                             dotCount++;
435                             break;
436                             
437                         case ' ':
438                         case '=':
439                             pos.start--;
440                             break;
441                             
442                         default:
443                             throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, I18n.err(
444                                 I18n.ERR_04197, c, pos.start ) );
445                     }
446                     
447                     break;
448 
449                 case '1':
450                 case '2':
451                 case '3':
452                 case '4':
453                 case '5':
454                 case '6':
455                 case '7':
456                 case '8':
457                 case '9':
458                     boolean inInnerLoop = true;
459                     
460                     while ( inInnerLoop )
461                     {
462                         c = nextChar( name, pos, true );
463                         
464                         switch ( c )
465                         {
466                             case ' ':
467                             case '=':
468                                 inInnerLoop = false;
469                                 pos.start--;
470                                 break;
471                                 
472                             case '.':
473                                 inInnerLoop = false;
474                                 dotCount++;
475                                 // no break!
476                             case '0':
477                             case '1':
478                             case '2':
479                             case '3':
480                             case '4':
481                             case '5':
482                             case '6':
483                             case '7':
484                             case '8':
485                             case '9':
486                                 break;
487                                 
488                             default:
489                                 throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, I18n.err(
490                                     I18n.ERR_04197, c, pos.start ) );
491                         }
492                     }
493                     
494                     break;
495                     
496                 case ' ':
497                 case '=':
498                     pos.start--;
499                     
500                     if ( dotCount > 0 )
501                     {
502                         return new String( name, start, pos.start - start );
503                     }
504                     else
505                     {
506                         throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, I18n.err( I18n.ERR_04198 ) );
507                     }
508                     
509                 default:
510                     throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, I18n.err( I18n.ERR_04199, c,
511                         pos.start ) );
512             }
513         }
514     }
515 
516 
517     /**
518      * Matches the equals character.
519      * 
520      * @param name the name
521      * @param pos the pos
522      * 
523      * @throws LdapInvalidDnException the invalid name exception
524      */
525     private static void matchEquals( char[] name, Position pos ) throws LdapInvalidDnException
526     {
527         char c = nextChar( name, pos, true );
528         
529         if ( c != '=' )
530         {
531             throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, I18n.err( I18n.ERR_04200, c, pos.start ) );
532         }
533     }
534 
535 
536     /**
537      * Matches the assertion value. This method only handles simple values.
538      * If we find any special character (BACKSLASH, PLUS, SHARP or DQUOTE),
539      * a TooComplexException will be thrown.
540      * 
541      * @param name the name
542      * @param pos the pos
543      * 
544      * @return the string
545      * 
546      * @throws LdapInvalidDnException the invalid name exception
547      */
548     private static String matchValue( char[] name, Position pos ) throws LdapInvalidDnException
549     {
550         //StringBuilder value = new StringBuilder();
551         int start = pos.start;
552         int numTrailingSpaces = 0;
553         
554         while ( true )
555         {
556             if ( !hasMoreChars( pos ) )
557             {
558                 pos.start -= numTrailingSpaces;
559                 return new String( name, start, pos.start - start );
560             }
561             
562             char c = nextChar( name, pos, true );
563             
564             switch ( c )
565             {
566                 case '\\':
567                 case '+':
568                 case '#':
569                 case '"':
570                     throw TooComplexDnException.INSTANCE;
571                     
572                 case ',':
573                 case ';':
574                     pos.start--;
575                     pos.start -= numTrailingSpaces;
576                     return new String( name, start, pos.start - start );
577                     
578                 case ' ':
579                     numTrailingSpaces++;
580                     break;
581                     
582                 default:
583                     numTrailingSpaces = 0;
584             }
585         }
586     }
587 
588 
589     /**
590      * Gets the next character.
591      * 
592      * @param name the name
593      * @param pos the pos
594      * @param increment true to increment the position
595      * 
596      * @return the character
597      * @throws LdapInvalidDnException If no more characters are available
598      */
599     private static char nextChar( char[] name, Position pos, boolean increment ) throws LdapInvalidDnException
600     {
601         if ( !hasMoreChars( pos ) )
602         {
603             throw new LdapInvalidDnException( ResultCodeEnum.INVALID_DN_SYNTAX, I18n.err( I18n.ERR_04201, pos.start ) );
604         }
605         
606         char c = name[ pos.start ];
607         
608         if ( increment )
609         {
610             pos.start++;
611         }
612         
613         return c;
614     }
615 
616 
617     /**
618      * Checks if there are more characters.
619      * 
620      * @param pos the pos
621      * 
622      * @return true, if more characters are available
623      */
624     private static boolean hasMoreChars( Position pos )
625     {
626         return pos.start < pos.length;
627     }
628 }