/* $OpenLDAP$ */
/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
 *
 * Copyright 2000-2021 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"

/*
 * Global data
 */

/*
 * This becomes the running context for subsequent calls to
 * rewrite_parse; it can be altered only by a 
 * rewriteContext config line or by a change in info.
 */
struct rewrite_context *rewrite_int_curr_context = NULL;

/*
 * Inits the info
 */
struct rewrite_info *
rewrite_info_init(
		int mode
)
{
	struct rewrite_info *info;
	struct rewrite_context *context;

	switch ( mode ) {
	case REWRITE_MODE_ERR:
	case REWRITE_MODE_OK:
	case REWRITE_MODE_COPY_INPUT:
	case REWRITE_MODE_USE_DEFAULT:
		break;
	default:
		mode = REWRITE_MODE_USE_DEFAULT;
		break;
		/* return NULL */
	}

	/*
	 * Resets the running context for parsing ...
	 */
	rewrite_int_curr_context = NULL;

	info = calloc( sizeof( struct rewrite_info ), 1 );
	if ( info == NULL ) {
		return NULL;
	}

	info->li_state = REWRITE_DEFAULT;
	info->li_max_passes = REWRITE_MAX_PASSES;
	info->li_max_passes_per_rule = REWRITE_MAX_PASSES;
	info->li_rewrite_mode = mode;

	/*
	 * Add the default (empty) rule
	 */
	context = rewrite_context_create( info, REWRITE_DEFAULT_CONTEXT );
	if ( context == NULL ) {
		free( info );
		return NULL;
	}

#ifdef USE_REWRITE_LDAP_PVT_THREADS
	if ( ldap_pvt_thread_rdwr_init( &info->li_cookies_mutex ) ) {
		avl_free( info->li_context, rewrite_context_free );
		free( info );
		return NULL;
	}
	if ( ldap_pvt_thread_rdwr_init( &info->li_params_mutex ) ) {
		ldap_pvt_thread_rdwr_destroy( &info->li_cookies_mutex );
		avl_free( info->li_context, rewrite_context_free );
		free( info );
		return NULL;
	}
#endif /* USE_REWRITE_LDAP_PVT_THREADS */
	
	return info;
}

/*
 * Cleans up the info structure
 */
int
rewrite_info_delete(
		struct rewrite_info **pinfo
)
{
	struct rewrite_info	*info;

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

	info = *pinfo;
	
	if ( info->li_context ) {
		avl_free( info->li_context, rewrite_context_free );
	}
	info->li_context = NULL;

	if ( info->li_maps ) {
		avl_free( info->li_maps, rewrite_builtin_map_free );
	}
	info->li_maps = NULL;

	rewrite_session_destroy( info );

#ifdef USE_REWRITE_LDAP_PVT_THREADS
	ldap_pvt_thread_rdwr_destroy( &info->li_cookies_mutex );
#endif /* USE_REWRITE_LDAP_PVT_THREADS */

	rewrite_param_destroy( info );
	
#ifdef USE_REWRITE_LDAP_PVT_THREADS
	ldap_pvt_thread_rdwr_destroy( &info->li_params_mutex );
#endif /* USE_REWRITE_LDAP_PVT_THREADS */

	free( info );
	*pinfo = NULL;

	return REWRITE_SUCCESS;
}

/*
 * Rewrites a string according to context.
 * If the engine is off, OK is returned, but the return string will be NULL.
 * In case of 'unwilling to perform', UNWILLING is returned, and the
 * return string will also be null. The same in case of error.
 * Otherwise, OK is returned, and result will hold a newly allocated string
 * with the rewriting.
 * 
 * What to do in case of non-existing rewrite context is still an issue.
 * Four possibilities:
 * 	- error, 
 * 	- ok with NULL result, 
 * 	- ok with copy of string as result,
 * 	- use the default rewrite context.
 */
int
rewrite(
		struct rewrite_info *info,
		const char *rewriteContext,
		const char *string,
		char **result
)
{
	return rewrite_session( info, rewriteContext, 
			string, NULL, result );
}

int
rewrite_session(
		struct rewrite_info *info,
		const char *rewriteContext,
		const char *string,
		const void *cookie,
		char **result
)
{
	struct rewrite_context *context;
	struct rewrite_op op = { 0, 0, NULL, NULL, NULL };
	int rc;
	
	assert( info != NULL );
	assert( rewriteContext != NULL );
	assert( string != NULL );
	assert( result != NULL );

	/*
	 * cookie can be null; means: don't care about session stuff
	 */

	*result = NULL;
	op.lo_cookie = cookie;
	
	/*
	 * Engine not on means no failure, but explicit no rewriting
	 */
	if ( info->li_state != REWRITE_ON ) {
		rc = REWRITE_REGEXEC_OK;
		goto rc_return;
	}
	
	/*
	 * Undefined context means no rewriting also
	 * (conservative, are we sure it's what we want?)
	 */
	context = rewrite_context_find( info, rewriteContext );
	if ( context == NULL ) {
		switch ( info->li_rewrite_mode ) {
		case REWRITE_MODE_ERR:
			rc = REWRITE_REGEXEC_ERR;
			goto rc_return;
			
		case REWRITE_MODE_OK:
			rc = REWRITE_REGEXEC_OK;
			goto rc_return;

		case REWRITE_MODE_COPY_INPUT:
			*result = strdup( string );
			rc = ( *result != NULL ) ? REWRITE_REGEXEC_OK : REWRITE_REGEXEC_ERR;
			goto rc_return;

		case REWRITE_MODE_USE_DEFAULT:
			context = rewrite_context_find( info,
					REWRITE_DEFAULT_CONTEXT );
			break;
		}
	}

#if 0 /* FIXME: not used anywhere! (debug? then, why strdup?) */
	op.lo_string = strdup( string );
	if ( op.lo_string == NULL ) {
		rc = REWRITE_REGEXEC_ERR;
		goto rc_return;
	}
#endif
	
	/*
	 * Applies rewrite context
	 */
	rc = rewrite_context_apply( info, &op, context, string, result );
	assert( op.lo_depth == 0 );

#if 0 /* FIXME: not used anywhere! (debug? then, why strdup?) */	
	free( op.lo_string );
#endif
	
	switch ( rc ) {
	/*
	 * Success
	 */
	case REWRITE_REGEXEC_OK:
	case REWRITE_REGEXEC_STOP:
		/*
		 * If rewrite succeeded return OK regardless of how
		 * the successful rewriting was obtained!
		 */
		rc = REWRITE_REGEXEC_OK;
		break;
		
	
	/*
	 * Internal or forced error, return = NULL; rc already OK.
	 */
	case REWRITE_REGEXEC_UNWILLING:
	case REWRITE_REGEXEC_ERR:
		if ( *result != NULL ) {
			if ( *result != string ) {
				free( *result );
			}
			*result = NULL;
		}

	default:
		break;
	}

rc_return:;
	if ( op.lo_vars ) {
		rewrite_var_delete( op.lo_vars );
	}
	
	return rc;
}

