README: document *next* branch etc.
[s-mailx.git] / list.c
blob82483d30b129c4e17ecd91da5175e72a4a0b5198
1 /*
2 * S-nail - a mail user agent derived from Berkeley Mail.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 Steffen "Daode" Nurpmeso.
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"
41 #include <ctype.h>
42 #include "extern.h"
43 #ifdef HAVE_WCTYPE_H
44 #include <wctype.h>
45 #endif /* HAVE_WCTYPE_H */
48 * Mail -- a mail program
50 * Message list handling.
53 enum idfield {
54 ID_REFERENCES,
55 ID_IN_REPLY_TO
58 static char **add_to_namelist(char ***namelist, size_t *nmlsize,
59 char **np, char *string);
60 static int markall(char *buf, int f);
61 static int evalcol(int col);
62 static int check(int mesg, int f);
63 static int scan(char **sp);
64 static void regret(int token);
65 static void scaninit(void);
66 static int matchsender(char *str, int mesg, int allnet);
67 static int matchmid(char *id, enum idfield idfield, int mesg);
68 static int matchsubj(char *str, int mesg);
69 static void unmark(int mesg);
70 static int metamess(int meta, int f);
72 static size_t STRINGLEN;
74 static int lexnumber; /* Number of TNUMBER from scan() */
75 static char *lexstring; /* String from TSTRING, scan() */
76 static int regretp; /* Pointer to TOS of regret tokens */
77 static int regretstack[REGDEP]; /* Stack of regretted tokens */
78 static char *string_stack[REGDEP]; /* Stack of regretted strings */
79 static int numberstack[REGDEP]; /* Stack of regretted numbers */
80 static int threadflag; /* mark entire threads */
83 * Convert the user string of message numbers and
84 * store the numbers into vector.
86 * Returns the count of messages picked up or -1 on error.
88 int
89 getmsglist(char *buf, int *vector, int flags)
91 int *ip;
92 struct message *mp;
93 int mc;
95 if (msgCount == 0) {
96 *vector = 0;
97 return 0;
99 if (markall(buf, flags) < 0)
100 return(-1);
101 ip = vector;
102 if (inhook & 2) {
103 mc = 0;
104 for (mp = &message[0]; mp < &message[msgCount]; mp++)
105 if (mp->m_flag & MMARK) {
106 if ((mp->m_flag & MNEWEST) == 0)
107 unmark(mp - &message[0] + 1);
108 else
109 mc++;
111 if (mc == 0)
112 return -1;
114 if (mb.mb_threaded == 0) {
115 for (mp = &message[0]; mp < &message[msgCount]; mp++)
116 if (mp->m_flag & MMARK)
117 *ip++ = mp - &message[0] + 1;
118 } else {
119 for (mp = threadroot; mp; mp = next_in_thread(mp))
120 if (mp->m_flag & MMARK)
121 *ip++ = mp - &message[0] + 1;
123 *ip = 0;
124 return(ip - vector);
128 * Mark all messages that the user wanted from the command
129 * line in the message structure. Return 0 on success, -1
130 * on error.
134 * Bit values for colon modifiers.
137 #define CMNEW 01 /* New messages */
138 #define CMOLD 02 /* Old messages */
139 #define CMUNREAD 04 /* Unread messages */
140 #define CMDELETED 010 /* Deleted messages */
141 #define CMREAD 020 /* Read messages */
142 #define CMFLAG 040 /* Flagged messages */
143 #define CMANSWER 0100 /* Answered messages */
144 #define CMDRAFT 0200 /* Draft messages */
145 #define CMKILL 0400 /* Killed messages */
146 #define CMJUNK 01000 /* Junk messages */
149 * The following table describes the letters which can follow
150 * the colon and gives the corresponding modifier bit.
153 static struct coltab {
154 char co_char; /* What to find past : */
155 int co_bit; /* Associated modifier bit */
156 int co_mask; /* m_status bits to mask */
157 int co_equal; /* ... must equal this */
158 } coltab[] = {
159 { 'n', CMNEW, MNEW, MNEW },
160 { 'o', CMOLD, MNEW, 0 },
161 { 'u', CMUNREAD, MREAD, 0 },
162 { 'd', CMDELETED, MDELETED, MDELETED },
163 { 'r', CMREAD, MREAD, MREAD },
164 { 'f', CMFLAG, MFLAGGED, MFLAGGED },
165 { 'a', CMANSWER, MANSWERED, MANSWERED },
166 { 't', CMDRAFT, MDRAFTED, MDRAFTED },
167 { 'k', CMKILL, MKILL, MKILL },
168 { 'j', CMJUNK, MJUNK, MJUNK },
169 { 0, 0, 0, 0 }
172 static int lastcolmod;
174 static char **
175 add_to_namelist(char ***namelist, size_t *nmlsize, char **np, char *string)
177 size_t idx;
179 if ((idx = np - *namelist) >= *nmlsize) {
180 *namelist = srealloc(*namelist, (*nmlsize += 8) * sizeof *np);
181 np = &(*namelist)[idx];
183 *np++ = string;
184 return np;
187 #define markall_ret(i) { \
188 retval = i; \
189 ac_free(lexstring); \
190 goto out; \
193 static int
194 markall(char *buf, int f)
196 char **np, **nq;
197 int i, retval, gotheaders;
198 struct message *mp, *mx;
199 char **namelist, *bufp, *id = NULL, *cp;
200 int tok, beg, mc, star, other, valdot, colmod, colresult, topen, tback;
201 size_t nmlsize;
202 enum idfield idfield = ID_REFERENCES;
204 lexstring = ac_alloc(STRINGLEN = 2 * strlen(buf) + 1);
205 valdot = dot - &message[0] + 1;
206 colmod = 0;
207 for (i = 1; i <= msgCount; i++) {
208 message[i-1].m_flag &= ~MOLDMARK;
209 if (message[i-1].m_flag & MMARK)
210 message[i-1].m_flag |= MOLDMARK;
211 unmark(i);
213 bufp = buf;
214 mc = 0;
215 namelist = smalloc((nmlsize = 8) * sizeof *namelist);
216 np = &namelist[0];
217 scaninit();
218 tok = scan(&bufp);
219 star = 0;
220 other = 0;
221 beg = 0;
222 topen = 0;
223 tback = 0;
224 gotheaders = 0;
225 while (tok != TEOL) {
226 switch (tok) {
227 case TNUMBER:
228 number:
229 if (star) {
230 printf(catgets(catd, CATSET, 112,
231 "No numbers mixed with *\n"));
232 markall_ret(-1)
234 mc++;
235 other++;
236 if (beg != 0) {
237 if (check(lexnumber, f))
238 markall_ret(-1)
239 i = beg;
240 while (mb.mb_threaded ? 1 : i <= lexnumber) {
241 if (!(message[i-1].m_flag&MHIDDEN) &&
242 (f == MDELETED ||
243 (message[i-1].m_flag &
244 MDELETED) == 0))
245 mark(i, f);
246 if (mb.mb_threaded) {
247 if (i == lexnumber)
248 break;
249 mx = next_in_thread(&message[i-1]);
250 if (mx == NULL)
251 markall_ret(-1)
252 i = mx-message+1;
253 } else
254 i++;
256 beg = 0;
257 break;
259 beg = lexnumber;
260 if (check(beg, f))
261 markall_ret(-1)
262 tok = scan(&bufp);
263 regret(tok);
264 if (tok != TDASH) {
265 mark(beg, f);
266 beg = 0;
268 break;
270 case TPLUS:
271 if (beg != 0) {
272 printf(catgets(catd, CATSET, 113,
273 "Non-numeric second argument\n"));
274 markall_ret(-1)
276 i = valdot;
277 do {
278 if (mb.mb_threaded) {
279 mx = next_in_thread(&message[i-1]);
280 i = mx ? mx-message+1 : msgCount+1;
281 } else
282 i++;
283 if (i > msgCount) {
284 printf(catgets(catd, CATSET, 114,
285 "Referencing beyond EOF\n"));
286 markall_ret(-1)
288 } while (message[i-1].m_flag == MHIDDEN ||
289 (message[i-1].m_flag & MDELETED) !=
290 (unsigned)f ||
291 (message[i-1].m_flag & MKILL));
292 mark(i, f);
293 break;
295 case TDASH:
296 if (beg == 0) {
297 i = valdot;
298 do {
299 if (mb.mb_threaded) {
300 mx = prev_in_thread(
301 &message[i-1]);
302 i = mx ? mx-message+1 : 0;
303 } else
304 i--;
305 if (i <= 0) {
306 printf(catgets(catd, CATSET,
307 115,
308 "Referencing before 1\n"));
309 markall_ret(-1)
311 } while ((message[i-1].m_flag & MHIDDEN) ||
312 (message[i-1].m_flag & MDELETED)
313 != (unsigned)f ||
314 (message[i-1].m_flag & MKILL));
315 mark(i, f);
317 break;
319 case TSTRING:
320 if (beg != 0) {
321 printf(catgets(catd, CATSET, 116,
322 "Non-numeric second argument\n"));
323 markall_ret(-1)
325 other++;
326 if (lexstring[0] == ':') {
327 colresult = evalcol(lexstring[1]);
328 if (colresult == 0) {
329 printf(catgets(catd, CATSET, 117,
330 "Unknown colon modifier \"%s\"\n"),
331 lexstring);
332 markall_ret(-1)
334 colmod |= colresult;
336 else
337 np = add_to_namelist(&namelist, &nmlsize,
338 np, savestr(lexstring));
339 break;
341 case TOPEN:
342 if (imap_search(lexstring, f) == STOP)
343 markall_ret(-1)
344 topen++;
345 break;
347 case TDOLLAR:
348 case TUP:
349 case TDOT:
350 case TSEMI:
351 lexnumber = metamess(lexstring[0], f);
352 if (lexnumber == -1)
353 markall_ret(-1)
354 goto number;
356 case TBACK:
357 tback = 1;
358 for (i = 1; i <= msgCount; i++) {
359 if ((message[i-1].m_flag & MHIDDEN) ||
360 (message[i-1].m_flag&MDELETED)
361 != (unsigned)f)
362 continue;
363 if (message[i-1].m_flag&MOLDMARK)
364 mark(i, f);
366 break;
368 case TSTAR:
369 if (other) {
370 printf(catgets(catd, CATSET, 118,
371 "Can't mix \"*\" with anything\n"));
372 markall_ret(-1)
374 star++;
375 break;
377 case TCOMMA:
378 if (mb.mb_type == MB_IMAP && gotheaders++ == 0)
379 imap_getheaders(1, msgCount);
380 if (id == NULL && (cp = hfield1("in-reply-to", dot))
381 != NULL) {
382 id = savestr(cp);
383 idfield = ID_IN_REPLY_TO;
385 if (id == NULL && (cp = hfield1("references", dot))
386 != NULL) {
387 struct name *np;
388 if ((np = extract(cp, GREF)) != NULL) {
389 while (np->n_flink != NULL)
390 np = np->n_flink;
391 id = savestr(np->n_name);
392 idfield = ID_REFERENCES;
395 if (id == NULL) {
396 printf(catgets(catd, CATSET, 227,
397 "Cannot determine parent Message-ID of the current message\n"));
398 markall_ret(-1)
400 break;
402 case TERROR:
403 markall_ret(-1)
405 threadflag = 0;
406 tok = scan(&bufp);
408 lastcolmod = colmod;
409 np = add_to_namelist(&namelist, &nmlsize, np, NULL);
410 np--;
411 mc = 0;
412 if (star) {
413 for (i = 0; i < msgCount; i++) {
414 if (!(message[i].m_flag & MHIDDEN) &&
415 (message[i].m_flag & MDELETED) ==
416 (unsigned)f) {
417 mark(i+1, f);
418 mc++;
421 if (mc == 0) {
422 if (!inhook)
423 printf(catgets(catd, CATSET, 119,
424 "No applicable messages.\n"));
425 markall_ret(-1)
427 markall_ret(0)
430 if ((topen || tback) && mc == 0) {
431 for (i = 0; i < msgCount; i++)
432 if (message[i].m_flag & MMARK)
433 mc++;
434 if (mc == 0) {
435 if (!inhook)
436 printf(tback ?
437 "No previously marked messages.\n" :
438 "No messages satisfy (criteria).\n");
439 markall_ret(-1)
444 * If no numbers were given, mark all of the messages,
445 * so that we can unmark any whose sender was not selected
446 * if any user names were given.
449 if ((np > namelist || colmod != 0 || id) && mc == 0)
450 for (i = 1; i <= msgCount; i++) {
451 if (!(message[i-1].m_flag & MHIDDEN) &&
452 (message[i-1].m_flag & MDELETED) ==
453 (unsigned)f)
454 mark(i, f);
458 * If any names were given, go through and eliminate any
459 * messages whose senders were not requested.
462 if (np > namelist || id) {
463 int allnet = value("allnet") != NULL;
465 if (mb.mb_type == MB_IMAP && gotheaders++ == 0)
466 imap_getheaders(1, msgCount);
467 for (i = 1; i <= msgCount; i++) {
468 mc = 0;
469 if (np > namelist) {
470 for (nq = &namelist[0]; *nq != NULL; nq++) {
471 if (**nq == '/') {
472 if (matchsubj(*nq, i)) {
473 mc++;
474 break;
477 else {
478 if (matchsender(*nq, i,
479 allnet)) {
480 mc++;
481 break;
486 if (mc == 0 && id && matchmid(id, idfield, i))
487 mc++;
488 if (mc == 0)
489 unmark(i);
493 * Make sure we got some decent messages.
496 mc = 0;
497 for (i = 1; i <= msgCount; i++)
498 if (message[i-1].m_flag & MMARK) {
499 mc++;
500 break;
502 if (mc == 0) {
503 if (!inhook && np > namelist) {
504 printf(catgets(catd, CATSET, 120,
505 "No applicable messages from {%s"),
506 namelist[0]);
507 for (nq = &namelist[1]; *nq != NULL; nq++)
508 printf(catgets(catd, CATSET, 121,
509 ", %s"), *nq);
510 printf(catgets(catd, CATSET, 122, "}\n"));
511 } else if (id) {
512 printf(catgets(catd, CATSET, 227,
513 "Parent message not found\n"));
515 markall_ret(-1)
520 * If any colon modifiers were given, go through and
521 * unmark any messages which do not satisfy the modifiers.
524 if (colmod != 0) {
525 for (i = 1; i <= msgCount; i++) {
526 struct coltab *colp;
528 mp = &message[i - 1];
529 for (colp = &coltab[0]; colp->co_char; colp++)
530 if (colp->co_bit & colmod)
531 if ((mp->m_flag & colp->co_mask)
532 != (unsigned)colp->co_equal)
533 unmark(i);
536 for (mp = &message[0]; mp < &message[msgCount]; mp++)
537 if (mp->m_flag & MMARK)
538 break;
539 if (mp >= &message[msgCount]) {
540 struct coltab *colp;
542 if (!inhook) {
543 printf(catgets(catd, CATSET, 123,
544 "No messages satisfy"));
545 for (colp = &coltab[0]; colp->co_char; colp++)
546 if (colp->co_bit & colmod)
547 printf(" :%c", colp->co_char);
548 printf("\n");
550 markall_ret(-1)
553 markall_ret(0)
554 out:
555 free(namelist);
556 return retval;
560 * Turn the character after a colon modifier into a bit
561 * value.
563 static int
564 evalcol(int col)
566 struct coltab *colp;
568 if (col == 0)
569 return(lastcolmod);
570 for (colp = &coltab[0]; colp->co_char; colp++)
571 if (colp->co_char == col)
572 return(colp->co_bit);
573 return(0);
577 * Check the passed message number for legality and proper flags.
578 * If f is MDELETED, then either kind will do. Otherwise, the message
579 * has to be undeleted.
581 static int
582 check(int mesg, int f)
584 struct message *mp;
586 if (mesg < 1 || mesg > msgCount) {
587 printf(catgets(catd, CATSET, 124,
588 "%d: Invalid message number\n"), mesg);
589 return(-1);
591 mp = &message[mesg-1];
592 if (mp->m_flag & MHIDDEN || (f != MDELETED &&
593 (mp->m_flag & MDELETED) != 0)) {
594 printf(catgets(catd, CATSET, 125,
595 "%d: Inappropriate message\n"), mesg);
596 return(-1);
598 return(0);
602 * Scan out the list of string arguments, shell style
603 * for a RAWLIST.
606 getrawlist(const char *line, size_t linesize, char **argv, int argc,
607 int echolist)
609 char c, *cp2, quotec;
610 const char *cp;
611 int argn;
612 char *linebuf;
614 argn = 0;
615 cp = line;
616 linebuf = ac_alloc(linesize + 1);
617 for (;;) {
618 for (; blankchar(*cp & 0377); cp++);
619 if (*cp == '\0')
620 break;
621 if (argn >= argc - 1) {
622 printf(catgets(catd, CATSET, 126,
623 "Too many elements in the list; excess discarded.\n"));
624 break;
626 cp2 = linebuf;
627 quotec = '\0';
628 while ((c = *cp) != '\0') {
629 cp++;
630 if (quotec != '\0') {
631 if (c == quotec) {
632 quotec = '\0';
633 if (echolist)
634 *cp2++ = c;
635 } else if (c == '\\')
636 switch (c = *cp++) {
637 case '\0':
638 *cp2++ = '\\';
639 cp--;
640 break;
642 case '0': case '1': case '2': case '3':
643 case '4': case '5': case '6': case '7':
644 c -= '0';
645 if (*cp >= '0' && *cp <= '7')
646 c = c * 8 + *cp++ - '0';
647 if (*cp >= '0' && *cp <= '7')
648 c = c * 8 + *cp++ - '0';
649 *cp2++ = c;
650 break;
651 case 'b':
652 *cp2++ = '\b';
653 break;
654 case 'f':
655 *cp2++ = '\f';
656 break;
657 case 'n':
658 *cp2++ = '\n';
659 break;
660 case 'r':
661 *cp2++ = '\r';
662 break;
663 case 't':
664 *cp2++ = '\t';
665 break;
666 case 'v':
667 *cp2++ = '\v';
668 break;
670 default:
671 if (cp[-1]!=quotec || echolist)
672 *cp2++ = '\\';
673 *cp2++ = c;
675 /*else if (c == '^') {
676 c = *cp++;
677 if (c == '?')
678 *cp2++ = '\177';
679 /\* null doesn't show up anyway *\/
680 else if ((c >= 'A' && c <= '_') ||
681 (c >= 'a' && c <= 'z'))
682 *cp2++ = c & 037;
683 else {
684 *cp2++ = '^';
685 cp--;
687 }*/ else
688 *cp2++ = c;
689 } else if (c == '"' || c == '\'') {
690 if (echolist)
691 *cp2++ = c;
692 quotec = c;
693 } else if (c == '\\' && !echolist) {
694 if (*cp)
695 *cp2++ = *cp++;
696 else
697 *cp2++ = c;
698 } else if (blankchar(c & 0377))
699 break;
700 else
701 *cp2++ = c;
703 *cp2 = '\0';
704 argv[argn++] = savestr(linebuf);
706 argv[argn] = NULL;
707 ac_free(linebuf);
708 return argn;
712 * scan out a single lexical item and return its token number,
713 * updating the string pointer passed **p. Also, store the value
714 * of the number or string scanned in lexnumber or lexstring as
715 * appropriate. In any event, store the scanned `thing' in lexstring.
718 static struct lex {
719 char l_char;
720 enum ltoken l_token;
721 } singles[] = {
722 { '$', TDOLLAR },
723 { '.', TDOT },
724 { '^', TUP },
725 { '*', TSTAR },
726 { '-', TDASH },
727 { '+', TPLUS },
728 { '(', TOPEN },
729 { ')', TCLOSE },
730 { ',', TCOMMA },
731 { ';', TSEMI },
732 { '`', TBACK },
733 { 0, 0 }
736 static int
737 scan(char **sp)
739 char *cp, *cp2;
740 int c, level, inquote;
741 struct lex *lp;
742 int quotec;
744 if (regretp >= 0) {
745 strncpy(lexstring, string_stack[regretp], STRINGLEN);
746 lexstring[STRINGLEN-1]='\0';
747 lexnumber = numberstack[regretp];
748 return(regretstack[regretp--]);
750 cp = *sp;
751 cp2 = lexstring;
752 c = *cp++;
755 * strip away leading white space.
758 while (blankchar(c))
759 c = *cp++;
762 * If no characters remain, we are at end of line,
763 * so report that.
766 if (c == '\0') {
767 *sp = --cp;
768 return(TEOL);
772 * Select members of a message thread.
774 if (c == '&') {
775 threadflag = 1;
776 if (*cp == '\0' || spacechar(*cp&0377)) {
777 lexstring[0] = '.';
778 lexstring[1] = '\0';
779 *sp = cp;
780 return TDOT;
782 c = *cp++;
786 * If the leading character is a digit, scan
787 * the number and convert it on the fly.
788 * Return TNUMBER when done.
791 if (digitchar(c)) {
792 lexnumber = 0;
793 while (digitchar(c)) {
794 lexnumber = lexnumber*10 + c - '0';
795 *cp2++ = c;
796 c = *cp++;
798 *cp2 = '\0';
799 *sp = --cp;
800 return(TNUMBER);
804 * An IMAP SEARCH list. Note that TOPEN has always been included
805 * in singles[] in Mail and mailx. Thus although there is no formal
806 * definition for (LIST) lists, they do not collide with historical
807 * practice because a subject string (LIST) could never been matched
808 * this way.
811 if (c == '(') {
812 level = 1;
813 inquote = 0;
814 *cp2++ = c;
815 do {
816 if ((c = *cp++&0377) == '\0') {
817 mtop: fprintf(stderr, "Missing \")\".\n");
818 return TERROR;
820 if (inquote && c == '\\') {
821 *cp2++ = c;
822 c = *cp++&0377;
823 if (c == '\0')
824 goto mtop;
825 } else if (c == '"')
826 inquote = !inquote;
827 else if (inquote)
828 /*EMPTY*/;
829 else if (c == '(')
830 level++;
831 else if (c == ')')
832 level--;
833 else if (spacechar(c)) {
835 * Replace unquoted whitespace by single
836 * space characters, to make the string
837 * IMAP SEARCH conformant.
839 c = ' ';
840 if (cp2[-1] == ' ')
841 cp2--;
843 *cp2++ = c;
844 } while (c != ')' || level > 0);
845 *cp2 = '\0';
846 *sp = cp;
847 return TOPEN;
851 * Check for single character tokens; return such
852 * if found.
855 for (lp = &singles[0]; lp->l_char != 0; lp++)
856 if (c == lp->l_char) {
857 lexstring[0] = c;
858 lexstring[1] = '\0';
859 *sp = cp;
860 return(lp->l_token);
864 * We've got a string! Copy all the characters
865 * of the string into lexstring, until we see
866 * a null, space, or tab.
867 * If the lead character is a " or ', save it
868 * and scan until you get another.
871 quotec = 0;
872 if (c == '\'' || c == '"') {
873 quotec = c;
874 c = *cp++;
876 while (c != '\0') {
877 if (quotec == 0 && c == '\\' && *cp)
878 c = *cp++;
879 if (c == quotec) {
880 cp++;
881 break;
883 if (quotec == 0 && blankchar(c))
884 break;
885 if ((size_t)(cp2 - lexstring) < (size_t)STRINGLEN - 1)
886 *cp2++ = c;
887 c = *cp++;
889 if (quotec && c == 0) {
890 fprintf(stderr, catgets(catd, CATSET, 127,
891 "Missing %c\n"), quotec);
892 return TERROR;
894 *sp = --cp;
895 *cp2 = '\0';
896 return(TSTRING);
900 * Unscan the named token by pushing it onto the regret stack.
902 static void
903 regret(int token)
905 if (++regretp >= REGDEP)
906 panic(catgets(catd, CATSET, 128, "Too many regrets"));
907 regretstack[regretp] = token;
908 lexstring[STRINGLEN-1] = '\0';
909 string_stack[regretp] = savestr(lexstring);
910 numberstack[regretp] = lexnumber;
914 * Reset all the scanner global variables.
916 static void
917 scaninit(void)
919 regretp = -1;
920 threadflag = 0;
924 * Find the first message whose flags & m == f and return
925 * its message number.
927 int
928 first(int f, int m)
930 struct message *mp;
932 if (msgCount == 0)
933 return 0;
934 f &= MDELETED;
935 m &= MDELETED;
936 for (mp = dot; mb.mb_threaded ? mp != NULL : mp < &message[msgCount];
937 mb.mb_threaded ? mp = next_in_thread(mp) : mp++) {
938 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & m) == (unsigned)f)
939 return mp - message + 1;
941 if (dot > &message[0]) {
942 for (mp = dot-1; mb.mb_threaded ?
943 mp != NULL : mp >= &message[0];
944 mb.mb_threaded ?
945 mp = prev_in_thread(mp) : mp--) {
946 if (! (mp->m_flag & MHIDDEN) &&
947 (mp->m_flag & m) == (unsigned)f)
948 return mp - message + 1;
951 return 0;
955 * See if the passed name sent the passed message number. Return true
956 * if so.
958 static int
959 matchsender(char *str, int mesg, int allnet)
961 if (allnet) {
962 char *cp = nameof(&message[mesg - 1], 0);
964 do {
965 if ((*cp == '@' || *cp == '\0') &&
966 (*str == '@' || *str == '\0'))
967 return 1;
968 if (*cp != *str)
969 break;
970 } while (cp++, *str++ != '\0');
971 return 0;
973 return !strcmp(str, (value("showname") ? realname : skin)
974 (name1(&message[mesg - 1], 0)));
977 static int
978 matchmid(char *id, enum idfield idfield, int mesg)
980 struct name *np;
981 char *cp;
983 if ((cp = hfield1("message-id", &message[mesg - 1])) != NULL) {
984 switch (idfield) {
985 case ID_REFERENCES:
986 return msgidcmp(id, cp) == 0;
987 case ID_IN_REPLY_TO:
988 if ((np = extract(id, GREF)) != NULL)
989 do {
990 if (msgidcmp(np->n_name, cp) == 0)
991 return 1;
992 } while ((np = np->n_flink) != NULL);
993 break;
996 return 0;
1000 * See if the given string matches inside the subject field of the
1001 * given message. For the purpose of the scan, we ignore case differences.
1002 * If it does, return true. The string search argument is assumed to
1003 * have the form "/search-string." If it is of the form "/," we use the
1004 * previous search string.
1007 static char lastscan[128];
1009 static int
1010 matchsubj(char *str, int mesg)
1012 struct message *mp;
1013 char *cp, *cp2;
1014 struct str in, out;
1015 int i;
1017 str++;
1018 if (strlen(str) == 0) {
1019 str = lastscan;
1020 } else {
1021 strncpy(lastscan, str, sizeof lastscan);
1022 lastscan[sizeof lastscan - 1]='\0';
1024 mp = &message[mesg-1];
1027 * Now look, ignoring case, for the word in the string.
1030 if (value("searchheaders") && (cp = strchr(str, ':'))) {
1031 *cp++ = '\0';
1032 cp2 = hfieldX(str, mp);
1033 cp[-1] = ':';
1034 str = cp;
1035 } else {
1036 cp = str;
1037 cp2 = hfield1("subject", mp);
1039 if (cp2 == NULL)
1040 return(0);
1041 in.s = cp2;
1042 in.l = strlen(cp2);
1043 mime_fromhdr(&in, &out, TD_ICONV);
1044 i = substr(out.s, cp);
1045 free(out.s);
1046 return i;
1050 * Mark the named message by setting its mark bit.
1052 void
1053 mark(int mesg, int f)
1055 struct message *mp;
1056 int i;
1058 i = mesg;
1059 if (i < 1 || i > msgCount)
1060 panic(catgets(catd, CATSET, 129, "Bad message number to mark"));
1061 if (mb.mb_threaded == 1 && threadflag) {
1062 if ((message[i-1].m_flag & MHIDDEN) == 0) {
1063 if (f == MDELETED ||
1064 (message[i-1].m_flag&MDELETED) == 0)
1065 message[i-1].m_flag |= MMARK;
1067 if (message[i-1].m_child) {
1068 mp = message[i-1].m_child;
1069 mark(mp-message+1, f);
1070 for (mp = mp->m_younger; mp; mp = mp->m_younger)
1071 mark(mp-message+1, f);
1073 } else
1074 message[i-1].m_flag |= MMARK;
1078 * Unmark the named message.
1080 static void
1081 unmark(int mesg)
1083 int i;
1085 i = mesg;
1086 if (i < 1 || i > msgCount)
1087 panic(catgets(catd, CATSET, 130,
1088 "Bad message number to unmark"));
1089 message[i-1].m_flag &= ~MMARK;
1093 * Return the message number corresponding to the passed meta character.
1095 static int
1096 metamess(int meta, int f)
1098 int c, m;
1099 struct message *mp;
1101 c = meta;
1102 switch (c) {
1103 case '^':
1105 * First 'good' message left.
1107 mp = mb.mb_threaded ? threadroot : &message[0];
1108 while (mp < &message[msgCount]) {
1109 if (!(mp->m_flag & (MHIDDEN|MKILL)) &&
1110 (mp->m_flag & MDELETED) == (unsigned)f)
1111 return(mp - &message[0] + 1);
1112 if (mb.mb_threaded) {
1113 mp = next_in_thread(mp);
1114 if (mp == NULL)
1115 break;
1116 } else
1117 mp++;
1119 if (!inhook)
1120 printf(catgets(catd, CATSET, 131,
1121 "No applicable messages\n"));
1122 return(-1);
1124 case '$':
1126 * Last 'good message left.
1128 mp = mb.mb_threaded ? this_in_thread(threadroot, -1) :
1129 &message[msgCount-1];
1130 while (mp >= &message[0]) {
1131 if (!(mp->m_flag & (MHIDDEN|MKILL)) &&
1132 (mp->m_flag & MDELETED) == (unsigned)f)
1133 return(mp - &message[0] + 1);
1134 if (mb.mb_threaded) {
1135 mp = prev_in_thread(mp);
1136 if (mp == NULL)
1137 break;
1138 } else
1139 mp--;
1141 if (!inhook)
1142 printf(catgets(catd, CATSET, 132,
1143 "No applicable messages\n"));
1144 return(-1);
1146 case '.':
1148 * Current message.
1150 m = dot - &message[0] + 1;
1151 if ((dot->m_flag & MHIDDEN) ||
1152 (dot->m_flag & MDELETED) != (unsigned)f) {
1153 printf(catgets(catd, CATSET, 133,
1154 "%d: Inappropriate message\n"), m);
1155 return(-1);
1157 return(m);
1159 case ';':
1161 * Previously current message.
1163 if (prevdot == NULL) {
1164 printf(catgets(catd, CATSET, 228,
1165 "No previously current message\n"));
1166 return(-1);
1168 m = prevdot - &message[0] + 1;
1169 if ((prevdot->m_flag & MHIDDEN) ||
1170 (prevdot->m_flag & MDELETED) != (unsigned)f) {
1171 printf(catgets(catd, CATSET, 133,
1172 "%d: Inappropriate message\n"), m);
1173 return(-1);
1175 return(m);
1177 default:
1178 printf(catgets(catd, CATSET, 134,
1179 "Unknown metachar (%c)\n"), c);
1180 return(-1);