/***************************************************************************/
/*                                                                         */
/* Project:     OpenSLP - OpenSource implementation of Service Location    */
/*              Protocol Version 2                                         */
/*                                                                         */
/* File:        slpd_database.c                                            */
/*                                                                         */
/* Abstract:    This files contains an implementation of LDAPv3 search     */
/*              filters for SLP (as specified in RFC 2254).                */
/*                                                                         */
/*-------------------------------------------------------------------------*/
/*                                                                         */
/*     Please submit patches to http://www.openslp.org                     */
/*                                                                         */
/*-------------------------------------------------------------------------*/
/*                                                                         */
/* Copyright (C) 2000 Caldera Systems, Inc                                 */
/* All rights reserved.                                                    */
/*                                                                         */
/* Redistribution and use in source and binary forms, with or without      */
/* modification, are permitted provided that the following conditions are  */
/* met:                                                                    */ 
/*                                                                         */
/*      Redistributions of source code must retain the above copyright     */
/*      notice, this list of conditions and the following disclaimer.      */
/*                                                                         */
/*      Redistributions in binary form must reproduce the above copyright  */
/*      notice, this list of conditions and the following disclaimer in    */
/*      the documentation and/or other materials provided with the         */
/*      distribution.                                                      */
/*                                                                         */
/*      Neither the name of Caldera Systems nor the names of its           */
/*      contributors may be used to endorse or promote products derived    */
/*      from this software without specific prior written permission.      */
/*                                                                         */
/* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS     */
/* `AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT      */
/* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR   */
/* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE CALDERA      */
/* SYSTEMS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, */
/* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT        */
/* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;  LOSS OF USE,  */
/* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON       */
/* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT */
/* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE   */
/* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.    */
/*                                                                         */
/***************************************************************************/

/*********/
/* TODO: The current implementation reparses the predicate string every    */
/*       time it is evaluated. This implementation should be refactored to */
/*       parse the predicate string once, build some sort of data-         */
/*       structure out of that, and then compare the predicate DS with the */
/*       attribute DS.                                                     */
/*********/

/*********
 *
 * Assumptions:
 *  - If a tag specified in the query string does not exist, that's a FALSE
 *  - The "Undefined" value mentioned in the LDAPv3 RFC (2251) is synonymous 
 *    with "false".
 *  - The "Approx" operator matches as an equal operator. 
 *  - Variable matching on type is strict: ie, the right-hand-side of an 
 *    operator must evaluate to the same type as the tag referenced on the 
 *    left-hand-side. 
 *
 * Known Bugs/TODO:
 *  - If trash follows the last operand to a binary argument, it will be 
 *    ignored if the operand is not evaluated due to short circuiting: 
 *    ie, in "(&(first=*)(second<=3)trash)", "trash" will be silently 
 *    ignored _if_ "(first=*)" is true.
 *  - Escaped '*' characters are treated as wildcards in the string equality 
 *    operator: ie, in "(string=abc\2axyz)" will match the literal string
 *    "abc\2axyz" instead of "abc*xyz". 
 *  - No operations can be performed on opaque data types. 
 *********/

#include <assert.h>
#include <ctype.h>
#include <stdlib.h>

#include "slpd_predicate.h"

#include "../libslpattr/libslpattr.h"
#include "../libslpattr/libslpattr_internal.h"

/* The character that is a wildcard. */
#define WILDCARD ('*')
#define BRACKET_OPEN '('
#define BRACKET_CLOSE ')'

#ifndef MIN
    #define MIN(x,y) (x < y ? x : y)
#endif

/************************* <Lifted from slp_attr.c> ***********************/

/* Tests a character to see if it reserved (as defined in RFC 2608, p11). */
#define IS_RESERVED(x) (((x) == '(' || (x) == ')' || (x) == ',' || (x) == '\\' || (x) == '!' || (x) == '<' || (x) == '=' || (x) == '>' || (x) == '~') || ((((char)0x01 <= (x)) && ((char)0x1F >= (x))) || ((x) == (char)0x7F)))


#define IS_INVALID_VALUE_CHAR(x) IS_RESERVED(x)

#define IS_INVALID_TAG_CHAR(x) (IS_RESERVED(x) || ((x) == '*') || ((x) == (char)0x0D) || ((x) == (char)0x0A) || ((x) == (char)0x09) || ((x) == '_'))

#define IS_VALID_TAG_CHAR(x) (!IS_INVALID_TAG_CHAR(x))

/************************* </Lifted from slp_attr.c> ***********************/


/*--------------------------------------------------------------------------*/
const char *substr(const char *haystack, 
                   const char *needle, 
                   int needle_len)
/* Does a case insensitive substring match for needle in haystack.          */
/*                                                                          */
/* Returns pointer to the start of the substring. NULL if the substring is  */
/* not found.                                                               */
/* FIXME This implementation isn't exactly blazingly fast...                */
/*--------------------------------------------------------------------------*/
{
    const char *hs_cur; 

    for(hs_cur = haystack; *hs_cur != 0; hs_cur++)
    {
        if(strncasecmp(hs_cur, needle, needle_len) == 0)
        {
            return hs_cur;
        }
    }

    return NULL;
}


/*--------------------------------------------------------------------------*/
typedef enum
{
    FR_UNSET /* Placeholder. Used to detect errors. */, 
    FR_INTERNAL_SYSTEM_ERROR /* Internal error. */,
    FR_PARSE_ERROR /* Parse error detected. */, 
    FR_MEMORY_ALLOC_FAILED /* Ran out of memory. */, 
    FR_EVAL_TRUE /* Expression successfully evaluated to true. */, 
    FR_EVAL_FALSE /* Expression successfully evaluated to false. */
} FilterResult;
/*--------------------------------------------------------------------------*/

