#if !defined(lint) && !defined(DOS)
static char rcsid[] = "$Id: detoken.c 769 2007-10-24 00:15:40Z hubert@u.washington.edu $";
#endif

/*
 * ========================================================================
 * Copyright 2013-2020 Eduardo Chappa
 * Copyright 2006 University of Washington
 *
 * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
 *
 * ========================================================================
 */

#include "../pith/headers.h"
#include "../pith/detoken.h"
#include "../pith/state.h"
#include "../pith/conf.h"
#include "../pith/status.h"
#include "../pith/pattern.h"
#include "../pith/reply.h"
#include "../pith/mailindx.h"
#include "../pith/options.h"


/*
 * Hook to read signature from local file
 */
char	*(*pith_opt_get_signature_file)(char *, int, int, int);


/*
 * Internal prototypes
 */
char	*detoken_guts(char *, int, ENVELOPE *, ACTION_S *, REDRAFT_POS_S **, int, int *);
char	*handle_if_token(char *, char *, int, ENVELOPE *, ACTION_S *, char **);
char	*get_token_arg(char *, char **);


/*
 * Detokenize signature or template files.
 *
 * If is_sig, we always use literal sigs before sigfiles if they are
 * defined. So, check for role->litsig and use it. If it doesn't exist, use
 * the global literal sig if defined. Else the role->sig file or the
 * global signature file.
 *
 * If !is_sig, use role->template.
 *
 * So we start with a literal signature or a signature or template file.
 * If that's a file, we read it first. The file could be remote.
 * Then we detokenize the literal signature or file contents and return
 * an allocated string which the caller frees.
 *
 * Args    role -- See above about what happens depending on is_sig.
 *		   relative to the pinerc dir.
 *          env -- The envelope to use for detokenizing. May be NULL.
 *  prenewlines -- How many blank lines should be included at start.
 * postnewlines -- How many blank lines should be included after.
 *       is_sig -- This is a signature (not a template file).
 *  redraft_pos -- This is a return value. If it is non-NULL coming in,
 *		   then the cursor position is returned here.
 *         impl -- This is a combination argument which is both an input
 *		   argument and a return value. If it is non-NULL and = 0,
 *		   that means that we want the cursor position returned here,
 *		   even if that position is set implicitly to the end of
 *		   the output string. If it is = 1 coming in, that means
 *		   we only want the cursor position to be set if it is set
 *		   explicitly. If it is 2, or if redraft_pos is NULL,
 *                 we don't set it at all.
 *		   If the cursor position gets set explicitly by a
 *		   _CURSORPOS_ token in the file then this is set to 2
 *		   on return. If the cursor position is set implicitly to
 *		   the end of the included file, then this is set to 1
 *                 on return.
 *
 * Returns -- An allocated string is returned.
 */
char *
detoken(ACTION_S *role, ENVELOPE *env, int prenewlines, int postnewlines,
	int is_sig, REDRAFT_POS_S **redraft_pos, int *impl)
{
    char *ret = NULL,
         *src = NULL,
         *literal_sig = NULL,
         *sigfile = NULL;

    if(is_sig){
	/*
	 * If role->litsig is set, we use it;
	 * Else, if VAR_LITERAL_SIG is set, we use that;
	 * Else, if role->sig is set, we use that;
	 * Else, if VAR_SIGNATURE_FILE is set, we use that.
	 * This can be a little surprising if you set the VAR_LITERAL_SIG
	 * and don't set a role->litsig but do set a role->sig. The
	 * VAR_LITERAL_SIG will be used, not role->sig. The reason for this
	 * is mostly that it is much easier to display the right stuff
	 * in the various config screens if we do it that way. Besides,
	 * people will typically use only literal sigs or only sig files,
	 * there is no reason to mix them, so we don't provide support to
	 * do so.
	 */
	if(role && role->litsig)
	  literal_sig = role->litsig;
	else if(ps_global->VAR_LITERAL_SIG)
	  literal_sig = ps_global->VAR_LITERAL_SIG;
	else if(role && role->sig)
	  sigfile = role->sig;
	else
	  sigfile = ps_global->VAR_SIGNATURE_FILE;
    }
    else if(role && role->template)
      sigfile = role->template;

    if(literal_sig)
      src = get_signature_lit(literal_sig, prenewlines, postnewlines, is_sig,1);
    else if(sigfile && pith_opt_get_signature_file)
      src = (*pith_opt_get_signature_file)(sigfile, prenewlines, postnewlines, is_sig);
	
    if(src){
	if(*src)
	  ret = detoken_src(src, FOR_TEMPLATE, env, role, redraft_pos, impl);

	fs_give((void **)&src);
    }

    return(ret);
}


