FixFix IMAP "+" expansion and more (Predrag Punosevac)
[s-mailx.git] / cmd2.c
blob240015a81e41d74edf1769c4a7e91a591fa8e7ad
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ More user commands.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2015 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
6 */
7 /*
8 * Copyright (c) 1980, 1993
9 * The Regents of the University of California. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. Neither the name of the University nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
35 #undef n_FILE
36 #define n_FILE cmd2
38 #ifndef HAVE_AMALGAMATION
39 # include "nail.h"
40 #endif
42 /* Save/copy the indicated messages at the end of the passed file name.
43 * If mark is true, mark the message "saved" */
44 static int save1(char *str, int domark, char const *cmd,
45 struct ignoretab *ignoret, int convert, int sender_record,
46 int domove);
48 /* Snarf the file from the end of the command line and return a pointer to it.
49 * If there is no file attached, return the mbox file. Put a null in front of
50 * the file name so that the message list processing won't see it, unless the
51 * file name is the only thing on the line, in which case, return 0 in the
52 * reference flag variable */
53 static char * snarf(char *linebuf, bool_t *flag, bool_t usembox);
55 /* Delete the indicated messages. Set dot to some nice place afterwards */
56 static int delm(int *msgvec);
58 static int ignore1(char **list, struct ignoretab *tab, char const *which);
60 /* Print out all currently retained fields */
61 static int igshow(struct ignoretab *tab, char const *which);
63 /* Compare two names for sorting ignored field list */
64 static int igcomp(void const *l, void const *r);
66 /* */
67 static int _unignore(char **list, struct ignoretab *tab, char const *which);
68 static void __unign_all(struct ignoretab *tab);
69 static void __unign_one(struct ignoretab *tab, char const *name);
71 static int
72 save1(char *str, int domark, char const *cmd, struct ignoretab *ignoret,
73 int convert, int sender_record, int domove)
75 ui64_t mstats[1], tstats[2];
76 struct stat st;
77 int newfile = 0, last = 0, *msgvec, *ip;
78 struct message *mp;
79 char *file = NULL, *cp, *cq;
80 char const *disp = "";
81 FILE *obuf;
82 enum protocol prot;
83 bool_t success = FAL0, f;
84 NYD_ENTER;
86 msgvec = salloc((msgCount + 2) * sizeof *msgvec);
87 if (sender_record) {
88 for (cp = str; *cp != '\0' && blankchar(*cp); ++cp)
90 f = (*cp != '\0');
91 } else {
92 if ((file = snarf(str, &f, convert != SEND_TOFILE)) == NULL)
93 goto jleave;
96 if (!f) {
97 *msgvec = first(0, MMNORM);
98 msgvec[1] = 0;
99 } else if (getmsglist(str, msgvec, 0) < 0)
100 goto jleave;
101 if (*msgvec == 0) {
102 if (pstate & PS_HOOK_MASK) {
103 success = TRU1;
104 goto jleave;
106 printf(_("No messages to %s.\n"), cmd);
107 goto jleave;
110 if (sender_record) {
111 if ((cp = nameof(message + *msgvec - 1, 0)) == NULL) {
112 printf(_("Cannot determine message sender to %s.\n"), cmd);
113 goto jleave;
116 for (cq = cp; *cq != '\0' && *cq != '@'; cq++)
118 *cq = '\0';
119 if (ok_blook(outfolder)) {
120 size_t sz = strlen(cp) +1;
121 file = salloc(sz + 1);
122 file[0] = '+';
123 memcpy(file + 1, cp, sz);
124 } else
125 file = cp;
128 if ((file = expand(file)) == NULL)
129 goto jleave;
130 prot = which_protocol(file);
131 if (prot != PROTO_IMAP) {
132 if (access(file, F_OK) >= 0) {
133 newfile = 0;
134 disp = _("[Appended]");
135 } else {
136 newfile = 1;
137 disp = _("[New file]");
141 obuf = ((convert == SEND_TOFILE) ? Fopen(file, "a+") : Zopen(file, "a+"));
142 if (obuf == NULL) {
143 obuf = ((convert == SEND_TOFILE) ? Fopen(file, "wx") : Zopen(file, "wx"));
144 if (obuf == NULL) {
145 n_perr(file, 0);
146 goto jleave;
148 } else {
149 if (!newfile && !fstat(fileno(obuf), &st) && S_ISREG(st.st_mode) &&
150 fseek(obuf, -2L, SEEK_END) == 0) {
151 char buf[2];
152 int prependnl = 0;
154 switch (fread(buf, sizeof *buf, 2, obuf)) {
155 case 2:
156 if (buf[1] != '\n') {
157 prependnl = 1;
158 break;
160 /* FALLTHRU */
161 case 1:
162 if (buf[0] != '\n')
163 prependnl = 1;
164 break;
165 default:
166 if (ferror(obuf)) {
167 n_perr(file, 0);
168 goto jleave;
170 prependnl = 0;
173 fflush(obuf);
174 if (prependnl) {
175 putc('\n', obuf);
176 fflush(obuf);
181 success = TRU1;
182 tstats[0] = tstats[1] = 0;
183 imap_created_mailbox = 0;
185 srelax_hold();
186 for (ip = msgvec; *ip != 0 && UICMP(z, PTR2SIZE(ip - msgvec), <, msgCount);
187 ++ip) {
188 mp = message + *ip - 1;
189 if (prot == PROTO_IMAP && ignoret[0].i_count == 0 &&
190 ignoret[1].i_count == 0
191 #ifdef HAVE_IMAP /* TODO revisit */
192 && imap_thisaccount(file)
193 #endif
195 #ifdef HAVE_IMAP
196 if (imap_copy(mp, *ip, file) == STOP)
197 #endif
199 #ifndef HAVE_IMAP
200 # ifdef ENOSYS
201 errno = ENOSYS;
202 # elif defined EOPNOTSUPP
203 errno = EOPNOTSUPP;
204 # else
205 errno = EINVAL;
206 # endif
207 #endif
208 success = FAL0;
209 goto jferr;
211 #ifdef HAVE_IMAP
212 mstats[0] = mp->m_xsize;
213 #endif
214 } else if (sendmp(mp, obuf, ignoret, NULL, convert, mstats) < 0) {
215 success = FAL0;
216 goto jferr;
218 srelax();
220 touch(mp);
221 if (domark)
222 mp->m_flag |= MSAVED;
223 if (domove) {
224 mp->m_flag |= MDELETED | MSAVED;
225 last = *ip;
228 tstats[0] += mstats[0];
229 tstats[1] += mp->m_lines;/* TODO won't work, need target! v15!! */
231 srelax_rele();
233 fflush(obuf);
234 if (ferror(obuf)) {
235 jferr:
236 n_perr(file, 0);
237 if (!success)
238 srelax_rele();
239 success = FAL0;
241 if (Fclose(obuf) != 0)
242 success = FAL0;
244 if (success) {
245 if (prot == PROTO_IMAP || prot == PROTO_MAILDIR) {
246 disp = (
247 #ifdef HAVE_IMAP
248 ((prot == PROTO_IMAP) && disconnected(file)) ? "[Queued]" :
249 #endif
250 (imap_created_mailbox ? "[New file]" : "[Appended]"));
252 printf("\"%s\" %s %" /*PRIu64 "/%"*/ PRIu64 " bytes\n",
253 file, disp, /*tstats[1], TODO v15: lines written */ tstats[0]);
254 } else if (domark) {
255 newfile = ~MSAVED;
256 goto jiterand;
257 } else if (domove) {
258 newfile = ~(MSAVED | MDELETED);
259 jiterand:
260 for (ip = msgvec; *ip != 0 &&
261 UICMP(z, PTR2SIZE(ip - msgvec), <, msgCount); ++ip) {
262 mp = message + *ip - 1;
263 mp->m_flag &= newfile;
267 if (domove && last && success) {
268 setdot(message + last - 1);
269 last = first(0, MDELETED);
270 setdot(message + (last != 0 ? last - 1 : 0));
272 jleave:
273 NYD_LEAVE;
274 return (success == FAL0);
277 static char *
278 snarf(char *linebuf, bool_t *flag, bool_t usembox)
280 char *cp;
281 NYD_ENTER;
283 if ((cp = laststring(linebuf, flag, FAL0)) == NULL) {
284 if (usembox) {
285 *flag = FAL0;
286 cp = expand("&");
287 } else
288 n_err(_("No file specified\n"));
290 NYD_LEAVE;
291 return cp;
294 static int
295 delm(int *msgvec)
297 struct message *mp;
298 int rv = -1, *ip, last;
299 NYD_ENTER;
301 last = 0;
302 for (ip = msgvec; *ip != 0; ++ip) {
303 mp = message + *ip - 1;
304 touch(mp);
305 mp->m_flag |= MDELETED | MTOUCH;
306 mp->m_flag &= ~(MPRESERVE | MSAVED | MBOX);
307 last = *ip;
309 if (last != 0) {
310 setdot(message + last - 1);
311 last = first(0, MDELETED);
312 if (last != 0) {
313 setdot(message + last - 1);
314 rv = 0;
315 } else {
316 setdot(message);
319 NYD_LEAVE;
320 return rv;
323 static int
324 ignore1(char **list, struct ignoretab *tab, char const *which)
326 int h;
327 struct ignored *igp;
328 char **ap;
329 NYD_ENTER;
331 if (*list == NULL) {
332 h = igshow(tab, which);
333 goto jleave;
336 for (ap = list; *ap != 0; ++ap) {
337 char *field;
338 size_t sz;
340 sz = strlen(*ap) +1;
341 field = ac_alloc(sz);
342 i_strcpy(field, *ap, sz);
343 if (member(field, tab))
344 goto jnext;
346 h = hash(field);
347 igp = scalloc(1, sizeof *igp);
348 sz = strlen(field) +1;
349 igp->i_field = smalloc(sz);
350 memcpy(igp->i_field, field, sz);
351 igp->i_link = tab->i_head[h];
352 tab->i_head[h] = igp;
353 ++tab->i_count;
354 jnext:
355 ac_free(field);
357 h = 0;
358 jleave:
359 NYD_LEAVE;
360 return h;
363 static int
364 igshow(struct ignoretab *tab, char const *which)
366 int h;
367 struct ignored *igp;
368 char **ap, **ring;
369 NYD_ENTER;
371 if (tab->i_count == 0) {
372 printf(_("No fields currently being %s.\n"), which);
373 goto jleave;
376 ring = salloc((tab->i_count + 1) * sizeof *ring);
377 ap = ring;
378 for (h = 0; h < HSHSIZE; ++h)
379 for (igp = tab->i_head[h]; igp != 0; igp = igp->i_link)
380 *ap++ = igp->i_field;
381 *ap = 0;
383 qsort(ring, tab->i_count, sizeof *ring, igcomp);
385 for (ap = ring; *ap != NULL; ++ap)
386 printf("%s\n", *ap);
387 jleave:
388 NYD_LEAVE;
389 return 0;
392 static int
393 igcomp(void const *l, void const *r)
395 int rv;
396 NYD_ENTER;
398 rv = strcmp(*(char**)UNCONST(l), *(char**)UNCONST(r));
399 NYD_LEAVE;
400 return rv;
403 static int
404 _unignore(char **list, struct ignoretab *tab, char const *which)
406 char *field;
407 NYD_ENTER;
409 if (tab->i_count == 0)
410 printf(_("No fields currently being %s.\n"), which);
411 else
412 while ((field = *list++) != NULL)
413 if (field[0] == '*' && field[1] == '\0') {
414 __unign_all(tab);
415 break;
416 } else
417 __unign_one(tab, field);
418 NYD_LEAVE;
419 return 0;
422 static void
423 __unign_all(struct ignoretab *tab)
425 size_t i;
426 struct ignored *n, *x;
427 NYD_ENTER;
429 for (i = 0; i < NELEM(tab->i_head); ++i)
430 for (n = tab->i_head[i]; n != NULL; n = x) {
431 x = n->i_link;
432 free(n->i_field);
433 free(n);
435 memset(tab, 0, sizeof *tab);
436 NYD_LEAVE;
439 static void
440 __unign_one(struct ignoretab *tab, char const *name)
442 struct ignored *ip, *iq;
443 int h;
444 NYD_ENTER;
446 h = hash(name);
447 for (iq = NULL, ip = tab->i_head[h]; ip != NULL; ip = ip->i_link) {
448 if (!asccasecmp(ip->i_field, name)) {
449 free(ip->i_field);
450 if (iq != NULL)
451 iq->i_link = ip->i_link;
452 else
453 tab->i_head[h] = ip->i_link;
454 free(ip);
455 --tab->i_count;
456 break;
458 iq = ip;
460 NYD_LEAVE;
463 FL int
464 c_next(void *v)
466 int list[2], *ip, *ip2, mdot, *msgvec = v, rv = 1;
467 struct message *mp;
468 NYD_ENTER;
470 if (*msgvec != 0) {
471 /* If some messages were supplied, find the first applicable one
472 * following dot using wrap around */
473 mdot = (int)PTR2SIZE(dot - message + 1);
475 /* Find first message in supplied message list which follows dot */
476 for (ip = msgvec; *ip != 0; ++ip) {
477 if ((mb.mb_threaded ? message[*ip - 1].m_threadpos > dot->m_threadpos
478 : *ip > mdot))
479 break;
481 if (*ip == 0)
482 ip = msgvec;
483 ip2 = ip;
484 do {
485 mp = message + *ip2 - 1;
486 if (!(mp->m_flag & MMNDEL)) {
487 setdot(mp);
488 goto jhitit;
490 if (*ip2 != 0)
491 ++ip2;
492 if (*ip2 == 0)
493 ip2 = msgvec;
494 } while (ip2 != ip);
495 printf(_("No messages applicable\n"));
496 goto jleave;
499 /* If this is the first command, select message 1. Note that this must
500 * exist for us to get here at all */
501 if (!(pstate & PS_SAW_COMMAND)) {
502 if (msgCount == 0)
503 goto jateof;
504 goto jhitit;
507 /* Just find the next good message after dot, no wraparound */
508 if (mb.mb_threaded == 0) {
509 for (mp = dot + !!(pstate & PS_DID_PRINT_DOT);
510 PTRCMP(mp, <, message + msgCount); ++mp)
511 if (!(mp->m_flag & MMNORM))
512 break;
513 } else {
514 /* TODO The threading code had some bugs that caused crashes.
515 * TODO The last thing (before the deep look) happens here,
516 * TODO so let's not trust PS_DID_PRINT_DOT but check & hope it fixes */
517 if ((mp = dot) != NULL && (pstate & PS_DID_PRINT_DOT))
518 mp = next_in_thread(mp);
519 while (mp != NULL && (mp->m_flag & MMNORM))
520 mp = next_in_thread(mp);
522 if (mp == NULL || PTRCMP(mp, >=, message + msgCount)) {
523 jateof:
524 printf(_("At EOF\n"));
525 rv = 0;
526 goto jleave;
528 setdot(mp);
530 /* Print dot */
531 jhitit:
532 list[0] = (int)PTR2SIZE(dot - message + 1);
533 list[1] = 0;
534 rv = c_type(list);
535 jleave:
536 NYD_LEAVE;
537 return rv;
540 FL int
541 c_save(void *v)
543 char *str = v;
544 int rv;
545 NYD_ENTER;
547 rv = save1(str, 1, "save", saveignore, SEND_MBOX, 0, 0);
548 NYD_LEAVE;
549 return rv;
552 FL int
553 c_Save(void *v)
555 char *str = v;
556 int rv;
557 NYD_ENTER;
559 rv = save1(str, 1, "save", saveignore, SEND_MBOX, 1, 0);
560 NYD_LEAVE;
561 return rv;
564 FL int
565 c_copy(void *v)
567 char *str = v;
568 int rv;
569 NYD_ENTER;
571 rv = save1(str, 0, "copy", saveignore, SEND_MBOX, 0, 0);
572 NYD_LEAVE;
573 return rv;
576 FL int
577 c_Copy(void *v)
579 char *str = v;
580 int rv;
581 NYD_ENTER;
583 rv = save1(str, 0, "copy", saveignore, SEND_MBOX, 1, 0);
584 NYD_LEAVE;
585 return rv;
588 FL int
589 c_move(void *v)
591 char *str = v;
592 int rv;
593 NYD_ENTER;
595 rv = save1(str, 0, "move", saveignore, SEND_MBOX, 0, 1);
596 NYD_LEAVE;
597 return rv;
600 FL int
601 c_Move(void *v)
603 char *str = v;
604 int rv;
605 NYD_ENTER;
607 rv = save1(str, 0, "move", saveignore, SEND_MBOX, 1, 1);
608 NYD_LEAVE;
609 return rv;
612 FL int
613 c_decrypt(void *v)
615 char *str = v;
616 int rv;
617 NYD_ENTER;
619 rv = save1(str, 0, "decrypt", saveignore, SEND_DECRYPT, 0, 0);
620 NYD_LEAVE;
621 return rv;
624 FL int
625 c_Decrypt(void *v)
627 char *str = v;
628 int rv;
629 NYD_ENTER;
631 rv = save1(str, 0, "decrypt", saveignore, SEND_DECRYPT, 1, 0);
632 NYD_LEAVE;
633 return rv;
636 FL int
637 c_write(void *v)
639 char *str = v;
640 int rv;
641 NYD_ENTER;
643 if (str == NULL || *str == '\0')
644 str = savestr("/dev/null");
645 rv = save1(str, 0, "write", allignore, SEND_TOFILE, 0, 0);
646 NYD_LEAVE;
647 return rv;
650 FL int
651 c_delete(void *v)
653 int *msgvec = v;
654 NYD_ENTER;
656 delm(msgvec);
657 NYD_LEAVE;
658 return 0;
661 FL int
662 c_deltype(void *v)
664 int list[2], rv = 0, *msgvec = v, lastdot;
665 NYD_ENTER;
667 lastdot = (int)PTR2SIZE(dot - message + 1);
668 if (delm(msgvec) >= 0) {
669 list[0] = (int)PTR2SIZE(dot - message + 1);
670 if (list[0] > lastdot) {
671 touch(dot);
672 list[1] = 0;
673 rv = c_type(list);
674 goto jleave;
676 printf(_("At EOF\n"));
677 } else
678 printf(_("No more messages\n"));
679 jleave:
680 NYD_LEAVE;
681 return rv;
684 FL int
685 c_undelete(void *v)
687 int *msgvec = v, *ip;
688 struct message *mp;
689 NYD_ENTER;
691 for (ip = msgvec; *ip != 0 && UICMP(z, PTR2SIZE(ip - msgvec), <, msgCount);
692 ++ip) {
693 mp = message + *ip - 1;
694 touch(mp);
695 setdot(mp);
696 if (mp->m_flag & (MDELETED | MSAVED))
697 mp->m_flag &= ~(MDELETED | MSAVED);
698 else
699 mp->m_flag &= ~MDELETED;
700 #ifdef HAVE_IMAP
701 if (mb.mb_type == MB_IMAP || mb.mb_type == MB_CACHE)
702 imap_undelete(mp, *ip);
703 #endif
705 NYD_LEAVE;
706 return 0;
709 FL int
710 c_retfield(void *v)
712 char **list = v;
713 int rv;
714 NYD_ENTER;
716 rv = ignore1(list, ignore + 1, "retained");
717 NYD_LEAVE;
718 return rv;
721 FL int
722 c_igfield(void *v)
724 char **list = v;
725 int rv;
726 NYD_ENTER;
728 rv = ignore1(list, ignore, "ignored");
729 NYD_LEAVE;
730 return rv;
733 FL int
734 c_saveretfield(void *v)
736 char **list = v;
737 int rv;
738 NYD_ENTER;
740 rv = ignore1(list, saveignore + 1, "retained");
741 NYD_LEAVE;
742 return rv;
745 FL int
746 c_saveigfield(void *v)
748 char **list = v;
749 int rv;
750 NYD_ENTER;
752 rv = ignore1(list, saveignore, "ignored");
753 NYD_LEAVE;
754 return rv;
757 FL int
758 c_fwdretfield(void *v)
760 char **list = v;
761 int rv;
762 NYD_ENTER;
764 rv = ignore1(list, fwdignore + 1, "retained");
765 NYD_LEAVE;
766 return rv;
769 FL int
770 c_fwdigfield(void *v)
772 char **list = v;
773 int rv;
774 NYD_ENTER;
776 rv = ignore1(list, fwdignore, "ignored");
777 NYD_LEAVE;
778 return rv;
781 FL int
782 c_unignore(void *v)
784 int rv;
785 NYD_ENTER;
787 rv = _unignore((char**)v, ignore, "ignored");
788 NYD_LEAVE;
789 return rv;
792 FL int
793 c_unretain(void *v)
795 int rv;
796 NYD_ENTER;
798 rv = _unignore((char**)v, ignore + 1, "retained");
799 NYD_LEAVE;
800 return rv;
803 FL int
804 c_unsaveignore(void *v)
806 int rv;
807 NYD_ENTER;
809 rv = _unignore((char**)v, saveignore, "ignored");
810 NYD_LEAVE;
811 return rv;
814 FL int
815 c_unsaveretain(void *v)
817 int rv;
818 NYD_ENTER;
820 rv = _unignore((char**)v, saveignore + 1, "retained");
821 NYD_LEAVE;
822 return rv;
825 FL int
826 c_unfwdignore(void *v)
828 int rv;
829 NYD_ENTER;
831 rv = _unignore((char**)v, fwdignore, "ignored");
832 NYD_LEAVE;
833 return rv;
836 FL int
837 c_unfwdretain(void *v)
839 int rv;
840 NYD_ENTER;
842 rv = _unignore((char**)v, fwdignore + 1, "retained");
843 NYD_LEAVE;
844 return rv;
847 /* s-it-mode */