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