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_io_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>
81 /* write the tmp file */
82 so_seek(input_so
, 0L, 0);
83 if((tmpf_so
= so_get(FileStar
, tmpfile
, WRITE_ACCESS
|OWNER_ONLY
|WRITE_TO_LOCALE
)) != NULL
){
85 so_puts(tmpf_so
, filter_session_key());
86 so_puts(tmpf_so
, NEWLINE
);
88 /* copy input to tmp file */
89 gf_set_so_readc(&gc
, input_so
);
90 gf_set_so_writec(&pc
, tmpf_so
);
92 status
= gf_pipe(gc
, pc
);
93 gf_clear_so_readc(input_so
);
94 gf_clear_so_writec(tmpf_so
);
95 if(so_give(&tmpf_so
) != 0 && status
== NULL
)
96 status
= error_description(errno
);
98 /* prepare the terminal in case the filter uses it */
100 if((filter_pipe
= open_system_pipe(cmd
, NULL
, NULL
,
101 PIPE_USER
| (silent
? PIPE_SILENT
:
102 (F_ON(F_DISABLE_TERM_RESET_DISP
, ps_global
) ? 0 : PIPE_RESET
)),
103 0, pipe_callback
, NULL
)) != NULL
){
104 if(close_system_pipe(&filter_pipe
, NULL
, pipe_callback
) == 0){
105 /* pull result out of tmp file */
106 if((fp
= our_fopen(tmpfile
, "rb")) != NULL
){
107 gf_set_readc(&gc
, fp
, 0L, FileStar
, READ_FROM_LOCALE
);
110 for( ; aux_filters
->filter
; aux_filters
++)
111 gf_link_filter(aux_filters
->filter
,
114 status
= gf_pipe(gc
, output_pc
);
118 status
= "Can't read result of display filter";
121 status
= "Filter command returned error.";
124 status
= "Can't open pipe for display filter";
130 status
= "Can't open display filter tmp file";
132 else if((status
= gf_filter(cmd
, key
? filter_session_key() : NULL
,
133 input_so
, output_pc
, aux_filters
, silent
,
134 F_ON(F_DISABLE_TERM_RESET_DISP
, ps_global
),
135 pipe_callback
)) != NULL
){
138 fprintf(stdout
,"\r\n%s Hit return to continue.", status
);
140 while((ch
= read_char(300)) != ctrl('M') && ch
!= NO_OP_IDLE
)
145 if(name_file_size(resultf
) > 0L)
146 display_output_file(resultf
, "Filter", NULL
, DOF_BRIEF
);
148 fs_give((void **)&resultf
);
156 fs_give((void **)&cmd
);
164 * expand_filter_tokens - return an alloc'd string with any special tokens
165 * in the given filter expanded, NULL otherwise.
168 expand_filter_tokens(char *filter
, ENVELOPE
*env
, char **tmpf
, char **resultf
,
169 char **mtypef
, int *key
, int *hdrs
, int *silent
)
172 char *bp
, *cmd
= NULL
, *p
= NULL
,
173 *tfn
= NULL
, *rfn
= NULL
, *dfn
= NULL
, *mfn
= NULL
,
174 *freeme_tfn
= NULL
, *freeme_rfn
= NULL
, *freeme_mfn
= NULL
;
179 * break filter into words delimited by whitespace so that we can
180 * look for tokens. First we count how many words.
182 if((bp
= cpystr(filter
)) != NULL
)
183 p
= strtok(bp
, " \t");
187 while(strtok(NULL
, " \t") != NULL
)
192 dprint((1, "Unexpected failure creating sending_filter\n"));
194 fs_give((void **)&bp
);
199 q
= array
= (char **) fs_get((n
+1) * sizeof(*array
));
200 memset(array
, 0, (n
+1) * sizeof(*array
));
201 /* restore bp and form the array */
202 strncpy(bp
, filter
, strlen(filter
)+1);
203 if((p
= strtok(bp
, " \t")) != NULL
){
207 while((p
= strtok(NULL
, " \t")) != NULL
&& (q
-array
< n
+1))
212 fs_give((void **)&bp
);
214 for(q
= array
; *q
!= NULL
; q
++){
215 if(!strcmp(*q
, "_RECIPIENTS_")){
221 char *to_l
= addr_list_string(env
->to
,
222 simple_addr_string
, 0),
223 *cc_l
= addr_list_string(env
->cc
,
224 simple_addr_string
, 0),
225 *bcc_l
= addr_list_string(env
->bcc
,
226 simple_addr_string
, 0);
228 l
= strlen(to_l
) + strlen(cc_l
) + strlen(bcc_l
) + 2;
229 rl
= (char *) fs_get((l
+1) * sizeof(char));
230 snprintf(rl
, l
+1, "%s %s %s", to_l
, cc_l
, bcc_l
);
231 fs_give((void **)&to_l
);
232 fs_give((void **)&cc_l
);
233 fs_give((void **)&bcc_l
);
234 for(to_l
= rl
; *to_l
; to_l
++) /* to_l overloaded! */
235 if(*to_l
== ',') /* space delim'd list */
240 *q
= rl
? rl
: cpystr("");
242 else if(!strcmp(*q
, "_TMPFILE_")){
244 tfn
= temp_nam(NULL
, "sf"); /* send filter file */
246 dprint((1, "FAILED creat of _TMPFILE_\n"));
255 *q
= cpystr(tfn
? tfn
: "");
257 else if(!strcmp(*q
, "_RESULTFILE_")){
259 rfn
= temp_nam(NULL
, "rf");
261 * We don't create the result file, the user does.
262 * That means we have to remove it after temp_nam creates it.
267 dprint((1, "FAILED creat of _RESULTFILE_\n"));
276 *q
= cpystr(rfn
? rfn
: "");
278 else if(!strcmp(*q
, "_MIMETYPE_")){
280 mfn
= temp_nam(NULL
, "mt");
282 * We don't create the mimetype file, the user does.
283 * That means we have to remove it after temp_nam creates it.
288 dprint((1, "FAILED creat of _MIMETYPE_\n"));
297 *q
= cpystr(mfn
? mfn
: "");
299 else if(!strcmp(*q
, "_DATAFILE_")){
300 if((dfn
= filter_data_file(1)) == NULL
) /* filter data file */
301 dprint((1, "FAILED creat of _DATAFILE_\n"));
304 *q
= cpystr(dfn
? dfn
: "");
306 else if(!strcmp(*q
, "_PREPENDKEY_")){
311 else if(!strcmp(*q
, "_INCLUDEALLHDRS_")){
316 else if(!strcmp(*q
, "_SILENT_")){
323 /* count up required length */
324 for(len
= 0, q
= array
; *q
!= NULL
; q
++)
325 len
+= (strlen(*q
)+1);
327 cmd
= fs_get((len
+1) * sizeof(char));
330 /* cat together all the args */
332 for(q
= array
; *q
!= NULL
; q
++){
333 sstrncpy(&p
, *q
, len
+1-(p
-cmd
));
334 sstrncpy(&p
, " ", len
+1-(p
-cmd
));
340 fs_give((void **) &freeme_rfn
);
342 if(freeme_tfn
){ /* this shouldn't happen */
343 our_unlink(freeme_tfn
);
344 fs_give((void **) &freeme_tfn
);
348 fs_give((void **) &freeme_mfn
);
350 free_list_array(&array
);
357 * filter_session_key - function to return randomly generated number
358 * representing a key for this session. The idea is
359 * the display/sending filter could use it to muddle
360 * up any pass phrase or such stored in the
364 filter_session_key(void)
366 static char *key
= NULL
;
369 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
, "%ld", random());
370 key
= cpystr(tmp_20k_buf
);
380 * filter_data_file - function to return filename of scratch file for
381 * display and sending filters. This file is created
382 * the first time it's needed, and persists until pine
386 filter_data_file(int create_it
)
388 static char *fn
= NULL
;
391 fn
= temp_nam(NULL
, "df");
398 * df_static_trigger - look thru the display filter list and set the
399 * filter to any triggers that don't require scanning
400 * the message segment.
403 dfilter_trigger(struct mail_bodystruct
*body
, char *cmdbuf
, size_t cmdbuflen
)
406 char **l
, *test
, *cmd
;
408 for(l
= ps_global
->VAR_DISPLAY_FILTERS
; l
&& *l
&& !passed
; l
++){
410 get_pair(*l
, &test
, &cmd
, 1, 1);
412 dprint((5, "static trigger: \"%s\" --> \"%s\" and \"%s\"",
413 (l
&& *l
) ? *l
: "?",
414 test
? test
: "<NULL>", cmd
? cmd
: "<NULL>"));
416 if((passed
= (df_valid_test(body
, test
) && valid_filter_command(&cmd
))) != 0){
417 strncpy(cmdbuf
, cmd
, cmdbuflen
);
418 cmdbuf
[cmdbuflen
-1] = '\0';
421 fs_give((void **) &test
);
422 fs_give((void **) &cmd
);
425 return(passed
? cmdbuf
: NULL
);
431 df_valid_test(struct mail_bodystruct
*body
, char *test
)
435 if(!(passed
= !test
)){ /* NO test always wins */
437 passed
++; /* NULL test also wins! */
439 else if(body
&& !struncmp(test
, "_CHARSET(", 9)){
440 char *p
= strrindex(test
, ')');
443 *p
= '\0'; /* tie off user charset */
444 if((p
= parameter_val(body
->parameter
,"charset")) != NULL
){
445 passed
= !strucmp(test
+ 9, p
);
446 fs_give((void **) &p
);
449 passed
= !strucmp(test
+ 9, "us-ascii");
453 "filter trigger: malformed test: %s\n",