/*--------------------------------------------------------------------------*/
int escaped_verify(char *escaped, int len, int *punescaped_len) 
/* Verifies and finds the length of an escaped string                       */
/*                                                                          */
/* Params:                                                                  */
/*  escaped -- (IN) string of escaped characters                            */
/*  len -- (IN) length of escaped                                           */
/*  unescaped_len -- (OUT) pointer to location to write the unescaped       */
/*     length of escaped to                                                 */
/*                                                                          */
/* Returns:                                                                 */
/*  1 if valid string, 0 if invalid. If 0, then punescaped_count will not   */
/*  be set                                                                  */
/*--------------------------------------------------------------------------*/
{
    int i;
    int unescaped_len;
    int seq_pos; /* Position in the current escape sequence. Set to zero when not in escape seq. */

    seq_pos = 0;

    for(i = unescaped_len = 0; i < len; i++)
    {
        /* Verify escape sequences. */
        if(seq_pos == 1 || seq_pos == 2)
        {
            if(!isxdigit((int) escaped[i]))
            {
                return 0;
            }

            if(seq_pos == 2)
            {
                seq_pos = 0;
            }
            else
            {
                seq_pos++;
            }
        }
        else
        {
            unescaped_len++;
        }


        /* Check for escape sequences. */
        if(escaped[i] == '\\')
        {
            seq_pos = 1;
        }
    }

    if(punescaped_len)
    {
        *punescaped_len = unescaped_len;
    }

    return 1;
}

/*--------------------------------------------------------------------------*/
int unescape_check(char d1, char d2, char *val) 
/* Unescape the character represented by the two given hex values.          */
/*                                                                          */
/* Params:                                                                  */
/*  d1 -- (IN) First hex char                                               */
/*  d1 -- (IN) second hex char                                              */
/*  val -- (OUT) the value of the character is written to here              */
/*                                                                          */
/* Returns:                                                                 */
/*  1 on successful conversion, 0 if one or more of the hex digits are      */
/*  invalid                                                                 */
/*--------------------------------------------------------------------------*/
{
    if(!isxdigit((int) d1) || !isxdigit((int) d2))
    {
        return 0;
    }

    if((d1 >= 'A') && (d1 <= 'F'))
        d1 = d1 - 'A' + 0x0A;
    else
        d1 = d1 - '0';

    if((d2 >= 'A') && (d2 <= 'F'))
        d2 = d2 - 'A' + 0x0A;
    else
        d2 = d2 - '0';

    *val = d2 + (d1 * 0x10);
    return 1;
}


/*--------------------------------------------------------------------------*/
FilterResult unescape_cmp(const char *escaped, int escaped_len, const char *verbatim, int verbatim_len, SLPBoolean strict_len, int *punescaped_count)
/* Compares two strings that (potentially) contain escaped characters       */
/*                                                                          */
/* Params:                                                                  */
/*   escaped -- escaped string to compare                                   */
/*   escaped_len -- length of the escaped string (including esc chars)      */
/*   verbatim -- string to compare against (not escaped)                    */
/*   verbatim_len -- length of the verbatim string                          */
/*   strict_len -- if TRUE, the number of characters in verbatim that match */
/*            must be _exactly_ verbatim_len. If FALSE, at most verbatim_len*/
/*            characters may match                                          */
/*   unescaped_count -- (OUT) the number of unescaped characters. Can be    */
/*            NULL                                                          */
/*                                                                          */
/*--------------------------------------------------------------------------*/
{
    char unesc; /* Holder for unescaped characters. */

    int esc_i; /* Index into escaped. */
    int ver_i; /* Index into verbatim. */

    int unescaped_count;

    unescaped_count = esc_i = ver_i = 0;

    /***** Compare every pair of characters. *****/
    while(1)
    {
        /**** Check for string end. ****/
        if(esc_i >= escaped_len)
        {
            if(punescaped_count != NULL)
            {
                *punescaped_count = unescaped_count;
            }

            if(strict_len == SLP_TRUE)
            {
                if(ver_i >= verbatim_len)
                {
                    return FR_EVAL_TRUE;
                }
                else
                {
                    return FR_EVAL_FALSE;
                }
            }

            return FR_EVAL_TRUE;
        }

        /**** Check for escaping ****/
        if(escaped[esc_i] == '\\')
        {
            /*** Check for truncated escape values. ***/
            if(esc_i + 2 >= escaped_len)
            {
                return FR_PARSE_ERROR;
            }
            /*** Unescape. ***/
            if(!unescape_check(escaped[esc_i+1], escaped[esc_i+2], &unesc))
            {
                return FR_PARSE_ERROR;
            }

            /*** Increment. ***/
            esc_i += 2;
        }
        else
        {
            unesc = escaped[esc_i];
        }

        if(unesc != verbatim[ver_i])        /* quick check for equality*/
        {
            if(! isascii(unesc)         /* case insensitive check */
               || ! isalpha(unesc)
               || ! isalpha(verbatim[ver_i])
               || tolower(unesc) != tolower(verbatim[ver_i]))
            {
                return FR_EVAL_FALSE;
            }
        }

        unescaped_count++;
        esc_i++;
        ver_i++;
    }
}



/*--------------------------------------------------------------------------*/
void *my_memmem(char *haystack, int haystack_len, char *needle, int needle_len, int *punescaped_len) 
/* locate an (escaped) substring                                            */
/*                                                                          */
/* Params:                                                                  */
/*  haystack -- (IN) unescaped memory to look in                            */
/*  haystack_len -- (IN) length of haystack                                 */
/*  needle -- (IN) escaped memory to search for                             */
/*  needle_len -- (IN) length of needle                                     */
/*  punescaped_len -- (OUT) the size of the unescaped needle. invalid if    */
/*       NULL is returned                                                   */
/*                                                                          */
/* Returns:                                                                 */
/*  pointer to substring. NULL if there is no substring                     */
/*--------------------------------------------------------------------------*/
{
    int offset;
    int search_len;

    if(escaped_verify(haystack, haystack_len, &search_len) == 0)
    {
        return NULL;
    }

    for(offset = 0; offset <= search_len; offset++)
    {
        FilterResult err;

        err = unescape_cmp(needle, needle_len, haystack + offset, haystack_len - offset, SLP_FALSE, punescaped_len);
        if(err == FR_EVAL_TRUE)
        {
            return(void *)(haystack + offset);
        }
        else if(err != FR_EVAL_FALSE)
        {
            return NULL;
        }
    }

    return NULL;
}



