Merge branch 'topic/unibidi'
[s-mailx.git] / cmd3.c
blob3cbd9afa4302d421ab2e26c44048012449a79283
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Still 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 struct cond_stack {
45 struct cond_stack *c_outer;
46 bool_t c_noop; /* Outer stack !c_go, entirely no-op */
47 bool_t c_go; /* Green light */
48 bool_t c_else; /* In `else' clause */
49 ui8_t __dummy[5];
52 static struct cond_stack *_cond_stack;
53 static char *_bang_buf;
54 static size_t _bang_size;
56 /* Modify subject we reply to to begin with Re: if it does not already */
57 static char * _reedit(char *subj);
59 /* Expand the shell escape by expanding unescaped !'s into the last issued
60 * command where possible */
61 static void _bangexp(char **str, size_t *size);
63 static void make_ref_and_cs(struct message *mp, struct header *head);
65 /* Get PTF to implementation of command `c' (i.e., take care for *flipr*) */
66 static int (* respond_or_Respond(int c))(int *, int);
68 /* Reply to a single message. Extract each name from the message header and
69 * send them off to mail1() */
70 static int respond_internal(int *msgvec, int recipient_record);
72 /* Reply to a series of messages by simply mailing to the senders and not
73 * messing around with the To: and Cc: lists as in normal reply */
74 static int Respond_internal(int *msgvec, int recipient_record);
76 /* Forward a message to a new recipient, in the sense of RFC 2822 */
77 static int forward1(char *str, int recipient_record);
79 /* Modify the subject we are replying to to begin with Fwd: */
80 static char * fwdedit(char *subj);
82 /* Sort the passed string vecotor into ascending dictionary order */
83 static void asort(char **list);
85 /* Do a dictionary order comparison of the arguments from qsort */
86 static int diction(void const *a, void const *b);
88 /* Do the real work of resending */
89 static int _resend1(void *v, bool_t add_resent);
91 /* ..to stdout */
92 static void list_shortcuts(void);
94 /* */
95 static enum okay delete_shortcut(char const *str);
97 static char *
98 _reedit(char *subj)
100 struct str in, out;
101 char *newsubj = NULL;
102 NYD_ENTER;
104 if (subj == NULL || *subj == '\0')
105 goto j_leave;
107 in.s = subj;
108 in.l = strlen(subj);
109 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
111 /* TODO _reedit: should be localizable (see cmd1.c:__subject_trim()!) */
112 if ((out.s[0] == 'r' || out.s[0] == 'R') &&
113 (out.s[1] == 'e' || out.s[1] == 'E') && out.s[2] == ':') {
114 newsubj = savestr(out.s);
115 goto jleave;
117 newsubj = salloc(out.l + 4 +1);
118 sstpcpy(sstpcpy(newsubj, "Re: "), out.s);
119 jleave:
120 free(out.s);
121 j_leave:
122 NYD_LEAVE;
123 return newsubj;
126 static void
127 _bangexp(char **str, size_t *size)
129 char *bangbuf;
130 int changed = 0;
131 bool_t dobang;
132 size_t sz, i, j, bangbufsize;
133 NYD_ENTER;
135 dobang = ok_blook(bang);
137 bangbuf = smalloc(bangbufsize = *size);
138 i = j = 0;
139 while ((*str)[i]) {
140 if (dobang) {
141 if ((*str)[i] == '!') {
142 sz = strlen(_bang_buf);
143 bangbuf = srealloc(bangbuf, bangbufsize += sz);
144 ++changed;
145 memcpy(bangbuf + j, _bang_buf, sz + 1);
146 j += sz;
147 i++;
148 continue;
151 if ((*str)[i] == '\\' && (*str)[i + 1] == '!') {
152 bangbuf[j++] = '!';
153 i += 2;
154 ++changed;
156 bangbuf[j++] = (*str)[i++];
158 bangbuf[j] = '\0';
159 if (changed) {
160 printf("!%s\n", bangbuf);
161 fflush(stdout);
163 sz = j + 1;
164 if (sz > *size)
165 *str = srealloc(*str, *size = sz);
166 memcpy(*str, bangbuf, sz);
167 if (sz > _bang_size)
168 _bang_buf = srealloc(_bang_buf, _bang_size = sz);
169 memcpy(_bang_buf, bangbuf, sz);
170 free(bangbuf);
171 NYD_LEAVE;
174 static void
175 make_ref_and_cs(struct message *mp, struct header *head)
177 char *oldref, *oldmsgid, *newref, *cp;
178 size_t oldreflen = 0, oldmsgidlen = 0, reflen;
179 unsigned i;
180 struct name *n;
181 NYD_ENTER;
183 oldref = hfield1("references", mp);
184 oldmsgid = hfield1("message-id", mp);
185 if (oldmsgid == NULL || *oldmsgid == '\0') {
186 head->h_ref = NULL;
187 goto jleave;
190 reflen = 1;
191 if (oldref) {
192 oldreflen = strlen(oldref);
193 reflen += oldreflen + 2;
195 if (oldmsgid) {
196 oldmsgidlen = strlen(oldmsgid);
197 reflen += oldmsgidlen;
200 newref = ac_alloc(reflen);
201 if (oldref != NULL) {
202 memcpy(newref, oldref, oldreflen +1);
203 if (oldmsgid != NULL) {
204 newref[oldreflen++] = ',';
205 newref[oldreflen++] = ' ';
206 memcpy(newref + oldreflen, oldmsgid, oldmsgidlen +1);
208 } else if (oldmsgid)
209 memcpy(newref, oldmsgid, oldmsgidlen +1);
210 n = extract(newref, GREF);
211 ac_free(newref);
213 /* Limit number of references */
214 while (n->n_flink != NULL)
215 n = n->n_flink;
216 for (i = 1; i < 21; ++i) { /* XXX no magics */
217 if (n->n_blink != NULL)
218 n = n->n_blink;
219 else
220 break;
222 n->n_blink = NULL;
223 head->h_ref = n;
224 if (ok_blook(reply_in_same_charset) &&
225 (cp = hfield1("content-type", mp)) != NULL)
226 head->h_charset = mime_getparam("charset", cp);
227 jleave:
228 NYD_LEAVE;
231 static int
232 (*respond_or_Respond(int c))(int *, int)
234 int opt;
235 int (*rv)(int*, int);
236 NYD_ENTER;
238 opt = ok_blook(Replyall);
239 opt += ok_blook(flipr);
240 rv = ((opt == 1) ^ (c == 'R')) ? &Respond_internal : &respond_internal;
241 NYD_LEAVE;
242 return rv;
245 static int
246 respond_internal(int *msgvec, int recipient_record)
248 struct header head;
249 struct message *mp;
250 char *cp, *rcv;
251 struct name *np = NULL;
252 enum gfield gf;
253 int rv = 1;
254 NYD_ENTER;
256 gf = ok_blook(fullnames) ? GFULL : GSKIN;
258 if (msgvec[1] != 0) {
259 fprintf(stderr, tr(37,
260 "Sorry, can't reply to multiple messages at once\n"));
261 goto jleave;
264 mp = message + msgvec[0] - 1;
265 touch(mp);
266 setdot(mp);
268 if ((rcv = hfield1("reply-to", mp)) == NULL)
269 if ((rcv = hfield1("from", mp)) == NULL)
270 rcv = nameof(mp, 1);
271 if (rcv != NULL)
272 np = lextract(rcv, GTO | gf);
273 if (!ok_blook(recipients_in_cc) && (cp = hfield1("to", mp)) != NULL)
274 np = cat(np, lextract(cp, GTO | gf));
275 /* Delete my name from reply list, and with it, all my alternate names */
276 np = elide(delete_alternates(np));
277 if (np == NULL)
278 np = lextract(rcv, GTO | gf);
280 memset(&head, 0, sizeof head);
281 head.h_to = np;
282 head.h_subject = hfield1("subject", mp);
283 head.h_subject = _reedit(head.h_subject);
285 /* Cc: */
286 np = NULL;
287 if (ok_blook(recipients_in_cc) && (cp = hfield1("to", mp)) != NULL)
288 np = lextract(cp, GCC | gf);
289 if ((cp = hfield1("cc", mp)) != NULL)
290 np = cat(np, lextract(cp, GCC | gf));
291 if (np != NULL)
292 head.h_cc = elide(delete_alternates(np));
293 make_ref_and_cs(mp, &head);
295 if (ok_blook(quote_as_attachment)) {
296 head.h_attach = csalloc(1, sizeof *head.h_attach);
297 head.h_attach->a_msgno = *msgvec;
298 head.h_attach->a_content_description = tr(512,
299 "Original message content");
302 if (mail1(&head, 1, mp, NULL, recipient_record, 0) == OKAY &&
303 ok_blook(markanswered) && !(mp->m_flag & MANSWERED))
304 mp->m_flag |= MANSWER | MANSWERED;
305 rv = 0;
306 jleave:
307 NYD_LEAVE;
308 return rv;
311 static int
312 Respond_internal(int *msgvec, int recipient_record)
314 struct header head;
315 struct message *mp;
316 int *ap;
317 char *cp;
318 enum gfield gf;
319 NYD_ENTER;
321 memset(&head, 0, sizeof head);
322 gf = ok_blook(fullnames) ? GFULL : GSKIN;
324 for (ap = msgvec; *ap != 0; ++ap) {
325 mp = message + *ap - 1;
326 touch(mp);
327 setdot(mp);
328 if ((cp = hfield1("reply-to", mp)) == NULL)
329 if ((cp = hfield1("from", mp)) == NULL)
330 cp = nameof(mp, 2);
331 head.h_to = cat(head.h_to, lextract(cp, GTO | gf));
333 if (head.h_to == NULL)
334 goto jleave;
336 mp = message + msgvec[0] - 1;
337 head.h_subject = hfield1("subject", mp);
338 head.h_subject = _reedit(head.h_subject);
339 make_ref_and_cs(mp, &head);
341 if (ok_blook(quote_as_attachment)) {
342 head.h_attach = csalloc(1, sizeof *head.h_attach);
343 head.h_attach->a_msgno = *msgvec;
344 head.h_attach->a_content_description = tr(512,
345 "Original message content");
348 if (mail1(&head, 1, mp, NULL, recipient_record, 0) == OKAY &&
349 ok_blook(markanswered) && !(mp->m_flag & MANSWERED))
350 mp->m_flag |= MANSWER | MANSWERED;
351 jleave:
352 NYD_LEAVE;
353 return 0;
356 static int
357 forward1(char *str, int recipient_record)
359 struct header head;
360 int *msgvec, rv = 1;
361 char *recipient;
362 struct message *mp;
363 bool_t f, forward_as_attachment;
364 NYD_ENTER;
366 if ((recipient = laststring(str, &f, 0)) == NULL) {
367 puts(tr(47, "No recipient specified."));
368 goto jleave;
371 forward_as_attachment = ok_blook(forward_as_attachment);
372 msgvec = salloc((msgCount + 2) * sizeof *msgvec);
374 if (!f) {
375 *msgvec = first(0, MMNORM);
376 if (*msgvec == 0) {
377 if (inhook) {
378 rv = 0;
379 goto jleave;
381 printf("No messages to forward.\n");
382 goto jleave;
384 msgvec[1] = 0;
385 } else if (getmsglist(str, msgvec, 0) < 0)
386 goto jleave;
388 if (*msgvec == 0) {
389 if (inhook) {
390 rv = 0;
391 goto jleave;
393 printf("No applicable messages.\n");
394 goto jleave;
396 if (msgvec[1] != 0) {
397 printf("Cannot forward multiple messages at once\n");
398 goto jleave;
401 memset(&head, 0, sizeof head);
402 if ((head.h_to = lextract(recipient,
403 (GTO | (ok_blook(fullnames) ? GFULL : GSKIN)))) == NULL)
404 goto jleave;
406 mp = message + *msgvec - 1;
408 if (forward_as_attachment) {
409 head.h_attach = csalloc(1, sizeof *head.h_attach);
410 head.h_attach->a_msgno = *msgvec;
411 head.h_attach->a_content_description = "Forwarded message";
412 } else {
413 touch(mp);
414 setdot(mp);
416 head.h_subject = hfield1("subject", mp);
417 head.h_subject = fwdedit(head.h_subject);
418 mail1(&head, 1, (forward_as_attachment ? NULL : mp), NULL, recipient_record,
420 rv = 0;
421 jleave:
422 NYD_LEAVE;
423 return rv;
426 static char *
427 fwdedit(char *subj)
429 struct str in, out;
430 char *newsubj = NULL;
431 NYD_ENTER;
433 if (subj == NULL || *subj == '\0')
434 goto jleave;
436 in.s = subj;
437 in.l = strlen(subj);
438 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
440 newsubj = salloc(out.l + 6);
441 memcpy(newsubj, "Fwd: ", 5); /* XXX localizable */
442 memcpy(newsubj + 5, out.s, out.l + 1);
443 free(out.s);
444 jleave:
445 NYD_LEAVE;
446 return newsubj;
449 static void
450 asort(char **list)
452 char **ap;
453 size_t i;
454 NYD_ENTER;
456 for (ap = list; *ap != NULL; ++ap)
458 if ((i = PTR2SIZE(ap - list)) >= 2)
459 qsort(list, i, sizeof *list, diction);
460 NYD_LEAVE;
463 static int
464 diction(void const *a, void const *b)
466 int rv;
467 NYD_ENTER;
469 rv = strcmp(*(char**)UNCONST(a), *(char**)UNCONST(b));
470 NYD_LEAVE;
471 return rv;
474 static int
475 _resend1(void *v, bool_t add_resent)
477 char *name, *str;
478 struct name *to, *sn;
479 int *ip, *msgvec;
480 bool_t f = TRU1;
481 NYD_ENTER;
483 str = v;
484 msgvec = salloc((msgCount + 2) * sizeof *msgvec);
485 name = laststring(str, &f, 1);
486 if (name == NULL) {
487 puts(tr(47, "No recipient specified."));
488 goto jleave;
491 if (!f) {
492 *msgvec = first(0, MMNORM);
493 if (*msgvec == 0) {
494 if (inhook) {
495 f = FAL0;
496 goto jleave;
498 puts(tr(48, "No applicable messages."));
499 goto jleave;
501 msgvec[1] = 0;
502 } else if (getmsglist(str, msgvec, 0) < 0)
503 goto jleave;
505 if (*msgvec == 0) {
506 if (inhook) {
507 f = FAL0;
508 goto jleave;
510 printf("No applicable messages.\n");
511 goto jleave;
514 sn = nalloc(name, GTO | GSKIN);
515 to = usermap(sn, FAL0);
516 for (ip = msgvec; *ip != 0 && UICMP(z, PTR2SIZE(ip - msgvec), <, msgCount);
517 ++ip)
518 if (resend_msg(message + *ip - 1, to, add_resent) != OKAY)
519 goto jleave;
520 f = FAL0;
521 jleave:
522 NYD_LEAVE;
523 return (f != FAL0);
526 static void
527 list_shortcuts(void)
529 struct shortcut *s;
530 NYD_ENTER;
532 for (s = shortcuts; s != NULL; s = s->sh_next)
533 printf("%s=%s\n", s->sh_short, s->sh_long);
534 NYD_LEAVE;
537 static enum okay
538 delete_shortcut(char const *str)
540 struct shortcut *sp, *sq;
541 enum okay rv = STOP;
542 NYD_ENTER;
544 for (sp = shortcuts, sq = NULL; sp != NULL; sq = sp, sp = sp->sh_next) {
545 if (!strcmp(sp->sh_short, str)) {
546 free(sp->sh_short);
547 free(sp->sh_long);
548 if (sq != NULL)
549 sq->sh_next = sp->sh_next;
550 if (sp == shortcuts)
551 shortcuts = sp->sh_next;
552 free(sp);
553 rv = OKAY;
554 break;
557 NYD_LEAVE;
558 return rv;
561 FL int
562 c_shell(void *v)
564 char const *sh = NULL;
565 char *str = v, *cmd;
566 size_t cmdsize;
567 sigset_t mask;
568 sighandler_type sigint;
569 NYD_ENTER;
571 cmd = smalloc(cmdsize = strlen(str) +1);
572 memcpy(cmd, str, cmdsize);
573 _bangexp(&cmd, &cmdsize);
574 if ((sh = ok_vlook(SHELL)) == NULL)
575 sh = XSHELL;
577 sigint = safe_signal(SIGINT, SIG_IGN);
578 sigemptyset(&mask);
579 run_command(sh, &mask, -1, -1, "-c", cmd, NULL);
580 safe_signal(SIGINT, sigint);
581 printf("!\n");
583 free(cmd);
584 NYD_LEAVE;
585 return 0;
588 FL int
589 c_dosh(void *v)
591 sighandler_type sigint;
592 char const *sh;
593 NYD_ENTER;
594 UNUSED(v);
596 if ((sh = ok_vlook(SHELL)) == NULL)
597 sh = XSHELL;
599 sigint = safe_signal(SIGINT, SIG_IGN);
600 run_command(sh, 0, -1, -1, NULL, NULL, NULL);
601 safe_signal(SIGINT, sigint);
602 putchar('\n');
603 NYD_LEAVE;
604 return 0;
607 FL int
608 c_help(void *v)
610 int ret = 0;
611 char *arg;
612 NYD_ENTER;
614 arg = *(char**)v;
616 if (arg != NULL) {
617 #ifdef HAVE_DOCSTRINGS
618 ret = !print_comm_docstr(arg);
619 if (ret)
620 fprintf(stderr, tr(91, "Unknown command: `%s'\n"), arg);
621 #else
622 ret = c_cmdnotsupp(NULL);
623 #endif
624 goto jleave;
627 /* Very ugly, but take care for compiler supported string lengths :( */
628 printf(tr(295, "%s commands:\n"), progname);
629 puts(tr(296,
630 "type <message list> type messages\n"
631 "next goto and type next message\n"
632 "from <message list> give head lines of messages\n"
633 "headers print out active message headers\n"
634 "delete <message list> delete messages\n"
635 "undelete <message list> undelete messages\n"));
636 puts(tr(297,
637 "save <message list> folder append messages to folder and mark as saved\n"
638 "copy <message list> folder append messages to folder without marking them\n"
639 "write <message list> file append message texts to file, save attachments\n"
640 "preserve <message list> keep incoming messages in mailbox even if saved\n"
641 "Reply <message list> reply to message senders\n"
642 "reply <message list> reply to message senders and all recipients\n"));
643 puts(tr(298,
644 "mail addresses mail to specific recipients\n"
645 "file folder change to another folder\n"
646 "quit quit and apply changes to folder\n"
647 "xit quit and discard changes made to folder\n"
648 "! shell escape\n"
649 "cd <directory> chdir to directory or home if none given\n"
650 "list list names of all available commands\n"));
651 printf(tr(299,
652 "\nA <message list> consists of integers, ranges of same, or other criteria\n"
653 "separated by spaces. If omitted, %s uses the last message typed.\n"),
654 progname);
656 jleave:
657 NYD_LEAVE;
658 return ret;
661 FL int
662 c_cwd(void *v)
664 char buf[PATH_MAX]; /* TODO getcwd(3) may return a larger value */
665 NYD_ENTER;
667 if (getcwd(buf, sizeof buf) != NULL) {
668 puts(buf);
669 v = (void*)0x1;
670 } else {
671 perror("getcwd");
672 v = NULL;
674 NYD_LEAVE;
675 return (v == NULL);
678 FL int
679 c_chdir(void *v)
681 char **arglist = v;
682 char const *cp;
683 NYD_ENTER;
685 if (*arglist == NULL)
686 cp = homedir;
687 else if ((cp = file_expand(*arglist)) == NULL)
688 goto jleave;
689 if (chdir(cp) == -1) {
690 perror(cp);
691 cp = NULL;
693 jleave:
694 NYD_LEAVE;
695 return (cp == NULL);
698 FL int
699 c_respond(void *v)
701 int rv;
702 NYD_ENTER;
704 rv = (*respond_or_Respond('r'))(v, 0);
705 NYD_LEAVE;
706 return rv;
709 FL int
710 c_respondall(void *v)
712 int rv;
713 NYD_ENTER;
715 rv = respond_internal(v, 0);
716 NYD_LEAVE;
717 return rv;
720 FL int
721 c_respondsender(void *v)
723 int rv;
724 NYD_ENTER;
726 rv = Respond_internal(v, 0);
727 NYD_LEAVE;
728 return rv;
731 FL int
732 c_Respond(void *v)
734 int rv;
735 NYD_ENTER;
737 rv = (*respond_or_Respond('R'))(v, 0);
738 NYD_LEAVE;
739 return rv;
742 FL int
743 c_followup(void *v)
745 int rv;
746 NYD_ENTER;
748 rv = (*respond_or_Respond('r'))(v, 1);
749 NYD_LEAVE;
750 return rv;
753 FL int
754 c_followupall(void *v)
756 int rv;
757 NYD_ENTER;
759 rv = respond_internal(v, 1);
760 NYD_LEAVE;
761 return rv;
764 FL int
765 c_followupsender(void *v)
767 int rv;
768 NYD_ENTER;
770 rv = Respond_internal(v, 1);
771 NYD_LEAVE;
772 return rv;
775 FL int
776 c_Followup(void *v)
778 int rv;
779 NYD_ENTER;
781 rv = (*respond_or_Respond('R'))(v, 1);
782 NYD_LEAVE;
783 return rv;
786 FL int
787 c_forward(void *v)
789 int rv;
790 NYD_ENTER;
792 rv = forward1(v, 0);
793 NYD_LEAVE;
794 return rv;
797 FL int
798 c_Forward(void *v)
800 int rv;
801 NYD_ENTER;
803 rv = forward1(v, 1);
804 NYD_LEAVE;
805 return rv;
808 FL int
809 c_resend(void *v)
811 int rv;
812 NYD_ENTER;
814 rv = _resend1(v, TRU1);
815 NYD_LEAVE;
816 return rv;
819 FL int
820 c_Resend(void *v)
822 int rv;
823 NYD_ENTER;
825 rv = _resend1(v, FAL0);
826 NYD_LEAVE;
827 return rv;
830 FL int
831 c_preserve(void *v)
833 int *msgvec = v, *ip, mesg, rv = 1;
834 struct message *mp;
835 NYD_ENTER;
837 if (edit) {
838 printf(tr(39, "Cannot \"preserve\" in edit mode\n"));
839 goto jleave;
842 for (ip = msgvec; *ip != 0; ++ip) {
843 mesg = *ip;
844 mp = message + mesg - 1;
845 mp->m_flag |= MPRESERVE;
846 mp->m_flag &= ~MBOX;
847 setdot(mp);
848 did_print_dot = TRU1;
850 rv = 0;
851 jleave:
852 NYD_LEAVE;
853 return rv;
856 FL int
857 c_unread(void *v)
859 int *msgvec = v, *ip;
860 NYD_ENTER;
862 for (ip = msgvec; *ip != 0; ++ip) {
863 setdot(message + *ip - 1);
864 dot->m_flag &= ~(MREAD | MTOUCH);
865 dot->m_flag |= MSTATUS;
866 #ifdef HAVE_IMAP
867 if (mb.mb_type == MB_IMAP || mb.mb_type == MB_CACHE)
868 imap_unread(message + *ip - 1, *ip); /* TODO return? */
869 #endif
870 did_print_dot = TRU1;
872 NYD_LEAVE;
873 return 0;
876 FL int
877 c_seen(void *v)
879 int *msgvec = v, *ip;
880 NYD_ENTER;
882 for (ip = msgvec; *ip != 0; ++ip) {
883 struct message *mp = message + *ip - 1;
884 setdot(mp);
885 touch(mp);
887 NYD_LEAVE;
888 return 0;
891 FL int
892 c_messize(void *v)
894 int *msgvec = v, *ip, mesg;
895 struct message *mp;
896 NYD_ENTER;
898 for (ip = msgvec; *ip != 0; ++ip) {
899 mesg = *ip;
900 mp = message + mesg - 1;
901 printf("%d: ", mesg);
902 if (mp->m_xlines > 0)
903 printf("%ld", mp->m_xlines);
904 else
905 putchar(' ');
906 printf("/%lu\n", (ul_it)mp->m_xsize);
908 NYD_LEAVE;
909 return 0;
912 FL int
913 c_rexit(void *v)
915 UNUSED(v);
916 NYD_ENTER;
918 if (!sourcing)
919 exit(0);
920 NYD_LEAVE;
921 return 1;
924 FL int
925 c_group(void *v)
927 char **argv = v, **ap, *gname, **p;
928 struct grouphead *gh;
929 struct group *gp;
930 int h, s;
931 NYD_ENTER;
933 if (*argv == NULL) {
934 for (h = 0, s = 1; h < HSHSIZE; ++h)
935 for (gh = groups[h]; gh != NULL; gh = gh->g_link)
936 ++s;
937 ap = salloc(s * sizeof *ap);
939 for (h = 0, p = ap; h < HSHSIZE; ++h)
940 for (gh = groups[h]; gh != NULL; gh = gh->g_link)
941 *p++ = gh->g_name;
942 *p = NULL;
944 asort(ap);
946 for (p = ap; *p != NULL; ++p)
947 printgroup(*p);
948 goto jleave;
951 if (argv[1] == NULL) {
952 printgroup(*argv);
953 goto jleave;
956 gname = *argv;
957 h = hash(gname);
958 if ((gh = findgroup(gname)) == NULL) {
959 gh = scalloc(1, sizeof *gh);
960 gh->g_name = sstrdup(gname);
961 gh->g_list = NULL;
962 gh->g_link = groups[h];
963 groups[h] = gh;
966 /* Insert names from the command list into the group. Who cares if there
967 * are duplicates? They get tossed later anyway */
968 for (ap = argv + 1; *ap != NULL; ++ap) {
969 gp = scalloc(1, sizeof *gp);
970 gp->ge_name = sstrdup(*ap);
971 gp->ge_link = gh->g_list;
972 gh->g_list = gp;
974 jleave:
975 NYD_LEAVE;
976 return 0;
979 FL int
980 c_ungroup(void *v)
982 char **argv = v;
983 int rv = 1;
984 NYD_ENTER;
986 if (*argv == NULL) {
987 fprintf(stderr, tr(209, "Must specify alias to remove\n"));
988 goto jleave;
992 remove_group(*argv);
993 while (*++argv != NULL);
994 rv = 0;
995 jleave:
996 NYD_LEAVE;
997 return rv;
1000 FL int
1001 c_file(void *v)
1003 char **argv = v;
1004 int i;
1005 NYD_ENTER;
1007 if (*argv == NULL) {
1008 newfileinfo();
1009 i = 0;
1010 goto jleave;
1013 if (inhook) {
1014 fprintf(stderr, tr(516, "Cannot change folder from within a hook.\n"));
1015 i = 1;
1016 goto jleave;
1019 save_mbox_for_possible_quitstuff();
1021 i = setfile(*argv, 0);
1022 if (i < 0) {
1023 i = 1;
1024 goto jleave;
1026 callhook(mailname, 0);
1027 if (i > 0 && !ok_blook(emptystart)) {
1028 i = 1;
1029 goto jleave;
1031 announce(ok_blook(bsdcompat) || ok_blook(bsdannounce));
1032 i = 0;
1033 jleave:
1034 NYD_LEAVE;
1035 return i;
1038 FL int
1039 c_echo(void *v)
1041 char const **argv = v, **ap, *cp;
1042 int c;
1043 NYD_ENTER;
1045 for (ap = argv; *ap != NULL; ++ap) {
1046 cp = *ap;
1047 if ((cp = fexpand(cp, FEXP_NSHORTCUT)) != NULL) {
1048 if (ap != argv)
1049 putchar(' ');
1050 c = 0;
1051 while (*cp != '\0' && (c = expand_shell_escape(&cp, FAL0)) > 0)
1052 putchar(c);
1053 /* \c ends overall processing */
1054 if (c < 0)
1055 goto jleave;
1058 putchar('\n');
1059 jleave:
1060 NYD_LEAVE;
1061 return 0;
1064 FL int
1065 c_if(void *v)
1067 struct cond_stack *csp;
1068 int rv = 1;
1069 char **argv = v, *cp, *op;
1070 NYD_ENTER;
1072 csp = smalloc(sizeof *csp);
1073 csp->c_outer = _cond_stack;
1074 csp->c_noop = condstack_isskip();
1075 csp->c_go = TRU1;
1076 csp->c_else = FAL0;
1077 _cond_stack = csp;
1079 cp = argv[0];
1080 if (*cp != '$' && argv[1] != NULL) {
1081 jesyn:
1082 fprintf(stderr, tr(528, "Invalid conditional expression \"%s %s %s\"\n"),
1083 argv[0], (argv[1] != NULL ? argv[1] : ""),
1084 (argv[2] != NULL ? argv[2] : ""));
1085 goto jleave;
1088 switch (*cp) {
1089 case '0':
1090 csp->c_go = FAL0;
1091 break;
1092 case 'R': case 'r':
1093 csp->c_go = !(options & OPT_SENDMODE);
1094 break;
1095 case 'S': case 's':
1096 csp->c_go = ((options & OPT_SENDMODE) != 0);
1097 break;
1098 case 'T': case 't':
1099 csp->c_go = ((options & OPT_TTYIN) != 0);
1100 break;
1101 case '$':
1102 /* Look up the value in question, we need it anyway */
1103 v = vok_vlook(++cp);
1105 /* Single argument, "implicit boolean" form? */
1106 if ((op = argv[1]) == NULL) {
1107 csp->c_go = (v != NULL);
1108 break;
1111 /* Three argument comparison form? */
1112 if (argv[2] == NULL || op[0] == '\0' || op[1] != '=' || op[2] != '\0')
1113 goto jesyn;
1114 /* A null value is treated as the empty string */
1115 if (v == NULL)
1116 v = UNCONST("");
1117 if (strcmp(v, argv[2]))
1118 v = NULL;
1119 switch (op[0]) {
1120 case '!':
1121 case '=':
1122 csp->c_go = ((op[0] == '=') ^ (v == NULL));
1123 break;
1124 default:
1125 goto jesyn;
1127 break;
1128 default:
1129 fprintf(stderr, tr(43, "Unrecognized if-keyword: \"%s\"\n"), cp);
1130 case '1':
1131 csp->c_go = TRU1;
1132 goto jleave;
1134 rv = 0;
1135 jleave:
1136 NYD_LEAVE;
1137 return rv;
1140 FL int
1141 c_else(void *v)
1143 int rv;
1144 NYD_ENTER;
1145 UNUSED(v);
1147 if (_cond_stack == NULL || _cond_stack->c_else) {
1148 fprintf(stderr, tr(44, "\"else\" without matching \"if\"\n"));
1149 rv = 1;
1150 } else {
1151 _cond_stack->c_go = !_cond_stack->c_go;
1152 _cond_stack->c_else = TRU1;
1153 rv = 0;
1155 NYD_LEAVE;
1156 return rv;
1159 FL int
1160 c_endif(void *v)
1162 struct cond_stack *csp;
1163 int rv;
1164 NYD_ENTER;
1165 UNUSED(v);
1167 if ((csp = _cond_stack) == NULL) {
1168 fprintf(stderr, tr(46, "\"endif\" without matching \"if\"\n"));
1169 rv = 1;
1170 } else {
1171 _cond_stack = csp->c_outer;
1172 free(csp);
1173 rv = 0;
1175 NYD_LEAVE;
1176 return rv;
1179 FL bool_t
1180 condstack_isskip(void)
1182 bool_t rv;
1183 NYD_ENTER;
1185 rv = (_cond_stack != NULL && (_cond_stack->c_noop || !_cond_stack->c_go));
1186 NYD_LEAVE;
1187 return rv;
1190 FL void *
1191 condstack_release(void)
1193 void *rv;
1194 NYD_ENTER;
1196 rv = _cond_stack;
1197 _cond_stack = NULL;
1198 NYD_LEAVE;
1199 return rv;
1202 FL bool_t
1203 condstack_take(void *self)
1205 struct cond_stack *csp;
1206 bool_t rv;
1207 NYD_ENTER;
1209 if (!(rv = ((csp = _cond_stack) == NULL)))
1210 do {
1211 _cond_stack = csp->c_outer;
1212 free(csp);
1213 } while ((csp = _cond_stack) != NULL);
1215 _cond_stack = self;
1216 NYD_LEAVE;
1217 return rv;
1220 FL int
1221 c_alternates(void *v)
1223 size_t l;
1224 char **namelist = v, **ap, **ap2, *cp;
1225 NYD_ENTER;
1227 l = argcount(namelist) + 1;
1228 if (l == 1) {
1229 if (altnames == NULL)
1230 goto jleave;
1231 for (ap = altnames; *ap != NULL; ++ap)
1232 printf("%s ", *ap);
1233 printf("\n");
1234 goto jleave;
1237 if (altnames != NULL) {
1238 for (ap = altnames; *ap != NULL; ++ap)
1239 free(*ap);
1240 free(altnames);
1242 altnames = smalloc(l * sizeof(char*));
1243 for (ap = namelist, ap2 = altnames; *ap != NULL; ++ap, ++ap2) {
1244 l = strlen(*ap) + 1;
1245 cp = smalloc(l);
1246 memcpy(cp, *ap, l);
1247 *ap2 = cp;
1249 *ap2 = NULL;
1250 jleave:
1251 NYD_LEAVE;
1252 return 0;
1255 FL int
1256 c_newmail(void *v)
1258 int val = 1, mdot;
1259 NYD_ENTER;
1260 UNUSED(v);
1262 if (
1263 #ifdef HAVE_IMAP
1264 (mb.mb_type != MB_IMAP || imap_newmail(1)) &&
1265 #endif
1266 (val = setfile(mailname, 1)) == 0) {
1267 mdot = getmdot(1);
1268 setdot(message + mdot - 1);
1270 NYD_LEAVE;
1271 return val;
1274 FL int
1275 c_shortcut(void *v)
1277 char **args = v;
1278 struct shortcut *s;
1279 int rv;
1280 NYD_ENTER;
1282 if (args[0] == NULL) {
1283 list_shortcuts();
1284 rv = 0;
1285 goto jleave;
1288 rv = 1;
1289 if (args[1] == NULL) {
1290 fprintf(stderr, tr(220, "expansion name for shortcut missing\n"));
1291 goto jleave;
1293 if (args[2] != NULL) {
1294 fprintf(stderr, tr(221, "too many arguments\n"));
1295 goto jleave;
1298 if ((s = get_shortcut(args[0])) != NULL) {
1299 free(s->sh_long);
1300 s->sh_long = sstrdup(args[1]);
1301 } else {
1302 s = scalloc(1, sizeof *s);
1303 s->sh_short = sstrdup(args[0]);
1304 s->sh_long = sstrdup(args[1]);
1305 s->sh_next = shortcuts;
1306 shortcuts = s;
1308 rv = 0;
1309 jleave:
1310 NYD_LEAVE;
1311 return rv;
1314 FL struct shortcut *
1315 get_shortcut(char const *str)
1317 struct shortcut *s;
1318 NYD_ENTER;
1320 for (s = shortcuts; s != NULL; s = s->sh_next)
1321 if (!strcmp(str, s->sh_short))
1322 break;
1323 NYD_LEAVE;
1324 return s;
1327 FL int
1328 c_unshortcut(void *v)
1330 char **args = v;
1331 bool_t errs = FAL0;
1332 NYD_ENTER;
1334 if (args[0] == NULL) {
1335 fprintf(stderr, tr(222, "need shortcut names to remove\n"));
1336 errs = TRU1;
1337 goto jleave;
1340 while (*args != NULL) {
1341 if (delete_shortcut(*args) != OKAY) {
1342 errs = TRU1;
1343 fprintf(stderr, tr(223, "%s: no such shortcut\n"), *args);
1345 ++args;
1347 jleave:
1348 NYD_LEAVE;
1349 return errs;
1352 FL int
1353 c_flag(void *v)
1355 struct message *m;
1356 int *msgvec = v, *ip;
1357 NYD_ENTER;
1359 for (ip = msgvec; *ip != 0; ++ip) {
1360 m = message + *ip - 1;
1361 setdot(m);
1362 if (!(m->m_flag & (MFLAG | MFLAGGED)))
1363 m->m_flag |= MFLAG | MFLAGGED;
1365 NYD_LEAVE;
1366 return 0;
1369 FL int
1370 c_unflag(void *v)
1372 struct message *m;
1373 int *msgvec = v, *ip;
1374 NYD_ENTER;
1376 for (ip = msgvec; *ip != 0; ++ip) {
1377 m = message + *ip - 1;
1378 setdot(m);
1379 if (m->m_flag & (MFLAG | MFLAGGED)) {
1380 m->m_flag &= ~(MFLAG | MFLAGGED);
1381 m->m_flag |= MUNFLAG;
1384 NYD_LEAVE;
1385 return 0;
1388 FL int
1389 c_answered(void *v)
1391 struct message *m;
1392 int *msgvec = v, *ip;
1393 NYD_ENTER;
1395 for (ip = msgvec; *ip != 0; ++ip) {
1396 m = message + *ip - 1;
1397 setdot(m);
1398 if (!(m->m_flag & (MANSWER | MANSWERED)))
1399 m->m_flag |= MANSWER | MANSWERED;
1401 NYD_LEAVE;
1402 return 0;
1405 FL int
1406 c_unanswered(void *v)
1408 struct message *m;
1409 int *msgvec = v, *ip;
1410 NYD_ENTER;
1412 for (ip = msgvec; *ip != 0; ++ip) {
1413 m = message + *ip - 1;
1414 setdot(m);
1415 if (m->m_flag & (MANSWER | MANSWERED)) {
1416 m->m_flag &= ~(MANSWER | MANSWERED);
1417 m->m_flag |= MUNANSWER;
1420 NYD_LEAVE;
1421 return 0;
1424 FL int
1425 c_draft(void *v)
1427 struct message *m;
1428 int *msgvec = v, *ip;
1429 NYD_ENTER;
1431 for (ip = msgvec; *ip != 0; ++ip) {
1432 m = message + *ip - 1;
1433 setdot(m);
1434 if (!(m->m_flag & (MDRAFT | MDRAFTED)))
1435 m->m_flag |= MDRAFT | MDRAFTED;
1437 NYD_LEAVE;
1438 return 0;
1441 FL int
1442 c_undraft(void *v)
1444 struct message *m;
1445 int *msgvec = v, *ip;
1446 NYD_ENTER;
1448 for (ip = msgvec; *ip != 0; ++ip) {
1449 m = message + *ip - 1;
1450 setdot(m);
1451 if (m->m_flag & (MDRAFT | MDRAFTED)) {
1452 m->m_flag &= ~(MDRAFT | MDRAFTED);
1453 m->m_flag |= MUNDRAFT;
1456 NYD_LEAVE;
1457 return 0;
1460 FL int
1461 c_noop(void *v)
1463 int rv = 0;
1464 NYD_ENTER;
1465 UNUSED(v);
1467 switch (mb.mb_type) {
1468 case MB_IMAP:
1469 #ifdef HAVE_IMAP
1470 imap_noop();
1471 #else
1472 rv = c_cmdnotsupp(NULL);
1473 #endif
1474 break;
1475 case MB_POP3:
1476 #ifdef HAVE_POP3
1477 pop3_noop();
1478 #else
1479 rv = c_cmdnotsupp(NULL);
1480 #endif
1481 break;
1482 default:
1483 break;
1485 NYD_LEAVE;
1486 return rv;
1489 FL int
1490 c_remove(void *v)
1492 char const *fmt;
1493 size_t fmt_len;
1494 char **args = v, *name;
1495 int ec = 0;
1496 NYD_ENTER;
1498 if (*args == NULL) {
1499 fprintf(stderr, tr(290, "Syntax is: remove mailbox ...\n"));
1500 ec = 1;
1501 goto jleave;
1504 fmt = tr(287, "Remove \"%s\" (y/n) ? ");
1505 fmt_len = strlen(fmt);
1506 do {
1507 if ((name = expand(*args)) == NULL)
1508 continue;
1510 if (!strcmp(name, mailname)) {
1511 fprintf(stderr, tr(286, "Cannot remove current mailbox \"%s\".\n"),
1512 name);
1513 ec |= 1;
1514 continue;
1517 size_t vl = strlen(name) + fmt_len +1;
1518 char *vb = ac_alloc(vl);
1519 bool_t asw;
1520 snprintf(vb, vl, fmt, name);
1521 asw = getapproval(vb, TRU1);
1522 ac_free(vb);
1523 if (!asw)
1524 continue;
1527 switch (which_protocol(name)) {
1528 case PROTO_FILE:
1529 if (unlink(name) == -1) { /* TODO do not handle .gz .bz2 .xz.. */
1530 perror(name);
1531 ec |= 1;
1533 break;
1534 case PROTO_POP3:
1535 fprintf(stderr, tr(288, "Cannot remove POP3 mailbox \"%s\".\n"),name);
1536 ec |= 1;
1537 break;
1538 case PROTO_IMAP:
1539 #ifdef HAVE_IMAP
1540 if (imap_remove(name) != OKAY)
1541 #endif
1542 ec |= 1;
1543 break;
1544 case PROTO_MAILDIR:
1545 if (maildir_remove(name) != OKAY)
1546 ec |= 1;
1547 break;
1548 case PROTO_UNKNOWN:
1549 fprintf(stderr, tr(289, "Unknown protocol in \"%s\". Not removed.\n"),
1550 name);
1551 ec |= 1;
1552 break;
1554 } while (*++args != NULL);
1555 jleave:
1556 NYD_LEAVE;
1557 return ec;
1560 FL int
1561 c_rename(void *v)
1563 char **args = v, *old, *new;
1564 enum protocol oldp, newp;
1565 int ec;
1566 NYD_ENTER;
1568 ec = 1;
1570 if (args[0] == NULL || args[1] == NULL || args[2] != NULL) {
1571 fprintf(stderr, "Syntax: rename old new\n");
1572 goto jleave;
1575 if ((old = expand(args[0])) == NULL)
1576 goto jleave;
1577 oldp = which_protocol(old);
1578 if ((new = expand(args[1])) == NULL)
1579 goto jleave;
1580 newp = which_protocol(new);
1582 if (!strcmp(old, mailname) || !strcmp(new, mailname)) {
1583 fprintf(stderr, tr(291, "Cannot rename current mailbox \"%s\".\n"), old);
1584 goto jleave;
1586 if ((oldp == PROTO_IMAP || newp == PROTO_IMAP) && oldp != newp) {
1587 fprintf(stderr, tr(292, "Can only rename folders of same type.\n"));
1588 goto jleave;
1591 ec = 0;
1593 if (newp == PROTO_POP3)
1594 goto jnopop3;
1595 switch (oldp) {
1596 case PROTO_FILE:
1597 if (link(old, new) == -1) {
1598 switch (errno) {
1599 case EACCES:
1600 case EEXIST:
1601 case ENAMETOOLONG:
1602 case ENOENT:
1603 case ENOSPC:
1604 case EXDEV:
1605 perror(new);
1606 break;
1607 default:
1608 perror(old);
1610 ec |= 1;
1611 } else if (unlink(old) == -1) {
1612 perror(old);
1613 ec |= 1;
1615 break;
1616 case PROTO_MAILDIR:
1617 if (rename(old, new) == -1) {
1618 perror(old);
1619 ec |= 1;
1621 break;
1622 case PROTO_POP3:
1623 jnopop3:
1624 fprintf(stderr, tr(293, "Cannot rename POP3 mailboxes.\n"));
1625 ec |= 1;
1626 break;
1627 #ifdef HAVE_IMAP
1628 case PROTO_IMAP:
1629 if (imap_rename(old, new) != OKAY)
1630 ec |= 1;
1631 break;
1632 #endif
1633 case PROTO_UNKNOWN:
1634 default:
1635 fprintf(stderr, tr(294,
1636 "Unknown protocol in \"%s\" and \"%s\". Not renamed.\n"), old, new);
1637 ec |= 1;
1638 break;
1640 jleave:
1641 NYD_LEAVE;
1642 return ec;
1645 FL int
1646 c_urlenc(void *v) /* XXX IDNA?? */
1648 char **ap;
1649 NYD_ENTER;
1651 for (ap = v; *ap != NULL; ++ap) {
1652 char *in = *ap, *out = urlxenc(in);
1654 printf(" in: <%s> (%" ZFMT " bytes)\nout: <%s> (%" ZFMT " bytes)\n",
1655 in, strlen(in), out, strlen(out));
1657 NYD_LEAVE;
1658 return 0;
1661 FL int
1662 c_urldec(void *v) /* XXX IDNA?? */
1664 char **ap;
1665 NYD_ENTER;
1667 for (ap = v; *ap != NULL; ++ap) {
1668 char *in = *ap, *out = urlxdec(in);
1670 printf(" in: <%s> (%" ZFMT " bytes)\nout: <%s> (%" ZFMT " bytes)\n",
1671 in, strlen(in), out, strlen(out));
1673 NYD_LEAVE;
1674 return 0;
1677 /* vim:set fenc=utf-8:s-it-mode */