skipre(): do not AND, lowerconv() takes care
[s-mailx.git] / cmd2.c
blobab5ec89a26d349564b2fd68a02ddf07662983c27
1 /*
2 * S-nail - a mail user agent derived from Berkeley Mail.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 Steffen "Daode" Nurpmeso.
6 */
7 /*
8 * Copyright (c) 1980, 1993
9 * The Regents of the University of California. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. All advertising materials mentioning features or use of this software
20 * must display the following acknowledgement:
21 * This product includes software developed by the University of
22 * California, Berkeley and its contributors.
23 * 4. Neither the name of the University nor the names of its contributors
24 * may be used to endorse or promote products derived from this software
25 * without specific prior written permission.
27 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
28 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
31 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37 * SUCH DAMAGE.
40 #include "rcv.h"
41 #include "extern.h"
42 #include <sys/wait.h>
43 #include <sys/stat.h>
44 #include <unistd.h>
47 * Mail -- a mail program
49 * More user commands.
52 static int save1(char *str, int mark, char *cmd, struct ignoretab *ignore,
53 int convert, int sender_record, int domove);
54 static char * snarf(char *linebuf, int *flag, int usembox);
55 static int delm(int *msgvec);
56 #ifdef HAVE_ASSERTS
57 static void clob1(int n);
58 #endif
59 static int ignore1(char **list, struct ignoretab *tab, char *which);
60 static int igshow(struct ignoretab *tab, char *which);
61 static int igcomp(const void *l, const void *r);
62 static void unignore_one(const char *name, struct ignoretab *tab);
63 static int unignore1(char **list, struct ignoretab *tab, char *which);
66 * If any arguments were given, go to the next applicable argument
67 * following dot, otherwise, go to the next applicable message.
68 * If given as first command with no arguments, print first message.
70 int
71 next(void *v)
73 int *msgvec = v;
74 struct message *mp;
75 int *ip, *ip2;
76 int list[2], mdot;
78 if (*msgvec != 0) {
81 * If some messages were supplied, find the
82 * first applicable one following dot using
83 * wrap around.
86 mdot = dot - &message[0] + 1;
89 * Find the first message in the supplied
90 * message list which follows dot.
93 for (ip = msgvec; *ip != 0; ip++) {
94 #ifdef _CRAY
96 * Work around an optimizer bug in Cray Standard C Version 4.0.3 (057126).
97 * Otherwise, SIGFPE is received when mb.mb_threaded != 0.
99 #pragma _CRI suppress ip
100 #endif /* _CRAY */
101 if (mb.mb_threaded ? message[*ip-1].m_threadpos >
102 dot->m_threadpos :
103 *ip > mdot)
104 break;
106 if (*ip == 0)
107 ip = msgvec;
108 ip2 = ip;
109 do {
110 mp = &message[*ip2 - 1];
111 if ((mp->m_flag & (MDELETED|MHIDDEN)) == 0) {
112 setdot(mp);
113 goto hitit;
115 if (*ip2 != 0)
116 ip2++;
117 if (*ip2 == 0)
118 ip2 = msgvec;
119 } while (ip2 != ip);
120 printf(catgets(catd, CATSET, 21, "No messages applicable\n"));
121 return(1);
125 * If this is the first command, select message 1.
126 * Note that this must exist for us to get here at all.
129 if (!sawcom) {
130 if (msgCount == 0)
131 goto ateof;
132 goto hitit;
136 * Just find the next good message after dot, no
137 * wraparound.
140 if (mb.mb_threaded == 0) {
141 for (mp = dot + did_print_dot; mp < &message[msgCount]; mp++)
142 if ((mp->m_flag & (MDELETED|MSAVED|MHIDDEN|MKILL)) == 0)
143 break;
144 } else {
145 mp = dot;
146 if (did_print_dot)
147 mp = next_in_thread(mp);
148 while (mp && mp->m_flag & (MDELETED|MSAVED|MHIDDEN|MKILL))
149 mp = next_in_thread(mp);
151 if (mp == NULL || mp >= &message[msgCount]) {
152 ateof:
153 printf(catgets(catd, CATSET, 22, "At EOF\n"));
154 return(0);
156 setdot(mp);
157 hitit:
159 * Print dot.
162 list[0] = dot - &message[0] + 1;
163 list[1] = 0;
164 return(type(list));
168 * Save a message in a file. Mark the message as saved
169 * so we can discard when the user quits.
171 int
172 save(void *v)
174 char *str = v;
176 return save1(str, 1, "save", saveignore, SEND_MBOX, 0, 0);
179 int
180 Save(void *v)
182 char *str = v;
184 return save1(str, 1, "save", saveignore, SEND_MBOX, 1, 0);
188 * Copy a message to a file without affected its saved-ness
190 int
191 copycmd(void *v)
193 char *str = v;
195 return save1(str, 0, "copy", saveignore, SEND_MBOX, 0, 0);
198 int
199 Copycmd(void *v)
201 char *str = v;
203 return save1(str, 0, "copy", saveignore, SEND_MBOX, 1, 0);
207 * Move a message to a file.
209 int
210 cmove(void *v)
212 char *str = v;
214 return save1(str, 0, "move", saveignore, SEND_MBOX, 0, 1);
217 int
218 cMove(void *v)
220 char *str = v;
222 return save1(str, 0, "move", saveignore, SEND_MBOX, 1, 1);
226 * Decrypt and copy a message to a file.
228 int
229 cdecrypt(void *v)
231 char *str = v;
233 return save1(str, 0, "decrypt", saveignore, SEND_DECRYPT, 0, 0);
236 int
237 cDecrypt(void *v)
239 char *str = v;
241 return save1(str, 0, "decrypt", saveignore, SEND_DECRYPT, 1, 0);
245 * Save/copy the indicated messages at the end of the passed file name.
246 * If mark is true, mark the message "saved."
248 static int
249 save1(char *str, int mark, char *cmd, struct ignoretab *ignore,
250 int convert, int sender_record, int domove)
252 struct stat st;
253 int *ip;
254 struct message *mp;
255 char *file = NULL, *disp = "";
256 int f, *msgvec;
257 FILE *obuf;
258 int newfile = 0;
259 char *cp, *cq;
260 off_t mstats[2], tstats[2];
261 int compressed = 0;
262 enum protocol prot;
263 int success = 1, last = 0;
265 /*LINTED*/
266 msgvec = (int *)salloc((msgCount + 2) * sizeof *msgvec);
267 if (sender_record) {
268 for (cp = str; *cp && blankchar(*cp & 0377); cp++);
269 f = (*cp != '\0');
270 } else {
271 if ((file = snarf(str, &f, convert != SEND_TOFILE)) == NULL)
272 return(1);
274 if (!f) {
275 *msgvec = first(0, MMNORM);
276 if (*msgvec == 0) {
277 if (inhook)
278 return 0;
279 printf(catgets(catd, CATSET, 23,
280 "No messages to %s.\n"), cmd);
281 return(1);
283 msgvec[1] = 0;
285 if (f && getmsglist(str, msgvec, 0) < 0)
286 return(1);
287 if (*msgvec == 0) {
288 if (inhook)
289 return 0;
290 printf("No applicable messages.\n");
291 return 1;
293 if (sender_record) {
294 if ((cp = nameof(&message[*msgvec - 1], 0)) == NULL) {
295 printf(catgets(catd, CATSET, 24,
296 "Cannot determine message sender to %s.\n"),
297 cmd);
298 return 1;
300 for (cq = cp; *cq && *cq != '@'; cq++);
301 *cq = '\0';
302 if (value("outfolder")) {
303 file = salloc(strlen(cp) + 2);
304 file[0] = '+';
305 strcpy(&file[1], cp);
306 } else
307 file = cp;
309 if ((file = expand(file)) == NULL)
310 return(1);
311 prot = which_protocol(file);
312 if (prot != PROTO_IMAP) {
313 if (access(file, 0) >= 0) {
314 newfile = 0;
315 disp = catgets(catd, CATSET, 25, "[Appended]");
316 } else {
317 newfile = 1;
318 disp = catgets(catd, CATSET, 26, "[New file]");
321 if ((obuf = convert == SEND_TOFILE ? Fopen(file, "a+") :
322 Zopen(file, "a+", &compressed)) == NULL) {
323 if ((obuf = convert == SEND_TOFILE ? Fopen(file, "wx") :
324 Zopen(file, "wx", &compressed)) == NULL) {
325 perror(file);
326 return(1);
328 } else {
329 if (compressed) {
330 newfile = 0;
331 disp = catgets(catd, CATSET, 25, "[Appended]");
333 if (!newfile && fstat(fileno(obuf), &st) &&
334 S_ISREG(st.st_mode) &&
335 fseek(obuf, -2L, SEEK_END) == 0) {
336 char buf[2];
337 int prependnl = 0;
339 switch (fread(buf, sizeof *buf, 2, obuf)) {
340 case 2:
341 if (buf[1] != '\n') {
342 prependnl = 1;
343 break;
345 /*FALLTHRU*/
346 case 1:
347 if (buf[0] != '\n')
348 prependnl = 1;
349 break;
350 default:
351 if (ferror(obuf)) {
352 perror(file);
353 return(1);
355 prependnl = 0;
357 fflush(obuf);
358 if (prependnl) {
359 putc('\n', obuf);
360 fflush(obuf);
364 tstats[0] = tstats[1] = 0;
365 imap_created_mailbox = 0;
366 for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) {
367 mp = &message[*ip - 1];
368 if (prot == PROTO_IMAP &&
369 ignore[0].i_count == 0 &&
370 ignore[1].i_count == 0 &&
371 imap_thisaccount(file)) {
372 if (imap_copy(mp, *ip, file) == STOP)
373 goto ferr;
374 mstats[0] = -1;
375 mstats[1] = mp->m_xsize;
376 } else if (send(mp, obuf, ignore, NULL,
377 convert, mstats) < 0) {
378 perror(file);
379 goto ferr;
381 touch(mp);
382 if (mark)
383 mp->m_flag |= MSAVED;
384 if (domove) {
385 mp->m_flag |= MDELETED|MSAVED;
386 last = *ip;
388 tstats[0] += mstats[0];
389 tstats[1] += mstats[1];
391 fflush(obuf);
392 if (ferror(obuf)) {
393 perror(file);
394 ferr: success = 0;
396 if (Fclose(obuf) != 0)
397 success = 0;
398 if (success) {
399 if (prot == PROTO_IMAP || prot == PROTO_MAILDIR)
400 disp = prot == PROTO_IMAP && disconnected(file) ?
401 "[Queued]" : imap_created_mailbox ?
402 "[New file]" : "[Appended]";
403 printf("\"%s\" %s ", file, disp);
404 if (tstats[0] >= 0)
405 printf("%lu", (long)tstats[0]);
406 else
407 printf(catgets(catd, CATSET, 27, "binary"));
408 printf("/%lu\n", (long)tstats[1]);
409 } else if (mark) {
410 for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) {
411 mp = &message[*ip - 1];
412 mp->m_flag &= ~MSAVED;
414 } else if (domove) {
415 for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) {
416 mp = &message[*ip - 1];
417 mp->m_flag &= ~(MSAVED|MDELETED);
420 if (domove && last && success) {
421 setdot(&message[last-1]);
422 last = first(0, MDELETED);
423 setdot(&message[last ? last-1 : 0]);
425 return(success == 0);
429 * Write the indicated messages at the end of the passed
430 * file name, minus header and trailing blank line.
431 * This is the MIME save function.
433 int
434 cwrite(void *v)
436 char *str = v;
438 if (str == NULL || *str == '\0')
439 str = savestr("/dev/null");
440 return (save1(str, 0, "write", allignore, SEND_TOFILE, 0, 0));
444 * Snarf the file from the end of the command line and
445 * return a pointer to it. If there is no file attached,
446 * return the mbox file. Put a null in front of the file
447 * name so that the message list processing won't see it,
448 * unless the file name is the only thing on the line, in
449 * which case, return 0 in the reference flag variable.
452 static char *
453 snarf(char *linebuf, int *flag, int usembox)
455 char *cp;
457 *flag = 1;
458 if ((cp = laststring(linebuf, flag, 0)) == NULL) {
459 if (usembox) {
460 *flag = 0;
461 cp = expand("&");
462 } else
463 printf(tr(28, "No file specified.\n"));
465 return (cp);
469 * Delete messages.
471 int
472 delete(void *v)
474 int *msgvec = v;
475 delm(msgvec);
476 return 0;
480 * Delete messages, then type the new dot.
482 int
483 deltype(void *v)
485 int *msgvec = v;
486 int list[2];
487 int lastdot;
489 lastdot = dot - &message[0] + 1;
490 if (delm(msgvec) >= 0) {
491 list[0] = dot - &message[0] + 1;
492 if (list[0] > lastdot) {
493 touch(dot);
494 list[1] = 0;
495 return(type(list));
497 printf(catgets(catd, CATSET, 29, "At EOF\n"));
498 } else
499 printf(catgets(catd, CATSET, 30, "No more messages\n"));
500 return(0);
504 * Delete the indicated messages.
505 * Set dot to some nice place afterwards.
506 * Internal interface.
508 static int
509 delm(int *msgvec)
511 struct message *mp;
512 int *ip;
513 int last;
515 last = 0;
516 for (ip = msgvec; *ip != 0; ip++) {
517 mp = &message[*ip - 1];
518 touch(mp);
519 mp->m_flag |= MDELETED|MTOUCH;
520 mp->m_flag &= ~(MPRESERVE|MSAVED|MBOX);
521 last = *ip;
523 if (last != 0) {
524 setdot(&message[last-1]);
525 last = first(0, MDELETED);
526 if (last != 0) {
527 setdot(&message[last-1]);
528 return(0);
530 else {
531 setdot(&message[0]);
532 return(-1);
537 * Following can't happen -- it keeps lint happy
540 return(-1);
544 * Undelete the indicated messages.
546 int
547 undeletecmd(void *v)
549 int *msgvec = v;
550 struct message *mp;
551 int *ip;
553 for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) {
554 mp = &message[*ip - 1];
555 touch(mp);
556 setdot(mp);
557 if (mp->m_flag & (MDELETED|MSAVED))
558 mp->m_flag &= ~(MDELETED|MSAVED);
559 else
560 mp->m_flag &= ~MDELETED;
561 if (mb.mb_type == MB_IMAP || mb.mb_type == MB_CACHE)
562 imap_undelete(mp, *ip);
564 return 0;
567 #ifdef HAVE_ASSERTS
569 * Interactively dump core on "core"
571 /*ARGSUSED*/
572 int
573 core(void *v)
575 int pid;
576 # ifdef WCOREDUMP
577 extern int wait_status;
578 # endif
579 (void)v;
581 switch (pid = fork()) {
582 case -1:
583 perror("fork");
584 return (1);
585 case 0:
586 abort();
587 _exit(1);
589 (void)printf(tr(31, "Okie dokie"));
590 (void)fflush(stdout);
591 (void)wait_child(pid);
592 # ifdef WCOREDUMP
593 if (WCOREDUMP(wait_status))
594 (void)printf(tr(32, " -- Core dumped.\n"));
595 else
596 (void)printf(tr(33, " -- Can't dump core.\n"));
597 # endif
598 return (0);
601 static void
602 clob1(int n)
604 char buf[512], *cp;
606 if (n <= 0)
607 return;
608 for (cp = buf; cp < &buf[512]; *cp++ = (char)0xFF)
610 clob1(n - 1);
614 * Clobber as many bytes of stack as the user requests.
616 int
617 clobber(void *v)
619 char **argv = v;
620 int times;
622 if (argv[0] == 0)
623 times = 1;
624 else
625 times = (atoi(argv[0]) + 511) / 512;
626 clob1(times);
627 return (0);
629 #endif /* HAVE_ASSERTS */
632 * Add the given header fields to the retained list.
633 * If no arguments, print the current list of retained fields.
635 int
636 retfield(void *v)
638 char **list = v;
640 return ignore1(list, ignore + 1, "retained");
644 * Add the given header fields to the ignored list.
645 * If no arguments, print the current list of ignored fields.
647 int
648 igfield(void *v)
650 char **list = v;
652 return ignore1(list, ignore, "ignored");
655 int
656 saveretfield(void *v)
658 char **list = v;
660 return ignore1(list, saveignore + 1, "retained");
663 int
664 saveigfield(void *v)
666 char **list = v;
668 return ignore1(list, saveignore, "ignored");
671 int
672 fwdretfield(void *v)
674 char **list = v;
676 return ignore1(list, fwdignore + 1, "retained");
679 int
680 fwdigfield(void *v)
682 char **list = v;
684 return ignore1(list, fwdignore, "ignored");
687 static int
688 ignore1(char **list, struct ignoretab *tab, char *which)
690 int h;
691 struct ignore *igp;
692 char **ap;
694 if (*list == NULL)
695 return igshow(tab, which);
696 for (ap = list; *ap != 0; ap++) {
697 char *field;
698 size_t sz;
700 sz = strlen(*ap);
701 field = ac_alloc(sz + 1);
702 i_strcpy(field, *ap, sz + 1);
703 field[sz]='\0';
704 if (member(field, tab)) {
705 ac_free(field);
706 continue;
708 h = hash(field);
709 igp = (struct ignore *)scalloc(1, sizeof (struct ignore));
710 igp->i_field = smalloc(strlen(field) + 1);
711 strcpy(igp->i_field, field);
712 igp->i_link = tab->i_head[h];
713 tab->i_head[h] = igp;
714 tab->i_count++;
715 ac_free(field);
717 return 0;
721 * Print out all currently retained fields.
723 static int
724 igshow(struct ignoretab *tab, char *which)
726 int h;
727 struct ignore *igp;
728 char **ap, **ring;
730 if (tab->i_count == 0) {
731 printf(catgets(catd, CATSET, 34,
732 "No fields currently being %s.\n"), which);
733 return 0;
735 /*LINTED*/
736 ring = (char **)salloc((tab->i_count + 1) * sizeof (char *));
737 ap = ring;
738 for (h = 0; h < HSHSIZE; h++)
739 for (igp = tab->i_head[h]; igp != 0; igp = igp->i_link)
740 *ap++ = igp->i_field;
741 *ap = 0;
742 qsort(ring, tab->i_count, sizeof (char *), igcomp);
743 for (ap = ring; *ap != 0; ap++)
744 printf("%s\n", *ap);
745 return 0;
749 * Compare two names for sorting ignored field list.
751 static int
752 igcomp(const void *l, const void *r)
754 return (strcmp(*(char **)l, *(char **)r));
757 int
758 unignore(void *v)
760 return unignore1((char **)v, ignore, "ignored");
763 int
764 unretain(void *v)
766 return unignore1((char **)v, ignore + 1, "retained");
769 int
770 unsaveignore(void *v)
772 return unignore1((char **)v, saveignore, "ignored");
775 int
776 unsaveretain(void *v)
778 return unignore1((char **)v, saveignore + 1, "retained");
781 int
782 unfwdignore(void *v)
784 return unignore1((char **)v, fwdignore, "ignored");
787 int
788 unfwdretain(void *v)
790 return unignore1((char **)v, fwdignore + 1, "retained");
793 static void
794 unignore_one(const char *name, struct ignoretab *tab)
796 struct ignore *ip, *iq = NULL;
797 int h = hash(name);
799 for (ip = tab->i_head[h]; ip; ip = ip->i_link) {
800 if (asccasecmp(ip->i_field, name) == 0) {
801 free(ip->i_field);
802 if (iq != NULL)
803 iq->i_link = ip->i_link;
804 else
805 tab->i_head[h] = ip->i_link;
806 free(ip);
807 tab->i_count--;
808 break;
810 iq = ip;
814 static int
815 unignore1(char **list, struct ignoretab *tab, char *which)
817 if (tab->i_count == 0) {
818 printf(catgets(catd, CATSET, 34,
819 "No fields currently being %s.\n"), which);
820 return 0;
822 while (*list)
823 unignore_one(*list++, tab);
824 return 0;