run_editor(): also compare size when deciding "has changed"
[s-mailx.git] / cmd3.c
bloba5b9b2402f86cbf158a96ecd1f0321ca0fa812b5
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, _(
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 = _(
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 = _(
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(_("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(_("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(_("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, _("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(_("%s commands:\n"), progname);
629 puts(_(
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(_(
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(_(
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(_(
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(_("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, _("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, _("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, _("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' ||
1113 #ifdef HAVE_REGEX
1114 (op[1] != '=' && op[1] != '~') ||
1115 #else
1116 op[1] != '=' ||
1117 #endif
1118 op[2] != '\0')
1119 goto jesyn;
1121 /* A null value is treated as the empty string */
1122 if (v == NULL)
1123 v = UNCONST("");
1124 #ifdef HAVE_REGEX
1125 if (op[1] == '~') {
1126 regex_t re;
1128 if (regcomp(&re, argv[2], REG_EXTENDED | REG_ICASE | REG_NOSUB))
1129 goto jesyn;
1130 if (regexec(&re, v, 0,NULL, 0) == REG_NOMATCH)
1131 v = NULL;
1132 regfree(&re);
1133 } else
1134 #endif
1135 if (strcmp(v, argv[2]))
1136 v = NULL;
1137 switch (op[0]) {
1138 case '!':
1139 case '=':
1140 csp->c_go = ((op[0] == '=') ^ (v == NULL));
1141 break;
1142 default:
1143 goto jesyn;
1145 break;
1146 default:
1147 fprintf(stderr, _("Unrecognized if-keyword: \"%s\"\n"), cp);
1148 case '1':
1149 csp->c_go = TRU1;
1150 goto jleave;
1152 rv = 0;
1153 jleave:
1154 NYD_LEAVE;
1155 return rv;
1158 FL int
1159 c_elif(void *v)
1161 struct cond_stack *csp;
1162 int rv;
1163 NYD_ENTER;
1165 if ((csp = _cond_stack) == NULL || csp->c_else) {
1166 fprintf(stderr, _("`elif' without matching `if'\n"));
1167 rv = 1;
1168 } else {
1169 csp->c_go = !csp->c_go;
1170 rv = c_if(v);
1171 _cond_stack->c_outer = csp->c_outer;
1172 free(csp);
1174 NYD_LEAVE;
1175 return rv;
1178 FL int
1179 c_else(void *v)
1181 int rv;
1182 NYD_ENTER;
1183 UNUSED(v);
1185 if (_cond_stack == NULL || _cond_stack->c_else) {
1186 fprintf(stderr, _("`else' without matching `if'\n"));
1187 rv = 1;
1188 } else {
1189 _cond_stack->c_go = !_cond_stack->c_go;
1190 _cond_stack->c_else = TRU1;
1191 rv = 0;
1193 NYD_LEAVE;
1194 return rv;
1197 FL int
1198 c_endif(void *v)
1200 struct cond_stack *csp;
1201 int rv;
1202 NYD_ENTER;
1203 UNUSED(v);
1205 if ((csp = _cond_stack) == NULL) {
1206 fprintf(stderr, _("`endif' without matching `if'\n"));
1207 rv = 1;
1208 } else {
1209 _cond_stack = csp->c_outer;
1210 free(csp);
1211 rv = 0;
1213 NYD_LEAVE;
1214 return rv;
1217 FL bool_t
1218 condstack_isskip(void)
1220 bool_t rv;
1221 NYD_ENTER;
1223 rv = (_cond_stack != NULL && (_cond_stack->c_noop || !_cond_stack->c_go));
1224 NYD_LEAVE;
1225 return rv;
1228 FL void *
1229 condstack_release(void)
1231 void *rv;
1232 NYD_ENTER;
1234 rv = _cond_stack;
1235 _cond_stack = NULL;
1236 NYD_LEAVE;
1237 return rv;
1240 FL bool_t
1241 condstack_take(void *self)
1243 struct cond_stack *csp;
1244 bool_t rv;
1245 NYD_ENTER;
1247 if (!(rv = ((csp = _cond_stack) == NULL)))
1248 do {
1249 _cond_stack = csp->c_outer;
1250 free(csp);
1251 } while ((csp = _cond_stack) != NULL);
1253 _cond_stack = self;
1254 NYD_LEAVE;
1255 return rv;
1258 FL int
1259 c_alternates(void *v)
1261 size_t l;
1262 char **namelist = v, **ap, **ap2, *cp;
1263 NYD_ENTER;
1265 l = argcount(namelist) + 1;
1266 if (l == 1) {
1267 if (altnames == NULL)
1268 goto jleave;
1269 for (ap = altnames; *ap != NULL; ++ap)
1270 printf("%s ", *ap);
1271 printf("\n");
1272 goto jleave;
1275 if (altnames != NULL) {
1276 for (ap = altnames; *ap != NULL; ++ap)
1277 free(*ap);
1278 free(altnames);
1280 altnames = smalloc(l * sizeof(char*));
1281 for (ap = namelist, ap2 = altnames; *ap != NULL; ++ap, ++ap2) {
1282 l = strlen(*ap) + 1;
1283 cp = smalloc(l);
1284 memcpy(cp, *ap, l);
1285 *ap2 = cp;
1287 *ap2 = NULL;
1288 jleave:
1289 NYD_LEAVE;
1290 return 0;
1293 FL int
1294 c_newmail(void *v)
1296 int val = 1, mdot;
1297 NYD_ENTER;
1298 UNUSED(v);
1300 if (
1301 #ifdef HAVE_IMAP
1302 (mb.mb_type != MB_IMAP || imap_newmail(1)) &&
1303 #endif
1304 (val = setfile(mailname, 1)) == 0) {
1305 mdot = getmdot(1);
1306 setdot(message + mdot - 1);
1308 NYD_LEAVE;
1309 return val;
1312 FL int
1313 c_shortcut(void *v)
1315 char **args = v;
1316 struct shortcut *s;
1317 int rv;
1318 NYD_ENTER;
1320 if (args[0] == NULL) {
1321 list_shortcuts();
1322 rv = 0;
1323 goto jleave;
1326 rv = 1;
1327 if (args[1] == NULL) {
1328 fprintf(stderr, _("expansion name for shortcut missing\n"));
1329 goto jleave;
1331 if (args[2] != NULL) {
1332 fprintf(stderr, _("too many arguments\n"));
1333 goto jleave;
1336 if ((s = get_shortcut(args[0])) != NULL) {
1337 free(s->sh_long);
1338 s->sh_long = sstrdup(args[1]);
1339 } else {
1340 s = scalloc(1, sizeof *s);
1341 s->sh_short = sstrdup(args[0]);
1342 s->sh_long = sstrdup(args[1]);
1343 s->sh_next = shortcuts;
1344 shortcuts = s;
1346 rv = 0;
1347 jleave:
1348 NYD_LEAVE;
1349 return rv;
1352 FL struct shortcut *
1353 get_shortcut(char const *str)
1355 struct shortcut *s;
1356 NYD_ENTER;
1358 for (s = shortcuts; s != NULL; s = s->sh_next)
1359 if (!strcmp(str, s->sh_short))
1360 break;
1361 NYD_LEAVE;
1362 return s;
1365 FL int
1366 c_unshortcut(void *v)
1368 char **args = v;
1369 bool_t errs = FAL0;
1370 NYD_ENTER;
1372 if (args[0] == NULL) {
1373 fprintf(stderr, _("need shortcut names to remove\n"));
1374 errs = TRU1;
1375 goto jleave;
1378 while (*args != NULL) {
1379 if (delete_shortcut(*args) != OKAY) {
1380 errs = TRU1;
1381 fprintf(stderr, _("%s: no such shortcut\n"), *args);
1383 ++args;
1385 jleave:
1386 NYD_LEAVE;
1387 return errs;
1390 FL int
1391 c_flag(void *v)
1393 struct message *m;
1394 int *msgvec = v, *ip;
1395 NYD_ENTER;
1397 for (ip = msgvec; *ip != 0; ++ip) {
1398 m = message + *ip - 1;
1399 setdot(m);
1400 if (!(m->m_flag & (MFLAG | MFLAGGED)))
1401 m->m_flag |= MFLAG | MFLAGGED;
1403 NYD_LEAVE;
1404 return 0;
1407 FL int
1408 c_unflag(void *v)
1410 struct message *m;
1411 int *msgvec = v, *ip;
1412 NYD_ENTER;
1414 for (ip = msgvec; *ip != 0; ++ip) {
1415 m = message + *ip - 1;
1416 setdot(m);
1417 if (m->m_flag & (MFLAG | MFLAGGED)) {
1418 m->m_flag &= ~(MFLAG | MFLAGGED);
1419 m->m_flag |= MUNFLAG;
1422 NYD_LEAVE;
1423 return 0;
1426 FL int
1427 c_answered(void *v)
1429 struct message *m;
1430 int *msgvec = v, *ip;
1431 NYD_ENTER;
1433 for (ip = msgvec; *ip != 0; ++ip) {
1434 m = message + *ip - 1;
1435 setdot(m);
1436 if (!(m->m_flag & (MANSWER | MANSWERED)))
1437 m->m_flag |= MANSWER | MANSWERED;
1439 NYD_LEAVE;
1440 return 0;
1443 FL int
1444 c_unanswered(void *v)
1446 struct message *m;
1447 int *msgvec = v, *ip;
1448 NYD_ENTER;
1450 for (ip = msgvec; *ip != 0; ++ip) {
1451 m = message + *ip - 1;
1452 setdot(m);
1453 if (m->m_flag & (MANSWER | MANSWERED)) {
1454 m->m_flag &= ~(MANSWER | MANSWERED);
1455 m->m_flag |= MUNANSWER;
1458 NYD_LEAVE;
1459 return 0;
1462 FL int
1463 c_draft(void *v)
1465 struct message *m;
1466 int *msgvec = v, *ip;
1467 NYD_ENTER;
1469 for (ip = msgvec; *ip != 0; ++ip) {
1470 m = message + *ip - 1;
1471 setdot(m);
1472 if (!(m->m_flag & (MDRAFT | MDRAFTED)))
1473 m->m_flag |= MDRAFT | MDRAFTED;
1475 NYD_LEAVE;
1476 return 0;
1479 FL int
1480 c_undraft(void *v)
1482 struct message *m;
1483 int *msgvec = v, *ip;
1484 NYD_ENTER;
1486 for (ip = msgvec; *ip != 0; ++ip) {
1487 m = message + *ip - 1;
1488 setdot(m);
1489 if (m->m_flag & (MDRAFT | MDRAFTED)) {
1490 m->m_flag &= ~(MDRAFT | MDRAFTED);
1491 m->m_flag |= MUNDRAFT;
1494 NYD_LEAVE;
1495 return 0;
1498 FL int
1499 c_noop(void *v)
1501 int rv = 0;
1502 NYD_ENTER;
1503 UNUSED(v);
1505 switch (mb.mb_type) {
1506 case MB_IMAP:
1507 #ifdef HAVE_IMAP
1508 imap_noop();
1509 #else
1510 rv = c_cmdnotsupp(NULL);
1511 #endif
1512 break;
1513 case MB_POP3:
1514 #ifdef HAVE_POP3
1515 pop3_noop();
1516 #else
1517 rv = c_cmdnotsupp(NULL);
1518 #endif
1519 break;
1520 default:
1521 break;
1523 NYD_LEAVE;
1524 return rv;
1527 FL int
1528 c_remove(void *v)
1530 char const *fmt;
1531 size_t fmt_len;
1532 char **args = v, *name;
1533 int ec = 0;
1534 NYD_ENTER;
1536 if (*args == NULL) {
1537 fprintf(stderr, _("Syntax is: remove mailbox ...\n"));
1538 ec = 1;
1539 goto jleave;
1542 fmt = _("Remove \"%s\" (y/n) ? ");
1543 fmt_len = strlen(fmt);
1544 do {
1545 if ((name = expand(*args)) == NULL)
1546 continue;
1548 if (!strcmp(name, mailname)) {
1549 fprintf(stderr, _("Cannot remove current mailbox \"%s\".\n"),
1550 name);
1551 ec |= 1;
1552 continue;
1555 size_t vl = strlen(name) + fmt_len +1;
1556 char *vb = ac_alloc(vl);
1557 bool_t asw;
1558 snprintf(vb, vl, fmt, name);
1559 asw = getapproval(vb, TRU1);
1560 ac_free(vb);
1561 if (!asw)
1562 continue;
1565 switch (which_protocol(name)) {
1566 case PROTO_FILE:
1567 if (unlink(name) == -1) { /* TODO do not handle .gz .bz2 .xz.. */
1568 perror(name);
1569 ec |= 1;
1571 break;
1572 case PROTO_POP3:
1573 fprintf(stderr, _("Cannot remove POP3 mailbox \"%s\".\n"),name);
1574 ec |= 1;
1575 break;
1576 case PROTO_IMAP:
1577 #ifdef HAVE_IMAP
1578 if (imap_remove(name) != OKAY)
1579 #endif
1580 ec |= 1;
1581 break;
1582 case PROTO_MAILDIR:
1583 if (maildir_remove(name) != OKAY)
1584 ec |= 1;
1585 break;
1586 case PROTO_UNKNOWN:
1587 fprintf(stderr, _("Unknown protocol in \"%s\". Not removed.\n"),
1588 name);
1589 ec |= 1;
1590 break;
1592 } while (*++args != NULL);
1593 jleave:
1594 NYD_LEAVE;
1595 return ec;
1598 FL int
1599 c_rename(void *v)
1601 char **args = v, *old, *new;
1602 enum protocol oldp, newp;
1603 int ec;
1604 NYD_ENTER;
1606 ec = 1;
1608 if (args[0] == NULL || args[1] == NULL || args[2] != NULL) {
1609 fprintf(stderr, "Syntax: rename old new\n");
1610 goto jleave;
1613 if ((old = expand(args[0])) == NULL)
1614 goto jleave;
1615 oldp = which_protocol(old);
1616 if ((new = expand(args[1])) == NULL)
1617 goto jleave;
1618 newp = which_protocol(new);
1620 if (!strcmp(old, mailname) || !strcmp(new, mailname)) {
1621 fprintf(stderr, _("Cannot rename current mailbox \"%s\".\n"), old);
1622 goto jleave;
1624 if ((oldp == PROTO_IMAP || newp == PROTO_IMAP) && oldp != newp) {
1625 fprintf(stderr, _("Can only rename folders of same type.\n"));
1626 goto jleave;
1629 ec = 0;
1631 if (newp == PROTO_POP3)
1632 goto jnopop3;
1633 switch (oldp) {
1634 case PROTO_FILE:
1635 if (link(old, new) == -1) {
1636 switch (errno) {
1637 case EACCES:
1638 case EEXIST:
1639 case ENAMETOOLONG:
1640 case ENOENT:
1641 case ENOSPC:
1642 case EXDEV:
1643 perror(new);
1644 break;
1645 default:
1646 perror(old);
1648 ec |= 1;
1649 } else if (unlink(old) == -1) {
1650 perror(old);
1651 ec |= 1;
1653 break;
1654 case PROTO_MAILDIR:
1655 if (rename(old, new) == -1) {
1656 perror(old);
1657 ec |= 1;
1659 break;
1660 case PROTO_POP3:
1661 jnopop3:
1662 fprintf(stderr, _("Cannot rename POP3 mailboxes.\n"));
1663 ec |= 1;
1664 break;
1665 #ifdef HAVE_IMAP
1666 case PROTO_IMAP:
1667 if (imap_rename(old, new) != OKAY)
1668 ec |= 1;
1669 break;
1670 #endif
1671 case PROTO_UNKNOWN:
1672 default:
1673 fprintf(stderr, _(
1674 "Unknown protocol in \"%s\" and \"%s\". Not renamed.\n"), old, new);
1675 ec |= 1;
1676 break;
1678 jleave:
1679 NYD_LEAVE;
1680 return ec;
1683 FL int
1684 c_urlencode(void *v) /* XXX IDNA?? */
1686 char **ap;
1687 NYD_ENTER;
1689 for (ap = v; *ap != NULL; ++ap) {
1690 char *in = *ap, *out = urlxenc(in, FAL0);
1692 printf(" in: <%s> (%" ZFMT " bytes)\nout: <%s> (%" ZFMT " bytes)\n",
1693 in, strlen(in), out, strlen(out));
1695 NYD_LEAVE;
1696 return 0;
1699 FL int
1700 c_urldecode(void *v) /* XXX IDNA?? */
1702 char **ap;
1703 NYD_ENTER;
1705 for (ap = v; *ap != NULL; ++ap) {
1706 char *in = *ap, *out = urlxdec(in);
1708 printf(" in: <%s> (%" ZFMT " bytes)\nout: <%s> (%" ZFMT " bytes)\n",
1709 in, strlen(in), out, strlen(out));
1711 NYD_LEAVE;
1712 return 0;
1715 /* vim:set fenc=utf-8:s-it-mode */