list.c revision 1.15 1 /* $NetBSD: list.c,v 1.15 2005/07/19 23:07:10 christos 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.15 2005/07/19 23:07:10 christos 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 (void)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 (void)printf("Non-numeric second argument\n");
169 return(-1);
170 }
171 i = valdot;
172 do {
173 i++;
174 if (i > msgCount) {
175 (void)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 (void)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 (void)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 (void)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 (void)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 (void)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 (void)printf("No applicable messages from {%s",
299 namelist[0]);
300 for (np = &namelist[1]; *np != NULL; np++)
301 (void)printf(", %s", *np);
302 (void)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 (void)printf("No messages satisfy");
331 for (colp = &coltab[0]; colp->co_char; colp++)
332 if (colp->co_bit & colmod)
333 (void)printf(" :%c", colp->co_char);
334 (void)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 (void)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 (void)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(const char line[], char **argv, int argc)
386 {
387 char c, *cp2, quotec;
388 const char *cp;
389 int argn;
390 char linebuf[BUFSIZ];
391
392 argn = 0;
393 cp = line;
394 for (;;) {
395 for (; *cp == ' ' || *cp == '\t'; cp++)
396 ;
397 if (*cp == '\0')
398 break;
399 if (argn >= argc - 1) {
400 (void)printf(
401 "Too many elements in the list; excess discarded.\n");
402 break;
403 }
404 cp2 = linebuf;
405 quotec = '\0';
406 while ((c = *cp) != '\0') {
407 cp++;
408 if (quotec != '\0') {
409 if (c == quotec)
410 quotec = '\0';
411 else if (c == '\\')
412 switch (c = *cp++) {
413 case '\0':
414 *cp2++ = '\\';
415 cp--;
416 break;
417 case '0': case '1': case '2': case '3':
418 case '4': case '5': case '6': case '7':
419 c -= '0';
420 if (*cp >= '0' && *cp <= '7')
421 c = c * 8 + *cp++ - '0';
422 if (*cp >= '0' && *cp <= '7')
423 c = c * 8 + *cp++ - '0';
424 *cp2++ = c;
425 break;
426 case 'b':
427 *cp2++ = '\b';
428 break;
429 case 'f':
430 *cp2++ = '\f';
431 break;
432 case 'n':
433 *cp2++ = '\n';
434 break;
435 case 'r':
436 *cp2++ = '\r';
437 break;
438 case 't':
439 *cp2++ = '\t';
440 break;
441 case 'v':
442 *cp2++ = '\v';
443 break;
444 default:
445 *cp2++ = c;
446 }
447 else if (c == '^') {
448 c = *cp++;
449 if (c == '?')
450 *cp2++ = '\177';
451 /* null doesn't show up anyway */
452 else if ((c >= 'A' && c <= '_') ||
453 (c >= 'a' && c <= 'z'))
454 *cp2++ = c & 037;
455 else {
456 *cp2++ = '^';
457 cp--;
458 }
459 } else
460 *cp2++ = c;
461 } else if (c == '"' || c == '\'')
462 quotec = c;
463 else if (c == ' ' || c == '\t')
464 break;
465 else
466 *cp2++ = c;
467 }
468 *cp2 = '\0';
469 argv[argn++] = savestr(linebuf);
470 }
471 argv[argn] = NULL;
472 return argn;
473 }
474
475 /*
476 * scan out a single lexical item and return its token number,
477 * updating the string pointer passed **p. Also, store the value
478 * of the number or string scanned in lexnumber or lexstring as
479 * appropriate. In any event, store the scanned `thing' in lexstring.
480 */
481
482 struct lex {
483 char l_char;
484 char l_token;
485 } singles[] = {
486 { '$', TDOLLAR },
487 { '.', TDOT },
488 { '^', TUP },
489 { '*', TSTAR },
490 { '-', TDASH },
491 { '+', TPLUS },
492 { '(', TOPEN },
493 { ')', TCLOSE },
494 { 0, 0 }
495 };
496
497 int
498 scan(char **sp)
499 {
500 char *cp, *cp2;
501 int c;
502 struct lex *lp;
503 int quotec;
504
505 if (regretp >= 0) {
506 (void)strcpy(lexstring, string_stack[regretp]);
507 lexnumber = numberstack[regretp];
508 return(regretstack[regretp--]);
509 }
510 cp = *sp;
511 cp2 = lexstring;
512 c = *cp++;
513
514 /*
515 * strip away leading white space.
516 */
517
518 while (c == ' ' || c == '\t')
519 c = *cp++;
520
521 /*
522 * If no characters remain, we are at end of line,
523 * so report that.
524 */
525
526 if (c == '\0') {
527 *sp = --cp;
528 return(TEOL);
529 }
530
531 /*
532 * If the leading character is a digit, scan
533 * the number and convert it on the fly.
534 * Return TNUMBER when done.
535 */
536
537 if (isdigit(c)) {
538 lexnumber = 0;
539 while (isdigit(c)) {
540 lexnumber = lexnumber*10 + c - '0';
541 *cp2++ = c;
542 c = *cp++;
543 }
544 *cp2 = '\0';
545 *sp = --cp;
546 return(TNUMBER);
547 }
548
549 /*
550 * Check for single character tokens; return such
551 * if found.
552 */
553
554 for (lp = &singles[0]; lp->l_char != 0; lp++)
555 if (c == lp->l_char) {
556 lexstring[0] = c;
557 lexstring[1] = '\0';
558 *sp = cp;
559 return(lp->l_token);
560 }
561
562 /*
563 * We've got a string! Copy all the characters
564 * of the string into lexstring, until we see
565 * a null, space, or tab.
566 * If the lead character is a " or ', save it
567 * and scan until you get another.
568 */
569
570 quotec = 0;
571 if (c == '\'' || c == '"') {
572 quotec = c;
573 c = *cp++;
574 }
575 while (c != '\0') {
576 if (c == quotec) {
577 cp++;
578 break;
579 }
580 if (quotec == 0 && (c == ' ' || c == '\t'))
581 break;
582 if (cp2 - lexstring < STRINGLEN-1)
583 *cp2++ = c;
584 c = *cp++;
585 }
586 if (quotec && c == 0) {
587 (void)fprintf(stderr, "Missing %c\n", quotec);
588 return TERROR;
589 }
590 *sp = --cp;
591 *cp2 = '\0';
592 return(TSTRING);
593 }
594
595 /*
596 * Unscan the named token by pushing it onto the regret stack.
597 */
598 void
599 regret(int token)
600 {
601 if (++regretp >= REGDEP)
602 errx(1, "Too many regrets");
603 regretstack[regretp] = token;
604 lexstring[STRINGLEN-1] = '\0';
605 string_stack[regretp] = savestr(lexstring);
606 numberstack[regretp] = lexnumber;
607 }
608
609 /*
610 * Reset all the scanner global variables.
611 */
612 void
613 scaninit(void)
614 {
615 regretp = -1;
616 }
617
618 /*
619 * Find the first message whose flags & m == f and return
620 * its message number.
621 */
622 int
623 first(int f, int m)
624 {
625 struct message *mp;
626
627 if (msgCount == 0)
628 return 0;
629 f &= MDELETED;
630 m &= MDELETED;
631 for (mp = dot; mp < &message[msgCount]; mp++)
632 if ((mp->m_flag & m) == f)
633 return mp - message + 1;
634 for (mp = dot-1; mp >= &message[0]; mp--)
635 if ((mp->m_flag & m) == f)
636 return mp - message + 1;
637 return 0;
638 }
639
640 /*
641 * See if the passed name sent the passed message number. Return true
642 * if so.
643 */
644 int
645 matchsender(char *str, int mesg)
646 {
647 char *cp, *cp2, *backup;
648
649 if (!*str) /* null string matches nothing instead of everything */
650 return 0;
651 backup = cp2 = nameof(&message[mesg - 1], 0);
652 cp = str;
653 while (*cp2) {
654 if (*cp == 0)
655 return(1);
656 if (upcase(*cp++) != upcase(*cp2++)) {
657 cp2 = ++backup;
658 cp = str;
659 }
660 }
661 return(*cp == 0);
662 }
663
664 /*
665 * See if the passed name received the passed message number. Return true
666 * if so.
667 */
668
669 static const char *to_fields[] = { "to", "cc", "bcc", 0 };
670
671 int
672 matchto(char *str, int mesg)
673 {
674 struct message *mp;
675 char *cp, *cp2, *backup;
676 const char **to;
677
678 str++;
679
680 if (*str == 0) /* null string matches nothing instead of everything */
681 return(0);
682
683 mp = &message[mesg-1];
684
685 for (to = to_fields; *to; to++) {
686 cp = str;
687 cp2 = hfield(*to, mp);
688 if (cp2 != NULL) {
689 backup = cp2;
690 while (*cp2) {
691 if (*cp == 0)
692 return(1);
693 if (upcase(*cp++) != upcase(*cp2++)) {
694 cp2 = ++backup;
695 cp = str;
696 }
697 }
698 if (*cp == 0)
699 return(1);
700 }
701 }
702 return(0);
703 }
704
705 /*
706 * See if the given string matches inside the subject field of the
707 * given message. For the purpose of the scan, we ignore case differences.
708 * If it does, return true. The string search argument is assumed to
709 * have the form "/search-string." If it is of the form "/," we use the
710 * previous search string.
711 */
712
713 char lastscan[STRINGLEN];
714 int
715 matchsubj(char *str, int mesg)
716 {
717 struct message *mp;
718 char *cp, *cp2, *backup;
719
720 str++;
721 if (*str == '\0')
722 str = lastscan;
723 else {
724 (void)strncpy(lastscan, str, STRINGLEN - 1);
725 lastscan[STRINGLEN - 1] = '\0' ;
726 }
727 mp = &message[mesg-1];
728
729 /*
730 * Now look, ignoring case, for the word in the string.
731 */
732
733 if (value("searchheaders") && (cp = strchr(str, ':'))) {
734 /* Check for special case "/To:" */
735 if (upcase(str[0]) == 'T' && upcase(str[1]) == 'O' &&
736 str[2] == ':')
737 return(matchto(cp, mesg));
738 *cp++ = '\0';
739 cp2 = hfield(*str ? str : "subject", mp);
740 cp[-1] = ':';
741 str = cp;
742 } else {
743 cp = str;
744 cp2 = hfield("subject", mp);
745 }
746 if (cp2 == NULL)
747 return(0);
748 backup = cp2;
749 while (*cp2) {
750 if (*cp == 0)
751 return(1);
752 if (upcase(*cp++) != upcase(*cp2++)) {
753 cp2 = ++backup;
754 cp = str;
755 }
756 }
757 return(*cp == 0);
758 }
759
760 /*
761 * Mark the named message by setting its mark bit.
762 */
763 void
764 mark(int mesg)
765 {
766 int i;
767
768 i = mesg;
769 if (i < 1 || i > msgCount)
770 errx(1, "Bad message number to mark");
771 message[i-1].m_flag |= MMARK;
772 }
773
774 /*
775 * Unmark the named message.
776 */
777 void
778 unmark(int mesg)
779 {
780 int i;
781
782 i = mesg;
783 if (i < 1 || i > msgCount)
784 errx(1, "Bad message number to unmark");
785 message[i-1].m_flag &= ~MMARK;
786 }
787
788 /*
789 * Return the message number corresponding to the passed meta character.
790 */
791 int
792 metamess(int meta, int f)
793 {
794 int c, m;
795 struct message *mp;
796
797 c = meta;
798 switch (c) {
799 case '^':
800 /*
801 * First 'good' message left.
802 */
803 for (mp = &message[0]; mp < &message[msgCount]; mp++)
804 if ((mp->m_flag & MDELETED) == f)
805 return(mp - &message[0] + 1);
806 (void)printf("No applicable messages\n");
807 return(-1);
808
809 case '$':
810 /*
811 * Last 'good message left.
812 */
813 for (mp = &message[msgCount-1]; mp >= &message[0]; mp--)
814 if ((mp->m_flag & MDELETED) == f)
815 return(mp - &message[0] + 1);
816 (void)printf("No applicable messages\n");
817 return(-1);
818
819 case '.':
820 /*
821 * Current message.
822 */
823 m = dot - &message[0] + 1;
824 if ((dot->m_flag & MDELETED) != f) {
825 (void)printf("%d: Inappropriate message\n", m);
826 return(-1);
827 }
828 return(m);
829
830 default:
831 (void)printf("Unknown metachar (%c)\n", c);
832 return(-1);
833 }
834 }
835