2 * ========================================================================
3 * Copyright 2006-2008 University of Washington
4 * Copyright 2013-2022 Eduardo Chappa
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 /*======================================================================
17 Display filter technology ;)
23 #include "../pith/state.h"
24 #include "../pith/conf.h"
25 #include "../pith/filter.h"
26 #include "../pith/store.h"
27 #include "../pith/addrstring.h"
28 #include "../pith/mimedesc.h"
29 #include "../pith/list.h"
30 #include "../pith/detach.h"
38 /* internal prototypes */
39 int df_valid_test(BODY
*, char *);
44 * dfilter - pipe the data from the given storage object thru the
45 * global display filter and into whatever the putchar's
48 * Input is assumed to be UTF-8.
49 * That's converted to user's locale and passed to rawcmd.
50 * That's converted back to UTF-8 and passed through aux_filters.
53 dfilter(char *rawcmd
, STORE_S
*input_so
, gf_o_t output_pc
, FILTLIST_S
*aux_filters
)
55 char *status
= NULL
, *cmd
, *resultf
= NULL
, *tmpfile
= NULL
;
56 int key
= 0, silent
= 0;
58 if((cmd
= expand_filter_tokens(rawcmd
,NULL
,&tmpfile
,&resultf
,NULL
,&key
,NULL
, &silent
)) != NULL
){
62 ps_global
->mangled_screen
= 1;
69 * If it was requested that the interaction take place via
70 * a tmpfile, fill it with text from our input_so, and let
71 * system_pipe handle the rest. Session key and tmp file
72 * mode support additions based loosely on a patch
73 * supplied by Thomas Stroesslin <thomas.stroesslin@epfl.ch>
82 /* write the tmp file */
83 so_seek(input_so
, 0L, 0);
84 if((tmpf_so
= so_get(FileStar
, tmpfile
, WRITE_ACCESS
|OWNER_ONLY
|WRITE_TO_LOCALE
)) != NULL
){
86 so_puts(tmpf_so
, filter_session_key());
87 so_puts(tmpf_so
, NEWLINE
);
89 /* copy input to tmp file */
90 gf_set_so_readc(&gc
, input_so
);
91 gf_set_so_writec(&pc
, tmpf_so
);
93 status
= gf_pipe(gc
, pc
);
94 gf_clear_so_readc(input_so
);
95 gf_clear_so_writec(tmpf_so
);
96 if(so_give(&tmpf_so
) != 0 && status
== NULL
)
97 status
= error_description(errno
);
99 /* prepare the terminal in case the filter uses it */
101 if((filter_pipe
= open_system_pipe(cmd
, NULL
, NULL
,
102 PIPE_USER
| (silent
? PIPE_SILENT
:
103 (F_ON(F_DISABLE_TERM_RESET_DISP
, ps_global
) ? 0 : PIPE_RESET
)),
104 0, pipe_callback
, NULL
)) != NULL
){
105 if(close_system_pipe(&filter_pipe
, NULL
, pipe_callback
) == 0){
106 /* pull result out of tmp file */
107 if((fp
= our_fopen(tmpfile
, "rb")) != NULL
){
108 gf_set_readc(&gc
, fp
, 0L, FileStar
, READ_FROM_LOCALE
);
111 for( ; aux_filters
->filter
; aux_filters
++)
112 gf_link_filter(aux_filters
->filter
,
115 status
= gf_pipe(gc
, output_pc
);
119 status
= "Can't read result of display filter";
122 status
= "Filter command returned error.";
125 status
= "Can't open pipe for display filter";
131 status
= "Can't open display filter tmp file";
133 else if((status
= gf_filter(cmd
, key
? filter_session_key() : NULL
,
134 input_so
, output_pc
, aux_filters
, silent
,
135 F_ON(F_DISABLE_TERM_RESET_DISP
, ps_global
),
136 pipe_callback
)) != NULL
){
139 fprintf(stdout
,"\r\n%s Hit return to continue.", status
);
141 while((ch
= read_char(300)) != ctrl('M') && ch
!= NO_OP_IDLE
)
146 if(name_file_size(resultf
) > 0L)
147 display_output_file(resultf
, "Filter", NULL
, DOF_BRIEF
);
149 fs_give((void **)&resultf
);
157 fs_give((void **)&cmd
);
165 * expand_filter_tokens - return an alloc'd string with any special tokens
166 * in the given filter expanded, NULL otherwise.
169 expand_filter_tokens(char *filter
, ENVELOPE
*env
, char **tmpf
, char **resultf
,
170 char **mtypef
, int *key
, int *hdrs
, int *silent
)
173 char *bp
, *cmd
= NULL
, *p
= NULL
,
174 *tfn
= NULL
, *rfn
= NULL
, *dfn
= NULL
, *mfn
= NULL
,
175 *freeme_tfn
= NULL
, *freeme_rfn
= NULL
, *freeme_mfn
= NULL
;
180 * break filter into words delimited by whitespace so that we can
181 * look for tokens. First we count how many words.
183 if((bp
= cpystr(filter
)) != NULL
)
184 p
= strtok(bp
, " \t");
188 while(strtok(NULL
, " \t") != NULL
)
193 dprint((1, "Unexpected failure creating sending_filter\n"));
195 fs_give((void **)&bp
);
200 q
= array
= (char **) fs_get((n
+1) * sizeof(*array
));
201 memset(array
, 0, (n
+1) * sizeof(*array
));
202 /* restore bp and form the array */
203 strncpy(bp
, filter
, strlen(filter
)+1);
204 if((p
= strtok(bp
, " \t")) != NULL
){
208 while((p
= strtok(NULL
, " \t")) != NULL
&& (q
-array
< n
+1))
213 fs_give((void **)&bp
);
215 for(q
= array
; *q
!= NULL
; q
++){
216 if(!strcmp(*q
, "_RECIPIENTS_")){
222 char *to_l
= addr_list_string(env
->to
,
223 simple_addr_string
, 0),
224 *cc_l
= addr_list_string(env
->cc
,
225 simple_addr_string
, 0),
226 *bcc_l
= addr_list_string(env
->bcc
,
227 simple_addr_string
, 0);
229 l
= strlen(to_l
) + strlen(cc_l
) + strlen(bcc_l
) + 2;
230 rl
= (char *) fs_get((l
+1) * sizeof(char));
231 snprintf(rl
, l
+1, "%s %s %s", to_l
, cc_l
, bcc_l
);
232 fs_give((void **)&to_l
);
233 fs_give((void **)&cc_l
);
234 fs_give((void **)&bcc_l
);
235 for(to_l
= rl
; *to_l
; to_l
++) /* to_l overloaded! */
236 if(*to_l
== ',') /* space delim'd list */
241 *q
= rl
? rl
: cpystr("");
243 else if(!strcmp(*q
, "_TMPFILE_")){
245 tfn
= temp_nam(NULL
, "sf"); /* send filter file */
247 dprint((1, "FAILED creat of _TMPFILE_\n"));
256 *q
= cpystr(tfn
? tfn
: "");
258 else if(!strcmp(*q
, "_RESULTFILE_")){
260 rfn
= temp_nam(NULL
, "rf");
262 * We don't create the result file, the user does.
263 * That means we have to remove it after temp_nam creates it.
268 dprint((1, "FAILED creat of _RESULTFILE_\n"));
277 *q
= cpystr(rfn
? rfn
: "");
279 else if(!strcmp(*q
, "_MIMETYPE_")){
281 mfn
= temp_nam(NULL
, "mt");
283 * We don't create the mimetype file, the user does.
284 * That means we have to remove it after temp_nam creates it.
289 dprint((1, "FAILED creat of _MIMETYPE_\n"));
298 *q
= cpystr(mfn
? mfn
: "");
300 else if(!strcmp(*q
, "_DATAFILE_")){
301 if((dfn
= filter_data_file(1)) == NULL
) /* filter data file */
302 dprint((1, "FAILED creat of _DATAFILE_\n"));
305 *q
= cpystr(dfn
? dfn
: "");
307 else if(!strcmp(*q
, "_PREPENDKEY_")){
312 else if(!strcmp(*q
, "_INCLUDEALLHDRS_")){
317 else if(!strcmp(*q
, "_SILENT_")){
324 /* count up required length */
325 for(len
= 0, q
= array
; *q
!= NULL
; q
++)
326 len
+= (strlen(*q
)+1);
328 cmd
= fs_get((len
+1) * sizeof(char));
331 /* cat together all the args */
333 for(q
= array
; *q
!= NULL
; q
++){
334 sstrncpy(&p
, *q
, len
+1-(p
-cmd
));
335 sstrncpy(&p
, " ", len
+1-(p
-cmd
));
341 fs_give((void **) &freeme_rfn
);
343 if(freeme_tfn
){ /* this shouldn't happen */
344 our_unlink(freeme_tfn
);
345 fs_give((void **) &freeme_tfn
);
349 fs_give((void **) &freeme_mfn
);
351 free_list_array(&array
);
358 * filter_session_key - function to return randomly generated number
359 * representing a key for this session. The idea is
360 * the display/sending filter could use it to muddle
361 * up any pass phrase or such stored in the
365 filter_session_key(void)
367 static char *key
= NULL
;
370 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
, "%ld", random());
371 key
= cpystr(tmp_20k_buf
);
381 * filter_data_file - function to return filename of scratch file for
382 * display and sending filters. This file is created
383 * the first time it's needed, and persists until pine
387 filter_data_file(int create_it
)
389 static char *fn
= NULL
;
392 fn
= temp_nam(NULL
, "df");
399 * df_static_trigger - look thru the display filter list and set the
400 * filter to any triggers that don't require scanning
401 * the message segment.
404 dfilter_trigger(struct mail_bodystruct
*body
, char *cmdbuf
, size_t cmdbuflen
)
407 char **l
, *test
, *cmd
;
409 for(l
= ps_global
->VAR_DISPLAY_FILTERS
; l
&& *l
&& !passed
; l
++){
411 get_pair(*l
, &test
, &cmd
, 1, 1);
413 dprint((5, "static trigger: \"%s\" --> \"%s\" and \"%s\"",
414 (l
&& *l
) ? *l
: "?",
415 test
? test
: "<NULL>", cmd
? cmd
: "<NULL>"));
417 if((passed
= (df_valid_test(body
, test
) && valid_filter_command(&cmd
))) != 0){
418 strncpy(cmdbuf
, cmd
, cmdbuflen
);
419 cmdbuf
[cmdbuflen
-1] = '\0';
422 fs_give((void **) &test
);
423 fs_give((void **) &cmd
);
426 return(passed
? cmdbuf
: NULL
);
432 df_valid_test(struct mail_bodystruct
*body
, char *test
)
436 if(!(passed
= !test
)){ /* NO test always wins */
438 passed
++; /* NULL test also wins! */
440 else if(body
&& !struncmp(test
, "_CHARSET(", 9)){
441 char *p
= strrindex(test
, ')');
444 *p
= '\0'; /* tie off user charset */
445 if((p
= parameter_val(body
->parameter
,"charset")) != NULL
){
446 passed
= !strucmp(test
+ 9, p
);
447 fs_give((void **) &p
);
450 passed
= !strucmp(test
+ 9, "us-ascii");
454 "filter trigger: malformed test: %s\n",