nail.h: add struct sendbundle
[s-mailx.git] / list.c
blob7fe487285943f18dbea21740ac9e2d7c4bd9c225
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 - 2014 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.
40 #ifndef HAVE_AMALGAMATION
41 # include "nail.h"
42 #endif
44 /* Token values returned by the scanner used for argument lists.
45 * Also, sizes of scanner-related things */
46 enum ltoken {
47 TEOL = 0, /* End of the command line */
48 TNUMBER = 1, /* A message number */
49 TDASH = 2, /* A simple dash */
50 TSTRING = 3, /* A string (possibly containing -) */
51 TDOT = 4, /* A "." */
52 TUP = 5, /* A "^" */
53 TDOLLAR = 6, /* A "$" */
54 TSTAR = 7, /* A "*" */
55 TOPEN = 8, /* A '(' */
56 TCLOSE = 9, /* A ')' */
57 TPLUS = 10, /* A '+' */
58 TERROR = 11, /* A lexical error */
59 TCOMMA = 12, /* A ',' */
60 TSEMI = 13, /* A ';' */
61 TBACK = 14 /* A '`' */
64 #define REGDEP 2 /* Maximum regret depth. */
66 enum idfield {
67 ID_REFERENCES,
68 ID_IN_REPLY_TO
71 enum {
72 CMNEW = 1<<0, /* New messages */
73 CMOLD = 1<<1, /* Old messages */
74 CMUNREAD = 1<<2, /* Unread messages */
75 CMDELETED =1<<3, /* Deleted messages */
76 CMREAD = 1<<4, /* Read messages */
77 CMFLAG = 1<<5, /* Flagged messages */
78 CMANSWER = 1<<6, /* Answered messages */
79 CMDRAFT = 1<<7, /* Draft messages */
80 CMSPAM = 1<<8 /* Spam messages */
83 struct coltab {
84 char co_char; /* What to find past : */
85 int co_bit; /* Associated modifier bit */
86 int co_mask; /* m_status bits to mask */
87 int co_equal; /* ... must equal this */
90 struct lex {
91 char l_char;
92 enum ltoken l_token;
95 static struct coltab const _coltab[] = {
96 { 'n', CMNEW, MNEW, MNEW },
97 { 'o', CMOLD, MNEW, 0 },
98 { 'u', CMUNREAD, MREAD, 0 },
99 { 'd', CMDELETED, MDELETED, MDELETED },
100 { 'r', CMREAD, MREAD, MREAD },
101 { 'f', CMFLAG, MFLAGGED, MFLAGGED },
102 { 'a', CMANSWER, MANSWERED, MANSWERED },
103 { 't', CMDRAFT, MDRAFTED, MDRAFTED },
104 { 's', CMSPAM, MSPAM, MSPAM },
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, mc, other, valdot, colmod, colresult;
205 struct message *mp, *mx;
206 bool_t 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 beg = mc = star = other = topen = tback = FAL0;
234 #ifdef HAVE_IMAP
235 gotheaders = 0;
236 #endif
238 for (tok = scan(&bufp); tok != TEOL;) {
239 switch (tok) {
240 case TNUMBER:
241 number:
242 if (star) {
243 fprintf(stderr, tr(112, "No numbers mixed with *\n"));
244 markall_ret(-1)
246 list_saw_numbers = TRU1;
247 ++mc;
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 msglist_is_single = FAL0;
283 if (beg != 0) {
284 printf(tr(113, "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 fprintf(stderr, tr(114, "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 msglist_is_single = FAL0;
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 fprintf(stderr, tr(115, "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 msglist_is_single = FAL0;
325 if (beg != 0) {
326 fprintf(stderr, tr(116, "Non-numeric second argument\n"));
327 markall_ret(-1)
329 ++other;
330 if (lexstring[0] == ':') {
331 colresult = evalcol(lexstring[1]);
332 if (colresult == 0) {
333 fprintf(stderr, tr(117, "Unknown colon modifier \"%s\"\n"),
334 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 msglist_is_single = FAL0;
346 if (imap_search(lexstring, f) == STOP)
347 markall_ret(-1)
348 topen = TRU1;
349 #else
350 fprintf(stderr, tr(42,
351 "`%s': the used selector is optional and not available\n"),
352 lexstring);
353 markall_ret(-1)
354 #endif
355 break;
357 case TDOLLAR:
358 case TUP:
359 case TDOT:
360 case TSEMI:
361 msglist_is_single = FAL0;
362 lexnumber = metamess(lexstring[0], f);
363 if (lexnumber == -1)
364 markall_ret(-1)
365 goto number;
367 case TBACK:
368 msglist_is_single = FAL0;
369 tback = TRU1;
370 for (i = 1; i <= msgCount; i++) {
371 if ((message[i - 1].m_flag & MHIDDEN) ||
372 (message[i - 1].m_flag & MDELETED) != (unsigned)f)
373 continue;
374 if (message[i - 1].m_flag & MOLDMARK)
375 mark(i, f);
377 break;
379 case TSTAR:
380 msglist_is_single = FAL0;
381 if (other) {
382 fprintf(stderr, tr(118, "Can't mix \"*\" with anything\n"));
383 markall_ret(-1)
385 star = TRU1;
386 break;
388 case TCOMMA:
389 msglist_is_single = FAL0;
390 #ifdef HAVE_IMAP
391 if (mb.mb_type == MB_IMAP && gotheaders++ == 0)
392 imap_getheaders(1, msgCount);
393 #endif
394 if (id == NULL && (cp = hfield1("in-reply-to", dot)) != NULL) {
395 id = savestr(cp);
396 idfield = ID_IN_REPLY_TO;
398 if (id == NULL && (cp = hfield1("references", dot)) != NULL) {
399 struct name *enp;
401 if ((enp = extract(cp, GREF)) != NULL) {
402 while (enp->n_flink != NULL)
403 enp = enp->n_flink;
404 id = savestr(enp->n_name);
405 idfield = ID_REFERENCES;
408 if (id == NULL) {
409 printf(tr(227,
410 "Cannot determine parent Message-ID of the current message\n"));
411 markall_ret(-1)
413 break;
415 case TERROR:
416 list_saw_numbers = TRU1;
417 msglist_is_single = FAL0;
418 markall_ret(-1)
420 threadflag = 0;
421 tok = scan(&bufp);
424 lastcolmod = colmod;
425 np = add_to_namelist(&namelist, &nmlsize, np, NULL);
426 --np;
427 mc = 0;
428 if (star) {
429 for (i = 0; i < msgCount; ++i) {
430 if (!(message[i].m_flag & MHIDDEN) &&
431 (message[i].m_flag & MDELETED) == (unsigned)f) {
432 mark(i + 1, f);
433 ++mc;
436 if (mc == 0) {
437 if (!inhook)
438 printf(tr(119, "No applicable messages.\n"));
439 markall_ret(-1)
441 markall_ret(0)
444 if ((topen || tback) && mc == 0) {
445 for (i = 0; i < msgCount; ++i)
446 if (message[i].m_flag & MMARK)
447 ++mc;
448 if (mc == 0) {
449 if (!inhook) {
450 if (tback)
451 fprintf(stderr, tr(131, "No previously marked messages.\n"));
452 else
453 printf("No messages satisfy (criteria).\n");/*TODO tr*/
455 markall_ret(-1)
459 /* If no numbers were given, mark all messages, so that we can unmark
460 * any whose sender was not selected if any user names were given */
461 if ((np > namelist || colmod != 0 || id) && mc == 0)
462 for (i = 1; i <= msgCount; ++i) {
463 if (!(message[i - 1].m_flag & MHIDDEN) &&
464 (message[i - 1].m_flag & MDELETED) == (unsigned)f)
465 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 fprintf(stderr, tr(525, "Empty `[@..]@' search expression\n"));
501 rv = -1;
502 continue;
504 #ifdef HAVE_REGEX
505 if (anyof("^.[]*+?(){}|$", x)) {
506 sep[j].ss_sexpr = NULL;
507 if (regcomp(&sep[j].ss_reexpr, x,
508 REG_EXTENDED | REG_ICASE | REG_NOSUB) != 0) {
509 fprintf(stderr, tr(526,
510 "Invalid regular expression: >>> %s <<<\n"), x);
511 rv = -1;
512 continue;
514 } else
515 #endif
516 sep[j].ss_sexpr = x;
518 if (rv < 0)
519 goto jnamesearch_sepfree;
522 #ifdef HAVE_IMAP
523 if (mb.mb_type == MB_IMAP && gotheaders++ == 0)
524 imap_getheaders(1, msgCount);
525 #endif
526 srelax_hold();
527 allnet = ok_blook(allnet);
528 for (i = 1; i <= msgCount; ++i) {
529 mp = message + i - 1;
530 j = 0;
531 if (np > namelist) {
532 for (nq = namelist; *nq != NULL; ++nq) {
533 if (**nq == '@') {
534 if (_match_at(mp, sep + PTR2SIZE(nq - namelist))) {
535 ++j;
536 break;
538 } else if (**nq == '/') {
539 if (_match_dash(mp, *nq)) {
540 ++j;
541 break;
543 } else if (_matchsender(mp, *nq, allnet)) {
544 ++j;
545 break;
549 if (j == 0 && id && _matchmid(mp, id, idfield))
550 ++j;
551 if (j == 0)
552 mp->m_flag &= ~MMARK;
553 srelax();
555 srelax_rele();
557 /* Make sure we got some decent messages */
558 j = 0;
559 for (i = 1; i <= msgCount; ++i)
560 if (message[i - 1].m_flag & MMARK) {
561 ++j;
562 break;
564 if (j == 0) {
565 if (!inhook && np > namelist) {
566 printf(tr(120, "No applicable messages from {%s"), namelist[0]);
567 for (nq = namelist + 1; *nq != NULL; ++nq)
568 printf(tr(121, ", %s"), *nq);
569 printf(tr(122, "}\n"));
570 } else if (id)
571 printf(tr(227, "Parent message not found\n"));
572 rv = -1;
573 goto jnamesearch_sepfree;
576 jnamesearch_sepfree:
577 if (sep != NULL) {
578 #ifdef HAVE_REGEX
579 for (j = PTR2SIZE(np - namelist); j-- != 0;)
580 if (sep[j].ss_sexpr == NULL)
581 regfree(&sep[j].ss_reexpr);
582 #endif
583 free(sep);
585 if (rv < 0)
586 goto jleave;
589 /* If any colon modifiers were given, go through and unmark any
590 * messages which do not satisfy the modifiers */
591 if (colmod != 0) {
592 for (i = 1; i <= msgCount; ++i) {
593 struct coltab const *colp;
594 bool_t bad = TRU1;
596 mp = message + i - 1;
597 for (colp = _coltab; colp->co_char != '\0'; ++colp)
598 if ((colp->co_bit & colmod) &&
599 ((mp->m_flag & colp->co_mask) == (unsigned)colp->co_equal))
600 bad = FAL0;
601 if (bad)
602 unmark(i);
604 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
605 if (mp->m_flag & MMARK)
606 break;
607 if (PTRCMP(mp, >=, message + msgCount)) {
608 struct coltab const *colp;
610 if (!inhook) {
611 printf(tr(123, "No messages satisfy"));
612 for (colp = _coltab; colp->co_char != '\0'; ++colp)
613 if (colp->co_bit & colmod)
614 printf(" :%c", colp->co_char);
615 printf("\n");
617 markall_ret(-1)
621 markall_ret(0)
622 jleave:
623 free(namelist);
624 ac_free(lexstring);
625 NYD_LEAVE;
626 return rv;
628 #undef markall_ret
631 static int
632 evalcol(int col)
634 struct coltab const *colp;
635 int rv;
636 NYD_ENTER;
638 if (col == 0)
639 rv = lastcolmod;
640 else {
641 rv = 0;
642 for (colp = _coltab; colp->co_char != '\0'; ++colp)
643 if (colp->co_char == col) {
644 rv = colp->co_bit;
645 break;
648 NYD_LEAVE;
649 return rv;
652 static int
653 check(int mesg, int f)
655 struct message *mp;
656 NYD_ENTER;
658 if (mesg < 1 || mesg > msgCount) {
659 printf(tr(124, "%d: Invalid message number\n"), mesg);
660 goto jem1;
662 mp = message + mesg - 1;
663 if (mp->m_flag & MHIDDEN ||
664 (f != MDELETED && (mp->m_flag & MDELETED) != 0)) {
665 fprintf(stderr, tr(125, "%d: Inappropriate message\n"), mesg);
666 goto jem1;
668 f = 0;
669 jleave:
670 NYD_LEAVE;
671 return f;
672 jem1:
673 f = -1;
674 goto jleave;
677 static int
678 scan(char **sp)
680 char *cp, *cp2;
681 int rv, c, inquote, quotec;
682 struct lex const *lp;
683 NYD_ENTER;
685 if (regretp >= 0) {
686 strncpy(lexstring, string_stack[regretp], STRINGLEN);
687 lexstring[STRINGLEN -1] = '\0';
688 lexnumber = numberstack[regretp];
689 rv = regretstack[regretp--];
690 goto jleave;
693 cp = *sp;
694 cp2 = lexstring;
695 c = *cp++;
697 /* strip away leading white space */
698 while (blankchar(c))
699 c = *cp++;
701 /* If no characters remain, we are at end of line, so report that */
702 if (c == '\0') {
703 *sp = --cp;
704 rv = TEOL;
705 goto jleave;
708 /* Select members of a message thread */
709 if (c == '&') {
710 threadflag = 1;
711 if (*cp == '\0' || spacechar(*cp)) {
712 lexstring[0] = '.';
713 lexstring[1] = '\0';
714 *sp = cp;
715 rv = TDOT;
716 goto jleave;
718 c = *cp++;
721 /* If the leading character is a digit, scan the number and convert it
722 * on the fly. Return TNUMBER when done */
723 if (digitchar(c)) {
724 lexnumber = 0;
725 while (digitchar(c)) {
726 lexnumber = lexnumber*10 + c - '0';
727 *cp2++ = c;
728 c = *cp++;
730 *cp2 = '\0';
731 *sp = --cp;
732 rv = TNUMBER;
733 goto jleave;
736 /* An IMAP SEARCH list. Note that TOPEN has always been included in
737 * singles[] in Mail and mailx. Thus although there is no formal
738 * definition for (LIST) lists, they do not collide with historical
739 * practice because a subject string (LIST) could never been matched
740 * this way */
741 if (c == '(') {
742 ui32_t level = 1;
743 inquote = 0;
744 *cp2++ = c;
745 do {
746 if ((c = *cp++&0377) == '\0') {
747 jmtop:
748 fprintf(stderr, "Missing \")\".\n");
749 rv = TERROR;
750 goto jleave;
752 if (inquote && c == '\\') {
753 *cp2++ = c;
754 c = *cp++&0377;
755 if (c == '\0')
756 goto jmtop;
757 } else if (c == '"')
758 inquote = !inquote;
759 else if (inquote)
760 /*EMPTY*/;
761 else if (c == '(')
762 ++level;
763 else if (c == ')')
764 --level;
765 else if (spacechar(c)) {
766 /* Replace unquoted whitespace by single space characters, to make
767 * the string IMAP SEARCH conformant */
768 c = ' ';
769 if (cp2[-1] == ' ')
770 --cp2;
772 *cp2++ = c;
773 } while (c != ')' || level > 0);
774 *cp2 = '\0';
775 *sp = cp;
776 rv = TOPEN;
777 goto jleave;
780 /* Check for single character tokens; return such if found */
781 for (lp = _singles; lp->l_char != '\0'; ++lp)
782 if (c == lp->l_char) {
783 lexstring[0] = c;
784 lexstring[1] = '\0';
785 *sp = cp;
786 rv = lp->l_token;
787 goto jleave;
790 /* We've got a string! Copy all the characters of the string into
791 * lexstring, until we see a null, space, or tab. If the lead character is
792 * a " or ', save it and scan until you get another */
793 quotec = 0;
794 if (c == '\'' || c == '"') {
795 quotec = c;
796 c = *cp++;
798 while (c != '\0') {
799 if (quotec == 0 && c == '\\' && *cp != '\0')
800 c = *cp++;
801 if (c == quotec) {
802 ++cp;
803 break;
805 if (quotec == 0 && blankchar(c))
806 break;
807 if (PTRCMP(cp2 - lexstring, <, STRINGLEN - 1))
808 *cp2++ = c;
809 c = *cp++;
811 if (quotec && c == 0) {
812 fprintf(stderr, tr(127, "Missing %c\n"), quotec);
813 rv = TERROR;
814 goto jleave;
816 *sp = --cp;
817 *cp2 = '\0';
818 rv = TSTRING;
819 jleave:
820 NYD_LEAVE;
821 return rv;
824 static void
825 regret(int token)
827 NYD_ENTER;
828 if (++regretp >= REGDEP)
829 panic(tr(128, "Too many regrets"));
830 regretstack[regretp] = token;
831 lexstring[STRINGLEN -1] = '\0';
832 string_stack[regretp] = savestr(lexstring);
833 numberstack[regretp] = lexnumber;
834 NYD_LEAVE;
837 static void
838 scaninit(void)
840 NYD_ENTER;
841 regretp = -1;
842 threadflag = 0;
843 NYD_LEAVE;
846 static bool_t
847 _matchsender(struct message *mp, char const *str, bool_t allnet)
849 bool_t rv;
850 NYD_ENTER;
852 if (allnet) {
853 char *cp = nameof(mp, 0);
855 do {
856 if ((*cp == '@' || *cp == '\0') && (*str == '@' || *str == '\0')) {
857 rv = TRU1;
858 goto jleave;
860 if (*cp != *str)
861 break;
862 } while (++cp, *str++ != '\0');
863 rv = FAL0;
864 goto jleave;
866 rv = !strcmp(str, (*(ok_blook(showname) ? &realname : &skin))(name1(mp, 0)));
867 jleave:
868 NYD_LEAVE;
869 return rv;
872 static bool_t
873 _matchmid(struct message *mp, char *id, enum idfield idfield)
875 char *cp;
876 bool_t rv;
877 NYD_ENTER;
879 if ((cp = hfield1("message-id", mp)) != NULL) {
880 switch (idfield) {
881 case ID_REFERENCES:
882 rv = !msgidcmp(id, cp);
883 goto jleave;
884 case ID_IN_REPLY_TO: {
885 struct name *np;
887 if ((np = extract(id, GREF)) != NULL)
888 do {
889 if (!msgidcmp(np->n_name, cp)) {
890 rv = TRU1;
891 goto jleave;
893 } while ((np = np->n_flink) != NULL);
894 break;
898 rv = FAL0;
899 jleave:
900 NYD_LEAVE;
901 return rv;
904 static bool_t
905 _match_dash(struct message *mp, char const *str)
907 static char lastscan[128];
909 struct str in, out;
910 char *hfield, *hbody;
911 bool_t rv;
912 NYD_ENTER;
914 if (*++str == '\0') {
915 str = lastscan;
916 } else {
917 strncpy(lastscan, str, sizeof lastscan); /* XXX use new n_str object! */
918 lastscan[sizeof lastscan -1] = '\0';
921 /* Now look, ignoring case, for the word in the string */
922 if (ok_blook(searchheaders) && (hfield = strchr(str, ':'))) {
923 size_t l = PTR2SIZE(hfield - str);
924 hfield = ac_alloc(l +1);
925 memcpy(hfield, str, l);
926 hfield[l] = '\0';
927 hbody = hfieldX(hfield, mp);
928 ac_free(hfield);
929 hfield = UNCONST(str + l + 1);
930 } else {
931 hfield = UNCONST(str);
932 hbody = hfield1("subject", mp);
934 if (hbody == NULL) {
935 rv = FAL0;
936 goto jleave;
939 in.s = hbody;
940 in.l = strlen(hbody);
941 mime_fromhdr(&in, &out, TD_ICONV);
942 rv = substr(out.s, hfield);
943 free(out.s);
944 jleave:
945 NYD_LEAVE;
946 return rv;
949 static bool_t
950 _match_at(struct message *mp, struct search_expr *sep)
952 struct str in, out;
953 char *nfield, *cfield;
954 bool_t rv = FAL0;
955 NYD_ENTER;
957 nfield = savestr(sep->ss_where);
959 while ((cfield = n_strsep(&nfield, ',', TRU1)) != NULL) {
960 if (!asccasecmp(cfield, "body")) {
961 rv = FAL0;
962 jmsg:
963 if ((rv = message_match(mp, sep, rv)))
964 break;
965 } else if (!asccasecmp(cfield, "text")) {
966 rv = TRU1;
967 goto jmsg;
968 } else if ((in.s = hfieldX(cfield, mp)) == NULL)
969 continue;
970 else {
971 in.l = strlen(in.s);
972 mime_fromhdr(&in, &out, TD_ICONV);
973 #ifdef HAVE_REGEX
974 if (sep->ss_sexpr == NULL)
975 rv = (regexec(&sep->ss_reexpr, out.s, 0,NULL, 0) != REG_NOMATCH);
976 else
977 #endif
978 rv = substr(out.s, sep->ss_sexpr);
979 free(out.s);
980 if (rv)
981 break;
984 NYD_LEAVE;
985 return rv;
988 static void
989 unmark(int mesg)
991 size_t i;
992 NYD_ENTER;
994 i = (size_t)mesg;
995 if (i < 1 || UICMP(z, i, >, msgCount))
996 panic(tr(130, "Bad message number to unmark"));
997 message[i - 1].m_flag &= ~MMARK;
998 NYD_LEAVE;
1001 static int
1002 metamess(int meta, int f)
1004 int c, m;
1005 struct message *mp;
1006 NYD_ENTER;
1008 c = meta;
1009 switch (c) {
1010 case '^': /* First 'good' message left */
1011 mp = mb.mb_threaded ? threadroot : message;
1012 while (PTRCMP(mp, <, message + msgCount)) {
1013 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & MDELETED) == (ui32_t)f) {
1014 c = (int)PTR2SIZE(mp - message + 1);
1015 goto jleave;
1017 if (mb.mb_threaded) {
1018 mp = next_in_thread(mp);
1019 if (mp == NULL)
1020 break;
1021 } else
1022 ++mp;
1024 if (!inhook)
1025 printf(tr(132, "No applicable messages\n"));
1026 goto jem1;
1028 case '$': /* Last 'good message left */
1029 mp = mb.mb_threaded
1030 ? this_in_thread(threadroot, -1) : message + msgCount - 1;
1031 while (mp >= message) {
1032 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & MDELETED) == (ui32_t)f) {
1033 c = (int)PTR2SIZE(mp - message + 1);
1034 goto jleave;
1036 if (mb.mb_threaded) {
1037 mp = prev_in_thread(mp);
1038 if (mp == NULL)
1039 break;
1040 } else
1041 --mp;
1043 if (!inhook)
1044 printf(tr(132, "No applicable messages\n"));
1045 goto jem1;
1047 case '.':
1048 /* Current message */
1049 m = dot - message + 1;
1050 if ((dot->m_flag & MHIDDEN) || (dot->m_flag & MDELETED) != (ui32_t)f) {
1051 printf(tr(133, "%d: Inappropriate message\n"), m);
1052 goto jem1;
1054 c = m;
1055 break;
1057 case ';':
1058 /* Previously current message */
1059 if (prevdot == NULL) {
1060 fprintf(stderr, tr(228, "No previously current message\n"));
1061 goto jem1;
1063 m = prevdot - message + 1;
1064 if ((prevdot->m_flag & MHIDDEN) ||
1065 (prevdot->m_flag & MDELETED) != (ui32_t)f) {
1066 fprintf(stderr, tr(133, "%d: Inappropriate message\n"), m);
1067 goto jem1;
1069 c = m;
1070 break;
1072 default:
1073 fprintf(stderr, tr(134, "Unknown metachar (%c)\n"), c);
1074 goto jem1;
1076 jleave:
1077 NYD_LEAVE;
1078 return c;
1079 jem1:
1080 c = -1;
1081 goto jleave;
1084 FL int
1085 getmsglist(char *buf, int *vector, int flags)
1087 int *ip, mc;
1088 struct message *mp;
1089 NYD_ENTER;
1091 list_saw_numbers =
1092 msglist_is_single = FAL0;
1094 if (msgCount == 0) {
1095 *vector = 0;
1096 mc = 0;
1097 goto jleave;
1100 msglist_is_single = TRU1;
1101 if (markall(buf, flags) < 0) {
1102 mc = -1;
1103 goto jleave;
1106 ip = vector;
1107 if (inhook & 2) {
1108 mc = 0;
1109 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
1110 if (mp->m_flag & MMARK) {
1111 if (!(mp->m_flag & MNEWEST))
1112 unmark((int)PTR2SIZE(mp - message + 1));
1113 else
1114 ++mc;
1116 if (mc == 0) {
1117 mc = -1;
1118 goto jleave;
1122 if (mb.mb_threaded == 0) {
1123 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
1124 if (mp->m_flag & MMARK)
1125 *ip++ = (int)PTR2SIZE(mp - message + 1);
1126 } else {
1127 for (mp = threadroot; mp != NULL; mp = next_in_thread(mp))
1128 if (mp->m_flag & MMARK)
1129 *ip++ = (int)PTR2SIZE(mp - message + 1);
1131 *ip = 0;
1132 mc = (int)PTR2SIZE(ip - vector);
1133 msglist_is_single = (mc == 1);
1134 jleave:
1135 NYD_LEAVE;
1136 return mc;
1139 FL int
1140 getrawlist(char const *line, size_t linesize, char **argv, int argc,
1141 int echolist)
1143 char c, *cp2, quotec, *linebuf;
1144 char const *cp;
1145 int argn;
1146 NYD_ENTER;
1148 list_saw_numbers = FAL0;
1150 argn = 0;
1151 cp = line;
1152 linebuf = ac_alloc(linesize +1);
1153 for (;;) {
1154 for (; blankchar(*cp); ++cp)
1156 if (*cp == '\0')
1157 break;
1158 if (argn >= argc - 1) {
1159 printf(tr(126, "Too many elements in the list; excess discarded.\n"));
1160 break;
1162 cp2 = linebuf;
1163 quotec = '\0';
1164 while ((c = *cp) != '\0') {
1165 cp++;
1166 if (quotec != '\0') {
1167 if (c == quotec) {
1168 quotec = '\0';
1169 if (echolist)
1170 *cp2++ = c;
1171 } else if (c == '\\')
1172 switch (c = *cp++) {
1173 case '\0':
1174 *cp2++ = '\\';
1175 cp--;
1176 break;
1178 case '0': case '1': case '2': case '3':
1179 case '4': case '5': case '6': case '7':
1180 c -= '0';
1181 if (*cp >= '0' && *cp <= '7')
1182 c = c * 8 + *cp++ - '0';
1183 if (*cp >= '0' && *cp <= '7')
1184 c = c * 8 + *cp++ - '0';
1185 *cp2++ = c;
1186 break;
1187 case 'b':
1188 *cp2++ = '\b';
1189 break;
1190 case 'f':
1191 *cp2++ = '\f';
1192 break;
1193 case 'n':
1194 *cp2++ = '\n';
1195 break;
1196 case 'r':
1197 *cp2++ = '\r';
1198 break;
1199 case 't':
1200 *cp2++ = '\t';
1201 break;
1202 case 'v':
1203 *cp2++ = '\v';
1204 break;
1206 default:
1207 if (cp[-1] != quotec || echolist)
1208 *cp2++ = '\\';
1209 *cp2++ = c;
1211 /*else if (c == '^') {
1212 c = *cp++;
1213 if (c == '?')
1214 *cp2++ = '\177';
1215 /\* null doesn't show up anyway *\/
1216 else if ((c >= 'A' && c <= '_') ||
1217 (c >= 'a' && c <= 'z'))
1218 *cp2++ = c & 037;
1219 else {
1220 *cp2++ = '^';
1221 cp--;
1223 }*/ else
1224 *cp2++ = c;
1225 } else if (c == '"' || c == '\'') {
1226 if (echolist)
1227 *cp2++ = c;
1228 quotec = c;
1229 } else if (c == '\\' && !echolist) {
1230 if (*cp)
1231 *cp2++ = *cp++;
1232 else
1233 *cp2++ = c;
1234 } else if (blankchar(c))
1235 break;
1236 else
1237 *cp2++ = c;
1239 *cp2 = '\0';
1240 argv[argn++] = savestr(linebuf);
1242 argv[argn] = NULL;
1243 ac_free(linebuf);
1244 NYD_LEAVE;
1245 return argn;
1248 FL int
1249 first(int f, int m)
1251 struct message *mp;
1252 int rv;
1253 NYD_ENTER;
1255 if (msgCount == 0) {
1256 rv = 0;
1257 goto jleave;
1260 f &= MDELETED;
1261 m &= MDELETED;
1262 for (mp = dot;
1263 mb.mb_threaded ? (mp != NULL) : PTRCMP(mp, <, message + msgCount);
1264 mb.mb_threaded ? (mp = next_in_thread(mp)) : ++mp) {
1265 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & m) == (ui32_t)f) {
1266 rv = (int)PTR2SIZE(mp - message + 1);
1267 goto jleave;
1271 if (dot > message) {
1272 for (mp = dot - 1; (mb.mb_threaded ? (mp != NULL) : (mp >= message));
1273 mb.mb_threaded ? (mp = prev_in_thread(mp)) : --mp) {
1274 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & m) == (ui32_t)f) {
1275 rv = (int)PTR2SIZE(mp - message + 1);
1276 goto jleave;
1280 rv = 0;
1281 jleave:
1282 NYD_LEAVE;
1283 return rv;
1286 FL void
1287 mark(int mesg, int f)
1289 struct message *mp;
1290 int i;
1291 NYD_ENTER;
1293 i = mesg;
1294 if (i < 1 || i > msgCount)
1295 panic(tr(129, "Bad message number to mark"));
1296 if (mb.mb_threaded == 1 && threadflag) {
1297 if (!(message[i - 1].m_flag & MHIDDEN)) {
1298 if (f == MDELETED || !(message[i - 1].m_flag & MDELETED))
1299 message[i - 1].m_flag |= MMARK;
1302 if (message[i - 1].m_child) {
1303 mp = message[i - 1].m_child;
1304 mark((int)PTR2SIZE(mp - message + 1), f);
1305 for (mp = mp->m_younger; mp != NULL; mp = mp->m_younger)
1306 mark((int)PTR2SIZE(mp - message + 1), f);
1308 } else
1309 message[i - 1].m_flag |= MMARK;
1310 NYD_LEAVE;
1313 /* vim:set fenc=utf-8:s-it-mode */