/*--------------------------------------------------------------------------*/
FilterResult wildcard_wc_str(char *pattern, int pattern_len, char *str, int str_len)
/*                                                                          */
/* Params:                                                                  */
/*  pattern -- the pattern to evaluate                                      */
/*  pattern_len -- the length of the pattern (in bytes)                     */
/*  str -- the  string to test the pattern on                               */
/*  str_len -- the length of the string                                     */
/*                                                                          */
/* Returns:                                                                 */
/*   -1 error,  0 success,  1 failure                                       */
/*--------------------------------------------------------------------------*/
{
    char *text_start; /* Pointer to the text following the WC(s) */
    int text_len; /* Length of the text. */
    char *found; /* The start of a string found in a search. */

    char *rem_start; /* Start of the remaining characters being searched for. */
    int rem_len; /* Length of the remaining charcters being searched for. */

    if(pattern_len == 0 && str_len == 0)
    {
        return FR_EVAL_TRUE;
    }

    if(pattern_len == 0)
    {
        return FR_EVAL_FALSE;
    }

    /***** Find text following wildcard *****/
    /**** Find end of WCs ****/
    text_start = pattern;
    text_len = 0;
    while(*text_start == WILDCARD)
    {
        text_start++;
        if(text_start == pattern + pattern_len)
        {
            /* No following text. Therefore we match. */
            return FR_EVAL_TRUE;
        }
    }

    /**** Find end of text. ****/
    found = memchr(text_start, WILDCARD, pattern_len - text_len);
    if(found == NULL)
    {
        /* No trailing WC. Set to end. */
        found = pattern + pattern_len;
    }
    text_len = found - text_start;

    /***** Look for the text in the source string *****/
    rem_start = str;
    rem_len = str_len;
    while(1)
    {
        int result;
        int match_len;

        found = my_memmem(rem_start, rem_len, text_start, text_len, &match_len);
        assert(found == NULL || ((found >= rem_start) && (found <= rem_start + rem_len)));

        if(found == NULL)
        {
            /* Desired text is not in the string. */
            return FR_EVAL_FALSE;
        }

        rem_len = str_len - (found - str);
        rem_start = found + 1;

        /**** Make recursive call. ****/
        result = wildcard_wc_str(text_start + text_len, (pattern + pattern_len) - (text_start + text_len), found + text_len, rem_len - match_len);

        if(result != FR_EVAL_FALSE)
        {
            return result;
        }
    }
    /*NOTREACHED*/
    assert(0);
}


/*--------------------------------------------------------------------------*/
FilterResult wildcard(const char *pattern, int pattern_len, const char *str, int str_len)
/* Check the pattern against the given string. Public interface to wildcard */
/* functionality.                                                           */
/*                                                                          */
/* Params:                                                                  */
/*  pattern -- the pattern to evaluate                                      */
/*  pattern_len -- the length of the pattern (in bytes)                     */
/*  str -- the  string to test the pattern on                               */
/*  str_len -- the length of the string                                     */
/*                                                                          */
/* Returns:                                                                 */
/*   -1 error,  0 success,  1 failure                                       */
/*                                                                          */
/* Implementation:                                                          */
/*  This is the interface to wildcard finding. It actually just prepares    */
/*  for repeated calls to wildcard_wc_str()                                 */
/*--------------------------------------------------------------------------*/
{
    char *first_wc; /* Position of the first WC in the pattern. */
    int first_wc_len; /* Position of first_wc in pattern. */
    int unescaped_first_wc_len; /* Position of first_wc in unescaped version of pattern. */

    /***** Check text up to first WC *****/
    /**** Get text ****/
    first_wc = memchr(pattern, WILDCARD, pattern_len);
    if(first_wc == NULL)
    {
        /* No wc */
        return unescape_cmp(pattern, pattern_len, str, str_len, SLP_TRUE, NULL);
    }
    else
    {
        first_wc_len = first_wc - pattern;
    }

    /**** Compare. ****/
    unescaped_first_wc_len = first_wc_len;
    if(first_wc_len > 0)
    {
        FilterResult err;
        err = unescape_cmp(pattern, first_wc_len, str, str_len, SLP_FALSE, &unescaped_first_wc_len);
        if(err != FR_EVAL_TRUE)
        {
            /* The leading text is different. */
            return err;
        }
    }

    /***** Start recursive call. *****/
    return wildcard_wc_str(first_wc, pattern_len - first_wc_len, (char *)str + unescaped_first_wc_len, str_len - unescaped_first_wc_len);
}


/*--------------------------------------------------------------------------*/
int is_bool_string(const char *str, int str_len, SLPBoolean *val)
/* Tests a string to see if it is a boolean. */
/*--------------------------------------------------------------------------*/
{
    if(str_len == 4 && memcmp(str, "true", 4) == 0) /* 4 -> length of string "true" */
    {
        *val = SLP_TRUE;
        return 1;
    }
    if(str_len == 5 && memcmp(str, "false", 5) == 0) /* 5 -> len of "false" */
    {
        *val = SLP_FALSE;
        return 1;
    }
    return 0;
}


/*--------------------------------------------------------------------------*/
typedef enum
{
    EQUAL, APPROX, GREATER, LESS, PRESENT
} Operation;
/*--------------------------------------------------------------------------*/

/*--------------------------------------------------------------------------*/
FilterResult int_op(SLPAttributes slp_attr, 
                    char *tag, 
                    int tag_len,
                    char *rhs, 
                    Operation op)
