/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 * 
 * The contents of this file are subject to the Mozilla Public License Version 
 * 1.1 (the "License"); you may not use this file except in compliance with 
 * the License. You may obtain a copy of the License at 
 * http://www.mozilla.org/MPL/
 * 
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 * 
 * The Original Code is Mozilla Communicator client code, released
 * March 31, 1998.
 * 
 * The Initial Developer of the Original Code is
 * Netscape Communications Corporation.
 * Portions created by the Initial Developer are Copyright (C) 1998-1999
 * the Initial Developer. All Rights Reserved.
 * 
 * Contributor(s):
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 * 
 * ***** END LICENSE BLOCK ***** */
/*
 *  Copyright (c) 1993 Regents of the University of Michigan.
 *  All rights reserved.
 */
/*
 *  getfilter.c -- optional add-on to libldap
 */

#if 0
#ifndef lint 
static char copyright[] = "@(#) Copyright (c) 1993 Regents of the University of Michigan.\nAll rights reserved.\n";
#endif
#endif

#include "ldap-int.h"
#include "regex.h"

static int break_into_words( char *str, char *delims, char ***wordsp );

#if !defined( macintosh ) && !defined( DOS )
extern char	* LDAP_CALL re_comp();
#endif

#define FILT_MAX_LINE_LEN	1024

LDAPFiltDesc *
LDAP_CALL
ldap_init_getfilter( char *fname )
{
    FILE		*fp;
    char		*buf;
    long		rlen, len;
    int 		eof;
    LDAPFiltDesc	*lfdp;

    if (( fp = NSLDAPI_FOPEN( fname, "r" )) == NULL ) {
	return( NULL );
    }

    if ( fseek( fp, 0L, SEEK_END ) != 0 ) {	/* move to end to get len */
	fclose( fp );
	return( NULL );
    }

    len = ftell( fp );

    if ( fseek( fp, 0L, SEEK_SET ) != 0 ) {	/* back to start of file */
	fclose( fp );
	return( NULL );
    }

    if (( buf = NSLDAPI_MALLOC( (size_t)len )) == NULL ) {
	fclose( fp );
	return( NULL );
    }

    rlen = fread( buf, 1, (size_t)len, fp );
    eof = feof( fp );
    fclose( fp );

    if ( rlen != len && !eof ) {	/* error:  didn't get the whole file */
	NSLDAPI_FREE( buf );
	return( NULL );
    }


    lfdp = ldap_init_getfilter_buf( buf, rlen );
    NSLDAPI_FREE( buf );

    return( lfdp );
}


