Many: use srelax() is some places
[s-mailx.git] / list.c
blobeecaa3d73aa47543327be2cd322a208810d36d6c
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;
107 if (mb.mb_threaded == 0) {
108 for (mp = &message[0]; mp < &message[msgCount]; mp++)
109 if (mp->m_flag & MMARK)
110 *ip++ = mp - &message[0] + 1;
111 } else {
112 for (mp = threadroot; mp; mp = next_in_thread(mp))
113 if (mp->m_flag & MMARK)
114 *ip++ = mp - &message[0] + 1;
116 *ip = 0;
117 if ((size_t)(ip - vector) != 1)
118 msglist_is_single = FAL0;
119 return(ip - vector);
123 * Mark all messages that the user wanted from the command
124 * line in the message structure. Return 0 on success, -1
125 * on error.
129 * Bit values for colon modifiers.
132 #define CMNEW 01 /* New messages */
133 #define CMOLD 02 /* Old messages */
134 #define CMUNREAD 04 /* Unread messages */
135 #define CMDELETED 010 /* Deleted messages */
136 #define CMREAD 020 /* Read messages */
137 #define CMFLAG 040 /* Flagged messages */
138 #define CMANSWER 0100 /* Answered messages */
139 #define CMDRAFT 0200 /* Draft messages */
140 #define CMSPAM 0400 /* Spam messages */
143 * The following table describes the letters which can follow
144 * the colon and gives the corresponding modifier bit.
147 static struct coltab {
148 char co_char; /* What to find past : */
149 int co_bit; /* Associated modifier bit */
150 int co_mask; /* m_status bits to mask */
151 int co_equal; /* ... must equal this */
152 } coltab[] = {
153 { 'n', CMNEW, MNEW, MNEW },
154 { 'o', CMOLD, MNEW, 0 },
155 { 'u', CMUNREAD, MREAD, 0 },
156 { 'd', CMDELETED, MDELETED, MDELETED },
157 { 'r', CMREAD, MREAD, MREAD },
158 { 'f', CMFLAG, MFLAGGED, MFLAGGED },
159 { 'a', CMANSWER, MANSWERED, MANSWERED },
160 { 't', CMDRAFT, MDRAFTED, MDRAFTED },
161 { 's', CMSPAM, MSPAM, MSPAM },
162 { 0, 0, 0, 0 }
165 static int lastcolmod;
167 static char **
168 add_to_namelist(char ***namelist, size_t *nmlsize, char **np, char *string)
170 size_t idx;
172 if ((idx = np - *namelist) >= *nmlsize) {
173 *namelist = srealloc(*namelist, (*nmlsize += 8) * sizeof *np);
174 np = &(*namelist)[idx];
176 *np++ = string;
177 return np;
180 static int
181 markall(char *buf, int f)
183 #define markall_ret(i) do { retval = i; goto out; } while (0);
184 char **np, **nq;
185 int i, retval;
186 struct message *mp, *mx;
187 char **namelist, *bufp, *id = NULL, *cp;
188 int tok, beg, mc, star, other, valdot, colmod, colresult, topen, tback;
189 size_t nmlsize;
190 enum idfield idfield = ID_REFERENCES;
191 #ifdef HAVE_IMAP
192 int gotheaders;
193 #endif
195 lexstring = ac_alloc(STRINGLEN = 2 * strlen(buf) + 1);
196 valdot = dot - &message[0] + 1;
197 colmod = 0;
198 for (i = 1; i <= msgCount; i++) {
199 message[i-1].m_flag &= ~MOLDMARK;
200 if (message[i-1].m_flag & MMARK)
201 message[i-1].m_flag |= MOLDMARK;
202 unmark(i);
204 bufp = buf;
205 mc = 0;
206 namelist = smalloc((nmlsize = 8) * sizeof *namelist);
207 np = &namelist[0];
208 scaninit();
209 tok = scan(&bufp);
210 star = 0;
211 other = 0;
212 beg = 0;
213 topen = 0;
214 tback = 0;
215 #ifdef HAVE_IMAP
216 gotheaders = 0;
217 #endif
219 while (tok != TEOL) {
220 switch (tok) {
221 case TNUMBER:
222 number:
223 if (star) {
224 printf(tr(112, "No numbers mixed with *\n"));
225 markall_ret(-1)
227 mc++;
228 other++;
229 if (beg != 0) {
230 if (check(lexnumber, f))
231 markall_ret(-1)
232 i = beg;
233 while (mb.mb_threaded ? 1 : i <= lexnumber) {
234 if (!(message[i-1].m_flag&MHIDDEN) &&
235 (f == MDELETED ||
236 (message[i-1].m_flag &
237 MDELETED) == 0))
238 mark(i, f);
239 if (mb.mb_threaded) {
240 if (i == lexnumber)
241 break;
242 mx = next_in_thread(&message[i-1]);
243 if (mx == NULL)
244 markall_ret(-1)
245 i = mx-message+1;
246 } else
247 i++;
249 beg = 0;
250 break;
252 beg = lexnumber;
253 if (check(beg, f))
254 markall_ret(-1)
255 tok = scan(&bufp);
256 regret(tok);
257 if (tok != TDASH) {
258 mark(beg, f);
259 beg = 0;
261 break;
263 case TPLUS:
264 msglist_is_single = FAL0;
265 if (beg != 0) {
266 printf(tr(113,
267 "Non-numeric second argument\n"));
268 markall_ret(-1)
270 i = valdot;
271 do {
272 if (mb.mb_threaded) {
273 mx = next_in_thread(&message[i-1]);
274 i = mx ? mx-message+1 : msgCount+1;
275 } else
276 i++;
277 if (i > msgCount) {
278 printf(tr(114,
279 "Referencing beyond EOF\n"));
280 markall_ret(-1)
282 } while (message[i-1].m_flag == MHIDDEN ||
283 (message[i-1].m_flag & MDELETED) !=
284 (unsigned)f);
285 mark(i, f);
286 break;
288 case TDASH:
289 msglist_is_single = FAL0;
290 if (beg == 0) {
291 i = valdot;
292 do {
293 if (mb.mb_threaded) {
294 mx = prev_in_thread(
295 &message[i-1]);
296 i = mx ? mx-message+1 : 0;
297 } else
298 i--;
299 if (i <= 0) {
300 printf(tr(115,
301 "Referencing before 1\n"));
302 markall_ret(-1)
304 } while ((message[i-1].m_flag & MHIDDEN) ||
305 (message[i-1].m_flag & MDELETED)
306 != (unsigned)f);
307 mark(i, f);
309 break;
311 case TSTRING:
312 msglist_is_single = FAL0;
313 if (beg != 0) {
314 printf(tr(116,
315 "Non-numeric second argument\n"));
316 markall_ret(-1)
318 other++;
319 if (lexstring[0] == ':') {
320 colresult = evalcol(lexstring[1]);
321 if (colresult == 0) {
322 printf(tr(117,
323 "Unknown colon modifier \"%s\"\n"),
324 lexstring);
325 markall_ret(-1)
327 colmod |= colresult;
329 else
330 np = add_to_namelist(&namelist, &nmlsize,
331 np, savestr(lexstring));
332 break;
334 case TOPEN:
335 msglist_is_single = FAL0;
336 if (imap_search(lexstring, f) == STOP)
337 markall_ret(-1)
338 topen++;
339 break;
341 case TDOLLAR:
342 case TUP:
343 case TDOT:
344 case TSEMI:
345 msglist_is_single = FAL0;
346 lexnumber = metamess(lexstring[0], f);
347 if (lexnumber == -1)
348 markall_ret(-1)
349 goto number;
351 case TBACK:
352 msglist_is_single = FAL0;
353 tback = 1;
354 for (i = 1; i <= msgCount; i++) {
355 if ((message[i-1].m_flag & MHIDDEN) ||
356 (message[i-1].m_flag&MDELETED)
357 != (unsigned)f)
358 continue;
359 if (message[i-1].m_flag&MOLDMARK)
360 mark(i, f);
362 break;
364 case TSTAR:
365 msglist_is_single = FAL0;
366 if (other) {
367 printf(tr(118,
368 "Can't mix \"*\" with anything\n"));
369 markall_ret(-1)
371 star++;
372 break;
374 case TCOMMA:
375 msglist_is_single = FAL0;
376 #ifdef HAVE_IMAP
377 if (mb.mb_type == MB_IMAP && gotheaders++ == 0)
378 imap_getheaders(1, msgCount);
379 #endif
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 *enp;
388 if ((enp = extract(cp, GREF)) != NULL) {
389 while (enp->n_flink != NULL)
390 enp = enp->n_flink;
391 id = savestr(enp->n_name);
392 idfield = ID_REFERENCES;
395 if (id == NULL) {
396 printf(tr(227,
397 "Cannot determine parent Message-ID of the current message\n"));
398 markall_ret(-1)
400 break;
402 case TERROR:
403 msglist_is_single = FAL0;
404 markall_ret(-1)
406 threadflag = 0;
407 tok = scan(&bufp);
409 lastcolmod = colmod;
410 np = add_to_namelist(&namelist, &nmlsize, np, NULL);
411 np--;
412 mc = 0;
413 if (star) {
414 for (i = 0; i < msgCount; i++) {
415 if (!(message[i].m_flag & MHIDDEN) &&
416 (message[i].m_flag & MDELETED) ==
417 (unsigned)f) {
418 mark(i+1, f);
419 mc++;
422 if (mc == 0) {
423 if (!inhook)
424 printf(tr(119,
425 "No applicable messages.\n"));
426 markall_ret(-1)
428 markall_ret(0)
431 if ((topen || tback) && mc == 0) {
432 for (i = 0; i < msgCount; i++)
433 if (message[i].m_flag & MMARK)
434 mc++;
435 if (mc == 0) {
436 if (!inhook)
437 printf(tback ?
438 "No previously marked messages.\n" :
439 "No messages satisfy (criteria).\n");
440 markall_ret(-1)
445 * If no numbers were given, mark all of the messages,
446 * so that we can unmark any whose sender was not selected
447 * if any user names were given.
450 if ((np > namelist || colmod != 0 || id) && mc == 0)
451 for (i = 1; i <= msgCount; i++) {
452 if (!(message[i-1].m_flag & MHIDDEN) &&
453 (message[i-1].m_flag & MDELETED) ==
454 (unsigned)f)
455 mark(i, f);
459 * If any names were given, go through and eliminate any
460 * messages whose senders were not requested.
463 if (np > namelist || id) {
464 int allnet = value("allnet") != NULL;
466 #ifdef HAVE_IMAP
467 if (mb.mb_type == MB_IMAP && gotheaders++ == 0)
468 imap_getheaders(1, msgCount);
469 #endif
470 srelax_hold();
471 for (i = 1; i <= msgCount; i++) {
472 mc = 0;
473 if (np > namelist) {
474 for (nq = &namelist[0]; *nq != NULL; nq++) {
475 if (**nq == '/') {
476 if (matchsubj(*nq, i)) {
477 mc++;
478 break;
481 else {
482 if (matchsender(*nq, i,
483 allnet)) {
484 mc++;
485 break;
490 if (mc == 0 && id && matchmid(id, idfield, i))
491 mc++;
492 if (mc == 0)
493 unmark(i);
494 srelax();
496 srelax_rele();
499 * Make sure we got some decent messages.
502 mc = 0;
503 for (i = 1; i <= msgCount; i++)
504 if (message[i-1].m_flag & MMARK) {
505 mc++;
506 break;
508 if (mc == 0) {
509 if (!inhook && np > namelist) {
510 printf(tr(120,
511 "No applicable messages from {%s"),
512 namelist[0]);
513 for (nq = &namelist[1]; *nq != NULL; nq++)
514 printf(tr(121, ", %s"), *nq);
515 printf(tr(122, "}\n"));
516 } else if (id) {
517 printf(tr(227, "Parent message not found\n"));
519 markall_ret(-1)
524 * If any colon modifiers were given, go through and
525 * unmark any messages which do not satisfy the modifiers.
528 if (colmod != 0) {
529 for (i = 1; i <= msgCount; i++) {
530 struct coltab *colp;
531 bool_t bad = TRU1;
533 mp = &message[i - 1];
534 for (colp = &coltab[0]; colp->co_char; colp++)
535 if ((colp->co_bit & colmod) &&
536 ((mp->m_flag & colp->co_mask)
537 == (unsigned)colp->co_equal))
538 bad = FAL0;
539 if (bad)
540 unmark(i);
542 for (mp = &message[0]; mp < &message[msgCount]; mp++)
543 if (mp->m_flag & MMARK)
544 break;
545 if (mp >= &message[msgCount]) {
546 struct coltab *colp;
548 if (!inhook) {
549 printf(tr(123, "No messages satisfy"));
550 for (colp = &coltab[0]; colp->co_char; colp++)
551 if (colp->co_bit & colmod)
552 printf(" :%c", colp->co_char);
553 printf("\n");
555 markall_ret(-1)
558 markall_ret(0)
559 out:
560 free(namelist);
561 ac_free(lexstring);
562 return retval;
563 #undef markall_ret
567 * Turn the character after a colon modifier into a bit
568 * value.
570 static int
571 evalcol(int col)
573 struct coltab *colp;
575 if (col == 0)
576 return(lastcolmod);
577 for (colp = &coltab[0]; colp->co_char; colp++)
578 if (colp->co_char == col)
579 return(colp->co_bit);
580 return(0);
584 * Check the passed message number for legality and proper flags.
585 * If f is MDELETED, then either kind will do. Otherwise, the message
586 * has to be undeleted.
588 static int
589 check(int mesg, int f)
591 struct message *mp;
593 if (mesg < 1 || mesg > msgCount) {
594 printf(tr(124, "%d: Invalid message number\n"), mesg);
595 return(-1);
597 mp = &message[mesg-1];
598 if (mp->m_flag & MHIDDEN || (f != MDELETED &&
599 (mp->m_flag & MDELETED) != 0)) {
600 printf(tr(125, "%d: Inappropriate message\n"), mesg);
601 return(-1);
603 return(0);
607 * Scan out the list of string arguments, shell style
608 * for a RAWLIST.
611 getrawlist(const char *line, size_t linesize, char **argv, int argc,
612 int echolist)
614 char c, *cp2, quotec;
615 const char *cp;
616 int argn;
617 char *linebuf;
619 argn = 0;
620 cp = line;
621 linebuf = ac_alloc(linesize + 1);
622 for (;;) {
623 for (; blankchar(*cp & 0377); cp++);
624 if (*cp == '\0')
625 break;
626 if (argn >= argc - 1) {
627 printf(tr(126,
628 "Too many elements in the list; excess discarded.\n"));
629 break;
631 cp2 = linebuf;
632 quotec = '\0';
633 while ((c = *cp) != '\0') {
634 cp++;
635 if (quotec != '\0') {
636 if (c == quotec) {
637 quotec = '\0';
638 if (echolist)
639 *cp2++ = c;
640 } else if (c == '\\')
641 switch (c = *cp++) {
642 case '\0':
643 *cp2++ = '\\';
644 cp--;
645 break;
647 case '0': case '1': case '2': case '3':
648 case '4': case '5': case '6': case '7':
649 c -= '0';
650 if (*cp >= '0' && *cp <= '7')
651 c = c * 8 + *cp++ - '0';
652 if (*cp >= '0' && *cp <= '7')
653 c = c * 8 + *cp++ - '0';
654 *cp2++ = c;
655 break;
656 case 'b':
657 *cp2++ = '\b';
658 break;
659 case 'f':
660 *cp2++ = '\f';
661 break;
662 case 'n':
663 *cp2++ = '\n';
664 break;
665 case 'r':
666 *cp2++ = '\r';
667 break;
668 case 't':
669 *cp2++ = '\t';
670 break;
671 case 'v':
672 *cp2++ = '\v';
673 break;
675 default:
676 if (cp[-1]!=quotec || echolist)
677 *cp2++ = '\\';
678 *cp2++ = c;
680 /*else if (c == '^') {
681 c = *cp++;
682 if (c == '?')
683 *cp2++ = '\177';
684 /\* null doesn't show up anyway *\/
685 else if ((c >= 'A' && c <= '_') ||
686 (c >= 'a' && c <= 'z'))
687 *cp2++ = c & 037;
688 else {
689 *cp2++ = '^';
690 cp--;
692 }*/ else
693 *cp2++ = c;
694 } else if (c == '"' || c == '\'') {
695 if (echolist)
696 *cp2++ = c;
697 quotec = c;
698 } else if (c == '\\' && !echolist) {
699 if (*cp)
700 *cp2++ = *cp++;
701 else
702 *cp2++ = c;
703 } else if (blankchar(c & 0377))
704 break;
705 else
706 *cp2++ = c;
708 *cp2 = '\0';
709 argv[argn++] = savestr(linebuf);
711 argv[argn] = NULL;
712 ac_free(linebuf);
713 return argn;
717 * scan out a single lexical item and return its token number,
718 * updating the string pointer passed **p. Also, store the value
719 * of the number or string scanned in lexnumber or lexstring as
720 * appropriate. In any event, store the scanned `thing' in lexstring.
723 static struct lex {
724 char l_char;
725 enum ltoken l_token;
726 } singles[] = {
727 { '$', TDOLLAR },
728 { '.', TDOT },
729 { '^', TUP },
730 { '*', TSTAR },
731 { '-', TDASH },
732 { '+', TPLUS },
733 { '(', TOPEN },
734 { ')', TCLOSE },
735 { ',', TCOMMA },
736 { ';', TSEMI },
737 { '`', TBACK },
738 { 0, 0 }
741 static int
742 scan(char **sp)
744 char *cp, *cp2;
745 int c, inquote;
746 struct lex *lp;
747 int quotec;
749 if (regretp >= 0) {
750 strncpy(lexstring, string_stack[regretp], STRINGLEN);
751 lexstring[STRINGLEN-1]='\0';
752 lexnumber = numberstack[regretp];
753 return(regretstack[regretp--]);
755 cp = *sp;
756 cp2 = lexstring;
757 c = *cp++;
760 * strip away leading white space.
763 while (blankchar(c))
764 c = *cp++;
767 * If no characters remain, we are at end of line,
768 * so report that.
771 if (c == '\0') {
772 *sp = --cp;
773 return(TEOL);
777 * Select members of a message thread.
779 if (c == '&') {
780 threadflag = 1;
781 if (*cp == '\0' || spacechar(*cp&0377)) {
782 lexstring[0] = '.';
783 lexstring[1] = '\0';
784 *sp = cp;
785 return TDOT;
787 c = *cp++;
791 * If the leading character is a digit, scan
792 * the number and convert it on the fly.
793 * Return TNUMBER when done.
796 if (digitchar(c)) {
797 lexnumber = 0;
798 while (digitchar(c)) {
799 lexnumber = lexnumber*10 + c - '0';
800 *cp2++ = c;
801 c = *cp++;
803 *cp2 = '\0';
804 *sp = --cp;
805 return(TNUMBER);
809 * An IMAP SEARCH list. Note that TOPEN has always been included
810 * in singles[] in Mail and mailx. Thus although there is no formal
811 * definition for (LIST) lists, they do not collide with historical
812 * practice because a subject string (LIST) could never been matched
813 * this way.
816 if (c == '(') {
817 ui32_t level = 1;
818 inquote = 0;
819 *cp2++ = c;
820 do {
821 if ((c = *cp++&0377) == '\0') {
822 mtop: fprintf(stderr, "Missing \")\".\n");
823 return TERROR;
825 if (inquote && c == '\\') {
826 *cp2++ = c;
827 c = *cp++&0377;
828 if (c == '\0')
829 goto mtop;
830 } else if (c == '"')
831 inquote = !inquote;
832 else if (inquote)
833 /*EMPTY*/;
834 else if (c == '(')
835 level++;
836 else if (c == ')')
837 level--;
838 else if (spacechar(c)) {
840 * Replace unquoted whitespace by single
841 * space characters, to make the string
842 * IMAP SEARCH conformant.
844 c = ' ';
845 if (cp2[-1] == ' ')
846 cp2--;
848 *cp2++ = c;
849 } while (c != ')' || level > 0);
850 *cp2 = '\0';
851 *sp = cp;
852 return TOPEN;
856 * Check for single character tokens; return such
857 * if found.
860 for (lp = &singles[0]; lp->l_char != 0; lp++)
861 if (c == lp->l_char) {
862 lexstring[0] = c;
863 lexstring[1] = '\0';
864 *sp = cp;
865 return(lp->l_token);
869 * We've got a string! Copy all the characters
870 * of the string into lexstring, until we see
871 * a null, space, or tab.
872 * If the lead character is a " or ', save it
873 * and scan until you get another.
876 quotec = 0;
877 if (c == '\'' || c == '"') {
878 quotec = c;
879 c = *cp++;
881 while (c != '\0') {
882 if (quotec == 0 && c == '\\' && *cp)
883 c = *cp++;
884 if (c == quotec) {
885 cp++;
886 break;
888 if (quotec == 0 && blankchar(c))
889 break;
890 if ((size_t)(cp2 - lexstring) < (size_t)STRINGLEN - 1)
891 *cp2++ = c;
892 c = *cp++;
894 if (quotec && c == 0) {
895 fprintf(stderr, tr(127, "Missing %c\n"), quotec);
896 return TERROR;
898 *sp = --cp;
899 *cp2 = '\0';
900 return(TSTRING);
904 * Unscan the named token by pushing it onto the regret stack.
906 static void
907 regret(int token)
909 if (++regretp >= REGDEP)
910 panic(tr(128, "Too many regrets"));
911 regretstack[regretp] = token;
912 lexstring[STRINGLEN-1] = '\0';
913 string_stack[regretp] = savestr(lexstring);
914 numberstack[regretp] = lexnumber;
918 * Reset all the scanner global variables.
920 static void
921 scaninit(void)
923 regretp = -1;
924 threadflag = 0;
928 * Find the first message whose flags & m == f and return
929 * its message number.
931 int
932 first(int f, int m)
934 struct message *mp;
936 if (msgCount == 0)
937 return 0;
938 f &= MDELETED;
939 m &= MDELETED;
940 for (mp = dot; mb.mb_threaded ? mp != NULL : mp < &message[msgCount];
941 mb.mb_threaded ? mp = next_in_thread(mp) : mp++) {
942 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & m) == (unsigned)f)
943 return mp - message + 1;
945 if (dot > &message[0]) {
946 for (mp = dot-1; mb.mb_threaded ?
947 mp != NULL : mp >= &message[0];
948 mb.mb_threaded ?
949 mp = prev_in_thread(mp) : mp--) {
950 if (! (mp->m_flag & MHIDDEN) &&
951 (mp->m_flag & m) == (unsigned)f)
952 return mp - message + 1;
955 return 0;
959 * See if the passed name sent the passed message number. Return true
960 * if so.
962 static int
963 matchsender(char *str, int mesg, int allnet)
965 if (allnet) {
966 char *cp = nameof(&message[mesg - 1], 0);
968 do {
969 if ((*cp == '@' || *cp == '\0') &&
970 (*str == '@' || *str == '\0'))
971 return 1;
972 if (*cp != *str)
973 break;
974 } while (cp++, *str++ != '\0');
975 return 0;
977 return !strcmp(str, (value("showname") ? realname : skin)
978 (name1(&message[mesg - 1], 0)));
981 static int
982 matchmid(char *id, enum idfield idfield, int mesg)
984 struct name *np;
985 char *cp;
987 if ((cp = hfield1("message-id", &message[mesg - 1])) != NULL) {
988 switch (idfield) {
989 case ID_REFERENCES:
990 return msgidcmp(id, cp) == 0;
991 case ID_IN_REPLY_TO:
992 if ((np = extract(id, GREF)) != NULL)
993 do {
994 if (msgidcmp(np->n_name, cp) == 0)
995 return 1;
996 } while ((np = np->n_flink) != NULL);
997 break;
1000 return 0;
1004 * See if the given string matches inside the subject field of the
1005 * given message. For the purpose of the scan, we ignore case differences.
1006 * If it does, return true. The string search argument is assumed to
1007 * have the form "/search-string." If it is of the form "/," we use the
1008 * previous search string.
1011 static char lastscan[128];
1013 static int
1014 matchsubj(char *str, int mesg)
1016 struct message *mp;
1017 char *cp, *cp2;
1018 struct str in, out;
1019 int i;
1021 str++;
1022 if (strlen(str) == 0) {
1023 str = lastscan;
1024 } else {
1025 strncpy(lastscan, str, sizeof lastscan);
1026 lastscan[sizeof lastscan - 1]='\0';
1028 mp = &message[mesg-1];
1031 * Now look, ignoring case, for the word in the string.
1034 if (value("searchheaders") && (cp = strchr(str, ':'))) {
1035 *cp++ = '\0';
1036 cp2 = hfieldX(str, mp);
1037 cp[-1] = ':';
1038 } else {
1039 cp = str;
1040 cp2 = hfield1("subject", mp);
1042 if (cp2 == NULL)
1043 return(0);
1044 in.s = cp2;
1045 in.l = strlen(cp2);
1046 mime_fromhdr(&in, &out, TD_ICONV);
1047 i = substr(out.s, cp);
1048 free(out.s);
1049 return i;
1053 * Mark the named message by setting its mark bit.
1055 void
1056 mark(int mesg, int f)
1058 struct message *mp;
1059 int i;
1061 i = mesg;
1062 if (i < 1 || i > msgCount)
1063 panic(tr(129, "Bad message number to mark"));
1064 if (mb.mb_threaded == 1 && threadflag) {
1065 if ((message[i-1].m_flag & MHIDDEN) == 0) {
1066 if (f == MDELETED ||
1067 (message[i-1].m_flag&MDELETED) == 0)
1068 message[i-1].m_flag |= MMARK;
1070 if (message[i-1].m_child) {
1071 mp = message[i-1].m_child;
1072 mark(mp-message+1, f);
1073 for (mp = mp->m_younger; mp; mp = mp->m_younger)
1074 mark(mp-message+1, f);
1076 } else
1077 message[i-1].m_flag |= MMARK;
1081 * Unmark the named message.
1083 static void
1084 unmark(int mesg)
1086 int i;
1088 i = mesg;
1089 if (i < 1 || i > msgCount)
1090 panic(tr(130, "Bad message number to unmark"));
1091 message[i-1].m_flag &= ~MMARK;
1095 * Return the message number corresponding to the passed meta character.
1097 static int
1098 metamess(int meta, int f)
1100 int c, m;
1101 struct message *mp;
1103 c = meta;
1104 switch (c) {
1105 case '^':
1107 * First 'good' message left.
1109 mp = mb.mb_threaded ? threadroot : &message[0];
1110 while (mp < &message[msgCount]) {
1111 if (!(mp->m_flag & MHIDDEN) &&
1112 (mp->m_flag & MDELETED) == (unsigned)f)
1113 return(mp - &message[0] + 1);
1114 if (mb.mb_threaded) {
1115 mp = next_in_thread(mp);
1116 if (mp == NULL)
1117 break;
1118 } else
1119 mp++;
1121 if (!inhook)
1122 printf(tr(131, "No applicable messages\n"));
1123 return(-1);
1125 case '$':
1127 * Last 'good message left.
1129 mp = mb.mb_threaded ? this_in_thread(threadroot, -1) :
1130 &message[msgCount-1];
1131 while (mp >= &message[0]) {
1132 if (!(mp->m_flag & MHIDDEN) &&
1133 (mp->m_flag & MDELETED) == (unsigned)f)
1134 return(mp - &message[0] + 1);
1135 if (mb.mb_threaded) {
1136 mp = prev_in_thread(mp);
1137 if (mp == NULL)
1138 break;
1139 } else
1140 mp--;
1142 if (!inhook)
1143 printf(tr(132, "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(tr(133, "%d: Inappropriate message\n"), m);
1154 return(-1);
1156 return(m);
1158 case ';':
1160 * Previously current message.
1162 if (prevdot == NULL) {
1163 printf(tr(228, "No previously current message\n"));
1164 return(-1);
1166 m = prevdot - &message[0] + 1;
1167 if ((prevdot->m_flag & MHIDDEN) ||
1168 (prevdot->m_flag & MDELETED) != (unsigned)f) {
1169 printf(tr(133, "%d: Inappropriate message\n"), m);
1170 return(-1);
1172 return(m);
1174 default:
1175 printf(tr(134, "Unknown metachar (%c)\n"), c);
1176 return(-1);