/*
 * Filter the source string from the template file and return an allocated
 * copy of the result with text replacements for the tokens.
 * Fill in offset in redraft_pos.
 *
 * This is really inefficient but who cares? It's just cpu time.
 */
char *
detoken_src(char *src, int for_what, ENVELOPE *env, ACTION_S *role,
	    REDRAFT_POS_S **redraft_pos, int *impl)
{
    int   loopcnt = 25;		/* just in case, avoid infinite loop */
    char *ret, *str1, *str2;
    int   done = 0;

    if(!src)
      return(src);
    
    /*
     * We keep running it through until it stops changing so user can
     * nest calls to token stuff.
     */
    str1 = src;
    do {
	/* short-circuit if no chance it will change */
	if(strindex(str1, '_'))
	  str2 = detoken_guts(str1, for_what, env, role, NULL, 0, NULL);
	else
	  str2 = str1;

	if(str1 && str2 && (str1 == str2 || !strcmp(str1, str2))){
	    done++;				/* It stopped changing */
	    if(str1 && str1 != src && str1 != str2)
	      fs_give((void **)&str1);
	}
	else{					/* Still changing */
	    if(str1 && str1 != src && str1 != str2)
	      fs_give((void **)&str1);
	    
	    str1 = str2;
	}

    } while(str2 && !done && loopcnt-- > 0);

    /*
     * Have to run it through once more to get the redraft_pos and
     * to remove any backslash escape for a token.
     */
    if((str2 && strindex(str2, '_')) ||
       (impl && *impl == 0 && redraft_pos && !*redraft_pos)){
	ret = detoken_guts(str2, for_what, env, role, redraft_pos, 1, impl);
	if(str2 != src)
	  fs_give((void **)&str2);
    }
    else if(str2){
	if(str2 == src)
	  ret = cpystr(str2);
	else
	  ret = str2;
    }

    return(ret);
}


/*
 * The guts of the detokenizing routines. Filter the src string looking for
 * tokens and replace them with the appropriate text. In the case of the
 * cursor_pos token we set redraft_pos instead.
 *
 * Args        src --  The source string
 *        for_what --  
 *             env --  Envelope to look in for token replacements.
 *     redraft_pos --  Return the redraft offset here, if non-zero.
 *       last_pass --  This is a flag to tell detoken_guts whether or not to do
 *                     the replacement for _CURSORPOS_. Leave it as is until
 *                     the last pass. We need this because we want to defer
 *                     cursor placement until the very last call to detoken,
 *                     otherwise we'd have to keep track of the cursor
 *                     position as subsequent text replacements (nested)
 *                     take place.
 *                     This same flag is also used to decide when to eliminate
 *                     backslash escapes from in front of tokens. The only
 *                     use of backslash escapes is to escape an entire token.
 *                     That is, \_DATE_ is a literal _DATE_, but any other
 *                     backslash is a literal backslash. That way, nobody
 *                     but wackos will have to worry about backslashes.
 *         impl -- This is a combination argument which is both an input
 *		   argument and a return value. If it is non-NULL and 0
 *		   coming in, that means that we should set redraft_pos,
 *		   even if that position is set implicitly to the end of
 *		   the output string. If it is 1 coming in, that means
 *		   we only want the cursor position to be set if it is set
 *		   explicitly. If it is 2 coming in (or if
 *                 redraft_pos is NULL) then we don't set it at all.
 *		   If the cursor position gets set explicitly by a
 *		   _CURSORPOS_ token in the file then this is set to 2
 *		   on return. If the cursor position is set implicitly to
 *		   the end of the included file, then this is set to 1
 *                 on return.
 *
 * Returns   pointer to alloced result
 */
