mk-release.inc: auto-append checksum file to announcement mail
[s-mailx.git] / message.c
blob0b82a3d2b471359d69a97550b9f94da658e0c016
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 - 2016 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 enum a_message_token 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},
104 {'\0', {0,}, 0, 0, 0}
107 static struct a_message_lex const a_message_singles[] = {
108 {'$', a_MESSAGE_T_DOLLAR},
109 {'.', a_MESSAGE_T_DOT},
110 {'^', a_MESSAGE_T_UP},
111 {'*', a_MESSAGE_T_ASTER},
112 {'-', a_MESSAGE_T_MINUS},
113 {'+', a_MESSAGE_T_PLUS},
114 {'(', a_MESSAGE_T_OPEN},
115 {')', a_MESSAGE_T_CLOSE},
116 {',', a_MESSAGE_T_COMMA},
117 {';', a_MESSAGE_T_SEMI},
118 {'`', a_MESSAGE_T_BACK},
119 {'\0', 0}
122 /* Slots in ::message */
123 static size_t a_message_mem_space;
125 /* Mark entire threads */
126 static bool_t a_message_threadflag;
128 /* :d on its way HACK TODO */
129 static bool_t a_message_list_saw_d, a_message_list_last_saw_d;
131 /* String from a_MESSAGE_T_STRING, scan() */
132 static struct str a_message_lexstr;
133 /* Number of a_MESSAGE_T_NUMBER from scan() */
134 static int a_message_lexno;
136 /* Lazy load message header fields */
137 static enum okay a_message_get_header(struct message *mp);
139 /* Append, taking care of resizes TODO vector */
140 static char ** a_message_add_to_namelist(char ***namelist, size_t *nmlsize,
141 char **np, char *string);
143 /* Mark all messages that the user wanted from the command line in the message
144 * structure. Return 0 on success, -1 on error */
145 static int a_message_markall(char *buf, int f);
147 /* Turn the character after a colon modifier into a bit value */
148 static int a_message_evalcol(int col);
150 /* Check the passed message number for legality and proper flags. Unless f is
151 * MDELETED the message has to be undeleted */
152 static bool_t a_message_check(int mno, int f);
154 /* Scan out a single lexical item and return its token number, updating the
155 * string pointer passed **sp. Also, store the value of the number or string
156 * scanned in a_message_lexno or a_message_lexstr as appropriate.
157 * In any event, store the scanned "thing" in a_message_lexstr.
158 * Returns the token as a negative number when we also saw & to mark a thread */
159 static int a_message_scan(char **sp);
161 /* See if the passed name sent the passed message */
162 static bool_t a_message_match_sender(struct message *mp, char const *str,
163 bool_t allnet);
165 /* Check whether the given message-id or references match */
166 static bool_t a_message_match_mid(struct message *mp, char const *id,
167 enum a_message_idfield idfield);
169 /* See if the given string matches.
170 * For the purpose of the scan, we ignore case differences.
171 * This is the engine behind the "/" search */
172 static bool_t a_message_match_dash(struct message *mp, char const *str);
174 /* See if the given search expression matches.
175 * For the purpose of the scan, we ignore case differences.
176 * This is the engine behind the "@[..]@" search */
177 static bool_t a_message_match_at(struct message *mp, struct search_expr *sep);
179 /* Unmark the named message */
180 static void a_message_unmark(int mesg);
182 /* Return the message number corresponding to the passed meta character */
183 static int a_message_metamess(int meta, int f);
185 /* Helper for mark(): self valid, threading enabled */
186 static void a_message__threadmark(struct message *self, int f);
188 static enum okay
189 a_message_get_header(struct message *mp){
190 enum okay rv;
191 NYD2_ENTER;
192 UNUSED(mp);
194 switch(mb.mb_type){
195 case MB_FILE:
196 case MB_MAILDIR:
197 rv = OKAY;
198 break;
199 #ifdef HAVE_POP3
200 case MB_POP3:
201 rv = pop3_header(mp);
202 break;
203 #endif
204 case MB_VOID:
205 default:
206 rv = STOP;
207 break;
209 NYD2_LEAVE;
210 return rv;
213 static char **
214 a_message_add_to_namelist(char ***namelist, size_t *nmlsize, char **np,
215 char *string){
216 size_t idx;
217 NYD2_ENTER;
219 if((idx = PTR2SIZE(np - *namelist)) >= *nmlsize){
220 *namelist = srealloc(*namelist, (*nmlsize += 8) * sizeof *np);
221 np = &(*namelist)[idx];
223 *np++ = string;
224 NYD2_LEAVE;
225 return np;
228 static int
229 a_message_markall(char *buf, int f){
230 struct message *mp, *mx;
231 enum a_message_idfield idfield;
232 size_t j, nmlsize;
233 char const *id;
234 char **np, **nq, **namelist, *bufp, *cp;
235 int i, valdot, beg, colmod, tok, colresult;
236 enum{
237 a_NONE = 0,
238 a_ALLNET = 1u<<0,
239 a_ALLOC = 1u<<1, /* Have allocated something */
240 a_ERROR = 1u<<2,
241 a_ANY = 1u<<3, /* Have marked just ANY */
242 a_RANGE_OPEN = 1u<<8, /* Seen dash, await close */
243 a_ASTER = 1u<<16,
244 a_TOPEN = 1u<<17, /* ( used (and didn't match) */
245 a_TBACK = 1u<<18, /* ` used (and didn't match) */
246 a_TMP = 1u<<30
247 } flags;
248 NYD_ENTER;
249 n_LCTA((ui32_t)a_ALLNET == (ui32_t)TRU1,
250 "Constant is converted to bool_t via AND, thus");
252 /* Update message array: clear MMARK but remember its former state for `.
253 * An empty selector input is identical to * asterisk */
254 for(i = 0; i < msgCount; ++i){
255 enum mflag mf;
257 mf = (mp = &message[i])->m_flag;
258 if(mf & MMARK)
259 mf |= MOLDMARK;
260 else
261 mf &= ~MOLDMARK;
262 mf &= ~MMARK;
263 mp->m_flag = mf;
266 /* Strip all leading WS from user buffer */
267 while(blankspacechar(*buf))
268 ++buf;
269 /* If there is no input buffer, we are done! */
270 if(buf[0] == '\0'){
271 flags = a_NONE;
272 goto jleave;
275 UNINIT(beg, 0);
276 UNINIT(idfield, a_MESSAGE_ID_REFERENCES);
277 a_message_threadflag = FAL0;
278 a_message_lexstr.s = ac_alloc(a_message_lexstr.l = 2 * strlen(buf) +1);
279 np = namelist = smalloc((nmlsize = 8) * sizeof *namelist); /* TODO vector */
280 bufp = buf;
281 valdot = (int)PTR2SIZE(dot - message + 1);
282 colmod = 0;
283 id = NULL;
284 flags = a_ALLOC;
286 while((tok = a_message_scan(&bufp)) != a_MESSAGE_T_EOL){
287 if((a_message_threadflag = (tok < 0)))
288 tok &= INT_MAX;
290 switch(tok){
291 case a_MESSAGE_T_NUMBER:
292 pstate |= PS_MSGLIST_GABBY;
293 jnumber:
294 if(flags & a_RANGE_OPEN){
295 int i_base;
297 flags ^= a_RANGE_OPEN;
299 if(!a_message_check(a_message_lexno, f))
300 goto jerr;
301 if(beg < a_message_lexno){
302 i = beg;
303 beg = 1; /* TODO does not work: (i < a_message_lexno)
304 * TODO we need to detect whether both ends of a range
305 * TODO belong to the same thread first, then iterate
306 * TODO over the subset in between those points */
307 }else{
308 i = a_message_lexno;
309 a_message_lexno = beg;
312 /* Problem: until the TODO above can be worked and we simply get an
313 * iterator object for the thread (-range), we need to walk
314 * a threaded list two times */
315 i_base = i;
316 jnumber__thr:
317 while(mb.mb_threaded ? 1 : i <= a_message_lexno){
318 mp = &message[i - 1];
319 if((!mb.mb_threaded || i_base < 0) &&
320 !(mp->m_flag & MHIDDEN) &&
321 (f == MDELETED || !(mp->m_flag & MDELETED))){
322 mark(i, f);
323 flags |= a_ANY;
325 if(mb.mb_threaded){
326 if(i == a_message_lexno)
327 break;
328 mx = beg ? next_in_thread(mp) : prev_in_thread(mp);
329 if(mx == NULL){
330 id = N_("Range crosses multiple threads\n");
331 goto jerrmsg;
333 i = (int)PTR2SIZE(mx - message + 1);
334 }else
335 ++i;
337 if(i_base >= 0){
338 i = i_base;
339 i_base = -1;
340 goto jnumber__thr;
342 beg = 0;
343 break;
344 }else{
345 if(!a_message_check(a_message_lexno, f))
346 goto jerr;
347 /* Inclusive range? */
348 if(bufp[0] == '-'){
349 ++bufp;
350 beg = a_message_lexno;
351 flags |= a_RANGE_OPEN;
352 }else{
353 mark(a_message_lexno, f);
354 flags |= a_ANY;
357 break;
358 case a_MESSAGE_T_PLUS:
359 pstate &= ~PS_MSGLIST_DIRECT;
360 pstate |= PS_MSGLIST_GABBY;
361 i = valdot;
363 if(mb.mb_threaded){
364 mx = next_in_thread(&message[i - 1]);
365 i = mx ? (int)PTR2SIZE(mx - message + 1) : msgCount + 1;
366 }else
367 ++i;
368 if(i > msgCount){
369 id = N_("Referencing beyond last message\n");
370 goto jerrmsg;
372 }while(message[i - 1].m_flag == MHIDDEN ||
373 (message[i - 1].m_flag & MDELETED) != (unsigned)f);
374 a_message_lexno = i;
375 goto jnumber;
376 case a_MESSAGE_T_MINUS:
377 pstate &= ~PS_MSGLIST_DIRECT;
378 pstate |= PS_MSGLIST_GABBY;
379 i = valdot;
381 if(mb.mb_threaded){
382 mx = prev_in_thread(&message[i - 1]);
383 i = mx ? (int)PTR2SIZE(mx - message + 1) : 0;
384 }else
385 --i;
386 if(i <= 0){
387 id = N_("Referencing before first message\n");
388 goto jerrmsg;
390 }while(message[i - 1].m_flag == MHIDDEN ||
391 (message[i - 1].m_flag & MDELETED) != (unsigned)f);
392 a_message_lexno = i;
393 goto jnumber;
394 case a_MESSAGE_T_STRING:
395 pstate &= ~PS_MSGLIST_DIRECT;
396 if(flags & a_RANGE_OPEN)
397 goto jebadrange;
399 /* This may be a colon modifier */
400 if((cp = a_message_lexstr.s)[0] != ':')
401 np = a_message_add_to_namelist(&namelist, &nmlsize, np,
402 savestr(a_message_lexstr.s));
403 else{
404 while(*++cp != '\0'){
405 colresult = a_message_evalcol(*cp);
406 if(colresult == 0){
407 n_err(_("Unknown colon modifier: %s\n"), a_message_lexstr.s);
408 goto jerr;
410 if(colresult == a_MESSAGE_S_DELETED){
411 a_message_list_saw_d = TRU1;
412 f |= MDELETED;
414 colmod |= colresult;
417 break;
418 case a_MESSAGE_T_OPEN:
419 pstate &= ~PS_MSGLIST_DIRECT;
420 if(flags & a_RANGE_OPEN)
421 goto jebadrange;
422 flags |= a_TOPEN;
424 #ifdef HAVE_IMAP_SEARCH
425 /* C99 */{
426 ssize_t ires;
428 if((ires = imap_search(a_message_lexstr.s, f)) >= 0){
429 if(ires > 0)
430 flags |= a_ANY;
431 break;
434 #else
435 n_err(_("Optional selector is not available: %s\n"),
436 a_message_lexstr.s);
437 #endif
438 goto jerr;
439 case a_MESSAGE_T_DOLLAR:
440 case a_MESSAGE_T_UP:
441 case a_MESSAGE_T_SEMI:
442 pstate |= PS_MSGLIST_GABBY;
443 /* FALLTHRU */
444 case a_MESSAGE_T_DOT: /* Don't set _GABBY for dot, to _allow_ history.. */
445 pstate &= ~PS_MSGLIST_DIRECT;
446 a_message_lexno = a_message_metamess(a_message_lexstr.s[0], f);
447 if(a_message_lexno == -1)
448 goto jerr;
449 goto jnumber;
450 case a_MESSAGE_T_BACK:
451 pstate &= ~PS_MSGLIST_DIRECT;
452 if(flags & a_RANGE_OPEN)
453 goto jebadrange;
455 flags |= a_TBACK;
456 for(i = 0; i < msgCount; ++i){
457 if((mp = &message[i])->m_flag & MHIDDEN)
458 continue;
459 if((mp->m_flag & MDELETED) != (unsigned)f){
460 if(!a_message_list_last_saw_d)
461 continue;
462 a_message_list_saw_d = TRU1;
464 if(mp->m_flag & MOLDMARK){
465 mark(i + 1, f);
466 flags &= ~a_TBACK;
467 flags |= a_ANY;
470 break;
471 case a_MESSAGE_T_ASTER:
472 pstate &= ~PS_MSGLIST_DIRECT;
473 if(flags & a_RANGE_OPEN)
474 goto jebadrange;
475 flags |= a_ASTER;
476 break;
477 case a_MESSAGE_T_COMMA:
478 pstate &= ~PS_MSGLIST_DIRECT;
479 pstate |= PS_MSGLIST_GABBY;
480 if(flags & a_RANGE_OPEN)
481 goto jebadrange;
483 if(id == NULL){
484 if((cp = hfield1("in-reply-to", dot)) != NULL)
485 idfield = a_MESSAGE_ID_IN_REPLY_TO;
486 else if((cp = hfield1("references", dot)) != NULL){
487 struct name *enp;
489 if((enp = extract(cp, GREF)) != NULL){
490 while(enp->n_flink != NULL)
491 enp = enp->n_flink;
492 cp = enp->n_name;
493 idfield = a_MESSAGE_ID_REFERENCES;
494 }else
495 cp = NULL;
498 if(cp != NULL)
499 id = savestr(cp);
500 else{
501 id = N_("Message-ID of parent of \"dot\" is indeterminable\n");
502 goto jerrmsg;
504 }else if(!(pstate & PS_HOOK) && (options & OPT_D_V))
505 n_err(_("Ignoring redundant specification of , selector\n"));
506 break;
507 case a_MESSAGE_T_ERROR:
508 pstate &= ~PS_MSGLIST_DIRECT;
509 pstate |= PS_MSGLIST_GABBY;
510 goto jerr;
513 /* Explicitly disallow invalid ranges for future safety */
514 if(bufp[0] == '-' && !(flags & a_RANGE_OPEN)){
515 if(!(pstate & PS_HOOK))
516 n_err(_("Ignoring invalid range before: %s\n"), bufp);
517 ++bufp;
520 if(flags & a_RANGE_OPEN){
521 id = N_("Missing second range argument\n");
522 goto jerrmsg;
525 np = a_message_add_to_namelist(&namelist, &nmlsize, np, NULL);
526 --np;
528 /* * is special at this point, after we have parsed the entire line */
529 if(flags & a_ASTER){
530 for(i = 0; i < msgCount; ++i){
531 if((mp = &message[i])->m_flag & MHIDDEN)
532 continue;
533 if(!a_message_list_saw_d && (mp->m_flag & MDELETED) != (unsigned)f)
534 continue;
535 mark(i + 1, f);
536 flags |= a_ANY;
538 if(!(flags & a_ANY))
539 goto jenoapp;
540 goto jleave;
543 /* If any names were given, add any messages which match */
544 if(np > namelist || id != NULL){
545 struct search_expr *sep = NULL;
547 /* The @ search works with struct search_expr, so build an array.
548 * To simplify array, i.e., regex_t destruction, and optimize for the
549 * common case we walk the entire array even in case of errors */
550 if(np > namelist){
551 sep = scalloc(PTR2SIZE(np - namelist), sizeof(*sep));
552 for(j = 0, nq = namelist; *nq != NULL; ++j, ++nq){
553 char *x, *y;
555 sep[j].ss_sexpr = x = *nq;
556 if(*x != '@' || (flags & a_ERROR))
557 continue;
559 for(y = &x[1];; ++y){
560 if(*y == '\0' || !fieldnamechar(*y)){
561 x = NULL;
562 break;
564 if(*y == '@'){
565 x = y;
566 break;
569 sep[j].ss_where = (x == NULL || x - 1 == *nq)
570 ? "subject" : savestrbuf(&(*nq)[1], PTR2SIZE(x - *nq) - 1);
572 x = (x == NULL ? *nq : x) + 1;
573 if(*x == '\0'){ /* XXX Simply remove from list instead? */
574 n_err(_("Empty [@..]@ search expression\n"));
575 flags |= a_ERROR;
576 continue;
578 #ifdef HAVE_REGEX
579 if(is_maybe_regex(x)){
580 sep[j].ss_sexpr = NULL;
581 if(regcomp(&sep[j].ss_regex, x,
582 REG_EXTENDED | REG_ICASE | REG_NOSUB) != 0){
583 if(!(pstate & PS_HOOK) && (options & OPT_D_V))
584 n_err(_("Invalid regular expression: >>> %s <<<\n"), x);
585 flags |= a_ERROR;
586 continue;
588 }else
589 #endif
590 sep[j].ss_sexpr = x;
592 if(flags & a_ERROR)
593 goto jnamesearch_sepfree;
596 /* Iterate the entire message array */
597 srelax_hold();
598 if(ok_blook(allnet))
599 flags |= a_ALLNET;
600 for(i = 0; i < msgCount; ++i){
601 mp = &message[i];
602 if(mp->m_flag & MMARK)
603 continue;
605 flags &= ~a_TMP;
606 if(np > namelist){
607 for(nq = namelist; *nq != NULL; ++nq){
608 if(**nq == '@'){
609 if(a_message_match_at(mp, sep + PTR2SIZE(nq - namelist))){
610 flags |= a_TMP;
611 break;
613 }else if(**nq == '/'){
614 if(a_message_match_dash(mp, *nq)){
615 flags |= a_TMP;
616 break;
618 }else if(a_message_match_sender(mp, *nq, (flags & a_ALLNET))){
619 flags |= a_TMP;
620 break;
624 if(!(flags & a_TMP) &&
625 id != NULL && a_message_match_mid(mp, id, idfield))
626 flags |= a_TMP;
628 if(flags & a_TMP){
629 mark(i + 1, f);
630 flags |= a_ANY;
632 srelax();
634 srelax_rele();
636 jnamesearch_sepfree:
637 if(sep != NULL){
638 #ifdef HAVE_REGEX
639 for(j = PTR2SIZE(np - namelist); j-- != 0;)
640 if(sep[j].ss_sexpr == NULL)
641 regfree(&sep[j].ss_regex);
642 #endif
643 free(sep);
645 if(flags & a_ERROR)
646 goto jerr;
649 /* If any colon modifiers were given, go through and mark any messages which
650 * do satisfy the modifiers */
651 if(colmod != 0){
652 for(i = 0; i < msgCount; ++i){
653 struct a_message_coltab const *colp;
655 if((mp = &message[i])->m_flag & MMARK)
656 continue;
658 for(colp = a_message_coltabs; colp->mco_char != '\0'; ++colp)
659 if((colp->mco_bit & colmod) &&
660 ((mp->m_flag & colp->mco_mask) == (unsigned)colp->mco_equal)){
661 mark(i + 1, f);
662 flags |= a_ANY;
663 break;
668 /* It shall be an error if ` didn't match anything, and nothing else did */
669 if((flags & (a_TBACK | a_ANY)) == a_TBACK){
670 id = N_("No previously marked messages\n");
671 goto jerrmsg;
672 }else if(!(flags & a_ANY))
673 goto jenoapp;
675 assert(!(flags & a_ERROR));
676 jleave:
677 if(flags & a_ALLOC){
678 free(namelist);
679 ac_free(a_message_lexstr.s);
681 NYD_LEAVE;
682 return (flags & a_ERROR) ? -1 : 0;
684 jebadrange:
685 id = N_("Invalid range endpoint\n");
686 goto jerrmsg;
687 jenoapp:
688 id = N_("No applicable messages\n");
689 jerrmsg:
690 if(!(pstate & PS_HOOK_MASK))
691 n_err(V_(id));
692 jerr:
693 flags |= a_ERROR;
694 goto jleave;
697 static int
698 a_message_evalcol(int col){
699 struct a_message_coltab const *colp;
700 int rv;
701 NYD2_ENTER;
703 rv = 0;
704 for(colp = a_message_coltabs; colp->mco_char != '\0'; ++colp)
705 if(colp->mco_char == col){
706 rv = colp->mco_bit;
707 break;
709 NYD2_LEAVE;
710 return rv;
713 static bool_t
714 a_message_check(int mno, int f){
715 struct message *mp;
716 NYD2_ENTER;
718 if(mno < 1 || mno > msgCount){
719 n_err(_("%d: Invalid message number\n"), mno);
720 mno = 1;
721 }else if(((mp = &message[mno - 1])->m_flag & MHIDDEN) ||
722 (f != MDELETED && (mp->m_flag & MDELETED) != 0))
723 n_err(_("%d: inappropriate message\n"), mno);
724 else
725 mno = 0;
726 NYD2_LEAVE;
727 return (mno == 0);
730 static int
731 a_message_scan(char **sp)
733 char *cp, *cp2;
734 struct a_message_lex const *lp;
735 int rv, c, inquote, quotec;
736 NYD_ENTER;
738 rv = a_MESSAGE_T_EOL;
740 cp = *sp;
741 cp2 = a_message_lexstr.s;
742 c = *cp++;
744 /* strip away leading white space */
745 while(blankchar(c))
746 c = *cp++;
748 /* If no characters remain, we are at end of line, so report that */
749 if(c == '\0'){
750 *sp = --cp;
751 goto jleave;
754 /* Select members of a message thread */
755 if(c == '&'){
756 if(*cp == '\0' || spacechar(*cp)){
757 a_message_lexstr.s[0] = '.';
758 a_message_lexstr.s[1] = '\0';
759 *sp = cp;
760 rv = a_MESSAGE_T_DOT | INT_MIN;
761 goto jleave;
763 rv = INT_MIN;
764 c = *cp++;
767 /* If the leading character is a digit, scan the number and convert it
768 * on the fly. Return a_MESSAGE_T_NUMBER when done */
769 if(digitchar(c)){
770 a_message_lexno = 0;
772 a_message_lexno = (a_message_lexno * 10) + c - '0';
773 *cp2++ = c;
774 }while((c = *cp++, digitchar(c)));
775 *cp2 = '\0';
776 *sp = --cp;
777 rv |= a_MESSAGE_T_NUMBER;
778 goto jleave;
781 /* An IMAP SEARCH list. Note that a_MESSAGE_T_OPEN has always been included
782 * in singles[] in Mail and mailx. Thus although there is no formal
783 * definition for (LIST) lists, they do not collide with historical
784 * practice because a subject string (LIST) could never been matched
785 * this way */
786 if (c == '(') {
787 ui32_t level = 1;
788 inquote = 0;
789 *cp2++ = c;
790 do {
791 if ((c = *cp++&0377) == '\0') {
792 jmtop:
793 n_err(_("Missing )\n"));
794 rv = a_MESSAGE_T_ERROR;
795 goto jleave;
797 if (inquote && c == '\\') {
798 *cp2++ = c;
799 c = *cp++&0377;
800 if (c == '\0')
801 goto jmtop;
802 } else if (c == '"')
803 inquote = !inquote;
804 else if (inquote)
805 /*EMPTY*/;
806 else if (c == '(')
807 ++level;
808 else if (c == ')')
809 --level;
810 else if (spacechar(c)) {
811 /* Replace unquoted whitespace by single space characters, to make
812 * the string IMAP SEARCH conformant */
813 c = ' ';
814 if (cp2[-1] == ' ')
815 --cp2;
817 *cp2++ = c;
818 } while (c != ')' || level > 0);
819 *cp2 = '\0';
820 *sp = cp;
821 rv |= a_MESSAGE_T_OPEN;
822 goto jleave;
825 /* Check for single character tokens; return such if found */
826 for(lp = a_message_singles; lp->ml_char != '\0'; ++lp)
827 if(c == lp->ml_char){
828 a_message_lexstr.s[0] = c;
829 a_message_lexstr.s[1] = '\0';
830 *sp = cp;
831 rv |= lp->ml_token;
832 goto jleave;
835 /* We've got a string! Copy all the characters of the string into
836 * a_message_lexstr, until we see a null, space, or tab. If the lead
837 * character is a " or ', save it and scan until you get another */
838 quotec = 0;
839 if (c == '\'' || c == '"') {
840 quotec = c;
841 c = *cp++;
843 while (c != '\0') {
844 if (quotec == 0 && c == '\\' && *cp != '\0')
845 c = *cp++;
846 if (c == quotec) {
847 ++cp;
848 break;
850 if (quotec == 0 && blankchar(c))
851 break;
852 if (PTRCMP(cp2 - a_message_lexstr.s, <, a_message_lexstr.l))
853 *cp2++ = c;
854 c = *cp++;
856 if (quotec && c == 0) {
857 n_err(_("Missing %c\n"), quotec);
858 rv = a_MESSAGE_T_ERROR;
859 goto jleave;
861 *sp = --cp;
862 *cp2 = '\0';
863 rv |= a_MESSAGE_T_STRING;
864 jleave:
865 NYD_LEAVE;
866 return rv;
869 static bool_t
870 a_message_match_sender(struct message *mp, char const *str, bool_t allnet){
871 char const *str_base, *np_base, *np;
872 char sc, nc;
873 bool_t rv;
874 NYD2_ENTER;
876 /* Empty string doesn't match */
877 if(*(str_base = str) == '\0'){
878 rv = FAL0;
879 goto jleave;
882 /* *allnet* is POSIX and, since it explicitly mentions login and user names,
883 * most likely case-sensitive. XXX Still allow substr matching, though
884 * XXX possibly the first letter should be case-insensitive, then? */
885 if(allnet){
886 for(np_base = np = nameof(mp, 0);;){
887 if((sc = *str++) == '@')
888 sc = '\0';
889 if((nc = *np++) == '@' || nc == '\0' || sc == '\0')
890 break;
891 if(sc != nc){
892 np = ++np_base;
893 str = str_base;
896 rv = (sc == '\0');
897 }else{
898 /* TODO POSIX says ~"match any address as shown in header overview",
899 * TODO but a normalized match would be more sane i guess.
900 * TODO struct name should gain a comparison method, normalize realname
901 * TODO content (in TODO) and thus match as likewise
902 * TODO "Buddy (Today) <here>" and "(Now) Buddy <here>" */
903 char const *real_base;
904 bool_t again;
906 real_base = name1(mp, 0);
907 again = ok_blook(showname);
908 jagain:
909 np_base = np = again ? realname(real_base) : skin(real_base);
910 str = str_base;
911 for(;;){
912 sc = *str++;
913 if((nc = *np++) == '\0' || sc == '\0')
914 break;
915 sc = upperconv(sc);
916 nc = upperconv(nc);
917 if(sc != nc){
918 np = ++np_base;
919 str = str_base;
923 /* And really if i want to match 'on@' then i want it to match even if
924 * *showname* is set! */
925 if(!(rv = (sc == '\0')) && again){
926 again = FAL0;
927 goto jagain;
930 jleave:
931 NYD2_LEAVE;
932 return rv;
935 static bool_t
936 a_message_match_mid(struct message *mp, char const *id,
937 enum a_message_idfield idfield){
938 char const *cp;
939 bool_t rv;
940 NYD2_ENTER;
942 rv = FAL0;
944 if((cp = hfield1("message-id", mp)) != NULL){
945 switch(idfield){
946 case a_MESSAGE_ID_REFERENCES:
947 if(!msgidcmp(id, cp))
948 rv = TRU1;
949 break;
950 case a_MESSAGE_ID_IN_REPLY_TO:{
951 struct name *np;
953 if((np = extract(id, GREF)) != NULL)
955 if(!msgidcmp(np->n_name, cp)){
956 rv = TRU1;
957 break;
959 }while((np = np->n_flink) != NULL);
960 break;
964 NYD2_LEAVE;
965 return rv;
968 static bool_t
969 a_message_match_dash(struct message *mp, char const *str){
970 static char lastscan[128];
972 struct str in, out;
973 char *hfield, *hbody;
974 bool_t rv;
975 NYD2_ENTER;
977 rv = FAL0;
979 if(*++str == '\0')
980 str = lastscan;
981 else
982 n_strscpy(lastscan, str, sizeof lastscan); /* XXX use new n_str object! */
984 /* Now look, ignoring case, for the word in the string */
985 if(ok_blook(searchheaders) && (hfield = strchr(str, ':'))){
986 size_t l;
988 l = PTR2SIZE(hfield - str);
989 hfield = ac_alloc(l +1);
990 memcpy(hfield, str, l);
991 hfield[l] = '\0';
992 hbody = hfieldX(hfield, mp);
993 ac_free(hfield);
994 hfield = UNCONST(str + l + 1);
995 }else{
996 hfield = UNCONST(str);
997 hbody = hfield1("subject", mp);
999 if(hbody == NULL)
1000 goto jleave;
1002 in.l = strlen(in.s = hbody);
1003 mime_fromhdr(&in, &out, TD_ICONV);
1004 rv = substr(out.s, hfield);
1005 free(out.s);
1006 jleave:
1007 NYD2_LEAVE;
1008 return rv;
1011 static bool_t
1012 a_message_match_at(struct message *mp, struct search_expr *sep){
1013 struct str in, out;
1014 char *nfield;
1015 char const *cfield;
1016 bool_t rv;
1017 NYD2_ENTER;
1019 rv = FAL0;
1020 nfield = savestr(sep->ss_where);
1022 while((cfield = n_strsep(&nfield, ',', TRU1)) != NULL){
1023 if(!asccasecmp(cfield, "body") ||
1024 (cfield[1] == '\0' && cfield[0] == '>')){
1025 rv = FAL0;
1026 jmsg:
1027 if((rv = message_match(mp, sep, rv)))
1028 break;
1029 continue;
1030 }else if(!asccasecmp(cfield, "text") ||
1031 (cfield[1] == '\0' && cfield[0] == '=')){
1032 rv = TRU1;
1033 goto jmsg;
1036 if(!asccasecmp(cfield, "header") ||
1037 (cfield[1] == '\0' && cfield[0] == '<')){
1038 if((rv = header_match(mp, sep)))
1039 break;
1040 continue;
1043 /* This is not a special name, so take care for the "skin" prefix !
1044 * and possible abbreviations */
1045 /* C99 */{
1046 struct name *np;
1047 bool_t doskin;
1049 if((doskin = (*cfield == '~')))
1050 ++cfield;
1051 if(cfield[0] != '\0' && cfield[1] == '\0'){
1052 char const x[][8] = {"from", "to", "cc", "bcc", "subject"};
1053 size_t i;
1054 char c1;
1056 c1 = lowerconv(cfield[0]);
1057 for(i = 0; i < NELEM(x); ++i){
1058 if(c1 == x[i][0]){
1059 cfield = x[i];
1060 break;
1064 if((in.s = hfieldX(cfield, mp)) == NULL)
1065 continue;
1067 /* Shall we split into address list and match the addresses only? */
1068 if(doskin){
1069 np = lextract(in.s, GSKIN);
1070 if(np == NULL)
1071 continue;
1072 out.s = np->n_name;
1073 }else{
1074 np = NULL;
1075 in.l = strlen(in.s);
1076 mime_fromhdr(&in, &out, TD_ICONV);
1079 jnext_name:
1080 #ifdef HAVE_REGEX
1081 if(sep->ss_sexpr == NULL)
1082 rv = (regexec(&sep->ss_regex, out.s, 0,NULL, 0) != REG_NOMATCH);
1083 else
1084 #endif
1085 rv = substr(out.s, sep->ss_sexpr);
1086 if(np == NULL)
1087 free(out.s);
1088 if(rv)
1089 break;
1090 if(np != NULL && (np = np->n_flink) != NULL){
1091 out.s = np->n_name;
1092 goto jnext_name;
1096 NYD2_LEAVE;
1097 return rv;
1100 static void
1101 a_message_unmark(int mesg){
1102 size_t i;
1103 NYD2_ENTER;
1105 i = (size_t)mesg;
1106 if(i < 1 || UICMP(z, i, >, msgCount))
1107 n_panic(_("Bad message number to unmark"));
1108 message[--i].m_flag &= ~MMARK;
1109 NYD2_LEAVE;
1112 static int
1113 a_message_metamess(int meta, int f)
1115 int c, m;
1116 struct message *mp;
1117 NYD2_ENTER;
1119 c = meta;
1120 switch (c) {
1121 case '^': /* First 'good' message left */
1122 mp = mb.mb_threaded ? threadroot : message;
1123 while (PTRCMP(mp, <, message + msgCount)) {
1124 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & MDELETED) == (ui32_t)f) {
1125 c = (int)PTR2SIZE(mp - message + 1);
1126 goto jleave;
1128 if (mb.mb_threaded) {
1129 mp = next_in_thread(mp);
1130 if (mp == NULL)
1131 break;
1132 } else
1133 ++mp;
1135 if (!(pstate & PS_HOOK_MASK))
1136 n_err(_("No applicable messages\n"));
1137 goto jem1;
1139 case '$': /* Last 'good message left */
1140 mp = mb.mb_threaded
1141 ? this_in_thread(threadroot, -1) : message + msgCount - 1;
1142 while (mp >= message) {
1143 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & MDELETED) == (ui32_t)f) {
1144 c = (int)PTR2SIZE(mp - message + 1);
1145 goto jleave;
1147 if (mb.mb_threaded) {
1148 mp = prev_in_thread(mp);
1149 if (mp == NULL)
1150 break;
1151 } else
1152 --mp;
1154 if (!(pstate & PS_HOOK_MASK))
1155 n_err(_("No applicable messages\n"));
1156 goto jem1;
1158 case '.':
1159 /* Current message */
1160 m = dot - message + 1;
1161 if ((dot->m_flag & MHIDDEN) || (dot->m_flag & MDELETED) != (ui32_t)f) {
1162 n_err(_("%d: inappropriate message\n"), m);
1163 goto jem1;
1165 c = m;
1166 break;
1168 case ';':
1169 /* Previously current message */
1170 if (prevdot == NULL) {
1171 n_err(_("No previously current message\n"));
1172 goto jem1;
1174 m = prevdot - message + 1;
1175 if ((prevdot->m_flag & MHIDDEN) ||
1176 (prevdot->m_flag & MDELETED) != (ui32_t)f) {
1177 n_err(_("%d: inappropriate message\n"), m);
1178 goto jem1;
1180 c = m;
1181 break;
1183 default:
1184 n_err(_("Unknown selector: %c\n"), c);
1185 goto jem1;
1187 jleave:
1188 NYD2_LEAVE;
1189 return c;
1190 jem1:
1191 c = -1;
1192 goto jleave;
1195 static void
1196 a_message__threadmark(struct message *self, int f){
1197 NYD2_ENTER;
1198 if(!(self->m_flag & MHIDDEN) &&
1199 (f == MDELETED || !(self->m_flag & MDELETED) || a_message_list_saw_d))
1200 self->m_flag |= MMARK;
1202 if((self = self->m_child) != NULL){
1203 goto jcall;
1204 while((self = self->m_younger) != NULL)
1205 if(self->m_child != NULL)
1206 jcall:
1207 a_message__threadmark(self, f);
1208 else
1209 self->m_flag |= MMARK;
1211 NYD2_LEAVE;
1214 FL FILE *
1215 setinput(struct mailbox *mp, struct message *m, enum needspec need){
1216 FILE *rv;
1217 enum okay ok;
1218 NYD_ENTER;
1220 rv = NULL;
1221 ok = STOP;
1223 switch(need){
1224 case NEED_HEADER:
1225 ok = (m->m_have & HAVE_HEADER) ? OKAY : a_message_get_header(m);
1226 break;
1227 case NEED_BODY:
1228 ok = (m->m_have & HAVE_BODY) ? OKAY : get_body(m);
1229 break;
1230 case NEED_UNSPEC:
1231 ok = OKAY;
1232 break;
1234 if(ok != OKAY)
1235 goto jleave;
1237 fflush(mp->mb_otf);
1238 if(fseek(mp->mb_itf, (long)mailx_positionof(m->m_block, m->m_offset),
1239 SEEK_SET) == -1){
1240 n_perr(_("fseek"), 0);
1241 n_panic(_("temporary file seek"));
1243 rv = mp->mb_itf;
1244 jleave:
1245 NYD_LEAVE;
1246 return rv;
1249 FL enum okay
1250 get_body(struct message *mp){
1251 enum okay rv;
1252 NYD_ENTER;
1253 UNUSED(mp);
1255 switch(mb.mb_type){
1256 case MB_FILE:
1257 case MB_MAILDIR:
1258 rv = OKAY;
1259 break;
1260 #ifdef HAVE_POP3
1261 case MB_POP3:
1262 rv = pop3_body(mp);
1263 break;
1264 #endif
1265 case MB_VOID:
1266 default:
1267 rv = STOP;
1268 break;
1270 NYD_LEAVE;
1271 return rv;
1274 FL void
1275 message_reset(void){
1276 NYD_ENTER;
1277 if(message != NULL){
1278 free(message);
1279 message = NULL;
1281 msgCount = 0;
1282 a_message_mem_space = 0;
1283 NYD_LEAVE;
1286 FL void
1287 message_append(struct message *mp){
1288 NYD_ENTER;
1289 if(UICMP(z, msgCount + 1, >=, a_message_mem_space)){
1290 /* XXX remove _mem_space magics (or use s_Vector) */
1291 a_message_mem_space = ((a_message_mem_space >= 128 &&
1292 a_message_mem_space <= 1000000)
1293 ? a_message_mem_space << 1 : a_message_mem_space + 64);
1294 message = srealloc(message, a_message_mem_space * sizeof(*message));
1296 if(msgCount > 0){
1297 if(mp != NULL)
1298 message[msgCount - 1] = *mp;
1299 else
1300 memset(&message[msgCount - 1], 0, sizeof *message);
1302 NYD_LEAVE;
1305 FL void
1306 message_append_null(void){
1307 NYD_ENTER;
1308 if(msgCount == 0)
1309 message_append(NULL);
1310 setdot(message);
1311 message[msgCount].m_size = 0;
1312 message[msgCount].m_lines = 0;
1313 NYD_LEAVE;
1316 FL bool_t
1317 message_match(struct message *mp, struct search_expr const *sep,
1318 bool_t with_headers){
1319 char **line;
1320 size_t *linesize, cnt;
1321 FILE *fp;
1322 bool_t rv;
1323 NYD_ENTER;
1325 rv = FAL0;
1327 if((fp = Ftmp(NULL, "mpmatch", OF_RDWR | OF_UNLINK | OF_REGISTER)) == NULL)
1328 goto j_leave;
1330 if(sendmp(mp, fp, NULL, NULL, SEND_TOSRCH, NULL) < 0)
1331 goto jleave;
1332 fflush_rewind(fp);
1334 cnt = fsize(fp);
1335 line = &termios_state.ts_linebuf; /* XXX line pool */
1336 linesize = &termios_state.ts_linesize; /* XXX line pool */
1338 if(!with_headers)
1339 while(fgetline(line, linesize, &cnt, NULL, fp, 0))
1340 if (**line == '\n')
1341 break;
1343 while(fgetline(line, linesize, &cnt, NULL, fp, 0)){
1344 #ifdef HAVE_REGEX
1345 if(sep->ss_sexpr == NULL){
1346 if(regexec(&sep->ss_regex, *line, 0,NULL, 0) == REG_NOMATCH)
1347 continue;
1348 }else
1349 #endif
1350 if(!substr(*line, sep->ss_sexpr))
1351 continue;
1352 rv = TRU1;
1353 break;
1356 jleave:
1357 Fclose(fp);
1358 j_leave:
1359 NYD_LEAVE;
1360 return rv;
1363 FL struct message *
1364 setdot(struct message *mp){
1365 NYD_ENTER;
1366 if(dot != mp){
1367 prevdot = dot;
1368 pstate &= ~PS_DID_PRINT_DOT;
1370 dot = mp;
1371 uncollapse1(dot, 0);
1372 NYD_LEAVE;
1373 return dot;
1376 FL void
1377 touch(struct message *mp){
1378 NYD_ENTER;
1379 mp->m_flag |= MTOUCH;
1380 if(!(mp->m_flag & MREAD))
1381 mp->m_flag |= MREAD | MSTATUS;
1382 NYD_LEAVE;
1385 FL int
1386 getmsglist(char *buf, int *vector, int flags)
1388 int *ip, mc;
1389 struct message *mp;
1390 NYD_ENTER;
1392 pstate &= ~PS_ARGLIST_MASK;
1393 a_message_list_last_saw_d = a_message_list_saw_d;
1394 a_message_list_saw_d = FAL0;
1396 if(msgCount == 0){
1397 *vector = 0;
1398 mc = 0;
1399 goto jleave;
1402 pstate |= PS_MSGLIST_DIRECT;
1404 if(a_message_markall(buf, flags) < 0){
1405 mc = -1;
1406 goto jleave;
1409 ip = vector;
1410 if(pstate & PS_HOOK_NEWMAIL){
1411 mc = 0;
1412 for(mp = message; mp < &message[msgCount]; ++mp)
1413 if(mp->m_flag & MMARK){
1414 if(!(mp->m_flag & MNEWEST))
1415 a_message_unmark((int)PTR2SIZE(mp - message + 1));
1416 else
1417 ++mc;
1419 if(mc == 0){
1420 mc = -1;
1421 goto jleave;
1425 if(mb.mb_threaded == 0){
1426 for(mp = message; mp < &message[msgCount]; ++mp)
1427 if(mp->m_flag & MMARK)
1428 *ip++ = (int)PTR2SIZE(mp - message + 1);
1429 }else{
1430 for(mp = threadroot; mp != NULL; mp = next_in_thread(mp))
1431 if(mp->m_flag & MMARK)
1432 *ip++ = (int)PTR2SIZE(mp - message + 1);
1434 *ip = 0;
1435 mc = (int)PTR2SIZE(ip - vector);
1436 if(mc != 1)
1437 pstate &= ~PS_MSGLIST_DIRECT;
1438 jleave:
1439 NYD_LEAVE;
1440 return mc;
1443 FL int
1444 first(int f, int m)
1446 struct message *mp;
1447 int rv;
1448 NYD_ENTER;
1450 if (msgCount == 0) {
1451 rv = 0;
1452 goto jleave;
1455 f &= MDELETED;
1456 m &= MDELETED;
1457 for (mp = dot;
1458 mb.mb_threaded ? (mp != NULL) : PTRCMP(mp, <, message + msgCount);
1459 mb.mb_threaded ? (mp = next_in_thread(mp)) : ++mp) {
1460 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & m) == (ui32_t)f) {
1461 rv = (int)PTR2SIZE(mp - message + 1);
1462 goto jleave;
1466 if (dot > message) {
1467 for (mp = dot - 1; (mb.mb_threaded ? (mp != NULL) : (mp >= message));
1468 mb.mb_threaded ? (mp = prev_in_thread(mp)) : --mp) {
1469 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & m) == (ui32_t)f) {
1470 rv = (int)PTR2SIZE(mp - message + 1);
1471 goto jleave;
1475 rv = 0;
1476 jleave:
1477 NYD_LEAVE;
1478 return rv;
1481 FL void
1482 mark(int mno, int f){
1483 struct message *mp, *xmp;
1484 int i;
1485 NYD_ENTER;
1487 i = mno;
1488 if(i < 1 || i > msgCount)
1489 n_panic(_("Bad message number to mark"));
1490 mp = &message[--i];
1492 if(mb.mb_threaded == 1 && a_message_threadflag)
1493 a_message__threadmark(mp, f);
1494 else{
1495 assert(!(mp->m_flag & MHIDDEN));
1496 mp->m_flag |= MMARK;
1498 NYD_LEAVE;
1501 /* s-it-mode */