2 * Heirloom mailx - a mail user agent derived from Berkeley Mail.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
7 * Copyright (c) 1980, 1993
8 * The Regents of the University of California. All rights reserved.
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 * 3. All advertising materials mentioning features or use of this software
19 * must display the following acknowledgement:
20 * This product includes software developed by the University of
21 * California, Berkeley and its contributors.
22 * 4. Neither the name of the University nor the names of its contributors
23 * may be used to endorse or promote products derived from this software
24 * without specific prior written permission.
26 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
27 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
30 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
31 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
32 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
34 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
35 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
41 static char sccsid
[] = "@(#)list.c 2.62 (gritter) 12/11/08";
50 #endif /* HAVE_WCTYPE_H */
53 * Mail -- a mail program
55 * Message list handling.
63 static char **add_to_namelist(char ***namelist
, size_t *nmlsize
,
64 char **np
, char *string
);
65 static int markall(char *buf
, int f
);
66 static int evalcol(int col
);
67 static int check(int mesg
, int f
);
68 static int scan(char **sp
);
69 static void regret(int token
);
70 static void scaninit(void);
71 static int matchsender(char *str
, int mesg
, int allnet
);
72 static int matchmid(char *id
, enum idfield idfield
, int mesg
);
73 static int matchsubj(char *str
, int mesg
);
74 static void unmark(int mesg
);
75 static int metamess(int meta
, int f
);
77 static size_t STRINGLEN
;
79 static int lexnumber
; /* Number of TNUMBER from scan() */
80 static char *lexstring
; /* String from TSTRING, scan() */
81 static int regretp
; /* Pointer to TOS of regret tokens */
82 static int regretstack
[REGDEP
]; /* Stack of regretted tokens */
83 static char *string_stack
[REGDEP
]; /* Stack of regretted strings */
84 static int numberstack
[REGDEP
]; /* Stack of regretted numbers */
85 static int threadflag
; /* mark entire threads */
88 * Convert the user string of message numbers and
89 * store the numbers into vector.
91 * Returns the count of messages picked up or -1 on error.
94 getmsglist(char *buf
, int *vector
, int flags
)
104 if (markall(buf
, flags
) < 0)
109 for (mp
= &message
[0]; mp
< &message
[msgCount
]; mp
++)
110 if (mp
->m_flag
& MMARK
) {
111 if ((mp
->m_flag
& MNEWEST
) == 0)
112 unmark(mp
- &message
[0] + 1);
119 if (mb
.mb_threaded
== 0) {
120 for (mp
= &message
[0]; mp
< &message
[msgCount
]; mp
++)
121 if (mp
->m_flag
& MMARK
)
122 *ip
++ = mp
- &message
[0] + 1;
124 for (mp
= threadroot
; mp
; mp
= next_in_thread(mp
))
125 if (mp
->m_flag
& MMARK
)
126 *ip
++ = mp
- &message
[0] + 1;
133 * Mark all messages that the user wanted from the command
134 * line in the message structure. Return 0 on success, -1
139 * Bit values for colon modifiers.
142 #define CMNEW 01 /* New messages */
143 #define CMOLD 02 /* Old messages */
144 #define CMUNREAD 04 /* Unread messages */
145 #define CMDELETED 010 /* Deleted messages */
146 #define CMREAD 020 /* Read messages */
147 #define CMFLAG 040 /* Flagged messages */
148 #define CMANSWER 0100 /* Answered messages */
149 #define CMDRAFT 0200 /* Draft messages */
150 #define CMKILL 0400 /* Killed messages */
151 #define CMJUNK 01000 /* Junk messages */
154 * The following table describes the letters which can follow
155 * the colon and gives the corresponding modifier bit.
158 static struct coltab
{
159 char co_char
; /* What to find past : */
160 int co_bit
; /* Associated modifier bit */
161 int co_mask
; /* m_status bits to mask */
162 int co_equal
; /* ... must equal this */
164 { 'n', CMNEW
, MNEW
, MNEW
},
165 { 'o', CMOLD
, MNEW
, 0 },
166 { 'u', CMUNREAD
, MREAD
, 0 },
167 { 'd', CMDELETED
, MDELETED
, MDELETED
},
168 { 'r', CMREAD
, MREAD
, MREAD
},
169 { 'f', CMFLAG
, MFLAGGED
, MFLAGGED
},
170 { 'a', CMANSWER
, MANSWERED
, MANSWERED
},
171 { 't', CMDRAFT
, MDRAFTED
, MDRAFTED
},
172 { 'k', CMKILL
, MKILL
, MKILL
},
173 { 'j', CMJUNK
, MJUNK
, MJUNK
},
177 static int lastcolmod
;
180 add_to_namelist(char ***namelist
, size_t *nmlsize
, char **np
, char *string
)
184 if ((idx
= np
- *namelist
) >= *nmlsize
) {
185 *namelist
= srealloc(*namelist
, (*nmlsize
+= 8) * sizeof *np
);
186 np
= &(*namelist
)[idx
];
192 #define markall_ret(i) { \
194 ac_free(lexstring); \
199 markall(char *buf
, int f
)
202 int i
, retval
, gotheaders
;
203 struct message
*mp
, *mx
;
204 char **namelist
, *bufp
, *id
= NULL
, *cp
;
205 int tok
, beg
, mc
, star
, other
, valdot
, colmod
, colresult
, topen
, tback
;
207 enum idfield idfield
= ID_REFERENCES
;
209 lexstring
= ac_alloc(STRINGLEN
= 2 * strlen(buf
) + 1);
210 valdot
= dot
- &message
[0] + 1;
212 for (i
= 1; i
<= msgCount
; i
++) {
213 message
[i
-1].m_flag
&= ~MOLDMARK
;
214 if (message
[i
-1].m_flag
& MMARK
)
215 message
[i
-1].m_flag
|= MOLDMARK
;
220 namelist
= smalloc((nmlsize
= 8) * sizeof *namelist
);
230 while (tok
!= TEOL
) {
235 printf(catgets(catd
, CATSET
, 112,
236 "No numbers mixed with *\n"));
242 if (check(lexnumber
, f
))
245 while (mb
.mb_threaded
? 1 : i
<= lexnumber
) {
246 if (!(message
[i
-1].m_flag
&MHIDDEN
) &&
248 (message
[i
-1].m_flag
&
251 if (mb
.mb_threaded
) {
254 mx
= next_in_thread(&message
[i
-1]);
277 printf(catgets(catd
, CATSET
, 113,
278 "Non-numeric second argument\n"));
283 if (mb
.mb_threaded
) {
284 mx
= next_in_thread(&message
[i
-1]);
285 i
= mx
? mx
-message
+1 : msgCount
+1;
289 printf(catgets(catd
, CATSET
, 114,
290 "Referencing beyond EOF\n"));
293 } while (message
[i
-1].m_flag
== MHIDDEN
||
294 (message
[i
-1].m_flag
& MDELETED
) != f
||
295 message
[i
-1].m_flag
& MKILL
);
303 if (mb
.mb_threaded
) {
306 i
= mx
? mx
-message
+1 : 0;
310 printf(catgets(catd
, CATSET
,
312 "Referencing before 1\n"));
315 } while (message
[i
-1].m_flag
& MHIDDEN
||
316 (message
[i
-1].m_flag
& MDELETED
)
318 message
[i
-1].m_flag
& MKILL
);
325 printf(catgets(catd
, CATSET
, 116,
326 "Non-numeric second argument\n"));
330 if (lexstring
[0] == ':') {
331 colresult
= evalcol(lexstring
[1]);
332 if (colresult
== 0) {
333 printf(catgets(catd
, CATSET
, 117,
334 "Unknown colon modifier \"%s\"\n"),
341 np
= add_to_namelist(&namelist
, &nmlsize
,
342 np
, savestr(lexstring
));
346 if (imap_search(lexstring
, f
) == STOP
)
355 lexnumber
= metamess(lexstring
[0], f
);
362 for (i
= 1; i
<= msgCount
; i
++) {
363 if (message
[i
-1].m_flag
&MHIDDEN
||
364 (message
[i
-1].m_flag
&MDELETED
)
367 if (message
[i
-1].m_flag
&MOLDMARK
)
374 printf(catgets(catd
, CATSET
, 118,
375 "Can't mix \"*\" with anything\n"));
382 if (mb
.mb_type
== MB_IMAP
&& gotheaders
++ == 0)
383 imap_getheaders(1, msgCount
);
384 if (id
== NULL
&& (cp
= hfield("in-reply-to", dot
))
387 idfield
= ID_IN_REPLY_TO
;
389 if (id
== NULL
&& (cp
= hfield("references", dot
))
392 if ((np
= extract(cp
, GREF
)) != NULL
) {
393 while (np
->n_flink
!= NULL
)
395 id
= savestr(np
->n_name
);
396 idfield
= ID_REFERENCES
;
400 printf(catgets(catd
, CATSET
, 227,
401 "Cannot determine parent Message-ID of the current message\n"));
413 np
= add_to_namelist(&namelist
, &nmlsize
, np
, NULL
);
417 for (i
= 0; i
< msgCount
; i
++) {
418 if (!(message
[i
].m_flag
& MHIDDEN
) &&
419 (message
[i
].m_flag
& MDELETED
) == f
) {
426 printf(catgets(catd
, CATSET
, 119,
427 "No applicable messages.\n"));
433 if ((topen
|| tback
) && mc
== 0) {
434 for (i
= 0; i
< msgCount
; i
++)
435 if (message
[i
].m_flag
& MMARK
)
440 "No previously marked messages.\n" :
441 "No messages satisfy (criteria).\n");
447 * If no numbers were given, mark all of the messages,
448 * so that we can unmark any whose sender was not selected
449 * if any user names were given.
452 if ((np
> namelist
|| colmod
!= 0 || id
) && mc
== 0)
453 for (i
= 1; i
<= msgCount
; i
++) {
454 if (!(message
[i
-1].m_flag
& MHIDDEN
) &&
455 (message
[i
-1].m_flag
& MDELETED
) == f
)
460 * If any names were given, go through and eliminate any
461 * messages whose senders were not requested.
464 if (np
> namelist
|| id
) {
465 int allnet
= value("allnet") != NULL
;
467 if (mb
.mb_type
== MB_IMAP
&& gotheaders
++ == 0)
468 imap_getheaders(1, msgCount
);
469 for (i
= 1; i
<= msgCount
; i
++) {
472 for (nq
= &namelist
[0]; *nq
!= NULL
; nq
++) {
474 if (matchsubj(*nq
, i
)) {
480 if (matchsender(*nq
, i
,
488 if (mc
== 0 && id
&& matchmid(id
, idfield
, i
))
495 * Make sure we got some decent messages.
499 for (i
= 1; i
<= msgCount
; i
++)
500 if (message
[i
-1].m_flag
& MMARK
) {
505 if (!inhook
&& np
> namelist
) {
506 printf(catgets(catd
, CATSET
, 120,
507 "No applicable messages from {%s"),
509 for (nq
= &namelist
[1]; *nq
!= NULL
; nq
++)
510 printf(catgets(catd
, CATSET
, 121,
512 printf(catgets(catd
, CATSET
, 122, "}\n"));
514 printf(catgets(catd
, CATSET
, 227,
515 "Parent message not found\n"));
522 * If any colon modifiers were given, go through and
523 * unmark any messages which do not satisfy the modifiers.
527 for (i
= 1; i
<= msgCount
; i
++) {
530 mp
= &message
[i
- 1];
531 for (colp
= &coltab
[0]; colp
->co_char
; colp
++)
532 if (colp
->co_bit
& colmod
)
533 if ((mp
->m_flag
& colp
->co_mask
)
538 for (mp
= &message
[0]; mp
< &message
[msgCount
]; mp
++)
539 if (mp
->m_flag
& MMARK
)
541 if (mp
>= &message
[msgCount
]) {
545 printf(catgets(catd
, CATSET
, 123,
546 "No messages satisfy"));
547 for (colp
= &coltab
[0]; colp
->co_char
; colp
++)
548 if (colp
->co_bit
& colmod
)
549 printf(" :%c", colp
->co_char
);
562 * Turn the character after a colon modifier into a bit
572 for (colp
= &coltab
[0]; colp
->co_char
; colp
++)
573 if (colp
->co_char
== col
)
574 return(colp
->co_bit
);
579 * Check the passed message number for legality and proper flags.
580 * If f is MDELETED, then either kind will do. Otherwise, the message
581 * has to be undeleted.
584 check(int mesg
, int f
)
588 if (mesg
< 1 || mesg
> msgCount
) {
589 printf(catgets(catd
, CATSET
, 124,
590 "%d: Invalid message number\n"), mesg
);
593 mp
= &message
[mesg
-1];
594 if (mp
->m_flag
& MHIDDEN
|| (f
!= MDELETED
&&
595 (mp
->m_flag
& MDELETED
) != 0)) {
596 printf(catgets(catd
, CATSET
, 125,
597 "%d: Inappropriate message\n"), mesg
);
604 * Scan out the list of string arguments, shell style
608 getrawlist(const char *line
, size_t linesize
, char **argv
, int argc
,
611 char c
, *cp2
, quotec
;
618 linebuf
= ac_alloc(linesize
+ 1);
620 for (; blankchar(*cp
& 0377); cp
++);
623 if (argn
>= argc
- 1) {
624 printf(catgets(catd
, CATSET
, 126,
625 "Too many elements in the list; excess discarded.\n"));
630 while ((c
= *cp
) != '\0') {
632 if (quotec
!= '\0') {
637 } else if (c
== '\\')
644 case '0': case '1': case '2': case '3':
645 case '4': case '5': case '6': case '7':
647 if (*cp >= '0' && *cp <= '7')
648 c = c * 8 + *cp++ - '0';
649 if (*cp >= '0' && *cp <= '7')
650 c = c * 8 + *cp++ - '0';
673 if (cp
[-1]!=quotec
|| echolist
)
677 /*else if (c == '^') {
681 /\* null doesn't show up anyway *\/
682 else if ((c >= 'A' && c <= '_') ||
683 (c >= 'a' && c <= 'z'))
691 } else if (c
== '"' || c
== '\'') {
695 } else if (c
== '\\' && !echolist
) {
700 } else if (blankchar(c
& 0377))
706 argv
[argn
++] = savestr(linebuf
);
714 * scan out a single lexical item and return its token number,
715 * updating the string pointer passed **p. Also, store the value
716 * of the number or string scanned in lexnumber or lexstring as
717 * appropriate. In any event, store the scanned `thing' in lexstring.
742 int c
, level
, inquote
;
747 strncpy(lexstring
, string_stack
[regretp
], STRINGLEN
);
748 lexstring
[STRINGLEN
-1]='\0';
749 lexnumber
= numberstack
[regretp
];
750 return(regretstack
[regretp
--]);
757 * strip away leading white space.
764 * If no characters remain, we are at end of line,
774 * Select members of a message thread.
778 if (*cp
== '\0' || spacechar(*cp
&0377)) {
788 * If the leading character is a digit, scan
789 * the number and convert it on the fly.
790 * Return TNUMBER when done.
795 while (digitchar(c
)) {
796 lexnumber
= lexnumber
*10 + c
- '0';
806 * An IMAP SEARCH list. Note that TOPEN has always been included
807 * in singles[] in Mail and mailx. Thus although there is no formal
808 * definition for (LIST) lists, they do not collide with historical
809 * practice because a subject string (LIST) could never been matched
818 if ((c
= *cp
++&0377) == '\0') {
819 mtop
: fprintf(stderr
, "Missing \")\".\n");
822 if (inquote
&& c
== '\\') {
835 else if (spacechar(c
)) {
837 * Replace unquoted whitespace by single
838 * space characters, to make the string
839 * IMAP SEARCH conformant.
846 } while (c
!= ')' || level
> 0);
853 * Check for single character tokens; return such
857 for (lp
= &singles
[0]; lp
->l_char
!= 0; lp
++)
858 if (c
== lp
->l_char
) {
866 * We've got a string! Copy all the characters
867 * of the string into lexstring, until we see
868 * a null, space, or tab.
869 * If the lead character is a " or ', save it
870 * and scan until you get another.
874 if (c
== '\'' || c
== '"') {
879 if (quotec
== 0 && c
== '\\' && *cp
)
885 if (quotec
== 0 && blankchar(c
))
887 if (cp2
- lexstring
< STRINGLEN
-1)
891 if (quotec
&& c
== 0) {
892 fprintf(stderr
, catgets(catd
, CATSET
, 127,
893 "Missing %c\n"), quotec
);
902 * Unscan the named token by pushing it onto the regret stack.
907 if (++regretp
>= REGDEP
)
908 panic(catgets(catd
, CATSET
, 128, "Too many regrets"));
909 regretstack
[regretp
] = token
;
910 lexstring
[STRINGLEN
-1] = '\0';
911 string_stack
[regretp
] = savestr(lexstring
);
912 numberstack
[regretp
] = lexnumber
;
916 * Reset all the scanner global variables.
926 * Find the first message whose flags & m == f and return
927 * its message number.
938 for (mp
= dot
; mb
.mb_threaded
? mp
!= NULL
: mp
< &message
[msgCount
];
939 mb
.mb_threaded
? mp
= next_in_thread(mp
) : mp
++) {
940 if (!(mp
->m_flag
& MHIDDEN
) && (mp
->m_flag
& m
) == f
)
941 return mp
- message
+ 1;
943 if (dot
> &message
[0]) {
944 for (mp
= dot
-1; mb
.mb_threaded
?
945 mp
!= NULL
: mp
>= &message
[0];
947 mp
= prev_in_thread(mp
) : mp
--) {
948 if (!(mp
->m_flag
& MHIDDEN
) && (mp
->m_flag
& m
) == f
)
949 return mp
- message
+ 1;
956 * See if the passed name sent the passed message number. Return true
960 matchsender(char *str
, int mesg
, int allnet
)
963 char *cp
= nameof(&message
[mesg
- 1], 0);
966 if ((*cp
== '@' || *cp
== '\0') &&
967 (*str
== '@' || *str
== '\0'))
971 } while (cp
++, *str
++ != '\0');
974 return !strcmp(str
, (value("showname") ? realname
: skin
)
975 (name1(&message
[mesg
- 1], 0)));
979 matchmid(char *id
, enum idfield idfield
, int mesg
)
984 if ((cp
= hfield("message-id", &message
[mesg
- 1])) != NULL
) {
987 return msgidcmp(id
, cp
) == 0;
989 if ((np
= extract(id
, GREF
)) != NULL
)
991 if (msgidcmp(np
->n_name
, cp
) == 0)
993 } while ((np
= np
->n_flink
) != NULL
);
1001 * See if the given string matches inside the subject field of the
1002 * given message. For the purpose of the scan, we ignore case differences.
1003 * If it does, return true. The string search argument is assumed to
1004 * have the form "/search-string." If it is of the form "/," we use the
1005 * previous search string.
1008 static char lastscan
[128];
1011 matchsubj(char *str
, int mesg
)
1019 if (strlen(str
) == 0) {
1022 strncpy(lastscan
, str
, sizeof lastscan
);
1023 lastscan
[sizeof lastscan
- 1]='\0';
1025 mp
= &message
[mesg
-1];
1028 * Now look, ignoring case, for the word in the string.
1031 if (value("searchheaders") && (cp
= strchr(str
, ':'))) {
1033 cp2
= hfield(str
, mp
);
1038 cp2
= hfield("subject", mp
);
1044 mime_fromhdr(&in
, &out
, TD_ICONV
);
1045 i
= substr(out
.s
, cp
);
1051 * Mark the named message by setting its mark bit.
1054 mark(int mesg
, int f
)
1060 if (i
< 1 || i
> msgCount
)
1061 panic(catgets(catd
, CATSET
, 129, "Bad message number to mark"));
1062 if (mb
.mb_threaded
== 1 && threadflag
) {
1063 if ((message
[i
-1].m_flag
& MHIDDEN
) == 0) {
1064 if (f
== MDELETED
||
1065 (message
[i
-1].m_flag
&MDELETED
) == 0)
1066 message
[i
-1].m_flag
|= MMARK
;
1068 if (message
[i
-1].m_child
) {
1069 mp
= message
[i
-1].m_child
;
1070 mark(mp
-message
+1, f
);
1071 for (mp
= mp
->m_younger
; mp
; mp
= mp
->m_younger
)
1072 mark(mp
-message
+1, f
);
1075 message
[i
-1].m_flag
|= MMARK
;
1079 * Unmark the named message.
1087 if (i
< 1 || i
> msgCount
)
1088 panic(catgets(catd
, CATSET
, 130,
1089 "Bad message number to unmark"));
1090 message
[i
-1].m_flag
&= ~MMARK
;
1094 * Return the message number corresponding to the passed meta character.
1097 metamess(int meta
, int f
)
1106 * First 'good' message left.
1108 mp
= mb
.mb_threaded
? threadroot
: &message
[0];
1109 while (mp
< &message
[msgCount
]) {
1110 if (!(mp
->m_flag
& (MHIDDEN
|MKILL
)) &&
1111 (mp
->m_flag
& MDELETED
) == f
)
1112 return(mp
- &message
[0] + 1);
1113 if (mb
.mb_threaded
) {
1114 mp
= next_in_thread(mp
);
1121 printf(catgets(catd
, CATSET
, 131,
1122 "No applicable messages\n"));
1127 * Last 'good message left.
1129 mp
= mb
.mb_threaded
? this_in_thread(threadroot
, -1) :
1130 &message
[msgCount
-1];
1131 while (mp
>= &message
[0]) {
1132 if (!(mp
->m_flag
& (MHIDDEN
|MKILL
)) &&
1133 (mp
->m_flag
& MDELETED
) == f
)
1134 return(mp
- &message
[0] + 1);
1135 if (mb
.mb_threaded
) {
1136 mp
= prev_in_thread(mp
);
1143 printf(catgets(catd
, CATSET
, 132,
1144 "No applicable messages\n"));
1151 m
= dot
- &message
[0] + 1;
1152 if (dot
->m_flag
& MHIDDEN
|| (dot
->m_flag
& MDELETED
) != f
) {
1153 printf(catgets(catd
, CATSET
, 133,
1154 "%d: Inappropriate message\n"), m
);
1161 * Previously current message.
1163 if (prevdot
== NULL
) {
1164 printf(catgets(catd
, CATSET
, 228,
1165 "No previously current message\n"));
1168 m
= prevdot
- &message
[0] + 1;
1169 if (prevdot
->m_flag
&MHIDDEN
|| (prevdot
->m_flag
&MDELETED
)!=f
) {
1170 printf(catgets(catd
, CATSET
, 133,
1171 "%d: Inappropriate message\n"), m
);
1177 printf(catgets(catd
, CATSET
, 134,
1178 "Unknown metachar (%c)\n"), c
);