NEWS: update for s-nail-14_5_2-sort.patch
[s-mailx.git] / list.c
blobdcb5420b071006554bfb7645b5cccd31b7707d24
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 list_saw_numbers =
87 msglist_is_single = FAL0;
89 if (msgCount == 0) {
90 *vector = 0;
91 return 0;
94 msglist_is_single = TRU1;
95 if (markall(buf, flags) < 0)
96 return(-1);
97 ip = vector;
98 if (inhook & 2) {
99 mc = 0;
100 for (mp = &message[0]; mp < &message[msgCount]; mp++)
101 if (mp->m_flag & MMARK) {
102 if ((mp->m_flag & MNEWEST) == 0)
103 unmark(mp - &message[0] + 1);
104 else
105 mc++;
107 if (mc == 0)
108 return -1;
111 if (mb.mb_threaded == 0) {
112 for (mp = &message[0]; mp < &message[msgCount]; mp++)
113 if (mp->m_flag & MMARK)
114 *ip++ = mp - &message[0] + 1;
115 } else {
116 for (mp = threadroot; mp; mp = next_in_thread(mp))
117 if (mp->m_flag & MMARK)
118 *ip++ = mp - &message[0] + 1;
120 *ip = 0;
121 if ((size_t)(ip - vector) != 1)
122 msglist_is_single = FAL0;
123 return(ip - vector);
127 * Mark all messages that the user wanted from the command
128 * line in the message structure. Return 0 on success, -1
129 * on error.
133 * Bit values for colon modifiers.
136 #define CMNEW 01 /* New messages */
137 #define CMOLD 02 /* Old messages */
138 #define CMUNREAD 04 /* Unread messages */
139 #define CMDELETED 010 /* Deleted messages */
140 #define CMREAD 020 /* Read messages */
141 #define CMFLAG 040 /* Flagged messages */
142 #define CMANSWER 0100 /* Answered messages */
143 #define CMDRAFT 0200 /* Draft messages */
144 #define CMSPAM 0400 /* Spam messages */
147 * The following table describes the letters which can follow
148 * the colon and gives the corresponding modifier bit.
151 static struct coltab {
152 char co_char; /* What to find past : */
153 int co_bit; /* Associated modifier bit */
154 int co_mask; /* m_status bits to mask */
155 int co_equal; /* ... must equal this */
156 } coltab[] = {
157 { 'n', CMNEW, MNEW, MNEW },
158 { 'o', CMOLD, MNEW, 0 },
159 { 'u', CMUNREAD, MREAD, 0 },
160 { 'd', CMDELETED, MDELETED, MDELETED },
161 { 'r', CMREAD, MREAD, MREAD },
162 { 'f', CMFLAG, MFLAGGED, MFLAGGED },
163 { 'a', CMANSWER, MANSWERED, MANSWERED },
164 { 't', CMDRAFT, MDRAFTED, MDRAFTED },
165 { 's', CMSPAM, MSPAM, MSPAM },
166 { 0, 0, 0, 0 }
169 static int lastcolmod;
171 static char **
172 add_to_namelist(char ***namelist, size_t *nmlsize, char **np, char *string)
174 size_t idx;
176 if ((idx = np - *namelist) >= *nmlsize) {
177 *namelist = srealloc(*namelist, (*nmlsize += 8) * sizeof *np);
178 np = &(*namelist)[idx];
180 *np++ = string;
181 return np;
184 static int
185 markall(char *buf, int f)
187 #define markall_ret(i) do { retval = i; goto out; } while (0);
188 char **np, **nq;
189 int i, retval;
190 struct message *mp, *mx;
191 char **namelist, *bufp, *id = NULL, *cp;
192 int tok, beg, mc, star, other, valdot, colmod, colresult, topen, tback;
193 size_t nmlsize;
194 enum idfield idfield = ID_REFERENCES;
195 #ifdef HAVE_IMAP
196 int gotheaders;
197 #endif
199 lexstring = ac_alloc(STRINGLEN = 2 * strlen(buf) + 1);
200 valdot = dot - &message[0] + 1;
201 colmod = 0;
202 for (i = 1; i <= msgCount; i++) {
203 message[i-1].m_flag &= ~MOLDMARK;
204 if (message[i-1].m_flag & MMARK)
205 message[i-1].m_flag |= MOLDMARK;
206 unmark(i);
208 bufp = buf;
209 mc = 0;
210 namelist = smalloc((nmlsize = 8) * sizeof *namelist);
211 np = &namelist[0];
212 scaninit();
213 tok = scan(&bufp);
214 star = 0;
215 other = 0;
216 beg = 0;
217 topen = 0;
218 tback = 0;
219 #ifdef HAVE_IMAP
220 gotheaders = 0;
221 #endif
223 while (tok != TEOL) {
224 switch (tok) {
225 case TNUMBER:
226 number:
227 if (star) {
228 printf(tr(112, "No numbers mixed with *\n"));
229 markall_ret(-1)
231 list_saw_numbers = TRU1;
232 mc++;
233 other++;
234 if (beg != 0) {
235 if (check(lexnumber, f))
236 markall_ret(-1)
237 i = beg;
238 while (mb.mb_threaded ? 1 : i <= lexnumber) {
239 if (!(message[i-1].m_flag&MHIDDEN) &&
240 (f == MDELETED ||
241 (message[i-1].m_flag &
242 MDELETED) == 0))
243 mark(i, f);
244 if (mb.mb_threaded) {
245 if (i == lexnumber)
246 break;
247 mx = next_in_thread(&message[i-1]);
248 if (mx == NULL)
249 markall_ret(-1)
250 i = mx-message+1;
251 } else
252 i++;
254 beg = 0;
255 break;
257 beg = lexnumber;
258 if (check(beg, f))
259 markall_ret(-1)
260 tok = scan(&bufp);
261 regret(tok);
262 if (tok != TDASH) {
263 mark(beg, f);
264 beg = 0;
266 break;
268 case TPLUS:
269 msglist_is_single = FAL0;
270 if (beg != 0) {
271 printf(tr(113,
272 "Non-numeric second argument\n"));
273 markall_ret(-1)
275 i = valdot;
276 do {
277 if (mb.mb_threaded) {
278 mx = next_in_thread(&message[i-1]);
279 i = mx ? mx-message+1 : msgCount+1;
280 } else
281 i++;
282 if (i > msgCount) {
283 printf(tr(114,
284 "Referencing beyond EOF\n"));
285 markall_ret(-1)
287 } while (message[i-1].m_flag == MHIDDEN ||
288 (message[i-1].m_flag & MDELETED) !=
289 (unsigned)f);
290 mark(i, f);
291 break;
293 case TDASH:
294 msglist_is_single = FAL0;
295 if (beg == 0) {
296 i = valdot;
297 do {
298 if (mb.mb_threaded) {
299 mx = prev_in_thread(
300 &message[i-1]);
301 i = mx ? mx-message+1 : 0;
302 } else
303 i--;
304 if (i <= 0) {
305 printf(tr(115,
306 "Referencing before 1\n"));
307 markall_ret(-1)
309 } while ((message[i-1].m_flag & MHIDDEN) ||
310 (message[i-1].m_flag & MDELETED)
311 != (unsigned)f);
312 mark(i, f);
314 break;
316 case TSTRING:
317 msglist_is_single = FAL0;
318 if (beg != 0) {
319 printf(tr(116,
320 "Non-numeric second argument\n"));
321 markall_ret(-1)
323 other++;
324 if (lexstring[0] == ':') {
325 colresult = evalcol(lexstring[1]);
326 if (colresult == 0) {
327 printf(tr(117,
328 "Unknown colon modifier \"%s\"\n"),
329 lexstring);
330 markall_ret(-1)
332 colmod |= colresult;
334 else
335 np = add_to_namelist(&namelist, &nmlsize,
336 np, savestr(lexstring));
337 break;
339 case TOPEN:
340 msglist_is_single = FAL0;
341 if (imap_search(lexstring, f) == STOP)
342 markall_ret(-1)
343 topen++;
344 break;
346 case TDOLLAR:
347 case TUP:
348 case TDOT:
349 case TSEMI:
350 msglist_is_single = FAL0;
351 lexnumber = metamess(lexstring[0], f);
352 if (lexnumber == -1)
353 markall_ret(-1)
354 goto number;
356 case TBACK:
357 msglist_is_single = FAL0;
358 tback = 1;
359 for (i = 1; i <= msgCount; i++) {
360 if ((message[i-1].m_flag & MHIDDEN) ||
361 (message[i-1].m_flag&MDELETED)
362 != (unsigned)f)
363 continue;
364 if (message[i-1].m_flag&MOLDMARK)
365 mark(i, f);
367 break;
369 case TSTAR:
370 msglist_is_single = FAL0;
371 if (other) {
372 printf(tr(118,
373 "Can't mix \"*\" with anything\n"));
374 markall_ret(-1)
376 star++;
377 break;
379 case TCOMMA:
380 msglist_is_single = FAL0;
381 #ifdef HAVE_IMAP
382 if (mb.mb_type == MB_IMAP && gotheaders++ == 0)
383 imap_getheaders(1, msgCount);
384 #endif
385 if (id == NULL && (cp = hfield1("in-reply-to", dot))
386 != NULL) {
387 id = savestr(cp);
388 idfield = ID_IN_REPLY_TO;
390 if (id == NULL && (cp = hfield1("references", dot))
391 != NULL) {
392 struct name *enp;
393 if ((enp = extract(cp, GREF)) != NULL) {
394 while (enp->n_flink != NULL)
395 enp = enp->n_flink;
396 id = savestr(enp->n_name);
397 idfield = ID_REFERENCES;
400 if (id == NULL) {
401 printf(tr(227,
402 "Cannot determine parent Message-ID of the current message\n"));
403 markall_ret(-1)
405 break;
407 case TERROR:
408 list_saw_numbers = TRU1;
409 msglist_is_single = FAL0;
410 markall_ret(-1)
412 threadflag = 0;
413 tok = scan(&bufp);
415 lastcolmod = colmod;
416 np = add_to_namelist(&namelist, &nmlsize, np, NULL);
417 np--;
418 mc = 0;
419 if (star) {
420 for (i = 0; i < msgCount; i++) {
421 if (!(message[i].m_flag & MHIDDEN) &&
422 (message[i].m_flag & MDELETED) ==
423 (unsigned)f) {
424 mark(i+1, f);
425 mc++;
428 if (mc == 0) {
429 if (!inhook)
430 printf(tr(119,
431 "No applicable messages.\n"));
432 markall_ret(-1)
434 markall_ret(0)
437 if ((topen || tback) && mc == 0) {
438 for (i = 0; i < msgCount; i++)
439 if (message[i].m_flag & MMARK)
440 mc++;
441 if (mc == 0) {
442 if (!inhook)
443 printf(tback ?
444 "No previously marked messages.\n" :
445 "No messages satisfy (criteria).\n");
446 markall_ret(-1)
451 * If no numbers were given, mark all of the messages,
452 * so that we can unmark any whose sender was not selected
453 * if any user names were given.
456 if ((np > namelist || colmod != 0 || id) && mc == 0)
457 for (i = 1; i <= msgCount; i++) {
458 if (!(message[i-1].m_flag & MHIDDEN) &&
459 (message[i-1].m_flag & MDELETED) ==
460 (unsigned)f)
461 mark(i, f);
465 * If any names were given, go through and eliminate any
466 * messages whose senders were not requested.
469 if (np > namelist || id) {
470 bool_t allnet = ok_blook(allnet);
472 #ifdef HAVE_IMAP
473 if (mb.mb_type == MB_IMAP && gotheaders++ == 0)
474 imap_getheaders(1, msgCount);
475 #endif
476 srelax_hold();
477 for (i = 1; i <= msgCount; i++) {
478 mc = 0;
479 if (np > namelist) {
480 for (nq = &namelist[0]; *nq != NULL; nq++) {
481 if (**nq == '/') {
482 if (matchsubj(*nq, i)) {
483 mc++;
484 break;
487 else {
488 if (matchsender(*nq, i,
489 allnet)) {
490 mc++;
491 break;
496 if (mc == 0 && id && matchmid(id, idfield, i))
497 mc++;
498 if (mc == 0)
499 unmark(i);
500 srelax();
502 srelax_rele();
505 * Make sure we got some decent messages.
508 mc = 0;
509 for (i = 1; i <= msgCount; i++)
510 if (message[i-1].m_flag & MMARK) {
511 mc++;
512 break;
514 if (mc == 0) {
515 if (!inhook && np > namelist) {
516 printf(tr(120,
517 "No applicable messages from {%s"),
518 namelist[0]);
519 for (nq = &namelist[1]; *nq != NULL; nq++)
520 printf(tr(121, ", %s"), *nq);
521 printf(tr(122, "}\n"));
522 } else if (id) {
523 printf(tr(227, "Parent message not found\n"));
525 markall_ret(-1)
530 * If any colon modifiers were given, go through and
531 * unmark any messages which do not satisfy the modifiers.
534 if (colmod != 0) {
535 for (i = 1; i <= msgCount; i++) {
536 struct coltab *colp;
537 bool_t bad = TRU1;
539 mp = &message[i - 1];
540 for (colp = &coltab[0]; colp->co_char; colp++)
541 if ((colp->co_bit & colmod) &&
542 ((mp->m_flag & colp->co_mask)
543 == (unsigned)colp->co_equal))
544 bad = FAL0;
545 if (bad)
546 unmark(i);
548 for (mp = &message[0]; mp < &message[msgCount]; mp++)
549 if (mp->m_flag & MMARK)
550 break;
551 if (mp >= &message[msgCount]) {
552 struct coltab *colp;
554 if (!inhook) {
555 printf(tr(123, "No messages satisfy"));
556 for (colp = &coltab[0]; colp->co_char; colp++)
557 if (colp->co_bit & colmod)
558 printf(" :%c", colp->co_char);
559 printf("\n");
561 markall_ret(-1)
564 markall_ret(0)
565 out:
566 free(namelist);
567 ac_free(lexstring);
568 return retval;
569 #undef markall_ret
573 * Turn the character after a colon modifier into a bit
574 * value.
576 static int
577 evalcol(int col)
579 struct coltab *colp;
581 if (col == 0)
582 return(lastcolmod);
583 for (colp = &coltab[0]; colp->co_char; colp++)
584 if (colp->co_char == col)
585 return(colp->co_bit);
586 return(0);
590 * Check the passed message number for legality and proper flags.
591 * If f is MDELETED, then either kind will do. Otherwise, the message
592 * has to be undeleted.
594 static int
595 check(int mesg, int f)
597 struct message *mp;
599 if (mesg < 1 || mesg > msgCount) {
600 printf(tr(124, "%d: Invalid message number\n"), mesg);
601 return(-1);
603 mp = &message[mesg-1];
604 if (mp->m_flag & MHIDDEN || (f != MDELETED &&
605 (mp->m_flag & MDELETED) != 0)) {
606 printf(tr(125, "%d: Inappropriate message\n"), mesg);
607 return(-1);
609 return(0);
613 * Scan out the list of string arguments, shell style
614 * for a RAWLIST.
616 FL int
617 getrawlist(const char *line, size_t linesize, char **argv, int argc,
618 int echolist)
620 char c, *cp2, quotec;
621 const char *cp;
622 int argn;
623 char *linebuf;
625 list_saw_numbers = FAL0;
627 argn = 0;
628 cp = line;
629 linebuf = ac_alloc(linesize + 1);
630 for (;;) {
631 for (; blankchar(*cp & 0377); cp++);
632 if (*cp == '\0')
633 break;
634 if (argn >= argc - 1) {
635 printf(tr(126,
636 "Too many elements in the list; excess discarded.\n"));
637 break;
639 cp2 = linebuf;
640 quotec = '\0';
641 while ((c = *cp) != '\0') {
642 cp++;
643 if (quotec != '\0') {
644 if (c == quotec) {
645 quotec = '\0';
646 if (echolist)
647 *cp2++ = c;
648 } else if (c == '\\')
649 switch (c = *cp++) {
650 case '\0':
651 *cp2++ = '\\';
652 cp--;
653 break;
655 case '0': case '1': case '2': case '3':
656 case '4': case '5': case '6': case '7':
657 c -= '0';
658 if (*cp >= '0' && *cp <= '7')
659 c = c * 8 + *cp++ - '0';
660 if (*cp >= '0' && *cp <= '7')
661 c = c * 8 + *cp++ - '0';
662 *cp2++ = c;
663 break;
664 case 'b':
665 *cp2++ = '\b';
666 break;
667 case 'f':
668 *cp2++ = '\f';
669 break;
670 case 'n':
671 *cp2++ = '\n';
672 break;
673 case 'r':
674 *cp2++ = '\r';
675 break;
676 case 't':
677 *cp2++ = '\t';
678 break;
679 case 'v':
680 *cp2++ = '\v';
681 break;
683 default:
684 if (cp[-1]!=quotec || echolist)
685 *cp2++ = '\\';
686 *cp2++ = c;
688 /*else if (c == '^') {
689 c = *cp++;
690 if (c == '?')
691 *cp2++ = '\177';
692 /\* null doesn't show up anyway *\/
693 else if ((c >= 'A' && c <= '_') ||
694 (c >= 'a' && c <= 'z'))
695 *cp2++ = c & 037;
696 else {
697 *cp2++ = '^';
698 cp--;
700 }*/ else
701 *cp2++ = c;
702 } else if (c == '"' || c == '\'') {
703 if (echolist)
704 *cp2++ = c;
705 quotec = c;
706 } else if (c == '\\' && !echolist) {
707 if (*cp)
708 *cp2++ = *cp++;
709 else
710 *cp2++ = c;
711 } else if (blankchar(c & 0377))
712 break;
713 else
714 *cp2++ = c;
716 *cp2 = '\0';
717 argv[argn++] = savestr(linebuf);
719 argv[argn] = NULL;
720 ac_free(linebuf);
721 return argn;
725 * scan out a single lexical item and return its token number,
726 * updating the string pointer passed **p. Also, store the value
727 * of the number or string scanned in lexnumber or lexstring as
728 * appropriate. In any event, store the scanned `thing' in lexstring.
731 static struct lex {
732 char l_char;
733 enum ltoken l_token;
734 } singles[] = {
735 { '$', TDOLLAR },
736 { '.', TDOT },
737 { '^', TUP },
738 { '*', TSTAR },
739 { '-', TDASH },
740 { '+', TPLUS },
741 { '(', TOPEN },
742 { ')', TCLOSE },
743 { ',', TCOMMA },
744 { ';', TSEMI },
745 { '`', TBACK },
746 { 0, 0 }
749 static int
750 scan(char **sp)
752 char *cp, *cp2;
753 int c, inquote;
754 struct lex *lp;
755 int quotec;
757 if (regretp >= 0) {
758 strncpy(lexstring, string_stack[regretp], STRINGLEN);
759 lexstring[STRINGLEN-1]='\0';
760 lexnumber = numberstack[regretp];
761 return(regretstack[regretp--]);
763 cp = *sp;
764 cp2 = lexstring;
765 c = *cp++;
768 * strip away leading white space.
771 while (blankchar(c))
772 c = *cp++;
775 * If no characters remain, we are at end of line,
776 * so report that.
779 if (c == '\0') {
780 *sp = --cp;
781 return(TEOL);
785 * Select members of a message thread.
787 if (c == '&') {
788 threadflag = 1;
789 if (*cp == '\0' || spacechar(*cp&0377)) {
790 lexstring[0] = '.';
791 lexstring[1] = '\0';
792 *sp = cp;
793 return TDOT;
795 c = *cp++;
799 * If the leading character is a digit, scan
800 * the number and convert it on the fly.
801 * Return TNUMBER when done.
804 if (digitchar(c)) {
805 lexnumber = 0;
806 while (digitchar(c)) {
807 lexnumber = lexnumber*10 + c - '0';
808 *cp2++ = c;
809 c = *cp++;
811 *cp2 = '\0';
812 *sp = --cp;
813 return(TNUMBER);
817 * An IMAP SEARCH list. Note that TOPEN has always been included
818 * in singles[] in Mail and mailx. Thus although there is no formal
819 * definition for (LIST) lists, they do not collide with historical
820 * practice because a subject string (LIST) could never been matched
821 * this way.
824 if (c == '(') {
825 ui32_t level = 1;
826 inquote = 0;
827 *cp2++ = c;
828 do {
829 if ((c = *cp++&0377) == '\0') {
830 mtop: fprintf(stderr, "Missing \")\".\n");
831 return TERROR;
833 if (inquote && c == '\\') {
834 *cp2++ = c;
835 c = *cp++&0377;
836 if (c == '\0')
837 goto mtop;
838 } else if (c == '"')
839 inquote = !inquote;
840 else if (inquote)
841 /*EMPTY*/;
842 else if (c == '(')
843 level++;
844 else if (c == ')')
845 level--;
846 else if (spacechar(c)) {
848 * Replace unquoted whitespace by single
849 * space characters, to make the string
850 * IMAP SEARCH conformant.
852 c = ' ';
853 if (cp2[-1] == ' ')
854 cp2--;
856 *cp2++ = c;
857 } while (c != ')' || level > 0);
858 *cp2 = '\0';
859 *sp = cp;
860 return TOPEN;
864 * Check for single character tokens; return such
865 * if found.
868 for (lp = &singles[0]; lp->l_char != 0; lp++)
869 if (c == lp->l_char) {
870 lexstring[0] = c;
871 lexstring[1] = '\0';
872 *sp = cp;
873 return(lp->l_token);
877 * We've got a string! Copy all the characters
878 * of the string into lexstring, until we see
879 * a null, space, or tab.
880 * If the lead character is a " or ', save it
881 * and scan until you get another.
884 quotec = 0;
885 if (c == '\'' || c == '"') {
886 quotec = c;
887 c = *cp++;
889 while (c != '\0') {
890 if (quotec == 0 && c == '\\' && *cp)
891 c = *cp++;
892 if (c == quotec) {
893 cp++;
894 break;
896 if (quotec == 0 && blankchar(c))
897 break;
898 if ((size_t)(cp2 - lexstring) < (size_t)STRINGLEN - 1)
899 *cp2++ = c;
900 c = *cp++;
902 if (quotec && c == 0) {
903 fprintf(stderr, tr(127, "Missing %c\n"), quotec);
904 return TERROR;
906 *sp = --cp;
907 *cp2 = '\0';
908 return(TSTRING);
912 * Unscan the named token by pushing it onto the regret stack.
914 static void
915 regret(int token)
917 if (++regretp >= REGDEP)
918 panic(tr(128, "Too many regrets"));
919 regretstack[regretp] = token;
920 lexstring[STRINGLEN-1] = '\0';
921 string_stack[regretp] = savestr(lexstring);
922 numberstack[regretp] = lexnumber;
926 * Reset all the scanner global variables.
928 static void
929 scaninit(void)
931 regretp = -1;
932 threadflag = 0;
936 * Find the first message whose flags & m == f and return
937 * its message number.
939 FL int
940 first(int f, int m)
942 struct message *mp;
944 if (msgCount == 0)
945 return 0;
946 f &= MDELETED;
947 m &= MDELETED;
948 for (mp = dot; mb.mb_threaded ? mp != NULL : mp < &message[msgCount];
949 mb.mb_threaded ? mp = next_in_thread(mp) : mp++) {
950 if (!(mp->m_flag & MHIDDEN) && (mp->m_flag & m) == (unsigned)f)
951 return mp - message + 1;
953 if (dot > &message[0]) {
954 for (mp = dot-1; mb.mb_threaded ?
955 mp != NULL : mp >= &message[0];
956 mb.mb_threaded ?
957 mp = prev_in_thread(mp) : mp--) {
958 if (! (mp->m_flag & MHIDDEN) &&
959 (mp->m_flag & m) == (unsigned)f)
960 return mp - message + 1;
963 return 0;
967 * See if the passed name sent the passed message number. Return true
968 * if so.
970 static int
971 matchsender(char *str, int mesg, int allnet)
973 if (allnet) {
974 char *cp = nameof(&message[mesg - 1], 0);
976 do {
977 if ((*cp == '@' || *cp == '\0') &&
978 (*str == '@' || *str == '\0'))
979 return 1;
980 if (*cp != *str)
981 break;
982 } while (cp++, *str++ != '\0');
983 return 0;
985 return !strcmp(str, (ok_blook(showname) ? realname : skin)
986 (name1(&message[mesg - 1], 0)));
989 static int
990 matchmid(char *id, enum idfield idfield, int mesg)
992 struct name *np;
993 char *cp;
995 if ((cp = hfield1("message-id", &message[mesg - 1])) != NULL) {
996 switch (idfield) {
997 case ID_REFERENCES:
998 return msgidcmp(id, cp) == 0;
999 case ID_IN_REPLY_TO:
1000 if ((np = extract(id, GREF)) != NULL)
1001 do {
1002 if (msgidcmp(np->n_name, cp) == 0)
1003 return 1;
1004 } while ((np = np->n_flink) != NULL);
1005 break;
1008 return 0;
1012 * See if the given string matches inside the subject field of the
1013 * given message. For the purpose of the scan, we ignore case differences.
1014 * If it does, return true. The string search argument is assumed to
1015 * have the form "/search-string." If it is of the form "/," we use the
1016 * previous search string.
1019 static char lastscan[128];
1021 static int
1022 matchsubj(char *str, int mesg)
1024 struct message *mp;
1025 char *cp, *cp2;
1026 struct str in, out;
1027 int i;
1029 str++;
1030 if (strlen(str) == 0) {
1031 str = lastscan;
1032 } else {
1033 strncpy(lastscan, str, sizeof lastscan);
1034 lastscan[sizeof lastscan - 1]='\0';
1036 mp = &message[mesg-1];
1039 * Now look, ignoring case, for the word in the string.
1042 if (ok_blook(searchheaders) && (cp = strchr(str, ':'))) {
1043 *cp++ = '\0';
1044 cp2 = hfieldX(str, mp);
1045 cp[-1] = ':';
1046 } else {
1047 cp = str;
1048 cp2 = hfield1("subject", mp);
1050 if (cp2 == NULL)
1051 return(0);
1052 in.s = cp2;
1053 in.l = strlen(cp2);
1054 mime_fromhdr(&in, &out, TD_ICONV);
1055 i = substr(out.s, cp);
1056 free(out.s);
1057 return i;
1061 * Mark the named message by setting its mark bit.
1063 FL void
1064 mark(int mesg, int f)
1066 struct message *mp;
1067 int i;
1069 i = mesg;
1070 if (i < 1 || i > msgCount)
1071 panic(tr(129, "Bad message number to mark"));
1072 if (mb.mb_threaded == 1 && threadflag) {
1073 if ((message[i-1].m_flag & MHIDDEN) == 0) {
1074 if (f == MDELETED ||
1075 (message[i-1].m_flag&MDELETED) == 0)
1076 message[i-1].m_flag |= MMARK;
1078 if (message[i-1].m_child) {
1079 mp = message[i-1].m_child;
1080 mark(mp-message+1, f);
1081 for (mp = mp->m_younger; mp; mp = mp->m_younger)
1082 mark(mp-message+1, f);
1084 } else
1085 message[i-1].m_flag |= MMARK;
1089 * Unmark the named message.
1091 static void
1092 unmark(int mesg)
1094 ui32_t i;
1096 i = (ui32_t)mesg;
1097 if (i < 1 || i > (ui32_t)msgCount)
1098 panic(tr(130, "Bad message number to unmark"));
1099 message[i-1].m_flag &= ~MMARK;
1103 * Return the message number corresponding to the passed meta character.
1105 static int
1106 metamess(int meta, int f)
1108 int c, m;
1109 struct message *mp;
1111 c = meta;
1112 switch (c) {
1113 case '^':
1115 * First 'good' message left.
1117 mp = mb.mb_threaded ? threadroot : &message[0];
1118 while (mp < &message[msgCount]) {
1119 if (!(mp->m_flag & MHIDDEN) &&
1120 (mp->m_flag & MDELETED) == (unsigned)f)
1121 return(mp - &message[0] + 1);
1122 if (mb.mb_threaded) {
1123 mp = next_in_thread(mp);
1124 if (mp == NULL)
1125 break;
1126 } else
1127 mp++;
1129 if (!inhook)
1130 printf(tr(131, "No applicable messages\n"));
1131 return(-1);
1133 case '$':
1135 * Last 'good message left.
1137 mp = mb.mb_threaded ? this_in_thread(threadroot, -1) :
1138 &message[msgCount-1];
1139 while (mp >= &message[0]) {
1140 if (!(mp->m_flag & MHIDDEN) &&
1141 (mp->m_flag & MDELETED) == (unsigned)f)
1142 return(mp - &message[0] + 1);
1143 if (mb.mb_threaded) {
1144 mp = prev_in_thread(mp);
1145 if (mp == NULL)
1146 break;
1147 } else
1148 mp--;
1150 if (!inhook)
1151 printf(tr(132, "No applicable messages\n"));
1152 return(-1);
1154 case '.':
1156 * Current message.
1158 m = dot - &message[0] + 1;
1159 if ((dot->m_flag & MHIDDEN) ||
1160 (dot->m_flag & MDELETED) != (unsigned)f) {
1161 printf(tr(133, "%d: Inappropriate message\n"), m);
1162 return(-1);
1164 return(m);
1166 case ';':
1168 * Previously current message.
1170 if (prevdot == NULL) {
1171 printf(tr(228, "No previously current message\n"));
1172 return(-1);
1174 m = prevdot - &message[0] + 1;
1175 if ((prevdot->m_flag & MHIDDEN) ||
1176 (prevdot->m_flag & MDELETED) != (unsigned)f) {
1177 printf(tr(133, "%d: Inappropriate message\n"), m);
1178 return(-1);
1180 return(m);
1182 default:
1183 printf(tr(134, "Unknown metachar (%c)\n"), c);
1184 return(-1);