From 0fca8e8c7e19434aeb20afda1db6c1fcae66a2d8 Mon Sep 17 00:00:00 2001
From: neil_a_wilson <neil_a_wilson@localhost>
Date: Thu, 05 Jul 2007 20:28:03 +0000
Subject: [PATCH] Fix a nummber of issues related to search filter processing:

---
 opendj-sdk/opends/src/server/org/opends/server/messages/CoreMessages.java                                 |   50 +++++++
 opendj-sdk/opends/src/server/org/opends/server/protocols/ldap/LDAPFilter.java                             |   11 +
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/types/SearchFilterTests.java       |   11 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestLDAPFilter.java |    8 -
 opendj-sdk/opends/src/server/org/opends/server/types/SearchFilter.java                                    |  270 ++++++++++++++++++++++++++++++++++++--
 opendj-sdk/opends/src/server/org/opends/server/messages/ProtocolMessages.java                             |   17 ++
 6 files changed, 341 insertions(+), 26 deletions(-)

diff --git a/opendj-sdk/opends/src/server/org/opends/server/messages/CoreMessages.java b/opendj-sdk/opends/src/server/org/opends/server/messages/CoreMessages.java
index 48da4b4..499793c 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/messages/CoreMessages.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/messages/CoreMessages.java
@@ -6222,6 +6222,41 @@
 
 
   /**
+   * The message ID for the message that will be used if an attempt is made to
+   * create an extensible match search filter without providing either an
+   * attribute type or a matching rule ID.  This does not take any arguments.
+   */
+  public static final int
+       MSGID_SEARCH_FILTER_CREATE_EXTENSIBLE_MATCH_NO_AT_OR_MR =
+            CATEGORY_MASK_CORE | SEVERITY_MASK_SEVERE_ERROR | 625;
+
+
+
+  /**
+   * The message ID for the message that will be used if a filter string cannot
+   * be decoded because it contained an extensible match component that did not
+   * include either an attribute description or a matching rule ID.  This takes
+   * two arguments, which are the filter string and the start position of the
+   * extensible match component within that filter string.
+   */
+  public static final int MSGID_SEARCH_FILTER_EXTENSIBLE_MATCH_NO_AD_OR_MR =
+            CATEGORY_MASK_CORE | SEVERITY_MASK_SEVERE_ERROR | 626;
+
+
+
+  /**
+   * The message ID for the message that will be used if a filter string cannot
+   * be decoded because it contained an extensible match component that
+   * referenced an unknown matching rule ID.  This takes three arguments, which
+   * are the filter string, the start position of the extensible match component
+   * within that filter string, and the unknown matching rule ID.
+   */
+  public static final int MSGID_SEARCH_FILTER_EXTENSIBLE_MATCH_NO_SUCH_MR =
+            CATEGORY_MASK_CORE | SEVERITY_MASK_SEVERE_ERROR | 627;
+
+
+
+  /**
    * Associates a set of generic messages with the message IDs defined
    * in this class.
    */
@@ -6686,6 +6721,11 @@
                     "exception was caught during processing:  %s");
 
 
+    registerMessage(MSGID_SEARCH_FILTER_CREATE_EXTENSIBLE_MATCH_NO_AT_OR_MR,
+                    "Unable to create an extensible match search filter " +
+                    "using the provided information because it did not " +
+                    "contain either an attribute type or a matching rule " +
+                    "ID.  At least one of these must be provided");
     registerMessage(MSGID_SEARCH_FILTER_NULL,
                     "Unable to decode the provided filter string as a search " +
                     "filter because the provided string was empty or null");
@@ -6729,6 +6769,16 @@
                     "because the extensible match component starting at " +
                     "position %d did not have a colon to denote the end of " +
                     "the attribute type name");
