2 * Heirloom mailx - a mail user agent derived from Berkeley Mail.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 Steffen "Daode" Nurpmeso.
8 * Copyright (c) 1980, 1993
9 * The Regents of the University of California. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. All advertising materials mentioning features or use of this software
20 * must display the following acknowledgement:
21 * This product includes software developed by the University of
22 * California, Berkeley and its contributors.
23 * 4. Neither the name of the University nor the names of its contributors
24 * may be used to endorse or promote products derived from this software
25 * without specific prior written permission.
27 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
28 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
31 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
42 static char sccsid
[] = "@(#)cmd1.c 2.97 (gritter) 6/16/07";
53 * Mail -- a mail program
59 * Print the current active headings.
60 * Don't change dot if invoker didn't give an argument.
64 static void onpipe(int signo
);
65 static int dispc(struct message
*mp
, const char *a
);
66 static int scroll1(char *arg
, int onlynew
);
67 static void hprf(const char *fmt
, int mesg
, FILE *f
, int threaded
,
68 const char *attrlist
);
69 static int putindent(FILE *fp
, struct message
*mp
, int maxwidth
);
70 static int type1(int *msgvec
, int doign
, int page
, int pipe
, int decode
,
71 char *cmd
, off_t
*tstats
);
72 static int pipe1(char *str
, int doign
);
73 void brokpipe(int signo
);
81 if (cp
== NULL
|| *cp
== '\0')
82 cp
= value("bsdcompat") ? "more" : "pg";
90 int g
, k
, n
, mesg
, flag
= 0, lastg
= 1;
91 struct message
*mp
, *mq
, *lastmq
= NULL
;
93 enum mflag fl
= MNEW
|MFLAGGED
;
96 n
= msgvec
[0]; /* n == {-2, -1, 0}: called from scroll() */
104 if (mb
.mb_threaded
== 0) {
107 for (mp
= &message
[0]; mp
< &message
[msgCount
]; mp
++)
115 if ((n
> 0 && mp
== &message
[n
-1]) ||
116 (n
== 0 && g
== k
) ||
117 (n
== -2 && g
== k
+ size
&&
120 (mp
->m_flag
& fl
) != 0))
124 if (lastmq
&& (n
==-2 || (n
==-1 && mp
== &message
[msgCount
]))) {
130 mesg
= mp
- &message
[0];
131 if (dot
!= &message
[n
-1]) {
132 for (mq
= mp
; mq
< &message
[msgCount
]; mq
++)
138 if (mb
.mb_type
== MB_IMAP
)
139 imap_getheaders(mesg
+1, mesg
+ size
);
140 for (; mp
< &message
[msgCount
]; mp
++) {
146 printhead(mesg
, stdout
, 0);
148 } else { /* threaded */
151 for (mp
= threadroot
; mp
; mp
= next_in_thread(mp
))
152 if (visible(mp
) && (mp
->m_collapsed
<= 0 ||
153 mp
== &message
[n
-1])) {
160 if ((n
> 0 && mp
== &message
[n
-1]) ||
161 (n
== 0 && g
== k
) ||
162 (n
== -2 && g
== k
+ size
&&
165 (mp
->m_flag
& fl
) != 0))
169 if (lastmq
&& (n
==-2 || (n
==-1 && mp
==&message
[msgCount
]))) {
175 if (dot
!= &message
[n
-1]) {
176 for (mq
= mp
; mq
; mq
= next_in_thread(mq
))
177 if (visible(mq
) && mq
->m_collapsed
<= 0) {
183 if (visible(mp
) && (mp
->m_collapsed
<= 0 ||
184 mp
== &message
[n
-1])) {
187 printhead(mp
- &message
[0] + 1, stdout
,
190 mp
= next_in_thread(mp
);
194 printf(catgets(catd
, CATSET
, 6, "No more mail.\n"));
201 * Scroll to the next/previous screen
206 return scroll1(v
, 0);
212 return scroll1(v
, 1);
216 scroll1(char *arg
, int onlynew
)
221 cur
[0] = onlynew
? -1 : 0;
224 case '1': case '2': case '3': case '4': case '5':
225 case '6': case '7': case '8': case '9': case '0':
232 screen
= msgCount
/ size
;
238 screen
+= atoi(arg
+ 1);
240 if (screen
* size
> msgCount
) {
241 screen
= msgCount
/ size
;
242 printf(catgets(catd
, CATSET
, 7,
243 "On last screenful of messages\n"));
251 screen
-= atoi(arg
+ 1);
254 printf(catgets(catd
, CATSET
, 8,
255 "On first screenful of messages\n"));
262 printf(catgets(catd
, CATSET
, 9,
263 "Unrecognized scrolling command \"%s\"\n"), arg
);
266 return(headers(cur
));
270 * Compute screen size.
278 if ((cp
= value("screen")) != NULL
&& (s
= atoi(cp
)) > 0)
280 return scrnheight
- 4;
283 static sigjmp_buf pipejmp
;
290 siglongjmp(pipejmp
, 1);
294 * Print out the headlines for each message
295 * in the passed message list.
305 if (is_a_tty
[0] && is_a_tty
[1] && (cp
= value("crt")) != NULL
) {
306 for (n
= 0, ip
= msgvec
; *ip
; ip
++)
308 if (n
> (*cp
== '\0' ? screensize() : atoi(cp
)) + 3) {
310 /* TODO should be below the Popen?
311 * TODO Problem: Popen doesn't encapsulate it,
312 * TODO may leave child running if fdopen() fails!
313 * TODO even more such stuff in this file! */
314 if (sigsetjmp(pipejmp
, 1))
316 if ((obuf
= Popen(cp
, "w", NULL
, 1)) == NULL
) {
320 safe_signal(SIGPIPE
, onpipe
);
323 for (ip
= msgvec
; *ip
!= 0; ip
++)
324 printhead(*ip
, obuf
, mb
.mb_threaded
);
326 setdot(&message
[*ip
- 1]);
328 if (obuf
!= stdout
) {
329 safe_signal(SIGPIPE
, SIG_IGN
);
331 safe_signal(SIGPIPE
, dflpipe
);
337 dispc(struct message
*mp
, const char *a
)
344 if ((mp
->m_flag
& (MREAD
|MNEW
)) == MREAD
)
346 if ((mp
->m_flag
& (MREAD
|MNEW
)) == (MREAD
|MNEW
))
348 if (mp
->m_flag
& MANSWERED
)
350 if (mp
->m_flag
& MDRAFTED
)
352 if ((mp
->m_flag
& (MREAD
|MNEW
)) == MNEW
)
354 if ((mp
->m_flag
& (MREAD
|MNEW
)) == 0)
356 if (mp
->m_flag
& MJUNK
)
358 if (mp
->m_flag
& MSAVED
)
360 if (mp
->m_flag
& MPRESERVE
)
362 if (mp
->m_flag
& (MBOX
|MBOXED
))
364 if (mp
->m_flag
& MFLAGGED
)
366 if (mp
->m_flag
& MKILL
)
368 if (mb
.mb_threaded
== 1 && mp
->m_collapsed
> 0)
370 if (mb
.mb_threaded
== 1 && mp
->m_collapsed
< 0)
376 hprf(const char *fmt
, int mesg
, FILE *f
, int threaded
, const char *attrlist
)
378 struct message
*mp
= &message
[mesg
-1];
379 char *headline
= NULL
, *subjline
, *name
, *cp
, *pbuf
= NULL
;
386 int subjlen
= scrnwidth
, fromlen
, isto
= 0, isaddr
= 0;
389 if ((mp
->m_flag
& MNOFROM
) == 0) {
390 if ((ibuf
= setinput(&mb
, mp
, NEED_HEADER
)) == NULL
)
392 if ((headlen
= readline(ibuf
, &headline
, &headsize
)) < 0)
395 if ((subjline
= hfield("subject", mp
)) == NULL
)
396 subjline
= hfield("subj", mp
);
397 if (subjline
== NULL
) {
402 in
.l
= strlen(subjline
);
403 mime_fromhdr(&in
, &out
, TD_ICONV
| TD_ISPR
);
406 if ((mp
->m_flag
& MNOFROM
) == 0) {
407 pbuf
= ac_alloc(headlen
+ 1);
408 parse(headline
, headlen
, &hl
, pbuf
);
410 hl
.l_from
= /*fakefrom(mp);*/NULL
;
412 hl
.l_date
= fakedate(mp
->m_time
);
414 if (value("datefield") && (cp
= hfield("date", mp
)) != NULL
)
415 hl
.l_date
= fakedate(rfctime(cp
));
417 if ((name
= hfield("newsgroups", mp
)) == NULL
)
418 if ((name
= hfield("article-id", mp
)) == NULL
)
421 } else if (value("show-rcpt") == NULL
) {
424 if (value("showto") && name
&& is_myname(skin(name
))) {
425 if ((cp
= hfield("to", mp
)) != NULL
) {
432 if ((name
= hfield("to", mp
)) != NULL
)
440 if (value("showname"))
441 name
= realname(name
);
443 name
= prstr(skin(name
));
446 for (fp
= fmt
; *fp
; fp
++) {
450 } else if (*fp
== '+')
452 while (digitchar(*fp
&0377))
457 #if defined (HAVE_MBTOWC) && defined (HAVE_WCWIDTH)
458 if (mb_cur_max
> 1) {
460 if ((s
= mbtowc(&wc
, fp
, mb_cur_max
)) < 0)
463 if ((n
= wcwidth(wc
)) < 0)
467 #endif /* HAVE_MBTOWC && HAVE_WCWIDTH */
476 for (fp
= fmt
; *fp
; fp
++) {
484 } else if (*fp
== '+')
486 if (digitchar(*fp
&0377)) {
488 n
= 10*n
+ *fp
- '0';
489 while (fp
++, digitchar(*fp
&0377));
501 c
= dot
== mp
? *fp
&0377 : ' ';
506 c
= dispc(mp
, attrlist
);
514 for (i
=msgCount
; i
>999; i
/=10)
517 subjlen
-= fprintf(f
, "%*d", n
, mesg
);
525 fprintf(f
, "%s%s", isto
? "To " : "",
526 colalign(name
, fromlen
, 1));
532 subjlen
-= fprintf(f
, "%*.*s", n
, n
, hl
.l_date
);
538 subjlen
-= fprintf(f
, "%*ld", n
,
549 subjlen
-= fprintf(f
, "%*lu", n
,
554 subjlen
-= putindent(f
, mp
,
561 n
= n
>0 ? n
: subjlen
- 2;
564 if (subjline
!= NULL
&& n
>= 0) {
565 /* pretty pathetic */
566 fprintf(f
, B
? "\"%s\"" : "%s",
567 colalign(subjline
, n
, 0));
573 subjlen
-= fprintf(f
, "%*lu", n
, mp
->m_uid
);
578 subjlen
-= fprintf(f
, "%*u", n
, threaded
== 1 ?
585 for (i
=msgCount
; i
>999; i
/=10)
588 fprintf(f
, "%*ld", n
, threaded
?
589 mp
->m_threadpos
: mesg
);
595 subjlen
-= fprintf(f
, "%*g", n
, mp
->m_score
);
611 * Print out the indenting in threaded display.
614 putindent(FILE *fp
, struct message
*mp
, int maxwidth
)
620 int important
= MNEW
|MFLAGGED
;
622 if (mp
->m_level
== 0)
624 cs
= ac_alloc(mp
->m_level
);
625 us
= ac_alloc(mp
->m_level
* sizeof *us
);
627 if (mp
->m_younger
&& (unsigned)i
+ 1 == mp
->m_younger
->m_level
) {
628 if (mp
->m_parent
&& mp
->m_parent
->m_flag
& important
)
629 us
[i
] = mp
->m_flag
& important
? 0x2523 : 0x2520;
631 us
[i
] = mp
->m_flag
& important
? 0x251D : 0x251C;
634 if (mp
->m_parent
&& mp
->m_parent
->m_flag
& important
)
635 us
[i
] = mp
->m_flag
& important
? 0x2517 : 0x2516;
637 us
[i
] = mp
->m_flag
& important
? 0x2515 : 0x2514;
641 for (i
= mp
->m_level
- 2; i
>= 0; i
--) {
643 if ((unsigned)i
> mq
->m_level
- 1) {
649 mq
->m_parent
->m_flag
&important
)
660 for (indent
= 0; (unsigned)indent
< mp
->m_level
&& indent
< maxwidth
;
662 if (indent
< maxwidth
- 1)
663 putuc(us
[indent
], cs
[indent
] & 0377, fp
);
665 putuc(0x21B8, '^', fp
);
673 * Print out the header of a specific message.
674 * This is a slight improvement to the standard one.
677 printhead(int mesg
, FILE *f
, int threaded
)
679 int bsdflags
, bsdheadline
, sz
;
680 char *fmt
, attrlist
[30], *cp
;
682 bsdflags
= value("bsdcompat") != NULL
|| value("bsdflags") != NULL
||
683 getenv("SYSV3") != NULL
;
684 strcpy(attrlist
, bsdflags
? "NU *HMFATK+-J" : "NUROSPMFATK+-J");
685 if ((cp
= value("attrlist")) != NULL
) {
687 if (sz
> (int)sizeof attrlist
- 1)
688 sz
= (int)sizeof attrlist
- 1;
689 memcpy(attrlist
, cp
, sz
);
691 bsdheadline
= value("bsdcompat") != NULL
||
692 value("bsdheadline") != NULL
;
693 if ((fmt
= value("headline")) == NULL
)
695 "%>%a%m %20f %16d %3l/%-5o %i%S" :
696 "%>%a%m %18f %16d %4l/%-5o %i%s";
697 hprf(fmt
, mesg
, f
, threaded
, attrlist
);
701 * Print out the value of dot.
708 printf(catgets(catd
, CATSET
, 13, "%d\n"),
709 (int)(dot
- &message
[0] + 1));
714 * Print out all the possible commands.
720 extern const struct cmd cmdtab
[];
721 const struct cmd
*cp
;
725 printf(catgets(catd
, CATSET
, 14, "Commands are:\n"));
726 for (cc
= 0, cp
= cmdtab
; cp
->c_name
!= NULL
; cp
++) {
727 cc
+= strlen(cp
->c_name
) + 2;
730 cc
= strlen(cp
->c_name
) + 2;
732 if ((cp
+1)->c_name
!= NULL
)
733 printf(catgets(catd
, CATSET
, 15, "%s, "), cp
->c_name
);
735 printf("%s\n", cp
->c_name
);
741 * Type out the messages requested.
743 static sigjmp_buf pipestop
;
746 type1(int *msgvec
, int doign
, int page
, int pipe
, int decode
,
747 char *cmd
, off_t
*tstats
)
755 * Must be static to become excluded from sigsetjmp().
759 /* Avoid longjmp clobbering */
766 if (sigsetjmp(pipestop
, 1))
772 obuf
= Popen(cmd
, "w", cp
, 1);
777 safe_signal(SIGPIPE
, brokpipe
);
779 } else if (value("interactive") != NULL
&&
780 (page
|| (cp
= value("crt")) != NULL
)) {
783 for (ip
= msgvec
; *ip
&& ip
-msgvec
< msgCount
; ip
++) {
784 if ((message
[*ip
-1].m_have
& HAVE_BODY
) == 0) {
785 if ((get_body(&message
[*ip
- 1])) !=
789 nlines
+= message
[*ip
- 1].m_lines
;
792 if (page
|| nlines
> (*cp
? atoi(cp
) : realscreenheight
)) {
794 obuf
= Popen(cp
, "w", NULL
, 1);
799 safe_signal(SIGPIPE
, brokpipe
);
802 for (ip
= msgvec
; *ip
&& ip
- msgvec
< msgCount
; ip
++) {
803 mp
= &message
[*ip
- 1];
807 if (value("quiet") == NULL
)
808 fprintf(obuf
, catgets(catd
, CATSET
, 17,
809 "Message %2d:\n"), *ip
);
810 send(mp
, obuf
, doign
? ignore
: 0, NULL
,
811 pipe
&& value("piperaw") ? SEND_MBOX
:
813 doign
? SEND_TODISP
: SEND_TODISP_ALL
,
815 if (pipe
&& value("page")) {
819 tstats
[0] += mstats
[0];
820 tstats
[1] += mstats
[1];
824 if (obuf
!= stdout
) {
826 * Ignore SIGPIPE so it can't cause a duplicate close.
828 safe_signal(SIGPIPE
, SIG_IGN
);
830 safe_signal(SIGPIPE
, dflpipe
);
836 * Get the last, possibly quoted part of linebuf.
839 laststring(char *linebuf
, int *flag
, int strip
)
845 cp
= strlen(linebuf
) + linebuf
- 1;
848 * Strip away trailing blanks.
850 while (cp
> linebuf
&& whitechar(*cp
& 0377))
859 * Now search for the beginning of the command name.
862 if (quoted
== '\'' || quoted
== '\"') {
867 while (cp
> linebuf
) {
870 } else if (*(cp
- 1) != '\\') {
888 while (cp
> linebuf
&& !whitechar(*cp
& 0377))
890 if (whitechar(*cp
& 0377))
902 * Pipe the messages requested.
905 pipe1(char *str
, int doign
)
912 msgvec
= (int *)salloc((msgCount
+ 2) * sizeof *msgvec
);
913 if ((cmd
= laststring(str
, &f
, 1)) == NULL
) {
915 if (cmd
== NULL
|| *cmd
== '\0') {
916 fputs(catgets(catd
, CATSET
, 16,
917 "variable cmd not set\n"), stderr
);
922 *msgvec
= first(0, MMNORM
);
926 puts(catgets(catd
, CATSET
, 18, "No messages to pipe."));
930 } else if (getmsglist(str
, msgvec
, 0) < 0)
935 printf("No applicable messages.\n");
938 printf(catgets(catd
, CATSET
, 268, "Pipe to: \"%s\"\n"), cmd
);
939 stats
[0] = stats
[1] = 0;
940 if ((ret
= type1(msgvec
, doign
, 0, 1, 0, cmd
, stats
)) == 0) {
941 printf("\"%s\" ", cmd
);
943 printf("%lu", (long)stats
[0]);
945 printf(catgets(catd
, CATSET
, 27, "binary"));
946 printf("/%lu\n", (long)stats
[1]);
952 * Paginate messages, honor ignored fields.
958 return (type1(msgvec
, 1, 1, 0, 0, NULL
, NULL
));
962 * Paginate messages, even printing ignored fields.
969 return (type1(msgvec
, 0, 1, 0, 0, NULL
, NULL
));
973 * Type out messages, honor ignored fields.
980 return(type1(msgvec
, 1, 0, 0, 0, NULL
, NULL
));
984 * Type out messages, even printing ignored fields.
991 return(type1(msgvec
, 0, 0, 0, 0, NULL
, NULL
));
995 * Show MIME-encoded message text, including all fields.
1002 return(type1(msgvec
, 0, 0, 0, 1, NULL
, NULL
));
1006 * Pipe messages, honor ignored fields.
1012 return(pipe1(str
, 1));
1015 * Pipe messages, not respecting ignored fields.
1021 return(pipe1(str
, 0));
1025 * Respond to a broken pipe signal --
1026 * probably caused by quitting more.
1033 siglongjmp(pipestop
, 1);
1037 * Print the top so many lines of each desired message.
1038 * The number of lines is taken from the variable "toplines"
1039 * and defaults to 5.
1047 int c
, topl
, lines
, lineb
;
1048 char *valtop
, *linebuf
= NULL
;
1053 valtop
= value("toplines");
1054 if (valtop
!= NULL
) {
1055 topl
= atoi(valtop
);
1056 if (topl
< 0 || topl
> 10000)
1060 for (ip
= msgvec
; *ip
&& ip
-msgvec
< msgCount
; ip
++) {
1061 mp
= &message
[*ip
- 1];
1065 if (value("quiet") == NULL
)
1066 printf(catgets(catd
, CATSET
, 19,
1067 "Message %2d:\n"), *ip
);
1068 if (mp
->m_flag
& MNOFROM
)
1069 printf("From %s %s\n", fakefrom(mp
),
1070 fakedate(mp
->m_time
));
1071 if ((ibuf
= setinput(&mb
, mp
, NEED_BODY
)) == NULL
) /* XXX could use TOP */
1076 for (lines
= 0; lines
< c
&& lines
<= topl
; lines
++) {
1077 if (readline(ibuf
, &linebuf
, &linesize
) < 0)
1080 lineb
= blankline(linebuf
);
1089 * Touch all the given messages so that they will
1098 for (ip
= msgvec
; *ip
!= 0; ip
++) {
1099 setdot(&message
[*ip
-1]);
1100 dot
->m_flag
|= MTOUCH
;
1101 dot
->m_flag
&= ~MPRESERVE
;
1103 * POSIX interpretation necessary.
1111 * Make sure all passed messages get mboxed.
1119 for (ip
= msgvec
; *ip
!= 0; ip
++) {
1120 setdot(&message
[*ip
-1]);
1121 dot
->m_flag
|= MTOUCH
|MBOX
;
1122 dot
->m_flag
&= ~MPRESERVE
;
1124 * POSIX interpretation necessary.
1132 * List the folders the user currently has.
1138 char dirname
[PATHSIZE
];
1142 name
= expand(*argv
);
1143 else if (getfold(dirname
, sizeof dirname
) < 0) {
1144 printf(catgets(catd
, CATSET
, 20,
1145 "No value set for \"folder\"\n"));
1149 if (which_protocol(name
) == PROTO_IMAP
)
1150 imap_folders(name
, *argv
== NULL
);
1152 if ((cmd
= value("LISTER")) == NULL
)
1154 run_command(cmd
, 0, -1, -1, name
, NULL
, NULL
);