a_go_evaluate(): fix un/signed comparison
[s-mailx.git] / message.c
blobd63394a9155cbfa8a2c852c54f65c82bbb9284f9
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 */
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 struct a_message_speclex{
96 char *msl_str; /* If parsed a string */
97 int msl_no; /* If parsed a number TODO siz_t! */
98 char msl__smallstrbuf[4];
99 /* We directly adjust pointer in .ca_arg.ca_str.s, do not adjust .l */
100 struct n_cmd_arg *msl_cap;
101 char const *msl_input_orig;
104 static struct a_message_coltab const a_message_coltabs[] = {
105 {'n', {0,}, a_MESSAGE_S_NEW, MNEW, MNEW},
106 {'o', {0,}, a_MESSAGE_S_OLD, MNEW, 0},
107 {'u', {0,}, a_MESSAGE_S_UNREAD, MREAD, 0},
108 {'d', {0,}, a_MESSAGE_S_DELETED, MDELETED, MDELETED},
109 {'r', {0,}, a_MESSAGE_S_READ, MREAD, MREAD},
110 {'f', {0,}, a_MESSAGE_S_FLAG, MFLAGGED, MFLAGGED},
111 {'a', {0,}, a_MESSAGE_S_ANSWERED, MANSWERED, MANSWERED},
112 {'t', {0,}, a_MESSAGE_S_DRAFT, MDRAFTED, MDRAFTED},
113 {'s', {0,}, a_MESSAGE_S_SPAM, MSPAM, MSPAM},
114 {'S', {0,}, a_MESSAGE_S_SPAMUNSURE, MSPAMUNSURE, MSPAMUNSURE},
115 /* These have no per-message flags, but must be evaluated */
116 {'l', {0,}, a_MESSAGE_S_MLIST, 0, 0},
117 {'L', {0,}, a_MESSAGE_S_MLSUBSCRIBE, 0, 0},
120 static struct a_message_lex const a_message_singles[] = {
121 {'$', a_MESSAGE_T_DOLLAR},
122 {'.', a_MESSAGE_T_DOT},
123 {'^', a_MESSAGE_T_UP},
124 {'*', a_MESSAGE_T_ASTER},
125 {'-', a_MESSAGE_T_MINUS},
126 {'+', a_MESSAGE_T_PLUS},
127 {'(', a_MESSAGE_T_OPEN},
128 {')', a_MESSAGE_T_CLOSE},
129 {',', a_MESSAGE_T_COMMA},
130 {';', a_MESSAGE_T_SEMI},
131 {'`', a_MESSAGE_T_BACK}
134 /* Slots in ::message */
135 static size_t a_message_mem_space;
137 /* Mark entire threads */
138 static bool_t a_message_threadflag;
140 /* :d on its way HACK TODO */
141 static bool_t a_message_list_saw_d, a_message_list_last_saw_d;
143 /* Lazy load message header fields */
144 static enum okay a_message_get_header(struct message *mp);
146 /* Append, taking care of resizes TODO vector */
147 static char **a_message_add_to_nmadat(char ***nmadat, size_t *nmasize,
148 char **np, char *string);
150 /* Mark all messages that the user wanted from the command line in the message
151 * structure. Return 0 on success, -1 on error */
152 static int a_message_markall(char const *orig, struct n_cmd_arg *cap, int f);
154 /* Turn the character after a colon modifier into a bit value */
155 static int a_message_evalcol(int col);
157 /* Check the passed message number for legality and proper flags. Unless f is
158 * MDELETED the message has to be undeleted */
159 static bool_t a_message_check(int mno, int f);
161 /* Scan out a single lexical item and return its token number, updating *mslp */
162 static int a_message_scan(struct a_message_speclex *mslp);
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_nmadat(char ***nmadat, size_t *nmasize, /* TODO Vector */
224 char **np, char *string){
225 size_t idx, i;
226 NYD2_ENTER;
228 if((idx = PTR2SIZE(np - *nmadat)) >= *nmasize){
229 char **narr;
231 i = *nmasize << 1;
232 *nmasize = i;
233 narr = n_autorec_alloc(i * sizeof *np);
234 memcpy(narr, *nmadat, i >>= 1);
235 *nmadat = narr;
236 np = &narr[idx];
238 *np++ = string;
239 NYD2_LEAVE;
240 return np;
243 static int
244 a_message_markall(char const *orig, struct n_cmd_arg *cap, int f){
245 struct a_message_speclex msl;
246 enum a_message_idfield idfield;
247 size_t j, nmasize;
248 char const *id;
249 char **nmadat_lofi, **nmadat, **np, **nq, *cp;
250 struct message *mp, *mx;
251 int i, valdot, beg, colmod, tok, colresult;
252 enum{
253 a_NONE = 0,
254 a_ALLNET = 1u<<0, /* (CTA()d to be == TRU1 */
255 a_ALLOC = 1u<<1, /* Have allocated something */
256 a_THREADED = 1u<<2,
257 a_ERROR = 1u<<3,
258 a_ANY = 1u<<4, /* Have marked just ANY */
259 a_RANGE = 1u<<5, /* Seen dash, await close */
260 a_ASTER = 1u<<8,
261 a_TOPEN = 1u<<9, /* ( used (and didn't match) */
262 a_TBACK = 1u<<10, /* ` used (and didn't match) */
263 #ifdef HAVE_IMAP
264 a_HAVE_IMAP_HEADERS = 1u<<14,
265 #endif
266 a_LOG = 1u<<29, /* Log errors */
267 a_TMP = 1u<<30
268 } flags;
269 NYD_ENTER;
270 n_LCTA((ui32_t)a_ALLNET == (ui32_t)TRU1,
271 "Constant is converted to bool_t via AND, thus");
273 /* Update message array: clear MMARK but remember its former state for ` */
274 for(i = msgCount; i-- > 0;){
275 enum mflag mf;
277 mf = (mp = &message[i])->m_flag;
278 if(mf & MMARK)
279 mf |= MOLDMARK;
280 else
281 mf &= ~MOLDMARK;
282 mf &= ~MMARK;
283 mp->m_flag = mf;
286 memset(&msl, 0, sizeof msl);
287 msl.msl_cap = cap;
288 msl.msl_input_orig = orig;
290 np = nmadat =
291 nmadat_lofi = n_lofi_alloc((nmasize = 64) * sizeof *np); /* TODO vector */
292 n_UNINIT(beg, 0);
293 n_UNINIT(idfield, a_MESSAGE_ID_REFERENCES);
294 a_message_threadflag = FAL0;
295 valdot = (int)PTR2SIZE(dot - message + 1);
296 colmod = 0;
297 id = NULL;
298 flags = a_ALLOC | (mb.mb_threaded ? a_THREADED : 0) |
299 ((!(n_pstate & n_PS_HOOK_MASK) || (n_poption & n_PO_D_V))
300 ? a_LOG : 0);
302 while((tok = a_message_scan(&msl)) != a_MESSAGE_T_EOL){
303 if((a_message_threadflag = (tok < 0)))
304 tok &= INT_MAX;
306 switch(tok){
307 case a_MESSAGE_T_NUMBER:
308 n_pstate |= n_PS_MSGLIST_GABBY;
309 jnumber:
310 if(!a_message_check(msl.msl_no, f))
311 goto jerr;
313 if(flags & a_RANGE){
314 flags ^= a_RANGE;
316 if(!(flags & a_THREADED)){
317 if(beg < msl.msl_no)
318 i = beg;
319 else{
320 i = msl.msl_no;
321 msl.msl_no = beg;
324 for(; i <= msl.msl_no; ++i){
325 mp = &message[i - 1];
326 if(!(mp->m_flag & MHIDDEN) &&
327 (f == MDELETED || !(mp->m_flag & MDELETED))){
328 mark(i, f);
329 flags |= a_ANY;
332 }else{
333 /* TODO threaded ranges are a mess */
334 enum{
335 a_T_NONE,
336 a_T_HOT = 1u<<0,
337 a_T_DIR_PREV = 1u<<1
338 } tf;
339 int i_base;
341 if(beg < msl.msl_no)
342 i = beg;
343 else{
344 i = msl.msl_no;
345 msl.msl_no = beg;
348 i_base = i;
349 tf = a_T_NONE;
350 jnumber__thr:
351 for(;;){
352 mp = &message[i - 1];
353 if(!(mp->m_flag & MHIDDEN) &&
354 (f == MDELETED || !(mp->m_flag & MDELETED))){
355 if(tf & a_T_HOT){
356 mark(i, f);
357 flags |= a_ANY;
361 /* We may have reached the endpoint. If we were still
362 * detecting the direction to search for it, restart.
363 * Otherwise finished */
364 if(i == msl.msl_no){ /* XXX */
365 if(!(tf & a_T_HOT)){
366 tf |= a_T_HOT;
367 i = i_base;
368 goto jnumber__thr;
370 break;
373 mx = (tf & a_T_DIR_PREV) ? prev_in_thread(mp)
374 : next_in_thread(mp);
375 if(mx == NULL){
376 /* We anyway have failed to reach the endpoint in this
377 * direction; if we already switched that, report error */
378 if(!(tf & a_T_DIR_PREV)){
379 tf |= a_T_DIR_PREV;
380 i = i_base;
381 goto jnumber__thr;
383 id = N_("Range crosses multiple threads\n");
384 goto jerrmsg;
386 i = (int)PTR2SIZE(mx - message + 1);
390 beg = 0;
391 }else{
392 /* Could be an inclusive range? */
393 if(msl.msl_cap != NULL &&
394 msl.msl_cap->ca_arg.ca_str.s[0] == '-'){
395 if(*++msl.msl_cap->ca_arg.ca_str.s == '\0')
396 msl.msl_cap = msl.msl_cap->ca_next;
397 beg = msl.msl_no;
398 flags |= a_RANGE;
399 }else{
400 mark(msl.msl_no, f);
401 flags |= a_ANY;
404 break;
405 case a_MESSAGE_T_PLUS:
406 n_pstate &= ~n_PS_MSGLIST_DIRECT;
407 n_pstate |= n_PS_MSGLIST_GABBY;
408 i = valdot;
410 if(flags & a_THREADED){
411 mx = next_in_thread(&message[i - 1]);
412 i = mx ? (int)PTR2SIZE(mx - message + 1) : msgCount + 1;
413 }else
414 ++i;
415 if(i > msgCount){
416 id = N_("Referencing beyond last message\n");
417 goto jerrmsg;
419 }while(message[i - 1].m_flag == MHIDDEN ||
420 (message[i - 1].m_flag & MDELETED) != (unsigned)f);
421 msl.msl_no = i;
422 goto jnumber;
423 case a_MESSAGE_T_MINUS:
424 n_pstate &= ~n_PS_MSGLIST_DIRECT;
425 n_pstate |= n_PS_MSGLIST_GABBY;
426 i = valdot;
428 if(flags & a_THREADED){
429 mx = prev_in_thread(&message[i - 1]);
430 i = mx ? (int)PTR2SIZE(mx - message + 1) : 0;
431 }else
432 --i;
433 if(i <= 0){
434 id = N_("Referencing before first message\n");
435 goto jerrmsg;
437 }while(message[i - 1].m_flag == MHIDDEN ||
438 (message[i - 1].m_flag & MDELETED) != (unsigned)f);
439 msl.msl_no = i;
440 goto jnumber;
441 case a_MESSAGE_T_STRING:
442 n_pstate &= ~n_PS_MSGLIST_DIRECT;
443 if(flags & a_RANGE)
444 goto jebadrange;
446 /* This may be a colon modifier */
447 if((cp = msl.msl_str)[0] != ':')
448 np = a_message_add_to_nmadat(&nmadat, &nmasize, np,
449 savestr(msl.msl_str));
450 else{
451 while(*++cp != '\0'){
452 colresult = a_message_evalcol(*cp);
453 if(colresult == 0){
454 if(flags & a_LOG)
455 n_err(_("Unknown colon modifier: %s\n"), msl.msl_str);
456 goto jerr;
458 if(colresult == a_MESSAGE_S_DELETED){
459 a_message_list_saw_d = TRU1;
460 f |= MDELETED;
462 colmod |= colresult;
465 break;
466 case a_MESSAGE_T_OPEN:
467 n_pstate &= ~n_PS_MSGLIST_DIRECT;
468 if(flags & a_RANGE)
469 goto jebadrange;
470 flags |= a_TOPEN;
472 #ifdef HAVE_IMAP_SEARCH
473 /* C99 */{
474 ssize_t ires;
476 if((ires = imap_search(msl.msl_str, f)) >= 0){
477 if(ires > 0)
478 flags |= a_ANY;
479 break;
482 #else
483 if(flags & a_LOG)
484 n_err(_("Optional selector not available: %s\n"), msl.msl_str);
485 #endif
486 goto jerr;
487 case a_MESSAGE_T_DOLLAR:
488 case a_MESSAGE_T_UP:
489 case a_MESSAGE_T_SEMI:
490 n_pstate |= n_PS_MSGLIST_GABBY;
491 /* FALLTHRU */
492 case a_MESSAGE_T_DOT: /* Don't set _GABBY for dot to allow history.. */
493 n_pstate &= ~n_PS_MSGLIST_DIRECT;
494 if((msl.msl_no = a_message_metamess(msl.msl_str[0], f)) == -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(msl.msl_cap != NULL && msl.msl_cap->ca_arg.ca_str.s[0] == '-' &&
569 !(flags & a_RANGE)){
570 if(flags & a_LOG)
571 n_err(_("Ignoring invalid range in: %s\n"), msl.msl_input_orig);
572 if(*++msl.msl_cap->ca_arg.ca_str.s == '\0')
573 msl.msl_cap = msl.msl_cap->ca_next;
576 if(flags & a_RANGE){
577 id = N_("Missing second range argument\n");
578 goto jerrmsg;
581 np = a_message_add_to_nmadat(&nmadat, &nmasize, np, NULL);
582 --np;
584 /* * is special at this point, after we have parsed the entire line */
585 if(flags & a_ASTER){
586 for(i = 0; i < msgCount; ++i){
587 if((mp = &message[i])->m_flag & MHIDDEN)
588 continue;
589 if(!a_message_list_saw_d && (mp->m_flag & MDELETED) != (unsigned)f)
590 continue;
591 mark(i + 1, f);
592 flags |= a_ANY;
594 if(!(flags & a_ANY))
595 goto jenoapp;
596 goto jleave;
599 /* If any names were given, add any messages which match */
600 if(np > nmadat || id != NULL){
601 struct search_expr *sep;
603 sep = NULL;
605 /* The @ search works with struct search_expr, so build an array.
606 * To simplify array, i.e., regex_t destruction, and optimize for the
607 * common case we walk the entire array even in case of errors */
608 /* XXX Like many other things around here: this should be outsourced */
609 if(np > nmadat){
610 j = PTR2SIZE(np - nmadat) * sizeof(*sep);
611 sep = n_lofi_alloc(j);
612 memset(sep, 0, j);
614 for(j = 0, nq = nmadat; *nq != NULL; ++j, ++nq){
615 char *xsave, *x, *y;
617 sep[j].ss_body = x = xsave = *nq;
618 if(*x != '@' || (flags & a_ERROR))
619 continue;
621 /* Cramp the namelist */
622 for(y = &x[1];; ++y){
623 if(*y == '\0'){
624 x = NULL;
625 break;
627 if(*y == '@'){
628 x = y;
629 break;
632 if(x == NULL || &x[-1] == xsave)
633 jat_where_default:
634 sep[j].ss_field = "subject";
635 else{
636 ++xsave;
637 if(*xsave == '~'){
638 sep[j].ss_skin = TRU1;
639 if(++xsave >= x){
640 if(flags & a_LOG)
641 n_err(_("[@..]@ search expression: no namelist, "
642 "only \"~\" skin indicator\n"));
643 flags |= a_ERROR;
644 continue;
647 cp = savestrbuf(xsave, PTR2SIZE(x - xsave));
649 /* Namelist could be a regular expression, too */
650 #ifdef HAVE_REGEX
651 if(n_is_maybe_regex(cp)){
652 int s;
654 assert(sep[j].ss_field == NULL);
655 if((s = regcomp(&sep[j].ss__fieldre_buf, cp,
656 REG_EXTENDED | REG_ICASE | REG_NOSUB)) != 0){
657 if(flags & a_LOG)
658 n_err(_("Invalid regular expression: %s: %s\n"),
659 n_shexp_quote_cp(cp, FAL0),
660 n_regex_err_to_doc(NULL, s));
661 flags |= a_ERROR;
662 continue;
664 sep[j].ss_fieldre = &sep[j].ss__fieldre_buf;
665 }else
666 #endif
668 struct str sio;
670 /* Because of the special cases we need to trim explicitly
671 * here, they are not covered by n_strsep() */
672 sio.s = cp;
673 sio.l = PTR2SIZE(x - xsave);
674 if(*(cp = n_str_trim(&sio, n_STR_TRIM_BOTH)->s) == '\0')
675 goto jat_where_default;
676 sep[j].ss_field = cp;
680 /* The actual search expression. If it is empty we only test the
681 * field(s) for existence */
682 x = &(x == NULL ? *nq : x)[1];
683 if(*x == '\0'){
684 sep[j].ss_field_exists = TRU1;
685 #ifdef HAVE_REGEX
686 }else if(n_is_maybe_regex(x)){
687 int s;
689 sep[j].ss_body = NULL;
690 if((s = regcomp(&sep[j].ss__bodyre_buf, x,
691 REG_EXTENDED | REG_ICASE | REG_NOSUB)) != 0){
692 if(flags & a_LOG)
693 n_err(_("Invalid regular expression: %s: %s\n"),
694 n_shexp_quote_cp(x, FAL0),
695 n_regex_err_to_doc(NULL, s));
696 flags |= a_ERROR;
697 continue;
699 sep[j].ss_bodyre = &sep[j].ss__bodyre_buf;
700 #endif
701 }else
702 sep[j].ss_body = x;
704 if(flags & a_ERROR)
705 goto jnamesearch_sepfree;
708 /* Iterate the entire message array */
709 #ifdef HAVE_IMAP
710 if(!(flags & a_HAVE_IMAP_HEADERS) && mb.mb_type == MB_IMAP){
711 flags |= a_HAVE_IMAP_HEADERS;
712 imap_getheaders(1, msgCount);
714 #endif
715 if(ok_blook(allnet))
716 flags |= a_ALLNET;
717 n_autorec_relax_create();
718 for(i = 0; i < msgCount; ++i){
719 if((mp = &message[i])->m_flag & (MMARK | MHIDDEN))
720 continue;
721 if(!a_message_list_saw_d && (mp->m_flag & MDELETED) != (unsigned)f)
722 continue;
724 flags &= ~a_TMP;
725 if(np > nmadat){
726 for(nq = nmadat; *nq != NULL; ++nq){
727 if(**nq == '@'){
728 if(a_message_match_at(mp, &sep[PTR2SIZE(nq - nmadat)])){
729 flags |= a_TMP;
730 break;
732 }else if(**nq == '/'){
733 if(a_message_match_dash(mp, *nq)){
734 flags |= a_TMP;
735 break;
737 }else if(a_message_match_sender(mp, *nq, (flags & a_ALLNET))){
738 flags |= a_TMP;
739 break;
743 if(!(flags & a_TMP) &&
744 id != NULL && a_message_match_mid(mp, id, idfield))
745 flags |= a_TMP;
747 if(flags & a_TMP){
748 mark(i + 1, f);
749 flags |= a_ANY;
751 n_autorec_relax_unroll();
753 n_autorec_relax_gut();
755 jnamesearch_sepfree:
756 if(sep != NULL){
757 #ifdef HAVE_REGEX
758 for(j = PTR2SIZE(np - nmadat); j-- != 0;){
759 if(sep[j].ss_fieldre != NULL)
760 regfree(sep[j].ss_fieldre);
761 if(sep[j].ss_bodyre != NULL)
762 regfree(sep[j].ss_bodyre);
764 #endif
765 n_lofi_free(sep);
767 if(flags & a_ERROR)
768 goto jerr;
771 /* If any colon modifiers were given, go through and mark any messages which
772 * do satisfy the modifiers */
773 if(colmod != 0){
774 for(i = 0; i < msgCount; ++i){
775 struct a_message_coltab const *colp;
777 if((mp = &message[i])->m_flag & (MMARK | MHIDDEN))
778 continue;
779 if(!a_message_list_saw_d && (mp->m_flag & MDELETED) != (unsigned)f)
780 continue;
782 for(colp = a_message_coltabs;
783 PTRCMP(colp, <, &a_message_coltabs[n_NELEM(a_message_coltabs)]);
784 ++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_MESSAGE_S_MLIST |
789 a_MESSAGE_S_MLSUBSCRIBE)){
790 enum mlist_state what;
792 what = (colp->mco_bit & a_MESSAGE_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_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(struct a_message_speclex *mslp){
872 struct a_message_lex const *lp;
873 char *cp, c;
874 int rv;
875 NYD_ENTER;
877 rv = a_MESSAGE_T_EOL;
879 /* Empty cap's even for IGNORE_EMPTY (quoted empty tokens produce output) */
880 for(;; mslp->msl_cap = mslp->msl_cap->ca_next){
881 if(mslp->msl_cap == NULL)
882 goto jleave;
884 cp = mslp->msl_cap->ca_arg.ca_str.s;
885 if((c = *cp++) != '\0')
886 break;
889 /* Select members of a message thread */
890 if(c == '&'){
891 c = *cp;
892 if(c == '\0' || spacechar(c)){
893 mslp->msl_str = mslp->msl__smallstrbuf;
894 mslp->msl_str[0] = '.';
895 mslp->msl_str[1] = '\0';
896 if(c == '\0')
897 mslp->msl_cap = mslp->msl_cap->ca_next;
898 else{
899 jshexp_err:
900 n_err(_("Message list: invalid syntax: %s (in %s)\n"),
901 n_shexp_quote_cp(cp, FAL0),
902 n_shexp_quote_cp(mslp->msl_input_orig, FAL0));
903 rv = a_MESSAGE_T_ERROR;
904 goto jleave;
906 rv = a_MESSAGE_T_DOT | INT_MIN;
907 goto jleave;
909 rv = INT_MIN;
910 ++cp;
913 /* If the leading character is a digit, scan the number and convert it
914 * on the fly. Return a_MESSAGE_T_NUMBER when done */
915 if(digitchar(c)){
916 mslp->msl_no = 0;
918 mslp->msl_no = (mslp->msl_no * 10) + c - '0'; /* XXX inline atoi */
919 while((c = *cp++, digitchar(c)));
921 if(c == '\0')
922 mslp->msl_cap = mslp->msl_cap->ca_next;
923 else{
924 --cp;
925 /* This could be a range */
926 if(c == '-')
927 mslp->msl_cap->ca_arg.ca_str.s = cp;
928 else
929 goto jshexp_err;
931 rv |= a_MESSAGE_T_NUMBER;
932 goto jleave;
935 /* An IMAP SEARCH list. Note that a_MESSAGE_T_OPEN has always been included
936 * in singles[] in Mail and mailx. Thus although there is no formal
937 * definition for (LIST) lists, they do not collide with historical
938 * practice because a subject string (LIST) could never been matched
939 * this way */
940 if (c == '(') {
941 bool_t inquote;
942 ui32_t level;
943 char *tocp;
945 (tocp = mslp->msl_str = mslp->msl_cap->ca_arg.ca_str.s)[0] = '(';
946 ++tocp;
947 level = 1;
948 inquote = FAL0;
949 do {
950 if ((c = *cp++) == '\0') {
951 jmtop:
952 n_err(_("Missing )\n"));
953 n_err(_("P.S.: message specifications are now shell tokens, "
954 "making it necessary to enclose IMAP search expressions "
955 "in (single) quotes, e.g., '(from \"me\")'\n"));
956 rv = a_MESSAGE_T_ERROR;
957 goto jleave;
959 if (inquote && c == '\\') {
960 *tocp++ = c;
961 c = *cp++;
962 if (c == '\0')
963 goto jmtop;
964 } else if (c == '"')
965 inquote = !inquote;
966 else if (inquote)
967 /*EMPTY*/;
968 else if (c == '(')
969 ++level;
970 else if (c == ')')
971 --level;
972 else if (spacechar(c)) {
973 /* Replace unquoted whitespace by single space characters, to make
974 * the string IMAP SEARCH conformant */
975 c = ' ';
976 if (tocp[-1] == ' ')
977 --tocp;
979 *tocp++ = c;
980 } while (c != ')' || level > 0);
981 *tocp = '\0';
982 if(*cp != '\0')
983 goto jshexp_err;
984 mslp->msl_cap = mslp->msl_cap->ca_next;
985 rv |= a_MESSAGE_T_OPEN;
986 goto jleave;
989 /* Check for single character tokens; return such if found */
990 for(lp = a_message_singles;
991 PTRCMP(lp, <, &a_message_singles[n_NELEM(a_message_singles)]); ++lp){
992 if(c == lp->ml_char){
993 mslp->msl_str = mslp->msl__smallstrbuf;
994 mslp->msl_str[0] = c;
995 mslp->msl_str[1] = '\0';
996 if(*cp != '\0')
997 goto jshexp_err;
998 mslp->msl_cap = mslp->msl_cap->ca_next;
999 rv = lp->ml_token;
1000 goto jleave;
1004 mslp->msl_cap = mslp->msl_cap->ca_next;
1005 mslp->msl_str = --cp;
1006 rv = a_MESSAGE_T_STRING;
1007 jleave:
1008 NYD_LEAVE;
1009 return rv;
1012 static bool_t
1013 a_message_match_sender(struct message *mp, char const *str, bool_t allnet){
1014 char const *str_base, *np_base, *np;
1015 char sc, nc;
1016 struct name *namep;
1017 bool_t rv;
1018 NYD2_ENTER;
1020 rv = FAL0;
1022 /* Empty string doesn't match */
1023 namep = lextract(n_header_senderfield_of(mp), GFULL | GSKIN);
1025 if(namep == NULL || *(str_base = str) == '\0')
1026 goto jleave;
1028 /* *allnet* is POSIX and, since it explicitly mentions login and user names,
1029 * most likely case-sensitive. XXX Still allow substr matching, though
1030 * XXX possibly the first letter should be case-insensitive, then? */
1031 if(allnet){
1032 for(; namep != NULL; str = str_base, namep = namep->n_flink){
1033 for(np_base = np = namep->n_name;;){
1034 if((sc = *str++) == '@')
1035 sc = '\0';
1036 if((nc = *np++) == '@' || nc == '\0' || sc == '\0'){
1037 if((rv = (sc == '\0')))
1038 goto jleave;
1039 break;
1041 if(sc != nc){
1042 np = ++np_base;
1043 str = str_base;
1047 }else{
1048 /* TODO POSIX says ~"match any address as shown in header overview",
1049 * TODO but a normalized match would be more sane i guess.
1050 * TODO struct name should gain a comparison method, normalize realname
1051 * TODO content (in TODO) and thus match as likewise
1052 * TODO "Buddy (Today) <here>" and "(Now) Buddy <here>" */
1053 bool_t again_base, again;
1055 again_base = ok_blook(showname);
1057 for(; namep != NULL; str = str_base, namep = namep->n_flink){
1058 again = again_base;
1059 jagain:
1060 np_base = np = again ? namep->n_fullname : namep->n_name;
1061 str = str_base;
1062 for(;;){
1063 sc = *str++;
1064 if((nc = *np++) == '\0' || sc == '\0'){
1065 if((rv = (sc == '\0')))
1066 goto jleave;
1067 break;
1069 sc = upperconv(sc);
1070 nc = upperconv(nc);
1071 if(sc != nc){
1072 np = ++np_base;
1073 str = str_base;
1077 /* And really if i want to match 'on@' then i want it to match even if
1078 * *showname* is set! */
1079 if(again){
1080 again = FAL0;
1081 goto jagain;
1085 jleave:
1086 NYD2_LEAVE;
1087 return rv;
1090 static bool_t
1091 a_message_match_mid(struct message *mp, char const *id,
1092 enum a_message_idfield idfield){
1093 char const *cp;
1094 bool_t rv;
1095 NYD2_ENTER;
1097 rv = FAL0;
1099 if((cp = hfield1("message-id", mp)) != NULL){
1100 switch(idfield){
1101 case a_MESSAGE_ID_REFERENCES:
1102 if(!msgidcmp(id, cp))
1103 rv = TRU1;
1104 break;
1105 case a_MESSAGE_ID_IN_REPLY_TO:{
1106 struct name *np;
1108 if((np = extract(id, GREF)) != NULL)
1110 if(!msgidcmp(np->n_name, cp)){
1111 rv = TRU1;
1112 break;
1114 }while((np = np->n_flink) != NULL);
1115 break;
1119 NYD2_LEAVE;
1120 return rv;
1123 static bool_t
1124 a_message_match_dash(struct message *mp, char const *str){
1125 static char lastscan[128];
1127 struct str in, out;
1128 char *hfield, *hbody;
1129 bool_t rv;
1130 NYD2_ENTER;
1132 rv = FAL0;
1134 if(*++str == '\0')
1135 str = lastscan;
1136 else
1137 n_strscpy(lastscan, str, sizeof lastscan); /* XXX use new n_str object! */
1139 /* Now look, ignoring case, for the word in the string */
1140 if(ok_blook(searchheaders) && (hfield = strchr(str, ':'))){
1141 size_t l;
1143 l = PTR2SIZE(hfield - str);
1144 hfield = n_lofi_alloc(l +1);
1145 memcpy(hfield, str, l);
1146 hfield[l] = '\0';
1147 hbody = hfieldX(hfield, mp);
1148 n_lofi_free(hfield);
1149 hfield = n_UNCONST(str + l + 1);
1150 }else{
1151 hfield = n_UNCONST(str);
1152 hbody = hfield1("subject", mp);
1154 if(hbody == NULL)
1155 goto jleave;
1157 in.l = strlen(in.s = hbody);
1158 mime_fromhdr(&in, &out, TD_ICONV);
1159 rv = substr(out.s, hfield);
1160 n_free(out.s);
1161 jleave:
1162 NYD2_LEAVE;
1163 return rv;
1166 static bool_t
1167 a_message_match_at(struct message *mp, struct search_expr *sep){
1168 char const *field;
1169 bool_t rv;
1170 NYD2_ENTER;
1172 /* Namelist regex only matches headers.
1173 * And there are the special cases header/<, "body"/> and "text"/=, the
1174 * latter two of which need to be handled in here */
1175 if((field = sep->ss_field) != NULL){
1176 if(!asccasecmp(field, "body") || (field[1] == '\0' && field[0] == '>')){
1177 rv = FAL0;
1178 jmsg:
1179 rv = message_match(mp, sep, rv);
1180 goto jleave;
1181 }else if(!asccasecmp(field, "text") ||
1182 (field[1] == '\0' && field[0] == '=')){
1183 rv = TRU1;
1184 goto jmsg;
1188 rv = n_header_match(mp, sep);
1189 jleave:
1190 NYD2_LEAVE;
1191 return rv;
1194 static void
1195 a_message_unmark(int mesg){
1196 size_t i;
1197 NYD2_ENTER;
1199 i = (size_t)mesg;
1200 if(i < 1 || UICMP(z, i, >, msgCount))
1201 n_panic(_("Bad message number to unmark"));
1202 message[--i].m_flag &= ~MMARK;
1203 NYD2_LEAVE;
1206 static int
1207 a_message_metamess(int meta, int f)
1209 int c, m;
1210 struct message *mp;
1211 NYD2_ENTER;
1213 c = meta;
1214 switch (c) {
1215 case '^': /* First 'good' message left */
1216 mp = mb.mb_threaded ? threadroot : message;
1217 while (PTRCMP(mp, <, message + msgCount)) {
1218 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & MDELETED) == (ui32_t)f) {
1219 c = (int)PTR2SIZE(mp - message + 1);
1220 goto jleave;
1222 if (mb.mb_threaded) {
1223 mp = next_in_thread(mp);
1224 if (mp == NULL)
1225 break;
1226 } else
1227 ++mp;
1229 if (!(n_pstate & n_PS_HOOK_MASK))
1230 n_err(_("No applicable messages\n"));
1231 goto jem1;
1233 case '$': /* Last 'good message left */
1234 mp = mb.mb_threaded
1235 ? this_in_thread(threadroot, -1) : message + msgCount - 1;
1236 while (mp >= message) {
1237 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & MDELETED) == (ui32_t)f) {
1238 c = (int)PTR2SIZE(mp - message + 1);
1239 goto jleave;
1241 if (mb.mb_threaded) {
1242 mp = prev_in_thread(mp);
1243 if (mp == NULL)
1244 break;
1245 } else
1246 --mp;
1248 if (!(n_pstate & n_PS_HOOK_MASK))
1249 n_err(_("No applicable messages\n"));
1250 goto jem1;
1252 case '.':
1253 /* Current message */
1254 m = dot - message + 1;
1255 if ((dot->m_flag & MHIDDEN) || (dot->m_flag & MDELETED) != (ui32_t)f) {
1256 n_err(_("%d: inappropriate message\n"), m);
1257 goto jem1;
1259 c = m;
1260 break;
1262 case ';':
1263 /* Previously current message */
1264 if (prevdot == NULL) {
1265 n_err(_("No previously current message\n"));
1266 goto jem1;
1268 m = prevdot - message + 1;
1269 if ((prevdot->m_flag & MHIDDEN) ||
1270 (prevdot->m_flag & MDELETED) != (ui32_t)f) {
1271 n_err(_("%d: inappropriate message\n"), m);
1272 goto jem1;
1274 c = m;
1275 break;
1277 default:
1278 n_err(_("Unknown selector: %c\n"), c);
1279 goto jem1;
1281 jleave:
1282 NYD2_LEAVE;
1283 return c;
1284 jem1:
1285 c = -1;
1286 goto jleave;
1289 static void
1290 a_message__threadmark(struct message *self, int f){
1291 NYD2_ENTER;
1292 if(!(self->m_flag & MHIDDEN) &&
1293 (f == MDELETED || !(self->m_flag & MDELETED) || a_message_list_saw_d))
1294 self->m_flag |= MMARK;
1296 if((self = self->m_child) != NULL){
1297 goto jcall;
1298 while((self = self->m_younger) != NULL)
1299 if(self->m_child != NULL)
1300 jcall:
1301 a_message__threadmark(self, f);
1302 else
1303 self->m_flag |= MMARK;
1305 NYD2_LEAVE;
1308 FL FILE *
1309 setinput(struct mailbox *mp, struct message *m, enum needspec need){
1310 enum okay ok;
1311 FILE *rv;
1312 NYD_ENTER;
1314 rv = NULL;
1316 switch(need){
1317 case NEED_HEADER:
1318 ok = (m->m_content_info & CI_HAVE_HEADER) ? OKAY
1319 : a_message_get_header(m);
1320 break;
1321 case NEED_BODY:
1322 ok = (m->m_content_info & CI_HAVE_BODY) ? OKAY : get_body(m);
1323 break;
1324 default:
1325 case NEED_UNSPEC:
1326 ok = OKAY;
1327 break;
1329 if(ok != OKAY)
1330 goto jleave;
1332 fflush(mp->mb_otf);
1333 if(fseek(mp->mb_itf, (long)mailx_positionof(m->m_block, m->m_offset),
1334 SEEK_SET) == -1){
1335 n_perr(_("fseek"), 0);
1336 n_panic(_("temporary file seek"));
1338 rv = mp->mb_itf;
1339 jleave:
1340 NYD_LEAVE;
1341 return rv;
1344 FL enum okay
1345 get_body(struct message *mp){
1346 enum okay rv;
1347 NYD_ENTER;
1348 n_UNUSED(mp);
1350 switch(mb.mb_type){
1351 case MB_FILE:
1352 case MB_MAILDIR:
1353 rv = OKAY;
1354 break;
1355 #ifdef HAVE_POP3
1356 case MB_POP3:
1357 rv = pop3_body(mp);
1358 break;
1359 #endif
1360 #ifdef HAVE_IMAP
1361 case MB_IMAP:
1362 case MB_CACHE:
1363 rv = imap_body(mp);
1364 break;
1365 #endif
1366 case MB_VOID:
1367 default:
1368 rv = STOP;
1369 break;
1371 NYD_LEAVE;
1372 return rv;
1375 FL void
1376 message_reset(void){
1377 NYD_ENTER;
1378 if(message != NULL){
1379 n_free(message);
1380 message = NULL;
1382 msgCount = 0;
1383 a_message_mem_space = 0;
1384 NYD_LEAVE;
1387 FL void
1388 message_append(struct message *mp){
1389 NYD_ENTER;
1390 if(UICMP(z, msgCount + 1, >=, a_message_mem_space)){
1391 /* XXX remove _mem_space magics (or use s_Vector) */
1392 a_message_mem_space = ((a_message_mem_space >= 128 &&
1393 a_message_mem_space <= 1000000)
1394 ? a_message_mem_space << 1 : a_message_mem_space + 64);
1395 message = n_realloc(message, a_message_mem_space * sizeof(*message));
1397 if(msgCount > 0){
1398 if(mp != NULL)
1399 message[msgCount - 1] = *mp;
1400 else
1401 memset(&message[msgCount - 1], 0, sizeof *message);
1403 NYD_LEAVE;
1406 FL void
1407 message_append_null(void){
1408 NYD_ENTER;
1409 if(msgCount == 0)
1410 message_append(NULL);
1411 setdot(message);
1412 message[msgCount].m_size = 0;
1413 message[msgCount].m_lines = 0;
1414 NYD_LEAVE;
1417 FL bool_t
1418 message_match(struct message *mp, struct search_expr const *sep,
1419 bool_t with_headers){
1420 char **line;
1421 size_t *linesize, cnt;
1422 FILE *fp;
1423 bool_t rv;
1424 NYD_ENTER;
1426 rv = FAL0;
1428 if((fp = Ftmp(NULL, "mpmatch", OF_RDWR | OF_UNLINK | OF_REGISTER)) == NULL)
1429 goto j_leave;
1431 if(sendmp(mp, fp, NULL, NULL, SEND_TOSRCH, NULL) < 0)
1432 goto jleave;
1433 fflush_rewind(fp);
1435 cnt = fsize(fp);
1436 line = &termios_state.ts_linebuf; /* XXX line pool */
1437 linesize = &termios_state.ts_linesize; /* XXX line pool */
1439 if(!with_headers)
1440 while(fgetline(line, linesize, &cnt, NULL, fp, 0))
1441 if(**line == '\n')
1442 break;
1444 while(fgetline(line, linesize, &cnt, NULL, fp, 0)){
1445 #ifdef HAVE_REGEX
1446 if(sep->ss_bodyre != NULL){
1447 if(regexec(sep->ss_bodyre, *line, 0,NULL, 0) == REG_NOMATCH)
1448 continue;
1449 }else
1450 #endif
1451 if(!substr(*line, sep->ss_body))
1452 continue;
1453 rv = TRU1;
1454 break;
1457 jleave:
1458 Fclose(fp);
1459 j_leave:
1460 NYD_LEAVE;
1461 return rv;
1464 FL struct message *
1465 setdot(struct message *mp){
1466 NYD_ENTER;
1467 if(dot != mp){
1468 prevdot = dot;
1469 n_pstate &= ~n_PS_DID_PRINT_DOT;
1471 dot = mp;
1472 uncollapse1(dot, 0);
1473 NYD_LEAVE;
1474 return dot;
1477 FL void
1478 touch(struct message *mp){
1479 NYD_ENTER;
1480 mp->m_flag |= MTOUCH;
1481 if(!(mp->m_flag & MREAD))
1482 mp->m_flag |= MREAD | MSTATUS;
1483 NYD_LEAVE;
1486 FL int
1487 n_getmsglist(char const *buf, int *vector, int flags,
1488 struct n_cmd_arg **capp_or_null)
1490 int *ip, mc;
1491 struct message *mp;
1492 NYD_ENTER;
1494 n_pstate &= ~n_PS_ARGLIST_MASK;
1495 n_pstate |= n_PS_MSGLIST_DIRECT;
1496 a_message_list_last_saw_d = a_message_list_saw_d;
1497 a_message_list_saw_d = FAL0;
1499 *vector = 0;
1500 if(capp_or_null != NULL)
1501 *capp_or_null = NULL;
1502 if(*buf == '\0'){
1503 mc = 0;
1504 goto jleave;
1507 /* TODO Parse the message spec into an ARGV; this should not happen here,
1508 * TODO but instead cmd_arg_parse() should feed in the list of parsed tokens
1509 * TODO to getmsglist(); as of today there are multiple getmsglist() users
1510 * TODO though, and they need to deal with that, then, too */
1511 /* C99 */{
1512 n_CMD_ARG_DESC_SUBCLASS_DEF(getmsglist, 1, pseudo_cad){
1513 {n_CMD_ARG_DESC_SHEXP | n_CMD_ARG_DESC_OPTION |
1514 n_CMD_ARG_DESC_GREEDY | n_CMD_ARG_DESC_HONOUR_STOP,
1515 n_SHEXP_PARSE_TRIM_IFSSPACE | n_SHEXP_PARSE_IFS_VAR |
1516 n_SHEXP_PARSE_IGNORE_EMPTY}
1517 }n_CMD_ARG_DESC_SUBCLASS_DEF_END;
1518 struct n_cmd_arg_ctx cac;
1520 cac.cac_desc = n_CMD_ARG_DESC_SUBCLASS_CAST(&pseudo_cad);
1521 cac.cac_indat = buf;
1522 cac.cac_inlen = UIZ_MAX;
1523 cac.cac_msgflag = flags;
1524 cac.cac_msgmask = 0;
1525 if(!n_cmd_arg_parse(&cac)){
1526 mc = -1;
1527 goto jleave;
1528 }else if(cac.cac_no == 0){
1529 mc = 0;
1530 goto jleave;
1531 }else{
1532 /* Is this indeed a (maybe optional) message list and a target? */
1533 if(capp_or_null != NULL){
1534 struct n_cmd_arg *cap, **lcapp;
1536 if((cap = cac.cac_arg)->ca_next == NULL){
1537 *capp_or_null = cap;
1538 mc = 0;
1539 goto jleave;
1541 for(;;){
1542 lcapp = &cap->ca_next;
1543 if((cap = *lcapp)->ca_next == NULL)
1544 break;
1546 *capp_or_null = cap;
1547 *lcapp = NULL;
1549 /* In the list-and-target mode we have to take special care, since
1550 * some commands use special call conventions historically (use the
1551 * MBOX, search for a message, whatever).
1552 * Thus, to allow things like "certsave '' bla" or "save '' ''",
1553 * watch out for two argument form with empty token first.
1554 * This special case is documented at the prototype */
1555 if(cac.cac_arg->ca_next == NULL &&
1556 cac.cac_arg->ca_arg.ca_str.s[0] == '\0'){
1557 mc = 0;
1558 goto jleave;
1562 if(msgCount == 0){
1563 mc = 0;
1564 goto jleave;
1565 }else if((mc = a_message_markall(buf, cac.cac_arg, flags)) < 0){
1566 mc = -1;
1567 goto jleave;
1572 ip = vector;
1573 if(n_pstate & n_PS_HOOK_NEWMAIL){
1574 mc = 0;
1575 for(mp = message; mp < &message[msgCount]; ++mp)
1576 if(mp->m_flag & MMARK){
1577 if(!(mp->m_flag & MNEWEST))
1578 a_message_unmark((int)PTR2SIZE(mp - message + 1));
1579 else
1580 ++mc;
1582 if(mc == 0){
1583 mc = -1;
1584 goto jleave;
1588 if(mb.mb_threaded == 0){
1589 for(mp = message; mp < &message[msgCount]; ++mp)
1590 if(mp->m_flag & MMARK)
1591 *ip++ = (int)PTR2SIZE(mp - message + 1);
1592 }else{
1593 for(mp = threadroot; mp != NULL; mp = next_in_thread(mp))
1594 if(mp->m_flag & MMARK)
1595 *ip++ = (int)PTR2SIZE(mp - message + 1);
1597 *ip = 0;
1598 mc = (int)PTR2SIZE(ip - vector);
1599 if(mc != 1)
1600 n_pstate &= ~n_PS_MSGLIST_DIRECT;
1601 jleave:
1602 NYD_LEAVE;
1603 return mc;
1606 FL int
1607 first(int f, int m)
1609 struct message *mp;
1610 int rv;
1611 NYD_ENTER;
1613 if (msgCount == 0) {
1614 rv = 0;
1615 goto jleave;
1618 f &= MDELETED;
1619 m &= MDELETED;
1620 for (mp = dot;
1621 mb.mb_threaded ? (mp != NULL) : PTRCMP(mp, <, message + msgCount);
1622 mb.mb_threaded ? (mp = next_in_thread(mp)) : ++mp) {
1623 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & m) == (ui32_t)f) {
1624 rv = (int)PTR2SIZE(mp - message + 1);
1625 goto jleave;
1629 if (dot > message) {
1630 for (mp = dot - 1; (mb.mb_threaded ? (mp != NULL) : (mp >= message));
1631 mb.mb_threaded ? (mp = prev_in_thread(mp)) : --mp) {
1632 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & m) == (ui32_t)f) {
1633 rv = (int)PTR2SIZE(mp - message + 1);
1634 goto jleave;
1638 rv = 0;
1639 jleave:
1640 NYD_LEAVE;
1641 return rv;
1644 FL void
1645 mark(int mno, int f){
1646 struct message *mp;
1647 int i;
1648 NYD_ENTER;
1650 i = mno;
1651 if(i < 1 || i > msgCount)
1652 n_panic(_("Bad message number to mark"));
1653 mp = &message[--i];
1655 if(mb.mb_threaded == 1 && a_message_threadflag)
1656 a_message__threadmark(mp, f);
1657 else{
1658 assert(!(mp->m_flag & MHIDDEN));
1659 mp->m_flag |= MMARK;
1661 NYD_LEAVE;
1664 /* s-it-mode */