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