cmd_tab.h: add the [I]nteractive bit to a few commands
[s-mailx.git] / list.c
blob79a834baad87de85f0fd0d647bdbcd772ff441a7
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 - 2014 Steffen "Daode" Nurpmeso <sdaoden@users.sf.net>.
6 */
7 /*
8 * Copyright (c) 1980, 1993
9 * The Regents of the University of California. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. All advertising materials mentioning features or use of this software
20 * must display the following acknowledgement:
21 * This product includes software developed by the University of
22 * California, Berkeley and its contributors.
23 * 4. Neither the name of the University nor the names of its contributors
24 * may be used to endorse or promote products derived from this software
25 * without specific prior written permission.
27 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
28 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
31 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37 * SUCH DAMAGE.
40 #ifndef HAVE_AMALGAMATION
41 # include "nail.h"
42 #endif
44 enum idfield {
45 ID_REFERENCES,
46 ID_IN_REPLY_TO
49 static char **add_to_namelist(char ***namelist, size_t *nmlsize,
50 char **np, char *string);
51 static int markall(char *buf, int f);
52 static int evalcol(int col);
53 static int check(int mesg, int f);
54 static int scan(char **sp);
55 static void regret(int token);
56 static void scaninit(void);
57 static int matchsender(char *str, int mesg, int allnet);
58 static int matchmid(char *id, enum idfield idfield, int mesg);
59 static int matchsubj(char *str, int mesg);
60 static void unmark(int mesg);
61 static int metamess(int meta, int f);
63 static size_t STRINGLEN;
65 static int lexnumber; /* Number of TNUMBER from scan() */
66 static char *lexstring; /* String from TSTRING, scan() */
67 static int regretp; /* Pointer to TOS of regret tokens */
68 static int regretstack[REGDEP]; /* Stack of regretted tokens */
69 static char *string_stack[REGDEP]; /* Stack of regretted strings */
70 static int numberstack[REGDEP]; /* Stack of regretted numbers */
71 static int threadflag; /* mark entire threads */
74 * Convert the user string of message numbers and
75 * store the numbers into vector.
77 * Returns the count of messages picked up or -1 on error.
79 FL int
80 getmsglist(char *buf, int *vector, int flags)
82 int *ip;
83 struct message *mp;
84 int mc;
86 msglist_is_single = FAL0;
87 if (msgCount == 0) {
88 *vector = 0;
89 return 0;
92 msglist_is_single = TRU1;
93 if (markall(buf, flags) < 0)
94 return(-1);
95 ip = vector;
96 if (inhook & 2) {
97 mc = 0;
98 for (mp = &message[0]; mp < &message[msgCount]; mp++)
99 if (mp->m_flag & MMARK) {
100 if ((mp->m_flag & MNEWEST) == 0)
101 unmark(mp - &message[0] + 1);
102 else
103 mc++;
105 if (mc == 0)
106 return -1;
109 if (mb.mb_threaded == 0) {
110 for (mp = &message[0]; mp < &message[msgCount]; mp++)
111 if (mp->m_flag & MMARK)
112 *ip++ = mp - &message[0] + 1;
113 } else {
114 for (mp = threadroot; mp; mp = next_in_thread(mp))
115 if (mp->m_flag & MMARK)
116 *ip++ = mp - &message[0] + 1;
118 *ip = 0;
119 if ((size_t)(ip - vector) != 1)
120 msglist_is_single = FAL0;
121 return(ip - vector);
125 * Mark all messages that the user wanted from the command
126 * line in the message structure. Return 0 on success, -1
127 * on error.
131 * Bit values for colon modifiers.
134 #define CMNEW 01 /* New messages */
135 #define CMOLD 02 /* Old messages */
136 #define CMUNREAD 04 /* Unread messages */
137 #define CMDELETED 010 /* Deleted messages */
138 #define CMREAD 020 /* Read messages */
139 #define CMFLAG 040 /* Flagged messages */
140 #define CMANSWER 0100 /* Answered messages */
141 #define CMDRAFT 0200 /* Draft messages */
142 #define CMSPAM 0400 /* Spam messages */
145 * The following table describes the letters which can follow
146 * the colon and gives the corresponding modifier bit.
149 static struct coltab {
150 char co_char; /* What to find past : */
151 int co_bit; /* Associated modifier bit */
152 int co_mask; /* m_status bits to mask */
153 int co_equal; /* ... must equal this */
154 } coltab[] = {
155 { 'n', CMNEW, MNEW, MNEW },
156 { 'o', CMOLD, MNEW, 0 },
157 { 'u', CMUNREAD, MREAD, 0 },
158 { 'd', CMDELETED, MDELETED, MDELETED },
159 { 'r', CMREAD, MREAD, MREAD },
160 { 'f', CMFLAG, MFLAGGED, MFLAGGED },
161 { 'a', CMANSWER, MANSWERED, MANSWERED },
162 { 't', CMDRAFT, MDRAFTED, MDRAFTED },
163 { 's', CMSPAM, MSPAM, MSPAM },
164 { 0, 0, 0, 0 }
167 static int lastcolmod;
169 static char **
170 add_to_namelist(char ***namelist, size_t *nmlsize, char **np, char *string)
172 size_t idx;
174 if ((idx = np - *namelist) >= *nmlsize) {
175 *namelist = srealloc(*namelist, (*nmlsize += 8) * sizeof *np);
176 np = &(*namelist)[idx];
178 *np++ = string;
179 return np;
182 static int
183 markall(char *buf, int f)
185 #define markall_ret(i) do { retval = i; goto out; } while (0);
186 char **np, **nq;
187 int i, retval;
188 struct message *mp, *mx;
189 char **namelist, *bufp, *id = NULL, *cp;
190 int tok, beg, mc, star, other, valdot, colmod, colresult, topen, tback;
191 size_t nmlsize;
192 enum idfield idfield = ID_REFERENCES;
193 #ifdef HAVE_IMAP
194 int gotheaders;
195 #endif
197 lexstring = ac_alloc(STRINGLEN = 2 * strlen(buf) + 1);
198 valdot = dot - &message[0] + 1;
199 colmod = 0;
200 for (i = 1; i <= msgCount; i++) {
201 message[i-1].m_flag &= ~MOLDMARK;
202 if (message[i-1].m_flag & MMARK)
203 message[i-1].m_flag |= MOLDMARK;
204 unmark(i);
206 bufp = buf;
207 mc = 0;
208 namelist = smalloc((nmlsize = 8) * sizeof *namelist);
209 np = &namelist[0];
210 scaninit();
211 tok = scan(&bufp);
212 star = 0;
213 other = 0;
214 beg = 0;
215 topen = 0;
216 tback = 0;
217 #ifdef HAVE_IMAP
218 gotheaders = 0;
219 #endif
221 while (tok != TEOL) {
222 switch (tok) {
223 case TNUMBER:
224 number:
225 if (star) {
226 printf(tr(112, "No numbers mixed with *\n"));
227 markall_ret(-1)
229 mc++;
230 other++;
231 if (beg != 0) {
232 if (check(lexnumber, f))
233 markall_ret(-1)
234 i = beg;
235 while (mb.mb_threaded ? 1 : i <= lexnumber) {
236 if (!(message[i-1].m_flag&MHIDDEN) &&
237 (f == MDELETED ||
238 (message[i-1].m_flag &
239 MDELETED) == 0))
240 mark(i, f);
241 if (mb.mb_threaded) {
242 if (i == lexnumber)
243 break;
244 mx = next_in_thread(&message[i-1]);
245 if (mx == NULL)
246 markall_ret(-1)
247 i = mx-message+1;
248 } else
249 i++;
251 beg = 0;
252 break;
254 beg = lexnumber;
255 if (check(beg, f))
256 markall_ret(-1)
257 tok = scan(&bufp);
258 regret(tok);
259 if (tok != TDASH) {
260 mark(beg, f);
261 beg = 0;
263 break;
265 case TPLUS:
266 msglist_is_single = FAL0;
267 if (beg != 0) {
268 printf(tr(113,
269 "Non-numeric second argument\n"));
270 markall_ret(-1)
272 i = valdot;
273 do {
274 if (mb.mb_threaded) {
275 mx = next_in_thread(&message[i-1]);
276 i = mx ? mx-message+1 : msgCount+1;
277 } else
278 i++;
279 if (i > msgCount) {
280 printf(tr(114,
281 "Referencing beyond EOF\n"));
282 markall_ret(-1)
284 } while (message[i-1].m_flag == MHIDDEN ||
285 (message[i-1].m_flag & MDELETED) !=
286 (unsigned)f);
287 mark(i, f);
288 break;
290 case TDASH:
291 msglist_is_single = FAL0;
292 if (beg == 0) {
293 i = valdot;
294 do {
295 if (mb.mb_threaded) {
296 mx = prev_in_thread(
297 &message[i-1]);
298 i = mx ? mx-message+1 : 0;
299 } else
300 i--;
301 if (i <= 0) {
302 printf(tr(115,
303 "Referencing before 1\n"));
304 markall_ret(-1)
306 } while ((message[i-1].m_flag & MHIDDEN) ||
307 (message[i-1].m_flag & MDELETED)
308 != (unsigned)f);
309 mark(i, f);
311 break;
313 case TSTRING:
314 msglist_is_single = FAL0;
315 if (beg != 0) {
316 printf(tr(116,
317 "Non-numeric second argument\n"));
318 markall_ret(-1)
320 other++;
321 if (lexstring[0] == ':') {
322 colresult = evalcol(lexstring[1]);
323 if (colresult == 0) {
324 printf(tr(117,
325 "Unknown colon modifier \"%s\"\n"),
326 lexstring);
327 markall_ret(-1)
329 colmod |= colresult;
331 else
332 np = add_to_namelist(&namelist, &nmlsize,
333 np, savestr(lexstring));
334 break;
336 case TOPEN:
337 msglist_is_single = FAL0;
338 if (imap_search(lexstring, f) == STOP)
339 markall_ret(-1)
340 topen++;
341 break;
343 case TDOLLAR:
344 case TUP:
345 case TDOT:
346 case TSEMI:
347 msglist_is_single = FAL0;
348 lexnumber = metamess(lexstring[0], f);
349 if (lexnumber == -1)
350 markall_ret(-1)
351 goto number;
353 case TBACK:
354 msglist_is_single = FAL0;
355 tback = 1;
356 for (i = 1; i <= msgCount; i++) {
357 if ((message[i-1].m_flag & MHIDDEN) ||
358 (message[i-1].m_flag&MDELETED)
359 != (unsigned)f)
360 continue;
361 if (message[i-1].m_flag&MOLDMARK)
362 mark(i, f);
364 break;
366 case TSTAR:
367 msglist_is_single = FAL0;
368 if (other) {
369 printf(tr(118,
370 "Can't mix \"*\" with anything\n"));
371 markall_ret(-1)
373 star++;
374 break;
376 case TCOMMA:
377 msglist_is_single = FAL0;
378 #ifdef HAVE_IMAP
379 if (mb.mb_type == MB_IMAP && gotheaders++ == 0)
380 imap_getheaders(1, msgCount);
381 #endif
382 if (id == NULL && (cp = hfield1("in-reply-to", dot))
383 != NULL) {
384 id = savestr(cp);
385 idfield = ID_IN_REPLY_TO;
387 if (id == NULL && (cp = hfield1("references", dot))
388 != NULL) {
389 struct name *enp;
390 if ((enp = extract(cp, GREF)) != NULL) {
391 while (enp->n_flink != NULL)
392 enp = enp->n_flink;
393 id = savestr(enp->n_name);
394 idfield = ID_REFERENCES;
397 if (id == NULL) {
398 printf(tr(227,
399 "Cannot determine parent Message-ID of the current message\n"));
400 markall_ret(-1)
402 break;
404 case TERROR:
405 msglist_is_single = FAL0;
406 markall_ret(-1)
408 threadflag = 0;
409 tok = scan(&bufp);
411 lastcolmod = colmod;
412 np = add_to_namelist(&namelist, &nmlsize, np, NULL);
413 np--;
414 mc = 0;
415 if (star) {
416 for (i = 0; i < msgCount; i++) {
417 if (!(message[i].m_flag & MHIDDEN) &&
418 (message[i].m_flag & MDELETED) ==
419 (unsigned)f) {
420 mark(i+1, f);
421 mc++;
424 if (mc == 0) {
425 if (!inhook)
426 printf(tr(119,
427 "No applicable messages.\n"));
428 markall_ret(-1)
430 markall_ret(0)
433 if ((topen || tback) && mc == 0) {
434 for (i = 0; i < msgCount; i++)
435 if (message[i].m_flag & MMARK)
436 mc++;
437 if (mc == 0) {
438 if (!inhook)
439 printf(tback ?
440 "No previously marked messages.\n" :
441 "No messages satisfy (criteria).\n");
442 markall_ret(-1)
447 * If no numbers were given, mark all of the messages,
448 * so that we can unmark any whose sender was not selected
449 * if any user names were given.
452 if ((np > namelist || colmod != 0 || id) && mc == 0)
453 for (i = 1; i <= msgCount; i++) {
454 if (!(message[i-1].m_flag & MHIDDEN) &&
455 (message[i-1].m_flag & MDELETED) ==
456 (unsigned)f)
457 mark(i, f);
461 * If any names were given, go through and eliminate any
462 * messages whose senders were not requested.
465 if (np > namelist || id) {
466 bool_t allnet = ok_blook(allnet);
468 #ifdef HAVE_IMAP
469 if (mb.mb_type == MB_IMAP && gotheaders++ == 0)
470 imap_getheaders(1, msgCount);
471 #endif
472 srelax_hold();
473 for (i = 1; i <= msgCount; i++) {
474 mc = 0;
475 if (np > namelist) {
476 for (nq = &namelist[0]; *nq != NULL; nq++) {
477 if (**nq == '/') {
478 if (matchsubj(*nq, i)) {
479 mc++;
480 break;
483 else {
484 if (matchsender(*nq, i,
485 allnet)) {
486 mc++;
487 break;
492 if (mc == 0 && id && matchmid(id, idfield, i))
493 mc++;
494 if (mc == 0)
495 unmark(i);
496 srelax();
498 srelax_rele();
501 * Make sure we got some decent messages.
504 mc = 0;
505 for (i = 1; i <= msgCount; i++)
506 if (message[i-1].m_flag & MMARK) {
507 mc++;
508 break;
510 if (mc == 0) {
511 if (!inhook && np > namelist) {
512 printf(tr(120,
513 "No applicable messages from {%s"),
514 namelist[0]);
515 for (nq = &namelist[1]; *nq != NULL; nq++)
516 printf(tr(121, ", %s"), *nq);
517 printf(tr(122, "}\n"));
518 } else if (id) {
519 printf(tr(227, "Parent message not found\n"));
521 markall_ret(-1)
526 * If any colon modifiers were given, go through and
527 * unmark any messages which do not satisfy the modifiers.
530 if (colmod != 0) {
531 for (i = 1; i <= msgCount; i++) {
532 struct coltab *colp;
533 bool_t bad = TRU1;
535 mp = &message[i - 1];
536 for (colp = &coltab[0]; colp->co_char; colp++)
537 if ((colp->co_bit & colmod) &&
538 ((mp->m_flag & colp->co_mask)
539 == (unsigned)colp->co_equal))
540 bad = FAL0;
541 if (bad)
542 unmark(i);
544 for (mp = &message[0]; mp < &message[msgCount]; mp++)
545 if (mp->m_flag & MMARK)
546 break;
547 if (mp >= &message[msgCount]) {
548 struct coltab *colp;
550 if (!inhook) {
551 printf(tr(123, "No messages satisfy"));
552 for (colp = &coltab[0]; colp->co_char; colp++)
553 if (colp->co_bit & colmod)
554 printf(" :%c", colp->co_char);
555 printf("\n");
557 markall_ret(-1)
560 markall_ret(0)
561 out:
562 free(namelist);
563 ac_free(lexstring);
564 return retval;
565 #undef markall_ret
569 * Turn the character after a colon modifier into a bit
570 * value.
572 static int
573 evalcol(int col)
575 struct coltab *colp;
577 if (col == 0)
578 return(lastcolmod);
579 for (colp = &coltab[0]; colp->co_char; colp++)
580 if (colp->co_char == col)
581 return(colp->co_bit);
582 return(0);
586 * Check the passed message number for legality and proper flags.
587 * If f is MDELETED, then either kind will do. Otherwise, the message
588 * has to be undeleted.
590 static int
591 check(int mesg, int f)
593 struct message *mp;
595 if (mesg < 1 || mesg > msgCount) {
596 printf(tr(124, "%d: Invalid message number\n"), mesg);
597 return(-1);
599 mp = &message[mesg-1];
600 if (mp->m_flag & MHIDDEN || (f != MDELETED &&
601 (mp->m_flag & MDELETED) != 0)) {
602 printf(tr(125, "%d: Inappropriate message\n"), mesg);
603 return(-1);
605 return(0);
609 * Scan out the list of string arguments, shell style
610 * for a RAWLIST.
612 FL int
613 getrawlist(const char *line, size_t linesize, char **argv, int argc,
614 int echolist)
616 char c, *cp2, quotec;
617 const char *cp;
618 int argn;
619 char *linebuf;
621 argn = 0;
622 cp = line;
623 linebuf = ac_alloc(linesize + 1);
624 for (;;) {
625 for (; blankchar(*cp & 0377); cp++);
626 if (*cp == '\0')
627 break;
628 if (argn >= argc - 1) {
629 printf(tr(126,
630 "Too many elements in the list; excess discarded.\n"));
631 break;
633 cp2 = linebuf;
634 quotec = '\0';
635 while ((c = *cp) != '\0') {
636 cp++;
637 if (quotec != '\0') {
638 if (c == quotec) {
639 quotec = '\0';
640 if (echolist)
641 *cp2++ = c;
642 } else if (c == '\\')
643 switch (c = *cp++) {
644 case '\0':
645 *cp2++ = '\\';
646 cp--;
647 break;
649 case '0': case '1': case '2': case '3':
650 case '4': case '5': case '6': case '7':
651 c -= '0';
652 if (*cp >= '0' && *cp <= '7')
653 c = c * 8 + *cp++ - '0';
654 if (*cp >= '0' && *cp <= '7')
655 c = c * 8 + *cp++ - '0';
656 *cp2++ = c;
657 break;
658 case 'b':
659 *cp2++ = '\b';
660 break;
661 case 'f':
662 *cp2++ = '\f';
663 break;
664 case 'n':
665 *cp2++ = '\n';
666 break;
667 case 'r':
668 *cp2++ = '\r';
669 break;
670 case 't':
671 *cp2++ = '\t';
672 break;
673 case 'v':
674 *cp2++ = '\v';
675 break;
677 default:
678 if (cp[-1]!=quotec || echolist)
679 *cp2++ = '\\';
680 *cp2++ = c;
682 /*else if (c == '^') {
683 c = *cp++;
684 if (c == '?')
685 *cp2++ = '\177';
686 /\* null doesn't show up anyway *\/
687 else if ((c >= 'A' && c <= '_') ||
688 (c >= 'a' && c <= 'z'))
689 *cp2++ = c & 037;
690 else {
691 *cp2++ = '^';
692 cp--;
694 }*/ else
695 *cp2++ = c;
696 } else if (c == '"' || c == '\'') {
697 if (echolist)
698 *cp2++ = c;
699 quotec = c;
700 } else if (c == '\\' && !echolist) {
701 if (*cp)
702 *cp2++ = *cp++;
703 else
704 *cp2++ = c;
705 } else if (blankchar(c & 0377))
706 break;
707 else
708 *cp2++ = c;
710 *cp2 = '\0';
711 argv[argn++] = savestr(linebuf);
713 argv[argn] = NULL;
714 ac_free(linebuf);
715 return argn;
719 * scan out a single lexical item and return its token number,
720 * updating the string pointer passed **p. Also, store the value
721 * of the number or string scanned in lexnumber or lexstring as
722 * appropriate. In any event, store the scanned `thing' in lexstring.
725 static struct lex {
726 char l_char;
727 enum ltoken l_token;
728 } singles[] = {
729 { '$', TDOLLAR },
730 { '.', TDOT },
731 { '^', TUP },
732 { '*', TSTAR },
733 { '-', TDASH },
734 { '+', TPLUS },
735 { '(', TOPEN },
736 { ')', TCLOSE },
737 { ',', TCOMMA },
738 { ';', TSEMI },
739 { '`', TBACK },
740 { 0, 0 }
743 static int
744 scan(char **sp)
746 char *cp, *cp2;
747 int c, inquote;
748 struct lex *lp;
749 int quotec;
751 if (regretp >= 0) {
752 strncpy(lexstring, string_stack[regretp], STRINGLEN);
753 lexstring[STRINGLEN-1]='\0';
754 lexnumber = numberstack[regretp];
755 return(regretstack[regretp--]);
757 cp = *sp;
758 cp2 = lexstring;
759 c = *cp++;
762 * strip away leading white space.
765 while (blankchar(c))
766 c = *cp++;
769 * If no characters remain, we are at end of line,
770 * so report that.
773 if (c == '\0') {
774 *sp = --cp;
775 return(TEOL);
779 * Select members of a message thread.
781 if (c == '&') {
782 threadflag = 1;
783 if (*cp == '\0' || spacechar(*cp&0377)) {
784 lexstring[0] = '.';
785 lexstring[1] = '\0';
786 *sp = cp;
787 return TDOT;
789 c = *cp++;
793 * If the leading character is a digit, scan
794 * the number and convert it on the fly.
795 * Return TNUMBER when done.
798 if (digitchar(c)) {
799 lexnumber = 0;
800 while (digitchar(c)) {
801 lexnumber = lexnumber*10 + c - '0';
802 *cp2++ = c;
803 c = *cp++;
805 *cp2 = '\0';
806 *sp = --cp;
807 return(TNUMBER);
811 * An IMAP SEARCH list. Note that TOPEN has always been included
812 * in singles[] in Mail and mailx. Thus although there is no formal
813 * definition for (LIST) lists, they do not collide with historical
814 * practice because a subject string (LIST) could never been matched
815 * this way.
818 if (c == '(') {
819 ui32_t level = 1;
820 inquote = 0;
821 *cp2++ = c;
822 do {
823 if ((c = *cp++&0377) == '\0') {
824 mtop: fprintf(stderr, "Missing \")\".\n");
825 return TERROR;
827 if (inquote && c == '\\') {
828 *cp2++ = c;
829 c = *cp++&0377;
830 if (c == '\0')
831 goto mtop;
832 } else if (c == '"')
833 inquote = !inquote;
834 else if (inquote)
835 /*EMPTY*/;
836 else if (c == '(')
837 level++;
838 else if (c == ')')
839 level--;
840 else if (spacechar(c)) {
842 * Replace unquoted whitespace by single
843 * space characters, to make the string
844 * IMAP SEARCH conformant.
846 c = ' ';
847 if (cp2[-1] == ' ')
848 cp2--;
850 *cp2++ = c;
851 } while (c != ')' || level > 0);
852 *cp2 = '\0';
853 *sp = cp;
854 return TOPEN;
858 * Check for single character tokens; return such
859 * if found.
862 for (lp = &singles[0]; lp->l_char != 0; lp++)
863 if (c == lp->l_char) {
864 lexstring[0] = c;
865 lexstring[1] = '\0';
866 *sp = cp;
867 return(lp->l_token);
871 * We've got a string! Copy all the characters
872 * of the string into lexstring, until we see
873 * a null, space, or tab.
874 * If the lead character is a " or ', save it
875 * and scan until you get another.
878 quotec = 0;
879 if (c == '\'' || c == '"') {
880 quotec = c;
881 c = *cp++;
883 while (c != '\0') {
884 if (quotec == 0 && c == '\\' && *cp)
885 c = *cp++;
886 if (c == quotec) {
887 cp++;
888 break;
890 if (quotec == 0 && blankchar(c))
891 break;
892 if ((size_t)(cp2 - lexstring) < (size_t)STRINGLEN - 1)
893 *cp2++ = c;
894 c = *cp++;
896 if (quotec && c == 0) {
897 fprintf(stderr, tr(127, "Missing %c\n"), quotec);
898 return TERROR;
900 *sp = --cp;
901 *cp2 = '\0';
902 return(TSTRING);
906 * Unscan the named token by pushing it onto the regret stack.
908 static void
909 regret(int token)
911 if (++regretp >= REGDEP)
912 panic(tr(128, "Too many regrets"));
913 regretstack[regretp] = token;
914 lexstring[STRINGLEN-1] = '\0';
915 string_stack[regretp] = savestr(lexstring);
916 numberstack[regretp] = lexnumber;
920 * Reset all the scanner global variables.
922 static void
923 scaninit(void)
925 regretp = -1;
926 threadflag = 0;
930 * Find the first message whose flags & m == f and return
931 * its message number.
933 FL int
934 first(int f, int m)
936 struct message *mp;
938 if (msgCount == 0)
939 return 0;
940 f &= MDELETED;
941 m &= MDELETED;
942 for (mp = dot; mb.mb_threaded ? mp != NULL : mp < &message[msgCount];
943 mb.mb_threaded ? mp = next_in_thread(mp) : mp++) {
944 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & m) == (unsigned)f)
945 return mp - message + 1;
947 if (dot > &message[0]) {
948 for (mp = dot-1; mb.mb_threaded ?
949 mp != NULL : mp >= &message[0];
950 mb.mb_threaded ?
951 mp = prev_in_thread(mp) : mp--) {
952 if (! (mp->m_flag & MHIDDEN) &&
953 (mp->m_flag & m) == (unsigned)f)
954 return mp - message + 1;
957 return 0;
961 * See if the passed name sent the passed message number. Return true
962 * if so.
964 static int
965 matchsender(char *str, int mesg, int allnet)
967 if (allnet) {
968 char *cp = nameof(&message[mesg - 1], 0);
970 do {
971 if ((*cp == '@' || *cp == '\0') &&
972 (*str == '@' || *str == '\0'))
973 return 1;
974 if (*cp != *str)
975 break;
976 } while (cp++, *str++ != '\0');
977 return 0;
979 return !strcmp(str, (ok_blook(showname) ? realname : skin)
980 (name1(&message[mesg - 1], 0)));
983 static int
984 matchmid(char *id, enum idfield idfield, int mesg)
986 struct name *np;
987 char *cp;
989 if ((cp = hfield1("message-id", &message[mesg - 1])) != NULL) {
990 switch (idfield) {
991 case ID_REFERENCES:
992 return msgidcmp(id, cp) == 0;
993 case ID_IN_REPLY_TO:
994 if ((np = extract(id, GREF)) != NULL)
995 do {
996 if (msgidcmp(np->n_name, cp) == 0)
997 return 1;
998 } while ((np = np->n_flink) != NULL);
999 break;
1002 return 0;
1006 * See if the given string matches inside the subject field of the
1007 * given message. For the purpose of the scan, we ignore case differences.
1008 * If it does, return true. The string search argument is assumed to
1009 * have the form "/search-string." If it is of the form "/," we use the
1010 * previous search string.
1013 static char lastscan[128];
1015 static int
1016 matchsubj(char *str, int mesg)
1018 struct message *mp;
1019 char *cp, *cp2;
1020 struct str in, out;
1021 int i;
1023 str++;
1024 if (strlen(str) == 0) {
1025 str = lastscan;
1026 } else {
1027 strncpy(lastscan, str, sizeof lastscan);
1028 lastscan[sizeof lastscan - 1]='\0';
1030 mp = &message[mesg-1];
1033 * Now look, ignoring case, for the word in the string.
1036 if (ok_blook(searchheaders) && (cp = strchr(str, ':'))) {
1037 *cp++ = '\0';
1038 cp2 = hfieldX(str, mp);
1039 cp[-1] = ':';
1040 } else {
1041 cp = str;
1042 cp2 = hfield1("subject", mp);
1044 if (cp2 == NULL)
1045 return(0);
1046 in.s = cp2;
1047 in.l = strlen(cp2);
1048 mime_fromhdr(&in, &out, TD_ICONV);
1049 i = substr(out.s, cp);
1050 free(out.s);
1051 return i;
1055 * Mark the named message by setting its mark bit.
1057 FL void
1058 mark(int mesg, int f)
1060 struct message *mp;
1061 int i;
1063 i = mesg;
1064 if (i < 1 || i > msgCount)
1065 panic(tr(129, "Bad message number to mark"));
1066 if (mb.mb_threaded == 1 && threadflag) {
1067 if ((message[i-1].m_flag & MHIDDEN) == 0) {
1068 if (f == MDELETED ||
1069 (message[i-1].m_flag&MDELETED) == 0)
1070 message[i-1].m_flag |= MMARK;
1072 if (message[i-1].m_child) {
1073 mp = message[i-1].m_child;
1074 mark(mp-message+1, f);
1075 for (mp = mp->m_younger; mp; mp = mp->m_younger)
1076 mark(mp-message+1, f);
1078 } else
1079 message[i-1].m_flag |= MMARK;
1083 * Unmark the named message.
1085 static void
1086 unmark(int mesg)
1088 ui32_t i;
1090 i = (ui32_t)mesg;
1091 if (i < 1 || i > (ui32_t)msgCount)
1092 panic(tr(130, "Bad message number to unmark"));
1093 message[i-1].m_flag &= ~MMARK;
1097 * Return the message number corresponding to the passed meta character.
1099 static int
1100 metamess(int meta, int f)
1102 int c, m;
1103 struct message *mp;
1105 c = meta;
1106 switch (c) {
1107 case '^':
1109 * First 'good' message left.
1111 mp = mb.mb_threaded ? threadroot : &message[0];
1112 while (mp < &message[msgCount]) {
1113 if (!(mp->m_flag & MHIDDEN) &&
1114 (mp->m_flag & MDELETED) == (unsigned)f)
1115 return(mp - &message[0] + 1);
1116 if (mb.mb_threaded) {
1117 mp = next_in_thread(mp);
1118 if (mp == NULL)
1119 break;
1120 } else
1121 mp++;
1123 if (!inhook)
1124 printf(tr(131, "No applicable messages\n"));
1125 return(-1);
1127 case '$':
1129 * Last 'good message left.
1131 mp = mb.mb_threaded ? this_in_thread(threadroot, -1) :
1132 &message[msgCount-1];
1133 while (mp >= &message[0]) {
1134 if (!(mp->m_flag & MHIDDEN) &&
1135 (mp->m_flag & MDELETED) == (unsigned)f)
1136 return(mp - &message[0] + 1);
1137 if (mb.mb_threaded) {
1138 mp = prev_in_thread(mp);
1139 if (mp == NULL)
1140 break;
1141 } else
1142 mp--;
1144 if (!inhook)
1145 printf(tr(132, "No applicable messages\n"));
1146 return(-1);
1148 case '.':
1150 * Current message.
1152 m = dot - &message[0] + 1;
1153 if ((dot->m_flag & MHIDDEN) ||
1154 (dot->m_flag & MDELETED) != (unsigned)f) {
1155 printf(tr(133, "%d: Inappropriate message\n"), m);
1156 return(-1);
1158 return(m);
1160 case ';':
1162 * Previously current message.
1164 if (prevdot == NULL) {
1165 printf(tr(228, "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(tr(133, "%d: Inappropriate message\n"), m);
1172 return(-1);
1174 return(m);
1176 default:
1177 printf(tr(134, "Unknown metachar (%c)\n"), c);
1178 return(-1);