LDAPFiltDesc *
LDAP_CALL
ldap_init_getfilter_buf( char *buf, long buflen )
{
    LDAPFiltDesc	*lfdp;
    LDAPFiltList	*flp, *nextflp;
    LDAPFiltInfo	*fip, *nextfip;
    char		*errmsg, *tag, **tok;
    int			tokcnt, i;

    if ( (buf == NULL) || (buflen < 0) ||
	 ( lfdp = (LDAPFiltDesc *)NSLDAPI_CALLOC(1, sizeof( LDAPFiltDesc)))
	 == NULL ) {
	return( NULL );
    }

    flp = nextflp = NULL;
    fip = NULL;
    tag = NULL;

    while ( buflen > 0 && ( tokcnt = nsldapi_next_line_tokens( &buf, &buflen,
	    &tok )) > 0 ) {
	switch( tokcnt ) {
	case 1:		/* tag line */
	    if ( tag != NULL ) {
		NSLDAPI_FREE( tag );
	    }
	    tag = tok[ 0 ];
	    NSLDAPI_FREE( tok );
	    break;
	case 4:
	case 5:		/* start of filter info. list */
	    if (( nextflp = (LDAPFiltList *)NSLDAPI_CALLOC( 1,
		    sizeof( LDAPFiltList ))) == NULL ) {
		ldap_getfilter_free( lfdp );
		return( NULL );
	    }
	    nextflp->lfl_tag = nsldapi_strdup( tag );
	    nextflp->lfl_pattern = tok[ 0 ];
	    if (( errmsg = re_comp( nextflp->lfl_pattern )) != NULL ) {
		char    msg[512];
		ldap_getfilter_free( lfdp );
		snprintf( msg, sizeof(msg),
			"bad regular expression \"%s\" - %s\n",
			nextflp->lfl_pattern, errmsg );
		ber_err_print( msg );
		nsldapi_free_strarray( tok );
		return( NULL );
	    }
		
	    nextflp->lfl_delims = tok[ 1 ];
	    nextflp->lfl_ilist = NULL;
	    nextflp->lfl_next = NULL;
	    if ( flp == NULL ) {	/* first one */
		lfdp->lfd_filtlist = nextflp;
	    } else {
		flp->lfl_next = nextflp;
	    }
	    flp = nextflp;
	    fip = NULL;
	    for ( i = 2; i < 5; ++i ) {
		tok[ i - 2 ] = tok[ i ];
	    }
	    /* fall through */

	case 2:
	case 3:		/* filter, desc, and optional search scope */
	    if ( nextflp != NULL ) { /* add to info list */
		if (( nextfip = (LDAPFiltInfo *)NSLDAPI_CALLOC( 1,
			sizeof( LDAPFiltInfo ))) == NULL ) {
		    ldap_getfilter_free( lfdp );
		    nsldapi_free_strarray( tok );
		    return( NULL );
		}
		if ( fip == NULL ) {	/* first one */
		    nextflp->lfl_ilist = nextfip;
		} else {
		    fip->lfi_next = nextfip;
		}
		fip = nextfip;
		nextfip->lfi_next = NULL;
		nextfip->lfi_filter = tok[ 0 ];
		nextfip->lfi_desc = tok[ 1 ];
		if ( tok[ 2 ] != NULL ) {
		    if ( strcasecmp( tok[ 2 ], "subtree" ) == 0 ) {
			nextfip->lfi_scope = LDAP_SCOPE_SUBTREE;
		    } else if ( strcasecmp( tok[ 2 ], "onelevel" ) == 0 ) {
			nextfip->lfi_scope = LDAP_SCOPE_ONELEVEL;
		    } else if ( strcasecmp( tok[ 2 ], "base" ) == 0 ) {
			nextfip->lfi_scope = LDAP_SCOPE_BASE;
		    } else {
			nsldapi_free_strarray( tok );
			ldap_getfilter_free( lfdp );
			return( NULL );
		    }
		    NSLDAPI_FREE( tok[ 2 ] );
		    tok[ 2 ] = NULL;
		} else {
		    nextfip->lfi_scope = LDAP_SCOPE_SUBTREE;	/* default */
		}
		nextfip->lfi_isexact = ( strchr( tok[ 0 ], '*' ) == NULL &&
			strchr( tok[ 0 ], '~' ) == NULL );
		NSLDAPI_FREE( tok );
	    }
	    break;

	default:
	    nsldapi_free_strarray( tok );
	    ldap_getfilter_free( lfdp );
	    return( NULL );
	}
    }

    if ( tag != NULL ) {
	NSLDAPI_FREE( tag );
    }

    return( lfdp );
}


int
LDAP_CALL
ldap_set_filter_additions( LDAPFiltDesc *lfdp, char *prefix, char *suffix )
{
    if ( lfdp == NULL ) {
	return( LDAP_PARAM_ERROR );
    }

    if ( lfdp->lfd_filtprefix != NULL ) {
	NSLDAPI_FREE( lfdp->lfd_filtprefix );
    }
    lfdp->lfd_filtprefix = ( prefix == NULL ) ? NULL : nsldapi_strdup( prefix );

    if ( lfdp->lfd_filtsuffix != NULL ) {
	NSLDAPI_FREE( lfdp->lfd_filtsuffix );
    }
    lfdp->lfd_filtsuffix = ( suffix == NULL ) ? NULL : nsldapi_strdup( suffix );

    return( LDAP_SUCCESS );
}


/*
 * ldap_setfilteraffixes() is deprecated -- use ldap_set_filter_additions()
 */
void
LDAP_CALL
ldap_setfilteraffixes( LDAPFiltDesc *lfdp, char *prefix, char *suffix )
{
    (void)ldap_set_filter_additions( lfdp, prefix, suffix );
}


