Localize cmdtab[] in lex.c..
[s-mailx.git] / list.c
blob57bfd941a2a13ee6b5b5cfa55e0da288df4f17f6
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 "nail.h"
42 enum idfield {
43 ID_REFERENCES,
44 ID_IN_REPLY_TO
47 static char **add_to_namelist(char ***namelist, size_t *nmlsize,
48 char **np, char *string);
49 static int markall(char *buf, int f);
50 static int evalcol(int col);
51 static int check(int mesg, int f);
52 static int scan(char **sp);
53 static void regret(int token);
54 static void scaninit(void);
55 static int matchsender(char *str, int mesg, int allnet);
56 static int matchmid(char *id, enum idfield idfield, int mesg);
57 static int matchsubj(char *str, int mesg);
58 static void unmark(int mesg);
59 static int metamess(int meta, int f);
61 static size_t STRINGLEN;
63 static int lexnumber; /* Number of TNUMBER from scan() */
64 static char *lexstring; /* String from TSTRING, scan() */
65 static int regretp; /* Pointer to TOS of regret tokens */
66 static int regretstack[REGDEP]; /* Stack of regretted tokens */
67 static char *string_stack[REGDEP]; /* Stack of regretted strings */
68 static int numberstack[REGDEP]; /* Stack of regretted numbers */
69 static int threadflag; /* mark entire threads */
72 * Convert the user string of message numbers and
73 * store the numbers into vector.
75 * Returns the count of messages picked up or -1 on error.
77 int
78 getmsglist(char *buf, int *vector, int flags)
80 int *ip;
81 struct message *mp;
82 int mc;
84 msglist_is_single = FAL0;
85 if (msgCount == 0) {
86 *vector = 0;
87 return 0;
90 msglist_is_single = TRU1;
91 if (markall(buf, flags) < 0)
92 return(-1);
93 ip = vector;
94 if (inhook & 2) {
95 mc = 0;
96 for (mp = &message[0]; mp < &message[msgCount]; mp++)
97 if (mp->m_flag & MMARK) {
98 if ((mp->m_flag & MNEWEST) == 0)
99 unmark(mp - &message[0] + 1);
100 else
101 mc++;
103 if (mc == 0)
104 return -1;
106 if (mb.mb_threaded == 0) {
107 for (mp = &message[0]; mp < &message[msgCount]; mp++)
108 if (mp->m_flag & MMARK)
109 *ip++ = mp - &message[0] + 1;
110 } else {
111 for (mp = threadroot; mp; mp = next_in_thread(mp))
112 if (mp->m_flag & MMARK)
113 *ip++ = mp - &message[0] + 1;
115 *ip = 0;
116 if ((size_t)(ip - vector) != 1)
117 msglist_is_single = FAL0;
118 return(ip - vector);
122 * Mark all messages that the user wanted from the command
123 * line in the message structure. Return 0 on success, -1
124 * on error.
128 * Bit values for colon modifiers.
131 #define CMNEW 01 /* New messages */
132 #define CMOLD 02 /* Old messages */
133 #define CMUNREAD 04 /* Unread messages */
134 #define CMDELETED 010 /* Deleted messages */
135 #define CMREAD 020 /* Read messages */
136 #define CMFLAG 040 /* Flagged messages */
137 #define CMANSWER 0100 /* Answered messages */
138 #define CMDRAFT 0200 /* Draft messages */
139 #define CMSPAM 0400 /* Spam messages */
142 * The following table describes the letters which can follow
143 * the colon and gives the corresponding modifier bit.
146 static struct coltab {
147 char co_char; /* What to find past : */
148 int co_bit; /* Associated modifier bit */
149 int co_mask; /* m_status bits to mask */
150 int co_equal; /* ... must equal this */
151 } coltab[] = {
152 { 'n', CMNEW, MNEW, MNEW },
153 { 'o', CMOLD, MNEW, 0 },
154 { 'u', CMUNREAD, MREAD, 0 },
155 { 'd', CMDELETED, MDELETED, MDELETED },
156 { 'r', CMREAD, MREAD, MREAD },
157 { 'f', CMFLAG, MFLAGGED, MFLAGGED },
158 { 'a', CMANSWER, MANSWERED, MANSWERED },
159 { 't', CMDRAFT, MDRAFTED, MDRAFTED },
160 { 's', CMSPAM, MSPAM, MSPAM },
161 { 0, 0, 0, 0 }
164 static int lastcolmod;
166 static char **
167 add_to_namelist(char ***namelist, size_t *nmlsize, char **np, char *string)
169 size_t idx;
171 if ((idx = np - *namelist) >= *nmlsize) {
172 *namelist = srealloc(*namelist, (*nmlsize += 8) * sizeof *np);
173 np = &(*namelist)[idx];
175 *np++ = string;
176 return np;
179 static int
180 markall(char *buf, int f)
182 #define markall_ret(i) do { retval = i; goto out; } while (0);
183 char **np, **nq;
184 int i, retval;
185 struct message *mp, *mx;
186 char **namelist, *bufp, *id = NULL, *cp;
187 int tok, beg, mc, star, other, valdot, colmod, colresult, topen, tback;
188 size_t nmlsize;
189 enum idfield idfield = ID_REFERENCES;
190 #ifdef HAVE_IMAP
191 int gotheaders;
192 #endif
194 lexstring = ac_alloc(STRINGLEN = 2 * strlen(buf) + 1);
195 valdot = dot - &message[0] + 1;
196 colmod = 0;
197 for (i = 1; i <= msgCount; i++) {
198 message[i-1].m_flag &= ~MOLDMARK;
199 if (message[i-1].m_flag & MMARK)
200 message[i-1].m_flag |= MOLDMARK;
201 unmark(i);
203 bufp = buf;
204 mc = 0;
205 namelist = smalloc((nmlsize = 8) * sizeof *namelist);
206 np = &namelist[0];
207 scaninit();
208 tok = scan(&bufp);
209 star = 0;
210 other = 0;
211 beg = 0;
212 topen = 0;
213 tback = 0;
214 #ifdef HAVE_IMAP
215 gotheaders = 0;
216 #endif
218 while (tok != TEOL) {
219 switch (tok) {
220 case TNUMBER:
221 number:
222 if (star) {
223 printf(tr(112, "No numbers mixed with *\n"));
224 markall_ret(-1)
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 ||
235 (message[i-1].m_flag &
236 MDELETED) == 0))
237 mark(i, f);
238 if (mb.mb_threaded) {
239 if (i == lexnumber)
240 break;
241 mx = next_in_thread(&message[i-1]);
242 if (mx == NULL)
243 markall_ret(-1)
244 i = mx-message+1;
245 } else
246 i++;
248 beg = 0;
249 break;
251 beg = lexnumber;
252 if (check(beg, f))
253 markall_ret(-1)
254 tok = scan(&bufp);
255 regret(tok);
256 if (tok != TDASH) {
257 mark(beg, f);
258 beg = 0;
260 break;
262 case TPLUS:
263 msglist_is_single = FAL0;
264 if (beg != 0) {
265 printf(tr(113,
266 "Non-numeric second argument\n"));
267 markall_ret(-1)
269 i = valdot;
270 do {
271 if (mb.mb_threaded) {
272 mx = next_in_thread(&message[i-1]);
273 i = mx ? mx-message+1 : msgCount+1;
274 } else
275 i++;
276 if (i > msgCount) {
277 printf(tr(114,
278 "Referencing beyond EOF\n"));
279 markall_ret(-1)
281 } while (message[i-1].m_flag == MHIDDEN ||
282 (message[i-1].m_flag & MDELETED) !=
283 (unsigned)f);
284 mark(i, f);
285 break;
287 case TDASH:
288 msglist_is_single = FAL0;
289 if (beg == 0) {
290 i = valdot;
291 do {
292 if (mb.mb_threaded) {
293 mx = prev_in_thread(
294 &message[i-1]);
295 i = mx ? mx-message+1 : 0;
296 } else
297 i--;
298 if (i <= 0) {
299 printf(tr(115,
300 "Referencing before 1\n"));
301 markall_ret(-1)
303 } while ((message[i-1].m_flag & MHIDDEN) ||
304 (message[i-1].m_flag & MDELETED)
305 != (unsigned)f);
306 mark(i, f);
308 break;
310 case TSTRING:
311 msglist_is_single = FAL0;
312 if (beg != 0) {
313 printf(tr(116,
314 "Non-numeric second argument\n"));
315 markall_ret(-1)
317 other++;
318 if (lexstring[0] == ':') {
319 colresult = evalcol(lexstring[1]);
320 if (colresult == 0) {
321 printf(tr(117,
322 "Unknown colon modifier \"%s\"\n"),
323 lexstring);
324 markall_ret(-1)
326 colmod |= colresult;
328 else
329 np = add_to_namelist(&namelist, &nmlsize,
330 np, savestr(lexstring));
331 break;
333 case TOPEN:
334 msglist_is_single = FAL0;
335 if (imap_search(lexstring, f) == STOP)
336 markall_ret(-1)
337 topen++;
338 break;
340 case TDOLLAR:
341 case TUP:
342 case TDOT:
343 case TSEMI:
344 msglist_is_single = FAL0;
345 lexnumber = metamess(lexstring[0], f);
346 if (lexnumber == -1)
347 markall_ret(-1)
348 goto number;
350 case TBACK:
351 msglist_is_single = FAL0;
352 tback = 1;
353 for (i = 1; i <= msgCount; i++) {
354 if ((message[i-1].m_flag & MHIDDEN) ||
355 (message[i-1].m_flag&MDELETED)
356 != (unsigned)f)
357 continue;
358 if (message[i-1].m_flag&MOLDMARK)
359 mark(i, f);
361 break;
363 case TSTAR:
364 msglist_is_single = FAL0;
365 if (other) {
366 printf(tr(118,
367 "Can't mix \"*\" with anything\n"));
368 markall_ret(-1)
370 star++;
371 break;
373 case TCOMMA:
374 msglist_is_single = FAL0;
375 #ifdef HAVE_IMAP
376 if (mb.mb_type == MB_IMAP && gotheaders++ == 0)
377 imap_getheaders(1, msgCount);
378 #endif
379 if (id == NULL && (cp = hfield1("in-reply-to", dot))
380 != NULL) {
381 id = savestr(cp);
382 idfield = ID_IN_REPLY_TO;
384 if (id == NULL && (cp = hfield1("references", dot))
385 != NULL) {
386 struct name *enp;
387 if ((enp = extract(cp, GREF)) != NULL) {
388 while (enp->n_flink != NULL)
389 enp = enp->n_flink;
390 id = savestr(enp->n_name);
391 idfield = ID_REFERENCES;
394 if (id == NULL) {
395 printf(tr(227,
396 "Cannot determine parent Message-ID of the current message\n"));
397 markall_ret(-1)
399 break;
401 case TERROR:
402 msglist_is_single = FAL0;
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(tr(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 #ifdef HAVE_IMAP
466 if (mb.mb_type == MB_IMAP && gotheaders++ == 0)
467 imap_getheaders(1, msgCount);
468 #endif
469 for (i = 1; i <= msgCount; i++) {
470 mc = 0;
471 if (np > namelist) {
472 for (nq = &namelist[0]; *nq != NULL; nq++) {
473 if (**nq == '/') {
474 if (matchsubj(*nq, i)) {
475 mc++;
476 break;
479 else {
480 if (matchsender(*nq, i,
481 allnet)) {
482 mc++;
483 break;
488 if (mc == 0 && id && matchmid(id, idfield, i))
489 mc++;
490 if (mc == 0)
491 unmark(i);
495 * Make sure we got some decent messages.
498 mc = 0;
499 for (i = 1; i <= msgCount; i++)
500 if (message[i-1].m_flag & MMARK) {
501 mc++;
502 break;
504 if (mc == 0) {
505 if (!inhook && np > namelist) {
506 printf(tr(120,
507 "No applicable messages from {%s"),
508 namelist[0]);
509 for (nq = &namelist[1]; *nq != NULL; nq++)
510 printf(tr(121, ", %s"), *nq);
511 printf(tr(122, "}\n"));
512 } else if (id) {
513 printf(tr(227, "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;
527 bool_t bad = TRU1;
529 mp = &message[i - 1];
530 for (colp = &coltab[0]; colp->co_char; colp++)
531 if ((colp->co_bit & colmod) &&
532 ((mp->m_flag & colp->co_mask)
533 == (unsigned)colp->co_equal))
534 bad = FAL0;
535 if (bad)
536 unmark(i);
538 for (mp = &message[0]; mp < &message[msgCount]; mp++)
539 if (mp->m_flag & MMARK)
540 break;
541 if (mp >= &message[msgCount]) {
542 struct coltab *colp;
544 if (!inhook) {
545 printf(tr(123, "No messages satisfy"));
546 for (colp = &coltab[0]; colp->co_char; colp++)
547 if (colp->co_bit & colmod)
548 printf(" :%c", colp->co_char);
549 printf("\n");
551 markall_ret(-1)
554 markall_ret(0)
555 out:
556 free(namelist);
557 ac_free(lexstring);
558 return retval;
559 #undef markall_ret
563 * Turn the character after a colon modifier into a bit
564 * value.
566 static int
567 evalcol(int col)
569 struct coltab *colp;
571 if (col == 0)
572 return(lastcolmod);
573 for (colp = &coltab[0]; colp->co_char; colp++)
574 if (colp->co_char == col)
575 return(colp->co_bit);
576 return(0);
580 * Check the passed message number for legality and proper flags.
581 * If f is MDELETED, then either kind will do. Otherwise, the message
582 * has to be undeleted.
584 static int
585 check(int mesg, int f)
587 struct message *mp;
589 if (mesg < 1 || mesg > msgCount) {
590 printf(tr(124, "%d: Invalid message number\n"), mesg);
591 return(-1);
593 mp = &message[mesg-1];
594 if (mp->m_flag & MHIDDEN || (f != MDELETED &&
595 (mp->m_flag & MDELETED) != 0)) {
596 printf(tr(125, "%d: Inappropriate message\n"), mesg);
597 return(-1);
599 return(0);
603 * Scan out the list of string arguments, shell style
604 * for a RAWLIST.
607 getrawlist(const char *line, size_t linesize, char **argv, int argc,
608 int echolist)
610 char c, *cp2, quotec;
611 const char *cp;
612 int argn;
613 char *linebuf;
615 argn = 0;
616 cp = line;
617 linebuf = ac_alloc(linesize + 1);
618 for (;;) {
619 for (; blankchar(*cp & 0377); cp++);
620 if (*cp == '\0')
621 break;
622 if (argn >= argc - 1) {
623 printf(tr(126,
624 "Too many elements in the list; excess discarded.\n"));
625 break;
627 cp2 = linebuf;
628 quotec = '\0';
629 while ((c = *cp) != '\0') {
630 cp++;
631 if (quotec != '\0') {
632 if (c == quotec) {
633 quotec = '\0';
634 if (echolist)
635 *cp2++ = c;
636 } else if (c == '\\')
637 switch (c = *cp++) {
638 case '\0':
639 *cp2++ = '\\';
640 cp--;
641 break;
643 case '0': case '1': case '2': case '3':
644 case '4': case '5': case '6': case '7':
645 c -= '0';
646 if (*cp >= '0' && *cp <= '7')
647 c = c * 8 + *cp++ - '0';
648 if (*cp >= '0' && *cp <= '7')
649 c = c * 8 + *cp++ - '0';
650 *cp2++ = c;
651 break;
652 case 'b':
653 *cp2++ = '\b';
654 break;
655 case 'f':
656 *cp2++ = '\f';
657 break;
658 case 'n':
659 *cp2++ = '\n';
660 break;
661 case 'r':
662 *cp2++ = '\r';
663 break;
664 case 't':
665 *cp2++ = '\t';
666 break;
667 case 'v':
668 *cp2++ = '\v';
669 break;
671 default:
672 if (cp[-1]!=quotec || echolist)
673 *cp2++ = '\\';
674 *cp2++ = c;
676 /*else if (c == '^') {
677 c = *cp++;
678 if (c == '?')
679 *cp2++ = '\177';
680 /\* null doesn't show up anyway *\/
681 else if ((c >= 'A' && c <= '_') ||
682 (c >= 'a' && c <= 'z'))
683 *cp2++ = c & 037;
684 else {
685 *cp2++ = '^';
686 cp--;
688 }*/ else
689 *cp2++ = c;
690 } else if (c == '"' || c == '\'') {
691 if (echolist)
692 *cp2++ = c;
693 quotec = c;
694 } else if (c == '\\' && !echolist) {
695 if (*cp)
696 *cp2++ = *cp++;
697 else
698 *cp2++ = c;
699 } else if (blankchar(c & 0377))
700 break;
701 else
702 *cp2++ = c;
704 *cp2 = '\0';
705 argv[argn++] = savestr(linebuf);
707 argv[argn] = NULL;
708 ac_free(linebuf);
709 return argn;
713 * scan out a single lexical item and return its token number,
714 * updating the string pointer passed **p. Also, store the value
715 * of the number or string scanned in lexnumber or lexstring as
716 * appropriate. In any event, store the scanned `thing' in lexstring.
719 static struct lex {
720 char l_char;
721 enum ltoken l_token;
722 } singles[] = {
723 { '$', TDOLLAR },
724 { '.', TDOT },
725 { '^', TUP },
726 { '*', TSTAR },
727 { '-', TDASH },
728 { '+', TPLUS },
729 { '(', TOPEN },
730 { ')', TCLOSE },
731 { ',', TCOMMA },
732 { ';', TSEMI },
733 { '`', TBACK },
734 { 0, 0 }
737 static int
738 scan(char **sp)
740 char *cp, *cp2;
741 int c, level, inquote;
742 struct lex *lp;
743 int quotec;
745 if (regretp >= 0) {
746 strncpy(lexstring, string_stack[regretp], STRINGLEN);
747 lexstring[STRINGLEN-1]='\0';
748 lexnumber = numberstack[regretp];
749 return(regretstack[regretp--]);
751 cp = *sp;
752 cp2 = lexstring;
753 c = *cp++;
756 * strip away leading white space.
759 while (blankchar(c))
760 c = *cp++;
763 * If no characters remain, we are at end of line,
764 * so report that.
767 if (c == '\0') {
768 *sp = --cp;
769 return(TEOL);
773 * Select members of a message thread.
775 if (c == '&') {
776 threadflag = 1;
777 if (*cp == '\0' || spacechar(*cp&0377)) {
778 lexstring[0] = '.';
779 lexstring[1] = '\0';
780 *sp = cp;
781 return TDOT;
783 c = *cp++;
787 * If the leading character is a digit, scan
788 * the number and convert it on the fly.
789 * Return TNUMBER when done.
792 if (digitchar(c)) {
793 lexnumber = 0;
794 while (digitchar(c)) {
795 lexnumber = lexnumber*10 + c - '0';
796 *cp2++ = c;
797 c = *cp++;
799 *cp2 = '\0';
800 *sp = --cp;
801 return(TNUMBER);
805 * An IMAP SEARCH list. Note that TOPEN has always been included
806 * in singles[] in Mail and mailx. Thus although there is no formal
807 * definition for (LIST) lists, they do not collide with historical
808 * practice because a subject string (LIST) could never been matched
809 * this way.
812 if (c == '(') {
813 level = 1;
814 inquote = 0;
815 *cp2++ = c;
816 do {
817 if ((c = *cp++&0377) == '\0') {
818 mtop: fprintf(stderr, "Missing \")\".\n");
819 return TERROR;
821 if (inquote && c == '\\') {
822 *cp2++ = c;
823 c = *cp++&0377;
824 if (c == '\0')
825 goto mtop;
826 } else if (c == '"')
827 inquote = !inquote;
828 else if (inquote)
829 /*EMPTY*/;
830 else if (c == '(')
831 level++;
832 else if (c == ')')
833 level--;
834 else if (spacechar(c)) {
836 * Replace unquoted whitespace by single
837 * space characters, to make the string
838 * IMAP SEARCH conformant.
840 c = ' ';
841 if (cp2[-1] == ' ')
842 cp2--;
844 *cp2++ = c;
845 } while (c != ')' || level > 0);
846 *cp2 = '\0';
847 *sp = cp;
848 return TOPEN;
852 * Check for single character tokens; return such
853 * if found.
856 for (lp = &singles[0]; lp->l_char != 0; lp++)
857 if (c == lp->l_char) {
858 lexstring[0] = c;
859 lexstring[1] = '\0';
860 *sp = cp;
861 return(lp->l_token);
865 * We've got a string! Copy all the characters
866 * of the string into lexstring, until we see
867 * a null, space, or tab.
868 * If the lead character is a " or ', save it
869 * and scan until you get another.
872 quotec = 0;
873 if (c == '\'' || c == '"') {
874 quotec = c;
875 c = *cp++;
877 while (c != '\0') {
878 if (quotec == 0 && c == '\\' && *cp)
879 c = *cp++;
880 if (c == quotec) {
881 cp++;
882 break;
884 if (quotec == 0 && blankchar(c))
885 break;
886 if ((size_t)(cp2 - lexstring) < (size_t)STRINGLEN - 1)
887 *cp2++ = c;
888 c = *cp++;
890 if (quotec && c == 0) {
891 fprintf(stderr, tr(127, "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(tr(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 } else {
1035 cp = str;
1036 cp2 = hfield1("subject", mp);
1038 if (cp2 == NULL)
1039 return(0);
1040 in.s = cp2;
1041 in.l = strlen(cp2);
1042 mime_fromhdr(&in, &out, TD_ICONV);
1043 i = substr(out.s, cp);
1044 free(out.s);
1045 return i;
1049 * Mark the named message by setting its mark bit.
1051 void
1052 mark(int mesg, int f)
1054 struct message *mp;
1055 int i;
1057 i = mesg;
1058 if (i < 1 || i > msgCount)
1059 panic(tr(129, "Bad message number to mark"));
1060 if (mb.mb_threaded == 1 && threadflag) {
1061 if ((message[i-1].m_flag & MHIDDEN) == 0) {
1062 if (f == MDELETED ||
1063 (message[i-1].m_flag&MDELETED) == 0)
1064 message[i-1].m_flag |= MMARK;
1066 if (message[i-1].m_child) {
1067 mp = message[i-1].m_child;
1068 mark(mp-message+1, f);
1069 for (mp = mp->m_younger; mp; mp = mp->m_younger)
1070 mark(mp-message+1, f);
1072 } else
1073 message[i-1].m_flag |= MMARK;
1077 * Unmark the named message.
1079 static void
1080 unmark(int mesg)
1082 int i;
1084 i = mesg;
1085 if (i < 1 || i > msgCount)
1086 panic(tr(130, "Bad message number to unmark"));
1087 message[i-1].m_flag &= ~MMARK;
1091 * Return the message number corresponding to the passed meta character.
1093 static int
1094 metamess(int meta, int f)
1096 int c, m;
1097 struct message *mp;
1099 c = meta;
1100 switch (c) {
1101 case '^':
1103 * First 'good' message left.
1105 mp = mb.mb_threaded ? threadroot : &message[0];
1106 while (mp < &message[msgCount]) {
1107 if (!(mp->m_flag & MHIDDEN) &&
1108 (mp->m_flag & MDELETED) == (unsigned)f)
1109 return(mp - &message[0] + 1);
1110 if (mb.mb_threaded) {
1111 mp = next_in_thread(mp);
1112 if (mp == NULL)
1113 break;
1114 } else
1115 mp++;
1117 if (!inhook)
1118 printf(tr(131, "No applicable messages\n"));
1119 return(-1);
1121 case '$':
1123 * Last 'good message left.
1125 mp = mb.mb_threaded ? this_in_thread(threadroot, -1) :
1126 &message[msgCount-1];
1127 while (mp >= &message[0]) {
1128 if (!(mp->m_flag & MHIDDEN) &&
1129 (mp->m_flag & MDELETED) == (unsigned)f)
1130 return(mp - &message[0] + 1);
1131 if (mb.mb_threaded) {
1132 mp = prev_in_thread(mp);
1133 if (mp == NULL)
1134 break;
1135 } else
1136 mp--;
1138 if (!inhook)
1139 printf(tr(132, "No applicable messages\n"));
1140 return(-1);
1142 case '.':
1144 * Current message.
1146 m = dot - &message[0] + 1;
1147 if ((dot->m_flag & MHIDDEN) ||
1148 (dot->m_flag & MDELETED) != (unsigned)f) {
1149 printf(tr(133, "%d: Inappropriate message\n"), m);
1150 return(-1);
1152 return(m);
1154 case ';':
1156 * Previously current message.
1158 if (prevdot == NULL) {
1159 printf(tr(228, "No previously current message\n"));
1160 return(-1);
1162 m = prevdot - &message[0] + 1;
1163 if ((prevdot->m_flag & MHIDDEN) ||
1164 (prevdot->m_flag & MDELETED) != (unsigned)f) {
1165 printf(tr(133, "%d: Inappropriate message\n"), m);
1166 return(-1);
1168 return(m);
1170 default:
1171 printf(tr(134, "Unknown metachar (%c)\n"), c);
1172 return(-1);