`unaccount', `undefine': return errors as appropriate..
[s-mailx.git] / list.c
blob4619723aa8844340f70910abc813aca7eb53abbd
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 bool_t _list_saw_d, _list_last_saw_d; /* :d on its way HACK TODO */
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 if (colresult == CMDELETED) {
338 _list_saw_d = TRU1;
339 f |= MDELETED;
341 colmod |= colresult;
343 } else
344 np = add_to_namelist(&namelist, &nmlsize, np, savestr(lexstring));
345 break;
347 case TOPEN:
348 #ifdef HAVE_IMAP_SEARCH
349 pstate &= ~PS_MSGLIST_DIRECT;
350 if (imap_search(lexstring, f) == STOP)
351 markall_ret(-1)
352 topen = TRU1;
353 #else
354 n_err(_("Optional selector is not available: \"%s\"\n"),
355 lexstring);
356 markall_ret(-1)
357 #endif
358 break;
360 case TDOLLAR:
361 case TUP:
362 case TDOT:
363 case TSEMI:
364 pstate &= ~PS_MSGLIST_DIRECT;
365 lexnumber = metamess(lexstring[0], f);
366 if (lexnumber == -1)
367 markall_ret(-1)
368 goto number;
370 case TBACK:
371 pstate &= ~PS_MSGLIST_DIRECT;
372 tback = TRU1;
373 for (i = 1; i <= msgCount; i++) {
374 struct message const *mp_t = message + i - 1;
376 if (mp_t->m_flag & MHIDDEN)
377 continue;
378 if ((mp_t->m_flag & MDELETED) != (unsigned)f) {
379 if (!_list_last_saw_d)
380 continue;
381 _list_saw_d = TRU1;
383 if (mp_t->m_flag & MOLDMARK)
384 mark(i, f);
386 break;
388 case TSTAR:
389 pstate &= ~PS_MSGLIST_DIRECT;
390 if (other) {
391 n_err(_("Can't mix \"*\" with anything\n"));
392 markall_ret(-1)
394 star = TRU1;
395 break;
397 case TCOMMA:
398 pstate &= ~PS_MSGLIST_DIRECT;
399 #ifdef HAVE_IMAP
400 if (mb.mb_type == MB_IMAP && gotheaders++ == 0)
401 imap_getheaders(1, msgCount);
402 #endif
403 if (id == NULL && (cp = hfield1("in-reply-to", dot)) != NULL) {
404 id = savestr(cp);
405 idfield = ID_IN_REPLY_TO;
407 if (id == NULL && (cp = hfield1("references", dot)) != NULL) {
408 struct name *enp;
410 if ((enp = extract(cp, GREF)) != NULL) {
411 while (enp->n_flink != NULL)
412 enp = enp->n_flink;
413 id = savestr(enp->n_name);
414 idfield = ID_REFERENCES;
417 if (id == NULL) {
418 printf(_(
419 "Cannot determine parent Message-ID of the current message\n"));
420 markall_ret(-1)
422 break;
424 case TERROR:
425 pstate &= ~PS_MSGLIST_DIRECT;
426 pstate |= PS_MSGLIST_SAW_NO;
427 markall_ret(-1)
429 threadflag = 0;
430 tok = scan(&bufp);
433 np = add_to_namelist(&namelist, &nmlsize, np, NULL);
434 --np;
435 mc = FAL0;
436 if (star) {
437 for (i = 0; i < msgCount; ++i) {
438 struct message const *mp_t = message + i;
440 if (mp_t->m_flag & MHIDDEN)
441 continue;
442 if (!_list_saw_d && (mp_t->m_flag & MDELETED) != (unsigned)f)
443 continue;
444 mark(i + 1, f);
445 mc = TRU1;
448 if (!mc) {
449 if (!(pstate & PS_HOOK_MASK))
450 printf(_("No applicable messages.\n"));
451 markall_ret(-1)
453 markall_ret(0)
456 if ((topen || tback) && !mc) {
457 for (i = 0; i < msgCount; ++i)
458 if (message[i].m_flag & MMARK)
459 mc = TRU1;
460 if (!mc) {
461 if (!(pstate & PS_HOOK_MASK)) {
462 if (tback)
463 n_err(_("No previously marked messages\n"));
464 else
465 printf(_("No messages satisfy (criteria)\n"));
467 markall_ret(-1)
471 /* If no numbers were given, mark all messages, so that we can unmark
472 * any whose sender was not selected if any user names were given */
473 if ((np > namelist || colmod != 0 || id) && !mc) {
474 for (i = 1; i <= msgCount; ++i) {
475 if (!(message[i - 1].m_flag & MHIDDEN) &&
476 (message[i - 1].m_flag & MDELETED) == (unsigned)f)
477 mark(i, f);
481 /* If any names were given, eliminate any messages which don't match */
482 if (np > namelist || id) {
483 struct search_expr *sep = NULL;
484 bool_t allnet;
486 /* The @ search works with struct search_expr, so build an array.
487 * To simplify array, i.e., regex_t destruction, and optimize for the
488 * common case we walk the entire array even in case of errors */
489 if (np > namelist) {
490 sep = scalloc(PTR2SIZE(np - namelist), sizeof(*sep));
491 for (j = 0, nq = namelist; *nq != NULL; ++j, ++nq) {
492 char *x = *nq, *y;
494 sep[j].ss_sexpr = x;
495 if (*x != '@' || rv < 0)
496 continue;
498 for (y = x + 1;; ++y) {
499 if (*y == '\0' || !fieldnamechar(*y)) {
500 x = NULL;
501 break;
503 if (*y == '@') {
504 x = y;
505 break;
508 sep[j].ss_where = (x == NULL || x - 1 == *nq)
509 ? "subject" : savestrbuf(*nq + 1, PTR2SIZE(x - *nq) - 1);
511 x = (x == NULL ? *nq : x) + 1;
512 if (*x == '\0') { /* XXX Simply remove from list instead? */
513 n_err(_("Empty \"[@..]@\" search expression\n"));
514 rv = -1;
515 continue;
517 #ifdef HAVE_REGEX
518 if (is_maybe_regex(x)) {
519 sep[j].ss_sexpr = NULL;
520 if (regcomp(&sep[j].ss_regex, x,
521 REG_EXTENDED | REG_ICASE | REG_NOSUB) != 0) {
522 n_err(_("Invalid regular expression: >>> %s <<<\n"), x);
523 rv = -1;
524 continue;
526 } else
527 #endif
528 sep[j].ss_sexpr = x;
530 if (rv < 0)
531 goto jnamesearch_sepfree;
534 #ifdef HAVE_IMAP
535 if (mb.mb_type == MB_IMAP && gotheaders++ == 0)
536 imap_getheaders(1, msgCount);
537 #endif
538 srelax_hold();
539 allnet = ok_blook(allnet);
540 for (i = 1; i <= msgCount; ++i) {
541 mp = message + i - 1;
542 j = 0;
543 if (np > namelist) {
544 for (nq = namelist; *nq != NULL; ++nq) {
545 if (**nq == '@') {
546 if (_match_at(mp, sep + PTR2SIZE(nq - namelist))) {
547 ++j;
548 break;
550 } else if (**nq == '/') {
551 if (_match_dash(mp, *nq)) {
552 ++j;
553 break;
555 } else if (_matchsender(mp, *nq, allnet)) {
556 ++j;
557 break;
561 if (j == 0 && id && _matchmid(mp, id, idfield))
562 ++j;
563 if (j == 0)
564 mp->m_flag &= ~MMARK;
565 srelax();
567 srelax_rele();
569 /* Make sure we got some decent messages */
570 j = 0;
571 for (i = 1; i <= msgCount; ++i)
572 if (message[i - 1].m_flag & MMARK) {
573 ++j;
574 break;
576 if (j == 0) {
577 if (!(pstate & PS_HOOK_MASK) && np > namelist) {
578 printf(_("No applicable messages from {%s"), namelist[0]);
579 for (nq = namelist + 1; *nq != NULL; ++nq)
580 printf(_(", %s"), *nq);
581 printf(_("}\n"));
582 } else if (id)
583 printf(_("Parent message not found\n"));
584 rv = -1;
585 goto jnamesearch_sepfree;
588 jnamesearch_sepfree:
589 if (sep != NULL) {
590 #ifdef HAVE_REGEX
591 for (j = PTR2SIZE(np - namelist); j-- != 0;)
592 if (sep[j].ss_sexpr == NULL)
593 regfree(&sep[j].ss_regex);
594 #endif
595 free(sep);
597 if (rv < 0)
598 goto jleave;
601 /* If any colon modifiers were given, go through and unmark any
602 * messages which do not satisfy the modifiers */
603 if (colmod != 0) {
604 for (i = 1; i <= msgCount; ++i) {
605 struct coltab const *colp;
606 bool_t bad = TRU1;
608 mp = message + i - 1;
609 for (colp = _coltab; colp->co_char != '\0'; ++colp)
610 if ((colp->co_bit & colmod) &&
611 ((mp->m_flag & colp->co_mask) == (unsigned)colp->co_equal)) {
612 bad = FAL0;
613 break;
615 if (bad)
616 unmark(i);
619 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
620 if (mp->m_flag & MMARK)
621 break;
623 if (PTRCMP(mp, >=, message + msgCount)) {
624 struct coltab const *colp;
626 if (!(pstate & PS_HOOK_MASK)) {
627 printf(_("No messages satisfy"));
628 for (colp = _coltab; colp->co_char != '\0'; ++colp)
629 if (colp->co_bit & colmod)
630 printf(" :%c", colp->co_char);
631 printf("\n");
633 markall_ret(-1)
637 markall_ret(0)
638 jleave:
639 free(namelist);
640 ac_free(lexstring);
641 NYD_LEAVE;
642 return rv;
644 #undef markall_ret
647 static int
648 evalcol(int col)
650 struct coltab const *colp;
651 int rv;
652 NYD_ENTER;
654 rv = 0;
655 for (colp = _coltab; colp->co_char != '\0'; ++colp)
656 if (colp->co_char == col) {
657 rv = colp->co_bit;
658 break;
660 NYD_LEAVE;
661 return rv;
664 static int
665 check(int mesg, int f)
667 struct message *mp;
668 NYD_ENTER;
670 if (mesg < 1 || mesg > msgCount) {
671 printf(_("%d: Invalid message number\n"), mesg);
672 goto jem1;
674 mp = message + mesg - 1;
675 if (mp->m_flag & MHIDDEN ||
676 (f != MDELETED && (mp->m_flag & MDELETED) != 0)) {
677 n_err(_("%d: inappropriate message\n"), mesg);
678 goto jem1;
680 f = 0;
681 jleave:
682 NYD_LEAVE;
683 return f;
684 jem1:
685 f = -1;
686 goto jleave;
689 static int
690 scan(char **sp)
692 char *cp, *cp2;
693 int rv, c, inquote, quotec;
694 struct lex const *lp;
695 NYD_ENTER;
697 if (regretp >= 0) {
698 strncpy(lexstring, string_stack[regretp], STRINGLEN);
699 lexstring[STRINGLEN -1] = '\0';
700 lexnumber = numberstack[regretp];
701 rv = regretstack[regretp--];
702 goto jleave;
705 cp = *sp;
706 cp2 = lexstring;
707 c = *cp++;
709 /* strip away leading white space */
710 while (blankchar(c))
711 c = *cp++;
713 /* If no characters remain, we are at end of line, so report that */
714 if (c == '\0') {
715 *sp = --cp;
716 rv = TEOL;
717 goto jleave;
720 /* Select members of a message thread */
721 if (c == '&') {
722 threadflag = 1;
723 if (*cp == '\0' || spacechar(*cp)) {
724 lexstring[0] = '.';
725 lexstring[1] = '\0';
726 *sp = cp;
727 rv = TDOT;
728 goto jleave;
730 c = *cp++;
733 /* If the leading character is a digit, scan the number and convert it
734 * on the fly. Return TNUMBER when done */
735 if (digitchar(c)) {
736 lexnumber = 0;
737 while (digitchar(c)) {
738 lexnumber = lexnumber*10 + c - '0';
739 *cp2++ = c;
740 c = *cp++;
742 *cp2 = '\0';
743 *sp = --cp;
744 rv = TNUMBER;
745 goto jleave;
748 /* An IMAP SEARCH list. Note that TOPEN has always been included in
749 * singles[] in Mail and mailx. Thus although there is no formal
750 * definition for (LIST) lists, they do not collide with historical
751 * practice because a subject string (LIST) could never been matched
752 * this way */
753 if (c == '(') {
754 ui32_t level = 1;
755 inquote = 0;
756 *cp2++ = c;
757 do {
758 if ((c = *cp++&0377) == '\0') {
759 jmtop:
760 n_err(_("Missing \")\"\n"));
761 rv = TERROR;
762 goto jleave;
764 if (inquote && c == '\\') {
765 *cp2++ = c;
766 c = *cp++&0377;
767 if (c == '\0')
768 goto jmtop;
769 } else if (c == '"')
770 inquote = !inquote;
771 else if (inquote)
772 /*EMPTY*/;
773 else if (c == '(')
774 ++level;
775 else if (c == ')')
776 --level;
777 else if (spacechar(c)) {
778 /* Replace unquoted whitespace by single space characters, to make
779 * the string IMAP SEARCH conformant */
780 c = ' ';
781 if (cp2[-1] == ' ')
782 --cp2;
784 *cp2++ = c;
785 } while (c != ')' || level > 0);
786 *cp2 = '\0';
787 *sp = cp;
788 rv = TOPEN;
789 goto jleave;
792 /* Check for single character tokens; return such if found */
793 for (lp = _singles; lp->l_char != '\0'; ++lp)
794 if (c == lp->l_char) {
795 lexstring[0] = c;
796 lexstring[1] = '\0';
797 *sp = cp;
798 rv = lp->l_token;
799 goto jleave;
802 /* We've got a string! Copy all the characters of the string into
803 * lexstring, until we see a null, space, or tab. If the lead character is
804 * a " or ', save it and scan until you get another */
805 quotec = 0;
806 if (c == '\'' || c == '"') {
807 quotec = c;
808 c = *cp++;
810 while (c != '\0') {
811 if (quotec == 0 && c == '\\' && *cp != '\0')
812 c = *cp++;
813 if (c == quotec) {
814 ++cp;
815 break;
817 if (quotec == 0 && blankchar(c))
818 break;
819 if (PTRCMP(cp2 - lexstring, <, STRINGLEN - 1))
820 *cp2++ = c;
821 c = *cp++;
823 if (quotec && c == 0) {
824 n_err(_("Missing %c\n"), quotec);
825 rv = TERROR;
826 goto jleave;
828 *sp = --cp;
829 *cp2 = '\0';
830 rv = TSTRING;
831 jleave:
832 NYD_LEAVE;
833 return rv;
836 static void
837 regret(int token)
839 NYD_ENTER;
840 if (++regretp >= REGDEP)
841 n_panic(_("Too many regrets"));
842 regretstack[regretp] = token;
843 lexstring[STRINGLEN -1] = '\0';
844 string_stack[regretp] = savestr(lexstring);
845 numberstack[regretp] = lexnumber;
846 NYD_LEAVE;
849 static void
850 scaninit(void)
852 NYD_ENTER;
853 regretp = -1;
854 threadflag = 0;
855 NYD_LEAVE;
858 static bool_t
859 _matchsender(struct message *mp, char const *str, bool_t allnet)
861 char const *str_base, *np_base, *np;
862 char sc, nc;
863 bool_t rv;
864 NYD_ENTER;
866 /* Empty string doesn't match */
867 if (*(str_base = str) == '\0') {
868 rv = FAL0;
869 goto jleave;
872 /* *allnet* is POSIX and, since it explicitly mentions login and user names,
873 * most likely case-sensitive. XXX Still allow substr matching, though
874 * XXX possibly the first letter should be case-insensitive, then? */
875 if (allnet) {
876 np_base = np = nameof(mp, 0);
877 for (;;) {
878 if ((sc = *str++) == '@')
879 sc = '\0';
880 if ((nc = *np++) == '@' || nc == '\0' || sc == '\0')
881 break;
882 if (sc != nc) {
883 np = ++np_base;
884 str = str_base;
887 rv = (sc == '\0');
888 } else {
889 char const *real_base = name1(mp, 0);
890 bool_t again = ok_blook(showname);
892 /* TODO POSIX says ~"match any address as shown in header overview",
893 * TODO but a normalized match would be more sane i guess.
894 * TODO struct name should gain a comparison method, normalize realname
895 * TODO content (in TODO) and thus match as likewise
896 * TODO "Buddy (Today) <here>" and "(Now) Buddy <here>" */
897 jagain:
898 np_base = np = again ? realname(real_base) : skin(real_base);
899 for (;;) {
900 sc = *str++;
901 if ((nc = *np++) == '\0' || sc == '\0')
902 break;
903 sc = upperconv(sc);
904 nc = upperconv(nc);
905 if (sc != nc) {
906 np = ++np_base;
907 str = str_base;
911 /* And really if i want to match 'on@' then i want it to match even if
912 * *showname* is set! */
913 if (!(rv = (sc == '\0')) && again) {
914 again = FAL0;
915 goto jagain;
918 jleave:
919 NYD_LEAVE;
920 return rv;
923 static bool_t
924 _matchmid(struct message *mp, char *id, enum idfield idfield)
926 char *cp;
927 bool_t rv;
928 NYD_ENTER;
930 if ((cp = hfield1("message-id", mp)) != NULL) {
931 switch (idfield) {
932 case ID_REFERENCES:
933 rv = !msgidcmp(id, cp);
934 goto jleave;
935 case ID_IN_REPLY_TO: {
936 struct name *np;
938 if ((np = extract(id, GREF)) != NULL)
939 do {
940 if (!msgidcmp(np->n_name, cp)) {
941 rv = TRU1;
942 goto jleave;
944 } while ((np = np->n_flink) != NULL);
945 break;
949 rv = FAL0;
950 jleave:
951 NYD_LEAVE;
952 return rv;
955 static bool_t
956 _match_dash(struct message *mp, char const *str)
958 static char lastscan[128];
960 struct str in, out;
961 char *hfield, *hbody;
962 bool_t rv;
963 NYD_ENTER;
965 if (*++str == '\0') {
966 str = lastscan;
967 } else {
968 strncpy(lastscan, str, sizeof lastscan); /* XXX use new n_str object! */
969 lastscan[sizeof lastscan -1] = '\0';
972 /* Now look, ignoring case, for the word in the string */
973 if (ok_blook(searchheaders) && (hfield = strchr(str, ':'))) {
974 size_t l = PTR2SIZE(hfield - str);
975 hfield = ac_alloc(l +1);
976 memcpy(hfield, str, l);
977 hfield[l] = '\0';
978 hbody = hfieldX(hfield, mp);
979 ac_free(hfield);
980 hfield = UNCONST(str + l + 1);
981 } else {
982 hfield = UNCONST(str);
983 hbody = hfield1("subject", mp);
985 if (hbody == NULL) {
986 rv = FAL0;
987 goto jleave;
990 in.s = hbody;
991 in.l = strlen(hbody);
992 mime_fromhdr(&in, &out, TD_ICONV);
993 rv = substr(out.s, hfield);
994 free(out.s);
995 jleave:
996 NYD_LEAVE;
997 return rv;
1000 static bool_t
1001 _match_at(struct message *mp, struct search_expr *sep)
1003 struct str in, out;
1004 char *nfield;
1005 char const *cfield;
1006 bool_t rv = FAL0;
1007 NYD_ENTER;
1009 nfield = savestr(sep->ss_where);
1011 while ((cfield = n_strsep(&nfield, ',', TRU1)) != NULL) {
1012 if (!asccasecmp(cfield, "body") ||
1013 (cfield[1] == '\0' && cfield[0] == '>')) {
1014 rv = FAL0;
1015 jmsg:
1016 if ((rv = message_match(mp, sep, rv)))
1017 break;
1018 continue;
1020 if (!asccasecmp(cfield, "text") ||
1021 (cfield[1] == '\0' && cfield[0] == '=')) {
1022 rv = TRU1;
1023 goto jmsg;
1026 if (!asccasecmp(cfield, "header") ||
1027 (cfield[1] == '\0' && cfield[0] == '<')) {
1028 if ((rv = header_match(mp, sep)))
1029 break;
1030 continue;
1033 /* This is not a special name, so take care for the "skin" prefix !
1034 * and possible abbreviations */
1036 struct name *np;
1037 bool_t doskin;
1039 if ((doskin = (*cfield == '~')))
1040 ++cfield;
1041 if (cfield[0] != '\0' && cfield[1] == '\0') {
1042 char const x[][8] = {
1043 "from", "to", "cc", "bcc", "subject"
1045 size_t i;
1046 char c1 = lowerconv(cfield[0]);
1048 for (i = 0; i < NELEM(x); ++i) {
1049 if (c1 == x[i][0]) {
1050 cfield = x[i];
1051 break;
1055 if ((in.s = hfieldX(cfield, mp)) == NULL)
1056 continue;
1058 /* Shall we split into address list and match the addresses only? */
1059 if (doskin) {
1060 np = lextract(in.s, GSKIN);
1061 if (np == NULL)
1062 continue;
1063 out.s = np->n_name;
1064 } else {
1065 np = NULL;
1066 in.l = strlen(in.s);
1067 mime_fromhdr(&in, &out, TD_ICONV);
1069 jnext_name:
1070 #ifdef HAVE_REGEX
1071 if (sep->ss_sexpr == NULL)
1072 rv = (regexec(&sep->ss_regex, out.s, 0,NULL, 0) != REG_NOMATCH);
1073 else
1074 #endif
1075 rv = substr(out.s, sep->ss_sexpr);
1076 if (np == NULL)
1077 free(out.s);
1078 if (rv)
1079 break;
1080 if (np != NULL && (np = np->n_flink) != NULL) {
1081 out.s = np->n_name;
1082 goto jnext_name;
1086 NYD_LEAVE;
1087 return rv;
1090 static void
1091 unmark(int mesg)
1093 size_t i;
1094 NYD_ENTER;
1096 i = (size_t)mesg;
1097 if (i < 1 || UICMP(z, i, >, msgCount))
1098 n_panic(_("Bad message number to unmark"));
1099 message[i - 1].m_flag &= ~MMARK;
1100 NYD_LEAVE;
1103 static int
1104 metamess(int meta, int f)
1106 int c, m;
1107 struct message *mp;
1108 NYD_ENTER;
1110 c = meta;
1111 switch (c) {
1112 case '^': /* First 'good' message left */
1113 mp = mb.mb_threaded ? threadroot : message;
1114 while (PTRCMP(mp, <, message + msgCount)) {
1115 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & MDELETED) == (ui32_t)f) {
1116 c = (int)PTR2SIZE(mp - message + 1);
1117 goto jleave;
1119 if (mb.mb_threaded) {
1120 mp = next_in_thread(mp);
1121 if (mp == NULL)
1122 break;
1123 } else
1124 ++mp;
1126 if (!(pstate & PS_HOOK_MASK))
1127 printf(_("No applicable messages\n"));
1128 goto jem1;
1130 case '$': /* Last 'good message left */
1131 mp = mb.mb_threaded
1132 ? this_in_thread(threadroot, -1) : message + msgCount - 1;
1133 while (mp >= message) {
1134 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & MDELETED) == (ui32_t)f) {
1135 c = (int)PTR2SIZE(mp - message + 1);
1136 goto jleave;
1138 if (mb.mb_threaded) {
1139 mp = prev_in_thread(mp);
1140 if (mp == NULL)
1141 break;
1142 } else
1143 --mp;
1145 if (!(pstate & PS_HOOK_MASK))
1146 printf(_("No applicable messages\n"));
1147 goto jem1;
1149 case '.':
1150 /* Current message */
1151 m = dot - message + 1;
1152 if ((dot->m_flag & MHIDDEN) || (dot->m_flag & MDELETED) != (ui32_t)f) {
1153 printf(_("%d: inappropriate message\n"), m);
1154 goto jem1;
1156 c = m;
1157 break;
1159 case ';':
1160 /* Previously current message */
1161 if (prevdot == NULL) {
1162 n_err(_("No previously current message\n"));
1163 goto jem1;
1165 m = prevdot - message + 1;
1166 if ((prevdot->m_flag & MHIDDEN) ||
1167 (prevdot->m_flag & MDELETED) != (ui32_t)f) {
1168 n_err(_("%d: inappropriate message\n"), m);
1169 goto jem1;
1171 c = m;
1172 break;
1174 default:
1175 n_err(_("Unknown metachar (%c)\n"), c);
1176 goto jem1;
1178 jleave:
1179 NYD_LEAVE;
1180 return c;
1181 jem1:
1182 c = -1;
1183 goto jleave;
1186 FL int
1187 getmsglist(char *buf, int *vector, int flags)
1189 int *ip, mc;
1190 struct message *mp;
1191 NYD_ENTER;
1193 pstate &= ~PS_MSGLIST_MASK;
1194 _list_last_saw_d = _list_saw_d;
1195 _list_saw_d = FAL0;
1197 if (msgCount == 0) {
1198 *vector = 0;
1199 mc = 0;
1200 goto jleave;
1203 pstate |= PS_MSGLIST_DIRECT;
1205 if (markall(buf, flags) < 0) {
1206 mc = -1;
1207 goto jleave;
1210 ip = vector;
1211 if (pstate & PS_HOOK_NEWMAIL) {
1212 mc = 0;
1213 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
1214 if (mp->m_flag & MMARK) {
1215 if (!(mp->m_flag & MNEWEST))
1216 unmark((int)PTR2SIZE(mp - message + 1));
1217 else
1218 ++mc;
1220 if (mc == 0) {
1221 mc = -1;
1222 goto jleave;
1226 if (mb.mb_threaded == 0) {
1227 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
1228 if (mp->m_flag & MMARK)
1229 *ip++ = (int)PTR2SIZE(mp - message + 1);
1230 } else {
1231 for (mp = threadroot; mp != NULL; mp = next_in_thread(mp))
1232 if (mp->m_flag & MMARK)
1233 *ip++ = (int)PTR2SIZE(mp - message + 1);
1235 *ip = 0;
1236 mc = (int)PTR2SIZE(ip - vector);
1237 if (mc != 1)
1238 pstate &= ~PS_MSGLIST_DIRECT;
1239 jleave:
1240 NYD_LEAVE;
1241 return mc;
1244 FL int
1245 getrawlist(char const *line, size_t linesize, char **argv, int argc,
1246 int echolist)
1248 char c, *cp2, quotec, *linebuf;
1249 int argn;
1250 NYD_ENTER;
1252 pstate &= ~PS_MSGLIST_MASK;
1254 linebuf = ac_alloc(linesize);
1256 for (argn = 0;;) {
1257 if (!argn || !echolist) {
1258 for (; blankchar(*line); ++line)
1260 if (*line == '\0')
1261 break;
1263 if (argn >= argc - 1) {
1264 n_err(_("Too many elements in the list; excess discarded\n"));
1265 break;
1268 cp2 = linebuf;
1269 for (quotec = '\0'; ((c = *line++) != '\0');) {
1270 if (quotec != '\0') {
1271 if (c == quotec) {
1272 quotec = '\0';
1273 if (echolist)
1274 *cp2++ = c;
1275 } else if (c == '\\') {
1276 switch (c = *line++) {
1277 case '\0':
1278 *cp2++ = '\\';
1279 --line;
1280 break;
1281 default:
1282 if (line[-1] != quotec || echolist)
1283 *cp2++ = '\\';
1284 *cp2++ = c;
1286 } else
1287 *cp2++ = c;
1288 } else if (c == '"' || c == '\'') {
1289 if (echolist)
1290 *cp2++ = c;
1291 quotec = c;
1292 } else if (c == '\\' && !echolist)
1293 *cp2++ = (*line != '\0') ? *line++ : c;
1294 else if (blankchar(c))
1295 break;
1296 else
1297 *cp2++ = c;
1299 argv[argn++] = savestrbuf(linebuf, PTR2SIZE(cp2 - linebuf));
1300 if (c == '\0')
1301 break;
1303 argv[argn] = NULL;
1305 ac_free(linebuf);
1306 NYD_LEAVE;
1307 return argn;
1310 FL int
1311 first(int f, int m)
1313 struct message *mp;
1314 int rv;
1315 NYD_ENTER;
1317 if (msgCount == 0) {
1318 rv = 0;
1319 goto jleave;
1322 f &= MDELETED;
1323 m &= MDELETED;
1324 for (mp = dot;
1325 mb.mb_threaded ? (mp != NULL) : PTRCMP(mp, <, message + msgCount);
1326 mb.mb_threaded ? (mp = next_in_thread(mp)) : ++mp) {
1327 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & m) == (ui32_t)f) {
1328 rv = (int)PTR2SIZE(mp - message + 1);
1329 goto jleave;
1333 if (dot > message) {
1334 for (mp = dot - 1; (mb.mb_threaded ? (mp != NULL) : (mp >= message));
1335 mb.mb_threaded ? (mp = prev_in_thread(mp)) : --mp) {
1336 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & m) == (ui32_t)f) {
1337 rv = (int)PTR2SIZE(mp - message + 1);
1338 goto jleave;
1342 rv = 0;
1343 jleave:
1344 NYD_LEAVE;
1345 return rv;
1348 FL void
1349 mark(int mesg, int f)
1351 struct message *mp;
1352 int i;
1353 NYD_ENTER;
1355 i = mesg;
1356 if (i < 1 || i > msgCount)
1357 n_panic(_("Bad message number to mark"));
1359 if (mb.mb_threaded == 1 && threadflag) {
1360 struct message *mp_t = message + i - 1;
1362 if (!(mp_t->m_flag & MHIDDEN)) {
1363 if (f == MDELETED || !(mp_t->m_flag & MDELETED) || _list_saw_d)
1364 mp_t->m_flag |= MMARK;
1367 if (mp_t->m_child != NULL) {
1368 mp = mp_t->m_child;
1369 mark((int)PTR2SIZE(mp - message + 1), f);
1370 for (mp = mp->m_younger; mp != NULL; mp = mp->m_younger)
1371 mark((int)PTR2SIZE(mp - message + 1), f);
1373 } else
1374 message[i - 1].m_flag |= MMARK;
1375 NYD_LEAVE;
1378 /* s-it-mode */