LDAPFiltInfo *
LDAP_CALL
ldap_getfirstfilter( LDAPFiltDesc *lfdp, char *tagpat, char *value )
{
    LDAPFiltList	*flp;

    if ( lfdp == NULL || tagpat == NULL || value == NULL ) {
	return( NULL );	/* punt */
    }

    if ( lfdp->lfd_curvalcopy != NULL ) {
	NSLDAPI_FREE( lfdp->lfd_curvalcopy );
	NSLDAPI_FREE( lfdp->lfd_curvalwords );
    }

    NSLDAPI_FREE(lfdp->lfd_curval);
    if ((lfdp->lfd_curval = nsldapi_strdup(value)) == NULL) {
	return( NULL );
    }

    lfdp->lfd_curfip = NULL;

    for ( flp = lfdp->lfd_filtlist; flp != NULL; flp = flp->lfl_next ) {
	if ( re_comp( tagpat ) == NULL && re_exec( flp->lfl_tag ) == 1
		&& re_comp( flp->lfl_pattern ) == NULL
		&& re_exec( lfdp->lfd_curval ) == 1 ) {
	    lfdp->lfd_curfip = flp->lfl_ilist;
	    break;
	}
    }

    if ( lfdp->lfd_curfip == NULL ) {
	return( NULL );
    }

    if (( lfdp->lfd_curvalcopy = nsldapi_strdup( value )) == NULL ) {
	return( NULL );
    }

    if ( break_into_words( lfdp->lfd_curvalcopy, flp->lfl_delims,
		&lfdp->lfd_curvalwords ) < 0 ) {
	NSLDAPI_FREE( lfdp->lfd_curvalcopy );
	lfdp->lfd_curvalcopy = NULL;
	return( NULL );
    }

    return( ldap_getnextfilter( lfdp ));
}


LDAPFiltInfo *
LDAP_CALL
ldap_getnextfilter( LDAPFiltDesc *lfdp )
{
    LDAPFiltInfo	*fip;

    if ( lfdp == NULL || ( fip = lfdp->lfd_curfip ) == NULL ) {
	return( NULL );
    }

    lfdp->lfd_curfip = fip->lfi_next;

    ldap_build_filter( lfdp->lfd_filter, LDAP_FILT_MAXSIZ, fip->lfi_filter,
	    lfdp->lfd_filtprefix, lfdp->lfd_filtsuffix, NULL,
	    lfdp->lfd_curval, lfdp->lfd_curvalwords );
    lfdp->lfd_retfi.lfi_filter = lfdp->lfd_filter;
    lfdp->lfd_retfi.lfi_desc = fip->lfi_desc;
    lfdp->lfd_retfi.lfi_scope = fip->lfi_scope;
    lfdp->lfd_retfi.lfi_isexact = fip->lfi_isexact;

    return( &lfdp->lfd_retfi );
}


static char*
filter_add_strn( char *f, char *flimit, char *v, size_t vlen )
     /* Copy v into f.  If flimit is too small, return NULL;
      * otherwise return (f + vlen).
      */
{
    auto size_t flen = flimit - f;
    if ( vlen > flen ) { /* flimit is too small */
	if ( flen > 0 ) SAFEMEMCPY( f, v, flen );
	return NULL;
    }
    if ( vlen > 0 ) SAFEMEMCPY( f, v, vlen );
    return f + vlen;
}

static char*
filter_add_value( char *f, char *flimit, char *v, int escape_all )
     /* Copy v into f, but with parentheses escaped.  But only escape * and \
      * if escape_all is non-zero so that either "*" or "\2a" can be used in
      * v, with different meanings.
      * If flimit is too small, return NULL; otherwise
      * return (f + the number of bytes copied).
      */
{
    auto char x[4];
    auto size_t slen;
    while ( f && *v ) {
	switch ( *v ) {
	case '*':
	    if ( escape_all ) {
		f = filter_add_strn( f, flimit, "\\2a", 3 );
		v++;
	    } else {
		if ( f < flimit ) {
		    *f++ = *v++;
		} else {
		    f = NULL; /* overflow */
		}
	    }
	    break;

	case '(':
	case ')':
	    sprintf( x, "\\%02x", (unsigned)*v );
	    f = filter_add_strn( f, flimit, x, 3 );
	    v++;
	    break;

	case '\\':
	    if ( escape_all ) {
		f = filter_add_strn( f, flimit, "\\5c", 3 );
		v++;
	    } else {
		slen = (ldap_utf8isxdigit( v+1 ) &&
			ldap_utf8isxdigit( v+2 )) ? 3 : (v[1] ? 2 : 1);
		f = filter_add_strn( f, flimit, v, slen );
		v += slen;
	    }
	    break;
	    
	default:
	    if ( f < flimit ) {
		*f++ = *v++;
	    } else {
		f = NULL; /* overflow */
	    }
	    break;
	}
    }
    return f;
}

