nail.h: fix sorting of #include<inttypes.h>
[s-mailx.git] / message.c
blob982542ef9a7c5bcd68bf6597c282ed5f6046fbb9
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 - 2017 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
6 */
7 /*
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
13 * are met:
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
33 * SUCH DAMAGE.
35 #undef n_FILE
36 #define n_FILE message
38 #ifndef HAVE_AMALGAMATION
39 # include "nail.h"
40 #endif
42 /* Token values returned by the scanner used for argument lists.
43 * Also, sizes of scanner-related things */
44 enum a_message_token{
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
67 enum a_message_state{
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 : */
84 ui8_t mco__dummy[3];
85 int mco_bit; /* Associated modifier bit */
86 int mco_mask; /* m_status bits to mask */
87 int mco_equal; /* ... must equal this */
90 struct a_message_lex{
91 char ml_char;
92 ui8_t ml_token;
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,
166 bool_t allnet);
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);
191 static enum okay
192 a_message_get_header(struct message *mp){
193 enum okay rv;
194 NYD2_ENTER;
195 n_UNUSED(mp);
197 switch(mb.mb_type){
198 case MB_FILE:
199 case MB_MAILDIR:
200 rv = OKAY;
201 break;
202 #ifdef HAVE_POP3
203 case MB_POP3:
204 rv = pop3_header(mp);
205 break;
206 #endif
207 #ifdef HAVE_IMAP
208 case MB_IMAP:
209 case MB_CACHE:
210 rv = imap_header(mp);
211 break;
212 #endif
213 case MB_VOID:
214 default:
215 rv = STOP;
216 break;
218 NYD2_LEAVE;
219 return rv;
222 static char **
223 a_message_add_to_namelist(char ***namelist, size_t *nmlsize, /* TODO Vector */
224 char **np, char *string){
225 size_t idx;
226 NYD2_ENTER;
228 if((idx = PTR2SIZE(np - *namelist)) >= *nmlsize){
229 *namelist = srealloc(*namelist, (*nmlsize += 8) * sizeof *np);
230 np = &(*namelist)[idx];
232 *np++ = string;
233 NYD2_LEAVE;
234 return np;
237 static int
238 a_message_markall(char const *buf, int f){
239 struct message *mp, *mx;
240 enum a_message_idfield idfield;
241 size_t j, nmlsize;
242 char const *id, *bufp;
243 char **np, **nq, **namelist, *cp;
244 int i, valdot, beg, colmod, tok, colresult;
245 enum{
246 a_NONE = 0,
247 a_ALLNET = 1u<<0, /* Must be TRU1 */
248 a_ALLOC = 1u<<1, /* Have allocated something */
249 a_THREADED = 1u<<2,
250 a_ERROR = 1u<<3,
251 a_ANY = 1u<<4, /* Have marked just ANY */
252 a_RANGE = 1u<<5, /* Seen dash, await close */
253 a_ASTER = 1u<<8,
254 a_TOPEN = 1u<<9, /* ( used (and didn't match) */
255 a_TBACK = 1u<<10, /* ` used (and didn't match) */
256 #ifdef HAVE_IMAP
257 a_HAVE_IMAP_HEADERS = 1u<<14,
258 #endif
259 a_TMP = 1u<<15
260 } flags;
261 NYD_ENTER;
262 n_LCTA((ui32_t)a_ALLNET == (ui32_t)TRU1,
263 "Constant is converted to bool_t via AND, thus");
265 /* Update message array: clear MMARK but remember its former state for ` */
266 for(i = msgCount; i-- > 0;){
267 enum mflag mf;
269 mf = (mp = &message[i])->m_flag;
270 if(mf & MMARK)
271 mf |= MOLDMARK;
272 else
273 mf &= ~MOLDMARK;
274 mf &= ~MMARK;
275 mp->m_flag = mf;
278 /* Strip all leading WS from user buffer */
279 while(blankspacechar(*buf))
280 ++buf;
281 /* If there is no input buffer, we are done! */
282 if(buf[0] == '\0'){
283 flags = a_NONE;
284 goto jleave;
287 n_UNINIT(beg, 0);
288 n_UNINIT(idfield, a_MESSAGE_ID_REFERENCES);
289 a_message_threadflag = FAL0;
290 a_message_lexstr.s = ac_alloc(a_message_lexstr.l = 2 * strlen(buf) +1);
291 np = namelist = smalloc((nmlsize = 8) * sizeof *namelist); /* TODO vector */
292 bufp = buf;
293 valdot = (int)PTR2SIZE(dot - message + 1);
294 colmod = 0;
295 id = NULL;
296 flags = a_ALLOC | (mb.mb_threaded ? a_THREADED : 0);
298 while((tok = a_message_scan(&bufp)) != a_MESSAGE_T_EOL){
299 if((a_message_threadflag = (tok < 0)))
300 tok &= INT_MAX;
302 switch(tok){
303 case a_MESSAGE_T_NUMBER:
304 n_pstate |= n_PS_MSGLIST_GABBY;
305 jnumber:
306 if(!a_message_check(a_message_lexno, f))
307 goto jerr;
309 if(flags & a_RANGE){
310 flags ^= a_RANGE;
312 if(!(flags & a_THREADED)){
313 if(beg < a_message_lexno)
314 i = beg;
315 else{
316 i = a_message_lexno;
317 a_message_lexno = beg;
320 for(; i <= a_message_lexno; ++i){
321 mp = &message[i - 1];
322 if(!(mp->m_flag & MHIDDEN) &&
323 (f == MDELETED || !(mp->m_flag & MDELETED))){
324 mark(i, f);
325 flags |= a_ANY;
328 }else{
329 /* TODO threaded ranges are a mess */
330 enum{
331 a_T_NONE,
332 a_T_HOT = 1u<<0,
333 a_T_DIR_PREV = 1u<<1
334 } tf;
335 int i_base;
337 if(beg < a_message_lexno)
338 i = beg;
339 else{
340 i = a_message_lexno;
341 a_message_lexno = beg;
344 i_base = i;
345 tf = a_T_NONE;
346 jnumber__thr:
347 for(;;){
348 mp = &message[i - 1];
349 if(!(mp->m_flag & MHIDDEN) &&
350 (f == MDELETED || !(mp->m_flag & MDELETED))){
351 if(tf & a_T_HOT){
352 mark(i, f);
353 flags |= a_ANY;
357 /* We may have reached the endpoint. If we were still
358 * detecting the direction to search for it, restart.
359 * Otherwise finished */
360 if(i == a_message_lexno){ /* XXX */
361 if(!(tf & a_T_HOT)){
362 tf |= a_T_HOT;
363 i = i_base;
364 goto jnumber__thr;
366 break;
369 mx = (tf & a_T_DIR_PREV) ? prev_in_thread(mp)
370 : next_in_thread(mp);
371 if(mx == NULL){
372 /* We anyway have failed to reach the endpoint in this
373 * direction; if we already switched that, report error */
374 if(!(tf & a_T_DIR_PREV)){
375 tf |= a_T_DIR_PREV;
376 i = i_base;
377 goto jnumber__thr;
379 id = N_("Range crosses multiple threads\n");
380 goto jerrmsg;
382 i = (int)PTR2SIZE(mx - message + 1);
386 beg = 0;
387 }else{
388 /* Could be an inclusive range? */
389 if(bufp[0] == '-'){
390 ++bufp;
391 beg = a_message_lexno;
392 flags |= a_RANGE;
393 }else{
394 mark(a_message_lexno, f);
395 flags |= a_ANY;
398 break;
399 case a_MESSAGE_T_PLUS:
400 n_pstate &= ~n_PS_MSGLIST_DIRECT;
401 n_pstate |= n_PS_MSGLIST_GABBY;
402 i = valdot;
404 if(flags & a_THREADED){
405 mx = next_in_thread(&message[i - 1]);
406 i = mx ? (int)PTR2SIZE(mx - message + 1) : msgCount + 1;
407 }else
408 ++i;
409 if(i > msgCount){
410 id = N_("Referencing beyond last message\n");
411 goto jerrmsg;
413 }while(message[i - 1].m_flag == MHIDDEN ||
414 (message[i - 1].m_flag & MDELETED) != (unsigned)f);
415 a_message_lexno = i;
416 goto jnumber;
417 case a_MESSAGE_T_MINUS:
418 n_pstate &= ~n_PS_MSGLIST_DIRECT;
419 n_pstate |= n_PS_MSGLIST_GABBY;
420 i = valdot;
422 if(flags & a_THREADED){
423 mx = prev_in_thread(&message[i - 1]);
424 i = mx ? (int)PTR2SIZE(mx - message + 1) : 0;
425 }else
426 --i;
427 if(i <= 0){
428 id = N_("Referencing before first message\n");
429 goto jerrmsg;
431 }while(message[i - 1].m_flag == MHIDDEN ||
432 (message[i - 1].m_flag & MDELETED) != (unsigned)f);
433 a_message_lexno = i;
434 goto jnumber;
435 case a_MESSAGE_T_STRING:
436 n_pstate &= ~n_PS_MSGLIST_DIRECT;
437 if(flags & a_RANGE)
438 goto jebadrange;
440 /* This may be a colon modifier */
441 if((cp = a_message_lexstr.s)[0] != ':')
442 np = a_message_add_to_namelist(&namelist, &nmlsize, np,
443 savestr(a_message_lexstr.s));
444 else{
445 while(*++cp != '\0'){
446 colresult = a_message_evalcol(*cp);
447 if(colresult == 0){
448 n_err(_("Unknown colon modifier: %s\n"), a_message_lexstr.s);
449 goto jerr;
451 if(colresult == a_MESSAGE_S_DELETED){
452 a_message_list_saw_d = TRU1;
453 f |= MDELETED;
455 colmod |= colresult;
458 break;
459 case a_MESSAGE_T_OPEN:
460 n_pstate &= ~n_PS_MSGLIST_DIRECT;
461 if(flags & a_RANGE)
462 goto jebadrange;
463 flags |= a_TOPEN;
465 #ifdef HAVE_IMAP_SEARCH
466 /* C99 */{
467 ssize_t ires;
469 if((ires = imap_search(a_message_lexstr.s, f)) >= 0){
470 if(ires > 0)
471 flags |= a_ANY;
472 break;
475 #else
476 n_err(_("Optional selector is not available: %s\n"),
477 a_message_lexstr.s);
478 #endif
479 goto jerr;
480 case a_MESSAGE_T_DOLLAR:
481 case a_MESSAGE_T_UP:
482 case a_MESSAGE_T_SEMI:
483 n_pstate |= n_PS_MSGLIST_GABBY;
484 /* FALLTHRU */
485 case a_MESSAGE_T_DOT: /* Don't set _GABBY for dot, to _allow_ history.. */
486 n_pstate &= ~n_PS_MSGLIST_DIRECT;
487 a_message_lexno = a_message_metamess(a_message_lexstr.s[0], f);
488 if(a_message_lexno == -1)
489 goto jerr;
490 goto jnumber;
491 case a_MESSAGE_T_BACK:
492 n_pstate &= ~n_PS_MSGLIST_DIRECT;
493 if(flags & a_RANGE)
494 goto jebadrange;
496 flags |= a_TBACK;
497 for(i = 0; i < msgCount; ++i){
498 if((mp = &message[i])->m_flag & MHIDDEN)
499 continue;
500 if((mp->m_flag & MDELETED) != (unsigned)f){
501 if(!a_message_list_last_saw_d)
502 continue;
503 a_message_list_saw_d = TRU1;
505 if(mp->m_flag & MOLDMARK){
506 mark(i + 1, f);
507 flags &= ~a_TBACK;
508 flags |= a_ANY;
511 break;
512 case a_MESSAGE_T_ASTER:
513 n_pstate &= ~n_PS_MSGLIST_DIRECT;
514 if(flags & a_RANGE)
515 goto jebadrange;
516 flags |= a_ASTER;
517 break;
518 case a_MESSAGE_T_COMMA:
519 n_pstate &= ~n_PS_MSGLIST_DIRECT;
520 n_pstate |= n_PS_MSGLIST_GABBY;
521 if(flags & a_RANGE)
522 goto jebadrange;
524 #ifdef HAVE_IMAP
525 if(!(flags & a_HAVE_IMAP_HEADERS) && mb.mb_type == MB_IMAP){
526 flags |= a_HAVE_IMAP_HEADERS;
527 imap_getheaders(1, msgCount);
529 #endif
531 if(id == NULL){
532 if((cp = hfield1("in-reply-to", dot)) != NULL)
533 idfield = a_MESSAGE_ID_IN_REPLY_TO;
534 else if((cp = hfield1("references", dot)) != NULL){
535 struct name *enp;
537 if((enp = extract(cp, GREF)) != NULL){
538 while(enp->n_flink != NULL)
539 enp = enp->n_flink;
540 cp = enp->n_name;
541 idfield = a_MESSAGE_ID_REFERENCES;
542 }else
543 cp = NULL;
546 if(cp != NULL)
547 id = savestr(cp);
548 else{
549 id = N_("Message-ID of parent of \"dot\" is indeterminable\n");
550 goto jerrmsg;
552 }else if(!(n_pstate & n_PS_HOOK) && (n_poption & n_PO_D_V))
553 n_err(_("Ignoring redundant specification of , selector\n"));
554 break;
555 case a_MESSAGE_T_ERROR:
556 n_pstate &= ~n_PS_MSGLIST_DIRECT;
557 n_pstate |= n_PS_MSGLIST_GABBY;
558 goto jerr;
561 /* Explicitly disallow invalid ranges for future safety */
562 if(bufp[0] == '-' && !(flags & a_RANGE)){
563 if(!(n_pstate & n_PS_HOOK))
564 n_err(_("Ignoring invalid range before: %s\n"), bufp);
565 ++bufp;
568 if(flags & a_RANGE){
569 id = N_("Missing second range argument\n");
570 goto jerrmsg;
573 np = a_message_add_to_namelist(&namelist, &nmlsize, np, NULL);
574 --np;
576 /* * is special at this point, after we have parsed the entire line */
577 if(flags & a_ASTER){
578 for(i = 0; i < msgCount; ++i){
579 if((mp = &message[i])->m_flag & MHIDDEN)
580 continue;
581 if(!a_message_list_saw_d && (mp->m_flag & MDELETED) != (unsigned)f)
582 continue;
583 mark(i + 1, f);
584 flags |= a_ANY;
586 if(!(flags & a_ANY))
587 goto jenoapp;
588 goto jleave;
591 /* If any names were given, add any messages which match */
592 if(np > namelist || id != NULL){
593 struct search_expr *sep = NULL;
595 /* The @ search works with struct search_expr, so build an array.
596 * To simplify array, i.e., regex_t destruction, and optimize for the
597 * common case we walk the entire array even in case of errors */
598 if(np > namelist){
599 sep = scalloc(PTR2SIZE(np - namelist), sizeof(*sep));
600 for(j = 0, nq = namelist; *nq != NULL; ++j, ++nq){
601 char *x, *y;
603 sep[j].ss_sexpr = x = *nq;
604 if(*x != '@' || (flags & a_ERROR))
605 continue;
607 for(y = &x[1];; ++y){
608 if(*y == '\0' || !fieldnamechar(*y)){
609 x = NULL;
610 break;
612 if(*y == '@'){
613 x = y;
614 break;
617 sep[j].ss_where = (x == NULL || x - 1 == *nq)
618 ? "subject" : savestrbuf(&(*nq)[1], PTR2SIZE(x - *nq) - 1);
620 x = (x == NULL ? *nq : x) + 1;
621 if(*x == '\0'){ /* XXX Simply remove from list instead? */
622 n_err(_("Empty [@..]@ search expression\n"));
623 flags |= a_ERROR;
624 continue;
626 #ifdef HAVE_REGEX
627 if(n_is_maybe_regex(x)){
628 int s;
630 sep[j].ss_sexpr = NULL;
631 if((s = regcomp(&sep[j].ss_regex, x,
632 REG_EXTENDED | REG_ICASE | REG_NOSUB)) != 0){
633 if(!(n_pstate & n_PS_HOOK))
634 n_err(_("Invalid regular expression: %s: %s\n"),
635 n_shexp_quote_cp(x, FAL0),
636 n_regex_err_to_doc(NULL, s));
637 flags |= a_ERROR;
638 continue;
640 }else
641 #endif
642 sep[j].ss_sexpr = x;
644 if(flags & a_ERROR)
645 goto jnamesearch_sepfree;
648 /* Iterate the entire message array */
649 #ifdef HAVE_IMAP
650 if(!(flags & a_HAVE_IMAP_HEADERS) && mb.mb_type == MB_IMAP){
651 flags |= a_HAVE_IMAP_HEADERS;
652 imap_getheaders(1, msgCount);
654 #endif
655 srelax_hold();
656 if(ok_blook(allnet))
657 flags |= a_ALLNET;
658 for(i = 0; i < msgCount; ++i){
659 if((mp = &message[i])->m_flag & (MMARK | MHIDDEN))
660 continue;
661 if(!a_message_list_saw_d && (mp->m_flag & MDELETED) != (unsigned)f)
662 continue;
664 flags &= ~a_TMP;
665 if(np > namelist){
666 for(nq = namelist; *nq != NULL; ++nq){
667 if(**nq == '@'){
668 if(a_message_match_at(mp, sep + PTR2SIZE(nq - namelist))){
669 flags |= a_TMP;
670 break;
672 }else if(**nq == '/'){
673 if(a_message_match_dash(mp, *nq)){
674 flags |= a_TMP;
675 break;
677 }else if(a_message_match_sender(mp, *nq, (flags & a_ALLNET))){
678 flags |= a_TMP;
679 break;
683 if(!(flags & a_TMP) &&
684 id != NULL && a_message_match_mid(mp, id, idfield))
685 flags |= a_TMP;
687 if(flags & a_TMP){
688 mark(i + 1, f);
689 flags |= a_ANY;
691 srelax();
693 srelax_rele();
695 jnamesearch_sepfree:
696 if(sep != NULL){
697 #ifdef HAVE_REGEX
698 for(j = PTR2SIZE(np - namelist); j-- != 0;)
699 if(sep[j].ss_sexpr == NULL)
700 regfree(&sep[j].ss_regex);
701 #endif
702 free(sep);
704 if(flags & a_ERROR)
705 goto jerr;
708 /* If any colon modifiers were given, go through and mark any messages which
709 * do satisfy the modifiers */
710 if(colmod != 0){
711 for(i = 0; i < msgCount; ++i){
712 struct a_message_coltab const *colp;
714 if((mp = &message[i])->m_flag & (MMARK | MHIDDEN))
715 continue;
716 if(!a_message_list_saw_d && (mp->m_flag & MDELETED) != (unsigned)f)
717 continue;
719 for(colp = a_message_coltabs;
720 PTRCMP(colp, <, &a_message_coltabs[n_NELEM(a_message_coltabs)]);
721 ++colp)
722 if(colp->mco_bit & colmod){
723 /* Is this a colon modifier that requires evaluation? */
724 if(colp->mco_mask == 0){
725 if(colp->mco_bit & (a_MESSAGE_S_MLIST |
726 a_MESSAGE_S_MLSUBSCRIBE)){
727 enum mlist_state what;
729 what = (colp->mco_bit & a_MESSAGE_S_MLIST) ? MLIST_KNOWN
730 : MLIST_SUBSCRIBED;
731 if(what == is_mlist_mp(mp, what))
732 goto jcolonmod_mark;
734 }else if((mp->m_flag & colp->mco_mask
735 ) == (enum mflag)colp->mco_equal){
736 jcolonmod_mark:
737 mark(i + 1, f);
738 flags |= a_ANY;
739 break;
745 /* It shall be an error if ` didn't match anything, and nothing else did */
746 if((flags & (a_TBACK | a_ANY)) == a_TBACK){
747 id = N_("No previously marked messages\n");
748 goto jerrmsg;
749 }else if(!(flags & a_ANY))
750 goto jenoapp;
752 assert(!(flags & a_ERROR));
753 jleave:
754 if(flags & a_ALLOC){
755 free(namelist);
756 ac_free(a_message_lexstr.s);
758 NYD_LEAVE;
759 return (flags & a_ERROR) ? -1 : 0;
761 jebadrange:
762 id = N_("Invalid range endpoint\n");
763 goto jerrmsg;
764 jenoapp:
765 id = N_("No applicable messages\n");
766 jerrmsg:
767 if(!(n_pstate & n_PS_HOOK_MASK))
768 n_err(V_(id));
769 jerr:
770 flags |= a_ERROR;
771 goto jleave;
774 static int
775 a_message_evalcol(int col){
776 struct a_message_coltab const *colp;
777 int rv;
778 NYD2_ENTER;
780 rv = 0;
781 for(colp = a_message_coltabs;
782 PTRCMP(colp, <, &a_message_coltabs[n_NELEM(a_message_coltabs)]);
783 ++colp)
784 if(colp->mco_char == col){
785 rv = colp->mco_bit;
786 break;
788 NYD2_LEAVE;
789 return rv;
792 static bool_t
793 a_message_check(int mno, int f){
794 struct message *mp;
795 NYD2_ENTER;
797 if(mno < 1 || mno > msgCount){
798 n_err(_("%d: Invalid message number\n"), mno);
799 mno = 1;
800 }else if(((mp = &message[mno - 1])->m_flag & MHIDDEN) ||
801 (f != MDELETED && (mp->m_flag & MDELETED) != 0))
802 n_err(_("%d: inappropriate message\n"), mno);
803 else
804 mno = 0;
805 NYD2_LEAVE;
806 return (mno == 0);
809 static int
810 a_message_scan(char const **sp)
812 struct a_message_lex const *lp;
813 char *cp2;
814 char const *cp;
815 int rv, c, inquote, quotec;
816 NYD_ENTER;
818 rv = a_MESSAGE_T_EOL;
820 cp = *sp;
821 cp2 = a_message_lexstr.s;
822 c = *cp++;
824 /* strip away leading white space */
825 while(blankchar(c))
826 c = *cp++;
828 /* If no characters remain, we are at end of line, so report that */
829 if(c == '\0'){
830 *sp = --cp;
831 goto jleave;
834 /* Select members of a message thread */
835 if(c == '&'){
836 if(*cp == '\0' || spacechar(*cp)){
837 a_message_lexstr.s[0] = '.';
838 a_message_lexstr.s[1] = '\0';
839 *sp = cp;
840 rv = a_MESSAGE_T_DOT | INT_MIN;
841 goto jleave;
843 rv = INT_MIN;
844 c = *cp++;
847 /* If the leading character is a digit, scan the number and convert it
848 * on the fly. Return a_MESSAGE_T_NUMBER when done */
849 if(digitchar(c)){
850 a_message_lexno = 0;
852 a_message_lexno = (a_message_lexno * 10) + c - '0';
853 *cp2++ = c;
854 }while((c = *cp++, digitchar(c)));
855 *cp2 = '\0';
856 *sp = --cp;
857 rv |= a_MESSAGE_T_NUMBER;
858 goto jleave;
861 /* An IMAP SEARCH list. Note that a_MESSAGE_T_OPEN has always been included
862 * in singles[] in Mail and mailx. Thus although there is no formal
863 * definition for (LIST) lists, they do not collide with historical
864 * practice because a subject string (LIST) could never been matched
865 * this way */
866 if (c == '(') {
867 ui32_t level = 1;
868 inquote = 0;
869 *cp2++ = c;
870 do {
871 if ((c = *cp++&0377) == '\0') {
872 jmtop:
873 n_err(_("Missing )\n"));
874 rv = a_MESSAGE_T_ERROR;
875 goto jleave;
877 if (inquote && c == '\\') {
878 *cp2++ = c;
879 c = *cp++&0377;
880 if (c == '\0')
881 goto jmtop;
882 } else if (c == '"')
883 inquote = !inquote;
884 else if (inquote)
885 /*EMPTY*/;
886 else if (c == '(')
887 ++level;
888 else if (c == ')')
889 --level;
890 else if (spacechar(c)) {
891 /* Replace unquoted whitespace by single space characters, to make
892 * the string IMAP SEARCH conformant */
893 c = ' ';
894 if (cp2[-1] == ' ')
895 --cp2;
897 *cp2++ = c;
898 } while (c != ')' || level > 0);
899 *cp2 = '\0';
900 *sp = cp;
901 rv |= a_MESSAGE_T_OPEN;
902 goto jleave;
905 /* Check for single character tokens; return such if found */
906 for(lp = a_message_singles;
907 PTRCMP(lp, <, &a_message_singles[n_NELEM(a_message_singles)]); ++lp)
908 if(c == lp->ml_char){
909 a_message_lexstr.s[0] = c;
910 a_message_lexstr.s[1] = '\0';
911 *sp = cp;
912 rv |= lp->ml_token;
913 goto jleave;
916 /* We've got a string! Copy all the characters of the string into
917 * a_message_lexstr, until we see a null, space, or tab. If the lead
918 * character is a " or ', save it and scan until you get another */
919 quotec = 0;
920 if (c == '\'' || c == '"') {
921 quotec = c;
922 c = *cp++;
924 while (c != '\0') {
925 if (quotec == 0 && c == '\\' && *cp != '\0')
926 c = *cp++;
927 if (c == quotec) {
928 ++cp;
929 break;
931 if (quotec == 0 && blankchar(c))
932 break;
933 if (PTRCMP(cp2 - a_message_lexstr.s, <, a_message_lexstr.l))
934 *cp2++ = c;
935 c = *cp++;
937 if (quotec && c == 0) {
938 n_err(_("Missing %c\n"), quotec);
939 rv = a_MESSAGE_T_ERROR;
940 goto jleave;
942 *sp = --cp;
943 *cp2 = '\0';
944 rv |= a_MESSAGE_T_STRING;
945 jleave:
946 NYD_LEAVE;
947 return rv;
950 static bool_t
951 a_message_match_sender(struct message *mp, char const *str, bool_t allnet){
952 char const *str_base, *np_base, *np;
953 char sc, nc;
954 bool_t rv;
955 NYD2_ENTER;
957 /* Empty string doesn't match */
958 if(*(str_base = str) == '\0'){
959 rv = FAL0;
960 goto jleave;
963 /* *allnet* is POSIX and, since it explicitly mentions login and user names,
964 * most likely case-sensitive. XXX Still allow substr matching, though
965 * XXX possibly the first letter should be case-insensitive, then? */
966 if(allnet){
967 for(np_base = np = nameof(mp, 0);;){
968 if((sc = *str++) == '@')
969 sc = '\0';
970 if((nc = *np++) == '@' || nc == '\0' || sc == '\0')
971 break;
972 if(sc != nc){
973 np = ++np_base;
974 str = str_base;
977 rv = (sc == '\0');
978 }else{
979 /* TODO POSIX says ~"match any address as shown in header overview",
980 * TODO but a normalized match would be more sane i guess.
981 * TODO struct name should gain a comparison method, normalize realname
982 * TODO content (in TODO) and thus match as likewise
983 * TODO "Buddy (Today) <here>" and "(Now) Buddy <here>" */
984 char const *real_base;
985 bool_t again;
987 real_base = name1(mp, 0);
988 again = ok_blook(showname);
989 jagain:
990 np_base = np = again ? realname(real_base) : skin(real_base);
991 str = str_base;
992 for(;;){
993 sc = *str++;
994 if((nc = *np++) == '\0' || sc == '\0')
995 break;
996 sc = upperconv(sc);
997 nc = upperconv(nc);
998 if(sc != nc){
999 np = ++np_base;
1000 str = str_base;
1004 /* And really if i want to match 'on@' then i want it to match even if
1005 * *showname* is set! */
1006 if(!(rv = (sc == '\0')) && again){
1007 again = FAL0;
1008 goto jagain;
1011 jleave:
1012 NYD2_LEAVE;
1013 return rv;
1016 static bool_t
1017 a_message_match_mid(struct message *mp, char const *id,
1018 enum a_message_idfield idfield){
1019 char const *cp;
1020 bool_t rv;
1021 NYD2_ENTER;
1023 rv = FAL0;
1025 if((cp = hfield1("message-id", mp)) != NULL){
1026 switch(idfield){
1027 case a_MESSAGE_ID_REFERENCES:
1028 if(!msgidcmp(id, cp))
1029 rv = TRU1;
1030 break;
1031 case a_MESSAGE_ID_IN_REPLY_TO:{
1032 struct name *np;
1034 if((np = extract(id, GREF)) != NULL)
1036 if(!msgidcmp(np->n_name, cp)){
1037 rv = TRU1;
1038 break;
1040 }while((np = np->n_flink) != NULL);
1041 break;
1045 NYD2_LEAVE;
1046 return rv;
1049 static bool_t
1050 a_message_match_dash(struct message *mp, char const *str){
1051 static char lastscan[128];
1053 struct str in, out;
1054 char *hfield, *hbody;
1055 bool_t rv;
1056 NYD2_ENTER;
1058 rv = FAL0;
1060 if(*++str == '\0')
1061 str = lastscan;
1062 else
1063 n_strscpy(lastscan, str, sizeof lastscan); /* XXX use new n_str object! */
1065 /* Now look, ignoring case, for the word in the string */
1066 if(ok_blook(searchheaders) && (hfield = strchr(str, ':'))){
1067 size_t l;
1069 l = PTR2SIZE(hfield - str);
1070 hfield = ac_alloc(l +1);
1071 memcpy(hfield, str, l);
1072 hfield[l] = '\0';
1073 hbody = hfieldX(hfield, mp);
1074 ac_free(hfield);
1075 hfield = n_UNCONST(str + l + 1);
1076 }else{
1077 hfield = n_UNCONST(str);
1078 hbody = hfield1("subject", mp);
1080 if(hbody == NULL)
1081 goto jleave;
1083 in.l = strlen(in.s = hbody);
1084 mime_fromhdr(&in, &out, TD_ICONV);
1085 rv = substr(out.s, hfield);
1086 free(out.s);
1087 jleave:
1088 NYD2_LEAVE;
1089 return rv;
1092 static bool_t
1093 a_message_match_at(struct message *mp, struct search_expr *sep){
1094 struct str in, out;
1095 char *nfield;
1096 char const *cfield;
1097 bool_t rv;
1098 NYD2_ENTER;
1100 rv = FAL0;
1101 nfield = savestr(sep->ss_where);
1103 while((cfield = n_strsep(&nfield, ',', TRU1)) != NULL){
1104 if(!asccasecmp(cfield, "body") ||
1105 (cfield[1] == '\0' && cfield[0] == '>')){
1106 rv = FAL0;
1107 jmsg:
1108 if((rv = message_match(mp, sep, rv)))
1109 break;
1110 continue;
1111 }else if(!asccasecmp(cfield, "text") ||
1112 (cfield[1] == '\0' && cfield[0] == '=')){
1113 rv = TRU1;
1114 goto jmsg;
1117 if(!asccasecmp(cfield, "header") ||
1118 (cfield[1] == '\0' && cfield[0] == '<')){
1119 if((rv = header_match(mp, sep)))
1120 break;
1121 continue;
1124 /* This is not a special name, so take care for the "skin" prefix !
1125 * and possible abbreviations */
1126 /* C99 */{
1127 char const x[][8] = {"from", "to", "cc", "bcc", "subject"};
1128 struct name *np;
1129 bool_t doskin;
1131 if((doskin = (*cfield == '~')))
1132 ++cfield;
1133 if(cfield[0] != '\0' && cfield[1] == '\0'){
1134 size_t i;
1135 char c1;
1137 c1 = lowerconv(cfield[0]);
1138 for(i = 0; i < n_NELEM(x); ++i){
1139 if(c1 == x[i][0]){
1140 cfield = x[i];
1141 break;
1145 if((in.s = hfieldX(cfield, mp)) == NULL)
1146 continue;
1148 /* Shall we split into address list and match the addresses only? */
1149 if(doskin){
1150 np = lextract(in.s, GSKIN);
1151 if(np == NULL)
1152 continue;
1153 out.s = np->n_name;
1154 }else{
1155 np = NULL;
1156 in.l = strlen(in.s);
1157 mime_fromhdr(&in, &out, TD_ICONV);
1160 jnext_name:
1161 #ifdef HAVE_REGEX
1162 if(sep->ss_sexpr == NULL)
1163 rv = (regexec(&sep->ss_regex, out.s, 0,NULL, 0) != REG_NOMATCH);
1164 else
1165 #endif
1166 rv = substr(out.s, sep->ss_sexpr);
1167 if(np == NULL)
1168 free(out.s);
1169 if(rv)
1170 break;
1171 if(np != NULL && (np = np->n_flink) != NULL){
1172 out.s = np->n_name;
1173 goto jnext_name;
1177 NYD2_LEAVE;
1178 return rv;
1181 static void
1182 a_message_unmark(int mesg){
1183 size_t i;
1184 NYD2_ENTER;
1186 i = (size_t)mesg;
1187 if(i < 1 || UICMP(z, i, >, msgCount))
1188 n_panic(_("Bad message number to unmark"));
1189 message[--i].m_flag &= ~MMARK;
1190 NYD2_LEAVE;
1193 static int
1194 a_message_metamess(int meta, int f)
1196 int c, m;
1197 struct message *mp;
1198 NYD2_ENTER;
1200 c = meta;
1201 switch (c) {
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);
1207 goto jleave;
1209 if (mb.mb_threaded) {
1210 mp = next_in_thread(mp);
1211 if (mp == NULL)
1212 break;
1213 } else
1214 ++mp;
1216 if (!(n_pstate & n_PS_HOOK_MASK))
1217 n_err(_("No applicable messages\n"));
1218 goto jem1;
1220 case '$': /* Last 'good message left */
1221 mp = mb.mb_threaded
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);
1226 goto jleave;
1228 if (mb.mb_threaded) {
1229 mp = prev_in_thread(mp);
1230 if (mp == NULL)
1231 break;
1232 } else
1233 --mp;
1235 if (!(n_pstate & n_PS_HOOK_MASK))
1236 n_err(_("No applicable messages\n"));
1237 goto jem1;
1239 case '.':
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);
1244 goto jem1;
1246 c = m;
1247 break;
1249 case ';':
1250 /* Previously current message */
1251 if (prevdot == NULL) {
1252 n_err(_("No previously current message\n"));
1253 goto jem1;
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);
1259 goto jem1;
1261 c = m;
1262 break;
1264 default:
1265 n_err(_("Unknown selector: %c\n"), c);
1266 goto jem1;
1268 jleave:
1269 NYD2_LEAVE;
1270 return c;
1271 jem1:
1272 c = -1;
1273 goto jleave;
1276 static void
1277 a_message__threadmark(struct message *self, int f){
1278 NYD2_ENTER;
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){
1284 goto jcall;
1285 while((self = self->m_younger) != NULL)
1286 if(self->m_child != NULL)
1287 jcall:
1288 a_message__threadmark(self, f);
1289 else
1290 self->m_flag |= MMARK;
1292 NYD2_LEAVE;
1295 FL FILE *
1296 setinput(struct mailbox *mp, struct message *m, enum needspec need){
1297 enum okay ok;
1298 FILE *rv;
1299 NYD_ENTER;
1301 rv = NULL;
1303 switch(need){
1304 case NEED_HEADER:
1305 ok = (m->m_content_info & CI_HAVE_HEADER) ? OKAY
1306 : a_message_get_header(m);
1307 break;
1308 case NEED_BODY:
1309 ok = (m->m_content_info & CI_HAVE_BODY) ? OKAY : get_body(m);
1310 break;
1311 default:
1312 case NEED_UNSPEC:
1313 ok = OKAY;
1314 break;
1316 if(ok != OKAY)
1317 goto jleave;
1319 fflush(mp->mb_otf);
1320 if(fseek(mp->mb_itf, (long)mailx_positionof(m->m_block, m->m_offset),
1321 SEEK_SET) == -1){
1322 n_perr(_("fseek"), 0);
1323 n_panic(_("temporary file seek"));
1325 rv = mp->mb_itf;
1326 jleave:
1327 NYD_LEAVE;
1328 return rv;
1331 FL enum okay
1332 get_body(struct message *mp){
1333 enum okay rv;
1334 NYD_ENTER;
1335 n_UNUSED(mp);
1337 switch(mb.mb_type){
1338 case MB_FILE:
1339 case MB_MAILDIR:
1340 rv = OKAY;
1341 break;
1342 #ifdef HAVE_POP3
1343 case MB_POP3:
1344 rv = pop3_body(mp);
1345 break;
1346 #endif
1347 #ifdef HAVE_IMAP
1348 case MB_IMAP:
1349 case MB_CACHE:
1350 rv = imap_body(mp);
1351 break;
1352 #endif
1353 case MB_VOID:
1354 default:
1355 rv = STOP;
1356 break;
1358 NYD_LEAVE;
1359 return rv;
1362 FL void
1363 message_reset(void){
1364 NYD_ENTER;
1365 if(message != NULL){
1366 free(message);
1367 message = NULL;
1369 msgCount = 0;
1370 a_message_mem_space = 0;
1371 NYD_LEAVE;
1374 FL void
1375 message_append(struct message *mp){
1376 NYD_ENTER;
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 = srealloc(message, a_message_mem_space * sizeof(*message));
1384 if(msgCount > 0){
1385 if(mp != NULL)
1386 message[msgCount - 1] = *mp;
1387 else
1388 memset(&message[msgCount - 1], 0, sizeof *message);
1390 NYD_LEAVE;
1393 FL void
1394 message_append_null(void){
1395 NYD_ENTER;
1396 if(msgCount == 0)
1397 message_append(NULL);
1398 setdot(message);
1399 message[msgCount].m_size = 0;
1400 message[msgCount].m_lines = 0;
1401 NYD_LEAVE;
1404 FL bool_t
1405 message_match(struct message *mp, struct search_expr const *sep,
1406 bool_t with_headers){
1407 char **line;
1408 size_t *linesize, cnt;
1409 FILE *fp;
1410 bool_t rv;
1411 NYD_ENTER;
1413 rv = FAL0;
1415 if((fp = Ftmp(NULL, "mpmatch", OF_RDWR | OF_UNLINK | OF_REGISTER)) == NULL)
1416 goto j_leave;
1418 if(sendmp(mp, fp, NULL, NULL, SEND_TOSRCH, NULL) < 0)
1419 goto jleave;
1420 fflush_rewind(fp);
1422 cnt = fsize(fp);
1423 line = &termios_state.ts_linebuf; /* XXX line pool */
1424 linesize = &termios_state.ts_linesize; /* XXX line pool */
1426 if(!with_headers)
1427 while(fgetline(line, linesize, &cnt, NULL, fp, 0))
1428 if (**line == '\n')
1429 break;
1431 while(fgetline(line, linesize, &cnt, NULL, fp, 0)){
1432 #ifdef HAVE_REGEX
1433 if(sep->ss_sexpr == NULL){
1434 if(regexec(&sep->ss_regex, *line, 0,NULL, 0) == REG_NOMATCH)
1435 continue;
1436 }else
1437 #endif
1438 if(!substr(*line, sep->ss_sexpr))
1439 continue;
1440 rv = TRU1;
1441 break;
1444 jleave:
1445 Fclose(fp);
1446 j_leave:
1447 NYD_LEAVE;
1448 return rv;
1451 FL struct message *
1452 setdot(struct message *mp){
1453 NYD_ENTER;
1454 if(dot != mp){
1455 prevdot = dot;
1456 n_pstate &= ~n_PS_DID_PRINT_DOT;
1458 dot = mp;
1459 uncollapse1(dot, 0);
1460 NYD_LEAVE;
1461 return dot;
1464 FL void
1465 touch(struct message *mp){
1466 NYD_ENTER;
1467 mp->m_flag |= MTOUCH;
1468 if(!(mp->m_flag & MREAD))
1469 mp->m_flag |= MREAD | MSTATUS;
1470 NYD_LEAVE;
1473 FL int
1474 getmsglist(char const *buf, int *vector, int flags)
1476 int *ip, mc;
1477 struct message *mp;
1478 NYD_ENTER;
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;
1484 if(msgCount == 0){
1485 *vector = 0;
1486 mc = 0;
1487 goto jleave;
1490 n_pstate |= n_PS_MSGLIST_DIRECT;
1492 if(a_message_markall(buf, flags) < 0){
1493 mc = -1;
1494 goto jleave;
1497 ip = vector;
1498 if(n_pstate & n_PS_HOOK_NEWMAIL){
1499 mc = 0;
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));
1504 else
1505 ++mc;
1507 if(mc == 0){
1508 mc = -1;
1509 goto jleave;
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);
1517 }else{
1518 for(mp = threadroot; mp != NULL; mp = next_in_thread(mp))
1519 if(mp->m_flag & MMARK)
1520 *ip++ = (int)PTR2SIZE(mp - message + 1);
1522 *ip = 0;
1523 mc = (int)PTR2SIZE(ip - vector);
1524 if(mc != 1)
1525 n_pstate &= ~n_PS_MSGLIST_DIRECT;
1526 jleave:
1527 NYD_LEAVE;
1528 return mc;
1531 FL int
1532 first(int f, int m)
1534 struct message *mp;
1535 int rv;
1536 NYD_ENTER;
1538 if (msgCount == 0) {
1539 rv = 0;
1540 goto jleave;
1543 f &= MDELETED;
1544 m &= MDELETED;
1545 for (mp = dot;
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);
1550 goto jleave;
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);
1559 goto jleave;
1563 rv = 0;
1564 jleave:
1565 NYD_LEAVE;
1566 return rv;
1569 FL void
1570 mark(int mno, int f){
1571 struct message *mp;
1572 int i;
1573 NYD_ENTER;
1575 i = mno;
1576 if(i < 1 || i > msgCount)
1577 n_panic(_("Bad message number to mark"));
1578 mp = &message[--i];
1580 if(mb.mb_threaded == 1 && a_message_threadflag)
1581 a_message__threadmark(mp, f);
1582 else{
1583 assert(!(mp->m_flag & MHIDDEN));
1584 mp->m_flag |= MMARK;
1586 NYD_LEAVE;
1589 /* s-it-mode */