FIX "," message specification (since [1c4b8c9], v14.8.4)..
[s-mailx.git] / list.c
blobb36538b67f5d5f475e9a00183953a1eed3467388
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 char const *real_base = name1(mp, 0);
879 bool_t again = ok_blook(showname);
881 /* TODO POSIX says ~"match any address as shown in header overview",
882 * TODO but a normalized match would be more sane i guess.
883 * TODO struct name should gain a comparison method, normalize realname
884 * TODO content (in TODO) and thus match as likewise
885 * TODO "Buddy (Today) <here>" and "(Now) Buddy <here>" */
886 jagain:
887 np_base = np = again ? realname(real_base) : skin(real_base);
888 for (;;) {
889 sc = *str++;
890 if ((nc = *np++) == '\0' || sc == '\0')
891 break;
892 sc = upperconv(sc);
893 nc = upperconv(nc);
894 if (sc != nc) {
895 np = ++np_base;
896 str = str_base;
900 /* And really if i want to match 'on@' then i want it to match even if
901 * *showname* is set! */
902 if (!(rv = (sc == '\0')) && again) {
903 again = FAL0;
904 goto jagain;
907 jleave:
908 NYD_LEAVE;
909 return rv;
912 static bool_t
913 _matchmid(struct message *mp, char *id, enum idfield idfield)
915 char *cp;
916 bool_t rv;
917 NYD_ENTER;
919 if ((cp = hfield1("message-id", mp)) != NULL) {
920 switch (idfield) {
921 case ID_REFERENCES:
922 rv = !msgidcmp(id, cp);
923 goto jleave;
924 case ID_IN_REPLY_TO: {
925 struct name *np;
927 if ((np = extract(id, GREF)) != NULL)
928 do {
929 if (!msgidcmp(np->n_name, cp)) {
930 rv = TRU1;
931 goto jleave;
933 } while ((np = np->n_flink) != NULL);
934 break;
938 rv = FAL0;
939 jleave:
940 NYD_LEAVE;
941 return rv;
944 static bool_t
945 _match_dash(struct message *mp, char const *str)
947 static char lastscan[128];
949 struct str in, out;
950 char *hfield, *hbody;
951 bool_t rv;
952 NYD_ENTER;
954 if (*++str == '\0') {
955 str = lastscan;
956 } else {
957 strncpy(lastscan, str, sizeof lastscan); /* XXX use new n_str object! */
958 lastscan[sizeof lastscan -1] = '\0';
961 /* Now look, ignoring case, for the word in the string */
962 if (ok_blook(searchheaders) && (hfield = strchr(str, ':'))) {
963 size_t l = PTR2SIZE(hfield - str);
964 hfield = ac_alloc(l +1);
965 memcpy(hfield, str, l);
966 hfield[l] = '\0';
967 hbody = hfieldX(hfield, mp);
968 ac_free(hfield);
969 hfield = UNCONST(str + l + 1);
970 } else {
971 hfield = UNCONST(str);
972 hbody = hfield1("subject", mp);
974 if (hbody == NULL) {
975 rv = FAL0;
976 goto jleave;
979 in.s = hbody;
980 in.l = strlen(hbody);
981 mime_fromhdr(&in, &out, TD_ICONV);
982 rv = substr(out.s, hfield);
983 free(out.s);
984 jleave:
985 NYD_LEAVE;
986 return rv;
989 static bool_t
990 _match_at(struct message *mp, struct search_expr *sep)
992 struct str in, out;
993 char *nfield;
994 char const *cfield;
995 bool_t rv = FAL0;
996 NYD_ENTER;
998 nfield = savestr(sep->ss_where);
1000 while ((cfield = n_strsep(&nfield, ',', TRU1)) != NULL) {
1001 if (!asccasecmp(cfield, "body") ||
1002 (cfield[1] == '\0' && cfield[0] == '>')) {
1003 rv = FAL0;
1004 jmsg:
1005 if ((rv = message_match(mp, sep, rv)))
1006 break;
1007 continue;
1009 if (!asccasecmp(cfield, "text") ||
1010 (cfield[1] == '\0' && cfield[0] == '=')) {
1011 rv = TRU1;
1012 goto jmsg;
1015 if (!asccasecmp(cfield, "header") ||
1016 (cfield[1] == '\0' && cfield[0] == '<')) {
1017 if ((rv = header_match(mp, sep)))
1018 break;
1019 continue;
1022 /* This is not a special name, so take care for the "skin" prefix !
1023 * and possible abbreviations */
1025 struct name *np;
1026 bool_t doskin;
1028 if ((doskin = (*cfield == '~')))
1029 ++cfield;
1030 if (cfield[0] != '\0' && cfield[1] == '\0') {
1031 char const x[][8] = {
1032 "from", "to", "cc", "bcc", "subject"
1034 size_t i;
1035 char c1 = lowerconv(cfield[0]);
1037 for (i = 0; i < NELEM(x); ++i) {
1038 if (c1 == x[i][0]) {
1039 cfield = x[i];
1040 break;
1044 if ((in.s = hfieldX(cfield, mp)) == NULL)
1045 continue;
1047 /* Shall we split into address list and match the addresses only? */
1048 if (doskin) {
1049 np = lextract(in.s, GSKIN);
1050 if (np == NULL)
1051 continue;
1052 out.s = np->n_name;
1053 } else {
1054 np = NULL;
1055 in.l = strlen(in.s);
1056 mime_fromhdr(&in, &out, TD_ICONV);
1058 jnext_name:
1059 #ifdef HAVE_REGEX
1060 if (sep->ss_sexpr == NULL)
1061 rv = (regexec(&sep->ss_regex, out.s, 0,NULL, 0) != REG_NOMATCH);
1062 else
1063 #endif
1064 rv = substr(out.s, sep->ss_sexpr);
1065 if (np == NULL)
1066 free(out.s);
1067 if (rv)
1068 break;
1069 if (np != NULL && (np = np->n_flink) != NULL) {
1070 out.s = np->n_name;
1071 goto jnext_name;
1075 NYD_LEAVE;
1076 return rv;
1079 static void
1080 unmark(int mesg)
1082 size_t i;
1083 NYD_ENTER;
1085 i = (size_t)mesg;
1086 if (i < 1 || UICMP(z, i, >, msgCount))
1087 n_panic(_("Bad message number to unmark"));
1088 message[i - 1].m_flag &= ~MMARK;
1089 NYD_LEAVE;
1092 static int
1093 metamess(int meta, int f)
1095 int c, m;
1096 struct message *mp;
1097 NYD_ENTER;
1099 c = meta;
1100 switch (c) {
1101 case '^': /* First 'good' message left */
1102 mp = mb.mb_threaded ? threadroot : message;
1103 while (PTRCMP(mp, <, message + msgCount)) {
1104 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & MDELETED) == (ui32_t)f) {
1105 c = (int)PTR2SIZE(mp - message + 1);
1106 goto jleave;
1108 if (mb.mb_threaded) {
1109 mp = next_in_thread(mp);
1110 if (mp == NULL)
1111 break;
1112 } else
1113 ++mp;
1115 if (!(pstate & PS_HOOK_MASK))
1116 printf(_("No applicable messages\n"));
1117 goto jem1;
1119 case '$': /* Last 'good message left */
1120 mp = mb.mb_threaded
1121 ? this_in_thread(threadroot, -1) : message + msgCount - 1;
1122 while (mp >= message) {
1123 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & MDELETED) == (ui32_t)f) {
1124 c = (int)PTR2SIZE(mp - message + 1);
1125 goto jleave;
1127 if (mb.mb_threaded) {
1128 mp = prev_in_thread(mp);
1129 if (mp == NULL)
1130 break;
1131 } else
1132 --mp;
1134 if (!(pstate & PS_HOOK_MASK))
1135 printf(_("No applicable messages\n"));
1136 goto jem1;
1138 case '.':
1139 /* Current message */
1140 m = dot - message + 1;
1141 if ((dot->m_flag & MHIDDEN) || (dot->m_flag & MDELETED) != (ui32_t)f) {
1142 printf(_("%d: inappropriate message\n"), m);
1143 goto jem1;
1145 c = m;
1146 break;
1148 case ';':
1149 /* Previously current message */
1150 if (prevdot == NULL) {
1151 n_err(_("No previously current message\n"));
1152 goto jem1;
1154 m = prevdot - message + 1;
1155 if ((prevdot->m_flag & MHIDDEN) ||
1156 (prevdot->m_flag & MDELETED) != (ui32_t)f) {
1157 n_err(_("%d: inappropriate message\n"), m);
1158 goto jem1;
1160 c = m;
1161 break;
1163 default:
1164 n_err(_("Unknown metachar (%c)\n"), c);
1165 goto jem1;
1167 jleave:
1168 NYD_LEAVE;
1169 return c;
1170 jem1:
1171 c = -1;
1172 goto jleave;
1175 FL int
1176 getmsglist(char *buf, int *vector, int flags)
1178 int *ip, mc;
1179 struct message *mp;
1180 NYD_ENTER;
1182 pstate &= ~PS_MSGLIST_MASK;
1184 if (msgCount == 0) {
1185 *vector = 0;
1186 mc = 0;
1187 goto jleave;
1190 pstate |= PS_MSGLIST_DIRECT;
1192 if (markall(buf, flags) < 0) {
1193 mc = -1;
1194 goto jleave;
1197 ip = vector;
1198 if (pstate & PS_HOOK_NEWMAIL) {
1199 mc = 0;
1200 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
1201 if (mp->m_flag & MMARK) {
1202 if (!(mp->m_flag & MNEWEST))
1203 unmark((int)PTR2SIZE(mp - message + 1));
1204 else
1205 ++mc;
1207 if (mc == 0) {
1208 mc = -1;
1209 goto jleave;
1213 if (mb.mb_threaded == 0) {
1214 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
1215 if (mp->m_flag & MMARK)
1216 *ip++ = (int)PTR2SIZE(mp - message + 1);
1217 } else {
1218 for (mp = threadroot; mp != NULL; mp = next_in_thread(mp))
1219 if (mp->m_flag & MMARK)
1220 *ip++ = (int)PTR2SIZE(mp - message + 1);
1222 *ip = 0;
1223 mc = (int)PTR2SIZE(ip - vector);
1224 if (mc != 1)
1225 pstate &= ~PS_MSGLIST_DIRECT;
1226 jleave:
1227 NYD_LEAVE;
1228 return mc;
1231 FL int
1232 getrawlist(char const *line, size_t linesize, char **argv, int argc,
1233 int echolist)
1235 char c, *cp2, quotec, *linebuf;
1236 int argn;
1237 NYD_ENTER;
1239 pstate &= ~PS_MSGLIST_MASK;
1241 linebuf = ac_alloc(linesize);
1243 for (argn = 0;;) {
1244 if (!argn || !echolist) {
1245 for (; blankchar(*line); ++line)
1247 if (*line == '\0')
1248 break;
1250 if (argn >= argc - 1) {
1251 n_err(_("Too many elements in the list; excess discarded\n"));
1252 break;
1255 cp2 = linebuf;
1256 for (quotec = '\0'; ((c = *line++) != '\0');) {
1257 if (quotec != '\0') {
1258 if (c == quotec) {
1259 quotec = '\0';
1260 if (echolist)
1261 *cp2++ = c;
1262 } else if (c == '\\') {
1263 switch (c = *line++) {
1264 case '\0':
1265 *cp2++ = '\\';
1266 --line;
1267 break;
1268 default:
1269 if (line[-1] != quotec || echolist)
1270 *cp2++ = '\\';
1271 *cp2++ = c;
1273 } else
1274 *cp2++ = c;
1275 } else if (c == '"' || c == '\'') {
1276 if (echolist)
1277 *cp2++ = c;
1278 quotec = c;
1279 } else if (c == '\\' && !echolist)
1280 *cp2++ = (*line != '\0') ? *line++ : c;
1281 else if (blankchar(c))
1282 break;
1283 else
1284 *cp2++ = c;
1286 argv[argn++] = savestrbuf(linebuf, PTR2SIZE(cp2 - linebuf));
1287 if (c == '\0')
1288 break;
1290 argv[argn] = NULL;
1292 ac_free(linebuf);
1293 NYD_LEAVE;
1294 return argn;
1297 FL int
1298 first(int f, int m)
1300 struct message *mp;
1301 int rv;
1302 NYD_ENTER;
1304 if (msgCount == 0) {
1305 rv = 0;
1306 goto jleave;
1309 f &= MDELETED;
1310 m &= MDELETED;
1311 for (mp = dot;
1312 mb.mb_threaded ? (mp != NULL) : PTRCMP(mp, <, message + msgCount);
1313 mb.mb_threaded ? (mp = next_in_thread(mp)) : ++mp) {
1314 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & m) == (ui32_t)f) {
1315 rv = (int)PTR2SIZE(mp - message + 1);
1316 goto jleave;
1320 if (dot > message) {
1321 for (mp = dot - 1; (mb.mb_threaded ? (mp != NULL) : (mp >= message));
1322 mb.mb_threaded ? (mp = prev_in_thread(mp)) : --mp) {
1323 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & m) == (ui32_t)f) {
1324 rv = (int)PTR2SIZE(mp - message + 1);
1325 goto jleave;
1329 rv = 0;
1330 jleave:
1331 NYD_LEAVE;
1332 return rv;
1335 FL void
1336 mark(int mesg, int f)
1338 struct message *mp;
1339 int i;
1340 NYD_ENTER;
1342 i = mesg;
1343 if (i < 1 || i > msgCount)
1344 n_panic(_("Bad message number to mark"));
1345 if (mb.mb_threaded == 1 && threadflag) {
1346 if (!(message[i - 1].m_flag & MHIDDEN)) {
1347 if (f == MDELETED || !(message[i - 1].m_flag & MDELETED))
1348 message[i - 1].m_flag |= MMARK;
1351 if (message[i - 1].m_child) {
1352 mp = message[i - 1].m_child;
1353 mark((int)PTR2SIZE(mp - message + 1), f);
1354 for (mp = mp->m_younger; mp != NULL; mp = mp->m_younger)
1355 mark((int)PTR2SIZE(mp - message + 1), f);
1357 } else
1358 message[i - 1].m_flag |= MMARK;
1359 NYD_LEAVE;
1362 /* s-it-mode */