int
LDAP_CALL
ldap_create_filter( char *filtbuf, unsigned long buflen, char *pattern,
	char *prefix, char *suffix, char *attr, char *value, char **valwords )
{
	char	*p, *f, *flimit;
	int	i, wordcount, wordnum, endwordnum, escape_all;

    /* 
     * there is some confusion on what to create for a filter if 
     * attr or value are null pointers.  For now we just leave them
     * as TO BE DEALT with
     */

	if ( filtbuf == NULL || buflen == 0 || pattern == NULL ){
		return( LDAP_PARAM_ERROR );
	}
	
	if ( valwords == NULL ) {
	    wordcount = 0;
	} else {
	    for ( wordcount = 0; valwords[ wordcount ] != NULL; ++wordcount ) {
		;
	    }
	}

	f = filtbuf;
	flimit = filtbuf + buflen - 1;

	if ( prefix != NULL ) {
	    f = filter_add_strn( f, flimit, prefix, strlen( prefix ));
	}

	for ( p = pattern; f != NULL && *p != '\0'; ++p ) {
	    if ( *p == '%' ) {
		++p;
		if ( *p == 'v' || *p == 'e' ) {
		    escape_all = ( *p == 'e' );
		    if ( ldap_utf8isdigit( p+1 )) {
			++p;
			wordnum = *p - '1';
			if ( *(p+1) == '-' ) {
			    ++p;
			    if ( ldap_utf8isdigit( p+1 )) {
				++p;
				endwordnum = *p - '1';	/* e.g., "%v2-4" */
				if ( endwordnum > wordcount - 1 ) {
				    endwordnum = wordcount - 1;
				}
			    } else {
				endwordnum = wordcount - 1;  /* e.g., "%v2-" */
			    }
			} else {
			    endwordnum = wordnum;	/* e.g., "%v2" */
			}

			if ( wordcount > 0 ) {
			    for ( i = wordnum; i <= endwordnum; ++i ) {
				if ( i > wordnum ) {  /* add blank btw words */
				    f = filter_add_strn( f, flimit, " ", 1 );
				    if ( f == NULL ) break;
				}
				f = filter_add_value( f, flimit, valwords[ i ],
					escape_all );
				if ( f == NULL ) break;
			    }
			}
		    } else if ( *(p+1) == '$' ) {
			++p;
			if ( wordcount > 0 ) {
			    wordnum = wordcount - 1;
			    f = filter_add_value( f, flimit,
				    valwords[ wordnum ], escape_all );
			}
		    } else if ( value != NULL ) {
			f = filter_add_value( f, flimit, value, escape_all );
		    }
		} else if ( *p == 'a' && attr != NULL ) {
		    f = filter_add_strn( f, flimit, attr, strlen( attr ));
		} else {
		    *f++ = *p;
		}
	    } else {
		*f++ = *p;
	    }
	    if ( f > flimit ) { /* overflow */
		f = NULL;
	    }
	}

	if ( suffix != NULL && f != NULL) {
	    f = filter_add_strn( f, flimit, suffix, strlen( suffix ));
	}

	if ( f == NULL ) {
	    *flimit = '\0';
	    return( LDAP_SIZELIMIT_EXCEEDED );
	}
	*f = '\0';
	return( LDAP_SUCCESS );
}


/*
 * ldap_build_filter() is deprecated -- use ldap_create_filter() instead
 */
void
LDAP_CALL
ldap_build_filter( char *filtbuf, unsigned long buflen, char *pattern,
	char *prefix, char *suffix, char *attr, char *value, char **valwords )
{
    (void)ldap_create_filter( filtbuf, buflen, pattern, prefix, suffix, attr,
	    value, valwords );
}


static int
break_into_words( char *str, char *delims, char ***wordsp )
{
    char	*word, **words;
    int		count;
    char	*lasts;
	
    if (( words = (char **)NSLDAPI_CALLOC( 1, sizeof( char * ))) == NULL ) {
	return( -1 );
    }
    count = 0;
    words[ count ] = NULL;

    word = ldap_utf8strtok_r( str, delims, &lasts );
    while ( word != NULL ) {
	if (( words = (char **)NSLDAPI_REALLOC( words,
		( count + 2 ) * sizeof( char * ))) == NULL ) {
	    return( -1 );
	}

	words[ count ] = word;
	words[ ++count ] = NULL;
	word = ldap_utf8strtok_r( NULL, delims, &lasts );
    }
	
    *wordsp = words;
    return( count );
}