/* Perform an integer operation.                                            */
/*--------------------------------------------------------------------------*/
{
    int rhs_val; /* The converted value of rhs. */
    char *end; /* Pointer to the end of op. */
    var_t *var; /* Pointer to the variable named by tag. */
    value_t *value; /* A value in var. */

    FilterResult result; /* Value to return. */

    assert(op != PRESENT);

    result = FR_UNSET; /* For verification. */ /* TODO Only do this in debug. */

    /***** Verify and convert rhs. *****/
    rhs_val = strtol(rhs, &end, 10);

    if(*end != 0 && *end != BRACKET_CLOSE)
    {
        /* Trying to compare an int with a non-int. */
        return FR_EVAL_FALSE;
    }

    /***** Get variable. *****/
    var = attr_val_find_str((struct xx_SLPAttributes *)slp_attr, tag, tag_len);
    if(var == NULL)
    {
        return FR_EVAL_FALSE;
    }
    /**** Check type. ****/
    if(var->type != SLP_INTEGER)
    {
        return FR_EVAL_FALSE;
    }


    /***** Compare. *****/
    value = var->list;
    assert(value != NULL);
    assert(op != PRESENT); 
    switch(op)
    {
    case(EQUAL):
        result = FR_EVAL_FALSE;
        for(; value; value = value->next)
        {
            if(value->data.va_int == rhs_val)
            {
                result = FR_EVAL_TRUE;
                break;
            }
        }
        break;
    case(APPROX):
        assert(0); /* TODO: Figure out how this works later. */
        result = FR_EVAL_FALSE; 
        break;
    case(GREATER):
        result = FR_EVAL_FALSE;
        for(; value; value = value->next)
        {
            if(value->data.va_int >= rhs_val)
            {
                result = FR_EVAL_TRUE;
                break;
            }
        }
        break;
    case(LESS):
        result = FR_EVAL_FALSE;
        for(; value; value = value->next)
        {
            if(value->data.va_int <= rhs_val)
            {
                result = FR_EVAL_TRUE;
                break;
            }
        }
        break;
    default:
        assert(0);
    }

    assert(result != FR_UNSET);
    return result;
}


/*--------------------------------------------------------------------------*/
FilterResult keyw_op(SLPAttributes slp_attr, 
                     char *tag, 
                     char *rhs, 
                     Operation op)
/* Perform a keyword operation.                                             */
/*--------------------------------------------------------------------------*/
{
    /* Note that the only test keywords are allowed to undergo is PRESENT, 
     * also note that PRESENT is handled by our calling function.
     *
     * We are therefore quite justified in saying:
     */
    assert(op != PRESENT);
    return FR_EVAL_FALSE;
}



/*--------------------------------------------------------------------------*/
FilterResult bool_op(SLPAttributes slp_attr, 
                     char *tag, 
                     int tag_len,
                     char *rhs, 
                     int rhs_len,
                     Operation op)
/* Perform a boolean operation. */
/*--------------------------------------------------------------------------*/
{
    SLPBoolean rhs_val; /* The value of the rhs. */
    var_t *var;

    FilterResult result;

    assert(op != PRESENT);

    result = FR_UNSET;

    /* Note that the only valid non-PRESENT operator is EQUAL. */
    if(op != EQUAL)
    {
        return FR_EVAL_FALSE;
    }

    /***** Get and check the rhs value. *****/
    if(!is_bool_string(rhs, rhs_len, &rhs_val))
    {
        return FR_EVAL_FALSE;
    }

    /***** Get the tag value. *****/
    var = attr_val_find_str((struct xx_SLPAttributes *)slp_attr, tag, tag_len);
    if(var == NULL)
    {
        return FR_EVAL_FALSE;
    }
    /**** Check type. ****/
    if(var->type != SLP_BOOLEAN)
    {
        return FR_EVAL_FALSE;
    }

    /***** Compare. *****/
    if(var->list->data.va_bool == rhs_val)
    {
        result = FR_EVAL_TRUE; 
    }
    else
    {
        result = FR_EVAL_FALSE;
    }

    assert(result != FR_UNSET);
    return result;
}



/*--------------------------------------------------------------------------*/
FilterResult str_op(SLPAttributes slp_attr, 
                    char *tag, 
                    int tag_len,
                    char *rhs, 
                    int rhs_len,
                    Operation op)
/* Perform a string operation.                                              */
/*--------------------------------------------------------------------------*/
{
    char *str_val; /* Converted value of rhs. */
    int str_len; /* Length of converted value. */

    var_t *var;

    assert(op != PRESENT);

    /***** Verify rhs. *****/
    str_val = rhs;
    str_len = rhs_len;

    /***** Get tag value. *****/
    var = attr_val_find_str((struct xx_SLPAttributes *)slp_attr, tag, tag_len);
    if(var == NULL)
    {
        return FR_EVAL_FALSE;
    }
    /**** Check type. ****/
    if(var->type != SLP_STRING)
    {
        return FR_EVAL_FALSE;
    }


    /***** Compare. *****/
    assert(op != PRESENT); 

    if(op == APPROX)
    {
        assert(0); /* TODO: Figure out how this works later. */
    }
    else if(op == EQUAL)
    {
        int result;
        value_t *value;

        for(value = var->list; value; value = value->next)
        {
            result = wildcard(str_val, str_len, value->data.va_str, value->unescaped_len);
            /* We only keep going if the test fails. Let caller handle other problems. */
            if(result != FR_EVAL_FALSE)
            {
                return result;
            }
        }
    }
    else
    {
        value_t *value;
        /* We know that the op must be comparative. */
        assert(op == LESS || op == GREATER);

        for(value = var->list; value; value = value->next)
        {
            int result;

            result = memcmp(value->data.va_str, str_val, MIN(str_len, value->unescaped_len));
            if(
              (result <= 0 && op == LESS) 
              || (result >= 0 && op == GREATER) 
              )
            {
                return FR_EVAL_TRUE;
            }
        }
    }

    return FR_EVAL_FALSE;
}


/*--------------------------------------------------------------------------*/
FilterResult opaque_op(SLPAttributes slp_attr, 
                       char *tag, 
                       int tag_len,
                       char *rhs, 
                       int rhs_len,
                       Operation op)
