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 - 2018 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
= 1u<<0,
69 a_MESSAGE_S_OLD
= 1u<<1,
70 a_MESSAGE_S_UNREAD
= 1u<<2,
71 a_MESSAGE_S_DELETED
= 1u<<3,
72 a_MESSAGE_S_READ
= 1u<<4,
73 a_MESSAGE_S_FLAG
= 1u<<5,
74 a_MESSAGE_S_ANSWERED
= 1u<<6,
75 a_MESSAGE_S_DRAFT
= 1u<<7,
76 a_MESSAGE_S_SPAM
= 1u<<8,
77 a_MESSAGE_S_SPAMUNSURE
= 1u<<9,
78 a_MESSAGE_S_MLIST
= 1u<<10,
79 a_MESSAGE_S_MLSUBSCRIBE
= 1u<<11
82 struct a_message_coltab
{
83 char mco_char
; /* What to find past : */
85 int mco_bit
; /* Associated modifier bit */
86 int mco_mask
; /* m_status bits to mask */
87 int mco_equal
; /* ... must equal this */
95 static struct a_message_coltab
const a_message_coltabs
[] = {
96 {'n', {0,}, a_MESSAGE_S_NEW
, MNEW
, MNEW
},
97 {'o', {0,}, a_MESSAGE_S_OLD
, MNEW
, 0},
98 {'u', {0,}, a_MESSAGE_S_UNREAD
, MREAD
, 0},
99 {'d', {0,}, a_MESSAGE_S_DELETED
, MDELETED
, MDELETED
},
100 {'r', {0,}, a_MESSAGE_S_READ
, MREAD
, MREAD
},
101 {'f', {0,}, a_MESSAGE_S_FLAG
, MFLAGGED
, MFLAGGED
},
102 {'a', {0,}, a_MESSAGE_S_ANSWERED
, MANSWERED
, MANSWERED
},
103 {'t', {0,}, a_MESSAGE_S_DRAFT
, MDRAFTED
, MDRAFTED
},
104 {'s', {0,}, a_MESSAGE_S_SPAM
, MSPAM
, MSPAM
},
105 {'S', {0,}, a_MESSAGE_S_SPAMUNSURE
, MSPAMUNSURE
, MSPAMUNSURE
},
106 /* These have no per-message flags, but must be evaluated */
107 {'l', {0,}, a_MESSAGE_S_MLIST
, 0, 0},
108 {'L', {0,}, a_MESSAGE_S_MLSUBSCRIBE
, 0, 0},
111 static struct a_message_lex
const a_message_singles
[] = {
112 {'$', a_MESSAGE_T_DOLLAR
},
113 {'.', a_MESSAGE_T_DOT
},
114 {'^', a_MESSAGE_T_UP
},
115 {'*', a_MESSAGE_T_ASTER
},
116 {'-', a_MESSAGE_T_MINUS
},
117 {'+', a_MESSAGE_T_PLUS
},
118 {'(', a_MESSAGE_T_OPEN
},
119 {')', a_MESSAGE_T_CLOSE
},
120 {',', a_MESSAGE_T_COMMA
},
121 {';', a_MESSAGE_T_SEMI
},
122 {'`', a_MESSAGE_T_BACK
}
125 /* Slots in ::message */
126 static size_t a_message_mem_space
;
128 /* Mark entire threads */
129 static bool_t a_message_threadflag
;
131 /* :d on its way HACK TODO */
132 static bool_t a_message_list_saw_d
, a_message_list_last_saw_d
;
134 /* String from a_MESSAGE_T_STRING, scan() */
135 static struct str a_message_lexstr
;
136 /* Number of a_MESSAGE_T_NUMBER from scan() */
137 static int a_message_lexno
;
139 /* Lazy load message header fields */
140 static enum okay
a_message_get_header(struct message
*mp
);
142 /* Append, taking care of resizes TODO vector */
143 static char **a_message_add_to_namelist(char ***namelist
, size_t *nmlsize
,
144 char **np
, char *string
);
146 /* Mark all messages that the user wanted from the command line in the message
147 * structure. Return 0 on success, -1 on error */
148 static int a_message_markall(char const *buf
, int f
);
150 /* Turn the character after a colon modifier into a bit value */
151 static int a_message_evalcol(int col
);
153 /* Check the passed message number for legality and proper flags. Unless f is
154 * MDELETED the message has to be undeleted */
155 static bool_t
a_message_check(int mno
, int f
);
157 /* Scan out a single lexical item and return its token number, updating the
158 * string pointer passed *sp. Also, store the value of the number or string
159 * scanned in a_message_lexno or a_message_lexstr as appropriate.
160 * In any event, store the scanned "thing" in a_message_lexstr.
161 * Returns the token as a negative number when we also saw & to mark a thread */
162 static int a_message_scan(char const **sp
);
164 /* See if the passed name sent the passed message */
165 static bool_t
a_message_match_sender(struct message
*mp
, char const *str
,
168 /* Check whether the given message-id or references match */
169 static bool_t
a_message_match_mid(struct message
*mp
, char const *id
,
170 enum a_message_idfield idfield
);
172 /* See if the given string matches.
173 * For the purpose of the scan, we ignore case differences.
174 * This is the engine behind the "/" search */
175 static bool_t
a_message_match_dash(struct message
*mp
, char const *str
);
177 /* See if the given search expression matches.
178 * For the purpose of the scan, we ignore case differences.
179 * This is the engine behind the "@[..@].." search */
180 static bool_t
a_message_match_at(struct message
*mp
, struct search_expr
*sep
);
182 /* Unmark the named message */
183 static void a_message_unmark(int mesg
);
185 /* Return the message number corresponding to the passed meta character */
186 static int a_message_metamess(int meta
, int f
);
188 /* Helper for mark(): self valid, threading enabled */
189 static void a_message__threadmark(struct message
*self
, int f
);
192 a_message_get_header(struct message
*mp
){
204 rv
= pop3_header(mp
);
210 rv
= imap_header(mp
);
223 a_message_add_to_namelist(char ***namelist
, size_t *nmlsize
, /* TODO Vector */
224 char **np
, char *string
){
228 if((idx
= PTR2SIZE(np
- *namelist
)) >= *nmlsize
){
229 *namelist
= n_realloc(*namelist
, (*nmlsize
+= 8) * sizeof *np
);
230 np
= &(*namelist
)[idx
];
238 a_message_markall(char const *buf
, int f
){
239 struct message
*mp
, *mx
;
240 enum a_message_idfield idfield
;
242 char const *id
, *bufp
;
243 char **np
, **nq
, **namelist
, *cp
;
244 int i
, valdot
, beg
, colmod
, tok
, colresult
;
247 a_ALLNET
= 1u<<0, /* Must be TRU1 */
248 a_ALLOC
= 1u<<1, /* Have allocated something */
251 a_ANY
= 1u<<4, /* Have marked just ANY */
252 a_RANGE
= 1u<<5, /* Seen dash, await close */
254 a_TOPEN
= 1u<<9, /* ( used (and didn't match) */
255 a_TBACK
= 1u<<10, /* ` used (and didn't match) */
257 a_HAVE_IMAP_HEADERS
= 1u<<14,
259 a_LOG
= 1u<<29, /* Log errors */
263 n_LCTA((ui32_t
)a_ALLNET
== (ui32_t
)TRU1
,
264 "Constant is converted to bool_t via AND, thus");
266 /* Update message array: clear MMARK but remember its former state for ` */
267 for(i
= msgCount
; i
-- > 0;){
270 mf
= (mp
= &message
[i
])->m_flag
;
279 /* Strip all leading WS from user buffer */
280 while(blankspacechar(*buf
))
282 /* If there is no input buffer, we are done! */
289 n_UNINIT(idfield
, a_MESSAGE_ID_REFERENCES
);
290 a_message_threadflag
= FAL0
;
291 a_message_lexstr
.s
= n_lofi_alloc(a_message_lexstr
.l
= 2 * strlen(buf
) +1);
292 np
= namelist
= n_alloc((nmlsize
= 8) * sizeof *namelist
); /* TODO vector */
294 valdot
= (int)PTR2SIZE(dot
- message
+ 1);
297 flags
= a_ALLOC
| (mb
.mb_threaded
? a_THREADED
: 0) |
298 ((!(n_pstate
& n_PS_HOOK_MASK
) || (n_poption
& n_PO_D_V
))
301 while((tok
= a_message_scan(&bufp
)) != a_MESSAGE_T_EOL
){
302 if((a_message_threadflag
= (tok
< 0)))
306 case a_MESSAGE_T_NUMBER
:
307 n_pstate
|= n_PS_MSGLIST_GABBY
;
309 if(!a_message_check(a_message_lexno
, f
))
315 if(!(flags
& a_THREADED
)){
316 if(beg
< a_message_lexno
)
320 a_message_lexno
= beg
;
323 for(; i
<= a_message_lexno
; ++i
){
324 mp
= &message
[i
- 1];
325 if(!(mp
->m_flag
& MHIDDEN
) &&
326 (f
== MDELETED
|| !(mp
->m_flag
& MDELETED
))){
332 /* TODO threaded ranges are a mess */
340 if(beg
< a_message_lexno
)
344 a_message_lexno
= beg
;
351 mp
= &message
[i
- 1];
352 if(!(mp
->m_flag
& MHIDDEN
) &&
353 (f
== MDELETED
|| !(mp
->m_flag
& MDELETED
))){
360 /* We may have reached the endpoint. If we were still
361 * detecting the direction to search for it, restart.
362 * Otherwise finished */
363 if(i
== a_message_lexno
){ /* XXX */
372 mx
= (tf
& a_T_DIR_PREV
) ? prev_in_thread(mp
)
373 : next_in_thread(mp
);
375 /* We anyway have failed to reach the endpoint in this
376 * direction; if we already switched that, report error */
377 if(!(tf
& a_T_DIR_PREV
)){
382 id
= N_("Range crosses multiple threads\n");
385 i
= (int)PTR2SIZE(mx
- message
+ 1);
391 /* Could be an inclusive range? */
394 beg
= a_message_lexno
;
397 mark(a_message_lexno
, f
);
402 case a_MESSAGE_T_PLUS
:
403 n_pstate
&= ~n_PS_MSGLIST_DIRECT
;
404 n_pstate
|= n_PS_MSGLIST_GABBY
;
407 if(flags
& a_THREADED
){
408 mx
= next_in_thread(&message
[i
- 1]);
409 i
= mx
? (int)PTR2SIZE(mx
- message
+ 1) : msgCount
+ 1;
413 id
= N_("Referencing beyond last message\n");
416 }while(message
[i
- 1].m_flag
== MHIDDEN
||
417 (message
[i
- 1].m_flag
& MDELETED
) != (unsigned)f
);
420 case a_MESSAGE_T_MINUS
:
421 n_pstate
&= ~n_PS_MSGLIST_DIRECT
;
422 n_pstate
|= n_PS_MSGLIST_GABBY
;
425 if(flags
& a_THREADED
){
426 mx
= prev_in_thread(&message
[i
- 1]);
427 i
= mx
? (int)PTR2SIZE(mx
- message
+ 1) : 0;
431 id
= N_("Referencing before first message\n");
434 }while(message
[i
- 1].m_flag
== MHIDDEN
||
435 (message
[i
- 1].m_flag
& MDELETED
) != (unsigned)f
);
438 case a_MESSAGE_T_STRING
:
439 n_pstate
&= ~n_PS_MSGLIST_DIRECT
;
443 /* This may be a colon modifier */
444 if((cp
= a_message_lexstr
.s
)[0] != ':')
445 np
= a_message_add_to_namelist(&namelist
, &nmlsize
, np
,
446 savestr(a_message_lexstr
.s
));
448 while(*++cp
!= '\0'){
449 colresult
= a_message_evalcol(*cp
);
452 n_err(_("Unknown colon modifier: %s\n"),
456 if(colresult
== a_MESSAGE_S_DELETED
){
457 a_message_list_saw_d
= TRU1
;
464 case a_MESSAGE_T_OPEN
:
465 n_pstate
&= ~n_PS_MSGLIST_DIRECT
;
470 #ifdef HAVE_IMAP_SEARCH
474 if((ires
= imap_search(a_message_lexstr
.s
, f
)) >= 0){
482 n_err(_("Optional selector is not available: %s\n"),
486 case a_MESSAGE_T_DOLLAR
:
488 case a_MESSAGE_T_SEMI
:
489 n_pstate
|= n_PS_MSGLIST_GABBY
;
491 case a_MESSAGE_T_DOT
: /* Don't set _GABBY for dot, to _allow_ history.. */
492 n_pstate
&= ~n_PS_MSGLIST_DIRECT
;
493 a_message_lexno
= a_message_metamess(a_message_lexstr
.s
[0], f
);
494 if(a_message_lexno
== -1)
497 case a_MESSAGE_T_BACK
:
498 n_pstate
&= ~n_PS_MSGLIST_DIRECT
;
503 for(i
= 0; i
< msgCount
; ++i
){
504 if((mp
= &message
[i
])->m_flag
& MHIDDEN
)
506 if((mp
->m_flag
& MDELETED
) != (unsigned)f
){
507 if(!a_message_list_last_saw_d
)
509 a_message_list_saw_d
= TRU1
;
511 if(mp
->m_flag
& MOLDMARK
){
518 case a_MESSAGE_T_ASTER
:
519 n_pstate
&= ~n_PS_MSGLIST_DIRECT
;
524 case a_MESSAGE_T_COMMA
:
525 n_pstate
&= ~n_PS_MSGLIST_DIRECT
;
526 n_pstate
|= n_PS_MSGLIST_GABBY
;
531 if(!(flags
& a_HAVE_IMAP_HEADERS
) && mb
.mb_type
== MB_IMAP
){
532 flags
|= a_HAVE_IMAP_HEADERS
;
533 imap_getheaders(1, msgCount
);
538 if((cp
= hfield1("in-reply-to", dot
)) != NULL
)
539 idfield
= a_MESSAGE_ID_IN_REPLY_TO
;
540 else if((cp
= hfield1("references", dot
)) != NULL
){
543 if((enp
= extract(cp
, GREF
)) != NULL
){
544 while(enp
->n_flink
!= NULL
)
547 idfield
= a_MESSAGE_ID_REFERENCES
;
555 id
= N_("Message-ID of parent of \"dot\" is indeterminable\n");
558 }else if(flags
& a_LOG
)
559 n_err(_("Ignoring redundant specification of , selector\n"));
561 case a_MESSAGE_T_ERROR
:
562 n_pstate
&= ~n_PS_MSGLIST_DIRECT
;
563 n_pstate
|= n_PS_MSGLIST_GABBY
;
567 /* Explicitly disallow invalid ranges for future safety */
568 if(bufp
[0] == '-' && !(flags
& a_RANGE
)){
570 n_err(_("Ignoring invalid range before: %s\n"), bufp
);
575 id
= N_("Missing second range argument\n");
579 np
= a_message_add_to_namelist(&namelist
, &nmlsize
, np
, NULL
);
582 /* * is special at this point, after we have parsed the entire line */
584 for(i
= 0; i
< msgCount
; ++i
){
585 if((mp
= &message
[i
])->m_flag
& MHIDDEN
)
587 if(!a_message_list_saw_d
&& (mp
->m_flag
& MDELETED
) != (unsigned)f
)
597 /* If any names were given, add any messages which match */
598 if(np
> namelist
|| id
!= NULL
){
599 struct search_expr
*sep
;
603 /* The @ search works with struct search_expr, so build an array.
604 * To simplify array, i.e., regex_t destruction, and optimize for the
605 * common case we walk the entire array even in case of errors */
606 /* XXX Like many other things around here: this should be outsourced */
608 j
= PTR2SIZE(np
- namelist
) * sizeof(*sep
);
609 sep
= n_lofi_alloc(j
);
612 for(j
= 0, nq
= namelist
; *nq
!= NULL
; ++j
, ++nq
){
615 sep
[j
].ss_body
= x
= xsave
= *nq
;
616 if(*x
!= '@' || (flags
& a_ERROR
))
619 /* Cramp the namelist */
620 for(y
= &x
[1];; ++y
){
630 if(x
== NULL
|| &x
[-1] == xsave
)
632 sep
[j
].ss_field
= "subject";
636 sep
[j
].ss_skin
= TRU1
;
639 n_err(_("[@..]@ search expression: no namelist, "
640 "only \"~\" skin indicator\n"));
645 cp
= savestrbuf(xsave
, PTR2SIZE(x
- xsave
));
647 /* Namelist could be a regular expression, too */
649 if(n_is_maybe_regex(cp
)){
652 assert(sep
[j
].ss_field
== NULL
);
653 if((s
= regcomp(&sep
[j
].ss__fieldre_buf
, cp
,
654 REG_EXTENDED
| REG_ICASE
| REG_NOSUB
)) != 0){
656 n_err(_("Invalid regular expression: %s: %s\n"),
657 n_shexp_quote_cp(cp
, FAL0
),
658 n_regex_err_to_doc(NULL
, s
));
662 sep
[j
].ss_fieldre
= &sep
[j
].ss__fieldre_buf
;
668 /* Because of the special cases we need to trim explicitly
669 * here, they are not covered by n_strsep() */
671 sio
.l
= PTR2SIZE(x
- xsave
);
672 if(*(cp
= n_str_trim(&sio
, n_STR_TRIM_BOTH
)->s
) == '\0')
673 goto jat_where_default
;
674 sep
[j
].ss_field
= cp
;
678 /* The actual search expression. If it is empty we only test the
679 * field(s) for existence */
680 x
= &(x
== NULL
? *nq
: x
)[1];
682 sep
[j
].ss_field_exists
= TRU1
;
684 }else if(n_is_maybe_regex(x
)){
687 sep
[j
].ss_body
= NULL
;
688 if((s
= regcomp(&sep
[j
].ss__bodyre_buf
, x
,
689 REG_EXTENDED
| REG_ICASE
| REG_NOSUB
)) != 0){
691 n_err(_("Invalid regular expression: %s: %s\n"),
692 n_shexp_quote_cp(x
, FAL0
),
693 n_regex_err_to_doc(NULL
, s
));
697 sep
[j
].ss_bodyre
= &sep
[j
].ss__bodyre_buf
;
703 goto jnamesearch_sepfree
;
706 /* Iterate the entire message array */
708 if(!(flags
& a_HAVE_IMAP_HEADERS
) && mb
.mb_type
== MB_IMAP
){
709 flags
|= a_HAVE_IMAP_HEADERS
;
710 imap_getheaders(1, msgCount
);
715 n_autorec_relax_create();
716 for(i
= 0; i
< msgCount
; ++i
){
717 if((mp
= &message
[i
])->m_flag
& (MMARK
| MHIDDEN
))
719 if(!a_message_list_saw_d
&& (mp
->m_flag
& MDELETED
) != (unsigned)f
)
724 for(nq
= namelist
; *nq
!= NULL
; ++nq
){
726 if(a_message_match_at(mp
, &sep
[PTR2SIZE(nq
- namelist
)])){
730 }else if(**nq
== '/'){
731 if(a_message_match_dash(mp
, *nq
)){
735 }else if(a_message_match_sender(mp
, *nq
, (flags
& a_ALLNET
))){
741 if(!(flags
& a_TMP
) &&
742 id
!= NULL
&& a_message_match_mid(mp
, id
, idfield
))
749 n_autorec_relax_unroll();
751 n_autorec_relax_gut();
756 for(j
= PTR2SIZE(np
- namelist
); j
-- != 0;){
757 if(sep
[j
].ss_fieldre
!= NULL
)
758 regfree(sep
[j
].ss_fieldre
);
759 if(sep
[j
].ss_bodyre
!= NULL
)
760 regfree(sep
[j
].ss_bodyre
);
769 /* If any colon modifiers were given, go through and mark any messages which
770 * do satisfy the modifiers */
772 for(i
= 0; i
< msgCount
; ++i
){
773 struct a_message_coltab
const *colp
;
775 if((mp
= &message
[i
])->m_flag
& (MMARK
| MHIDDEN
))
777 if(!a_message_list_saw_d
&& (mp
->m_flag
& MDELETED
) != (unsigned)f
)
780 for(colp
= a_message_coltabs
;
781 PTRCMP(colp
, <, &a_message_coltabs
[n_NELEM(a_message_coltabs
)]);
783 if(colp
->mco_bit
& colmod
){
784 /* Is this a colon modifier that requires evaluation? */
785 if(colp
->mco_mask
== 0){
786 if(colp
->mco_bit
& (a_MESSAGE_S_MLIST
|
787 a_MESSAGE_S_MLSUBSCRIBE
)){
788 enum mlist_state what
;
790 what
= (colp
->mco_bit
& a_MESSAGE_S_MLIST
) ? MLIST_KNOWN
792 if(what
== is_mlist_mp(mp
, what
))
795 }else if((mp
->m_flag
& colp
->mco_mask
796 ) == (enum mflag
)colp
->mco_equal
){
806 /* It shall be an error if ` didn't match anything, and nothing else did */
807 if((flags
& (a_TBACK
| a_ANY
)) == a_TBACK
){
808 id
= N_("No previously marked messages\n");
810 }else if(!(flags
& a_ANY
))
813 assert(!(flags
& a_ERROR
));
817 n_lofi_free(a_message_lexstr
.s
);
820 return (flags
& a_ERROR
) ? -1 : 0;
823 id
= N_("Invalid range endpoint\n");
826 id
= N_("No applicable messages\n");
836 a_message_evalcol(int col
){
837 struct a_message_coltab
const *colp
;
842 for(colp
= a_message_coltabs
;
843 PTRCMP(colp
, <, &a_message_coltabs
[n_NELEM(a_message_coltabs
)]);
845 if(colp
->mco_char
== col
){
854 a_message_check(int mno
, int f
){
858 if(mno
< 1 || mno
> msgCount
){
859 n_err(_("%d: Invalid message number\n"), mno
);
861 }else if(((mp
= &message
[mno
- 1])->m_flag
& MHIDDEN
) ||
862 (f
!= MDELETED
&& (mp
->m_flag
& MDELETED
) != 0))
863 n_err(_("%d: inappropriate message\n"), mno
);
871 a_message_scan(char const **sp
)
873 struct a_message_lex
const *lp
;
876 int rv
, c
, inquote
, quotec
;
879 rv
= a_MESSAGE_T_EOL
;
882 cp2
= a_message_lexstr
.s
;
885 /* strip away leading white space */
889 /* If no characters remain, we are at end of line, so report that */
895 /* Select members of a message thread */
897 if(*cp
== '\0' || spacechar(*cp
)){
898 a_message_lexstr
.s
[0] = '.';
899 a_message_lexstr
.s
[1] = '\0';
901 rv
= a_MESSAGE_T_DOT
| INT_MIN
;
908 /* If the leading character is a digit, scan the number and convert it
909 * on the fly. Return a_MESSAGE_T_NUMBER when done */
913 a_message_lexno
= (a_message_lexno
* 10) + c
- '0';
915 }while((c
= *cp
++, digitchar(c
)));
918 rv
|= a_MESSAGE_T_NUMBER
;
922 /* An IMAP SEARCH list. Note that a_MESSAGE_T_OPEN has always been included
923 * in singles[] in Mail and mailx. Thus although there is no formal
924 * definition for (LIST) lists, they do not collide with historical
925 * practice because a subject string (LIST) could never been matched
932 if ((c
= *cp
++&0377) == '\0') {
934 n_err(_("Missing )\n"));
935 rv
= a_MESSAGE_T_ERROR
;
938 if (inquote
&& c
== '\\') {
951 else if (spacechar(c
)) {
952 /* Replace unquoted whitespace by single space characters, to make
953 * the string IMAP SEARCH conformant */
959 } while (c
!= ')' || level
> 0);
962 rv
|= a_MESSAGE_T_OPEN
;
966 /* Check for single character tokens; return such if found */
967 for(lp
= a_message_singles
;
968 PTRCMP(lp
, <, &a_message_singles
[n_NELEM(a_message_singles
)]); ++lp
)
969 if(c
== lp
->ml_char
){
970 a_message_lexstr
.s
[0] = c
;
971 a_message_lexstr
.s
[1] = '\0';
977 /* We've got a string! Copy all the characters of the string into
978 * a_message_lexstr, until we see a null, space, or tab. If the lead
979 * character is a " or ', save it and scan until you get another */
981 if (c
== '\'' || c
== '"') {
986 if (quotec
== 0 && c
== '\\' && *cp
!= '\0')
992 if (quotec
== 0 && blankchar(c
))
994 if (PTRCMP(cp2
- a_message_lexstr
.s
, <, a_message_lexstr
.l
))
998 if (quotec
&& c
== 0) {
999 n_err(_("Missing %c\n"), quotec
);
1000 rv
= a_MESSAGE_T_ERROR
;
1005 rv
|= a_MESSAGE_T_STRING
;
1012 a_message_match_sender(struct message
*mp
, char const *str
, bool_t allnet
){
1013 char const *str_base
, *np_base
, *np
;
1018 /* Empty string doesn't match */
1019 if(*(str_base
= str
) == '\0'){
1024 /* *allnet* is POSIX and, since it explicitly mentions login and user names,
1025 * most likely case-sensitive. XXX Still allow substr matching, though
1026 * XXX possibly the first letter should be case-insensitive, then? */
1028 for(np_base
= np
= nameof(mp
, 0);;){
1029 if((sc
= *str
++) == '@')
1031 if((nc
= *np
++) == '@' || nc
== '\0' || sc
== '\0')
1040 /* TODO POSIX says ~"match any address as shown in header overview",
1041 * TODO but a normalized match would be more sane i guess.
1042 * TODO struct name should gain a comparison method, normalize realname
1043 * TODO content (in TODO) and thus match as likewise
1044 * TODO "Buddy (Today) <here>" and "(Now) Buddy <here>" */
1045 char const *real_base
;
1048 real_base
= name1(mp
, 0);
1049 again
= ok_blook(showname
);
1051 np_base
= np
= again
? realname(real_base
) : skin(real_base
);
1055 if((nc
= *np
++) == '\0' || sc
== '\0')
1065 /* And really if i want to match 'on@' then i want it to match even if
1066 * *showname* is set! */
1067 if(!(rv
= (sc
== '\0')) && again
){
1078 a_message_match_mid(struct message
*mp
, char const *id
,
1079 enum a_message_idfield idfield
){
1086 if((cp
= hfield1("message-id", mp
)) != NULL
){
1088 case a_MESSAGE_ID_REFERENCES
:
1089 if(!msgidcmp(id
, cp
))
1092 case a_MESSAGE_ID_IN_REPLY_TO
:{
1095 if((np
= extract(id
, GREF
)) != NULL
)
1097 if(!msgidcmp(np
->n_name
, cp
)){
1101 }while((np
= np
->n_flink
) != NULL
);
1111 a_message_match_dash(struct message
*mp
, char const *str
){
1112 static char lastscan
[128];
1115 char *hfield
, *hbody
;
1124 n_strscpy(lastscan
, str
, sizeof lastscan
); /* XXX use new n_str object! */
1126 /* Now look, ignoring case, for the word in the string */
1127 if(ok_blook(searchheaders
) && (hfield
= strchr(str
, ':'))){
1130 l
= PTR2SIZE(hfield
- str
);
1131 hfield
= n_lofi_alloc(l
+1);
1132 memcpy(hfield
, str
, l
);
1134 hbody
= hfieldX(hfield
, mp
);
1135 n_lofi_free(hfield
);
1136 hfield
= n_UNCONST(str
+ l
+ 1);
1138 hfield
= n_UNCONST(str
);
1139 hbody
= hfield1("subject", mp
);
1144 in
.l
= strlen(in
.s
= hbody
);
1145 mime_fromhdr(&in
, &out
, TD_ICONV
);
1146 rv
= substr(out
.s
, hfield
);
1154 a_message_match_at(struct message
*mp
, struct search_expr
*sep
){
1159 /* Namelist regex only matches headers.
1160 * And there are the special cases header/<, "body"/> and "text"/=, the
1161 * latter two of which need to be handled in here */
1162 if((field
= sep
->ss_field
) != NULL
){
1163 if(!asccasecmp(field
, "body") || (field
[1] == '\0' && field
[0] == '>')){
1166 rv
= message_match(mp
, sep
, rv
);
1168 }else if(!asccasecmp(field
, "text") ||
1169 (field
[1] == '\0' && field
[0] == '=')){
1175 rv
= n_header_match(mp
, sep
);
1182 a_message_unmark(int mesg
){
1187 if(i
< 1 || UICMP(z
, i
, >, msgCount
))
1188 n_panic(_("Bad message number to unmark"));
1189 message
[--i
].m_flag
&= ~MMARK
;
1194 a_message_metamess(int meta
, int f
)
1202 case '^': /* First 'good' message left */
1203 mp
= mb
.mb_threaded
? threadroot
: message
;
1204 while (PTRCMP(mp
, <, message
+ msgCount
)) {
1205 if (!(mp
->m_flag
& MHIDDEN
) && (mp
->m_flag
& MDELETED
) == (ui32_t
)f
) {
1206 c
= (int)PTR2SIZE(mp
- message
+ 1);
1209 if (mb
.mb_threaded
) {
1210 mp
= next_in_thread(mp
);
1216 if (!(n_pstate
& n_PS_HOOK_MASK
))
1217 n_err(_("No applicable messages\n"));
1220 case '$': /* Last 'good message left */
1222 ? this_in_thread(threadroot
, -1) : message
+ msgCount
- 1;
1223 while (mp
>= message
) {
1224 if (!(mp
->m_flag
& MHIDDEN
) && (mp
->m_flag
& MDELETED
) == (ui32_t
)f
) {
1225 c
= (int)PTR2SIZE(mp
- message
+ 1);
1228 if (mb
.mb_threaded
) {
1229 mp
= prev_in_thread(mp
);
1235 if (!(n_pstate
& n_PS_HOOK_MASK
))
1236 n_err(_("No applicable messages\n"));
1240 /* Current message */
1241 m
= dot
- message
+ 1;
1242 if ((dot
->m_flag
& MHIDDEN
) || (dot
->m_flag
& MDELETED
) != (ui32_t
)f
) {
1243 n_err(_("%d: inappropriate message\n"), m
);
1250 /* Previously current message */
1251 if (prevdot
== NULL
) {
1252 n_err(_("No previously current message\n"));
1255 m
= prevdot
- message
+ 1;
1256 if ((prevdot
->m_flag
& MHIDDEN
) ||
1257 (prevdot
->m_flag
& MDELETED
) != (ui32_t
)f
) {
1258 n_err(_("%d: inappropriate message\n"), m
);
1265 n_err(_("Unknown selector: %c\n"), c
);
1277 a_message__threadmark(struct message
*self
, int f
){
1279 if(!(self
->m_flag
& MHIDDEN
) &&
1280 (f
== MDELETED
|| !(self
->m_flag
& MDELETED
) || a_message_list_saw_d
))
1281 self
->m_flag
|= MMARK
;
1283 if((self
= self
->m_child
) != NULL
){
1285 while((self
= self
->m_younger
) != NULL
)
1286 if(self
->m_child
!= NULL
)
1288 a_message__threadmark(self
, f
);
1290 self
->m_flag
|= MMARK
;
1296 setinput(struct mailbox
*mp
, struct message
*m
, enum needspec need
){
1305 ok
= (m
->m_content_info
& CI_HAVE_HEADER
) ? OKAY
1306 : a_message_get_header(m
);
1309 ok
= (m
->m_content_info
& CI_HAVE_BODY
) ? OKAY
: get_body(m
);
1320 if(fseek(mp
->mb_itf
, (long)mailx_positionof(m
->m_block
, m
->m_offset
),
1322 n_perr(_("fseek"), 0);
1323 n_panic(_("temporary file seek"));
1332 get_body(struct message
*mp
){
1363 message_reset(void){
1365 if(message
!= NULL
){
1370 a_message_mem_space
= 0;
1375 message_append(struct message
*mp
){
1377 if(UICMP(z
, msgCount
+ 1, >=, a_message_mem_space
)){
1378 /* XXX remove _mem_space magics (or use s_Vector) */
1379 a_message_mem_space
= ((a_message_mem_space
>= 128 &&
1380 a_message_mem_space
<= 1000000)
1381 ? a_message_mem_space
<< 1 : a_message_mem_space
+ 64);
1382 message
= n_realloc(message
, a_message_mem_space
* sizeof(*message
));
1386 message
[msgCount
- 1] = *mp
;
1388 memset(&message
[msgCount
- 1], 0, sizeof *message
);
1394 message_append_null(void){
1397 message_append(NULL
);
1399 message
[msgCount
].m_size
= 0;
1400 message
[msgCount
].m_lines
= 0;
1405 message_match(struct message
*mp
, struct search_expr
const *sep
,
1406 bool_t with_headers
){
1408 size_t *linesize
, cnt
;
1415 if((fp
= Ftmp(NULL
, "mpmatch", OF_RDWR
| OF_UNLINK
| OF_REGISTER
)) == NULL
)
1418 if(sendmp(mp
, fp
, NULL
, NULL
, SEND_TOSRCH
, NULL
) < 0)
1423 line
= &termios_state
.ts_linebuf
; /* XXX line pool */
1424 linesize
= &termios_state
.ts_linesize
; /* XXX line pool */
1427 while(fgetline(line
, linesize
, &cnt
, NULL
, fp
, 0))
1431 while(fgetline(line
, linesize
, &cnt
, NULL
, fp
, 0)){
1433 if(sep
->ss_bodyre
!= NULL
){
1434 if(regexec(sep
->ss_bodyre
, *line
, 0,NULL
, 0) == REG_NOMATCH
)
1438 if(!substr(*line
, sep
->ss_body
))
1452 setdot(struct message
*mp
){
1456 n_pstate
&= ~n_PS_DID_PRINT_DOT
;
1459 uncollapse1(dot
, 0);
1465 touch(struct message
*mp
){
1467 mp
->m_flag
|= MTOUCH
;
1468 if(!(mp
->m_flag
& MREAD
))
1469 mp
->m_flag
|= MREAD
| MSTATUS
;
1474 getmsglist(char const *buf
, int *vector
, int flags
)
1480 n_pstate
&= ~n_PS_ARGLIST_MASK
;
1481 a_message_list_last_saw_d
= a_message_list_saw_d
;
1482 a_message_list_saw_d
= FAL0
;
1490 n_pstate
|= n_PS_MSGLIST_DIRECT
;
1492 if(a_message_markall(buf
, flags
) < 0){
1498 if(n_pstate
& n_PS_HOOK_NEWMAIL
){
1500 for(mp
= message
; mp
< &message
[msgCount
]; ++mp
)
1501 if(mp
->m_flag
& MMARK
){
1502 if(!(mp
->m_flag
& MNEWEST
))
1503 a_message_unmark((int)PTR2SIZE(mp
- message
+ 1));
1513 if(mb
.mb_threaded
== 0){
1514 for(mp
= message
; mp
< &message
[msgCount
]; ++mp
)
1515 if(mp
->m_flag
& MMARK
)
1516 *ip
++ = (int)PTR2SIZE(mp
- message
+ 1);
1518 for(mp
= threadroot
; mp
!= NULL
; mp
= next_in_thread(mp
))
1519 if(mp
->m_flag
& MMARK
)
1520 *ip
++ = (int)PTR2SIZE(mp
- message
+ 1);
1523 mc
= (int)PTR2SIZE(ip
- vector
);
1525 n_pstate
&= ~n_PS_MSGLIST_DIRECT
;
1538 if (msgCount
== 0) {
1546 mb
.mb_threaded
? (mp
!= NULL
) : PTRCMP(mp
, <, message
+ msgCount
);
1547 mb
.mb_threaded
? (mp
= next_in_thread(mp
)) : ++mp
) {
1548 if (!(mp
->m_flag
& MHIDDEN
) && (mp
->m_flag
& m
) == (ui32_t
)f
) {
1549 rv
= (int)PTR2SIZE(mp
- message
+ 1);
1554 if (dot
> message
) {
1555 for (mp
= dot
- 1; (mb
.mb_threaded
? (mp
!= NULL
) : (mp
>= message
));
1556 mb
.mb_threaded
? (mp
= prev_in_thread(mp
)) : --mp
) {
1557 if (!(mp
->m_flag
& MHIDDEN
) && (mp
->m_flag
& m
) == (ui32_t
)f
) {
1558 rv
= (int)PTR2SIZE(mp
- message
+ 1);
1570 mark(int mno
, int f
){
1576 if(i
< 1 || i
> msgCount
)
1577 n_panic(_("Bad message number to mark"));
1580 if(mb
.mb_threaded
== 1 && a_message_threadflag
)
1581 a_message__threadmark(mp
, f
);
1583 assert(!(mp
->m_flag
& MHIDDEN
));
1584 mp
->m_flag
|= MMARK
;