+    registerMessage(MSGID_SEARCH_FILTER_EXTENSIBLE_MATCH_NO_AD_OR_MR,
+                    "The provided search filter \"%s\" could not be decoded " +
+                    "because the extensible match component starting at " +
+                    "position %d did not contain either an attribute " +
+                    "description or a matching rule ID.  At least one of " +
+                    "these must be provided");
+    registerMessage(MSGID_SEARCH_FILTER_EXTENSIBLE_MATCH_NO_SUCH_MR,
+                    "The provided search filter \"%s\" could not be decoded " +
+                    "because the extensible match component starting at " +
+                    "position %d referenced an unknown matching rule %s");
     registerMessage(MSGID_SEARCH_FILTER_NOT_EXACTLY_ONE,
                     "The provided search filter \"%s\" could not be decoded " +
                     "because the NOT filter between positions %d and %d " +
diff --git a/opendj-sdk/opends/src/server/org/opends/server/messages/ProtocolMessages.java b/opendj-sdk/opends/src/server/org/opends/server/messages/ProtocolMessages.java
index aa9a360..7c1c813 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/messages/ProtocolMessages.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/messages/ProtocolMessages.java
@@ -4638,6 +4638,17 @@
 
 
   /**
+   * The message ID for the message that will be used if a search filter string
+   * includes an extensible match component without either an attribute
+   * description or a matching rule ID.  This takes a single argument, which is
+   * the filter string.
+   */
+  public static final int MSGID_LDAP_FILTER_EXTENSIBLE_MATCH_NO_AD_OR_MR =
+       CATEGORY_MASK_PROTOCOL | SEVERITY_MASK_MILD_ERROR | 430;
+
+
+
+  /**
    * Associates a set of generic messages with the message IDs defined in this
    * class.
    */
@@ -5418,6 +5429,12 @@
                     "because the extensible match component starting at " +
                     "position %d did not have a colon to denote the end of " +
                     "the attribute type name");
+    registerMessage(MSGID_LDAP_FILTER_EXTENSIBLE_MATCH_NO_AD_OR_MR,
+                    "The provided search filter \"%s\" could not be decoded " +
+                    "because the extensible match component starting at " +
+                    "position %d did not include either an attribute " +
+                    "description or a matching rule ID.  At least one of " +
+                    "them must be provided");
     registerMessage(MSGID_LDAP_FILTER_NOT_EXACTLY_ONE,
                     "The provided search filter \"%s\" could not be decoded " +
                     "because the NOT filter between positions %d and %d " +
diff --git a/opendj-sdk/opends/src/server/org/opends/server/protocols/ldap/LDAPFilter.java b/opendj-sdk/opends/src/server/org/opends/server/protocols/ldap/LDAPFilter.java
index 4f0325c..589565e 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/protocols/ldap/LDAPFilter.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/protocols/ldap/LDAPFilter.java
@@ -1740,6 +1740,17 @@
       value = new ASN1OctetString(valueBytes);
     }
 
+
+    // Make sure that the filter has at least one of an attribute description
+    // and/or a matching rule ID.
+    if ((attributeType == null) && (matchingRuleID == null))
+    {
+      int    msgID   = MSGID_LDAP_FILTER_EXTENSIBLE_MATCH_NO_AD_OR_MR;
+      String message = getMessage(msgID, filterString, startPos);
+      throw new LDAPException(LDAPResultCode.PROTOCOL_ERROR, msgID, message);
+    }
+
+
     return new LDAPFilter(FilterType.EXTENSIBLE_MATCH, null, null,
                           attributeType, value, null, null, null,
                           matchingRuleID, dnAttributes);
diff --git a/opendj-sdk/opends/src/server/org/opends/server/types/SearchFilter.java b/opendj-sdk/opends/src/server/org/opends/server/types/SearchFilter.java
index 6e6d8b9..056c53c 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/types/SearchFilter.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/types/SearchFilter.java
@@ -30,6 +30,7 @@
 
 import java.nio.ByteBuffer;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
@@ -39,6 +40,7 @@
 import java.util.Collections;
 
 import org.opends.server.api.MatchingRule;
+import org.opends.server.api.SubstringMatchingRule;
 import org.opends.server.core.DirectoryServer;
 import org.opends.server.protocols.asn1.ASN1OctetString;
 
@@ -521,13 +523,27 @@
    *                         attributes for extensible match filters.
    *
    * @return  The constructed search filter.
+   *
+   * @throws  DirectoryException  If the provided information is not
+   *                              sufficient to create an extensible
+   *                              match filter.
    */
   public static SearchFilter createExtensibleMatchFilter(
                                   AttributeType attributeType,
                                   AttributeValue assertionValue,
                                   String matchingRuleID,
                                   boolean dnAttributes)
+         throws DirectoryException
   {
+    if ((attributeType == null) && (matchingRuleID == null))
+    {
+      int msgID =
+           MSGID_SEARCH_FILTER_CREATE_EXTENSIBLE_MATCH_NO_AT_OR_MR;
+      String message = getMessage(msgID);
+      throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message,
+                                   msgID);
+    }
+
     return new SearchFilter(FilterType.EXTENSIBLE_MATCH, null, null,
                             attributeType, null, assertionValue, null,
                             null, null, matchingRuleID, dnAttributes);
@@ -552,6 +568,10 @@
    *                           filters.
    *
    * @return  The constructed search filter.
