Fix URL handling and more (Gavin Troy, Ypnose, Karol Blazewicz)..
[s-mailx.git] / list.c
blob78afa4e6fdf2dc4c7a89e8395c9464e83b32b33b
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, other, valdot, colmod, colresult;
205 struct message *mp, *mx;
206 bool_t mc, star, topen, tback;
207 size_t j, nmlsize;
208 enum idfield idfield = ID_REFERENCES;
209 #ifdef HAVE_IMAP
210 int gotheaders;
211 #endif
212 NYD_ENTER;
214 lexstring = ac_alloc(STRINGLEN = 2 * strlen(buf) +1);
215 valdot = (int)PTR2SIZE(dot - message + 1);
216 colmod = 0;
218 for (i = 1; i <= msgCount; ++i) {
219 enum mflag mf;
221 mf = message[i - 1].m_flag;
222 if (mf & MMARK)
223 mf |= MOLDMARK;
224 else
225 mf &= ~MOLDMARK;
226 mf &= ~MMARK;
227 message[i - 1].m_flag = mf;
230 np = namelist = smalloc((nmlsize = 8) * sizeof *namelist);
231 scaninit();
232 bufp = buf;
233 mc = FAL0;
234 beg = star = other = topen = tback = FAL0;
235 #ifdef HAVE_IMAP
236 gotheaders = 0;
237 #endif
239 for (tok = scan(&bufp); tok != TEOL;) {
240 switch (tok) {
241 case TNUMBER:
242 number:
243 if (star) {
244 fprintf(stderr, tr(112, "No numbers mixed with *\n"));
245 markall_ret(-1)
247 list_saw_numbers = TRU1;
248 mc = TRU1;
249 ++other;
250 if (beg != 0) {
251 if (check(lexnumber, f))
252 markall_ret(-1)
253 i = beg;
254 while (mb.mb_threaded ? 1 : i <= lexnumber) {
255 if (!(message[i - 1].m_flag & MHIDDEN) &&
256 (f == MDELETED || !(message[i - 1].m_flag & MDELETED)))
257 mark(i, f);
258 if (mb.mb_threaded) {
259 if (i == lexnumber)
260 break;
261 mx = next_in_thread(&message[i - 1]);
262 if (mx == NULL)
263 markall_ret(-1)
264 i = (int)PTR2SIZE(mx - message + 1);
265 } else
266 ++i;
268 beg = 0;
269 break;
271 beg = lexnumber;
272 if (check(beg, f))
273 markall_ret(-1)
274 tok = scan(&bufp);
275 regret(tok);
276 if (tok != TDASH) {
277 mark(beg, f);
278 beg = 0;
280 break;
282 case TPLUS:
283 msglist_is_single = FAL0;
284 if (beg != 0) {
285 printf(tr(113, "Non-numeric second argument\n"));
286 markall_ret(-1)
288 i = valdot;
289 do {
290 if (mb.mb_threaded) {
291 mx = next_in_thread(message + i - 1);
292 i = mx ? (int)PTR2SIZE(mx - message + 1) : msgCount + 1;
293 } else
294 ++i;
295 if (i > msgCount) {
296 fprintf(stderr, tr(114, "Referencing beyond EOF\n"));
297 markall_ret(-1)
299 } while (message[i - 1].m_flag == MHIDDEN ||
300 (message[i - 1].m_flag & MDELETED) != (unsigned)f);
301 mark(i, f);
302 break;
304 case TDASH:
305 msglist_is_single = FAL0;
306 if (beg == 0) {
307 i = valdot;
308 do {
309 if (mb.mb_threaded) {
310 mx = prev_in_thread(message + i - 1);
311 i = mx ? (int)PTR2SIZE(mx - message + 1) : 0;
312 } else
313 --i;
314 if (i <= 0) {
315 fprintf(stderr, tr(115, "Referencing before 1\n"));
316 markall_ret(-1)
318 } while ((message[i - 1].m_flag & MHIDDEN) ||
319 (message[i - 1].m_flag & MDELETED) != (unsigned)f);
320 mark(i, f);
322 break;
324 case TSTRING:
325 msglist_is_single = FAL0;
326 if (beg != 0) {
327 fprintf(stderr, tr(116, "Non-numeric second argument\n"));
328 markall_ret(-1)
330 ++other;
331 if (lexstring[0] == ':') {
332 colresult = evalcol(lexstring[1]);
333 if (colresult == 0) {
334 fprintf(stderr, tr(117, "Unknown colon modifier \"%s\"\n"),
335 lexstring);
336 markall_ret(-1)
338 colmod |= colresult;
340 else
341 np = add_to_namelist(&namelist, &nmlsize, np, savestr(lexstring));
342 break;
344 case TOPEN:
345 #ifdef HAVE_IMAP_SEARCH
346 msglist_is_single = FAL0;
347 if (imap_search(lexstring, f) == STOP)
348 markall_ret(-1)
349 topen = TRU1;
350 #else
351 fprintf(stderr, tr(42,
352 "`%s': the used selector is optional and not available\n"),
353 lexstring);
354 markall_ret(-1)
355 #endif
356 break;
358 case TDOLLAR:
359 case TUP:
360 case TDOT:
361 case TSEMI:
362 msglist_is_single = FAL0;
363 lexnumber = metamess(lexstring[0], f);
364 if (lexnumber == -1)
365 markall_ret(-1)
366 goto number;
368 case TBACK:
369 msglist_is_single = FAL0;
370 tback = TRU1;
371 for (i = 1; i <= msgCount; i++) {
372 if ((message[i - 1].m_flag & MHIDDEN) ||
373 (message[i - 1].m_flag & MDELETED) != (unsigned)f)
374 continue;
375 if (message[i - 1].m_flag & MOLDMARK)
376 mark(i, f);
378 break;
380 case TSTAR:
381 msglist_is_single = FAL0;
382 if (other) {
383 fprintf(stderr, tr(118, "Can't mix \"*\" with anything\n"));
384 markall_ret(-1)
386 star = TRU1;
387 break;
389 case TCOMMA:
390 msglist_is_single = FAL0;
391 #ifdef HAVE_IMAP
392 if (mb.mb_type == MB_IMAP && gotheaders++ == 0)
393 imap_getheaders(1, msgCount);
394 #endif
395 if (id == NULL && (cp = hfield1("in-reply-to", dot)) != NULL) {
396 id = savestr(cp);
397 idfield = ID_IN_REPLY_TO;
399 if (id == NULL && (cp = hfield1("references", dot)) != NULL) {
400 struct name *enp;
402 if ((enp = extract(cp, GREF)) != NULL) {
403 while (enp->n_flink != NULL)
404 enp = enp->n_flink;
405 id = savestr(enp->n_name);
406 idfield = ID_REFERENCES;
409 if (id == NULL) {
410 printf(tr(227,
411 "Cannot determine parent Message-ID of the current message\n"));
412 markall_ret(-1)
414 break;
416 case TERROR:
417 list_saw_numbers = TRU1;
418 msglist_is_single = FAL0;
419 markall_ret(-1)
421 threadflag = 0;
422 tok = scan(&bufp);
425 lastcolmod = colmod;
426 np = add_to_namelist(&namelist, &nmlsize, np, NULL);
427 --np;
428 mc = FAL0;
429 if (star) {
430 for (i = 0; i < msgCount; ++i) {
431 if (!(message[i].m_flag & MHIDDEN) &&
432 (message[i].m_flag & MDELETED) == (unsigned)f) {
433 mark(i + 1, f);
434 mc = TRU1;
437 if (!mc) {
438 if (!inhook)
439 printf(tr(119, "No applicable messages.\n"));
440 markall_ret(-1)
442 markall_ret(0)
445 if ((topen || tback) && !mc) {
446 for (i = 0; i < msgCount; ++i)
447 if (message[i].m_flag & MMARK)
448 mc = TRU1;
449 if (!mc) {
450 if (!inhook) {
451 if (tback)
452 fprintf(stderr, tr(131, "No previously marked messages.\n"));
453 else
454 printf("No messages satisfy (criteria).\n");/*TODO tr*/
456 markall_ret(-1)
460 /* If no numbers were given, mark all messages, so that we can unmark
461 * any whose sender was not selected if any user names were given */
462 if ((np > namelist || colmod != 0 || id) && !mc)
463 for (i = 1; i <= msgCount; ++i) {
464 if (!(message[i - 1].m_flag & MHIDDEN) &&
465 (message[i - 1].m_flag & MDELETED) == (unsigned)f)
466 mark(i, f);
469 /* If any names were given, eliminate any messages which don't match */
470 if (np > namelist || id) {
471 struct search_expr *sep = NULL;
472 bool_t allnet;
474 /* The `@' search works with struct search_expr, so build an array.
475 * To simplify array, i.e., regex_t destruction, and optimize for the
476 * common case we walk the entire array even in case of errors */
477 if (np > namelist) {
478 sep = scalloc(PTR2SIZE(np - namelist), sizeof(*sep));
479 for (j = 0, nq = namelist; *nq != NULL; ++j, ++nq) {
480 char *x = *nq, *y;
482 sep[j].ss_sexpr = x;
483 if (*x != '@' || rv < 0)
484 continue;
486 for (y = x + 1;; ++y) {
487 if (*y == '\0' || !fieldnamechar(*y)) {
488 x = NULL;
489 break;
491 if (*y == '@') {
492 x = y;
493 break;
496 sep[j].ss_where = (x == NULL || x - 1 == *nq)
497 ? "subject" : savestrbuf(*nq + 1, PTR2SIZE(x - *nq) - 1);
499 x = (x == NULL ? *nq : x) + 1;
500 if (*x == '\0') { /* XXX Simply remove from list instead? */
501 fprintf(stderr, tr(525, "Empty `[@..]@' search expression\n"));
502 rv = -1;
503 continue;
505 #ifdef HAVE_REGEX
506 if (anyof("^.[]*+?(){}|$", x)) {
507 sep[j].ss_sexpr = NULL;
508 if (regcomp(&sep[j].ss_reexpr, x,
509 REG_EXTENDED | REG_ICASE | REG_NOSUB) != 0) {
510 fprintf(stderr, tr(526,
511 "Invalid regular expression: >>> %s <<<\n"), x);
512 rv = -1;
513 continue;
515 } else
516 #endif
517 sep[j].ss_sexpr = x;
519 if (rv < 0)
520 goto jnamesearch_sepfree;
523 #ifdef HAVE_IMAP
524 if (mb.mb_type == MB_IMAP && gotheaders++ == 0)
525 imap_getheaders(1, msgCount);
526 #endif
527 srelax_hold();
528 allnet = ok_blook(allnet);
529 for (i = 1; i <= msgCount; ++i) {
530 mp = message + i - 1;
531 j = 0;
532 if (np > namelist) {
533 for (nq = namelist; *nq != NULL; ++nq) {
534 if (**nq == '@') {
535 if (_match_at(mp, sep + PTR2SIZE(nq - namelist))) {
536 ++j;
537 break;
539 } else if (**nq == '/') {
540 if (_match_dash(mp, *nq)) {
541 ++j;
542 break;
544 } else if (_matchsender(mp, *nq, allnet)) {
545 ++j;
546 break;
550 if (j == 0 && id && _matchmid(mp, id, idfield))
551 ++j;
552 if (j == 0)
553 mp->m_flag &= ~MMARK;
554 srelax();
556 srelax_rele();
558 /* Make sure we got some decent messages */
559 j = 0;
560 for (i = 1; i <= msgCount; ++i)
561 if (message[i - 1].m_flag & MMARK) {
562 ++j;
563 break;
565 if (j == 0) {
566 if (!inhook && np > namelist) {
567 printf(tr(120, "No applicable messages from {%s"), namelist[0]);
568 for (nq = namelist + 1; *nq != NULL; ++nq)
569 printf(tr(121, ", %s"), *nq);
570 printf(tr(122, "}\n"));
571 } else if (id)
572 printf(tr(227, "Parent message not found\n"));
573 rv = -1;
574 goto jnamesearch_sepfree;
577 jnamesearch_sepfree:
578 if (sep != NULL) {
579 #ifdef HAVE_REGEX
580 for (j = PTR2SIZE(np - namelist); j-- != 0;)
581 if (sep[j].ss_sexpr == NULL)
582 regfree(&sep[j].ss_reexpr);
583 #endif
584 free(sep);
586 if (rv < 0)
587 goto jleave;
590 /* If any colon modifiers were given, go through and unmark any
591 * messages which do not satisfy the modifiers */
592 if (colmod != 0) {
593 for (i = 1; i <= msgCount; ++i) {
594 struct coltab const *colp;
595 bool_t bad = TRU1;
597 mp = message + i - 1;
598 for (colp = _coltab; colp->co_char != '\0'; ++colp)
599 if ((colp->co_bit & colmod) &&
600 ((mp->m_flag & colp->co_mask) == (unsigned)colp->co_equal))
601 bad = FAL0;
602 if (bad)
603 unmark(i);
605 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
606 if (mp->m_flag & MMARK)
607 break;
608 if (PTRCMP(mp, >=, message + msgCount)) {
609 struct coltab const *colp;
611 if (!inhook) {
612 printf(tr(123, "No messages satisfy"));
613 for (colp = _coltab; colp->co_char != '\0'; ++colp)
614 if (colp->co_bit & colmod)
615 printf(" :%c", colp->co_char);
616 printf("\n");
618 markall_ret(-1)
622 markall_ret(0)
623 jleave:
624 free(namelist);
625 ac_free(lexstring);
626 NYD_LEAVE;
627 return rv;
629 #undef markall_ret
632 static int
633 evalcol(int col)
635 struct coltab const *colp;
636 int rv;
637 NYD_ENTER;
639 if (col == 0)
640 rv = lastcolmod;
641 else {
642 rv = 0;
643 for (colp = _coltab; colp->co_char != '\0'; ++colp)
644 if (colp->co_char == col) {
645 rv = colp->co_bit;
646 break;
649 NYD_LEAVE;
650 return rv;
653 static int
654 check(int mesg, int f)
656 struct message *mp;
657 NYD_ENTER;
659 if (mesg < 1 || mesg > msgCount) {
660 printf(tr(124, "%d: Invalid message number\n"), mesg);
661 goto jem1;
663 mp = message + mesg - 1;
664 if (mp->m_flag & MHIDDEN ||
665 (f != MDELETED && (mp->m_flag & MDELETED) != 0)) {
666 fprintf(stderr, tr(125, "%d: Inappropriate message\n"), mesg);
667 goto jem1;
669 f = 0;
670 jleave:
671 NYD_LEAVE;
672 return f;
673 jem1:
674 f = -1;
675 goto jleave;
678 static int
679 scan(char **sp)
681 char *cp, *cp2;
682 int rv, c, inquote, quotec;
683 struct lex const *lp;
684 NYD_ENTER;
686 if (regretp >= 0) {
687 strncpy(lexstring, string_stack[regretp], STRINGLEN);
688 lexstring[STRINGLEN -1] = '\0';
689 lexnumber = numberstack[regretp];
690 rv = regretstack[regretp--];
691 goto jleave;
694 cp = *sp;
695 cp2 = lexstring;
696 c = *cp++;
698 /* strip away leading white space */
699 while (blankchar(c))
700 c = *cp++;
702 /* If no characters remain, we are at end of line, so report that */
703 if (c == '\0') {
704 *sp = --cp;
705 rv = TEOL;
706 goto jleave;
709 /* Select members of a message thread */
710 if (c == '&') {
711 threadflag = 1;
712 if (*cp == '\0' || spacechar(*cp)) {
713 lexstring[0] = '.';
714 lexstring[1] = '\0';
715 *sp = cp;
716 rv = TDOT;
717 goto jleave;
719 c = *cp++;
722 /* If the leading character is a digit, scan the number and convert it
723 * on the fly. Return TNUMBER when done */
724 if (digitchar(c)) {
725 lexnumber = 0;
726 while (digitchar(c)) {
727 lexnumber = lexnumber*10 + c - '0';
728 *cp2++ = c;
729 c = *cp++;
731 *cp2 = '\0';
732 *sp = --cp;
733 rv = TNUMBER;
734 goto jleave;
737 /* An IMAP SEARCH list. Note that TOPEN has always been included in
738 * singles[] in Mail and mailx. Thus although there is no formal
739 * definition for (LIST) lists, they do not collide with historical
740 * practice because a subject string (LIST) could never been matched
741 * this way */
742 if (c == '(') {
743 ui32_t level = 1;
744 inquote = 0;
745 *cp2++ = c;
746 do {
747 if ((c = *cp++&0377) == '\0') {
748 jmtop:
749 fprintf(stderr, "Missing \")\".\n");
750 rv = TERROR;
751 goto jleave;
753 if (inquote && c == '\\') {
754 *cp2++ = c;
755 c = *cp++&0377;
756 if (c == '\0')
757 goto jmtop;
758 } else if (c == '"')
759 inquote = !inquote;
760 else if (inquote)
761 /*EMPTY*/;
762 else if (c == '(')
763 ++level;
764 else if (c == ')')
765 --level;
766 else if (spacechar(c)) {
767 /* Replace unquoted whitespace by single space characters, to make
768 * the string IMAP SEARCH conformant */
769 c = ' ';
770 if (cp2[-1] == ' ')
771 --cp2;
773 *cp2++ = c;
774 } while (c != ')' || level > 0);
775 *cp2 = '\0';
776 *sp = cp;
777 rv = TOPEN;
778 goto jleave;
781 /* Check for single character tokens; return such if found */
782 for (lp = _singles; lp->l_char != '\0'; ++lp)
783 if (c == lp->l_char) {
784 lexstring[0] = c;
785 lexstring[1] = '\0';
786 *sp = cp;
787 rv = lp->l_token;
788 goto jleave;
791 /* We've got a string! Copy all the characters of the string into
792 * lexstring, until we see a null, space, or tab. If the lead character is
793 * a " or ', save it and scan until you get another */
794 quotec = 0;
795 if (c == '\'' || c == '"') {
796 quotec = c;
797 c = *cp++;
799 while (c != '\0') {
800 if (quotec == 0 && c == '\\' && *cp != '\0')
801 c = *cp++;
802 if (c == quotec) {
803 ++cp;
804 break;
806 if (quotec == 0 && blankchar(c))
807 break;
808 if (PTRCMP(cp2 - lexstring, <, STRINGLEN - 1))
809 *cp2++ = c;
810 c = *cp++;
812 if (quotec && c == 0) {
813 fprintf(stderr, tr(127, "Missing %c\n"), quotec);
814 rv = TERROR;
815 goto jleave;
817 *sp = --cp;
818 *cp2 = '\0';
819 rv = TSTRING;
820 jleave:
821 NYD_LEAVE;
822 return rv;
825 static void
826 regret(int token)
828 NYD_ENTER;
829 if (++regretp >= REGDEP)
830 panic(tr(128, "Too many regrets"));
831 regretstack[regretp] = token;
832 lexstring[STRINGLEN -1] = '\0';
833 string_stack[regretp] = savestr(lexstring);
834 numberstack[regretp] = lexnumber;
835 NYD_LEAVE;
838 static void
839 scaninit(void)
841 NYD_ENTER;
842 regretp = -1;
843 threadflag = 0;
844 NYD_LEAVE;
847 static bool_t
848 _matchsender(struct message *mp, char const *str, bool_t allnet)
850 bool_t rv;
851 NYD_ENTER;
853 if (allnet) {
854 char *cp = nameof(mp, 0);
856 do {
857 if ((*cp == '@' || *cp == '\0') && (*str == '@' || *str == '\0')) {
858 rv = TRU1;
859 goto jleave;
861 if (*cp != *str)
862 break;
863 } while (++cp, *str++ != '\0');
864 rv = FAL0;
865 goto jleave;
867 rv = !strcmp(str, (*(ok_blook(showname) ? &realname : &skin))(name1(mp, 0)));
868 jleave:
869 NYD_LEAVE;
870 return rv;
873 static bool_t
874 _matchmid(struct message *mp, char *id, enum idfield idfield)
876 char *cp;
877 bool_t rv;
878 NYD_ENTER;
880 if ((cp = hfield1("message-id", mp)) != NULL) {
881 switch (idfield) {
882 case ID_REFERENCES:
883 rv = !msgidcmp(id, cp);
884 goto jleave;
885 case ID_IN_REPLY_TO: {
886 struct name *np;
888 if ((np = extract(id, GREF)) != NULL)
889 do {
890 if (!msgidcmp(np->n_name, cp)) {
891 rv = TRU1;
892 goto jleave;
894 } while ((np = np->n_flink) != NULL);
895 break;
899 rv = FAL0;
900 jleave:
901 NYD_LEAVE;
902 return rv;
905 static bool_t
906 _match_dash(struct message *mp, char const *str)
908 static char lastscan[128];
910 struct str in, out;
911 char *hfield, *hbody;
912 bool_t rv;
913 NYD_ENTER;
915 if (*++str == '\0') {
916 str = lastscan;
917 } else {
918 strncpy(lastscan, str, sizeof lastscan); /* XXX use new n_str object! */
919 lastscan[sizeof lastscan -1] = '\0';
922 /* Now look, ignoring case, for the word in the string */
923 if (ok_blook(searchheaders) && (hfield = strchr(str, ':'))) {
924 size_t l = PTR2SIZE(hfield - str);
925 hfield = ac_alloc(l +1);
926 memcpy(hfield, str, l);
927 hfield[l] = '\0';
928 hbody = hfieldX(hfield, mp);
929 ac_free(hfield);
930 hfield = UNCONST(str + l + 1);
931 } else {
932 hfield = UNCONST(str);
933 hbody = hfield1("subject", mp);
935 if (hbody == NULL) {
936 rv = FAL0;
937 goto jleave;
940 in.s = hbody;
941 in.l = strlen(hbody);
942 mime_fromhdr(&in, &out, TD_ICONV);
943 rv = substr(out.s, hfield);
944 free(out.s);
945 jleave:
946 NYD_LEAVE;
947 return rv;
950 static bool_t
951 _match_at(struct message *mp, struct search_expr *sep)
953 struct str in, out;
954 char *nfield, *cfield;
955 bool_t rv = FAL0;
956 NYD_ENTER;
958 nfield = savestr(sep->ss_where);
960 while ((cfield = n_strsep(&nfield, ',', TRU1)) != NULL) {
961 if (!asccasecmp(cfield, "body") ||
962 (cfield[1] == '\0' && cfield[0] == '>')) {
963 rv = FAL0;
964 jmsg:
965 if ((rv = message_match(mp, sep, rv)))
966 break;
967 } else if (!asccasecmp(cfield, "text") ||
968 (cfield[1] == '\0' && cfield[0] == '=')) {
969 rv = TRU1;
970 goto jmsg;
971 } else if (!asccasecmp(cfield, "header") ||
972 (cfield[1] == '\0' && cfield[0] == '<')) {
973 if ((rv = header_match(mp, sep)))
974 break;
975 } else if ((in.s = hfieldX(cfield, mp)) == NULL)
976 continue;
977 else {
978 in.l = strlen(in.s);
979 mime_fromhdr(&in, &out, TD_ICONV);
980 #ifdef HAVE_REGEX
981 if (sep->ss_sexpr == NULL)
982 rv = (regexec(&sep->ss_reexpr, out.s, 0,NULL, 0) != REG_NOMATCH);
983 else
984 #endif
985 rv = substr(out.s, sep->ss_sexpr);
986 free(out.s);
987 if (rv)
988 break;
991 NYD_LEAVE;
992 return rv;
995 static void
996 unmark(int mesg)
998 size_t i;
999 NYD_ENTER;
1001 i = (size_t)mesg;
1002 if (i < 1 || UICMP(z, i, >, msgCount))
1003 panic(tr(130, "Bad message number to unmark"));
1004 message[i - 1].m_flag &= ~MMARK;
1005 NYD_LEAVE;
1008 static int
1009 metamess(int meta, int f)
1011 int c, m;
1012 struct message *mp;
1013 NYD_ENTER;
1015 c = meta;
1016 switch (c) {
1017 case '^': /* First 'good' message left */
1018 mp = mb.mb_threaded ? threadroot : message;
1019 while (PTRCMP(mp, <, message + msgCount)) {
1020 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & MDELETED) == (ui32_t)f) {
1021 c = (int)PTR2SIZE(mp - message + 1);
1022 goto jleave;
1024 if (mb.mb_threaded) {
1025 mp = next_in_thread(mp);
1026 if (mp == NULL)
1027 break;
1028 } else
1029 ++mp;
1031 if (!inhook)
1032 printf(tr(132, "No applicable messages\n"));
1033 goto jem1;
1035 case '$': /* Last 'good message left */
1036 mp = mb.mb_threaded
1037 ? this_in_thread(threadroot, -1) : message + msgCount - 1;
1038 while (mp >= message) {
1039 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & MDELETED) == (ui32_t)f) {
1040 c = (int)PTR2SIZE(mp - message + 1);
1041 goto jleave;
1043 if (mb.mb_threaded) {
1044 mp = prev_in_thread(mp);
1045 if (mp == NULL)
1046 break;
1047 } else
1048 --mp;
1050 if (!inhook)
1051 printf(tr(132, "No applicable messages\n"));
1052 goto jem1;
1054 case '.':
1055 /* Current message */
1056 m = dot - message + 1;
1057 if ((dot->m_flag & MHIDDEN) || (dot->m_flag & MDELETED) != (ui32_t)f) {
1058 printf(tr(133, "%d: Inappropriate message\n"), m);
1059 goto jem1;
1061 c = m;
1062 break;
1064 case ';':
1065 /* Previously current message */
1066 if (prevdot == NULL) {
1067 fprintf(stderr, tr(228, "No previously current message\n"));
1068 goto jem1;
1070 m = prevdot - message + 1;
1071 if ((prevdot->m_flag & MHIDDEN) ||
1072 (prevdot->m_flag & MDELETED) != (ui32_t)f) {
1073 fprintf(stderr, tr(133, "%d: Inappropriate message\n"), m);
1074 goto jem1;
1076 c = m;
1077 break;
1079 default:
1080 fprintf(stderr, tr(134, "Unknown metachar (%c)\n"), c);
1081 goto jem1;
1083 jleave:
1084 NYD_LEAVE;
1085 return c;
1086 jem1:
1087 c = -1;
1088 goto jleave;
1091 FL int
1092 getmsglist(char *buf, int *vector, int flags)
1094 int *ip, mc;
1095 struct message *mp;
1096 NYD_ENTER;
1098 list_saw_numbers =
1099 msglist_is_single = FAL0;
1101 if (msgCount == 0) {
1102 *vector = 0;
1103 mc = 0;
1104 goto jleave;
1107 msglist_is_single = TRU1;
1108 if (markall(buf, flags) < 0) {
1109 mc = -1;
1110 goto jleave;
1113 ip = vector;
1114 if (inhook & 2) {
1115 mc = 0;
1116 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
1117 if (mp->m_flag & MMARK) {
1118 if (!(mp->m_flag & MNEWEST))
1119 unmark((int)PTR2SIZE(mp - message + 1));
1120 else
1121 ++mc;
1123 if (mc == 0) {
1124 mc = -1;
1125 goto jleave;
1129 if (mb.mb_threaded == 0) {
1130 for (mp = message; PTRCMP(mp, <, message + msgCount); ++mp)
1131 if (mp->m_flag & MMARK)
1132 *ip++ = (int)PTR2SIZE(mp - message + 1);
1133 } else {
1134 for (mp = threadroot; mp != NULL; mp = next_in_thread(mp))
1135 if (mp->m_flag & MMARK)
1136 *ip++ = (int)PTR2SIZE(mp - message + 1);
1138 *ip = 0;
1139 mc = (int)PTR2SIZE(ip - vector);
1140 msglist_is_single = (mc == 1);
1141 jleave:
1142 NYD_LEAVE;
1143 return mc;
1146 FL int
1147 getrawlist(char const *line, size_t linesize, char **argv, int argc,
1148 int echolist)
1150 char c, *cp2, quotec, *linebuf;
1151 char const *cp;
1152 int argn;
1153 NYD_ENTER;
1155 list_saw_numbers = FAL0;
1157 argn = 0;
1158 cp = line;
1159 linebuf = ac_alloc(linesize +1);
1160 for (;;) {
1161 for (; blankchar(*cp); ++cp)
1163 if (*cp == '\0')
1164 break;
1165 if (argn >= argc - 1) {
1166 fprintf(stderr, tr(126,
1167 "Too many elements in the list; excess discarded.\n"));
1168 break;
1170 cp2 = linebuf;
1171 quotec = '\0';
1172 while ((c = *cp) != '\0') {
1173 cp++;
1174 if (quotec != '\0') {
1175 if (c == quotec) {
1176 quotec = '\0';
1177 if (echolist)
1178 *cp2++ = c;
1179 } else if (c == '\\')
1180 switch (c = *cp++) {
1181 case '\0':
1182 *cp2++ = '\\';
1183 cp--;
1184 break;
1186 case '0': case '1': case '2': case '3':
1187 case '4': case '5': case '6': case '7':
1188 c -= '0';
1189 if (*cp >= '0' && *cp <= '7')
1190 c = c * 8 + *cp++ - '0';
1191 if (*cp >= '0' && *cp <= '7')
1192 c = c * 8 + *cp++ - '0';
1193 *cp2++ = c;
1194 break;
1195 case 'b':
1196 *cp2++ = '\b';
1197 break;
1198 case 'f':
1199 *cp2++ = '\f';
1200 break;
1201 case 'n':
1202 *cp2++ = '\n';
1203 break;
1204 case 'r':
1205 *cp2++ = '\r';
1206 break;
1207 case 't':
1208 *cp2++ = '\t';
1209 break;
1210 case 'v':
1211 *cp2++ = '\v';
1212 break;
1214 default:
1215 if (cp[-1] != quotec || echolist)
1216 *cp2++ = '\\';
1217 *cp2++ = c;
1219 /*else if (c == '^') {
1220 c = *cp++;
1221 if (c == '?')
1222 *cp2++ = '\177';
1223 /\* null doesn't show up anyway *\/
1224 else if ((c >= 'A' && c <= '_') ||
1225 (c >= 'a' && c <= 'z'))
1226 *cp2++ = c & 037;
1227 else {
1228 *cp2++ = '^';
1229 cp--;
1231 }*/ else
1232 *cp2++ = c;
1233 } else if (c == '"' || c == '\'') {
1234 if (echolist)
1235 *cp2++ = c;
1236 quotec = c;
1237 } else if (c == '\\' && !echolist) {
1238 if (*cp)
1239 *cp2++ = *cp++;
1240 else
1241 *cp2++ = c;
1242 } else if (blankchar(c))
1243 break;
1244 else
1245 *cp2++ = c;
1247 *cp2 = '\0';
1248 argv[argn++] = savestr(linebuf);
1250 argv[argn] = NULL;
1251 ac_free(linebuf);
1252 NYD_LEAVE;
1253 return argn;
1256 FL int
1257 first(int f, int m)
1259 struct message *mp;
1260 int rv;
1261 NYD_ENTER;
1263 if (msgCount == 0) {
1264 rv = 0;
1265 goto jleave;
1268 f &= MDELETED;
1269 m &= MDELETED;
1270 for (mp = dot;
1271 mb.mb_threaded ? (mp != NULL) : PTRCMP(mp, <, message + msgCount);
1272 mb.mb_threaded ? (mp = next_in_thread(mp)) : ++mp) {
1273 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & m) == (ui32_t)f) {
1274 rv = (int)PTR2SIZE(mp - message + 1);
1275 goto jleave;
1279 if (dot > message) {
1280 for (mp = dot - 1; (mb.mb_threaded ? (mp != NULL) : (mp >= message));
1281 mb.mb_threaded ? (mp = prev_in_thread(mp)) : --mp) {
1282 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & m) == (ui32_t)f) {
1283 rv = (int)PTR2SIZE(mp - message + 1);
1284 goto jleave;
1288 rv = 0;
1289 jleave:
1290 NYD_LEAVE;
1291 return rv;
1294 FL void
1295 mark(int mesg, int f)
1297 struct message *mp;
1298 int i;
1299 NYD_ENTER;
1301 i = mesg;
1302 if (i < 1 || i > msgCount)
1303 panic(tr(129, "Bad message number to mark"));
1304 if (mb.mb_threaded == 1 && threadflag) {
1305 if (!(message[i - 1].m_flag & MHIDDEN)) {
1306 if (f == MDELETED || !(message[i - 1].m_flag & MDELETED))
1307 message[i - 1].m_flag |= MMARK;
1310 if (message[i - 1].m_child) {
1311 mp = message[i - 1].m_child;
1312 mark((int)PTR2SIZE(mp - message + 1), f);
1313 for (mp = mp->m_younger; mp != NULL; mp = mp->m_younger)
1314 mark((int)PTR2SIZE(mp - message + 1), f);
1316 } else
1317 message[i - 1].m_flag |= MMARK;
1318 NYD_LEAVE;
1321 /* vim:set fenc=utf-8:s-it-mode */