INSTALL: update for v14.6
[s-mailx.git] / cmd2.c
blobb9a2204e9cfbf18a0789c33880c0c0bbb5bdaaf2
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ More user commands.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2014 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
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 #ifndef HAVE_AMALGAMATION
41 # include "nail.h"
42 #endif
44 #include <sys/wait.h>
46 /* Save/copy the indicated messages at the end of the passed file name.
47 * If mark is true, mark the message "saved" */
48 static int save1(char *str, int domark, char const *cmd,
49 struct ignoretab *ignore, int convert, int sender_record,
50 int domove);
52 /* Snarf the file from the end of the command line and return a pointer to it.
53 * If there is no file attached, return the mbox file. Put a null in front of
54 * the file name so that the message list processing won't see it, unless the
55 * file name is the only thing on the line, in which case, return 0 in the
56 * reference flag variable */
57 static char * snarf(char *linebuf, bool_t *flag, bool_t usembox);
59 /* Delete the indicated messages. Set dot to some nice place afterwards */
60 static int delm(int *msgvec);
62 static int ignore1(char **list, struct ignoretab *tab, char const *which);
64 /* Print out all currently retained fields */
65 static int igshow(struct ignoretab *tab, char const *which);
67 /* Compare two names for sorting ignored field list */
68 static int igcomp(const void *l, const void *r);
70 static void unignore_one(char const *name, struct ignoretab *tab);
71 static int unignore1(char **list, struct ignoretab *tab, char const *which);
73 static int
74 save1(char *str, int domark, char const *cmd, struct ignoretab *ignoret,
75 int convert, int sender_record, int domove)
77 off_t mstats[2], tstats[2];
78 struct stat st;
79 int newfile = 0, compressed = 0, last = 0, *msgvec, *ip;
80 struct message *mp;
81 char *file = NULL, *cp, *cq;
82 char const *disp = "";
83 FILE *obuf;
84 enum protocol prot;
85 bool_t success = FAL0, f;
86 NYD_ENTER;
88 msgvec = salloc((msgCount + 2) * sizeof *msgvec);
89 if (sender_record) {
90 for (cp = str; *cp != '\0' && blankchar(*cp); ++cp)
92 f = (*cp != '\0');
93 } else {
94 if ((file = snarf(str, &f, convert != SEND_TOFILE)) == NULL)
95 goto jleave;
98 if (!f) {
99 *msgvec = first(0, MMNORM);
100 if (*msgvec == 0) {
101 if (inhook) {
102 success = TRU1;
103 goto jleave;
105 printf(tr(23, "No messages to %s.\n"), cmd);
106 goto jleave;
108 msgvec[1] = 0;
109 } else if (getmsglist(str, msgvec, 0) < 0)
110 goto jleave;
111 if (*msgvec == 0) {
112 if (inhook) {
113 success = TRU1;
114 goto jleave;
116 printf("No applicable messages.\n");
117 goto jleave;
120 if (sender_record) {
121 if ((cp = nameof(&message[*msgvec - 1], 0)) == NULL) {
122 printf(tr(24, "Cannot determine message sender to %s.\n"), cmd);
123 goto jleave;
126 for (cq = cp; *cq != '\0' && *cq != '@'; cq++)
128 *cq = '\0';
129 if (ok_blook(outfolder)) {
130 size_t sz = strlen(cp) + 1;
131 file = salloc(sz + 1);
132 file[0] = '+';
133 memcpy(file + 1, cp, sz);
134 } else
135 file = cp;
138 if ((file = expand(file)) == NULL)
139 goto jleave;
140 prot = which_protocol(file);
141 if (prot != PROTO_IMAP) {
142 if (access(file, 0) >= 0) {
143 newfile = 0;
144 disp = tr(25, "[Appended]");
145 } else {
146 newfile = 1;
147 disp = tr(26, "[New file]");
151 obuf = ((convert == SEND_TOFILE) ? Fopen(file, "a+")
152 : Zopen(file, "a+", &compressed));
153 if (obuf == NULL) {
154 obuf = ((convert == SEND_TOFILE) ? Fopen(file, "wx")
155 : Zopen(file, "wx", &compressed));
156 if (obuf == NULL) {
157 perror(file);
158 goto jleave;
160 } else {
161 if (compressed) {
162 newfile = 0;
163 disp = tr(25, "[Appended]");
165 if (!newfile && fstat(fileno(obuf), &st) && S_ISREG(st.st_mode) &&
166 fseek(obuf, -2L, SEEK_END) == 0) {
167 char buf[2];
168 int prependnl = 0;
170 switch (fread(buf, sizeof *buf, 2, obuf)) {
171 case 2:
172 if (buf[1] != '\n') {
173 prependnl = 1;
174 break;
176 /* FALLTHRU */
177 case 1:
178 if (buf[0] != '\n')
179 prependnl = 1;
180 break;
181 default:
182 if (ferror(obuf)) {
183 perror(file);
184 goto jleave;
186 prependnl = 0;
188 fflush(obuf);
189 if (prependnl) {
190 putc('\n', obuf);
191 fflush(obuf);
196 success = TRU1;
197 tstats[0] = tstats[1] = 0;
198 imap_created_mailbox = 0;
199 srelax_hold();
200 for (ip = msgvec; *ip != 0 && UICMP(z, PTR2SIZE(ip - msgvec), <, msgCount);
201 ++ip) {
202 mp = &message[*ip - 1];
203 if (prot == PROTO_IMAP && ignoret[0].i_count == 0 &&
204 ignoret[1].i_count == 0
205 #ifdef HAVE_IMAP /* TODO revisit */
206 && imap_thisaccount(file)
207 #endif
209 #ifdef HAVE_IMAP
210 if (imap_copy(mp, *ip, file) == STOP)
211 #endif
212 goto jferr;
213 #ifdef HAVE_IMAP
214 mstats[0] = -1;
215 mstats[1] = mp->m_xsize;
216 #endif
217 } else if (sendmp(mp, obuf, ignoret, NULL, convert, mstats) < 0) {
218 perror(file);
219 goto jferr;
221 srelax();
222 touch(mp);
223 if (domark)
224 mp->m_flag |= MSAVED;
225 if (domove) {
226 mp->m_flag |= MDELETED | MSAVED;
227 last = *ip;
229 tstats[0] += mstats[0];
230 tstats[1] += mstats[1];
232 fflush(obuf);
233 if (ferror(obuf)) {
234 perror(file);
235 jferr:
236 success = FAL0;
238 if (Fclose(obuf) != 0)
239 success = FAL0;
240 srelax_rele();
242 if (success) {
243 if (prot == PROTO_IMAP || prot == PROTO_MAILDIR) {
244 disp = (
245 #ifdef HAVE_IMAP
246 ((prot == PROTO_IMAP) && disconnected(file)) ? "[Queued]" :
247 #endif
248 (imap_created_mailbox ? "[New file]" : "[Appended]"));
250 printf("\"%s\" %s ", file, disp);
251 if (tstats[0] >= 0)
252 printf("%lu", (ul_it)tstats[0]);
253 else
254 printf(tr(27, "binary"));
255 printf("/%lu\n", (ul_it)tstats[1]);
256 } else if (domark) {
257 newfile = ~MSAVED;
258 goto jiterand;
259 } else if (domove) {
260 newfile = ~(MSAVED | MDELETED);
261 jiterand:
262 for (ip = msgvec; *ip != 0 &&
263 UICMP(z, PTR2SIZE(ip - msgvec), <, msgCount); ++ip) {
264 mp = &message[*ip - 1];
265 mp->m_flag &= newfile;
269 if (domove && last && success) {
270 setdot(&message[last - 1]);
271 last = first(0, MDELETED);
272 setdot(&message[last ? last - 1 : 0]);
274 jleave:
275 NYD_LEAVE;
276 return (success == FAL0);
279 static char *
280 snarf(char *linebuf, bool_t *flag, bool_t usembox)
282 char *cp;
283 NYD_ENTER;
285 if ((cp = laststring(linebuf, flag, 0)) == NULL) {
286 if (usembox) {
287 *flag = FAL0;
288 cp = expand("&");
289 } else
290 fprintf(stderr, tr(28, "No file specified.\n"));
292 NYD_LEAVE;
293 return cp;
296 static int
297 delm(int *msgvec)
299 struct message *mp;
300 int rv = -1, *ip, last;
301 NYD_ENTER;
303 last = 0;
304 for (ip = msgvec; *ip != 0; ++ip) {
305 mp = &message[*ip - 1];
306 touch(mp);
307 mp->m_flag |= MDELETED | MTOUCH;
308 mp->m_flag &= ~(MPRESERVE | MSAVED | MBOX);
309 last = *ip;
311 if (last != 0) {
312 setdot(&message[last - 1]);
313 last = first(0, MDELETED);
314 if (last != 0) {
315 setdot(&message[last - 1]);
316 rv = 0;
317 } else {
318 setdot(&message[0]);
321 NYD_LEAVE;
322 return rv;
325 static int
326 ignore1(char **list, struct ignoretab *tab, char const *which)
328 int h;
329 struct ignore *igp;
330 char **ap;
331 NYD_ENTER;
333 if (*list == NULL) {
334 h = igshow(tab, which);
335 goto jleave;
338 for (ap = list; *ap != 0; ++ap) {
339 char *field;
340 size_t sz;
342 sz = strlen(*ap);
343 field = ac_alloc(sz + 1);
344 i_strcpy(field, *ap, sz + 1);
345 assert(field[sz] == '\0');
346 if (member(field, tab))
347 goto jnext;
349 h = hash(field);
350 igp = scalloc(1, sizeof *igp);
351 sz = strlen(field) + 1;
352 igp->i_field = smalloc(sz);
353 memcpy(igp->i_field, field, sz);
354 igp->i_link = tab->i_head[h];
355 tab->i_head[h] = igp;
356 ++tab->i_count;
357 jnext:
358 ac_free(field);
360 h = 0;
361 jleave:
362 NYD_LEAVE;
363 return h;
366 static int
367 igshow(struct ignoretab *tab, char const *which)
369 int h;
370 struct ignore *igp;
371 char **ap, **ring;
372 NYD_ENTER;
374 if (tab->i_count == 0) {
375 printf(tr(34, "No fields currently being %s.\n"), which);
376 goto jleave;
379 ring = salloc((tab->i_count + 1) * sizeof *ring);
380 ap = ring;
381 for (h = 0; h < HSHSIZE; h++)
382 for (igp = tab->i_head[h]; igp != 0; igp = igp->i_link)
383 *ap++ = igp->i_field;
384 *ap = 0;
386 qsort(ring, tab->i_count, sizeof *ring, igcomp);
388 for (ap = ring; *ap != NULL; ++ap)
389 printf("%s\n", *ap);
390 jleave:
391 NYD_LEAVE;
392 return 0;
395 static int
396 igcomp(const void *l, const void *r)
398 int rv;
399 NYD_ENTER;
401 rv = strcmp(*(char**)UNCONST(l), *(char**)UNCONST(r));
402 NYD_LEAVE;
403 return rv;
406 static void
407 unignore_one(char const *name, struct ignoretab *tab)
409 struct ignore *ip, *iq;
410 int h;
411 NYD_ENTER;
413 h = hash(name);
414 for (iq = NULL, ip = tab->i_head[h]; ip != NULL; ip = ip->i_link) {
415 if (!asccasecmp(ip->i_field, name)) {
416 free(ip->i_field);
417 if (iq != NULL)
418 iq->i_link = ip->i_link;
419 else
420 tab->i_head[h] = ip->i_link;
421 free(ip);
422 --tab->i_count;
423 break;
425 iq = ip;
427 NYD_LEAVE;
430 static int
431 unignore1(char **list, struct ignoretab *tab, char const *which)
433 NYD_ENTER;
435 if (tab->i_count == 0) {
436 printf(tr(34, "No fields currently being %s.\n"), which);
437 goto jleave;
440 while (*list != NULL)
441 unignore_one(*list++, tab);
442 jleave:
443 NYD_LEAVE;
444 return 0;
447 FL int
448 c_next(void *v)
450 int list[2], *ip, *ip2, mdot, *msgvec = v, rv = 1;
451 struct message *mp;
452 NYD_ENTER;
454 if (*msgvec != 0) {
455 /* If some messages were supplied, find the first applicable one
456 * following dot using wrap around */
457 mdot = (int)PTR2SIZE(dot - message + 1);
459 /* Find first message in supplied message list which follows dot */
460 for (ip = msgvec; *ip != 0; ++ip) {
461 if ((mb.mb_threaded ? message[*ip - 1].m_threadpos > dot->m_threadpos
462 : *ip > mdot))
463 break;
465 if (*ip == 0)
466 ip = msgvec;
467 ip2 = ip;
468 do {
469 mp = &message[*ip2 - 1];
470 if ((mp->m_flag & (MDELETED | MHIDDEN)) == 0) {
471 setdot(mp);
472 goto jhitit;
474 if (*ip2 != 0)
475 ++ip2;
476 if (*ip2 == 0)
477 ip2 = msgvec;
478 } while (ip2 != ip);
479 printf(tr(21, "No messages applicable\n"));
480 goto jleave;
483 /* If this is the first command, select message 1. Note that this must
484 * exist for us to get here at all */
485 if (!sawcom) {
486 if (msgCount == 0)
487 goto jateof;
488 goto jhitit;
491 /* Just find the next good message after dot, no wraparound */
492 if (mb.mb_threaded == 0) {
493 for (mp = dot + did_print_dot; PTRCMP(mp, <, message + msgCount); ++mp)
494 if (!(mp->m_flag & (MDELETED | MSAVED | MHIDDEN)))
495 break;
496 } else {
497 mp = dot;
498 if (did_print_dot)
499 mp = next_in_thread(mp);
500 while (mp && mp->m_flag & (MDELETED | MSAVED | MHIDDEN))
501 mp = next_in_thread(mp);
503 if (mp == NULL || PTRCMP(mp, >=, message + msgCount)) {
504 jateof:
505 printf(tr(22, "At EOF\n"));
506 rv = 0;
507 goto jleave;
509 setdot(mp);
511 /* Print dot */
512 jhitit:
513 list[0] = (int)PTR2SIZE(dot - message + 1);
514 list[1] = 0;
515 rv = c_type(list);
516 jleave:
517 NYD_LEAVE;
518 return rv;
521 FL int
522 c_save(void *v)
524 char *str = v;
525 int rv;
526 NYD_ENTER;
528 rv = save1(str, 1, "save", saveignore, SEND_MBOX, 0, 0);
529 NYD_LEAVE;
530 return rv;
533 FL int
534 c_Save(void *v)
536 char *str = v;
537 int rv;
538 NYD_ENTER;
540 rv = save1(str, 1, "save", saveignore, SEND_MBOX, 1, 0);
541 NYD_LEAVE;
542 return rv;
545 FL int
546 c_copy(void *v)
548 char *str = v;
549 int rv;
550 NYD_ENTER;
552 rv = save1(str, 0, "copy", saveignore, SEND_MBOX, 0, 0);
553 NYD_LEAVE;
554 return rv;
557 FL int
558 c_Copy(void *v)
560 char *str = v;
561 int rv;
562 NYD_ENTER;
564 rv = save1(str, 0, "copy", saveignore, SEND_MBOX, 1, 0);
565 NYD_LEAVE;
566 return rv;
569 FL int
570 c_move(void *v)
572 char *str = v;
573 int rv;
574 NYD_ENTER;
576 rv = save1(str, 0, "move", saveignore, SEND_MBOX, 0, 1);
577 NYD_LEAVE;
578 return rv;
581 FL int
582 c_Move(void *v)
584 char *str = v;
585 int rv;
586 NYD_ENTER;
588 rv = save1(str, 0, "move", saveignore, SEND_MBOX, 1, 1);
589 NYD_LEAVE;
590 return rv;
593 FL int
594 c_decrypt(void *v)
596 char *str = v;
597 int rv;
598 NYD_ENTER;
600 rv = save1(str, 0, "decrypt", saveignore, SEND_DECRYPT, 0, 0);
601 NYD_LEAVE;
602 return rv;
605 FL int
606 c_Decrypt(void *v)
608 char *str = v;
609 int rv;
610 NYD_ENTER;
612 rv = save1(str, 0, "decrypt", saveignore, SEND_DECRYPT, 1, 0);
613 NYD_LEAVE;
614 return rv;
617 FL int
618 c_write(void *v)
620 char *str = v;
621 int rv;
622 NYD_ENTER;
624 if (str == NULL || *str == '\0')
625 str = savestr("/dev/null");
626 rv = save1(str, 0, "write", allignore, SEND_TOFILE, 0, 0);
627 NYD_LEAVE;
628 return rv;
631 FL int
632 c_delete(void *v)
634 int *msgvec = v;
635 NYD_ENTER;
637 delm(msgvec);
638 NYD_LEAVE;
639 return 0;
642 FL int
643 c_deltype(void *v)
645 int list[2], rv = 0, *msgvec = v, lastdot;
646 NYD_ENTER;
648 lastdot = dot - message + 1;
649 if (delm(msgvec) >= 0) {
650 list[0] = (int)PTR2SIZE(dot - message + 1);
651 if (list[0] > lastdot) {
652 touch(dot);
653 list[1] = 0;
654 rv = c_type(list);
655 goto jleave;
657 printf(tr(29, "At EOF\n"));
658 } else
659 printf(tr(30, "No more messages\n"));
660 jleave:
661 NYD_LEAVE;
662 return rv;
665 FL int
666 c_undelete(void *v)
668 int *msgvec = v, *ip;
669 struct message *mp;
670 NYD_ENTER;
672 for (ip = msgvec; *ip != 0 && UICMP(z, PTR2SIZE(ip - msgvec), <, msgCount);
673 ++ip) {
674 mp = &message[*ip - 1];
675 touch(mp);
676 setdot(mp);
677 if (mp->m_flag & (MDELETED | MSAVED))
678 mp->m_flag &= ~(MDELETED | MSAVED);
679 else
680 mp->m_flag &= ~MDELETED;
681 #ifdef HAVE_IMAP
682 if (mb.mb_type == MB_IMAP || mb.mb_type == MB_CACHE)
683 imap_undelete(mp, *ip);
684 #endif
686 NYD_LEAVE;
687 return 0;
690 FL int
691 c_retfield(void *v)
693 char **list = v;
694 int rv;
695 NYD_ENTER;
697 rv = ignore1(list, ignore + 1, "retained");
698 NYD_LEAVE;
699 return rv;
702 FL int
703 c_igfield(void *v)
705 char **list = v;
706 int rv;
707 NYD_ENTER;
709 rv = ignore1(list, ignore, "ignored");
710 NYD_LEAVE;
711 return rv;
714 FL int
715 c_saveretfield(void *v)
717 char **list = v;
718 int rv;
719 NYD_ENTER;
721 rv = ignore1(list, saveignore + 1, "retained");
722 NYD_LEAVE;
723 return rv;
726 FL int
727 c_saveigfield(void *v)
729 char **list = v;
730 int rv;
731 NYD_ENTER;
733 rv = ignore1(list, saveignore, "ignored");
734 NYD_LEAVE;
735 return rv;
738 FL int
739 c_fwdretfield(void *v)
741 char **list = v;
742 int rv;
743 NYD_ENTER;
745 rv = ignore1(list, fwdignore + 1, "retained");
746 NYD_LEAVE;
747 return rv;
750 FL int
751 c_fwdigfield(void *v)
753 char **list = v;
754 int rv;
755 NYD_ENTER;
757 rv = ignore1(list, fwdignore, "ignored");
758 NYD_LEAVE;
759 return rv;
762 FL int
763 c_unignore(void *v)
765 int rv;
766 NYD_ENTER;
768 rv = unignore1((char**)v, ignore, "ignored");
769 NYD_LEAVE;
770 return rv;
773 FL int
774 c_unretain(void *v)
776 int rv;
777 NYD_ENTER;
779 rv = unignore1((char**)v, ignore + 1, "retained");
780 NYD_LEAVE;
781 return rv;
784 FL int
785 c_unsaveignore(void *v)
787 int rv;
788 NYD_ENTER;
790 rv = unignore1((char**)v, saveignore, "ignored");
791 NYD_LEAVE;
792 return rv;
795 FL int
796 c_unsaveretain(void *v)
798 int rv;
799 NYD_ENTER;
801 rv = unignore1((char**)v, saveignore + 1, "retained");
802 NYD_LEAVE;
803 return rv;
806 FL int
807 c_unfwdignore(void *v)
809 int rv;
810 NYD_ENTER;
812 rv = unignore1((char**)v, fwdignore, "ignored");
813 NYD_LEAVE;
814 return rv;
817 FL int
818 c_unfwdretain(void *v)
820 int rv;
821 NYD_ENTER;
823 rv = unignore1((char**)v, fwdignore + 1, "retained");
824 NYD_LEAVE;
825 return rv;
828 /* vim:set fenc=utf-8:s-it-mode */