Add n_hy[] ("-"), use it
[s-mailx.git] / message.c
blob74330333c144c4287eb8fb8832961eb436f63c0b
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 - 2018 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
6 */
7 /*
8 * Copyright (c) 1980, 1993
9 * The Regents of the University of California. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. Neither the name of the University nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
35 #undef n_FILE
36 #define n_FILE message
38 #ifndef HAVE_AMALGAMATION
39 # include "nail.h"
40 #endif
42 /* Token values returned by the scanner used for argument lists.
43 * Also, sizes of scanner-related things */
44 enum a_message_token{
45 a_MESSAGE_T_EOL, /* End of the command line */
46 a_MESSAGE_T_NUMBER, /* Message number */
47 a_MESSAGE_T_MINUS, /* - */
48 a_MESSAGE_T_STRING, /* A string (possibly containing -) */
49 a_MESSAGE_T_DOT, /* . */
50 a_MESSAGE_T_UP, /* ^ */
51 a_MESSAGE_T_DOLLAR, /* $ */
52 a_MESSAGE_T_ASTER, /* * */
53 a_MESSAGE_T_OPEN, /* ( */
54 a_MESSAGE_T_CLOSE, /* ) */
55 a_MESSAGE_T_PLUS, /* + */
56 a_MESSAGE_T_COMMA, /* , */
57 a_MESSAGE_T_SEMI, /* ; */
58 a_MESSAGE_T_BACK, /* ` */
59 a_MESSAGE_T_ERROR /* Lexical error */
62 enum a_message_idfield{
63 a_MESSAGE_ID_REFERENCES,
64 a_MESSAGE_ID_IN_REPLY_TO
67 enum a_message_state{
68 a_MESSAGE_S_NEW = 1u<<0,
69 a_MESSAGE_S_OLD = 1u<<1,
70 a_MESSAGE_S_UNREAD = 1u<<2,
71 a_MESSAGE_S_DELETED = 1u<<3,
72 a_MESSAGE_S_READ = 1u<<4,
73 a_MESSAGE_S_FLAG = 1u<<5,
74 a_MESSAGE_S_ANSWERED = 1u<<6,
75 a_MESSAGE_S_DRAFT = 1u<<7,
76 a_MESSAGE_S_SPAM = 1u<<8,
77 a_MESSAGE_S_SPAMUNSURE = 1u<<9,
78 a_MESSAGE_S_MLIST = 1u<<10,
79 a_MESSAGE_S_MLSUBSCRIBE = 1u<<11
82 struct a_message_coltab{
83 char mco_char; /* What to find past : */
84 ui8_t mco__dummy[3];
85 int mco_bit; /* Associated modifier bit */
86 int mco_mask; /* m_status bits to mask */
87 int mco_equal; /* ... must equal this */
90 struct a_message_lex{
91 char ml_char;
92 ui8_t ml_token;
95 static struct a_message_coltab const a_message_coltabs[] = {
96 {'n', {0,}, a_MESSAGE_S_NEW, MNEW, MNEW},
97 {'o', {0,}, a_MESSAGE_S_OLD, MNEW, 0},
98 {'u', {0,}, a_MESSAGE_S_UNREAD, MREAD, 0},
99 {'d', {0,}, a_MESSAGE_S_DELETED, MDELETED, MDELETED},
100 {'r', {0,}, a_MESSAGE_S_READ, MREAD, MREAD},
101 {'f', {0,}, a_MESSAGE_S_FLAG, MFLAGGED, MFLAGGED},
102 {'a', {0,}, a_MESSAGE_S_ANSWERED, MANSWERED, MANSWERED},
103 {'t', {0,}, a_MESSAGE_S_DRAFT, MDRAFTED, MDRAFTED},
104 {'s', {0,}, a_MESSAGE_S_SPAM, MSPAM, MSPAM},
105 {'S', {0,}, a_MESSAGE_S_SPAMUNSURE, MSPAMUNSURE, MSPAMUNSURE},
106 /* These have no per-message flags, but must be evaluated */
107 {'l', {0,}, a_MESSAGE_S_MLIST, 0, 0},
108 {'L', {0,}, a_MESSAGE_S_MLSUBSCRIBE, 0, 0},
111 static struct a_message_lex const a_message_singles[] = {
112 {'$', a_MESSAGE_T_DOLLAR},
113 {'.', a_MESSAGE_T_DOT},
114 {'^', a_MESSAGE_T_UP},
115 {'*', a_MESSAGE_T_ASTER},
116 {'-', a_MESSAGE_T_MINUS},
117 {'+', a_MESSAGE_T_PLUS},
118 {'(', a_MESSAGE_T_OPEN},
119 {')', a_MESSAGE_T_CLOSE},
120 {',', a_MESSAGE_T_COMMA},
121 {';', a_MESSAGE_T_SEMI},
122 {'`', a_MESSAGE_T_BACK}
125 /* Slots in ::message */
126 static size_t a_message_mem_space;
128 /* Mark entire threads */
129 static bool_t a_message_threadflag;
131 /* :d on its way HACK TODO */
132 static bool_t a_message_list_saw_d, a_message_list_last_saw_d;
134 /* String from a_MESSAGE_T_STRING, scan() */
135 static struct str a_message_lexstr;
136 /* Number of a_MESSAGE_T_NUMBER from scan() */
137 static int a_message_lexno;
139 /* Lazy load message header fields */
140 static enum okay a_message_get_header(struct message *mp);
142 /* Append, taking care of resizes TODO vector */
143 static char **a_message_add_to_namelist(char ***namelist, size_t *nmlsize,
144 char **np, char *string);
146 /* Mark all messages that the user wanted from the command line in the message
147 * structure. Return 0 on success, -1 on error */
148 static int a_message_markall(char const *buf, int f);
150 /* Turn the character after a colon modifier into a bit value */
151 static int a_message_evalcol(int col);
153 /* Check the passed message number for legality and proper flags. Unless f is
154 * MDELETED the message has to be undeleted */
155 static bool_t a_message_check(int mno, int f);
157 /* Scan out a single lexical item and return its token number, updating the
158 * string pointer passed *sp. Also, store the value of the number or string
159 * scanned in a_message_lexno or a_message_lexstr as appropriate.
160 * In any event, store the scanned "thing" in a_message_lexstr.
161 * Returns the token as a negative number when we also saw & to mark a thread */
162 static int a_message_scan(char const **sp);
164 /* See if the passed name sent the passed message */
165 static bool_t a_message_match_sender(struct message *mp, char const *str,
166 bool_t allnet);
168 /* Check whether the given message-id or references match */
169 static bool_t a_message_match_mid(struct message *mp, char const *id,
170 enum a_message_idfield idfield);
172 /* See if the given string matches.
173 * For the purpose of the scan, we ignore case differences.
174 * This is the engine behind the "/" search */
175 static bool_t a_message_match_dash(struct message *mp, char const *str);
177 /* See if the given search expression matches.
178 * For the purpose of the scan, we ignore case differences.
179 * This is the engine behind the "@[..@].." search */
180 static bool_t a_message_match_at(struct message *mp, struct search_expr *sep);
182 /* Unmark the named message */
183 static void a_message_unmark(int mesg);
185 /* Return the message number corresponding to the passed meta character */
186 static int a_message_metamess(int meta, int f);
188 /* Helper for mark(): self valid, threading enabled */
189 static void a_message__threadmark(struct message *self, int f);
191 static enum okay
192 a_message_get_header(struct message *mp){
193 enum okay rv;
194 NYD2_ENTER;
195 n_UNUSED(mp);
197 switch(mb.mb_type){
198 case MB_FILE:
199 case MB_MAILDIR:
200 rv = OKAY;
201 break;
202 #ifdef HAVE_POP3
203 case MB_POP3:
204 rv = pop3_header(mp);
205 break;
206 #endif
207 #ifdef HAVE_IMAP
208 case MB_IMAP:
209 case MB_CACHE:
210 rv = imap_header(mp);
211 break;
212 #endif
213 case MB_VOID:
214 default:
215 rv = STOP;
216 break;
218 NYD2_LEAVE;
219 return rv;
222 static char **
223 a_message_add_to_namelist(char ***namelist, size_t *nmlsize, /* TODO Vector */
224 char **np, char *string){
225 size_t idx;
226 NYD2_ENTER;
228 if((idx = PTR2SIZE(np - *namelist)) >= *nmlsize){
229 *namelist = n_realloc(*namelist, (*nmlsize += 8) * sizeof *np);
230 np = &(*namelist)[idx];
232 *np++ = string;
233 NYD2_LEAVE;
234 return np;
237 static int
238 a_message_markall(char const *buf, int f){
239 struct message *mp, *mx;
240 enum a_message_idfield idfield;
241 size_t j, nmlsize;
242 char const *id, *bufp;
243 char **np, **nq, **namelist, *cp;
244 int i, valdot, beg, colmod, tok, colresult;
245 enum{
246 a_NONE = 0,
247 a_ALLNET = 1u<<0, /* Must be TRU1 */
248 a_ALLOC = 1u<<1, /* Have allocated something */
249 a_THREADED = 1u<<2,
250 a_ERROR = 1u<<3,
251 a_ANY = 1u<<4, /* Have marked just ANY */
252 a_RANGE = 1u<<5, /* Seen dash, await close */
253 a_ASTER = 1u<<8,
254 a_TOPEN = 1u<<9, /* ( used (and didn't match) */
255 a_TBACK = 1u<<10, /* ` used (and didn't match) */
256 #ifdef HAVE_IMAP
257 a_HAVE_IMAP_HEADERS = 1u<<14,
258 #endif
259 a_LOG = 1u<<29, /* Log errors */
260 a_TMP = 1u<<30
261 } flags;
262 NYD_ENTER;
263 n_LCTA((ui32_t)a_ALLNET == (ui32_t)TRU1,
264 "Constant is converted to bool_t via AND, thus");
266 /* Update message array: clear MMARK but remember its former state for ` */
267 for(i = msgCount; i-- > 0;){
268 enum mflag mf;
270 mf = (mp = &message[i])->m_flag;
271 if(mf & MMARK)
272 mf |= MOLDMARK;
273 else
274 mf &= ~MOLDMARK;
275 mf &= ~MMARK;
276 mp->m_flag = mf;
279 /* Strip all leading WS from user buffer */
280 while(blankspacechar(*buf))
281 ++buf;
282 /* If there is no input buffer, we are done! */
283 if(buf[0] == '\0'){
284 flags = a_NONE;
285 goto jleave;
288 n_UNINIT(beg, 0);
289 n_UNINIT(idfield, a_MESSAGE_ID_REFERENCES);
290 a_message_threadflag = FAL0;
291 a_message_lexstr.s = n_lofi_alloc(a_message_lexstr.l = 2 * strlen(buf) +1);
292 np = namelist = n_alloc((nmlsize = 8) * sizeof *namelist); /* TODO vector */
293 bufp = buf;
294 valdot = (int)PTR2SIZE(dot - message + 1);
295 colmod = 0;
296 id = NULL;
297 flags = a_ALLOC | (mb.mb_threaded ? a_THREADED : 0) |
298 ((!(n_pstate & n_PS_HOOK_MASK) || (n_poption & n_PO_D_V))
299 ? a_LOG : 0);
301 while((tok = a_message_scan(&bufp)) != a_MESSAGE_T_EOL){
302 if((a_message_threadflag = (tok < 0)))
303 tok &= INT_MAX;
305 switch(tok){
306 case a_MESSAGE_T_NUMBER:
307 n_pstate |= n_PS_MSGLIST_GABBY;
308 jnumber:
309 if(!a_message_check(a_message_lexno, f))
310 goto jerr;
312 if(flags & a_RANGE){
313 flags ^= a_RANGE;
315 if(!(flags & a_THREADED)){
316 if(beg < a_message_lexno)
317 i = beg;
318 else{
319 i = a_message_lexno;
320 a_message_lexno = beg;
323 for(; i <= a_message_lexno; ++i){
324 mp = &message[i - 1];
325 if(!(mp->m_flag & MHIDDEN) &&
326 (f == MDELETED || !(mp->m_flag & MDELETED))){
327 mark(i, f);
328 flags |= a_ANY;
331 }else{
332 /* TODO threaded ranges are a mess */
333 enum{
334 a_T_NONE,
335 a_T_HOT = 1u<<0,
336 a_T_DIR_PREV = 1u<<1
337 } tf;
338 int i_base;
340 if(beg < a_message_lexno)
341 i = beg;
342 else{
343 i = a_message_lexno;
344 a_message_lexno = beg;
347 i_base = i;
348 tf = a_T_NONE;
349 jnumber__thr:
350 for(;;){
351 mp = &message[i - 1];
352 if(!(mp->m_flag & MHIDDEN) &&
353 (f == MDELETED || !(mp->m_flag & MDELETED))){
354 if(tf & a_T_HOT){
355 mark(i, f);
356 flags |= a_ANY;
360 /* We may have reached the endpoint. If we were still
361 * detecting the direction to search for it, restart.
362 * Otherwise finished */
363 if(i == a_message_lexno){ /* XXX */
364 if(!(tf & a_T_HOT)){
365 tf |= a_T_HOT;
366 i = i_base;
367 goto jnumber__thr;
369 break;
372 mx = (tf & a_T_DIR_PREV) ? prev_in_thread(mp)
373 : next_in_thread(mp);
374 if(mx == NULL){
375 /* We anyway have failed to reach the endpoint in this
376 * direction; if we already switched that, report error */
377 if(!(tf & a_T_DIR_PREV)){
378 tf |= a_T_DIR_PREV;
379 i = i_base;
380 goto jnumber__thr;
382 id = N_("Range crosses multiple threads\n");
383 goto jerrmsg;
385 i = (int)PTR2SIZE(mx - message + 1);
389 beg = 0;
390 }else{
391 /* Could be an inclusive range? */
392 if(bufp[0] == '-'){
393 ++bufp;
394 beg = a_message_lexno;
395 flags |= a_RANGE;
396 }else{
397 mark(a_message_lexno, f);
398 flags |= a_ANY;
401 break;
402 case a_MESSAGE_T_PLUS:
403 n_pstate &= ~n_PS_MSGLIST_DIRECT;
404 n_pstate |= n_PS_MSGLIST_GABBY;
405 i = valdot;
407 if(flags & a_THREADED){
408 mx = next_in_thread(&message[i - 1]);
409 i = mx ? (int)PTR2SIZE(mx - message + 1) : msgCount + 1;
410 }else
411 ++i;
412 if(i > msgCount){
413 id = N_("Referencing beyond last message\n");
414 goto jerrmsg;
416 }while(message[i - 1].m_flag == MHIDDEN ||
417 (message[i - 1].m_flag & MDELETED) != (unsigned)f);
418 a_message_lexno = i;
419 goto jnumber;
420 case a_MESSAGE_T_MINUS:
421 n_pstate &= ~n_PS_MSGLIST_DIRECT;
422 n_pstate |= n_PS_MSGLIST_GABBY;
423 i = valdot;
425 if(flags & a_THREADED){
426 mx = prev_in_thread(&message[i - 1]);
427 i = mx ? (int)PTR2SIZE(mx - message + 1) : 0;
428 }else
429 --i;
430 if(i <= 0){
431 id = N_("Referencing before first message\n");
432 goto jerrmsg;
434 }while(message[i - 1].m_flag == MHIDDEN ||
435 (message[i - 1].m_flag & MDELETED) != (unsigned)f);
436 a_message_lexno = i;
437 goto jnumber;
438 case a_MESSAGE_T_STRING:
439 n_pstate &= ~n_PS_MSGLIST_DIRECT;
440 if(flags & a_RANGE)
441 goto jebadrange;
443 /* This may be a colon modifier */
444 if((cp = a_message_lexstr.s)[0] != ':')
445 np = a_message_add_to_namelist(&namelist, &nmlsize, np,
446 savestr(a_message_lexstr.s));
447 else{
448 while(*++cp != '\0'){
449 colresult = a_message_evalcol(*cp);
450 if(colresult == 0){
451 if(flags & a_LOG)
452 n_err(_("Unknown colon modifier: %s\n"),
453 a_message_lexstr.s);
454 goto jerr;
456 if(colresult == a_MESSAGE_S_DELETED){
457 a_message_list_saw_d = TRU1;
458 f |= MDELETED;
460 colmod |= colresult;
463 break;
464 case a_MESSAGE_T_OPEN:
465 n_pstate &= ~n_PS_MSGLIST_DIRECT;
466 if(flags & a_RANGE)
467 goto jebadrange;
468 flags |= a_TOPEN;
470 #ifdef HAVE_IMAP_SEARCH
471 /* C99 */{
472 ssize_t ires;
474 if((ires = imap_search(a_message_lexstr.s, f)) >= 0){
475 if(ires > 0)
476 flags |= a_ANY;
477 break;
480 #else
481 if(flags & a_LOG)
482 n_err(_("Optional selector is not available: %s\n"),
483 a_message_lexstr.s);
484 #endif
485 goto jerr;
486 case a_MESSAGE_T_DOLLAR:
487 case a_MESSAGE_T_UP:
488 case a_MESSAGE_T_SEMI:
489 n_pstate |= n_PS_MSGLIST_GABBY;
490 /* FALLTHRU */
491 case a_MESSAGE_T_DOT: /* Don't set _GABBY for dot, to _allow_ history.. */
492 n_pstate &= ~n_PS_MSGLIST_DIRECT;
493 a_message_lexno = a_message_metamess(a_message_lexstr.s[0], f);
494 if(a_message_lexno == -1)
495 goto jerr;
496 goto jnumber;
497 case a_MESSAGE_T_BACK:
498 n_pstate &= ~n_PS_MSGLIST_DIRECT;
499 if(flags & a_RANGE)
500 goto jebadrange;
502 flags |= a_TBACK;
503 for(i = 0; i < msgCount; ++i){
504 if((mp = &message[i])->m_flag & MHIDDEN)
505 continue;
506 if((mp->m_flag & MDELETED) != (unsigned)f){
507 if(!a_message_list_last_saw_d)
508 continue;
509 a_message_list_saw_d = TRU1;
511 if(mp->m_flag & MOLDMARK){
512 mark(i + 1, f);
513 flags &= ~a_TBACK;
514 flags |= a_ANY;
517 break;
518 case a_MESSAGE_T_ASTER:
519 n_pstate &= ~n_PS_MSGLIST_DIRECT;
520 if(flags & a_RANGE)
521 goto jebadrange;
522 flags |= a_ASTER;
523 break;
524 case a_MESSAGE_T_COMMA:
525 n_pstate &= ~n_PS_MSGLIST_DIRECT;
526 n_pstate |= n_PS_MSGLIST_GABBY;
527 if(flags & a_RANGE)
528 goto jebadrange;
530 #ifdef HAVE_IMAP
531 if(!(flags & a_HAVE_IMAP_HEADERS) && mb.mb_type == MB_IMAP){
532 flags |= a_HAVE_IMAP_HEADERS;
533 imap_getheaders(1, msgCount);
535 #endif
537 if(id == NULL){
538 if((cp = hfield1("in-reply-to", dot)) != NULL)
539 idfield = a_MESSAGE_ID_IN_REPLY_TO;
540 else if((cp = hfield1("references", dot)) != NULL){
541 struct name *enp;
543 if((enp = extract(cp, GREF)) != NULL){
544 while(enp->n_flink != NULL)
545 enp = enp->n_flink;
546 cp = enp->n_name;
547 idfield = a_MESSAGE_ID_REFERENCES;
548 }else
549 cp = NULL;
552 if(cp != NULL)
553 id = savestr(cp);
554 else{
555 id = N_("Message-ID of parent of \"dot\" is indeterminable\n");
556 goto jerrmsg;
558 }else if(flags & a_LOG)
559 n_err(_("Ignoring redundant specification of , selector\n"));
560 break;
561 case a_MESSAGE_T_ERROR:
562 n_pstate &= ~n_PS_MSGLIST_DIRECT;
563 n_pstate |= n_PS_MSGLIST_GABBY;
564 goto jerr;
567 /* Explicitly disallow invalid ranges for future safety */
568 if(bufp[0] == '-' && !(flags & a_RANGE)){
569 if(flags & a_LOG)
570 n_err(_("Ignoring invalid range before: %s\n"), bufp);
571 ++bufp;
574 if(flags & a_RANGE){
575 id = N_("Missing second range argument\n");
576 goto jerrmsg;
579 np = a_message_add_to_namelist(&namelist, &nmlsize, np, NULL);
580 --np;
582 /* * is special at this point, after we have parsed the entire line */
583 if(flags & a_ASTER){
584 for(i = 0; i < msgCount; ++i){
585 if((mp = &message[i])->m_flag & MHIDDEN)
586 continue;
587 if(!a_message_list_saw_d && (mp->m_flag & MDELETED) != (unsigned)f)
588 continue;
589 mark(i + 1, f);
590 flags |= a_ANY;
592 if(!(flags & a_ANY))
593 goto jenoapp;
594 goto jleave;
597 /* If any names were given, add any messages which match */
598 if(np > namelist || id != NULL){
599 struct search_expr *sep;
601 sep = NULL;
603 /* The @ search works with struct search_expr, so build an array.
604 * To simplify array, i.e., regex_t destruction, and optimize for the
605 * common case we walk the entire array even in case of errors */
606 /* XXX Like many other things around here: this should be outsourced */
607 if(np > namelist){
608 j = PTR2SIZE(np - namelist) * sizeof(*sep);
609 sep = n_lofi_alloc(j);
610 memset(sep, 0, j);
612 for(j = 0, nq = namelist; *nq != NULL; ++j, ++nq){
613 char *xsave, *x, *y;
615 sep[j].ss_body = x = xsave = *nq;
616 if(*x != '@' || (flags & a_ERROR))
617 continue;
619 /* Cramp the namelist */
620 for(y = &x[1];; ++y){
621 if(*y == '\0'){
622 x = NULL;
623 break;
625 if(*y == '@'){
626 x = y;
627 break;
630 if(x == NULL || &x[-1] == xsave)
631 jat_where_default:
632 sep[j].ss_field = "subject";
633 else{
634 ++xsave;
635 if(*xsave == '~'){
636 sep[j].ss_skin = TRU1;
637 if(++xsave >= x){
638 if(flags & a_LOG)
639 n_err(_("[@..]@ search expression: no namelist, "
640 "only \"~\" skin indicator\n"));
641 flags |= a_ERROR;
642 continue;
645 cp = savestrbuf(xsave, PTR2SIZE(x - xsave));
647 /* Namelist could be a regular expression, too */
648 #ifdef HAVE_REGEX
649 if(n_is_maybe_regex(cp)){
650 int s;
652 assert(sep[j].ss_field == NULL);
653 if((s = regcomp(&sep[j].ss__fieldre_buf, cp,
654 REG_EXTENDED | REG_ICASE | REG_NOSUB)) != 0){
655 if(flags & a_LOG)
656 n_err(_("Invalid regular expression: %s: %s\n"),
657 n_shexp_quote_cp(cp, FAL0),
658 n_regex_err_to_doc(NULL, s));
659 flags |= a_ERROR;
660 continue;
662 sep[j].ss_fieldre = &sep[j].ss__fieldre_buf;
663 }else
664 #endif
666 struct str sio;
668 /* Because of the special cases we need to trim explicitly
669 * here, they are not covered by n_strsep() */
670 sio.s = cp;
671 sio.l = PTR2SIZE(x - xsave);
672 if(*(cp = n_str_trim(&sio, n_STR_TRIM_BOTH)->s) == '\0')
673 goto jat_where_default;
674 sep[j].ss_field = cp;
678 /* The actual search expression. If it is empty we only test the
679 * field(s) for existence */
680 x = &(x == NULL ? *nq : x)[1];
681 if(*x == '\0'){
682 sep[j].ss_field_exists = TRU1;
683 #ifdef HAVE_REGEX
684 }else if(n_is_maybe_regex(x)){
685 int s;
687 sep[j].ss_body = NULL;
688 if((s = regcomp(&sep[j].ss__bodyre_buf, x,
689 REG_EXTENDED | REG_ICASE | REG_NOSUB)) != 0){
690 if(flags & a_LOG)
691 n_err(_("Invalid regular expression: %s: %s\n"),
692 n_shexp_quote_cp(x, FAL0),
693 n_regex_err_to_doc(NULL, s));
694 flags |= a_ERROR;
695 continue;
697 sep[j].ss_bodyre = &sep[j].ss__bodyre_buf;
698 #endif
699 }else
700 sep[j].ss_body = x;
702 if(flags & a_ERROR)
703 goto jnamesearch_sepfree;
706 /* Iterate the entire message array */
707 #ifdef HAVE_IMAP
708 if(!(flags & a_HAVE_IMAP_HEADERS) && mb.mb_type == MB_IMAP){
709 flags |= a_HAVE_IMAP_HEADERS;
710 imap_getheaders(1, msgCount);
712 #endif
713 if(ok_blook(allnet))
714 flags |= a_ALLNET;
715 n_autorec_relax_create();
716 for(i = 0; i < msgCount; ++i){
717 if((mp = &message[i])->m_flag & (MMARK | MHIDDEN))
718 continue;
719 if(!a_message_list_saw_d && (mp->m_flag & MDELETED) != (unsigned)f)
720 continue;
722 flags &= ~a_TMP;
723 if(np > namelist){
724 for(nq = namelist; *nq != NULL; ++nq){
725 if(**nq == '@'){
726 if(a_message_match_at(mp, &sep[PTR2SIZE(nq - namelist)])){
727 flags |= a_TMP;
728 break;
730 }else if(**nq == '/'){
731 if(a_message_match_dash(mp, *nq)){
732 flags |= a_TMP;
733 break;
735 }else if(a_message_match_sender(mp, *nq, (flags & a_ALLNET))){
736 flags |= a_TMP;
737 break;
741 if(!(flags & a_TMP) &&
742 id != NULL && a_message_match_mid(mp, id, idfield))
743 flags |= a_TMP;
745 if(flags & a_TMP){
746 mark(i + 1, f);
747 flags |= a_ANY;
749 n_autorec_relax_unroll();
751 n_autorec_relax_gut();
753 jnamesearch_sepfree:
754 if(sep != NULL){
755 #ifdef HAVE_REGEX
756 for(j = PTR2SIZE(np - namelist); j-- != 0;){
757 if(sep[j].ss_fieldre != NULL)
758 regfree(sep[j].ss_fieldre);
759 if(sep[j].ss_bodyre != NULL)
760 regfree(sep[j].ss_bodyre);
762 #endif
763 n_lofi_free(sep);
765 if(flags & a_ERROR)
766 goto jerr;
769 /* If any colon modifiers were given, go through and mark any messages which
770 * do satisfy the modifiers */
771 if(colmod != 0){
772 for(i = 0; i < msgCount; ++i){
773 struct a_message_coltab const *colp;
775 if((mp = &message[i])->m_flag & (MMARK | MHIDDEN))
776 continue;
777 if(!a_message_list_saw_d && (mp->m_flag & MDELETED) != (unsigned)f)
778 continue;
780 for(colp = a_message_coltabs;
781 PTRCMP(colp, <, &a_message_coltabs[n_NELEM(a_message_coltabs)]);
782 ++colp)
783 if(colp->mco_bit & colmod){
784 /* Is this a colon modifier that requires evaluation? */
785 if(colp->mco_mask == 0){
786 if(colp->mco_bit & (a_MESSAGE_S_MLIST |
787 a_MESSAGE_S_MLSUBSCRIBE)){
788 enum mlist_state what;
790 what = (colp->mco_bit & a_MESSAGE_S_MLIST) ? MLIST_KNOWN
791 : MLIST_SUBSCRIBED;
792 if(what == is_mlist_mp(mp, what))
793 goto jcolonmod_mark;
795 }else if((mp->m_flag & colp->mco_mask
796 ) == (enum mflag)colp->mco_equal){
797 jcolonmod_mark:
798 mark(i + 1, f);
799 flags |= a_ANY;
800 break;
806 /* It shall be an error if ` didn't match anything, and nothing else did */
807 if((flags & (a_TBACK | a_ANY)) == a_TBACK){
808 id = N_("No previously marked messages\n");
809 goto jerrmsg;
810 }else if(!(flags & a_ANY))
811 goto jenoapp;
813 assert(!(flags & a_ERROR));
814 jleave:
815 if(flags & a_ALLOC){
816 n_free(namelist);
817 n_lofi_free(a_message_lexstr.s);
819 NYD_LEAVE;
820 return (flags & a_ERROR) ? -1 : 0;
822 jebadrange:
823 id = N_("Invalid range endpoint\n");
824 goto jerrmsg;
825 jenoapp:
826 id = N_("No applicable messages\n");
827 jerrmsg:
828 if(flags & a_LOG)
829 n_err(V_(id));
830 jerr:
831 flags |= a_ERROR;
832 goto jleave;
835 static int
836 a_message_evalcol(int col){
837 struct a_message_coltab const *colp;
838 int rv;
839 NYD2_ENTER;
841 rv = 0;
842 for(colp = a_message_coltabs;
843 PTRCMP(colp, <, &a_message_coltabs[n_NELEM(a_message_coltabs)]);
844 ++colp)
845 if(colp->mco_char == col){
846 rv = colp->mco_bit;
847 break;
849 NYD2_LEAVE;
850 return rv;
853 static bool_t
854 a_message_check(int mno, int f){
855 struct message *mp;
856 NYD2_ENTER;
858 if(mno < 1 || mno > msgCount){
859 n_err(_("%d: Invalid message number\n"), mno);
860 mno = 1;
861 }else if(((mp = &message[mno - 1])->m_flag & MHIDDEN) ||
862 (f != MDELETED && (mp->m_flag & MDELETED) != 0))
863 n_err(_("%d: inappropriate message\n"), mno);
864 else
865 mno = 0;
866 NYD2_LEAVE;
867 return (mno == 0);
870 static int
871 a_message_scan(char const **sp)
873 struct a_message_lex const *lp;
874 char *cp2;
875 char const *cp;
876 int rv, c, inquote, quotec;
877 NYD_ENTER;
879 rv = a_MESSAGE_T_EOL;
881 cp = *sp;
882 cp2 = a_message_lexstr.s;
883 c = *cp++;
885 /* strip away leading white space */
886 while(blankchar(c))
887 c = *cp++;
889 /* If no characters remain, we are at end of line, so report that */
890 if(c == '\0'){
891 *sp = --cp;
892 goto jleave;
895 /* Select members of a message thread */
896 if(c == '&'){
897 if(*cp == '\0' || spacechar(*cp)){
898 a_message_lexstr.s[0] = '.';
899 a_message_lexstr.s[1] = '\0';
900 *sp = cp;
901 rv = a_MESSAGE_T_DOT | INT_MIN;
902 goto jleave;
904 rv = INT_MIN;
905 c = *cp++;
908 /* If the leading character is a digit, scan the number and convert it
909 * on the fly. Return a_MESSAGE_T_NUMBER when done */
910 if(digitchar(c)){
911 a_message_lexno = 0;
913 a_message_lexno = (a_message_lexno * 10) + c - '0';
914 *cp2++ = c;
915 }while((c = *cp++, digitchar(c)));
916 *cp2 = '\0';
917 *sp = --cp;
918 rv |= a_MESSAGE_T_NUMBER;
919 goto jleave;
922 /* An IMAP SEARCH list. Note that a_MESSAGE_T_OPEN has always been included
923 * in singles[] in Mail and mailx. Thus although there is no formal
924 * definition for (LIST) lists, they do not collide with historical
925 * practice because a subject string (LIST) could never been matched
926 * this way */
927 if (c == '(') {
928 ui32_t level = 1;
929 inquote = 0;
930 *cp2++ = c;
931 do {
932 if ((c = *cp++&0377) == '\0') {
933 jmtop:
934 n_err(_("Missing )\n"));
935 rv = a_MESSAGE_T_ERROR;
936 goto jleave;
938 if (inquote && c == '\\') {
939 *cp2++ = c;
940 c = *cp++&0377;
941 if (c == '\0')
942 goto jmtop;
943 } else if (c == '"')
944 inquote = !inquote;
945 else if (inquote)
946 /*EMPTY*/;
947 else if (c == '(')
948 ++level;
949 else if (c == ')')
950 --level;
951 else if (spacechar(c)) {
952 /* Replace unquoted whitespace by single space characters, to make
953 * the string IMAP SEARCH conformant */
954 c = ' ';
955 if (cp2[-1] == ' ')
956 --cp2;
958 *cp2++ = c;
959 } while (c != ')' || level > 0);
960 *cp2 = '\0';
961 *sp = cp;
962 rv |= a_MESSAGE_T_OPEN;
963 goto jleave;
966 /* Check for single character tokens; return such if found */
967 for(lp = a_message_singles;
968 PTRCMP(lp, <, &a_message_singles[n_NELEM(a_message_singles)]); ++lp)
969 if(c == lp->ml_char){
970 a_message_lexstr.s[0] = c;
971 a_message_lexstr.s[1] = '\0';
972 *sp = cp;
973 rv |= lp->ml_token;
974 goto jleave;
977 /* We've got a string! Copy all the characters of the string into
978 * a_message_lexstr, until we see a null, space, or tab. If the lead
979 * character is a " or ', save it and scan until you get another */
980 quotec = 0;
981 if (c == '\'' || c == '"') {
982 quotec = c;
983 c = *cp++;
985 while (c != '\0') {
986 if (quotec == 0 && c == '\\' && *cp != '\0')
987 c = *cp++;
988 if (c == quotec) {
989 ++cp;
990 break;
992 if (quotec == 0 && blankchar(c))
993 break;
994 if (PTRCMP(cp2 - a_message_lexstr.s, <, a_message_lexstr.l))
995 *cp2++ = c;
996 c = *cp++;
998 if (quotec && c == 0) {
999 n_err(_("Missing %c\n"), quotec);
1000 rv = a_MESSAGE_T_ERROR;
1001 goto jleave;
1003 *sp = --cp;
1004 *cp2 = '\0';
1005 rv |= a_MESSAGE_T_STRING;
1006 jleave:
1007 NYD_LEAVE;
1008 return rv;
1011 static bool_t
1012 a_message_match_sender(struct message *mp, char const *str, bool_t allnet){
1013 char const *str_base, *np_base, *np;
1014 char sc, nc;
1015 struct name *namep;
1016 bool_t rv;
1017 NYD2_ENTER;
1019 rv = FAL0;
1021 /* Empty string doesn't match */
1022 namep = lextract(n_header_senderfield_of(mp), GFULL | GSKIN);
1024 if(namep == NULL || *(str_base = str) == '\0')
1025 goto jleave;
1027 /* *allnet* is POSIX and, since it explicitly mentions login and user names,
1028 * most likely case-sensitive. XXX Still allow substr matching, though
1029 * XXX possibly the first letter should be case-insensitive, then? */
1030 if(allnet){
1031 for(; namep != NULL; str = str_base, namep = namep->n_flink){
1032 for(np_base = np = namep->n_name;;){
1033 if((sc = *str++) == '@')
1034 sc = '\0';
1035 if((nc = *np++) == '@' || nc == '\0' || sc == '\0'){
1036 if((rv = (sc == '\0')))
1037 goto jleave;
1038 break;
1040 if(sc != nc){
1041 np = ++np_base;
1042 str = str_base;
1046 }else{
1047 /* TODO POSIX says ~"match any address as shown in header overview",
1048 * TODO but a normalized match would be more sane i guess.
1049 * TODO struct name should gain a comparison method, normalize realname
1050 * TODO content (in TODO) and thus match as likewise
1051 * TODO "Buddy (Today) <here>" and "(Now) Buddy <here>" */
1052 bool_t again_base, again;
1054 again_base = ok_blook(showname);
1056 for(; namep != NULL; str = str_base, namep = namep->n_flink){
1057 again = again_base;
1058 jagain:
1059 np_base = np = again ? namep->n_fullname : namep->n_name;
1060 str = str_base;
1061 for(;;){
1062 sc = *str++;
1063 if((nc = *np++) == '\0' || sc == '\0'){
1064 if((rv = (sc == '\0')))
1065 goto jleave;
1066 break;
1068 sc = upperconv(sc);
1069 nc = upperconv(nc);
1070 if(sc != nc){
1071 np = ++np_base;
1072 str = str_base;
1076 /* And really if i want to match 'on@' then i want it to match even if
1077 * *showname* is set! */
1078 if(again){
1079 again = FAL0;
1080 goto jagain;
1084 jleave:
1085 NYD2_LEAVE;
1086 return rv;
1089 static bool_t
1090 a_message_match_mid(struct message *mp, char const *id,
1091 enum a_message_idfield idfield){
1092 char const *cp;
1093 bool_t rv;
1094 NYD2_ENTER;
1096 rv = FAL0;
1098 if((cp = hfield1("message-id", mp)) != NULL){
1099 switch(idfield){
1100 case a_MESSAGE_ID_REFERENCES:
1101 if(!msgidcmp(id, cp))
1102 rv = TRU1;
1103 break;
1104 case a_MESSAGE_ID_IN_REPLY_TO:{
1105 struct name *np;
1107 if((np = extract(id, GREF)) != NULL)
1109 if(!msgidcmp(np->n_name, cp)){
1110 rv = TRU1;
1111 break;
1113 }while((np = np->n_flink) != NULL);
1114 break;
1118 NYD2_LEAVE;
1119 return rv;
1122 static bool_t
1123 a_message_match_dash(struct message *mp, char const *str){
1124 static char lastscan[128];
1126 struct str in, out;
1127 char *hfield, *hbody;
1128 bool_t rv;
1129 NYD2_ENTER;
1131 rv = FAL0;
1133 if(*++str == '\0')
1134 str = lastscan;
1135 else
1136 n_strscpy(lastscan, str, sizeof lastscan); /* XXX use new n_str object! */
1138 /* Now look, ignoring case, for the word in the string */
1139 if(ok_blook(searchheaders) && (hfield = strchr(str, ':'))){
1140 size_t l;
1142 l = PTR2SIZE(hfield - str);
1143 hfield = n_lofi_alloc(l +1);
1144 memcpy(hfield, str, l);
1145 hfield[l] = '\0';
1146 hbody = hfieldX(hfield, mp);
1147 n_lofi_free(hfield);
1148 hfield = n_UNCONST(str + l + 1);
1149 }else{
1150 hfield = n_UNCONST(str);
1151 hbody = hfield1("subject", mp);
1153 if(hbody == NULL)
1154 goto jleave;
1156 in.l = strlen(in.s = hbody);
1157 mime_fromhdr(&in, &out, TD_ICONV);
1158 rv = substr(out.s, hfield);
1159 n_free(out.s);
1160 jleave:
1161 NYD2_LEAVE;
1162 return rv;
1165 static bool_t
1166 a_message_match_at(struct message *mp, struct search_expr *sep){
1167 char const *field;
1168 bool_t rv;
1169 NYD2_ENTER;
1171 /* Namelist regex only matches headers.
1172 * And there are the special cases header/<, "body"/> and "text"/=, the
1173 * latter two of which need to be handled in here */
1174 if((field = sep->ss_field) != NULL){
1175 if(!asccasecmp(field, "body") || (field[1] == '\0' && field[0] == '>')){
1176 rv = FAL0;
1177 jmsg:
1178 rv = message_match(mp, sep, rv);
1179 goto jleave;
1180 }else if(!asccasecmp(field, "text") ||
1181 (field[1] == '\0' && field[0] == '=')){
1182 rv = TRU1;
1183 goto jmsg;
1187 rv = n_header_match(mp, sep);
1188 jleave:
1189 NYD2_LEAVE;
1190 return rv;
1193 static void
1194 a_message_unmark(int mesg){
1195 size_t i;
1196 NYD2_ENTER;
1198 i = (size_t)mesg;
1199 if(i < 1 || UICMP(z, i, >, msgCount))
1200 n_panic(_("Bad message number to unmark"));
1201 message[--i].m_flag &= ~MMARK;
1202 NYD2_LEAVE;
1205 static int
1206 a_message_metamess(int meta, int f)
1208 int c, m;
1209 struct message *mp;
1210 NYD2_ENTER;
1212 c = meta;
1213 switch (c) {
1214 case '^': /* First 'good' message left */
1215 mp = mb.mb_threaded ? threadroot : message;
1216 while (PTRCMP(mp, <, message + msgCount)) {
1217 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & MDELETED) == (ui32_t)f) {
1218 c = (int)PTR2SIZE(mp - message + 1);
1219 goto jleave;
1221 if (mb.mb_threaded) {
1222 mp = next_in_thread(mp);
1223 if (mp == NULL)
1224 break;
1225 } else
1226 ++mp;
1228 if (!(n_pstate & n_PS_HOOK_MASK))
1229 n_err(_("No applicable messages\n"));
1230 goto jem1;
1232 case '$': /* Last 'good message left */
1233 mp = mb.mb_threaded
1234 ? this_in_thread(threadroot, -1) : message + msgCount - 1;
1235 while (mp >= message) {
1236 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & MDELETED) == (ui32_t)f) {
1237 c = (int)PTR2SIZE(mp - message + 1);
1238 goto jleave;
1240 if (mb.mb_threaded) {
1241 mp = prev_in_thread(mp);
1242 if (mp == NULL)
1243 break;
1244 } else
1245 --mp;
1247 if (!(n_pstate & n_PS_HOOK_MASK))
1248 n_err(_("No applicable messages\n"));
1249 goto jem1;
1251 case '.':
1252 /* Current message */
1253 m = dot - message + 1;
1254 if ((dot->m_flag & MHIDDEN) || (dot->m_flag & MDELETED) != (ui32_t)f) {
1255 n_err(_("%d: inappropriate message\n"), m);
1256 goto jem1;
1258 c = m;
1259 break;
1261 case ';':
1262 /* Previously current message */
1263 if (prevdot == NULL) {
1264 n_err(_("No previously current message\n"));
1265 goto jem1;
1267 m = prevdot - message + 1;
1268 if ((prevdot->m_flag & MHIDDEN) ||
1269 (prevdot->m_flag & MDELETED) != (ui32_t)f) {
1270 n_err(_("%d: inappropriate message\n"), m);
1271 goto jem1;
1273 c = m;
1274 break;
1276 default:
1277 n_err(_("Unknown selector: %c\n"), c);
1278 goto jem1;
1280 jleave:
1281 NYD2_LEAVE;
1282 return c;
1283 jem1:
1284 c = -1;
1285 goto jleave;
1288 static void
1289 a_message__threadmark(struct message *self, int f){
1290 NYD2_ENTER;
1291 if(!(self->m_flag & MHIDDEN) &&
1292 (f == MDELETED || !(self->m_flag & MDELETED) || a_message_list_saw_d))
1293 self->m_flag |= MMARK;
1295 if((self = self->m_child) != NULL){
1296 goto jcall;
1297 while((self = self->m_younger) != NULL)
1298 if(self->m_child != NULL)
1299 jcall:
1300 a_message__threadmark(self, f);
1301 else
1302 self->m_flag |= MMARK;
1304 NYD2_LEAVE;
1307 FL FILE *
1308 setinput(struct mailbox *mp, struct message *m, enum needspec need){
1309 enum okay ok;
1310 FILE *rv;
1311 NYD_ENTER;
1313 rv = NULL;
1315 switch(need){
1316 case NEED_HEADER:
1317 ok = (m->m_content_info & CI_HAVE_HEADER) ? OKAY
1318 : a_message_get_header(m);
1319 break;
1320 case NEED_BODY:
1321 ok = (m->m_content_info & CI_HAVE_BODY) ? OKAY : get_body(m);
1322 break;
1323 default:
1324 case NEED_UNSPEC:
1325 ok = OKAY;
1326 break;
1328 if(ok != OKAY)
1329 goto jleave;
1331 fflush(mp->mb_otf);
1332 if(fseek(mp->mb_itf, (long)mailx_positionof(m->m_block, m->m_offset),
1333 SEEK_SET) == -1){
1334 n_perr(_("fseek"), 0);
1335 n_panic(_("temporary file seek"));
1337 rv = mp->mb_itf;
1338 jleave:
1339 NYD_LEAVE;
1340 return rv;
1343 FL enum okay
1344 get_body(struct message *mp){
1345 enum okay rv;
1346 NYD_ENTER;
1347 n_UNUSED(mp);
1349 switch(mb.mb_type){
1350 case MB_FILE:
1351 case MB_MAILDIR:
1352 rv = OKAY;
1353 break;
1354 #ifdef HAVE_POP3
1355 case MB_POP3:
1356 rv = pop3_body(mp);
1357 break;
1358 #endif
1359 #ifdef HAVE_IMAP
1360 case MB_IMAP:
1361 case MB_CACHE:
1362 rv = imap_body(mp);
1363 break;
1364 #endif
1365 case MB_VOID:
1366 default:
1367 rv = STOP;
1368 break;
1370 NYD_LEAVE;
1371 return rv;
1374 FL void
1375 message_reset(void){
1376 NYD_ENTER;
1377 if(message != NULL){
1378 n_free(message);
1379 message = NULL;
1381 msgCount = 0;
1382 a_message_mem_space = 0;
1383 NYD_LEAVE;
1386 FL void
1387 message_append(struct message *mp){
1388 NYD_ENTER;
1389 if(UICMP(z, msgCount + 1, >=, a_message_mem_space)){
1390 /* XXX remove _mem_space magics (or use s_Vector) */
1391 a_message_mem_space = ((a_message_mem_space >= 128 &&
1392 a_message_mem_space <= 1000000)
1393 ? a_message_mem_space << 1 : a_message_mem_space + 64);
1394 message = n_realloc(message, a_message_mem_space * sizeof(*message));
1396 if(msgCount > 0){
1397 if(mp != NULL)
1398 message[msgCount - 1] = *mp;
1399 else
1400 memset(&message[msgCount - 1], 0, sizeof *message);
1402 NYD_LEAVE;
1405 FL void
1406 message_append_null(void){
1407 NYD_ENTER;
1408 if(msgCount == 0)
1409 message_append(NULL);
1410 setdot(message);
1411 message[msgCount].m_size = 0;
1412 message[msgCount].m_lines = 0;
1413 NYD_LEAVE;
1416 FL bool_t
1417 message_match(struct message *mp, struct search_expr const *sep,
1418 bool_t with_headers){
1419 char **line;
1420 size_t *linesize, cnt;
1421 FILE *fp;
1422 bool_t rv;
1423 NYD_ENTER;
1425 rv = FAL0;
1427 if((fp = Ftmp(NULL, "mpmatch", OF_RDWR | OF_UNLINK | OF_REGISTER)) == NULL)
1428 goto j_leave;
1430 if(sendmp(mp, fp, NULL, NULL, SEND_TOSRCH, NULL) < 0)
1431 goto jleave;
1432 fflush_rewind(fp);
1434 cnt = fsize(fp);
1435 line = &termios_state.ts_linebuf; /* XXX line pool */
1436 linesize = &termios_state.ts_linesize; /* XXX line pool */
1438 if(!with_headers)
1439 while(fgetline(line, linesize, &cnt, NULL, fp, 0))
1440 if(**line == '\n')
1441 break;
1443 while(fgetline(line, linesize, &cnt, NULL, fp, 0)){
1444 #ifdef HAVE_REGEX
1445 if(sep->ss_bodyre != NULL){
1446 if(regexec(sep->ss_bodyre, *line, 0,NULL, 0) == REG_NOMATCH)
1447 continue;
1448 }else
1449 #endif
1450 if(!substr(*line, sep->ss_body))
1451 continue;
1452 rv = TRU1;
1453 break;
1456 jleave:
1457 Fclose(fp);
1458 j_leave:
1459 NYD_LEAVE;
1460 return rv;
1463 FL struct message *
1464 setdot(struct message *mp){
1465 NYD_ENTER;
1466 if(dot != mp){
1467 prevdot = dot;
1468 n_pstate &= ~n_PS_DID_PRINT_DOT;
1470 dot = mp;
1471 uncollapse1(dot, 0);
1472 NYD_LEAVE;
1473 return dot;
1476 FL void
1477 touch(struct message *mp){
1478 NYD_ENTER;
1479 mp->m_flag |= MTOUCH;
1480 if(!(mp->m_flag & MREAD))
1481 mp->m_flag |= MREAD | MSTATUS;
1482 NYD_LEAVE;
1485 FL int
1486 getmsglist(char const *buf, int *vector, int flags)
1488 int *ip, mc;
1489 struct message *mp;
1490 NYD_ENTER;
1492 n_pstate &= ~n_PS_ARGLIST_MASK;
1493 a_message_list_last_saw_d = a_message_list_saw_d;
1494 a_message_list_saw_d = FAL0;
1496 if(msgCount == 0){
1497 *vector = 0;
1498 mc = 0;
1499 goto jleave;
1502 n_pstate |= n_PS_MSGLIST_DIRECT;
1504 if(a_message_markall(buf, flags) < 0){
1505 mc = -1;
1506 goto jleave;
1509 ip = vector;
1510 if(n_pstate & n_PS_HOOK_NEWMAIL){
1511 mc = 0;
1512 for(mp = message; mp < &message[msgCount]; ++mp)
1513 if(mp->m_flag & MMARK){
1514 if(!(mp->m_flag & MNEWEST))
1515 a_message_unmark((int)PTR2SIZE(mp - message + 1));
1516 else
1517 ++mc;
1519 if(mc == 0){
1520 mc = -1;
1521 goto jleave;
1525 if(mb.mb_threaded == 0){
1526 for(mp = message; mp < &message[msgCount]; ++mp)
1527 if(mp->m_flag & MMARK)
1528 *ip++ = (int)PTR2SIZE(mp - message + 1);
1529 }else{
1530 for(mp = threadroot; mp != NULL; mp = next_in_thread(mp))
1531 if(mp->m_flag & MMARK)
1532 *ip++ = (int)PTR2SIZE(mp - message + 1);
1534 *ip = 0;
1535 mc = (int)PTR2SIZE(ip - vector);
1536 if(mc != 1)
1537 n_pstate &= ~n_PS_MSGLIST_DIRECT;
1538 jleave:
1539 NYD_LEAVE;
1540 return mc;
1543 FL int
1544 first(int f, int m)
1546 struct message *mp;
1547 int rv;
1548 NYD_ENTER;
1550 if (msgCount == 0) {
1551 rv = 0;
1552 goto jleave;
1555 f &= MDELETED;
1556 m &= MDELETED;
1557 for (mp = dot;
1558 mb.mb_threaded ? (mp != NULL) : PTRCMP(mp, <, message + msgCount);
1559 mb.mb_threaded ? (mp = next_in_thread(mp)) : ++mp) {
1560 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & m) == (ui32_t)f) {
1561 rv = (int)PTR2SIZE(mp - message + 1);
1562 goto jleave;
1566 if (dot > message) {
1567 for (mp = dot - 1; (mb.mb_threaded ? (mp != NULL) : (mp >= message));
1568 mb.mb_threaded ? (mp = prev_in_thread(mp)) : --mp) {
1569 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & m) == (ui32_t)f) {
1570 rv = (int)PTR2SIZE(mp - message + 1);
1571 goto jleave;
1575 rv = 0;
1576 jleave:
1577 NYD_LEAVE;
1578 return rv;
1581 FL void
1582 mark(int mno, int f){
1583 struct message *mp;
1584 int i;
1585 NYD_ENTER;
1587 i = mno;
1588 if(i < 1 || i > msgCount)
1589 n_panic(_("Bad message number to mark"));
1590 mp = &message[--i];
1592 if(mb.mb_threaded == 1 && a_message_threadflag)
1593 a_message__threadmark(mp, f);
1594 else{
1595 assert(!(mp->m_flag & MHIDDEN));
1596 mp->m_flag |= MMARK;
1598 NYD_LEAVE;
1601 /* s-it-mode */