/* Perform an opaque operation.                                              */
/*--------------------------------------------------------------------------*/
{
    char *str_val; /* Converted value of rhs. */
    int str_len; /* Length of converted value. */

    var_t *var;

    assert(op != PRESENT);

    /***** Verify and convert rhs. *****/
    str_val = rhs;
    str_len = rhs_len;

    /***** Get tag value. *****/
    var = attr_val_find_str((struct xx_SLPAttributes *)slp_attr, tag, tag_len);
    if(var == NULL)
    {
        return FR_EVAL_FALSE;
    }
    /**** Check type. ****/
    if(var->type != SLP_OPAQUE)
    {
        return FR_EVAL_FALSE;
    }


    /***** Compare. *****/
    assert(op != PRESENT); 

    if(op == APPROX)
    {
        assert(0); /* TODO: Figure out how this works later. */
    }
    else if(op == EQUAL)
    {
        int result;
        value_t *value;

        for(value = var->list; value; value = value->next)
        {
            result = wildcard(str_val, str_len, value->data.va_str, value->unescaped_len);
            /* We only keep going if the test fails. Let caller handle other problems. */
            if(result != FR_EVAL_FALSE)
            {
                return result;
            }
        }
    }
    else
    {
        value_t *value;
        /* We know that the op must be comparative. */
        assert(op == LESS || op == GREATER);

        for(value = var->list; value; value = value->next)
        {
            int result;

            result = memcmp(value->data.va_str, str_val, MIN(str_len, value->unescaped_len));
            if(
              (result <= 0 && op == LESS) 
              || (result >= 0 && op == GREATER) 
              )
            {
                return FR_EVAL_TRUE;
            }
        }
    }

    return FR_EVAL_FALSE;
}


/*--------------------------------------------------------------------------*/
int is_int_string(const char *str)
/* Tests a string to see if it consists wholly of numeric characters.       */
/*--------------------------------------------------------------------------*/
{
    int i;

    for(i=0; str[i] != '\0'; i++)
    {
        if(!((!isdigit((int)str[i])) || str[i] == '-'))
        {
            return 0;
        }
    }

    return 1;
}


/*--------------------------------------------------------------------------*/
char *find_substr(char *src, int src_len, char *to_find)
/* Returns ptr to start of substring, or null.                              */
/*--------------------------------------------------------------------------*/
{
    int i;

    for(i = 0; i < src_len; i++)
    {
        if(src[i] == *to_find)
        {
            int old_i = i; /* Save the old start. */
            int find_index;
            for(find_index = 0; (to_find[find_index] != '\0') && (i < src_len) && (to_find[find_index] == src[i]); to_find++, i++)
            {

            }

            if(to_find[find_index] == '\0')
            {
                return src + i;
            }

            i = old_i; /* Restor the old start. */
        }
    }
    return 0;
}



/*--------------------------------------------------------------------------*/
char *find_bracket_end(const char *str)
/* Finds a bracket matched to this one. Returns 0 if there isn't one.       */
/*--------------------------------------------------------------------------*/
{
    int open_count; 
    const char *cur;

    if(*str != BRACKET_OPEN)
    {
        return 0;
    }

    open_count = 0;

    /***** Count brackets. *****/
    for(cur = str; *cur != '\0'; cur++)
    {
        assert(open_count >= 0);

        /**** Check current character. ****/
        if(*cur == BRACKET_OPEN)
        {
            open_count++;
        }
        else if(*cur == BRACKET_CLOSE)
        {
            open_count--;
        }

        /**** Check if we've found bracket end. ****/
        if(open_count == 0)
        {
            return(char *)cur;
        }
    }

    return 0;
}       


/*--------------------------------------------------------------------------*/
struct pair
/* Represents a string and its length. */
{
    char *start;
    char *end; /* Stop reading _BEFORE_ end. ie, at end-1. */
};
/*--------------------------------------------------------------------------*/

#if defined(ENABLE_SLPv1)


/*--------------------------------------------------------------------------*/
FilterResult filterv1(const char *start, 
                      const char **end, 
                      SLPAttributes slp_attr, 
                      int recursion_depth)
