Merge branch 'topic/shoens-2000'
[s-mailx.git] / list.c
blob84855759cb3f8874f6b1c7a9a255a8f23ad4eef6
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 NYD_ENTER;
211 lexstring = ac_alloc(STRINGLEN = 2 * strlen(buf) +1);
212 valdot = (int)PTR2SIZE(dot - message + 1);
213 colmod = 0;
215 for (i = 1; i <= msgCount; ++i) {
216 enum mflag mf;
218 mf = message[i - 1].m_flag;
219 if (mf & MMARK)
220 mf |= MOLDMARK;
221 else
222 mf &= ~MOLDMARK;
223 mf &= ~MMARK;
224 message[i - 1].m_flag = mf;
227 np = namelist = smalloc((nmlsize = 8) * sizeof *namelist);
228 scaninit();
229 bufp = buf;
230 mc = FAL0;
231 beg = star = other = topen = tback = FAL0;
233 for (tok = scan(&bufp); tok != TEOL;) {
234 switch (tok) {
235 case TNUMBER:
236 number:
237 if (star) {
238 n_err(_("No numbers mixed with *\n"));
239 markall_ret(-1)
241 pstate |= PS_MSGLIST_SAW_NO;
242 ++other;
243 if (beg != 0) {
244 if (check(lexnumber, f))
245 markall_ret(-1)
246 i = beg;
247 while (mb.mb_threaded ? 1 : i <= lexnumber) {
248 if (!(message[i - 1].m_flag & MHIDDEN) &&
249 (f == MDELETED || !(message[i - 1].m_flag & MDELETED)))
250 mark(i, f);
251 if (mb.mb_threaded) {
252 if (i == lexnumber)
253 break;
254 mx = next_in_thread(&message[i - 1]);
255 if (mx == NULL)
256 markall_ret(-1)
257 i = (int)PTR2SIZE(mx - message + 1);
258 } else
259 ++i;
261 beg = 0;
262 break;
264 beg = lexnumber;
265 if (check(beg, f))
266 markall_ret(-1)
267 tok = scan(&bufp);
268 regret(tok);
269 if (tok != TDASH) {
270 mark(beg, f);
271 beg = 0;
273 break;
275 case TPLUS:
276 pstate &= ~PS_MSGLIST_DIRECT;
277 if (beg != 0) {
278 printf(_("Non-numeric second argument\n"));
279 markall_ret(-1)
281 i = valdot;
282 do {
283 if (mb.mb_threaded) {
284 mx = next_in_thread(message + i - 1);
285 i = mx ? (int)PTR2SIZE(mx - message + 1) : msgCount + 1;
286 } else
287 ++i;
288 if (i > msgCount) {
289 n_err(_("Referencing beyond EOF\n"));
290 markall_ret(-1)
292 } while (message[i - 1].m_flag == MHIDDEN ||
293 (message[i - 1].m_flag & MDELETED) != (unsigned)f);
294 mark(i, f);
295 break;
297 case TDASH:
298 pstate &= ~PS_MSGLIST_DIRECT;
299 if (beg == 0) {
300 i = valdot;
301 do {
302 if (mb.mb_threaded) {
303 mx = prev_in_thread(message + i - 1);
304 i = mx ? (int)PTR2SIZE(mx - message + 1) : 0;
305 } else
306 --i;
307 if (i <= 0) {
308 n_err(_("Referencing before 1\n"));
309 markall_ret(-1)
311 } while ((message[i - 1].m_flag & MHIDDEN) ||
312 (message[i - 1].m_flag & MDELETED) != (unsigned)f);
313 mark(i, f);
315 break;
317 case TSTRING:
318 pstate &= ~PS_MSGLIST_DIRECT;
319 if (beg != 0) {
320 n_err(_("Non-numeric second argument\n"));
321 markall_ret(-1)
323 ++other;
324 if ((cp = lexstring)[0] == ':') {
325 while (*++cp != '\0') {
326 colresult = evalcol(*cp);
327 if (colresult == 0) {
328 n_err(_("Unknown colon modifier \"%s\"\n"), lexstring);
329 markall_ret(-1)
331 if (colresult == CMDELETED) {
332 _list_saw_d = TRU1;
333 f |= MDELETED;
335 colmod |= colresult;
337 } else
338 np = add_to_namelist(&namelist, &nmlsize, np, savestr(lexstring));
339 break;
341 case TOPEN:
342 #ifdef HAVE_IMAP_SEARCH
343 pstate &= ~PS_MSGLIST_DIRECT;
344 if (imap_search(lexstring, f) == STOP)
345 markall_ret(-1)
346 topen = TRU1;
347 #else
348 n_err(_("Optional selector is not available: \"%s\"\n"),
349 lexstring);
350 markall_ret(-1)
351 #endif
352 break;
354 case TDOLLAR:
355 case TUP:
356 case TDOT:
357 case TSEMI:
358 pstate &= ~PS_MSGLIST_DIRECT;
359 lexnumber = metamess(lexstring[0], f);
360 if (lexnumber == -1)
361 markall_ret(-1)
362 goto number;
364 case TBACK:
365 pstate &= ~PS_MSGLIST_DIRECT;
366 tback = TRU1;
367 for (i = 1; i <= msgCount; i++) {
368 struct message const *mp_t = message + i - 1;
370 if (mp_t->m_flag & MHIDDEN)
371 continue;
372 if ((mp_t->m_flag & MDELETED) != (unsigned)f) {
373 if (!_list_last_saw_d)
374 continue;
375 _list_saw_d = TRU1;
377 if (mp_t->m_flag & MOLDMARK)
378 mark(i, f);
380 break;
382 case TSTAR:
383 pstate &= ~PS_MSGLIST_DIRECT;
384 if (other) {
385 n_err(_("Can't mix \"*\" with anything\n"));
386 markall_ret(-1)
388 star = TRU1;
389 break;
391 case TCOMMA:
392 pstate &= ~PS_MSGLIST_DIRECT;
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 np = add_to_namelist(&namelist, &nmlsize, np, NULL);
424 --np;
425 mc = FAL0;
426 if (star) {
427 for (i = 0; i < msgCount; ++i) {
428 struct message const *mp_t = message + i;
430 if (mp_t->m_flag & MHIDDEN)
431 continue;
432 if (!_list_saw_d && (mp_t->m_flag & MDELETED) != (unsigned)f)
433 continue;
434 mark(i + 1, f);
435 mc = TRU1;
438 if (!mc) {
439 if (!(pstate & PS_HOOK_MASK))
440 printf(_("No applicable messages.\n"));
441 markall_ret(-1)
443 markall_ret(0)
446 if ((topen || tback) && !mc) {
447 for (i = 0; i < msgCount; ++i)
448 if (message[i].m_flag & MMARK)
449 mc = TRU1;
450 if (!mc) {
451 if (!(pstate & PS_HOOK_MASK)) {
452 if (tback)
453 n_err(_("No previously marked messages\n"));
454 else
455 printf(_("No messages satisfy (criteria)\n"));
457 markall_ret(-1)
461 /* If no numbers were given, mark all messages, so that we can unmark
462 * any whose sender was not selected if any user names were given */
463 if ((np > namelist || colmod != 0 || id) && !mc) {
464 for (i = 1; i <= msgCount; ++i) {
465 if (!(message[i - 1].m_flag & MHIDDEN) &&
466 (message[i - 1].m_flag & MDELETED) == (unsigned)f)
467 mark(i, f);
471 /* If any names were given, eliminate any messages which don't match */
472 if (np > namelist || id) {
473 struct search_expr *sep = NULL;
474 bool_t allnet;
476 /* The @ search works with struct search_expr, so build an array.
477 * To simplify array, i.e., regex_t destruction, and optimize for the
478 * common case we walk the entire array even in case of errors */
479 if (np > namelist) {
480 sep = scalloc(PTR2SIZE(np - namelist), sizeof(*sep));
481 for (j = 0, nq = namelist; *nq != NULL; ++j, ++nq) {
482 char *x = *nq, *y;
484 sep[j].ss_sexpr = x;
485 if (*x != '@' || rv < 0)
486 continue;
488 for (y = x + 1;; ++y) {
489 if (*y == '\0' || !fieldnamechar(*y)) {
490 x = NULL;
491 break;
493 if (*y == '@') {
494 x = y;
495 break;
498 sep[j].ss_where = (x == NULL || x - 1 == *nq)
499 ? "subject" : savestrbuf(*nq + 1, PTR2SIZE(x - *nq) - 1);
501 x = (x == NULL ? *nq : x) + 1;
502 if (*x == '\0') { /* XXX Simply remove from list instead? */
503 n_err(_("Empty \"[@..]@\" search expression\n"));
504 rv = -1;
505 continue;
507 #ifdef HAVE_REGEX
508 if (is_maybe_regex(x)) {
509 sep[j].ss_sexpr = NULL;
510 if (regcomp(&sep[j].ss_regex, x,
511 REG_EXTENDED | REG_ICASE | REG_NOSUB) != 0) {
512 n_err(_("Invalid regular expression: >>> %s <<<\n"), x);
513 rv = -1;
514 continue;
516 } else
517 #endif
518 sep[j].ss_sexpr = x;
520 if (rv < 0)
521 goto jnamesearch_sepfree;
524 srelax_hold();
525 allnet = ok_blook(allnet);
526 for (i = 1; i <= msgCount; ++i) {
527 mp = message + i - 1;
528 j = 0;
529 if (np > namelist) {
530 for (nq = namelist; *nq != NULL; ++nq) {
531 if (**nq == '@') {
532 if (_match_at(mp, sep + PTR2SIZE(nq - namelist))) {
533 ++j;
534 break;
536 } else if (**nq == '/') {
537 if (_match_dash(mp, *nq)) {
538 ++j;
539 break;
541 } else if (_matchsender(mp, *nq, allnet)) {
542 ++j;
543 break;
547 if (j == 0 && id && _matchmid(mp, id, idfield))
548 ++j;
549 if (j == 0)
550 mp->m_flag &= ~MMARK;
551 srelax();
553 srelax_rele();
555 /* Make sure we got some decent messages */
556 j = 0;
557 for (i = 1; i <= msgCount; ++i)
558 if (message[i - 1].m_flag & MMARK) {
559 ++j;
560 break;
562 if (j == 0) {
563 if (!(pstate & PS_HOOK_MASK) && np > namelist) {
564 printf(_("No applicable messages from {%s"), namelist[0]);
565 for (nq = namelist + 1; *nq != NULL; ++nq)
566 printf(_(", %s"), *nq);
567 printf(_("}\n"));
568 } else if (id)
569 printf(_("Parent message not found\n"));
570 rv = -1;
571 goto jnamesearch_sepfree;
574 jnamesearch_sepfree:
575 if (sep != NULL) {
576 #ifdef HAVE_REGEX
577 for (j = PTR2SIZE(np - namelist); j-- != 0;)
578 if (sep[j].ss_sexpr == NULL)
579 regfree(&sep[j].ss_regex);
580 #endif
581 free(sep);
583 if (rv < 0)
584 goto jleave;
587 /* If any colon modifiers were given, go through and unmark any
588 * messages which do not satisfy the modifiers */
589 if (colmod != 0) {
590 for (i = 1; i <= msgCount; ++i) {
591 struct coltab const *colp;
592 bool_t bad = TRU1;
594 mp = message + i - 1;
595 for (colp = _coltab; colp->co_char != '\0'; ++colp)
596 if ((colp->co_bit & colmod) &&
597 ((mp->m_flag & colp->co_mask) == (unsigned)colp->co_equal)) {
598 bad = FAL0;
599 break;
601 if (bad)
602 unmark(i);
605 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
606 if (mp->m_flag & MMARK)
607 break;
609 if (PTRCMP(mp, >=, message + msgCount)) {
610 struct coltab const *colp;
612 if (!(pstate & PS_HOOK_MASK)) {
613 printf(_("No messages satisfy"));
614 for (colp = _coltab; colp->co_char != '\0'; ++colp)
615 if (colp->co_bit & colmod)
616 printf(" :%c", colp->co_char);
617 printf("\n");
619 markall_ret(-1)
623 markall_ret(0)
624 jleave:
625 free(namelist);
626 ac_free(lexstring);
627 NYD_LEAVE;
628 return rv;
630 #undef markall_ret
633 static int
634 evalcol(int col)
636 struct coltab const *colp;
637 int rv;
638 NYD_ENTER;
640 rv = 0;
641 for (colp = _coltab; colp->co_char != '\0'; ++colp)
642 if (colp->co_char == col) {
643 rv = colp->co_bit;
644 break;
646 NYD_LEAVE;
647 return rv;
650 static int
651 check(int mesg, int f)
653 struct message *mp;
654 NYD_ENTER;
656 if (mesg < 1 || mesg > msgCount) {
657 printf(_("%d: Invalid message number\n"), mesg);
658 goto jem1;
660 mp = message + mesg - 1;
661 if (mp->m_flag & MHIDDEN ||
662 (f != MDELETED && (mp->m_flag & MDELETED) != 0)) {
663 n_err(_("%d: inappropriate message\n"), mesg);
664 goto jem1;
666 f = 0;
667 jleave:
668 NYD_LEAVE;
669 return f;
670 jem1:
671 f = -1;
672 goto jleave;
675 static int
676 scan(char **sp)
678 char *cp, *cp2;
679 int rv, c, inquote, quotec;
680 struct lex const *lp;
681 NYD_ENTER;
683 if (regretp >= 0) {
684 strncpy(lexstring, string_stack[regretp], STRINGLEN);
685 lexstring[STRINGLEN -1] = '\0';
686 lexnumber = numberstack[regretp];
687 rv = regretstack[regretp--];
688 goto jleave;
691 cp = *sp;
692 cp2 = lexstring;
693 c = *cp++;
695 /* strip away leading white space */
696 while (blankchar(c))
697 c = *cp++;
699 /* If no characters remain, we are at end of line, so report that */
700 if (c == '\0') {
701 *sp = --cp;
702 rv = TEOL;
703 goto jleave;
706 /* Select members of a message thread */
707 if (c == '&') {
708 threadflag = 1;
709 if (*cp == '\0' || spacechar(*cp)) {
710 lexstring[0] = '.';
711 lexstring[1] = '\0';
712 *sp = cp;
713 rv = TDOT;
714 goto jleave;
716 c = *cp++;
719 /* If the leading character is a digit, scan the number and convert it
720 * on the fly. Return TNUMBER when done */
721 if (digitchar(c)) {
722 lexnumber = 0;
723 while (digitchar(c)) {
724 lexnumber = lexnumber*10 + c - '0';
725 *cp2++ = c;
726 c = *cp++;
728 *cp2 = '\0';
729 *sp = --cp;
730 rv = TNUMBER;
731 goto jleave;
734 /* An IMAP SEARCH list. Note that TOPEN has always been included in
735 * singles[] in Mail and mailx. Thus although there is no formal
736 * definition for (LIST) lists, they do not collide with historical
737 * practice because a subject string (LIST) could never been matched
738 * this way */
739 if (c == '(') {
740 ui32_t level = 1;
741 inquote = 0;
742 *cp2++ = c;
743 do {
744 if ((c = *cp++&0377) == '\0') {
745 jmtop:
746 n_err(_("Missing \")\"\n"));
747 rv = TERROR;
748 goto jleave;
750 if (inquote && c == '\\') {
751 *cp2++ = c;
752 c = *cp++&0377;
753 if (c == '\0')
754 goto jmtop;
755 } else if (c == '"')
756 inquote = !inquote;
757 else if (inquote)
758 /*EMPTY*/;
759 else if (c == '(')
760 ++level;
761 else if (c == ')')
762 --level;
763 else if (spacechar(c)) {
764 /* Replace unquoted whitespace by single space characters, to make
765 * the string IMAP SEARCH conformant */
766 c = ' ';
767 if (cp2[-1] == ' ')
768 --cp2;
770 *cp2++ = c;
771 } while (c != ')' || level > 0);
772 *cp2 = '\0';
773 *sp = cp;
774 rv = TOPEN;
775 goto jleave;
778 /* Check for single character tokens; return such if found */
779 for (lp = _singles; lp->l_char != '\0'; ++lp)
780 if (c == lp->l_char) {
781 lexstring[0] = c;
782 lexstring[1] = '\0';
783 *sp = cp;
784 rv = lp->l_token;
785 goto jleave;
788 /* We've got a string! Copy all the characters of the string into
789 * lexstring, until we see a null, space, or tab. If the lead character is
790 * a " or ', save it and scan until you get another */
791 quotec = 0;
792 if (c == '\'' || c == '"') {
793 quotec = c;
794 c = *cp++;
796 while (c != '\0') {
797 if (quotec == 0 && c == '\\' && *cp != '\0')
798 c = *cp++;
799 if (c == quotec) {
800 ++cp;
801 break;
803 if (quotec == 0 && blankchar(c))
804 break;
805 if (PTRCMP(cp2 - lexstring, <, STRINGLEN - 1))
806 *cp2++ = c;
807 c = *cp++;
809 if (quotec && c == 0) {
810 n_err(_("Missing %c\n"), quotec);
811 rv = TERROR;
812 goto jleave;
814 *sp = --cp;
815 *cp2 = '\0';
816 rv = TSTRING;
817 jleave:
818 NYD_LEAVE;
819 return rv;
822 static void
823 regret(int token)
825 NYD_ENTER;
826 if (++regretp >= REGDEP)
827 n_panic(_("Too many regrets"));
828 regretstack[regretp] = token;
829 lexstring[STRINGLEN -1] = '\0';
830 string_stack[regretp] = savestr(lexstring);
831 numberstack[regretp] = lexnumber;
832 NYD_LEAVE;
835 static void
836 scaninit(void)
838 NYD_ENTER;
839 regretp = -1;
840 threadflag = 0;
841 NYD_LEAVE;
844 static bool_t
845 _matchsender(struct message *mp, char const *str, bool_t allnet)
847 char const *str_base, *np_base, *np;
848 char sc, nc;
849 bool_t rv;
850 NYD_ENTER;
852 /* Empty string doesn't match */
853 if (*(str_base = str) == '\0') {
854 rv = FAL0;
855 goto jleave;
858 /* *allnet* is POSIX and, since it explicitly mentions login and user names,
859 * most likely case-sensitive. XXX Still allow substr matching, though
860 * XXX possibly the first letter should be case-insensitive, then? */
861 if (allnet) {
862 np_base = np = nameof(mp, 0);
863 for (;;) {
864 if ((sc = *str++) == '@')
865 sc = '\0';
866 if ((nc = *np++) == '@' || nc == '\0' || sc == '\0')
867 break;
868 if (sc != nc) {
869 np = ++np_base;
870 str = str_base;
873 rv = (sc == '\0');
874 } else {
875 char const *real_base = name1(mp, 0);
876 bool_t again = ok_blook(showname);
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 jagain:
884 np_base = np = again ? realname(real_base) : skin(real_base);
885 for (;;) {
886 sc = *str++;
887 if ((nc = *np++) == '\0' || sc == '\0')
888 break;
889 sc = upperconv(sc);
890 nc = upperconv(nc);
891 if (sc != nc) {
892 np = ++np_base;
893 str = str_base;
897 /* And really if i want to match 'on@' then i want it to match even if
898 * *showname* is set! */
899 if (!(rv = (sc == '\0')) && again) {
900 again = FAL0;
901 goto jagain;
904 jleave:
905 NYD_LEAVE;
906 return rv;
909 static bool_t
910 _matchmid(struct message *mp, char *id, enum idfield idfield)
912 char *cp;
913 bool_t rv;
914 NYD_ENTER;
916 if ((cp = hfield1("message-id", mp)) != NULL) {
917 switch (idfield) {
918 case ID_REFERENCES:
919 rv = !msgidcmp(id, cp);
920 goto jleave;
921 case ID_IN_REPLY_TO: {
922 struct name *np;
924 if ((np = extract(id, GREF)) != NULL)
925 do {
926 if (!msgidcmp(np->n_name, cp)) {
927 rv = TRU1;
928 goto jleave;
930 } while ((np = np->n_flink) != NULL);
931 break;
935 rv = FAL0;
936 jleave:
937 NYD_LEAVE;
938 return rv;
941 static bool_t
942 _match_dash(struct message *mp, char const *str)
944 static char lastscan[128];
946 struct str in, out;
947 char *hfield, *hbody;
948 bool_t rv;
949 NYD_ENTER;
951 if (*++str == '\0') {
952 str = lastscan;
953 } else {
954 strncpy(lastscan, str, sizeof lastscan); /* XXX use new n_str object! */
955 lastscan[sizeof lastscan -1] = '\0';
958 /* Now look, ignoring case, for the word in the string */
959 if (ok_blook(searchheaders) && (hfield = strchr(str, ':'))) {
960 size_t l = PTR2SIZE(hfield - str);
961 hfield = ac_alloc(l +1);
962 memcpy(hfield, str, l);
963 hfield[l] = '\0';
964 hbody = hfieldX(hfield, mp);
965 ac_free(hfield);
966 hfield = UNCONST(str + l + 1);
967 } else {
968 hfield = UNCONST(str);
969 hbody = hfield1("subject", mp);
971 if (hbody == NULL) {
972 rv = FAL0;
973 goto jleave;
976 in.s = hbody;
977 in.l = strlen(hbody);
978 mime_fromhdr(&in, &out, TD_ICONV);
979 rv = substr(out.s, hfield);
980 free(out.s);
981 jleave:
982 NYD_LEAVE;
983 return rv;
986 static bool_t
987 _match_at(struct message *mp, struct search_expr *sep)
989 struct str in, out;
990 char *nfield;
991 char const *cfield;
992 bool_t rv = FAL0;
993 NYD_ENTER;
995 nfield = savestr(sep->ss_where);
997 while ((cfield = n_strsep(&nfield, ',', TRU1)) != NULL) {
998 if (!asccasecmp(cfield, "body") ||
999 (cfield[1] == '\0' && cfield[0] == '>')) {
1000 rv = FAL0;
1001 jmsg:
1002 if ((rv = message_match(mp, sep, rv)))
1003 break;
1004 continue;
1006 if (!asccasecmp(cfield, "text") ||
1007 (cfield[1] == '\0' && cfield[0] == '=')) {
1008 rv = TRU1;
1009 goto jmsg;
1012 if (!asccasecmp(cfield, "header") ||
1013 (cfield[1] == '\0' && cfield[0] == '<')) {
1014 if ((rv = header_match(mp, sep)))
1015 break;
1016 continue;
1019 /* This is not a special name, so take care for the "skin" prefix !
1020 * and possible abbreviations */
1022 struct name *np;
1023 bool_t doskin;
1025 if ((doskin = (*cfield == '~')))
1026 ++cfield;
1027 if (cfield[0] != '\0' && cfield[1] == '\0') {
1028 char const x[][8] = {
1029 "from", "to", "cc", "bcc", "subject"
1031 size_t i;
1032 char c1 = lowerconv(cfield[0]);
1034 for (i = 0; i < NELEM(x); ++i) {
1035 if (c1 == x[i][0]) {
1036 cfield = x[i];
1037 break;
1041 if ((in.s = hfieldX(cfield, mp)) == NULL)
1042 continue;
1044 /* Shall we split into address list and match the addresses only? */
1045 if (doskin) {
1046 np = lextract(in.s, GSKIN);
1047 if (np == NULL)
1048 continue;
1049 out.s = np->n_name;
1050 } else {
1051 np = NULL;
1052 in.l = strlen(in.s);
1053 mime_fromhdr(&in, &out, TD_ICONV);
1055 jnext_name:
1056 #ifdef HAVE_REGEX
1057 if (sep->ss_sexpr == NULL)
1058 rv = (regexec(&sep->ss_regex, out.s, 0,NULL, 0) != REG_NOMATCH);
1059 else
1060 #endif
1061 rv = substr(out.s, sep->ss_sexpr);
1062 if (np == NULL)
1063 free(out.s);
1064 if (rv)
1065 break;
1066 if (np != NULL && (np = np->n_flink) != NULL) {
1067 out.s = np->n_name;
1068 goto jnext_name;
1072 NYD_LEAVE;
1073 return rv;
1076 static void
1077 unmark(int mesg)
1079 size_t i;
1080 NYD_ENTER;
1082 i = (size_t)mesg;
1083 if (i < 1 || UICMP(z, i, >, msgCount))
1084 n_panic(_("Bad message number to unmark"));
1085 message[i - 1].m_flag &= ~MMARK;
1086 NYD_LEAVE;
1089 static int
1090 metamess(int meta, int f)
1092 int c, m;
1093 struct message *mp;
1094 NYD_ENTER;
1096 c = meta;
1097 switch (c) {
1098 case '^': /* First 'good' message left */
1099 mp = mb.mb_threaded ? threadroot : message;
1100 while (PTRCMP(mp, <, message + msgCount)) {
1101 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & MDELETED) == (ui32_t)f) {
1102 c = (int)PTR2SIZE(mp - message + 1);
1103 goto jleave;
1105 if (mb.mb_threaded) {
1106 mp = next_in_thread(mp);
1107 if (mp == NULL)
1108 break;
1109 } else
1110 ++mp;
1112 if (!(pstate & PS_HOOK_MASK))
1113 printf(_("No applicable messages\n"));
1114 goto jem1;
1116 case '$': /* Last 'good message left */
1117 mp = mb.mb_threaded
1118 ? this_in_thread(threadroot, -1) : message + msgCount - 1;
1119 while (mp >= message) {
1120 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & MDELETED) == (ui32_t)f) {
1121 c = (int)PTR2SIZE(mp - message + 1);
1122 goto jleave;
1124 if (mb.mb_threaded) {
1125 mp = prev_in_thread(mp);
1126 if (mp == NULL)
1127 break;
1128 } else
1129 --mp;
1131 if (!(pstate & PS_HOOK_MASK))
1132 printf(_("No applicable messages\n"));
1133 goto jem1;
1135 case '.':
1136 /* Current message */
1137 m = dot - message + 1;
1138 if ((dot->m_flag & MHIDDEN) || (dot->m_flag & MDELETED) != (ui32_t)f) {
1139 printf(_("%d: inappropriate message\n"), m);
1140 goto jem1;
1142 c = m;
1143 break;
1145 case ';':
1146 /* Previously current message */
1147 if (prevdot == NULL) {
1148 n_err(_("No previously current message\n"));
1149 goto jem1;
1151 m = prevdot - message + 1;
1152 if ((prevdot->m_flag & MHIDDEN) ||
1153 (prevdot->m_flag & MDELETED) != (ui32_t)f) {
1154 n_err(_("%d: inappropriate message\n"), m);
1155 goto jem1;
1157 c = m;
1158 break;
1160 default:
1161 n_err(_("Unknown metachar (%c)\n"), c);
1162 goto jem1;
1164 jleave:
1165 NYD_LEAVE;
1166 return c;
1167 jem1:
1168 c = -1;
1169 goto jleave;
1172 FL int
1173 argcount(char **argv)/* FIXME -> lex.c */
1175 char **ap;
1176 NYD_ENTER;
1178 for (ap = argv; *ap++ != NULL;)
1180 NYD_LEAVE;
1181 return (int)PTR2SIZE(ap - argv - 1);
1184 FL int
1185 getmsglist(char *buf, int *vector, int flags)
1187 int *ip, mc;
1188 struct message *mp;
1189 NYD_ENTER;
1191 pstate &= ~PS_MSGLIST_MASK;
1192 _list_last_saw_d = _list_saw_d;
1193 _list_saw_d = FAL0;
1195 if (msgCount == 0) {
1196 *vector = 0;
1197 mc = 0;
1198 goto jleave;
1201 pstate |= PS_MSGLIST_DIRECT;
1203 if (markall(buf, flags) < 0) {
1204 mc = -1;
1205 goto jleave;
1208 ip = vector;
1209 if (pstate & PS_HOOK_NEWMAIL) {
1210 mc = 0;
1211 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
1212 if (mp->m_flag & MMARK) {
1213 if (!(mp->m_flag & MNEWEST))
1214 unmark((int)PTR2SIZE(mp - message + 1));
1215 else
1216 ++mc;
1218 if (mc == 0) {
1219 mc = -1;
1220 goto jleave;
1224 if (mb.mb_threaded == 0) {
1225 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
1226 if (mp->m_flag & MMARK)
1227 *ip++ = (int)PTR2SIZE(mp - message + 1);
1228 } else {
1229 for (mp = threadroot; mp != NULL; mp = next_in_thread(mp))
1230 if (mp->m_flag & MMARK)
1231 *ip++ = (int)PTR2SIZE(mp - message + 1);
1233 *ip = 0;
1234 mc = (int)PTR2SIZE(ip - vector);
1235 if (mc != 1)
1236 pstate &= ~PS_MSGLIST_DIRECT;
1237 jleave:
1238 NYD_LEAVE;
1239 return mc;
1242 FL int
1243 getrawlist(char const *line, size_t linesize, char **argv, int argc,
1244 int echolist)
1246 char c, *cp2, quotec, *linebuf;
1247 int argn;
1248 NYD_ENTER;
1250 pstate &= ~PS_MSGLIST_MASK;
1252 linebuf = ac_alloc(linesize);
1254 for (argn = 0;;) {
1255 if (!argn || !echolist) {
1256 for (; blankchar(*line); ++line)
1258 if (*line == '\0')
1259 break;
1261 if (argn >= argc - 1) {
1262 n_err(_("Too many elements in the list; excess discarded\n"));
1263 break;
1266 cp2 = linebuf;
1267 for (quotec = '\0'; ((c = *line++) != '\0');) {
1268 if (quotec != '\0') {
1269 if (c == quotec) {
1270 quotec = '\0';
1271 if (echolist)
1272 *cp2++ = c;
1273 } else if (c == '\\') {
1274 switch (c = *line++) {
1275 case '\0':
1276 *cp2++ = '\\';
1277 --line;
1278 break;
1279 default:
1280 if (line[-1] != quotec || echolist)
1281 *cp2++ = '\\';
1282 *cp2++ = c;
1284 } else
1285 *cp2++ = c;
1286 } else if (c == '"' || c == '\'') {
1287 if (echolist)
1288 *cp2++ = c;
1289 quotec = c;
1290 } else if (c == '\\' && !echolist)
1291 *cp2++ = (*line != '\0') ? *line++ : c;
1292 else if (blankchar(c))
1293 break;
1294 else
1295 *cp2++ = c;
1297 argv[argn++] = savestrbuf(linebuf, PTR2SIZE(cp2 - linebuf));
1298 if (c == '\0')
1299 break;
1301 argv[argn] = NULL;
1303 ac_free(linebuf);
1304 NYD_LEAVE;
1305 return argn;
1308 FL int
1309 first(int f, int m)
1311 struct message *mp;
1312 int rv;
1313 NYD_ENTER;
1315 if (msgCount == 0) {
1316 rv = 0;
1317 goto jleave;
1320 f &= MDELETED;
1321 m &= MDELETED;
1322 for (mp = dot;
1323 mb.mb_threaded ? (mp != NULL) : PTRCMP(mp, <, message + msgCount);
1324 mb.mb_threaded ? (mp = next_in_thread(mp)) : ++mp) {
1325 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & m) == (ui32_t)f) {
1326 rv = (int)PTR2SIZE(mp - message + 1);
1327 goto jleave;
1331 if (dot > message) {
1332 for (mp = dot - 1; (mb.mb_threaded ? (mp != NULL) : (mp >= message));
1333 mb.mb_threaded ? (mp = prev_in_thread(mp)) : --mp) {
1334 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & m) == (ui32_t)f) {
1335 rv = (int)PTR2SIZE(mp - message + 1);
1336 goto jleave;
1340 rv = 0;
1341 jleave:
1342 NYD_LEAVE;
1343 return rv;
1346 FL void
1347 mark(int mesg, int f)
1349 struct message *mp;
1350 int i;
1351 NYD_ENTER;
1353 i = mesg;
1354 if (i < 1 || i > msgCount)
1355 n_panic(_("Bad message number to mark"));
1357 if (mb.mb_threaded == 1 && threadflag) {
1358 struct message *mp_t = message + i - 1;
1360 if (!(mp_t->m_flag & MHIDDEN)) {
1361 if (f == MDELETED || !(mp_t->m_flag & MDELETED) || _list_saw_d)
1362 mp_t->m_flag |= MMARK;
1365 if (mp_t->m_child != NULL) {
1366 mp = mp_t->m_child;
1367 mark((int)PTR2SIZE(mp - message + 1), f);
1368 for (mp = mp->m_younger; mp != NULL; mp = mp->m_younger)
1369 mark((int)PTR2SIZE(mp - message + 1), f);
1371 } else
1372 message[i - 1].m_flag |= MMARK;
1373 NYD_LEAVE;
1376 /* s-it-mode */