mime.c:mime_write_tohdr(): complete rewrite (Peter Hofmann)..
[s-mailx.git] / cmd3.c
blobd62c9f5cad72e9a9367d730ec6516e8ecdffcca7
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 _fwd(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 /* c_file, c_File */
92 static int _c_file(void *v, enum fedit_mode fm);
94 /* ..to stdout */
95 static void list_shortcuts(void);
97 /* */
98 static enum okay delete_shortcut(char const *str);
100 static char *
101 _reedit(char *subj)
103 struct str in, out;
104 char *newsubj = NULL;
105 NYD_ENTER;
107 if (subj == NULL || *subj == '\0')
108 goto jleave;
110 in.s = subj;
111 in.l = strlen(subj);
112 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
114 if ((newsubj = subject_re_trim(out.s)) != out.s)
115 newsubj = savestr(out.s);
116 else {
117 newsubj = salloc(out.l + 4 +1);
118 sstpcpy(sstpcpy(newsubj, "Re: "), out.s);
121 free(out.s);
122 jleave:
123 NYD_LEAVE;
124 return newsubj;
127 static void
128 _bangexp(char **str, size_t *size)
130 char *bangbuf;
131 int changed = 0;
132 bool_t dobang;
133 size_t sz, i, j, bangbufsize;
134 NYD_ENTER;
136 dobang = ok_blook(bang);
138 bangbuf = smalloc(bangbufsize = *size);
139 i = j = 0;
140 while ((*str)[i]) {
141 if (dobang) {
142 if ((*str)[i] == '!') {
143 sz = strlen(_bang_buf);
144 bangbuf = srealloc(bangbuf, bangbufsize += sz);
145 ++changed;
146 memcpy(bangbuf + j, _bang_buf, sz + 1);
147 j += sz;
148 i++;
149 continue;
152 if ((*str)[i] == '\\' && (*str)[i + 1] == '!') {
153 bangbuf[j++] = '!';
154 i += 2;
155 ++changed;
157 bangbuf[j++] = (*str)[i++];
159 bangbuf[j] = '\0';
160 if (changed) {
161 printf("!%s\n", bangbuf);
162 fflush(stdout);
164 sz = j + 1;
165 if (sz > *size)
166 *str = srealloc(*str, *size = sz);
167 memcpy(*str, bangbuf, sz);
168 if (sz > _bang_size)
169 _bang_buf = srealloc(_bang_buf, _bang_size = sz);
170 memcpy(_bang_buf, bangbuf, sz);
171 free(bangbuf);
172 NYD_LEAVE;
175 static void
176 make_ref_and_cs(struct message *mp, struct header *head)
178 char *oldref, *oldmsgid, *newref, *cp;
179 size_t oldreflen = 0, oldmsgidlen = 0, reflen;
180 unsigned i;
181 struct name *n;
182 NYD_ENTER;
184 oldref = hfield1("references", mp);
185 oldmsgid = hfield1("message-id", mp);
186 if (oldmsgid == NULL || *oldmsgid == '\0') {
187 head->h_ref = NULL;
188 goto jleave;
191 reflen = 1;
192 if (oldref) {
193 oldreflen = strlen(oldref);
194 reflen += oldreflen + 2;
196 if (oldmsgid) {
197 oldmsgidlen = strlen(oldmsgid);
198 reflen += oldmsgidlen;
201 newref = ac_alloc(reflen);
202 if (oldref != NULL) {
203 memcpy(newref, oldref, oldreflen +1);
204 if (oldmsgid != NULL) {
205 newref[oldreflen++] = ',';
206 newref[oldreflen++] = ' ';
207 memcpy(newref + oldreflen, oldmsgid, oldmsgidlen +1);
209 } else if (oldmsgid)
210 memcpy(newref, oldmsgid, oldmsgidlen +1);
211 n = extract(newref, GREF);
212 ac_free(newref);
214 /* Limit number of references */
215 while (n->n_flink != NULL)
216 n = n->n_flink;
217 for (i = 1; i < 21; ++i) { /* XXX no magics */
218 if (n->n_blink != NULL)
219 n = n->n_blink;
220 else
221 break;
223 n->n_blink = NULL;
224 head->h_ref = n;
225 if (ok_blook(reply_in_same_charset) &&
226 (cp = hfield1("content-type", mp)) != NULL)
227 head->h_charset = mime_getparam("charset", cp);
228 jleave:
229 NYD_LEAVE;
232 static int
233 (*respond_or_Respond(int c))(int *, int)
235 int opt;
236 int (*rv)(int*, int);
237 NYD_ENTER;
239 opt = ok_blook(Replyall);
240 opt += ok_blook(flipr);
241 rv = ((opt == 1) ^ (c == 'R')) ? &Respond_internal : &respond_internal;
242 NYD_LEAVE;
243 return rv;
246 static int
247 respond_internal(int *msgvec, int recipient_record)
249 struct header head;
250 struct message *mp;
251 char *cp, *rcv;
252 struct name *np = NULL;
253 enum gfield gf;
254 int rv = 1;
255 NYD_ENTER;
257 gf = ok_blook(fullnames) ? GFULL : GSKIN;
259 if (msgvec[1] != 0) {
260 fprintf(stderr, _(
261 "Sorry, can't reply to multiple messages at once\n"));
262 goto jleave;
265 mp = message + msgvec[0] - 1;
266 touch(mp);
267 setdot(mp);
269 if ((rcv = hfield1("reply-to", mp)) == NULL)
270 if ((rcv = hfield1("from", mp)) == NULL)
271 rcv = nameof(mp, 1);
272 if (rcv != NULL)
273 np = lextract(rcv, GTO | gf);
274 if (!ok_blook(recipients_in_cc) && (cp = hfield1("to", mp)) != NULL)
275 np = cat(np, lextract(cp, GTO | gf));
276 /* Delete my name from reply list, and with it, all my alternate names */
277 np = elide(delete_alternates(np));
278 if (np == NULL)
279 np = lextract(rcv, GTO | gf);
281 memset(&head, 0, sizeof head);
282 head.h_to = np;
283 head.h_subject = hfield1("subject", mp);
284 head.h_subject = _reedit(head.h_subject);
286 /* Cc: */
287 np = NULL;
288 if (ok_blook(recipients_in_cc) && (cp = hfield1("to", mp)) != NULL)
289 np = lextract(cp, GCC | gf);
290 if ((cp = hfield1("cc", mp)) != NULL)
291 np = cat(np, lextract(cp, GCC | gf));
292 if (np != NULL)
293 head.h_cc = elide(delete_alternates(np));
294 make_ref_and_cs(mp, &head);
296 if (ok_blook(quote_as_attachment)) {
297 head.h_attach = csalloc(1, sizeof *head.h_attach);
298 head.h_attach->a_msgno = *msgvec;
299 head.h_attach->a_content_description = _(
300 "Original message content");
303 if (mail1(&head, 1, mp, NULL, recipient_record, 0) == OKAY &&
304 ok_blook(markanswered) && !(mp->m_flag & MANSWERED))
305 mp->m_flag |= MANSWER | MANSWERED;
306 rv = 0;
307 jleave:
308 NYD_LEAVE;
309 return rv;
312 static int
313 Respond_internal(int *msgvec, int recipient_record)
315 struct header head;
316 struct message *mp;
317 int *ap;
318 char *cp;
319 enum gfield gf;
320 NYD_ENTER;
322 memset(&head, 0, sizeof head);
323 gf = ok_blook(fullnames) ? GFULL : GSKIN;
325 for (ap = msgvec; *ap != 0; ++ap) {
326 mp = message + *ap - 1;
327 touch(mp);
328 setdot(mp);
329 if ((cp = hfield1("reply-to", mp)) == NULL)
330 if ((cp = hfield1("from", mp)) == NULL)
331 cp = nameof(mp, 2);
332 head.h_to = cat(head.h_to, lextract(cp, GTO | gf));
334 if (head.h_to == NULL)
335 goto jleave;
337 mp = message + msgvec[0] - 1;
338 head.h_subject = hfield1("subject", mp);
339 head.h_subject = _reedit(head.h_subject);
340 make_ref_and_cs(mp, &head);
342 if (ok_blook(quote_as_attachment)) {
343 head.h_attach = csalloc(1, sizeof *head.h_attach);
344 head.h_attach->a_msgno = *msgvec;
345 head.h_attach->a_content_description = _(
346 "Original message content");
349 if (mail1(&head, 1, mp, NULL, recipient_record, 0) == OKAY &&
350 ok_blook(markanswered) && !(mp->m_flag & MANSWERED))
351 mp->m_flag |= MANSWER | MANSWERED;
352 jleave:
353 NYD_LEAVE;
354 return 0;
357 static int
358 _fwd(char *str, int recipient_record)
360 struct header head;
361 int *msgvec, rv = 1;
362 char *recipient;
363 struct message *mp;
364 bool_t f, forward_as_attachment;
365 NYD_ENTER;
367 if ((recipient = laststring(str, &f, TRU1)) == NULL) {
368 puts(_("No recipient specified."));
369 goto jleave;
372 forward_as_attachment = ok_blook(forward_as_attachment);
373 msgvec = salloc((msgCount + 2) * sizeof *msgvec);
375 if (!f) {
376 *msgvec = first(0, MMNORM);
377 if (*msgvec == 0) {
378 if (inhook) {
379 rv = 0;
380 goto jleave;
382 printf("No messages to forward.\n");
383 goto jleave;
385 msgvec[1] = 0;
386 } else if (getmsglist(str, msgvec, 0) < 0)
387 goto jleave;
389 if (*msgvec == 0) {
390 if (inhook) {
391 rv = 0;
392 goto jleave;
394 printf("No applicable messages.\n");
395 goto jleave;
397 if (msgvec[1] != 0) {
398 printf("Cannot forward multiple messages at once\n");
399 goto jleave;
402 memset(&head, 0, sizeof head);
403 if ((head.h_to = lextract(recipient,
404 (GTO | (ok_blook(fullnames) ? GFULL : GSKIN)))) == NULL)
405 goto jleave;
407 mp = message + *msgvec - 1;
409 if (forward_as_attachment) {
410 head.h_attach = csalloc(1, sizeof *head.h_attach);
411 head.h_attach->a_msgno = *msgvec;
412 head.h_attach->a_content_description = "Forwarded message";
413 } else {
414 touch(mp);
415 setdot(mp);
417 head.h_subject = hfield1("subject", mp);
418 head.h_subject = __fwdedit(head.h_subject);
419 mail1(&head, 1, (forward_as_attachment ? NULL : mp), NULL, recipient_record,
421 rv = 0;
422 jleave:
423 NYD_LEAVE;
424 return rv;
427 static char *
428 __fwdedit(char *subj)
430 struct str in, out;
431 char *newsubj = NULL;
432 NYD_ENTER;
434 if (subj == NULL || *subj == '\0')
435 goto jleave;
437 in.s = subj;
438 in.l = strlen(subj);
439 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
441 newsubj = salloc(out.l + 6);
442 memcpy(newsubj, "Fwd: ", 5); /* XXX localizable */
443 memcpy(newsubj + 5, out.s, out.l +1);
444 free(out.s);
445 jleave:
446 NYD_LEAVE;
447 return newsubj;
450 static void
451 asort(char **list)
453 char **ap;
454 size_t i;
455 NYD_ENTER;
457 for (ap = list; *ap != NULL; ++ap)
459 if ((i = PTR2SIZE(ap - list)) >= 2)
460 qsort(list, i, sizeof *list, diction);
461 NYD_LEAVE;
464 static int
465 diction(void const *a, void const *b)
467 int rv;
468 NYD_ENTER;
470 rv = strcmp(*(char**)UNCONST(a), *(char**)UNCONST(b));
471 NYD_LEAVE;
472 return rv;
475 static int
476 _resend1(void *v, bool_t add_resent)
478 char *name, *str;
479 struct name *to, *sn;
480 int *ip, *msgvec;
481 bool_t f = TRU1;
482 NYD_ENTER;
484 str = v;
485 msgvec = salloc((msgCount + 2) * sizeof *msgvec);
486 name = laststring(str, &f, TRU1);
487 if (name == NULL) {
488 puts(_("No recipient specified."));
489 goto jleave;
492 if (!f) {
493 *msgvec = first(0, MMNORM);
494 if (*msgvec == 0) {
495 if (inhook) {
496 f = FAL0;
497 goto jleave;
499 puts(_("No applicable messages."));
500 goto jleave;
502 msgvec[1] = 0;
503 } else if (getmsglist(str, msgvec, 0) < 0)
504 goto jleave;
506 if (*msgvec == 0) {
507 if (inhook) {
508 f = FAL0;
509 goto jleave;
511 printf("No applicable messages.\n");
512 goto jleave;
515 sn = nalloc(name, GTO | GSKIN);
516 to = usermap(sn, FAL0);
517 for (ip = msgvec; *ip != 0 && UICMP(z, PTR2SIZE(ip - msgvec), <, msgCount);
518 ++ip)
519 if (resend_msg(message + *ip - 1, to, add_resent) != OKAY)
520 goto jleave;
521 f = FAL0;
522 jleave:
523 NYD_LEAVE;
524 return (f != FAL0);
527 static int
528 _c_file(void *v, enum fedit_mode fm)
530 char **argv = v;
531 int i;
532 NYD2_ENTER;
534 if (*argv == NULL) {
535 newfileinfo();
536 i = 0;
537 goto jleave;
540 if (inhook) {
541 fprintf(stderr, _("Cannot change folder from within a hook.\n"));
542 i = 1;
543 goto jleave;
546 save_mbox_for_possible_quitstuff();
548 i = setfile(*argv, fm);
549 if (i < 0) {
550 i = 1;
551 goto jleave;
553 callhook(mailname, 0);
554 if (i > 0 && !ok_blook(emptystart)) {
555 i = 1;
556 goto jleave;
558 announce(ok_blook(bsdcompat) || ok_blook(bsdannounce));
559 i = 0;
560 jleave:
561 NYD2_LEAVE;
562 return i;
565 static void
566 list_shortcuts(void)
568 struct shortcut *s;
569 NYD_ENTER;
571 for (s = shortcuts; s != NULL; s = s->sh_next)
572 printf("%s=%s\n", s->sh_short, s->sh_long);
573 NYD_LEAVE;
576 static enum okay
577 delete_shortcut(char const *str)
579 struct shortcut *sp, *sq;
580 enum okay rv = STOP;
581 NYD_ENTER;
583 for (sp = shortcuts, sq = NULL; sp != NULL; sq = sp, sp = sp->sh_next) {
584 if (!strcmp(sp->sh_short, str)) {
585 free(sp->sh_short);
586 free(sp->sh_long);
587 if (sq != NULL)
588 sq->sh_next = sp->sh_next;
589 if (sp == shortcuts)
590 shortcuts = sp->sh_next;
591 free(sp);
592 rv = OKAY;
593 break;
596 NYD_LEAVE;
597 return rv;
600 FL int
601 c_shell(void *v)
603 char const *sh = NULL;
604 char *str = v, *cmd;
605 size_t cmdsize;
606 sigset_t mask;
607 sighandler_type sigint;
608 NYD_ENTER;
610 cmd = smalloc(cmdsize = strlen(str) +1);
611 memcpy(cmd, str, cmdsize);
612 _bangexp(&cmd, &cmdsize);
613 if ((sh = ok_vlook(SHELL)) == NULL)
614 sh = XSHELL;
616 sigint = safe_signal(SIGINT, SIG_IGN);
617 sigemptyset(&mask);
618 run_command(sh, &mask, -1, -1, "-c", cmd, NULL);
619 safe_signal(SIGINT, sigint);
620 printf("!\n");
622 free(cmd);
623 NYD_LEAVE;
624 return 0;
627 FL int
628 c_dosh(void *v)
630 sighandler_type sigint;
631 char const *sh;
632 NYD_ENTER;
633 UNUSED(v);
635 if ((sh = ok_vlook(SHELL)) == NULL)
636 sh = XSHELL;
638 sigint = safe_signal(SIGINT, SIG_IGN);
639 run_command(sh, 0, -1, -1, NULL, NULL, NULL);
640 safe_signal(SIGINT, sigint);
641 putchar('\n');
642 NYD_LEAVE;
643 return 0;
646 FL int
647 c_help(void *v)
649 int ret = 0;
650 char *arg;
651 NYD_ENTER;
653 arg = *(char**)v;
655 if (arg != NULL) {
656 #ifdef HAVE_DOCSTRINGS
657 ret = !print_comm_docstr(arg);
658 if (ret)
659 fprintf(stderr, _("Unknown command: `%s'\n"), arg);
660 #else
661 ret = c_cmdnotsupp(NULL);
662 #endif
663 goto jleave;
666 /* Very ugly, but take care for compiler supported string lengths :( */
667 printf(_("%s commands:\n"), progname);
668 puts(_(
669 "type <message list> type messages\n"
670 "next goto and type next message\n"
671 "from <message list> give head lines of messages\n"
672 "headers print out active message headers\n"
673 "delete <message list> delete messages\n"
674 "undelete <message list> undelete messages\n"));
675 puts(_(
676 "save <message list> folder append messages to folder and mark as saved\n"
677 "copy <message list> folder append messages to folder without marking them\n"
678 "write <message list> file append message texts to file, save attachments\n"
679 "preserve <message list> keep incoming messages in mailbox even if saved\n"
680 "Reply <message list> reply to message senders\n"
681 "reply <message list> reply to message senders and all recipients\n"));
682 puts(_(
683 "mail addresses mail to specific recipients\n"
684 "file folder change to another folder\n"
685 "quit quit and apply changes to folder\n"
686 "xit quit and discard changes made to folder\n"
687 "! shell escape\n"
688 "cd <directory> chdir to directory or home if none given\n"
689 "list list names of all available commands\n"));
690 printf(_(
691 "A <message list> consists of integers, ranges of same, or other criteria\n"
692 "separated by spaces. If omitted, %s uses the last message typed.\n"),
693 progname);
695 jleave:
696 NYD_LEAVE;
697 return ret;
700 FL int
701 c_cwd(void *v)
703 char buf[PATH_MAX]; /* TODO getcwd(3) may return a larger value */
704 NYD_ENTER;
706 if (getcwd(buf, sizeof buf) != NULL) {
707 puts(buf);
708 v = (void*)0x1;
709 } else {
710 perror("getcwd");
711 v = NULL;
713 NYD_LEAVE;
714 return (v == NULL);
717 FL int
718 c_chdir(void *v)
720 char **arglist = v;
721 char const *cp;
722 NYD_ENTER;
724 if (*arglist == NULL)
725 cp = homedir;
726 else if ((cp = file_expand(*arglist)) == NULL)
727 goto jleave;
728 if (chdir(cp) == -1) {
729 perror(cp);
730 cp = NULL;
732 jleave:
733 NYD_LEAVE;
734 return (cp == NULL);
737 FL int
738 c_respond(void *v)
740 int rv;
741 NYD_ENTER;
743 rv = (*respond_or_Respond('r'))(v, 0);
744 NYD_LEAVE;
745 return rv;
748 FL int
749 c_respondall(void *v)
751 int rv;
752 NYD_ENTER;
754 rv = respond_internal(v, 0);
755 NYD_LEAVE;
756 return rv;
759 FL int
760 c_respondsender(void *v)
762 int rv;
763 NYD_ENTER;
765 rv = Respond_internal(v, 0);
766 NYD_LEAVE;
767 return rv;
770 FL int
771 c_Respond(void *v)
773 int rv;
774 NYD_ENTER;
776 rv = (*respond_or_Respond('R'))(v, 0);
777 NYD_LEAVE;
778 return rv;
781 FL int
782 c_followup(void *v)
784 int rv;
785 NYD_ENTER;
787 rv = (*respond_or_Respond('r'))(v, 1);
788 NYD_LEAVE;
789 return rv;
792 FL int
793 c_followupall(void *v)
795 int rv;
796 NYD_ENTER;
798 rv = respond_internal(v, 1);
799 NYD_LEAVE;
800 return rv;
803 FL int
804 c_followupsender(void *v)
806 int rv;
807 NYD_ENTER;
809 rv = Respond_internal(v, 1);
810 NYD_LEAVE;
811 return rv;
814 FL int
815 c_Followup(void *v)
817 int rv;
818 NYD_ENTER;
820 rv = (*respond_or_Respond('R'))(v, 1);
821 NYD_LEAVE;
822 return rv;
825 FL int
826 c_forward(void *v)
828 int rv;
829 NYD_ENTER;
831 rv = _fwd(v, 0);
832 NYD_LEAVE;
833 return rv;
836 FL int
837 c_Forward(void *v)
839 int rv;
840 NYD_ENTER;
842 rv = _fwd(v, 1);
843 NYD_LEAVE;
844 return rv;
847 FL int
848 c_resend(void *v)
850 int rv;
851 NYD_ENTER;
853 rv = _resend1(v, TRU1);
854 NYD_LEAVE;
855 return rv;
858 FL int
859 c_Resend(void *v)
861 int rv;
862 NYD_ENTER;
864 rv = _resend1(v, FAL0);
865 NYD_LEAVE;
866 return rv;
869 FL int
870 c_preserve(void *v)
872 int *msgvec = v, *ip, mesg, rv = 1;
873 struct message *mp;
874 NYD_ENTER;
876 if (edit) {
877 printf(_("Cannot \"preserve\" in edit mode\n"));
878 goto jleave;
881 for (ip = msgvec; *ip != 0; ++ip) {
882 mesg = *ip;
883 mp = message + mesg - 1;
884 mp->m_flag |= MPRESERVE;
885 mp->m_flag &= ~MBOX;
886 setdot(mp);
887 did_print_dot = TRU1;
889 rv = 0;
890 jleave:
891 NYD_LEAVE;
892 return rv;
895 FL int
896 c_unread(void *v)
898 int *msgvec = v, *ip;
899 NYD_ENTER;
901 for (ip = msgvec; *ip != 0; ++ip) {
902 setdot(message + *ip - 1);
903 dot->m_flag &= ~(MREAD | MTOUCH);
904 dot->m_flag |= MSTATUS;
905 #ifdef HAVE_IMAP
906 if (mb.mb_type == MB_IMAP || mb.mb_type == MB_CACHE)
907 imap_unread(message + *ip - 1, *ip); /* TODO return? */
908 #endif
909 did_print_dot = TRU1;
911 NYD_LEAVE;
912 return 0;
915 FL int
916 c_seen(void *v)
918 int *msgvec = v, *ip;
919 NYD_ENTER;
921 for (ip = msgvec; *ip != 0; ++ip) {
922 struct message *mp = message + *ip - 1;
923 setdot(mp);
924 touch(mp);
926 NYD_LEAVE;
927 return 0;
930 FL int
931 c_messize(void *v)
933 int *msgvec = v, *ip, mesg;
934 struct message *mp;
935 NYD_ENTER;
937 for (ip = msgvec; *ip != 0; ++ip) {
938 mesg = *ip;
939 mp = message + mesg - 1;
940 printf("%d: ", mesg);
941 if (mp->m_xlines > 0)
942 printf("%ld", mp->m_xlines);
943 else
944 putchar(' ');
945 printf("/%lu\n", (ul_i)mp->m_xsize);
947 NYD_LEAVE;
948 return 0;
951 FL int
952 c_rexit(void *v)
954 UNUSED(v);
955 NYD_ENTER;
957 if (!sourcing)
958 exit(0);
959 NYD_LEAVE;
960 return 1;
963 FL int
964 c_group(void *v)
966 char **argv = v, **ap, *gname, **p;
967 struct grouphead *gh;
968 struct group *gp;
969 int h, s;
970 NYD_ENTER;
972 if (*argv == NULL) {
973 for (h = 0, s = 1; h < HSHSIZE; ++h)
974 for (gh = groups[h]; gh != NULL; gh = gh->g_link)
975 ++s;
976 ap = salloc(s * sizeof *ap);
978 for (h = 0, p = ap; h < HSHSIZE; ++h)
979 for (gh = groups[h]; gh != NULL; gh = gh->g_link)
980 *p++ = gh->g_name;
981 *p = NULL;
983 asort(ap);
985 for (p = ap; *p != NULL; ++p)
986 printgroup(*p);
987 goto jleave;
990 if (argv[1] == NULL) {
991 printgroup(*argv);
992 goto jleave;
995 gname = *argv;
996 h = hash(gname);
997 if ((gh = findgroup(gname)) == NULL) {
998 gh = scalloc(1, sizeof *gh);
999 gh->g_name = sstrdup(gname);
1000 gh->g_list = NULL;
1001 gh->g_link = groups[h];
1002 groups[h] = gh;
1005 /* Insert names from the command list into the group. Who cares if there
1006 * are duplicates? They get tossed later anyway */
1007 for (ap = argv + 1; *ap != NULL; ++ap) {
1008 gp = scalloc(1, sizeof *gp);
1009 gp->ge_name = sstrdup(*ap);
1010 gp->ge_link = gh->g_list;
1011 gh->g_list = gp;
1013 jleave:
1014 NYD_LEAVE;
1015 return 0;
1018 FL int
1019 c_ungroup(void *v)
1021 char **argv = v;
1022 int rv = 1;
1023 NYD_ENTER;
1025 if (*argv == NULL) {
1026 fprintf(stderr, _("Must specify alias to remove\n"));
1027 goto jleave;
1031 remove_group(*argv);
1032 while (*++argv != NULL);
1033 rv = 0;
1034 jleave:
1035 NYD_LEAVE;
1036 return rv;
1039 FL int
1040 c_file(void *v)
1042 int rv;
1043 NYD_ENTER;
1045 rv = _c_file(v, FEDIT_NONE);
1046 NYD_LEAVE;
1047 return rv;
1050 FL int
1051 c_File(void *v)
1053 int rv;
1054 NYD_ENTER;
1056 rv = _c_file(v, FEDIT_RDONLY);
1057 NYD_LEAVE;
1058 return rv;
1061 FL int
1062 c_echo(void *v)
1064 char const **argv = v, **ap, *cp;
1065 int c;
1066 NYD_ENTER;
1068 for (ap = argv; *ap != NULL; ++ap) {
1069 cp = *ap;
1070 if ((cp = fexpand(cp, FEXP_NSHORTCUT)) != NULL) {
1071 if (ap != argv)
1072 putchar(' ');
1073 c = 0;
1074 while (*cp != '\0' && (c = expand_shell_escape(&cp, FAL0)) > 0)
1075 putchar(c);
1076 /* \c ends overall processing */
1077 if (c < 0)
1078 goto jleave;
1081 putchar('\n');
1082 jleave:
1083 NYD_LEAVE;
1084 return 0;
1087 FL int
1088 c_if(void *v)
1090 struct cond_stack *csp;
1091 int rv = 1;
1092 char **argv = v, *cp, *op;
1093 NYD_ENTER;
1095 csp = smalloc(sizeof *csp);
1096 csp->c_outer = _cond_stack;
1097 csp->c_noop = condstack_isskip();
1098 csp->c_go = TRU1;
1099 csp->c_else = FAL0;
1100 _cond_stack = csp;
1102 cp = argv[0];
1103 if (*cp != '$' && argv[1] != NULL) {
1104 jesyn:
1105 fprintf(stderr, _("Invalid conditional expression \"%s %s %s\"\n"),
1106 argv[0], (argv[1] != NULL ? argv[1] : ""),
1107 (argv[2] != NULL ? argv[2] : ""));
1108 goto jleave;
1111 switch (*cp) {
1112 case '0':
1113 csp->c_go = FAL0;
1114 break;
1115 case 'R': case 'r':
1116 csp->c_go = !(options & OPT_SENDMODE);
1117 break;
1118 case 'S': case 's':
1119 csp->c_go = ((options & OPT_SENDMODE) != 0);
1120 break;
1121 case 'T': case 't':
1122 csp->c_go = ((options & OPT_TTYIN) != 0);
1123 break;
1124 case '$':
1125 /* Look up the value in question, we need it anyway */
1126 v = vok_vlook(++cp);
1128 /* Single argument, "implicit boolean" form? */
1129 if ((op = argv[1]) == NULL) {
1130 csp->c_go = (v != NULL);
1131 break;
1134 /* Three argument comparison form? */
1135 if (argv[2] == NULL || op[0] == '\0' ||
1136 #ifdef HAVE_REGEX
1137 (op[1] != '=' && op[1] != '~') ||
1138 #else
1139 op[1] != '=' ||
1140 #endif
1141 op[2] != '\0')
1142 goto jesyn;
1144 /* A null value is treated as the empty string */
1145 if (v == NULL)
1146 v = UNCONST("");
1147 #ifdef HAVE_REGEX
1148 if (op[1] == '~') {
1149 regex_t re;
1151 if (regcomp(&re, argv[2], REG_EXTENDED | REG_ICASE | REG_NOSUB))
1152 goto jesyn;
1153 if (regexec(&re, v, 0,NULL, 0) == REG_NOMATCH)
1154 v = NULL;
1155 regfree(&re);
1156 } else
1157 #endif
1158 if (strcmp(v, argv[2]))
1159 v = NULL;
1160 switch (op[0]) {
1161 case '!':
1162 case '=':
1163 csp->c_go = ((op[0] == '=') ^ (v == NULL));
1164 break;
1165 default:
1166 goto jesyn;
1168 break;
1169 default:
1170 fprintf(stderr, _("Unrecognized if-keyword: \"%s\"\n"), cp);
1171 case '1':
1172 csp->c_go = TRU1;
1173 goto jleave;
1175 rv = 0;
1176 jleave:
1177 NYD_LEAVE;
1178 return rv;
1181 FL int
1182 c_elif(void *v)
1184 struct cond_stack *csp;
1185 int rv;
1186 NYD_ENTER;
1188 if ((csp = _cond_stack) == NULL || csp->c_else) {
1189 fprintf(stderr, _("`elif' without matching `if'\n"));
1190 rv = 1;
1191 } else {
1192 csp->c_go = !csp->c_go;
1193 rv = c_if(v);
1194 _cond_stack->c_outer = csp->c_outer;
1195 free(csp);
1197 NYD_LEAVE;
1198 return rv;
1201 FL int
1202 c_else(void *v)
1204 int rv;
1205 NYD_ENTER;
1206 UNUSED(v);
1208 if (_cond_stack == NULL || _cond_stack->c_else) {
1209 fprintf(stderr, _("`else' without matching `if'\n"));
1210 rv = 1;
1211 } else {
1212 _cond_stack->c_go = !_cond_stack->c_go;
1213 _cond_stack->c_else = TRU1;
1214 rv = 0;
1216 NYD_LEAVE;
1217 return rv;
1220 FL int
1221 c_endif(void *v)
1223 struct cond_stack *csp;
1224 int rv;
1225 NYD_ENTER;
1226 UNUSED(v);
1228 if ((csp = _cond_stack) == NULL) {
1229 fprintf(stderr, _("`endif' without matching `if'\n"));
1230 rv = 1;
1231 } else {
1232 _cond_stack = csp->c_outer;
1233 free(csp);
1234 rv = 0;
1236 NYD_LEAVE;
1237 return rv;
1240 FL bool_t
1241 condstack_isskip(void)
1243 bool_t rv;
1244 NYD_ENTER;
1246 rv = (_cond_stack != NULL && (_cond_stack->c_noop || !_cond_stack->c_go));
1247 NYD_LEAVE;
1248 return rv;
1251 FL void *
1252 condstack_release(void)
1254 void *rv;
1255 NYD_ENTER;
1257 rv = _cond_stack;
1258 _cond_stack = NULL;
1259 NYD_LEAVE;
1260 return rv;
1263 FL bool_t
1264 condstack_take(void *self)
1266 struct cond_stack *csp;
1267 bool_t rv;
1268 NYD_ENTER;
1270 if (!(rv = ((csp = _cond_stack) == NULL)))
1271 do {
1272 _cond_stack = csp->c_outer;
1273 free(csp);
1274 } while ((csp = _cond_stack) != NULL);
1276 _cond_stack = self;
1277 NYD_LEAVE;
1278 return rv;
1281 FL int
1282 c_alternates(void *v)
1284 size_t l;
1285 char **namelist = v, **ap, **ap2, *cp;
1286 NYD_ENTER;
1288 l = argcount(namelist) + 1;
1289 if (l == 1) {
1290 if (altnames == NULL)
1291 goto jleave;
1292 for (ap = altnames; *ap != NULL; ++ap)
1293 printf("%s ", *ap);
1294 printf("\n");
1295 goto jleave;
1298 if (altnames != NULL) {
1299 for (ap = altnames; *ap != NULL; ++ap)
1300 free(*ap);
1301 free(altnames);
1303 altnames = smalloc(l * sizeof(char*));
1304 for (ap = namelist, ap2 = altnames; *ap != NULL; ++ap, ++ap2) {
1305 l = strlen(*ap) + 1;
1306 cp = smalloc(l);
1307 memcpy(cp, *ap, l);
1308 *ap2 = cp;
1310 *ap2 = NULL;
1311 jleave:
1312 NYD_LEAVE;
1313 return 0;
1316 FL int
1317 c_newmail(void *v)
1319 int val = 1, mdot;
1320 NYD_ENTER;
1321 UNUSED(v);
1323 if (
1324 #ifdef HAVE_IMAP
1325 (mb.mb_type != MB_IMAP || imap_newmail(1)) &&
1326 #endif
1327 (val = setfile(mailname,
1328 FEDIT_NEWMAIL | ((mb.mb_perm & MB_DELE) ? 0 : FEDIT_RDONLY))
1329 ) == 0) {
1330 mdot = getmdot(1);
1331 setdot(message + mdot - 1);
1333 NYD_LEAVE;
1334 return val;
1337 FL int
1338 c_shortcut(void *v)
1340 char **args = v;
1341 struct shortcut *s;
1342 int rv;
1343 NYD_ENTER;
1345 if (args[0] == NULL) {
1346 list_shortcuts();
1347 rv = 0;
1348 goto jleave;
1351 rv = 1;
1352 if (args[1] == NULL) {
1353 fprintf(stderr, _("expansion name for shortcut missing\n"));
1354 goto jleave;
1356 if (args[2] != NULL) {
1357 fprintf(stderr, _("too many arguments\n"));
1358 goto jleave;
1361 if ((s = get_shortcut(args[0])) != NULL) {
1362 free(s->sh_long);
1363 s->sh_long = sstrdup(args[1]);
1364 } else {
1365 s = scalloc(1, sizeof *s);
1366 s->sh_short = sstrdup(args[0]);
1367 s->sh_long = sstrdup(args[1]);
1368 s->sh_next = shortcuts;
1369 shortcuts = s;
1371 rv = 0;
1372 jleave:
1373 NYD_LEAVE;
1374 return rv;
1377 FL struct shortcut *
1378 get_shortcut(char const *str)
1380 struct shortcut *s;
1381 NYD_ENTER;
1383 for (s = shortcuts; s != NULL; s = s->sh_next)
1384 if (!strcmp(str, s->sh_short))
1385 break;
1386 NYD_LEAVE;
1387 return s;
1390 FL int
1391 c_unshortcut(void *v)
1393 char **args = v;
1394 bool_t errs = FAL0;
1395 NYD_ENTER;
1397 if (args[0] == NULL) {
1398 fprintf(stderr, _("need shortcut names to remove\n"));
1399 errs = TRU1;
1400 goto jleave;
1403 while (*args != NULL) {
1404 if (delete_shortcut(*args) != OKAY) {
1405 errs = TRU1;
1406 fprintf(stderr, _("%s: no such shortcut\n"), *args);
1408 ++args;
1410 jleave:
1411 NYD_LEAVE;
1412 return errs;
1415 FL int
1416 c_flag(void *v)
1418 struct message *m;
1419 int *msgvec = v, *ip;
1420 NYD_ENTER;
1422 for (ip = msgvec; *ip != 0; ++ip) {
1423 m = message + *ip - 1;
1424 setdot(m);
1425 if (!(m->m_flag & (MFLAG | MFLAGGED)))
1426 m->m_flag |= MFLAG | MFLAGGED;
1428 NYD_LEAVE;
1429 return 0;
1432 FL int
1433 c_unflag(void *v)
1435 struct message *m;
1436 int *msgvec = v, *ip;
1437 NYD_ENTER;
1439 for (ip = msgvec; *ip != 0; ++ip) {
1440 m = message + *ip - 1;
1441 setdot(m);
1442 if (m->m_flag & (MFLAG | MFLAGGED)) {
1443 m->m_flag &= ~(MFLAG | MFLAGGED);
1444 m->m_flag |= MUNFLAG;
1447 NYD_LEAVE;
1448 return 0;
1451 FL int
1452 c_answered(void *v)
1454 struct message *m;
1455 int *msgvec = v, *ip;
1456 NYD_ENTER;
1458 for (ip = msgvec; *ip != 0; ++ip) {
1459 m = message + *ip - 1;
1460 setdot(m);
1461 if (!(m->m_flag & (MANSWER | MANSWERED)))
1462 m->m_flag |= MANSWER | MANSWERED;
1464 NYD_LEAVE;
1465 return 0;
1468 FL int
1469 c_unanswered(void *v)
1471 struct message *m;
1472 int *msgvec = v, *ip;
1473 NYD_ENTER;
1475 for (ip = msgvec; *ip != 0; ++ip) {
1476 m = message + *ip - 1;
1477 setdot(m);
1478 if (m->m_flag & (MANSWER | MANSWERED)) {
1479 m->m_flag &= ~(MANSWER | MANSWERED);
1480 m->m_flag |= MUNANSWER;
1483 NYD_LEAVE;
1484 return 0;
1487 FL int
1488 c_draft(void *v)
1490 struct message *m;
1491 int *msgvec = v, *ip;
1492 NYD_ENTER;
1494 for (ip = msgvec; *ip != 0; ++ip) {
1495 m = message + *ip - 1;
1496 setdot(m);
1497 if (!(m->m_flag & (MDRAFT | MDRAFTED)))
1498 m->m_flag |= MDRAFT | MDRAFTED;
1500 NYD_LEAVE;
1501 return 0;
1504 FL int
1505 c_undraft(void *v)
1507 struct message *m;
1508 int *msgvec = v, *ip;
1509 NYD_ENTER;
1511 for (ip = msgvec; *ip != 0; ++ip) {
1512 m = message + *ip - 1;
1513 setdot(m);
1514 if (m->m_flag & (MDRAFT | MDRAFTED)) {
1515 m->m_flag &= ~(MDRAFT | MDRAFTED);
1516 m->m_flag |= MUNDRAFT;
1519 NYD_LEAVE;
1520 return 0;
1523 FL int
1524 c_noop(void *v)
1526 int rv = 0;
1527 NYD_ENTER;
1528 UNUSED(v);
1530 switch (mb.mb_type) {
1531 case MB_IMAP:
1532 #ifdef HAVE_IMAP
1533 imap_noop();
1534 #else
1535 rv = c_cmdnotsupp(NULL);
1536 #endif
1537 break;
1538 case MB_POP3:
1539 #ifdef HAVE_POP3
1540 pop3_noop();
1541 #else
1542 rv = c_cmdnotsupp(NULL);
1543 #endif
1544 break;
1545 default:
1546 break;
1548 NYD_LEAVE;
1549 return rv;
1552 FL int
1553 c_remove(void *v)
1555 char const *fmt;
1556 size_t fmt_len;
1557 char **args = v, *name;
1558 int ec = 0;
1559 NYD_ENTER;
1561 if (*args == NULL) {
1562 fprintf(stderr, _("Syntax is: remove mailbox ...\n"));
1563 ec = 1;
1564 goto jleave;
1567 fmt = _("Remove \"%s\" (y/n) ? ");
1568 fmt_len = strlen(fmt);
1569 do {
1570 if ((name = expand(*args)) == NULL)
1571 continue;
1573 if (!strcmp(name, mailname)) {
1574 fprintf(stderr, _("Cannot remove current mailbox \"%s\".\n"),
1575 name);
1576 ec |= 1;
1577 continue;
1580 size_t vl = strlen(name) + fmt_len +1;
1581 char *vb = ac_alloc(vl);
1582 bool_t asw;
1583 snprintf(vb, vl, fmt, name);
1584 asw = getapproval(vb, TRU1);
1585 ac_free(vb);
1586 if (!asw)
1587 continue;
1590 switch (which_protocol(name)) {
1591 case PROTO_FILE:
1592 if (unlink(name) == -1) { /* TODO do not handle .gz .bz2 .xz.. */
1593 perror(name);
1594 ec |= 1;
1596 break;
1597 case PROTO_POP3:
1598 fprintf(stderr, _("Cannot remove POP3 mailbox \"%s\".\n"),name);
1599 ec |= 1;
1600 break;
1601 case PROTO_IMAP:
1602 #ifdef HAVE_IMAP
1603 if (imap_remove(name) != OKAY)
1604 #endif
1605 ec |= 1;
1606 break;
1607 case PROTO_MAILDIR:
1608 if (maildir_remove(name) != OKAY)
1609 ec |= 1;
1610 break;
1611 case PROTO_UNKNOWN:
1612 fprintf(stderr, _("Unknown protocol in \"%s\". Not removed.\n"),
1613 name);
1614 ec |= 1;
1615 break;
1617 } while (*++args != NULL);
1618 jleave:
1619 NYD_LEAVE;
1620 return ec;
1623 FL int
1624 c_rename(void *v)
1626 char **args = v, *old, *new;
1627 enum protocol oldp, newp;
1628 int ec;
1629 NYD_ENTER;
1631 ec = 1;
1633 if (args[0] == NULL || args[1] == NULL || args[2] != NULL) {
1634 fprintf(stderr, "Syntax: rename old new\n");
1635 goto jleave;
1638 if ((old = expand(args[0])) == NULL)
1639 goto jleave;
1640 oldp = which_protocol(old);
1641 if ((new = expand(args[1])) == NULL)
1642 goto jleave;
1643 newp = which_protocol(new);
1645 if (!strcmp(old, mailname) || !strcmp(new, mailname)) {
1646 fprintf(stderr, _("Cannot rename current mailbox \"%s\".\n"), old);
1647 goto jleave;
1649 if ((oldp == PROTO_IMAP || newp == PROTO_IMAP) && oldp != newp) {
1650 fprintf(stderr, _("Can only rename folders of same type.\n"));
1651 goto jleave;
1654 ec = 0;
1656 if (newp == PROTO_POP3)
1657 goto jnopop3;
1658 switch (oldp) {
1659 case PROTO_FILE:
1660 if (link(old, new) == -1) {
1661 switch (errno) {
1662 case EACCES:
1663 case EEXIST:
1664 case ENAMETOOLONG:
1665 case ENOENT:
1666 case ENOSPC:
1667 case EXDEV:
1668 perror(new);
1669 break;
1670 default:
1671 perror(old);
1673 ec |= 1;
1674 } else if (unlink(old) == -1) {
1675 perror(old);
1676 ec |= 1;
1678 break;
1679 case PROTO_MAILDIR:
1680 if (rename(old, new) == -1) {
1681 perror(old);
1682 ec |= 1;
1684 break;
1685 case PROTO_POP3:
1686 jnopop3:
1687 fprintf(stderr, _("Cannot rename POP3 mailboxes.\n"));
1688 ec |= 1;
1689 break;
1690 #ifdef HAVE_IMAP
1691 case PROTO_IMAP:
1692 if (imap_rename(old, new) != OKAY)
1693 ec |= 1;
1694 break;
1695 #endif
1696 case PROTO_UNKNOWN:
1697 default:
1698 fprintf(stderr, _(
1699 "Unknown protocol in \"%s\" and \"%s\". Not renamed.\n"), old, new);
1700 ec |= 1;
1701 break;
1703 jleave:
1704 NYD_LEAVE;
1705 return ec;
1708 FL int
1709 c_urlencode(void *v) /* XXX IDNA?? */
1711 char **ap;
1712 NYD_ENTER;
1714 for (ap = v; *ap != NULL; ++ap) {
1715 char *in = *ap, *out = urlxenc(in, FAL0);
1717 printf(" in: <%s> (%" PRIuZ " bytes)\nout: <%s> (%" PRIuZ " bytes)\n",
1718 in, strlen(in), out, strlen(out));
1720 NYD_LEAVE;
1721 return 0;
1724 FL int
1725 c_urldecode(void *v) /* XXX IDNA?? */
1727 char **ap;
1728 NYD_ENTER;
1730 for (ap = v; *ap != NULL; ++ap) {
1731 char *in = *ap, *out = urlxdec(in);
1733 printf(" in: <%s> (%" PRIuZ " bytes)\nout: <%s> (%" PRIuZ " bytes)\n",
1734 in, strlen(in), out, strlen(out));
1736 NYD_LEAVE;
1737 return 0;
1740 /* s-it-mode */