/* Parameters:                                                              */
/* start -- (IN) The start of the string to work in.                        */
/* end -- (OUT) The end of the string processed. After successful processing*/
/*              (ie, no PARSE_ERRORs), this will be pointed at the character*/
/*              following the last char in this level of the expression.    */
/* slp_attr -- (IN) The attributes handle to compare on.                    */
/* return_value -- (OUT) The result of the search. Only valid if SLP_OK was */
/*                       returned.                                          */
/*                                                                          */
/* Returns: SLP_OK on successful search (ie, the search was do-able).       */
/*          SLP_PARSE_ERROR on search error.  The end of the expression is  */
/*          returned through end.                                           */
/* NOTE: This attempt only implements one comparision operator (==)         */
/*--------------------------------------------------------------------------*/
{
    char *operator; /* Pointer to the operator substring. */
    const char *cur; /* Current working character. */
    const char *last_char; /* The last character in the working string. */
    FilterResult err = FR_UNSET; /* The result of an evaluation. */
    FilterResult stop_condition = FR_UNSET;

    if(recursion_depth <= 0)
    {
        return FR_PARSE_ERROR;
    }

    recursion_depth--;

    if(*start == 0)
    {
        *end = start;
        return FR_EVAL_TRUE;
    }

    if(*start != BRACKET_OPEN)
    {
        return FR_PARSE_ERROR;
    }

    /***** Get the current expression. *****/
    last_char = *end = find_bracket_end(start);
    if(*end == 0)
    {
        return FR_PARSE_ERROR;
    }
    (*end)++; /* Move the end pointer past the closing bracket. */

    /**** 
     * Check for the three legal characters that can follow an expression,
     * if the following character isn't one of those, the following character 
     * is trash.
     ****/
    if(!(**end == BRACKET_OPEN || **end == BRACKET_CLOSE || **end == '\0'))
    {
        return FR_PARSE_ERROR;
    }

    /***** check for boolean op. *****/
    cur = start;
    cur++;

    switch(*cur)
    {
    case('&'): /***** And. *****/
    case('|'): /***** Or. *****/
        {
            if(*cur == '&')
            {
                stop_condition = FR_EVAL_FALSE;
            }
            else if(*cur == '|')
            {
                stop_condition = FR_EVAL_TRUE;
            }

            cur++; /* Move past operator. */

            /*** Ensure that we have at least one operator. ***/
            if(*cur != BRACKET_OPEN || cur >= last_char)
            {
                return FR_PARSE_ERROR;
            }

            /*** Evaluate each operand. ***/
            /* NOTE: Due to the condition on the above "if", we are guarenteed that the first iteration of the loop is valid. */
            do 
            {
                err = filterv1(cur, &cur, slp_attr, recursion_depth);
                /*** Propagate errors. ***/
                if(err != FR_EVAL_TRUE && err != FR_EVAL_FALSE)
                {
                    return err;
                }

                /*** Short circuit. ***/
                if(err == stop_condition)
                {
                    return stop_condition;
                }
            } while(*cur == BRACKET_OPEN && cur < last_char); 


            /*** If we ever get here, it means we've evaluated every operand without short circuiting -- meaning that the operation failed. ***/
            return(stop_condition == FR_EVAL_TRUE ? FR_EVAL_FALSE : FR_EVAL_TRUE);
        }


    case('!'): /***** Not. *****/
        /**** Child. ****/
        cur++;
        err = filterv1(cur, &cur, slp_attr, recursion_depth);
        /*** Return errors. ***/
        if(err != FR_EVAL_TRUE && err != FR_EVAL_FALSE)
        {
            return err;
        }

        /*** Perform "not". ***/
        return(err == FR_EVAL_TRUE ? FR_EVAL_FALSE : FR_EVAL_TRUE);

    default: /***** Unknown operator. *****/
        ;
        /* We don't do anything here because this will catch the first character of every leaf predicate. */
    }

    /***** Check for leaf operator. *****/
    if(IS_VALID_TAG_CHAR(*cur))
    {
        Operation op;
        char *lhs, *rhs; /* The two operands. */
        char *val_start; /* The character after the operator. ie, the start of the rhs. */
        int lhs_len, rhs_len; /* Length of the lhs/rhs. */
        SLPType type;
        SLPError slp_err;

        /**** Demux leaf op. ****/
        /* Support only "==", we look for the equals 
         * sign, and then poke around on either side of that for the real 
         * value. 
         */
        operator = (char *)memchr(cur, '=', last_char - cur);
        if(operator == 0 || *(operator + 1) != '=')
        {
            /**** No search operator. ****/
            return FR_PARSE_ERROR;
        }

        /* The rhs always follows the operator. (This doesn't really make 
        sense for PRESENT ops, but ignore that). */
        val_start = operator + 2; 

        if(operator == cur)
        {/* Check that we can poke back one char. */
            return FR_PARSE_ERROR;
        }

        op = EQUAL;

        /***** Get operands. *****/
        /**** Left. ****/
        lhs_len = operator - cur;
        lhs = (char *)cur;

        /**** Right ****/
        rhs_len = last_char - val_start;
        rhs = val_start;

        /***** Do leaf operation. *****/
        /**** Check that tag exists. ****/
        slp_err = SLPAttrGetType_len(slp_attr, lhs, lhs_len, &type);
        if(slp_err == SLP_TAG_ERROR)
        { /* Tag  doesn't exist. */
            return FR_EVAL_FALSE;
        }
        else if(slp_err == SLP_OK)
        { /* Tag exists. */
            /**** Do operation. *****/
            if(op == PRESENT)
            {
                /*** Since the PRESENT operation is the same for all types, 
                do that now. ***/
                return FR_EVAL_TRUE;
            }
            else
            {
                /*** A type-specific operation. ***/
                switch(type)
                {
                case(SLP_BOOLEAN):
                    err = bool_op(slp_attr, lhs, lhs_len, rhs, rhs_len, op); 
                    break;
                case(SLP_INTEGER):
                    err = int_op(slp_attr, lhs, lhs_len, rhs, op); 
                    break;
                case(SLP_KEYWORD):
                    err = keyw_op(slp_attr, lhs, rhs, op); 
                    break;
                case(SLP_STRING):
                    err = str_op(slp_attr, lhs, lhs_len, rhs, rhs_len, op); 
                    break;
                case(SLP_OPAQUE):
                    assert(0); /* Opaque is not yet supported. */
                }
            }
        }
        else
        { /* Some other tag-related error. */
            err = FR_INTERNAL_SYSTEM_ERROR;
        }

        assert(err != FR_UNSET);
        return err;
    }

    /***** No operator. *****/
    return FR_PARSE_ERROR;
}
#endif

/*--------------------------------------------------------------------------*/
FilterResult filter(const char *start, 
                    const char **end, 
                    SLPAttributes slp_attr, 
                    int recursion_depth)
