INSTALL: update for v14.6.2
[s-mailx.git] / cmd2.c
blob418cc92f8bc6160453934c0eaef51fd75231df7f
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(void const *l, void const *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 != 0 ? 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);
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) +1;
343 field = ac_alloc(sz);
344 i_strcpy(field, *ap, sz);
345 if (member(field, tab))
346 goto jnext;
348 h = hash(field);
349 igp = scalloc(1, sizeof *igp);
350 sz = strlen(field) +1;
351 igp->i_field = smalloc(sz);
352 memcpy(igp->i_field, field, sz);
353 igp->i_link = tab->i_head[h];
354 tab->i_head[h] = igp;
355 ++tab->i_count;
356 jnext:
357 ac_free(field);
359 h = 0;
360 jleave:
361 NYD_LEAVE;
362 return h;
365 static int
366 igshow(struct ignoretab *tab, char const *which)
368 int h;
369 struct ignore *igp;
370 char **ap, **ring;
371 NYD_ENTER;
373 if (tab->i_count == 0) {
374 printf(tr(34, "No fields currently being %s.\n"), which);
375 goto jleave;
378 ring = salloc((tab->i_count + 1) * sizeof *ring);
379 ap = ring;
380 for (h = 0; h < HSHSIZE; ++h)
381 for (igp = tab->i_head[h]; igp != 0; igp = igp->i_link)
382 *ap++ = igp->i_field;
383 *ap = 0;
385 qsort(ring, tab->i_count, sizeof *ring, igcomp);
387 for (ap = ring; *ap != NULL; ++ap)
388 printf("%s\n", *ap);
389 jleave:
390 NYD_LEAVE;
391 return 0;
394 static int
395 igcomp(void const *l, void const *r)
397 int rv;
398 NYD_ENTER;
400 rv = strcmp(*(char**)UNCONST(l), *(char**)UNCONST(r));
401 NYD_LEAVE;
402 return rv;
405 static void
406 unignore_one(char const *name, struct ignoretab *tab)
408 struct ignore *ip, *iq;
409 int h;
410 NYD_ENTER;
412 h = hash(name);
413 for (iq = NULL, ip = tab->i_head[h]; ip != NULL; ip = ip->i_link) {
414 if (!asccasecmp(ip->i_field, name)) {
415 free(ip->i_field);
416 if (iq != NULL)
417 iq->i_link = ip->i_link;
418 else
419 tab->i_head[h] = ip->i_link;
420 free(ip);
421 --tab->i_count;
422 break;
424 iq = ip;
426 NYD_LEAVE;
429 static int
430 unignore1(char **list, struct ignoretab *tab, char const *which)
432 NYD_ENTER;
434 if (tab->i_count == 0) {
435 printf(tr(34, "No fields currently being %s.\n"), which);
436 goto jleave;
439 while (*list != NULL)
440 unignore_one(*list++, tab);
441 jleave:
442 NYD_LEAVE;
443 return 0;
446 FL int
447 c_next(void *v)
449 int list[2], *ip, *ip2, mdot, *msgvec = v, rv = 1;
450 struct message *mp;
451 NYD_ENTER;
453 if (*msgvec != 0) {
454 /* If some messages were supplied, find the first applicable one
455 * following dot using wrap around */
456 mdot = (int)PTR2SIZE(dot - message + 1);
458 /* Find first message in supplied message list which follows dot */
459 for (ip = msgvec; *ip != 0; ++ip) {
460 if ((mb.mb_threaded ? message[*ip - 1].m_threadpos > dot->m_threadpos
461 : *ip > mdot))
462 break;
464 if (*ip == 0)
465 ip = msgvec;
466 ip2 = ip;
467 do {
468 mp = message + *ip2 - 1;
469 if (!(mp->m_flag & MMNDEL)) {
470 setdot(mp);
471 goto jhitit;
473 if (*ip2 != 0)
474 ++ip2;
475 if (*ip2 == 0)
476 ip2 = msgvec;
477 } while (ip2 != ip);
478 printf(tr(21, "No messages applicable\n"));
479 goto jleave;
482 /* If this is the first command, select message 1. Note that this must
483 * exist for us to get here at all */
484 if (!sawcom) {
485 if (msgCount == 0)
486 goto jateof;
487 goto jhitit;
490 /* Just find the next good message after dot, no wraparound */
491 if (mb.mb_threaded == 0) {
492 for (mp = dot + did_print_dot; PTRCMP(mp, <, message + msgCount); ++mp)
493 if (!(mp->m_flag & MMNORM))
494 break;
495 } else {
496 mp = dot;
497 if (did_print_dot)
498 mp = next_in_thread(mp);
499 while (mp && (mp->m_flag & MMNORM))
500 mp = next_in_thread(mp);
502 if (mp == NULL || PTRCMP(mp, >=, message + msgCount)) {
503 jateof:
504 printf(tr(22, "At EOF\n"));
505 rv = 0;
506 goto jleave;
508 setdot(mp);
510 /* Print dot */
511 jhitit:
512 list[0] = (int)PTR2SIZE(dot - message + 1);
513 list[1] = 0;
514 rv = c_type(list);
515 jleave:
516 NYD_LEAVE;
517 return rv;
520 FL int
521 c_save(void *v)
523 char *str = v;
524 int rv;
525 NYD_ENTER;
527 rv = save1(str, 1, "save", saveignore, SEND_MBOX, 0, 0);
528 NYD_LEAVE;
529 return rv;
532 FL int
533 c_Save(void *v)
535 char *str = v;
536 int rv;
537 NYD_ENTER;
539 rv = save1(str, 1, "save", saveignore, SEND_MBOX, 1, 0);
540 NYD_LEAVE;
541 return rv;
544 FL int
545 c_copy(void *v)
547 char *str = v;
548 int rv;
549 NYD_ENTER;
551 rv = save1(str, 0, "copy", saveignore, SEND_MBOX, 0, 0);
552 NYD_LEAVE;
553 return rv;
556 FL int
557 c_Copy(void *v)
559 char *str = v;
560 int rv;
561 NYD_ENTER;
563 rv = save1(str, 0, "copy", saveignore, SEND_MBOX, 1, 0);
564 NYD_LEAVE;
565 return rv;
568 FL int
569 c_move(void *v)
571 char *str = v;
572 int rv;
573 NYD_ENTER;
575 rv = save1(str, 0, "move", saveignore, SEND_MBOX, 0, 1);
576 NYD_LEAVE;
577 return rv;
580 FL int
581 c_Move(void *v)
583 char *str = v;
584 int rv;
585 NYD_ENTER;
587 rv = save1(str, 0, "move", saveignore, SEND_MBOX, 1, 1);
588 NYD_LEAVE;
589 return rv;
592 FL int
593 c_decrypt(void *v)
595 char *str = v;
596 int rv;
597 NYD_ENTER;
599 rv = save1(str, 0, "decrypt", saveignore, SEND_DECRYPT, 0, 0);
600 NYD_LEAVE;
601 return rv;
604 FL int
605 c_Decrypt(void *v)
607 char *str = v;
608 int rv;
609 NYD_ENTER;
611 rv = save1(str, 0, "decrypt", saveignore, SEND_DECRYPT, 1, 0);
612 NYD_LEAVE;
613 return rv;
616 FL int
617 c_write(void *v)
619 char *str = v;
620 int rv;
621 NYD_ENTER;
623 if (str == NULL || *str == '\0')
624 str = savestr("/dev/null");
625 rv = save1(str, 0, "write", allignore, SEND_TOFILE, 0, 0);
626 NYD_LEAVE;
627 return rv;
630 FL int
631 c_delete(void *v)
633 int *msgvec = v;
634 NYD_ENTER;
636 delm(msgvec);
637 NYD_LEAVE;
638 return 0;
641 FL int
642 c_deltype(void *v)
644 int list[2], rv = 0, *msgvec = v, lastdot;
645 NYD_ENTER;
647 lastdot = dot - message + 1;
648 if (delm(msgvec) >= 0) {
649 list[0] = (int)PTR2SIZE(dot - message + 1);
650 if (list[0] > lastdot) {
651 touch(dot);
652 list[1] = 0;
653 rv = c_type(list);
654 goto jleave;
656 printf(tr(29, "At EOF\n"));
657 } else
658 printf(tr(30, "No more messages\n"));
659 jleave:
660 NYD_LEAVE;
661 return rv;
664 FL int
665 c_undelete(void *v)
667 int *msgvec = v, *ip;
668 struct message *mp;
669 NYD_ENTER;
671 for (ip = msgvec; *ip != 0 && UICMP(z, PTR2SIZE(ip - msgvec), <, msgCount);
672 ++ip) {
673 mp = message + *ip - 1;
674 touch(mp);
675 setdot(mp);
676 if (mp->m_flag & (MDELETED | MSAVED))
677 mp->m_flag &= ~(MDELETED | MSAVED);
678 else
679 mp->m_flag &= ~MDELETED;
680 #ifdef HAVE_IMAP
681 if (mb.mb_type == MB_IMAP || mb.mb_type == MB_CACHE)
682 imap_undelete(mp, *ip);
683 #endif
685 NYD_LEAVE;
686 return 0;
689 FL int
690 c_retfield(void *v)
692 char **list = v;
693 int rv;
694 NYD_ENTER;
696 rv = ignore1(list, ignore + 1, "retained");
697 NYD_LEAVE;
698 return rv;
701 FL int
702 c_igfield(void *v)
704 char **list = v;
705 int rv;
706 NYD_ENTER;
708 rv = ignore1(list, ignore, "ignored");
709 NYD_LEAVE;
710 return rv;
713 FL int
714 c_saveretfield(void *v)
716 char **list = v;
717 int rv;
718 NYD_ENTER;
720 rv = ignore1(list, saveignore + 1, "retained");
721 NYD_LEAVE;
722 return rv;
725 FL int
726 c_saveigfield(void *v)
728 char **list = v;
729 int rv;
730 NYD_ENTER;
732 rv = ignore1(list, saveignore, "ignored");
733 NYD_LEAVE;
734 return rv;
737 FL int
738 c_fwdretfield(void *v)
740 char **list = v;
741 int rv;
742 NYD_ENTER;
744 rv = ignore1(list, fwdignore + 1, "retained");
745 NYD_LEAVE;
746 return rv;
749 FL int
750 c_fwdigfield(void *v)
752 char **list = v;
753 int rv;
754 NYD_ENTER;
756 rv = ignore1(list, fwdignore, "ignored");
757 NYD_LEAVE;
758 return rv;
761 FL int
762 c_unignore(void *v)
764 int rv;
765 NYD_ENTER;
767 rv = unignore1((char**)v, ignore, "ignored");
768 NYD_LEAVE;
769 return rv;
772 FL int
773 c_unretain(void *v)
775 int rv;
776 NYD_ENTER;
778 rv = unignore1((char**)v, ignore + 1, "retained");
779 NYD_LEAVE;
780 return rv;
783 FL int
784 c_unsaveignore(void *v)
786 int rv;
787 NYD_ENTER;
789 rv = unignore1((char**)v, saveignore, "ignored");
790 NYD_LEAVE;
791 return rv;
794 FL int
795 c_unsaveretain(void *v)
797 int rv;
798 NYD_ENTER;
800 rv = unignore1((char**)v, saveignore + 1, "retained");
801 NYD_LEAVE;
802 return rv;
805 FL int
806 c_unfwdignore(void *v)
808 int rv;
809 NYD_ENTER;
811 rv = unignore1((char**)v, fwdignore, "ignored");
812 NYD_LEAVE;
813 return rv;
816 FL int
817 c_unfwdretain(void *v)
819 int rv;
820 NYD_ENTER;
822 rv = unignore1((char**)v, fwdignore + 1, "retained");
823 NYD_LEAVE;
824 return rv;
827 /* vim:set fenc=utf-8:s-it-mode */