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