nail.1: remove that folder-loss paragraph again; too rude
[s-mailx.git] / list.c
blob9cf286979c29ea6ccc0d67606d3656c5db6b80de
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 enum idfield {
45 ID_REFERENCES,
46 ID_IN_REPLY_TO
49 enum {
50 CMNEW = 1<<0, /* New messages */
51 CMOLD = 1<<1, /* Old messages */
52 CMUNREAD = 1<<2, /* Unread messages */
53 CMDELETED =1<<3, /* Deleted messages */
54 CMREAD = 1<<4, /* Read messages */
55 CMFLAG = 1<<5, /* Flagged messages */
56 CMANSWER = 1<<6, /* Answered messages */
57 CMDRAFT = 1<<7, /* Draft messages */
58 CMSPAM = 1<<8 /* Spam messages */
61 struct coltab {
62 char co_char; /* What to find past : */
63 int co_bit; /* Associated modifier bit */
64 int co_mask; /* m_status bits to mask */
65 int co_equal; /* ... must equal this */
68 struct lex {
69 char l_char;
70 enum ltoken l_token;
73 static struct coltab const _coltab[] = {
74 { 'n', CMNEW, MNEW, MNEW },
75 { 'o', CMOLD, MNEW, 0 },
76 { 'u', CMUNREAD, MREAD, 0 },
77 { 'd', CMDELETED, MDELETED, MDELETED },
78 { 'r', CMREAD, MREAD, MREAD },
79 { 'f', CMFLAG, MFLAGGED, MFLAGGED },
80 { 'a', CMANSWER, MANSWERED, MANSWERED },
81 { 't', CMDRAFT, MDRAFTED, MDRAFTED },
82 { 's', CMSPAM, MSPAM, MSPAM },
83 { '\0', 0, 0, 0 }
86 static struct lex const _singles[] = {
87 { '$', TDOLLAR },
88 { '.', TDOT },
89 { '^', TUP },
90 { '*', TSTAR },
91 { '-', TDASH },
92 { '+', TPLUS },
93 { '(', TOPEN },
94 { ')', TCLOSE },
95 { ',', TCOMMA },
96 { ';', TSEMI },
97 { '`', TBACK },
98 { '\0', 0 }
101 static int lastcolmod;
102 static size_t STRINGLEN;
103 static int lexnumber; /* Number of TNUMBER from scan() */
104 static char *lexstring; /* String from TSTRING, scan() */
105 static int regretp; /* Pointer to TOS of regret tokens */
106 static int regretstack[REGDEP]; /* Stack of regretted tokens */
107 static char *string_stack[REGDEP]; /* Stack of regretted strings */
108 static int numberstack[REGDEP]; /* Stack of regretted numbers */
109 static int threadflag; /* mark entire threads */
111 /* Append, taking care of resizes */
112 static char ** add_to_namelist(char ***namelist, size_t *nmlsize,
113 char **np, char *string);
115 /* Mark all messages that the user wanted from the command line in the message
116 * structure. Return 0 on success, -1 on error */
117 static int markall(char *buf, int f);
119 /* Turn the character after a colon modifier into a bit value */
120 static int evalcol(int col);
122 /* Check the passed message number for legality and proper flags. If f is
123 * MDELETED, then either kind will do. Otherwise, the message has to be
124 * undeleted */
125 static int check(int mesg, int f);
127 /* Scan out a single lexical item and return its token number, updating the
128 * string pointer passed **sp. Also, store the value of the number or string
129 * scanned in lexnumber or lexstring as appropriate. In any event, store the
130 * scanned `thing' in lexstring */
131 static int scan(char **sp);
133 /* Unscan the named token by pushing it onto the regret stack */
134 static void regret(int token);
136 /* Reset all the scanner global variables */
137 static void scaninit(void);
139 /* See if the passed name sent the passed message */
140 static bool_t _matchsender(struct message *mp, char const *str, bool_t allnet);
142 static bool_t _matchmid(struct message *mp, char *id, enum idfield idfield);
144 /* See if the given string matches.
145 * For the purpose of the scan, we ignore case differences.
146 * This is the engine behind the `/' search */
147 static bool_t _match_dash(struct message *mp, char const *str);
149 /* See if the given search expression matches.
150 * For the purpose of the scan, we ignore case differences.
151 * This is the engine behind the `@[..]@' search */
152 static bool_t _match_at(struct message *mp, struct search_expr *sep);
154 /* Unmark the named message */
155 static void unmark(int mesg);
157 /* Return the message number corresponding to the passed meta character */
158 static int metamess(int meta, int f);
160 static char **
161 add_to_namelist(char ***namelist, size_t *nmlsize, char **np, char *string)
163 size_t idx;
164 NYD_ENTER;
166 if ((idx = PTR2SIZE(np - *namelist)) >= *nmlsize) {
167 *namelist = srealloc(*namelist, (*nmlsize += 8) * sizeof *np);
168 np = *namelist + idx;
170 *np++ = string;
171 NYD_LEAVE;
172 return np;
175 static int
176 markall(char *buf, int f)
178 #define markall_ret(i) do { rv = i; goto jleave; } while (0);
180 /* TODO use a bit carrier for all the states */
181 char **np, **nq, **namelist, *bufp, *id = NULL, *cp;
182 int rv = 0, i, tok, beg, mc, other, valdot, colmod, colresult;
183 struct message *mp, *mx;
184 bool_t star, topen, tback;
185 size_t j, nmlsize;
186 enum idfield idfield = ID_REFERENCES;
187 #ifdef HAVE_IMAP
188 int gotheaders;
189 #endif
190 NYD_ENTER;
192 lexstring = ac_alloc(STRINGLEN = 2 * strlen(buf) +1);
193 valdot = (int)PTR2SIZE(dot - message + 1);
194 colmod = 0;
196 for (i = 1; i <= msgCount; ++i) {
197 enum mflag mf;
199 mf = message[i - 1].m_flag;
200 if (mf & MMARK)
201 mf |= MOLDMARK;
202 else
203 mf &= ~MOLDMARK;
204 mf &= ~MMARK;
205 message[i - 1].m_flag = mf;
208 np = namelist = smalloc((nmlsize = 8) * sizeof *namelist);
209 scaninit();
210 bufp = buf;
211 beg = mc = star = other = topen = tback = FAL0;
212 #ifdef HAVE_IMAP
213 gotheaders = 0;
214 #endif
216 for (tok = scan(&bufp); tok != TEOL;) {
217 switch (tok) {
218 case TNUMBER:
219 number:
220 if (star) {
221 fprintf(stderr, tr(112, "No numbers mixed with *\n"));
222 markall_ret(-1)
224 list_saw_numbers = TRU1;
225 ++mc;
226 ++other;
227 if (beg != 0) {
228 if (check(lexnumber, f))
229 markall_ret(-1)
230 i = beg;
231 while (mb.mb_threaded ? 1 : i <= lexnumber) {
232 if (!(message[i - 1].m_flag & MHIDDEN) &&
233 (f == MDELETED || !(message[i - 1].m_flag & MDELETED)))
234 mark(i, f);
235 if (mb.mb_threaded) {
236 if (i == lexnumber)
237 break;
238 mx = next_in_thread(&message[i - 1]);
239 if (mx == NULL)
240 markall_ret(-1)
241 i = (int)PTR2SIZE(mx - message + 1);
242 } else
243 ++i;
245 beg = 0;
246 break;
248 beg = lexnumber;
249 if (check(beg, f))
250 markall_ret(-1)
251 tok = scan(&bufp);
252 regret(tok);
253 if (tok != TDASH) {
254 mark(beg, f);
255 beg = 0;
257 break;
259 case TPLUS:
260 msglist_is_single = FAL0;
261 if (beg != 0) {
262 printf(tr(113, "Non-numeric second argument\n"));
263 markall_ret(-1)
265 i = valdot;
266 do {
267 if (mb.mb_threaded) {
268 mx = next_in_thread(message + i - 1);
269 i = mx ? (int)PTR2SIZE(mx - message + 1) : msgCount + 1;
270 } else
271 ++i;
272 if (i > msgCount) {
273 fprintf(stderr, tr(114, "Referencing beyond EOF\n"));
274 markall_ret(-1)
276 } while (message[i - 1].m_flag == MHIDDEN ||
277 (message[i - 1].m_flag & MDELETED) != (unsigned)f);
278 mark(i, f);
279 break;
281 case TDASH:
282 msglist_is_single = FAL0;
283 if (beg == 0) {
284 i = valdot;
285 do {
286 if (mb.mb_threaded) {
287 mx = prev_in_thread(message + i - 1);
288 i = mx ? (int)PTR2SIZE(mx - message + 1) : 0;
289 } else
290 --i;
291 if (i <= 0) {
292 fprintf(stderr, tr(115, "Referencing before 1\n"));
293 markall_ret(-1)
295 } while ((message[i - 1].m_flag & MHIDDEN) ||
296 (message[i - 1].m_flag & MDELETED) != (unsigned)f);
297 mark(i, f);
299 break;
301 case TSTRING:
302 msglist_is_single = FAL0;
303 if (beg != 0) {
304 fprintf(stderr, tr(116, "Non-numeric second argument\n"));
305 markall_ret(-1)
307 ++other;
308 if (lexstring[0] == ':') {
309 colresult = evalcol(lexstring[1]);
310 if (colresult == 0) {
311 fprintf(stderr, tr(117, "Unknown colon modifier \"%s\"\n"),
312 lexstring);
313 markall_ret(-1)
315 colmod |= colresult;
317 else
318 np = add_to_namelist(&namelist, &nmlsize, np, savestr(lexstring));
319 break;
321 case TOPEN:
322 #ifdef HAVE_IMAP_SEARCH
323 msglist_is_single = FAL0;
324 if (imap_search(lexstring, f) == STOP)
325 markall_ret(-1)
326 topen = TRU1;
327 #else
328 fprintf(stderr, tr(42,
329 "`%s': the used selector is optional and not available\n"),
330 lexstring);
331 markall_ret(-1)
332 #endif
333 break;
335 case TDOLLAR:
336 case TUP:
337 case TDOT:
338 case TSEMI:
339 msglist_is_single = FAL0;
340 lexnumber = metamess(lexstring[0], f);
341 if (lexnumber == -1)
342 markall_ret(-1)
343 goto number;
345 case TBACK:
346 msglist_is_single = FAL0;
347 tback = TRU1;
348 for (i = 1; i <= msgCount; i++) {
349 if ((message[i - 1].m_flag & MHIDDEN) ||
350 (message[i - 1].m_flag & MDELETED) != (unsigned)f)
351 continue;
352 if (message[i - 1].m_flag & MOLDMARK)
353 mark(i, f);
355 break;
357 case TSTAR:
358 msglist_is_single = FAL0;
359 if (other) {
360 fprintf(stderr, tr(118, "Can't mix \"*\" with anything\n"));
361 markall_ret(-1)
363 star = TRU1;
364 break;
366 case TCOMMA:
367 msglist_is_single = FAL0;
368 #ifdef HAVE_IMAP
369 if (mb.mb_type == MB_IMAP && gotheaders++ == 0)
370 imap_getheaders(1, msgCount);
371 #endif
372 if (id == NULL && (cp = hfield1("in-reply-to", dot)) != NULL) {
373 id = savestr(cp);
374 idfield = ID_IN_REPLY_TO;
376 if (id == NULL && (cp = hfield1("references", dot)) != NULL) {
377 struct name *enp;
379 if ((enp = extract(cp, GREF)) != NULL) {
380 while (enp->n_flink != NULL)
381 enp = enp->n_flink;
382 id = savestr(enp->n_name);
383 idfield = ID_REFERENCES;
386 if (id == NULL) {
387 printf(tr(227,
388 "Cannot determine parent Message-ID of the current message\n"));
389 markall_ret(-1)
391 break;
393 case TERROR:
394 list_saw_numbers = TRU1;
395 msglist_is_single = FAL0;
396 markall_ret(-1)
398 threadflag = 0;
399 tok = scan(&bufp);
402 lastcolmod = colmod;
403 np = add_to_namelist(&namelist, &nmlsize, np, NULL);
404 --np;
405 mc = 0;
406 if (star) {
407 for (i = 0; i < msgCount; ++i) {
408 if (!(message[i].m_flag & MHIDDEN) &&
409 (message[i].m_flag & MDELETED) == (unsigned)f) {
410 mark(i + 1, f);
411 ++mc;
414 if (mc == 0) {
415 if (!inhook)
416 printf(tr(119, "No applicable messages.\n"));
417 markall_ret(-1)
419 markall_ret(0)
422 if ((topen || tback) && mc == 0) {
423 for (i = 0; i < msgCount; ++i)
424 if (message[i].m_flag & MMARK)
425 ++mc;
426 if (mc == 0) {
427 if (!inhook) {
428 if (tback)
429 fprintf(stderr, tr(131, "No previously marked messages.\n"));
430 else
431 printf("No messages satisfy (criteria).\n");/*TODO tr*/
433 markall_ret(-1)
437 /* If no numbers were given, mark all messages, so that we can unmark
438 * any whose sender was not selected if any user names were given */
439 if ((np > namelist || colmod != 0 || id) && mc == 0)
440 for (i = 1; i <= msgCount; ++i) {
441 if (!(message[i - 1].m_flag & MHIDDEN) &&
442 (message[i - 1].m_flag & MDELETED) == (unsigned)f)
443 mark(i, f);
446 /* If any names were given, eliminate any messages which don't match */
447 if (np > namelist || id) {
448 struct search_expr *sep = NULL;
449 bool_t allnet;
451 /* The `@' search works with struct search_expr, so build an array.
452 * To simplify array, i.e., regex_t destruction, and optimize for the
453 * common case we walk the entire array even in case of errors */
454 if (np > namelist) {
455 sep = scalloc(PTR2SIZE(np - namelist), sizeof(*sep));
456 for (j = 0, nq = namelist; *nq != NULL; ++j, ++nq) {
457 char *x = *nq, *y;
459 sep[j].ss_sexpr = x;
460 if (*x != '@' || rv < 0)
461 continue;
463 for (y = x + 1;; ++y) {
464 if (*y == '\0' || !fieldnamechar(*y)) {
465 x = NULL;
466 break;
468 if (*y == '@') {
469 x = y;
470 break;
473 sep[j].ss_where = (x == NULL || x - 1 == *nq)
474 ? "subject" : savestrbuf(*nq + 1, PTR2SIZE(x - *nq) - 1);
476 x = (x == NULL ? *nq : x) + 1;
477 if (*x == '\0') { /* XXX Simply remove from list instead? */
478 fprintf(stderr, tr(525, "Empty `[@..]@' search expression\n"));
479 rv = -1;
480 continue;
482 #ifdef HAVE_REGEX
483 if (anyof("^.[]*+?(){}|$", x)) {
484 sep[j].ss_sexpr = NULL;
485 if (regcomp(&sep[j].ss_reexpr, x,
486 REG_EXTENDED | REG_ICASE | REG_NOSUB) != 0) {
487 fprintf(stderr, tr(526,
488 "Invalid regular expression: >>> %s <<<\n"), x);
489 rv = -1;
490 continue;
492 } else
493 #endif
494 sep[j].ss_sexpr = x;
496 if (rv < 0)
497 goto jnamesearch_sepfree;
500 #ifdef HAVE_IMAP
501 if (mb.mb_type == MB_IMAP && gotheaders++ == 0)
502 imap_getheaders(1, msgCount);
503 #endif
504 srelax_hold();
505 allnet = ok_blook(allnet);
506 for (i = 1; i <= msgCount; ++i) {
507 mp = message + i - 1;
508 j = 0;
509 if (np > namelist) {
510 for (nq = namelist; *nq != NULL; ++nq) {
511 if (**nq == '@') {
512 if (_match_at(mp, sep + PTR2SIZE(nq - namelist))) {
513 ++j;
514 break;
516 } else if (**nq == '/') {
517 if (_match_dash(mp, *nq)) {
518 ++j;
519 break;
521 } else if (_matchsender(mp, *nq, allnet)) {
522 ++j;
523 break;
527 if (j == 0 && id && _matchmid(mp, id, idfield))
528 ++j;
529 if (j == 0)
530 mp->m_flag &= ~MMARK;
531 srelax();
533 srelax_rele();
535 /* Make sure we got some decent messages */
536 j = 0;
537 for (i = 1; i <= msgCount; ++i)
538 if (message[i - 1].m_flag & MMARK) {
539 ++j;
540 break;
542 if (j == 0) {
543 if (!inhook && np > namelist) {
544 printf(tr(120, "No applicable messages from {%s"), namelist[0]);
545 for (nq = namelist + 1; *nq != NULL; ++nq)
546 printf(tr(121, ", %s"), *nq);
547 printf(tr(122, "}\n"));
548 } else if (id)
549 printf(tr(227, "Parent message not found\n"));
550 rv = -1;
551 goto jnamesearch_sepfree;
554 jnamesearch_sepfree:
555 if (sep != NULL) {
556 #ifdef HAVE_REGEX
557 for (j = PTR2SIZE(np - namelist); j-- != 0;)
558 if (sep[j].ss_sexpr == NULL)
559 regfree(&sep[j].ss_reexpr);
560 #endif
561 free(sep);
563 if (rv < 0)
564 goto jleave;
567 /* If any colon modifiers were given, go through and unmark any
568 * messages which do not satisfy the modifiers */
569 if (colmod != 0) {
570 for (i = 1; i <= msgCount; ++i) {
571 struct coltab const *colp;
572 bool_t bad = TRU1;
574 mp = message + i - 1;
575 for (colp = _coltab; colp->co_char != '\0'; ++colp)
576 if ((colp->co_bit & colmod) &&
577 ((mp->m_flag & colp->co_mask) == (unsigned)colp->co_equal))
578 bad = FAL0;
579 if (bad)
580 unmark(i);
582 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
583 if (mp->m_flag & MMARK)
584 break;
585 if (PTRCMP(mp, >=, message + msgCount)) {
586 struct coltab const *colp;
588 if (!inhook) {
589 printf(tr(123, "No messages satisfy"));
590 for (colp = _coltab; colp->co_char != '\0'; ++colp)
591 if (colp->co_bit & colmod)
592 printf(" :%c", colp->co_char);
593 printf("\n");
595 markall_ret(-1)
599 markall_ret(0)
600 jleave:
601 free(namelist);
602 ac_free(lexstring);
603 NYD_LEAVE;
604 return rv;
606 #undef markall_ret
609 static int
610 evalcol(int col)
612 struct coltab const *colp;
613 int rv;
614 NYD_ENTER;
616 if (col == 0)
617 rv = lastcolmod;
618 else {
619 rv = 0;
620 for (colp = _coltab; colp->co_char != '\0'; ++colp)
621 if (colp->co_char == col) {
622 rv = colp->co_bit;
623 break;
626 NYD_LEAVE;
627 return rv;
630 static int
631 check(int mesg, int f)
633 struct message *mp;
634 NYD_ENTER;
636 if (mesg < 1 || mesg > msgCount) {
637 printf(tr(124, "%d: Invalid message number\n"), mesg);
638 goto jem1;
640 mp = message + mesg - 1;
641 if (mp->m_flag & MHIDDEN ||
642 (f != MDELETED && (mp->m_flag & MDELETED) != 0)) {
643 fprintf(stderr, tr(125, "%d: Inappropriate message\n"), mesg);
644 goto jem1;
646 f = 0;
647 jleave:
648 NYD_LEAVE;
649 return f;
650 jem1:
651 f = -1;
652 goto jleave;
655 static int
656 scan(char **sp)
658 char *cp, *cp2;
659 int rv, c, inquote, quotec;
660 struct lex const *lp;
661 NYD_ENTER;
663 if (regretp >= 0) {
664 strncpy(lexstring, string_stack[regretp], STRINGLEN);
665 lexstring[STRINGLEN -1] = '\0';
666 lexnumber = numberstack[regretp];
667 rv = regretstack[regretp--];
668 goto jleave;
671 cp = *sp;
672 cp2 = lexstring;
673 c = *cp++;
675 /* strip away leading white space */
676 while (blankchar(c))
677 c = *cp++;
679 /* If no characters remain, we are at end of line, so report that */
680 if (c == '\0') {
681 *sp = --cp;
682 rv = TEOL;
683 goto jleave;
686 /* Select members of a message thread */
687 if (c == '&') {
688 threadflag = 1;
689 if (*cp == '\0' || spacechar(*cp)) {
690 lexstring[0] = '.';
691 lexstring[1] = '\0';
692 *sp = cp;
693 rv = TDOT;
694 goto jleave;
696 c = *cp++;
699 /* If the leading character is a digit, scan the number and convert it
700 * on the fly. Return TNUMBER when done */
701 if (digitchar(c)) {
702 lexnumber = 0;
703 while (digitchar(c)) {
704 lexnumber = lexnumber*10 + c - '0';
705 *cp2++ = c;
706 c = *cp++;
708 *cp2 = '\0';
709 *sp = --cp;
710 rv = TNUMBER;
711 goto jleave;
714 /* An IMAP SEARCH list. Note that TOPEN has always been included in
715 * singles[] in Mail and mailx. Thus although there is no formal
716 * definition for (LIST) lists, they do not collide with historical
717 * practice because a subject string (LIST) could never been matched
718 * this way */
719 if (c == '(') {
720 ui32_t level = 1;
721 inquote = 0;
722 *cp2++ = c;
723 do {
724 if ((c = *cp++&0377) == '\0') {
725 jmtop:
726 fprintf(stderr, "Missing \")\".\n");
727 rv = TERROR;
728 goto jleave;
730 if (inquote && c == '\\') {
731 *cp2++ = c;
732 c = *cp++&0377;
733 if (c == '\0')
734 goto jmtop;
735 } else if (c == '"')
736 inquote = !inquote;
737 else if (inquote)
738 /*EMPTY*/;
739 else if (c == '(')
740 ++level;
741 else if (c == ')')
742 --level;
743 else if (spacechar(c)) {
744 /* Replace unquoted whitespace by single space characters, to make
745 * the string IMAP SEARCH conformant */
746 c = ' ';
747 if (cp2[-1] == ' ')
748 --cp2;
750 *cp2++ = c;
751 } while (c != ')' || level > 0);
752 *cp2 = '\0';
753 *sp = cp;
754 rv = TOPEN;
755 goto jleave;
758 /* Check for single character tokens; return such if found */
759 for (lp = _singles; lp->l_char != '\0'; ++lp)
760 if (c == lp->l_char) {
761 lexstring[0] = c;
762 lexstring[1] = '\0';
763 *sp = cp;
764 rv = lp->l_token;
765 goto jleave;
768 /* We've got a string! Copy all the characters of the string into
769 * lexstring, until we see a null, space, or tab. If the lead character is
770 * a " or ', save it and scan until you get another */
771 quotec = 0;
772 if (c == '\'' || c == '"') {
773 quotec = c;
774 c = *cp++;
776 while (c != '\0') {
777 if (quotec == 0 && c == '\\' && *cp != '\0')
778 c = *cp++;
779 if (c == quotec) {
780 ++cp;
781 break;
783 if (quotec == 0 && blankchar(c))
784 break;
785 if (PTRCMP(cp2 - lexstring, <, STRINGLEN - 1))
786 *cp2++ = c;
787 c = *cp++;
789 if (quotec && c == 0) {
790 fprintf(stderr, tr(127, "Missing %c\n"), quotec);
791 rv = TERROR;
792 goto jleave;
794 *sp = --cp;
795 *cp2 = '\0';
796 rv = TSTRING;
797 jleave:
798 NYD_LEAVE;
799 return rv;
802 static void
803 regret(int token)
805 NYD_ENTER;
806 if (++regretp >= REGDEP)
807 panic(tr(128, "Too many regrets"));
808 regretstack[regretp] = token;
809 lexstring[STRINGLEN -1] = '\0';
810 string_stack[regretp] = savestr(lexstring);
811 numberstack[regretp] = lexnumber;
812 NYD_LEAVE;
815 static void
816 scaninit(void)
818 NYD_ENTER;
819 regretp = -1;
820 threadflag = 0;
821 NYD_LEAVE;
824 static bool_t
825 _matchsender(struct message *mp, char const *str, bool_t allnet)
827 bool_t rv;
828 NYD_ENTER;
830 if (allnet) {
831 char *cp = nameof(mp, 0);
833 do {
834 if ((*cp == '@' || *cp == '\0') && (*str == '@' || *str == '\0')) {
835 rv = TRU1;
836 goto jleave;
838 if (*cp != *str)
839 break;
840 } while (++cp, *str++ != '\0');
841 rv = FAL0;
842 goto jleave;
844 rv = !strcmp(str, (*(ok_blook(showname) ? &realname : &skin))(name1(mp, 0)));
845 jleave:
846 NYD_LEAVE;
847 return rv;
850 static bool_t
851 _matchmid(struct message *mp, char *id, enum idfield idfield)
853 char *cp;
854 bool_t rv;
855 NYD_ENTER;
857 if ((cp = hfield1("message-id", mp)) != NULL) {
858 switch (idfield) {
859 case ID_REFERENCES:
860 rv = !msgidcmp(id, cp);
861 goto jleave;
862 case ID_IN_REPLY_TO: {
863 struct name *np;
865 if ((np = extract(id, GREF)) != NULL)
866 do {
867 if (!msgidcmp(np->n_name, cp)) {
868 rv = TRU1;
869 goto jleave;
871 } while ((np = np->n_flink) != NULL);
872 break;
876 rv = FAL0;
877 jleave:
878 NYD_LEAVE;
879 return rv;
882 static bool_t
883 _match_dash(struct message *mp, char const *str)
885 static char lastscan[128];
887 struct str in, out;
888 char *hfield, *hbody;
889 bool_t rv;
890 NYD_ENTER;
892 if (*++str == '\0') {
893 str = lastscan;
894 } else {
895 strncpy(lastscan, str, sizeof lastscan); /* XXX use new n_str object! */
896 lastscan[sizeof lastscan -1] = '\0';
899 /* Now look, ignoring case, for the word in the string */
900 if (ok_blook(searchheaders) && (hfield = strchr(str, ':'))) {
901 size_t l = PTR2SIZE(hfield - str);
902 hfield = ac_alloc(l +1);
903 memcpy(hfield, str, l);
904 hfield[l] = '\0';
905 hbody = hfieldX(hfield, mp);
906 ac_free(hfield);
907 hfield = UNCONST(str + l + 1);
908 } else {
909 hfield = UNCONST(str);
910 hbody = hfield1("subject", mp);
912 if (hbody == NULL) {
913 rv = FAL0;
914 goto jleave;
917 in.s = hbody;
918 in.l = strlen(hbody);
919 mime_fromhdr(&in, &out, TD_ICONV);
920 rv = substr(out.s, hfield);
921 free(out.s);
922 jleave:
923 NYD_LEAVE;
924 return rv;
927 static bool_t
928 _match_at(struct message *mp, struct search_expr *sep)
930 struct str in, out;
931 char *nfield, *cfield;
932 bool_t rv = FAL0;
933 NYD_ENTER;
935 nfield = savestr(sep->ss_where);
937 while ((cfield = n_strsep(&nfield, ',', TRU1)) != NULL) {
938 if (!asccasecmp(cfield, "body")) {
939 rv = FAL0;
940 jmsg:
941 if ((rv = message_match(mp, sep, rv)))
942 break;
943 } else if (!asccasecmp(cfield, "text")) {
944 rv = TRU1;
945 goto jmsg;
946 } else if ((in.s = hfieldX(cfield, mp)) == NULL)
947 continue;
948 else {
949 in.l = strlen(in.s);
950 mime_fromhdr(&in, &out, TD_ICONV);
951 #ifdef HAVE_REGEX
952 if (sep->ss_sexpr == NULL)
953 rv = (regexec(&sep->ss_reexpr, out.s, 0,NULL, 0) != REG_NOMATCH);
954 else
955 #endif
956 rv = substr(out.s, sep->ss_sexpr);
957 free(out.s);
958 if (rv)
959 break;
962 NYD_LEAVE;
963 return rv;
966 static void
967 unmark(int mesg)
969 size_t i;
970 NYD_ENTER;
972 i = (size_t)mesg;
973 if (i < 1 || UICMP(z, i, >, msgCount))
974 panic(tr(130, "Bad message number to unmark"));
975 message[i - 1].m_flag &= ~MMARK;
976 NYD_LEAVE;
979 static int
980 metamess(int meta, int f)
982 int c, m;
983 struct message *mp;
984 NYD_ENTER;
986 c = meta;
987 switch (c) {
988 case '^': /* First 'good' message left */
989 mp = mb.mb_threaded ? threadroot : message;
990 while (PTRCMP(mp, <, message + msgCount)) {
991 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & MDELETED) == (ui32_t)f) {
992 c = (int)PTR2SIZE(mp - message + 1);
993 goto jleave;
995 if (mb.mb_threaded) {
996 mp = next_in_thread(mp);
997 if (mp == NULL)
998 break;
999 } else
1000 ++mp;
1002 if (!inhook)
1003 printf(tr(132, "No applicable messages\n"));
1004 goto jem1;
1006 case '$': /* Last 'good message left */
1007 mp = mb.mb_threaded
1008 ? this_in_thread(threadroot, -1) : message + msgCount - 1;
1009 while (mp >= message) {
1010 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & MDELETED) == (ui32_t)f) {
1011 c = (int)PTR2SIZE(mp - message + 1);
1012 goto jleave;
1014 if (mb.mb_threaded) {
1015 mp = prev_in_thread(mp);
1016 if (mp == NULL)
1017 break;
1018 } else
1019 --mp;
1021 if (!inhook)
1022 printf(tr(132, "No applicable messages\n"));
1023 goto jem1;
1025 case '.':
1026 /* Current message */
1027 m = dot - message + 1;
1028 if ((dot->m_flag & MHIDDEN) || (dot->m_flag & MDELETED) != (ui32_t)f) {
1029 printf(tr(133, "%d: Inappropriate message\n"), m);
1030 goto jem1;
1032 c = m;
1033 break;
1035 case ';':
1036 /* Previously current message */
1037 if (prevdot == NULL) {
1038 fprintf(stderr, tr(228, "No previously current message\n"));
1039 goto jem1;
1041 m = prevdot - message + 1;
1042 if ((prevdot->m_flag & MHIDDEN) ||
1043 (prevdot->m_flag & MDELETED) != (ui32_t)f) {
1044 fprintf(stderr, tr(133, "%d: Inappropriate message\n"), m);
1045 goto jem1;
1047 c = m;
1048 break;
1050 default:
1051 fprintf(stderr, tr(134, "Unknown metachar (%c)\n"), c);
1052 goto jem1;
1054 jleave:
1055 NYD_LEAVE;
1056 return c;
1057 jem1:
1058 c = -1;
1059 goto jleave;
1062 FL int
1063 getmsglist(char *buf, int *vector, int flags)
1065 int *ip, mc;
1066 struct message *mp;
1067 NYD_ENTER;
1069 list_saw_numbers =
1070 msglist_is_single = FAL0;
1072 if (msgCount == 0) {
1073 *vector = 0;
1074 mc = 0;
1075 goto jleave;
1078 msglist_is_single = TRU1;
1079 if (markall(buf, flags) < 0) {
1080 mc = -1;
1081 goto jleave;
1084 ip = vector;
1085 if (inhook & 2) {
1086 mc = 0;
1087 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
1088 if (mp->m_flag & MMARK) {
1089 if (!(mp->m_flag & MNEWEST))
1090 unmark((int)PTR2SIZE(mp - message + 1));
1091 else
1092 ++mc;
1094 if (mc == 0) {
1095 mc = -1;
1096 goto jleave;
1100 if (mb.mb_threaded == 0) {
1101 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
1102 if (mp->m_flag & MMARK)
1103 *ip++ = (int)PTR2SIZE(mp - message + 1);
1104 } else {
1105 for (mp = threadroot; mp != NULL; mp = next_in_thread(mp))
1106 if (mp->m_flag & MMARK)
1107 *ip++ = (int)PTR2SIZE(mp - message + 1);
1109 *ip = 0;
1110 mc = (int)PTR2SIZE(ip - vector);
1111 msglist_is_single = (mc == 1);
1112 jleave:
1113 NYD_LEAVE;
1114 return mc;
1117 FL int
1118 getrawlist(char const *line, size_t linesize, char **argv, int argc,
1119 int echolist)
1121 char c, *cp2, quotec, *linebuf;
1122 char const *cp;
1123 int argn;
1124 NYD_ENTER;
1126 list_saw_numbers = FAL0;
1128 argn = 0;
1129 cp = line;
1130 linebuf = ac_alloc(linesize +1);
1131 for (;;) {
1132 for (; blankchar(*cp); ++cp)
1134 if (*cp == '\0')
1135 break;
1136 if (argn >= argc - 1) {
1137 printf(tr(126, "Too many elements in the list; excess discarded.\n"));
1138 break;
1140 cp2 = linebuf;
1141 quotec = '\0';
1142 while ((c = *cp) != '\0') {
1143 cp++;
1144 if (quotec != '\0') {
1145 if (c == quotec) {
1146 quotec = '\0';
1147 if (echolist)
1148 *cp2++ = c;
1149 } else if (c == '\\')
1150 switch (c = *cp++) {
1151 case '\0':
1152 *cp2++ = '\\';
1153 cp--;
1154 break;
1156 case '0': case '1': case '2': case '3':
1157 case '4': case '5': case '6': case '7':
1158 c -= '0';
1159 if (*cp >= '0' && *cp <= '7')
1160 c = c * 8 + *cp++ - '0';
1161 if (*cp >= '0' && *cp <= '7')
1162 c = c * 8 + *cp++ - '0';
1163 *cp2++ = c;
1164 break;
1165 case 'b':
1166 *cp2++ = '\b';
1167 break;
1168 case 'f':
1169 *cp2++ = '\f';
1170 break;
1171 case 'n':
1172 *cp2++ = '\n';
1173 break;
1174 case 'r':
1175 *cp2++ = '\r';
1176 break;
1177 case 't':
1178 *cp2++ = '\t';
1179 break;
1180 case 'v':
1181 *cp2++ = '\v';
1182 break;
1184 default:
1185 if (cp[-1] != quotec || echolist)
1186 *cp2++ = '\\';
1187 *cp2++ = c;
1189 /*else if (c == '^') {
1190 c = *cp++;
1191 if (c == '?')
1192 *cp2++ = '\177';
1193 /\* null doesn't show up anyway *\/
1194 else if ((c >= 'A' && c <= '_') ||
1195 (c >= 'a' && c <= 'z'))
1196 *cp2++ = c & 037;
1197 else {
1198 *cp2++ = '^';
1199 cp--;
1201 }*/ else
1202 *cp2++ = c;
1203 } else if (c == '"' || c == '\'') {
1204 if (echolist)
1205 *cp2++ = c;
1206 quotec = c;
1207 } else if (c == '\\' && !echolist) {
1208 if (*cp)
1209 *cp2++ = *cp++;
1210 else
1211 *cp2++ = c;
1212 } else if (blankchar(c))
1213 break;
1214 else
1215 *cp2++ = c;
1217 *cp2 = '\0';
1218 argv[argn++] = savestr(linebuf);
1220 argv[argn] = NULL;
1221 ac_free(linebuf);
1222 NYD_LEAVE;
1223 return argn;
1226 FL int
1227 first(int f, int m)
1229 struct message *mp;
1230 int rv;
1231 NYD_ENTER;
1233 if (msgCount == 0) {
1234 rv = 0;
1235 goto jleave;
1238 f &= MDELETED;
1239 m &= MDELETED;
1240 for (mp = dot;
1241 mb.mb_threaded ? (mp != NULL) : PTRCMP(mp, <, message + msgCount);
1242 mb.mb_threaded ? (mp = next_in_thread(mp)) : ++mp) {
1243 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & m) == (ui32_t)f) {
1244 rv = (int)PTR2SIZE(mp - message + 1);
1245 goto jleave;
1249 if (dot > message) {
1250 for (mp = dot - 1; (mb.mb_threaded ? (mp != NULL) : (mp >= message));
1251 mb.mb_threaded ? (mp = prev_in_thread(mp)) : --mp) {
1252 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & m) == (ui32_t)f) {
1253 rv = (int)PTR2SIZE(mp - message + 1);
1254 goto jleave;
1258 rv = 0;
1259 jleave:
1260 NYD_LEAVE;
1261 return rv;
1264 FL void
1265 mark(int mesg, int f)
1267 struct message *mp;
1268 int i;
1269 NYD_ENTER;
1271 i = mesg;
1272 if (i < 1 || i > msgCount)
1273 panic(tr(129, "Bad message number to mark"));
1274 if (mb.mb_threaded == 1 && threadflag) {
1275 if (!(message[i - 1].m_flag & MHIDDEN)) {
1276 if (f == MDELETED || !(message[i - 1].m_flag & MDELETED))
1277 message[i - 1].m_flag |= MMARK;
1280 if (message[i - 1].m_child) {
1281 mp = message[i - 1].m_child;
1282 mark((int)PTR2SIZE(mp - message + 1), f);
1283 for (mp = mp->m_younger; mp != NULL; mp = mp->m_younger)
1284 mark((int)PTR2SIZE(mp - message + 1), f);
1286 } else
1287 message[i - 1].m_flag |= MMARK;
1288 NYD_LEAVE;
1291 /* vim:set fenc=utf-8:s-it-mode */