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