char *
detoken_guts(char *src, int for_what, ENVELOPE *env, ACTION_S *role,
	     REDRAFT_POS_S **redraft_pos, int last_pass, int *impl)
{
#define MAXSUB 500
    char          *p, *q = NULL, *dst = NULL;
    char           subbuf[MAXSUB+1], *repl;
    INDEX_PARSE_T *pt;
    long           l, cnt = 0L;
    int            sizing_pass = 1, suppress_tokens = 0;

    if(!src)
      return(NULL);

top:

    /*
     * The tokens we look for begin with _. The only escaping mechanism
     * is a backslash in front of a token. This will give you the literal
     * token. So \_DATE_ is a literal _DATE_.
     * Tokens like _word_ are replaced with the appropriate text if
     * word is recognized. If _word_ is followed immediately by a left paren
     * it is an if-else thingie. _word_(match_this,if_text,else_text) means to
     * replace that with either the if_text or else_text depending on whether
     * what _word_ (without the paren) would produce matches match_this or not.
     */
    p = src;
    while(*p){
	switch(*p){
	  case '_':		/* possible start of token */
	    if(!suppress_tokens &&
	       (pt = itoktype(p+1, for_what | DELIM_USCORE)) != NULL){
		char *free_this = NULL;

		p += (strlen(pt->name) + 2);	/* skip over token */

		repl = subbuf;
		subbuf[0] = '\0';

		if(pt->ctype == iCursorPos){
		    if(!last_pass){	/* put it back */
			subbuf[0] = '_';
			strncpy(subbuf+1, pt->name, sizeof(subbuf)-2);
			subbuf[sizeof(subbuf)-1] = '\0';
			strncat(subbuf, "_", sizeof(subbuf)-strlen(subbuf)-1);
			subbuf[sizeof(subbuf)-1] = '\0';
		    }

		    if(!sizing_pass){
			if(q-dst < cnt+1)
			  *q = '\0';

			l = strlen(dst);
			if(redraft_pos && impl && *impl != 2){
			    if(!*redraft_pos){
				*redraft_pos =
				 (REDRAFT_POS_S *)fs_get(sizeof(**redraft_pos));
				memset((void *)*redraft_pos, 0,
				       sizeof(**redraft_pos));
				(*redraft_pos)->hdrname = cpystr(":");
			    }

			    (*redraft_pos)->offset  = l;
			    *impl = 2;	/* set explicitly */
			}
		    }
		}
		else if(pt->what_for & FOR_REPLY_INTRO)
		  repl = get_reply_data(env, role, pt->ctype,
					subbuf, sizeof(subbuf)-1);

		if(*p == LPAREN){		/* if-else construct */
		    char *skip_ahead;

		    repl = free_this = handle_if_token(repl, p, for_what,
						       env, role,
						       &skip_ahead);
		    p = skip_ahead;
		}

		if(repl && repl[0]){
		    if(sizing_pass)
		      cnt += (long)strlen(repl);
		    else{
			strncpy(q, repl, cnt-(q-dst));
			dst[cnt] = '\0';
			q += strlen(repl);
		    }
		}

		if(free_this)
		  fs_give((void **)&free_this);
	    }
	    else{	/* unrecognized token, treat it just like text */
		suppress_tokens = 0;
		if(sizing_pass)
		  cnt++;
		else if(q-dst < cnt+1)
		  *q++ = *p;

		p++;
	    }

	    break;

	  case BSLASH:
	    /*
	     * If a real token follows the backslash, then the backslash
	     * is here to escape the token. Otherwise, it's just a
	     * regular character.
	     */
	    if(*(p+1) == '_' &&
	       ((pt = itoktype(p+2, for_what | DELIM_USCORE)) != NULL)){
		/*
		 * Backslash is escape for literal token.
		 * If we're on the last pass we want to eliminate the
		 * backslash, otherwise we keep it.
		 * In either case, suppress_tokens will cause the token
		 * lookup to be skipped above so that the token will
		 * be treated as literal text.
		 */
		suppress_tokens++;
		if(last_pass){
		    p++;
		    break;
		}
		/* else, fall through and keep backslash */
	    }
	    /* this is a literal backslash, fall through */
	
	  default:
	    if(sizing_pass)
	      cnt++;
	    else if(q-dst < cnt+1)
	      *q++ = *p;	/* copy the character */

	    p++;
	    break;
	}
    }

    if(!sizing_pass && q-dst < cnt+1)
      *q = '\0';

    if(sizing_pass){
	sizing_pass = 0;
	/*
	 * Now we're done figuring out how big the answer will be. We
	 * allocate space for it and go back through filling it in.
	 */
	cnt = MAX(cnt, 0L);
	q = dst = (char *)fs_get((cnt + 1) * sizeof(char));
	goto top;
    }

    /*
     * Set redraft_pos to character following the template, unless
     * it has already been set.
     */
    if(dst && impl && *impl == 0 && redraft_pos && !*redraft_pos){
	*redraft_pos = (REDRAFT_POS_S *)fs_get(sizeof(**redraft_pos));
	memset((void *)*redraft_pos, 0, sizeof(**redraft_pos));
	(*redraft_pos)->offset  = strlen(dst);
	(*redraft_pos)->hdrname = cpystr(":");
	*impl = 1;
    }

    if(dst && cnt >= 0)
      dst[cnt] = '\0';

    return(dst);
}


