list.c revision 1.13 1 /* $NetBSD: list.c,v 1.13 2003/08/07 11:14:39 agc Exp $ */
2
3 /*
4 * Copyright (c) 1980, 1993
5 * The Regents of the University of California. All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of the University nor the names of its contributors
16 * may be used to endorse or promote products derived from this software
17 * without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 */
31
32 #include <sys/cdefs.h>
33 #ifndef lint
34 #if 0
35 static char sccsid[] = "@(#)list.c 8.4 (Berkeley) 5/1/95";
36 #else
37 __RCSID("$NetBSD: list.c,v 1.13 2003/08/07 11:14:39 agc Exp $");
38 #endif
39 #endif /* not lint */
40
41 #include "rcv.h"
42 #include "extern.h"
43
44 int matchto(char *, int);
45
46 /*
47 * Mail -- a mail program
48 *
49 * Message list handling.
50 */
51
52 /*
53 * Convert the user string of message numbers and
54 * store the numbers into vector.
55 *
56 * Returns the count of messages picked up or -1 on error.
57 */
58 int
59 getmsglist(char *buf, int *vector, int flags)
60 {
61 int *ip;
62 struct message *mp;
63
64 if (msgCount == 0) {
65 *vector = 0;
66 return 0;
67 }
68 if (markall(buf, flags) < 0)
69 return(-1);
70 ip = vector;
71 for (mp = &message[0]; mp < &message[msgCount]; mp++)
72 if (mp->m_flag & MMARK)
73 *ip++ = mp - &message[0] + 1;
74 *ip = 0;
75 return(ip - vector);
76 }
77
78 /*
79 * Mark all messages that the user wanted from the command
80 * line in the message structure. Return 0 on success, -1
81 * on error.
82 */
83
84 /*
85 * Bit values for colon modifiers.
86 */
87
88 #define CMNEW 01 /* New messages */
89 #define CMOLD 02 /* Old messages */
90 #define CMUNREAD 04 /* Unread messages */
91 #define CMDELETED 010 /* Deleted messages */
92 #define CMREAD 020 /* Read messages */
93
94 /*
95 * The following table describes the letters which can follow
96 * the colon and gives the corresponding modifier bit.
97 */
98
99 struct coltab {
100 char co_char; /* What to find past : */
101 int co_bit; /* Associated modifier bit */
102 int co_mask; /* m_status bits to mask */
103 int co_equal; /* ... must equal this */
104 } coltab[] = {
105 { 'n', CMNEW, MNEW, MNEW },
106 { 'o', CMOLD, MNEW, 0 },
107 { 'u', CMUNREAD, MREAD, 0 },
108 { 'd', CMDELETED, MDELETED, MDELETED },
109 { 'r', CMREAD, MREAD, MREAD },
110 { 0, 0, 0, 0 }
111 };
112
113 static int lastcolmod;
114
115 int
116 markall(char buf[], int f)
117 {
118 char **np;
119 int i;
120 struct message *mp;
121 char *namelist[NMLSIZE], *bufp;
122 int tok, beg, mc, star, other, valdot, colmod, colresult;
123
124 valdot = dot - &message[0] + 1;
125 colmod = 0;
126 for (i = 1; i <= msgCount; i++)
127 unmark(i);
128 bufp = buf;
129 mc = 0;
130 np = &namelist[0];
131 scaninit();
132 tok = scan(&bufp);
133 star = 0;
134 other = 0;
135 beg = 0;
136 while (tok != TEOL) {
137 switch (tok) {
138 case TNUMBER:
139 number:
140 if (star) {
141 printf("No numbers mixed with *\n");
142 return(-1);
143 }
144 mc++;
145 other++;
146 if (beg != 0) {
147 if (check(lexnumber, f))
148 return(-1);
149 for (i = beg; i <= lexnumber; i++)
150 if (f == MDELETED || (message[i - 1].m_flag & MDELETED) == 0)
151 mark(i);
152 beg = 0;
153 break;
154 }
155 beg = lexnumber;
156 if (check(beg, f))
157 return(-1);
158 tok = scan(&bufp);
159 regret(tok);
160 if (tok != TDASH) {
161 mark(beg);
162 beg = 0;
163 }
164 break;
165
166 case TPLUS:
167 if (beg != 0) {
168 printf("Non-numeric second argument\n");
169 return(-1);
170 }
171 i = valdot;
172 do {
173 i++;
174 if (i > msgCount) {
175 printf("Referencing beyond EOF\n");
176 return(-1);
177 }
178 } while ((message[i - 1].m_flag & MDELETED) != f);
179 mark(i);
180 break;
181
182 case TDASH:
183 if (beg == 0) {
184 i = valdot;
185 do {
186 i--;
187 if (i <= 0) {
188 printf("Referencing before 1\n");
189 return(-1);
190 }
191 } while ((message[i - 1].m_flag & MDELETED) != f);
192 mark(i);
193 }
194 break;
195
196 case TSTRING:
197 if (beg != 0) {
198 printf("Non-numeric second argument\n");
199 return(-1);
200 }
201 other++;
202 if (lexstring[0] == ':') {
203 colresult = evalcol(lexstring[1]);
204 if (colresult == 0) {
205 printf("Unknown colon modifier \"%s\"\n",
206 lexstring);
207 return(-1);
208 }
209 colmod |= colresult;
210 }
211 else
212 *np++ = savestr(lexstring);
213 break;
214
215 case TDOLLAR:
216 case TUP:
217 case TDOT:
218 lexnumber = metamess(lexstring[0], f);
219 if (lexnumber == -1)
220 return(-1);
221 goto number;
222
223 case TSTAR:
224 if (other) {
225 printf("Can't mix \"*\" with anything\n");
226 return(-1);
227 }
228 star++;
229 break;
230
231 case TERROR:
232 return -1;
233 }
234 tok = scan(&bufp);
235 }
236 lastcolmod = colmod;
237 *np = NULL;
238 mc = 0;
239 if (star) {
240 for (i = 0; i < msgCount; i++)
241 if ((message[i].m_flag & MDELETED) == f) {
242 mark(i+1);
243 mc++;
244 }
245 if (mc == 0) {
246 printf("No applicable messages.\n");
247 return(-1);
248 }
249 return(0);
250 }
251
252 /*
253 * If no numbers were given, mark all of the messages,
254 * so that we can unmark any whose sender was not selected
255 * if any user names were given.
256 */
257
258 if ((np > namelist || colmod != 0) && mc == 0)
259 for (i = 1; i <= msgCount; i++)
260 if ((message[i-1].m_flag & MDELETED) == f)
261 mark(i);
262
263 /*
264 * If any names were given, go through and eliminate any
265 * messages whose senders were not requested.
266 */
267
268 if (np > namelist) {
269 for (i = 1; i <= msgCount; i++) {
270 for (mc = 0, np = &namelist[0]; *np != NULL; np++)
271 if (**np == '/') {
272 if (matchsubj(*np, i)) {
273 mc++;
274 break;
275 }
276 }
277 else {
278 if (matchsender(*np, i)) {
279 mc++;
280 break;
281 }
282 }
283 if (mc == 0)
284 unmark(i);
285 }
286
287 /*
288 * Make sure we got some decent messages.
289 */
290
291 mc = 0;
292 for (i = 1; i <= msgCount; i++)
293 if (message[i-1].m_flag & MMARK) {
294 mc++;
295 break;
296 }
297 if (mc == 0) {
298 printf("No applicable messages from {%s",
299 namelist[0]);
300 for (np = &namelist[1]; *np != NULL; np++)
301 printf(", %s", *np);
302 printf("}\n");
303 return(-1);
304 }
305 }
306
307 /*
308 * If any colon modifiers were given, go through and
309 * unmark any messages which do not satisfy the modifiers.
310 */
311
312 if (colmod != 0) {
313 for (i = 1; i <= msgCount; i++) {
314 struct coltab *colp;
315
316 mp = &message[i - 1];
317 for (colp = &coltab[0]; colp->co_char; colp++)
318 if (colp->co_bit & colmod)
319 if ((mp->m_flag & colp->co_mask)
320 != colp->co_equal)
321 unmark(i);
322
323 }
324 for (mp = &message[0]; mp < &message[msgCount]; mp++)
325 if (mp->m_flag & MMARK)
326 break;
327 if (mp >= &message[msgCount]) {
328 struct coltab *colp;
329
330 printf("No messages satisfy");
331 for (colp = &coltab[0]; colp->co_char; colp++)
332 if (colp->co_bit & colmod)
333 printf(" :%c", colp->co_char);
334 printf("\n");
335 return(-1);
336 }
337 }
338 return(0);
339 }
340
341 /*
342 * Turn the character after a colon modifier into a bit
343 * value.
344 */
345 int
346 evalcol(int col)
347 {
348 struct coltab *colp;
349
350 if (col == 0)
351 return(lastcolmod);
352 for (colp = &coltab[0]; colp->co_char; colp++)
353 if (colp->co_char == col)
354 return(colp->co_bit);
355 return(0);
356 }
357
358 /*
359 * Check the passed message number for legality and proper flags.
360 * If f is MDELETED, then either kind will do. Otherwise, the message
361 * has to be undeleted.
362 */
363 int
364 check(int mesg, int f)
365 {
366 struct message *mp;
367
368 if (mesg < 1 || mesg > msgCount) {
369 printf("%d: Invalid message number\n", mesg);
370 return(-1);
371 }
372 mp = &message[mesg-1];
373 if (f != MDELETED && (mp->m_flag & MDELETED) != 0) {
374 printf("%d: Inappropriate message\n", mesg);
375 return(-1);
376 }
377 return(0);
378 }
379
380 /*
381 * Scan out the list of string arguments, shell style
382 * for a RAWLIST.
383 */
384 int
385 getrawlist(char line[], char **argv, int argc)
386 {
387 char c, *cp, *cp2, quotec;
388 int argn;
389 char linebuf[BUFSIZ];
390
391 argn = 0;
392 cp = line;
393 for (;;) {
394 for (; *cp == ' ' || *cp == '\t'; cp++)
395 ;
396 if (*cp == '\0')
397 break;
398 if (argn >= argc - 1) {
399 printf(
400 "Too many elements in the list; excess discarded.\n");
401 break;
402 }
403 cp2 = linebuf;
404 quotec = '\0';
405 while ((c = *cp) != '\0') {
406 cp++;
407 if (quotec != '\0') {
408 if (c == quotec)
409 quotec = '\0';
410 else if (c == '\\')
411 switch (c = *cp++) {
412 case '\0':
413 *cp2++ = '\\';
414 cp--;
415 break;
416 case '0': case '1': case '2': case '3':
417 case '4': case '5': case '6': case '7':
418 c -= '0';
419 if (*cp >= '0' && *cp <= '7')
420 c = c * 8 + *cp++ - '0';
421 if (*cp >= '0' && *cp <= '7')
422 c = c * 8 + *cp++ - '0';
423 *cp2++ = c;
424 break;
425 case 'b':
426 *cp2++ = '\b';
427 break;
428 case 'f':
429 *cp2++ = '\f';
430 break;
431 case 'n':
432 *cp2++ = '\n';
433 break;
434 case 'r':
435 *cp2++ = '\r';
436 break;
437 case 't':
438 *cp2++ = '\t';
439 break;
440 case 'v':
441 *cp2++ = '\v';
442 break;
443 default:
444 *cp2++ = c;
445 }
446 else if (c == '^') {
447 c = *cp++;
448 if (c == '?')
449 *cp2++ = '\177';
450 /* null doesn't show up anyway */
451 else if ((c >= 'A' && c <= '_') ||
452 (c >= 'a' && c <= 'z'))
453 *cp2++ = c & 037;
454 else {
455 *cp2++ = '^';
456 cp--;
457 }
458 } else
459 *cp2++ = c;
460 } else if (c == '"' || c == '\'')
461 quotec = c;
462 else if (c == ' ' || c == '\t')
463 break;
464 else
465 *cp2++ = c;
466 }
467 *cp2 = '\0';
468 argv[argn++] = savestr(linebuf);
469 }
470 argv[argn] = NULL;
471 return argn;
472 }
473
474 /*
475 * scan out a single lexical item and return its token number,
476 * updating the string pointer passed **p. Also, store the value
477 * of the number or string scanned in lexnumber or lexstring as
478 * appropriate. In any event, store the scanned `thing' in lexstring.
479 */
480
481 struct lex {
482 char l_char;
483 char l_token;
484 } singles[] = {
485 { '$', TDOLLAR },
486 { '.', TDOT },
487 { '^', TUP },
488 { '*', TSTAR },
489 { '-', TDASH },
490 { '+', TPLUS },
491 { '(', TOPEN },
492 { ')', TCLOSE },
493 { 0, 0 }
494 };
495
496 int
497 scan(char **sp)
498 {
499 char *cp, *cp2;
500 int c;
501 struct lex *lp;
502 int quotec;
503
504 if (regretp >= 0) {
505 strcpy(lexstring, string_stack[regretp]);
506 lexnumber = numberstack[regretp];
507 return(regretstack[regretp--]);
508 }
509 cp = *sp;
510 cp2 = lexstring;
511 c = *cp++;
512
513 /*
514 * strip away leading white space.
515 */
516
517 while (c == ' ' || c == '\t')
518 c = *cp++;
519
520 /*
521 * If no characters remain, we are at end of line,
522 * so report that.
523 */
524
525 if (c == '\0') {
526 *sp = --cp;
527 return(TEOL);
528 }
529
530 /*
531 * If the leading character is a digit, scan
532 * the number and convert it on the fly.
533 * Return TNUMBER when done.
534 */
535
536 if (isdigit(c)) {
537 lexnumber = 0;
538 while (isdigit(c)) {
539 lexnumber = lexnumber*10 + c - '0';
540 *cp2++ = c;
541 c = *cp++;
542 }
543 *cp2 = '\0';
544 *sp = --cp;
545 return(TNUMBER);
546 }
547
548 /*
549 * Check for single character tokens; return such
550 * if found.
551 */
552
553 for (lp = &singles[0]; lp->l_char != 0; lp++)
554 if (c == lp->l_char) {
555 lexstring[0] = c;
556 lexstring[1] = '\0';
557 *sp = cp;
558 return(lp->l_token);
559 }
560
561 /*
562 * We've got a string! Copy all the characters
563 * of the string into lexstring, until we see
564 * a null, space, or tab.
565 * If the lead character is a " or ', save it
566 * and scan until you get another.
567 */
568
569 quotec = 0;
570 if (c == '\'' || c == '"') {
571 quotec = c;
572 c = *cp++;
573 }
574 while (c != '\0') {
575 if (c == quotec) {
576 cp++;
577 break;
578 }
579 if (quotec == 0 && (c == ' ' || c == '\t'))
580 break;
581 if (cp2 - lexstring < STRINGLEN-1)
582 *cp2++ = c;
583 c = *cp++;
584 }
585 if (quotec && c == 0) {
586 fprintf(stderr, "Missing %c\n", quotec);
587 return TERROR;
588 }
589 *sp = --cp;
590 *cp2 = '\0';
591 return(TSTRING);
592 }
593
594 /*
595 * Unscan the named token by pushing it onto the regret stack.
596 */
597 void
598 regret(int token)
599 {
600 if (++regretp >= REGDEP)
601 errx(1, "Too many regrets");
602 regretstack[regretp] = token;
603 lexstring[STRINGLEN-1] = '\0';
604 string_stack[regretp] = savestr(lexstring);
605 numberstack[regretp] = lexnumber;
606 }
607
608 /*
609 * Reset all the scanner global variables.
610 */
611 void
612 scaninit(void)
613 {
614 regretp = -1;
615 }
616
617 /*
618 * Find the first message whose flags & m == f and return
619 * its message number.
620 */
621 int
622 first(int f, int m)
623 {
624 struct message *mp;
625
626 if (msgCount == 0)
627 return 0;
628 f &= MDELETED;
629 m &= MDELETED;
630 for (mp = dot; mp < &message[msgCount]; mp++)
631 if ((mp->m_flag & m) == f)
632 return mp - message + 1;
633 for (mp = dot-1; mp >= &message[0]; mp--)
634 if ((mp->m_flag & m) == f)
635 return mp - message + 1;
636 return 0;
637 }
638
639 /*
640 * See if the passed name sent the passed message number. Return true
641 * if so.
642 */
643 int
644 matchsender(char *str, int mesg)
645 {
646 char *cp, *cp2, *backup;
647
648 if (!*str) /* null string matches nothing instead of everything */
649 return 0;
650 backup = cp2 = nameof(&message[mesg - 1], 0);
651 cp = str;
652 while (*cp2) {
653 if (*cp == 0)
654 return(1);
655 if (upcase(*cp++) != upcase(*cp2++)) {
656 cp2 = ++backup;
657 cp = str;
658 }
659 }
660 return(*cp == 0);
661 }
662
663 /*
664 * See if the passed name received the passed message number. Return true
665 * if so.
666 */
667
668 static char *to_fields[] = { "to", "cc", "bcc", 0 };
669
670 int
671 matchto(char *str, int mesg)
672 {
673 struct message *mp;
674 char *cp, *cp2, *backup, **to;
675
676 str++;
677
678 if (*str == 0) /* null string matches nothing instead of everything */
679 return(0);
680
681 mp = &message[mesg-1];
682
683 for (to = to_fields; *to; to++) {
684 cp = str;
685 cp2 = hfield(*to, mp);
686 if (cp2 != NULL) {
687 backup = cp2;
688 while (*cp2) {
689 if (*cp == 0)
690 return(1);
691 if (upcase(*cp++) != upcase(*cp2++)) {
692 cp2 = ++backup;
693 cp = str;
694 }
695 }
696 if (*cp == 0)
697 return(1);
698 }
699 }
700 return(0);
701 }
702
703 /*
704 * See if the given string matches inside the subject field of the
705 * given message. For the purpose of the scan, we ignore case differences.
706 * If it does, return true. The string search argument is assumed to
707 * have the form "/search-string." If it is of the form "/," we use the
708 * previous search string.
709 */
710
711 char lastscan[STRINGLEN];
712 int
713 matchsubj(char *str, int mesg)
714 {
715 struct message *mp;
716 char *cp, *cp2, *backup;
717
718 str++;
719 if (*str == '\0')
720 str = lastscan;
721 else {
722 strncpy(lastscan, str, STRINGLEN - 1);
723 lastscan[STRINGLEN - 1] = '\0' ;
724 }
725 mp = &message[mesg-1];
726
727 /*
728 * Now look, ignoring case, for the word in the string.
729 */
730
731 if (value("searchheaders") && (cp = strchr(str, ':'))) {
732 /* Check for special case "/To:" */
733 if (upcase(str[0]) == 'T' && upcase(str[1]) == 'O' &&
734 str[2] == ':')
735 return(matchto(cp, mesg));
736 *cp++ = '\0';
737 cp2 = hfield(*str ? str : "subject", mp);
738 cp[-1] = ':';
739 str = cp;
740 } else {
741 cp = str;
742 cp2 = hfield("subject", mp);
743 }
744 if (cp2 == NULL)
745 return(0);
746 backup = cp2;
747 while (*cp2) {
748 if (*cp == 0)
749 return(1);
750 if (upcase(*cp++) != upcase(*cp2++)) {
751 cp2 = ++backup;
752 cp = str;
753 }
754 }
755 return(*cp == 0);
756 }
757
758 /*
759 * Mark the named message by setting its mark bit.
760 */
761 void
762 mark(int mesg)
763 {
764 int i;
765
766 i = mesg;
767 if (i < 1 || i > msgCount)
768 errx(1, "Bad message number to mark");
769 message[i-1].m_flag |= MMARK;
770 }
771
772 /*
773 * Unmark the named message.
774 */
775 void
776 unmark(int mesg)
777 {
778 int i;
779
780 i = mesg;
781 if (i < 1 || i > msgCount)
782 errx(1, "Bad message number to unmark");
783 message[i-1].m_flag &= ~MMARK;
784 }
785
786 /*
787 * Return the message number corresponding to the passed meta character.
788 */
789 int
790 metamess(int meta, int f)
791 {
792 int c, m;
793 struct message *mp;
794
795 c = meta;
796 switch (c) {
797 case '^':
798 /*
799 * First 'good' message left.
800 */
801 for (mp = &message[0]; mp < &message[msgCount]; mp++)
802 if ((mp->m_flag & MDELETED) == f)
803 return(mp - &message[0] + 1);
804 printf("No applicable messages\n");
805 return(-1);
806
807 case '$':
808 /*
809 * Last 'good message left.
810 */
811 for (mp = &message[msgCount-1]; mp >= &message[0]; mp--)
812 if ((mp->m_flag & MDELETED) == f)
813 return(mp - &message[0] + 1);
814 printf("No applicable messages\n");
815 return(-1);
816
817 case '.':
818 /*
819 * Current message.
820 */
821 m = dot - &message[0] + 1;
822 if ((dot->m_flag & MDELETED) != f) {
823 printf("%d: Inappropriate message\n", m);
824 return(-1);
825 }
826 return(m);
827
828 default:
829 printf("Unknown metachar (%c)\n", c);
830 return(-1);
831 }
832 }
833