*inbox*: if empty, only bypass *folder* to $MAIL or builtin default
[s-mailx.git] / list.c
blobb92368da9f64854936caf87a2b12d732913032aa
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Message (search a.k.a. argument) list handling.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2015 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
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 list
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 static int lastcolmod;
124 static size_t STRINGLEN;
125 static int lexnumber; /* Number of TNUMBER from scan() */
126 static char *lexstring; /* String from TSTRING, scan() */
127 static int regretp; /* Pointer to TOS of regret tokens */
128 static int regretstack[REGDEP]; /* Stack of regretted tokens */
129 static char *string_stack[REGDEP]; /* Stack of regretted strings */
130 static int numberstack[REGDEP]; /* Stack of regretted numbers */
131 static int threadflag; /* mark entire threads */
133 /* Append, taking care of resizes */
134 static char ** add_to_namelist(char ***namelist, size_t *nmlsize,
135 char **np, char *string);
137 /* Mark all messages that the user wanted from the command line in the message
138 * structure. Return 0 on success, -1 on error */
139 static int markall(char *buf, int f);
141 /* Turn the character after a colon modifier into a bit value */
142 static int evalcol(int col);
144 /* Check the passed message number for legality and proper flags. If f is
145 * MDELETED, then either kind will do. Otherwise, the message has to be
146 * undeleted */
147 static int check(int mesg, int f);
149 /* Scan out a single lexical item and return its token number, updating the
150 * string pointer passed **sp. Also, store the value of the number or string
151 * scanned in lexnumber or lexstring as appropriate. In any event, store the
152 * scanned "thing" in lexstring */
153 static int scan(char **sp);
155 /* Unscan the named token by pushing it onto the regret stack */
156 static void regret(int token);
158 /* Reset all the scanner global variables */
159 static void scaninit(void);
161 /* See if the passed name sent the passed message */
162 static bool_t _matchsender(struct message *mp, char const *str, bool_t allnet);
164 static bool_t _matchmid(struct message *mp, char *id, enum idfield idfield);
166 /* See if the given string matches.
167 * For the purpose of the scan, we ignore case differences.
168 * This is the engine behind the "/" search */
169 static bool_t _match_dash(struct message *mp, char const *str);
171 /* See if the given search expression 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_at(struct message *mp, struct search_expr *sep);
176 /* Unmark the named message */
177 static void unmark(int mesg);
179 /* Return the message number corresponding to the passed meta character */
180 static int metamess(int meta, int f);
182 static char **
183 add_to_namelist(char ***namelist, size_t *nmlsize, char **np, char *string)
185 size_t idx;
186 NYD_ENTER;
188 if ((idx = PTR2SIZE(np - *namelist)) >= *nmlsize) {
189 *namelist = srealloc(*namelist, (*nmlsize += 8) * sizeof *np);
190 np = *namelist + idx;
192 *np++ = string;
193 NYD_LEAVE;
194 return np;
197 static int
198 markall(char *buf, int f)
200 #define markall_ret(i) do { rv = i; goto jleave; } while (0);
202 /* TODO use a bit carrier for all the states */
203 char **np, **nq, **namelist, *bufp, *id = NULL, *cp;
204 int rv = 0, i, tok, beg, other, valdot, colmod, colresult;
205 struct message *mp, *mx;
206 bool_t mc, star, topen, tback;
207 size_t j, nmlsize;
208 enum idfield idfield = ID_REFERENCES;
209 #ifdef HAVE_IMAP
210 int gotheaders;
211 #endif
212 NYD_ENTER;
214 lexstring = ac_alloc(STRINGLEN = 2 * strlen(buf) +1);
215 valdot = (int)PTR2SIZE(dot - message + 1);
216 colmod = 0;
218 for (i = 1; i <= msgCount; ++i) {
219 enum mflag mf;
221 mf = message[i - 1].m_flag;
222 if (mf & MMARK)
223 mf |= MOLDMARK;
224 else
225 mf &= ~MOLDMARK;
226 mf &= ~MMARK;
227 message[i - 1].m_flag = mf;
230 np = namelist = smalloc((nmlsize = 8) * sizeof *namelist);
231 scaninit();
232 bufp = buf;
233 mc = FAL0;
234 beg = star = other = topen = tback = FAL0;
235 #ifdef HAVE_IMAP
236 gotheaders = 0;
237 #endif
239 for (tok = scan(&bufp); tok != TEOL;) {
240 switch (tok) {
241 case TNUMBER:
242 number:
243 if (star) {
244 n_err(_("No numbers mixed with *\n"));
245 markall_ret(-1)
247 pstate |= PS_MSGLIST_SAW_NO;
248 ++other;
249 if (beg != 0) {
250 if (check(lexnumber, f))
251 markall_ret(-1)
252 i = beg;
253 while (mb.mb_threaded ? 1 : i <= lexnumber) {
254 if (!(message[i - 1].m_flag & MHIDDEN) &&
255 (f == MDELETED || !(message[i - 1].m_flag & MDELETED)))
256 mark(i, f);
257 if (mb.mb_threaded) {
258 if (i == lexnumber)
259 break;
260 mx = next_in_thread(&message[i - 1]);
261 if (mx == NULL)
262 markall_ret(-1)
263 i = (int)PTR2SIZE(mx - message + 1);
264 } else
265 ++i;
267 beg = 0;
268 break;
270 beg = lexnumber;
271 if (check(beg, f))
272 markall_ret(-1)
273 tok = scan(&bufp);
274 regret(tok);
275 if (tok != TDASH) {
276 mark(beg, f);
277 beg = 0;
279 break;
281 case TPLUS:
282 pstate &= ~PS_MSGLIST_DIRECT;
283 if (beg != 0) {
284 printf(_("Non-numeric second argument\n"));
285 markall_ret(-1)
287 i = valdot;
288 do {
289 if (mb.mb_threaded) {
290 mx = next_in_thread(message + i - 1);
291 i = mx ? (int)PTR2SIZE(mx - message + 1) : msgCount + 1;
292 } else
293 ++i;
294 if (i > msgCount) {
295 n_err(_("Referencing beyond EOF\n"));
296 markall_ret(-1)
298 } while (message[i - 1].m_flag == MHIDDEN ||
299 (message[i - 1].m_flag & MDELETED) != (unsigned)f);
300 mark(i, f);
301 break;
303 case TDASH:
304 pstate &= ~PS_MSGLIST_DIRECT;
305 if (beg == 0) {
306 i = valdot;
307 do {
308 if (mb.mb_threaded) {
309 mx = prev_in_thread(message + i - 1);
310 i = mx ? (int)PTR2SIZE(mx - message + 1) : 0;
311 } else
312 --i;
313 if (i <= 0) {
314 n_err(_("Referencing before 1\n"));
315 markall_ret(-1)
317 } while ((message[i - 1].m_flag & MHIDDEN) ||
318 (message[i - 1].m_flag & MDELETED) != (unsigned)f);
319 mark(i, f);
321 break;
323 case TSTRING:
324 pstate &= ~PS_MSGLIST_DIRECT;
325 if (beg != 0) {
326 n_err(_("Non-numeric second argument\n"));
327 markall_ret(-1)
329 ++other;
330 if ((cp = lexstring)[0] == ':') {
331 while (*++cp != '\0') {
332 colresult = evalcol(*cp);
333 if (colresult == 0) {
334 n_err(_("Unknown colon modifier \"%s\"\n"), lexstring);
335 markall_ret(-1)
337 colmod |= colresult;
339 } else
340 np = add_to_namelist(&namelist, &nmlsize, np, savestr(lexstring));
341 break;
343 case TOPEN:
344 #ifdef HAVE_IMAP_SEARCH
345 pstate &= ~PS_MSGLIST_DIRECT;
346 if (imap_search(lexstring, f) == STOP)
347 markall_ret(-1)
348 topen = TRU1;
349 #else
350 n_err(_("Optional selector is not available: \"%s\"\n"),
351 lexstring);
352 markall_ret(-1)
353 #endif
354 break;
356 case TDOLLAR:
357 case TUP:
358 case TDOT:
359 case TSEMI:
360 pstate &= ~PS_MSGLIST_DIRECT;
361 lexnumber = metamess(lexstring[0], f);
362 if (lexnumber == -1)
363 markall_ret(-1)
364 goto number;
366 case TBACK:
367 pstate &= ~PS_MSGLIST_DIRECT;
368 tback = TRU1;
369 for (i = 1; i <= msgCount; i++) {
370 if ((message[i - 1].m_flag & MHIDDEN) ||
371 (message[i - 1].m_flag & MDELETED) != (unsigned)f)
372 continue;
373 if (message[i - 1].m_flag & MOLDMARK)
374 mark(i, f);
376 break;
378 case TSTAR:
379 pstate &= ~PS_MSGLIST_DIRECT;
380 if (other) {
381 n_err(_("Can't mix \"*\" with anything\n"));
382 markall_ret(-1)
384 star = TRU1;
385 break;
387 case TCOMMA:
388 pstate &= ~PS_MSGLIST_DIRECT;
389 #ifdef HAVE_IMAP
390 if (mb.mb_type == MB_IMAP && gotheaders++ == 0)
391 imap_getheaders(1, msgCount);
392 #endif
393 if (id == NULL && (cp = hfield1("in-reply-to", dot)) != NULL) {
394 id = savestr(cp);
395 idfield = ID_IN_REPLY_TO;
397 if (id == NULL && (cp = hfield1("references", dot)) != NULL) {
398 struct name *enp;
400 if ((enp = extract(cp, GREF)) != NULL) {
401 while (enp->n_flink != NULL)
402 enp = enp->n_flink;
403 id = savestr(enp->n_name);
404 idfield = ID_REFERENCES;
407 if (id == NULL) {
408 printf(_(
409 "Cannot determine parent Message-ID of the current message\n"));
410 markall_ret(-1)
412 break;
414 case TERROR:
415 pstate &= ~PS_MSGLIST_DIRECT;
416 pstate |= PS_MSGLIST_SAW_NO;
417 markall_ret(-1)
419 threadflag = 0;
420 tok = scan(&bufp);
423 lastcolmod = colmod;
424 np = add_to_namelist(&namelist, &nmlsize, np, NULL);
425 --np;
426 mc = FAL0;
427 if (star) {
428 for (i = 0; i < msgCount; ++i) {
429 if (!(message[i].m_flag & MHIDDEN) &&
430 (message[i].m_flag & MDELETED) == (unsigned)f) {
431 mark(i + 1, f);
432 mc = TRU1;
435 if (!mc) {
436 if (!(pstate & PS_HOOK_MASK))
437 printf(_("No applicable messages.\n"));
438 markall_ret(-1)
440 markall_ret(0)
443 if ((topen || tback) && !mc) {
444 for (i = 0; i < msgCount; ++i)
445 if (message[i].m_flag & MMARK)
446 mc = TRU1;
447 if (!mc) {
448 if (!(pstate & PS_HOOK_MASK)) {
449 if (tback)
450 n_err(_("No previously marked messages\n"));
451 else
452 printf(_("No messages satisfy (criteria)\n"));
454 markall_ret(-1)
458 /* If no numbers were given, mark all messages, so that we can unmark
459 * any whose sender was not selected if any user names were given */
460 if ((np > namelist || colmod != 0 || id) && !mc) {
461 for (i = 1; i <= msgCount; ++i) {
462 if (!(message[i - 1].m_flag & MHIDDEN) &&
463 (message[i - 1].m_flag & MDELETED) == (unsigned)f)
464 mark(i, f);
468 /* If any names were given, eliminate any messages which don't match */
469 if (np > namelist || id) {
470 struct search_expr *sep = NULL;
471 bool_t allnet;
473 /* The @ search works with struct search_expr, so build an array.
474 * To simplify array, i.e., regex_t destruction, and optimize for the
475 * common case we walk the entire array even in case of errors */
476 if (np > namelist) {
477 sep = scalloc(PTR2SIZE(np - namelist), sizeof(*sep));
478 for (j = 0, nq = namelist; *nq != NULL; ++j, ++nq) {
479 char *x = *nq, *y;
481 sep[j].ss_sexpr = x;
482 if (*x != '@' || rv < 0)
483 continue;
485 for (y = x + 1;; ++y) {
486 if (*y == '\0' || !fieldnamechar(*y)) {
487 x = NULL;
488 break;
490 if (*y == '@') {
491 x = y;
492 break;
495 sep[j].ss_where = (x == NULL || x - 1 == *nq)
496 ? "subject" : savestrbuf(*nq + 1, PTR2SIZE(x - *nq) - 1);
498 x = (x == NULL ? *nq : x) + 1;
499 if (*x == '\0') { /* XXX Simply remove from list instead? */
500 n_err(_("Empty \"[@..]@\" search expression\n"));
501 rv = -1;
502 continue;
504 #ifdef HAVE_REGEX
505 if (is_maybe_regex(x)) {
506 sep[j].ss_sexpr = NULL;
507 if (regcomp(&sep[j].ss_regex, x,
508 REG_EXTENDED | REG_ICASE | REG_NOSUB) != 0) {
509 n_err(_("Invalid regular expression: >>> %s <<<\n"), x);
510 rv = -1;
511 continue;
513 } else
514 #endif
515 sep[j].ss_sexpr = x;
517 if (rv < 0)
518 goto jnamesearch_sepfree;
521 #ifdef HAVE_IMAP
522 if (mb.mb_type == MB_IMAP && gotheaders++ == 0)
523 imap_getheaders(1, msgCount);
524 #endif
525 srelax_hold();
526 allnet = ok_blook(allnet);
527 for (i = 1; i <= msgCount; ++i) {
528 mp = message + i - 1;
529 j = 0;
530 if (np > namelist) {
531 for (nq = namelist; *nq != NULL; ++nq) {
532 if (**nq == '@') {
533 if (_match_at(mp, sep + PTR2SIZE(nq - namelist))) {
534 ++j;
535 break;
537 } else if (**nq == '/') {
538 if (_match_dash(mp, *nq)) {
539 ++j;
540 break;
542 } else if (_matchsender(mp, *nq, allnet)) {
543 ++j;
544 break;
548 if (j == 0 && id && _matchmid(mp, id, idfield))
549 ++j;
550 if (j == 0)
551 mp->m_flag &= ~MMARK;
552 srelax();
554 srelax_rele();
556 /* Make sure we got some decent messages */
557 j = 0;
558 for (i = 1; i <= msgCount; ++i)
559 if (message[i - 1].m_flag & MMARK) {
560 ++j;
561 break;
563 if (j == 0) {
564 if (!(pstate & PS_HOOK_MASK) && np > namelist) {
565 printf(_("No applicable messages from {%s"), namelist[0]);
566 for (nq = namelist + 1; *nq != NULL; ++nq)
567 printf(_(", %s"), *nq);
568 printf(_("}\n"));
569 } else if (id)
570 printf(_("Parent message not found\n"));
571 rv = -1;
572 goto jnamesearch_sepfree;
575 jnamesearch_sepfree:
576 if (sep != NULL) {
577 #ifdef HAVE_REGEX
578 for (j = PTR2SIZE(np - namelist); j-- != 0;)
579 if (sep[j].ss_sexpr == NULL)
580 regfree(&sep[j].ss_regex);
581 #endif
582 free(sep);
584 if (rv < 0)
585 goto jleave;
588 /* If any colon modifiers were given, go through and unmark any
589 * messages which do not satisfy the modifiers */
590 if (colmod != 0) {
591 for (i = 1; i <= msgCount; ++i) {
592 struct coltab const *colp;
593 bool_t bad = TRU1;
595 mp = message + i - 1;
596 for (colp = _coltab; colp->co_char != '\0'; ++colp)
597 if ((colp->co_bit & colmod) &&
598 ((mp->m_flag & colp->co_mask) == (unsigned)colp->co_equal))
599 bad = FAL0;
600 if (bad)
601 unmark(i);
604 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
605 if (mp->m_flag & MMARK)
606 break;
608 if (PTRCMP(mp, >=, message + msgCount)) {
609 struct coltab const *colp;
611 if (!(pstate & PS_HOOK_MASK)) {
612 printf(_("No messages satisfy"));
613 for (colp = _coltab; colp->co_char != '\0'; ++colp)
614 if (colp->co_bit & colmod)
615 printf(" :%c", colp->co_char);
616 printf("\n");
618 markall_ret(-1)
622 markall_ret(0)
623 jleave:
624 free(namelist);
625 ac_free(lexstring);
626 NYD_LEAVE;
627 return rv;
629 #undef markall_ret
632 static int
633 evalcol(int col)
635 struct coltab const *colp;
636 int rv;
637 NYD_ENTER;
639 if (col == 0)
640 rv = lastcolmod;
641 else {
642 rv = 0;
643 for (colp = _coltab; colp->co_char != '\0'; ++colp)
644 if (colp->co_char == col) {
645 rv = colp->co_bit;
646 break;
649 NYD_LEAVE;
650 return rv;
653 static int
654 check(int mesg, int f)
656 struct message *mp;
657 NYD_ENTER;
659 if (mesg < 1 || mesg > msgCount) {
660 printf(_("%d: Invalid message number\n"), mesg);
661 goto jem1;
663 mp = message + mesg - 1;
664 if (mp->m_flag & MHIDDEN ||
665 (f != MDELETED && (mp->m_flag & MDELETED) != 0)) {
666 n_err(_("%d: inappropriate message\n"), mesg);
667 goto jem1;
669 f = 0;
670 jleave:
671 NYD_LEAVE;
672 return f;
673 jem1:
674 f = -1;
675 goto jleave;
678 static int
679 scan(char **sp)
681 char *cp, *cp2;
682 int rv, c, inquote, quotec;
683 struct lex const *lp;
684 NYD_ENTER;
686 if (regretp >= 0) {
687 strncpy(lexstring, string_stack[regretp], STRINGLEN);
688 lexstring[STRINGLEN -1] = '\0';
689 lexnumber = numberstack[regretp];
690 rv = regretstack[regretp--];
691 goto jleave;
694 cp = *sp;
695 cp2 = lexstring;
696 c = *cp++;
698 /* strip away leading white space */
699 while (blankchar(c))
700 c = *cp++;
702 /* If no characters remain, we are at end of line, so report that */
703 if (c == '\0') {
704 *sp = --cp;
705 rv = TEOL;
706 goto jleave;
709 /* Select members of a message thread */
710 if (c == '&') {
711 threadflag = 1;
712 if (*cp == '\0' || spacechar(*cp)) {
713 lexstring[0] = '.';
714 lexstring[1] = '\0';
715 *sp = cp;
716 rv = TDOT;
717 goto jleave;
719 c = *cp++;
722 /* If the leading character is a digit, scan the number and convert it
723 * on the fly. Return TNUMBER when done */
724 if (digitchar(c)) {
725 lexnumber = 0;
726 while (digitchar(c)) {
727 lexnumber = lexnumber*10 + c - '0';
728 *cp2++ = c;
729 c = *cp++;
731 *cp2 = '\0';
732 *sp = --cp;
733 rv = TNUMBER;
734 goto jleave;
737 /* An IMAP SEARCH list. Note that TOPEN has always been included in
738 * singles[] in Mail and mailx. Thus although there is no formal
739 * definition for (LIST) lists, they do not collide with historical
740 * practice because a subject string (LIST) could never been matched
741 * this way */
742 if (c == '(') {
743 ui32_t level = 1;
744 inquote = 0;
745 *cp2++ = c;
746 do {
747 if ((c = *cp++&0377) == '\0') {
748 jmtop:
749 n_err(_("Missing \")\"\n"));
750 rv = TERROR;
751 goto jleave;
753 if (inquote && c == '\\') {
754 *cp2++ = c;
755 c = *cp++&0377;
756 if (c == '\0')
757 goto jmtop;
758 } else if (c == '"')
759 inquote = !inquote;
760 else if (inquote)
761 /*EMPTY*/;
762 else if (c == '(')
763 ++level;
764 else if (c == ')')
765 --level;
766 else if (spacechar(c)) {
767 /* Replace unquoted whitespace by single space characters, to make
768 * the string IMAP SEARCH conformant */
769 c = ' ';
770 if (cp2[-1] == ' ')
771 --cp2;
773 *cp2++ = c;
774 } while (c != ')' || level > 0);
775 *cp2 = '\0';
776 *sp = cp;
777 rv = TOPEN;
778 goto jleave;
781 /* Check for single character tokens; return such if found */
782 for (lp = _singles; lp->l_char != '\0'; ++lp)
783 if (c == lp->l_char) {
784 lexstring[0] = c;
785 lexstring[1] = '\0';
786 *sp = cp;
787 rv = lp->l_token;
788 goto jleave;
791 /* We've got a string! Copy all the characters of the string into
792 * lexstring, until we see a null, space, or tab. If the lead character is
793 * a " or ', save it and scan until you get another */
794 quotec = 0;
795 if (c == '\'' || c == '"') {
796 quotec = c;
797 c = *cp++;
799 while (c != '\0') {
800 if (quotec == 0 && c == '\\' && *cp != '\0')
801 c = *cp++;
802 if (c == quotec) {
803 ++cp;
804 break;
806 if (quotec == 0 && blankchar(c))
807 break;
808 if (PTRCMP(cp2 - lexstring, <, STRINGLEN - 1))
809 *cp2++ = c;
810 c = *cp++;
812 if (quotec && c == 0) {
813 n_err(_("Missing %c\n"), quotec);
814 rv = TERROR;
815 goto jleave;
817 *sp = --cp;
818 *cp2 = '\0';
819 rv = TSTRING;
820 jleave:
821 NYD_LEAVE;
822 return rv;
825 static void
826 regret(int token)
828 NYD_ENTER;
829 if (++regretp >= REGDEP)
830 n_panic(_("Too many regrets"));
831 regretstack[regretp] = token;
832 lexstring[STRINGLEN -1] = '\0';
833 string_stack[regretp] = savestr(lexstring);
834 numberstack[regretp] = lexnumber;
835 NYD_LEAVE;
838 static void
839 scaninit(void)
841 NYD_ENTER;
842 regretp = -1;
843 threadflag = 0;
844 NYD_LEAVE;
847 static bool_t
848 _matchsender(struct message *mp, char const *str, bool_t allnet)
850 char const *str_base, *np_base, *np;
851 char sc, nc;
852 bool_t rv;
853 NYD_ENTER;
855 /* Empty string doesn't match */
856 if (*(str_base = str) == '\0') {
857 rv = FAL0;
858 goto jleave;
861 /* *allnet* is POSIX and, since it explicitly mentions login and user names,
862 * most likely case-sensitive. XXX Still allow substr matching, though
863 * XXX possibly the first letter should be case-insensitive, then? */
864 if (allnet) {
865 np_base = np = nameof(mp, 0);
866 for (;;) {
867 if ((sc = *str++) == '@')
868 sc = '\0';
869 if ((nc = *np++) == '@' || nc == '\0' || sc == '\0')
870 break;
871 if (sc != nc) {
872 np = ++np_base;
873 str = str_base;
876 rv = (sc == '\0');
877 } else {
878 /* TODO POSIX says ~"match any address as shown in header overview",
879 * TODO but a normalized match would be more sane i guess.
880 * TODO struct name should gain a comparison method, normalize realname
881 * TODO content (in TODO) and thus match as likewise
882 * TODO "Buddy (Today) <here>" and "(Now) Buddy <here>" */
883 char const *real_base;
884 bool_t again;
886 real_base = name1(mp, 0);
887 again = ok_blook(showname);
888 jagain:
889 np_base = np = again ? realname(real_base) : skin(real_base);
890 str = str_base;
891 for(;;){
892 sc = *str++;
893 if((nc = *np++) == '\0' || sc == '\0')
894 break;
895 sc = upperconv(sc);
896 nc = upperconv(nc);
897 if(sc != nc){
898 np = ++np_base;
899 str = str_base;
903 /* And really if i want to match 'on@' then i want it to match even if
904 * *showname* is set! */
905 if(!(rv = (sc == '\0')) && again){
906 again = FAL0;
907 goto jagain;
910 jleave:
911 NYD_LEAVE;
912 return rv;
915 static bool_t
916 _matchmid(struct message *mp, char *id, enum idfield idfield)
918 char *cp;
919 bool_t rv;
920 NYD_ENTER;
922 if ((cp = hfield1("message-id", mp)) != NULL) {
923 switch (idfield) {
924 case ID_REFERENCES:
925 rv = !msgidcmp(id, cp);
926 goto jleave;
927 case ID_IN_REPLY_TO: {
928 struct name *np;
930 if ((np = extract(id, GREF)) != NULL)
931 do {
932 if (!msgidcmp(np->n_name, cp)) {
933 rv = TRU1;
934 goto jleave;
936 } while ((np = np->n_flink) != NULL);
937 break;
941 rv = FAL0;
942 jleave:
943 NYD_LEAVE;
944 return rv;
947 static bool_t
948 _match_dash(struct message *mp, char const *str)
950 static char lastscan[128];
952 struct str in, out;
953 char *hfield, *hbody;
954 bool_t rv;
955 NYD_ENTER;
957 if (*++str == '\0') {
958 str = lastscan;
959 } else {
960 strncpy(lastscan, str, sizeof lastscan); /* XXX use new n_str object! */
961 lastscan[sizeof lastscan -1] = '\0';
964 /* Now look, ignoring case, for the word in the string */
965 if (ok_blook(searchheaders) && (hfield = strchr(str, ':'))) {
966 size_t l = PTR2SIZE(hfield - str);
967 hfield = ac_alloc(l +1);
968 memcpy(hfield, str, l);
969 hfield[l] = '\0';
970 hbody = hfieldX(hfield, mp);
971 ac_free(hfield);
972 hfield = UNCONST(str + l + 1);
973 } else {
974 hfield = UNCONST(str);
975 hbody = hfield1("subject", mp);
977 if (hbody == NULL) {
978 rv = FAL0;
979 goto jleave;
982 in.s = hbody;
983 in.l = strlen(hbody);
984 mime_fromhdr(&in, &out, TD_ICONV);
985 rv = substr(out.s, hfield);
986 free(out.s);
987 jleave:
988 NYD_LEAVE;
989 return rv;
992 static bool_t
993 _match_at(struct message *mp, struct search_expr *sep)
995 struct str in, out;
996 char *nfield;
997 char const *cfield;
998 bool_t rv = FAL0;
999 NYD_ENTER;
1001 nfield = savestr(sep->ss_where);
1003 while ((cfield = n_strsep(&nfield, ',', TRU1)) != NULL) {
1004 if (!asccasecmp(cfield, "body") ||
1005 (cfield[1] == '\0' && cfield[0] == '>')) {
1006 rv = FAL0;
1007 jmsg:
1008 if ((rv = message_match(mp, sep, rv)))
1009 break;
1010 continue;
1012 if (!asccasecmp(cfield, "text") ||
1013 (cfield[1] == '\0' && cfield[0] == '=')) {
1014 rv = TRU1;
1015 goto jmsg;
1018 if (!asccasecmp(cfield, "header") ||
1019 (cfield[1] == '\0' && cfield[0] == '<')) {
1020 if ((rv = header_match(mp, sep)))
1021 break;
1022 continue;
1025 /* This is not a special name, so take care for the "skin" prefix !
1026 * and possible abbreviations */
1028 struct name *np;
1029 bool_t doskin;
1031 if ((doskin = (*cfield == '~')))
1032 ++cfield;
1033 if (cfield[0] != '\0' && cfield[1] == '\0') {
1034 char const x[][8] = {
1035 "from", "to", "cc", "bcc", "subject"
1037 size_t i;
1038 char c1 = lowerconv(cfield[0]);
1040 for (i = 0; i < NELEM(x); ++i) {
1041 if (c1 == x[i][0]) {
1042 cfield = x[i];
1043 break;
1047 if ((in.s = hfieldX(cfield, mp)) == NULL)
1048 continue;
1050 /* Shall we split into address list and match the addresses only? */
1051 if (doskin) {
1052 np = lextract(in.s, GSKIN);
1053 if (np == NULL)
1054 continue;
1055 out.s = np->n_name;
1056 } else {
1057 np = NULL;
1058 in.l = strlen(in.s);
1059 mime_fromhdr(&in, &out, TD_ICONV);
1061 jnext_name:
1062 #ifdef HAVE_REGEX
1063 if (sep->ss_sexpr == NULL)
1064 rv = (regexec(&sep->ss_regex, out.s, 0,NULL, 0) != REG_NOMATCH);
1065 else
1066 #endif
1067 rv = substr(out.s, sep->ss_sexpr);
1068 if (np == NULL)
1069 free(out.s);
1070 if (rv)
1071 break;
1072 if (np != NULL && (np = np->n_flink) != NULL) {
1073 out.s = np->n_name;
1074 goto jnext_name;
1078 NYD_LEAVE;
1079 return rv;
1082 static void
1083 unmark(int mesg)
1085 size_t i;
1086 NYD_ENTER;
1088 i = (size_t)mesg;
1089 if (i < 1 || UICMP(z, i, >, msgCount))
1090 n_panic(_("Bad message number to unmark"));
1091 message[i - 1].m_flag &= ~MMARK;
1092 NYD_LEAVE;
1095 static int
1096 metamess(int meta, int f)
1098 int c, m;
1099 struct message *mp;
1100 NYD_ENTER;
1102 c = meta;
1103 switch (c) {
1104 case '^': /* First 'good' message left */
1105 mp = mb.mb_threaded ? threadroot : message;
1106 while (PTRCMP(mp, <, message + msgCount)) {
1107 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & MDELETED) == (ui32_t)f) {
1108 c = (int)PTR2SIZE(mp - message + 1);
1109 goto jleave;
1111 if (mb.mb_threaded) {
1112 mp = next_in_thread(mp);
1113 if (mp == NULL)
1114 break;
1115 } else
1116 ++mp;
1118 if (!(pstate & PS_HOOK_MASK))
1119 printf(_("No applicable messages\n"));
1120 goto jem1;
1122 case '$': /* Last 'good message left */
1123 mp = mb.mb_threaded
1124 ? this_in_thread(threadroot, -1) : message + msgCount - 1;
1125 while (mp >= message) {
1126 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & MDELETED) == (ui32_t)f) {
1127 c = (int)PTR2SIZE(mp - message + 1);
1128 goto jleave;
1130 if (mb.mb_threaded) {
1131 mp = prev_in_thread(mp);
1132 if (mp == NULL)
1133 break;
1134 } else
1135 --mp;
1137 if (!(pstate & PS_HOOK_MASK))
1138 printf(_("No applicable messages\n"));
1139 goto jem1;
1141 case '.':
1142 /* Current message */
1143 m = dot - message + 1;
1144 if ((dot->m_flag & MHIDDEN) || (dot->m_flag & MDELETED) != (ui32_t)f) {
1145 printf(_("%d: inappropriate message\n"), m);
1146 goto jem1;
1148 c = m;
1149 break;
1151 case ';':
1152 /* Previously current message */
1153 if (prevdot == NULL) {
1154 n_err(_("No previously current message\n"));
1155 goto jem1;
1157 m = prevdot - message + 1;
1158 if ((prevdot->m_flag & MHIDDEN) ||
1159 (prevdot->m_flag & MDELETED) != (ui32_t)f) {
1160 n_err(_("%d: inappropriate message\n"), m);
1161 goto jem1;
1163 c = m;
1164 break;
1166 default:
1167 n_err(_("Unknown metachar (%c)\n"), c);
1168 goto jem1;
1170 jleave:
1171 NYD_LEAVE;
1172 return c;
1173 jem1:
1174 c = -1;
1175 goto jleave;
1178 FL int
1179 getmsglist(char *buf, int *vector, int flags)
1181 int *ip, mc;
1182 struct message *mp;
1183 NYD_ENTER;
1185 pstate &= ~PS_MSGLIST_MASK;
1187 if (msgCount == 0) {
1188 *vector = 0;
1189 mc = 0;
1190 goto jleave;
1193 pstate |= PS_MSGLIST_DIRECT;
1195 if (markall(buf, flags) < 0) {
1196 mc = -1;
1197 goto jleave;
1200 ip = vector;
1201 if (pstate & PS_HOOK_NEWMAIL) {
1202 mc = 0;
1203 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
1204 if (mp->m_flag & MMARK) {
1205 if (!(mp->m_flag & MNEWEST))
1206 unmark((int)PTR2SIZE(mp - message + 1));
1207 else
1208 ++mc;
1210 if (mc == 0) {
1211 mc = -1;
1212 goto jleave;
1216 if (mb.mb_threaded == 0) {
1217 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
1218 if (mp->m_flag & MMARK)
1219 *ip++ = (int)PTR2SIZE(mp - message + 1);
1220 } else {
1221 for (mp = threadroot; mp != NULL; mp = next_in_thread(mp))
1222 if (mp->m_flag & MMARK)
1223 *ip++ = (int)PTR2SIZE(mp - message + 1);
1225 *ip = 0;
1226 mc = (int)PTR2SIZE(ip - vector);
1227 if (mc != 1)
1228 pstate &= ~PS_MSGLIST_DIRECT;
1229 jleave:
1230 NYD_LEAVE;
1231 return mc;
1234 FL int
1235 getrawlist(char const *line, size_t linesize, char **argv, int argc,
1236 int echolist)
1238 char c, *cp2, quotec, *linebuf;
1239 int argn;
1240 NYD_ENTER;
1242 pstate &= ~PS_MSGLIST_MASK;
1244 linebuf = ac_alloc(linesize);
1246 for (argn = 0;;) {
1247 if (!argn || !echolist) {
1248 for (; blankchar(*line); ++line)
1250 if (*line == '\0')
1251 break;
1253 if (argn >= argc - 1) {
1254 n_err(_("Too many elements in the list; excess discarded\n"));
1255 break;
1258 cp2 = linebuf;
1259 for (quotec = '\0'; ((c = *line++) != '\0');) {
1260 if (quotec != '\0') {
1261 if (c == quotec) {
1262 quotec = '\0';
1263 if (echolist)
1264 *cp2++ = c;
1265 } else if (c == '\\') {
1266 switch (c = *line++) {
1267 case '\0':
1268 *cp2++ = '\\';
1269 --line;
1270 break;
1271 default:
1272 if (line[-1] != quotec || echolist)
1273 *cp2++ = '\\';
1274 *cp2++ = c;
1276 } else
1277 *cp2++ = c;
1278 } else if (c == '"' || c == '\'') {
1279 if (echolist)
1280 *cp2++ = c;
1281 quotec = c;
1282 } else if (c == '\\' && !echolist)
1283 *cp2++ = (*line != '\0') ? *line++ : c;
1284 else if (blankchar(c))
1285 break;
1286 else
1287 *cp2++ = c;
1289 argv[argn++] = savestrbuf(linebuf, PTR2SIZE(cp2 - linebuf));
1290 if (c == '\0')
1291 break;
1293 argv[argn] = NULL;
1295 ac_free(linebuf);
1296 NYD_LEAVE;
1297 return argn;
1300 FL int
1301 first(int f, int m)
1303 struct message *mp;
1304 int rv;
1305 NYD_ENTER;
1307 if (msgCount == 0) {
1308 rv = 0;
1309 goto jleave;
1312 f &= MDELETED;
1313 m &= MDELETED;
1314 for (mp = dot;
1315 mb.mb_threaded ? (mp != NULL) : PTRCMP(mp, <, message + msgCount);
1316 mb.mb_threaded ? (mp = next_in_thread(mp)) : ++mp) {
1317 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & m) == (ui32_t)f) {
1318 rv = (int)PTR2SIZE(mp - message + 1);
1319 goto jleave;
1323 if (dot > message) {
1324 for (mp = dot - 1; (mb.mb_threaded ? (mp != NULL) : (mp >= message));
1325 mb.mb_threaded ? (mp = prev_in_thread(mp)) : --mp) {
1326 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & m) == (ui32_t)f) {
1327 rv = (int)PTR2SIZE(mp - message + 1);
1328 goto jleave;
1332 rv = 0;
1333 jleave:
1334 NYD_LEAVE;
1335 return rv;
1338 FL void
1339 mark(int mesg, int f)
1341 struct message *mp;
1342 int i;
1343 NYD_ENTER;
1345 i = mesg;
1346 if (i < 1 || i > msgCount)
1347 n_panic(_("Bad message number to mark"));
1348 if (mb.mb_threaded == 1 && threadflag) {
1349 if (!(message[i - 1].m_flag & MHIDDEN)) {
1350 if (f == MDELETED || !(message[i - 1].m_flag & MDELETED))
1351 message[i - 1].m_flag |= MMARK;
1354 if (message[i - 1].m_child) {
1355 mp = message[i - 1].m_child;
1356 mark((int)PTR2SIZE(mp - message + 1), f);
1357 for (mp = mp->m_younger; mp != NULL; mp = mp->m_younger)
1358 mark((int)PTR2SIZE(mp - message + 1), f);
1360 } else
1361 message[i - 1].m_flag |= MMARK;
1362 NYD_LEAVE;
1365 /* s-it-mode */