0N/A/*
2362N/A * Copyright (c) 1999, Oracle and/or its affiliates. All rights reserved.
0N/A * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0N/A *
0N/A * This code is free software; you can redistribute it and/or modify it
0N/A * under the terms of the GNU General Public License version 2 only, as
2362N/A * published by the Free Software Foundation. Oracle designates this
0N/A * particular file as subject to the "Classpath" exception as provided
2362N/A * by Oracle in the LICENSE file that accompanied this code.
0N/A *
0N/A * This code is distributed in the hope that it will be useful, but WITHOUT
0N/A * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0N/A * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0N/A * version 2 for more details (a copy is included in the LICENSE file that
0N/A * accompanied this code).
0N/A *
0N/A * You should have received a copy of the GNU General Public License version
0N/A * 2 along with this work; if not, write to the Free Software Foundation,
0N/A * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0N/A *
2362N/A * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
2362N/A * or visit www.oracle.com if you need additional information or have any
2362N/A * questions.
0N/A */
0N/Apackage com.sun.jndi.toolkit.dir;
0N/A
0N/Aimport javax.naming.*;
0N/Aimport javax.naming.directory.*;
0N/Aimport java.util.Enumeration;
0N/Aimport java.util.StringTokenizer;
0N/Aimport java.util.Vector;
0N/A
0N/A/**
0N/A * A class for parsing LDAP search filters (defined in RFC 1960, 2254)
0N/A *
0N/A * @author Jon Ruiz
0N/A * @author Rosanna Lee
0N/A */
0N/Apublic class SearchFilter implements AttrFilter {
0N/A
0N/A interface StringFilter extends AttrFilter {
0N/A public void parse() throws InvalidSearchFilterException;
0N/A }
0N/A
0N/A // %%% "filter" and "pos" are not declared "private" due to bug 4064984.
0N/A String filter;
0N/A int pos;
0N/A private StringFilter rootFilter;
0N/A
0N/A protected static final boolean debug = false;
0N/A
0N/A protected static final char BEGIN_FILTER_TOKEN = '(';
0N/A protected static final char END_FILTER_TOKEN = ')';
0N/A protected static final char AND_TOKEN = '&';
0N/A protected static final char OR_TOKEN = '|';
0N/A protected static final char NOT_TOKEN = '!';
0N/A protected static final char EQUAL_TOKEN = '=';
0N/A protected static final char APPROX_TOKEN = '~';
0N/A protected static final char LESS_TOKEN = '<';
0N/A protected static final char GREATER_TOKEN = '>';
0N/A protected static final char EXTEND_TOKEN = ':';
0N/A protected static final char WILDCARD_TOKEN = '*';
0N/A
0N/A public SearchFilter(String filter) throws InvalidSearchFilterException {
0N/A this.filter = filter;
0N/A pos = 0;
0N/A normalizeFilter();
0N/A rootFilter = this.createNextFilter();
0N/A }
0N/A
0N/A // Returns true if targetAttrs passes the filter
0N/A public boolean check(Attributes targetAttrs) throws NamingException {
0N/A if (targetAttrs == null)
0N/A return false;
0N/A
0N/A return rootFilter.check(targetAttrs);
0N/A }
0N/A
0N/A /*
0N/A * Utility routines used by member classes
0N/A */
0N/A
0N/A // does some pre-processing on the string to make it look exactly lik
0N/A // what the parser expects. This only needs to be called once.
0N/A protected void normalizeFilter() {
0N/A skipWhiteSpace(); // get rid of any leading whitespaces
0N/A
0N/A // Sometimes, search filters don't have "(" and ")" - add them
0N/A if(getCurrentChar() != BEGIN_FILTER_TOKEN) {
0N/A filter = BEGIN_FILTER_TOKEN + filter + END_FILTER_TOKEN;
0N/A }
0N/A // this would be a good place to strip whitespace if desired
0N/A
0N/A if(debug) {System.out.println("SearchFilter: normalized filter:" +
0N/A filter);}
0N/A }
0N/A
0N/A private void skipWhiteSpace() {
0N/A while (Character.isWhitespace(getCurrentChar())) {
0N/A consumeChar();
0N/A }
0N/A }
0N/A
0N/A protected StringFilter createNextFilter()
0N/A throws InvalidSearchFilterException {
0N/A StringFilter filter;
0N/A
0N/A skipWhiteSpace();
0N/A
0N/A try {
0N/A // make sure every filter starts with "("
0N/A if(getCurrentChar() != BEGIN_FILTER_TOKEN) {
0N/A throw new InvalidSearchFilterException("expected \"" +
0N/A BEGIN_FILTER_TOKEN +
0N/A "\" at position " +
0N/A pos);
0N/A }
0N/A
0N/A // skip past the "("
0N/A this.consumeChar();
0N/A
0N/A skipWhiteSpace();
0N/A
0N/A // use the next character to determine the type of filter
0N/A switch(getCurrentChar()) {
0N/A case AND_TOKEN:
0N/A if (debug) {System.out.println("SearchFilter: creating AND");}
0N/A filter = new CompoundFilter(true);
0N/A filter.parse();
0N/A break;
0N/A case OR_TOKEN:
0N/A if (debug) {System.out.println("SearchFilter: creating OR");}
0N/A filter = new CompoundFilter(false);
0N/A filter.parse();
0N/A break;
0N/A case NOT_TOKEN:
0N/A if (debug) {System.out.println("SearchFilter: creating OR");}
0N/A filter = new NotFilter();
0N/A filter.parse();
0N/A break;
0N/A default:
0N/A if (debug) {System.out.println("SearchFilter: creating SIMPLE");}
0N/A filter = new AtomicFilter();
0N/A filter.parse();
0N/A break;
0N/A }
0N/A
0N/A skipWhiteSpace();
0N/A
0N/A // make sure every filter ends with ")"
0N/A if(getCurrentChar() != END_FILTER_TOKEN) {
0N/A throw new InvalidSearchFilterException("expected \"" +
0N/A END_FILTER_TOKEN +
0N/A "\" at position " +
0N/A pos);
0N/A }
0N/A
0N/A // skip past the ")"
0N/A this.consumeChar();
0N/A } catch (InvalidSearchFilterException e) {
0N/A if (debug) {System.out.println("rethrowing e");}
0N/A throw e; // just rethrow these
0N/A
0N/A // catch all - any uncaught exception while parsing will end up here
0N/A } catch (Exception e) {
0N/A if(debug) {System.out.println(e.getMessage());e.printStackTrace();}
0N/A throw new InvalidSearchFilterException("Unable to parse " +
0N/A "character " + pos + " in \""+
0N/A this.filter + "\"");
0N/A }
0N/A
0N/A return filter;
0N/A }
0N/A
0N/A protected char getCurrentChar() {
0N/A return filter.charAt(pos);
0N/A }
0N/A
0N/A protected char relCharAt(int i) {
0N/A return filter.charAt(pos + i);
0N/A }
0N/A
0N/A protected void consumeChar() {
0N/A pos++;
0N/A }
0N/A
0N/A protected void consumeChars(int i) {
0N/A pos += i;
0N/A }
0N/A
0N/A protected int relIndexOf(int ch) {
0N/A return filter.indexOf(ch, pos) - pos;
0N/A }
0N/A
0N/A protected String relSubstring(int beginIndex, int endIndex){
0N/A if(debug){System.out.println("relSubString: " + beginIndex +
0N/A " " + endIndex);}
0N/A return filter.substring(beginIndex+pos, endIndex+pos);
0N/A }
0N/A
0N/A
0N/A /**
0N/A * A class for dealing with compound filters ("and" & "or" filters).
0N/A */
0N/A final class CompoundFilter implements StringFilter {
0N/A private Vector subFilters;
0N/A private boolean polarity;
0N/A
0N/A CompoundFilter(boolean polarity) {
0N/A subFilters = new Vector();
0N/A this.polarity = polarity;
0N/A }
0N/A
0N/A public void parse() throws InvalidSearchFilterException {
0N/A SearchFilter.this.consumeChar(); // consume the "&"
0N/A while(SearchFilter.this.getCurrentChar() != END_FILTER_TOKEN) {
0N/A if (debug) {System.out.println("CompoundFilter: adding");}
0N/A StringFilter filter = SearchFilter.this.createNextFilter();
0N/A subFilters.addElement(filter);
0N/A skipWhiteSpace();
0N/A }
0N/A }
0N/A
0N/A public boolean check(Attributes targetAttrs) throws NamingException {
0N/A for(int i = 0; i<subFilters.size(); i++) {
0N/A StringFilter filter = (StringFilter)subFilters.elementAt(i);
0N/A if(filter.check(targetAttrs) != this.polarity) {
0N/A return !polarity;
0N/A }
0N/A }
0N/A return polarity;
0N/A }
0N/A } /* CompoundFilter */
0N/A
0N/A /**
0N/A * A class for dealing with NOT filters
0N/A */
0N/A final class NotFilter implements StringFilter {
0N/A private StringFilter filter;
0N/A
0N/A public void parse() throws InvalidSearchFilterException {
0N/A SearchFilter.this.consumeChar(); // consume the "!"
0N/A filter = SearchFilter.this.createNextFilter();
0N/A }
0N/A
0N/A public boolean check(Attributes targetAttrs) throws NamingException {
0N/A return !filter.check(targetAttrs);
0N/A }
0N/A } /* notFilter */
0N/A
0N/A // note: declared here since member classes can't have static variables
0N/A static final int EQUAL_MATCH = 1;
0N/A static final int APPROX_MATCH = 2;
0N/A static final int GREATER_MATCH = 3;
0N/A static final int LESS_MATCH = 4;
0N/A
0N/A /**
0N/A * A class for dealing wtih atomic filters
0N/A */
0N/A final class AtomicFilter implements StringFilter {
0N/A private String attrID;
0N/A private String value;
0N/A private int matchType;
0N/A
0N/A public void parse() throws InvalidSearchFilterException {
0N/A
0N/A skipWhiteSpace();
0N/A
0N/A try {
0N/A // find the end
0N/A int endPos = SearchFilter.this.relIndexOf(END_FILTER_TOKEN);
0N/A
0N/A //determine the match type
0N/A int i = SearchFilter.this.relIndexOf(EQUAL_TOKEN);
0N/A if(debug) {System.out.println("AtomicFilter: = at " + i);}
0N/A int qualifier = SearchFilter.this.relCharAt(i-1);
0N/A switch(qualifier) {
0N/A case APPROX_TOKEN:
0N/A if (debug) {System.out.println("Atomic: APPROX found");}
0N/A matchType = APPROX_MATCH;
0N/A attrID = SearchFilter.this.relSubstring(0, i-1);
0N/A value = SearchFilter.this.relSubstring(i+1, endPos);
0N/A break;
0N/A
0N/A case GREATER_TOKEN:
0N/A if (debug) {System.out.println("Atomic: GREATER found");}
0N/A matchType = GREATER_MATCH;
0N/A attrID = SearchFilter.this.relSubstring(0, i-1);
0N/A value = SearchFilter.this.relSubstring(i+1, endPos);
0N/A break;
0N/A
0N/A case LESS_TOKEN:
0N/A if (debug) {System.out.println("Atomic: LESS found");}
0N/A matchType = LESS_MATCH;
0N/A attrID = SearchFilter.this.relSubstring(0, i-1);
0N/A value = SearchFilter.this.relSubstring(i+1, endPos);
0N/A break;
0N/A
0N/A case EXTEND_TOKEN:
0N/A if(debug) {System.out.println("Atomic: EXTEND found");}
0N/A throw new OperationNotSupportedException("Extensible match not supported");
0N/A
0N/A default:
0N/A if (debug) {System.out.println("Atomic: EQUAL found");}
0N/A matchType = EQUAL_MATCH;
0N/A attrID = SearchFilter.this.relSubstring(0,i);
0N/A value = SearchFilter.this.relSubstring(i+1, endPos);
0N/A break;
0N/A }
0N/A
0N/A attrID = attrID.trim();
0N/A value = value.trim();
0N/A
0N/A //update our position
0N/A SearchFilter.this.consumeChars(endPos);
0N/A
0N/A } catch (Exception e) {
0N/A if (debug) {System.out.println(e.getMessage());
0N/A e.printStackTrace();}
0N/A InvalidSearchFilterException sfe =
0N/A new InvalidSearchFilterException("Unable to parse " +
0N/A "character " + SearchFilter.this.pos + " in \""+
0N/A SearchFilter.this.filter + "\"");
0N/A sfe.setRootCause(e);
0N/A throw(sfe);
0N/A }
0N/A
0N/A if(debug) {System.out.println("AtomicFilter: " + attrID + "=" +
0N/A value);}
0N/A }
0N/A
0N/A public boolean check(Attributes targetAttrs) {
0N/A Enumeration candidates;
0N/A
0N/A try {
0N/A Attribute attr = targetAttrs.get(attrID);
0N/A if(attr == null) {
0N/A return false;
0N/A }
0N/A candidates = attr.getAll();
0N/A } catch (NamingException ne) {
0N/A if (debug) {System.out.println("AtomicFilter: should never " +
0N/A "here");}
0N/A return false;
0N/A }
0N/A
0N/A while(candidates.hasMoreElements()) {
0N/A String val = candidates.nextElement().toString();
0N/A if (debug) {System.out.println("Atomic: comparing: " + val);}
0N/A switch(matchType) {
0N/A case APPROX_MATCH:
0N/A case EQUAL_MATCH:
0N/A if(substringMatch(this.value, val)) {
0N/A if (debug) {System.out.println("Atomic: EQUAL match");}
0N/A return true;
0N/A }
0N/A break;
0N/A case GREATER_MATCH:
0N/A if (debug) {System.out.println("Atomic: GREATER match");}
0N/A if(val.compareTo(this.value) >= 0) {
0N/A return true;
0N/A }
0N/A break;
0N/A case LESS_MATCH:
0N/A if (debug) {System.out.println("Atomic: LESS match");}
0N/A if(val.compareTo(this.value) <= 0) {
0N/A return true;
0N/A }
0N/A break;
0N/A default:
0N/A if (debug) {System.out.println("AtomicFilter: unkown " +
0N/A "matchType");}
0N/A }
0N/A }
0N/A return false;
0N/A }
0N/A
0N/A // used for substring comparisons (where proto has "*" wildcards
0N/A private boolean substringMatch(String proto, String value) {
0N/A // simple case 1: "*" means attribute presence is being tested
0N/A if(proto.equals(new Character(WILDCARD_TOKEN).toString())) {
0N/A if(debug) {System.out.println("simple presence assertion");}
0N/A return true;
0N/A }
0N/A
0N/A // simple case 2: if there are no wildcards, call String.equals()
0N/A if(proto.indexOf(WILDCARD_TOKEN) == -1) {
0N/A return proto.equalsIgnoreCase(value);
0N/A }
0N/A
0N/A if(debug) {System.out.println("doing substring comparison");}
0N/A // do the work: make sure all the substrings are present
0N/A int currentPos = 0;
0N/A StringTokenizer subStrs = new StringTokenizer(proto, "*", false);
0N/A
0N/A // do we need to begin with the first token?
0N/A if(proto.charAt(0) != WILDCARD_TOKEN &&
0N/A !value.toString().toLowerCase().startsWith(
0N/A subStrs.nextToken().toLowerCase())) {
0N/A if(debug) {System.out.println("faild initial test");}
0N/A return false;
0N/A }
0N/A
0N/A
0N/A while(subStrs.hasMoreTokens()) {
0N/A String currentStr = subStrs.nextToken();
0N/A if (debug) {System.out.println("looking for \"" +
0N/A currentStr +"\"");}
0N/A currentPos = value.toLowerCase().indexOf(
0N/A currentStr.toLowerCase(), currentPos);
0N/A if(currentPos == -1) {
0N/A return false;
0N/A }
0N/A currentPos += currentStr.length();
0N/A }
0N/A
0N/A // do we need to end with the last token?
0N/A if(proto.charAt(proto.length() - 1) != WILDCARD_TOKEN &&
0N/A currentPos != value.length() ) {
0N/A if(debug) {System.out.println("faild final test");}
0N/A return false;
0N/A }
0N/A
0N/A return true;
0N/A }
0N/A
0N/A } /* AtomicFilter */
0N/A
0N/A // ----- static methods for producing string filters given attribute set
0N/A // ----- or object array
0N/A
0N/A
0N/A /**
0N/A * Creates an LDAP filter as a conjuction of the attributes supplied.
0N/A */
0N/A public static String format(Attributes attrs) throws NamingException {
0N/A if (attrs == null || attrs.size() == 0) {
0N/A return "objectClass=*";
0N/A }
0N/A
0N/A String answer;
0N/A answer = "(& ";
0N/A Attribute attr;
0N/A for (NamingEnumeration e = attrs.getAll(); e.hasMore(); ) {
0N/A attr = (Attribute)e.next();
0N/A if (attr.size() == 0 || (attr.size() == 1 && attr.get() == null)) {
0N/A // only checking presence of attribute
0N/A answer += "(" + attr.getID() + "=" + "*)";
0N/A } else {
0N/A for (NamingEnumeration ve = attr.getAll();
0N/A ve.hasMore();
0N/A ) {
0N/A String val = getEncodedStringRep(ve.next());
0N/A if (val != null) {
0N/A answer += "(" + attr.getID() + "=" + val + ")";
0N/A }
0N/A }
0N/A }
0N/A }
0N/A
0N/A answer += ")";
0N/A //System.out.println("filter: " + answer);
0N/A return answer;
0N/A }
0N/A
0N/A // Writes the hex representation of a byte to a StringBuffer.
0N/A private static void hexDigit(StringBuffer buf, byte x) {
0N/A char c;
0N/A
0N/A c = (char) ((x >> 4) & 0xf);
0N/A if (c > 9)
0N/A c = (char) ((c-10) + 'A');
0N/A else
0N/A c = (char)(c + '0');
0N/A
0N/A buf.append(c);
0N/A c = (char) (x & 0xf);
0N/A if (c > 9)
0N/A c = (char)((c-10) + 'A');
0N/A else
0N/A c = (char)(c + '0');
0N/A buf.append(c);
0N/A }
0N/A
0N/A
0N/A /**
0N/A * Returns the string representation of an object (such as an attr value).
0N/A * If obj is a byte array, encode each item as \xx, where xx is hex encoding
0N/A * of the byte value.
0N/A * Else, if obj is not a String, use its string representation (toString()).
0N/A * Special characters in obj (or its string representation) are then
0N/A * encoded appropriately according to RFC 2254.
0N/A * * \2a
0N/A * ( \28
0N/A * ) \29
0N/A * \ \5c
0N/A * NUL \00
0N/A */
0N/A private static String getEncodedStringRep(Object obj) throws NamingException {
0N/A String str;
0N/A if (obj == null)
0N/A return null;
0N/A
0N/A if (obj instanceof byte[]) {
0N/A // binary data must be encoded as \hh where hh is a hex char
0N/A byte[] bytes = (byte[])obj;
0N/A StringBuffer b1 = new StringBuffer(bytes.length*3);
0N/A for (int i = 0; i < bytes.length; i++) {
0N/A b1.append('\\');
0N/A hexDigit(b1, bytes[i]);
0N/A }
0N/A return b1.toString();
0N/A }
0N/A if (!(obj instanceof String)) {
0N/A str = obj.toString();
0N/A } else {
0N/A str = (String)obj;
0N/A }
0N/A int len = str.length();
0N/A StringBuffer buf = new StringBuffer(len);
0N/A char ch;
0N/A for (int i = 0; i < len; i++) {
0N/A switch (ch=str.charAt(i)) {
0N/A case '*':
0N/A buf.append("\\2a");
0N/A break;
0N/A case '(':
0N/A buf.append("\\28");
0N/A break;
0N/A case ')':
0N/A buf.append("\\29");
0N/A break;
0N/A case '\\':
0N/A buf.append("\\5c");
0N/A break;
0N/A case 0:
0N/A buf.append("\\00");
0N/A break;
0N/A default:
0N/A buf.append(ch);
0N/A }
0N/A }
0N/A return buf.toString();
0N/A }
0N/A
0N/A
0N/A /**
0N/A * Finds the first occurrence of <tt>ch</tt> in <tt>val</tt> starting
0N/A * from position <tt>start</tt>. It doesn't count if <tt>ch</tt>
0N/A * has been escaped by a backslash (\)
0N/A */
0N/A public static int findUnescaped(char ch, String val, int start) {
0N/A int len = val.length();
0N/A
0N/A while (start < len) {
0N/A int where = val.indexOf(ch, start);
0N/A // if at start of string, or not there at all, or if not escaped
0N/A if (where == start || where == -1 || val.charAt(where-1) != '\\')
0N/A return where;
0N/A
0N/A // start search after escaped star
0N/A start = where + 1;
0N/A }
0N/A return -1;
0N/A }
0N/A
0N/A /**
0N/A * Formats the expression <tt>expr</tt> using arguments from the array
0N/A * <tt>args</tt>.
0N/A *
0N/A * <code>{i}</code> specifies the <code>i</code>'th element from
0N/A * the array <code>args</code> is to be substituted for the
0N/A * string "<code>{i}</code>".
0N/A *
0N/A * To escape '{' or '}' (or any other character), use '\'.
0N/A *
0N/A * Uses getEncodedStringRep() to do encoding.
0N/A */
0N/A
0N/A public static String format(String expr, Object[] args)
0N/A throws NamingException {
0N/A
0N/A int param;
0N/A int where = 0, start = 0;
0N/A StringBuffer answer = new StringBuffer(expr.length());
0N/A
0N/A while ((where = findUnescaped('{', expr, start)) >= 0) {
0N/A int pstart = where + 1; // skip '{'
0N/A int pend = expr.indexOf('}', pstart);
0N/A
0N/A if (pend < 0) {
0N/A throw new InvalidSearchFilterException("unbalanced {: " + expr);
0N/A }
0N/A
0N/A // at this point, pend should be pointing at '}'
0N/A try {
0N/A param = Integer.parseInt(expr.substring(pstart, pend));
0N/A } catch (NumberFormatException e) {
0N/A throw new InvalidSearchFilterException(
0N/A "integer expected inside {}: " + expr);
0N/A }
0N/A
0N/A if (param >= args.length) {
0N/A throw new InvalidSearchFilterException(
0N/A "number exceeds argument list: " + param);
0N/A }
0N/A
0N/A answer.append(expr.substring(start, where)).append(getEncodedStringRep(args[param]));
0N/A start = pend + 1; // skip '}'
0N/A }
0N/A
0N/A if (start < expr.length())
0N/A answer.append(expr.substring(start));
0N/A
0N/A return answer.toString();
0N/A }
0N/A
0N/A /*
0N/A * returns an Attributes instance containing only attributeIDs given in
0N/A * "attributeIDs" whose values come from the given DSContext.
0N/A */
0N/A public static Attributes selectAttributes(Attributes originals,
0N/A String[] attrIDs) throws NamingException {
0N/A
0N/A if (attrIDs == null)
0N/A return originals;
0N/A
0N/A Attributes result = new BasicAttributes();
0N/A
0N/A for(int i=0; i<attrIDs.length; i++) {
0N/A Attribute attr = originals.get(attrIDs[i]);
0N/A if(attr != null) {
0N/A result.put(attr);
0N/A }
0N/A }
0N/A
0N/A return result;
0N/A }
0N/A
0N/A/* For testing filter
0N/A public static void main(String[] args) {
0N/A
0N/A Attributes attrs = new BasicAttributes(LdapClient.caseIgnore);
0N/A attrs.put("cn", "Rosanna Lee");
0N/A attrs.put("sn", "Lee");
0N/A attrs.put("fn", "Rosanna");
0N/A attrs.put("id", "10414");
0N/A attrs.put("machine", "jurassic");
0N/A
0N/A
0N/A try {
0N/A System.out.println(format(attrs));
0N/A
0N/A String expr = "(&(Age = {0})(Account Balance <= {1}))";
0N/A Object[] fargs = new Object[2];
0N/A // fill in the parameters
0N/A fargs[0] = new Integer(65);
0N/A fargs[1] = new Float(5000);
0N/A
0N/A System.out.println(format(expr, fargs));
0N/A
0N/A
0N/A System.out.println(format("bin={0}",
0N/A new Object[] {new byte[] {0, 1, 2, 3, 4, 5}}));
0N/A
0N/A System.out.println(format("bin=\\{anything}", null));
0N/A
0N/A } catch (NamingException e) {
0N/A e.printStackTrace();
0N/A }
0N/A }
0N/A*/
0N/A
0N/A}