+   *
+   * @throws  DirectoryException  If the provided information is not
+   *                              sufficient to create an extensible
+   *                              match filter.
    */
   public static SearchFilter createExtensibleMatchFilter(
                                   AttributeType attributeType,
@@ -559,7 +579,17 @@
                                   AttributeValue assertionValue,
                                   String matchingRuleID,
                                   boolean dnAttributes)
+         throws DirectoryException
   {
+    if ((attributeType == null) && (matchingRuleID == null))
+    {
+      int msgID =
+           MSGID_SEARCH_FILTER_CREATE_EXTENSIBLE_MATCH_NO_AT_OR_MR;
+      String message = getMessage(msgID);
+      throw new DirectoryException(ResultCode.PROTOCOL_ERROR, message,
+                                   msgID);
+    }
+
     return new SearchFilter(FilterType.EXTENSIBLE_MATCH, null, null,
                             attributeType, attributeOptions,
                             assertionValue, null, null, null,
@@ -2078,8 +2108,43 @@
       userValue = new ASN1OctetString(valueBytes);
     }
 
-    AttributeValue value = new AttributeValue(attributeType,
-                                              userValue);
+    // Make sure that the filter contains at least one of an attribute
+    // type or a matching rule ID.  Also, construct the appropriate
+    // attribute  value.
+    AttributeValue value;
+    if (attributeType == null)
+    {
+      if (matchingRuleID == null)
+      {
+        int msgID = MSGID_SEARCH_FILTER_EXTENSIBLE_MATCH_NO_AD_OR_MR;
+        String message = getMessage(msgID, filterString, startPos);
+        throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
+                                     message, msgID);
+      }
+      else
+      {
+        MatchingRule mr = DirectoryServer.getMatchingRule(
+                               toLowerCase(matchingRuleID));
+        if (mr == null)
+        {
+          int msgID = MSGID_SEARCH_FILTER_EXTENSIBLE_MATCH_NO_SUCH_MR;
+          String message = getMessage(msgID, filterString, startPos,
+                                      matchingRuleID);
+          throw new DirectoryException(ResultCode.PROTOCOL_ERROR,
+                                       message, msgID);
+        }
+        else
+        {
+          value = new AttributeValue(userValue,
+                                     mr.normalizeValue(userValue));
+        }
+      }
+    }
+    else
+    {
+      value = new AttributeValue(attributeType, userValue);
+    }
+
     return new SearchFilter(FilterType.EXTENSIBLE_MATCH, null, null,
                             attributeType, attributeOptions, value,
                             null, null, null, matchingRuleID,
@@ -3472,6 +3537,7 @@
         return notComponent.equals(f.notComponent);
       case EQUALITY:
         return (attributeType.equals(f.attributeType) &&
+                optionsEqual(attributeOptions, f.attributeOptions) &&
                 assertionValue.equals(f.assertionValue));
       case SUBSTRING:
         if (! attributeType.equals(f.attributeType))
@@ -3479,6 +3545,18 @@
           return false;
         }
 
+        SubstringMatchingRule smr =
+             attributeType.getSubstringMatchingRule();
+        if (smr == null)
+        {
+          return false;
+        }
+
+        if (! optionsEqual(attributeOptions, f.attributeOptions))
+        {
+          return false;
+        }
+
         if (subInitialElement == null)
         {
           if (f.subInitialElement != null)
@@ -3488,7 +3566,19 @@
         }
         else
         {
-          if (! subInitialElement.equals(f.subInitialElement))
+          try
+          {
+            ByteString nSI1 =
+                 smr.normalizeSubstring(subInitialElement);
+            ByteString nSI2 =
+                 smr.normalizeSubstring(f.subInitialElement);
+
+            if (! Arrays.equals(nSI1.value(), nSI2.value()))
+            {
+              return false;
+            }
+          }
+          catch (Exception e)
           {
             return false;
           }
@@ -3503,7 +3593,19 @@
         }
         else
         {
-          if (! subFinalElement.equals(f.subFinalElement))
+          try
+          {
+            ByteString nSF1 =
+                 smr.normalizeSubstring(subFinalElement);
+            ByteString nSF2 =
+                 smr.normalizeSubstring(f.subFinalElement);
+
+            if (! Arrays.equals(nSF1.value(), nSF2.value()))
+            {
+              return false;
+            }
+          }
+          catch (Exception e)
           {
             return false;
           }
@@ -3514,10 +3616,22 @@
           return false;
         }
 
