makefile:test: pass $(MAKEJOBS)
[s-mailx.git] / message.c
blobd800b25243e5d8a3ea9fc5bdfd740e5eca20ed77
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 *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 **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 case MB_VOID:
203 default:
204 rv = STOP;
205 break;
207 NYD2_LEAVE;
208 return rv;
211 static char **
212 a_message_add_to_namelist(char ***namelist, size_t *nmlsize, char **np,
213 char *string){
214 size_t idx;
215 NYD2_ENTER;
217 if((idx = PTR2SIZE(np - *namelist)) >= *nmlsize){
218 *namelist = srealloc(*namelist, (*nmlsize += 8) * sizeof *np);
219 np = &(*namelist)[idx];
221 *np++ = string;
222 NYD2_LEAVE;
223 return np;
226 static int
227 a_message_markall(char *buf, int f){
228 struct message *mp, *mx;
229 enum a_message_idfield idfield;
230 size_t j, nmlsize;
231 char const *id;
232 char **np, **nq, **namelist, *bufp, *cp;
233 int i, valdot, beg, colmod, tok, colresult;
234 enum{
235 a_NONE = 0,
236 a_ALLNET = 1u<<0, /* Must be TRU1 */
237 a_ALLOC = 1u<<1, /* Have allocated something */
238 a_THREADED = 1u<<2,
239 a_ERROR = 1u<<3,
240 a_ANY = 1u<<4, /* Have marked just ANY */
241 a_RANGE = 1u<<5, /* Seen dash, await close */
242 a_ASTER = 1u<<8,
243 a_TOPEN = 1u<<9, /* ( used (and didn't match) */
244 a_TBACK = 1u<<10, /* ` used (and didn't match) */
245 a_TMP = 1u<<15
246 } flags;
247 NYD_ENTER;
248 n_LCTA((ui32_t)a_ALLNET == (ui32_t)TRU1,
249 "Constant is converted to bool_t via AND, thus");
251 /* Update message array: clear MMARK but remember its former state for `.
252 * An empty selector input is identical to * asterisk */
253 for(i = msgCount; i-- > 0;){
254 enum mflag mf;
256 mf = (mp = &message[i])->m_flag;
257 if(mf & MMARK)
258 mf |= MOLDMARK;
259 else
260 mf &= ~MOLDMARK;
261 mf &= ~MMARK;
262 mp->m_flag = mf;
265 /* Strip all leading WS from user buffer */
266 while(blankspacechar(*buf))
267 ++buf;
268 /* If there is no input buffer, we are done! */
269 if(buf[0] == '\0'){
270 flags = a_NONE;
271 goto jleave;
274 n_UNINIT(beg, 0);
275 n_UNINIT(idfield, a_MESSAGE_ID_REFERENCES);
276 a_message_threadflag = FAL0;
277 a_message_lexstr.s = ac_alloc(a_message_lexstr.l = 2 * strlen(buf) +1);
278 np = namelist = smalloc((nmlsize = 8) * sizeof *namelist); /* TODO vector */
279 bufp = buf;
280 valdot = (int)PTR2SIZE(dot - message + 1);
281 colmod = 0;
282 id = NULL;
283 flags = a_ALLOC | (mb.mb_threaded ? a_THREADED : 0);
285 while((tok = a_message_scan(&bufp)) != a_MESSAGE_T_EOL){
286 if((a_message_threadflag = (tok < 0)))
287 tok &= INT_MAX;
289 switch(tok){
290 case a_MESSAGE_T_NUMBER:
291 n_pstate |= n_PS_MSGLIST_GABBY;
292 jnumber:
293 if(!a_message_check(a_message_lexno, f))
294 goto jerr;
296 if(flags & a_RANGE){
297 flags ^= a_RANGE;
299 if(!(flags & a_THREADED)){
300 if(beg < a_message_lexno)
301 i = beg;
302 else{
303 i = a_message_lexno;
304 a_message_lexno = beg;
307 for(; i <= a_message_lexno; ++i){
308 mp = &message[i - 1];
309 if(!(mp->m_flag & MHIDDEN) &&
310 (f == MDELETED || !(mp->m_flag & MDELETED))){
311 mark(i, f);
312 flags |= a_ANY;
315 }else{
316 /* TODO threaded ranges are a mess */
317 enum{
318 a_T_NONE,
319 a_T_HOT = 1u<<0,
320 a_T_DIR_PREV = 1u<<1
321 } tf;
322 int i_base;
324 if(beg < a_message_lexno)
325 i = beg;
326 else{
327 i = a_message_lexno;
328 a_message_lexno = beg;
331 i_base = i;
332 tf = a_T_NONE;
333 jnumber__thr:
334 for(;;){
335 mp = &message[i - 1];
336 if(!(mp->m_flag & MHIDDEN) &&
337 (f == MDELETED || !(mp->m_flag & MDELETED))){
338 if(tf & a_T_HOT){
339 mark(i, f);
340 flags |= a_ANY;
344 /* We may have reached the endpoint. If we were still
345 * detecting the direction to search for it, restart.
346 * Otherwise finished */
347 if(i == a_message_lexno){ /* XXX */
348 if(!(tf & a_T_HOT)){
349 tf |= a_T_HOT;
350 i = i_base;
351 goto jnumber__thr;
353 break;
356 mx = (tf & a_T_DIR_PREV) ? prev_in_thread(mp)
357 : next_in_thread(mp);
358 if(mx == NULL){
359 /* We anyway have failed to reach the endpoint in this
360 * direction; if we already switched that, report error */
361 if(!(tf & a_T_DIR_PREV)){
362 tf |= a_T_DIR_PREV;
363 i = i_base;
364 goto jnumber__thr;
366 id = N_("Range crosses multiple threads\n");
367 goto jerrmsg;
369 i = (int)PTR2SIZE(mx - message + 1);
373 beg = 0;
374 }else{
375 /* Could be an inclusive range? */
376 if(bufp[0] == '-'){
377 ++bufp;
378 beg = a_message_lexno;
379 flags |= a_RANGE;
380 }else{
381 mark(a_message_lexno, f);
382 flags |= a_ANY;
385 break;
386 case a_MESSAGE_T_PLUS:
387 n_pstate &= ~n_PS_MSGLIST_DIRECT;
388 n_pstate |= n_PS_MSGLIST_GABBY;
389 i = valdot;
391 if(flags & a_THREADED){
392 mx = next_in_thread(&message[i - 1]);
393 i = mx ? (int)PTR2SIZE(mx - message + 1) : msgCount + 1;
394 }else
395 ++i;
396 if(i > msgCount){
397 id = N_("Referencing beyond last message\n");
398 goto jerrmsg;
400 }while(message[i - 1].m_flag == MHIDDEN ||
401 (message[i - 1].m_flag & MDELETED) != (unsigned)f);
402 a_message_lexno = i;
403 goto jnumber;
404 case a_MESSAGE_T_MINUS:
405 n_pstate &= ~n_PS_MSGLIST_DIRECT;
406 n_pstate |= n_PS_MSGLIST_GABBY;
407 i = valdot;
409 if(flags & a_THREADED){
410 mx = prev_in_thread(&message[i - 1]);
411 i = mx ? (int)PTR2SIZE(mx - message + 1) : 0;
412 }else
413 --i;
414 if(i <= 0){
415 id = N_("Referencing before first message\n");
416 goto jerrmsg;
418 }while(message[i - 1].m_flag == MHIDDEN ||
419 (message[i - 1].m_flag & MDELETED) != (unsigned)f);
420 a_message_lexno = i;
421 goto jnumber;
422 case a_MESSAGE_T_STRING:
423 n_pstate &= ~n_PS_MSGLIST_DIRECT;
424 if(flags & a_RANGE)
425 goto jebadrange;
427 /* This may be a colon modifier */
428 if((cp = a_message_lexstr.s)[0] != ':')
429 np = a_message_add_to_namelist(&namelist, &nmlsize, np,
430 savestr(a_message_lexstr.s));
431 else{
432 while(*++cp != '\0'){
433 colresult = a_message_evalcol(*cp);
434 if(colresult == 0){
435 n_err(_("Unknown colon modifier: %s\n"), a_message_lexstr.s);
436 goto jerr;
438 if(colresult == a_MESSAGE_S_DELETED){
439 a_message_list_saw_d = TRU1;
440 f |= MDELETED;
442 colmod |= colresult;
445 break;
446 case a_MESSAGE_T_OPEN:
447 n_pstate &= ~n_PS_MSGLIST_DIRECT;
448 if(flags & a_RANGE)
449 goto jebadrange;
450 flags |= a_TOPEN;
452 #ifdef HAVE_IMAP_SEARCH
453 /* C99 */{
454 ssize_t ires;
456 if((ires = imap_search(a_message_lexstr.s, f)) >= 0){
457 if(ires > 0)
458 flags |= a_ANY;
459 break;
462 #else
463 n_err(_("Optional selector is not available: %s\n"),
464 a_message_lexstr.s);
465 #endif
466 goto jerr;
467 case a_MESSAGE_T_DOLLAR:
468 case a_MESSAGE_T_UP:
469 case a_MESSAGE_T_SEMI:
470 n_pstate |= n_PS_MSGLIST_GABBY;
471 /* FALLTHRU */
472 case a_MESSAGE_T_DOT: /* Don't set _GABBY for dot, to _allow_ history.. */
473 n_pstate &= ~n_PS_MSGLIST_DIRECT;
474 a_message_lexno = a_message_metamess(a_message_lexstr.s[0], f);
475 if(a_message_lexno == -1)
476 goto jerr;
477 goto jnumber;
478 case a_MESSAGE_T_BACK:
479 n_pstate &= ~n_PS_MSGLIST_DIRECT;
480 if(flags & a_RANGE)
481 goto jebadrange;
483 flags |= a_TBACK;
484 for(i = 0; i < msgCount; ++i){
485 if((mp = &message[i])->m_flag & MHIDDEN)
486 continue;
487 if((mp->m_flag & MDELETED) != (unsigned)f){
488 if(!a_message_list_last_saw_d)
489 continue;
490 a_message_list_saw_d = TRU1;
492 if(mp->m_flag & MOLDMARK){
493 mark(i + 1, f);
494 flags &= ~a_TBACK;
495 flags |= a_ANY;
498 break;
499 case a_MESSAGE_T_ASTER:
500 n_pstate &= ~n_PS_MSGLIST_DIRECT;
501 if(flags & a_RANGE)
502 goto jebadrange;
503 flags |= a_ASTER;
504 break;
505 case a_MESSAGE_T_COMMA:
506 n_pstate &= ~n_PS_MSGLIST_DIRECT;
507 n_pstate |= n_PS_MSGLIST_GABBY;
508 if(flags & a_RANGE)
509 goto jebadrange;
511 if(id == NULL){
512 if((cp = hfield1("in-reply-to", dot)) != NULL)
513 idfield = a_MESSAGE_ID_IN_REPLY_TO;
514 else if((cp = hfield1("references", dot)) != NULL){
515 struct name *enp;
517 if((enp = extract(cp, GREF)) != NULL){
518 while(enp->n_flink != NULL)
519 enp = enp->n_flink;
520 cp = enp->n_name;
521 idfield = a_MESSAGE_ID_REFERENCES;
522 }else
523 cp = NULL;
526 if(cp != NULL)
527 id = savestr(cp);
528 else{
529 id = N_("Message-ID of parent of \"dot\" is indeterminable\n");
530 goto jerrmsg;
532 }else if(!(n_pstate & n_PS_HOOK) && (n_poption & n_PO_D_V))
533 n_err(_("Ignoring redundant specification of , selector\n"));
534 break;
535 case a_MESSAGE_T_ERROR:
536 n_pstate &= ~n_PS_MSGLIST_DIRECT;
537 n_pstate |= n_PS_MSGLIST_GABBY;
538 goto jerr;
541 /* Explicitly disallow invalid ranges for future safety */
542 if(bufp[0] == '-' && !(flags & a_RANGE)){
543 if(!(n_pstate & n_PS_HOOK))
544 n_err(_("Ignoring invalid range before: %s\n"), bufp);
545 ++bufp;
548 if(flags & a_RANGE){
549 id = N_("Missing second range argument\n");
550 goto jerrmsg;
553 np = a_message_add_to_namelist(&namelist, &nmlsize, np, NULL);
554 --np;
556 /* * is special at this point, after we have parsed the entire line */
557 if(flags & a_ASTER){
558 for(i = 0; i < msgCount; ++i){
559 if((mp = &message[i])->m_flag & MHIDDEN)
560 continue;
561 if(!a_message_list_saw_d && (mp->m_flag & MDELETED) != (unsigned)f)
562 continue;
563 mark(i + 1, f);
564 flags |= a_ANY;
566 if(!(flags & a_ANY))
567 goto jenoapp;
568 goto jleave;
571 /* If any names were given, add any messages which match */
572 if(np > namelist || id != NULL){
573 struct search_expr *sep = NULL;
575 /* The @ search works with struct search_expr, so build an array.
576 * To simplify array, i.e., regex_t destruction, and optimize for the
577 * common case we walk the entire array even in case of errors */
578 if(np > namelist){
579 sep = scalloc(PTR2SIZE(np - namelist), sizeof(*sep));
580 for(j = 0, nq = namelist; *nq != NULL; ++j, ++nq){
581 char *x, *y;
583 sep[j].ss_sexpr = x = *nq;
584 if(*x != '@' || (flags & a_ERROR))
585 continue;
587 for(y = &x[1];; ++y){
588 if(*y == '\0' || !fieldnamechar(*y)){
589 x = NULL;
590 break;
592 if(*y == '@'){
593 x = y;
594 break;
597 sep[j].ss_where = (x == NULL || x - 1 == *nq)
598 ? "subject" : savestrbuf(&(*nq)[1], PTR2SIZE(x - *nq) - 1);
600 x = (x == NULL ? *nq : x) + 1;
601 if(*x == '\0'){ /* XXX Simply remove from list instead? */
602 n_err(_("Empty [@..]@ search expression\n"));
603 flags |= a_ERROR;
604 continue;
606 #ifdef HAVE_REGEX
607 if(n_is_maybe_regex(x)){
608 int s;
610 sep[j].ss_sexpr = NULL;
611 if((s = regcomp(&sep[j].ss_regex, x,
612 REG_EXTENDED | REG_ICASE | REG_NOSUB)) != 0){
613 if(!(n_pstate & n_PS_HOOK))
614 n_err(_("Invalid regular expression: %s: %s\n"),
615 n_shexp_quote_cp(x, FAL0),
616 n_regex_err_to_str(&sep[j].ss_regex, s));
617 flags |= a_ERROR;
618 continue;
620 }else
621 #endif
622 sep[j].ss_sexpr = x;
624 if(flags & a_ERROR)
625 goto jnamesearch_sepfree;
628 /* Iterate the entire message array */
629 srelax_hold();
630 if(ok_blook(allnet))
631 flags |= a_ALLNET;
632 for(i = 0; i < msgCount; ++i){
633 if((mp = &message[i])->m_flag & (MMARK | MHIDDEN))
634 continue;
635 if(!a_message_list_saw_d && (mp->m_flag & MDELETED) != (unsigned)f)
636 continue;
638 flags &= ~a_TMP;
639 if(np > namelist){
640 for(nq = namelist; *nq != NULL; ++nq){
641 if(**nq == '@'){
642 if(a_message_match_at(mp, sep + PTR2SIZE(nq - namelist))){
643 flags |= a_TMP;
644 break;
646 }else if(**nq == '/'){
647 if(a_message_match_dash(mp, *nq)){
648 flags |= a_TMP;
649 break;
651 }else if(a_message_match_sender(mp, *nq, (flags & a_ALLNET))){
652 flags |= a_TMP;
653 break;
657 if(!(flags & a_TMP) &&
658 id != NULL && a_message_match_mid(mp, id, idfield))
659 flags |= a_TMP;
661 if(flags & a_TMP){
662 mark(i + 1, f);
663 flags |= a_ANY;
665 srelax();
667 srelax_rele();
669 jnamesearch_sepfree:
670 if(sep != NULL){
671 #ifdef HAVE_REGEX
672 for(j = PTR2SIZE(np - namelist); j-- != 0;)
673 if(sep[j].ss_sexpr == NULL)
674 regfree(&sep[j].ss_regex);
675 #endif
676 free(sep);
678 if(flags & a_ERROR)
679 goto jerr;
682 /* If any colon modifiers were given, go through and mark any messages which
683 * do satisfy the modifiers */
684 if(colmod != 0){
685 for(i = 0; i < msgCount; ++i){
686 struct a_message_coltab const *colp;
688 if((mp = &message[i])->m_flag & (MMARK | MHIDDEN))
689 continue;
690 if(!a_message_list_saw_d && (mp->m_flag & MDELETED) != (unsigned)f)
691 continue;
693 for(colp = a_message_coltabs;
694 PTRCMP(colp, <, &a_message_coltabs[n_NELEM(a_message_coltabs)]);
695 ++colp)
696 if((colp->mco_bit & colmod) &&
697 ((mp->m_flag & colp->mco_mask) == (unsigned)colp->mco_equal)){
698 mark(i + 1, f);
699 flags |= a_ANY;
700 break;
705 /* It shall be an error if ` didn't match anything, and nothing else did */
706 if((flags & (a_TBACK | a_ANY)) == a_TBACK){
707 id = N_("No previously marked messages\n");
708 goto jerrmsg;
709 }else if(!(flags & a_ANY))
710 goto jenoapp;
712 assert(!(flags & a_ERROR));
713 jleave:
714 if(flags & a_ALLOC){
715 free(namelist);
716 ac_free(a_message_lexstr.s);
718 NYD_LEAVE;
719 return (flags & a_ERROR) ? -1 : 0;
721 jebadrange:
722 id = N_("Invalid range endpoint\n");
723 goto jerrmsg;
724 jenoapp:
725 id = N_("No applicable messages\n");
726 jerrmsg:
727 if(!(n_pstate & n_PS_HOOK_MASK))
728 n_err(V_(id));
729 jerr:
730 flags |= a_ERROR;
731 goto jleave;
734 static int
735 a_message_evalcol(int col){
736 struct a_message_coltab const *colp;
737 int rv;
738 NYD2_ENTER;
740 rv = 0;
741 for(colp = a_message_coltabs;
742 PTRCMP(colp, <, &a_message_coltabs[n_NELEM(a_message_coltabs)]);
743 ++colp)
744 if(colp->mco_char == col){
745 rv = colp->mco_bit;
746 break;
748 NYD2_LEAVE;
749 return rv;
752 static bool_t
753 a_message_check(int mno, int f){
754 struct message *mp;
755 NYD2_ENTER;
757 if(mno < 1 || mno > msgCount){
758 n_err(_("%d: Invalid message number\n"), mno);
759 mno = 1;
760 }else if(((mp = &message[mno - 1])->m_flag & MHIDDEN) ||
761 (f != MDELETED && (mp->m_flag & MDELETED) != 0))
762 n_err(_("%d: inappropriate message\n"), mno);
763 else
764 mno = 0;
765 NYD2_LEAVE;
766 return (mno == 0);
769 static int
770 a_message_scan(char **sp)
772 char *cp, *cp2;
773 struct a_message_lex const *lp;
774 int rv, c, inquote, quotec;
775 NYD_ENTER;
777 rv = a_MESSAGE_T_EOL;
779 cp = *sp;
780 cp2 = a_message_lexstr.s;
781 c = *cp++;
783 /* strip away leading white space */
784 while(blankchar(c))
785 c = *cp++;
787 /* If no characters remain, we are at end of line, so report that */
788 if(c == '\0'){
789 *sp = --cp;
790 goto jleave;
793 /* Select members of a message thread */
794 if(c == '&'){
795 if(*cp == '\0' || spacechar(*cp)){
796 a_message_lexstr.s[0] = '.';
797 a_message_lexstr.s[1] = '\0';
798 *sp = cp;
799 rv = a_MESSAGE_T_DOT | INT_MIN;
800 goto jleave;
802 rv = INT_MIN;
803 c = *cp++;
806 /* If the leading character is a digit, scan the number and convert it
807 * on the fly. Return a_MESSAGE_T_NUMBER when done */
808 if(digitchar(c)){
809 a_message_lexno = 0;
811 a_message_lexno = (a_message_lexno * 10) + c - '0';
812 *cp2++ = c;
813 }while((c = *cp++, digitchar(c)));
814 *cp2 = '\0';
815 *sp = --cp;
816 rv |= a_MESSAGE_T_NUMBER;
817 goto jleave;
820 /* An IMAP SEARCH list. Note that a_MESSAGE_T_OPEN has always been included
821 * in singles[] in Mail and mailx. Thus although there is no formal
822 * definition for (LIST) lists, they do not collide with historical
823 * practice because a subject string (LIST) could never been matched
824 * this way */
825 if (c == '(') {
826 ui32_t level = 1;
827 inquote = 0;
828 *cp2++ = c;
829 do {
830 if ((c = *cp++&0377) == '\0') {
831 jmtop:
832 n_err(_("Missing )\n"));
833 rv = a_MESSAGE_T_ERROR;
834 goto jleave;
836 if (inquote && c == '\\') {
837 *cp2++ = c;
838 c = *cp++&0377;
839 if (c == '\0')
840 goto jmtop;
841 } else if (c == '"')
842 inquote = !inquote;
843 else if (inquote)
844 /*EMPTY*/;
845 else if (c == '(')
846 ++level;
847 else if (c == ')')
848 --level;
849 else if (spacechar(c)) {
850 /* Replace unquoted whitespace by single space characters, to make
851 * the string IMAP SEARCH conformant */
852 c = ' ';
853 if (cp2[-1] == ' ')
854 --cp2;
856 *cp2++ = c;
857 } while (c != ')' || level > 0);
858 *cp2 = '\0';
859 *sp = cp;
860 rv |= a_MESSAGE_T_OPEN;
861 goto jleave;
864 /* Check for single character tokens; return such if found */
865 for(lp = a_message_singles;
866 PTRCMP(lp, <, &a_message_singles[n_NELEM(a_message_singles)]); ++lp)
867 if(c == lp->ml_char){
868 a_message_lexstr.s[0] = c;
869 a_message_lexstr.s[1] = '\0';
870 *sp = cp;
871 rv |= lp->ml_token;
872 goto jleave;
875 /* We've got a string! Copy all the characters of the string into
876 * a_message_lexstr, until we see a null, space, or tab. If the lead
877 * character is a " or ', save it and scan until you get another */
878 quotec = 0;
879 if (c == '\'' || c == '"') {
880 quotec = c;
881 c = *cp++;
883 while (c != '\0') {
884 if (quotec == 0 && c == '\\' && *cp != '\0')
885 c = *cp++;
886 if (c == quotec) {
887 ++cp;
888 break;
890 if (quotec == 0 && blankchar(c))
891 break;
892 if (PTRCMP(cp2 - a_message_lexstr.s, <, a_message_lexstr.l))
893 *cp2++ = c;
894 c = *cp++;
896 if (quotec && c == 0) {
897 n_err(_("Missing %c\n"), quotec);
898 rv = a_MESSAGE_T_ERROR;
899 goto jleave;
901 *sp = --cp;
902 *cp2 = '\0';
903 rv |= a_MESSAGE_T_STRING;
904 jleave:
905 NYD_LEAVE;
906 return rv;
909 static bool_t
910 a_message_match_sender(struct message *mp, char const *str, bool_t allnet){
911 char const *str_base, *np_base, *np;
912 char sc, nc;
913 bool_t rv;
914 NYD2_ENTER;
916 /* Empty string doesn't match */
917 if(*(str_base = str) == '\0'){
918 rv = FAL0;
919 goto jleave;
922 /* *allnet* is POSIX and, since it explicitly mentions login and user names,
923 * most likely case-sensitive. XXX Still allow substr matching, though
924 * XXX possibly the first letter should be case-insensitive, then? */
925 if(allnet){
926 for(np_base = np = nameof(mp, 0);;){
927 if((sc = *str++) == '@')
928 sc = '\0';
929 if((nc = *np++) == '@' || nc == '\0' || sc == '\0')
930 break;
931 if(sc != nc){
932 np = ++np_base;
933 str = str_base;
936 rv = (sc == '\0');
937 }else{
938 /* TODO POSIX says ~"match any address as shown in header overview",
939 * TODO but a normalized match would be more sane i guess.
940 * TODO struct name should gain a comparison method, normalize realname
941 * TODO content (in TODO) and thus match as likewise
942 * TODO "Buddy (Today) <here>" and "(Now) Buddy <here>" */
943 char const *real_base;
944 bool_t again;
946 real_base = name1(mp, 0);
947 again = ok_blook(showname);
948 jagain:
949 np_base = np = again ? realname(real_base) : skin(real_base);
950 str = str_base;
951 for(;;){
952 sc = *str++;
953 if((nc = *np++) == '\0' || sc == '\0')
954 break;
955 sc = upperconv(sc);
956 nc = upperconv(nc);
957 if(sc != nc){
958 np = ++np_base;
959 str = str_base;
963 /* And really if i want to match 'on@' then i want it to match even if
964 * *showname* is set! */
965 if(!(rv = (sc == '\0')) && again){
966 again = FAL0;
967 goto jagain;
970 jleave:
971 NYD2_LEAVE;
972 return rv;
975 static bool_t
976 a_message_match_mid(struct message *mp, char const *id,
977 enum a_message_idfield idfield){
978 char const *cp;
979 bool_t rv;
980 NYD2_ENTER;
982 rv = FAL0;
984 if((cp = hfield1("message-id", mp)) != NULL){
985 switch(idfield){
986 case a_MESSAGE_ID_REFERENCES:
987 if(!msgidcmp(id, cp))
988 rv = TRU1;
989 break;
990 case a_MESSAGE_ID_IN_REPLY_TO:{
991 struct name *np;
993 if((np = extract(id, GREF)) != NULL)
995 if(!msgidcmp(np->n_name, cp)){
996 rv = TRU1;
997 break;
999 }while((np = np->n_flink) != NULL);
1000 break;
1004 NYD2_LEAVE;
1005 return rv;
1008 static bool_t
1009 a_message_match_dash(struct message *mp, char const *str){
1010 static char lastscan[128];
1012 struct str in, out;
1013 char *hfield, *hbody;
1014 bool_t rv;
1015 NYD2_ENTER;
1017 rv = FAL0;
1019 if(*++str == '\0')
1020 str = lastscan;
1021 else
1022 n_strscpy(lastscan, str, sizeof lastscan); /* XXX use new n_str object! */
1024 /* Now look, ignoring case, for the word in the string */
1025 if(ok_blook(searchheaders) && (hfield = strchr(str, ':'))){
1026 size_t l;
1028 l = PTR2SIZE(hfield - str);
1029 hfield = ac_alloc(l +1);
1030 memcpy(hfield, str, l);
1031 hfield[l] = '\0';
1032 hbody = hfieldX(hfield, mp);
1033 ac_free(hfield);
1034 hfield = n_UNCONST(str + l + 1);
1035 }else{
1036 hfield = n_UNCONST(str);
1037 hbody = hfield1("subject", mp);
1039 if(hbody == NULL)
1040 goto jleave;
1042 in.l = strlen(in.s = hbody);
1043 mime_fromhdr(&in, &out, TD_ICONV);
1044 rv = substr(out.s, hfield);
1045 free(out.s);
1046 jleave:
1047 NYD2_LEAVE;
1048 return rv;
1051 static bool_t
1052 a_message_match_at(struct message *mp, struct search_expr *sep){
1053 struct str in, out;
1054 char *nfield;
1055 char const *cfield;
1056 bool_t rv;
1057 NYD2_ENTER;
1059 rv = FAL0;
1060 nfield = savestr(sep->ss_where);
1062 while((cfield = n_strsep(&nfield, ',', TRU1)) != NULL){
1063 if(!asccasecmp(cfield, "body") ||
1064 (cfield[1] == '\0' && cfield[0] == '>')){
1065 rv = FAL0;
1066 jmsg:
1067 if((rv = message_match(mp, sep, rv)))
1068 break;
1069 continue;
1070 }else if(!asccasecmp(cfield, "text") ||
1071 (cfield[1] == '\0' && cfield[0] == '=')){
1072 rv = TRU1;
1073 goto jmsg;
1076 if(!asccasecmp(cfield, "header") ||
1077 (cfield[1] == '\0' && cfield[0] == '<')){
1078 if((rv = header_match(mp, sep)))
1079 break;
1080 continue;
1083 /* This is not a special name, so take care for the "skin" prefix !
1084 * and possible abbreviations */
1085 /* C99 */{
1086 struct name *np;
1087 bool_t doskin;
1089 if((doskin = (*cfield == '~')))
1090 ++cfield;
1091 if(cfield[0] != '\0' && cfield[1] == '\0'){
1092 char const x[][8] = {"from", "to", "cc", "bcc", "subject"};
1093 size_t i;
1094 char c1;
1096 c1 = lowerconv(cfield[0]);
1097 for(i = 0; i < n_NELEM(x); ++i){
1098 if(c1 == x[i][0]){
1099 cfield = x[i];
1100 break;
1104 if((in.s = hfieldX(cfield, mp)) == NULL)
1105 continue;
1107 /* Shall we split into address list and match the addresses only? */
1108 if(doskin){
1109 np = lextract(in.s, GSKIN);
1110 if(np == NULL)
1111 continue;
1112 out.s = np->n_name;
1113 }else{
1114 np = NULL;
1115 in.l = strlen(in.s);
1116 mime_fromhdr(&in, &out, TD_ICONV);
1119 jnext_name:
1120 #ifdef HAVE_REGEX
1121 if(sep->ss_sexpr == NULL)
1122 rv = (regexec(&sep->ss_regex, out.s, 0,NULL, 0) != REG_NOMATCH);
1123 else
1124 #endif
1125 rv = substr(out.s, sep->ss_sexpr);
1126 if(np == NULL)
1127 free(out.s);
1128 if(rv)
1129 break;
1130 if(np != NULL && (np = np->n_flink) != NULL){
1131 out.s = np->n_name;
1132 goto jnext_name;
1136 NYD2_LEAVE;
1137 return rv;
1140 static void
1141 a_message_unmark(int mesg){
1142 size_t i;
1143 NYD2_ENTER;
1145 i = (size_t)mesg;
1146 if(i < 1 || UICMP(z, i, >, msgCount))
1147 n_panic(_("Bad message number to unmark"));
1148 message[--i].m_flag &= ~MMARK;
1149 NYD2_LEAVE;
1152 static int
1153 a_message_metamess(int meta, int f)
1155 int c, m;
1156 struct message *mp;
1157 NYD2_ENTER;
1159 c = meta;
1160 switch (c) {
1161 case '^': /* First 'good' message left */
1162 mp = mb.mb_threaded ? threadroot : message;
1163 while (PTRCMP(mp, <, message + msgCount)) {
1164 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & MDELETED) == (ui32_t)f) {
1165 c = (int)PTR2SIZE(mp - message + 1);
1166 goto jleave;
1168 if (mb.mb_threaded) {
1169 mp = next_in_thread(mp);
1170 if (mp == NULL)
1171 break;
1172 } else
1173 ++mp;
1175 if (!(n_pstate & n_PS_HOOK_MASK))
1176 n_err(_("No applicable messages\n"));
1177 goto jem1;
1179 case '$': /* Last 'good message left */
1180 mp = mb.mb_threaded
1181 ? this_in_thread(threadroot, -1) : message + msgCount - 1;
1182 while (mp >= message) {
1183 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & MDELETED) == (ui32_t)f) {
1184 c = (int)PTR2SIZE(mp - message + 1);
1185 goto jleave;
1187 if (mb.mb_threaded) {
1188 mp = prev_in_thread(mp);
1189 if (mp == NULL)
1190 break;
1191 } else
1192 --mp;
1194 if (!(n_pstate & n_PS_HOOK_MASK))
1195 n_err(_("No applicable messages\n"));
1196 goto jem1;
1198 case '.':
1199 /* Current message */
1200 m = dot - message + 1;
1201 if ((dot->m_flag & MHIDDEN) || (dot->m_flag & MDELETED) != (ui32_t)f) {
1202 n_err(_("%d: inappropriate message\n"), m);
1203 goto jem1;
1205 c = m;
1206 break;
1208 case ';':
1209 /* Previously current message */
1210 if (prevdot == NULL) {
1211 n_err(_("No previously current message\n"));
1212 goto jem1;
1214 m = prevdot - message + 1;
1215 if ((prevdot->m_flag & MHIDDEN) ||
1216 (prevdot->m_flag & MDELETED) != (ui32_t)f) {
1217 n_err(_("%d: inappropriate message\n"), m);
1218 goto jem1;
1220 c = m;
1221 break;
1223 default:
1224 n_err(_("Unknown selector: %c\n"), c);
1225 goto jem1;
1227 jleave:
1228 NYD2_LEAVE;
1229 return c;
1230 jem1:
1231 c = -1;
1232 goto jleave;
1235 static void
1236 a_message__threadmark(struct message *self, int f){
1237 NYD2_ENTER;
1238 if(!(self->m_flag & MHIDDEN) &&
1239 (f == MDELETED || !(self->m_flag & MDELETED) || a_message_list_saw_d))
1240 self->m_flag |= MMARK;
1242 if((self = self->m_child) != NULL){
1243 goto jcall;
1244 while((self = self->m_younger) != NULL)
1245 if(self->m_child != NULL)
1246 jcall:
1247 a_message__threadmark(self, f);
1248 else
1249 self->m_flag |= MMARK;
1251 NYD2_LEAVE;
1254 FL FILE *
1255 setinput(struct mailbox *mp, struct message *m, enum needspec need){
1256 FILE *rv;
1257 enum okay ok;
1258 NYD_ENTER;
1260 rv = NULL;
1261 ok = STOP;
1263 switch(need){
1264 case NEED_HEADER:
1265 ok = (m->m_content_info & CI_HAVE_HEADER) ? OKAY
1266 : a_message_get_header(m);
1267 break;
1268 case NEED_BODY:
1269 ok = (m->m_content_info & CI_HAVE_BODY) ? OKAY : get_body(m);
1270 break;
1271 case NEED_UNSPEC:
1272 ok = OKAY;
1273 break;
1275 if(ok != OKAY)
1276 goto jleave;
1278 fflush(mp->mb_otf);
1279 if(fseek(mp->mb_itf, (long)mailx_positionof(m->m_block, m->m_offset),
1280 SEEK_SET) == -1){
1281 n_perr(_("fseek"), 0);
1282 n_panic(_("temporary file seek"));
1284 rv = mp->mb_itf;
1285 jleave:
1286 NYD_LEAVE;
1287 return rv;
1290 FL enum okay
1291 get_body(struct message *mp){
1292 enum okay rv;
1293 NYD_ENTER;
1294 n_UNUSED(mp);
1296 switch(mb.mb_type){
1297 case MB_FILE:
1298 case MB_MAILDIR:
1299 rv = OKAY;
1300 break;
1301 #ifdef HAVE_POP3
1302 case MB_POP3:
1303 rv = pop3_body(mp);
1304 break;
1305 #endif
1306 case MB_VOID:
1307 default:
1308 rv = STOP;
1309 break;
1311 NYD_LEAVE;
1312 return rv;
1315 FL void
1316 message_reset(void){
1317 NYD_ENTER;
1318 if(message != NULL){
1319 free(message);
1320 message = NULL;
1322 msgCount = 0;
1323 a_message_mem_space = 0;
1324 NYD_LEAVE;
1327 FL void
1328 message_append(struct message *mp){
1329 NYD_ENTER;
1330 if(UICMP(z, msgCount + 1, >=, a_message_mem_space)){
1331 /* XXX remove _mem_space magics (or use s_Vector) */
1332 a_message_mem_space = ((a_message_mem_space >= 128 &&
1333 a_message_mem_space <= 1000000)
1334 ? a_message_mem_space << 1 : a_message_mem_space + 64);
1335 message = srealloc(message, a_message_mem_space * sizeof(*message));
1337 if(msgCount > 0){
1338 if(mp != NULL)
1339 message[msgCount - 1] = *mp;
1340 else
1341 memset(&message[msgCount - 1], 0, sizeof *message);
1343 NYD_LEAVE;
1346 FL void
1347 message_append_null(void){
1348 NYD_ENTER;
1349 if(msgCount == 0)
1350 message_append(NULL);
1351 setdot(message);
1352 message[msgCount].m_size = 0;
1353 message[msgCount].m_lines = 0;
1354 NYD_LEAVE;
1357 FL bool_t
1358 message_match(struct message *mp, struct search_expr const *sep,
1359 bool_t with_headers){
1360 char **line;
1361 size_t *linesize, cnt;
1362 FILE *fp;
1363 bool_t rv;
1364 NYD_ENTER;
1366 rv = FAL0;
1368 if((fp = Ftmp(NULL, "mpmatch", OF_RDWR | OF_UNLINK | OF_REGISTER)) == NULL)
1369 goto j_leave;
1371 if(sendmp(mp, fp, NULL, NULL, SEND_TOSRCH, NULL) < 0)
1372 goto jleave;
1373 fflush_rewind(fp);
1375 cnt = fsize(fp);
1376 line = &termios_state.ts_linebuf; /* XXX line pool */
1377 linesize = &termios_state.ts_linesize; /* XXX line pool */
1379 if(!with_headers)
1380 while(fgetline(line, linesize, &cnt, NULL, fp, 0))
1381 if (**line == '\n')
1382 break;
1384 while(fgetline(line, linesize, &cnt, NULL, fp, 0)){
1385 #ifdef HAVE_REGEX
1386 if(sep->ss_sexpr == NULL){
1387 if(regexec(&sep->ss_regex, *line, 0,NULL, 0) == REG_NOMATCH)
1388 continue;
1389 }else
1390 #endif
1391 if(!substr(*line, sep->ss_sexpr))
1392 continue;
1393 rv = TRU1;
1394 break;
1397 jleave:
1398 Fclose(fp);
1399 j_leave:
1400 NYD_LEAVE;
1401 return rv;
1404 FL struct message *
1405 setdot(struct message *mp){
1406 NYD_ENTER;
1407 if(dot != mp){
1408 prevdot = dot;
1409 n_pstate &= ~n_PS_DID_PRINT_DOT;
1411 dot = mp;
1412 uncollapse1(dot, 0);
1413 NYD_LEAVE;
1414 return dot;
1417 FL void
1418 touch(struct message *mp){
1419 NYD_ENTER;
1420 mp->m_flag |= MTOUCH;
1421 if(!(mp->m_flag & MREAD))
1422 mp->m_flag |= MREAD | MSTATUS;
1423 NYD_LEAVE;
1426 FL int
1427 getmsglist(char *buf, int *vector, int flags)
1429 int *ip, mc;
1430 struct message *mp;
1431 NYD_ENTER;
1433 n_pstate &= ~n_PS_ARGLIST_MASK;
1434 a_message_list_last_saw_d = a_message_list_saw_d;
1435 a_message_list_saw_d = FAL0;
1437 if(msgCount == 0){
1438 *vector = 0;
1439 mc = 0;
1440 goto jleave;
1443 n_pstate |= n_PS_MSGLIST_DIRECT;
1445 if(a_message_markall(buf, flags) < 0){
1446 mc = -1;
1447 goto jleave;
1450 ip = vector;
1451 if(n_pstate & n_PS_HOOK_NEWMAIL){
1452 mc = 0;
1453 for(mp = message; mp < &message[msgCount]; ++mp)
1454 if(mp->m_flag & MMARK){
1455 if(!(mp->m_flag & MNEWEST))
1456 a_message_unmark((int)PTR2SIZE(mp - message + 1));
1457 else
1458 ++mc;
1460 if(mc == 0){
1461 mc = -1;
1462 goto jleave;
1466 if(mb.mb_threaded == 0){
1467 for(mp = message; mp < &message[msgCount]; ++mp)
1468 if(mp->m_flag & MMARK)
1469 *ip++ = (int)PTR2SIZE(mp - message + 1);
1470 }else{
1471 for(mp = threadroot; mp != NULL; mp = next_in_thread(mp))
1472 if(mp->m_flag & MMARK)
1473 *ip++ = (int)PTR2SIZE(mp - message + 1);
1475 *ip = 0;
1476 mc = (int)PTR2SIZE(ip - vector);
1477 if(mc != 1)
1478 n_pstate &= ~n_PS_MSGLIST_DIRECT;
1479 jleave:
1480 NYD_LEAVE;
1481 return mc;
1484 FL int
1485 first(int f, int m)
1487 struct message *mp;
1488 int rv;
1489 NYD_ENTER;
1491 if (msgCount == 0) {
1492 rv = 0;
1493 goto jleave;
1496 f &= MDELETED;
1497 m &= MDELETED;
1498 for (mp = dot;
1499 mb.mb_threaded ? (mp != NULL) : PTRCMP(mp, <, message + msgCount);
1500 mb.mb_threaded ? (mp = next_in_thread(mp)) : ++mp) {
1501 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & m) == (ui32_t)f) {
1502 rv = (int)PTR2SIZE(mp - message + 1);
1503 goto jleave;
1507 if (dot > message) {
1508 for (mp = dot - 1; (mb.mb_threaded ? (mp != NULL) : (mp >= message));
1509 mb.mb_threaded ? (mp = prev_in_thread(mp)) : --mp) {
1510 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & m) == (ui32_t)f) {
1511 rv = (int)PTR2SIZE(mp - message + 1);
1512 goto jleave;
1516 rv = 0;
1517 jleave:
1518 NYD_LEAVE;
1519 return rv;
1522 FL void
1523 mark(int mno, int f){
1524 struct message *mp;
1525 int i;
1526 NYD_ENTER;
1528 i = mno;
1529 if(i < 1 || i > msgCount)
1530 n_panic(_("Bad message number to mark"));
1531 mp = &message[--i];
1533 if(mb.mb_threaded == 1 && a_message_threadflag)
1534 a_message__threadmark(mp, f);
1535 else{
1536 assert(!(mp->m_flag & MHIDDEN));
1537 mp->m_flag |= MMARK;
1539 NYD_LEAVE;
1542 /* s-it-mode */