Add the DragonFly cvs id and perform general cleanups on cvs/rcs/sccs ids. Most
[dragonfly.git] / usr.bin / mail / list.c
1 /*
2  * Copyright (c) 1980, 1993
3  *      The Regents of the University of California.  All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. All advertising materials mentioning features or use of this software
14  *    must display the following acknowledgement:
15  *      This product includes software developed by the University of
16  *      California, Berkeley and its contributors.
17  * 4. Neither the name of the University nor the names of its contributors
18  *    may be used to endorse or promote products derived from this software
19  *    without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  *
33  * @(#)list.c   8.4 (Berkeley) 5/1/95
34  * $FreeBSD: src/usr.bin/mail/list.c,v 1.2.12.3 2003/01/06 05:46:03 mikeh Exp $
35  * $DragonFly: src/usr.bin/mail/list.c,v 1.2 2003/06/17 04:29:28 dillon Exp $
36  */
37
38 #include "rcv.h"
39 #include <ctype.h>
40 #include "extern.h"
41
42 /*
43  * Mail -- a mail program
44  *
45  * Message list handling.
46  */
47
48 /*
49  * Convert the user string of message numbers and
50  * store the numbers into vector.
51  *
52  * Returns the count of messages picked up or -1 on error.
53  */
54 int
55 getmsglist(buf, vector, flags)
56         char *buf;
57         int *vector, flags;
58 {
59         int *ip;
60         struct message *mp;
61
62         if (msgCount == 0) {
63                 *vector = 0;
64                 return (0);
65         }
66         if (markall(buf, flags) < 0)
67                 return (-1);
68         ip = vector;
69         for (mp = &message[0]; mp < &message[msgCount]; mp++)
70                 if (mp->m_flag & MMARK)
71                         *ip++ = mp - &message[0] + 1;
72         *ip = 0;
73         return (ip - vector);
74 }
75
76 /*
77  * Mark all messages that the user wanted from the command
78  * line in the message structure.  Return 0 on success, -1
79  * on error.
80  */
81
82 /*
83  * Bit values for colon modifiers.
84  */
85
86 #define CMNEW           01              /* New messages */
87 #define CMOLD           02              /* Old messages */
88 #define CMUNREAD        04              /* Unread messages */
89 #define CMDELETED       010             /* Deleted messages */
90 #define CMREAD          020             /* Read messages */
91
92 /*
93  * The following table describes the letters which can follow
94  * the colon and gives the corresponding modifier bit.
95  */
96
97 struct coltab {
98         char    co_char;                /* What to find past : */
99         int     co_bit;                 /* Associated modifier bit */
100         int     co_mask;                /* m_status bits to mask */
101         int     co_equal;               /* ... must equal this */
102 } coltab[] = {
103         { 'n',          CMNEW,          MNEW,           MNEW    },
104         { 'o',          CMOLD,          MNEW,           0       },
105         { 'u',          CMUNREAD,       MREAD,          0       },
106         { 'd',          CMDELETED,      MDELETED,       MDELETED},
107         { 'r',          CMREAD,         MREAD,          MREAD   },
108         { 0,            0,              0,              0       }
109 };
110
111 static  int     lastcolmod;
112
113 int
114 markall(buf, f)
115         char buf[];
116         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 (matchfield(*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 != '\0'; 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 != '\0'; 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(col)
347         int col;
348 {
349         struct coltab *colp;
350
351         if (col == 0)
352                 return (lastcolmod);
353         for (colp = &coltab[0]; colp->co_char != '\0'; colp++)
354                 if (colp->co_char == col)
355                         return (colp->co_bit);
356         return (0);
357 }
358
359 /*
360  * Check the passed message number for legality and proper flags.
361  * If f is MDELETED, then either kind will do.  Otherwise, the message
362  * has to be undeleted.
363  */
364 int
365 check(mesg, f)
366         int mesg, f;
367 {
368         struct message *mp;
369
370         if (mesg < 1 || mesg > msgCount) {
371                 printf("%d: Invalid message number\n", mesg);
372                 return (-1);
373         }
374         mp = &message[mesg-1];
375         if (f != MDELETED && (mp->m_flag & MDELETED) != 0) {
376                 printf("%d: Inappropriate message\n", mesg);
377                 return (-1);
378         }
379         return (0);
380 }
381
382 /*
383  * Scan out the list of string arguments, shell style
384  * for a RAWLIST.
385  */
386 int
387 getrawlist(line, argv, argc)
388         char line[];
389         char **argv;
390         int  argc;
391 {
392         char c, *cp, *cp2, quotec;
393         int argn;
394         char *linebuf;
395         size_t linebufsize = BUFSIZ;
396
397         if ((linebuf = malloc(linebufsize)) == NULL)
398                 err(1, "Out of memory");
399
400         argn = 0;
401         cp = line;
402         for (;;) {
403                 for (; *cp == ' ' || *cp == '\t'; cp++)
404                         ;
405                 if (*cp == '\0')
406                         break;
407                 if (argn >= argc - 1) {
408                         printf(
409                         "Too many elements in the list; excess discarded.\n");
410                         break;
411                 }
412                 cp2 = linebuf;
413                 quotec = '\0';
414                 while ((c = *cp) != '\0') {
415                         /* Allocate more space if necessary */
416                         if (cp2 - linebuf == linebufsize - 1) {
417                                 linebufsize += BUFSIZ;
418                                 if ((linebuf = realloc(linebuf, linebufsize)) == NULL)
419                                         err(1, "Out of memory");
420                                 cp2 = linebuf + linebufsize - BUFSIZ - 1;
421                         }
422                         cp++;
423                         if (quotec != '\0') {
424                                 if (c == quotec)
425                                         quotec = '\0';
426                                 else if (c == '\\')
427                                         switch (c = *cp++) {
428                                         case '\0':
429                                                 *cp2++ = '\\';
430                                                 cp--;
431                                                 break;
432                                         case '0': case '1': case '2': case '3':
433                                         case '4': case '5': case '6': case '7':
434                                                 c -= '0';
435                                                 if (*cp >= '0' && *cp <= '7')
436                                                         c = c * 8 + *cp++ - '0';
437                                                 if (*cp >= '0' && *cp <= '7')
438                                                         c = c * 8 + *cp++ - '0';
439                                                 *cp2++ = c;
440                                                 break;
441                                         case 'b':
442                                                 *cp2++ = '\b';
443                                                 break;
444                                         case 'f':
445                                                 *cp2++ = '\f';
446                                                 break;
447                                         case 'n':
448                                                 *cp2++ = '\n';
449                                                 break;
450                                         case 'r':
451                                                 *cp2++ = '\r';
452                                                 break;
453                                         case 't':
454                                                 *cp2++ = '\t';
455                                                 break;
456                                         case 'v':
457                                                 *cp2++ = '\v';
458                                                 break;
459                                         default:
460                                                 *cp2++ = c;
461                                         }
462                                 else if (c == '^') {
463                                         c = *cp++;
464                                         if (c == '?')
465                                                 *cp2++ = '\177';
466                                         /* null doesn't show up anyway */
467                                         else if ((c >= 'A' && c <= '_') ||
468                                             (c >= 'a' && c <= 'z'))
469                                                 *cp2++ = c & 037;
470                                         else {
471                                                 *cp2++ = '^';
472                                                 cp--;
473                                         }
474                                 } else
475                                         *cp2++ = c;
476                         } else if (c == '"' || c == '\'')
477                                 quotec = c;
478                         else if (c == ' ' || c == '\t')
479                                 break;
480                         else
481                                 *cp2++ = c;
482                 }
483                 *cp2 = '\0';
484                 argv[argn++] = savestr(linebuf);
485         }
486         argv[argn] = NULL;
487         (void)free(linebuf);
488         return (argn);
489 }
490
491 /*
492  * scan out a single lexical item and return its token number,
493  * updating the string pointer passed **p.  Also, store the value
494  * of the number or string scanned in lexnumber or lexstring as
495  * appropriate.  In any event, store the scanned `thing' in lexstring.
496  */
497
498 struct lex {
499         char    l_char;
500         char    l_token;
501 } singles[] = {
502         { '$',  TDOLLAR },
503         { '.',  TDOT    },
504         { '^',  TUP     },
505         { '*',  TSTAR   },
506         { '-',  TDASH   },
507         { '+',  TPLUS   },
508         { '(',  TOPEN   },
509         { ')',  TCLOSE  },
510         { 0,    0       }
511 };
512
513 int
514 scan(sp)
515         char **sp;
516 {
517         char *cp, *cp2;
518         int c;
519         struct lex *lp;
520         int quotec;
521
522         if (regretp >= 0) {
523                 strcpy(lexstring, string_stack[regretp]);
524                 lexnumber = numberstack[regretp];
525                 return (regretstack[regretp--]);
526         }
527         cp = *sp;
528         cp2 = lexstring;
529         c = *cp++;
530
531         /*
532          * strip away leading white space.
533          */
534
535         while (c == ' ' || c == '\t')
536                 c = *cp++;
537
538         /*
539          * If no characters remain, we are at end of line,
540          * so report that.
541          */
542
543         if (c == '\0') {
544                 *sp = --cp;
545                 return (TEOL);
546         }
547
548         /*
549          * If the leading character is a digit, scan
550          * the number and convert it on the fly.
551          * Return TNUMBER when done.
552          */
553
554         if (isdigit((unsigned char)c)) {
555                 lexnumber = 0;
556                 while (isdigit((unsigned char)c)) {
557                         lexnumber = lexnumber*10 + c - '0';
558                         *cp2++ = c;
559                         c = *cp++;
560                 }
561                 *cp2 = '\0';
562                 *sp = --cp;
563                 return (TNUMBER);
564         }
565
566         /*
567          * Check for single character tokens; return such
568          * if found.
569          */
570
571         for (lp = &singles[0]; lp->l_char != '\0'; lp++)
572                 if (c == lp->l_char) {
573                         lexstring[0] = c;
574                         lexstring[1] = '\0';
575                         *sp = cp;
576                         return (lp->l_token);
577                 }
578
579         /*
580          * We've got a string!  Copy all the characters
581          * of the string into lexstring, until we see
582          * a null, space, or tab.
583          * If the lead character is a " or ', save it
584          * and scan until you get another.
585          */
586
587         quotec = 0;
588         if (c == '\'' || c == '"') {
589                 quotec = c;
590                 c = *cp++;
591         }
592         while (c != '\0') {
593                 if (c == quotec) {
594                         cp++;
595                         break;
596                 }
597                 if (quotec == 0 && (c == ' ' || c == '\t'))
598                         break;
599                 if (cp2 - lexstring < STRINGLEN-1)
600                         *cp2++ = c;
601                 c = *cp++;
602         }
603         if (quotec && c == '\0') {
604                 fprintf(stderr, "Missing %c\n", quotec);
605                 return (TERROR);
606         }
607         *sp = --cp;
608         *cp2 = '\0';
609         return (TSTRING);
610 }
611
612 /*
613  * Unscan the named token by pushing it onto the regret stack.
614  */
615 void
616 regret(token)
617         int token;
618 {
619         if (++regretp >= REGDEP)
620                 errx(1, "Too many regrets");
621         regretstack[regretp] = token;
622         lexstring[STRINGLEN-1] = '\0';
623         string_stack[regretp] = savestr(lexstring);
624         numberstack[regretp] = lexnumber;
625 }
626
627 /*
628  * Reset all the scanner global variables.
629  */
630 void
631 scaninit()
632 {
633         regretp = -1;
634 }
635
636 /*
637  * Find the first message whose flags & m == f  and return
638  * its message number.
639  */
640 int
641 first(f, m)
642         int f, m;
643 {
644         struct message *mp;
645
646         if (msgCount == 0)
647                 return (0);
648         f &= MDELETED;
649         m &= MDELETED;
650         for (mp = dot; mp < &message[msgCount]; mp++)
651                 if ((mp->m_flag & m) == f)
652                         return (mp - message + 1);
653         for (mp = dot-1; mp >= &message[0]; mp--)
654                 if ((mp->m_flag & m) == f)
655                         return (mp - message + 1);
656         return (0);
657 }
658
659 /*
660  * See if the passed name sent the passed message number.  Return true
661  * if so.
662  */
663 int
664 matchsender(str, mesg)
665         char *str;
666         int mesg;
667 {
668         char *cp;
669
670         /* null string matches nothing instead of everything */
671         if (*str == '\0')
672                 return (0);
673
674         cp = nameof(&message[mesg - 1], 0);
675         return (strcasestr(cp, str) != NULL);
676 }
677
678 /*
679  * See if the passed name received the passed message number.  Return true
680  * if so.
681  */
682
683 static char *to_fields[] = { "to", "cc", "bcc", NULL };
684
685 int
686 matchto(str, mesg)
687         char *str;
688         int mesg;
689 {
690         struct message *mp;
691         char *cp, **to;
692
693         str++;
694
695         /* null string matches nothing instead of everything */
696         if (*str == '\0')
697                 return (0);
698
699         mp = &message[mesg - 1];
700
701         for (to = to_fields; *to != NULL; to++) {
702                 cp = hfield(*to, mp);
703                 if (cp != NULL && strcasestr(cp, str) != NULL)
704                         return (1);
705         }
706         return (0);
707 }
708
709 /*
710  * See if the given substring is contained within the specified field. If
711  * 'searchheaders' is set, then the form '/x:y' will be accepted and matches
712  * any message with the substring 'y' in field 'x'. If 'x' is omitted or
713  * 'searchheaders' is not set, then the search matches any messages
714  * with the substring 'y' in the 'Subject'. The search is case insensitive.
715  *
716  * The form '/to:y' is a special case, and will match all messages
717  * containing the substring 'y' in the 'To', 'Cc', or 'Bcc' header
718  * fields. The search for 'to' is case sensitive, so that '/To:y' can
719  * be used to limit the search to just the 'To' field.
720  */
721
722 char lastscan[STRINGLEN];
723 int
724 matchfield(str, mesg)
725         char *str;
726         int mesg;
727 {
728         struct message *mp;
729         char *cp, *cp2;
730
731         str++;
732         if (*str == '\0')
733                 str = lastscan;
734         else
735                 strlcpy(lastscan, str, sizeof(lastscan));
736         mp = &message[mesg-1];
737
738         /*
739          * Now look, ignoring case, for the word in the string.
740          */
741
742         if (value("searchheaders") && (cp = strchr(str, ':')) != NULL) {
743                 /* Check for special case "/to:" */
744                 if (strncmp(str, "to:", 3) == 0)
745                         return (matchto(cp, mesg));
746                 *cp++ = '\0';
747                 cp2 = hfield(*str != '\0' ? str : "subject", mp);
748                 cp[-1] = ':';
749                 str = cp;
750                 cp = cp2;
751         } else
752                 cp = hfield("subject", mp);
753
754         if (cp == NULL)
755                 return (0);
756
757         return (strcasestr(cp, str) != NULL);
758 }
759
760 /*
761  * Mark the named message by setting its mark bit.
762  */
763 void
764 mark(mesg)
765         int mesg;
766 {
767         int i;
768
769         i = mesg;
770         if (i < 1 || i > msgCount)
771                 errx(1, "Bad message number to mark");
772         message[i-1].m_flag |= MMARK;
773 }
774
775 /*
776  * Unmark the named message.
777  */
778 void
779 unmark(mesg)
780         int mesg;
781 {
782         int i;
783
784         i = mesg;
785         if (i < 1 || i > msgCount)
786                 errx(1, "Bad message number to unmark");
787         message[i-1].m_flag &= ~MMARK;
788 }
789
790 /*
791  * Return the message number corresponding to the passed meta character.
792  */
793 int
794 metamess(meta, f)
795         int meta, f;
796 {
797         int c, m;
798         struct message *mp;
799
800         c = meta;
801         switch (c) {
802         case '^':
803                 /*
804                  * First 'good' message left.
805                  */
806                 for (mp = &message[0]; mp < &message[msgCount]; mp++)
807                         if ((mp->m_flag & MDELETED) == f)
808                                 return (mp - &message[0] + 1);
809                 printf("No applicable messages\n");
810                 return (-1);
811
812         case '$':
813                 /*
814                  * Last 'good message left.
815                  */
816                 for (mp = &message[msgCount-1]; mp >= &message[0]; mp--)
817                         if ((mp->m_flag & MDELETED) == f)
818                                 return (mp - &message[0] + 1);
819                 printf("No applicable messages\n");
820                 return (-1);
821
822         case '.':
823                 /*
824                  * Current message.
825                  */
826                 m = dot - &message[0] + 1;
827                 if ((dot->m_flag & MDELETED) != f) {
828                         printf("%d: Inappropriate message\n", m);
829                         return (-1);
830                 }
831                 return (m);
832
833         default:
834                 printf("Unknown metachar (%c)\n", c);
835                 return (-1);
836         }
837 }