nail.1: last fixes
[s-mailx.git] / message.c
blob43e273dca514829c1b18bff220c263fe144255d9
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 = 1<<0,
69 a_MESSAGE_S_OLD = 1<<1,
70 a_MESSAGE_S_UNREAD = 1<<2,
71 a_MESSAGE_S_DELETED = 1<<3,
72 a_MESSAGE_S_READ = 1<<4,
73 a_MESSAGE_S_FLAG = 1<<5,
74 a_MESSAGE_S_ANSWERED = 1<<6,
75 a_MESSAGE_S_DRAFT = 1<<7,
76 a_MESSAGE_S_SPAM = 1<<8,
77 a_MESSAGE_S_SPAMUNSURE = 1<<9
80 struct a_message_coltab{
81 char mco_char; /* What to find past : */
82 ui8_t mco__dummy[3];
83 int mco_bit; /* Associated modifier bit */
84 int mco_mask; /* m_status bits to mask */
85 int mco_equal; /* ... must equal this */
88 struct a_message_lex{
89 char ml_char;
90 ui8_t ml_token;
93 static struct a_message_coltab const a_message_coltabs[] = {
94 {'n', {0,}, a_MESSAGE_S_NEW, MNEW, MNEW},
95 {'o', {0,}, a_MESSAGE_S_OLD, MNEW, 0},
96 {'u', {0,}, a_MESSAGE_S_UNREAD, MREAD, 0},
97 {'d', {0,}, a_MESSAGE_S_DELETED, MDELETED, MDELETED},
98 {'r', {0,}, a_MESSAGE_S_READ, MREAD, MREAD},
99 {'f', {0,}, a_MESSAGE_S_FLAG, MFLAGGED, MFLAGGED},
100 {'a', {0,}, a_MESSAGE_S_ANSWERED, MANSWERED, MANSWERED},
101 {'t', {0,}, a_MESSAGE_S_DRAFT, MDRAFTED, MDRAFTED},
102 {'s', {0,}, a_MESSAGE_S_SPAM, MSPAM, MSPAM},
103 {'S', {0,}, a_MESSAGE_S_SPAMUNSURE, MSPAMUNSURE, MSPAMUNSURE}
106 static struct a_message_lex const a_message_singles[] = {
107 {'$', a_MESSAGE_T_DOLLAR},
108 {'.', a_MESSAGE_T_DOT},
109 {'^', a_MESSAGE_T_UP},
110 {'*', a_MESSAGE_T_ASTER},
111 {'-', a_MESSAGE_T_MINUS},
112 {'+', a_MESSAGE_T_PLUS},
113 {'(', a_MESSAGE_T_OPEN},
114 {')', a_MESSAGE_T_CLOSE},
115 {',', a_MESSAGE_T_COMMA},
116 {';', a_MESSAGE_T_SEMI},
117 {'`', a_MESSAGE_T_BACK}
120 /* Slots in ::message */
121 static size_t a_message_mem_space;
123 /* Mark entire threads */
124 static bool_t a_message_threadflag;
126 /* :d on its way HACK TODO */
127 static bool_t a_message_list_saw_d, a_message_list_last_saw_d;
129 /* String from a_MESSAGE_T_STRING, scan() */
130 static struct str a_message_lexstr;
131 /* Number of a_MESSAGE_T_NUMBER from scan() */
132 static int a_message_lexno;
134 /* Lazy load message header fields */
135 static enum okay a_message_get_header(struct message *mp);
137 /* Append, taking care of resizes TODO vector */
138 static char **a_message_add_to_namelist(char ***namelist, size_t *nmlsize,
139 char **np, char *string);
141 /* Mark all messages that the user wanted from the command line in the message
142 * structure. Return 0 on success, -1 on error */
143 static int a_message_markall(char const *buf, int f);
145 /* Turn the character after a colon modifier into a bit value */
146 static int a_message_evalcol(int col);
148 /* Check the passed message number for legality and proper flags. Unless f is
149 * MDELETED the message has to be undeleted */
150 static bool_t a_message_check(int mno, int f);
152 /* Scan out a single lexical item and return its token number, updating the
153 * string pointer passed *sp. Also, store the value of the number or string
154 * scanned in a_message_lexno or a_message_lexstr as appropriate.
155 * In any event, store the scanned "thing" in a_message_lexstr.
156 * Returns the token as a negative number when we also saw & to mark a thread */
157 static int a_message_scan(char const **sp);
159 /* See if the passed name sent the passed message */
160 static bool_t a_message_match_sender(struct message *mp, char const *str,
161 bool_t allnet);
163 /* Check whether the given message-id or references match */
164 static bool_t a_message_match_mid(struct message *mp, char const *id,
165 enum a_message_idfield idfield);
167 /* See if the given string matches.
168 * For the purpose of the scan, we ignore case differences.
169 * This is the engine behind the "/" search */
170 static bool_t a_message_match_dash(struct message *mp, char const *str);
172 /* See if the given search expression 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_at(struct message *mp, struct search_expr *sep);
177 /* Unmark the named message */
178 static void a_message_unmark(int mesg);
180 /* Return the message number corresponding to the passed meta character */
181 static int a_message_metamess(int meta, int f);
183 /* Helper for mark(): self valid, threading enabled */
184 static void a_message__threadmark(struct message *self, int f);
186 static enum okay
187 a_message_get_header(struct message *mp){
188 enum okay rv;
189 NYD2_ENTER;
190 n_UNUSED(mp);
192 switch(mb.mb_type){
193 case MB_FILE:
194 case MB_MAILDIR:
195 rv = OKAY;
196 break;
197 #ifdef HAVE_POP3
198 case MB_POP3:
199 rv = pop3_header(mp);
200 break;
201 #endif
202 #ifdef HAVE_IMAP
203 case MB_IMAP:
204 case MB_CACHE:
205 rv = imap_header(mp);
206 break;
207 #endif
208 case MB_VOID:
209 default:
210 rv = STOP;
211 break;
213 NYD2_LEAVE;
214 return rv;
217 static char **
218 a_message_add_to_namelist(char ***namelist, size_t *nmlsize, /* TODO Vector */
219 char **np, char *string){
220 size_t idx;
221 NYD2_ENTER;
223 if((idx = PTR2SIZE(np - *namelist)) >= *nmlsize){
224 *namelist = srealloc(*namelist, (*nmlsize += 8) * sizeof *np);
225 np = &(*namelist)[idx];
227 *np++ = string;
228 NYD2_LEAVE;
229 return np;
232 static int
233 a_message_markall(char const *buf, int f){
234 struct message *mp, *mx;
235 enum a_message_idfield idfield;
236 size_t j, nmlsize;
237 char const *id, *bufp;
238 char **np, **nq, **namelist, *cp;
239 int i, valdot, beg, colmod, tok, colresult;
240 enum{
241 a_NONE = 0,
242 a_ALLNET = 1u<<0, /* Must be TRU1 */
243 a_ALLOC = 1u<<1, /* Have allocated something */
244 a_THREADED = 1u<<2,
245 a_ERROR = 1u<<3,
246 a_ANY = 1u<<4, /* Have marked just ANY */
247 a_RANGE = 1u<<5, /* Seen dash, await close */
248 a_ASTER = 1u<<8,
249 a_TOPEN = 1u<<9, /* ( used (and didn't match) */
250 a_TBACK = 1u<<10, /* ` used (and didn't match) */
251 #ifdef HAVE_IMAP
252 a_HAVE_IMAP_HEADERS = 1u<<14,
253 #endif
254 a_TMP = 1u<<15
255 } flags;
256 NYD_ENTER;
257 n_LCTA((ui32_t)a_ALLNET == (ui32_t)TRU1,
258 "Constant is converted to bool_t via AND, thus");
260 /* Update message array: clear MMARK but remember its former state for ` */
261 for(i = msgCount; i-- > 0;){
262 enum mflag mf;
264 mf = (mp = &message[i])->m_flag;
265 if(mf & MMARK)
266 mf |= MOLDMARK;
267 else
268 mf &= ~MOLDMARK;
269 mf &= ~MMARK;
270 mp->m_flag = mf;
273 /* Strip all leading WS from user buffer */
274 while(blankspacechar(*buf))
275 ++buf;
276 /* If there is no input buffer, we are done! */
277 if(buf[0] == '\0'){
278 flags = a_NONE;
279 goto jleave;
282 n_UNINIT(beg, 0);
283 n_UNINIT(idfield, a_MESSAGE_ID_REFERENCES);
284 a_message_threadflag = FAL0;
285 a_message_lexstr.s = ac_alloc(a_message_lexstr.l = 2 * strlen(buf) +1);
286 np = namelist = smalloc((nmlsize = 8) * sizeof *namelist); /* TODO vector */
287 bufp = buf;
288 valdot = (int)PTR2SIZE(dot - message + 1);
289 colmod = 0;
290 id = NULL;
291 flags = a_ALLOC | (mb.mb_threaded ? a_THREADED : 0);
293 while((tok = a_message_scan(&bufp)) != a_MESSAGE_T_EOL){
294 if((a_message_threadflag = (tok < 0)))
295 tok &= INT_MAX;
297 switch(tok){
298 case a_MESSAGE_T_NUMBER:
299 n_pstate |= n_PS_MSGLIST_GABBY;
300 jnumber:
301 if(!a_message_check(a_message_lexno, f))
302 goto jerr;
304 if(flags & a_RANGE){
305 flags ^= a_RANGE;
307 if(!(flags & a_THREADED)){
308 if(beg < a_message_lexno)
309 i = beg;
310 else{
311 i = a_message_lexno;
312 a_message_lexno = beg;
315 for(; i <= a_message_lexno; ++i){
316 mp = &message[i - 1];
317 if(!(mp->m_flag & MHIDDEN) &&
318 (f == MDELETED || !(mp->m_flag & MDELETED))){
319 mark(i, f);
320 flags |= a_ANY;
323 }else{
324 /* TODO threaded ranges are a mess */
325 enum{
326 a_T_NONE,
327 a_T_HOT = 1u<<0,
328 a_T_DIR_PREV = 1u<<1
329 } tf;
330 int i_base;
332 if(beg < a_message_lexno)
333 i = beg;
334 else{
335 i = a_message_lexno;
336 a_message_lexno = beg;
339 i_base = i;
340 tf = a_T_NONE;
341 jnumber__thr:
342 for(;;){
343 mp = &message[i - 1];
344 if(!(mp->m_flag & MHIDDEN) &&
345 (f == MDELETED || !(mp->m_flag & MDELETED))){
346 if(tf & a_T_HOT){
347 mark(i, f);
348 flags |= a_ANY;
352 /* We may have reached the endpoint. If we were still
353 * detecting the direction to search for it, restart.
354 * Otherwise finished */
355 if(i == a_message_lexno){ /* XXX */
356 if(!(tf & a_T_HOT)){
357 tf |= a_T_HOT;
358 i = i_base;
359 goto jnumber__thr;
361 break;
364 mx = (tf & a_T_DIR_PREV) ? prev_in_thread(mp)
365 : next_in_thread(mp);
366 if(mx == NULL){
367 /* We anyway have failed to reach the endpoint in this
368 * direction; if we already switched that, report error */
369 if(!(tf & a_T_DIR_PREV)){
370 tf |= a_T_DIR_PREV;
371 i = i_base;
372 goto jnumber__thr;
374 id = N_("Range crosses multiple threads\n");
375 goto jerrmsg;
377 i = (int)PTR2SIZE(mx - message + 1);
381 beg = 0;
382 }else{
383 /* Could be an inclusive range? */
384 if(bufp[0] == '-'){
385 ++bufp;
386 beg = a_message_lexno;
387 flags |= a_RANGE;
388 }else{
389 mark(a_message_lexno, f);
390 flags |= a_ANY;
393 break;
394 case a_MESSAGE_T_PLUS:
395 n_pstate &= ~n_PS_MSGLIST_DIRECT;
396 n_pstate |= n_PS_MSGLIST_GABBY;
397 i = valdot;
399 if(flags & a_THREADED){
400 mx = next_in_thread(&message[i - 1]);
401 i = mx ? (int)PTR2SIZE(mx - message + 1) : msgCount + 1;
402 }else
403 ++i;
404 if(i > msgCount){
405 id = N_("Referencing beyond last message\n");
406 goto jerrmsg;
408 }while(message[i - 1].m_flag == MHIDDEN ||
409 (message[i - 1].m_flag & MDELETED) != (unsigned)f);
410 a_message_lexno = i;
411 goto jnumber;
412 case a_MESSAGE_T_MINUS:
413 n_pstate &= ~n_PS_MSGLIST_DIRECT;
414 n_pstate |= n_PS_MSGLIST_GABBY;
415 i = valdot;
417 if(flags & a_THREADED){
418 mx = prev_in_thread(&message[i - 1]);
419 i = mx ? (int)PTR2SIZE(mx - message + 1) : 0;
420 }else
421 --i;
422 if(i <= 0){
423 id = N_("Referencing before first message\n");
424 goto jerrmsg;
426 }while(message[i - 1].m_flag == MHIDDEN ||
427 (message[i - 1].m_flag & MDELETED) != (unsigned)f);
428 a_message_lexno = i;
429 goto jnumber;
430 case a_MESSAGE_T_STRING:
431 n_pstate &= ~n_PS_MSGLIST_DIRECT;
432 if(flags & a_RANGE)
433 goto jebadrange;
435 /* This may be a colon modifier */
436 if((cp = a_message_lexstr.s)[0] != ':')
437 np = a_message_add_to_namelist(&namelist, &nmlsize, np,
438 savestr(a_message_lexstr.s));
439 else{
440 while(*++cp != '\0'){
441 colresult = a_message_evalcol(*cp);
442 if(colresult == 0){
443 n_err(_("Unknown colon modifier: %s\n"), a_message_lexstr.s);
444 goto jerr;
446 if(colresult == a_MESSAGE_S_DELETED){
447 a_message_list_saw_d = TRU1;
448 f |= MDELETED;
450 colmod |= colresult;
453 break;
454 case a_MESSAGE_T_OPEN:
455 n_pstate &= ~n_PS_MSGLIST_DIRECT;
456 if(flags & a_RANGE)
457 goto jebadrange;
458 flags |= a_TOPEN;
460 #ifdef HAVE_IMAP_SEARCH
461 /* C99 */{
462 ssize_t ires;
464 if((ires = imap_search(a_message_lexstr.s, f)) >= 0){
465 if(ires > 0)
466 flags |= a_ANY;
467 break;
470 #else
471 n_err(_("Optional selector is not available: %s\n"),
472 a_message_lexstr.s);
473 #endif
474 goto jerr;
475 case a_MESSAGE_T_DOLLAR:
476 case a_MESSAGE_T_UP:
477 case a_MESSAGE_T_SEMI:
478 n_pstate |= n_PS_MSGLIST_GABBY;
479 /* FALLTHRU */
480 case a_MESSAGE_T_DOT: /* Don't set _GABBY for dot, to _allow_ history.. */
481 n_pstate &= ~n_PS_MSGLIST_DIRECT;
482 a_message_lexno = a_message_metamess(a_message_lexstr.s[0], f);
483 if(a_message_lexno == -1)
484 goto jerr;
485 goto jnumber;
486 case a_MESSAGE_T_BACK:
487 n_pstate &= ~n_PS_MSGLIST_DIRECT;
488 if(flags & a_RANGE)
489 goto jebadrange;
491 flags |= a_TBACK;
492 for(i = 0; i < msgCount; ++i){
493 if((mp = &message[i])->m_flag & MHIDDEN)
494 continue;
495 if((mp->m_flag & MDELETED) != (unsigned)f){
496 if(!a_message_list_last_saw_d)
497 continue;
498 a_message_list_saw_d = TRU1;
500 if(mp->m_flag & MOLDMARK){
501 mark(i + 1, f);
502 flags &= ~a_TBACK;
503 flags |= a_ANY;
506 break;
507 case a_MESSAGE_T_ASTER:
508 n_pstate &= ~n_PS_MSGLIST_DIRECT;
509 if(flags & a_RANGE)
510 goto jebadrange;
511 flags |= a_ASTER;
512 break;
513 case a_MESSAGE_T_COMMA:
514 n_pstate &= ~n_PS_MSGLIST_DIRECT;
515 n_pstate |= n_PS_MSGLIST_GABBY;
516 if(flags & a_RANGE)
517 goto jebadrange;
519 #ifdef HAVE_IMAP
520 if(!(flags & a_HAVE_IMAP_HEADERS) && mb.mb_type == MB_IMAP){
521 flags |= a_HAVE_IMAP_HEADERS;
522 imap_getheaders(1, msgCount);
524 #endif
526 if(id == NULL){
527 if((cp = hfield1("in-reply-to", dot)) != NULL)
528 idfield = a_MESSAGE_ID_IN_REPLY_TO;
529 else if((cp = hfield1("references", dot)) != NULL){
530 struct name *enp;
532 if((enp = extract(cp, GREF)) != NULL){
533 while(enp->n_flink != NULL)
534 enp = enp->n_flink;
535 cp = enp->n_name;
536 idfield = a_MESSAGE_ID_REFERENCES;
537 }else
538 cp = NULL;
541 if(cp != NULL)
542 id = savestr(cp);
543 else{
544 id = N_("Message-ID of parent of \"dot\" is indeterminable\n");
545 goto jerrmsg;
547 }else if(!(n_pstate & n_PS_HOOK) && (n_poption & n_PO_D_V))
548 n_err(_("Ignoring redundant specification of , selector\n"));
549 break;
550 case a_MESSAGE_T_ERROR:
551 n_pstate &= ~n_PS_MSGLIST_DIRECT;
552 n_pstate |= n_PS_MSGLIST_GABBY;
553 goto jerr;
556 /* Explicitly disallow invalid ranges for future safety */
557 if(bufp[0] == '-' && !(flags & a_RANGE)){
558 if(!(n_pstate & n_PS_HOOK))
559 n_err(_("Ignoring invalid range before: %s\n"), bufp);
560 ++bufp;
563 if(flags & a_RANGE){
564 id = N_("Missing second range argument\n");
565 goto jerrmsg;
568 np = a_message_add_to_namelist(&namelist, &nmlsize, np, NULL);
569 --np;
571 /* * is special at this point, after we have parsed the entire line */
572 if(flags & a_ASTER){
573 for(i = 0; i < msgCount; ++i){
574 if((mp = &message[i])->m_flag & MHIDDEN)
575 continue;
576 if(!a_message_list_saw_d && (mp->m_flag & MDELETED) != (unsigned)f)
577 continue;
578 mark(i + 1, f);
579 flags |= a_ANY;
581 if(!(flags & a_ANY))
582 goto jenoapp;
583 goto jleave;
586 /* If any names were given, add any messages which match */
587 if(np > namelist || id != NULL){
588 struct search_expr *sep = NULL;
590 /* The @ search works with struct search_expr, so build an array.
591 * To simplify array, i.e., regex_t destruction, and optimize for the
592 * common case we walk the entire array even in case of errors */
593 if(np > namelist){
594 sep = scalloc(PTR2SIZE(np - namelist), sizeof(*sep));
595 for(j = 0, nq = namelist; *nq != NULL; ++j, ++nq){
596 char *x, *y;
598 sep[j].ss_sexpr = x = *nq;
599 if(*x != '@' || (flags & a_ERROR))
600 continue;
602 for(y = &x[1];; ++y){
603 if(*y == '\0' || !fieldnamechar(*y)){
604 x = NULL;
605 break;
607 if(*y == '@'){
608 x = y;
609 break;
612 sep[j].ss_where = (x == NULL || x - 1 == *nq)
613 ? "subject" : savestrbuf(&(*nq)[1], PTR2SIZE(x - *nq) - 1);
615 x = (x == NULL ? *nq : x) + 1;
616 if(*x == '\0'){ /* XXX Simply remove from list instead? */
617 n_err(_("Empty [@..]@ search expression\n"));
618 flags |= a_ERROR;
619 continue;
621 #ifdef HAVE_REGEX
622 if(n_is_maybe_regex(x)){
623 int s;
625 sep[j].ss_sexpr = NULL;
626 if((s = regcomp(&sep[j].ss_regex, x,
627 REG_EXTENDED | REG_ICASE | REG_NOSUB)) != 0){
628 if(!(n_pstate & n_PS_HOOK))
629 n_err(_("Invalid regular expression: %s: %s\n"),
630 n_shexp_quote_cp(x, FAL0),
631 n_regex_err_to_doc(&sep[j].ss_regex, s));
632 flags |= a_ERROR;
633 continue;
635 }else
636 #endif
637 sep[j].ss_sexpr = x;
639 if(flags & a_ERROR)
640 goto jnamesearch_sepfree;
643 /* Iterate the entire message array */
644 #ifdef HAVE_IMAP
645 if(!(flags & a_HAVE_IMAP_HEADERS) && mb.mb_type == MB_IMAP){
646 flags |= a_HAVE_IMAP_HEADERS;
647 imap_getheaders(1, msgCount);
649 #endif
650 srelax_hold();
651 if(ok_blook(allnet))
652 flags |= a_ALLNET;
653 for(i = 0; i < msgCount; ++i){
654 if((mp = &message[i])->m_flag & (MMARK | MHIDDEN))
655 continue;
656 if(!a_message_list_saw_d && (mp->m_flag & MDELETED) != (unsigned)f)
657 continue;
659 flags &= ~a_TMP;
660 if(np > namelist){
661 for(nq = namelist; *nq != NULL; ++nq){
662 if(**nq == '@'){
663 if(a_message_match_at(mp, sep + PTR2SIZE(nq - namelist))){
664 flags |= a_TMP;
665 break;
667 }else if(**nq == '/'){
668 if(a_message_match_dash(mp, *nq)){
669 flags |= a_TMP;
670 break;
672 }else if(a_message_match_sender(mp, *nq, (flags & a_ALLNET))){
673 flags |= a_TMP;
674 break;
678 if(!(flags & a_TMP) &&
679 id != NULL && a_message_match_mid(mp, id, idfield))
680 flags |= a_TMP;
682 if(flags & a_TMP){
683 mark(i + 1, f);
684 flags |= a_ANY;
686 srelax();
688 srelax_rele();
690 jnamesearch_sepfree:
691 if(sep != NULL){
692 #ifdef HAVE_REGEX
693 for(j = PTR2SIZE(np - namelist); j-- != 0;)
694 if(sep[j].ss_sexpr == NULL)
695 regfree(&sep[j].ss_regex);
696 #endif
697 free(sep);
699 if(flags & a_ERROR)
700 goto jerr;
703 /* If any colon modifiers were given, go through and mark any messages which
704 * do satisfy the modifiers */
705 if(colmod != 0){
706 for(i = 0; i < msgCount; ++i){
707 struct a_message_coltab const *colp;
709 if((mp = &message[i])->m_flag & (MMARK | MHIDDEN))
710 continue;
711 if(!a_message_list_saw_d && (mp->m_flag & MDELETED) != (unsigned)f)
712 continue;
714 for(colp = a_message_coltabs;
715 PTRCMP(colp, <, &a_message_coltabs[n_NELEM(a_message_coltabs)]);
716 ++colp)
717 if((colp->mco_bit & colmod) &&
718 ((mp->m_flag & colp->mco_mask) == (unsigned)colp->mco_equal)){
719 mark(i + 1, f);
720 flags |= a_ANY;
721 break;
726 /* It shall be an error if ` didn't match anything, and nothing else did */
727 if((flags & (a_TBACK | a_ANY)) == a_TBACK){
728 id = N_("No previously marked messages\n");
729 goto jerrmsg;
730 }else if(!(flags & a_ANY))
731 goto jenoapp;
733 assert(!(flags & a_ERROR));
734 jleave:
735 if(flags & a_ALLOC){
736 free(namelist);
737 ac_free(a_message_lexstr.s);
739 NYD_LEAVE;
740 return (flags & a_ERROR) ? -1 : 0;
742 jebadrange:
743 id = N_("Invalid range endpoint\n");
744 goto jerrmsg;
745 jenoapp:
746 id = N_("No applicable messages\n");
747 jerrmsg:
748 if(!(n_pstate & n_PS_HOOK_MASK))
749 n_err(V_(id));
750 jerr:
751 flags |= a_ERROR;
752 goto jleave;
755 static int
756 a_message_evalcol(int col){
757 struct a_message_coltab const *colp;
758 int rv;
759 NYD2_ENTER;
761 rv = 0;
762 for(colp = a_message_coltabs;
763 PTRCMP(colp, <, &a_message_coltabs[n_NELEM(a_message_coltabs)]);
764 ++colp)
765 if(colp->mco_char == col){
766 rv = colp->mco_bit;
767 break;
769 NYD2_LEAVE;
770 return rv;
773 static bool_t
774 a_message_check(int mno, int f){
775 struct message *mp;
776 NYD2_ENTER;
778 if(mno < 1 || mno > msgCount){
779 n_err(_("%d: Invalid message number\n"), mno);
780 mno = 1;
781 }else if(((mp = &message[mno - 1])->m_flag & MHIDDEN) ||
782 (f != MDELETED && (mp->m_flag & MDELETED) != 0))
783 n_err(_("%d: inappropriate message\n"), mno);
784 else
785 mno = 0;
786 NYD2_LEAVE;
787 return (mno == 0);
790 static int
791 a_message_scan(char const **sp)
793 struct a_message_lex const *lp;
794 char *cp2;
795 char const *cp;
796 int rv, c, inquote, quotec;
797 NYD_ENTER;
799 rv = a_MESSAGE_T_EOL;
801 cp = *sp;
802 cp2 = a_message_lexstr.s;
803 c = *cp++;
805 /* strip away leading white space */
806 while(blankchar(c))
807 c = *cp++;
809 /* If no characters remain, we are at end of line, so report that */
810 if(c == '\0'){
811 *sp = --cp;
812 goto jleave;
815 /* Select members of a message thread */
816 if(c == '&'){
817 if(*cp == '\0' || spacechar(*cp)){
818 a_message_lexstr.s[0] = '.';
819 a_message_lexstr.s[1] = '\0';
820 *sp = cp;
821 rv = a_MESSAGE_T_DOT | INT_MIN;
822 goto jleave;
824 rv = INT_MIN;
825 c = *cp++;
828 /* If the leading character is a digit, scan the number and convert it
829 * on the fly. Return a_MESSAGE_T_NUMBER when done */
830 if(digitchar(c)){
831 a_message_lexno = 0;
833 a_message_lexno = (a_message_lexno * 10) + c - '0';
834 *cp2++ = c;
835 }while((c = *cp++, digitchar(c)));
836 *cp2 = '\0';
837 *sp = --cp;
838 rv |= a_MESSAGE_T_NUMBER;
839 goto jleave;
842 /* An IMAP SEARCH list. Note that a_MESSAGE_T_OPEN has always been included
843 * in singles[] in Mail and mailx. Thus although there is no formal
844 * definition for (LIST) lists, they do not collide with historical
845 * practice because a subject string (LIST) could never been matched
846 * this way */
847 if (c == '(') {
848 ui32_t level = 1;
849 inquote = 0;
850 *cp2++ = c;
851 do {
852 if ((c = *cp++&0377) == '\0') {
853 jmtop:
854 n_err(_("Missing )\n"));
855 rv = a_MESSAGE_T_ERROR;
856 goto jleave;
858 if (inquote && c == '\\') {
859 *cp2++ = c;
860 c = *cp++&0377;
861 if (c == '\0')
862 goto jmtop;
863 } else if (c == '"')
864 inquote = !inquote;
865 else if (inquote)
866 /*EMPTY*/;
867 else if (c == '(')
868 ++level;
869 else if (c == ')')
870 --level;
871 else if (spacechar(c)) {
872 /* Replace unquoted whitespace by single space characters, to make
873 * the string IMAP SEARCH conformant */
874 c = ' ';
875 if (cp2[-1] == ' ')
876 --cp2;
878 *cp2++ = c;
879 } while (c != ')' || level > 0);
880 *cp2 = '\0';
881 *sp = cp;
882 rv |= a_MESSAGE_T_OPEN;
883 goto jleave;
886 /* Check for single character tokens; return such if found */
887 for(lp = a_message_singles;
888 PTRCMP(lp, <, &a_message_singles[n_NELEM(a_message_singles)]); ++lp)
889 if(c == lp->ml_char){
890 a_message_lexstr.s[0] = c;
891 a_message_lexstr.s[1] = '\0';
892 *sp = cp;
893 rv |= lp->ml_token;
894 goto jleave;
897 /* We've got a string! Copy all the characters of the string into
898 * a_message_lexstr, until we see a null, space, or tab. If the lead
899 * character is a " or ', save it and scan until you get another */
900 quotec = 0;
901 if (c == '\'' || c == '"') {
902 quotec = c;
903 c = *cp++;
905 while (c != '\0') {
906 if (quotec == 0 && c == '\\' && *cp != '\0')
907 c = *cp++;
908 if (c == quotec) {
909 ++cp;
910 break;
912 if (quotec == 0 && blankchar(c))
913 break;
914 if (PTRCMP(cp2 - a_message_lexstr.s, <, a_message_lexstr.l))
915 *cp2++ = c;
916 c = *cp++;
918 if (quotec && c == 0) {
919 n_err(_("Missing %c\n"), quotec);
920 rv = a_MESSAGE_T_ERROR;
921 goto jleave;
923 *sp = --cp;
924 *cp2 = '\0';
925 rv |= a_MESSAGE_T_STRING;
926 jleave:
927 NYD_LEAVE;
928 return rv;
931 static bool_t
932 a_message_match_sender(struct message *mp, char const *str, bool_t allnet){
933 char const *str_base, *np_base, *np;
934 char sc, nc;
935 bool_t rv;
936 NYD2_ENTER;
938 /* Empty string doesn't match */
939 if(*(str_base = str) == '\0'){
940 rv = FAL0;
941 goto jleave;
944 /* *allnet* is POSIX and, since it explicitly mentions login and user names,
945 * most likely case-sensitive. XXX Still allow substr matching, though
946 * XXX possibly the first letter should be case-insensitive, then? */
947 if(allnet){
948 for(np_base = np = nameof(mp, 0);;){
949 if((sc = *str++) == '@')
950 sc = '\0';
951 if((nc = *np++) == '@' || nc == '\0' || sc == '\0')
952 break;
953 if(sc != nc){
954 np = ++np_base;
955 str = str_base;
958 rv = (sc == '\0');
959 }else{
960 /* TODO POSIX says ~"match any address as shown in header overview",
961 * TODO but a normalized match would be more sane i guess.
962 * TODO struct name should gain a comparison method, normalize realname
963 * TODO content (in TODO) and thus match as likewise
964 * TODO "Buddy (Today) <here>" and "(Now) Buddy <here>" */
965 char const *real_base;
966 bool_t again;
968 real_base = name1(mp, 0);
969 again = ok_blook(showname);
970 jagain:
971 np_base = np = again ? realname(real_base) : skin(real_base);
972 str = str_base;
973 for(;;){
974 sc = *str++;
975 if((nc = *np++) == '\0' || sc == '\0')
976 break;
977 sc = upperconv(sc);
978 nc = upperconv(nc);
979 if(sc != nc){
980 np = ++np_base;
981 str = str_base;
985 /* And really if i want to match 'on@' then i want it to match even if
986 * *showname* is set! */
987 if(!(rv = (sc == '\0')) && again){
988 again = FAL0;
989 goto jagain;
992 jleave:
993 NYD2_LEAVE;
994 return rv;
997 static bool_t
998 a_message_match_mid(struct message *mp, char const *id,
999 enum a_message_idfield idfield){
1000 char const *cp;
1001 bool_t rv;
1002 NYD2_ENTER;
1004 rv = FAL0;
1006 if((cp = hfield1("message-id", mp)) != NULL){
1007 switch(idfield){
1008 case a_MESSAGE_ID_REFERENCES:
1009 if(!msgidcmp(id, cp))
1010 rv = TRU1;
1011 break;
1012 case a_MESSAGE_ID_IN_REPLY_TO:{
1013 struct name *np;
1015 if((np = extract(id, GREF)) != NULL)
1017 if(!msgidcmp(np->n_name, cp)){
1018 rv = TRU1;
1019 break;
1021 }while((np = np->n_flink) != NULL);
1022 break;
1026 NYD2_LEAVE;
1027 return rv;
1030 static bool_t
1031 a_message_match_dash(struct message *mp, char const *str){
1032 static char lastscan[128];
1034 struct str in, out;
1035 char *hfield, *hbody;
1036 bool_t rv;
1037 NYD2_ENTER;
1039 rv = FAL0;
1041 if(*++str == '\0')
1042 str = lastscan;
1043 else
1044 n_strscpy(lastscan, str, sizeof lastscan); /* XXX use new n_str object! */
1046 /* Now look, ignoring case, for the word in the string */
1047 if(ok_blook(searchheaders) && (hfield = strchr(str, ':'))){
1048 size_t l;
1050 l = PTR2SIZE(hfield - str);
1051 hfield = ac_alloc(l +1);
1052 memcpy(hfield, str, l);
1053 hfield[l] = '\0';
1054 hbody = hfieldX(hfield, mp);
1055 ac_free(hfield);
1056 hfield = n_UNCONST(str + l + 1);
1057 }else{
1058 hfield = n_UNCONST(str);
1059 hbody = hfield1("subject", mp);
1061 if(hbody == NULL)
1062 goto jleave;
1064 in.l = strlen(in.s = hbody);
1065 mime_fromhdr(&in, &out, TD_ICONV);
1066 rv = substr(out.s, hfield);
1067 free(out.s);
1068 jleave:
1069 NYD2_LEAVE;
1070 return rv;
1073 static bool_t
1074 a_message_match_at(struct message *mp, struct search_expr *sep){
1075 struct str in, out;
1076 char *nfield;
1077 char const *cfield;
1078 bool_t rv;
1079 NYD2_ENTER;
1081 rv = FAL0;
1082 nfield = savestr(sep->ss_where);
1084 while((cfield = n_strsep(&nfield, ',', TRU1)) != NULL){
1085 if(!asccasecmp(cfield, "body") ||
1086 (cfield[1] == '\0' && cfield[0] == '>')){
1087 rv = FAL0;
1088 jmsg:
1089 if((rv = message_match(mp, sep, rv)))
1090 break;
1091 continue;
1092 }else if(!asccasecmp(cfield, "text") ||
1093 (cfield[1] == '\0' && cfield[0] == '=')){
1094 rv = TRU1;
1095 goto jmsg;
1098 if(!asccasecmp(cfield, "header") ||
1099 (cfield[1] == '\0' && cfield[0] == '<')){
1100 if((rv = header_match(mp, sep)))
1101 break;
1102 continue;
1105 /* This is not a special name, so take care for the "skin" prefix !
1106 * and possible abbreviations */
1107 /* C99 */{
1108 char const x[][8] = {"from", "to", "cc", "bcc", "subject"};
1109 struct name *np;
1110 bool_t doskin;
1112 if((doskin = (*cfield == '~')))
1113 ++cfield;
1114 if(cfield[0] != '\0' && cfield[1] == '\0'){
1115 size_t i;
1116 char c1;
1118 c1 = lowerconv(cfield[0]);
1119 for(i = 0; i < n_NELEM(x); ++i){
1120 if(c1 == x[i][0]){
1121 cfield = x[i];
1122 break;
1126 if((in.s = hfieldX(cfield, mp)) == NULL)
1127 continue;
1129 /* Shall we split into address list and match the addresses only? */
1130 if(doskin){
1131 np = lextract(in.s, GSKIN);
1132 if(np == NULL)
1133 continue;
1134 out.s = np->n_name;
1135 }else{
1136 np = NULL;
1137 in.l = strlen(in.s);
1138 mime_fromhdr(&in, &out, TD_ICONV);
1141 jnext_name:
1142 #ifdef HAVE_REGEX
1143 if(sep->ss_sexpr == NULL)
1144 rv = (regexec(&sep->ss_regex, out.s, 0,NULL, 0) != REG_NOMATCH);
1145 else
1146 #endif
1147 rv = substr(out.s, sep->ss_sexpr);
1148 if(np == NULL)
1149 free(out.s);
1150 if(rv)
1151 break;
1152 if(np != NULL && (np = np->n_flink) != NULL){
1153 out.s = np->n_name;
1154 goto jnext_name;
1158 NYD2_LEAVE;
1159 return rv;
1162 static void
1163 a_message_unmark(int mesg){
1164 size_t i;
1165 NYD2_ENTER;
1167 i = (size_t)mesg;
1168 if(i < 1 || UICMP(z, i, >, msgCount))
1169 n_panic(_("Bad message number to unmark"));
1170 message[--i].m_flag &= ~MMARK;
1171 NYD2_LEAVE;
1174 static int
1175 a_message_metamess(int meta, int f)
1177 int c, m;
1178 struct message *mp;
1179 NYD2_ENTER;
1181 c = meta;
1182 switch (c) {
1183 case '^': /* First 'good' message left */
1184 mp = mb.mb_threaded ? threadroot : message;
1185 while (PTRCMP(mp, <, message + msgCount)) {
1186 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & MDELETED) == (ui32_t)f) {
1187 c = (int)PTR2SIZE(mp - message + 1);
1188 goto jleave;
1190 if (mb.mb_threaded) {
1191 mp = next_in_thread(mp);
1192 if (mp == NULL)
1193 break;
1194 } else
1195 ++mp;
1197 if (!(n_pstate & n_PS_HOOK_MASK))
1198 n_err(_("No applicable messages\n"));
1199 goto jem1;
1201 case '$': /* Last 'good message left */
1202 mp = mb.mb_threaded
1203 ? this_in_thread(threadroot, -1) : message + msgCount - 1;
1204 while (mp >= message) {
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 = prev_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 '.':
1221 /* Current message */
1222 m = dot - message + 1;
1223 if ((dot->m_flag & MHIDDEN) || (dot->m_flag & MDELETED) != (ui32_t)f) {
1224 n_err(_("%d: inappropriate message\n"), m);
1225 goto jem1;
1227 c = m;
1228 break;
1230 case ';':
1231 /* Previously current message */
1232 if (prevdot == NULL) {
1233 n_err(_("No previously current message\n"));
1234 goto jem1;
1236 m = prevdot - message + 1;
1237 if ((prevdot->m_flag & MHIDDEN) ||
1238 (prevdot->m_flag & MDELETED) != (ui32_t)f) {
1239 n_err(_("%d: inappropriate message\n"), m);
1240 goto jem1;
1242 c = m;
1243 break;
1245 default:
1246 n_err(_("Unknown selector: %c\n"), c);
1247 goto jem1;
1249 jleave:
1250 NYD2_LEAVE;
1251 return c;
1252 jem1:
1253 c = -1;
1254 goto jleave;
1257 static void
1258 a_message__threadmark(struct message *self, int f){
1259 NYD2_ENTER;
1260 if(!(self->m_flag & MHIDDEN) &&
1261 (f == MDELETED || !(self->m_flag & MDELETED) || a_message_list_saw_d))
1262 self->m_flag |= MMARK;
1264 if((self = self->m_child) != NULL){
1265 goto jcall;
1266 while((self = self->m_younger) != NULL)
1267 if(self->m_child != NULL)
1268 jcall:
1269 a_message__threadmark(self, f);
1270 else
1271 self->m_flag |= MMARK;
1273 NYD2_LEAVE;
1276 FL FILE *
1277 setinput(struct mailbox *mp, struct message *m, enum needspec need){
1278 enum okay ok;
1279 FILE *rv;
1280 NYD_ENTER;
1282 rv = NULL;
1284 switch(need){
1285 case NEED_HEADER:
1286 ok = (m->m_content_info & CI_HAVE_HEADER) ? OKAY
1287 : a_message_get_header(m);
1288 break;
1289 case NEED_BODY:
1290 ok = (m->m_content_info & CI_HAVE_BODY) ? OKAY : get_body(m);
1291 break;
1292 default:
1293 case NEED_UNSPEC:
1294 ok = OKAY;
1295 break;
1297 if(ok != OKAY)
1298 goto jleave;
1300 fflush(mp->mb_otf);
1301 if(fseek(mp->mb_itf, (long)mailx_positionof(m->m_block, m->m_offset),
1302 SEEK_SET) == -1){
1303 n_perr(_("fseek"), 0);
1304 n_panic(_("temporary file seek"));
1306 rv = mp->mb_itf;
1307 jleave:
1308 NYD_LEAVE;
1309 return rv;
1312 FL enum okay
1313 get_body(struct message *mp){
1314 enum okay rv;
1315 NYD_ENTER;
1316 n_UNUSED(mp);
1318 switch(mb.mb_type){
1319 case MB_FILE:
1320 case MB_MAILDIR:
1321 rv = OKAY;
1322 break;
1323 #ifdef HAVE_POP3
1324 case MB_POP3:
1325 rv = pop3_body(mp);
1326 break;
1327 #endif
1328 #ifdef HAVE_IMAP
1329 case MB_IMAP:
1330 case MB_CACHE:
1331 rv = imap_body(mp);
1332 break;
1333 #endif
1334 case MB_VOID:
1335 default:
1336 rv = STOP;
1337 break;
1339 NYD_LEAVE;
1340 return rv;
1343 FL void
1344 message_reset(void){
1345 NYD_ENTER;
1346 if(message != NULL){
1347 free(message);
1348 message = NULL;
1350 msgCount = 0;
1351 a_message_mem_space = 0;
1352 NYD_LEAVE;
1355 FL void
1356 message_append(struct message *mp){
1357 NYD_ENTER;
1358 if(UICMP(z, msgCount + 1, >=, a_message_mem_space)){
1359 /* XXX remove _mem_space magics (or use s_Vector) */
1360 a_message_mem_space = ((a_message_mem_space >= 128 &&
1361 a_message_mem_space <= 1000000)
1362 ? a_message_mem_space << 1 : a_message_mem_space + 64);
1363 message = srealloc(message, a_message_mem_space * sizeof(*message));
1365 if(msgCount > 0){
1366 if(mp != NULL)
1367 message[msgCount - 1] = *mp;
1368 else
1369 memset(&message[msgCount - 1], 0, sizeof *message);
1371 NYD_LEAVE;
1374 FL void
1375 message_append_null(void){
1376 NYD_ENTER;
1377 if(msgCount == 0)
1378 message_append(NULL);
1379 setdot(message);
1380 message[msgCount].m_size = 0;
1381 message[msgCount].m_lines = 0;
1382 NYD_LEAVE;
1385 FL bool_t
1386 message_match(struct message *mp, struct search_expr const *sep,
1387 bool_t with_headers){
1388 char **line;
1389 size_t *linesize, cnt;
1390 FILE *fp;
1391 bool_t rv;
1392 NYD_ENTER;
1394 rv = FAL0;
1396 if((fp = Ftmp(NULL, "mpmatch", OF_RDWR | OF_UNLINK | OF_REGISTER)) == NULL)
1397 goto j_leave;
1399 if(sendmp(mp, fp, NULL, NULL, SEND_TOSRCH, NULL) < 0)
1400 goto jleave;
1401 fflush_rewind(fp);
1403 cnt = fsize(fp);
1404 line = &termios_state.ts_linebuf; /* XXX line pool */
1405 linesize = &termios_state.ts_linesize; /* XXX line pool */
1407 if(!with_headers)
1408 while(fgetline(line, linesize, &cnt, NULL, fp, 0))
1409 if (**line == '\n')
1410 break;
1412 while(fgetline(line, linesize, &cnt, NULL, fp, 0)){
1413 #ifdef HAVE_REGEX
1414 if(sep->ss_sexpr == NULL){
1415 if(regexec(&sep->ss_regex, *line, 0,NULL, 0) == REG_NOMATCH)
1416 continue;
1417 }else
1418 #endif
1419 if(!substr(*line, sep->ss_sexpr))
1420 continue;
1421 rv = TRU1;
1422 break;
1425 jleave:
1426 Fclose(fp);
1427 j_leave:
1428 NYD_LEAVE;
1429 return rv;
1432 FL struct message *
1433 setdot(struct message *mp){
1434 NYD_ENTER;
1435 if(dot != mp){
1436 prevdot = dot;
1437 n_pstate &= ~n_PS_DID_PRINT_DOT;
1439 dot = mp;
1440 uncollapse1(dot, 0);
1441 NYD_LEAVE;
1442 return dot;
1445 FL void
1446 touch(struct message *mp){
1447 NYD_ENTER;
1448 mp->m_flag |= MTOUCH;
1449 if(!(mp->m_flag & MREAD))
1450 mp->m_flag |= MREAD | MSTATUS;
1451 NYD_LEAVE;
1454 FL int
1455 getmsglist(char const *buf, int *vector, int flags)
1457 int *ip, mc;
1458 struct message *mp;
1459 NYD_ENTER;
1461 n_pstate &= ~n_PS_ARGLIST_MASK;
1462 a_message_list_last_saw_d = a_message_list_saw_d;
1463 a_message_list_saw_d = FAL0;
1465 if(msgCount == 0){
1466 *vector = 0;
1467 mc = 0;
1468 goto jleave;
1471 n_pstate |= n_PS_MSGLIST_DIRECT;
1473 if(a_message_markall(buf, flags) < 0){
1474 mc = -1;
1475 goto jleave;
1478 ip = vector;
1479 if(n_pstate & n_PS_HOOK_NEWMAIL){
1480 mc = 0;
1481 for(mp = message; mp < &message[msgCount]; ++mp)
1482 if(mp->m_flag & MMARK){
1483 if(!(mp->m_flag & MNEWEST))
1484 a_message_unmark((int)PTR2SIZE(mp - message + 1));
1485 else
1486 ++mc;
1488 if(mc == 0){
1489 mc = -1;
1490 goto jleave;
1494 if(mb.mb_threaded == 0){
1495 for(mp = message; mp < &message[msgCount]; ++mp)
1496 if(mp->m_flag & MMARK)
1497 *ip++ = (int)PTR2SIZE(mp - message + 1);
1498 }else{
1499 for(mp = threadroot; mp != NULL; mp = next_in_thread(mp))
1500 if(mp->m_flag & MMARK)
1501 *ip++ = (int)PTR2SIZE(mp - message + 1);
1503 *ip = 0;
1504 mc = (int)PTR2SIZE(ip - vector);
1505 if(mc != 1)
1506 n_pstate &= ~n_PS_MSGLIST_DIRECT;
1507 jleave:
1508 NYD_LEAVE;
1509 return mc;
1512 FL int
1513 first(int f, int m)
1515 struct message *mp;
1516 int rv;
1517 NYD_ENTER;
1519 if (msgCount == 0) {
1520 rv = 0;
1521 goto jleave;
1524 f &= MDELETED;
1525 m &= MDELETED;
1526 for (mp = dot;
1527 mb.mb_threaded ? (mp != NULL) : PTRCMP(mp, <, message + msgCount);
1528 mb.mb_threaded ? (mp = next_in_thread(mp)) : ++mp) {
1529 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & m) == (ui32_t)f) {
1530 rv = (int)PTR2SIZE(mp - message + 1);
1531 goto jleave;
1535 if (dot > message) {
1536 for (mp = dot - 1; (mb.mb_threaded ? (mp != NULL) : (mp >= message));
1537 mb.mb_threaded ? (mp = prev_in_thread(mp)) : --mp) {
1538 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & m) == (ui32_t)f) {
1539 rv = (int)PTR2SIZE(mp - message + 1);
1540 goto jleave;
1544 rv = 0;
1545 jleave:
1546 NYD_LEAVE;
1547 return rv;
1550 FL void
1551 mark(int mno, int f){
1552 struct message *mp;
1553 int i;
1554 NYD_ENTER;
1556 i = mno;
1557 if(i < 1 || i > msgCount)
1558 n_panic(_("Bad message number to mark"));
1559 mp = &message[--i];
1561 if(mb.mb_threaded == 1 && a_message_threadflag)
1562 a_message__threadmark(mp, f);
1563 else{
1564 assert(!(mp->m_flag & MHIDDEN));
1565 mp->m_flag |= MMARK;
1567 NYD_LEAVE;
1570 /* s-it-mode */