/* Parameters:                                                              */
/* start -- (IN) The start of the string to work in.                        */
/* end -- (OUT) The end of the string processed. After successful processing*/
/*              (ie, no PARSE_ERRORs), this will be pointed at the character*/
/*              following the last char in this level of the expression.    */
/* slp_attr -- (IN) The attributes handle to compare on.                    */
/* return_value -- (OUT) The result of the search. Only valid if SLP_OK was */
/*                       returned.                                          */
/*                                                                          */
/* Returns: SLP_OK on successful search (ie, the search was do-able).       */
/*          SLP_PARSE_ERROR on search error.  The end of the expression is  */
/*          returned through end.                                           */
/*--------------------------------------------------------------------------*/
{
    char *operator; /* Pointer to the operator substring. */
    const char *cur; /* Current working character. */
    const char *last_char; /* The last character in the working string. */
    FilterResult err = FR_UNSET; /* The result of an evaluation. */
    FilterResult stop_condition = FR_UNSET;

    if(recursion_depth <= 0)
    {
        return FR_PARSE_ERROR;
    }

    recursion_depth--;

    if(*start != BRACKET_OPEN)
    {
        return FR_PARSE_ERROR;
    }

    /***** Get the current expression. *****/
    last_char = *end = find_bracket_end(start);
    if(*end == 0)
    {
        return FR_PARSE_ERROR;
    }
    (*end)++; /* Move the end pointer past the closing bracket. */

    /**** 
     * Check for the three legal characters that can follow an expression,
     * if the following character isn't one of those, the following character 
     * is trash.
     ****/
    if(!(**end == BRACKET_OPEN || **end == BRACKET_CLOSE || **end == '\0'))
    {
        return FR_PARSE_ERROR;
    }

    /***** check for boolean op. *****/
    cur = start;
    cur++;

    switch(*cur)
    {
    case('&'): /***** And. *****/
    case('|'): /***** Or. *****/
        {
            if(*cur == '&')
            {
                stop_condition = FR_EVAL_FALSE;
            }
            else if(*cur == '|')
            {
                stop_condition = FR_EVAL_TRUE;
            }

            cur++; /* Move past operator. */

            /*** Ensure that we have at least one operator. ***/
            if(*cur != BRACKET_OPEN || cur >= last_char)
            {
                return FR_PARSE_ERROR;
            }

            /*** Evaluate each operand. ***/
            /* NOTE: Due to the condition on the above "if", we are guarenteed that the first iteration of the loop is valid. */
            do 
            {
                err = filter(cur, &cur, slp_attr, recursion_depth);
                /*** Propagate errors. ***/
                if(err != FR_EVAL_TRUE && err != FR_EVAL_FALSE)
                {
                    return err;
                }

                /*** Short circuit. ***/
                if(err == stop_condition)
                {
                    return stop_condition;
                }
            } while(*cur == BRACKET_OPEN && cur < last_char); 


            /*** If we ever get here, it means we've evaluated every operand without short circuiting -- meaning that the operation failed. ***/
            return(stop_condition == FR_EVAL_TRUE ? FR_EVAL_FALSE : FR_EVAL_TRUE);
        }


    case('!'): /***** Not. *****/
        /**** Child. ****/
        cur++;
        err = filter(cur, &cur, slp_attr, recursion_depth);
        /*** Return errors. ***/
        if(err != FR_EVAL_TRUE && err != FR_EVAL_FALSE)
        {
            return err;
        }

        /*** Perform "not". ***/
        return(err == FR_EVAL_TRUE ? FR_EVAL_FALSE : FR_EVAL_TRUE);

    default: /***** Unknown operator. *****/
        ;
        /* We don't do anything here because this will catch the first character of every leaf predicate. */
    }

    /***** Check for leaf operator. *****/
    if(IS_VALID_TAG_CHAR(*cur))
    {
        Operation op;
        char *lhs, *rhs; /* The two operands. */
        char *val_start; /* The character after the operator. ie, the start of the rhs. */
        int lhs_len, rhs_len; /* Length of the lhs/rhs. */
        SLPType type;
        SLPError slp_err;

        /**** Demux leaf op. ****/
        /* Since all search operators contain a "=", we look for the equals 
         * sign, and then poke around on either side of that for the real 
         * value. 
         */
        operator = (char *)memchr(cur, '=', last_char - cur);
        if(operator == 0)
        {
            /**** No search operator. ****/
            return FR_PARSE_ERROR;
        }

        /* The rhs always follows the operator. (This doesn't really make 
        sense for PRESENT ops, but ignore that). */
        val_start = operator + 1; 

        /* Check for APPROX, GREATER, or LESS. Note that we shuffle the 
        operator pointer back to point at the start of the op. */
        if(operator == cur)
        {/* Check that we can poke back one char. */
            return FR_PARSE_ERROR;
        }

        switch(*(operator - 1))
        {
        case('~'):
            op = EQUAL; /* See Assumptions. */
            operator--;
            break;
        case('>'):
            op = GREATER;
            operator--;
            break;
        case('<'):
            op = LESS;
            operator--;
            break;
        default: /* No prefix to the '='. */
            /**** Check for PRESENT. ****/
            if((operator == last_char - 2) && (*(operator+1) == '*'))
            {
                op = PRESENT;
            }
            /**** It's none of the above: therefore it's EQUAL. ****/
            else
            {
                op = EQUAL;
            }
        }


        /***** Get operands. *****/
        /**** Left. ****/
        lhs_len = operator - cur;
        lhs = (char *)cur;

        /**** Right ****/
        rhs_len = last_char - val_start;
        rhs = val_start;

        /***** Do leaf operation. *****/
        /**** Check that tag exists. ****/
        slp_err = SLPAttrGetType_len(slp_attr, lhs, lhs_len, &type);
        if(slp_err == SLP_TAG_ERROR)
        { /* Tag  doesn't exist. */
            return FR_EVAL_FALSE;
        }
        else if(slp_err == SLP_OK)
        { /* Tag exists. */
            /**** Do operation. *****/
            if(op == PRESENT)
            {
                /*** Since the PRESENT operation is the same for all types, 
                do that now. ***/
                return FR_EVAL_TRUE;
            }
            else
            {
                /*** A type-specific operation. ***/
                switch(type)
                {
                case(SLP_BOOLEAN):
                    err = bool_op(slp_attr, lhs, lhs_len, rhs, rhs_len, op); 
                    break;
                case(SLP_INTEGER):
                    err = int_op(slp_attr, lhs, lhs_len, rhs, op); 
                    break;
                case(SLP_KEYWORD):
                    err = keyw_op(slp_attr, lhs, rhs, op); 
                    break;
                case(SLP_STRING):
                    err = str_op(slp_attr, lhs, lhs_len, rhs, rhs_len, op); 
                    break;
                case(SLP_OPAQUE):
                    assert(0); /* Opaque is not yet supported. */
                }
            }
        }
        else
        { /* Some other tag-related error. */
            err = FR_INTERNAL_SYSTEM_ERROR;
        }

        assert(err != FR_UNSET);
        return err;
    }

    /***** No operator. *****/
    return FR_PARSE_ERROR;
}


