1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Message list handling.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2014 Steffen "Daode" Nurpmeso <sdaoden@users.sf.net>.
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
40 #ifndef HAVE_AMALGAMATION
49 static char **add_to_namelist(char ***namelist
, size_t *nmlsize
,
50 char **np
, char *string
);
51 static int markall(char *buf
, int f
);
52 static int evalcol(int col
);
53 static int check(int mesg
, int f
);
54 static int scan(char **sp
);
55 static void regret(int token
);
56 static void scaninit(void);
57 static int matchsender(char *str
, int mesg
, int allnet
);
58 static int matchmid(char *id
, enum idfield idfield
, int mesg
);
59 static int matchsubj(char *str
, int mesg
);
60 static void unmark(int mesg
);
61 static int metamess(int meta
, int f
);
63 static size_t STRINGLEN
;
65 static int lexnumber
; /* Number of TNUMBER from scan() */
66 static char *lexstring
; /* String from TSTRING, scan() */
67 static int regretp
; /* Pointer to TOS of regret tokens */
68 static int regretstack
[REGDEP
]; /* Stack of regretted tokens */
69 static char *string_stack
[REGDEP
]; /* Stack of regretted strings */
70 static int numberstack
[REGDEP
]; /* Stack of regretted numbers */
71 static int threadflag
; /* mark entire threads */
74 * Convert the user string of message numbers and
75 * store the numbers into vector.
77 * Returns the count of messages picked up or -1 on error.
80 getmsglist(char *buf
, int *vector
, int flags
)
87 msglist_is_single
= FAL0
;
94 msglist_is_single
= TRU1
;
95 if (markall(buf
, flags
) < 0)
100 for (mp
= &message
[0]; mp
< &message
[msgCount
]; mp
++)
101 if (mp
->m_flag
& MMARK
) {
102 if ((mp
->m_flag
& MNEWEST
) == 0)
103 unmark(mp
- &message
[0] + 1);
111 if (mb
.mb_threaded
== 0) {
112 for (mp
= &message
[0]; mp
< &message
[msgCount
]; mp
++)
113 if (mp
->m_flag
& MMARK
)
114 *ip
++ = mp
- &message
[0] + 1;
116 for (mp
= threadroot
; mp
; mp
= next_in_thread(mp
))
117 if (mp
->m_flag
& MMARK
)
118 *ip
++ = mp
- &message
[0] + 1;
121 if ((size_t)(ip
- vector
) != 1)
122 msglist_is_single
= FAL0
;
127 * Mark all messages that the user wanted from the command
128 * line in the message structure. Return 0 on success, -1
133 * Bit values for colon modifiers.
136 #define CMNEW 01 /* New messages */
137 #define CMOLD 02 /* Old messages */
138 #define CMUNREAD 04 /* Unread messages */
139 #define CMDELETED 010 /* Deleted messages */
140 #define CMREAD 020 /* Read messages */
141 #define CMFLAG 040 /* Flagged messages */
142 #define CMANSWER 0100 /* Answered messages */
143 #define CMDRAFT 0200 /* Draft messages */
144 #define CMSPAM 0400 /* Spam messages */
147 * The following table describes the letters which can follow
148 * the colon and gives the corresponding modifier bit.
151 static struct coltab
{
152 char co_char
; /* What to find past : */
153 int co_bit
; /* Associated modifier bit */
154 int co_mask
; /* m_status bits to mask */
155 int co_equal
; /* ... must equal this */
157 { 'n', CMNEW
, MNEW
, MNEW
},
158 { 'o', CMOLD
, MNEW
, 0 },
159 { 'u', CMUNREAD
, MREAD
, 0 },
160 { 'd', CMDELETED
, MDELETED
, MDELETED
},
161 { 'r', CMREAD
, MREAD
, MREAD
},
162 { 'f', CMFLAG
, MFLAGGED
, MFLAGGED
},
163 { 'a', CMANSWER
, MANSWERED
, MANSWERED
},
164 { 't', CMDRAFT
, MDRAFTED
, MDRAFTED
},
165 { 's', CMSPAM
, MSPAM
, MSPAM
},
169 static int lastcolmod
;
172 add_to_namelist(char ***namelist
, size_t *nmlsize
, char **np
, char *string
)
176 if ((idx
= np
- *namelist
) >= *nmlsize
) {
177 *namelist
= srealloc(*namelist
, (*nmlsize
+= 8) * sizeof *np
);
178 np
= &(*namelist
)[idx
];
185 markall(char *buf
, int f
)
187 #define markall_ret(i) do { retval = i; goto out; } while (0);
190 struct message
*mp
, *mx
;
191 char **namelist
, *bufp
, *id
= NULL
, *cp
;
192 int tok
, beg
, mc
, star
, other
, valdot
, colmod
, colresult
, topen
, tback
;
194 enum idfield idfield
= ID_REFERENCES
;
199 lexstring
= ac_alloc(STRINGLEN
= 2 * strlen(buf
) + 1);
200 valdot
= dot
- &message
[0] + 1;
202 for (i
= 1; i
<= msgCount
; i
++) {
203 message
[i
-1].m_flag
&= ~MOLDMARK
;
204 if (message
[i
-1].m_flag
& MMARK
)
205 message
[i
-1].m_flag
|= MOLDMARK
;
210 namelist
= smalloc((nmlsize
= 8) * sizeof *namelist
);
223 while (tok
!= TEOL
) {
228 printf(tr(112, "No numbers mixed with *\n"));
231 list_saw_numbers
= TRU1
;
235 if (check(lexnumber
, f
))
238 while (mb
.mb_threaded
? 1 : i
<= lexnumber
) {
239 if (!(message
[i
-1].m_flag
&MHIDDEN
) &&
241 (message
[i
-1].m_flag
&
244 if (mb
.mb_threaded
) {
247 mx
= next_in_thread(&message
[i
-1]);
269 msglist_is_single
= FAL0
;
272 "Non-numeric second argument\n"));
277 if (mb
.mb_threaded
) {
278 mx
= next_in_thread(&message
[i
-1]);
279 i
= mx
? mx
-message
+1 : msgCount
+1;
284 "Referencing beyond EOF\n"));
287 } while (message
[i
-1].m_flag
== MHIDDEN
||
288 (message
[i
-1].m_flag
& MDELETED
) !=
294 msglist_is_single
= FAL0
;
298 if (mb
.mb_threaded
) {
301 i
= mx
? mx
-message
+1 : 0;
306 "Referencing before 1\n"));
309 } while ((message
[i
-1].m_flag
& MHIDDEN
) ||
310 (message
[i
-1].m_flag
& MDELETED
)
317 msglist_is_single
= FAL0
;
320 "Non-numeric second argument\n"));
324 if (lexstring
[0] == ':') {
325 colresult
= evalcol(lexstring
[1]);
326 if (colresult
== 0) {
328 "Unknown colon modifier \"%s\"\n"),
335 np
= add_to_namelist(&namelist
, &nmlsize
,
336 np
, savestr(lexstring
));
340 msglist_is_single
= FAL0
;
341 if (imap_search(lexstring
, f
) == STOP
)
350 msglist_is_single
= FAL0
;
351 lexnumber
= metamess(lexstring
[0], f
);
357 msglist_is_single
= FAL0
;
359 for (i
= 1; i
<= msgCount
; i
++) {
360 if ((message
[i
-1].m_flag
& MHIDDEN
) ||
361 (message
[i
-1].m_flag
&MDELETED
)
364 if (message
[i
-1].m_flag
&MOLDMARK
)
370 msglist_is_single
= FAL0
;
373 "Can't mix \"*\" with anything\n"));
380 msglist_is_single
= FAL0
;
382 if (mb
.mb_type
== MB_IMAP
&& gotheaders
++ == 0)
383 imap_getheaders(1, msgCount
);
385 if (id
== NULL
&& (cp
= hfield1("in-reply-to", dot
))
388 idfield
= ID_IN_REPLY_TO
;
390 if (id
== NULL
&& (cp
= hfield1("references", dot
))
393 if ((enp
= extract(cp
, GREF
)) != NULL
) {
394 while (enp
->n_flink
!= NULL
)
396 id
= savestr(enp
->n_name
);
397 idfield
= ID_REFERENCES
;
402 "Cannot determine parent Message-ID of the current message\n"));
408 list_saw_numbers
= TRU1
;
409 msglist_is_single
= FAL0
;
416 np
= add_to_namelist(&namelist
, &nmlsize
, np
, NULL
);
420 for (i
= 0; i
< msgCount
; i
++) {
421 if (!(message
[i
].m_flag
& MHIDDEN
) &&
422 (message
[i
].m_flag
& MDELETED
) ==
431 "No applicable messages.\n"));
437 if ((topen
|| tback
) && mc
== 0) {
438 for (i
= 0; i
< msgCount
; i
++)
439 if (message
[i
].m_flag
& MMARK
)
444 "No previously marked messages.\n" :
445 "No messages satisfy (criteria).\n");
451 * If no numbers were given, mark all of the messages,
452 * so that we can unmark any whose sender was not selected
453 * if any user names were given.
456 if ((np
> namelist
|| colmod
!= 0 || id
) && mc
== 0)
457 for (i
= 1; i
<= msgCount
; i
++) {
458 if (!(message
[i
-1].m_flag
& MHIDDEN
) &&
459 (message
[i
-1].m_flag
& MDELETED
) ==
465 * If any names were given, go through and eliminate any
466 * messages whose senders were not requested.
469 if (np
> namelist
|| id
) {
470 bool_t allnet
= ok_blook(allnet
);
473 if (mb
.mb_type
== MB_IMAP
&& gotheaders
++ == 0)
474 imap_getheaders(1, msgCount
);
477 for (i
= 1; i
<= msgCount
; i
++) {
480 for (nq
= &namelist
[0]; *nq
!= NULL
; nq
++) {
482 if (matchsubj(*nq
, i
)) {
488 if (matchsender(*nq
, i
,
496 if (mc
== 0 && id
&& matchmid(id
, idfield
, i
))
505 * Make sure we got some decent messages.
509 for (i
= 1; i
<= msgCount
; i
++)
510 if (message
[i
-1].m_flag
& MMARK
) {
515 if (!inhook
&& np
> namelist
) {
517 "No applicable messages from {%s"),
519 for (nq
= &namelist
[1]; *nq
!= NULL
; nq
++)
520 printf(tr(121, ", %s"), *nq
);
521 printf(tr(122, "}\n"));
523 printf(tr(227, "Parent message not found\n"));
530 * If any colon modifiers were given, go through and
531 * unmark any messages which do not satisfy the modifiers.
535 for (i
= 1; i
<= msgCount
; i
++) {
539 mp
= &message
[i
- 1];
540 for (colp
= &coltab
[0]; colp
->co_char
; colp
++)
541 if ((colp
->co_bit
& colmod
) &&
542 ((mp
->m_flag
& colp
->co_mask
)
543 == (unsigned)colp
->co_equal
))
548 for (mp
= &message
[0]; mp
< &message
[msgCount
]; mp
++)
549 if (mp
->m_flag
& MMARK
)
551 if (mp
>= &message
[msgCount
]) {
555 printf(tr(123, "No messages satisfy"));
556 for (colp
= &coltab
[0]; colp
->co_char
; colp
++)
557 if (colp
->co_bit
& colmod
)
558 printf(" :%c", colp
->co_char
);
573 * Turn the character after a colon modifier into a bit
583 for (colp
= &coltab
[0]; colp
->co_char
; colp
++)
584 if (colp
->co_char
== col
)
585 return(colp
->co_bit
);
590 * Check the passed message number for legality and proper flags.
591 * If f is MDELETED, then either kind will do. Otherwise, the message
592 * has to be undeleted.
595 check(int mesg
, int f
)
599 if (mesg
< 1 || mesg
> msgCount
) {
600 printf(tr(124, "%d: Invalid message number\n"), mesg
);
603 mp
= &message
[mesg
-1];
604 if (mp
->m_flag
& MHIDDEN
|| (f
!= MDELETED
&&
605 (mp
->m_flag
& MDELETED
) != 0)) {
606 printf(tr(125, "%d: Inappropriate message\n"), mesg
);
613 * Scan out the list of string arguments, shell style
617 getrawlist(const char *line
, size_t linesize
, char **argv
, int argc
,
620 char c
, *cp2
, quotec
;
625 list_saw_numbers
= FAL0
;
629 linebuf
= ac_alloc(linesize
+ 1);
631 for (; blankchar(*cp
& 0377); cp
++);
634 if (argn
>= argc
- 1) {
636 "Too many elements in the list; excess discarded.\n"));
641 while ((c
= *cp
) != '\0') {
643 if (quotec
!= '\0') {
648 } else if (c
== '\\')
655 case '0': case '1': case '2': case '3':
656 case '4': case '5': case '6': case '7':
658 if (*cp >= '0' && *cp <= '7')
659 c = c * 8 + *cp++ - '0';
660 if (*cp >= '0' && *cp <= '7')
661 c = c * 8 + *cp++ - '0';
684 if (cp
[-1]!=quotec
|| echolist
)
688 /*else if (c == '^') {
692 /\* null doesn't show up anyway *\/
693 else if ((c >= 'A' && c <= '_') ||
694 (c >= 'a' && c <= 'z'))
702 } else if (c
== '"' || c
== '\'') {
706 } else if (c
== '\\' && !echolist
) {
711 } else if (blankchar(c
& 0377))
717 argv
[argn
++] = savestr(linebuf
);
725 * scan out a single lexical item and return its token number,
726 * updating the string pointer passed **p. Also, store the value
727 * of the number or string scanned in lexnumber or lexstring as
728 * appropriate. In any event, store the scanned `thing' in lexstring.
758 strncpy(lexstring
, string_stack
[regretp
], STRINGLEN
);
759 lexstring
[STRINGLEN
-1]='\0';
760 lexnumber
= numberstack
[regretp
];
761 return(regretstack
[regretp
--]);
768 * strip away leading white space.
775 * If no characters remain, we are at end of line,
785 * Select members of a message thread.
789 if (*cp
== '\0' || spacechar(*cp
&0377)) {
799 * If the leading character is a digit, scan
800 * the number and convert it on the fly.
801 * Return TNUMBER when done.
806 while (digitchar(c
)) {
807 lexnumber
= lexnumber
*10 + c
- '0';
817 * An IMAP SEARCH list. Note that TOPEN has always been included
818 * in singles[] in Mail and mailx. Thus although there is no formal
819 * definition for (LIST) lists, they do not collide with historical
820 * practice because a subject string (LIST) could never been matched
829 if ((c
= *cp
++&0377) == '\0') {
830 mtop
: fprintf(stderr
, "Missing \")\".\n");
833 if (inquote
&& c
== '\\') {
846 else if (spacechar(c
)) {
848 * Replace unquoted whitespace by single
849 * space characters, to make the string
850 * IMAP SEARCH conformant.
857 } while (c
!= ')' || level
> 0);
864 * Check for single character tokens; return such
868 for (lp
= &singles
[0]; lp
->l_char
!= 0; lp
++)
869 if (c
== lp
->l_char
) {
877 * We've got a string! Copy all the characters
878 * of the string into lexstring, until we see
879 * a null, space, or tab.
880 * If the lead character is a " or ', save it
881 * and scan until you get another.
885 if (c
== '\'' || c
== '"') {
890 if (quotec
== 0 && c
== '\\' && *cp
)
896 if (quotec
== 0 && blankchar(c
))
898 if ((size_t)(cp2
- lexstring
) < (size_t)STRINGLEN
- 1)
902 if (quotec
&& c
== 0) {
903 fprintf(stderr
, tr(127, "Missing %c\n"), quotec
);
912 * Unscan the named token by pushing it onto the regret stack.
917 if (++regretp
>= REGDEP
)
918 panic(tr(128, "Too many regrets"));
919 regretstack
[regretp
] = token
;
920 lexstring
[STRINGLEN
-1] = '\0';
921 string_stack
[regretp
] = savestr(lexstring
);
922 numberstack
[regretp
] = lexnumber
;
926 * Reset all the scanner global variables.
936 * Find the first message whose flags & m == f and return
937 * its message number.
948 for (mp
= dot
; mb
.mb_threaded
? mp
!= NULL
: mp
< &message
[msgCount
];
949 mb
.mb_threaded
? mp
= next_in_thread(mp
) : mp
++) {
950 if (!(mp
->m_flag
& MHIDDEN
) && (mp
->m_flag
& m
) == (unsigned)f
)
951 return mp
- message
+ 1;
953 if (dot
> &message
[0]) {
954 for (mp
= dot
-1; mb
.mb_threaded
?
955 mp
!= NULL
: mp
>= &message
[0];
957 mp
= prev_in_thread(mp
) : mp
--) {
958 if (! (mp
->m_flag
& MHIDDEN
) &&
959 (mp
->m_flag
& m
) == (unsigned)f
)
960 return mp
- message
+ 1;
967 * See if the passed name sent the passed message number. Return true
971 matchsender(char *str
, int mesg
, int allnet
)
974 char *cp
= nameof(&message
[mesg
- 1], 0);
977 if ((*cp
== '@' || *cp
== '\0') &&
978 (*str
== '@' || *str
== '\0'))
982 } while (cp
++, *str
++ != '\0');
985 return !strcmp(str
, (ok_blook(showname
) ? realname
: skin
)
986 (name1(&message
[mesg
- 1], 0)));
990 matchmid(char *id
, enum idfield idfield
, int mesg
)
995 if ((cp
= hfield1("message-id", &message
[mesg
- 1])) != NULL
) {
998 return msgidcmp(id
, cp
) == 0;
1000 if ((np
= extract(id
, GREF
)) != NULL
)
1002 if (msgidcmp(np
->n_name
, cp
) == 0)
1004 } while ((np
= np
->n_flink
) != NULL
);
1012 * See if the given string matches inside the subject field of the
1013 * given message. For the purpose of the scan, we ignore case differences.
1014 * If it does, return true. The string search argument is assumed to
1015 * have the form "/search-string." If it is of the form "/," we use the
1016 * previous search string.
1019 static char lastscan
[128];
1022 matchsubj(char *str
, int mesg
)
1030 if (strlen(str
) == 0) {
1033 strncpy(lastscan
, str
, sizeof lastscan
);
1034 lastscan
[sizeof lastscan
- 1]='\0';
1036 mp
= &message
[mesg
-1];
1039 * Now look, ignoring case, for the word in the string.
1042 if (ok_blook(searchheaders
) && (cp
= strchr(str
, ':'))) {
1044 cp2
= hfieldX(str
, mp
);
1048 cp2
= hfield1("subject", mp
);
1054 mime_fromhdr(&in
, &out
, TD_ICONV
);
1055 i
= substr(out
.s
, cp
);
1061 * Mark the named message by setting its mark bit.
1064 mark(int mesg
, int f
)
1070 if (i
< 1 || i
> msgCount
)
1071 panic(tr(129, "Bad message number to mark"));
1072 if (mb
.mb_threaded
== 1 && threadflag
) {
1073 if ((message
[i
-1].m_flag
& MHIDDEN
) == 0) {
1074 if (f
== MDELETED
||
1075 (message
[i
-1].m_flag
&MDELETED
) == 0)
1076 message
[i
-1].m_flag
|= MMARK
;
1078 if (message
[i
-1].m_child
) {
1079 mp
= message
[i
-1].m_child
;
1080 mark(mp
-message
+1, f
);
1081 for (mp
= mp
->m_younger
; mp
; mp
= mp
->m_younger
)
1082 mark(mp
-message
+1, f
);
1085 message
[i
-1].m_flag
|= MMARK
;
1089 * Unmark the named message.
1097 if (i
< 1 || i
> (ui32_t
)msgCount
)
1098 panic(tr(130, "Bad message number to unmark"));
1099 message
[i
-1].m_flag
&= ~MMARK
;
1103 * Return the message number corresponding to the passed meta character.
1106 metamess(int meta
, int f
)
1115 * First 'good' message left.
1117 mp
= mb
.mb_threaded
? threadroot
: &message
[0];
1118 while (mp
< &message
[msgCount
]) {
1119 if (!(mp
->m_flag
& MHIDDEN
) &&
1120 (mp
->m_flag
& MDELETED
) == (unsigned)f
)
1121 return(mp
- &message
[0] + 1);
1122 if (mb
.mb_threaded
) {
1123 mp
= next_in_thread(mp
);
1130 printf(tr(131, "No applicable messages\n"));
1135 * Last 'good message left.
1137 mp
= mb
.mb_threaded
? this_in_thread(threadroot
, -1) :
1138 &message
[msgCount
-1];
1139 while (mp
>= &message
[0]) {
1140 if (!(mp
->m_flag
& MHIDDEN
) &&
1141 (mp
->m_flag
& MDELETED
) == (unsigned)f
)
1142 return(mp
- &message
[0] + 1);
1143 if (mb
.mb_threaded
) {
1144 mp
= prev_in_thread(mp
);
1151 printf(tr(132, "No applicable messages\n"));
1158 m
= dot
- &message
[0] + 1;
1159 if ((dot
->m_flag
& MHIDDEN
) ||
1160 (dot
->m_flag
& MDELETED
) != (unsigned)f
) {
1161 printf(tr(133, "%d: Inappropriate message\n"), m
);
1168 * Previously current message.
1170 if (prevdot
== NULL
) {
1171 printf(tr(228, "No previously current message\n"));
1174 m
= prevdot
- &message
[0] + 1;
1175 if ((prevdot
->m_flag
& MHIDDEN
) ||
1176 (prevdot
->m_flag
& MDELETED
) != (unsigned)f
) {
1177 printf(tr(133, "%d: Inappropriate message\n"), m
);
1183 printf(tr(134, "Unknown metachar (%c)\n"), c
);