-        for (int i = 0; i < subAnyElements.size(); i++) {
-          ByteString sub1 = subAnyElements.get(i);
-          ByteString sub2 = f.subAnyElements.get(i);
-          if (!sub1.equals(sub2)) {
+        for (int i = 0; i < subAnyElements.size(); i++)
+        {
+          try
+          {
+            ByteString nSA1 =
+                 smr.normalizeSubstring(subAnyElements.get(i));
+            ByteString nSA2 =
+                 smr.normalizeSubstring(f.subAnyElements.get(i));
+
+            if (! Arrays.equals(nSA1.value(), nSA2.value()))
+            {
+              return false;
+            }
+          }
+          catch (Exception e)
+          {
             return false;
           }
         }
@@ -3525,14 +3639,18 @@
         return true;
       case GREATER_OR_EQUAL:
         return (attributeType.equals(f.attributeType) &&
+                optionsEqual(attributeOptions, f.attributeOptions) &&
                 assertionValue.equals(f.assertionValue));
       case LESS_OR_EQUAL:
         return (attributeType.equals(f.attributeType) &&
+                optionsEqual(attributeOptions, f.attributeOptions) &&
                 assertionValue.equals(f.assertionValue));
       case PRESENT:
-        return (attributeType.equals(f.attributeType));
+        return (attributeType.equals(f.attributeType) &&
+                optionsEqual(attributeOptions, f.attributeOptions));
       case APPROXIMATE_MATCH:
         return (attributeType.equals(f.attributeType) &&
+                optionsEqual(attributeOptions, f.attributeOptions) &&
                 assertionValue.equals(f.assertionValue));
       case EXTENSIBLE_MATCH:
         if (attributeType == null)
@@ -3548,6 +3666,11 @@
           {
             return false;
           }
+
+          if (! optionsEqual(attributeOptions, f.attributeOptions))
+          {
+            return false;
+          }
         }
 
         if (dnAttributes != f.dnAttributes)
@@ -3579,9 +3702,39 @@
         }
         else
         {
-          if (! assertionValue.equals(f.assertionValue))
+          if (matchingRuleID == null)
           {
-            return false;
+            if (! assertionValue.equals(f.assertionValue))
+            {
+              return false;
+            }
+          }
+          else
+          {
+            MatchingRule mr =
+                 DirectoryServer.getMatchingRule(
+                      toLowerCase(matchingRuleID));
+            if (mr == null)
+            {
+              return false;
+            }
+            else
+            {
+              try
+              {
+                ConditionResult cr = mr.valuesMatch(
+                     mr.normalizeValue(assertionValue.getValue()),
+                     mr.normalizeValue(f.assertionValue.getValue()));
+                if (cr != ConditionResult.TRUE)
+                {
+                  return false;
+                }
+              }
+              catch (Exception e)
+              {
+                return false;
+              }
+            }
           }
         }
 
@@ -3592,6 +3745,57 @@
   }
 
 