/*=========================================================================*/
int SLPDPredicateTest(int version,
                      int attrlistlen,
                      const char* attrlist,
                      int predicatelen,
                      const char* predicate)
/* Determine whether the specified attribute list satisfies                */
/* the specified predicate                                                 */
/*                                                                         */
/* version    (IN) SLP version of the predicate string (should always be   */
/*                 2 since we don't handle SLPv1 predicates yet)           */
/*                                                                         */
/* attrlistlen  (IN) length of attrlist                                    */
/*                                                                         */
/* attr         (IN) attribute list to test                                */
/*                                                                         */
/* predicatelen (IN) length of the predicate string                        */
/*                                                                         */
/* predicate    (IN) the predicate string                                  */
/*                                                                         */
/* Returns: Boolean value.  Zero of test fails.  Non-zero if test fails    */
/*          or if there is a parse error in the predicate string           */
/*=========================================================================*/
{
    SLPAttributes   attr;
    const char      *end; /* Pointer to the end of the parsed attribute string. */
    FilterResult    err;
    char            attrnull;
    char            prednull;
    int             result = 0;

    /* An NULL or empty string is always true. */
    if(predicate == 0 || *(char *)predicate == 0)
    {
        return 1;
    }

    /* Attempt v1 too. */
    if(version != 2 
#if defined(ENABLE_SLPv1)        
       && version != 1
#endif
      )
    {
        return 1;
    }

    /* TRICKY: Temporarily NULL terminate the attribute list     */
    /*         and the predicate string.  We can do this because */
    /*         there is room in the corresponding SLPv2 SRVREG   */
    /*         and SRVRQST messages.  Basically we are squashing */
    /*         the authcount and the spi string length.  Don't   */
    /*         worry, we fix things up later and it is MUCH      */
    /*         faster than a malloc() for a new buffer 1 byte    */
    /*         longer!                                           */
    prednull = predicate[predicatelen];
    ((char*)predicate)[predicatelen] = 0;
    attrnull = attrlist[attrlistlen];
    ((char*)attrlist)[attrlistlen] = 0;

    /* Generate an SLPAttr from the comma delimited list */
    if(SLPAttrAlloc("en", NULL, SLP_FALSE, &attr) == 0)
    {
        if(SLPAttrFreshen(attr, attrlist) == 0)
        {
            switch(version)
            {
#if defined(ENABLE_SLPv1)
            case 1:
                err=filterv1(predicate, &end, attr, SLPD_ATTR_RECURSION_DEPTH);
                break;
#endif
            default:
                err=filter(predicate, &end, attr, SLPD_ATTR_RECURSION_DEPTH);
                break;
            }

            /* Check for trailing trash data. */
            if((err == FR_EVAL_TRUE || err == FR_EVAL_FALSE) && *end != 0)
            {
                result = 0;
            }
            else if(err == FR_EVAL_TRUE)
            {
                result = 1;
            }
        }

        SLPAttrFree(attr);
    }

    /* Un null terminate */
    ((char*)predicate)[predicatelen] = prednull;
    ((char*)attrlist)[attrlistlen] = attrnull;

    return result;
}



/*=========================================================================*/
int SLPDFilterAttributes(int attrlistlen,
                         const char* attrlist,
                         int taglistlen,
                         const char* taglist,
                         int* resultlen,
                         char** result)
/* Copies attributes from the specified attribute list to a result string  */
/* according to the taglist as described by section 10.4. of RFC 2608      */
/*                                                                         */
/* version    (IN) SLP version of the predicate string (should always be   */
/*                 2 since we don't handle SLPv1 predicates yet)           */
/*                                                                         */
/* attrlistlen  (IN) length of attrlist                                    */
/*                                                                         */
/* attr         (IN) attribute list to test                                */
/*                                                                         */
/* predicatelen (IN) length of the predicate string                        */
/*                                                                         */
/* predicate    (IN) the predicate string                                  */
/*                                                                         */
/* Returns: Zero on success.  Nonzero on failure                           */
/*=========================================================================*/
{
    SLPAttributes   attr;
    FilterResult    err;
    char            attrnull;
    char            tagnull;

    *result = 0;
    *resultlen = 0;

    /* TRICKY: Temporarily NULL terminate the attribute list     */
    /*         and the tag string.  We can do this because       */
    /*         there is room in the corresponding SLPv2 SRVREG   */
    /*         and ATTRRQST messages.  Basically we are squashing */
    /*         the authcount and the spi string length.  Don't   */
    /*         worry, we fix things up later and it is MUCH      */
    /*         faster than a malloc() for a new buffer 1 byte    */
    /*         longer!                                           */
    tagnull = taglist[taglistlen];
    ((char*)taglist)[taglistlen] = 0;
    attrnull = attrlist[attrlistlen];
    ((char*)attrlist)[attrlistlen] = 0;


    /* Generate an SLPAttr from the comma delimited list */
    err = 1;
    if(SLPAttrAlloc("en", NULL, SLP_FALSE, &attr) == 0)
    {
        if(SLPAttrFreshen(attr, attrlist) == 0)
        {
            err = SLPAttrSerialize(attr,
                                   taglist,
                                   result,
                                   *resultlen,
                                   resultlen,
                                   SLP_FALSE);
        }

        SLPAttrFree(attr);
    }

    /* SLPAttrSerialize counts the NULL terminator which we don't care about*/
    if(*resultlen)
    {
        *resultlen -= 1;
    }

    /* Un null terminate */
    ((char*)taglist)[taglistlen] = tagnull;
    ((char*)attrlist)[attrlistlen] = attrnull;

    return(*resultlen == 0);
}


syntax highlighted by Code2HTML, v. 0.9.1