c_help(): puts(3) appends NL, do not add another one
[s-mailx.git] / cmd2.c
bloba295d436f456a836fd08f67d5a5f192bb052cc3d
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 /* */
71 static int _unignore(char **list, struct ignoretab *tab, char const *which);
72 static void __unign_all(struct ignoretab *tab);
73 static void __unign_one(struct ignoretab *tab, char const *name);
75 static int
76 save1(char *str, int domark, char const *cmd, struct ignoretab *ignoret,
77 int convert, int sender_record, int domove)
79 off_t mstats[2], tstats[2];
80 struct stat st;
81 int newfile = 0, compressed = 0, last = 0, *msgvec, *ip;
82 struct message *mp;
83 char *file = NULL, *cp, *cq;
84 char const *disp = "";
85 FILE *obuf;
86 enum protocol prot;
87 bool_t success = FAL0, f;
88 NYD_ENTER;
90 msgvec = salloc((msgCount + 2) * sizeof *msgvec);
91 if (sender_record) {
92 for (cp = str; *cp != '\0' && blankchar(*cp); ++cp)
94 f = (*cp != '\0');
95 } else {
96 if ((file = snarf(str, &f, convert != SEND_TOFILE)) == NULL)
97 goto jleave;
100 if (!f) {
101 *msgvec = first(0, MMNORM);
102 if (*msgvec == 0) {
103 if (inhook) {
104 success = TRU1;
105 goto jleave;
107 printf(_("No messages to %s.\n"), cmd);
108 goto jleave;
110 msgvec[1] = 0;
111 } else if (getmsglist(str, msgvec, 0) < 0)
112 goto jleave;
113 if (*msgvec == 0) {
114 if (inhook) {
115 success = TRU1;
116 goto jleave;
118 printf("No applicable messages.\n");
119 goto jleave;
122 if (sender_record) {
123 if ((cp = nameof(message + *msgvec - 1, 0)) == NULL) {
124 printf(_("Cannot determine message sender to %s.\n"), cmd);
125 goto jleave;
128 for (cq = cp; *cq != '\0' && *cq != '@'; cq++)
130 *cq = '\0';
131 if (ok_blook(outfolder)) {
132 size_t sz = strlen(cp) +1;
133 file = salloc(sz + 1);
134 file[0] = '+';
135 memcpy(file + 1, cp, sz);
136 } else
137 file = cp;
140 if ((file = expand(file)) == NULL)
141 goto jleave;
142 prot = which_protocol(file);
143 if (prot != PROTO_IMAP) {
144 if (access(file, 0) >= 0) {
145 newfile = 0;
146 disp = _("[Appended]");
147 } else {
148 newfile = 1;
149 disp = _("[New file]");
153 obuf = ((convert == SEND_TOFILE) ? Fopen(file, "a+")
154 : Zopen(file, "a+", &compressed));
155 if (obuf == NULL) {
156 obuf = ((convert == SEND_TOFILE) ? Fopen(file, "wx")
157 : Zopen(file, "wx", &compressed));
158 if (obuf == NULL) {
159 perror(file);
160 goto jleave;
162 } else {
163 if (compressed) {
164 newfile = 0;
165 disp = _("[Appended]");
167 if (!newfile && fstat(fileno(obuf), &st) && S_ISREG(st.st_mode) &&
168 fseek(obuf, -2L, SEEK_END) == 0) {
169 char buf[2];
170 int prependnl = 0;
172 switch (fread(buf, sizeof *buf, 2, obuf)) {
173 case 2:
174 if (buf[1] != '\n') {
175 prependnl = 1;
176 break;
178 /* FALLTHRU */
179 case 1:
180 if (buf[0] != '\n')
181 prependnl = 1;
182 break;
183 default:
184 if (ferror(obuf)) {
185 perror(file);
186 goto jleave;
188 prependnl = 0;
190 fflush(obuf);
191 if (prependnl) {
192 putc('\n', obuf);
193 fflush(obuf);
198 success = TRU1;
199 tstats[0] = tstats[1] = 0;
200 imap_created_mailbox = 0;
201 srelax_hold();
202 for (ip = msgvec; *ip != 0 && UICMP(z, PTR2SIZE(ip - msgvec), <, msgCount);
203 ++ip) {
204 mp = message + *ip - 1;
205 if (prot == PROTO_IMAP && ignoret[0].i_count == 0 &&
206 ignoret[1].i_count == 0
207 #ifdef HAVE_IMAP /* TODO revisit */
208 && imap_thisaccount(file)
209 #endif
211 #ifdef HAVE_IMAP
212 if (imap_copy(mp, *ip, file) == STOP)
213 #endif
214 goto jferr;
215 #ifdef HAVE_IMAP
216 mstats[0] = -1;
217 mstats[1] = mp->m_xsize;
218 #endif
219 } else if (sendmp(mp, obuf, ignoret, NULL, convert, mstats) < 0) {
220 perror(file);
221 goto jferr;
223 srelax();
224 touch(mp);
225 if (domark)
226 mp->m_flag |= MSAVED;
227 if (domove) {
228 mp->m_flag |= MDELETED | MSAVED;
229 last = *ip;
231 tstats[0] += mstats[0];
232 tstats[1] += mstats[1];
234 fflush(obuf);
235 if (ferror(obuf)) {
236 perror(file);
237 jferr:
238 success = FAL0;
240 if (Fclose(obuf) != 0)
241 success = FAL0;
242 srelax_rele();
244 if (success) {
245 if (prot == PROTO_IMAP || prot == PROTO_MAILDIR) {
246 disp = (
247 #ifdef HAVE_IMAP
248 ((prot == PROTO_IMAP) && disconnected(file)) ? "[Queued]" :
249 #endif
250 (imap_created_mailbox ? "[New file]" : "[Appended]"));
252 printf("\"%s\" %s ", file, disp);
253 if (tstats[0] >= 0)
254 printf("%lu", (ul_it)tstats[0]);
255 else
256 printf(_("binary"));
257 printf("/%lu\n", (ul_it)tstats[1]);
258 } else if (domark) {
259 newfile = ~MSAVED;
260 goto jiterand;
261 } else if (domove) {
262 newfile = ~(MSAVED | MDELETED);
263 jiterand:
264 for (ip = msgvec; *ip != 0 &&
265 UICMP(z, PTR2SIZE(ip - msgvec), <, msgCount); ++ip) {
266 mp = message + *ip - 1;
267 mp->m_flag &= newfile;
271 if (domove && last && success) {
272 setdot(message + last - 1);
273 last = first(0, MDELETED);
274 setdot(message + (last != 0 ? last - 1 : 0));
276 jleave:
277 NYD_LEAVE;
278 return (success == FAL0);
281 static char *
282 snarf(char *linebuf, bool_t *flag, bool_t usembox)
284 char *cp;
285 NYD_ENTER;
287 if ((cp = laststring(linebuf, flag, FAL0)) == NULL) {
288 if (usembox) {
289 *flag = FAL0;
290 cp = expand("&");
291 } else
292 fprintf(stderr, _("No file specified.\n"));
294 NYD_LEAVE;
295 return cp;
298 static int
299 delm(int *msgvec)
301 struct message *mp;
302 int rv = -1, *ip, last;
303 NYD_ENTER;
305 last = 0;
306 for (ip = msgvec; *ip != 0; ++ip) {
307 mp = message + *ip - 1;
308 touch(mp);
309 mp->m_flag |= MDELETED | MTOUCH;
310 mp->m_flag &= ~(MPRESERVE | MSAVED | MBOX);
311 last = *ip;
313 if (last != 0) {
314 setdot(message + last - 1);
315 last = first(0, MDELETED);
316 if (last != 0) {
317 setdot(message + last - 1);
318 rv = 0;
319 } else {
320 setdot(message);
323 NYD_LEAVE;
324 return rv;
327 static int
328 ignore1(char **list, struct ignoretab *tab, char const *which)
330 int h;
331 struct ignore *igp;
332 char **ap;
333 NYD_ENTER;
335 if (*list == NULL) {
336 h = igshow(tab, which);
337 goto jleave;
340 for (ap = list; *ap != 0; ++ap) {
341 char *field;
342 size_t sz;
344 sz = strlen(*ap) +1;
345 field = ac_alloc(sz);
346 i_strcpy(field, *ap, sz);
347 if (member(field, tab))
348 goto jnext;
350 h = hash(field);
351 igp = scalloc(1, sizeof *igp);
352 sz = strlen(field) +1;
353 igp->i_field = smalloc(sz);
354 memcpy(igp->i_field, field, sz);
355 igp->i_link = tab->i_head[h];
356 tab->i_head[h] = igp;
357 ++tab->i_count;
358 jnext:
359 ac_free(field);
361 h = 0;
362 jleave:
363 NYD_LEAVE;
364 return h;
367 static int
368 igshow(struct ignoretab *tab, char const *which)
370 int h;
371 struct ignore *igp;
372 char **ap, **ring;
373 NYD_ENTER;
375 if (tab->i_count == 0) {
376 printf(_("No fields currently being %s.\n"), which);
377 goto jleave;
380 ring = salloc((tab->i_count + 1) * sizeof *ring);
381 ap = ring;
382 for (h = 0; h < HSHSIZE; ++h)
383 for (igp = tab->i_head[h]; igp != 0; igp = igp->i_link)
384 *ap++ = igp->i_field;
385 *ap = 0;
387 qsort(ring, tab->i_count, sizeof *ring, igcomp);
389 for (ap = ring; *ap != NULL; ++ap)
390 printf("%s\n", *ap);
391 jleave:
392 NYD_LEAVE;
393 return 0;
396 static int
397 igcomp(void const *l, void const *r)
399 int rv;
400 NYD_ENTER;
402 rv = strcmp(*(char**)UNCONST(l), *(char**)UNCONST(r));
403 NYD_LEAVE;
404 return rv;
407 static int
408 _unignore(char **list, struct ignoretab *tab, char const *which)
410 char *field;
411 NYD_ENTER;
413 if (tab->i_count == 0)
414 printf(_("No fields currently being %s.\n"), which);
415 else
416 while ((field = *list++) != NULL)
417 if (field[0] == '*' && field[1] == '\0') {
418 __unign_all(tab);
419 break;
420 } else
421 __unign_one(tab, field);
422 NYD_LEAVE;
423 return 0;
426 static void
427 __unign_all(struct ignoretab *tab)
429 size_t i;
430 struct ignore *n, *x;
431 NYD_ENTER;
433 for (i = 0; i < NELEM(tab->i_head); ++i)
434 for (n = tab->i_head[i]; n != NULL; n = x) {
435 x = n->i_link;
436 free(n->i_field);
437 free(n);
439 memset(tab, 0, sizeof *tab);
440 NYD_LEAVE;
443 static void
444 __unign_one(struct ignoretab *tab, char const *name)
446 struct ignore *ip, *iq;
447 int h;
448 NYD_ENTER;
450 h = hash(name);
451 for (iq = NULL, ip = tab->i_head[h]; ip != NULL; ip = ip->i_link) {
452 if (!asccasecmp(ip->i_field, name)) {
453 free(ip->i_field);
454 if (iq != NULL)
455 iq->i_link = ip->i_link;
456 else
457 tab->i_head[h] = ip->i_link;
458 free(ip);
459 --tab->i_count;
460 break;
462 iq = ip;
464 NYD_LEAVE;
467 FL int
468 c_next(void *v)
470 int list[2], *ip, *ip2, mdot, *msgvec = v, rv = 1;
471 struct message *mp;
472 NYD_ENTER;
474 if (*msgvec != 0) {
475 /* If some messages were supplied, find the first applicable one
476 * following dot using wrap around */
477 mdot = (int)PTR2SIZE(dot - message + 1);
479 /* Find first message in supplied message list which follows dot */
480 for (ip = msgvec; *ip != 0; ++ip) {
481 if ((mb.mb_threaded ? message[*ip - 1].m_threadpos > dot->m_threadpos
482 : *ip > mdot))
483 break;
485 if (*ip == 0)
486 ip = msgvec;
487 ip2 = ip;
488 do {
489 mp = message + *ip2 - 1;
490 if (!(mp->m_flag & MMNDEL)) {
491 setdot(mp);
492 goto jhitit;
494 if (*ip2 != 0)
495 ++ip2;
496 if (*ip2 == 0)
497 ip2 = msgvec;
498 } while (ip2 != ip);
499 printf(_("No messages applicable\n"));
500 goto jleave;
503 /* If this is the first command, select message 1. Note that this must
504 * exist for us to get here at all */
505 if (!sawcom) {
506 if (msgCount == 0)
507 goto jateof;
508 goto jhitit;
511 /* Just find the next good message after dot, no wraparound */
512 if (mb.mb_threaded == 0) {
513 for (mp = dot + did_print_dot; PTRCMP(mp, <, message + msgCount); ++mp)
514 if (!(mp->m_flag & MMNORM))
515 break;
516 } else {
517 mp = dot;
518 if (did_print_dot)
519 mp = next_in_thread(mp);
520 while (mp && (mp->m_flag & MMNORM))
521 mp = next_in_thread(mp);
523 if (mp == NULL || PTRCMP(mp, >=, message + msgCount)) {
524 jateof:
525 printf(_("At EOF\n"));
526 rv = 0;
527 goto jleave;
529 setdot(mp);
531 /* Print dot */
532 jhitit:
533 list[0] = (int)PTR2SIZE(dot - message + 1);
534 list[1] = 0;
535 rv = c_type(list);
536 jleave:
537 NYD_LEAVE;
538 return rv;
541 FL int
542 c_save(void *v)
544 char *str = v;
545 int rv;
546 NYD_ENTER;
548 rv = save1(str, 1, "save", saveignore, SEND_MBOX, 0, 0);
549 NYD_LEAVE;
550 return rv;
553 FL int
554 c_Save(void *v)
556 char *str = v;
557 int rv;
558 NYD_ENTER;
560 rv = save1(str, 1, "save", saveignore, SEND_MBOX, 1, 0);
561 NYD_LEAVE;
562 return rv;
565 FL int
566 c_copy(void *v)
568 char *str = v;
569 int rv;
570 NYD_ENTER;
572 rv = save1(str, 0, "copy", saveignore, SEND_MBOX, 0, 0);
573 NYD_LEAVE;
574 return rv;
577 FL int
578 c_Copy(void *v)
580 char *str = v;
581 int rv;
582 NYD_ENTER;
584 rv = save1(str, 0, "copy", saveignore, SEND_MBOX, 1, 0);
585 NYD_LEAVE;
586 return rv;
589 FL int
590 c_move(void *v)
592 char *str = v;
593 int rv;
594 NYD_ENTER;
596 rv = save1(str, 0, "move", saveignore, SEND_MBOX, 0, 1);
597 NYD_LEAVE;
598 return rv;
601 FL int
602 c_Move(void *v)
604 char *str = v;
605 int rv;
606 NYD_ENTER;
608 rv = save1(str, 0, "move", saveignore, SEND_MBOX, 1, 1);
609 NYD_LEAVE;
610 return rv;
613 FL int
614 c_decrypt(void *v)
616 char *str = v;
617 int rv;
618 NYD_ENTER;
620 rv = save1(str, 0, "decrypt", saveignore, SEND_DECRYPT, 0, 0);
621 NYD_LEAVE;
622 return rv;
625 FL int
626 c_Decrypt(void *v)
628 char *str = v;
629 int rv;
630 NYD_ENTER;
632 rv = save1(str, 0, "decrypt", saveignore, SEND_DECRYPT, 1, 0);
633 NYD_LEAVE;
634 return rv;
637 FL int
638 c_write(void *v)
640 char *str = v;
641 int rv;
642 NYD_ENTER;
644 if (str == NULL || *str == '\0')
645 str = savestr("/dev/null");
646 rv = save1(str, 0, "write", allignore, SEND_TOFILE, 0, 0);
647 NYD_LEAVE;
648 return rv;
651 FL int
652 c_delete(void *v)
654 int *msgvec = v;
655 NYD_ENTER;
657 delm(msgvec);
658 NYD_LEAVE;
659 return 0;
662 FL int
663 c_deltype(void *v)
665 int list[2], rv = 0, *msgvec = v, lastdot;
666 NYD_ENTER;
668 lastdot = (int)PTR2SIZE(dot - message + 1);
669 if (delm(msgvec) >= 0) {
670 list[0] = (int)PTR2SIZE(dot - message + 1);
671 if (list[0] > lastdot) {
672 touch(dot);
673 list[1] = 0;
674 rv = c_type(list);
675 goto jleave;
677 printf(_("At EOF\n"));
678 } else
679 printf(_("No more messages\n"));
680 jleave:
681 NYD_LEAVE;
682 return rv;
685 FL int
686 c_undelete(void *v)
688 int *msgvec = v, *ip;
689 struct message *mp;
690 NYD_ENTER;
692 for (ip = msgvec; *ip != 0 && UICMP(z, PTR2SIZE(ip - msgvec), <, msgCount);
693 ++ip) {
694 mp = message + *ip - 1;
695 touch(mp);
696 setdot(mp);
697 if (mp->m_flag & (MDELETED | MSAVED))
698 mp->m_flag &= ~(MDELETED | MSAVED);
699 else
700 mp->m_flag &= ~MDELETED;
701 #ifdef HAVE_IMAP
702 if (mb.mb_type == MB_IMAP || mb.mb_type == MB_CACHE)
703 imap_undelete(mp, *ip);
704 #endif
706 NYD_LEAVE;
707 return 0;
710 FL int
711 c_retfield(void *v)
713 char **list = v;
714 int rv;
715 NYD_ENTER;
717 rv = ignore1(list, ignore + 1, "retained");
718 NYD_LEAVE;
719 return rv;
722 FL int
723 c_igfield(void *v)
725 char **list = v;
726 int rv;
727 NYD_ENTER;
729 rv = ignore1(list, ignore, "ignored");
730 NYD_LEAVE;
731 return rv;
734 FL int
735 c_saveretfield(void *v)
737 char **list = v;
738 int rv;
739 NYD_ENTER;
741 rv = ignore1(list, saveignore + 1, "retained");
742 NYD_LEAVE;
743 return rv;
746 FL int
747 c_saveigfield(void *v)
749 char **list = v;
750 int rv;
751 NYD_ENTER;
753 rv = ignore1(list, saveignore, "ignored");
754 NYD_LEAVE;
755 return rv;
758 FL int
759 c_fwdretfield(void *v)
761 char **list = v;
762 int rv;
763 NYD_ENTER;
765 rv = ignore1(list, fwdignore + 1, "retained");
766 NYD_LEAVE;
767 return rv;
770 FL int
771 c_fwdigfield(void *v)
773 char **list = v;
774 int rv;
775 NYD_ENTER;
777 rv = ignore1(list, fwdignore, "ignored");
778 NYD_LEAVE;
779 return rv;
782 FL int
783 c_unignore(void *v)
785 int rv;
786 NYD_ENTER;
788 rv = _unignore((char**)v, ignore, "ignored");
789 NYD_LEAVE;
790 return rv;
793 FL int
794 c_unretain(void *v)
796 int rv;
797 NYD_ENTER;
799 rv = _unignore((char**)v, ignore + 1, "retained");
800 NYD_LEAVE;
801 return rv;
804 FL int
805 c_unsaveignore(void *v)
807 int rv;
808 NYD_ENTER;
810 rv = _unignore((char**)v, saveignore, "ignored");
811 NYD_LEAVE;
812 return rv;
815 FL int
816 c_unsaveretain(void *v)
818 int rv;
819 NYD_ENTER;
821 rv = _unignore((char**)v, saveignore + 1, "retained");
822 NYD_LEAVE;
823 return rv;
826 FL int
827 c_unfwdignore(void *v)
829 int rv;
830 NYD_ENTER;
832 rv = _unignore((char**)v, fwdignore, "ignored");
833 NYD_LEAVE;
834 return rv;
837 FL int
838 c_unfwdretain(void *v)
840 int rv;
841 NYD_ENTER;
843 rv = _unignore((char**)v, fwdignore + 1, "retained");
844 NYD_LEAVE;
845 return rv;
848 /* s-it-mode */