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 * https://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 */ 020package org.apache.directory.api.ldap.model.filter; 021 022 023import java.text.ParseException; 024 025import org.apache.directory.api.i18n.I18n; 026import org.apache.directory.api.ldap.model.entry.AttributeUtils; 027import org.apache.directory.api.ldap.model.entry.Value; 028import org.apache.directory.api.ldap.model.exception.LdapException; 029import org.apache.directory.api.ldap.model.exception.LdapInvalidAttributeValueException; 030import org.apache.directory.api.ldap.model.schema.AttributeType; 031import org.apache.directory.api.ldap.model.schema.SchemaManager; 032import org.apache.directory.api.util.Chars; 033import org.apache.directory.api.util.Hex; 034import org.apache.directory.api.util.Position; 035import org.apache.directory.api.util.Strings; 036import org.apache.directory.api.util.Unicode; 037 038 039/** 040 * This class parse a Ldap filter. The grammar is given in RFC 4515 041 * 042 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 043 */ 044public final class FilterParser 045{ 046 private FilterParser() 047 { 048 } 049 050 051 /** 052 * Parses a search filter from it's string representation to an expression node object. 053 * 054 * @param filter the search filter in it's string representation 055 * @return the expression node object 056 * @throws ParseException If the filter is invalid 057 */ 058 public static ExprNode parse( String filter ) throws ParseException 059 { 060 return parse( null, filter, false ); 061 } 062 063 064 /** 065 * Parses a search filter from it's string representation to an expression node object. 066 * 067 * In <code>relaxed</code> mode the filter may violate RFC 4515, e.g. the underscore in attribute names is allowed. 068 * 069 * @param filter the search filter in it's string representation 070 * @param relaxed <code>true</code> to parse the filter in relaxed mode 071 * @return the expression node object 072 * @throws ParseException If the filter is invalid 073 */ 074 public static ExprNode parse( String filter, boolean relaxed ) throws ParseException 075 { 076 return parse( null, filter, relaxed ); 077 } 078 079 080 /** 081 * Parses a search filter from it's string representation to an expression node object, 082 * using the provided SchemaManager 083 * 084 * @param schemaManager The SchemaManager to use 085 * @param filter the search filter in it's string representation 086 * @return the expression node object 087 * @throws ParseException If the filter is invalid 088 */ 089 public static ExprNode parse( SchemaManager schemaManager, String filter ) throws ParseException 090 { 091 return parse( schemaManager, filter, false ); 092 } 093 094 095 /** 096 * Skip the white spaces (0x20, 0x09, 0x0a and 0x0d) 097 * 098 * @param filter The filter being parsed 099 * @param pos The current position in the filter 100 */ 101 private static void skipWhiteSpaces( byte[] filter, Position pos ) 102 { 103 while ( Strings.isCharASCII( filter, pos.start, ' ' ) 104 || Strings.isCharASCII( filter, pos.start, '\t' ) 105 || Strings.isCharASCII( filter, pos.start, '\n' ) ) 106 { 107 pos.start++; 108 } 109 } 110 111 112 /** 113 * Parses a search filter from it's string representation to an expression node object, 114 * using the provided SchemaManager 115 * 116 * @param schemaManager The SchemaManager to use 117 * @param filter the search filter in it's string representation 118 * @param relaxed <code>true</code> to parse the filter in relaxed mode 119 * @return the expression node object 120 * @throws ParseException If the filter is invalid 121 */ 122 public static ExprNode parse( SchemaManager schemaManager, String filter, boolean relaxed ) throws ParseException 123 { 124 if ( Strings.isEmpty( filter ) ) 125 { 126 throw new ParseException( I18n.err( I18n.ERR_13316_EMPTY_FILTER ), 0 ); 127 } 128 129 /** Convert the filter to an array of bytes, as this is what we expect */ 130 byte[] filterBytes = Strings.getBytesUtf8( filter ); 131 132 Position pos = new Position(); 133 pos.start = 0; 134 pos.end = 0; 135 pos.length = filterBytes.length; 136 137 try 138 { 139 ExprNode node = parseFilterInternal( schemaManager, filterBytes, pos, relaxed ); 140 141 if ( node == UndefinedNode.UNDEFINED_NODE ) 142 { 143 return null; 144 } 145 else 146 { 147 return node; 148 } 149 } 150 catch ( LdapException le ) 151 { 152 throw new ParseException( le.getMessage(), pos.start ); 153 } 154 } 155 156 157 /** 158 * Parse an extensible 159 * 160 *<pre> 161 * extensible = ( attr [":dn"] [':' oid] ":=" assertionvalue ) 162 * / ( [":dn"] ':' oid ":=" assertionvalue ) 163 * matchingrule = ":" oid 164 * </pre> 165 * 166 * @param schemaManager The {@link SchemaManager} 167 * @param attribute The filter's attribute 168 * @param filterBytes The filter bytes to parse 169 * @param pos The position in the filter bytes 170 * @param relaxed If the filter is analyzed in relaxed mode or not 171 * @return the created {@link ExprNode} 172 * @throws ParseException If the Node can't be parsed 173 * @throws LdapException If we met an error while parsing a filter element 174 */ 175 private static ExprNode parseExtensible( SchemaManager schemaManager, String attribute, byte[] filterBytes, 176 Position pos, boolean relaxed ) throws LdapException, ParseException 177 { 178 ExtensibleNode node; 179 180 if ( schemaManager != null ) 181 { 182 AttributeType attributeType = schemaManager.getAttributeType( attribute ); 183 184 if ( attributeType != null ) 185 { 186 node = new ExtensibleNode( attributeType ); 187 } 188 else 189 { 190 return UndefinedNode.UNDEFINED_NODE; 191 } 192 } 193 else 194 { 195 node = new ExtensibleNode( attribute ); 196 } 197 198 if ( attribute != null ) 199 { 200 // First check if we have a ":dn" 201 if ( Strings.isCharASCII( filterBytes, pos.start, 'd' ) && Strings.isCharASCII( filterBytes, pos.start + 1, 'n' ) ) 202 { 203 // Set the dnAttributes flag and move forward in the string 204 node.setDnAttributes( true ); 205 pos.start += 2; 206 } 207 else 208 { 209 // Push back the ':' 210 pos.start--; 211 } 212 213 // Do we have a MatchingRule ? 214 if ( Strings.byteAt( filterBytes, pos.start ) == ':' ) 215 { 216 pos.start++; 217 218 if ( Strings.byteAt( filterBytes, pos.start ) == '=' ) 219 { 220 pos.start++; 221 222 // Get the assertionValue 223 node.setValue( parseAssertionValue( schemaManager, filterBytes, pos ) ); 224 225 return node; 226 } 227 else 228 { 229 String matchingRuleId = AttributeUtils.parseAttribute( filterBytes, pos, false, relaxed ); 230 231 node.setMatchingRuleId( matchingRuleId ); 232 233 if ( Strings.isCharASCII( filterBytes, pos.start, ':' ) && Strings.isCharASCII( filterBytes, pos.start + 1, '=' ) ) 234 { 235 pos.start += 2; 236 237 // Get the assertionValue 238 node.setValue( parseAssertionValue( schemaManager, filterBytes, pos ) ); 239 240 return node; 241 } 242 else 243 { 244 throw new ParseException( I18n.err( I18n.ERR_13305_ASSERTION_VALUE_EXPECTED ), pos.start ); 245 } 246 } 247 } 248 else 249 { 250 throw new ParseException( I18n.err( I18n.ERR_13306_MR_OR_ASSERTION_VALUE_EXPECTED ), pos.start ); 251 } 252 } 253 else 254 { 255 // No attribute 256 boolean oidRequested = false; 257 258 // First check if we have a ":dn" 259 if ( Strings.isCharASCII( filterBytes, pos.start, ':' ) 260 && Strings.isCharASCII( filterBytes, pos.start + 1, 'd' ) 261 && Strings.isCharASCII( filterBytes, pos.start + 2, 'n' ) ) 262 { 263 // Set the dnAttributes flag and move forward in the string 264 node.setDnAttributes( true ); 265 pos.start += 3; 266 } 267 else 268 { 269 oidRequested = true; 270 } 271 272 // Do we have a MatchingRule ? 273 if ( Strings.byteAt( filterBytes, pos.start ) == ':' ) 274 { 275 pos.start++; 276 277 if ( Strings.byteAt( filterBytes, pos.start ) == '=' ) 278 { 279 if ( oidRequested ) 280 { 281 throw new ParseException( I18n.err( I18n.ERR_13307_MATCHING_RULE_EXPECTED ), pos.start ); 282 } 283 284 pos.start++; 285 286 // Get the assertionValue 287 node.setValue( parseAssertionValue( schemaManager, null, filterBytes, pos ) ); 288 289 return node; 290 } 291 else 292 { 293 String matchingRuleId = AttributeUtils.parseAttribute( filterBytes, pos, false, relaxed ); 294 295 node.setMatchingRuleId( matchingRuleId ); 296 297 if ( Strings.isCharASCII( filterBytes, pos.start, ':' ) && Strings.isCharASCII( filterBytes, pos.start + 1, '=' ) ) 298 { 299 pos.start += 2; 300 301 // Get the assertionValue 302 node.setValue( parseAssertionValue( schemaManager, null, filterBytes, pos ) ); 303 304 return node; 305 } 306 else 307 { 308 throw new ParseException( I18n.err( I18n.ERR_13305_ASSERTION_VALUE_EXPECTED ), pos.start ); 309 } 310 } 311 } 312 else 313 { 314 throw new ParseException( I18n.err( I18n.ERR_13306_MR_OR_ASSERTION_VALUE_EXPECTED ), pos.start ); 315 } 316 } 317 } 318 319 320 /** 321 * An assertion value : 322 * 323 * <pre> 324 * assertionvalue = valueencoding 325 * valueencoding = 0*(normal / escaped) 326 * normal = UTF1SUBSET / UTFMB 327 * escaped = '\' HEX HEX 328 * HEX = '0'-'9' / 'A'-'F' / 'a'-'f' 329 * UTF1SUBSET = %x01-27 / %x2B-5B / %x5D-7F (Everything but '\0', '*', '(', ')' and '\') 330 * UTFMB = UTF2 / UTF3 / UTF4 331 * UTF0 = %x80-BF 332 * UTF2 = %xC2-DF UTF0 333 * UTF3 = %xE0 %xA0-BF UTF0 / %xE1-EC UTF0 UTF0 / %xED %x80-9F UTF0 / %xEE-EF UTF0 UTF0 334 * UTF4 = %xF0 %x90-BF UTF0 UTF0 / %xF1-F3 UTF0 UTF0 UTF0 / %xF4 %x80-8F UTF0 UTF0 335 * </pre> 336 * 337 * With the specific constraints (RFC 4515): 338 * 339 * <pre> 340 * "The <valueencoding< rule ensures that the entire filter string is a" 341 * "valid UTF-8 string and provides that the octets that represent the" 342 * "ASCII characters "*" (ASCII 0x2a), "(" (ASCII 0x28), ")" (ASCII" 343 * "0x29), "\" (ASCII 0x5c), and NUL (ASCII 0x00) are represented as a" 344 * "backslash "\" (ASCII 0x5c) followed by the two hexadecimal digits" 345 * "representing the value of the encoded octet." 346 * </pre> 347 * 348 * The incoming String is already transformed from UTF-8 to unicode, so we must assume that the 349 * grammar we have to check is the following : 350 * 351 * <pre> 352 * assertionvalue = valueencoding 353 * valueencoding = 0*(normal / escaped) 354 * normal = unicodeSubset 355 * escaped = '\' HEX HEX 356 * HEX = '0'-'9' / 'A'-'F' / 'a'-'f' 357 * unicodeSubset = %x01-27 / %x2B-5B / %x5D-FFFF 358 * </pre> 359 * 360 * @param schemaManager The {@link SchemaManager} 361 * @param attribute The associated Attribute 362 * @param filterBytes The filter bytes to parse 363 * @param pos The position in the filter bytes 364 * @return The parsed value 365 * @throws ParseException If the value can't be parsed 366 * @throws LdapInvalidAttributeValueException If the value is invalid 367 */ 368 private static Value parseAssertionValue( SchemaManager schemaManager, String attribute, byte[] filterBytes, 369 Position pos ) throws ParseException, LdapInvalidAttributeValueException 370 { 371 byte b = Strings.byteAt( filterBytes, pos.start ); 372 373 // Create a buffer big enough to contain the value once converted 374 byte[] value = new byte[filterBytes.length - pos.start]; 375 int current = 0; 376 377 do 378 { 379 if ( Unicode.isUnicodeSubset( b ) ) 380 { 381 value[current++] = b; 382 pos.start++; 383 } 384 else if ( Strings.isCharASCII( filterBytes, pos.start, '\\' ) ) 385 { 386 // Maybe an escaped 387 pos.start++; 388 389 // First hex 390 if ( Chars.isHex( filterBytes, pos.start ) ) 391 { 392 pos.start++; 393 } 394 else 395 { 396 throw new ParseException( I18n.err( I18n.ERR_13308_NOT_A_VALID_ESCAPED_VALUE ), pos.start ); 397 } 398 399 // second hex 400 if ( Chars.isHex( filterBytes, pos.start ) ) 401 { 402 value[current++] = Hex.getHexValue( filterBytes[pos.start - 1], filterBytes[pos.start] ); 403 pos.start++; 404 } 405 else 406 { 407 throw new ParseException( I18n.err( I18n.ERR_13308_NOT_A_VALID_ESCAPED_VALUE ), pos.start ); 408 } 409 } 410 else 411 { 412 // not a valid char, so let's get out 413 break; 414 } 415 416 b = Strings.byteAt( filterBytes, pos.start ); 417 } 418 while ( b != '\0' ); 419 420 if ( current != 0 ) 421 { 422 if ( schemaManager != null ) 423 { 424 AttributeType attributeType = schemaManager.getAttributeType( attribute ); 425 426 if ( attributeType == null ) 427 { 428 byte[] bytes = new byte[current]; 429 System.arraycopy( value, 0, bytes, 0, current ); 430 431 return new Value( bytes ); 432 } 433 434 if ( attributeType.getSyntax().isHumanReadable() ) 435 { 436 return new Value( attributeType, Strings.utf8ToString( value, current ) ); 437 } 438 else 439 { 440 byte[] bytes = new byte[current]; 441 System.arraycopy( value, 0, bytes, 0, current ); 442 443 return new Value( attributeType, bytes ); 444 } 445 } 446 else 447 { 448 byte[] bytes = new byte[current]; 449 System.arraycopy( value, 0, bytes, 0, current ); 450 451 return new Value( bytes ); 452 } 453 } 454 else 455 { 456 if ( schemaManager != null ) 457 { 458 AttributeType attributeType = schemaManager.getAttributeType( attribute ); 459 460 if ( attributeType.getEquality().getSyntax().isHumanReadable() ) 461 { 462 return new Value( attributeType, ( String ) null ); 463 } 464 else 465 { 466 return new Value( attributeType, ( byte[] ) null ); 467 } 468 } 469 else 470 { 471 return new Value( ( byte[] ) null ); 472 } 473 } 474 } 475 476 477 /** 478 * An assertion value : 479 * 480 * <pre> 481 * assertionvalue = valueencoding 482 * valueencoding = 0*(normal / escaped) 483 * normal = UTF1SUBSET / UTFMB 484 * escaped = '\' HEX HEX 485 * HEX = '0'-'9' / 'A'-'F' / 'a'-'f' 486 * UTF1SUBSET = %x01-27 / %x2B-5B / %x5D-7F (Everything but '\0', '*', '(', ')' and '\') 487 * UTFMB = UTF2 / UTF3 / UTF4 488 * UTF0 = %x80-BF 489 * UTF2 = %xC2-DF UTF0 490 * UTF3 = %xE0 %xA0-BF UTF0 / %xE1-EC UTF0 UTF0 / %xED %x80-9F UTF0 / %xEE-EF UTF0 UTF0 491 * UTF4 = %xF0 %x90-BF UTF0 UTF0 / %xF1-F3 UTF0 UTF0 UTF0 / %xF4 %x80-8F UTF0 UTF0 492 * </pre> 493 * 494 * With the specific constraints (RFC 4515): 495 * 496 * <pre> 497 * "The <valueencoding> rule ensures that the entire filter string is a" 498 * "valid UTF-8 string and provides that the octets that represent the" 499 * "ASCII characters "*" (ASCII 0x2a), "(" (ASCII 0x28), ")" (ASCII" 500 * "0x29), "\" (ASCII 0x5c), and NUL (ASCII 0x00) are represented as a" 501 * "backslash "\" (ASCII 0x5c) followed by the two hexadecimal digits" 502 * "representing the value of the encoded octet." 503 * </pre> 504 * 505 * The incoming String is already transformed from UTF-8 to unicode, so we must assume that the 506 * grammar we have to check is the following : 507 * 508 * <pre> 509 * assertionvalue = valueencoding 510 * valueencoding = 0*(normal / escaped) 511 * normal = unicodeSubset 512 * escaped = '\' HEX HEX 513 * HEX = '0'-'9' / 'A'-'F' / 'a'-'f' 514 * unicodeSubset = %x01-27 / %x2B-5B / %x5D-FFFF 515 * </pre> 516 * 517 * @param schemaManager The {@link SchemaManager} 518 * @param filterBytes The filter bytes to parse 519 * @param pos The position in the filter bytes 520 * @return The parsed value 521 * @throws ParseException If the value can't be parsed 522 */ 523 private static Value parseAssertionValue( SchemaManager schemaManager, byte[] filterBytes, Position pos ) 524 throws ParseException 525 { 526 byte b = Strings.byteAt( filterBytes, pos.start ); 527 528 // Create a buffer big enough to contain the value once converted 529 byte[] value = new byte[filterBytes.length - pos.start]; 530 int current = 0; 531 532 do 533 { 534 if ( Unicode.isUnicodeSubset( b ) ) 535 { 536 value[current++] = b; 537 pos.start++; 538 } 539 else if ( Strings.isCharASCII( filterBytes, pos.start, '\\' ) ) 540 { 541 // Maybe an escaped 542 pos.start++; 543 544 // First hex 545 if ( Chars.isHex( filterBytes, pos.start ) ) 546 { 547 pos.start++; 548 } 549 else 550 { 551 throw new ParseException( I18n.err( I18n.ERR_13308_NOT_A_VALID_ESCAPED_VALUE ), pos.start ); 552 } 553 554 // second hex 555 if ( Chars.isHex( filterBytes, pos.start ) ) 556 { 557 value[current++] = Hex.getHexValue( filterBytes[pos.start - 1], filterBytes[pos.start] ); 558 pos.start++; 559 } 560 else 561 { 562 throw new ParseException( I18n.err( I18n.ERR_13308_NOT_A_VALID_ESCAPED_VALUE ), pos.start ); 563 } 564 } 565 else 566 { 567 // not a valid char, so let's get out 568 break; 569 } 570 571 b = Strings.byteAt( filterBytes, pos.start ); 572 } 573 while ( b != '\0' ); 574 575 if ( current != 0 ) 576 { 577 byte[] result = new byte[current]; 578 System.arraycopy( value, 0, result, 0, current ); 579 580 return new Value( result ); 581 } 582 else 583 { 584 return new Value( ( byte[] ) null ); 585 } 586 } 587 588 589 /** 590 * Parse a substring 591 * 592 * @param schemaManager The {@link SchemaManager} 593 * @param attribute The filter's attribute 594 * @param initial The filter's initial part 595 * @param filterBytes The filter bytes to parse 596 * @param pos The position in the filter bytes 597 * @return the created {@link ExprNode} 598 * @throws ParseException If the Node can't be parsed 599 * @throws LdapException If we met an error while parsing a filter element 600 */ 601 private static ExprNode parseSubstring( SchemaManager schemaManager, String attribute, Value initial, 602 byte[] filterBytes, Position pos ) throws ParseException, LdapException 603 { 604 SubstringNode node; 605 606 if ( schemaManager != null ) 607 { 608 AttributeType attributeType = schemaManager.lookupAttributeTypeRegistry( attribute ); 609 610 if ( attributeType != null ) 611 { 612 node = new SubstringNode( schemaManager.lookupAttributeTypeRegistry( attribute ) ); 613 } 614 else 615 { 616 return null; 617 } 618 } 619 else 620 { 621 node = new SubstringNode( attribute ); 622 } 623 624 if ( ( initial != null ) && !initial.isNull() ) 625 { 626 // We have a substring starting with a value : val*... 627 // Set the initial value. It must be a String 628 String initialStr = initial.getString(); 629 node.setInitial( initialStr ); 630 } 631 632 if ( Strings.isCharASCII( filterBytes, pos.start, ')' ) ) 633 { 634 // No any or final, we are done 635 return node; 636 } 637 638 // 639 while ( true ) 640 { 641 Value assertionValue = parseAssertionValue( schemaManager, attribute, filterBytes, pos ); 642 643 // Is there anything else but a ')' after the value ? 644 if ( Strings.isCharASCII( filterBytes, pos.start, ')' ) ) 645 { 646 // Nope : as we have had [initial] '*' (any '*' ) *, 647 // this is the final 648 if ( !assertionValue.isNull() ) 649 { 650 String finalStr = assertionValue.getString(); 651 node.setFinal( finalStr ); 652 } 653 654 return node; 655 } 656 else if ( Strings.isCharASCII( filterBytes, pos.start, '*' ) ) 657 { 658 // We have a '*' : it's an any 659 // If the value is empty, that means we have more than 660 // one consecutive '*' : do nothing in this case. 661 if ( !assertionValue.isNull() ) 662 { 663 String anyStr = assertionValue.getString(); 664 node.addAny( anyStr ); 665 } 666 667 pos.start++; 668 669 // Skip any following '*' 670 while ( Strings.isCharASCII( filterBytes, pos.start, '*' ) ) 671 { 672 pos.start++; 673 } 674 675 // that may have been the closing '*' 676 if ( Strings.isCharASCII( filterBytes, pos.start, ')' ) ) 677 { 678 return node; 679 } 680 681 } 682 else 683 { 684 // This is an error 685 throw new ParseException( I18n.err( I18n.ERR_13309_BAD_SUBSTRING ), pos.start ); 686 } 687 } 688 } 689 690 691 /** 692 * Here is the grammar to parse : 693 * <pre> 694 * simple ::= '=' assertionValue 695 * present ::= '=' '*' 696 * substring ::= '=' [initial] any [final] 697 * initial ::= assertionValue 698 * any ::= '*' ( assertionValue '*')* 699 * </pre> 700 * As we can see, there is an ambiguity in the grammar : attr=* can be 701 * seen as a present or as a substring. As stated in the RFC : 702 * 703 * <pre> 704 * "Note that although both the <substring> and <present> productions in" 705 * "the grammar above can produce the "attr=*" construct, this construct" 706 * "is used only to denote a presence filter." (RFC 4515, 3) 707 * </pre> 708 * 709 * We have also to consider the difference between a substring and the 710 * equality node : this last node does not contain a '*' 711 * 712 * @param schemaManager The {@link SchemaManager} 713 * @param attribute The filter's attribute 714 * @param filterBytes The filter bytes to parse 715 * @param pos The position in the filter bytes 716 * @return the created {@link ExprNode} 717 * @throws ParseException If the Node can't be parsed 718 * @throws LdapException If we met an error while parsing a filter element 719 */ 720 private static ExprNode parsePresenceEqOrSubstring( SchemaManager schemaManager, String attribute, byte[] filterBytes, 721 Position pos ) throws ParseException, LdapException 722 { 723 byte b = Strings.byteAt( filterBytes, pos.start ); 724 725 switch ( b ) 726 { 727 case '*' : 728 // To be a present node, the next char should be a ')' 729 pos.start++; 730 731 if ( Strings.isCharASCII( filterBytes, pos.start, ')' ) ) 732 { 733 // This is a present node 734 if ( schemaManager != null ) 735 { 736 AttributeType attributeType = schemaManager.getAttributeType( attribute ); 737 738 if ( attributeType != null ) 739 { 740 return new PresenceNode( attributeType ); 741 } 742 else 743 { 744 return null; 745 } 746 } 747 else 748 { 749 return new PresenceNode( attribute ); 750 } 751 } 752 else 753 { 754 // Definitively a substring with no initial or an error 755 return parseSubstring( schemaManager, attribute, null, filterBytes, pos ); 756 } 757 758 case ')' : 759 // An empty equality Node 760 if ( schemaManager != null ) 761 { 762 AttributeType attributeType = schemaManager.getAttributeType( attribute ); 763 764 if ( attributeType != null ) 765 { 766 return new EqualityNode( attributeType, new Value( ( byte[] ) null ) ); 767 } 768 769 else 770 { 771 return null; 772 } 773 } 774 else 775 { 776 return new EqualityNode( attribute, ( byte[] ) null ); 777 } 778 779 default : 780 // A substring or an equality node 781 Value value = parseAssertionValue( schemaManager, attribute, filterBytes, pos ); 782 783 // Is there anything else but a ')' after the value ? 784 b = Strings.byteAt( filterBytes, pos.start ); 785 786 switch ( b ) 787 { 788 case ')' : 789 // This is an equality node 790 if ( schemaManager != null ) 791 { 792 AttributeType attributeType = schemaManager.getAttributeType( attribute ); 793 794 if ( attributeType != null ) 795 { 796 return new EqualityNode( attributeType, value ); 797 } 798 else 799 { 800 return null; 801 } 802 } 803 else 804 { 805 return new EqualityNode( attribute, value.getBytes() ); 806 } 807 808 case '*' : 809 pos.start++; 810 811 return parseSubstring( schemaManager, attribute, value, filterBytes, pos ); 812 813 814 default : 815 // This is an error 816 throw new ParseException( I18n.err( I18n.ERR_13309_BAD_SUBSTRING ), pos.start ); 817 } 818 } 819 } 820 821 822 /** 823 * Parse the following grammar : 824 * 825 * <pre> 826 * item = simple / present / substring / extensible 827 * simple = attr WSP* filtertype WSP* assertionvalue 828 * filtertype = '=' / '~=' / '>=' / '<=' 829 * present = attr WSP* '=' '*' 830 * substring = attr WSP* '=' WSP* [initial] any [final] 831 * extensible = ( attr [":dn"] [':' oid] ":=" assertionvalue ) 832 * / ( [":dn"] ':' oid ":=" assertionvalue ) 833 * matchingrule = ":" oid 834 * </pre> 835 * An item starts with an attribute or a colon. 836 * 837 * @param schemaManager The {@link SchemaManager} 838 * @param filterBytes The filter bytes to parse 839 * @param pos The position in the filter bytes 840 * @param b The type of item 841 * @param relaxed If the filter is analyzed in relaxed mode or not 842 * @return the created {@link ExprNode} 843 * @throws ParseException If the Node can't be parsed 844 * @throws LdapException If we met an error while parsing a filter element 845 */ 846 @SuppressWarnings({ "rawtypes", }) 847 private static ExprNode parseItem( SchemaManager schemaManager, byte[] filterBytes, Position pos, byte b, 848 boolean relaxed ) throws ParseException, LdapException 849 { 850 String attribute; 851 852 if ( b == '\0' ) 853 { 854 throw new ParseException( I18n.err( I18n.ERR_13310_BAD_CHAR ), pos.start ); 855 } 856 857 if ( b == ':' ) 858 { 859 // If we have a colon, then the item is an extensible one 860 return parseExtensible( schemaManager, null, filterBytes, pos, relaxed ); 861 } 862 else 863 { 864 // We must have an attribute 865 attribute = AttributeUtils.parseAttribute( filterBytes, pos, true, relaxed ); 866 867 // Skip spaces 868 skipWhiteSpaces( filterBytes, pos ); 869 870 // Now, we may have a present, substring, simple or an extensible 871 b = Strings.byteAt( filterBytes, pos.start ); 872 873 switch ( b ) 874 { 875 case '=': 876 // It can be a presence, an equal or a substring 877 pos.start++; 878 879 return parsePresenceEqOrSubstring( schemaManager, attribute, filterBytes, pos ); 880 881 case '~': 882 // Approximate node 883 pos.start++; 884 885 // Check that we have a '=' 886 if ( !Strings.isCharASCII( filterBytes, pos.start, '=' ) ) 887 { 888 throw new ParseException( I18n.err( I18n.ERR_13311_EXPECTING_EQUAL ), pos.start ); 889 } 890 891 pos.start++; 892 893 // Parse the value and create the node 894 if ( schemaManager == null ) 895 { 896 return new ApproximateNode( attribute, parseAssertionValue( schemaManager, attribute, filterBytes, 897 pos ).getBytes() ); 898 } 899 else 900 { 901 AttributeType attributeType = schemaManager.getAttributeType( attribute ); 902 903 if ( attributeType != null ) 904 { 905 return new ApproximateNode( attributeType, parseAssertionValue( schemaManager, attribute, 906 filterBytes, pos ) ); 907 } 908 else 909 { 910 return UndefinedNode.UNDEFINED_NODE; 911 } 912 } 913 914 case '>': 915 // Greater or equal node 916 pos.start++; 917 918 // Check that we have a '=' 919 if ( !Strings.isCharASCII( filterBytes, pos.start, '=' ) ) 920 { 921 throw new ParseException( I18n.err( I18n.ERR_13311_EXPECTING_EQUAL ), pos.start ); 922 } 923 924 pos.start++; 925 926 // Parse the value and create the node 927 if ( schemaManager == null ) 928 { 929 return new GreaterEqNode( attribute, 930 parseAssertionValue( schemaManager, attribute, filterBytes, pos ).getBytes() ); 931 } 932 else 933 { 934 AttributeType attributeType = schemaManager.getAttributeType( attribute ); 935 936 if ( attributeType != null ) 937 { 938 return new GreaterEqNode( attributeType, parseAssertionValue( schemaManager, attribute, 939 filterBytes, pos ) ); 940 } 941 else 942 { 943 return UndefinedNode.UNDEFINED_NODE; 944 } 945 } 946 947 case '<': 948 // Less or equal node 949 pos.start++; 950 951 // Check that we have a '=' 952 if ( !Strings.isCharASCII( filterBytes, pos.start, '=' ) ) 953 { 954 throw new ParseException( I18n.err( I18n.ERR_13311_EXPECTING_EQUAL ), pos.start ); 955 } 956 957 pos.start++; 958 959 // Parse the value and create the node 960 if ( schemaManager == null ) 961 { 962 return new LessEqNode( attribute, 963 parseAssertionValue( schemaManager, attribute, filterBytes, pos ).getBytes() ); 964 } 965 else 966 { 967 AttributeType attributeType = schemaManager.getAttributeType( attribute ); 968 969 if ( attributeType != null ) 970 { 971 return new LessEqNode( attributeType, parseAssertionValue( schemaManager, attribute, 972 filterBytes, pos ) ); 973 } 974 else 975 { 976 return UndefinedNode.UNDEFINED_NODE; 977 } 978 } 979 980 case ':': 981 // An extensible node 982 pos.start++; 983 984 return parseExtensible( schemaManager, attribute, filterBytes, pos, relaxed ); 985 986 default: 987 // This is an error 988 throw new ParseException( I18n.err( I18n.ERR_13312_ITEM_EXPECTED ), pos.start ); 989 } 990 } 991 } 992 993 994 /** 995 * Parse AND, OR and NOT nodes : 996 * <pre> 997 * and = '&' filterlist 998 * or = '|' filterlist 999 * not = '!' filter 1000 * filterlist = 1*filter 1001 * </pre> 1002 * 1003 * @param schemaManager The {@link SchemaManager} 1004 * @param node The node to feed 1005 * @param filterBytes The filter bytes to parse 1006 * @param pos The position in the filter bytes 1007 * @param relaxed If the filter is analyzed in relaxed mode or not 1008 * @return the created {@link ExprNode} 1009 * @throws ParseException If the Node can't be parsed 1010 * @throws LdapException If we met an error while parsing a filter element 1011 */ 1012 private static ExprNode parseBranchNode( SchemaManager schemaManager, ExprNode node, byte[] filterBytes, Position pos, 1013 boolean relaxed ) throws ParseException, LdapException 1014 { 1015 BranchNode branchNode = ( BranchNode ) node; 1016 int nbChildren = 0; 1017 1018 // We must have at least one filter 1019 ExprNode child = parseFilterInternal( schemaManager, filterBytes, pos, relaxed ); 1020 1021 if ( child != UndefinedNode.UNDEFINED_NODE ) 1022 { 1023 // Add the child to the node children 1024 branchNode.addNode( child ); 1025 1026 if ( branchNode instanceof NotNode ) 1027 { 1028 return node; 1029 } 1030 1031 nbChildren++; 1032 } 1033 else if ( node instanceof AndNode ) 1034 { 1035 return UndefinedNode.UNDEFINED_NODE; 1036 } 1037 1038 // Now, iterate recusively though all the remaining filters, if any 1039 while ( ( child = parseFilterInternal( schemaManager, filterBytes, pos, relaxed ) ) != UndefinedNode.UNDEFINED_NODE ) 1040 { 1041 // Add the child to the node children if not null 1042 if ( child != null ) 1043 { 1044 branchNode.addNode( child ); 1045 nbChildren++; 1046 } 1047 else if ( node instanceof AndNode ) 1048 { 1049 return UndefinedNode.UNDEFINED_NODE; 1050 } 1051 } 1052 1053 if ( nbChildren > 0 ) 1054 { 1055 return node; 1056 } 1057 else 1058 { 1059 return UndefinedNode.UNDEFINED_NODE; 1060 } 1061 } 1062 1063 1064 /** 1065 * <pre> 1066 * filtercomp = and / or / not / item 1067 * and = '&' WSP* filterlist 1068 * or = '|' WSP* filterlist 1069 * not = '!' WSP* filter 1070 * item = simple / present / substring / extensible 1071 * simple = attr WSP* filtertype WSP* assertionvalue 1072 * present = attr WSP* EQUALS ASTERISK 1073 * substring = attr WSP* EQUALS WSP* [initial] any [final] 1074 * extensible = ( attr [dnattrs] 1075 * [matchingrule] COLON EQUALS assertionvalue ) 1076 * / ( [dnattrs] 1077 * matchingrule COLON EQUALS assertionvalue ) 1078 * </pre> 1079 * 1080 * @param schemaManager The {@link SchemaManager} 1081 * @param filterBytes The filter bytes to parse 1082 * @param pos The position in the filter bytes 1083 * @param relaxed If the filter is analyzed in relaxed mode or not 1084 * @return the created {@link ExprNode} 1085 * @throws ParseException If the Node can't be parsed 1086 * @throws LdapException If we met an error while parsing a filter element 1087 */ 1088 private static ExprNode parseFilterComp( SchemaManager schemaManager, byte[] filterBytes, Position pos, 1089 boolean relaxed ) throws ParseException, LdapException 1090 { 1091 ExprNode node; 1092 1093 if ( pos.start == pos.length ) 1094 { 1095 throw new ParseException( I18n.err( I18n.ERR_13313_EMPTY_FILTERCOMP ), pos.start ); 1096 } 1097 1098 byte b = Strings.byteAt( filterBytes, pos.start ); 1099 1100 switch ( b ) 1101 { 1102 case '&': 1103 // This is a AND node 1104 pos.start++; 1105 1106 // Skip spaces 1107 skipWhiteSpaces( filterBytes, pos ); 1108 1109 node = new AndNode(); 1110 node = parseBranchNode( schemaManager, node, filterBytes, pos, relaxed ); 1111 break; 1112 1113 case '|': 1114 // This is an OR node 1115 pos.start++; 1116 1117 // Skip spaces 1118 skipWhiteSpaces( filterBytes, pos ); 1119 1120 node = new OrNode(); 1121 node = parseBranchNode( schemaManager, node, filterBytes, pos, relaxed ); 1122 break; 1123 1124 case '!': 1125 // This is a NOT node 1126 pos.start++; 1127 1128 // Skip spaces 1129 skipWhiteSpaces( filterBytes, pos ); 1130 1131 node = new NotNode(); 1132 node = parseBranchNode( schemaManager, node, filterBytes, pos, relaxed ); 1133 break; 1134 1135 default: 1136 // This is an item 1137 node = parseItem( schemaManager, filterBytes, pos, b, relaxed ); 1138 break; 1139 } 1140 1141 return node; 1142 } 1143 1144 1145 /** 1146 * Parse the grammar rule : 1147 * <pre> 1148 * filter ::= WSP* '(' WSP* filterComp WSP* ')' WSP* 1149 * </pre> 1150 * 1151 * @param schemaManager The {@link SchemaManager} 1152 * @param filterBytes The filter bytes to parse 1153 * @param pos The position in the filter bytes 1154 * @param relaxed If the filter is analyzed in relaxed mode or not 1155 * @return the created {@link ExprNode} 1156 * @throws ParseException If the Node can't be parsed 1157 * @throws LdapException If we met an error while parsing a filter element 1158 */ 1159 private static ExprNode parseFilterInternal( SchemaManager schemaManager, byte[] filterBytes, Position pos, 1160 boolean relaxed ) throws ParseException, LdapException 1161 { 1162 // Skip spaces 1163 skipWhiteSpaces( filterBytes, pos ); 1164 1165 // Check for the left '(' 1166 if ( !Strings.isCharASCII( filterBytes, pos.start, '(' ) ) 1167 { 1168 // No more node, get out 1169 if ( ( pos.start == 0 ) && ( pos.length != 0 ) ) 1170 { 1171 throw new ParseException( I18n.err( I18n.ERR_13314_FILTER_MISSING_OPEN_PAR ), 0 ); 1172 } 1173 else 1174 { 1175 return UndefinedNode.UNDEFINED_NODE; 1176 } 1177 } 1178 1179 pos.start++; 1180 1181 // Skip spaces 1182 skipWhiteSpaces( filterBytes, pos ); 1183 1184 // parse the filter component 1185 ExprNode node = parseFilterComp( schemaManager, filterBytes, pos, relaxed ); 1186 1187 if ( node == UndefinedNode.UNDEFINED_NODE ) 1188 { 1189 return UndefinedNode.UNDEFINED_NODE; 1190 } 1191 1192 // Skip spaces 1193 skipWhiteSpaces( filterBytes, pos ); 1194 1195 // Check that we have a right ')' 1196 if ( !Strings.isCharASCII( filterBytes, pos.start, ')' ) ) 1197 { 1198 throw new ParseException( I18n.err( I18n.ERR_13315_FILTER_MISSING_CLOSE_PAR ), pos.start ); 1199 } 1200 1201 pos.start++; 1202 1203 // Skip spaces 1204 skipWhiteSpaces( filterBytes, pos ); 1205 1206 return node; 1207 } 1208}