1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Message, message array, getmsglist(), and related operations.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2016 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
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. Neither the name of the University nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
36 #define n_FILE message
38 #ifndef HAVE_AMALGAMATION
42 /* Token values returned by the scanner used for argument lists.
43 * Also, sizes of scanner-related things */
45 a_MESSAGE_T_EOL
, /* End of the command line */
46 a_MESSAGE_T_NUMBER
, /* Message number */
47 a_MESSAGE_T_MINUS
, /* - */
48 a_MESSAGE_T_STRING
, /* A string (possibly containing -) */
49 a_MESSAGE_T_DOT
, /* . */
50 a_MESSAGE_T_UP
, /* ^ */
51 a_MESSAGE_T_DOLLAR
, /* $ */
52 a_MESSAGE_T_ASTER
, /* * */
53 a_MESSAGE_T_OPEN
, /* ( */
54 a_MESSAGE_T_CLOSE
, /* ) */
55 a_MESSAGE_T_PLUS
, /* + */
56 a_MESSAGE_T_COMMA
, /* , */
57 a_MESSAGE_T_SEMI
, /* ; */
58 a_MESSAGE_T_BACK
, /* ` */
59 a_MESSAGE_T_ERROR
/* Lexical error */
62 enum a_message_idfield
{
63 a_MESSAGE_ID_REFERENCES
,
64 a_MESSAGE_ID_IN_REPLY_TO
68 a_MESSAGE_S_NEW
= 1<<0,
69 a_MESSAGE_S_OLD
= 1<<1,
70 a_MESSAGE_S_UNREAD
= 1<<2,
71 a_MESSAGE_S_DELETED
=1<<3,
72 a_MESSAGE_S_READ
= 1<<4,
73 a_MESSAGE_S_FLAG
= 1<<5,
74 a_MESSAGE_S_ANSWERED
= 1<<6,
75 a_MESSAGE_S_DRAFT
= 1<<7,
76 a_MESSAGE_S_SPAM
= 1<<8,
77 a_MESSAGE_S_SPAMUNSURE
= 1<<9
80 struct a_message_coltab
{
81 char mco_char
; /* What to find past : */
83 int mco_bit
; /* Associated modifier bit */
84 int mco_mask
; /* m_status bits to mask */
85 int mco_equal
; /* ... must equal this */
90 enum a_message_token ml_token
;
93 static struct a_message_coltab
const a_message_coltabs
[] = {
94 {'n', {0,}, a_MESSAGE_S_NEW
, MNEW
, MNEW
},
95 {'o', {0,}, a_MESSAGE_S_OLD
, MNEW
, 0},
96 {'u', {0,}, a_MESSAGE_S_UNREAD
, MREAD
, 0},
97 {'d', {0,}, a_MESSAGE_S_DELETED
, MDELETED
, MDELETED
},
98 {'r', {0,}, a_MESSAGE_S_READ
, MREAD
, MREAD
},
99 {'f', {0,}, a_MESSAGE_S_FLAG
, MFLAGGED
, MFLAGGED
},
100 {'a', {0,}, a_MESSAGE_S_ANSWERED
, MANSWERED
, MANSWERED
},
101 {'t', {0,}, a_MESSAGE_S_DRAFT
, MDRAFTED
, MDRAFTED
},
102 {'s', {0,}, a_MESSAGE_S_SPAM
, MSPAM
, MSPAM
},
103 {'S', {0,}, a_MESSAGE_S_SPAMUNSURE
, MSPAMUNSURE
, MSPAMUNSURE
},
104 {'\0', {0,}, 0, 0, 0}
107 static struct a_message_lex
const a_message_singles
[] = {
108 {'$', a_MESSAGE_T_DOLLAR
},
109 {'.', a_MESSAGE_T_DOT
},
110 {'^', a_MESSAGE_T_UP
},
111 {'*', a_MESSAGE_T_ASTER
},
112 {'-', a_MESSAGE_T_MINUS
},
113 {'+', a_MESSAGE_T_PLUS
},
114 {'(', a_MESSAGE_T_OPEN
},
115 {')', a_MESSAGE_T_CLOSE
},
116 {',', a_MESSAGE_T_COMMA
},
117 {';', a_MESSAGE_T_SEMI
},
118 {'`', a_MESSAGE_T_BACK
},
122 /* Slots in ::message */
123 static size_t a_message_mem_space
;
125 /* Mark entire threads */
126 static bool_t a_message_threadflag
;
128 /* :d on its way HACK TODO */
129 static bool_t a_message_list_saw_d
, a_message_list_last_saw_d
;
131 /* String from a_MESSAGE_T_STRING, scan() */
132 static struct str a_message_lexstr
;
133 /* Number of a_MESSAGE_T_NUMBER from scan() */
134 static int a_message_lexno
;
136 /* Lazy load message header fields */
137 static enum okay
a_message_get_header(struct message
*mp
);
139 /* Append, taking care of resizes TODO vector */
140 static char ** a_message_add_to_namelist(char ***namelist
, size_t *nmlsize
,
141 char **np
, char *string
);
143 /* Mark all messages that the user wanted from the command line in the message
144 * structure. Return 0 on success, -1 on error */
145 static int a_message_markall(char *buf
, int f
);
147 /* Turn the character after a colon modifier into a bit value */
148 static int a_message_evalcol(int col
);
150 /* Check the passed message number for legality and proper flags. Unless f is
151 * MDELETED the message has to be undeleted */
152 static bool_t
a_message_check(int mno
, int f
);
154 /* Scan out a single lexical item and return its token number, updating the
155 * string pointer passed **sp. Also, store the value of the number or string
156 * scanned in a_message_lexno or a_message_lexstr as appropriate.
157 * In any event, store the scanned "thing" in a_message_lexstr.
158 * Returns the token as a negative number when we also saw & to mark a thread */
159 static int a_message_scan(char **sp
);
161 /* See if the passed name sent the passed message */
162 static bool_t
a_message_match_sender(struct message
*mp
, char const *str
,
165 /* Check whether the given message-id or references match */
166 static bool_t
a_message_match_mid(struct message
*mp
, char const *id
,
167 enum a_message_idfield idfield
);
169 /* See if the given string matches.
170 * For the purpose of the scan, we ignore case differences.
171 * This is the engine behind the "/" search */
172 static bool_t
a_message_match_dash(struct message
*mp
, char const *str
);
174 /* See if the given search expression matches.
175 * For the purpose of the scan, we ignore case differences.
176 * This is the engine behind the "@[..]@" search */
177 static bool_t
a_message_match_at(struct message
*mp
, struct search_expr
*sep
);
179 /* Unmark the named message */
180 static void a_message_unmark(int mesg
);
182 /* Return the message number corresponding to the passed meta character */
183 static int a_message_metamess(int meta
, int f
);
185 /* Helper for mark(): self valid, threading enabled */
186 static void a_message__threadmark(struct message
*self
, int f
);
189 a_message_get_header(struct message
*mp
){
201 rv
= pop3_header(mp
);
214 a_message_add_to_namelist(char ***namelist
, size_t *nmlsize
, char **np
,
219 if((idx
= PTR2SIZE(np
- *namelist
)) >= *nmlsize
){
220 *namelist
= srealloc(*namelist
, (*nmlsize
+= 8) * sizeof *np
);
221 np
= &(*namelist
)[idx
];
229 a_message_markall(char *buf
, int f
){
230 struct message
*mp
, *mx
;
231 enum a_message_idfield idfield
;
234 char **np
, **nq
, **namelist
, *bufp
, *cp
;
235 int i
, valdot
, beg
, colmod
, tok
, colresult
;
239 a_ALLOC
= 1u<<1, /* Have allocated something */
241 a_ANY
= 1u<<3, /* Have marked just ANY */
242 a_RANGE_OPEN
= 1u<<8, /* Seen dash, await close */
244 a_TOPEN
= 1u<<17, /* ( used (and didn't match) */
245 a_TBACK
= 1u<<18, /* ` used (and didn't match) */
249 n_LCTA((ui32_t
)a_ALLNET
== (ui32_t
)TRU1
,
250 "Constant is converted to bool_t via AND, thus");
252 /* Update message array: clear MMARK but remember its former state for `.
253 * An empty selector input is identical to * asterisk */
254 for(i
= 0; i
< msgCount
; ++i
){
257 mf
= (mp
= &message
[i
])->m_flag
;
266 /* Strip all leading WS from user buffer */
267 while(blankspacechar(*buf
))
269 /* If there is no input buffer, we are done! */
276 UNINIT(idfield
, a_MESSAGE_ID_REFERENCES
);
277 a_message_threadflag
= FAL0
;
278 a_message_lexstr
.s
= ac_alloc(a_message_lexstr
.l
= 2 * strlen(buf
) +1);
279 np
= namelist
= smalloc((nmlsize
= 8) * sizeof *namelist
); /* TODO vector */
281 valdot
= (int)PTR2SIZE(dot
- message
+ 1);
286 while((tok
= a_message_scan(&bufp
)) != a_MESSAGE_T_EOL
){
287 if((a_message_threadflag
= (tok
< 0)))
291 case a_MESSAGE_T_NUMBER
:
292 pstate
|= PS_MSGLIST_GABBY
;
294 if(flags
& a_RANGE_OPEN
){
297 flags
^= a_RANGE_OPEN
;
299 if(!a_message_check(a_message_lexno
, f
))
301 if(beg
< a_message_lexno
){
303 beg
= 1; /* TODO does not work: (i < a_message_lexno)
304 * TODO we need to detect whether both ends of a range
305 * TODO belong to the same thread first, then iterate
306 * TODO over the subset in between those points */
309 a_message_lexno
= beg
;
312 /* Problem: until the TODO above can be worked and we simply get an
313 * iterator object for the thread (-range), we need to walk
314 * a threaded list two times */
317 while(mb
.mb_threaded
? 1 : i
<= a_message_lexno
){
318 mp
= &message
[i
- 1];
319 if((!mb
.mb_threaded
|| i_base
< 0) &&
320 !(mp
->m_flag
& MHIDDEN
) &&
321 (f
== MDELETED
|| !(mp
->m_flag
& MDELETED
))){
326 if(i
== a_message_lexno
)
328 mx
= beg
? next_in_thread(mp
) : prev_in_thread(mp
);
330 id
= N_("Range crosses multiple threads\n");
333 i
= (int)PTR2SIZE(mx
- message
+ 1);
345 if(!a_message_check(a_message_lexno
, f
))
347 /* Inclusive range? */
350 beg
= a_message_lexno
;
351 flags
|= a_RANGE_OPEN
;
353 mark(a_message_lexno
, f
);
358 case a_MESSAGE_T_PLUS
:
359 pstate
&= ~PS_MSGLIST_DIRECT
;
360 pstate
|= PS_MSGLIST_GABBY
;
364 mx
= next_in_thread(&message
[i
- 1]);
365 i
= mx
? (int)PTR2SIZE(mx
- message
+ 1) : msgCount
+ 1;
369 id
= N_("Referencing beyond last message\n");
372 }while(message
[i
- 1].m_flag
== MHIDDEN
||
373 (message
[i
- 1].m_flag
& MDELETED
) != (unsigned)f
);
376 case a_MESSAGE_T_MINUS
:
377 pstate
&= ~PS_MSGLIST_DIRECT
;
378 pstate
|= PS_MSGLIST_GABBY
;
382 mx
= prev_in_thread(&message
[i
- 1]);
383 i
= mx
? (int)PTR2SIZE(mx
- message
+ 1) : 0;
387 id
= N_("Referencing before first message\n");
390 }while(message
[i
- 1].m_flag
== MHIDDEN
||
391 (message
[i
- 1].m_flag
& MDELETED
) != (unsigned)f
);
394 case a_MESSAGE_T_STRING
:
395 pstate
&= ~PS_MSGLIST_DIRECT
;
396 if(flags
& a_RANGE_OPEN
)
399 /* This may be a colon modifier */
400 if((cp
= a_message_lexstr
.s
)[0] != ':')
401 np
= a_message_add_to_namelist(&namelist
, &nmlsize
, np
,
402 savestr(a_message_lexstr
.s
));
404 while(*++cp
!= '\0'){
405 colresult
= a_message_evalcol(*cp
);
407 n_err(_("Unknown colon modifier: %s\n"), a_message_lexstr
.s
);
410 if(colresult
== a_MESSAGE_S_DELETED
){
411 a_message_list_saw_d
= TRU1
;
418 case a_MESSAGE_T_OPEN
:
419 pstate
&= ~PS_MSGLIST_DIRECT
;
420 if(flags
& a_RANGE_OPEN
)
424 #ifdef HAVE_IMAP_SEARCH
428 if((ires
= imap_search(a_message_lexstr
.s
, f
)) >= 0){
435 n_err(_("Optional selector is not available: %s\n"),
439 case a_MESSAGE_T_DOLLAR
:
441 case a_MESSAGE_T_SEMI
:
442 pstate
|= PS_MSGLIST_GABBY
;
444 case a_MESSAGE_T_DOT
: /* Don't set _GABBY for dot, to _allow_ history.. */
445 pstate
&= ~PS_MSGLIST_DIRECT
;
446 a_message_lexno
= a_message_metamess(a_message_lexstr
.s
[0], f
);
447 if(a_message_lexno
== -1)
450 case a_MESSAGE_T_BACK
:
451 pstate
&= ~PS_MSGLIST_DIRECT
;
452 if(flags
& a_RANGE_OPEN
)
456 for(i
= 0; i
< msgCount
; ++i
){
457 if((mp
= &message
[i
])->m_flag
& MHIDDEN
)
459 if((mp
->m_flag
& MDELETED
) != (unsigned)f
){
460 if(!a_message_list_last_saw_d
)
462 a_message_list_saw_d
= TRU1
;
464 if(mp
->m_flag
& MOLDMARK
){
471 case a_MESSAGE_T_ASTER
:
472 pstate
&= ~PS_MSGLIST_DIRECT
;
473 if(flags
& a_RANGE_OPEN
)
477 case a_MESSAGE_T_COMMA
:
478 pstate
&= ~PS_MSGLIST_DIRECT
;
479 pstate
|= PS_MSGLIST_GABBY
;
480 if(flags
& a_RANGE_OPEN
)
484 if((cp
= hfield1("in-reply-to", dot
)) != NULL
)
485 idfield
= a_MESSAGE_ID_IN_REPLY_TO
;
486 else if((cp
= hfield1("references", dot
)) != NULL
){
489 if((enp
= extract(cp
, GREF
)) != NULL
){
490 while(enp
->n_flink
!= NULL
)
493 idfield
= a_MESSAGE_ID_REFERENCES
;
501 id
= N_("Message-ID of parent of \"dot\" is indeterminable\n");
504 }else if(!(pstate
& PS_HOOK
) && (options
& OPT_D_V
))
505 n_err(_("Ignoring redundant specification of , selector\n"));
507 case a_MESSAGE_T_ERROR
:
508 pstate
&= ~PS_MSGLIST_DIRECT
;
509 pstate
|= PS_MSGLIST_GABBY
;
513 /* Explicitly disallow invalid ranges for future safety */
514 if(bufp
[0] == '-' && !(flags
& a_RANGE_OPEN
)){
515 if(!(pstate
& PS_HOOK
))
516 n_err(_("Ignoring invalid range before: %s\n"), bufp
);
520 if(flags
& a_RANGE_OPEN
){
521 id
= N_("Missing second range argument\n");
525 np
= a_message_add_to_namelist(&namelist
, &nmlsize
, np
, NULL
);
528 /* * is special at this point, after we have parsed the entire line */
530 for(i
= 0; i
< msgCount
; ++i
){
531 if((mp
= &message
[i
])->m_flag
& MHIDDEN
)
533 if(!a_message_list_saw_d
&& (mp
->m_flag
& MDELETED
) != (unsigned)f
)
543 /* If any names were given, add any messages which match */
544 if(np
> namelist
|| id
!= NULL
){
545 struct search_expr
*sep
= NULL
;
547 /* The @ search works with struct search_expr, so build an array.
548 * To simplify array, i.e., regex_t destruction, and optimize for the
549 * common case we walk the entire array even in case of errors */
551 sep
= scalloc(PTR2SIZE(np
- namelist
), sizeof(*sep
));
552 for(j
= 0, nq
= namelist
; *nq
!= NULL
; ++j
, ++nq
){
555 sep
[j
].ss_sexpr
= x
= *nq
;
556 if(*x
!= '@' || (flags
& a_ERROR
))
559 for(y
= &x
[1];; ++y
){
560 if(*y
== '\0' || !fieldnamechar(*y
)){
569 sep
[j
].ss_where
= (x
== NULL
|| x
- 1 == *nq
)
570 ? "subject" : savestrbuf(&(*nq
)[1], PTR2SIZE(x
- *nq
) - 1);
572 x
= (x
== NULL
? *nq
: x
) + 1;
573 if(*x
== '\0'){ /* XXX Simply remove from list instead? */
574 n_err(_("Empty [@..]@ search expression\n"));
579 if(is_maybe_regex(x
)){
580 sep
[j
].ss_sexpr
= NULL
;
581 if(regcomp(&sep
[j
].ss_regex
, x
,
582 REG_EXTENDED
| REG_ICASE
| REG_NOSUB
) != 0){
583 if(!(pstate
& PS_HOOK
) && (options
& OPT_D_V
))
584 n_err(_("Invalid regular expression: >>> %s <<<\n"), x
);
593 goto jnamesearch_sepfree
;
596 /* Iterate the entire message array */
600 for(i
= 0; i
< msgCount
; ++i
){
602 if(mp
->m_flag
& MMARK
)
607 for(nq
= namelist
; *nq
!= NULL
; ++nq
){
609 if(a_message_match_at(mp
, sep
+ PTR2SIZE(nq
- namelist
))){
613 }else if(**nq
== '/'){
614 if(a_message_match_dash(mp
, *nq
)){
618 }else if(a_message_match_sender(mp
, *nq
, (flags
& a_ALLNET
))){
624 if(!(flags
& a_TMP
) &&
625 id
!= NULL
&& a_message_match_mid(mp
, id
, idfield
))
639 for(j
= PTR2SIZE(np
- namelist
); j
-- != 0;)
640 if(sep
[j
].ss_sexpr
== NULL
)
641 regfree(&sep
[j
].ss_regex
);
649 /* If any colon modifiers were given, go through and mark any messages which
650 * do satisfy the modifiers */
652 for(i
= 0; i
< msgCount
; ++i
){
653 struct a_message_coltab
const *colp
;
655 if((mp
= &message
[i
])->m_flag
& MMARK
)
658 for(colp
= a_message_coltabs
; colp
->mco_char
!= '\0'; ++colp
)
659 if((colp
->mco_bit
& colmod
) &&
660 ((mp
->m_flag
& colp
->mco_mask
) == (unsigned)colp
->mco_equal
)){
668 /* It shall be an error if ` didn't match anything, and nothing else did */
669 if((flags
& (a_TBACK
| a_ANY
)) == a_TBACK
){
670 id
= N_("No previously marked messages\n");
672 }else if(!(flags
& a_ANY
))
675 assert(!(flags
& a_ERROR
));
679 ac_free(a_message_lexstr
.s
);
682 return (flags
& a_ERROR
) ? -1 : 0;
685 id
= N_("Invalid range endpoint\n");
688 id
= N_("No applicable messages\n");
690 if(!(pstate
& PS_HOOK_MASK
))
698 a_message_evalcol(int col
){
699 struct a_message_coltab
const *colp
;
704 for(colp
= a_message_coltabs
; colp
->mco_char
!= '\0'; ++colp
)
705 if(colp
->mco_char
== col
){
714 a_message_check(int mno
, int f
){
718 if(mno
< 1 || mno
> msgCount
){
719 n_err(_("%d: Invalid message number\n"), mno
);
721 }else if(((mp
= &message
[mno
- 1])->m_flag
& MHIDDEN
) ||
722 (f
!= MDELETED
&& (mp
->m_flag
& MDELETED
) != 0))
723 n_err(_("%d: inappropriate message\n"), mno
);
731 a_message_scan(char **sp
)
734 struct a_message_lex
const *lp
;
735 int rv
, c
, inquote
, quotec
;
738 rv
= a_MESSAGE_T_EOL
;
741 cp2
= a_message_lexstr
.s
;
744 /* strip away leading white space */
748 /* If no characters remain, we are at end of line, so report that */
754 /* Select members of a message thread */
756 if(*cp
== '\0' || spacechar(*cp
)){
757 a_message_lexstr
.s
[0] = '.';
758 a_message_lexstr
.s
[1] = '\0';
760 rv
= a_MESSAGE_T_DOT
| INT_MIN
;
767 /* If the leading character is a digit, scan the number and convert it
768 * on the fly. Return a_MESSAGE_T_NUMBER when done */
772 a_message_lexno
= (a_message_lexno
* 10) + c
- '0';
774 }while((c
= *cp
++, digitchar(c
)));
777 rv
|= a_MESSAGE_T_NUMBER
;
781 /* An IMAP SEARCH list. Note that a_MESSAGE_T_OPEN has always been included
782 * in singles[] in Mail and mailx. Thus although there is no formal
783 * definition for (LIST) lists, they do not collide with historical
784 * practice because a subject string (LIST) could never been matched
791 if ((c
= *cp
++&0377) == '\0') {
793 n_err(_("Missing )\n"));
794 rv
= a_MESSAGE_T_ERROR
;
797 if (inquote
&& c
== '\\') {
810 else if (spacechar(c
)) {
811 /* Replace unquoted whitespace by single space characters, to make
812 * the string IMAP SEARCH conformant */
818 } while (c
!= ')' || level
> 0);
821 rv
|= a_MESSAGE_T_OPEN
;
825 /* Check for single character tokens; return such if found */
826 for(lp
= a_message_singles
; lp
->ml_char
!= '\0'; ++lp
)
827 if(c
== lp
->ml_char
){
828 a_message_lexstr
.s
[0] = c
;
829 a_message_lexstr
.s
[1] = '\0';
835 /* We've got a string! Copy all the characters of the string into
836 * a_message_lexstr, until we see a null, space, or tab. If the lead
837 * character is a " or ', save it and scan until you get another */
839 if (c
== '\'' || c
== '"') {
844 if (quotec
== 0 && c
== '\\' && *cp
!= '\0')
850 if (quotec
== 0 && blankchar(c
))
852 if (PTRCMP(cp2
- a_message_lexstr
.s
, <, a_message_lexstr
.l
))
856 if (quotec
&& c
== 0) {
857 n_err(_("Missing %c\n"), quotec
);
858 rv
= a_MESSAGE_T_ERROR
;
863 rv
|= a_MESSAGE_T_STRING
;
870 a_message_match_sender(struct message
*mp
, char const *str
, bool_t allnet
){
871 char const *str_base
, *np_base
, *np
;
876 /* Empty string doesn't match */
877 if(*(str_base
= str
) == '\0'){
882 /* *allnet* is POSIX and, since it explicitly mentions login and user names,
883 * most likely case-sensitive. XXX Still allow substr matching, though
884 * XXX possibly the first letter should be case-insensitive, then? */
886 for(np_base
= np
= nameof(mp
, 0);;){
887 if((sc
= *str
++) == '@')
889 if((nc
= *np
++) == '@' || nc
== '\0' || sc
== '\0')
898 /* TODO POSIX says ~"match any address as shown in header overview",
899 * TODO but a normalized match would be more sane i guess.
900 * TODO struct name should gain a comparison method, normalize realname
901 * TODO content (in TODO) and thus match as likewise
902 * TODO "Buddy (Today) <here>" and "(Now) Buddy <here>" */
903 char const *real_base
;
906 real_base
= name1(mp
, 0);
907 again
= ok_blook(showname
);
909 np_base
= np
= again
? realname(real_base
) : skin(real_base
);
913 if((nc
= *np
++) == '\0' || sc
== '\0')
923 /* And really if i want to match 'on@' then i want it to match even if
924 * *showname* is set! */
925 if(!(rv
= (sc
== '\0')) && again
){
936 a_message_match_mid(struct message
*mp
, char const *id
,
937 enum a_message_idfield idfield
){
944 if((cp
= hfield1("message-id", mp
)) != NULL
){
946 case a_MESSAGE_ID_REFERENCES
:
947 if(!msgidcmp(id
, cp
))
950 case a_MESSAGE_ID_IN_REPLY_TO
:{
953 if((np
= extract(id
, GREF
)) != NULL
)
955 if(!msgidcmp(np
->n_name
, cp
)){
959 }while((np
= np
->n_flink
) != NULL
);
969 a_message_match_dash(struct message
*mp
, char const *str
){
970 static char lastscan
[128];
973 char *hfield
, *hbody
;
982 n_strscpy(lastscan
, str
, sizeof lastscan
); /* XXX use new n_str object! */
984 /* Now look, ignoring case, for the word in the string */
985 if(ok_blook(searchheaders
) && (hfield
= strchr(str
, ':'))){
988 l
= PTR2SIZE(hfield
- str
);
989 hfield
= ac_alloc(l
+1);
990 memcpy(hfield
, str
, l
);
992 hbody
= hfieldX(hfield
, mp
);
994 hfield
= UNCONST(str
+ l
+ 1);
996 hfield
= UNCONST(str
);
997 hbody
= hfield1("subject", mp
);
1002 in
.l
= strlen(in
.s
= hbody
);
1003 mime_fromhdr(&in
, &out
, TD_ICONV
);
1004 rv
= substr(out
.s
, hfield
);
1012 a_message_match_at(struct message
*mp
, struct search_expr
*sep
){
1020 nfield
= savestr(sep
->ss_where
);
1022 while((cfield
= n_strsep(&nfield
, ',', TRU1
)) != NULL
){
1023 if(!asccasecmp(cfield
, "body") ||
1024 (cfield
[1] == '\0' && cfield
[0] == '>')){
1027 if((rv
= message_match(mp
, sep
, rv
)))
1030 }else if(!asccasecmp(cfield
, "text") ||
1031 (cfield
[1] == '\0' && cfield
[0] == '=')){
1036 if(!asccasecmp(cfield
, "header") ||
1037 (cfield
[1] == '\0' && cfield
[0] == '<')){
1038 if((rv
= header_match(mp
, sep
)))
1043 /* This is not a special name, so take care for the "skin" prefix !
1044 * and possible abbreviations */
1049 if((doskin
= (*cfield
== '~')))
1051 if(cfield
[0] != '\0' && cfield
[1] == '\0'){
1052 char const x
[][8] = {"from", "to", "cc", "bcc", "subject"};
1056 c1
= lowerconv(cfield
[0]);
1057 for(i
= 0; i
< NELEM(x
); ++i
){
1064 if((in
.s
= hfieldX(cfield
, mp
)) == NULL
)
1067 /* Shall we split into address list and match the addresses only? */
1069 np
= lextract(in
.s
, GSKIN
);
1075 in
.l
= strlen(in
.s
);
1076 mime_fromhdr(&in
, &out
, TD_ICONV
);
1081 if(sep
->ss_sexpr
== NULL
)
1082 rv
= (regexec(&sep
->ss_regex
, out
.s
, 0,NULL
, 0) != REG_NOMATCH
);
1085 rv
= substr(out
.s
, sep
->ss_sexpr
);
1090 if(np
!= NULL
&& (np
= np
->n_flink
) != NULL
){
1101 a_message_unmark(int mesg
){
1106 if(i
< 1 || UICMP(z
, i
, >, msgCount
))
1107 n_panic(_("Bad message number to unmark"));
1108 message
[--i
].m_flag
&= ~MMARK
;
1113 a_message_metamess(int meta
, int f
)
1121 case '^': /* First 'good' message left */
1122 mp
= mb
.mb_threaded
? threadroot
: message
;
1123 while (PTRCMP(mp
, <, message
+ msgCount
)) {
1124 if (!(mp
->m_flag
& MHIDDEN
) && (mp
->m_flag
& MDELETED
) == (ui32_t
)f
) {
1125 c
= (int)PTR2SIZE(mp
- message
+ 1);
1128 if (mb
.mb_threaded
) {
1129 mp
= next_in_thread(mp
);
1135 if (!(pstate
& PS_HOOK_MASK
))
1136 n_err(_("No applicable messages\n"));
1139 case '$': /* Last 'good message left */
1141 ? this_in_thread(threadroot
, -1) : message
+ msgCount
- 1;
1142 while (mp
>= message
) {
1143 if (!(mp
->m_flag
& MHIDDEN
) && (mp
->m_flag
& MDELETED
) == (ui32_t
)f
) {
1144 c
= (int)PTR2SIZE(mp
- message
+ 1);
1147 if (mb
.mb_threaded
) {
1148 mp
= prev_in_thread(mp
);
1154 if (!(pstate
& PS_HOOK_MASK
))
1155 n_err(_("No applicable messages\n"));
1159 /* Current message */
1160 m
= dot
- message
+ 1;
1161 if ((dot
->m_flag
& MHIDDEN
) || (dot
->m_flag
& MDELETED
) != (ui32_t
)f
) {
1162 n_err(_("%d: inappropriate message\n"), m
);
1169 /* Previously current message */
1170 if (prevdot
== NULL
) {
1171 n_err(_("No previously current message\n"));
1174 m
= prevdot
- message
+ 1;
1175 if ((prevdot
->m_flag
& MHIDDEN
) ||
1176 (prevdot
->m_flag
& MDELETED
) != (ui32_t
)f
) {
1177 n_err(_("%d: inappropriate message\n"), m
);
1184 n_err(_("Unknown selector: %c\n"), c
);
1196 a_message__threadmark(struct message
*self
, int f
){
1198 if(!(self
->m_flag
& MHIDDEN
) &&
1199 (f
== MDELETED
|| !(self
->m_flag
& MDELETED
) || a_message_list_saw_d
))
1200 self
->m_flag
|= MMARK
;
1202 if((self
= self
->m_child
) != NULL
){
1204 while((self
= self
->m_younger
) != NULL
)
1205 if(self
->m_child
!= NULL
)
1207 a_message__threadmark(self
, f
);
1209 self
->m_flag
|= MMARK
;
1215 setinput(struct mailbox
*mp
, struct message
*m
, enum needspec need
){
1225 ok
= (m
->m_have
& HAVE_HEADER
) ? OKAY
: a_message_get_header(m
);
1228 ok
= (m
->m_have
& HAVE_BODY
) ? OKAY
: get_body(m
);
1238 if(fseek(mp
->mb_itf
, (long)mailx_positionof(m
->m_block
, m
->m_offset
),
1240 n_perr(_("fseek"), 0);
1241 n_panic(_("temporary file seek"));
1250 get_body(struct message
*mp
){
1275 message_reset(void){
1277 if(message
!= NULL
){
1282 a_message_mem_space
= 0;
1287 message_append(struct message
*mp
){
1289 if(UICMP(z
, msgCount
+ 1, >=, a_message_mem_space
)){
1290 /* XXX remove _mem_space magics (or use s_Vector) */
1291 a_message_mem_space
= ((a_message_mem_space
>= 128 &&
1292 a_message_mem_space
<= 1000000)
1293 ? a_message_mem_space
<< 1 : a_message_mem_space
+ 64);
1294 message
= srealloc(message
, a_message_mem_space
* sizeof(*message
));
1298 message
[msgCount
- 1] = *mp
;
1300 memset(&message
[msgCount
- 1], 0, sizeof *message
);
1306 message_append_null(void){
1309 message_append(NULL
);
1311 message
[msgCount
].m_size
= 0;
1312 message
[msgCount
].m_lines
= 0;
1317 message_match(struct message
*mp
, struct search_expr
const *sep
,
1318 bool_t with_headers
){
1320 size_t *linesize
, cnt
;
1327 if((fp
= Ftmp(NULL
, "mpmatch", OF_RDWR
| OF_UNLINK
| OF_REGISTER
)) == NULL
)
1330 if(sendmp(mp
, fp
, NULL
, NULL
, SEND_TOSRCH
, NULL
) < 0)
1335 line
= &termios_state
.ts_linebuf
; /* XXX line pool */
1336 linesize
= &termios_state
.ts_linesize
; /* XXX line pool */
1339 while(fgetline(line
, linesize
, &cnt
, NULL
, fp
, 0))
1343 while(fgetline(line
, linesize
, &cnt
, NULL
, fp
, 0)){
1345 if(sep
->ss_sexpr
== NULL
){
1346 if(regexec(&sep
->ss_regex
, *line
, 0,NULL
, 0) == REG_NOMATCH
)
1350 if(!substr(*line
, sep
->ss_sexpr
))
1364 setdot(struct message
*mp
){
1368 pstate
&= ~PS_DID_PRINT_DOT
;
1371 uncollapse1(dot
, 0);
1377 touch(struct message
*mp
){
1379 mp
->m_flag
|= MTOUCH
;
1380 if(!(mp
->m_flag
& MREAD
))
1381 mp
->m_flag
|= MREAD
| MSTATUS
;
1386 getmsglist(char *buf
, int *vector
, int flags
)
1392 pstate
&= ~PS_ARGLIST_MASK
;
1393 a_message_list_last_saw_d
= a_message_list_saw_d
;
1394 a_message_list_saw_d
= FAL0
;
1402 pstate
|= PS_MSGLIST_DIRECT
;
1404 if(a_message_markall(buf
, flags
) < 0){
1410 if(pstate
& PS_HOOK_NEWMAIL
){
1412 for(mp
= message
; mp
< &message
[msgCount
]; ++mp
)
1413 if(mp
->m_flag
& MMARK
){
1414 if(!(mp
->m_flag
& MNEWEST
))
1415 a_message_unmark((int)PTR2SIZE(mp
- message
+ 1));
1425 if(mb
.mb_threaded
== 0){
1426 for(mp
= message
; mp
< &message
[msgCount
]; ++mp
)
1427 if(mp
->m_flag
& MMARK
)
1428 *ip
++ = (int)PTR2SIZE(mp
- message
+ 1);
1430 for(mp
= threadroot
; mp
!= NULL
; mp
= next_in_thread(mp
))
1431 if(mp
->m_flag
& MMARK
)
1432 *ip
++ = (int)PTR2SIZE(mp
- message
+ 1);
1435 mc
= (int)PTR2SIZE(ip
- vector
);
1437 pstate
&= ~PS_MSGLIST_DIRECT
;
1450 if (msgCount
== 0) {
1458 mb
.mb_threaded
? (mp
!= NULL
) : PTRCMP(mp
, <, message
+ msgCount
);
1459 mb
.mb_threaded
? (mp
= next_in_thread(mp
)) : ++mp
) {
1460 if (!(mp
->m_flag
& MHIDDEN
) && (mp
->m_flag
& m
) == (ui32_t
)f
) {
1461 rv
= (int)PTR2SIZE(mp
- message
+ 1);
1466 if (dot
> message
) {
1467 for (mp
= dot
- 1; (mb
.mb_threaded
? (mp
!= NULL
) : (mp
>= message
));
1468 mb
.mb_threaded
? (mp
= prev_in_thread(mp
)) : --mp
) {
1469 if (!(mp
->m_flag
& MHIDDEN
) && (mp
->m_flag
& m
) == (ui32_t
)f
) {
1470 rv
= (int)PTR2SIZE(mp
- message
+ 1);
1482 mark(int mno
, int f
){
1483 struct message
*mp
, *xmp
;
1488 if(i
< 1 || i
> msgCount
)
1489 n_panic(_("Bad message number to mark"));
1492 if(mb
.mb_threaded
== 1 && a_message_threadflag
)
1493 a_message__threadmark(mp
, f
);
1495 assert(!(mp
->m_flag
& MHIDDEN
));
1496 mp
->m_flag
|= MMARK
;