Fix false resource release <-> double free (Bob Tennent)..
[s-mailx.git] / list.c
blobd05faa7a857db93ae1f028f55c2e8a42b854ae0b
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. All advertising materials mentioning features or use of this software
20 * must display the following acknowledgement:
21 * This product includes software developed by the University of
22 * California, Berkeley and its contributors.
23 * 4. Neither the name of the University nor the names of its contributors
24 * may be used to endorse or promote products derived from this software
25 * without specific prior written permission.
27 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
28 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
31 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37 * SUCH DAMAGE.
39 #undef n_FILE
40 #define n_FILE list
42 #ifndef HAVE_AMALGAMATION
43 # include "nail.h"
44 #endif
46 /* Token values returned by the scanner used for argument lists.
47 * Also, sizes of scanner-related things */
48 enum ltoken {
49 TEOL = 0, /* End of the command line */
50 TNUMBER = 1, /* A message number */
51 TDASH = 2, /* A simple dash */
52 TSTRING = 3, /* A string (possibly containing -) */
53 TDOT = 4, /* A "." */
54 TUP = 5, /* A "^" */
55 TDOLLAR = 6, /* A "$" */
56 TSTAR = 7, /* A "*" */
57 TOPEN = 8, /* A '(' */
58 TCLOSE = 9, /* A ')' */
59 TPLUS = 10, /* A '+' */
60 TERROR = 11, /* A lexical error */
61 TCOMMA = 12, /* A ',' */
62 TSEMI = 13, /* A ';' */
63 TBACK = 14 /* A '`' */
66 #define REGDEP 2 /* Maximum regret depth. */
68 enum idfield {
69 ID_REFERENCES,
70 ID_IN_REPLY_TO
73 enum {
74 CMNEW = 1<<0, /* New messages */
75 CMOLD = 1<<1, /* Old messages */
76 CMUNREAD = 1<<2, /* Unread messages */
77 CMDELETED =1<<3, /* Deleted messages */
78 CMREAD = 1<<4, /* Read messages */
79 CMFLAG = 1<<5, /* Flagged messages */
80 CMANSWER = 1<<6, /* Answered messages */
81 CMDRAFT = 1<<7, /* Draft messages */
82 CMSPAM = 1<<8, /* Spam messages */
83 CMSPAMUN = 1<<9 /* Maybe spam messages (unsure) */
86 struct coltab {
87 char co_char; /* What to find past : */
88 int co_bit; /* Associated modifier bit */
89 int co_mask; /* m_status bits to mask */
90 int co_equal; /* ... must equal this */
93 struct lex {
94 char l_char;
95 enum ltoken l_token;
98 static struct coltab const _coltab[] = {
99 { 'n', CMNEW, MNEW, MNEW },
100 { 'o', CMOLD, MNEW, 0 },
101 { 'u', CMUNREAD, MREAD, 0 },
102 { 'd', CMDELETED, MDELETED, MDELETED },
103 { 'r', CMREAD, MREAD, MREAD },
104 { 'f', CMFLAG, MFLAGGED, MFLAGGED },
105 { 'a', CMANSWER, MANSWERED, MANSWERED },
106 { 't', CMDRAFT, MDRAFTED, MDRAFTED },
107 { 's', CMSPAM, MSPAM, MSPAM },
108 { 'S', CMSPAMUN, MSPAMUNSURE, MSPAMUNSURE },
109 { '\0', 0, 0, 0 }
112 static struct lex const _singles[] = {
113 { '$', TDOLLAR },
114 { '.', TDOT },
115 { '^', TUP },
116 { '*', TSTAR },
117 { '-', TDASH },
118 { '+', TPLUS },
119 { '(', TOPEN },
120 { ')', TCLOSE },
121 { ',', TCOMMA },
122 { ';', TSEMI },
123 { '`', TBACK },
124 { '\0', 0 }
127 static int lastcolmod;
128 static size_t STRINGLEN;
129 static int lexnumber; /* Number of TNUMBER from scan() */
130 static char *lexstring; /* String from TSTRING, scan() */
131 static int regretp; /* Pointer to TOS of regret tokens */
132 static int regretstack[REGDEP]; /* Stack of regretted tokens */
133 static char *string_stack[REGDEP]; /* Stack of regretted strings */
134 static int numberstack[REGDEP]; /* Stack of regretted numbers */
135 static int threadflag; /* mark entire threads */
137 /* Append, taking care of resizes */
138 static char ** add_to_namelist(char ***namelist, size_t *nmlsize,
139 char **np, char *string);
141 /* Mark all messages that the user wanted from the command line in the message
142 * structure. Return 0 on success, -1 on error */
143 static int markall(char *buf, int f);
145 /* Turn the character after a colon modifier into a bit value */
146 static int evalcol(int col);
148 /* Check the passed message number for legality and proper flags. If f is
149 * MDELETED, then either kind will do. Otherwise, the message has to be
150 * undeleted */
151 static int check(int mesg, int f);
153 /* Scan out a single lexical item and return its token number, updating the
154 * string pointer passed **sp. Also, store the value of the number or string
155 * scanned in lexnumber or lexstring as appropriate. In any event, store the
156 * scanned "thing" in lexstring */
157 static int scan(char **sp);
159 /* Unscan the named token by pushing it onto the regret stack */
160 static void regret(int token);
162 /* Reset all the scanner global variables */
163 static void scaninit(void);
165 /* See if the passed name sent the passed message */
166 static bool_t _matchsender(struct message *mp, char const *str, bool_t allnet);
168 static bool_t _matchmid(struct message *mp, char *id, enum idfield idfield);
170 /* See if the given string matches.
171 * For the purpose of the scan, we ignore case differences.
172 * This is the engine behind the "/" search */
173 static bool_t _match_dash(struct message *mp, char const *str);
175 /* See if the given search expression matches.
176 * For the purpose of the scan, we ignore case differences.
177 * This is the engine behind the "@[..]@" search */
178 static bool_t _match_at(struct message *mp, struct search_expr *sep);
180 /* Unmark the named message */
181 static void unmark(int mesg);
183 /* Return the message number corresponding to the passed meta character */
184 static int metamess(int meta, int f);
186 static char **
187 add_to_namelist(char ***namelist, size_t *nmlsize, char **np, char *string)
189 size_t idx;
190 NYD_ENTER;
192 if ((idx = PTR2SIZE(np - *namelist)) >= *nmlsize) {
193 *namelist = srealloc(*namelist, (*nmlsize += 8) * sizeof *np);
194 np = *namelist + idx;
196 *np++ = string;
197 NYD_LEAVE;
198 return np;
201 static int
202 markall(char *buf, int f)
204 #define markall_ret(i) do { rv = i; goto jleave; } while (0);
206 /* TODO use a bit carrier for all the states */
207 char **np, **nq, **namelist, *bufp, *id = NULL, *cp;
208 int rv = 0, i, tok, beg, other, valdot, colmod, colresult;
209 struct message *mp, *mx;
210 bool_t mc, star, topen, tback;
211 size_t j, nmlsize;
212 enum idfield idfield = ID_REFERENCES;
213 #ifdef HAVE_IMAP
214 int gotheaders;
215 #endif
216 NYD_ENTER;
218 lexstring = ac_alloc(STRINGLEN = 2 * strlen(buf) +1);
219 valdot = (int)PTR2SIZE(dot - message + 1);
220 colmod = 0;
222 for (i = 1; i <= msgCount; ++i) {
223 enum mflag mf;
225 mf = message[i - 1].m_flag;
226 if (mf & MMARK)
227 mf |= MOLDMARK;
228 else
229 mf &= ~MOLDMARK;
230 mf &= ~MMARK;
231 message[i - 1].m_flag = mf;
234 np = namelist = smalloc((nmlsize = 8) * sizeof *namelist);
235 scaninit();
236 bufp = buf;
237 mc = FAL0;
238 beg = star = other = topen = tback = FAL0;
239 #ifdef HAVE_IMAP
240 gotheaders = 0;
241 #endif
243 for (tok = scan(&bufp); tok != TEOL;) {
244 switch (tok) {
245 case TNUMBER:
246 number:
247 if (star) {
248 fprintf(stderr, _("No numbers mixed with *\n"));
249 markall_ret(-1)
251 pstate |= PS_MSGLIST_SAW_NO;
252 ++other;
253 if (beg != 0) {
254 if (check(lexnumber, f))
255 markall_ret(-1)
256 i = beg;
257 while (mb.mb_threaded ? 1 : i <= lexnumber) {
258 if (!(message[i - 1].m_flag & MHIDDEN) &&
259 (f == MDELETED || !(message[i - 1].m_flag & MDELETED)))
260 mark(i, f);
261 if (mb.mb_threaded) {
262 if (i == lexnumber)
263 break;
264 mx = next_in_thread(&message[i - 1]);
265 if (mx == NULL)
266 markall_ret(-1)
267 i = (int)PTR2SIZE(mx - message + 1);
268 } else
269 ++i;
271 beg = 0;
272 break;
274 beg = lexnumber;
275 if (check(beg, f))
276 markall_ret(-1)
277 tok = scan(&bufp);
278 regret(tok);
279 if (tok != TDASH) {
280 mark(beg, f);
281 beg = 0;
283 break;
285 case TPLUS:
286 pstate &= ~PS_MSGLIST_DIRECT;
287 if (beg != 0) {
288 printf(_("Non-numeric second argument\n"));
289 markall_ret(-1)
291 i = valdot;
292 do {
293 if (mb.mb_threaded) {
294 mx = next_in_thread(message + i - 1);
295 i = mx ? (int)PTR2SIZE(mx - message + 1) : msgCount + 1;
296 } else
297 ++i;
298 if (i > msgCount) {
299 fprintf(stderr, _("Referencing beyond EOF\n"));
300 markall_ret(-1)
302 } while (message[i - 1].m_flag == MHIDDEN ||
303 (message[i - 1].m_flag & MDELETED) != (unsigned)f);
304 mark(i, f);
305 break;
307 case TDASH:
308 pstate &= ~PS_MSGLIST_DIRECT;
309 if (beg == 0) {
310 i = valdot;
311 do {
312 if (mb.mb_threaded) {
313 mx = prev_in_thread(message + i - 1);
314 i = mx ? (int)PTR2SIZE(mx - message + 1) : 0;
315 } else
316 --i;
317 if (i <= 0) {
318 fprintf(stderr, _("Referencing before 1\n"));
319 markall_ret(-1)
321 } while ((message[i - 1].m_flag & MHIDDEN) ||
322 (message[i - 1].m_flag & MDELETED) != (unsigned)f);
323 mark(i, f);
325 break;
327 case TSTRING:
328 pstate &= ~PS_MSGLIST_DIRECT;
329 if (beg != 0) {
330 fprintf(stderr, _("Non-numeric second argument\n"));
331 markall_ret(-1)
333 ++other;
334 if ((cp = lexstring)[0] == ':') {
335 while (*++cp != '\0') {
336 colresult = evalcol(*cp);
337 if (colresult == 0) {
338 fprintf(stderr, _("Unknown colon modifier \"%s\"\n"),
339 lexstring);
340 markall_ret(-1)
342 colmod |= colresult;
344 } else
345 np = add_to_namelist(&namelist, &nmlsize, np, savestr(lexstring));
346 break;
348 case TOPEN:
349 #ifdef HAVE_IMAP_SEARCH
350 pstate &= ~PS_MSGLIST_DIRECT;
351 if (imap_search(lexstring, f) == STOP)
352 markall_ret(-1)
353 topen = TRU1;
354 #else
355 fprintf(stderr, _("Optional selector is not available: \"%s\"\n"),
356 lexstring);
357 markall_ret(-1)
358 #endif
359 break;
361 case TDOLLAR:
362 case TUP:
363 case TDOT:
364 case TSEMI:
365 pstate &= ~PS_MSGLIST_DIRECT;
366 lexnumber = metamess(lexstring[0], f);
367 if (lexnumber == -1)
368 markall_ret(-1)
369 goto number;
371 case TBACK:
372 pstate &= ~PS_MSGLIST_DIRECT;
373 tback = TRU1;
374 for (i = 1; i <= msgCount; i++) {
375 if ((message[i - 1].m_flag & MHIDDEN) ||
376 (message[i - 1].m_flag & MDELETED) != (unsigned)f)
377 continue;
378 if (message[i - 1].m_flag & MOLDMARK)
379 mark(i, f);
381 break;
383 case TSTAR:
384 pstate &= ~PS_MSGLIST_DIRECT;
385 if (other) {
386 fprintf(stderr, _("Can't mix \"*\" with anything\n"));
387 markall_ret(-1)
389 star = TRU1;
390 break;
392 case TCOMMA:
393 pstate &= ~PS_MSGLIST_DIRECT;
394 #ifdef HAVE_IMAP
395 if (mb.mb_type == MB_IMAP && gotheaders++ == 0)
396 imap_getheaders(1, msgCount);
397 #endif
398 if (id == NULL && (cp = hfield1("in-reply-to", dot)) != NULL) {
399 id = savestr(cp);
400 idfield = ID_IN_REPLY_TO;
402 if (id == NULL && (cp = hfield1("references", dot)) != NULL) {
403 struct name *enp;
405 if ((enp = extract(cp, GREF)) != NULL) {
406 while (enp->n_flink != NULL)
407 enp = enp->n_flink;
408 id = savestr(enp->n_name);
409 idfield = ID_REFERENCES;
412 if (id == NULL) {
413 printf(_(
414 "Cannot determine parent Message-ID of the current message\n"));
415 markall_ret(-1)
417 break;
419 case TERROR:
420 pstate &= ~PS_MSGLIST_DIRECT;
421 pstate |= PS_MSGLIST_SAW_NO;
422 markall_ret(-1)
424 threadflag = 0;
425 tok = scan(&bufp);
428 lastcolmod = colmod;
429 np = add_to_namelist(&namelist, &nmlsize, np, NULL);
430 --np;
431 mc = FAL0;
432 if (star) {
433 for (i = 0; i < msgCount; ++i) {
434 if (!(message[i].m_flag & MHIDDEN) &&
435 (message[i].m_flag & MDELETED) == (unsigned)f) {
436 mark(i + 1, f);
437 mc = TRU1;
440 if (!mc) {
441 if (!(pstate & PS_HOOK_MASK))
442 printf(_("No applicable messages.\n"));
443 markall_ret(-1)
445 markall_ret(0)
448 if ((topen || tback) && !mc) {
449 for (i = 0; i < msgCount; ++i)
450 if (message[i].m_flag & MMARK)
451 mc = TRU1;
452 if (!mc) {
453 if (!(pstate & PS_HOOK_MASK)) {
454 if (tback)
455 fprintf(stderr, _("No previously marked messages.\n"));
456 else
457 printf("No messages satisfy (criteria).\n");/*TODO tr*/
459 markall_ret(-1)
463 /* If no numbers were given, mark all messages, so that we can unmark
464 * any whose sender was not selected if any user names were given */
465 if ((np > namelist || colmod != 0 || id) && !mc) {
466 for (i = 1; i <= msgCount; ++i) {
467 if (!(message[i - 1].m_flag & MHIDDEN) &&
468 (message[i - 1].m_flag & MDELETED) == (unsigned)f)
469 mark(i, f);
473 /* If any names were given, eliminate any messages which don't match */
474 if (np > namelist || id) {
475 struct search_expr *sep = NULL;
476 bool_t allnet;
478 /* The @ search works with struct search_expr, so build an array.
479 * To simplify array, i.e., regex_t destruction, and optimize for the
480 * common case we walk the entire array even in case of errors */
481 if (np > namelist) {
482 sep = scalloc(PTR2SIZE(np - namelist), sizeof(*sep));
483 for (j = 0, nq = namelist; *nq != NULL; ++j, ++nq) {
484 char *x = *nq, *y;
486 sep[j].ss_sexpr = x;
487 if (*x != '@' || rv < 0)
488 continue;
490 for (y = x + 1;; ++y) {
491 if (*y == '\0' || !fieldnamechar(*y)) {
492 x = NULL;
493 break;
495 if (*y == '@') {
496 x = y;
497 break;
500 sep[j].ss_where = (x == NULL || x - 1 == *nq)
501 ? "subject" : savestrbuf(*nq + 1, PTR2SIZE(x - *nq) - 1);
503 x = (x == NULL ? *nq : x) + 1;
504 if (*x == '\0') { /* XXX Simply remove from list instead? */
505 fprintf(stderr, _("Empty \"[@..]@\" search expression\n"));
506 rv = -1;
507 continue;
509 #ifdef HAVE_REGEX
510 if (is_maybe_regex(x)) {
511 sep[j].ss_sexpr = NULL;
512 if (regcomp(&sep[j].ss_regex, x,
513 REG_EXTENDED | REG_ICASE | REG_NOSUB) != 0) {
514 fprintf(stderr, _(
515 "Invalid regular expression: >>> %s <<<\n"), x);
516 rv = -1;
517 continue;
519 } else
520 #endif
521 sep[j].ss_sexpr = x;
523 if (rv < 0)
524 goto jnamesearch_sepfree;
527 #ifdef HAVE_IMAP
528 if (mb.mb_type == MB_IMAP && gotheaders++ == 0)
529 imap_getheaders(1, msgCount);
530 #endif
531 srelax_hold();
532 allnet = ok_blook(allnet);
533 for (i = 1; i <= msgCount; ++i) {
534 mp = message + i - 1;
535 j = 0;
536 if (np > namelist) {
537 for (nq = namelist; *nq != NULL; ++nq) {
538 if (**nq == '@') {
539 if (_match_at(mp, sep + PTR2SIZE(nq - namelist))) {
540 ++j;
541 break;
543 } else if (**nq == '/') {
544 if (_match_dash(mp, *nq)) {
545 ++j;
546 break;
548 } else if (_matchsender(mp, *nq, allnet)) {
549 ++j;
550 break;
554 if (j == 0 && id && _matchmid(mp, id, idfield))
555 ++j;
556 if (j == 0)
557 mp->m_flag &= ~MMARK;
558 srelax();
560 srelax_rele();
562 /* Make sure we got some decent messages */
563 j = 0;
564 for (i = 1; i <= msgCount; ++i)
565 if (message[i - 1].m_flag & MMARK) {
566 ++j;
567 break;
569 if (j == 0) {
570 if (!(pstate & PS_HOOK_MASK) && np > namelist) {
571 printf(_("No applicable messages from {%s"), namelist[0]);
572 for (nq = namelist + 1; *nq != NULL; ++nq)
573 printf(_(", %s"), *nq);
574 printf(_("}\n"));
575 } else if (id)
576 printf(_("Parent message not found\n"));
577 rv = -1;
578 goto jnamesearch_sepfree;
581 jnamesearch_sepfree:
582 if (sep != NULL) {
583 #ifdef HAVE_REGEX
584 for (j = PTR2SIZE(np - namelist); j-- != 0;)
585 if (sep[j].ss_sexpr == NULL)
586 regfree(&sep[j].ss_regex);
587 #endif
588 free(sep);
590 if (rv < 0)
591 goto jleave;
594 /* If any colon modifiers were given, go through and unmark any
595 * messages which do not satisfy the modifiers */
596 if (colmod != 0) {
597 for (i = 1; i <= msgCount; ++i) {
598 struct coltab const *colp;
599 bool_t bad = TRU1;
601 mp = message + i - 1;
602 for (colp = _coltab; colp->co_char != '\0'; ++colp)
603 if ((colp->co_bit & colmod) &&
604 ((mp->m_flag & colp->co_mask) == (unsigned)colp->co_equal))
605 bad = FAL0;
606 if (bad)
607 unmark(i);
610 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
611 if (mp->m_flag & MMARK)
612 break;
614 if (PTRCMP(mp, >=, message + msgCount)) {
615 struct coltab const *colp;
617 if (!(pstate & PS_HOOK_MASK)) {
618 printf(_("No messages satisfy"));
619 for (colp = _coltab; colp->co_char != '\0'; ++colp)
620 if (colp->co_bit & colmod)
621 printf(" :%c", colp->co_char);
622 printf("\n");
624 markall_ret(-1)
628 markall_ret(0)
629 jleave:
630 free(namelist);
631 ac_free(lexstring);
632 NYD_LEAVE;
633 return rv;
635 #undef markall_ret
638 static int
639 evalcol(int col)
641 struct coltab const *colp;
642 int rv;
643 NYD_ENTER;
645 if (col == 0)
646 rv = lastcolmod;
647 else {
648 rv = 0;
649 for (colp = _coltab; colp->co_char != '\0'; ++colp)
650 if (colp->co_char == col) {
651 rv = colp->co_bit;
652 break;
655 NYD_LEAVE;
656 return rv;
659 static int
660 check(int mesg, int f)
662 struct message *mp;
663 NYD_ENTER;
665 if (mesg < 1 || mesg > msgCount) {
666 printf(_("%d: Invalid message number\n"), mesg);
667 goto jem1;
669 mp = message + mesg - 1;
670 if (mp->m_flag & MHIDDEN ||
671 (f != MDELETED && (mp->m_flag & MDELETED) != 0)) {
672 fprintf(stderr, _("%d: Inappropriate message\n"), mesg);
673 goto jem1;
675 f = 0;
676 jleave:
677 NYD_LEAVE;
678 return f;
679 jem1:
680 f = -1;
681 goto jleave;
684 static int
685 scan(char **sp)
687 char *cp, *cp2;
688 int rv, c, inquote, quotec;
689 struct lex const *lp;
690 NYD_ENTER;
692 if (regretp >= 0) {
693 strncpy(lexstring, string_stack[regretp], STRINGLEN);
694 lexstring[STRINGLEN -1] = '\0';
695 lexnumber = numberstack[regretp];
696 rv = regretstack[regretp--];
697 goto jleave;
700 cp = *sp;
701 cp2 = lexstring;
702 c = *cp++;
704 /* strip away leading white space */
705 while (blankchar(c))
706 c = *cp++;
708 /* If no characters remain, we are at end of line, so report that */
709 if (c == '\0') {
710 *sp = --cp;
711 rv = TEOL;
712 goto jleave;
715 /* Select members of a message thread */
716 if (c == '&') {
717 threadflag = 1;
718 if (*cp == '\0' || spacechar(*cp)) {
719 lexstring[0] = '.';
720 lexstring[1] = '\0';
721 *sp = cp;
722 rv = TDOT;
723 goto jleave;
725 c = *cp++;
728 /* If the leading character is a digit, scan the number and convert it
729 * on the fly. Return TNUMBER when done */
730 if (digitchar(c)) {
731 lexnumber = 0;
732 while (digitchar(c)) {
733 lexnumber = lexnumber*10 + c - '0';
734 *cp2++ = c;
735 c = *cp++;
737 *cp2 = '\0';
738 *sp = --cp;
739 rv = TNUMBER;
740 goto jleave;
743 /* An IMAP SEARCH list. Note that TOPEN has always been included in
744 * singles[] in Mail and mailx. Thus although there is no formal
745 * definition for (LIST) lists, they do not collide with historical
746 * practice because a subject string (LIST) could never been matched
747 * this way */
748 if (c == '(') {
749 ui32_t level = 1;
750 inquote = 0;
751 *cp2++ = c;
752 do {
753 if ((c = *cp++&0377) == '\0') {
754 jmtop:
755 fprintf(stderr, "Missing \")\".\n");
756 rv = TERROR;
757 goto jleave;
759 if (inquote && c == '\\') {
760 *cp2++ = c;
761 c = *cp++&0377;
762 if (c == '\0')
763 goto jmtop;
764 } else if (c == '"')
765 inquote = !inquote;
766 else if (inquote)
767 /*EMPTY*/;
768 else if (c == '(')
769 ++level;
770 else if (c == ')')
771 --level;
772 else if (spacechar(c)) {
773 /* Replace unquoted whitespace by single space characters, to make
774 * the string IMAP SEARCH conformant */
775 c = ' ';
776 if (cp2[-1] == ' ')
777 --cp2;
779 *cp2++ = c;
780 } while (c != ')' || level > 0);
781 *cp2 = '\0';
782 *sp = cp;
783 rv = TOPEN;
784 goto jleave;
787 /* Check for single character tokens; return such if found */
788 for (lp = _singles; lp->l_char != '\0'; ++lp)
789 if (c == lp->l_char) {
790 lexstring[0] = c;
791 lexstring[1] = '\0';
792 *sp = cp;
793 rv = lp->l_token;
794 goto jleave;
797 /* We've got a string! Copy all the characters of the string into
798 * lexstring, until we see a null, space, or tab. If the lead character is
799 * a " or ', save it and scan until you get another */
800 quotec = 0;
801 if (c == '\'' || c == '"') {
802 quotec = c;
803 c = *cp++;
805 while (c != '\0') {
806 if (quotec == 0 && c == '\\' && *cp != '\0')
807 c = *cp++;
808 if (c == quotec) {
809 ++cp;
810 break;
812 if (quotec == 0 && blankchar(c))
813 break;
814 if (PTRCMP(cp2 - lexstring, <, STRINGLEN - 1))
815 *cp2++ = c;
816 c = *cp++;
818 if (quotec && c == 0) {
819 fprintf(stderr, _("Missing %c\n"), quotec);
820 rv = TERROR;
821 goto jleave;
823 *sp = --cp;
824 *cp2 = '\0';
825 rv = TSTRING;
826 jleave:
827 NYD_LEAVE;
828 return rv;
831 static void
832 regret(int token)
834 NYD_ENTER;
835 if (++regretp >= REGDEP)
836 panic(_("Too many regrets"));
837 regretstack[regretp] = token;
838 lexstring[STRINGLEN -1] = '\0';
839 string_stack[regretp] = savestr(lexstring);
840 numberstack[regretp] = lexnumber;
841 NYD_LEAVE;
844 static void
845 scaninit(void)
847 NYD_ENTER;
848 regretp = -1;
849 threadflag = 0;
850 NYD_LEAVE;
853 static bool_t
854 _matchsender(struct message *mp, char const *str, bool_t allnet)
856 bool_t rv;
857 NYD_ENTER;
859 if (allnet) {
860 char *cp = nameof(mp, 0);
862 do {
863 if ((*cp == '@' || *cp == '\0') && (*str == '@' || *str == '\0')) {
864 rv = TRU1;
865 goto jleave;
867 if (*cp != *str)
868 break;
869 } while (++cp, *str++ != '\0');
870 rv = FAL0;
871 goto jleave;
873 rv = !strcmp(str, (*(ok_blook(showname) ? &realname : &skin))(name1(mp, 0)));
874 jleave:
875 NYD_LEAVE;
876 return rv;
879 static bool_t
880 _matchmid(struct message *mp, char *id, enum idfield idfield)
882 char *cp;
883 bool_t rv;
884 NYD_ENTER;
886 if ((cp = hfield1("message-id", mp)) != NULL) {
887 switch (idfield) {
888 case ID_REFERENCES:
889 rv = !msgidcmp(id, cp);
890 goto jleave;
891 case ID_IN_REPLY_TO: {
892 struct name *np;
894 if ((np = extract(id, GREF)) != NULL)
895 do {
896 if (!msgidcmp(np->n_name, cp)) {
897 rv = TRU1;
898 goto jleave;
900 } while ((np = np->n_flink) != NULL);
901 break;
905 rv = FAL0;
906 jleave:
907 NYD_LEAVE;
908 return rv;
911 static bool_t
912 _match_dash(struct message *mp, char const *str)
914 static char lastscan[128];
916 struct str in, out;
917 char *hfield, *hbody;
918 bool_t rv;
919 NYD_ENTER;
921 if (*++str == '\0') {
922 str = lastscan;
923 } else {
924 strncpy(lastscan, str, sizeof lastscan); /* XXX use new n_str object! */
925 lastscan[sizeof lastscan -1] = '\0';
928 /* Now look, ignoring case, for the word in the string */
929 if (ok_blook(searchheaders) && (hfield = strchr(str, ':'))) {
930 size_t l = PTR2SIZE(hfield - str);
931 hfield = ac_alloc(l +1);
932 memcpy(hfield, str, l);
933 hfield[l] = '\0';
934 hbody = hfieldX(hfield, mp);
935 ac_free(hfield);
936 hfield = UNCONST(str + l + 1);
937 } else {
938 hfield = UNCONST(str);
939 hbody = hfield1("subject", mp);
941 if (hbody == NULL) {
942 rv = FAL0;
943 goto jleave;
946 in.s = hbody;
947 in.l = strlen(hbody);
948 mime_fromhdr(&in, &out, TD_ICONV);
949 rv = substr(out.s, hfield);
950 free(out.s);
951 jleave:
952 NYD_LEAVE;
953 return rv;
956 static bool_t
957 _match_at(struct message *mp, struct search_expr *sep)
959 struct str in, out;
960 char *nfield, *cfield;
961 bool_t rv = FAL0;
962 NYD_ENTER;
964 nfield = savestr(sep->ss_where);
966 while ((cfield = n_strsep(&nfield, ',', TRU1)) != NULL) {
967 if (!asccasecmp(cfield, "body") ||
968 (cfield[1] == '\0' && cfield[0] == '>')) {
969 rv = FAL0;
970 jmsg:
971 if ((rv = message_match(mp, sep, rv)))
972 break;
973 } else if (!asccasecmp(cfield, "text") ||
974 (cfield[1] == '\0' && cfield[0] == '=')) {
975 rv = TRU1;
976 goto jmsg;
977 } else if (!asccasecmp(cfield, "header") ||
978 (cfield[1] == '\0' && cfield[0] == '<')) {
979 if ((rv = header_match(mp, sep)))
980 break;
981 } else if ((in.s = hfieldX(cfield, mp)) == NULL)
982 continue;
983 else {
984 in.l = strlen(in.s);
985 mime_fromhdr(&in, &out, TD_ICONV);
986 #ifdef HAVE_REGEX
987 if (sep->ss_sexpr == NULL)
988 rv = (regexec(&sep->ss_regex, out.s, 0,NULL, 0) != REG_NOMATCH);
989 else
990 #endif
991 rv = substr(out.s, sep->ss_sexpr);
992 free(out.s);
993 if (rv)
994 break;
997 NYD_LEAVE;
998 return rv;
1001 static void
1002 unmark(int mesg)
1004 size_t i;
1005 NYD_ENTER;
1007 i = (size_t)mesg;
1008 if (i < 1 || UICMP(z, i, >, msgCount))
1009 panic(_("Bad message number to unmark"));
1010 message[i - 1].m_flag &= ~MMARK;
1011 NYD_LEAVE;
1014 static int
1015 metamess(int meta, int f)
1017 int c, m;
1018 struct message *mp;
1019 NYD_ENTER;
1021 c = meta;
1022 switch (c) {
1023 case '^': /* First 'good' message left */
1024 mp = mb.mb_threaded ? threadroot : message;
1025 while (PTRCMP(mp, <, message + msgCount)) {
1026 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & MDELETED) == (ui32_t)f) {
1027 c = (int)PTR2SIZE(mp - message + 1);
1028 goto jleave;
1030 if (mb.mb_threaded) {
1031 mp = next_in_thread(mp);
1032 if (mp == NULL)
1033 break;
1034 } else
1035 ++mp;
1037 if (!(pstate & PS_HOOK_MASK))
1038 printf(_("No applicable messages\n"));
1039 goto jem1;
1041 case '$': /* Last 'good message left */
1042 mp = mb.mb_threaded
1043 ? this_in_thread(threadroot, -1) : message + msgCount - 1;
1044 while (mp >= message) {
1045 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & MDELETED) == (ui32_t)f) {
1046 c = (int)PTR2SIZE(mp - message + 1);
1047 goto jleave;
1049 if (mb.mb_threaded) {
1050 mp = prev_in_thread(mp);
1051 if (mp == NULL)
1052 break;
1053 } else
1054 --mp;
1056 if (!(pstate & PS_HOOK_MASK))
1057 printf(_("No applicable messages\n"));
1058 goto jem1;
1060 case '.':
1061 /* Current message */
1062 m = dot - message + 1;
1063 if ((dot->m_flag & MHIDDEN) || (dot->m_flag & MDELETED) != (ui32_t)f) {
1064 printf(_("%d: Inappropriate message\n"), m);
1065 goto jem1;
1067 c = m;
1068 break;
1070 case ';':
1071 /* Previously current message */
1072 if (prevdot == NULL) {
1073 fprintf(stderr, _("No previously current message\n"));
1074 goto jem1;
1076 m = prevdot - message + 1;
1077 if ((prevdot->m_flag & MHIDDEN) ||
1078 (prevdot->m_flag & MDELETED) != (ui32_t)f) {
1079 fprintf(stderr, _("%d: Inappropriate message\n"), m);
1080 goto jem1;
1082 c = m;
1083 break;
1085 default:
1086 fprintf(stderr, _("Unknown metachar (%c)\n"), c);
1087 goto jem1;
1089 jleave:
1090 NYD_LEAVE;
1091 return c;
1092 jem1:
1093 c = -1;
1094 goto jleave;
1097 FL int
1098 getmsglist(char *buf, int *vector, int flags)
1100 int *ip, mc;
1101 struct message *mp;
1102 NYD_ENTER;
1104 pstate &= ~PS_MSGLIST_MASK;
1106 if (msgCount == 0) {
1107 *vector = 0;
1108 mc = 0;
1109 goto jleave;
1112 pstate |= PS_MSGLIST_DIRECT;
1114 if (markall(buf, flags) < 0) {
1115 mc = -1;
1116 goto jleave;
1119 ip = vector;
1120 if (pstate & PS_HOOK_NEWMAIL) {
1121 mc = 0;
1122 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
1123 if (mp->m_flag & MMARK) {
1124 if (!(mp->m_flag & MNEWEST))
1125 unmark((int)PTR2SIZE(mp - message + 1));
1126 else
1127 ++mc;
1129 if (mc == 0) {
1130 mc = -1;
1131 goto jleave;
1135 if (mb.mb_threaded == 0) {
1136 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
1137 if (mp->m_flag & MMARK)
1138 *ip++ = (int)PTR2SIZE(mp - message + 1);
1139 } else {
1140 for (mp = threadroot; mp != NULL; mp = next_in_thread(mp))
1141 if (mp->m_flag & MMARK)
1142 *ip++ = (int)PTR2SIZE(mp - message + 1);
1144 *ip = 0;
1145 mc = (int)PTR2SIZE(ip - vector);
1146 if (mc != 1)
1147 pstate &= ~PS_MSGLIST_DIRECT;
1148 jleave:
1149 NYD_LEAVE;
1150 return mc;
1153 FL int
1154 getrawlist(char const *line, size_t linesize, char **argv, int argc,
1155 int echolist)
1157 char c, *cp2, quotec, *linebuf;
1158 int argn;
1159 NYD_ENTER;
1161 pstate &= ~PS_MSGLIST_MASK;
1163 linebuf = ac_alloc(linesize);
1165 for (argn = 0;;) {
1166 if (!argn || !echolist) {
1167 for (; blankchar(*line); ++line)
1169 if (*line == '\0')
1170 break;
1172 if (argn >= argc - 1) {
1173 fprintf(stderr,
1174 _("Too many elements in the list; excess discarded.\n"));
1175 break;
1178 cp2 = linebuf;
1179 for (quotec = '\0'; ((c = *line++) != '\0');) {
1180 if (quotec != '\0') {
1181 if (c == quotec) {
1182 quotec = '\0';
1183 if (echolist)
1184 *cp2++ = c;
1185 } else if (c == '\\') {
1186 switch (c = *line++) {
1187 case '\0':
1188 *cp2++ = '\\';
1189 --line;
1190 break;
1191 default:
1192 if (line[-1] != quotec || echolist)
1193 *cp2++ = '\\';
1194 *cp2++ = c;
1196 } else
1197 *cp2++ = c;
1198 } else if (c == '"' || c == '\'') {
1199 if (echolist)
1200 *cp2++ = c;
1201 quotec = c;
1202 } else if (c == '\\' && !echolist)
1203 *cp2++ = (*line != '\0') ? *line++ : c;
1204 else if (blankchar(c))
1205 break;
1206 else
1207 *cp2++ = c;
1209 argv[argn++] = savestrbuf(linebuf, PTR2SIZE(cp2 - linebuf));
1210 if (c == '\0')
1211 break;
1213 argv[argn] = NULL;
1215 ac_free(linebuf);
1216 NYD_LEAVE;
1217 return argn;
1220 FL int
1221 first(int f, int m)
1223 struct message *mp;
1224 int rv;
1225 NYD_ENTER;
1227 if (msgCount == 0) {
1228 rv = 0;
1229 goto jleave;
1232 f &= MDELETED;
1233 m &= MDELETED;
1234 for (mp = dot;
1235 mb.mb_threaded ? (mp != NULL) : PTRCMP(mp, <, message + msgCount);
1236 mb.mb_threaded ? (mp = next_in_thread(mp)) : ++mp) {
1237 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & m) == (ui32_t)f) {
1238 rv = (int)PTR2SIZE(mp - message + 1);
1239 goto jleave;
1243 if (dot > message) {
1244 for (mp = dot - 1; (mb.mb_threaded ? (mp != NULL) : (mp >= message));
1245 mb.mb_threaded ? (mp = prev_in_thread(mp)) : --mp) {
1246 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & m) == (ui32_t)f) {
1247 rv = (int)PTR2SIZE(mp - message + 1);
1248 goto jleave;
1252 rv = 0;
1253 jleave:
1254 NYD_LEAVE;
1255 return rv;
1258 FL void
1259 mark(int mesg, int f)
1261 struct message *mp;
1262 int i;
1263 NYD_ENTER;
1265 i = mesg;
1266 if (i < 1 || i > msgCount)
1267 panic(_("Bad message number to mark"));
1268 if (mb.mb_threaded == 1 && threadflag) {
1269 if (!(message[i - 1].m_flag & MHIDDEN)) {
1270 if (f == MDELETED || !(message[i - 1].m_flag & MDELETED))
1271 message[i - 1].m_flag |= MMARK;
1274 if (message[i - 1].m_child) {
1275 mp = message[i - 1].m_child;
1276 mark((int)PTR2SIZE(mp - message + 1), f);
1277 for (mp = mp->m_younger; mp != NULL; mp = mp->m_younger)
1278 mark((int)PTR2SIZE(mp - message + 1), f);
1280 } else
1281 message[i - 1].m_flag |= MMARK;
1282 NYD_LEAVE;
1285 /* s-it-mode */