Fix *editalong* with new [okeys] interface (introduced reverso)
[s-mailx.git] / cmd2.c
blobfa4ab0ef39935655f1316dd68604b807ac245077
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 static int save1(char *str, int domark, char const *cmd,
47 struct ignoretab *ignore, int convert,
48 int sender_record, int domove);
49 static char * snarf(char *linebuf, bool_t *flag, bool_t usembox);
50 static int delm(int *msgvec);
51 static int ignore1(char **list, struct ignoretab *tab, char const *which);
52 static int igshow(struct ignoretab *tab, char const *which);
53 static int igcomp(const void *l, const void *r);
54 static void unignore_one(const char *name, struct ignoretab *tab);
55 static int unignore1(char **list, struct ignoretab *tab,
56 char const *which);
59 * If any arguments were given, go to the next applicable argument
60 * following dot, otherwise, go to the next applicable message.
61 * If given as first command with no arguments, print first message.
63 FL int
64 next(void *v)
66 int *msgvec = v;
67 struct message *mp;
68 int *ip, *ip2;
69 int list[2], mdot;
71 if (*msgvec != 0) {
74 * If some messages were supplied, find the
75 * first applicable one following dot using
76 * wrap around.
79 mdot = dot - &message[0] + 1;
82 * Find the first message in the supplied
83 * message list which follows dot.
86 for (ip = msgvec; *ip != 0; ip++) {
87 #ifdef _CRAY
89 * Work around an optimizer bug in Cray Standard C Version 4.0.3 (057126).
90 * Otherwise, SIGFPE is received when mb.mb_threaded != 0.
92 #pragma _CRI suppress ip
93 #endif /* _CRAY */
94 if (mb.mb_threaded ? message[*ip-1].m_threadpos >
95 dot->m_threadpos :
96 *ip > mdot)
97 break;
99 if (*ip == 0)
100 ip = msgvec;
101 ip2 = ip;
102 do {
103 mp = &message[*ip2 - 1];
104 if ((mp->m_flag & (MDELETED|MHIDDEN)) == 0) {
105 setdot(mp);
106 goto hitit;
108 if (*ip2 != 0)
109 ip2++;
110 if (*ip2 == 0)
111 ip2 = msgvec;
112 } while (ip2 != ip);
113 printf(tr(21, "No messages applicable\n"));
114 return(1);
118 * If this is the first command, select message 1.
119 * Note that this must exist for us to get here at all.
122 if (!sawcom) {
123 if (msgCount == 0)
124 goto ateof;
125 goto hitit;
129 * Just find the next good message after dot, no
130 * wraparound.
133 if (mb.mb_threaded == 0) {
134 for (mp = dot + did_print_dot; mp < &message[msgCount]; mp++)
135 if ((mp->m_flag & (MDELETED|MSAVED|MHIDDEN)) == 0)
136 break;
137 } else {
138 mp = dot;
139 if (did_print_dot)
140 mp = next_in_thread(mp);
141 while (mp && mp->m_flag & (MDELETED|MSAVED|MHIDDEN))
142 mp = next_in_thread(mp);
144 if (mp == NULL || mp >= &message[msgCount]) {
145 ateof:
146 printf(tr(22, "At EOF\n"));
147 return(0);
149 setdot(mp);
150 hitit:
152 * Print dot.
155 list[0] = dot - &message[0] + 1;
156 list[1] = 0;
157 return(type(list));
161 * Save a message in a file. Mark the message as saved
162 * so we can discard when the user quits.
164 FL int
165 save(void *v)
167 char *str = v;
169 return save1(str, 1, "save", saveignore, SEND_MBOX, 0, 0);
172 FL int
173 Save(void *v)
175 char *str = v;
177 return save1(str, 1, "save", saveignore, SEND_MBOX, 1, 0);
181 * Copy a message to a file without affected its saved-ness
183 FL int
184 copycmd(void *v)
186 char *str = v;
188 return save1(str, 0, "copy", saveignore, SEND_MBOX, 0, 0);
191 FL int
192 Copycmd(void *v)
194 char *str = v;
196 return save1(str, 0, "copy", saveignore, SEND_MBOX, 1, 0);
200 * Move a message to a file.
202 FL int
203 cmove(void *v)
205 char *str = v;
207 return save1(str, 0, "move", saveignore, SEND_MBOX, 0, 1);
210 FL int
211 cMove(void *v)
213 char *str = v;
215 return save1(str, 0, "move", saveignore, SEND_MBOX, 1, 1);
219 * Decrypt and copy a message to a file.
221 FL int
222 cdecrypt(void *v)
224 char *str = v;
226 return save1(str, 0, "decrypt", saveignore, SEND_DECRYPT, 0, 0);
229 FL int
230 cDecrypt(void *v)
232 char *str = v;
234 return save1(str, 0, "decrypt", saveignore, SEND_DECRYPT, 1, 0);
238 * Save/copy the indicated messages at the end of the passed file name.
239 * If mark is true, mark the message "saved."
241 static int
242 save1(char *str, int domark, char const *cmd, struct ignoretab *ignoret,
243 int convert, int sender_record, int domove)
245 off_t mstats[2], tstats[2];
246 struct stat st;
247 int newfile = 0, compressed = 0, success = 1, last = 0, *msgvec, *ip;
248 struct message *mp;
249 char *file = NULL, *cp, *cq;
250 char const *disp = "";
251 FILE *obuf;
252 enum protocol prot;
253 bool_t f;
255 /*LINTED*/
256 msgvec = (int *)salloc((msgCount + 2) * sizeof *msgvec);
257 if (sender_record) {
258 for (cp = str; *cp && blankchar(*cp); cp++)
260 f = (*cp != '\0');
261 } else {
262 if ((file = snarf(str, &f, convert != SEND_TOFILE)) == NULL)
263 return(1);
265 if (!f) {
266 *msgvec = first(0, MMNORM);
267 if (*msgvec == 0) {
268 if (inhook)
269 return 0;
270 printf(tr(23, "No messages to %s.\n"), cmd);
271 return(1);
273 msgvec[1] = 0;
274 } else if (getmsglist(str, msgvec, 0) < 0)
275 return(1);
276 if (*msgvec == 0) {
277 if (inhook)
278 return 0;
279 printf("No applicable messages.\n");
280 return 1;
282 if (sender_record) {
283 if ((cp = nameof(&message[*msgvec - 1], 0)) == NULL) {
284 printf(tr(24,
285 "Cannot determine message sender to %s.\n"),
286 cmd);
287 return 1;
289 for (cq = cp; *cq && *cq != '@'; cq++);
290 *cq = '\0';
291 if (ok_blook(outfolder)) {
292 size_t sz = strlen(cp) + 1;
293 file = salloc(sz + 1);
294 file[0] = '+';
295 memcpy(file + 1, cp, sz);
296 } else
297 file = cp;
299 if ((file = expand(file)) == NULL)
300 return (1);
301 prot = which_protocol(file);
302 if (prot != PROTO_IMAP) {
303 if (access(file, 0) >= 0) {
304 newfile = 0;
305 disp = tr(25, "[Appended]");
306 } else {
307 newfile = 1;
308 disp = tr(26, "[New file]");
311 if ((obuf = convert == SEND_TOFILE ? Fopen(file, "a+") :
312 Zopen(file, "a+", &compressed)) == NULL) {
313 if ((obuf = convert == SEND_TOFILE ? Fopen(file, "wx") :
314 Zopen(file, "wx", &compressed)) == NULL) {
315 perror(file);
316 return(1);
318 } else {
319 if (compressed) {
320 newfile = 0;
321 disp = tr(25, "[Appended]");
323 if (!newfile && fstat(fileno(obuf), &st) &&
324 S_ISREG(st.st_mode) &&
325 fseek(obuf, -2L, SEEK_END) == 0) {
326 char buf[2];
327 int prependnl = 0;
329 switch (fread(buf, sizeof *buf, 2, obuf)) {
330 case 2:
331 if (buf[1] != '\n') {
332 prependnl = 1;
333 break;
335 /*FALLTHRU*/
336 case 1:
337 if (buf[0] != '\n')
338 prependnl = 1;
339 break;
340 default:
341 if (ferror(obuf)) {
342 perror(file);
343 return(1);
345 prependnl = 0;
347 fflush(obuf);
348 if (prependnl) {
349 putc('\n', obuf);
350 fflush(obuf);
355 tstats[0] = tstats[1] = 0;
356 imap_created_mailbox = 0;
357 srelax_hold();
358 for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) {
359 mp = &message[*ip - 1];
360 if (prot == PROTO_IMAP &&
361 ignoret[0].i_count == 0 &&
362 ignoret[1].i_count == 0
363 #ifdef HAVE_IMAP /* TODO revisit */
364 && imap_thisaccount(file)
365 #endif
367 #ifdef HAVE_IMAP
368 if (imap_copy(mp, *ip, file) == STOP)
369 #endif
370 goto jferr;
371 #ifdef HAVE_IMAP
372 mstats[0] = -1;
373 mstats[1] = mp->m_xsize;
374 #endif
375 } else if (sendmp(mp, obuf, ignoret, NULL,
376 convert, mstats) < 0) {
377 perror(file);
378 goto jferr;
380 srelax();
381 touch(mp);
382 if (domark)
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 jferr:
395 success = 0;
397 if (Fclose(obuf) != 0)
398 success = 0;
399 srelax_rele();
401 if (success) {
402 if (prot == PROTO_IMAP || prot == PROTO_MAILDIR) {
403 disp = (
404 #ifdef HAVE_IMAP
405 ((prot == PROTO_IMAP) && disconnected(file))
406 ? "[Queued]" :
407 #endif
408 (imap_created_mailbox ? "[New file]"
409 : "[Appended]"));
411 printf("\"%s\" %s ", file, disp);
412 if (tstats[0] >= 0)
413 printf("%lu", (long)tstats[0]);
414 else
415 printf(tr(27, "binary"));
416 printf("/%lu\n", (long)tstats[1]);
417 } else if (domark) {
418 for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) {
419 mp = &message[*ip - 1];
420 mp->m_flag &= ~MSAVED;
422 } else if (domove) {
423 for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) {
424 mp = &message[*ip - 1];
425 mp->m_flag &= ~(MSAVED|MDELETED);
428 if (domove && last && success) {
429 setdot(&message[last-1]);
430 last = first(0, MDELETED);
431 setdot(&message[last ? last-1 : 0]);
433 return(success == 0);
437 * Write the indicated messages at the end of the passed
438 * file name, minus header and trailing blank line.
439 * This is the MIME save function.
441 FL int
442 cwrite(void *v)
444 char *str = v;
446 if (str == NULL || *str == '\0')
447 str = savestr("/dev/null");
448 return (save1(str, 0, "write", allignore, SEND_TOFILE, 0, 0));
452 * Snarf the file from the end of the command line and
453 * return a pointer to it. If there is no file attached,
454 * return the mbox file. Put a null in front of the file
455 * name so that the message list processing won't see it,
456 * unless the file name is the only thing on the line, in
457 * which case, return 0 in the reference flag variable.
459 static char *
460 snarf(char *linebuf, bool_t *flag, bool_t usembox)
462 char *cp;
464 if ((cp = laststring(linebuf, flag, 0)) == NULL) {
465 if (usembox) {
466 *flag = FAL0;
467 cp = expand("&");
468 } else
469 fprintf(stderr, tr(28, "No file specified.\n"));
471 return (cp);
475 * Delete messages.
477 FL int
478 delete(void *v)
480 int *msgvec = v;
481 delm(msgvec);
482 return 0;
486 * Delete messages, then type the new dot.
488 FL int
489 deltype(void *v)
491 int *msgvec = v;
492 int list[2];
493 int lastdot;
495 lastdot = dot - &message[0] + 1;
496 if (delm(msgvec) >= 0) {
497 list[0] = dot - &message[0] + 1;
498 if (list[0] > lastdot) {
499 touch(dot);
500 list[1] = 0;
501 return(type(list));
503 printf(tr(29, "At EOF\n"));
504 } else
505 printf(tr(30, "No more messages\n"));
506 return(0);
510 * Delete the indicated messages.
511 * Set dot to some nice place afterwards.
512 * Internal interface.
514 static int
515 delm(int *msgvec)
517 struct message *mp;
518 int *ip;
519 int last;
521 last = 0;
522 for (ip = msgvec; *ip != 0; ip++) {
523 mp = &message[*ip - 1];
524 touch(mp);
525 mp->m_flag |= MDELETED|MTOUCH;
526 mp->m_flag &= ~(MPRESERVE|MSAVED|MBOX);
527 last = *ip;
529 if (last != 0) {
530 setdot(&message[last-1]);
531 last = first(0, MDELETED);
532 if (last != 0) {
533 setdot(&message[last-1]);
534 return(0);
536 else {
537 setdot(&message[0]);
538 return(-1);
543 * Following can't happen -- it keeps lint happy
546 return(-1);
550 * Undelete the indicated messages.
552 FL int
553 undeletecmd(void *v)
555 int *msgvec = v;
556 struct message *mp;
557 int *ip;
559 for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) {
560 mp = &message[*ip - 1];
561 touch(mp);
562 setdot(mp);
563 if (mp->m_flag & (MDELETED|MSAVED))
564 mp->m_flag &= ~(MDELETED|MSAVED);
565 else
566 mp->m_flag &= ~MDELETED;
567 #ifdef HAVE_IMAP
568 if (mb.mb_type == MB_IMAP || mb.mb_type == MB_CACHE)
569 imap_undelete(mp, *ip);
570 #endif
572 return 0;
576 * Add the given header fields to the retained list.
577 * If no arguments, print the current list of retained fields.
579 FL int
580 retfield(void *v)
582 char **list = v;
584 return ignore1(list, ignore + 1, "retained");
588 * Add the given header fields to the ignored list.
589 * If no arguments, print the current list of ignored fields.
591 FL int
592 igfield(void *v)
594 char **list = v;
596 return ignore1(list, ignore, "ignored");
599 FL int
600 saveretfield(void *v)
602 char **list = v;
604 return ignore1(list, saveignore + 1, "retained");
607 FL int
608 saveigfield(void *v)
610 char **list = v;
612 return ignore1(list, saveignore, "ignored");
615 FL int
616 fwdretfield(void *v)
618 char **list = v;
620 return ignore1(list, fwdignore + 1, "retained");
623 FL int
624 fwdigfield(void *v)
626 char **list = v;
628 return ignore1(list, fwdignore, "ignored");
631 static int
632 ignore1(char **list, struct ignoretab *tab, char const *which)
634 int h;
635 struct ignore *igp;
636 char **ap;
638 if (*list == NULL)
639 return igshow(tab, which);
640 for (ap = list; *ap != 0; ap++) {
641 char *field;
642 size_t sz;
644 sz = strlen(*ap);
645 field = ac_alloc(sz + 1);
646 i_strcpy(field, *ap, sz + 1);
647 field[sz]='\0';
648 if (member(field, tab)) {
649 ac_free(field);
650 continue;
652 h = hash(field);
653 igp = (struct ignore *)scalloc(1, sizeof (struct ignore));
654 sz = strlen(field) + 1;
655 igp->i_field = smalloc(sz);
656 memcpy(igp->i_field, field, sz);
657 igp->i_link = tab->i_head[h];
658 tab->i_head[h] = igp;
659 tab->i_count++;
660 ac_free(field);
662 return 0;
666 * Print out all currently retained fields.
668 static int
669 igshow(struct ignoretab *tab, char const *which)
671 int h;
672 struct ignore *igp;
673 char **ap, **ring;
675 if (tab->i_count == 0) {
676 printf(tr(34, "No fields currently being %s.\n"), which);
677 return 0;
679 /*LINTED*/
680 ring = (char **)salloc((tab->i_count + 1) * sizeof (char *));
681 ap = ring;
682 for (h = 0; h < HSHSIZE; h++)
683 for (igp = tab->i_head[h]; igp != 0; igp = igp->i_link)
684 *ap++ = igp->i_field;
685 *ap = 0;
686 qsort(ring, tab->i_count, sizeof (char *), igcomp);
687 for (ap = ring; *ap != 0; ap++)
688 printf("%s\n", *ap);
689 return 0;
693 * Compare two names for sorting ignored field list.
695 static int
696 igcomp(const void *l, const void *r)
698 return (strcmp(*(char**)UNCONST(l), *(char**)UNCONST(r)));
701 FL int
702 unignore(void *v)
704 return unignore1((char **)v, ignore, "ignored");
707 FL int
708 unretain(void *v)
710 return unignore1((char **)v, ignore + 1, "retained");
713 FL int
714 unsaveignore(void *v)
716 return unignore1((char **)v, saveignore, "ignored");
719 FL int
720 unsaveretain(void *v)
722 return unignore1((char **)v, saveignore + 1, "retained");
725 FL int
726 unfwdignore(void *v)
728 return unignore1((char **)v, fwdignore, "ignored");
731 FL int
732 unfwdretain(void *v)
734 return unignore1((char **)v, fwdignore + 1, "retained");
737 static void
738 unignore_one(const char *name, struct ignoretab *tab)
740 struct ignore *ip, *iq = NULL;
741 int h = hash(name);
743 for (ip = tab->i_head[h]; ip; ip = ip->i_link) {
744 if (asccasecmp(ip->i_field, name) == 0) {
745 free(ip->i_field);
746 if (iq != NULL)
747 iq->i_link = ip->i_link;
748 else
749 tab->i_head[h] = ip->i_link;
750 free(ip);
751 tab->i_count--;
752 break;
754 iq = ip;
758 static int
759 unignore1(char **list, struct ignoretab *tab, char const *which)
761 if (tab->i_count == 0) {
762 printf(tr(34, "No fields currently being %s.\n"), which);
763 return 0;
765 while (*list)
766 unignore_one(*list++, tab);
767 return 0;