/* $OpenLDAP$ */
/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
 *
 * Copyright 2000-2022 The OpenLDAP Foundation.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted only as authorized by the OpenLDAP
 * Public License.
 *
 * A copy of this license is available in the file LICENSE in the
 * top-level directory of the distribution or, alternatively, at
 * <http://www.OpenLDAP.org/license.html>.
 */
/* ACKNOWLEDGEMENT:
 * This work was initially developed by Pierangelo Masarati for
 * inclusion in OpenLDAP Software.
 */

#include <portable.h>

#include "rewrite-int.h"

/*
 * Compares two struct rewrite_context based on the name;
 * used by avl stuff
 */
static int
rewrite_context_cmp(
		const void *c1,
		const void *c2
)
{
	const struct rewrite_context *lc1, *lc2;
	
	lc1 = (const struct rewrite_context *)c1;
	lc2 = (const struct rewrite_context *)c2;
	
	assert( c1 != NULL );
	assert( c2 != NULL );
	assert( lc1->lc_name != NULL );
	assert( lc2->lc_name != NULL );
	
	return strcasecmp( lc1->lc_name, lc2->lc_name );
}

/*
 * Returns -1 in case a duplicate struct rewrite_context
 * has been inserted; used by avl stuff
 */
static int
rewrite_context_dup(
		void *c1,
		void *c2
		)
{
	struct rewrite_context *lc1, *lc2;
	
	lc1 = (struct rewrite_context *)c1;
	lc2 = (struct rewrite_context *)c2;
	
	assert( c1 != NULL );
	assert( c2 != NULL );
	assert( lc1->lc_name != NULL );
	assert( lc2->lc_name != NULL );
	
	return( strcasecmp( lc1->lc_name, lc2->lc_name) == 0 ? -1 : 0 );
}

/*
 * Finds the context named rewriteContext in the context tree
 */
struct rewrite_context *
rewrite_context_find(
		struct rewrite_info *info,
		const char *rewriteContext
)
{
	struct rewrite_context *context, c;

	assert( info != NULL );
	assert( rewriteContext != NULL );

	/*
	 * Fetches the required rewrite context
	 */
	c.lc_name = (char *)rewriteContext;
	context = (struct rewrite_context *)ldap_avl_find( info->li_context, 
			(caddr_t)&c, rewrite_context_cmp );
	if ( context == NULL ) {
		return NULL;
	}

	/*
	 * De-aliases the context if required
	 */
	if ( context->lc_alias ) {
		return context->lc_alias;
	}

	return context;
}

/*
 * Creates a new context called rewriteContext and stores in into the tree
 */
struct rewrite_context *
rewrite_context_create(
		struct rewrite_info *info,
		const char *rewriteContext
)
{
	struct rewrite_context *context;
	int rc;

	assert( info != NULL );
	assert( rewriteContext != NULL );
	
	context = calloc( sizeof( struct rewrite_context ), 1 );
	if ( context == NULL ) {
		return NULL;
	}
	
	/*
	 * Context name
	 */
	context->lc_name = strdup( rewriteContext );
	if ( context->lc_name == NULL ) {
		free( context );
		return NULL;
	}

	/*
	 * The first, empty rule
	 */
	context->lc_rule = calloc( sizeof( struct rewrite_rule ), 1 );
	if ( context->lc_rule == NULL ) {
		free( context->lc_name );
		free( context );
		return NULL;
	}
	memset( context->lc_rule, 0, sizeof( struct rewrite_rule ) );
	
	/*
	 * Add context to tree
	 */
	rc = ldap_avl_insert( &info->li_context, (caddr_t)context,
			rewrite_context_cmp, rewrite_context_dup );
	if ( rc == -1 ) {
		free( context->lc_rule );
		free( context->lc_name );
		free( context );
		return NULL;
	}

	return context;
}

/*
 * Finds the next rule according to a goto action statement,
 * or null in case of error.
 * Helper for rewrite_context_apply.
 */
static struct rewrite_rule *
rewrite_action_goto(
		struct rewrite_action *action,
		struct rewrite_rule *rule
)
{
	int n;
	
	assert( action != NULL );
	assert( action->la_args != NULL );
	assert( rule != NULL );
	
	n = ((int *)action->la_args)[ 0 ];
	
	if ( n > 0 ) {
		for ( ; n > 1 && rule != NULL ; n-- ) {
			rule = rule->lr_next;
		}
	} else if ( n <= 0 ) {
		for ( ; n < 1 && rule != NULL ; n++ ) {
			rule = rule->lr_prev;
		}
	}

	return rule;
}

/*
 * Rewrites string according to context; may return:
 *      OK:     fine; if *result != NULL rule matched and rewrite succeeded.
 *      STOP:   fine, rule matched; stop processing following rules
 *      UNWILL: rule matched; force 'unwilling to perform'
 */