/*
 * Do the if-else part of the detokenization for one case of if-else.
 * The input src should like (match_this, if_matched, else)...
 * 
 * Args expands_to -- This is what the token to the left of the paren
 *                    expanded to, and this is the thing we're going to
 *                    compare with the match_this part.
 *             src -- The source string beginning with the left paren.
 *        for_what -- 
 *             env -- 
 *      skip_ahead -- Tells caller how long the (...) part was so caller can
 *                    skip over that part of the source.
 *
 * Returns -- an allocated string which is the answer, or NULL if nothing.
 */
char *
handle_if_token(char *expands_to, char *src, int for_what, ENVELOPE *env,
		ACTION_S *role, char **skip_ahead)
{
    char *ret = NULL;
    char *skip_to;
    char *match_this, *if_matched, *else_part;

    if(skip_ahead)
      *skip_ahead = src;

    if(!src || *src != LPAREN){
	dprint((1,"botch calling handle_if_token, missing paren\n"));
	return(ret);
    }

    if(!*++src){
	q_status_message(SM_ORDER, 3, 3,
	    "Unexpected end of token string in Reply-LeadIn, Sig, or template");
	return(ret);
    }

    match_this = get_token_arg(src, &skip_to);
    
    /*
     * If the match_this argument is a token, detokenize it first.
     */
    if(match_this && *match_this == '_'){
	char *exp_match_this;

	exp_match_this = detoken_src(match_this, for_what, env,
				     role, NULL, NULL);
	fs_give((void **)&match_this);
	match_this = exp_match_this;
    }

    if(!match_this)
      match_this = cpystr("");

    if(!expands_to)
      expands_to = "";

    src = skip_to;
    while(src && *src && (isspace((unsigned char)*src) || *src == ','))
      src++;

    if_matched = get_token_arg(src, &skip_to);
    src = skip_to;
    while(src && *src && (isspace((unsigned char)*src) || *src == ','))
      src++;

    else_part = get_token_arg(src, &skip_to);
    src = skip_to;
    while(src && *src && *src != RPAREN)
      src++;

    if(src && *src == RPAREN)
      src++;

    if(skip_ahead)
      *skip_ahead = src;

    if(!strcmp(match_this, expands_to)){
	ret = if_matched;
	if(else_part)
	  fs_give((void **)&else_part);
    }
    else{
	ret = else_part;
	if(if_matched)
	  fs_give((void **)&if_matched);
    }

    fs_give((void **)&match_this);

    return(ret);
}


char *
get_token_arg(char *src, char **skip_to)
{
    int   quotes = 0, done = 0;
    char *ret = NULL, *p;

    while(*src && isspace((unsigned char)*src))	/* skip space before string */
      src++;

    if(*src == RPAREN){
	if(skip_to)
	  *skip_to = src;

	return(ret);
    }

    p = ret = (char *)fs_get((strlen(src) + 1) * sizeof(char));
    while(!done){
	switch(*src){
	  case QUOTE:
	    if(++quotes == 2)
	      done++;

	    src++;
	    break;

	  case BSLASH:	/* don't count \" as a quote, just copy */
	    if(*(src+1) == BSLASH || *(src+1) == QUOTE){
		src++;  /* skip backslash */
		*p++ = *src++;
	    }
	    else
	      src++;

	    break;
	
	  case SPACE:
	  case TAB:
	  case RPAREN:
	  case COMMA:
	    if(quotes)
	      *p++ = *src++;
	    else
	      done++;
	    
	    break;

	  case '\0':
	    done++;
	    break;

	  default:
	    *p++ = *src++;
	    break;
	}
    }

    *p = '\0';
    if(skip_to)
      *skip_to = src;
    
    return(ret);
}
