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