(Pseudo) Fix history (non gabbiness) by stripping PS_ARGLIST_MASK
[s-mailx.git] / message.c
blobcebc3df639a3d0402f1a2cd20fa6aa20342408f0
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Message, message array, getmsglist(), and related operations.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2016 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
6 */
7 /*
8 * Copyright (c) 1980, 1993
9 * The Regents of the University of California. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. Neither the name of the University nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
35 #undef n_FILE
36 #define n_FILE message
38 #ifndef HAVE_AMALGAMATION
39 # include "nail.h"
40 #endif
42 /* Token values returned by the scanner used for argument lists.
43 * Also, sizes of scanner-related things */
44 enum ltoken {
45 TEOL = 0, /* End of the command line */
46 TNUMBER = 1, /* A message number */
47 TDASH = 2, /* A simple dash */
48 TSTRING = 3, /* A string (possibly containing -) */
49 TDOT = 4, /* A "." */
50 TUP = 5, /* A "^" */
51 TDOLLAR = 6, /* A "$" */
52 TSTAR = 7, /* A "*" */
53 TOPEN = 8, /* A '(' */
54 TCLOSE = 9, /* A ')' */
55 TPLUS = 10, /* A '+' */
56 TERROR = 11, /* A lexical error */
57 TCOMMA = 12, /* A ',' */
58 TSEMI = 13, /* A ';' */
59 TBACK = 14 /* A '`' */
62 #define REGDEP 2 /* Maximum regret depth. */
64 enum idfield {
65 ID_REFERENCES,
66 ID_IN_REPLY_TO
69 enum {
70 CMNEW = 1<<0, /* New messages */
71 CMOLD = 1<<1, /* Old messages */
72 CMUNREAD = 1<<2, /* Unread messages */
73 CMDELETED =1<<3, /* Deleted messages */
74 CMREAD = 1<<4, /* Read messages */
75 CMFLAG = 1<<5, /* Flagged messages */
76 CMANSWER = 1<<6, /* Answered messages */
77 CMDRAFT = 1<<7, /* Draft messages */
78 CMSPAM = 1<<8, /* Spam messages */
79 CMSPAMUN = 1<<9 /* Maybe spam messages (unsure) */
82 struct coltab {
83 char co_char; /* What to find past : */
84 int co_bit; /* Associated modifier bit */
85 int co_mask; /* m_status bits to mask */
86 int co_equal; /* ... must equal this */
89 struct lex {
90 char l_char;
91 enum ltoken l_token;
94 static struct coltab const _coltab[] = {
95 { 'n', CMNEW, MNEW, MNEW },
96 { 'o', CMOLD, MNEW, 0 },
97 { 'u', CMUNREAD, MREAD, 0 },
98 { 'd', CMDELETED, MDELETED, MDELETED },
99 { 'r', CMREAD, MREAD, MREAD },
100 { 'f', CMFLAG, MFLAGGED, MFLAGGED },
101 { 'a', CMANSWER, MANSWERED, MANSWERED },
102 { 't', CMDRAFT, MDRAFTED, MDRAFTED },
103 { 's', CMSPAM, MSPAM, MSPAM },
104 { 'S', CMSPAMUN, MSPAMUNSURE, MSPAMUNSURE },
105 { '\0', 0, 0, 0 }
108 static struct lex const _singles[] = {
109 { '$', TDOLLAR },
110 { '.', TDOT },
111 { '^', TUP },
112 { '*', TSTAR },
113 { '-', TDASH },
114 { '+', TPLUS },
115 { '(', TOPEN },
116 { ')', TCLOSE },
117 { ',', TCOMMA },
118 { ';', TSEMI },
119 { '`', TBACK },
120 { '\0', 0 }
123 /* Slots in ::message */
124 static size_t _message_space;
126 static bool_t _list_saw_d, _list_last_saw_d; /* :d on its way HACK TODO */
127 static size_t STRINGLEN;
128 static int lexnumber; /* Number of TNUMBER from scan() */
129 static char *lexstring; /* String from TSTRING, scan() */
130 static int regretp; /* Pointer to TOS of regret tokens */
131 static int regretstack[REGDEP]; /* Stack of regretted tokens */
132 static char *string_stack[REGDEP]; /* Stack of regretted strings */
133 static int numberstack[REGDEP]; /* Stack of regretted numbers */
134 static int threadflag; /* mark entire threads */
136 static enum okay get_header(struct message *mp);
138 /* Append, taking care of resizes */
139 static char ** add_to_namelist(char ***namelist, size_t *nmlsize,
140 char **np, char *string);
142 /* Mark all messages that the user wanted from the command line in the message
143 * structure. Return 0 on success, -1 on error */
144 static int markall(char *buf, int f);
146 /* Turn the character after a colon modifier into a bit value */
147 static int evalcol(int col);
149 /* Check the passed message number for legality and proper flags. If f is
150 * MDELETED, then either kind will do. Otherwise, the message has to be
151 * undeleted */
152 static int check(int mesg, int f);
154 /* Scan out a single lexical item and return its token number, updating the
155 * string pointer passed **sp. Also, store the value of the number or string
156 * scanned in lexnumber or lexstring as appropriate. In any event, store the
157 * scanned "thing" in lexstring */
158 static int scan(char **sp);
160 /* Unscan the named token by pushing it onto the regret stack */
161 static void regret(int token);
163 /* Reset all the scanner global variables */
164 static void scaninit(void);
166 /* See if the passed name sent the passed message */
167 static bool_t _matchsender(struct message *mp, char const *str, bool_t allnet);
169 static bool_t _matchmid(struct message *mp, char *id, enum idfield idfield);
171 /* See if the given string matches.
172 * For the purpose of the scan, we ignore case differences.
173 * This is the engine behind the "/" search */
174 static bool_t _match_dash(struct message *mp, char const *str);
176 /* See if the given search expression matches.
177 * For the purpose of the scan, we ignore case differences.
178 * This is the engine behind the "@[..]@" search */
179 static bool_t _match_at(struct message *mp, struct search_expr *sep);
181 /* Unmark the named message */
182 static void unmark(int mesg);
184 /* Return the message number corresponding to the passed meta character */
185 static int metamess(int meta, int f);
187 static enum okay
188 get_header(struct message *mp)
190 enum okay rv;
191 NYD_ENTER;
192 UNUSED(mp);
194 switch (mb.mb_type) {
195 case MB_FILE:
196 case MB_MAILDIR:
197 rv = OKAY;
198 break;
199 #ifdef HAVE_POP3
200 case MB_POP3:
201 rv = pop3_header(mp);
202 break;
203 #endif
204 case MB_VOID:
205 default:
206 rv = STOP;
207 break;
209 NYD_LEAVE;
210 return rv;
213 static char **
214 add_to_namelist(char ***namelist, size_t *nmlsize, char **np, char *string)
216 size_t idx;
217 NYD_ENTER;
219 if ((idx = PTR2SIZE(np - *namelist)) >= *nmlsize) {
220 *namelist = srealloc(*namelist, (*nmlsize += 8) * sizeof *np);
221 np = *namelist + idx;
223 *np++ = string;
224 NYD_LEAVE;
225 return np;
228 static int
229 markall(char *buf, int f)
231 #define markall_ret(i) do { rv = i; goto jleave; } while (0);
233 /* TODO use a bit carrier for all the states */
234 char **np, **nq, **namelist, *bufp, *id = NULL, *cp;
235 int rv = 0, i, tok, beg, other, valdot, colmod, colresult;
236 struct message *mp, *mx;
237 bool_t mc, star, topen, tback;
238 size_t j, nmlsize;
239 enum idfield idfield = ID_REFERENCES;
240 NYD_ENTER;
242 lexstring = ac_alloc(STRINGLEN = 2 * strlen(buf) +1);
243 valdot = (int)PTR2SIZE(dot - message + 1);
244 colmod = 0;
246 for (i = 1; i <= msgCount; ++i) {
247 enum mflag mf;
249 mf = message[i - 1].m_flag;
250 if (mf & MMARK)
251 mf |= MOLDMARK;
252 else
253 mf &= ~MOLDMARK;
254 mf &= ~MMARK;
255 message[i - 1].m_flag = mf;
258 np = namelist = smalloc((nmlsize = 8) * sizeof *namelist);
259 scaninit();
260 bufp = buf;
261 mc = FAL0;
262 beg = star = other = topen = tback = FAL0;
264 for (tok = scan(&bufp); tok != TEOL;) {
265 switch (tok) {
266 case TNUMBER:
267 number:
268 if (star) {
269 n_err(_("No numbers mixed with *\n"));
270 markall_ret(-1)
272 pstate |= PS_MSGLIST_SAW_NO;
273 ++other;
274 if (beg != 0) {
275 if (check(lexnumber, f))
276 markall_ret(-1)
277 i = beg;
278 while (mb.mb_threaded ? 1 : i <= lexnumber) {
279 if (!(message[i - 1].m_flag & MHIDDEN) &&
280 (f == MDELETED || !(message[i - 1].m_flag & MDELETED)))
281 mark(i, f);
282 if (mb.mb_threaded) {
283 if (i == lexnumber)
284 break;
285 mx = next_in_thread(&message[i - 1]);
286 if (mx == NULL)
287 markall_ret(-1)
288 i = (int)PTR2SIZE(mx - message + 1);
289 } else
290 ++i;
292 beg = 0;
293 break;
295 beg = lexnumber;
296 if (check(beg, f))
297 markall_ret(-1)
298 tok = scan(&bufp);
299 regret(tok);
300 if (tok != TDASH) {
301 mark(beg, f);
302 beg = 0;
304 break;
306 case TPLUS:
307 pstate &= ~PS_MSGLIST_DIRECT;
308 if (beg != 0) {
309 printf(_("Non-numeric second argument\n"));
310 markall_ret(-1)
312 i = valdot;
313 do {
314 if (mb.mb_threaded) {
315 mx = next_in_thread(message + i - 1);
316 i = mx ? (int)PTR2SIZE(mx - message + 1) : msgCount + 1;
317 } else
318 ++i;
319 if (i > msgCount) {
320 n_err(_("Referencing beyond EOF\n"));
321 markall_ret(-1)
323 } while (message[i - 1].m_flag == MHIDDEN ||
324 (message[i - 1].m_flag & MDELETED) != (unsigned)f);
325 mark(i, f);
326 break;
328 case TDASH:
329 pstate &= ~PS_MSGLIST_DIRECT;
330 if (beg == 0) {
331 i = valdot;
332 do {
333 if (mb.mb_threaded) {
334 mx = prev_in_thread(message + i - 1);
335 i = mx ? (int)PTR2SIZE(mx - message + 1) : 0;
336 } else
337 --i;
338 if (i <= 0) {
339 n_err(_("Referencing before 1\n"));
340 markall_ret(-1)
342 } while ((message[i - 1].m_flag & MHIDDEN) ||
343 (message[i - 1].m_flag & MDELETED) != (unsigned)f);
344 mark(i, f);
346 break;
348 case TSTRING:
349 pstate &= ~PS_MSGLIST_DIRECT;
350 if (beg != 0) {
351 n_err(_("Non-numeric second argument\n"));
352 markall_ret(-1)
354 ++other;
355 if ((cp = lexstring)[0] == ':') {
356 while (*++cp != '\0') {
357 colresult = evalcol(*cp);
358 if (colresult == 0) {
359 n_err(_("Unknown colon modifier: %s\n"), lexstring);
360 markall_ret(-1)
362 if (colresult == CMDELETED) {
363 _list_saw_d = TRU1;
364 f |= MDELETED;
366 colmod |= colresult;
368 } else
369 np = add_to_namelist(&namelist, &nmlsize, np, savestr(lexstring));
370 break;
372 case TOPEN:
373 #ifdef HAVE_IMAP_SEARCH
374 pstate &= ~PS_MSGLIST_DIRECT;
375 if (imap_search(lexstring, f) == STOP)
376 markall_ret(-1)
377 topen = TRU1;
378 #else
379 n_err(_("Optional selector is not available: %s\n"),
380 lexstring);
381 markall_ret(-1)
382 #endif
383 break;
385 case TDOLLAR:
386 case TUP:
387 case TDOT:
388 case TSEMI:
389 pstate &= ~PS_MSGLIST_DIRECT;
390 lexnumber = metamess(lexstring[0], f);
391 if (lexnumber == -1)
392 markall_ret(-1)
393 goto number;
395 case TBACK:
396 pstate &= ~PS_MSGLIST_DIRECT;
397 tback = TRU1;
398 for (i = 1; i <= msgCount; i++) {
399 struct message const *mp_t = message + i - 1;
401 if (mp_t->m_flag & MHIDDEN)
402 continue;
403 if ((mp_t->m_flag & MDELETED) != (unsigned)f) {
404 if (!_list_last_saw_d)
405 continue;
406 _list_saw_d = TRU1;
408 if (mp_t->m_flag & MOLDMARK)
409 mark(i, f);
411 break;
413 case TSTAR:
414 pstate &= ~PS_MSGLIST_DIRECT;
415 if (other) {
416 n_err(_("Can't mix * with anything\n"));
417 markall_ret(-1)
419 star = TRU1;
420 break;
422 case TCOMMA:
423 pstate &= ~PS_MSGLIST_DIRECT;
424 if (id == NULL && (cp = hfield1("in-reply-to", dot)) != NULL) {
425 id = savestr(cp);
426 idfield = ID_IN_REPLY_TO;
428 if (id == NULL && (cp = hfield1("references", dot)) != NULL) {
429 struct name *enp;
431 if ((enp = extract(cp, GREF)) != NULL) {
432 while (enp->n_flink != NULL)
433 enp = enp->n_flink;
434 id = savestr(enp->n_name);
435 idfield = ID_REFERENCES;
438 if (id == NULL) {
439 printf(_(
440 "Cannot determine parent Message-ID of the current message\n"));
441 markall_ret(-1)
443 break;
445 case TERROR:
446 pstate &= ~PS_MSGLIST_DIRECT;
447 pstate |= PS_MSGLIST_SAW_NO;
448 markall_ret(-1)
450 threadflag = 0;
451 tok = scan(&bufp);
454 np = add_to_namelist(&namelist, &nmlsize, np, NULL);
455 --np;
456 mc = FAL0;
457 if (star) {
458 for (i = 0; i < msgCount; ++i) {
459 struct message const *mp_t = message + i;
461 if (mp_t->m_flag & MHIDDEN)
462 continue;
463 if (!_list_saw_d && (mp_t->m_flag & MDELETED) != (unsigned)f)
464 continue;
465 mark(i + 1, f);
466 mc = TRU1;
469 if (!mc) {
470 if (!(pstate & PS_HOOK_MASK))
471 printf(_("No applicable messages.\n"));
472 markall_ret(-1)
474 markall_ret(0)
477 if ((topen || tback) && !mc) {
478 for (i = 0; i < msgCount; ++i)
479 if (message[i].m_flag & MMARK)
480 mc = TRU1;
481 if (!mc) {
482 if (!(pstate & PS_HOOK_MASK)) {
483 if (tback)
484 n_err(_("No previously marked messages\n"));
485 else
486 printf(_("No messages satisfy (criteria)\n"));
488 markall_ret(-1)
492 /* If no numbers were given, mark all messages, so that we can unmark
493 * any whose sender was not selected if any user names were given */
494 if ((np > namelist || colmod != 0 || id) && !mc) {
495 for (i = 1; i <= msgCount; ++i) {
496 if (!(message[i - 1].m_flag & MHIDDEN) &&
497 (message[i - 1].m_flag & MDELETED) == (unsigned)f)
498 mark(i, f);
502 /* If any names were given, eliminate any messages which don't match */
503 if (np > namelist || id) {
504 struct search_expr *sep = NULL;
505 bool_t allnet;
507 /* The @ search works with struct search_expr, so build an array.
508 * To simplify array, i.e., regex_t destruction, and optimize for the
509 * common case we walk the entire array even in case of errors */
510 if (np > namelist) {
511 sep = scalloc(PTR2SIZE(np - namelist), sizeof(*sep));
512 for (j = 0, nq = namelist; *nq != NULL; ++j, ++nq) {
513 char *x = *nq, *y;
515 sep[j].ss_sexpr = x;
516 if (*x != '@' || rv < 0)
517 continue;
519 for (y = x + 1;; ++y) {
520 if (*y == '\0' || !fieldnamechar(*y)) {
521 x = NULL;
522 break;
524 if (*y == '@') {
525 x = y;
526 break;
529 sep[j].ss_where = (x == NULL || x - 1 == *nq)
530 ? "subject" : savestrbuf(*nq + 1, PTR2SIZE(x - *nq) - 1);
532 x = (x == NULL ? *nq : x) + 1;
533 if (*x == '\0') { /* XXX Simply remove from list instead? */
534 n_err(_("Empty [@..]@ search expression\n"));
535 rv = -1;
536 continue;
538 #ifdef HAVE_REGEX
539 if (is_maybe_regex(x)) {
540 sep[j].ss_sexpr = NULL;
541 if (regcomp(&sep[j].ss_regex, x,
542 REG_EXTENDED | REG_ICASE | REG_NOSUB) != 0) {
543 n_err(_("Invalid regular expression: >>> %s <<<\n"), x);
544 rv = -1;
545 continue;
547 } else
548 #endif
549 sep[j].ss_sexpr = x;
551 if (rv < 0)
552 goto jnamesearch_sepfree;
555 srelax_hold();
556 allnet = ok_blook(allnet);
557 for (i = 1; i <= msgCount; ++i) {
558 mp = message + i - 1;
559 j = 0;
560 if (np > namelist) {
561 for (nq = namelist; *nq != NULL; ++nq) {
562 if (**nq == '@') {
563 if (_match_at(mp, sep + PTR2SIZE(nq - namelist))) {
564 ++j;
565 break;
567 } else if (**nq == '/') {
568 if (_match_dash(mp, *nq)) {
569 ++j;
570 break;
572 } else if (_matchsender(mp, *nq, allnet)) {
573 ++j;
574 break;
578 if (j == 0 && id && _matchmid(mp, id, idfield))
579 ++j;
580 if (j == 0)
581 mp->m_flag &= ~MMARK;
582 srelax();
584 srelax_rele();
586 /* Make sure we got some decent messages */
587 j = 0;
588 for (i = 1; i <= msgCount; ++i)
589 if (message[i - 1].m_flag & MMARK) {
590 ++j;
591 break;
593 if (j == 0) {
594 if (!(pstate & PS_HOOK_MASK) && np > namelist) {
595 printf(_("No applicable messages from {%s"), namelist[0]);
596 for (nq = namelist + 1; *nq != NULL; ++nq)
597 printf(_(", %s"), *nq);
598 printf(_("}\n"));
599 } else if (id)
600 printf(_("Parent message not found\n"));
601 rv = -1;
602 goto jnamesearch_sepfree;
605 jnamesearch_sepfree:
606 if (sep != NULL) {
607 #ifdef HAVE_REGEX
608 for (j = PTR2SIZE(np - namelist); j-- != 0;)
609 if (sep[j].ss_sexpr == NULL)
610 regfree(&sep[j].ss_regex);
611 #endif
612 free(sep);
614 if (rv < 0)
615 goto jleave;
618 /* If any colon modifiers were given, go through and unmark any
619 * messages which do not satisfy the modifiers */
620 if (colmod != 0) {
621 for (i = 1; i <= msgCount; ++i) {
622 struct coltab const *colp;
623 bool_t bad = TRU1;
625 mp = message + i - 1;
626 for (colp = _coltab; colp->co_char != '\0'; ++colp)
627 if ((colp->co_bit & colmod) &&
628 ((mp->m_flag & colp->co_mask) == (unsigned)colp->co_equal)) {
629 bad = FAL0;
630 break;
632 if (bad)
633 unmark(i);
636 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
637 if (mp->m_flag & MMARK)
638 break;
640 if (PTRCMP(mp, >=, message + msgCount)) {
641 struct coltab const *colp;
643 if (!(pstate & PS_HOOK_MASK)) {
644 printf(_("No messages satisfy"));
645 for (colp = _coltab; colp->co_char != '\0'; ++colp)
646 if (colp->co_bit & colmod)
647 printf(" :%c", colp->co_char);
648 printf("\n");
650 markall_ret(-1)
654 markall_ret(0)
655 jleave:
656 free(namelist);
657 ac_free(lexstring);
658 NYD_LEAVE;
659 return rv;
661 #undef markall_ret
664 static int
665 evalcol(int col)
667 struct coltab const *colp;
668 int rv;
669 NYD_ENTER;
671 rv = 0;
672 for (colp = _coltab; colp->co_char != '\0'; ++colp)
673 if (colp->co_char == col) {
674 rv = colp->co_bit;
675 break;
677 NYD_LEAVE;
678 return rv;
681 static int
682 check(int mesg, int f)
684 struct message *mp;
685 NYD_ENTER;
687 if (mesg < 1 || mesg > msgCount) {
688 printf(_("%d: Invalid message number\n"), mesg);
689 goto jem1;
691 mp = message + mesg - 1;
692 if (mp->m_flag & MHIDDEN ||
693 (f != MDELETED && (mp->m_flag & MDELETED) != 0)) {
694 n_err(_("%d: inappropriate message\n"), mesg);
695 goto jem1;
697 f = 0;
698 jleave:
699 NYD_LEAVE;
700 return f;
701 jem1:
702 f = -1;
703 goto jleave;
706 static int
707 scan(char **sp)
709 char *cp, *cp2;
710 int rv, c, inquote, quotec;
711 struct lex const *lp;
712 NYD_ENTER;
714 if (regretp >= 0) {
715 strncpy(lexstring, string_stack[regretp], STRINGLEN);
716 lexstring[STRINGLEN -1] = '\0';
717 lexnumber = numberstack[regretp];
718 rv = regretstack[regretp--];
719 goto jleave;
722 cp = *sp;
723 cp2 = lexstring;
724 c = *cp++;
726 /* strip away leading white space */
727 while (blankchar(c))
728 c = *cp++;
730 /* If no characters remain, we are at end of line, so report that */
731 if (c == '\0') {
732 *sp = --cp;
733 rv = TEOL;
734 goto jleave;
737 /* Select members of a message thread */
738 if (c == '&') {
739 threadflag = 1;
740 if (*cp == '\0' || spacechar(*cp)) {
741 lexstring[0] = '.';
742 lexstring[1] = '\0';
743 *sp = cp;
744 rv = TDOT;
745 goto jleave;
747 c = *cp++;
750 /* If the leading character is a digit, scan the number and convert it
751 * on the fly. Return TNUMBER when done */
752 if (digitchar(c)) {
753 lexnumber = 0;
754 while (digitchar(c)) {
755 lexnumber = lexnumber*10 + c - '0';
756 *cp2++ = c;
757 c = *cp++;
759 *cp2 = '\0';
760 *sp = --cp;
761 rv = TNUMBER;
762 goto jleave;
765 /* An IMAP SEARCH list. Note that TOPEN has always been included in
766 * singles[] in Mail and mailx. Thus although there is no formal
767 * definition for (LIST) lists, they do not collide with historical
768 * practice because a subject string (LIST) could never been matched
769 * this way */
770 if (c == '(') {
771 ui32_t level = 1;
772 inquote = 0;
773 *cp2++ = c;
774 do {
775 if ((c = *cp++&0377) == '\0') {
776 jmtop:
777 n_err(_("Missing )\n"));
778 rv = TERROR;
779 goto jleave;
781 if (inquote && c == '\\') {
782 *cp2++ = c;
783 c = *cp++&0377;
784 if (c == '\0')
785 goto jmtop;
786 } else if (c == '"')
787 inquote = !inquote;
788 else if (inquote)
789 /*EMPTY*/;
790 else if (c == '(')
791 ++level;
792 else if (c == ')')
793 --level;
794 else if (spacechar(c)) {
795 /* Replace unquoted whitespace by single space characters, to make
796 * the string IMAP SEARCH conformant */
797 c = ' ';
798 if (cp2[-1] == ' ')
799 --cp2;
801 *cp2++ = c;
802 } while (c != ')' || level > 0);
803 *cp2 = '\0';
804 *sp = cp;
805 rv = TOPEN;
806 goto jleave;
809 /* Check for single character tokens; return such if found */
810 for (lp = _singles; lp->l_char != '\0'; ++lp)
811 if (c == lp->l_char) {
812 lexstring[0] = c;
813 lexstring[1] = '\0';
814 *sp = cp;
815 rv = lp->l_token;
816 goto jleave;
819 /* We've got a string! Copy all the characters of the string into
820 * lexstring, until we see a null, space, or tab. If the lead character is
821 * a " or ', save it and scan until you get another */
822 quotec = 0;
823 if (c == '\'' || c == '"') {
824 quotec = c;
825 c = *cp++;
827 while (c != '\0') {
828 if (quotec == 0 && c == '\\' && *cp != '\0')
829 c = *cp++;
830 if (c == quotec) {
831 ++cp;
832 break;
834 if (quotec == 0 && blankchar(c))
835 break;
836 if (PTRCMP(cp2 - lexstring, <, STRINGLEN - 1))
837 *cp2++ = c;
838 c = *cp++;
840 if (quotec && c == 0) {
841 n_err(_("Missing %c\n"), quotec);
842 rv = TERROR;
843 goto jleave;
845 *sp = --cp;
846 *cp2 = '\0';
847 rv = TSTRING;
848 jleave:
849 NYD_LEAVE;
850 return rv;
853 static void
854 regret(int token)
856 NYD_ENTER;
857 if (++regretp >= REGDEP)
858 n_panic(_("Too many regrets"));
859 regretstack[regretp] = token;
860 lexstring[STRINGLEN -1] = '\0';
861 string_stack[regretp] = savestr(lexstring);
862 numberstack[regretp] = lexnumber;
863 NYD_LEAVE;
866 static void
867 scaninit(void)
869 NYD_ENTER;
870 regretp = -1;
871 threadflag = 0;
872 NYD_LEAVE;
875 static bool_t
876 _matchsender(struct message *mp, char const *str, bool_t allnet)
878 char const *str_base, *np_base, *np;
879 char sc, nc;
880 bool_t rv;
881 NYD_ENTER;
883 /* Empty string doesn't match */
884 if (*(str_base = str) == '\0') {
885 rv = FAL0;
886 goto jleave;
889 /* *allnet* is POSIX and, since it explicitly mentions login and user names,
890 * most likely case-sensitive. XXX Still allow substr matching, though
891 * XXX possibly the first letter should be case-insensitive, then? */
892 if (allnet) {
893 np_base = np = nameof(mp, 0);
894 for (;;) {
895 if ((sc = *str++) == '@')
896 sc = '\0';
897 if ((nc = *np++) == '@' || nc == '\0' || sc == '\0')
898 break;
899 if (sc != nc) {
900 np = ++np_base;
901 str = str_base;
904 rv = (sc == '\0');
905 } else {
906 char const *real_base = name1(mp, 0);
907 bool_t again = ok_blook(showname);
909 /* TODO POSIX says ~"match any address as shown in header overview",
910 * TODO but a normalized match would be more sane i guess.
911 * TODO struct name should gain a comparison method, normalize realname
912 * TODO content (in TODO) and thus match as likewise
913 * TODO "Buddy (Today) <here>" and "(Now) Buddy <here>" */
914 jagain:
915 np_base = np = again ? realname(real_base) : skin(real_base);
916 for (;;) {
917 sc = *str++;
918 if ((nc = *np++) == '\0' || sc == '\0')
919 break;
920 sc = upperconv(sc);
921 nc = upperconv(nc);
922 if (sc != nc) {
923 np = ++np_base;
924 str = str_base;
928 /* And really if i want to match 'on@' then i want it to match even if
929 * *showname* is set! */
930 if (!(rv = (sc == '\0')) && again) {
931 again = FAL0;
932 goto jagain;
935 jleave:
936 NYD_LEAVE;
937 return rv;
940 static bool_t
941 _matchmid(struct message *mp, char *id, enum idfield idfield)
943 char *cp;
944 bool_t rv;
945 NYD_ENTER;
947 if ((cp = hfield1("message-id", mp)) != NULL) {
948 switch (idfield) {
949 case ID_REFERENCES:
950 rv = !msgidcmp(id, cp);
951 goto jleave;
952 case ID_IN_REPLY_TO: {
953 struct name *np;
955 if ((np = extract(id, GREF)) != NULL)
956 do {
957 if (!msgidcmp(np->n_name, cp)) {
958 rv = TRU1;
959 goto jleave;
961 } while ((np = np->n_flink) != NULL);
962 break;
966 rv = FAL0;
967 jleave:
968 NYD_LEAVE;
969 return rv;
972 static bool_t
973 _match_dash(struct message *mp, char const *str)
975 static char lastscan[128];
977 struct str in, out;
978 char *hfield, *hbody;
979 bool_t rv;
980 NYD_ENTER;
982 if (*++str == '\0') {
983 str = lastscan;
984 } else {
985 strncpy(lastscan, str, sizeof lastscan); /* XXX use new n_str object! */
986 lastscan[sizeof lastscan -1] = '\0';
989 /* Now look, ignoring case, for the word in the string */
990 if (ok_blook(searchheaders) && (hfield = strchr(str, ':'))) {
991 size_t l = PTR2SIZE(hfield - str);
992 hfield = ac_alloc(l +1);
993 memcpy(hfield, str, l);
994 hfield[l] = '\0';
995 hbody = hfieldX(hfield, mp);
996 ac_free(hfield);
997 hfield = UNCONST(str + l + 1);
998 } else {
999 hfield = UNCONST(str);
1000 hbody = hfield1("subject", mp);
1002 if (hbody == NULL) {
1003 rv = FAL0;
1004 goto jleave;
1007 in.s = hbody;
1008 in.l = strlen(hbody);
1009 mime_fromhdr(&in, &out, TD_ICONV);
1010 rv = substr(out.s, hfield);
1011 free(out.s);
1012 jleave:
1013 NYD_LEAVE;
1014 return rv;
1017 static bool_t
1018 _match_at(struct message *mp, struct search_expr *sep)
1020 struct str in, out;
1021 char *nfield;
1022 char const *cfield;
1023 bool_t rv = FAL0;
1024 NYD_ENTER;
1026 nfield = savestr(sep->ss_where);
1028 while ((cfield = n_strsep(&nfield, ',', TRU1)) != NULL) {
1029 if (!asccasecmp(cfield, "body") ||
1030 (cfield[1] == '\0' && cfield[0] == '>')) {
1031 rv = FAL0;
1032 jmsg:
1033 if ((rv = message_match(mp, sep, rv)))
1034 break;
1035 continue;
1037 if (!asccasecmp(cfield, "text") ||
1038 (cfield[1] == '\0' && cfield[0] == '=')) {
1039 rv = TRU1;
1040 goto jmsg;
1043 if (!asccasecmp(cfield, "header") ||
1044 (cfield[1] == '\0' && cfield[0] == '<')) {
1045 if ((rv = header_match(mp, sep)))
1046 break;
1047 continue;
1050 /* This is not a special name, so take care for the "skin" prefix !
1051 * and possible abbreviations */
1053 struct name *np;
1054 bool_t doskin;
1056 if ((doskin = (*cfield == '~')))
1057 ++cfield;
1058 if (cfield[0] != '\0' && cfield[1] == '\0') {
1059 char const x[][8] = {
1060 "from", "to", "cc", "bcc", "subject"
1062 size_t i;
1063 char c1 = lowerconv(cfield[0]);
1065 for (i = 0; i < NELEM(x); ++i) {
1066 if (c1 == x[i][0]) {
1067 cfield = x[i];
1068 break;
1072 if ((in.s = hfieldX(cfield, mp)) == NULL)
1073 continue;
1075 /* Shall we split into address list and match the addresses only? */
1076 if (doskin) {
1077 np = lextract(in.s, GSKIN);
1078 if (np == NULL)
1079 continue;
1080 out.s = np->n_name;
1081 } else {
1082 np = NULL;
1083 in.l = strlen(in.s);
1084 mime_fromhdr(&in, &out, TD_ICONV);
1086 jnext_name:
1087 #ifdef HAVE_REGEX
1088 if (sep->ss_sexpr == NULL)
1089 rv = (regexec(&sep->ss_regex, out.s, 0,NULL, 0) != REG_NOMATCH);
1090 else
1091 #endif
1092 rv = substr(out.s, sep->ss_sexpr);
1093 if (np == NULL)
1094 free(out.s);
1095 if (rv)
1096 break;
1097 if (np != NULL && (np = np->n_flink) != NULL) {
1098 out.s = np->n_name;
1099 goto jnext_name;
1103 NYD_LEAVE;
1104 return rv;
1107 static void
1108 unmark(int mesg)
1110 size_t i;
1111 NYD_ENTER;
1113 i = (size_t)mesg;
1114 if (i < 1 || UICMP(z, i, >, msgCount))
1115 n_panic(_("Bad message number to unmark"));
1116 message[i - 1].m_flag &= ~MMARK;
1117 NYD_LEAVE;
1120 static int
1121 metamess(int meta, int f)
1123 int c, m;
1124 struct message *mp;
1125 NYD_ENTER;
1127 c = meta;
1128 switch (c) {
1129 case '^': /* First 'good' message left */
1130 mp = mb.mb_threaded ? threadroot : message;
1131 while (PTRCMP(mp, <, message + msgCount)) {
1132 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & MDELETED) == (ui32_t)f) {
1133 c = (int)PTR2SIZE(mp - message + 1);
1134 goto jleave;
1136 if (mb.mb_threaded) {
1137 mp = next_in_thread(mp);
1138 if (mp == NULL)
1139 break;
1140 } else
1141 ++mp;
1143 if (!(pstate & PS_HOOK_MASK))
1144 printf(_("No applicable messages\n"));
1145 goto jem1;
1147 case '$': /* Last 'good message left */
1148 mp = mb.mb_threaded
1149 ? this_in_thread(threadroot, -1) : message + msgCount - 1;
1150 while (mp >= message) {
1151 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & MDELETED) == (ui32_t)f) {
1152 c = (int)PTR2SIZE(mp - message + 1);
1153 goto jleave;
1155 if (mb.mb_threaded) {
1156 mp = prev_in_thread(mp);
1157 if (mp == NULL)
1158 break;
1159 } else
1160 --mp;
1162 if (!(pstate & PS_HOOK_MASK))
1163 printf(_("No applicable messages\n"));
1164 goto jem1;
1166 case '.':
1167 /* Current message */
1168 m = dot - message + 1;
1169 if ((dot->m_flag & MHIDDEN) || (dot->m_flag & MDELETED) != (ui32_t)f) {
1170 printf(_("%d: inappropriate message\n"), m);
1171 goto jem1;
1173 c = m;
1174 break;
1176 case ';':
1177 /* Previously current message */
1178 if (prevdot == NULL) {
1179 n_err(_("No previously current message\n"));
1180 goto jem1;
1182 m = prevdot - message + 1;
1183 if ((prevdot->m_flag & MHIDDEN) ||
1184 (prevdot->m_flag & MDELETED) != (ui32_t)f) {
1185 n_err(_("%d: inappropriate message\n"), m);
1186 goto jem1;
1188 c = m;
1189 break;
1191 default:
1192 n_err(_("Unknown metachar (%c)\n"), c);
1193 goto jem1;
1195 jleave:
1196 NYD_LEAVE;
1197 return c;
1198 jem1:
1199 c = -1;
1200 goto jleave;
1203 FL FILE *
1204 setinput(struct mailbox *mp, struct message *m, enum needspec need)
1206 FILE *rv = NULL;
1207 enum okay ok = STOP;
1208 NYD_ENTER;
1210 switch (need) {
1211 case NEED_HEADER:
1212 ok = (m->m_have & HAVE_HEADER) ? OKAY : get_header(m);
1213 break;
1214 case NEED_BODY:
1215 ok = (m->m_have & HAVE_BODY) ? OKAY : get_body(m);
1216 break;
1217 case NEED_UNSPEC:
1218 ok = OKAY;
1219 break;
1221 if (ok != OKAY)
1222 goto jleave;
1224 fflush(mp->mb_otf);
1225 if (fseek(mp->mb_itf, (long)mailx_positionof(m->m_block, m->m_offset),
1226 SEEK_SET) == -1) {
1227 n_perr(_("fseek"), 0);
1228 n_panic(_("temporary file seek"));
1230 rv = mp->mb_itf;
1231 jleave:
1232 NYD_LEAVE;
1233 return rv;
1236 FL enum okay
1237 get_body(struct message *mp)
1239 enum okay rv;
1240 NYD_ENTER;
1241 UNUSED(mp);
1243 switch (mb.mb_type) {
1244 case MB_FILE:
1245 case MB_MAILDIR:
1246 rv = OKAY;
1247 break;
1248 #ifdef HAVE_POP3
1249 case MB_POP3:
1250 rv = pop3_body(mp);
1251 break;
1252 #endif
1253 case MB_VOID:
1254 default:
1255 rv = STOP;
1256 break;
1258 NYD_LEAVE;
1259 return rv;
1262 FL void
1263 message_reset(void)
1265 NYD_ENTER;
1266 if (message != NULL) {
1267 free(message);
1268 message = NULL;
1270 msgCount = 0;
1271 _message_space = 0;
1272 NYD_LEAVE;
1275 FL void
1276 message_append(struct message *mp)
1278 NYD_ENTER;
1279 if (UICMP(z, msgCount + 1, >=, _message_space)) {
1280 /* XXX remove _message_space magics (or use s_Vector) */
1281 _message_space = (_message_space >= 128 && _message_space <= 1000000)
1282 ? _message_space << 1 : _message_space + 64;
1283 message = srealloc(message, _message_space * sizeof *message);
1285 if (msgCount > 0) {
1286 if (mp != NULL)
1287 message[msgCount - 1] = *mp;
1288 else
1289 memset(message + msgCount - 1, 0, sizeof *message);
1291 NYD_LEAVE;
1294 FL void
1295 message_append_null(void)
1297 NYD_ENTER;
1298 if (msgCount == 0)
1299 message_append(NULL);
1300 setdot(message);
1301 message[msgCount].m_size = 0;
1302 message[msgCount].m_lines = 0;
1303 NYD_LEAVE;
1306 FL bool_t
1307 message_match(struct message *mp, struct search_expr const *sep,
1308 bool_t with_headers)
1310 char **line;
1311 size_t *linesize, cnt;
1312 FILE *fp;
1313 bool_t rv = FAL0;
1314 NYD_ENTER;
1316 if ((fp = Ftmp(NULL, "mpmatch", OF_RDWR | OF_UNLINK | OF_REGISTER)) == NULL)
1317 goto j_leave;
1319 if (sendmp(mp, fp, NULL, NULL, SEND_TOSRCH, NULL) < 0)
1320 goto jleave;
1321 fflush_rewind(fp);
1323 cnt = fsize(fp);
1324 line = &termios_state.ts_linebuf; /* XXX line pool */
1325 linesize = &termios_state.ts_linesize; /* XXX line pool */
1327 if (!with_headers)
1328 while (fgetline(line, linesize, &cnt, NULL, fp, 0))
1329 if (**line == '\n')
1330 break;
1332 while (fgetline(line, linesize, &cnt, NULL, fp, 0)) {
1333 #ifdef HAVE_REGEX
1334 if (sep->ss_sexpr == NULL) {
1335 if (regexec(&sep->ss_regex, *line, 0,NULL, 0) == REG_NOMATCH)
1336 continue;
1337 } else
1338 #endif
1339 if (!substr(*line, sep->ss_sexpr))
1340 continue;
1341 rv = TRU1;
1342 break;
1345 jleave:
1346 Fclose(fp);
1347 j_leave:
1348 NYD_LEAVE;
1349 return rv;
1352 FL struct message *
1353 setdot(struct message *mp)
1355 NYD_ENTER;
1356 if (dot != mp) {
1357 prevdot = dot;
1358 pstate &= ~PS_DID_PRINT_DOT;
1360 dot = mp;
1361 uncollapse1(dot, 0);
1362 NYD_LEAVE;
1363 return dot;
1366 FL void
1367 touch(struct message *mp)
1369 NYD_ENTER;
1370 mp->m_flag |= MTOUCH;
1371 if (!(mp->m_flag & MREAD))
1372 mp->m_flag |= MREAD | MSTATUS;
1373 NYD_LEAVE;
1376 FL int
1377 getmsglist(char *buf, int *vector, int flags)
1379 int *ip, mc;
1380 struct message *mp;
1381 NYD_ENTER;
1383 pstate &= ~PS_ARGLIST_MASK;
1384 _list_last_saw_d = _list_saw_d;
1385 _list_saw_d = FAL0;
1387 if (msgCount == 0) {
1388 *vector = 0;
1389 mc = 0;
1390 goto jleave;
1393 pstate |= PS_MSGLIST_DIRECT;
1395 if (markall(buf, flags) < 0) {
1396 mc = -1;
1397 goto jleave;
1400 ip = vector;
1401 if (pstate & PS_HOOK_NEWMAIL) {
1402 mc = 0;
1403 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
1404 if (mp->m_flag & MMARK) {
1405 if (!(mp->m_flag & MNEWEST))
1406 unmark((int)PTR2SIZE(mp - message + 1));
1407 else
1408 ++mc;
1410 if (mc == 0) {
1411 mc = -1;
1412 goto jleave;
1416 if (mb.mb_threaded == 0) {
1417 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
1418 if (mp->m_flag & MMARK)
1419 *ip++ = (int)PTR2SIZE(mp - message + 1);
1420 } else {
1421 for (mp = threadroot; mp != NULL; mp = next_in_thread(mp))
1422 if (mp->m_flag & MMARK)
1423 *ip++ = (int)PTR2SIZE(mp - message + 1);
1425 *ip = 0;
1426 mc = (int)PTR2SIZE(ip - vector);
1427 if (mc != 1)
1428 pstate &= ~PS_MSGLIST_DIRECT;
1429 jleave:
1430 NYD_LEAVE;
1431 return mc;
1434 FL int
1435 first(int f, int m)
1437 struct message *mp;
1438 int rv;
1439 NYD_ENTER;
1441 if (msgCount == 0) {
1442 rv = 0;
1443 goto jleave;
1446 f &= MDELETED;
1447 m &= MDELETED;
1448 for (mp = dot;
1449 mb.mb_threaded ? (mp != NULL) : PTRCMP(mp, <, message + msgCount);
1450 mb.mb_threaded ? (mp = next_in_thread(mp)) : ++mp) {
1451 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & m) == (ui32_t)f) {
1452 rv = (int)PTR2SIZE(mp - message + 1);
1453 goto jleave;
1457 if (dot > message) {
1458 for (mp = dot - 1; (mb.mb_threaded ? (mp != NULL) : (mp >= message));
1459 mb.mb_threaded ? (mp = prev_in_thread(mp)) : --mp) {
1460 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & m) == (ui32_t)f) {
1461 rv = (int)PTR2SIZE(mp - message + 1);
1462 goto jleave;
1466 rv = 0;
1467 jleave:
1468 NYD_LEAVE;
1469 return rv;
1472 FL void
1473 mark(int mesg, int f)
1475 struct message *mp;
1476 int i;
1477 NYD_ENTER;
1479 i = mesg;
1480 if (i < 1 || i > msgCount)
1481 n_panic(_("Bad message number to mark"));
1483 if (mb.mb_threaded == 1 && threadflag) {
1484 struct message *mp_t = message + i - 1;
1486 if (!(mp_t->m_flag & MHIDDEN)) {
1487 if (f == MDELETED || !(mp_t->m_flag & MDELETED) || _list_saw_d)
1488 mp_t->m_flag |= MMARK;
1491 if (mp_t->m_child != NULL) {
1492 mp = mp_t->m_child;
1493 mark((int)PTR2SIZE(mp - message + 1), f);
1494 for (mp = mp->m_younger; mp != NULL; mp = mp->m_younger)
1495 mark((int)PTR2SIZE(mp - message + 1), f);
1497 } else
1498 message[i - 1].m_flag |= MMARK;
1499 NYD_LEAVE;
1502 /* s-it-mode */