README: mention *crawl* branch
[s-mailx.git] / list.c
blob1ca5460d52bdcfdd19ab1ca0fe13e68cf218a0dc
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Message list handling.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2013 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 #include "rcv.h"
42 #include <ctype.h>
43 #ifdef HAVE_WCTYPE_H
44 # include <wctype.h>
45 #endif
47 #include "extern.h"
49 enum idfield {
50 ID_REFERENCES,
51 ID_IN_REPLY_TO
54 static char **add_to_namelist(char ***namelist, size_t *nmlsize,
55 char **np, char *string);
56 static int markall(char *buf, int f);
57 static int evalcol(int col);
58 static int check(int mesg, int f);
59 static int scan(char **sp);
60 static void regret(int token);
61 static void scaninit(void);
62 static int matchsender(char *str, int mesg, int allnet);
63 static int matchmid(char *id, enum idfield idfield, int mesg);
64 static int matchsubj(char *str, int mesg);
65 static void unmark(int mesg);
66 static int metamess(int meta, int f);
68 static size_t STRINGLEN;
70 static int lexnumber; /* Number of TNUMBER from scan() */
71 static char *lexstring; /* String from TSTRING, scan() */
72 static int regretp; /* Pointer to TOS of regret tokens */
73 static int regretstack[REGDEP]; /* Stack of regretted tokens */
74 static char *string_stack[REGDEP]; /* Stack of regretted strings */
75 static int numberstack[REGDEP]; /* Stack of regretted numbers */
76 static int threadflag; /* mark entire threads */
79 * Convert the user string of message numbers and
80 * store the numbers into vector.
82 * Returns the count of messages picked up or -1 on error.
84 int
85 getmsglist(char *buf, int *vector, int flags)
87 int *ip;
88 struct message *mp;
89 int mc;
91 msglist_is_single = FAL0;
92 if (msgCount == 0) {
93 *vector = 0;
94 return 0;
97 msglist_is_single = TRU1;
98 if (markall(buf, flags) < 0)
99 return(-1);
100 ip = vector;
101 if (inhook & 2) {
102 mc = 0;
103 for (mp = &message[0]; mp < &message[msgCount]; mp++)
104 if (mp->m_flag & MMARK) {
105 if ((mp->m_flag & MNEWEST) == 0)
106 unmark(mp - &message[0] + 1);
107 else
108 mc++;
110 if (mc == 0)
111 return -1;
113 if (mb.mb_threaded == 0) {
114 for (mp = &message[0]; mp < &message[msgCount]; mp++)
115 if (mp->m_flag & MMARK)
116 *ip++ = mp - &message[0] + 1;
117 } else {
118 for (mp = threadroot; mp; mp = next_in_thread(mp))
119 if (mp->m_flag & MMARK)
120 *ip++ = mp - &message[0] + 1;
122 *ip = 0;
123 if ((size_t)(ip - vector) != 1)
124 msglist_is_single = FAL0;
125 return(ip - vector);
129 * Mark all messages that the user wanted from the command
130 * line in the message structure. Return 0 on success, -1
131 * on error.
135 * Bit values for colon modifiers.
138 #define CMNEW 01 /* New messages */
139 #define CMOLD 02 /* Old messages */
140 #define CMUNREAD 04 /* Unread messages */
141 #define CMDELETED 010 /* Deleted messages */
142 #define CMREAD 020 /* Read messages */
143 #define CMFLAG 040 /* Flagged messages */
144 #define CMANSWER 0100 /* Answered messages */
145 #define CMDRAFT 0200 /* Draft messages */
146 #define CMKILL 0400 /* Killed messages */
147 #define CMJUNK 01000 /* Junk messages */
150 * The following table describes the letters which can follow
151 * the colon and gives the corresponding modifier bit.
154 static struct coltab {
155 char co_char; /* What to find past : */
156 int co_bit; /* Associated modifier bit */
157 int co_mask; /* m_status bits to mask */
158 int co_equal; /* ... must equal this */
159 } coltab[] = {
160 { 'n', CMNEW, MNEW, MNEW },
161 { 'o', CMOLD, MNEW, 0 },
162 { 'u', CMUNREAD, MREAD, 0 },
163 { 'd', CMDELETED, MDELETED, MDELETED },
164 { 'r', CMREAD, MREAD, MREAD },
165 { 'f', CMFLAG, MFLAGGED, MFLAGGED },
166 { 'a', CMANSWER, MANSWERED, MANSWERED },
167 { 't', CMDRAFT, MDRAFTED, MDRAFTED },
168 { 'k', CMKILL, MKILL, MKILL },
169 { 'j', CMJUNK, MJUNK, MJUNK },
170 { 0, 0, 0, 0 }
173 static int lastcolmod;
175 static char **
176 add_to_namelist(char ***namelist, size_t *nmlsize, char **np, char *string)
178 size_t idx;
180 if ((idx = np - *namelist) >= *nmlsize) {
181 *namelist = srealloc(*namelist, (*nmlsize += 8) * sizeof *np);
182 np = &(*namelist)[idx];
184 *np++ = string;
185 return np;
188 static int
189 markall(char *buf, int f)
191 #define markall_ret(i) do { retval = i; goto out; } while (0);
192 char **np, **nq;
193 int i, retval;
194 struct message *mp, *mx;
195 char **namelist, *bufp, *id = NULL, *cp;
196 int tok, beg, mc, star, other, valdot, colmod, colresult, topen, tback;
197 size_t nmlsize;
198 enum idfield idfield = ID_REFERENCES;
199 #ifdef USE_IMAP
200 int gotheaders;
201 #endif
203 lexstring = ac_alloc(STRINGLEN = 2 * strlen(buf) + 1);
204 valdot = dot - &message[0] + 1;
205 colmod = 0;
206 for (i = 1; i <= msgCount; i++) {
207 message[i-1].m_flag &= ~MOLDMARK;
208 if (message[i-1].m_flag & MMARK)
209 message[i-1].m_flag |= MOLDMARK;
210 unmark(i);
212 bufp = buf;
213 mc = 0;
214 namelist = smalloc((nmlsize = 8) * sizeof *namelist);
215 np = &namelist[0];
216 scaninit();
217 tok = scan(&bufp);
218 star = 0;
219 other = 0;
220 beg = 0;
221 topen = 0;
222 tback = 0;
223 #ifdef USE_IMAP
224 gotheaders = 0;
225 #endif
227 while (tok != TEOL) {
228 switch (tok) {
229 case TNUMBER:
230 number:
231 if (star) {
232 printf(catgets(catd, CATSET, 112,
233 "No numbers mixed with *\n"));
234 markall_ret(-1)
236 mc++;
237 other++;
238 if (beg != 0) {
239 if (check(lexnumber, f))
240 markall_ret(-1)
241 i = beg;
242 while (mb.mb_threaded ? 1 : i <= lexnumber) {
243 if (!(message[i-1].m_flag&MHIDDEN) &&
244 (f == MDELETED ||
245 (message[i-1].m_flag &
246 MDELETED) == 0))
247 mark(i, f);
248 if (mb.mb_threaded) {
249 if (i == lexnumber)
250 break;
251 mx = next_in_thread(&message[i-1]);
252 if (mx == NULL)
253 markall_ret(-1)
254 i = mx-message+1;
255 } else
256 i++;
258 beg = 0;
259 break;
261 beg = lexnumber;
262 if (check(beg, f))
263 markall_ret(-1)
264 tok = scan(&bufp);
265 regret(tok);
266 if (tok != TDASH) {
267 mark(beg, f);
268 beg = 0;
270 break;
272 case TPLUS:
273 msglist_is_single = FAL0;
274 if (beg != 0) {
275 printf(catgets(catd, CATSET, 113,
276 "Non-numeric second argument\n"));
277 markall_ret(-1)
279 i = valdot;
280 do {
281 if (mb.mb_threaded) {
282 mx = next_in_thread(&message[i-1]);
283 i = mx ? mx-message+1 : msgCount+1;
284 } else
285 i++;
286 if (i > msgCount) {
287 printf(catgets(catd, CATSET, 114,
288 "Referencing beyond EOF\n"));
289 markall_ret(-1)
291 } while (message[i-1].m_flag == MHIDDEN ||
292 (message[i-1].m_flag & MDELETED) !=
293 (unsigned)f ||
294 (message[i-1].m_flag & MKILL));
295 mark(i, f);
296 break;
298 case TDASH:
299 msglist_is_single = FAL0;
300 if (beg == 0) {
301 i = valdot;
302 do {
303 if (mb.mb_threaded) {
304 mx = prev_in_thread(
305 &message[i-1]);
306 i = mx ? mx-message+1 : 0;
307 } else
308 i--;
309 if (i <= 0) {
310 printf(catgets(catd, CATSET,
311 115,
312 "Referencing before 1\n"));
313 markall_ret(-1)
315 } while ((message[i-1].m_flag & MHIDDEN) ||
316 (message[i-1].m_flag & MDELETED)
317 != (unsigned)f ||
318 (message[i-1].m_flag & MKILL));
319 mark(i, f);
321 break;
323 case TSTRING:
324 msglist_is_single = FAL0;
325 if (beg != 0) {
326 printf(catgets(catd, CATSET, 116,
327 "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 printf(catgets(catd, CATSET, 117,
335 "Unknown colon modifier \"%s\"\n"),
336 lexstring);
337 markall_ret(-1)
339 colmod |= colresult;
341 else
342 np = add_to_namelist(&namelist, &nmlsize,
343 np, savestr(lexstring));
344 break;
346 case TOPEN:
347 msglist_is_single = FAL0;
348 if (imap_search(lexstring, f) == STOP)
349 markall_ret(-1)
350 topen++;
351 break;
353 case TDOLLAR:
354 case TUP:
355 case TDOT:
356 case TSEMI:
357 msglist_is_single = FAL0;
358 lexnumber = metamess(lexstring[0], f);
359 if (lexnumber == -1)
360 markall_ret(-1)
361 goto number;
363 case TBACK:
364 msglist_is_single = FAL0;
365 tback = 1;
366 for (i = 1; i <= msgCount; i++) {
367 if ((message[i-1].m_flag & MHIDDEN) ||
368 (message[i-1].m_flag&MDELETED)
369 != (unsigned)f)
370 continue;
371 if (message[i-1].m_flag&MOLDMARK)
372 mark(i, f);
374 break;
376 case TSTAR:
377 msglist_is_single = FAL0;
378 if (other) {
379 printf(catgets(catd, CATSET, 118,
380 "Can't mix \"*\" with anything\n"));
381 markall_ret(-1)
383 star++;
384 break;
386 case TCOMMA:
387 msglist_is_single = FAL0;
388 #ifdef USE_IMAP
389 if (mb.mb_type == MB_IMAP && gotheaders++ == 0)
390 imap_getheaders(1, msgCount);
391 #endif
392 if (id == NULL && (cp = hfield1("in-reply-to", dot))
393 != NULL) {
394 id = savestr(cp);
395 idfield = ID_IN_REPLY_TO;
397 if (id == NULL && (cp = hfield1("references", dot))
398 != NULL) {
399 struct name *np;
400 if ((np = extract(cp, GREF)) != NULL) {
401 while (np->n_flink != NULL)
402 np = np->n_flink;
403 id = savestr(np->n_name);
404 idfield = ID_REFERENCES;
407 if (id == NULL) {
408 printf(catgets(catd, CATSET, 227,
409 "Cannot determine parent Message-ID of the current message\n"));
410 markall_ret(-1)
412 break;
414 case TERROR:
415 msglist_is_single = FAL0;
416 markall_ret(-1)
418 threadflag = 0;
419 tok = scan(&bufp);
421 lastcolmod = colmod;
422 np = add_to_namelist(&namelist, &nmlsize, np, NULL);
423 np--;
424 mc = 0;
425 if (star) {
426 for (i = 0; i < msgCount; i++) {
427 if (!(message[i].m_flag & MHIDDEN) &&
428 (message[i].m_flag & MDELETED) ==
429 (unsigned)f) {
430 mark(i+1, f);
431 mc++;
434 if (mc == 0) {
435 if (!inhook)
436 printf(catgets(catd, CATSET, 119,
437 "No applicable messages.\n"));
438 markall_ret(-1)
440 markall_ret(0)
443 if ((topen || tback) && mc == 0) {
444 for (i = 0; i < msgCount; i++)
445 if (message[i].m_flag & MMARK)
446 mc++;
447 if (mc == 0) {
448 if (!inhook)
449 printf(tback ?
450 "No previously marked messages.\n" :
451 "No messages satisfy (criteria).\n");
452 markall_ret(-1)
457 * If no numbers were given, mark all of the messages,
458 * so that we can unmark any whose sender was not selected
459 * if any user names were given.
462 if ((np > namelist || colmod != 0 || id) && mc == 0)
463 for (i = 1; i <= msgCount; i++) {
464 if (!(message[i-1].m_flag & MHIDDEN) &&
465 (message[i-1].m_flag & MDELETED) ==
466 (unsigned)f)
467 mark(i, f);
471 * If any names were given, go through and eliminate any
472 * messages whose senders were not requested.
475 if (np > namelist || id) {
476 int allnet = value("allnet") != NULL;
478 #ifdef USE_IMAP
479 if (mb.mb_type == MB_IMAP && gotheaders++ == 0)
480 imap_getheaders(1, msgCount);
481 #endif
482 for (i = 1; i <= msgCount; i++) {
483 mc = 0;
484 if (np > namelist) {
485 for (nq = &namelist[0]; *nq != NULL; nq++) {
486 if (**nq == '/') {
487 if (matchsubj(*nq, i)) {
488 mc++;
489 break;
492 else {
493 if (matchsender(*nq, i,
494 allnet)) {
495 mc++;
496 break;
501 if (mc == 0 && id && matchmid(id, idfield, i))
502 mc++;
503 if (mc == 0)
504 unmark(i);
508 * Make sure we got some decent messages.
511 mc = 0;
512 for (i = 1; i <= msgCount; i++)
513 if (message[i-1].m_flag & MMARK) {
514 mc++;
515 break;
517 if (mc == 0) {
518 if (!inhook && np > namelist) {
519 printf(catgets(catd, CATSET, 120,
520 "No applicable messages from {%s"),
521 namelist[0]);
522 for (nq = &namelist[1]; *nq != NULL; nq++)
523 printf(catgets(catd, CATSET, 121,
524 ", %s"), *nq);
525 printf(catgets(catd, CATSET, 122, "}\n"));
526 } else if (id) {
527 printf(catgets(catd, CATSET, 227,
528 "Parent message not found\n"));
530 markall_ret(-1)
535 * If any colon modifiers were given, go through and
536 * unmark any messages which do not satisfy the modifiers.
539 if (colmod != 0) {
540 for (i = 1; i <= msgCount; i++) {
541 struct coltab *colp;
543 mp = &message[i - 1];
544 for (colp = &coltab[0]; colp->co_char; colp++)
545 if (colp->co_bit & colmod)
546 if ((mp->m_flag & colp->co_mask)
547 != (unsigned)colp->co_equal)
548 unmark(i);
551 for (mp = &message[0]; mp < &message[msgCount]; mp++)
552 if (mp->m_flag & MMARK)
553 break;
554 if (mp >= &message[msgCount]) {
555 struct coltab *colp;
557 if (!inhook) {
558 printf(catgets(catd, CATSET, 123,
559 "No messages satisfy"));
560 for (colp = &coltab[0]; colp->co_char; colp++)
561 if (colp->co_bit & colmod)
562 printf(" :%c", colp->co_char);
563 printf("\n");
565 markall_ret(-1)
568 markall_ret(0)
569 out:
570 free(namelist);
571 ac_free(lexstring);
572 return retval;
573 #undef markall_ret
577 * Turn the character after a colon modifier into a bit
578 * value.
580 static int
581 evalcol(int col)
583 struct coltab *colp;
585 if (col == 0)
586 return(lastcolmod);
587 for (colp = &coltab[0]; colp->co_char; colp++)
588 if (colp->co_char == col)
589 return(colp->co_bit);
590 return(0);
594 * Check the passed message number for legality and proper flags.
595 * If f is MDELETED, then either kind will do. Otherwise, the message
596 * has to be undeleted.
598 static int
599 check(int mesg, int f)
601 struct message *mp;
603 if (mesg < 1 || mesg > msgCount) {
604 printf(catgets(catd, CATSET, 124,
605 "%d: Invalid message number\n"), mesg);
606 return(-1);
608 mp = &message[mesg-1];
609 if (mp->m_flag & MHIDDEN || (f != MDELETED &&
610 (mp->m_flag & MDELETED) != 0)) {
611 printf(catgets(catd, CATSET, 125,
612 "%d: Inappropriate message\n"), mesg);
613 return(-1);
615 return(0);
619 * Scan out the list of string arguments, shell style
620 * for a RAWLIST.
623 getrawlist(const char *line, size_t linesize, char **argv, int argc,
624 int echolist)
626 char c, *cp2, quotec;
627 const char *cp;
628 int argn;
629 char *linebuf;
631 argn = 0;
632 cp = line;
633 linebuf = ac_alloc(linesize + 1);
634 for (;;) {
635 for (; blankchar(*cp & 0377); cp++);
636 if (*cp == '\0')
637 break;
638 if (argn >= argc - 1) {
639 printf(catgets(catd, CATSET, 126,
640 "Too many elements in the list; excess discarded.\n"));
641 break;
643 cp2 = linebuf;
644 quotec = '\0';
645 while ((c = *cp) != '\0') {
646 cp++;
647 if (quotec != '\0') {
648 if (c == quotec) {
649 quotec = '\0';
650 if (echolist)
651 *cp2++ = c;
652 } else if (c == '\\')
653 switch (c = *cp++) {
654 case '\0':
655 *cp2++ = '\\';
656 cp--;
657 break;
659 case '0': case '1': case '2': case '3':
660 case '4': case '5': case '6': case '7':
661 c -= '0';
662 if (*cp >= '0' && *cp <= '7')
663 c = c * 8 + *cp++ - '0';
664 if (*cp >= '0' && *cp <= '7')
665 c = c * 8 + *cp++ - '0';
666 *cp2++ = c;
667 break;
668 case 'b':
669 *cp2++ = '\b';
670 break;
671 case 'f':
672 *cp2++ = '\f';
673 break;
674 case 'n':
675 *cp2++ = '\n';
676 break;
677 case 'r':
678 *cp2++ = '\r';
679 break;
680 case 't':
681 *cp2++ = '\t';
682 break;
683 case 'v':
684 *cp2++ = '\v';
685 break;
687 default:
688 if (cp[-1]!=quotec || echolist)
689 *cp2++ = '\\';
690 *cp2++ = c;
692 /*else if (c == '^') {
693 c = *cp++;
694 if (c == '?')
695 *cp2++ = '\177';
696 /\* null doesn't show up anyway *\/
697 else if ((c >= 'A' && c <= '_') ||
698 (c >= 'a' && c <= 'z'))
699 *cp2++ = c & 037;
700 else {
701 *cp2++ = '^';
702 cp--;
704 }*/ else
705 *cp2++ = c;
706 } else if (c == '"' || c == '\'') {
707 if (echolist)
708 *cp2++ = c;
709 quotec = c;
710 } else if (c == '\\' && !echolist) {
711 if (*cp)
712 *cp2++ = *cp++;
713 else
714 *cp2++ = c;
715 } else if (blankchar(c & 0377))
716 break;
717 else
718 *cp2++ = c;
720 *cp2 = '\0';
721 argv[argn++] = savestr(linebuf);
723 argv[argn] = NULL;
724 ac_free(linebuf);
725 return argn;
729 * scan out a single lexical item and return its token number,
730 * updating the string pointer passed **p. Also, store the value
731 * of the number or string scanned in lexnumber or lexstring as
732 * appropriate. In any event, store the scanned `thing' in lexstring.
735 static struct lex {
736 char l_char;
737 enum ltoken l_token;
738 } singles[] = {
739 { '$', TDOLLAR },
740 { '.', TDOT },
741 { '^', TUP },
742 { '*', TSTAR },
743 { '-', TDASH },
744 { '+', TPLUS },
745 { '(', TOPEN },
746 { ')', TCLOSE },
747 { ',', TCOMMA },
748 { ';', TSEMI },
749 { '`', TBACK },
750 { 0, 0 }
753 static int
754 scan(char **sp)
756 char *cp, *cp2;
757 int c, level, inquote;
758 struct lex *lp;
759 int quotec;
761 if (regretp >= 0) {
762 strncpy(lexstring, string_stack[regretp], STRINGLEN);
763 lexstring[STRINGLEN-1]='\0';
764 lexnumber = numberstack[regretp];
765 return(regretstack[regretp--]);
767 cp = *sp;
768 cp2 = lexstring;
769 c = *cp++;
772 * strip away leading white space.
775 while (blankchar(c))
776 c = *cp++;
779 * If no characters remain, we are at end of line,
780 * so report that.
783 if (c == '\0') {
784 *sp = --cp;
785 return(TEOL);
789 * Select members of a message thread.
791 if (c == '&') {
792 threadflag = 1;
793 if (*cp == '\0' || spacechar(*cp&0377)) {
794 lexstring[0] = '.';
795 lexstring[1] = '\0';
796 *sp = cp;
797 return TDOT;
799 c = *cp++;
803 * If the leading character is a digit, scan
804 * the number and convert it on the fly.
805 * Return TNUMBER when done.
808 if (digitchar(c)) {
809 lexnumber = 0;
810 while (digitchar(c)) {
811 lexnumber = lexnumber*10 + c - '0';
812 *cp2++ = c;
813 c = *cp++;
815 *cp2 = '\0';
816 *sp = --cp;
817 return(TNUMBER);
821 * An IMAP SEARCH list. Note that TOPEN has always been included
822 * in singles[] in Mail and mailx. Thus although there is no formal
823 * definition for (LIST) lists, they do not collide with historical
824 * practice because a subject string (LIST) could never been matched
825 * this way.
828 if (c == '(') {
829 level = 1;
830 inquote = 0;
831 *cp2++ = c;
832 do {
833 if ((c = *cp++&0377) == '\0') {
834 mtop: fprintf(stderr, "Missing \")\".\n");
835 return TERROR;
837 if (inquote && c == '\\') {
838 *cp2++ = c;
839 c = *cp++&0377;
840 if (c == '\0')
841 goto mtop;
842 } else if (c == '"')
843 inquote = !inquote;
844 else if (inquote)
845 /*EMPTY*/;
846 else if (c == '(')
847 level++;
848 else if (c == ')')
849 level--;
850 else if (spacechar(c)) {
852 * Replace unquoted whitespace by single
853 * space characters, to make the string
854 * IMAP SEARCH conformant.
856 c = ' ';
857 if (cp2[-1] == ' ')
858 cp2--;
860 *cp2++ = c;
861 } while (c != ')' || level > 0);
862 *cp2 = '\0';
863 *sp = cp;
864 return TOPEN;
868 * Check for single character tokens; return such
869 * if found.
872 for (lp = &singles[0]; lp->l_char != 0; lp++)
873 if (c == lp->l_char) {
874 lexstring[0] = c;
875 lexstring[1] = '\0';
876 *sp = cp;
877 return(lp->l_token);
881 * We've got a string! Copy all the characters
882 * of the string into lexstring, until we see
883 * a null, space, or tab.
884 * If the lead character is a " or ', save it
885 * and scan until you get another.
888 quotec = 0;
889 if (c == '\'' || c == '"') {
890 quotec = c;
891 c = *cp++;
893 while (c != '\0') {
894 if (quotec == 0 && c == '\\' && *cp)
895 c = *cp++;
896 if (c == quotec) {
897 cp++;
898 break;
900 if (quotec == 0 && blankchar(c))
901 break;
902 if ((size_t)(cp2 - lexstring) < (size_t)STRINGLEN - 1)
903 *cp2++ = c;
904 c = *cp++;
906 if (quotec && c == 0) {
907 fprintf(stderr, catgets(catd, CATSET, 127,
908 "Missing %c\n"), quotec);
909 return TERROR;
911 *sp = --cp;
912 *cp2 = '\0';
913 return(TSTRING);
917 * Unscan the named token by pushing it onto the regret stack.
919 static void
920 regret(int token)
922 if (++regretp >= REGDEP)
923 panic(catgets(catd, CATSET, 128, "Too many regrets"));
924 regretstack[regretp] = token;
925 lexstring[STRINGLEN-1] = '\0';
926 string_stack[regretp] = savestr(lexstring);
927 numberstack[regretp] = lexnumber;
931 * Reset all the scanner global variables.
933 static void
934 scaninit(void)
936 regretp = -1;
937 threadflag = 0;
941 * Find the first message whose flags & m == f and return
942 * its message number.
944 int
945 first(int f, int m)
947 struct message *mp;
949 if (msgCount == 0)
950 return 0;
951 f &= MDELETED;
952 m &= MDELETED;
953 for (mp = dot; mb.mb_threaded ? mp != NULL : mp < &message[msgCount];
954 mb.mb_threaded ? mp = next_in_thread(mp) : mp++) {
955 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & m) == (unsigned)f)
956 return mp - message + 1;
958 if (dot > &message[0]) {
959 for (mp = dot-1; mb.mb_threaded ?
960 mp != NULL : mp >= &message[0];
961 mb.mb_threaded ?
962 mp = prev_in_thread(mp) : mp--) {
963 if (! (mp->m_flag & MHIDDEN) &&
964 (mp->m_flag & m) == (unsigned)f)
965 return mp - message + 1;
968 return 0;
972 * See if the passed name sent the passed message number. Return true
973 * if so.
975 static int
976 matchsender(char *str, int mesg, int allnet)
978 if (allnet) {
979 char *cp = nameof(&message[mesg - 1], 0);
981 do {
982 if ((*cp == '@' || *cp == '\0') &&
983 (*str == '@' || *str == '\0'))
984 return 1;
985 if (*cp != *str)
986 break;
987 } while (cp++, *str++ != '\0');
988 return 0;
990 return !strcmp(str, (value("showname") ? realname : skin)
991 (name1(&message[mesg - 1], 0)));
994 static int
995 matchmid(char *id, enum idfield idfield, int mesg)
997 struct name *np;
998 char *cp;
1000 if ((cp = hfield1("message-id", &message[mesg - 1])) != NULL) {
1001 switch (idfield) {
1002 case ID_REFERENCES:
1003 return msgidcmp(id, cp) == 0;
1004 case ID_IN_REPLY_TO:
1005 if ((np = extract(id, GREF)) != NULL)
1006 do {
1007 if (msgidcmp(np->n_name, cp) == 0)
1008 return 1;
1009 } while ((np = np->n_flink) != NULL);
1010 break;
1013 return 0;
1017 * See if the given string matches inside the subject field of the
1018 * given message. For the purpose of the scan, we ignore case differences.
1019 * If it does, return true. The string search argument is assumed to
1020 * have the form "/search-string." If it is of the form "/," we use the
1021 * previous search string.
1024 static char lastscan[128];
1026 static int
1027 matchsubj(char *str, int mesg)
1029 struct message *mp;
1030 char *cp, *cp2;
1031 struct str in, out;
1032 int i;
1034 str++;
1035 if (strlen(str) == 0) {
1036 str = lastscan;
1037 } else {
1038 strncpy(lastscan, str, sizeof lastscan);
1039 lastscan[sizeof lastscan - 1]='\0';
1041 mp = &message[mesg-1];
1044 * Now look, ignoring case, for the word in the string.
1047 if (value("searchheaders") && (cp = strchr(str, ':'))) {
1048 *cp++ = '\0';
1049 cp2 = hfieldX(str, mp);
1050 cp[-1] = ':';
1051 } else {
1052 cp = str;
1053 cp2 = hfield1("subject", mp);
1055 if (cp2 == NULL)
1056 return(0);
1057 in.s = cp2;
1058 in.l = strlen(cp2);
1059 mime_fromhdr(&in, &out, TD_ICONV);
1060 i = substr(out.s, cp);
1061 free(out.s);
1062 return i;
1066 * Mark the named message by setting its mark bit.
1068 void
1069 mark(int mesg, int f)
1071 struct message *mp;
1072 int i;
1074 i = mesg;
1075 if (i < 1 || i > msgCount)
1076 panic(catgets(catd, CATSET, 129, "Bad message number to mark"));
1077 if (mb.mb_threaded == 1 && threadflag) {
1078 if ((message[i-1].m_flag & MHIDDEN) == 0) {
1079 if (f == MDELETED ||
1080 (message[i-1].m_flag&MDELETED) == 0)
1081 message[i-1].m_flag |= MMARK;
1083 if (message[i-1].m_child) {
1084 mp = message[i-1].m_child;
1085 mark(mp-message+1, f);
1086 for (mp = mp->m_younger; mp; mp = mp->m_younger)
1087 mark(mp-message+1, f);
1089 } else
1090 message[i-1].m_flag |= MMARK;
1094 * Unmark the named message.
1096 static void
1097 unmark(int mesg)
1099 int i;
1101 i = mesg;
1102 if (i < 1 || i > msgCount)
1103 panic(catgets(catd, CATSET, 130,
1104 "Bad message number to unmark"));
1105 message[i-1].m_flag &= ~MMARK;
1109 * Return the message number corresponding to the passed meta character.
1111 static int
1112 metamess(int meta, int f)
1114 int c, m;
1115 struct message *mp;
1117 c = meta;
1118 switch (c) {
1119 case '^':
1121 * First 'good' message left.
1123 mp = mb.mb_threaded ? threadroot : &message[0];
1124 while (mp < &message[msgCount]) {
1125 if (!(mp->m_flag & (MHIDDEN|MKILL)) &&
1126 (mp->m_flag & MDELETED) == (unsigned)f)
1127 return(mp - &message[0] + 1);
1128 if (mb.mb_threaded) {
1129 mp = next_in_thread(mp);
1130 if (mp == NULL)
1131 break;
1132 } else
1133 mp++;
1135 if (!inhook)
1136 printf(catgets(catd, CATSET, 131,
1137 "No applicable messages\n"));
1138 return(-1);
1140 case '$':
1142 * Last 'good message left.
1144 mp = mb.mb_threaded ? this_in_thread(threadroot, -1) :
1145 &message[msgCount-1];
1146 while (mp >= &message[0]) {
1147 if (!(mp->m_flag & (MHIDDEN|MKILL)) &&
1148 (mp->m_flag & MDELETED) == (unsigned)f)
1149 return(mp - &message[0] + 1);
1150 if (mb.mb_threaded) {
1151 mp = prev_in_thread(mp);
1152 if (mp == NULL)
1153 break;
1154 } else
1155 mp--;
1157 if (!inhook)
1158 printf(catgets(catd, CATSET, 132,
1159 "No applicable messages\n"));
1160 return(-1);
1162 case '.':
1164 * Current message.
1166 m = dot - &message[0] + 1;
1167 if ((dot->m_flag & MHIDDEN) ||
1168 (dot->m_flag & MDELETED) != (unsigned)f) {
1169 printf(catgets(catd, CATSET, 133,
1170 "%d: Inappropriate message\n"), m);
1171 return(-1);
1173 return(m);
1175 case ';':
1177 * Previously current message.
1179 if (prevdot == NULL) {
1180 printf(catgets(catd, CATSET, 228,
1181 "No previously current message\n"));
1182 return(-1);
1184 m = prevdot - &message[0] + 1;
1185 if ((prevdot->m_flag & MHIDDEN) ||
1186 (prevdot->m_flag & MDELETED) != (unsigned)f) {
1187 printf(catgets(catd, CATSET, 133,
1188 "%d: Inappropriate message\n"), m);
1189 return(-1);
1191 return(m);
1193 default:
1194 printf(catgets(catd, CATSET, 134,
1195 "Unknown metachar (%c)\n"), c);
1196 return(-1);