1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Message, message array, n_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>.
6 * SPDX-License-Identifier: BSD-3-Clause
9 * Copyright (c) 1980, 1993
10 * The Regents of the University of California. All rights reserved.
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
15 * 1. Redistributions of source code must retain the above copyright
16 * notice, this list of conditions and the following disclaimer.
17 * 2. Redistributions in binary form must reproduce the above copyright
18 * notice, this list of conditions and the following disclaimer in the
19 * documentation and/or other materials provided with the distribution.
20 * 3. Neither the name of the University nor the names of its contributors
21 * may be used to endorse or promote products derived from this software
22 * without specific prior written permission.
24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37 #define n_FILE message
39 #ifndef HAVE_AMALGAMATION
43 /* Token values returned by the scanner used for argument lists.
44 * Also, sizes of scanner-related things */
46 a_MSG_T_EOL
, /* End of the command line */
47 a_MSG_T_NUMBER
, /* Message number */
48 a_MSG_T_MINUS
, /* - */
49 a_MSG_T_STRING
, /* A string (possibly containing -) */
52 a_MSG_T_DOLLAR
, /* $ */
53 a_MSG_T_ASTER
, /* * */
55 a_MSG_T_CLOSE
, /* ) */
57 a_MSG_T_COMMA
, /* , */
60 a_MSG_T_ERROR
/* Lexical error */
71 a_MSG_S_UNREAD
= 1u<<2,
72 a_MSG_S_DELETED
= 1u<<3,
75 a_MSG_S_ANSWERED
= 1u<<6,
76 a_MSG_S_DRAFT
= 1u<<7,
78 a_MSG_S_SPAMUNSURE
= 1u<<9,
79 a_MSG_S_MLIST
= 1u<<10,
80 a_MSG_S_MLSUBSCRIBE
= 1u<<11
84 char mco_char
; /* What to find past : */
86 int mco_bit
; /* Associated modifier bit */
87 int mco_mask
; /* m_status bits to mask */
88 int mco_equal
; /* ... must equal this */
97 char *msl_str
; /* If parsed a string */
98 int msl_no
; /* If parsed a number TODO siz_t! */
99 char msl__smallstrbuf
[4];
100 /* We directly adjust pointer in .ca_arg.ca_str.s, do not adjust .l */
101 struct n_cmd_arg
*msl_cap
;
102 char const *msl_input_orig
;
105 static struct a_msg_coltab
const a_msg_coltabs
[] = {
106 {'n', {0,}, a_MSG_S_NEW
, MNEW
, MNEW
},
107 {'o', {0,}, a_MSG_S_OLD
, MNEW
, 0},
108 {'u', {0,}, a_MSG_S_UNREAD
, MREAD
, 0},
109 {'d', {0,}, a_MSG_S_DELETED
, MDELETED
, MDELETED
},
110 {'r', {0,}, a_MSG_S_READ
, MREAD
, MREAD
},
111 {'f', {0,}, a_MSG_S_FLAG
, MFLAGGED
, MFLAGGED
},
112 {'a', {0,}, a_MSG_S_ANSWERED
, MANSWERED
, MANSWERED
},
113 {'t', {0,}, a_MSG_S_DRAFT
, MDRAFTED
, MDRAFTED
},
114 {'s', {0,}, a_MSG_S_SPAM
, MSPAM
, MSPAM
},
115 {'S', {0,}, a_MSG_S_SPAMUNSURE
, MSPAMUNSURE
, MSPAMUNSURE
},
116 /* These have no per-message flags, but must be evaluated */
117 {'l', {0,}, a_MSG_S_MLIST
, 0, 0},
118 {'L', {0,}, a_MSG_S_MLSUBSCRIBE
, 0, 0},
121 static struct a_msg_lex
const a_msg_singles
[] = {
122 {'$', a_MSG_T_DOLLAR
},
125 {'*', a_MSG_T_ASTER
},
126 {'-', a_MSG_T_MINUS
},
129 {')', a_MSG_T_CLOSE
},
130 {',', a_MSG_T_COMMA
},
135 /* Slots in ::message */
136 static size_t a_msg_mem_space
;
138 /* Mark entire threads */
139 static bool_t a_msg_threadflag
;
141 /* :d on its way HACK TODO */
142 static bool_t a_msg_list_saw_d
, a_msg_list_last_saw_d
;
144 /* Lazy load message header fields */
145 static enum okay
a_msg_get_header(struct message
*mp
);
147 /* Append, taking care of resizes TODO vector */
148 static char **a_msg_add_to_nmadat(char ***nmadat
, size_t *nmasize
,
149 char **np
, char *string
);
151 /* Mark all messages that the user wanted from the command line in the message
152 * structure. Return 0 on success, -1 on error */
153 static int a_msg_markall(char const *orig
, struct n_cmd_arg
*cap
, int f
);
155 /* Turn the character after a colon modifier into a bit value */
156 static int a_msg_evalcol(int col
);
158 /* Check the passed message number for legality and proper flags. Unless f is
159 * MDELETED the message has to be undeleted */
160 static bool_t
a_msg_check(int mno
, int f
);
162 /* Scan out a single lexical item and return its token number, updating *mslp */
163 static int a_msg_scan(struct a_msg_speclex
*mslp
);
165 /* See if the passed name sent the passed message */
166 static bool_t
a_msg_match_sender(struct message
*mp
, char const *str
,
169 /* Check whether the given message-id or references match */
170 static bool_t
a_msg_match_mid(struct message
*mp
, char const *id
,
171 enum a_msg_idfield idfield
);
173 /* See if the given string matches.
174 * For the purpose of the scan, we ignore case differences.
175 * This is the engine behind the "/" search */
176 static bool_t
a_msg_match_dash(struct message
*mp
, char const *str
);
178 /* See if the given search expression matches.
179 * For the purpose of the scan, we ignore case differences.
180 * This is the engine behind the "@[..@].." search */
181 static bool_t
a_msg_match_at(struct message
*mp
, struct search_expr
*sep
);
183 /* Unmark the named message */
184 static void a_msg_unmark(int mesg
);
186 /* Return the message number corresponding to the passed meta character */
187 static int a_msg_metamess(int meta
, int f
);
189 /* Helper for mark(): self valid, threading enabled */
190 static void a_msg__threadmark(struct message
*self
, int f
);
193 a_msg_get_header(struct message
*mp
){
205 rv
= pop3_header(mp
);
211 rv
= imap_header(mp
);
224 a_msg_add_to_nmadat(char ***nmadat
, size_t *nmasize
, /* TODO Vector */
225 char **np
, char *string
){
229 if((idx
= PTR2SIZE(np
- *nmadat
)) >= *nmasize
){
234 narr
= n_autorec_alloc(i
* sizeof *np
);
235 memcpy(narr
, *nmadat
, i
>>= 1);
245 a_msg_markall(char const *orig
, struct n_cmd_arg
*cap
, int f
){
246 struct a_msg_speclex msl
;
247 enum a_msg_idfield idfield
;
250 char **nmadat_lofi
, **nmadat
, **np
, **nq
, *cp
;
251 struct message
*mp
, *mx
;
252 int i
, valdot
, beg
, colmod
, tok
, colresult
;
255 a_ALLNET
= 1u<<0, /* (CTA()d to be == TRU1 */
256 a_ALLOC
= 1u<<1, /* Have allocated something */
259 a_ANY
= 1u<<4, /* Have marked just ANY */
260 a_RANGE
= 1u<<5, /* Seen dash, await close */
262 a_TOPEN
= 1u<<9, /* ( used (and didn't match) */
263 a_TBACK
= 1u<<10, /* ` used (and didn't match) */
265 a_HAVE_IMAP_HEADERS
= 1u<<14,
267 a_LOG
= 1u<<29, /* Log errors */
271 n_LCTA((ui32_t
)a_ALLNET
== (ui32_t
)TRU1
,
272 "Constant is converted to bool_t via AND, thus");
274 /* Update message array: clear MMARK but remember its former state for ` */
275 for(i
= msgCount
; i
-- > 0;){
278 mf
= (mp
= &message
[i
])->m_flag
;
287 memset(&msl
, 0, sizeof msl
);
289 msl
.msl_input_orig
= orig
;
292 nmadat_lofi
= n_lofi_alloc((nmasize
= 64) * sizeof *np
); /* TODO vector */
294 n_UNINIT(idfield
, a_MSG_ID_REFERENCES
);
295 a_msg_threadflag
= FAL0
;
296 valdot
= (int)PTR2SIZE(dot
- message
+ 1);
299 flags
= a_ALLOC
| (mb
.mb_threaded
? a_THREADED
: 0) |
300 ((!(n_pstate
& n_PS_HOOK_MASK
) || (n_poption
& n_PO_D_V
))
303 while((tok
= a_msg_scan(&msl
)) != a_MSG_T_EOL
){
304 if((a_msg_threadflag
= (tok
< 0)))
309 n_pstate
|= n_PS_MSGLIST_GABBY
;
311 if(!a_msg_check(msl
.msl_no
, f
))
317 if(!(flags
& a_THREADED
)){
325 for(; i
<= msl
.msl_no
; ++i
){
326 mp
= &message
[i
- 1];
327 if(!(mp
->m_flag
& MHIDDEN
) &&
328 (f
== MDELETED
|| !(mp
->m_flag
& MDELETED
))){
334 /* TODO threaded ranges are a mess */
353 mp
= &message
[i
- 1];
354 if(!(mp
->m_flag
& MHIDDEN
) &&
355 (f
== MDELETED
|| !(mp
->m_flag
& MDELETED
))){
362 /* We may have reached the endpoint. If we were still
363 * detecting the direction to search for it, restart.
364 * Otherwise finished */
365 if(i
== msl
.msl_no
){ /* XXX */
374 mx
= (tf
& a_T_DIR_PREV
) ? prev_in_thread(mp
)
375 : next_in_thread(mp
);
377 /* We anyway have failed to reach the endpoint in this
378 * direction; if we already switched that, report error */
379 if(!(tf
& a_T_DIR_PREV
)){
384 id
= N_("Range crosses multiple threads\n");
387 i
= (int)PTR2SIZE(mx
- message
+ 1);
393 /* Could be an inclusive range? */
394 if(msl
.msl_cap
!= NULL
&&
395 msl
.msl_cap
->ca_arg
.ca_str
.s
[0] == '-'){
396 if(*++msl
.msl_cap
->ca_arg
.ca_str
.s
== '\0')
397 msl
.msl_cap
= msl
.msl_cap
->ca_next
;
407 n_pstate
&= ~n_PS_MSGLIST_DIRECT
;
408 n_pstate
|= n_PS_MSGLIST_GABBY
;
411 if(flags
& a_THREADED
){
412 mx
= next_in_thread(&message
[i
- 1]);
413 i
= mx
? (int)PTR2SIZE(mx
- message
+ 1) : msgCount
+ 1;
417 id
= N_("Referencing beyond last message\n");
420 }while(message
[i
- 1].m_flag
== MHIDDEN
||
421 (message
[i
- 1].m_flag
& MDELETED
) != (unsigned)f
);
425 n_pstate
&= ~n_PS_MSGLIST_DIRECT
;
426 n_pstate
|= n_PS_MSGLIST_GABBY
;
429 if(flags
& a_THREADED
){
430 mx
= prev_in_thread(&message
[i
- 1]);
431 i
= mx
? (int)PTR2SIZE(mx
- message
+ 1) : 0;
435 id
= N_("Referencing before first message\n");
438 }while(message
[i
- 1].m_flag
== MHIDDEN
||
439 (message
[i
- 1].m_flag
& MDELETED
) != (unsigned)f
);
443 n_pstate
&= ~n_PS_MSGLIST_DIRECT
;
447 /* This may be a colon modifier */
448 if((cp
= msl
.msl_str
)[0] != ':')
449 np
= a_msg_add_to_nmadat(&nmadat
, &nmasize
, np
,
450 savestr(msl
.msl_str
));
452 while(*++cp
!= '\0'){
453 colresult
= a_msg_evalcol(*cp
);
456 n_err(_("Unknown colon modifier: %s\n"), msl
.msl_str
);
459 if(colresult
== a_MSG_S_DELETED
){
460 a_msg_list_saw_d
= TRU1
;
468 n_pstate
&= ~n_PS_MSGLIST_DIRECT
;
473 #ifdef HAVE_IMAP_SEARCH
477 if((ires
= imap_search(msl
.msl_str
, f
)) >= 0){
485 n_err(_("Optional selector not available: %s\n"), msl
.msl_str
);
491 n_pstate
|= n_PS_MSGLIST_GABBY
;
493 case a_MSG_T_DOT
: /* Don't set _GABBY for dot to allow history.. */
494 n_pstate
&= ~n_PS_MSGLIST_DIRECT
;
495 if((msl
.msl_no
= a_msg_metamess(msl
.msl_str
[0], f
)) == -1)
499 n_pstate
&= ~n_PS_MSGLIST_DIRECT
;
504 for(i
= 0; i
< msgCount
; ++i
){
505 if((mp
= &message
[i
])->m_flag
& MHIDDEN
)
507 if((mp
->m_flag
& MDELETED
) != (unsigned)f
){
508 if(!a_msg_list_last_saw_d
)
510 a_msg_list_saw_d
= TRU1
;
512 if(mp
->m_flag
& MOLDMARK
){
520 n_pstate
&= ~n_PS_MSGLIST_DIRECT
;
526 n_pstate
&= ~n_PS_MSGLIST_DIRECT
;
527 n_pstate
|= n_PS_MSGLIST_GABBY
;
532 if(!(flags
& a_HAVE_IMAP_HEADERS
) && mb
.mb_type
== MB_IMAP
){
533 flags
|= a_HAVE_IMAP_HEADERS
;
534 imap_getheaders(1, msgCount
);
539 if((cp
= hfield1("in-reply-to", dot
)) != NULL
)
540 idfield
= a_MSG_ID_IN_REPLY_TO
;
541 else if((cp
= hfield1("references", dot
)) != NULL
){
544 if((enp
= extract(cp
, GREF
)) != NULL
){
545 while(enp
->n_flink
!= NULL
)
548 idfield
= a_MSG_ID_REFERENCES
;
556 id
= N_("Message-ID of parent of \"dot\" is indeterminable\n");
559 }else if(flags
& a_LOG
)
560 n_err(_("Ignoring redundant specification of , selector\n"));
563 n_pstate
&= ~n_PS_MSGLIST_DIRECT
;
564 n_pstate
|= n_PS_MSGLIST_GABBY
;
568 /* Explicitly disallow invalid ranges for future safety */
569 if(msl
.msl_cap
!= NULL
&& msl
.msl_cap
->ca_arg
.ca_str
.s
[0] == '-' &&
572 n_err(_("Ignoring invalid range in: %s\n"), msl
.msl_input_orig
);
573 if(*++msl
.msl_cap
->ca_arg
.ca_str
.s
== '\0')
574 msl
.msl_cap
= msl
.msl_cap
->ca_next
;
578 id
= N_("Missing second range argument\n");
582 np
= a_msg_add_to_nmadat(&nmadat
, &nmasize
, np
, NULL
);
585 /* * is special at this point, after we have parsed the entire line */
587 for(i
= 0; i
< msgCount
; ++i
){
588 if((mp
= &message
[i
])->m_flag
& MHIDDEN
)
590 if(!a_msg_list_saw_d
&& (mp
->m_flag
& MDELETED
) != (unsigned)f
)
600 /* If any names were given, add any messages which match */
601 if(np
> nmadat
|| id
!= NULL
){
602 struct search_expr
*sep
;
606 /* The @ search works with struct search_expr, so build an array.
607 * To simplify array, i.e., regex_t destruction, and optimize for the
608 * common case we walk the entire array even in case of errors */
609 /* XXX Like many other things around here: this should be outsourced */
611 j
= PTR2SIZE(np
- nmadat
) * sizeof(*sep
);
612 sep
= n_lofi_alloc(j
);
615 for(j
= 0, nq
= nmadat
; *nq
!= NULL
; ++j
, ++nq
){
618 sep
[j
].ss_body
= x
= xsave
= *nq
;
619 if(*x
!= '@' || (flags
& a_ERROR
))
622 /* Cramp the namelist */
623 for(y
= &x
[1];; ++y
){
633 if(x
== NULL
|| &x
[-1] == xsave
)
635 sep
[j
].ss_field
= "subject";
639 sep
[j
].ss_skin
= TRU1
;
642 n_err(_("[@..]@ search expression: no namelist, "
643 "only \"~\" skin indicator\n"));
648 cp
= savestrbuf(xsave
, PTR2SIZE(x
- xsave
));
650 /* Namelist could be a regular expression, too */
652 if(n_is_maybe_regex(cp
)){
655 assert(sep
[j
].ss_field
== NULL
);
656 if((s
= regcomp(&sep
[j
].ss__fieldre_buf
, cp
,
657 REG_EXTENDED
| REG_ICASE
| REG_NOSUB
)) != 0){
659 n_err(_("Invalid regular expression: %s: %s\n"),
660 n_shexp_quote_cp(cp
, FAL0
),
661 n_regex_err_to_doc(NULL
, s
));
665 sep
[j
].ss_fieldre
= &sep
[j
].ss__fieldre_buf
;
671 /* Because of the special cases we need to trim explicitly
672 * here, they are not covered by n_strsep() */
674 sio
.l
= PTR2SIZE(x
- xsave
);
675 if(*(cp
= n_str_trim(&sio
, n_STR_TRIM_BOTH
)->s
) == '\0')
676 goto jat_where_default
;
677 sep
[j
].ss_field
= cp
;
681 /* The actual search expression. If it is empty we only test the
682 * field(s) for existence */
683 x
= &(x
== NULL
? *nq
: x
)[1];
685 sep
[j
].ss_field_exists
= TRU1
;
687 }else if(n_is_maybe_regex(x
)){
690 sep
[j
].ss_body
= NULL
;
691 if((s
= regcomp(&sep
[j
].ss__bodyre_buf
, x
,
692 REG_EXTENDED
| REG_ICASE
| REG_NOSUB
)) != 0){
694 n_err(_("Invalid regular expression: %s: %s\n"),
695 n_shexp_quote_cp(x
, FAL0
),
696 n_regex_err_to_doc(NULL
, s
));
700 sep
[j
].ss_bodyre
= &sep
[j
].ss__bodyre_buf
;
706 goto jnamesearch_sepfree
;
709 /* Iterate the entire message array */
711 if(!(flags
& a_HAVE_IMAP_HEADERS
) && mb
.mb_type
== MB_IMAP
){
712 flags
|= a_HAVE_IMAP_HEADERS
;
713 imap_getheaders(1, msgCount
);
718 n_autorec_relax_create();
719 for(i
= 0; i
< msgCount
; ++i
){
720 if((mp
= &message
[i
])->m_flag
& (MMARK
| MHIDDEN
))
722 if(!a_msg_list_saw_d
&& (mp
->m_flag
& MDELETED
) != (unsigned)f
)
727 for(nq
= nmadat
; *nq
!= NULL
; ++nq
){
729 if(a_msg_match_at(mp
, &sep
[PTR2SIZE(nq
- nmadat
)])){
733 }else if(**nq
== '/'){
734 if(a_msg_match_dash(mp
, *nq
)){
738 }else if(a_msg_match_sender(mp
, *nq
, (flags
& a_ALLNET
))){
744 if(!(flags
& a_TMP
) &&
745 id
!= NULL
&& a_msg_match_mid(mp
, id
, idfield
))
752 n_autorec_relax_unroll();
754 n_autorec_relax_gut();
759 for(j
= PTR2SIZE(np
- nmadat
); j
-- != 0;){
760 if(sep
[j
].ss_fieldre
!= NULL
)
761 regfree(sep
[j
].ss_fieldre
);
762 if(sep
[j
].ss_bodyre
!= NULL
)
763 regfree(sep
[j
].ss_bodyre
);
772 /* If any colon modifiers were given, go through and mark any messages which
773 * do satisfy the modifiers */
775 for(i
= 0; i
< msgCount
; ++i
){
776 struct a_msg_coltab
const *colp
;
778 if((mp
= &message
[i
])->m_flag
& (MMARK
| MHIDDEN
))
780 if(!a_msg_list_saw_d
&& (mp
->m_flag
& MDELETED
) != (unsigned)f
)
783 for(colp
= a_msg_coltabs
;
784 PTRCMP(colp
, <, &a_msg_coltabs
[n_NELEM(a_msg_coltabs
)]); ++colp
)
785 if(colp
->mco_bit
& colmod
){
786 /* Is this a colon modifier that requires evaluation? */
787 if(colp
->mco_mask
== 0){
788 if(colp
->mco_bit
& (a_MSG_S_MLIST
|
789 a_MSG_S_MLSUBSCRIBE
)){
790 enum mlist_state what
;
792 what
= (colp
->mco_bit
& a_MSG_S_MLIST
) ? MLIST_KNOWN
794 if(what
== is_mlist_mp(mp
, what
))
797 }else if((mp
->m_flag
& colp
->mco_mask
798 ) == (enum mflag
)colp
->mco_equal
){
808 /* It shall be an error if ` didn't match anything, and nothing else did */
809 if((flags
& (a_TBACK
| a_ANY
)) == a_TBACK
){
810 id
= N_("No previously marked messages\n");
812 }else if(!(flags
& a_ANY
))
815 assert(!(flags
& a_ERROR
));
818 n_lofi_free(nmadat_lofi
);
820 return (flags
& a_ERROR
) ? -1 : 0;
823 id
= N_("Invalid range endpoint\n");
826 id
= N_("No applicable messages\n");
836 a_msg_evalcol(int col
){
837 struct a_msg_coltab
const *colp
;
842 for(colp
= a_msg_coltabs
;
843 PTRCMP(colp
, <, &a_msg_coltabs
[n_NELEM(a_msg_coltabs
)]); ++colp
)
844 if(colp
->mco_char
== col
){
853 a_msg_check(int mno
, int f
){
857 if(mno
< 1 || mno
> msgCount
){
858 n_err(_("%d: Invalid message number\n"), mno
);
860 }else if(((mp
= &message
[mno
- 1])->m_flag
& MHIDDEN
) ||
861 (f
!= MDELETED
&& (mp
->m_flag
& MDELETED
) != 0))
862 n_err(_("%d: inappropriate message\n"), mno
);
870 a_msg_scan(struct a_msg_speclex
*mslp
){
871 struct a_msg_lex
const *lp
;
878 /* Empty cap's even for IGNORE_EMPTY (quoted empty tokens produce output) */
879 for(;; mslp
->msl_cap
= mslp
->msl_cap
->ca_next
){
880 if(mslp
->msl_cap
== NULL
)
883 cp
= mslp
->msl_cap
->ca_arg
.ca_str
.s
;
884 if((c
= *cp
++) != '\0')
888 /* Select members of a message thread */
891 if(c
== '\0' || spacechar(c
)){
892 mslp
->msl_str
= mslp
->msl__smallstrbuf
;
893 mslp
->msl_str
[0] = '.';
894 mslp
->msl_str
[1] = '\0';
896 mslp
->msl_cap
= mslp
->msl_cap
->ca_next
;
899 n_err(_("Message list: invalid syntax: %s (in %s)\n"),
900 n_shexp_quote_cp(cp
, FAL0
),
901 n_shexp_quote_cp(mslp
->msl_input_orig
, FAL0
));
905 rv
= a_MSG_T_DOT
| INT_MIN
;
912 /* If the leading character is a digit, scan the number and convert it
913 * on the fly. Return a_MSG_T_NUMBER when done */
917 mslp
->msl_no
= (mslp
->msl_no
* 10) + c
- '0'; /* XXX inline atoi */
918 while((c
= *cp
++, digitchar(c
)));
921 mslp
->msl_cap
= mslp
->msl_cap
->ca_next
;
924 /* This could be a range */
926 mslp
->msl_cap
->ca_arg
.ca_str
.s
= cp
;
930 rv
|= a_MSG_T_NUMBER
;
934 /* An IMAP SEARCH list. Note that a_MSG_T_OPEN has always been included
935 * in singles[] in Mail and mailx. Thus although there is no formal
936 * definition for (LIST) lists, they do not collide with historical
937 * practice because a subject string (LIST) could never been matched
944 (tocp
= mslp
->msl_str
= mslp
->msl_cap
->ca_arg
.ca_str
.s
)[0] = '(';
949 if ((c
= *cp
++) == '\0') {
951 n_err(_("Missing )\n"));
952 n_err(_("P.S.: message specifications are now shell tokens, "
953 "making it necessary to enclose IMAP search expressions "
954 "in (single) quotes, e.g., '(from \"me\")'\n"));
958 if (inquote
&& c
== '\\') {
971 else if (spacechar(c
)) {
972 /* Replace unquoted whitespace by single space characters, to make
973 * the string IMAP SEARCH conformant */
979 } while (c
!= ')' || level
> 0);
983 mslp
->msl_cap
= mslp
->msl_cap
->ca_next
;
988 /* Check for single character tokens; return such if found */
989 for(lp
= a_msg_singles
;
990 PTRCMP(lp
, <, &a_msg_singles
[n_NELEM(a_msg_singles
)]); ++lp
){
991 if(c
== lp
->ml_char
){
992 mslp
->msl_str
= mslp
->msl__smallstrbuf
;
993 mslp
->msl_str
[0] = c
;
994 mslp
->msl_str
[1] = '\0';
997 mslp
->msl_cap
= mslp
->msl_cap
->ca_next
;
1003 mslp
->msl_cap
= mslp
->msl_cap
->ca_next
;
1004 mslp
->msl_str
= --cp
;
1005 rv
= a_MSG_T_STRING
;
1012 a_msg_match_sender(struct message
*mp
, char const *str
, bool_t allnet
){
1013 char const *str_base
, *np_base
, *np
;
1021 /* Empty string doesn't match */
1022 namep
= lextract(n_header_senderfield_of(mp
), GFULL
| GSKIN
);
1024 if(namep
== NULL
|| *(str_base
= str
) == '\0')
1027 /* *allnet* is POSIX and, since it explicitly mentions login and user names,
1028 * most likely case-sensitive. XXX Still allow substr matching, though
1029 * XXX possibly the first letter should be case-insensitive, then? */
1031 for(; namep
!= NULL
; str
= str_base
, namep
= namep
->n_flink
){
1032 for(np_base
= np
= namep
->n_name
;;){
1033 if((sc
= *str
++) == '@')
1035 if((nc
= *np
++) == '@' || nc
== '\0' || sc
== '\0'){
1036 if((rv
= (sc
== '\0')))
1047 /* TODO POSIX says ~"match any address as shown in header overview",
1048 * TODO but a normalized match would be more sane i guess.
1049 * TODO struct name should gain a comparison method, normalize realname
1050 * TODO content (in TODO) and thus match as likewise
1051 * TODO "Buddy (Today) <here>" and "(Now) Buddy <here>" */
1052 bool_t again_base
, again
;
1054 again_base
= ok_blook(showname
);
1056 for(; namep
!= NULL
; str
= str_base
, namep
= namep
->n_flink
){
1059 np_base
= np
= again
? namep
->n_fullname
: namep
->n_name
;
1063 if((nc
= *np
++) == '\0' || sc
== '\0'){
1064 if((rv
= (sc
== '\0')))
1076 /* And really if i want to match 'on@' then i want it to match even if
1077 * *showname* is set! */
1090 a_msg_match_mid(struct message
*mp
, char const *id
,
1091 enum a_msg_idfield idfield
){
1098 if((cp
= hfield1("message-id", mp
)) != NULL
){
1100 case a_MSG_ID_REFERENCES
:
1101 if(!msgidcmp(id
, cp
))
1104 case a_MSG_ID_IN_REPLY_TO
:{
1107 if((np
= extract(id
, GREF
)) != NULL
)
1109 if(!msgidcmp(np
->n_name
, cp
)){
1113 }while((np
= np
->n_flink
) != NULL
);
1123 a_msg_match_dash(struct message
*mp
, char const *str
){
1124 static char lastscan
[128];
1127 char *hfield
, *hbody
;
1136 n_strscpy(lastscan
, str
, sizeof lastscan
); /* XXX use new n_str object! */
1138 /* Now look, ignoring case, for the word in the string */
1139 if(ok_blook(searchheaders
) && (hfield
= strchr(str
, ':'))){
1142 l
= PTR2SIZE(hfield
- str
);
1143 hfield
= n_lofi_alloc(l
+1);
1144 memcpy(hfield
, str
, l
);
1146 hbody
= hfieldX(hfield
, mp
);
1147 n_lofi_free(hfield
);
1148 hfield
= n_UNCONST(str
+ l
+ 1);
1150 hfield
= n_UNCONST(str
);
1151 hbody
= hfield1("subject", mp
);
1156 in
.l
= strlen(in
.s
= hbody
);
1157 mime_fromhdr(&in
, &out
, TD_ICONV
);
1158 rv
= substr(out
.s
, hfield
);
1166 a_msg_match_at(struct message
*mp
, struct search_expr
*sep
){
1171 /* Namelist regex only matches headers.
1172 * And there are the special cases header/<, "body"/> and "text"/=, the
1173 * latter two of which need to be handled in here */
1174 if((field
= sep
->ss_field
) != NULL
){
1175 if(!asccasecmp(field
, "body") || (field
[1] == '\0' && field
[0] == '>')){
1178 rv
= message_match(mp
, sep
, rv
);
1180 }else if(!asccasecmp(field
, "text") ||
1181 (field
[1] == '\0' && field
[0] == '=')){
1187 rv
= n_header_match(mp
, sep
);
1194 a_msg_unmark(int mesg
){
1199 if(i
< 1 || UICMP(z
, i
, >, msgCount
))
1200 n_panic(_("Bad message number to unmark"));
1201 message
[--i
].m_flag
&= ~MMARK
;
1206 a_msg_metamess(int meta
, int f
)
1214 case '^': /* First 'good' message left */
1215 mp
= mb
.mb_threaded
? threadroot
: message
;
1216 while (PTRCMP(mp
, <, message
+ msgCount
)) {
1217 if (!(mp
->m_flag
& MHIDDEN
) && (mp
->m_flag
& MDELETED
) == (ui32_t
)f
) {
1218 c
= (int)PTR2SIZE(mp
- message
+ 1);
1221 if (mb
.mb_threaded
) {
1222 mp
= next_in_thread(mp
);
1228 if (!(n_pstate
& n_PS_HOOK_MASK
))
1229 n_err(_("No applicable messages\n"));
1232 case '$': /* Last 'good message left */
1234 ? this_in_thread(threadroot
, -1) : message
+ msgCount
- 1;
1235 while (mp
>= message
) {
1236 if (!(mp
->m_flag
& MHIDDEN
) && (mp
->m_flag
& MDELETED
) == (ui32_t
)f
) {
1237 c
= (int)PTR2SIZE(mp
- message
+ 1);
1240 if (mb
.mb_threaded
) {
1241 mp
= prev_in_thread(mp
);
1247 if (!(n_pstate
& n_PS_HOOK_MASK
))
1248 n_err(_("No applicable messages\n"));
1252 /* Current message */
1253 m
= dot
- message
+ 1;
1254 if ((dot
->m_flag
& MHIDDEN
) || (dot
->m_flag
& MDELETED
) != (ui32_t
)f
) {
1255 n_err(_("%d: inappropriate message\n"), m
);
1262 /* Previously current message */
1263 if (prevdot
== NULL
) {
1264 n_err(_("No previously current message\n"));
1267 m
= prevdot
- message
+ 1;
1268 if ((prevdot
->m_flag
& MHIDDEN
) ||
1269 (prevdot
->m_flag
& MDELETED
) != (ui32_t
)f
) {
1270 n_err(_("%d: inappropriate message\n"), m
);
1277 n_err(_("Unknown selector: %c\n"), c
);
1289 a_msg__threadmark(struct message
*self
, int f
){
1291 if(!(self
->m_flag
& MHIDDEN
) &&
1292 (f
== MDELETED
|| !(self
->m_flag
& MDELETED
) || a_msg_list_saw_d
))
1293 self
->m_flag
|= MMARK
;
1295 if((self
= self
->m_child
) != NULL
){
1297 while((self
= self
->m_younger
) != NULL
)
1298 if(self
->m_child
!= NULL
)
1300 a_msg__threadmark(self
, f
);
1302 self
->m_flag
|= MMARK
;
1308 setinput(struct mailbox
*mp
, struct message
*m
, enum needspec need
){
1317 ok
= (m
->m_content_info
& CI_HAVE_HEADER
) ? OKAY
1318 : a_msg_get_header(m
);
1321 ok
= (m
->m_content_info
& CI_HAVE_BODY
) ? OKAY
: get_body(m
);
1332 if(fseek(mp
->mb_itf
, (long)mailx_positionof(m
->m_block
, m
->m_offset
),
1334 n_perr(_("fseek"), 0);
1335 n_panic(_("temporary file seek"));
1344 get_body(struct message
*mp
){
1375 message_reset(void){
1377 if(message
!= NULL
){
1382 a_msg_mem_space
= 0;
1387 message_append(struct message
*mp
){
1389 if(UICMP(z
, msgCount
+ 1, >=, a_msg_mem_space
)){
1390 /* XXX remove _mem_space magics (or use s_Vector) */
1391 a_msg_mem_space
= ((a_msg_mem_space
>= 128 &&
1392 a_msg_mem_space
<= 1000000)
1393 ? a_msg_mem_space
<< 1 : a_msg_mem_space
+ 64);
1394 message
= n_realloc(message
, a_msg_mem_space
* sizeof(*message
));
1398 message
[msgCount
- 1] = *mp
;
1400 memset(&message
[msgCount
- 1], 0, sizeof *message
);
1406 message_append_null(void){
1409 message_append(NULL
);
1411 message
[msgCount
].m_size
= 0;
1412 message
[msgCount
].m_lines
= 0;
1417 message_match(struct message
*mp
, struct search_expr
const *sep
,
1418 bool_t with_headers
){
1420 size_t *linesize
, cnt
;
1427 if((fp
= Ftmp(NULL
, "mpmatch", OF_RDWR
| OF_UNLINK
| OF_REGISTER
)) == NULL
)
1430 if(sendmp(mp
, fp
, NULL
, NULL
, SEND_TOSRCH
, NULL
) < 0)
1435 line
= &termios_state
.ts_linebuf
; /* XXX line pool */
1436 linesize
= &termios_state
.ts_linesize
; /* XXX line pool */
1439 while(fgetline(line
, linesize
, &cnt
, NULL
, fp
, 0))
1443 while(fgetline(line
, linesize
, &cnt
, NULL
, fp
, 0)){
1445 if(sep
->ss_bodyre
!= NULL
){
1446 if(regexec(sep
->ss_bodyre
, *line
, 0,NULL
, 0) == REG_NOMATCH
)
1450 if(!substr(*line
, sep
->ss_body
))
1464 setdot(struct message
*mp
){
1468 n_pstate
&= ~n_PS_DID_PRINT_DOT
;
1471 uncollapse1(dot
, 0);
1477 touch(struct message
*mp
){
1479 mp
->m_flag
|= MTOUCH
;
1480 if(!(mp
->m_flag
& MREAD
))
1481 mp
->m_flag
|= MREAD
| MSTATUS
;
1486 n_getmsglist(char const *buf
, int *vector
, int flags
,
1487 struct n_cmd_arg
**capp_or_null
)
1493 n_pstate
&= ~n_PS_ARGLIST_MASK
;
1494 n_pstate
|= n_PS_MSGLIST_DIRECT
;
1495 a_msg_list_last_saw_d
= a_msg_list_saw_d
;
1496 a_msg_list_saw_d
= FAL0
;
1499 if(capp_or_null
!= NULL
)
1500 *capp_or_null
= NULL
;
1506 /* TODO Parse the message spec into an ARGV; this should not happen here,
1507 * TODO but instead cmd_arg_parse() should feed in the list of parsed tokens
1508 * TODO to getmsglist(); as of today there are multiple getmsglist() users
1509 * TODO though, and they need to deal with that, then, too */
1511 n_CMD_ARG_DESC_SUBCLASS_DEF(getmsglist
, 1, pseudo_cad
){
1512 {n_CMD_ARG_DESC_SHEXP
| n_CMD_ARG_DESC_OPTION
|
1513 n_CMD_ARG_DESC_GREEDY
| n_CMD_ARG_DESC_HONOUR_STOP
,
1514 n_SHEXP_PARSE_TRIM_IFSSPACE
| n_SHEXP_PARSE_IFS_VAR
|
1515 n_SHEXP_PARSE_IGNORE_EMPTY
}
1516 }n_CMD_ARG_DESC_SUBCLASS_DEF_END
;
1517 struct n_cmd_arg_ctx cac
;
1519 cac
.cac_desc
= n_CMD_ARG_DESC_SUBCLASS_CAST(&pseudo_cad
);
1520 cac
.cac_indat
= buf
;
1521 cac
.cac_inlen
= UIZ_MAX
;
1522 cac
.cac_msgflag
= flags
;
1523 cac
.cac_msgmask
= 0;
1524 if(!n_cmd_arg_parse(&cac
)){
1527 }else if(cac
.cac_no
== 0){
1531 /* Is this indeed a (maybe optional) message list and a target? */
1532 if(capp_or_null
!= NULL
){
1533 struct n_cmd_arg
*cap
, **lcapp
;
1535 if((cap
= cac
.cac_arg
)->ca_next
== NULL
){
1536 *capp_or_null
= cap
;
1541 lcapp
= &cap
->ca_next
;
1542 if((cap
= *lcapp
)->ca_next
== NULL
)
1545 *capp_or_null
= cap
;
1548 /* In the list-and-target mode we have to take special care, since
1549 * some commands use special call conventions historically (use the
1550 * MBOX, search for a message, whatever).
1551 * Thus, to allow things like "certsave '' bla" or "save '' ''",
1552 * watch out for two argument form with empty token first.
1553 * This special case is documented at the prototype */
1554 if(cac
.cac_arg
->ca_next
== NULL
&&
1555 cac
.cac_arg
->ca_arg
.ca_str
.s
[0] == '\0'){
1564 }else if((mc
= a_msg_markall(buf
, cac
.cac_arg
, flags
)) < 0){
1572 if(n_pstate
& n_PS_HOOK_NEWMAIL
){
1574 for(mp
= message
; mp
< &message
[msgCount
]; ++mp
)
1575 if(mp
->m_flag
& MMARK
){
1576 if(!(mp
->m_flag
& MNEWEST
))
1577 a_msg_unmark((int)PTR2SIZE(mp
- message
+ 1));
1587 if(mb
.mb_threaded
== 0){
1588 for(mp
= message
; mp
< &message
[msgCount
]; ++mp
)
1589 if(mp
->m_flag
& MMARK
)
1590 *ip
++ = (int)PTR2SIZE(mp
- message
+ 1);
1592 for(mp
= threadroot
; mp
!= NULL
; mp
= next_in_thread(mp
))
1593 if(mp
->m_flag
& MMARK
)
1594 *ip
++ = (int)PTR2SIZE(mp
- message
+ 1);
1597 mc
= (int)PTR2SIZE(ip
- vector
);
1599 n_pstate
&= ~n_PS_MSGLIST_DIRECT
;
1612 if (msgCount
== 0) {
1620 mb
.mb_threaded
? (mp
!= NULL
) : PTRCMP(mp
, <, message
+ msgCount
);
1621 mb
.mb_threaded
? (mp
= next_in_thread(mp
)) : ++mp
) {
1622 if (!(mp
->m_flag
& MHIDDEN
) && (mp
->m_flag
& m
) == (ui32_t
)f
) {
1623 rv
= (int)PTR2SIZE(mp
- message
+ 1);
1628 if (dot
> message
) {
1629 for (mp
= dot
- 1; (mb
.mb_threaded
? (mp
!= NULL
) : (mp
>= message
));
1630 mb
.mb_threaded
? (mp
= prev_in_thread(mp
)) : --mp
) {
1631 if (!(mp
->m_flag
& MHIDDEN
) && (mp
->m_flag
& m
) == (ui32_t
)f
) {
1632 rv
= (int)PTR2SIZE(mp
- message
+ 1);
1644 mark(int mno
, int f
){
1650 if(i
< 1 || i
> msgCount
)
1651 n_panic(_("Bad message number to mark"));
1654 if(mb
.mb_threaded
== 1 && a_msg_threadflag
)
1655 a_msg__threadmark(mp
, f
);
1657 assert(!(mp
->m_flag
& MHIDDEN
));
1658 mp
->m_flag
|= MMARK
;