Finally: drop IMAP support!..
[s-mailx.git] / cmd2.c
blob8806e1e27800e612aed89cc647fc5eed736f29cd
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 - 2015 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. Neither the name of the University nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
35 #undef n_FILE
36 #define n_FILE cmd2
38 #ifndef HAVE_AMALGAMATION
39 # include "nail.h"
40 #endif
42 /* Save/copy the indicated messages at the end of the passed file name.
43 * If mark is true, mark the message "saved" */
44 static int save1(char *str, int domark, char const *cmd,
45 struct ignoretab *ignoret, int convert, int sender_record,
46 int domove);
48 /* Snarf the file from the end of the command line and return a pointer to it.
49 * If there is no file attached, return the mbox file. Put a null in front of
50 * the file name so that the message list processing won't see it, unless the
51 * file name is the only thing on the line, in which case, return 0 in the
52 * reference flag variable */
53 static char * snarf(char *linebuf, bool_t *flag, bool_t usembox);
55 /* Delete the indicated messages. Set dot to some nice place afterwards */
56 static int delm(int *msgvec);
58 static int ignore1(char **list, struct ignoretab *tab, char const *which);
60 /* Print out all currently retained fields */
61 static int igshow(struct ignoretab *tab, char const *which);
63 /* Compare two names for sorting ignored field list */
64 static int igcomp(void const *l, void const *r);
66 /* */
67 static int _unignore(char **list, struct ignoretab *tab, char const *which);
68 static void __unign_all(struct ignoretab *tab);
69 static void __unign_one(struct ignoretab *tab, char const *name);
71 static int
72 save1(char *str, int domark, char const *cmd, struct ignoretab *ignoret,
73 int convert, int sender_record, int domove)
75 ui64_t mstats[1], tstats[2];
76 struct stat st;
77 int newfile = 0, last = 0, *msgvec, *ip;
78 struct message *mp;
79 char *file = NULL, *cp, *cq;
80 char const *disp = "";
81 FILE *obuf;
82 bool_t success = FAL0, f;
83 NYD_ENTER;
85 msgvec = salloc((msgCount + 2) * sizeof *msgvec);
86 if (sender_record) {
87 for (cp = str; *cp != '\0' && blankchar(*cp); ++cp)
89 f = (*cp != '\0');
90 } else {
91 if ((file = snarf(str, &f, convert != SEND_TOFILE)) == NULL)
92 goto jleave;
95 if (!f) {
96 *msgvec = first(0, MMNORM);
97 msgvec[1] = 0;
98 } else if (getmsglist(str, msgvec, 0) < 0)
99 goto jleave;
100 if (*msgvec == 0) {
101 if (pstate & PS_HOOK_MASK) {
102 success = TRU1;
103 goto jleave;
105 printf(_("No messages to %s.\n"), cmd);
106 goto jleave;
109 if (sender_record) {
110 if ((cp = nameof(message + *msgvec - 1, 0)) == NULL) {
111 printf(_("Cannot determine message sender to %s.\n"), cmd);
112 goto jleave;
115 for (cq = cp; *cq != '\0' && *cq != '@'; cq++)
117 *cq = '\0';
118 if (ok_blook(outfolder)) {
119 size_t sz = strlen(cp) +1;
120 file = salloc(sz + 1);
121 file[0] = '+';
122 memcpy(file + 1, cp, sz);
123 } else
124 file = cp;
127 if ((file = expand(file)) == NULL)
128 goto jleave;
129 if (access(file, F_OK) >= 0) {
130 newfile = 0;
131 disp = _("[Appended]");
132 } else {
133 newfile = 1;
134 disp = _("[New file]");
137 obuf = ((convert == SEND_TOFILE) ? Fopen(file, "a+") : Zopen(file, "a+"));
138 if (obuf == NULL) {
139 obuf = ((convert == SEND_TOFILE) ? Fopen(file, "wx") : Zopen(file, "wx"));
140 if (obuf == NULL) {
141 n_perr(file, 0);
142 goto jleave;
144 } else {
145 if (!newfile && !fstat(fileno(obuf), &st) && S_ISREG(st.st_mode) &&
146 fseek(obuf, -2L, SEEK_END) == 0) {
147 char buf[2];
148 int prependnl = 0;
150 switch (fread(buf, sizeof *buf, 2, obuf)) {
151 case 2:
152 if (buf[1] != '\n') {
153 prependnl = 1;
154 break;
156 /* FALLTHRU */
157 case 1:
158 if (buf[0] != '\n')
159 prependnl = 1;
160 break;
161 default:
162 if (ferror(obuf)) {
163 n_perr(file, 0);
164 goto jleave;
166 prependnl = 0;
169 fflush(obuf);
170 if (prependnl) {
171 putc('\n', obuf);
172 fflush(obuf);
177 success = TRU1;
178 tstats[0] = tstats[1] = 0;
180 srelax_hold();
181 for (ip = msgvec; *ip != 0 && UICMP(z, PTR2SIZE(ip - msgvec), <, msgCount);
182 ++ip) {
183 mp = message + *ip - 1;
184 if (sendmp(mp, obuf, ignoret, NULL, convert, mstats) < 0) {
185 success = FAL0;
186 goto jferr;
188 srelax();
190 touch(mp);
191 if (domark)
192 mp->m_flag |= MSAVED;
193 if (domove) {
194 mp->m_flag |= MDELETED | MSAVED;
195 last = *ip;
198 tstats[0] += mstats[0];
199 tstats[1] += mp->m_lines;/* TODO won't work, need target! v15!! */
201 srelax_rele();
203 fflush(obuf);
204 if (ferror(obuf)) {
205 jferr:
206 n_perr(file, 0);
207 if (!success)
208 srelax_rele();
209 success = FAL0;
211 if (Fclose(obuf) != 0)
212 success = FAL0;
214 if (success) {
215 printf("\"%s\" %s %" /*PRIu64 "/%"*/ PRIu64 " bytes\n",
216 file, disp, /*tstats[1], TODO v15: lines written */ tstats[0]);
217 } else if (domark) {
218 newfile = ~MSAVED;
219 goto jiterand;
220 } else if (domove) {
221 newfile = ~(MSAVED | MDELETED);
222 jiterand:
223 for (ip = msgvec; *ip != 0 &&
224 UICMP(z, PTR2SIZE(ip - msgvec), <, msgCount); ++ip) {
225 mp = message + *ip - 1;
226 mp->m_flag &= newfile;
230 if (domove && last && success) {
231 setdot(message + last - 1);
232 last = first(0, MDELETED);
233 setdot(message + (last != 0 ? last - 1 : 0));
235 jleave:
236 NYD_LEAVE;
237 return (success == FAL0);
240 static char *
241 snarf(char *linebuf, bool_t *flag, bool_t usembox)
243 char *cp;
244 NYD_ENTER;
246 if ((cp = laststring(linebuf, flag, FAL0)) == NULL) {
247 if (usembox) {
248 *flag = FAL0;
249 cp = expand("&");
250 } else
251 n_err(_("No file specified\n"));
253 NYD_LEAVE;
254 return cp;
257 static int
258 delm(int *msgvec)
260 struct message *mp;
261 int rv = -1, *ip, last;
262 NYD_ENTER;
264 last = 0;
265 for (ip = msgvec; *ip != 0; ++ip) {
266 mp = message + *ip - 1;
267 touch(mp);
268 mp->m_flag |= MDELETED | MTOUCH;
269 mp->m_flag &= ~(MPRESERVE | MSAVED | MBOX);
270 last = *ip;
272 if (last != 0) {
273 setdot(message + last - 1);
274 last = first(0, MDELETED);
275 if (last != 0) {
276 setdot(message + last - 1);
277 rv = 0;
278 } else {
279 setdot(message);
282 NYD_LEAVE;
283 return rv;
286 static int
287 ignore1(char **list, struct ignoretab *tab, char const *which)
289 int h;
290 struct ignored *igp;
291 char **ap;
292 NYD_ENTER;
294 if (*list == NULL) {
295 h = igshow(tab, which);
296 goto jleave;
299 for (ap = list; *ap != 0; ++ap) {
300 char *field;
301 size_t sz;
303 sz = strlen(*ap) +1;
304 field = ac_alloc(sz);
305 i_strcpy(field, *ap, sz);
306 if (member(field, tab))
307 goto jnext;
309 h = hash(field);
310 igp = scalloc(1, sizeof *igp);
311 sz = strlen(field) +1;
312 igp->i_field = smalloc(sz);
313 memcpy(igp->i_field, field, sz);
314 igp->i_link = tab->i_head[h];
315 tab->i_head[h] = igp;
316 ++tab->i_count;
317 jnext:
318 ac_free(field);
320 h = 0;
321 jleave:
322 NYD_LEAVE;
323 return h;
326 static int
327 igshow(struct ignoretab *tab, char const *which)
329 int h;
330 struct ignored *igp;
331 char **ap, **ring;
332 NYD_ENTER;
334 if (tab->i_count == 0) {
335 printf(_("No fields currently being %s.\n"), which);
336 goto jleave;
339 ring = salloc((tab->i_count + 1) * sizeof *ring);
340 ap = ring;
341 for (h = 0; h < HSHSIZE; ++h)
342 for (igp = tab->i_head[h]; igp != 0; igp = igp->i_link)
343 *ap++ = igp->i_field;
344 *ap = 0;
346 qsort(ring, tab->i_count, sizeof *ring, igcomp);
348 for (ap = ring; *ap != NULL; ++ap)
349 printf("%s\n", *ap);
350 jleave:
351 NYD_LEAVE;
352 return 0;
355 static int
356 igcomp(void const *l, void const *r)
358 int rv;
359 NYD_ENTER;
361 rv = strcmp(*(char**)UNCONST(l), *(char**)UNCONST(r));
362 NYD_LEAVE;
363 return rv;
366 static int
367 _unignore(char **list, struct ignoretab *tab, char const *which)
369 char *field;
370 NYD_ENTER;
372 if (tab->i_count == 0)
373 printf(_("No fields currently being %s.\n"), which);
374 else
375 while ((field = *list++) != NULL)
376 if (field[0] == '*' && field[1] == '\0') {
377 __unign_all(tab);
378 break;
379 } else
380 __unign_one(tab, field);
381 NYD_LEAVE;
382 return 0;
385 static void
386 __unign_all(struct ignoretab *tab)
388 size_t i;
389 struct ignored *n, *x;
390 NYD_ENTER;
392 for (i = 0; i < NELEM(tab->i_head); ++i)
393 for (n = tab->i_head[i]; n != NULL; n = x) {
394 x = n->i_link;
395 free(n->i_field);
396 free(n);
398 memset(tab, 0, sizeof *tab);
399 NYD_LEAVE;
402 static void
403 __unign_one(struct ignoretab *tab, char const *name)
405 struct ignored *ip, *iq;
406 int h;
407 NYD_ENTER;
409 h = hash(name);
410 for (iq = NULL, ip = tab->i_head[h]; ip != NULL; ip = ip->i_link) {
411 if (!asccasecmp(ip->i_field, name)) {
412 free(ip->i_field);
413 if (iq != NULL)
414 iq->i_link = ip->i_link;
415 else
416 tab->i_head[h] = ip->i_link;
417 free(ip);
418 --tab->i_count;
419 break;
421 iq = ip;
423 NYD_LEAVE;
426 FL int
427 c_next(void *v)
429 int list[2], *ip, *ip2, mdot, *msgvec = v, rv = 1;
430 struct message *mp;
431 NYD_ENTER;
433 if (*msgvec != 0) {
434 /* If some messages were supplied, find the first applicable one
435 * following dot using wrap around */
436 mdot = (int)PTR2SIZE(dot - message + 1);
438 /* Find first message in supplied message list which follows dot */
439 for (ip = msgvec; *ip != 0; ++ip) {
440 if ((mb.mb_threaded ? message[*ip - 1].m_threadpos > dot->m_threadpos
441 : *ip > mdot))
442 break;
444 if (*ip == 0)
445 ip = msgvec;
446 ip2 = ip;
447 do {
448 mp = message + *ip2 - 1;
449 if (!(mp->m_flag & MMNDEL)) {
450 setdot(mp);
451 goto jhitit;
453 if (*ip2 != 0)
454 ++ip2;
455 if (*ip2 == 0)
456 ip2 = msgvec;
457 } while (ip2 != ip);
458 printf(_("No messages applicable\n"));
459 goto jleave;
462 /* If this is the first command, select message 1. Note that this must
463 * exist for us to get here at all */
464 if (!(pstate & PS_SAW_COMMAND)) {
465 if (msgCount == 0)
466 goto jateof;
467 goto jhitit;
470 /* Just find the next good message after dot, no wraparound */
471 if (mb.mb_threaded == 0) {
472 for (mp = dot + !!(pstate & PS_DID_PRINT_DOT);
473 PTRCMP(mp, <, message + msgCount); ++mp)
474 if (!(mp->m_flag & MMNORM))
475 break;
476 } else {
477 /* TODO The threading code had some bugs that caused crashes.
478 * TODO The last thing (before the deep look) happens here,
479 * TODO so let's not trust PS_DID_PRINT_DOT but check & hope it fixes */
480 if ((mp = dot) != NULL && (pstate & PS_DID_PRINT_DOT))
481 mp = next_in_thread(mp);
482 while (mp != NULL && (mp->m_flag & MMNORM))
483 mp = next_in_thread(mp);
485 if (mp == NULL || PTRCMP(mp, >=, message + msgCount)) {
486 jateof:
487 printf(_("At EOF\n"));
488 rv = 0;
489 goto jleave;
491 setdot(mp);
493 /* Print dot */
494 jhitit:
495 list[0] = (int)PTR2SIZE(dot - message + 1);
496 list[1] = 0;
497 rv = c_type(list);
498 jleave:
499 NYD_LEAVE;
500 return rv;
503 FL int
504 c_save(void *v)
506 char *str = v;
507 int rv;
508 NYD_ENTER;
510 rv = save1(str, 1, "save", saveignore, SEND_MBOX, 0, 0);
511 NYD_LEAVE;
512 return rv;
515 FL int
516 c_Save(void *v)
518 char *str = v;
519 int rv;
520 NYD_ENTER;
522 rv = save1(str, 1, "save", saveignore, SEND_MBOX, 1, 0);
523 NYD_LEAVE;
524 return rv;
527 FL int
528 c_copy(void *v)
530 char *str = v;
531 int rv;
532 NYD_ENTER;
534 rv = save1(str, 0, "copy", saveignore, SEND_MBOX, 0, 0);
535 NYD_LEAVE;
536 return rv;
539 FL int
540 c_Copy(void *v)
542 char *str = v;
543 int rv;
544 NYD_ENTER;
546 rv = save1(str, 0, "copy", saveignore, SEND_MBOX, 1, 0);
547 NYD_LEAVE;
548 return rv;
551 FL int
552 c_move(void *v)
554 char *str = v;
555 int rv;
556 NYD_ENTER;
558 rv = save1(str, 0, "move", saveignore, SEND_MBOX, 0, 1);
559 NYD_LEAVE;
560 return rv;
563 FL int
564 c_Move(void *v)
566 char *str = v;
567 int rv;
568 NYD_ENTER;
570 rv = save1(str, 0, "move", saveignore, SEND_MBOX, 1, 1);
571 NYD_LEAVE;
572 return rv;
575 FL int
576 c_decrypt(void *v)
578 char *str = v;
579 int rv;
580 NYD_ENTER;
582 rv = save1(str, 0, "decrypt", saveignore, SEND_DECRYPT, 0, 0);
583 NYD_LEAVE;
584 return rv;
587 FL int
588 c_Decrypt(void *v)
590 char *str = v;
591 int rv;
592 NYD_ENTER;
594 rv = save1(str, 0, "decrypt", saveignore, SEND_DECRYPT, 1, 0);
595 NYD_LEAVE;
596 return rv;
599 FL int
600 c_write(void *v)
602 char *str = v;
603 int rv;
604 NYD_ENTER;
606 if (str == NULL || *str == '\0')
607 str = savestr("/dev/null");
608 rv = save1(str, 0, "write", allignore, SEND_TOFILE, 0, 0);
609 NYD_LEAVE;
610 return rv;
613 FL int
614 c_delete(void *v)
616 int *msgvec = v;
617 NYD_ENTER;
619 delm(msgvec);
620 NYD_LEAVE;
621 return 0;
624 FL int
625 c_deltype(void *v)
627 int list[2], rv = 0, *msgvec = v, lastdot;
628 NYD_ENTER;
630 lastdot = (int)PTR2SIZE(dot - message + 1);
631 if (delm(msgvec) >= 0) {
632 list[0] = (int)PTR2SIZE(dot - message + 1);
633 if (list[0] > lastdot) {
634 touch(dot);
635 list[1] = 0;
636 rv = c_type(list);
637 goto jleave;
639 printf(_("At EOF\n"));
640 } else
641 printf(_("No more messages\n"));
642 jleave:
643 NYD_LEAVE;
644 return rv;
647 FL int
648 c_undelete(void *v)
650 int *msgvec = v, *ip;
651 struct message *mp;
652 NYD_ENTER;
654 for (ip = msgvec; *ip != 0 && UICMP(z, PTR2SIZE(ip - msgvec), <, msgCount);
655 ++ip) {
656 mp = message + *ip - 1;
657 touch(mp);
658 setdot(mp);
659 if (mp->m_flag & (MDELETED | MSAVED))
660 mp->m_flag &= ~(MDELETED | MSAVED);
661 else
662 mp->m_flag &= ~MDELETED;
664 NYD_LEAVE;
665 return 0;
668 FL int
669 c_retfield(void *v)
671 char **list = v;
672 int rv;
673 NYD_ENTER;
675 rv = ignore1(list, ignore + 1, "retained");
676 NYD_LEAVE;
677 return rv;
680 FL int
681 c_igfield(void *v)
683 char **list = v;
684 int rv;
685 NYD_ENTER;
687 rv = ignore1(list, ignore, "ignored");
688 NYD_LEAVE;
689 return rv;
692 FL int
693 c_saveretfield(void *v)
695 char **list = v;
696 int rv;
697 NYD_ENTER;
699 rv = ignore1(list, saveignore + 1, "retained");
700 NYD_LEAVE;
701 return rv;
704 FL int
705 c_saveigfield(void *v)
707 char **list = v;
708 int rv;
709 NYD_ENTER;
711 rv = ignore1(list, saveignore, "ignored");
712 NYD_LEAVE;
713 return rv;
716 FL int
717 c_fwdretfield(void *v)
719 char **list = v;
720 int rv;
721 NYD_ENTER;
723 rv = ignore1(list, fwdignore + 1, "retained");
724 NYD_LEAVE;
725 return rv;
728 FL int
729 c_fwdigfield(void *v)
731 char **list = v;
732 int rv;
733 NYD_ENTER;
735 rv = ignore1(list, fwdignore, "ignored");
736 NYD_LEAVE;
737 return rv;
740 FL int
741 c_unignore(void *v)
743 int rv;
744 NYD_ENTER;
746 rv = _unignore((char**)v, ignore, "ignored");
747 NYD_LEAVE;
748 return rv;
751 FL int
752 c_unretain(void *v)
754 int rv;
755 NYD_ENTER;
757 rv = _unignore((char**)v, ignore + 1, "retained");
758 NYD_LEAVE;
759 return rv;
762 FL int
763 c_unsaveignore(void *v)
765 int rv;
766 NYD_ENTER;
768 rv = _unignore((char**)v, saveignore, "ignored");
769 NYD_LEAVE;
770 return rv;
773 FL int
774 c_unsaveretain(void *v)
776 int rv;
777 NYD_ENTER;
779 rv = _unignore((char**)v, saveignore + 1, "retained");
780 NYD_LEAVE;
781 return rv;
784 FL int
785 c_unfwdignore(void *v)
787 int rv;
788 NYD_ENTER;
790 rv = _unignore((char**)v, fwdignore, "ignored");
791 NYD_LEAVE;
792 return rv;
795 FL int
796 c_unfwdretain(void *v)
798 int rv;
799 NYD_ENTER;
801 rv = _unignore((char**)v, fwdignore + 1, "retained");
802 NYD_LEAVE;
803 return rv;
806 /* s-it-mode */