* clear out some warnings by gcc 9.3.1.
[alpine.git] / pith / detoken.c
blob7e75b56425cd8ce1d74c8b1974fd40f915a20bb3
1 #if !defined(lint) && !defined(DOS)
2 static char rcsid[] = "$Id: detoken.c 769 2007-10-24 00:15:40Z hubert@u.washington.edu $";
3 #endif
5 /*
6 * ========================================================================
7 * Copyright 2013-2020 Eduardo Chappa
8 * Copyright 2006 University of Washington
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * ========================================================================
19 #include "../pith/headers.h"
20 #include "../pith/detoken.h"
21 #include "../pith/state.h"
22 #include "../pith/conf.h"
23 #include "../pith/status.h"
24 #include "../pith/pattern.h"
25 #include "../pith/reply.h"
26 #include "../pith/mailindx.h"
27 #include "../pith/options.h"
31 * Hook to read signature from local file
33 char *(*pith_opt_get_signature_file)(char *, int, int, int);
37 * Internal prototypes
39 char *detoken_guts(char *, int, ENVELOPE *, ACTION_S *, REDRAFT_POS_S **, int, int *);
40 char *handle_if_token(char *, char *, int, ENVELOPE *, ACTION_S *, char **);
41 char *get_token_arg(char *, char **);
45 * Detokenize signature or template files.
47 * If is_sig, we always use literal sigs before sigfiles if they are
48 * defined. So, check for role->litsig and use it. If it doesn't exist, use
49 * the global literal sig if defined. Else the role->sig file or the
50 * global signature file.
52 * If !is_sig, use role->template.
54 * So we start with a literal signature or a signature or template file.
55 * If that's a file, we read it first. The file could be remote.
56 * Then we detokenize the literal signature or file contents and return
57 * an allocated string which the caller frees.
59 * Args role -- See above about what happens depending on is_sig.
60 * relative to the pinerc dir.
61 * env -- The envelope to use for detokenizing. May be NULL.
62 * prenewlines -- How many blank lines should be included at start.
63 * postnewlines -- How many blank lines should be included after.
64 * is_sig -- This is a signature (not a template file).
65 * redraft_pos -- This is a return value. If it is non-NULL coming in,
66 * then the cursor position is returned here.
67 * impl -- This is a combination argument which is both an input
68 * argument and a return value. If it is non-NULL and = 0,
69 * that means that we want the cursor position returned here,
70 * even if that position is set implicitly to the end of
71 * the output string. If it is = 1 coming in, that means
72 * we only want the cursor position to be set if it is set
73 * explicitly. If it is 2, or if redraft_pos is NULL,
74 * we don't set it at all.
75 * If the cursor position gets set explicitly by a
76 * _CURSORPOS_ token in the file then this is set to 2
77 * on return. If the cursor position is set implicitly to
78 * the end of the included file, then this is set to 1
79 * on return.
81 * Returns -- An allocated string is returned.
83 char *
84 detoken(ACTION_S *role, ENVELOPE *env, int prenewlines, int postnewlines,
85 int is_sig, REDRAFT_POS_S **redraft_pos, int *impl)
87 char *ret = NULL,
88 *src = NULL,
89 *literal_sig = NULL,
90 *sigfile = NULL;
92 if(is_sig){
94 * If role->litsig is set, we use it;
95 * Else, if VAR_LITERAL_SIG is set, we use that;
96 * Else, if role->sig is set, we use that;
97 * Else, if VAR_SIGNATURE_FILE is set, we use that.
98 * This can be a little surprising if you set the VAR_LITERAL_SIG
99 * and don't set a role->litsig but do set a role->sig. The
100 * VAR_LITERAL_SIG will be used, not role->sig. The reason for this
101 * is mostly that it is much easier to display the right stuff
102 * in the various config screens if we do it that way. Besides,
103 * people will typically use only literal sigs or only sig files,
104 * there is no reason to mix them, so we don't provide support to
105 * do so.
107 if(role && role->litsig)
108 literal_sig = role->litsig;
109 else if(ps_global->VAR_LITERAL_SIG)
110 literal_sig = ps_global->VAR_LITERAL_SIG;
111 else if(role && role->sig)
112 sigfile = role->sig;
113 else
114 sigfile = ps_global->VAR_SIGNATURE_FILE;
116 else if(role && role->template)
117 sigfile = role->template;
119 if(literal_sig)
120 src = get_signature_lit(literal_sig, prenewlines, postnewlines, is_sig,1);
121 else if(sigfile && pith_opt_get_signature_file)
122 src = (*pith_opt_get_signature_file)(sigfile, prenewlines, postnewlines, is_sig);
124 if(src){
125 if(*src)
126 ret = detoken_src(src, FOR_TEMPLATE, env, role, redraft_pos, impl);
128 fs_give((void **)&src);
131 return(ret);
136 * Filter the source string from the template file and return an allocated
137 * copy of the result with text replacements for the tokens.
138 * Fill in offset in redraft_pos.
140 * This is really inefficient but who cares? It's just cpu time.
142 char *
143 detoken_src(char *src, int for_what, ENVELOPE *env, ACTION_S *role,
144 REDRAFT_POS_S **redraft_pos, int *impl)
146 int loopcnt = 25; /* just in case, avoid infinite loop */
147 char *ret, *str1, *str2;
148 int done = 0;
150 if(!src)
151 return(src);
154 * We keep running it through until it stops changing so user can
155 * nest calls to token stuff.
157 str1 = src;
158 do {
159 /* short-circuit if no chance it will change */
160 if(strindex(str1, '_'))
161 str2 = detoken_guts(str1, for_what, env, role, NULL, 0, NULL);
162 else
163 str2 = str1;
165 if(str1 && str2 && (str1 == str2 || !strcmp(str1, str2))){
166 done++; /* It stopped changing */
167 if(str1 && str1 != src && str1 != str2)
168 fs_give((void **)&str1);
170 else{ /* Still changing */
171 if(str1 && str1 != src && str1 != str2)
172 fs_give((void **)&str1);
174 str1 = str2;
177 } while(str2 && !done && loopcnt-- > 0);
180 * Have to run it through once more to get the redraft_pos and
181 * to remove any backslash escape for a token.
183 if((str2 && strindex(str2, '_')) ||
184 (impl && *impl == 0 && redraft_pos && !*redraft_pos)){
185 ret = detoken_guts(str2, for_what, env, role, redraft_pos, 1, impl);
186 if(str2 != src)
187 fs_give((void **)&str2);
189 else if(str2){
190 if(str2 == src)
191 ret = cpystr(str2);
192 else
193 ret = str2;
196 return(ret);
201 * The guts of the detokenizing routines. Filter the src string looking for
202 * tokens and replace them with the appropriate text. In the case of the
203 * cursor_pos token we set redraft_pos instead.
205 * Args src -- The source string
206 * for_what --
207 * env -- Envelope to look in for token replacements.
208 * redraft_pos -- Return the redraft offset here, if non-zero.
209 * last_pass -- This is a flag to tell detoken_guts whether or not to do
210 * the replacement for _CURSORPOS_. Leave it as is until
211 * the last pass. We need this because we want to defer
212 * cursor placement until the very last call to detoken,
213 * otherwise we'd have to keep track of the cursor
214 * position as subsequent text replacements (nested)
215 * take place.
216 * This same flag is also used to decide when to eliminate
217 * backslash escapes from in front of tokens. The only
218 * use of backslash escapes is to escape an entire token.
219 * That is, \_DATE_ is a literal _DATE_, but any other
220 * backslash is a literal backslash. That way, nobody
221 * but wackos will have to worry about backslashes.
222 * impl -- This is a combination argument which is both an input
223 * argument and a return value. If it is non-NULL and 0
224 * coming in, that means that we should set redraft_pos,
225 * even if that position is set implicitly to the end of
226 * the output string. If it is 1 coming in, that means
227 * we only want the cursor position to be set if it is set
228 * explicitly. If it is 2 coming in (or if
229 * redraft_pos is NULL) then we don't set it at all.
230 * If the cursor position gets set explicitly by a
231 * _CURSORPOS_ token in the file then this is set to 2
232 * on return. If the cursor position is set implicitly to
233 * the end of the included file, then this is set to 1
234 * on return.
236 * Returns pointer to alloced result
238 char *
239 detoken_guts(char *src, int for_what, ENVELOPE *env, ACTION_S *role,
240 REDRAFT_POS_S **redraft_pos, int last_pass, int *impl)
242 #define MAXSUB 500
243 char *p, *q = NULL, *dst = NULL;
244 char subbuf[MAXSUB+1], *repl;
245 INDEX_PARSE_T *pt;
246 long l, cnt = 0L;
247 int sizing_pass = 1, suppress_tokens = 0;
249 if(!src)
250 return(NULL);
252 top:
255 * The tokens we look for begin with _. The only escaping mechanism
256 * is a backslash in front of a token. This will give you the literal
257 * token. So \_DATE_ is a literal _DATE_.
258 * Tokens like _word_ are replaced with the appropriate text if
259 * word is recognized. If _word_ is followed immediately by a left paren
260 * it is an if-else thingie. _word_(match_this,if_text,else_text) means to
261 * replace that with either the if_text or else_text depending on whether
262 * what _word_ (without the paren) would produce matches match_this or not.
264 p = src;
265 while(*p){
266 switch(*p){
267 case '_': /* possible start of token */
268 if(!suppress_tokens &&
269 (pt = itoktype(p+1, for_what | DELIM_USCORE)) != NULL){
270 char *free_this = NULL;
272 p += (strlen(pt->name) + 2); /* skip over token */
274 repl = subbuf;
275 subbuf[0] = '\0';
277 if(pt->ctype == iCursorPos){
278 if(!last_pass){ /* put it back */
279 subbuf[0] = '_';
280 strncpy(subbuf+1, pt->name, sizeof(subbuf)-2);
281 subbuf[sizeof(subbuf)-1] = '\0';
282 strncat(subbuf, "_", sizeof(subbuf)-strlen(subbuf)-1);
283 subbuf[sizeof(subbuf)-1] = '\0';
286 if(!sizing_pass){
287 if(q-dst < cnt+1)
288 *q = '\0';
290 l = strlen(dst);
291 if(redraft_pos && impl && *impl != 2){
292 if(!*redraft_pos){
293 *redraft_pos =
294 (REDRAFT_POS_S *)fs_get(sizeof(**redraft_pos));
295 memset((void *)*redraft_pos, 0,
296 sizeof(**redraft_pos));
297 (*redraft_pos)->hdrname = cpystr(":");
300 (*redraft_pos)->offset = l;
301 *impl = 2; /* set explicitly */
305 else if(pt->what_for & FOR_REPLY_INTRO)
306 repl = get_reply_data(env, role, pt->ctype,
307 subbuf, sizeof(subbuf)-1);
309 if(*p == LPAREN){ /* if-else construct */
310 char *skip_ahead;
312 repl = free_this = handle_if_token(repl, p, for_what,
313 env, role,
314 &skip_ahead);
315 p = skip_ahead;
318 if(repl && repl[0]){
319 if(sizing_pass)
320 cnt += (long)strlen(repl);
321 else{
322 strncpy(q, repl, cnt-(q-dst));
323 dst[cnt] = '\0';
324 q += strlen(repl);
328 if(free_this)
329 fs_give((void **)&free_this);
331 else{ /* unrecognized token, treat it just like text */
332 suppress_tokens = 0;
333 if(sizing_pass)
334 cnt++;
335 else if(q-dst < cnt+1)
336 *q++ = *p;
338 p++;
341 break;
343 case BSLASH:
345 * If a real token follows the backslash, then the backslash
346 * is here to escape the token. Otherwise, it's just a
347 * regular character.
349 if(*(p+1) == '_' &&
350 ((pt = itoktype(p+2, for_what | DELIM_USCORE)) != NULL)){
352 * Backslash is escape for literal token.
353 * If we're on the last pass we want to eliminate the
354 * backslash, otherwise we keep it.
355 * In either case, suppress_tokens will cause the token
356 * lookup to be skipped above so that the token will
357 * be treated as literal text.
359 suppress_tokens++;
360 if(last_pass){
361 p++;
362 break;
364 /* else, fall through and keep backslash */
366 /* this is a literal backslash, fall through */
368 default:
369 if(sizing_pass)
370 cnt++;
371 else if(q-dst < cnt+1)
372 *q++ = *p; /* copy the character */
374 p++;
375 break;
379 if(!sizing_pass && q-dst < cnt+1)
380 *q = '\0';
382 if(sizing_pass){
383 sizing_pass = 0;
385 * Now we're done figuring out how big the answer will be. We
386 * allocate space for it and go back through filling it in.
388 cnt = MAX(cnt, 0L);
389 q = dst = (char *)fs_get((cnt + 1) * sizeof(char));
390 goto top;
394 * Set redraft_pos to character following the template, unless
395 * it has already been set.
397 if(dst && impl && *impl == 0 && redraft_pos && !*redraft_pos){
398 *redraft_pos = (REDRAFT_POS_S *)fs_get(sizeof(**redraft_pos));
399 memset((void *)*redraft_pos, 0, sizeof(**redraft_pos));
400 (*redraft_pos)->offset = strlen(dst);
401 (*redraft_pos)->hdrname = cpystr(":");
402 *impl = 1;
405 if(dst && cnt >= 0)
406 dst[cnt] = '\0';
408 return(dst);
413 * Do the if-else part of the detokenization for one case of if-else.
414 * The input src should like (match_this, if_matched, else)...
416 * Args expands_to -- This is what the token to the left of the paren
417 * expanded to, and this is the thing we're going to
418 * compare with the match_this part.
419 * src -- The source string beginning with the left paren.
420 * for_what --
421 * env --
422 * skip_ahead -- Tells caller how long the (...) part was so caller can
423 * skip over that part of the source.
425 * Returns -- an allocated string which is the answer, or NULL if nothing.
427 char *
428 handle_if_token(char *expands_to, char *src, int for_what, ENVELOPE *env,
429 ACTION_S *role, char **skip_ahead)
431 char *ret = NULL;
432 char *skip_to;
433 char *match_this, *if_matched, *else_part;
435 if(skip_ahead)
436 *skip_ahead = src;
438 if(!src || *src != LPAREN){
439 dprint((1,"botch calling handle_if_token, missing paren\n"));
440 return(ret);
443 if(!*++src){
444 q_status_message(SM_ORDER, 3, 3,
445 "Unexpected end of token string in Reply-LeadIn, Sig, or template");
446 return(ret);
449 match_this = get_token_arg(src, &skip_to);
452 * If the match_this argument is a token, detokenize it first.
454 if(match_this && *match_this == '_'){
455 char *exp_match_this;
457 exp_match_this = detoken_src(match_this, for_what, env,
458 role, NULL, NULL);
459 fs_give((void **)&match_this);
460 match_this = exp_match_this;
463 if(!match_this)
464 match_this = cpystr("");
466 if(!expands_to)
467 expands_to = "";
469 src = skip_to;
470 while(src && *src && (isspace((unsigned char)*src) || *src == ','))
471 src++;
473 if_matched = get_token_arg(src, &skip_to);
474 src = skip_to;
475 while(src && *src && (isspace((unsigned char)*src) || *src == ','))
476 src++;
478 else_part = get_token_arg(src, &skip_to);
479 src = skip_to;
480 while(src && *src && *src != RPAREN)
481 src++;
483 if(src && *src == RPAREN)
484 src++;
486 if(skip_ahead)
487 *skip_ahead = src;
489 if(!strcmp(match_this, expands_to)){
490 ret = if_matched;
491 if(else_part)
492 fs_give((void **)&else_part);
494 else{
495 ret = else_part;
496 if(if_matched)
497 fs_give((void **)&if_matched);
500 fs_give((void **)&match_this);
502 return(ret);
506 char *
507 get_token_arg(char *src, char **skip_to)
509 int quotes = 0, done = 0;
510 char *ret = NULL, *p;
512 while(*src && isspace((unsigned char)*src)) /* skip space before string */
513 src++;
515 if(*src == RPAREN){
516 if(skip_to)
517 *skip_to = src;
519 return(ret);
522 p = ret = (char *)fs_get((strlen(src) + 1) * sizeof(char));
523 while(!done){
524 switch(*src){
525 case QUOTE:
526 if(++quotes == 2)
527 done++;
529 src++;
530 break;
532 case BSLASH: /* don't count \" as a quote, just copy */
533 if(*(src+1) == BSLASH || *(src+1) == QUOTE){
534 src++; /* skip backslash */
535 *p++ = *src++;
537 else
538 src++;
540 break;
542 case SPACE:
543 case TAB:
544 case RPAREN:
545 case COMMA:
546 if(quotes)
547 *p++ = *src++;
548 else
549 done++;
551 break;
553 case '\0':
554 done++;
555 break;
557 default:
558 *p++ = *src++;
559 break;
563 *p = '\0';
564 if(skip_to)
565 *skip_to = src;
567 return(ret);