1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Iterating over, and over such housekeeping message user commands.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2018 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
6 * SPDX-License-Identifier: BSD-3-Clause
9 * Copyright (c) 1980, 1993
10 * The Regents of the University of California. All rights reserved.
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
15 * 1. Redistributions of source code must retain the above copyright
16 * notice, this list of conditions and the following disclaimer.
17 * 2. Redistributions in binary form must reproduce the above copyright
18 * notice, this list of conditions and the following disclaimer in the
19 * documentation and/or other materials provided with the distribution.
20 * 3. Neither the name of the University nor the names of its contributors
21 * may be used to endorse or promote products derived from this software
22 * without specific prior written permission.
24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37 #define n_FILE cmd_msg
39 #ifndef HAVE_AMALGAMATION
43 /* Prepare and print "[Message: xy]:" intro */
44 static bool_t
a_cmsg_show_overview(FILE *obuf
, struct message
*mp
, int msg_no
);
46 /* Show the requested messages */
47 static int _type1(int *msgvec
, bool_t doign
, bool_t dopage
, bool_t dopipe
,
48 bool_t donotdecode
, char *cmd
, ui64_t
*tstats
);
50 /* Pipe the requested messages */
51 static int a_cmsg_pipe1(void *vp
, bool_t doign
);
54 static int a_cmsg_top(void *vp
, struct n_ignore
const *itp
);
56 /* Delete the indicated messages. Set dot to some nice place afterwards */
57 static int delm(int *msgvec
);
60 a_cmsg_show_overview(FILE *obuf
, struct message
*mp
, int msg_no
){
62 char const *cpre
, *csuf
;
65 cpre
= csuf
= n_empty
;
67 if(n_COLOUR_IS_ACTIVE()){
68 struct n_colour_pen
*cpen
;
70 if((cpen
= n_colour_pen_create(n_COLOUR_ID_VIEW_MSGINFO
, NULL
)) != NULL
){
73 if((sp
= n_colour_pen_to_str(cpen
)) != NULL
)
75 if((sp
= n_colour_reset_to_str()) != NULL
)
80 /* XXX Message info uses wire format for line count */
82 A_("%s[-- Message %2d -- %lu lines, %lu bytes --]:%s\n"),
83 cpre
, msg_no
, (ul_i
)mp
->m_lines
, (ul_i
)mp
->m_size
, csuf
) > 0);
89 _type1(int *msgvec
, bool_t doign
, bool_t dopage
, bool_t dopipe
,
90 bool_t donotdecode
, char *cmd
, ui64_t
*tstats
)
96 enum sendaction action
;
97 bool_t
volatile formfeed
;
104 formfeed
= (dopipe
&& ok_blook(page
));
105 action
= ((dopipe
&& ok_blook(piperaw
))
106 ? SEND_MBOX
: donotdecode
108 ? SEND_TODISP
: SEND_TODISP_ALL
);
111 if ((obuf
= Popen(cmd
, "w", ok_vlook(SHELL
), NULL
, 1)) == NULL
) {
115 } else if ((n_psonce
& n_PSO_TTYOUT
) && (dopage
||
116 ((n_psonce
& n_PSO_INTERACTIVE
) && (cp
= ok_vlook(crt
)) != NULL
))) {
122 for (ip
= msgvec
; *ip
&& PTRCMP(ip
- msgvec
, <, msgCount
); ++ip
) {
123 mp
= message
+ *ip
- 1;
124 if (!(mp
->m_content_info
& CI_HAVE_BODY
))
125 if (get_body(mp
) != OKAY
)
127 nlines
+= mp
->m_lines
+ 1; /* TODO BUT wire format, not display! */
131 /* >= not <: we return to the prompt */
132 if(dopage
|| nlines
>= (*cp
!= '\0'
133 ? (n_idec_uiz_cp(&lib
, cp
, 0, NULL
), lib
)
134 : (uiz_t
)n_realscreenheight
)){
135 if((obuf
= n_pager_open()) == NULL
)
139 if(action
== SEND_TODISP
|| action
== SEND_TODISP_ALL
)
140 n_colour_env_create(n_COLOUR_CTX_VIEW
, obuf
, obuf
!= n_stdout
);
144 else if(action
== SEND_TODISP
|| action
== SEND_TODISP_ALL
)
145 n_colour_env_create(n_COLOUR_CTX_VIEW
, n_stdout
, FAL0
);
150 for (ip
= msgvec
; *ip
&& PTRCMP(ip
- msgvec
, <, msgCount
); ++ip
) {
151 mp
= message
+ *ip
- 1;
154 n_pstate
|= n_PS_DID_PRINT_DOT
;
156 if(!dopipe
&& ip
!= msgvec
&& fprintf(obuf
, "\n") < 0){
160 if(action
!= SEND_MBOX
&& !a_cmsg_show_overview(obuf
, mp
, *ip
)){
164 if(sendmp(mp
, obuf
, (doign
? n_IGNORE_TYPE
: NULL
), NULL
, action
, mstats
170 if(formfeed
){ /* TODO a nicer way to separate piped messages! */
171 if(putc('\f', obuf
) == EOF
){
177 tstats
[0] += mstats
[0];
181 if(!dopipe
&& (action
== SEND_TODISP
|| action
== SEND_TODISP_ALL
))
185 if (obuf
!= n_stdout
)
192 a_cmsg_pipe1(void *vp
, bool_t doign
){
194 char const *cmd
, *cmdq
;
196 struct n_cmd_arg
*cap
;
197 struct n_cmd_arg_ctx
*cacp
;
202 msgvec
= cap
->ca_arg
.ca_msglist
;
206 if((cmd
= cap
->ca_arg
.ca_str
.s
)[0] == '\0' &&
207 ((cmd
= ok_vlook(cmd
)) == NULL
|| *cmd
== '\0')){
208 n_err(_("%s: variable *cmd* not set\n"), cacp
->cac_desc
->cad_name
);
212 cmdq
= n_shexp_quote_cp(cmd
, FAL0
);
213 fprintf(n_stdout
, _("Pipe to: %s\n"), cmdq
);
215 if((rv
= _type1(msgvec
, doign
, FAL0
, TRU1
, FAL0
, n_UNCONST(cmd
), stats
)
217 fprintf(n_stdout
, "%s %" PRIu64
" bytes\n", cmdq
, stats
[0]);
224 a_cmsg_top(void *vp
, struct n_ignore
const *itp
){
227 enum{a_NONE
, a_SQUEEZE
= 1u<<0,
228 a_EMPTY
= 1u<<8, a_STOP
= 1u<<9, a_WORKMASK
= 0xFF00u
} f
;
233 if((iobuf
= Ftmp(NULL
, "topio", OF_RDWR
| OF_UNLINK
| OF_REGISTER
)) == NULL
){
234 n_perr(_("`top': I/O temporary file"), 0);
238 if((pbuf
= Ftmp(NULL
, "toppag", OF_RDWR
| OF_UNLINK
| OF_REGISTER
)) == NULL
){
239 n_perr(_("`top': temporary pager file"), 0);
244 /* TODO In v15 we should query the m_message object, and directly send only
245 * TODO those parts, optionally over empty-line-squeeze and quote-strip
246 * TODO filters, in which we are interested in: only text content!
247 * TODO And: with *topsqueeze*, header/content separating empty line.. */
248 n_pstate
&= ~n_PS_MSGLIST_DIRECT
; /* TODO NO ATTACHMENTS */
251 n_COLOUR( n_colour_env_create(n_COLOUR_CTX_VIEW
, iobuf
, FAL0
); )
252 n_string_creat_auto(&s
);
256 if((n_idec_siz_cp(&l
, ok_vlook(toplines
), 0, NULL
257 ) & (n_IDEC_STATE_EMASK
| n_IDEC_STATE_CONSUMED
)
258 ) != n_IDEC_STATE_CONSUMED
)
261 tmax
= n_screensize();
269 f
= ok_blook(topsqueeze
) ? a_SQUEEZE
: a_NONE
;
271 for(ip
= msgvec
= vp
; *ip
!= 0; ++ip
){
274 mp
= &message
[*ip
- 1];
277 n_pstate
|= n_PS_DID_PRINT_DOT
;
281 if(ftruncate(fileno(iobuf
), 0)){
282 n_perr(_("`top': ftruncate(2)"), 0);
287 if(!a_cmsg_show_overview(iobuf
, mp
, *ip
) ||
288 sendmp(mp
, iobuf
, itp
, NULL
, SEND_TODISP_ALL
, NULL
) < 0){
289 n_err(_("`top': failed to prepare message %d\n"), *ip
);
293 fflush_rewind(iobuf
);
295 /* TODO Skip over the _msg_overview line -- this is a hack to make
296 * TODO colours work: colour contexts should be objects */
300 if((c
= getc(iobuf
)) == EOF
|| putc(c
, pbuf
) == EOF
){
313 n_string_trunc(&s
, 0);
314 for(l
= 0, f
&= ~a_WORKMASK
; !(f
& a_STOP
);){
317 if((c
= getc(iobuf
)) == EOF
){
323 n_string_push_c(&s
, c
);
324 else if((f
& a_SQUEEZE
) && s
.s_len
== 0){
325 if(!(f
& a_STOP
) && ((f
& a_EMPTY
) || tmax
- 1 <= l
))
327 if(putc('\n', pbuf
) == EOF
){
334 char const *cp
, *xcp
;
336 cp
= n_string_cp_const(&s
);
337 /* TODO Brute simple skip part overviews; see above.. */
340 else if(s
.s_len
> 8 &&
341 (xcp
= strstr(cp
, "[-- ")) != NULL
&&
342 strstr(&xcp
[1], " --]") != NULL
)
347 for(qcp
= ok_vlook(quote_chars
); (c
= *cp
) != '\0'; ++cp
){
350 if(!blankspacechar(c
)){
351 if(strchr(qcp
, c
) == NULL
)
360 if(fputs(n_string_cp_const(&s
), pbuf
) == EOF
||
361 putc('\n', pbuf
) == EOF
){
370 n_string_trunc(&s
, 0);
378 if(!(f
& a_EMPTY
) && putc('\n', pbuf
) == EOF
){
388 n_COLOUR( n_colour_env_gut(); )
391 page_or_print(pbuf
, plines
);
405 int rv
= -1, *ip
, last
;
409 for (ip
= msgvec
; *ip
!= 0; ++ip
) {
410 mp
= message
+ *ip
- 1;
412 mp
->m_flag
|= MDELETED
| MTOUCH
;
413 mp
->m_flag
&= ~(MPRESERVE
| MSAVED
| MBOX
);
417 setdot(message
+ last
- 1);
418 last
= first(0, MDELETED
);
420 setdot(message
+ last
- 1);
436 rv
= _type1(msgvec
, TRU1
, TRU1
, FAL0
, FAL0
, NULL
, NULL
);
447 rv
= _type1(msgvec
, FAL0
, TRU1
, FAL0
, FAL0
, NULL
, NULL
);
458 rv
= _type1(msgvec
, TRU1
, FAL0
, FAL0
, FAL0
, NULL
, NULL
);
469 rv
= _type1(msgvec
, FAL0
, FAL0
, FAL0
, FAL0
, NULL
, NULL
);
480 rv
= _type1(msgvec
, FAL0
, FAL0
, FAL0
, TRU1
, NULL
, NULL
);
486 c_mimeview(void *vp
){ /* TODO direct addressable parts, multiple such */
491 if((msgvec
= vp
)[1] != 0){
492 n_err(_("`mimeview': can yet only take one message, sorry!\n"));/* TODO */
493 n_pstate_err_no
= n_ERR_NOTSUP
;
498 mp
= &message
[*msgvec
- 1];
501 n_pstate
|= n_PS_DID_PRINT_DOT
;
505 n_colour_env_create(n_COLOUR_CTX_VIEW
, n_stdout
, FAL0
);
508 if(!a_cmsg_show_overview(n_stdout
, mp
, *msgvec
))
509 n_pstate_err_no
= n_ERR_IO
;
510 else if(sendmp(mp
, n_stdout
, n_IGNORE_TYPE
, NULL
, SEND_TODISP_PARTS
,
512 n_pstate_err_no
= n_ERR_IO
;
514 n_pstate_err_no
= n_ERR_NONE
;
520 rv
= (n_pstate_err_no
!= n_ERR_NONE
);
531 rv
= a_cmsg_pipe1(vp
, TRU1
);
541 rv
= a_cmsg_pipe1(vp
, FAL0
);
548 struct n_ignore
*itp
;
552 if(n_ignore_is_any(n_IGNORE_TOP
))
555 itp
= n_ignore_new(TRU1
);
556 n_ignore_insert(itp
, TRU1
, "from", sizeof("from") -1);
557 n_ignore_insert(itp
, TRU1
, "to", sizeof("to") -1);
558 n_ignore_insert(itp
, TRU1
, "cc", sizeof("cc") -1);
559 n_ignore_insert(itp
, TRU1
, "subject", sizeof("subject") -1);
562 rv
= !a_cmsg_top(v
, itp
);
572 rv
= !a_cmsg_top(v
, n_IGNORE_TYPE
);
580 int list
[2], *ip
, *ip2
, mdot
, *msgvec
= v
, rv
= 1;
585 /* If some messages were supplied, find the first applicable one
586 * following dot using wrap around */
587 mdot
= (int)PTR2SIZE(dot
- message
+ 1);
589 /* Find first message in supplied message list which follows dot */
590 for (ip
= msgvec
; *ip
!= 0; ++ip
) {
591 if ((mb
.mb_threaded
? message
[*ip
- 1].m_threadpos
> dot
->m_threadpos
599 mp
= message
+ *ip2
- 1;
600 if (!(mp
->m_flag
& MMNDEL
)) {
609 fprintf(n_stdout
, _("No messages applicable\n"));
613 /* If this is the first command, select message 1. Note that this must
614 * exist for us to get here at all */
615 if (!(n_pstate
& n_PS_SAW_COMMAND
)) {
621 /* Just find the next good message after dot, no wraparound */
622 if (mb
.mb_threaded
== 0) {
623 for (mp
= dot
+ !!(n_pstate
& n_PS_DID_PRINT_DOT
);
624 PTRCMP(mp
, <, message
+ msgCount
); ++mp
)
625 if (!(mp
->m_flag
& MMNORM
))
628 /* TODO The threading code had some bugs that caused crashes.
629 * TODO The last thing (before the deep look) happens here,
630 * TODO so let's not trust n_PS_DID_PRINT_DOT but check & hope it fixes */
631 if ((mp
= dot
) != NULL
&& (n_pstate
& n_PS_DID_PRINT_DOT
))
632 mp
= next_in_thread(mp
);
633 while (mp
!= NULL
&& (mp
->m_flag
& MMNORM
))
634 mp
= next_in_thread(mp
);
636 if (mp
== NULL
|| PTRCMP(mp
, >=, message
+ msgCount
)) {
638 fprintf(n_stdout
, _("At EOF\n"));
646 list
[0] = (int)PTR2SIZE(dot
- message
+ 1);
656 char cbuf
[n_IENC_BUFFER_SIZE
], sep1
, sep2
;
657 struct n_string s
, *sp
;
659 struct n_cmd_arg_ctx
*cacp
;
663 n_pstate_err_no
= n_ERR_NONE
;
664 sp
= n_string_creat_auto(&s
);
665 sep1
= *ok_vlook(ifs
);
666 sep2
= *ok_vlook(ifs_ws
);
674 for(mlp
= cacp
->cac_arg
->ca_arg
.ca_msglist
; *mlp
!= 0; ++mlp
){
675 if(!n_string_can_book(sp
, n_IENC_BUFFER_SIZE
+ 2u)){
676 n_err(_("`=': overflow: string too long!\n"));
677 n_pstate_err_no
= n_ERR_OVERFLOW
;
682 sp
= n_string_push_c(sp
, sep1
);
684 sp
= n_string_push_c(sp
, sep2
);
686 sp
= n_string_push_cp(sp
,
687 n_ienc_buf(cbuf
, (ui32_t
)*mlp
, 10, n_IENC_MODE_NONE
));
690 (void)n_string_cp(sp
);
691 if(cacp
->cac_vput
== NULL
){
692 if(fprintf(n_stdout
, "%s\n", sp
->s_dat
) < 0){
693 n_pstate_err_no
= n_err_no
;
696 }else if(!n_var_vset(cacp
->cac_vput
, (uintptr_t)sp
->s_dat
)){
697 n_pstate_err_no
= n_ERR_NOTSUP
;
701 /* n_string_gut(sp); */
709 int *msgvec
= v
, *ip
, mesg
;
713 for (ip
= msgvec
; *ip
!= 0; ++ip
) {
715 mp
= message
+ mesg
- 1;
716 fprintf(n_stdout
, "%d: ", mesg
);
717 if (mp
->m_xlines
> 0)
718 fprintf(n_stdout
, "%ld", mp
->m_xlines
);
721 fprintf(n_stdout
, "/%lu\n", (ul_i
)mp
->m_xsize
);
741 int list
[2], rv
= 0, *msgvec
= v
, lastdot
;
744 lastdot
= (int)PTR2SIZE(dot
- message
+ 1);
745 if (delm(msgvec
) >= 0) {
746 list
[0] = (int)PTR2SIZE(dot
- message
+ 1);
747 if (list
[0] > lastdot
) {
753 fprintf(n_stdout
, _("At EOF\n"));
755 fprintf(n_stdout
, _("No more messages\n"));
764 int *msgvec
= v
, *ip
;
768 for (ip
= msgvec
; *ip
!= 0; ++ip
) {
769 mp
= &message
[*ip
- 1];
772 if (mp
->m_flag
& (MDELETED
| MSAVED
))
773 mp
->m_flag
&= ~(MDELETED
| MSAVED
);
775 mp
->m_flag
&= ~MDELETED
;
777 if (mb
.mb_type
== MB_IMAP
|| mb
.mb_type
== MB_CACHE
)
778 imap_undelete(mp
, *ip
);
788 int *msgvec
= v
, *ip
;
791 for (ip
= msgvec
; *ip
!= 0; ++ip
) {
792 setdot(message
+ *ip
- 1);
793 dot
->m_flag
|= MTOUCH
;
794 dot
->m_flag
&= ~MPRESERVE
;
795 n_pstate
|= n_PS_DID_PRINT_DOT
;
804 int *msgvec
= v
, *ip
;
807 if (n_pstate
& n_PS_EDIT
) {
808 n_err(_("`mbox' can only be used in a system mailbox\n")); /* TODO */
812 for (ip
= msgvec
; *ip
!= 0; ++ip
) {
813 setdot(message
+ *ip
- 1);
814 dot
->m_flag
|= MTOUCH
| MBOX
;
815 dot
->m_flag
&= ~MPRESERVE
;
816 n_pstate
|= n_PS_DID_PRINT_DOT
;
826 int *msgvec
= v
, *ip
, mesg
, rv
= 1;
830 if (n_pstate
& n_PS_EDIT
) {
831 fprintf(n_stdout
, _("Cannot `preserve' in a system mailbox\n"));
835 for (ip
= msgvec
; *ip
!= 0; ++ip
) {
837 mp
= message
+ mesg
- 1;
838 mp
->m_flag
|= MPRESERVE
;
841 n_pstate
|= n_PS_DID_PRINT_DOT
;
853 int *msgvec
= v
, *ip
;
856 for (ip
= msgvec
; *ip
!= 0; ++ip
) {
857 mp
= &message
[*ip
- 1];
859 dot
->m_flag
&= ~(MREAD
| MTOUCH
);
860 dot
->m_flag
|= MSTATUS
;
862 if (mb
.mb_type
== MB_IMAP
|| mb
.mb_type
== MB_CACHE
)
863 imap_unread(mp
, *ip
); /* TODO return? */
865 n_pstate
|= n_PS_DID_PRINT_DOT
;
874 int *msgvec
= v
, *ip
;
877 for (ip
= msgvec
; *ip
!= 0; ++ip
) {
878 struct message
*mp
= message
+ *ip
- 1;
890 int *msgvec
= v
, *ip
;
893 for (ip
= msgvec
; *ip
!= 0; ++ip
) {
894 m
= message
+ *ip
- 1;
896 if (!(m
->m_flag
& (MFLAG
| MFLAGGED
)))
897 m
->m_flag
|= MFLAG
| MFLAGGED
;
907 int *msgvec
= v
, *ip
;
910 for (ip
= msgvec
; *ip
!= 0; ++ip
) {
911 m
= message
+ *ip
- 1;
913 if (m
->m_flag
& (MFLAG
| MFLAGGED
)) {
914 m
->m_flag
&= ~(MFLAG
| MFLAGGED
);
915 m
->m_flag
|= MUNFLAG
;
926 int *msgvec
= v
, *ip
;
929 for (ip
= msgvec
; *ip
!= 0; ++ip
) {
930 m
= message
+ *ip
- 1;
932 if (!(m
->m_flag
& (MANSWER
| MANSWERED
)))
933 m
->m_flag
|= MANSWER
| MANSWERED
;
940 c_unanswered(void *v
)
943 int *msgvec
= v
, *ip
;
946 for (ip
= msgvec
; *ip
!= 0; ++ip
) {
947 m
= message
+ *ip
- 1;
949 if (m
->m_flag
& (MANSWER
| MANSWERED
)) {
950 m
->m_flag
&= ~(MANSWER
| MANSWERED
);
951 m
->m_flag
|= MUNANSWER
;
962 int *msgvec
= v
, *ip
;
965 for (ip
= msgvec
; *ip
!= 0; ++ip
) {
966 m
= message
+ *ip
- 1;
968 if (!(m
->m_flag
& (MDRAFT
| MDRAFTED
)))
969 m
->m_flag
|= MDRAFT
| MDRAFTED
;
979 int *msgvec
= v
, *ip
;
982 for (ip
= msgvec
; *ip
!= 0; ++ip
) {
983 m
= message
+ *ip
- 1;
985 if (m
->m_flag
& (MDRAFT
| MDRAFTED
)) {
986 m
->m_flag
&= ~(MDRAFT
| MDRAFTED
);
987 m
->m_flag
|= MUNDRAFT
;