/******************************************************************************* * Copyright (c) 2008 * The code, documentation and other materials contained herein have been * licensed under the Eclipse Public License - v 1.0 by the individual * copyright holders listed below, as Initial Contributors under such license. * The text of such license is available at * http://www.eclipse.org/legal/epl-v10.html. * * Contributors: * Henrik Lindberg *******************************************************************************/ package org.eclipse.equinox.p2.authoring.forms.validators; import java.text.CharacterIterator; import java.text.StringCharacterIterator; import org.eclipse.equinox.p2.authoring.forms.EditAdapter; /** * Validates that the input string can represent an instance of an LDAP filter as described in RFC 2254, but without * LDAP extensions, or OCTET STRING * * This validator accepts empty input as valid. See {@link RequiredValidator} if a warning or error is needed on empty * input. * * @author Henrik Lindberg * */ public class LDAPFilterValidator implements IEditValidator { private static LDAPFilterValidator s_instance; public static LDAPFilterValidator instance() { if(s_instance == null) s_instance = new LDAPFilterValidator(); return s_instance; } public boolean isValid(String input, EditAdapter editAdapter) { if(input != null && input.length() > 0) { try { parse(input); } catch(IllegalArgumentException e) { editAdapter.setErrorMessage("Invalid format"); return false; } catch(ParseException e) { editAdapter.setErrorMessage("Invalid format: " + e.message + " : at position " + Integer.toString(e.index)); return false; } } editAdapter.clearMessages(); return true; } public String inputFilter(String inputString) { return null; } private void parse(String input) throws ParseException { StringCharacterIterator citor = new StringCharacterIterator(input); char c = skipWhitespace(citor); if(c == CharacterIterator.DONE) return; // empty input is ok expectFilter(citor); c = skipWhitespace(citor); if(c != CharacterIterator.DONE) throw new ParseException("Trailing '"+ safeCharToString(c)+"' found after valid expression", citor.getIndex()); } public void expectFilter(StringCharacterIterator citor) throws ParseException { char c = skipWhitespace(citor); if(c != '(') throw new ParseException("Filter must be enclosed in parentheses", citor.getIndex()); else c = citor.next(); switch(c) { case StringCharacterIterator.DONE: throw new ParseException("Reached end while expecting a filter specification", -1); case '&': citor.next(); expectFilterList(citor); break; case '|': citor.next(); expectFilterList(citor); break; case '!': citor.next(); expectFilter(citor); break; default: expectItem(citor); break; } // matching RPAR at end c = skipWhitespace(citor); if(c != ')') throw new ParseException("Expected ')' - got " + safeCharToString(c), citor.getIndex()); citor.next(); // consume the ')' } private String safeCharToString(char c) { if(c == StringCharacterIterator.DONE) return "END"; return Character.toString(c); } public void expectFilterList(StringCharacterIterator citor) throws ParseException { // Expects at least 1 filter char c = StringCharacterIterator.DONE; do { expectFilter(citor); c = skipWhitespace(citor); } while(c == '('); } public void expectItem(StringCharacterIterator citor) throws ParseException { expectName(citor); expectOperator(citor); expectValue(citor); } public void expectName(StringCharacterIterator citor) throws ParseException { // allow sequence of a-zA-Z0-9-. char c = citor.current(); do { if(!isLetter(c)) throw new ParseException("attribute name must start with a letter", citor.getIndex()); do { c = citor.next(); } while(isWordChar(c)); if(c != '.') break; c = citor.next(); } while(true); } /** * Allows a-z and A-Z * * @param c * @return */ private boolean isLetter(char c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); } /** * Allows 0-9 * * @param c * @return */ private boolean isDigit(char c) { return c >= '0' && c <= '9'; } private boolean isHexDigit(char c) { return isDigit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); } /** * Allows Letter, Digit or Hyphen * * @param c * @return */ private boolean isWordChar(char c) { return isLetter(c) || isDigit(c) || c == '-'; } public void expectOperator(StringCharacterIterator citor) throws ParseException { switch(citor.current()) { case '=': citor.next(); return; case '<': citor.next(); break; case '>': citor.next(); break; case '�': if(citor.next() != '=') throw new ParseException("Expected '=' after '~'", citor.getIndex()); return; default: throw new ParseException("Expression does not have a valid operator", citor.getIndex()); } // end up here for < and > which can be followed by "=", but does not have to if(citor.current() != '=') return; // skip the '=' citor.next(); return; } public void expectValue(StringCharacterIterator citor) throws ParseException { char c = citor.current(); int length = 0; boolean lastWasAsterix = false; for(;;) { switch(c) { case StringCharacterIterator.DONE: case ')': case '(': // not allowed in value - terminators if(length < 1) throw new ParseException("value in expression can not be empty", citor.getIndex()); return; case '*': if(lastWasAsterix) throw new ParseException("two adjacent '*' not allowed", citor.getIndex()); lastWasAsterix = true; c = citor.next(); length++; break; case 0x5c: // backslash if(!(isHexDigit(citor.next()) && isHexDigit(citor.next()))) throw new ParseException("'\' must be followed by two hex digits", citor.getIndex()); lastWasAsterix = false; c = citor.next(); length++; break; default: // terminate on whitespace - perhaps too lenient if(Character.isWhitespace(c)) { if(length < 1) throw new ParseException("value in expression can not be empty", citor.getIndex()); else return; } length++; c = citor.next(); lastWasAsterix = false; break; } } } /** * Returns the first non whitespace char starting with 'current position' * * @param citor * @return */ private char skipWhitespace(StringCharacterIterator citor) { char c = CharacterIterator.DONE; for(c = citor.current(); c != CharacterIterator.DONE && Character.isWhitespace(c); c = citor.next()) ; return c; } private static class ParseException extends Exception { private static final long serialVersionUID = 1L; public String message; public int index; public ParseException(String message, int index) { this.message = message; this.index = index; } } }