/** BEGIN COPYRIGHT BLOCK * This Program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; version 2 of the License. * * This Program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with * this Program; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA. * * In addition, as a special exception, Red Hat, Inc. gives You the additional * right to link the code of this Program with code not covered under the GNU * General Public License ("Non-GPL Code") and to distribute linked combinations * including the two, subject to the limitations in this paragraph. Non-GPL Code * permitted under this exception must only link to the code of this Program * through those well defined interfaces identified in the file named EXCEPTION * found in the source code files (the "Approved Interfaces"). The files of * Non-GPL Code may instantiate templates or use macros or inline functions from * the Approved Interfaces without causing the resulting work to be covered by * the GNU General Public License. Only Red Hat, Inc. may make changes or * additions to the list of Approved Interfaces. You must obey the GNU General * Public License in all respects for all of the Program code and other code used * in conjunction with the Program except the Non-GPL Code covered by this * exception. If you modify this file, you may extend this exception to your * version of the file, but you are not obligated to do so. If you do not wish to * provide this exception without modification, you must delete this exception * statement from your version and license this file solely under the GPL without * exception. * * * Copyright (C) 2001 Sun Microsystems, Inc. Used by permission. * Copyright (C) 2005 Red Hat, Inc. * All rights reserved. * END COPYRIGHT BLOCK **/ #ifdef HAVE_CONFIG_H # include #endif /* filtercmp.c - routines for comparing filters */ #include #include #include #include "slap.h" /* very simple hash function */ static PRUint32 addhash(PRUint32 hash, unsigned char *data, int size) { int i; if (!data || !size) return hash; for (i = 0; i < size; i++) hash = (hash << 5) + (hash >> 27) + data[i]; return hash; } #define addhash_long(h, l) addhash((h), (unsigned char *)&(l), sizeof(long)) #define addhash_str(h, str) addhash((h), (unsigned char *)(str), strlen(str)) #define addhash_bv(h, bv) addhash((h), (unsigned char *)(bv).bv_val, \ (bv).bv_len) static PRUint32 addhash_casestr(PRUint32 hash, char *data) { unsigned char *normstr; normstr = slapi_utf8StrToLower((unsigned char *)data); hash = addhash(hash, normstr, normstr ? strlen((char *)normstr) : 0); if ((char *)normstr != data) slapi_ch_free((void **)&normstr); return hash; } static PRUint32 stir(PRUint32 hash, PRUint32 x) { hash = (hash << 5) + (hash >> 27); hash = hash ^ (x << 16); hash = hash ^ (x >> 16); return hash; } #define STIR(h) (h) = stir((h), 0x2EC6DEAD); static Slapi_Value **get_normalized_value(const Slapi_Attr *sattr, struct ava *ava) { Slapi_Value *svlist[2], **keylist, sv; sv.bv = ava->ava_value; sv.v_csnset = NULL; svlist[0] = &sv; svlist[1] = NULL; if ((slapi_attr_values2keys_sv(sattr, svlist, &keylist, LDAP_FILTER_EQUALITY) != 0) || !keylist || !keylist[0]) return NULL; return keylist; } /* this is not pretty. matching rules seem to be pretty elaborate to use, * so comparing these kind of filters may be undesirably slow just because * of the overhead of normalizing the values. most of this code is stolen * from the backend vlv code (matchrule.c) */ static Slapi_PBlock *get_mr_normval(char *oid, char *type, struct berval **inval, struct berval ***outval) { Slapi_PBlock *pb = slapi_pblock_new(); unsigned int sort_indicator = SLAPI_PLUGIN_MR_USAGE_SORT; IFP mrIndex = NULL; if (!pb) return NULL; slapi_pblock_set(pb, SLAPI_PLUGIN_MR_OID, oid); slapi_pblock_set(pb, SLAPI_PLUGIN_MR_TYPE, type); slapi_pblock_set(pb, SLAPI_PLUGIN_MR_USAGE, (void *)&sort_indicator); if (slapi_mr_indexer_create(pb) != 0) { slapi_pblock_destroy(pb); return NULL; } if ((slapi_pblock_get(pb, SLAPI_PLUGIN_MR_INDEX_FN, &mrIndex) != 0) || !mrIndex) { /* shouldn't ever happen */ slapi_pblock_destroy(pb); return NULL; } /* now, call the indexer */ slapi_pblock_set(pb, SLAPI_PLUGIN_MR_VALUES, inval); (*mrIndex)(pb); slapi_pblock_get(pb, SLAPI_PLUGIN_MR_KEYS, outval); return pb; } /* the opposite of above: shut down the matching rule pblock and free * the memory. */ static void done_mr_normval(Slapi_PBlock *pb) { IFP mrDestroy = NULL; if (slapi_pblock_get(pb, SLAPI_PLUGIN_DESTROY_FN, &mrDestroy) == 0) { if (mrDestroy) (*mrDestroy)(pb); } slapi_pblock_destroy(pb); } static int hash_filters = 0; void set_hash_filters(int i) { hash_filters = i; } /* calculate the hash value of a node in a filter (assumes that any sub-nodes * of the filter have already had their hash value calculated). * -- the annoying part of this is normalizing any values in the filter. */ void filter_compute_hash(struct slapi_filter *f) { PRUint32 h; char **a; struct slapi_filter *fx; Slapi_Value **keylist; Slapi_PBlock *pb; struct berval *inval[2], **outval; Slapi_Attr sattr; if (! hash_filters) return; h = addhash_long(0, f->f_choice); switch (f->f_choice) { case LDAP_FILTER_EQUALITY: case LDAP_FILTER_GE: case LDAP_FILTER_LE: case LDAP_FILTER_APPROX: slapi_attr_init(&sattr, f->f_ava.ava_type); keylist = get_normalized_value(&sattr, &f->f_ava); attr_done(&sattr); if (keylist) { h = addhash_str(h, f->f_avtype); STIR(h); h = addhash_bv(h, *(slapi_value_get_berval(keylist[0]))); valuearray_free(&keylist); } break; case LDAP_FILTER_SUBSTRINGS: h = addhash_str(h, f->f_sub_type); STIR(h); if (f->f_sub_initial) h = addhash_casestr(h, f->f_sub_initial); if (f->f_sub_any) { for (a = f->f_sub_any; *a; a++) { STIR(h); h = addhash_casestr(h, *a); } } STIR(h); if (f->f_sub_final) h = addhash_casestr(h, f->f_sub_final); break; case LDAP_FILTER_PRESENT: h = addhash_str(h, f->f_type); break; case LDAP_FILTER_AND: case LDAP_FILTER_OR: case LDAP_FILTER_NOT: /* should be able to just mix in the hashes from lower levels */ for (fx = f->f_list; fx; fx = fx->f_next) h = h ^ fx->f_hash; break; case LDAP_FILTER_EXTENDED: if (f->f_mr_oid) h = addhash_str(h, f->f_mr_oid); STIR(h); if (f->f_mr_type) h = addhash_str(h, f->f_mr_type); inval[0] = &f->f_mr_value; inval[1] = NULL; /* get the normalized value (according to the matching rule) */ pb = get_mr_normval(f->f_mr_oid, f->f_mr_type, inval, &outval); if (!pb) { LDAPDebug(LDAP_DEBUG_ANY, "$$$ out of memory !\n", 0, 0, 0); return; } if (outval && outval[0]) { STIR(h); h = addhash_bv(h, *(outval[0])); } done_mr_normval(pb); if (f->f_mr_dnAttrs) STIR(h); break; default: LDAPDebug(LDAP_DEBUG_ANY, "$$$ can't handle filter type %d !\n", f->f_choice, 0, 0); } f->f_hash = h; } /* match compare: given two arrays of size N, determine if each item in * the first array matches with each item in the second array, with a * one-to-one correspondence. this will be DOG SLOW for large values of N * (it scales as N^2) but we generally expect N < 5. */ static int filter_compare_substrings(struct slapi_filter *f1, struct slapi_filter *f2) { int buf[20], *tally; char **a1, **a2; int count1 = 0, count2 = 0, ret, i, j, ok; /* ok to pass NULL to utf8casecmp */ if ((slapi_UTF8CASECMP(f1->f_sub_initial, f2->f_sub_initial) != 0) || (slapi_UTF8CASECMP(f1->f_sub_final, f2->f_sub_final) != 0)) return 1; /* match compare (would be expensive for large numbers of 'any' * substrings, which we don't expect to see) */ for (a1 = f1->f_sub_any; a1 && *a1; a1++, count1++); for (a2 = f2->f_sub_any; a2 && *a2; a2++, count2++); if (count1 != count2) return 1; ret = 1; /* assume failure until done comparing */ if (count1 > 20) tally = (int *)slapi_ch_malloc(count1); else tally = buf; if (!tally) goto done; /* this is bad; out of memory */ for (i = 0; i < count1; i++) tally[i] = 0; /* ok. the theory is we tally up all the matched pairs we find, * stopping if we can't find a match that hasn't already been paired. */ a1 = f1->f_sub_any; for (i = 0; i < count1; i++, a1++) { a2 = f2->f_sub_any; ok = 0; for (j = 0; j < count1; j++, a2++) { if (!tally[j] && (slapi_UTF8CASECMP(*a1, *a2) == 0)) { tally[j] = ok = 1; break; } } if (!ok) goto done; /* didn't find a match for that one */ } /* done! matched */ ret = 0; done: if ((count1 > 20) && tally) slapi_ch_free((void **)&tally); return ret; } /* same as above, but this time for lists of filter nodes */ static int filter_compare_lists(struct slapi_filter *f1, struct slapi_filter *f2) { int buf[20], *tally; struct slapi_filter *fx1, *fx2; int count1 = 0, count2 = 0, ret, i, j, ok; for (fx1 = f1->f_list; fx1; fx1 = fx1->f_next, count1++); for (fx2 = f2->f_list; fx2; fx2 = fx2->f_next, count2++); if (count1 != count2) return 1; ret = 1; if (count1 > 20) tally = (int *)slapi_ch_malloc(count1); else tally = buf; if (!tally) goto done; /* very bad */ for (i = 0; i < count1; i++) tally[i] = 0; /* brute-force match compare now */ fx1 = f1->f_list; for (i = 0; i < count1; i++, fx1 = fx1->f_next) { fx2 = f2->f_list; ok = 0; for (j = 0; j < count1; j++, fx2 = fx2->f_next) { if (!tally[j] && (slapi_filter_compare(fx1, fx2) == 0)) { tally[j] = ok = 1; break; } } if (!ok) goto done; /* no match */ } /* done! all matched */ ret = 0; done: if ((count1 > 20) && tally) slapi_ch_free((void **)&tally); return ret; } /* returns 0 if two filters are "identical" * (items under AND/OR are allowed to be in different order) */ int slapi_filter_compare(struct slapi_filter *f1, struct slapi_filter *f2) { Slapi_Value **key1, **key2; Slapi_PBlock *pb1, *pb2; struct berval *inval1[2], *inval2[2], **outval1, **outval2; int ret; Slapi_Attr sattr; LDAPDebug(LDAP_DEBUG_TRACE, "=> filter compare\n", 0, 0, 0); /* allow for the possibility that one of the filters hasn't had a hash * computed (and is therefore 0). this means that a filter node whose * hash is computed as 0 will always get compared the expensive way, * but this should happen VERY rarely (if ever). */ if ((f1->f_hash != f2->f_hash) && (f1->f_hash) && (f2->f_hash)) { ret = 1; goto done; } /* brute-force comparison now */ if (f1->f_choice != f2->f_choice) { ret = 1; goto done; } switch (f1->f_choice) { case LDAP_FILTER_EQUALITY: case LDAP_FILTER_GE: case LDAP_FILTER_LE: case LDAP_FILTER_APPROX: if (slapi_UTF8CASECMP(f1->f_avtype, f2->f_avtype) != 0) { ret = 1; break; } slapi_attr_init(&sattr, f1->f_ava.ava_type); key1 = get_normalized_value(&sattr, &f1->f_ava); if (key1) { key2 = get_normalized_value(&sattr, &f2->f_ava); if (key2) { ret = memcmp(slapi_value_get_string(key1[0]), slapi_value_get_string(key2[0]), slapi_value_get_length(key1[0])); valuearray_free(&key1); valuearray_free(&key2); attr_done(&sattr); break; } valuearray_free(&key1); } attr_done(&sattr); ret = 1; break; case LDAP_FILTER_PRESENT: ret = (slapi_UTF8CASECMP(f1->f_type, f2->f_type)); break; case LDAP_FILTER_SUBSTRINGS: ret = filter_compare_substrings(f1, f2); break; case LDAP_FILTER_AND: case LDAP_FILTER_OR: case LDAP_FILTER_NOT: ret = filter_compare_lists(f1, f2); break; case LDAP_FILTER_EXTENDED: if ((slapi_UTF8CASECMP(f1->f_mr_oid, f2->f_mr_oid) != 0) || (slapi_UTF8CASECMP(f1->f_mr_type, f2->f_mr_type) != 0) || (f1->f_mr_dnAttrs != f2->f_mr_dnAttrs)) { ret = 1; break; } /* painstakingly compare the values (using the matching rule) */ inval1[0] = &f1->f_mr_value; inval2[0] = &f2->f_mr_value; inval1[1] = inval2[1] = NULL; pb1 = get_mr_normval(f1->f_mr_oid, f1->f_mr_type, inval1, &outval1); pb2 = get_mr_normval(f2->f_mr_oid, f2->f_mr_type, inval2, &outval2); if (!pb1 || !pb2 || !outval1 || !outval2 || !outval1[0] || !outval2[0] || (outval1[0]->bv_len != outval2[0]->bv_len) || (memcmp(outval1[0]->bv_val, outval2[0]->bv_val, outval1[0]->bv_len) != 0)) { ret = 1; } else { ret = 0; } if (pb1) done_mr_normval(pb1); if (pb2) done_mr_normval(pb2); break; default: LDAPDebug(LDAP_DEBUG_ANY, "ERR can't handle filter %d\n", f1->f_choice, 0, 0); ret = 1; } done: LDAPDebug(LDAP_DEBUG_TRACE, "<= filter compare: %d\n", ret, 0, 0); return ret; }