int
rewrite_context_apply(
		struct rewrite_info *info,
		struct rewrite_op *op,
		struct rewrite_context *context,
		const char *string,
		char **result
)
{
	struct rewrite_rule *rule;
	char *s, *res = NULL;
	int return_code = REWRITE_REGEXEC_OK;
	
	assert( info != NULL );
	assert( op != NULL );
	assert( context != NULL );
	assert( context->lc_rule != NULL );
	assert( string != NULL );
	assert( result != NULL );

	op->lo_depth++;

	Debug( LDAP_DEBUG_TRACE, "==> rewrite_context_apply"
			" [depth=%d] string='%s'\n",
			op->lo_depth, string );
	assert( op->lo_depth > 0 );
	
	s = (char *)string;
	
	for ( rule = context->lc_rule->lr_next;
			rule != NULL && op->lo_num_passes < info->li_max_passes;
			rule = rule->lr_next, op->lo_num_passes++ ) {
		int rc;
		
		/*
		 * Apply a single rule
		 */
		rc = rewrite_rule_apply( info, op, rule, s, &res );
		
		/*
		 * A rule may return:
		 * 	OK 		with result != NULL if matched
		 * 	ERR		if anything was wrong
		 * 	UNWILLING	if the server should drop the request
		 * the latter case in honored immediately;
		 * the other two may require some special actions to take
		 * place.
		 */
		switch ( rc ) {
			
		case REWRITE_REGEXEC_ERR:
			Debug( LDAP_DEBUG_ANY, "==> rewrite_context_apply"
					" error ...\n" );

			/*
			 * Checks for special actions to be taken
			 * in case of error ...
			 */
			if ( rule->lr_action != NULL ) {
				struct rewrite_action *action;
				int do_continue = 0;
				
				for ( action = rule->lr_action;
						action != NULL;
						action = action->la_next ) {
					switch ( action->la_type ) {
					
					/*
					 * This action takes precedence
					 * over the others in case of failure
					 */
					case REWRITE_ACTION_IGNORE_ERR:
						Debug( LDAP_DEBUG_ANY,
					"==> rewrite_context_apply"
					" ignoring error ...\n" );
						do_continue = 1;
						break;

					/*
					 * Goto is honored only if it comes
					 * after ignore error
					 */
					case REWRITE_ACTION_GOTO:
						if ( do_continue ) {
							rule = rewrite_action_goto( action, rule );
							if ( rule == NULL ) {
								return_code = REWRITE_REGEXEC_ERR;
								goto rc_end_of_context;
							}
						}
						break;

					/*
					 * Other actions are ignored
					 */
					default:
						break;
					}
				}

				if ( do_continue ) {
					if ( rule->lr_next == NULL ) {
						res = s;
					}
					goto rc_continue;
				}
			}

			/* 
			 * Default behavior is to bail out ...
			 */
			return_code = REWRITE_REGEXEC_ERR;
			goto rc_end_of_context;
		
		/*
		 * OK means there were no errors or special return codes;
		 * if res is defined, it means the rule matched and we
		 * got a successful rewriting
		 */
		case REWRITE_REGEXEC_OK:

			/*
			 * It matched! Check for actions ...
			 */
			if ( res != NULL ) {
				struct rewrite_action *action;
				
				if ( s != string && s != res ) {
					free( s );
				}
				s = res;

				for ( action = rule->lr_action;
						action != NULL;
						action = action->la_next ) {

					switch ( action->la_type ) {

					/*
					 * This ends the rewrite context
					 * successfully
					 */
					case REWRITE_ACTION_STOP:
						goto rc_end_of_context;
					
					/*
					 * This instructs the server to return
					 * an `unwilling to perform' error
					 * message
					 */
					case REWRITE_ACTION_UNWILLING:
						return_code = REWRITE_REGEXEC_UNWILLING;
						goto rc_end_of_context;
					
					/*
					 * This causes the processing to
					 * jump n rules back and forth
					 */
					case REWRITE_ACTION_GOTO:
						rule = rewrite_action_goto( action, rule );
						if ( rule == NULL ) {
							return_code = REWRITE_REGEXEC_ERR;
							goto rc_end_of_context;
						}
						break;

					/*
					 * This ends the rewrite context
					 * and returns a user-defined
					 * error code
					 */
					case REWRITE_ACTION_USER:
						return_code = ((int *)action->la_args)[ 0 ];
						goto rc_end_of_context;
					
					default:
						/* ... */
						break;
					}
				}

			/*
			 * If result was OK and string didn't match,
			 * in case of last rule we need to set the
			 * result back to the string
			 */
			} else if ( rule->lr_next == NULL ) {
				res = s;
			}
			
			break;

		/*
		 * A STOP has propagated ...
		 */
		case REWRITE_REGEXEC_STOP:
			goto rc_end_of_context;

		/*
		 * This will instruct the server to return
		 * an `unwilling to perform' error message
		 */
		case REWRITE_REGEXEC_UNWILLING:
			return_code = REWRITE_REGEXEC_UNWILLING;
			goto rc_end_of_context;

		/*
		 * A user-defined error code has propagated ...
		 */
		default:
			assert( rc >= REWRITE_REGEXEC_USER );
			goto rc_end_of_context;

		}
		
rc_continue:;	/* sent here by actions that require to continue */

	}

rc_end_of_context:;
	*result = res;

	Debug( LDAP_DEBUG_TRACE, "==> rewrite_context_apply"
			" [depth=%d] res={%d,'%s'}\n",
			op->lo_depth, return_code, ( res ? res : "NULL" ) );

	assert( op->lo_depth > 0 );
	op->lo_depth--;

	return return_code;
}

void
rewrite_context_free(
		void *tmp
)
{
	struct rewrite_context *context = (struct rewrite_context *)tmp;

	assert( tmp != NULL );

	rewrite_context_destroy( &context );
}

int
rewrite_context_destroy(
		struct rewrite_context **pcontext
)
{
	struct rewrite_context *context;
	struct rewrite_rule *r;

	assert( pcontext != NULL );
	assert( *pcontext != NULL );

	context = *pcontext;

	assert( context->lc_rule != NULL );

	for ( r = context->lc_rule->lr_next; r; ) {
		struct rewrite_rule *cr = r;

		r = r->lr_next;
		rewrite_rule_destroy( &cr );
	}

	free( context->lc_rule );
	context->lc_rule = NULL;

	assert( context->lc_name != NULL );
	free( context->lc_name );
	context->lc_name = NULL;

	free( context );
	*pcontext = NULL;
	
	return 0;
}