+
+  /**
+   * Indicates whether the two provided sets of attribute options
+   * should be considered equal.
+   *
+   * @param  options1  The first set of attribute options for which to
+   *                   make the determination.
+   * @param  options2  The second set of attribute options for which
+   *                   to make the determination.
+   *
+   * @return  {@code true} if the sets of attribute options are equal,
+   *          or {@code false} if not.
+   */
+  private static boolean optionsEqual(Set<String> options1,
+                                      Set<String> options2)
+  {
+    if ((options1 == null) || options1.isEmpty())
+    {
+      return ((options2 == null) || options2.isEmpty());
+    }
+    else if ((options2 == null) || options2.isEmpty())
+    {
+      return false;
+    }
+    else
+    {
+      if (options1.size() != options2.size())
+      {
+        return false;
+      }
+
+      HashSet<String> lowerOptions =
+           new HashSet<String>(options1.size());
+      for (String option : options1)
+      {
+        lowerOptions.add(toLowerCase(option));
+      }
+
+      for (String option : options2)
+      {
+        if (! lowerOptions.remove(toLowerCase(option)))
+        {
+          return false;
+        }
+      }
+
+      return lowerOptions.isEmpty();
+    }
+  }
+
+
   /**
    * Retrieves the hash code for this search filter.
    *
@@ -3618,22 +3822,60 @@
       case SUBSTRING:
         hashCode = attributeType.hashCode();
 
+        SubstringMatchingRule smr =
+             attributeType.getSubstringMatchingRule();
+
         if (subInitialElement != null)
         {
-          hashCode += subInitialElement.hashCode();
+          if (smr == null)
+          {
+            hashCode += subInitialElement.hashCode();
+          }
+          else
+          {
+            try
+            {
+              hashCode += smr.normalizeSubstring(
+                               subInitialElement).hashCode();
+            }
+            catch (Exception e) {}
+          }
         }
 
         if (subAnyElements != null)
         {
           for (ByteString e : subAnyElements)
           {
-            hashCode += e.hashCode();
+            if (smr == null)
+            {
+              hashCode += e.hashCode();
+            }
+            else
+            {
+              try
+              {
+                hashCode += smr.normalizeSubstring(e).hashCode();
+              }
+              catch (Exception e2) {}
+            }
           }
         }
 
         if (subFinalElement != null)
         {
-          hashCode += subFinalElement.hashCode();
+          if (smr == null)
+          {
+            hashCode += subFinalElement.hashCode();
+          }
+          else
+          {
+            try
+            {
+              hashCode +=
+                   smr.normalizeSubstring(subFinalElement).hashCode();
+            }
+            catch (Exception e) {}
+          }
         }
 
         return hashCode;
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestLDAPFilter.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestLDAPFilter.java
index 448c76c..2ecf493 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestLDAPFilter.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/protocols/ldap/TestLDAPFilter.java
@@ -73,6 +73,7 @@
       { "((uid=user.0))", null },
       { "(&&(uid=user.0))", null },
       { "!uid=user.0", null },
+      { "(:dn:=Sally)", null },
 
     };
   }
@@ -136,10 +137,6 @@
                                                 null,
                                            new ASN1OctetString("\\John* (Doe)"),
                                                 false);
-    LDAPFilter extensible6 = LDAPFilter.createExtensibleFilter(null,
-                                                null,
-                                           new ASN1OctetString(""),
-                                                true);
 
     ArrayList<RawFilter> list1 = new ArrayList<RawFilter>();
     list1.add(equal);
@@ -186,8 +183,6 @@
 
         { "(:2.4.6.8.19:=\\5cJohn\\2a \\28Doe\\29)", extensible5 },
 
-        { "(:dn:=)", extensible6 },
-
         { "(&(objectClass=\\5ctest\\2a\\28Value\\29)(sn~=\\5ctest\\2a\\28Value\\29))",
             LDAPFilter.createANDFilter(list1) },
 
@@ -296,7 +291,6 @@
           "(cn=*n)" +
           "(cn=n*)" +
           "(cn=n*n)" +
-          "(:dn:=Sally)" +
           "(:dn:1.2.3.4:=Doe)" +
           "(cn:2.4.6.8.10:=)" +
         ")");
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/types/SearchFilterTests.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/types/SearchFilterTests.java
index 4ef6643..5d92167 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/types/SearchFilterTests.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/types/SearchFilterTests.java
@@ -287,6 +287,7 @@
             {"(sn=\\H1)"},
             {"(!(sn=test)(cn=test))"},
             {"(!)"},
+            {"(:dn:=Sally)"}
     };
   }
 
@@ -1024,10 +1025,10 @@
           {"(sn:caseExactMatch:=Smith)", "(sn:caseExactMatch:=Smith)", true, true},
 
           // This demonstrates bug 704.
-//          {"(sn:caseExactMatch:=Smith)", "(sn:caseExactMatch:=smith)", false, false},
+          {"(sn:caseExactMatch:=Smith)", "(sn:caseExactMatch:=smith)", false, false},
 
-          // TODO: open a bug for this.
-//          {"(:dn:caseExactMatch:=example)", "(:DN:caseExactMatch:=example)", true, true}, // ? String not match
+          // Ensure that ":dn:" is treated in a case-insensitive manner.
+          {"(:dn:caseExactMatch:=example)", "(:DN:caseExactMatch:=example)", true, true}, // ? String not match
 
           // 2.5.4.4 is 'sn'
           {"(2.5.4.4=Smith)", "(2.5.4.4=Smith)", true, true},
@@ -1036,11 +1037,11 @@
           {"(sn;lang-en=Smith)", "(sn;lang-en=Smith)", true, true},
 
           // This demonstrates bug 706
-//          {"(sn;lang-en=Smith)", "(sn=Smith)", false, false},
+          {"(sn;lang-en=Smith)", "(sn=Smith)", false, false},
 
 
           // This demonstrates bug 705.
-//          {"(sn=s*t*h)", "(sn=S*T*H)", true, false},
+          {"(sn=s*t*h)", "(sn=S*T*H)", true, false},
 
           // These should be case sensitive
           {"(labeledURI=http://opends.org)", "(labeledURI=http://OpenDS.org)", false, false},

--
Gitblit v1.10.0