make-config.in: complete path (leftover of [807f64e2], 2015-12-26!)
[s-mailx.git] / message.c
blob11b73a8a091a81f2094b05d218f74e59c126ce70
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Message, message array, n_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 * SPDX-License-Identifier: BSD-3-Clause
7 */
8 /*
9 * Copyright (c) 1980, 1993
10 * The Regents of the University of California. All rights reserved.
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
14 * are met:
15 * 1. Redistributions of source code must retain the above copyright
16 * notice, this list of conditions and the following disclaimer.
17 * 2. Redistributions in binary form must reproduce the above copyright
18 * notice, this list of conditions and the following disclaimer in the
19 * documentation and/or other materials provided with the distribution.
20 * 3. Neither the name of the University nor the names of its contributors
21 * may be used to endorse or promote products derived from this software
22 * without specific prior written permission.
24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 * SUCH DAMAGE.
36 #undef n_FILE
37 #define n_FILE message
39 #ifndef HAVE_AMALGAMATION
40 # include "nail.h"
41 #endif
43 /* Token values returned by the scanner used for argument lists.
44 * Also, sizes of scanner-related things */
45 enum a_msg_token{
46 a_MSG_T_EOL, /* End of the command line */
47 a_MSG_T_NUMBER, /* Message number */
48 a_MSG_T_MINUS, /* - */
49 a_MSG_T_STRING, /* A string (possibly containing -) */
50 a_MSG_T_DOT, /* . */
51 a_MSG_T_UP, /* ^ */
52 a_MSG_T_DOLLAR, /* $ */
53 a_MSG_T_ASTER, /* * */
54 a_MSG_T_OPEN, /* ( */
55 a_MSG_T_CLOSE, /* ) */
56 a_MSG_T_PLUS, /* + */
57 a_MSG_T_COMMA, /* , */
58 a_MSG_T_SEMI, /* ; */
59 a_MSG_T_BACK, /* ` */
60 a_MSG_T_ERROR /* Lexical error */
63 enum a_msg_idfield{
64 a_MSG_ID_REFERENCES,
65 a_MSG_ID_IN_REPLY_TO
68 enum a_msg_state{
69 a_MSG_S_NEW = 1u<<0,
70 a_MSG_S_OLD = 1u<<1,
71 a_MSG_S_UNREAD = 1u<<2,
72 a_MSG_S_DELETED = 1u<<3,
73 a_MSG_S_READ = 1u<<4,
74 a_MSG_S_FLAG = 1u<<5,
75 a_MSG_S_ANSWERED = 1u<<6,
76 a_MSG_S_DRAFT = 1u<<7,
77 a_MSG_S_SPAM = 1u<<8,
78 a_MSG_S_SPAMUNSURE = 1u<<9,
79 a_MSG_S_MLIST = 1u<<10,
80 a_MSG_S_MLSUBSCRIBE = 1u<<11
83 struct a_msg_coltab{
84 char mco_char; /* What to find past : */
85 ui8_t mco__dummy[3];
86 int mco_bit; /* Associated modifier bit */
87 int mco_mask; /* m_status bits to mask */
88 int mco_equal; /* ... must equal this */
91 struct a_msg_lex{
92 char ml_char;
93 ui8_t ml_token;
96 struct a_msg_speclex{
97 char *msl_str; /* If parsed a string */
98 int msl_no; /* If parsed a number TODO siz_t! */
99 char msl__smallstrbuf[4];
100 /* We directly adjust pointer in .ca_arg.ca_str.s, do not adjust .l */
101 struct n_cmd_arg *msl_cap;
102 char const *msl_input_orig;
105 static struct a_msg_coltab const a_msg_coltabs[] = {
106 {'n', {0,}, a_MSG_S_NEW, MNEW, MNEW},
107 {'o', {0,}, a_MSG_S_OLD, MNEW, 0},
108 {'u', {0,}, a_MSG_S_UNREAD, MREAD, 0},
109 {'d', {0,}, a_MSG_S_DELETED, MDELETED, MDELETED},
110 {'r', {0,}, a_MSG_S_READ, MREAD, MREAD},
111 {'f', {0,}, a_MSG_S_FLAG, MFLAGGED, MFLAGGED},
112 {'a', {0,}, a_MSG_S_ANSWERED, MANSWERED, MANSWERED},
113 {'t', {0,}, a_MSG_S_DRAFT, MDRAFTED, MDRAFTED},
114 {'s', {0,}, a_MSG_S_SPAM, MSPAM, MSPAM},
115 {'S', {0,}, a_MSG_S_SPAMUNSURE, MSPAMUNSURE, MSPAMUNSURE},
116 /* These have no per-message flags, but must be evaluated */
117 {'l', {0,}, a_MSG_S_MLIST, 0, 0},
118 {'L', {0,}, a_MSG_S_MLSUBSCRIBE, 0, 0},
121 static struct a_msg_lex const a_msg_singles[] = {
122 {'$', a_MSG_T_DOLLAR},
123 {'.', a_MSG_T_DOT},
124 {'^', a_MSG_T_UP},
125 {'*', a_MSG_T_ASTER},
126 {'-', a_MSG_T_MINUS},
127 {'+', a_MSG_T_PLUS},
128 {'(', a_MSG_T_OPEN},
129 {')', a_MSG_T_CLOSE},
130 {',', a_MSG_T_COMMA},
131 {';', a_MSG_T_SEMI},
132 {'`', a_MSG_T_BACK}
135 /* Slots in ::message */
136 static size_t a_msg_mem_space;
138 /* Mark entire threads */
139 static bool_t a_msg_threadflag;
141 /* :d on its way HACK TODO */
142 static bool_t a_msg_list_saw_d, a_msg_list_last_saw_d;
144 /* Lazy load message header fields */
145 static enum okay a_msg_get_header(struct message *mp);
147 /* Append, taking care of resizes TODO vector */
148 static char **a_msg_add_to_nmadat(char ***nmadat, size_t *nmasize,
149 char **np, char *string);
151 /* Mark all messages that the user wanted from the command line in the message
152 * structure. Return 0 on success, -1 on error */
153 static int a_msg_markall(char const *orig, struct n_cmd_arg *cap, int f);
155 /* Turn the character after a colon modifier into a bit value */
156 static int a_msg_evalcol(int col);
158 /* Check the passed message number for legality and proper flags. Unless f is
159 * MDELETED the message has to be undeleted */
160 static bool_t a_msg_check(int mno, int f);
162 /* Scan out a single lexical item and return its token number, updating *mslp */
163 static int a_msg_scan(struct a_msg_speclex *mslp);
165 /* See if the passed name sent the passed message */
166 static bool_t a_msg_match_sender(struct message *mp, char const *str,
167 bool_t allnet);
169 /* Check whether the given message-id or references match */
170 static bool_t a_msg_match_mid(struct message *mp, char const *id,
171 enum a_msg_idfield idfield);
173 /* See if the given string matches.
174 * For the purpose of the scan, we ignore case differences.
175 * This is the engine behind the "/" search */
176 static bool_t a_msg_match_dash(struct message *mp, char const *str);
178 /* See if the given search expression matches.
179 * For the purpose of the scan, we ignore case differences.
180 * This is the engine behind the "@[..@].." search */
181 static bool_t a_msg_match_at(struct message *mp, struct search_expr *sep);
183 /* Unmark the named message */
184 static void a_msg_unmark(int mesg);
186 /* Return the message number corresponding to the passed meta character */
187 static int a_msg_metamess(int meta, int f);
189 /* Helper for mark(): self valid, threading enabled */
190 static void a_msg__threadmark(struct message *self, int f);
192 static enum okay
193 a_msg_get_header(struct message *mp){
194 enum okay rv;
195 NYD2_ENTER;
196 n_UNUSED(mp);
198 switch(mb.mb_type){
199 case MB_FILE:
200 case MB_MAILDIR:
201 rv = OKAY;
202 break;
203 #ifdef HAVE_POP3
204 case MB_POP3:
205 rv = pop3_header(mp);
206 break;
207 #endif
208 #ifdef HAVE_IMAP
209 case MB_IMAP:
210 case MB_CACHE:
211 rv = imap_header(mp);
212 break;
213 #endif
214 case MB_VOID:
215 default:
216 rv = STOP;
217 break;
219 NYD2_LEAVE;
220 return rv;
223 static char **
224 a_msg_add_to_nmadat(char ***nmadat, size_t *nmasize, /* TODO Vector */
225 char **np, char *string){
226 size_t idx, i;
227 NYD2_ENTER;
229 if((idx = PTR2SIZE(np - *nmadat)) >= *nmasize){
230 char **narr;
232 i = *nmasize << 1;
233 *nmasize = i;
234 narr = n_autorec_alloc(i * sizeof *np);
235 memcpy(narr, *nmadat, i >>= 1);
236 *nmadat = narr;
237 np = &narr[idx];
239 *np++ = string;
240 NYD2_LEAVE;
241 return np;
244 static int
245 a_msg_markall(char const *orig, struct n_cmd_arg *cap, int f){
246 struct a_msg_speclex msl;
247 enum a_msg_idfield idfield;
248 size_t j, nmasize;
249 char const *id;
250 char **nmadat_lofi, **nmadat, **np, **nq, *cp;
251 struct message *mp, *mx;
252 int i, valdot, beg, colmod, tok, colresult;
253 enum{
254 a_NONE = 0,
255 a_ALLNET = 1u<<0, /* (CTA()d to be == TRU1 */
256 a_ALLOC = 1u<<1, /* Have allocated something */
257 a_THREADED = 1u<<2,
258 a_ERROR = 1u<<3,
259 a_ANY = 1u<<4, /* Have marked just ANY */
260 a_RANGE = 1u<<5, /* Seen dash, await close */
261 a_ASTER = 1u<<8,
262 a_TOPEN = 1u<<9, /* ( used (and didn't match) */
263 a_TBACK = 1u<<10, /* ` used (and didn't match) */
264 #ifdef HAVE_IMAP
265 a_HAVE_IMAP_HEADERS = 1u<<14,
266 #endif
267 a_LOG = 1u<<29, /* Log errors */
268 a_TMP = 1u<<30
269 } flags;
270 NYD_ENTER;
271 n_LCTA((ui32_t)a_ALLNET == (ui32_t)TRU1,
272 "Constant is converted to bool_t via AND, thus");
274 /* Update message array: clear MMARK but remember its former state for ` */
275 for(i = msgCount; i-- > 0;){
276 enum mflag mf;
278 mf = (mp = &message[i])->m_flag;
279 if(mf & MMARK)
280 mf |= MOLDMARK;
281 else
282 mf &= ~MOLDMARK;
283 mf &= ~MMARK;
284 mp->m_flag = mf;
287 memset(&msl, 0, sizeof msl);
288 msl.msl_cap = cap;
289 msl.msl_input_orig = orig;
291 np = nmadat =
292 nmadat_lofi = n_lofi_alloc((nmasize = 64) * sizeof *np); /* TODO vector */
293 n_UNINIT(beg, 0);
294 n_UNINIT(idfield, a_MSG_ID_REFERENCES);
295 a_msg_threadflag = FAL0;
296 valdot = (int)PTR2SIZE(dot - message + 1);
297 colmod = 0;
298 id = NULL;
299 flags = a_ALLOC | (mb.mb_threaded ? a_THREADED : 0) |
300 ((!(n_pstate & n_PS_HOOK_MASK) || (n_poption & n_PO_D_V))
301 ? a_LOG : 0);
303 while((tok = a_msg_scan(&msl)) != a_MSG_T_EOL){
304 if((a_msg_threadflag = (tok < 0)))
305 tok &= INT_MAX;
307 switch(tok){
308 case a_MSG_T_NUMBER:
309 n_pstate |= n_PS_MSGLIST_GABBY;
310 jnumber:
311 if(!a_msg_check(msl.msl_no, f))
312 goto jerr;
314 if(flags & a_RANGE){
315 flags ^= a_RANGE;
317 if(!(flags & a_THREADED)){
318 if(beg < msl.msl_no)
319 i = beg;
320 else{
321 i = msl.msl_no;
322 msl.msl_no = beg;
325 for(; i <= msl.msl_no; ++i){
326 mp = &message[i - 1];
327 if(!(mp->m_flag & MHIDDEN) &&
328 (f == MDELETED || !(mp->m_flag & MDELETED))){
329 mark(i, f);
330 flags |= a_ANY;
333 }else{
334 /* TODO threaded ranges are a mess */
335 enum{
336 a_T_NONE,
337 a_T_HOT = 1u<<0,
338 a_T_DIR_PREV = 1u<<1
339 } tf;
340 int i_base;
342 if(beg < msl.msl_no)
343 i = beg;
344 else{
345 i = msl.msl_no;
346 msl.msl_no = beg;
349 i_base = i;
350 tf = a_T_NONE;
351 jnumber__thr:
352 for(;;){
353 mp = &message[i - 1];
354 if(!(mp->m_flag & MHIDDEN) &&
355 (f == MDELETED || !(mp->m_flag & MDELETED))){
356 if(tf & a_T_HOT){
357 mark(i, f);
358 flags |= a_ANY;
362 /* We may have reached the endpoint. If we were still
363 * detecting the direction to search for it, restart.
364 * Otherwise finished */
365 if(i == msl.msl_no){ /* XXX */
366 if(!(tf & a_T_HOT)){
367 tf |= a_T_HOT;
368 i = i_base;
369 goto jnumber__thr;
371 break;
374 mx = (tf & a_T_DIR_PREV) ? prev_in_thread(mp)
375 : next_in_thread(mp);
376 if(mx == NULL){
377 /* We anyway have failed to reach the endpoint in this
378 * direction; if we already switched that, report error */
379 if(!(tf & a_T_DIR_PREV)){
380 tf |= a_T_DIR_PREV;
381 i = i_base;
382 goto jnumber__thr;
384 id = N_("Range crosses multiple threads\n");
385 goto jerrmsg;
387 i = (int)PTR2SIZE(mx - message + 1);
391 beg = 0;
392 }else{
393 /* Could be an inclusive range? */
394 if(msl.msl_cap != NULL &&
395 msl.msl_cap->ca_arg.ca_str.s[0] == '-'){
396 if(*++msl.msl_cap->ca_arg.ca_str.s == '\0')
397 msl.msl_cap = msl.msl_cap->ca_next;
398 beg = msl.msl_no;
399 flags |= a_RANGE;
400 }else{
401 mark(msl.msl_no, f);
402 flags |= a_ANY;
405 break;
406 case a_MSG_T_PLUS:
407 n_pstate &= ~n_PS_MSGLIST_DIRECT;
408 n_pstate |= n_PS_MSGLIST_GABBY;
409 i = valdot;
411 if(flags & a_THREADED){
412 mx = next_in_thread(&message[i - 1]);
413 i = mx ? (int)PTR2SIZE(mx - message + 1) : msgCount + 1;
414 }else
415 ++i;
416 if(i > msgCount){
417 id = N_("Referencing beyond last message\n");
418 goto jerrmsg;
420 }while(message[i - 1].m_flag == MHIDDEN ||
421 (message[i - 1].m_flag & MDELETED) != (unsigned)f);
422 msl.msl_no = i;
423 goto jnumber;
424 case a_MSG_T_MINUS:
425 n_pstate &= ~n_PS_MSGLIST_DIRECT;
426 n_pstate |= n_PS_MSGLIST_GABBY;
427 i = valdot;
429 if(flags & a_THREADED){
430 mx = prev_in_thread(&message[i - 1]);
431 i = mx ? (int)PTR2SIZE(mx - message + 1) : 0;
432 }else
433 --i;
434 if(i <= 0){
435 id = N_("Referencing before first message\n");
436 goto jerrmsg;
438 }while(message[i - 1].m_flag == MHIDDEN ||
439 (message[i - 1].m_flag & MDELETED) != (unsigned)f);
440 msl.msl_no = i;
441 goto jnumber;
442 case a_MSG_T_STRING:
443 n_pstate &= ~n_PS_MSGLIST_DIRECT;
444 if(flags & a_RANGE)
445 goto jebadrange;
447 /* This may be a colon modifier */
448 if((cp = msl.msl_str)[0] != ':')
449 np = a_msg_add_to_nmadat(&nmadat, &nmasize, np,
450 savestr(msl.msl_str));
451 else{
452 while(*++cp != '\0'){
453 colresult = a_msg_evalcol(*cp);
454 if(colresult == 0){
455 if(flags & a_LOG)
456 n_err(_("Unknown colon modifier: %s\n"), msl.msl_str);
457 goto jerr;
459 if(colresult == a_MSG_S_DELETED){
460 a_msg_list_saw_d = TRU1;
461 f |= MDELETED;
463 colmod |= colresult;
466 break;
467 case a_MSG_T_OPEN:
468 n_pstate &= ~n_PS_MSGLIST_DIRECT;
469 if(flags & a_RANGE)
470 goto jebadrange;
471 flags |= a_TOPEN;
473 #ifdef HAVE_IMAP_SEARCH
474 /* C99 */{
475 ssize_t ires;
477 if((ires = imap_search(msl.msl_str, f)) >= 0){
478 if(ires > 0)
479 flags |= a_ANY;
480 break;
483 #else
484 if(flags & a_LOG)
485 n_err(_("Optional selector not available: %s\n"), msl.msl_str);
486 #endif
487 goto jerr;
488 case a_MSG_T_DOLLAR:
489 case a_MSG_T_UP:
490 case a_MSG_T_SEMI:
491 n_pstate |= n_PS_MSGLIST_GABBY;
492 /* FALLTHRU */
493 case a_MSG_T_DOT: /* Don't set _GABBY for dot to allow history.. */
494 n_pstate &= ~n_PS_MSGLIST_DIRECT;
495 if((msl.msl_no = a_msg_metamess(msl.msl_str[0], f)) == -1)
496 goto jerr;
497 goto jnumber;
498 case a_MSG_T_BACK:
499 n_pstate &= ~n_PS_MSGLIST_DIRECT;
500 if(flags & a_RANGE)
501 goto jebadrange;
503 flags |= a_TBACK;
504 for(i = 0; i < msgCount; ++i){
505 if((mp = &message[i])->m_flag & MHIDDEN)
506 continue;
507 if((mp->m_flag & MDELETED) != (unsigned)f){
508 if(!a_msg_list_last_saw_d)
509 continue;
510 a_msg_list_saw_d = TRU1;
512 if(mp->m_flag & MOLDMARK){
513 mark(i + 1, f);
514 flags &= ~a_TBACK;
515 flags |= a_ANY;
518 break;
519 case a_MSG_T_ASTER:
520 n_pstate &= ~n_PS_MSGLIST_DIRECT;
521 if(flags & a_RANGE)
522 goto jebadrange;
523 flags |= a_ASTER;
524 break;
525 case a_MSG_T_COMMA:
526 n_pstate &= ~n_PS_MSGLIST_DIRECT;
527 n_pstate |= n_PS_MSGLIST_GABBY;
528 if(flags & a_RANGE)
529 goto jebadrange;
531 #ifdef HAVE_IMAP
532 if(!(flags & a_HAVE_IMAP_HEADERS) && mb.mb_type == MB_IMAP){
533 flags |= a_HAVE_IMAP_HEADERS;
534 imap_getheaders(1, msgCount);
536 #endif
538 if(id == NULL){
539 if((cp = hfield1("in-reply-to", dot)) != NULL)
540 idfield = a_MSG_ID_IN_REPLY_TO;
541 else if((cp = hfield1("references", dot)) != NULL){
542 struct name *enp;
544 if((enp = extract(cp, GREF)) != NULL){
545 while(enp->n_flink != NULL)
546 enp = enp->n_flink;
547 cp = enp->n_name;
548 idfield = a_MSG_ID_REFERENCES;
549 }else
550 cp = NULL;
553 if(cp != NULL)
554 id = savestr(cp);
555 else{
556 id = N_("Message-ID of parent of \"dot\" is indeterminable\n");
557 goto jerrmsg;
559 }else if(flags & a_LOG)
560 n_err(_("Ignoring redundant specification of , selector\n"));
561 break;
562 case a_MSG_T_ERROR:
563 n_pstate &= ~n_PS_MSGLIST_DIRECT;
564 n_pstate |= n_PS_MSGLIST_GABBY;
565 goto jerr;
568 /* Explicitly disallow invalid ranges for future safety */
569 if(msl.msl_cap != NULL && msl.msl_cap->ca_arg.ca_str.s[0] == '-' &&
570 !(flags & a_RANGE)){
571 if(flags & a_LOG)
572 n_err(_("Ignoring invalid range in: %s\n"), msl.msl_input_orig);
573 if(*++msl.msl_cap->ca_arg.ca_str.s == '\0')
574 msl.msl_cap = msl.msl_cap->ca_next;
577 if(flags & a_RANGE){
578 id = N_("Missing second range argument\n");
579 goto jerrmsg;
582 np = a_msg_add_to_nmadat(&nmadat, &nmasize, np, NULL);
583 --np;
585 /* * is special at this point, after we have parsed the entire line */
586 if(flags & a_ASTER){
587 for(i = 0; i < msgCount; ++i){
588 if((mp = &message[i])->m_flag & MHIDDEN)
589 continue;
590 if(!a_msg_list_saw_d && (mp->m_flag & MDELETED) != (unsigned)f)
591 continue;
592 mark(i + 1, f);
593 flags |= a_ANY;
595 if(!(flags & a_ANY))
596 goto jenoapp;
597 goto jleave;
600 /* If any names were given, add any messages which match */
601 if(np > nmadat || id != NULL){
602 struct search_expr *sep;
604 sep = NULL;
606 /* The @ search works with struct search_expr, so build an array.
607 * To simplify array, i.e., regex_t destruction, and optimize for the
608 * common case we walk the entire array even in case of errors */
609 /* XXX Like many other things around here: this should be outsourced */
610 if(np > nmadat){
611 j = PTR2SIZE(np - nmadat) * sizeof(*sep);
612 sep = n_lofi_alloc(j);
613 memset(sep, 0, j);
615 for(j = 0, nq = nmadat; *nq != NULL; ++j, ++nq){
616 char *xsave, *x, *y;
618 sep[j].ss_body = x = xsave = *nq;
619 if(*x != '@' || (flags & a_ERROR))
620 continue;
622 /* Cramp the namelist */
623 for(y = &x[1];; ++y){
624 if(*y == '\0'){
625 x = NULL;
626 break;
628 if(*y == '@'){
629 x = y;
630 break;
633 if(x == NULL || &x[-1] == xsave)
634 jat_where_default:
635 sep[j].ss_field = "subject";
636 else{
637 ++xsave;
638 if(*xsave == '~'){
639 sep[j].ss_skin = TRU1;
640 if(++xsave >= x){
641 if(flags & a_LOG)
642 n_err(_("[@..]@ search expression: no namelist, "
643 "only \"~\" skin indicator\n"));
644 flags |= a_ERROR;
645 continue;
648 cp = savestrbuf(xsave, PTR2SIZE(x - xsave));
650 /* Namelist could be a regular expression, too */
651 #ifdef HAVE_REGEX
652 if(n_is_maybe_regex(cp)){
653 int s;
655 assert(sep[j].ss_field == NULL);
656 if((s = regcomp(&sep[j].ss__fieldre_buf, cp,
657 REG_EXTENDED | REG_ICASE | REG_NOSUB)) != 0){
658 if(flags & a_LOG)
659 n_err(_("Invalid regular expression: %s: %s\n"),
660 n_shexp_quote_cp(cp, FAL0),
661 n_regex_err_to_doc(NULL, s));
662 flags |= a_ERROR;
663 continue;
665 sep[j].ss_fieldre = &sep[j].ss__fieldre_buf;
666 }else
667 #endif
669 struct str sio;
671 /* Because of the special cases we need to trim explicitly
672 * here, they are not covered by n_strsep() */
673 sio.s = cp;
674 sio.l = PTR2SIZE(x - xsave);
675 if(*(cp = n_str_trim(&sio, n_STR_TRIM_BOTH)->s) == '\0')
676 goto jat_where_default;
677 sep[j].ss_field = cp;
681 /* The actual search expression. If it is empty we only test the
682 * field(s) for existence */
683 x = &(x == NULL ? *nq : x)[1];
684 if(*x == '\0'){
685 sep[j].ss_field_exists = TRU1;
686 #ifdef HAVE_REGEX
687 }else if(n_is_maybe_regex(x)){
688 int s;
690 sep[j].ss_body = NULL;
691 if((s = regcomp(&sep[j].ss__bodyre_buf, x,
692 REG_EXTENDED | REG_ICASE | REG_NOSUB)) != 0){
693 if(flags & a_LOG)
694 n_err(_("Invalid regular expression: %s: %s\n"),
695 n_shexp_quote_cp(x, FAL0),
696 n_regex_err_to_doc(NULL, s));
697 flags |= a_ERROR;
698 continue;
700 sep[j].ss_bodyre = &sep[j].ss__bodyre_buf;
701 #endif
702 }else
703 sep[j].ss_body = x;
705 if(flags & a_ERROR)
706 goto jnamesearch_sepfree;
709 /* Iterate the entire message array */
710 #ifdef HAVE_IMAP
711 if(!(flags & a_HAVE_IMAP_HEADERS) && mb.mb_type == MB_IMAP){
712 flags |= a_HAVE_IMAP_HEADERS;
713 imap_getheaders(1, msgCount);
715 #endif
716 if(ok_blook(allnet))
717 flags |= a_ALLNET;
718 n_autorec_relax_create();
719 for(i = 0; i < msgCount; ++i){
720 if((mp = &message[i])->m_flag & (MMARK | MHIDDEN))
721 continue;
722 if(!a_msg_list_saw_d && (mp->m_flag & MDELETED) != (unsigned)f)
723 continue;
725 flags &= ~a_TMP;
726 if(np > nmadat){
727 for(nq = nmadat; *nq != NULL; ++nq){
728 if(**nq == '@'){
729 if(a_msg_match_at(mp, &sep[PTR2SIZE(nq - nmadat)])){
730 flags |= a_TMP;
731 break;
733 }else if(**nq == '/'){
734 if(a_msg_match_dash(mp, *nq)){
735 flags |= a_TMP;
736 break;
738 }else if(a_msg_match_sender(mp, *nq, (flags & a_ALLNET))){
739 flags |= a_TMP;
740 break;
744 if(!(flags & a_TMP) &&
745 id != NULL && a_msg_match_mid(mp, id, idfield))
746 flags |= a_TMP;
748 if(flags & a_TMP){
749 mark(i + 1, f);
750 flags |= a_ANY;
752 n_autorec_relax_unroll();
754 n_autorec_relax_gut();
756 jnamesearch_sepfree:
757 if(sep != NULL){
758 #ifdef HAVE_REGEX
759 for(j = PTR2SIZE(np - nmadat); j-- != 0;){
760 if(sep[j].ss_fieldre != NULL)
761 regfree(sep[j].ss_fieldre);
762 if(sep[j].ss_bodyre != NULL)
763 regfree(sep[j].ss_bodyre);
765 #endif
766 n_lofi_free(sep);
768 if(flags & a_ERROR)
769 goto jerr;
772 /* If any colon modifiers were given, go through and mark any messages which
773 * do satisfy the modifiers */
774 if(colmod != 0){
775 for(i = 0; i < msgCount; ++i){
776 struct a_msg_coltab const *colp;
778 if((mp = &message[i])->m_flag & (MMARK | MHIDDEN))
779 continue;
780 if(!a_msg_list_saw_d && (mp->m_flag & MDELETED) != (unsigned)f)
781 continue;
783 for(colp = a_msg_coltabs;
784 PTRCMP(colp, <, &a_msg_coltabs[n_NELEM(a_msg_coltabs)]); ++colp)
785 if(colp->mco_bit & colmod){
786 /* Is this a colon modifier that requires evaluation? */
787 if(colp->mco_mask == 0){
788 if(colp->mco_bit & (a_MSG_S_MLIST |
789 a_MSG_S_MLSUBSCRIBE)){
790 enum mlist_state what;
792 what = (colp->mco_bit & a_MSG_S_MLIST) ? MLIST_KNOWN
793 : MLIST_SUBSCRIBED;
794 if(what == is_mlist_mp(mp, what))
795 goto jcolonmod_mark;
797 }else if((mp->m_flag & colp->mco_mask
798 ) == (enum mflag)colp->mco_equal){
799 jcolonmod_mark:
800 mark(i + 1, f);
801 flags |= a_ANY;
802 break;
808 /* It shall be an error if ` didn't match anything, and nothing else did */
809 if((flags & (a_TBACK | a_ANY)) == a_TBACK){
810 id = N_("No previously marked messages\n");
811 goto jerrmsg;
812 }else if(!(flags & a_ANY))
813 goto jenoapp;
815 assert(!(flags & a_ERROR));
816 jleave:
817 if(flags & a_ALLOC)
818 n_lofi_free(nmadat_lofi);
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_msg_evalcol(int col){
837 struct a_msg_coltab const *colp;
838 int rv;
839 NYD2_ENTER;
841 rv = 0;
842 for(colp = a_msg_coltabs;
843 PTRCMP(colp, <, &a_msg_coltabs[n_NELEM(a_msg_coltabs)]); ++colp)
844 if(colp->mco_char == col){
845 rv = colp->mco_bit;
846 break;
848 NYD2_LEAVE;
849 return rv;
852 static bool_t
853 a_msg_check(int mno, int f){
854 struct message *mp;
855 NYD2_ENTER;
857 if(mno < 1 || mno > msgCount){
858 n_err(_("%d: Invalid message number\n"), mno);
859 mno = 1;
860 }else if(((mp = &message[mno - 1])->m_flag & MHIDDEN) ||
861 (f != MDELETED && (mp->m_flag & MDELETED) != 0))
862 n_err(_("%d: inappropriate message\n"), mno);
863 else
864 mno = 0;
865 NYD2_LEAVE;
866 return (mno == 0);
869 static int
870 a_msg_scan(struct a_msg_speclex *mslp){
871 struct a_msg_lex const *lp;
872 char *cp, c;
873 int rv;
874 NYD_ENTER;
876 rv = a_MSG_T_EOL;
878 /* Empty cap's even for IGNORE_EMPTY (quoted empty tokens produce output) */
879 for(;; mslp->msl_cap = mslp->msl_cap->ca_next){
880 if(mslp->msl_cap == NULL)
881 goto jleave;
883 cp = mslp->msl_cap->ca_arg.ca_str.s;
884 if((c = *cp++) != '\0')
885 break;
888 /* Select members of a message thread */
889 if(c == '&'){
890 c = *cp;
891 if(c == '\0' || spacechar(c)){
892 mslp->msl_str = mslp->msl__smallstrbuf;
893 mslp->msl_str[0] = '.';
894 mslp->msl_str[1] = '\0';
895 if(c == '\0')
896 mslp->msl_cap = mslp->msl_cap->ca_next;
897 else{
898 jshexp_err:
899 n_err(_("Message list: invalid syntax: %s (in %s)\n"),
900 n_shexp_quote_cp(cp, FAL0),
901 n_shexp_quote_cp(mslp->msl_input_orig, FAL0));
902 rv = a_MSG_T_ERROR;
903 goto jleave;
905 rv = a_MSG_T_DOT | INT_MIN;
906 goto jleave;
908 rv = INT_MIN;
909 ++cp;
912 /* If the leading character is a digit, scan the number and convert it
913 * on the fly. Return a_MSG_T_NUMBER when done */
914 if(digitchar(c)){
915 mslp->msl_no = 0;
917 mslp->msl_no = (mslp->msl_no * 10) + c - '0'; /* XXX inline atoi */
918 while((c = *cp++, digitchar(c)));
920 if(c == '\0')
921 mslp->msl_cap = mslp->msl_cap->ca_next;
922 else{
923 --cp;
924 /* This could be a range */
925 if(c == '-')
926 mslp->msl_cap->ca_arg.ca_str.s = cp;
927 else
928 goto jshexp_err;
930 rv |= a_MSG_T_NUMBER;
931 goto jleave;
934 /* An IMAP SEARCH list. Note that a_MSG_T_OPEN has always been included
935 * in singles[] in Mail and mailx. Thus although there is no formal
936 * definition for (LIST) lists, they do not collide with historical
937 * practice because a subject string (LIST) could never been matched
938 * this way */
939 if (c == '(') {
940 bool_t inquote;
941 ui32_t level;
942 char *tocp;
944 (tocp = mslp->msl_str = mslp->msl_cap->ca_arg.ca_str.s)[0] = '(';
945 ++tocp;
946 level = 1;
947 inquote = FAL0;
948 do {
949 if ((c = *cp++) == '\0') {
950 jmtop:
951 n_err(_("Missing )\n"));
952 n_err(_("P.S.: message specifications are now shell tokens, "
953 "making it necessary to enclose IMAP search expressions "
954 "in (single) quotes, e.g., '(from \"me\")'\n"));
955 rv = a_MSG_T_ERROR;
956 goto jleave;
958 if (inquote && c == '\\') {
959 *tocp++ = c;
960 c = *cp++;
961 if (c == '\0')
962 goto jmtop;
963 } else if (c == '"')
964 inquote = !inquote;
965 else if (inquote)
966 /*EMPTY*/;
967 else if (c == '(')
968 ++level;
969 else if (c == ')')
970 --level;
971 else if (spacechar(c)) {
972 /* Replace unquoted whitespace by single space characters, to make
973 * the string IMAP SEARCH conformant */
974 c = ' ';
975 if (tocp[-1] == ' ')
976 --tocp;
978 *tocp++ = c;
979 } while (c != ')' || level > 0);
980 *tocp = '\0';
981 if(*cp != '\0')
982 goto jshexp_err;
983 mslp->msl_cap = mslp->msl_cap->ca_next;
984 rv |= a_MSG_T_OPEN;
985 goto jleave;
988 /* Check for single character tokens; return such if found */
989 for(lp = a_msg_singles;
990 PTRCMP(lp, <, &a_msg_singles[n_NELEM(a_msg_singles)]); ++lp){
991 if(c == lp->ml_char){
992 mslp->msl_str = mslp->msl__smallstrbuf;
993 mslp->msl_str[0] = c;
994 mslp->msl_str[1] = '\0';
995 if(*cp != '\0')
996 goto jshexp_err;
997 mslp->msl_cap = mslp->msl_cap->ca_next;
998 rv = lp->ml_token;
999 goto jleave;
1003 mslp->msl_cap = mslp->msl_cap->ca_next;
1004 mslp->msl_str = --cp;
1005 rv = a_MSG_T_STRING;
1006 jleave:
1007 NYD_LEAVE;
1008 return rv;
1011 static bool_t
1012 a_msg_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_msg_match_mid(struct message *mp, char const *id,
1091 enum a_msg_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_MSG_ID_REFERENCES:
1101 if(!msgidcmp(id, cp))
1102 rv = TRU1;
1103 break;
1104 case a_MSG_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_msg_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_msg_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_msg_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_msg_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_msg__threadmark(struct message *self, int f){
1290 NYD2_ENTER;
1291 if(!(self->m_flag & MHIDDEN) &&
1292 (f == MDELETED || !(self->m_flag & MDELETED) || a_msg_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_msg__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_msg_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_msg_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_msg_mem_space)){
1390 /* XXX remove _mem_space magics (or use s_Vector) */
1391 a_msg_mem_space = ((a_msg_mem_space >= 128 &&
1392 a_msg_mem_space <= 1000000)
1393 ? a_msg_mem_space << 1 : a_msg_mem_space + 64);
1394 message = n_realloc(message, a_msg_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 n_getmsglist(char const *buf, int *vector, int flags,
1487 struct n_cmd_arg **capp_or_null)
1489 int *ip, mc;
1490 struct message *mp;
1491 NYD_ENTER;
1493 n_pstate &= ~n_PS_ARGLIST_MASK;
1494 n_pstate |= n_PS_MSGLIST_DIRECT;
1495 a_msg_list_last_saw_d = a_msg_list_saw_d;
1496 a_msg_list_saw_d = FAL0;
1498 *vector = 0;
1499 if(capp_or_null != NULL)
1500 *capp_or_null = NULL;
1501 if(*buf == '\0'){
1502 mc = 0;
1503 goto jleave;
1506 /* TODO Parse the message spec into an ARGV; this should not happen here,
1507 * TODO but instead cmd_arg_parse() should feed in the list of parsed tokens
1508 * TODO to getmsglist(); as of today there are multiple getmsglist() users
1509 * TODO though, and they need to deal with that, then, too */
1510 /* C99 */{
1511 n_CMD_ARG_DESC_SUBCLASS_DEF(getmsglist, 1, pseudo_cad){
1512 {n_CMD_ARG_DESC_SHEXP | n_CMD_ARG_DESC_OPTION |
1513 n_CMD_ARG_DESC_GREEDY | n_CMD_ARG_DESC_HONOUR_STOP,
1514 n_SHEXP_PARSE_TRIM_IFSSPACE | n_SHEXP_PARSE_IFS_VAR |
1515 n_SHEXP_PARSE_IGNORE_EMPTY}
1516 }n_CMD_ARG_DESC_SUBCLASS_DEF_END;
1517 struct n_cmd_arg_ctx cac;
1519 cac.cac_desc = n_CMD_ARG_DESC_SUBCLASS_CAST(&pseudo_cad);
1520 cac.cac_indat = buf;
1521 cac.cac_inlen = UIZ_MAX;
1522 cac.cac_msgflag = flags;
1523 cac.cac_msgmask = 0;
1524 if(!n_cmd_arg_parse(&cac)){
1525 mc = -1;
1526 goto jleave;
1527 }else if(cac.cac_no == 0){
1528 mc = 0;
1529 goto jleave;
1530 }else{
1531 /* Is this indeed a (maybe optional) message list and a target? */
1532 if(capp_or_null != NULL){
1533 struct n_cmd_arg *cap, **lcapp;
1535 if((cap = cac.cac_arg)->ca_next == NULL){
1536 *capp_or_null = cap;
1537 mc = 0;
1538 goto jleave;
1540 for(;;){
1541 lcapp = &cap->ca_next;
1542 if((cap = *lcapp)->ca_next == NULL)
1543 break;
1545 *capp_or_null = cap;
1546 *lcapp = NULL;
1548 /* In the list-and-target mode we have to take special care, since
1549 * some commands use special call conventions historically (use the
1550 * MBOX, search for a message, whatever).
1551 * Thus, to allow things like "certsave '' bla" or "save '' ''",
1552 * watch out for two argument form with empty token first.
1553 * This special case is documented at the prototype */
1554 if(cac.cac_arg->ca_next == NULL &&
1555 cac.cac_arg->ca_arg.ca_str.s[0] == '\0'){
1556 mc = 0;
1557 goto jleave;
1561 if(msgCount == 0){
1562 mc = 0;
1563 goto jleave;
1564 }else if((mc = a_msg_markall(buf, cac.cac_arg, flags)) < 0){
1565 mc = -1;
1566 goto jleave;
1571 ip = vector;
1572 if(n_pstate & n_PS_HOOK_NEWMAIL){
1573 mc = 0;
1574 for(mp = message; mp < &message[msgCount]; ++mp)
1575 if(mp->m_flag & MMARK){
1576 if(!(mp->m_flag & MNEWEST))
1577 a_msg_unmark((int)PTR2SIZE(mp - message + 1));
1578 else
1579 ++mc;
1581 if(mc == 0){
1582 mc = -1;
1583 goto jleave;
1587 if(mb.mb_threaded == 0){
1588 for(mp = message; mp < &message[msgCount]; ++mp)
1589 if(mp->m_flag & MMARK)
1590 *ip++ = (int)PTR2SIZE(mp - message + 1);
1591 }else{
1592 for(mp = threadroot; mp != NULL; mp = next_in_thread(mp))
1593 if(mp->m_flag & MMARK)
1594 *ip++ = (int)PTR2SIZE(mp - message + 1);
1596 *ip = 0;
1597 mc = (int)PTR2SIZE(ip - vector);
1598 if(mc != 1)
1599 n_pstate &= ~n_PS_MSGLIST_DIRECT;
1600 jleave:
1601 NYD_LEAVE;
1602 return mc;
1605 FL int
1606 first(int f, int m)
1608 struct message *mp;
1609 int rv;
1610 NYD_ENTER;
1612 if (msgCount == 0) {
1613 rv = 0;
1614 goto jleave;
1617 f &= MDELETED;
1618 m &= MDELETED;
1619 for (mp = dot;
1620 mb.mb_threaded ? (mp != NULL) : PTRCMP(mp, <, message + msgCount);
1621 mb.mb_threaded ? (mp = next_in_thread(mp)) : ++mp) {
1622 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & m) == (ui32_t)f) {
1623 rv = (int)PTR2SIZE(mp - message + 1);
1624 goto jleave;
1628 if (dot > message) {
1629 for (mp = dot - 1; (mb.mb_threaded ? (mp != NULL) : (mp >= message));
1630 mb.mb_threaded ? (mp = prev_in_thread(mp)) : --mp) {
1631 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & m) == (ui32_t)f) {
1632 rv = (int)PTR2SIZE(mp - message + 1);
1633 goto jleave;
1637 rv = 0;
1638 jleave:
1639 NYD_LEAVE;
1640 return rv;
1643 FL void
1644 mark(int mno, int f){
1645 struct message *mp;
1646 int i;
1647 NYD_ENTER;
1649 i = mno;
1650 if(i < 1 || i > msgCount)
1651 n_panic(_("Bad message number to mark"));
1652 mp = &message[--i];
1654 if(mb.mb_threaded == 1 && a_msg_threadflag)
1655 a_msg__threadmark(mp, f);
1656 else{
1657 assert(!(mp->m_flag & MHIDDEN));
1658 mp->m_flag |= MMARK;
1660 NYD_LEAVE;
1663 /* s-it-mode */