Review: dotlock.c
[s-mailx.git] / list.c
blobcbb309afb2f5ee57259d6aa2d99c6f6a362d7b67
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_qm(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 namelist = smalloc((nmlsize = 8) * sizeof *namelist);
209 np = &namelist[0];
210 scaninit();
211 bufp = buf;
212 beg = mc = star = other = topen = tback = FAL0;
213 #ifdef HAVE_IMAP
214 gotheaders = 0;
215 #endif
217 for (tok = scan(&bufp); tok != TEOL;) {
218 switch (tok) {
219 case TNUMBER:
220 number:
221 if (star) {
222 fprintf(stderr, tr(112, "No numbers mixed with *\n"));
223 markall_ret(-1)
225 list_saw_numbers = TRU1;
226 mc++;
227 other++;
228 if (beg != 0) {
229 if (check(lexnumber, f))
230 markall_ret(-1)
231 i = beg;
232 while (mb.mb_threaded ? 1 : i <= lexnumber) {
233 if (!(message[i - 1].m_flag & MHIDDEN) &&
234 (f == MDELETED || !(message[i - 1].m_flag & MDELETED)))
235 mark(i, f);
236 if (mb.mb_threaded) {
237 if (i == lexnumber)
238 break;
239 mx = next_in_thread(&message[i - 1]);
240 if (mx == NULL)
241 markall_ret(-1)
242 i = (int)PTR2SIZE(mx - message + 1);
243 } else
244 ++i;
246 beg = 0;
247 break;
249 beg = lexnumber;
250 if (check(beg, f))
251 markall_ret(-1)
252 tok = scan(&bufp);
253 regret(tok);
254 if (tok != TDASH) {
255 mark(beg, f);
256 beg = 0;
258 break;
260 case TPLUS:
261 msglist_is_single = FAL0;
262 if (beg != 0) {
263 printf(tr(113, "Non-numeric second argument\n"));
264 markall_ret(-1)
266 i = valdot;
267 do {
268 if (mb.mb_threaded) {
269 mx = next_in_thread(&message[i - 1]);
270 i = mx ? (int)PTR2SIZE(mx - message + 1) : msgCount + 1;
271 } else
272 ++i;
273 if (i > msgCount) {
274 fprintf(stderr, tr(114, "Referencing beyond EOF\n"));
275 markall_ret(-1)
277 } while (message[i-1].m_flag == MHIDDEN ||
278 (message[i - 1].m_flag & MDELETED) != (unsigned)f);
279 mark(i, f);
280 break;
282 case TDASH:
283 msglist_is_single = FAL0;
284 if (beg == 0) {
285 i = valdot;
286 do {
287 if (mb.mb_threaded) {
288 mx = prev_in_thread(&message[i - 1]);
289 i = mx ? (int)PTR2SIZE(mx - message + 1) : 0;
290 } else
291 --i;
292 if (i <= 0) {
293 fprintf(stderr, tr(115, "Referencing before 1\n"));
294 markall_ret(-1)
296 } while ((message[i - 1].m_flag & MHIDDEN) ||
297 (message[i - 1].m_flag & MDELETED) != (unsigned)f);
298 mark(i, f);
300 break;
302 case TSTRING:
303 msglist_is_single = FAL0;
304 if (beg != 0) {
305 fprintf(stderr, tr(116, "Non-numeric second argument\n"));
306 markall_ret(-1)
308 ++other;
309 if (lexstring[0] == ':') {
310 colresult = evalcol(lexstring[1]);
311 if (colresult == 0) {
312 fprintf(stderr, tr(117, "Unknown colon modifier \"%s\"\n"),
313 lexstring);
314 markall_ret(-1)
316 colmod |= colresult;
318 else
319 np = add_to_namelist(&namelist, &nmlsize, np, savestr(lexstring));
320 break;
322 case TOPEN:
323 #ifdef HAVE_IMAP_SEARCH
324 msglist_is_single = FAL0;
325 if (imap_search(lexstring, f) == STOP)
326 markall_ret(-1)
327 topen = TRU1;
328 #else
329 fprintf(stderr, tr(42,
330 "`%s': the used selector is optional and not available\n"),
331 lexstring);
332 markall_ret(-1)
333 #endif
334 break;
336 case TDOLLAR:
337 case TUP:
338 case TDOT:
339 case TSEMI:
340 msglist_is_single = FAL0;
341 lexnumber = metamess(lexstring[0], f);
342 if (lexnumber == -1)
343 markall_ret(-1)
344 goto number;
346 case TBACK:
347 msglist_is_single = FAL0;
348 tback = TRU1;
349 for (i = 1; i <= msgCount; i++) {
350 if ((message[i - 1].m_flag & MHIDDEN) ||
351 (message[i - 1].m_flag & MDELETED) != (unsigned)f)
352 continue;
353 if (message[i - 1].m_flag & MOLDMARK)
354 mark(i, f);
356 break;
358 case TSTAR:
359 msglist_is_single = FAL0;
360 if (other) {
361 fprintf(stderr, tr(118, "Can't mix \"*\" with anything\n"));
362 markall_ret(-1)
364 star = TRU1;
365 break;
367 case TCOMMA:
368 msglist_is_single = FAL0;
369 #ifdef HAVE_IMAP
370 if (mb.mb_type == MB_IMAP && gotheaders++ == 0)
371 imap_getheaders(1, msgCount);
372 #endif
373 if (id == NULL && (cp = hfield1("in-reply-to", dot)) != NULL) {
374 id = savestr(cp);
375 idfield = ID_IN_REPLY_TO;
377 if (id == NULL && (cp = hfield1("references", dot)) != NULL) {
378 struct name *enp;
380 if ((enp = extract(cp, GREF)) != NULL) {
381 while (enp->n_flink != NULL)
382 enp = enp->n_flink;
383 id = savestr(enp->n_name);
384 idfield = ID_REFERENCES;
387 if (id == NULL) {
388 printf(tr(227,
389 "Cannot determine parent Message-ID of the current message\n"));
390 markall_ret(-1)
392 break;
394 case TERROR:
395 list_saw_numbers = TRU1;
396 msglist_is_single = FAL0;
397 markall_ret(-1)
399 threadflag = 0;
400 tok = scan(&bufp);
403 lastcolmod = colmod;
404 np = add_to_namelist(&namelist, &nmlsize, np, NULL);
405 --np;
406 mc = 0;
407 if (star) {
408 for (i = 0; i < msgCount; ++i) {
409 if (!(message[i].m_flag & MHIDDEN) &&
410 (message[i].m_flag & MDELETED) == (unsigned)f) {
411 mark(i + 1, f);
412 ++mc;
415 if (mc == 0) {
416 if (!inhook)
417 printf(tr(119, "No applicable messages.\n"));
418 markall_ret(-1)
420 markall_ret(0)
423 if ((topen || tback) && mc == 0) {
424 for (i = 0; i < msgCount; ++i)
425 if (message[i].m_flag & MMARK)
426 ++mc;
427 if (mc == 0) {
428 if (!inhook) {
429 if (tback)
430 fprintf(stderr, tr(131, "No previously marked messages.\n"));
431 else
432 printf("No messages satisfy (criteria).\n");/*TODO tr*/
434 markall_ret(-1)
438 /* If no numbers were given, mark all messages, so that we can unmark
439 * any whose sender was not selected if any user names were given */
440 if ((np > namelist || colmod != 0 || id) && mc == 0)
441 for (i = 1; i <= msgCount; ++i) {
442 if (!(message[i - 1].m_flag & MHIDDEN) &&
443 (message[i - 1].m_flag & MDELETED) == (unsigned)f)
444 mark(i, f);
447 /* If any names were given, eliminate any messages which don't match */
448 if (np > namelist || id) {
449 struct search_expr *sep = NULL;
450 bool_t allnet;
452 /* The `@' search works with struct search_expr, so build an array.
453 * To simplify array, i.e., regex_t destruction, and optimize for the
454 * common case we walk the entire array even in case of errors */
455 if (np > namelist) {
456 sep = scalloc(PTR2SIZE(np - namelist), sizeof(*sep));
457 for (j = 0, nq = namelist; *nq != NULL; ++j, ++nq) {
458 char *x = *nq, *y;
460 sep[j].ss_sexpr = x;
461 if (*x != '@' || rv < 0)
462 continue;
464 for (y = x + 1;; ++y) {
465 if (*y == '\0' || !fieldnamechar(*y)) {
466 x = NULL;
467 break;
469 if (*y == '@') {
470 x = y;
471 break;
474 sep[j].ss_where = (x == NULL || x - 1 == *nq) ? "subject"
475 : savestrbuf(*nq + 1, PTR2SIZE(x - *nq) - 1);
477 x = (x == NULL ? *nq : x) + 1;
478 if (*x == '\0') { /* XXX Simply remove from list instead? */
479 fprintf(stderr, tr(525, "Empty `[@..]@' search expression\n"));
480 rv = -1;
481 continue;
483 #ifdef HAVE_REGEX
484 if (anyof("^.[]*+?(){}|$", x)) {
485 sep[j].ss_sexpr = NULL;
486 if (regcomp(&sep[j].ss_reexpr, x,
487 REG_EXTENDED | REG_ICASE | REG_NOSUB) != 0) {
488 fprintf(stderr, tr(526,
489 "Invalid regular expression: >>> %s <<<\n"), x);
490 rv = -1;
491 continue;
493 } else
494 #endif
495 sep[j].ss_sexpr = x;
497 if (rv < 0)
498 goto jnamesearch_sepfree;
501 #ifdef HAVE_IMAP
502 if (mb.mb_type == MB_IMAP && gotheaders++ == 0)
503 imap_getheaders(1, msgCount);
504 #endif
505 srelax_hold();
506 allnet = ok_blook(allnet);
507 for (i = 1; i <= msgCount; ++i) {
508 mp = message + i - 1;
509 j = 0;
510 if (np > namelist) {
511 for (nq = namelist; *nq != NULL; ++nq) {
512 if (**nq == '@') {
513 if (_match_qm(mp, sep + PTR2SIZE(nq - namelist))) {
514 ++j;
515 break;
517 } else if (**nq == '/') {
518 if (_match_dash(mp, *nq)) {
519 ++j;
520 break;
522 } else if (_matchsender(mp, *nq, allnet)) {
523 ++j;
524 break;
528 if (j == 0 && id && _matchmid(mp, id, idfield))
529 ++j;
530 if (j == 0)
531 mp->m_flag &= ~MMARK;
532 srelax();
534 srelax_rele();
536 /* Make sure we got some decent messages */
537 j = 0;
538 for (i = 1; i <= msgCount; ++i)
539 if (message[i - 1].m_flag & MMARK) {
540 ++j;
541 break;
543 if (j == 0) {
544 if (!inhook && np > namelist) {
545 printf(tr(120, "No applicable messages from {%s"), namelist[0]);
546 for (nq = namelist + 1; *nq != NULL; ++nq)
547 printf(tr(121, ", %s"), *nq);
548 printf(tr(122, "}\n"));
549 } else if (id)
550 printf(tr(227, "Parent message not found\n"));
551 rv = -1;
552 goto jnamesearch_sepfree;
555 jnamesearch_sepfree:
556 if (sep != NULL) {
557 #ifdef HAVE_REGEX
558 for (j = PTR2SIZE(np - namelist); j-- != 0;)
559 if (sep[j].ss_sexpr == NULL)
560 regfree(&sep[j].ss_reexpr);
561 #endif
562 free(sep);
564 if (rv < 0)
565 goto jleave;
568 /* If any colon modifiers were given, go through and unmark any
569 * messages which do not satisfy the modifiers */
570 if (colmod != 0) {
571 for (i = 1; i <= msgCount; ++i) {
572 struct coltab const *colp;
573 bool_t bad = TRU1;
575 mp = &message[i - 1];
576 for (colp = _coltab; colp->co_char != '\0'; ++colp)
577 if ((colp->co_bit & colmod) &&
578 ((mp->m_flag & colp->co_mask) == (unsigned)colp->co_equal))
579 bad = FAL0;
580 if (bad)
581 unmark(i);
583 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
584 if (mp->m_flag & MMARK)
585 break;
586 if (PTRCMP(mp, >=, message + msgCount)) {
587 struct coltab const *colp;
589 if (!inhook) {
590 printf(tr(123, "No messages satisfy"));
591 for (colp = _coltab; colp->co_char != '\0'; ++colp)
592 if (colp->co_bit & colmod)
593 printf(" :%c", colp->co_char);
594 printf("\n");
596 markall_ret(-1)
600 markall_ret(0)
601 jleave:
602 free(namelist);
603 ac_free(lexstring);
604 NYD_LEAVE;
605 return rv;
607 #undef markall_ret
610 static int
611 evalcol(int col)
613 struct coltab const *colp;
614 int rv;
615 NYD_ENTER;
617 if (col == 0)
618 rv = lastcolmod;
619 else {
620 rv = 0;
621 for (colp = _coltab; colp->co_char != '\0'; ++colp)
622 if (colp->co_char == col) {
623 rv = colp->co_bit;
624 break;
627 NYD_LEAVE;
628 return rv;
631 static int
632 check(int mesg, int f)
634 struct message *mp;
635 NYD_ENTER;
637 if (mesg < 1 || mesg > msgCount) {
638 printf(tr(124, "%d: Invalid message number\n"), mesg);
639 goto jem1;
641 mp = &message[mesg - 1];
642 if (mp->m_flag & MHIDDEN ||
643 (f != MDELETED && (mp->m_flag & MDELETED) != 0)) {
644 fprintf(stderr, tr(125, "%d: Inappropriate message\n"), mesg);
645 goto jem1;
647 f = 0;
648 jleave:
649 NYD_LEAVE;
650 return f;
651 jem1:
652 f = -1;
653 goto jleave;
656 static int
657 scan(char **sp)
659 char *cp, *cp2;
660 int rv, c, inquote, quotec;
661 struct lex const *lp;
662 NYD_ENTER;
664 if (regretp >= 0) {
665 strncpy(lexstring, string_stack[regretp], STRINGLEN);
666 lexstring[STRINGLEN-1]='\0';
667 lexnumber = numberstack[regretp];
668 rv = regretstack[regretp--];
669 goto jleave;
672 cp = *sp;
673 cp2 = lexstring;
674 c = *cp++;
676 /* strip away leading white space */
677 while (blankchar(c))
678 c = *cp++;
680 /* If no characters remain, we are at end of line, so report that */
681 if (c == '\0') {
682 *sp = --cp;
683 rv = TEOL;
684 goto jleave;
687 /* Select members of a message thread */
688 if (c == '&') {
689 threadflag = 1;
690 if (*cp == '\0' || spacechar(*cp)) {
691 lexstring[0] = '.';
692 lexstring[1] = '\0';
693 *sp = cp;
694 rv = TDOT;
695 goto jleave;
697 c = *cp++;
700 /* If the leading character is a digit, scan the number and convert it
701 * on the fly. Return TNUMBER when done */
702 if (digitchar(c)) {
703 lexnumber = 0;
704 while (digitchar(c)) {
705 lexnumber = lexnumber*10 + c - '0';
706 *cp2++ = c;
707 c = *cp++;
709 *cp2 = '\0';
710 *sp = --cp;
711 rv = TNUMBER;
712 goto jleave;
715 /* An IMAP SEARCH list. Note that TOPEN has always been included in
716 * singles[] in Mail and mailx. Thus although there is no formal
717 * definition for (LIST) lists, they do not collide with historical
718 * practice because a subject string (LIST) could never been matched
719 * this way */
720 if (c == '(') {
721 ui32_t level = 1;
722 inquote = 0;
723 *cp2++ = c;
724 do {
725 if ((c = *cp++&0377) == '\0') {
726 jmtop:
727 fprintf(stderr, "Missing \")\".\n");
728 rv = TERROR;
729 goto jleave;
731 if (inquote && c == '\\') {
732 *cp2++ = c;
733 c = *cp++&0377;
734 if (c == '\0')
735 goto jmtop;
736 } else if (c == '"')
737 inquote = !inquote;
738 else if (inquote)
739 /*EMPTY*/;
740 else if (c == '(')
741 level++;
742 else if (c == ')')
743 level--;
744 else if (spacechar(c)) {
745 /* Replace unquoted whitespace by single space characters, to make
746 * the string IMAP SEARCH conformant */
747 c = ' ';
748 if (cp2[-1] == ' ')
749 cp2--;
751 *cp2++ = c;
752 } while (c != ')' || level > 0);
753 *cp2 = '\0';
754 *sp = cp;
755 rv = TOPEN;
756 goto jleave;
759 /* Check for single character tokens; return such if found */
760 for (lp = _singles; lp->l_char != '\0'; ++lp)
761 if (c == lp->l_char) {
762 lexstring[0] = c;
763 lexstring[1] = '\0';
764 *sp = cp;
765 rv = lp->l_token;
766 goto jleave;
769 /* We've got a string! Copy all the characters of the string into
770 * lexstring, until we see a null, space, or tab. If the lead character is
771 * a " or ', save it and scan until you get another */
772 quotec = 0;
773 if (c == '\'' || c == '"') {
774 quotec = c;
775 c = *cp++;
777 while (c != '\0') {
778 if (quotec == 0 && c == '\\' && *cp)
779 c = *cp++;
780 if (c == quotec) {
781 cp++;
782 break;
784 if (quotec == 0 && blankchar(c))
785 break;
786 if (PTRCMP(cp2 - lexstring, <, STRINGLEN - 1))
787 *cp2++ = c;
788 c = *cp++;
790 if (quotec && c == 0) {
791 fprintf(stderr, tr(127, "Missing %c\n"), quotec);
792 rv = TERROR;
793 goto jleave;
795 *sp = --cp;
796 *cp2 = '\0';
797 rv = TSTRING;
798 jleave:
799 NYD_LEAVE;
800 return rv;
803 static void
804 regret(int token)
806 NYD_ENTER;
807 if (++regretp >= REGDEP)
808 panic(tr(128, "Too many regrets"));
809 regretstack[regretp] = token;
810 lexstring[STRINGLEN - 1] = '\0';
811 string_stack[regretp] = savestr(lexstring);
812 numberstack[regretp] = lexnumber;
813 NYD_LEAVE;
816 static void
817 scaninit(void)
819 NYD_ENTER;
820 regretp = -1;
821 threadflag = 0;
822 NYD_LEAVE;
825 static bool_t
826 _matchsender(struct message *mp, char const *str, bool_t allnet)
828 bool_t rv;
829 NYD_ENTER;
831 if (allnet) {
832 char *cp = nameof(mp, 0);
834 do {
835 if ((*cp == '@' || *cp == '\0') && (*str == '@' || *str == '\0')) {
836 rv = TRU1;
837 goto jleave;
839 if (*cp != *str)
840 break;
841 } while (cp++, *str++ != '\0');
842 rv = FAL0;
843 goto jleave;
845 rv = !strcmp(str, (ok_blook(showname) ? realname : skin)(name1(mp, 0)));
846 jleave:
847 NYD_LEAVE;
848 return rv;
851 static bool_t
852 _matchmid(struct message *mp, char *id, enum idfield idfield)
854 char *cp;
855 bool_t rv;
856 NYD_ENTER;
858 if ((cp = hfield1("message-id", mp)) != NULL) {
859 switch (idfield) {
860 case ID_REFERENCES:
861 rv = !msgidcmp(id, cp);
862 goto jleave;
863 case ID_IN_REPLY_TO: {
864 struct name *np;
866 if ((np = extract(id, GREF)) != NULL)
867 do {
868 if (!msgidcmp(np->n_name, cp)) {
869 rv = TRU1;
870 goto jleave;
872 } while ((np = np->n_flink) != NULL);
873 break;
877 rv = FAL0;
878 jleave:
879 NYD_LEAVE;
880 return rv;
883 static bool_t
884 _match_dash(struct message *mp, char const *str)
886 static char lastscan[128];
888 struct str in, out;
889 char *hfield, *hbody;
890 bool_t rv;
891 NYD_ENTER;
893 if (*++str == '\0') {
894 str = lastscan;
895 } else {
896 strncpy(lastscan, str, sizeof lastscan); /* XXX use new n_str object! */
897 lastscan[sizeof lastscan - 1] = '\0';
900 /* Now look, ignoring case, for the word in the string */
901 if (ok_blook(searchheaders) && (hfield = strchr(str, ':'))) {
902 size_t l = PTR2SIZE(hfield - str);
903 hfield = ac_alloc(l + 1);
904 memcpy(hfield, str, l);
905 hfield[l] = '\0';
906 hbody = hfieldX(hfield, mp);
907 ac_free(hfield);
908 hfield = UNCONST(str + l + 1);
909 } else {
910 hfield = UNCONST(str);
911 hbody = hfield1("subject", mp);
913 if (hbody == NULL) {
914 rv = FAL0;
915 goto jleave;
918 in.s = hbody;
919 in.l = strlen(hbody);
920 mime_fromhdr(&in, &out, TD_ICONV);
921 rv = substr(out.s, hfield);
922 free(out.s);
923 jleave:
924 NYD_LEAVE;
925 return rv;
928 static bool_t
929 _match_qm(struct message *mp, struct search_expr *sep)
931 struct str in, out;
932 char *nfield, *cfield;
933 bool_t rv = FAL0;
934 NYD_ENTER;
936 nfield = savestr(sep->ss_where);
938 while ((cfield = n_strsep(&nfield, ',', TRU1)) != NULL) {
939 if (!asccasecmp(cfield, "body")) {
940 rv = FAL0;
941 jmsg:
942 if ((rv = message_match(mp, sep, rv)))
943 break;
944 } else if (!asccasecmp(cfield, "text")) {
945 rv = TRU1;
946 goto jmsg;
947 } else if ((in.s = hfieldX(cfield, mp)) == NULL)
948 continue;
949 else {
950 in.l = strlen(in.s);
951 mime_fromhdr(&in, &out, TD_ICONV);
952 #ifdef HAVE_REGEX
953 if (sep->ss_sexpr == NULL)
954 rv = (regexec(&sep->ss_reexpr, out.s, 0,NULL, 0) != REG_NOMATCH);
955 else
956 #endif
957 rv = substr(out.s, sep->ss_sexpr);
958 free(out.s);
959 if (rv)
960 break;
963 NYD_LEAVE;
964 return rv;
967 static void
968 unmark(int mesg)
970 size_t i;
971 NYD_ENTER;
973 i = (size_t)mesg;
974 if (i < 1 || UICMP(z, i, >, msgCount))
975 panic(tr(130, "Bad message number to unmark"));
976 message[i - 1].m_flag &= ~MMARK;
977 NYD_LEAVE;
980 static int
981 metamess(int meta, int f)
983 int c, m;
984 struct message *mp;
985 NYD_ENTER;
987 c = meta;
988 switch (c) {
989 case '^': /* First 'good' message left */
990 mp = mb.mb_threaded ? threadroot : &message[0];
991 while (PTRCMP(mp, <, message + msgCount)) {
992 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & MDELETED) ==(unsigned)f){
993 c = (int)PTR2SIZE(mp - message + 1);
994 goto jleave;
996 if (mb.mb_threaded) {
997 mp = next_in_thread(mp);
998 if (mp == NULL)
999 break;
1000 } else
1001 ++mp;
1003 if (!inhook)
1004 printf(tr(132, "No applicable messages\n"));
1005 goto jem1;
1007 case '$': /* Last 'good message left */
1008 mp = mb.mb_threaded ? this_in_thread(threadroot, -1)
1009 : &message[msgCount-1];
1010 while (mp >= &message[0]) {
1011 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & MDELETED) == (ui32_t)f) {
1012 c = (int)PTR2SIZE(mp - message + 1);
1013 goto jleave;
1015 if (mb.mb_threaded) {
1016 mp = prev_in_thread(mp);
1017 if (mp == NULL)
1018 break;
1019 } else
1020 --mp;
1022 if (!inhook)
1023 printf(tr(132, "No applicable messages\n"));
1024 goto jem1;
1026 case '.':
1027 /* Current message */
1028 m = dot - message + 1;
1029 if ((dot->m_flag & MHIDDEN) || (dot->m_flag & MDELETED) != (unsigned)f) {
1030 printf(tr(133, "%d: Inappropriate message\n"), m);
1031 goto jem1;
1033 c = m;
1034 break;
1036 case ';':
1037 /* Previously current message */
1038 if (prevdot == NULL) {
1039 fprintf(stderr, tr(228, "No previously current message\n"));
1040 goto jem1;
1042 m = prevdot - message + 1;
1043 if ((prevdot->m_flag & MHIDDEN) ||
1044 (prevdot->m_flag & MDELETED) != (unsigned)f) {
1045 fprintf(stderr, tr(133, "%d: Inappropriate message\n"), m);
1046 goto jem1;
1048 c = m;
1049 break;
1051 default:
1052 fprintf(stderr, tr(134, "Unknown metachar (%c)\n"), c);
1053 goto jem1;
1055 jleave:
1056 NYD_LEAVE;
1057 return c;
1058 jem1:
1059 c = -1;
1060 goto jleave;
1063 FL int
1064 getmsglist(char *buf, int *vector, int flags)
1066 int *ip, mc;
1067 struct message *mp;
1068 NYD_ENTER;
1070 list_saw_numbers =
1071 msglist_is_single = FAL0;
1073 if (msgCount == 0) {
1074 *vector = 0;
1075 mc = 0;
1076 goto jleave;
1079 msglist_is_single = TRU1;
1080 if (markall(buf, flags) < 0) {
1081 mc = -1;
1082 goto jleave;
1085 ip = vector;
1086 if (inhook & 2) {
1087 mc = 0;
1088 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
1089 if (mp->m_flag & MMARK) {
1090 if ((mp->m_flag & MNEWEST) == 0)
1091 unmark((int)PTR2SIZE(mp - message + 1));
1092 else
1093 ++mc;
1095 if (mc == 0) {
1096 mc = -1;
1097 goto jleave;
1101 if (mb.mb_threaded == 0) {
1102 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
1103 if (mp->m_flag & MMARK)
1104 *ip++ = (int)PTR2SIZE(mp - message + 1);
1105 } else {
1106 for (mp = threadroot; mp != NULL; mp = next_in_thread(mp))
1107 if (mp->m_flag & MMARK)
1108 *ip++ = (int)PTR2SIZE(mp - message + 1);
1110 *ip = 0;
1111 mc = (int)PTR2SIZE(ip - vector);
1112 msglist_is_single = (mc == 1);
1113 jleave:
1114 NYD_LEAVE;
1115 return mc;
1118 FL int
1119 getrawlist(char const *line, size_t linesize, char **argv, int argc,
1120 int echolist)
1122 char c, *cp2, quotec, *linebuf;
1123 char const *cp;
1124 int argn;
1125 NYD_ENTER;
1127 list_saw_numbers = FAL0;
1129 argn = 0;
1130 cp = line;
1131 linebuf = ac_alloc(linesize + 1);
1132 for (;;) {
1133 for (; blankchar(*cp); ++cp)
1135 if (*cp == '\0')
1136 break;
1137 if (argn >= argc - 1) {
1138 printf(tr(126, "Too many elements in the list; excess discarded.\n"));
1139 break;
1141 cp2 = linebuf;
1142 quotec = '\0';
1143 while ((c = *cp) != '\0') {
1144 cp++;
1145 if (quotec != '\0') {
1146 if (c == quotec) {
1147 quotec = '\0';
1148 if (echolist)
1149 *cp2++ = c;
1150 } else if (c == '\\')
1151 switch (c = *cp++) {
1152 case '\0':
1153 *cp2++ = '\\';
1154 cp--;
1155 break;
1157 case '0': case '1': case '2': case '3':
1158 case '4': case '5': case '6': case '7':
1159 c -= '0';
1160 if (*cp >= '0' && *cp <= '7')
1161 c = c * 8 + *cp++ - '0';
1162 if (*cp >= '0' && *cp <= '7')
1163 c = c * 8 + *cp++ - '0';
1164 *cp2++ = c;
1165 break;
1166 case 'b':
1167 *cp2++ = '\b';
1168 break;
1169 case 'f':
1170 *cp2++ = '\f';
1171 break;
1172 case 'n':
1173 *cp2++ = '\n';
1174 break;
1175 case 'r':
1176 *cp2++ = '\r';
1177 break;
1178 case 't':
1179 *cp2++ = '\t';
1180 break;
1181 case 'v':
1182 *cp2++ = '\v';
1183 break;
1185 default:
1186 if (cp[-1] != quotec || echolist)
1187 *cp2++ = '\\';
1188 *cp2++ = c;
1190 /*else if (c == '^') {
1191 c = *cp++;
1192 if (c == '?')
1193 *cp2++ = '\177';
1194 /\* null doesn't show up anyway *\/
1195 else if ((c >= 'A' && c <= '_') ||
1196 (c >= 'a' && c <= 'z'))
1197 *cp2++ = c & 037;
1198 else {
1199 *cp2++ = '^';
1200 cp--;
1202 }*/ else
1203 *cp2++ = c;
1204 } else if (c == '"' || c == '\'') {
1205 if (echolist)
1206 *cp2++ = c;
1207 quotec = c;
1208 } else if (c == '\\' && !echolist) {
1209 if (*cp)
1210 *cp2++ = *cp++;
1211 else
1212 *cp2++ = c;
1213 } else if (blankchar(c))
1214 break;
1215 else
1216 *cp2++ = c;
1218 *cp2 = '\0';
1219 argv[argn++] = savestr(linebuf);
1221 argv[argn] = NULL;
1222 ac_free(linebuf);
1223 NYD_LEAVE;
1224 return argn;
1227 FL int
1228 first(int f, int m)
1230 struct message *mp;
1231 int rv;
1232 NYD_ENTER;
1234 if (msgCount == 0) {
1235 rv = 0;
1236 goto jleave;
1239 f &= MDELETED;
1240 m &= MDELETED;
1241 for (mp = dot;
1242 mb.mb_threaded ? mp != NULL : PTRCMP(mp, <, message + msgCount);
1243 mb.mb_threaded ? mp = next_in_thread(mp) : ++mp) {
1244 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & m) == (unsigned)f) {
1245 rv = (int)PTR2SIZE(mp - message + 1);
1246 goto jleave;
1250 if (dot > message) {
1251 for (mp = dot-1; (mb.mb_threaded ? mp != NULL : mp >= message);
1252 mb.mb_threaded ? mp = prev_in_thread(mp) : --mp) {
1253 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & m) == (unsigned)f) {
1254 rv = (int)PTR2SIZE(mp - message + 1);
1255 goto jleave;
1259 rv = 0;
1260 jleave:
1261 NYD_LEAVE;
1262 return rv;
1265 FL void
1266 mark(int mesg, int f)
1268 struct message *mp;
1269 int i;
1270 NYD_ENTER;
1272 i = mesg;
1273 if (i < 1 || i > msgCount)
1274 panic(tr(129, "Bad message number to mark"));
1275 if (mb.mb_threaded == 1 && threadflag) {
1276 if ((message[i - 1].m_flag & MHIDDEN) == 0) {
1277 if (f == MDELETED || (message[i - 1].m_flag&MDELETED) == 0)
1278 message[i - 1].m_flag |= MMARK;
1281 if (message[i - 1].m_child) {
1282 mp = message[i - 1].m_child;
1283 mark((int)PTR2SIZE(mp - message + 1), f);
1284 for (mp = mp->m_younger; mp; mp = mp->m_younger)
1285 mark((int)PTR2SIZE(mp - message + 1), f);
1287 } else
1288 message[i - 1].m_flag |= MMARK;
1289 NYD_LEAVE;
1292 /* vim:set fenc=utf-8:s-it-mode */