Add support for tab-completion when selecting by rule
[alpine.git] / pith / detoken.c
blob6f4c4cc10b3c1d857c815ae051c623b8c01370b9
1 /*
2 * ========================================================================
3 * Copyright 2013-2022 Eduardo Chappa
4 * Copyright 2006 University of Washington
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * ========================================================================
15 #include "../pith/headers.h"
16 #include "../pith/detoken.h"
17 #include "../pith/state.h"
18 #include "../pith/conf.h"
19 #include "../pith/status.h"
20 #include "../pith/pattern.h"
21 #include "../pith/reply.h"
22 #include "../pith/mailindx.h"
23 #include "../pith/options.h"
27 * Hook to read signature from local file
29 char *(*pith_opt_get_signature_file)(char *, int, int, int);
33 * Internal prototypes
35 char *detoken_guts(char *, int, ENVELOPE *, ACTION_S *, REDRAFT_POS_S **, int, int *);
36 char *handle_if_token(char *, char *, int, ENVELOPE *, ACTION_S *, char **);
37 char *get_token_arg(char *, char **);
41 * Detokenize signature or template files.
43 * If is_sig, we always use literal sigs before sigfiles if they are
44 * defined. So, check for role->litsig and use it. If it doesn't exist, use
45 * the global literal sig if defined. Else the role->sig file or the
46 * global signature file.
48 * If !is_sig, use role->template.
50 * So we start with a literal signature or a signature or template file.
51 * If that's a file, we read it first. The file could be remote.
52 * Then we detokenize the literal signature or file contents and return
53 * an allocated string which the caller frees.
55 * Args role -- See above about what happens depending on is_sig.
56 * relative to the pinerc dir.
57 * env -- The envelope to use for detokenizing. May be NULL.
58 * prenewlines -- How many blank lines should be included at start.
59 * postnewlines -- How many blank lines should be included after.
60 * is_sig -- This is a signature (not a template file).
61 * redraft_pos -- This is a return value. If it is non-NULL coming in,
62 * then the cursor position is returned here.
63 * impl -- This is a combination argument which is both an input
64 * argument and a return value. If it is non-NULL and = 0,
65 * that means that we want the cursor position returned here,
66 * even if that position is set implicitly to the end of
67 * the output string. If it is = 1 coming in, that means
68 * we only want the cursor position to be set if it is set
69 * explicitly. If it is 2, or if redraft_pos is NULL,
70 * we don't set it at all.
71 * If the cursor position gets set explicitly by a
72 * _CURSORPOS_ token in the file then this is set to 2
73 * on return. If the cursor position is set implicitly to
74 * the end of the included file, then this is set to 1
75 * on return.
77 * Returns -- An allocated string is returned.
79 char *
80 detoken(ACTION_S *role, ENVELOPE *env, int prenewlines, int postnewlines,
81 int is_sig, REDRAFT_POS_S **redraft_pos, int *impl)
83 char *ret = NULL,
84 *src = NULL,
85 *literal_sig = NULL,
86 *sigfile = NULL;
88 if(is_sig){
90 * If role->litsig is set, we use it;
91 * Else, if VAR_LITERAL_SIG is set, we use that;
92 * Else, if role->sig is set, we use that;
93 * Else, if VAR_SIGNATURE_FILE is set, we use that.
94 * This can be a little surprising if you set the VAR_LITERAL_SIG
95 * and don't set a role->litsig but do set a role->sig. The
96 * VAR_LITERAL_SIG will be used, not role->sig. The reason for this
97 * is mostly that it is much easier to display the right stuff
98 * in the various config screens if we do it that way. Besides,
99 * people will typically use only literal sigs or only sig files,
100 * there is no reason to mix them, so we don't provide support to
101 * do so.
103 if(role && role->litsig)
104 literal_sig = role->litsig;
105 else if(ps_global->VAR_LITERAL_SIG)
106 literal_sig = ps_global->VAR_LITERAL_SIG;
107 else if(role && role->sig)
108 sigfile = role->sig;
109 else
110 sigfile = ps_global->VAR_SIGNATURE_FILE;
112 else if(role && role->template)
113 sigfile = role->template;
115 if(literal_sig)
116 src = get_signature_lit(literal_sig, prenewlines, postnewlines, is_sig,1);
117 else if(sigfile && pith_opt_get_signature_file)
118 src = (*pith_opt_get_signature_file)(sigfile, prenewlines, postnewlines, is_sig);
120 if(src){
121 if(*src)
122 ret = detoken_src(src, FOR_TEMPLATE, env, role, redraft_pos, impl);
124 fs_give((void **)&src);
127 return(ret);
132 * Filter the source string from the template file and return an allocated
133 * copy of the result with text replacements for the tokens.
134 * Fill in offset in redraft_pos.
136 * This is really inefficient but who cares? It's just cpu time.
138 char *
139 detoken_src(char *src, int for_what, ENVELOPE *env, ACTION_S *role,
140 REDRAFT_POS_S **redraft_pos, int *impl)
142 int loopcnt = 25; /* just in case, avoid infinite loop */
143 char *ret = NULL, *str1, *str2;
144 int done = 0;
146 if(!src)
147 return(src);
150 * We keep running it through until it stops changing so user can
151 * nest calls to token stuff.
153 str1 = src;
154 do {
155 /* short-circuit if no chance it will change */
156 if(strindex(str1, '_'))
157 str2 = detoken_guts(str1, for_what, env, role, NULL, 0, NULL);
158 else
159 str2 = str1;
161 if(str1 && str2 && (str1 == str2 || !strcmp(str1, str2))){
162 done++; /* It stopped changing */
163 if(str1 && str1 != src && str1 != str2)
164 fs_give((void **)&str1);
166 else{ /* Still changing */
167 if(str1 && str1 != src && str1 != str2)
168 fs_give((void **)&str1);
170 str1 = str2;
173 } while(str2 && !done && loopcnt-- > 0);
176 * Have to run it through once more to get the redraft_pos and
177 * to remove any backslash escape for a token.
179 if((str2 && strindex(str2, '_')) ||
180 (impl && *impl == 0 && redraft_pos && !*redraft_pos)){
181 ret = detoken_guts(str2, for_what, env, role, redraft_pos, 1, impl);
182 if(str2 != src)
183 fs_give((void **)&str2);
185 else if(str2){
186 if(str2 == src)
187 ret = cpystr(str2);
188 else
189 ret = str2;
192 return(ret);
197 * The guts of the detokenizing routines. Filter the src string looking for
198 * tokens and replace them with the appropriate text. In the case of the
199 * cursor_pos token we set redraft_pos instead.
201 * Args src -- The source string
202 * for_what --
203 * env -- Envelope to look in for token replacements.
204 * redraft_pos -- Return the redraft offset here, if non-zero.
205 * last_pass -- This is a flag to tell detoken_guts whether or not to do
206 * the replacement for _CURSORPOS_. Leave it as is until
207 * the last pass. We need this because we want to defer
208 * cursor placement until the very last call to detoken,
209 * otherwise we'd have to keep track of the cursor
210 * position as subsequent text replacements (nested)
211 * take place.
212 * This same flag is also used to decide when to eliminate
213 * backslash escapes from in front of tokens. The only
214 * use of backslash escapes is to escape an entire token.
215 * That is, \_DATE_ is a literal _DATE_, but any other
216 * backslash is a literal backslash. That way, nobody
217 * but wackos will have to worry about backslashes.
218 * impl -- This is a combination argument which is both an input
219 * argument and a return value. If it is non-NULL and 0
220 * coming in, that means that we should set redraft_pos,
221 * even if that position is set implicitly to the end of
222 * the output string. If it is 1 coming in, that means
223 * we only want the cursor position to be set if it is set
224 * explicitly. If it is 2 coming in (or if
225 * redraft_pos is NULL) then we don't set it at all.
226 * If the cursor position gets set explicitly by a
227 * _CURSORPOS_ token in the file then this is set to 2
228 * on return. If the cursor position is set implicitly to
229 * the end of the included file, then this is set to 1
230 * on return.
232 * Returns pointer to alloced result
234 char *
235 detoken_guts(char *src, int for_what, ENVELOPE *env, ACTION_S *role,
236 REDRAFT_POS_S **redraft_pos, int last_pass, int *impl)
238 #define MAXSUB 500
239 char *p, *q = NULL, *dst = NULL;
240 char subbuf[MAXSUB+1], *repl;
241 INDEX_PARSE_T *pt;
242 long l, cnt = 0L;
243 int sizing_pass = 1, suppress_tokens = 0;
245 if(!src)
246 return(NULL);
248 top:
251 * The tokens we look for begin with _. The only escaping mechanism
252 * is a backslash in front of a token. This will give you the literal
253 * token. So \_DATE_ is a literal _DATE_.
254 * Tokens like _word_ are replaced with the appropriate text if
255 * word is recognized. If _word_ is followed immediately by a left paren
256 * it is an if-else thingie. _word_(match_this,if_text,else_text) means to
257 * replace that with either the if_text or else_text depending on whether
258 * what _word_ (without the paren) would produce matches match_this or not.
260 p = src;
261 while(*p){
262 switch(*p){
263 case '_': /* possible start of token */
264 if(!suppress_tokens &&
265 (pt = itoktype(p+1, for_what | DELIM_USCORE)) != NULL){
266 char *free_this = NULL;
268 p += (strlen(pt->name) + 2); /* skip over token */
270 repl = subbuf;
271 subbuf[0] = '\0';
273 if(pt->ctype == iCursorPos){
274 if(!last_pass){ /* put it back */
275 subbuf[0] = '_';
276 strncpy(subbuf+1, pt->name, sizeof(subbuf)-2);
277 subbuf[sizeof(subbuf)-1] = '\0';
278 strncat(subbuf, "_", sizeof(subbuf)-strlen(subbuf)-1);
279 subbuf[sizeof(subbuf)-1] = '\0';
282 if(!sizing_pass){
283 if(q-dst < cnt+1)
284 *q = '\0';
286 l = strlen(dst);
287 if(redraft_pos && impl && *impl != 2){
288 if(!*redraft_pos){
289 *redraft_pos =
290 (REDRAFT_POS_S *)fs_get(sizeof(**redraft_pos));
291 memset((void *)*redraft_pos, 0,
292 sizeof(**redraft_pos));
293 (*redraft_pos)->hdrname = cpystr(":");
296 (*redraft_pos)->offset = l;
297 *impl = 2; /* set explicitly */
301 else if(pt->what_for & FOR_REPLY_INTRO)
302 repl = get_reply_data(env, role, pt->ctype,
303 subbuf, sizeof(subbuf)-1);
305 if(*p == LPAREN){ /* if-else construct */
306 char *skip_ahead;
308 repl = free_this = handle_if_token(repl, p, for_what,
309 env, role,
310 &skip_ahead);
311 p = skip_ahead;
314 if(repl && repl[0]){
315 if(sizing_pass)
316 cnt += (long)strlen(repl);
317 else{
318 strncpy(q, repl, cnt-(q-dst));
319 dst[cnt] = '\0';
320 q += strlen(repl);
324 if(free_this)
325 fs_give((void **)&free_this);
327 else{ /* unrecognized token, treat it just like text */
328 suppress_tokens = 0;
329 if(sizing_pass)
330 cnt++;
331 else if(q-dst < cnt+1)
332 *q++ = *p;
334 p++;
337 break;
339 case BSLASH:
341 * If a real token follows the backslash, then the backslash
342 * is here to escape the token. Otherwise, it's just a
343 * regular character.
345 if(*(p+1) == '_' &&
346 ((pt = itoktype(p+2, for_what | DELIM_USCORE)) != NULL)){
348 * Backslash is escape for literal token.
349 * If we're on the last pass we want to eliminate the
350 * backslash, otherwise we keep it.
351 * In either case, suppress_tokens will cause the token
352 * lookup to be skipped above so that the token will
353 * be treated as literal text.
355 suppress_tokens++;
356 if(last_pass){
357 p++;
358 break;
360 /* else, fall through and keep backslash */
362 /* this is a literal backslash, fall through */
364 default:
365 if(sizing_pass)
366 cnt++;
367 else if(q-dst < cnt+1)
368 *q++ = *p; /* copy the character */
370 p++;
371 break;
375 if(!sizing_pass && q-dst < cnt+1)
376 *q = '\0';
378 if(sizing_pass){
379 sizing_pass = 0;
381 * Now we're done figuring out how big the answer will be. We
382 * allocate space for it and go back through filling it in.
384 cnt = MAX(cnt, 0L);
385 q = dst = (char *)fs_get((cnt + 1) * sizeof(char));
386 goto top;
390 * Set redraft_pos to character following the template, unless
391 * it has already been set.
393 if(dst && impl && *impl == 0 && redraft_pos && !*redraft_pos){
394 *redraft_pos = (REDRAFT_POS_S *)fs_get(sizeof(**redraft_pos));
395 memset((void *)*redraft_pos, 0, sizeof(**redraft_pos));
396 (*redraft_pos)->offset = strlen(dst);
397 (*redraft_pos)->hdrname = cpystr(":");
398 *impl = 1;
401 if(dst && cnt >= 0)
402 dst[cnt] = '\0';
404 return(dst);
409 * Do the if-else part of the detokenization for one case of if-else.
410 * The input src should like (match_this, if_matched, else)...
412 * Args expands_to -- This is what the token to the left of the paren
413 * expanded to, and this is the thing we're going to
414 * compare with the match_this part.
415 * src -- The source string beginning with the left paren.
416 * for_what --
417 * env --
418 * skip_ahead -- Tells caller how long the (...) part was so caller can
419 * skip over that part of the source.
421 * Returns -- an allocated string which is the answer, or NULL if nothing.
423 char *
424 handle_if_token(char *expands_to, char *src, int for_what, ENVELOPE *env,
425 ACTION_S *role, char **skip_ahead)
427 char *ret = NULL;
428 char *skip_to;
429 char *match_this, *if_matched, *else_part;
431 if(skip_ahead)
432 *skip_ahead = src;
434 if(!src || *src != LPAREN){
435 dprint((1,"botch calling handle_if_token, missing paren\n"));
436 return(ret);
439 if(!*++src){
440 q_status_message(SM_ORDER, 3, 3,
441 "Unexpected end of token string in Reply-LeadIn, Sig, or template");
442 return(ret);
445 match_this = get_token_arg(src, &skip_to);
448 * If the match_this argument is a token, detokenize it first.
450 if(match_this && *match_this == '_'){
451 char *exp_match_this;
453 exp_match_this = detoken_src(match_this, for_what, env,
454 role, NULL, NULL);
455 fs_give((void **)&match_this);
456 match_this = exp_match_this;
459 if(!match_this)
460 match_this = cpystr("");
462 if(!expands_to)
463 expands_to = "";
465 src = skip_to;
466 while(src && *src && (isspace((unsigned char)*src) || *src == ','))
467 src++;
469 if_matched = get_token_arg(src, &skip_to);
470 src = skip_to;
471 while(src && *src && (isspace((unsigned char)*src) || *src == ','))
472 src++;
474 else_part = get_token_arg(src, &skip_to);
475 src = skip_to;
476 while(src && *src && *src != RPAREN)
477 src++;
479 if(src && *src == RPAREN)
480 src++;
482 if(skip_ahead)
483 *skip_ahead = src;
485 if(!strcmp(match_this, expands_to)){
486 ret = if_matched;
487 if(else_part)
488 fs_give((void **)&else_part);
490 else{
491 ret = else_part;
492 if(if_matched)
493 fs_give((void **)&if_matched);
496 fs_give((void **)&match_this);
498 return(ret);
502 char *
503 get_token_arg(char *src, char **skip_to)
505 int quotes = 0, done = 0;
506 char *ret = NULL, *p;
508 while(*src && isspace((unsigned char)*src)) /* skip space before string */
509 src++;
511 if(*src == RPAREN){
512 if(skip_to)
513 *skip_to = src;
515 return(ret);
518 p = ret = (char *)fs_get((strlen(src) + 1) * sizeof(char));
519 while(!done){
520 switch(*src){
521 case QUOTE:
522 if(++quotes == 2)
523 done++;
525 src++;
526 break;
528 case BSLASH: /* don't count \" as a quote, just copy */
529 if(*(src+1) == BSLASH || *(src+1) == QUOTE){
530 src++; /* skip backslash */
531 *p++ = *src++;
533 else
534 src++;
536 break;
538 case SPACE:
539 case TAB:
540 case RPAREN:
541 case COMMA:
542 if(quotes)
543 *p++ = *src++;
544 else
545 done++;
547 break;
549 case '\0':
550 done++;
551 break;
553 default:
554 *p++ = *src++;
555 break;
559 *p = '\0';
560 if(skip_to)
561 *skip_to = src;
563 return(ret);