-s, ~s: normalize NL/CR characters (Debian #419840)
[s-mailx.git] / cmd2.c
blobca29b690ba82195d97569395f1f9eba59b151b73
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 - 2016 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
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 n_ignore const *itp, 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
59 save1(char *str, int domark, char const *cmd, struct n_ignore const *itp,
60 int convert, int sender_record, int domove)
62 ui64_t mstats[1], tstats[2];
63 struct stat st;
64 int last = 0, *msgvec, *ip;
65 struct message *mp;
66 char *file = NULL, *cp, *cq;
67 char const *disp = n_empty, *shell = NULL;
68 FILE *obuf;
69 bool_t success = FAL0, isflag;
70 NYD_ENTER;
72 msgvec = salloc((msgCount + 2) * sizeof *msgvec);
73 if (sender_record) {
74 for (cp = str; *cp != '\0' && spacechar(*cp); ++cp)
76 isflag = (*cp != '\0');
77 } else {
78 if ((file = snarf(str, &isflag, convert != SEND_TOFILE)) == NULL)
79 goto jleave;
80 while(spacechar(*file))
81 ++file;
82 if (*file == '|') {
83 ++file;
84 shell = ok_vlook(SHELL);
88 if (!isflag) {
89 *msgvec = first(0, MMNORM);
90 msgvec[1] = 0;
91 } else if (getmsglist(str, msgvec, 0) < 0)
92 goto jleave;
93 if (*msgvec == 0) {
94 if (pstate & (PS_HOOK_MASK | PS_ROBOT)) {
95 success = TRU1;
96 goto jleave;
98 printf(_("No messages to %s.\n"), cmd);
99 goto jleave;
102 if (sender_record) {
103 if ((cp = nameof(message + *msgvec - 1, 0)) == NULL) {
104 printf(_("Cannot determine message sender to %s.\n"), cmd);
105 goto jleave;
108 for (cq = cp; *cq != '\0' && *cq != '@'; cq++)
110 *cq = '\0';
111 if (ok_blook(outfolder)) {
112 size_t sz = strlen(cp) +1;
113 file = salloc(sz + 1);
114 file[0] = '+';
115 memcpy(file + 1, cp, sz);
116 } else
117 file = cp;
120 /* Pipe target is special TODO hacked in later, normalize flow! */
121 if (shell != NULL) {
122 if ((obuf = Popen(file, "w", shell, NULL, 1)) == NULL) {
123 int esave = errno;
125 n_perr(file, esave);
126 errno = esave;
127 goto jleave;
129 isflag = FAL0;
130 disp = _("[Piped]");
131 goto jsend;
134 if ((file = expand(file)) == NULL)
135 goto jleave;
137 obuf = ((convert == SEND_TOFILE) ? Fopen(file, "a+") : Zopen(file, "a+"));
138 if (obuf == NULL) {
139 obuf = ((convert == SEND_TOFILE) ? Fopen(file, "wx") : Zopen(file, "wx"));
140 if (obuf == NULL) {
141 n_perr(file, 0);
142 goto jleave;
144 isflag = TRU1;
145 disp = _("[New file]");
146 } else {
147 isflag = FAL0;
148 disp = _("[Appended]");
151 /* TODO RETURN check, but be aware of protocols: v15: Mailbox->lock()! */
152 n_file_lock(fileno(obuf), FLT_WRITE, 0,0, UIZ_MAX);
154 if (!isflag && !fstat(fileno(obuf), &st) && S_ISREG(st.st_mode) &&
155 fseek(obuf, -2L, SEEK_END) == 0) {
156 char buf[2];
157 int prependnl = 0;
159 switch (fread(buf, sizeof *buf, 2, obuf)) {
160 case 2:
161 if (buf[1] != '\n') {
162 prependnl = 1;
163 break;
165 /* FALLTHRU */
166 case 1:
167 if (buf[0] != '\n')
168 prependnl = 1;
169 break;
170 default:
171 if (ferror(obuf)) {
172 n_perr(file, 0);
173 goto jleave;
175 prependnl = 0;
178 fflush(obuf);
179 if (prependnl) {
180 putc('\n', obuf);
181 fflush(obuf);
185 jsend:
186 success = TRU1;
187 tstats[0] = tstats[1] = 0;
189 srelax_hold();
190 for (ip = msgvec; *ip != 0 && UICMP(z, PTR2SIZE(ip - msgvec), <, msgCount);
191 ++ip) {
192 mp = message + *ip - 1;
193 if (sendmp(mp, obuf, itp, NULL, convert, mstats) < 0) {
194 success = FAL0;
195 goto jferr;
197 srelax();
199 touch(mp);
200 if (domark)
201 mp->m_flag |= MSAVED;
202 if (domove) {
203 mp->m_flag |= MDELETED | MSAVED;
204 last = *ip;
207 tstats[0] += mstats[0];
208 tstats[1] += mp->m_lines;/* TODO won't work, need target! v15!! */
210 srelax_rele();
212 fflush(obuf);
213 if (ferror(obuf)) {
214 jferr:
215 n_perr(file, 0);
216 if (!success)
217 srelax_rele();
218 success = FAL0;
220 if (shell != NULL) {
221 if (!Pclose(obuf, TRU1))
222 success = FAL0;
223 } else if (Fclose(obuf) != 0)
224 success = FAL0;
226 if (success) {
227 printf("%s %s %" /*PRIu64 "/%"*/ PRIu64 " bytes\n",
228 n_shexp_quote_cp(file, FAL0), disp,
229 /*tstats[1], TODO v15: lines written */ tstats[0]);
230 } else if (domark) {
231 for (ip = msgvec; *ip != 0 &&
232 UICMP(z, PTR2SIZE(ip - msgvec), <, msgCount); ++ip) {
233 mp = message + *ip - 1;
234 mp->m_flag &= ~MSAVED;
236 } else if (domove) {
237 for (ip = msgvec; *ip != 0 &&
238 UICMP(z, PTR2SIZE(ip - msgvec), <, msgCount); ++ip) {
239 mp = message + *ip - 1;
240 mp->m_flag &= ~(MSAVED | MDELETED);
244 if (domove && last && success) {
245 setdot(message + last - 1);
246 last = first(0, MDELETED);
247 setdot(message + (last != 0 ? last - 1 : 0));
249 jleave:
250 NYD_LEAVE;
251 return (success == FAL0);
254 static char *
255 snarf(char *linebuf, bool_t *flag, bool_t usembox)
257 char *cp;
258 NYD_ENTER;
260 if ((cp = laststring(linebuf, flag, TRU1)) == NULL) {
261 if (usembox) {
262 *flag = FAL0;
263 cp = expand("&");
264 } else
265 n_err(_("No file specified\n"));
267 NYD_LEAVE;
268 return cp;
271 static int
272 delm(int *msgvec)
274 struct message *mp;
275 int rv = -1, *ip, last;
276 NYD_ENTER;
278 last = 0;
279 for (ip = msgvec; *ip != 0; ++ip) {
280 mp = message + *ip - 1;
281 touch(mp);
282 mp->m_flag |= MDELETED | MTOUCH;
283 mp->m_flag &= ~(MPRESERVE | MSAVED | MBOX);
284 last = *ip;
286 if (last != 0) {
287 setdot(message + last - 1);
288 last = first(0, MDELETED);
289 if (last != 0) {
290 setdot(message + last - 1);
291 rv = 0;
292 } else {
293 setdot(message);
296 NYD_LEAVE;
297 return rv;
300 FL int
301 c_next(void *v)
303 int list[2], *ip, *ip2, mdot, *msgvec = v, rv = 1;
304 struct message *mp;
305 NYD_ENTER;
307 if (*msgvec != 0) {
308 /* If some messages were supplied, find the first applicable one
309 * following dot using wrap around */
310 mdot = (int)PTR2SIZE(dot - message + 1);
312 /* Find first message in supplied message list which follows dot */
313 for (ip = msgvec; *ip != 0; ++ip) {
314 if ((mb.mb_threaded ? message[*ip - 1].m_threadpos > dot->m_threadpos
315 : *ip > mdot))
316 break;
318 if (*ip == 0)
319 ip = msgvec;
320 ip2 = ip;
321 do {
322 mp = message + *ip2 - 1;
323 if (!(mp->m_flag & MMNDEL)) {
324 setdot(mp);
325 goto jhitit;
327 if (*ip2 != 0)
328 ++ip2;
329 if (*ip2 == 0)
330 ip2 = msgvec;
331 } while (ip2 != ip);
332 printf(_("No messages applicable\n"));
333 goto jleave;
336 /* If this is the first command, select message 1. Note that this must
337 * exist for us to get here at all */
338 if (!(pstate & PS_SAW_COMMAND)) {
339 if (msgCount == 0)
340 goto jateof;
341 goto jhitit;
344 /* Just find the next good message after dot, no wraparound */
345 if (mb.mb_threaded == 0) {
346 for (mp = dot + !!(pstate & PS_DID_PRINT_DOT);
347 PTRCMP(mp, <, message + msgCount); ++mp)
348 if (!(mp->m_flag & MMNORM))
349 break;
350 } else {
351 /* TODO The threading code had some bugs that caused crashes.
352 * TODO The last thing (before the deep look) happens here,
353 * TODO so let's not trust PS_DID_PRINT_DOT but check & hope it fixes */
354 if ((mp = dot) != NULL && (pstate & PS_DID_PRINT_DOT))
355 mp = next_in_thread(mp);
356 while (mp != NULL && (mp->m_flag & MMNORM))
357 mp = next_in_thread(mp);
359 if (mp == NULL || PTRCMP(mp, >=, message + msgCount)) {
360 jateof:
361 printf(_("At EOF\n"));
362 rv = 0;
363 goto jleave;
365 setdot(mp);
367 /* Print dot */
368 jhitit:
369 list[0] = (int)PTR2SIZE(dot - message + 1);
370 list[1] = 0;
371 rv = c_type(list);
372 jleave:
373 NYD_LEAVE;
374 return rv;
377 FL int
378 c_dotmove(void *v)
380 char const *args;
381 int msgvec[2], rv;
382 NYD_ENTER;
384 if (*(args = v) == '\0' || args[1] != '\0') {
385 jerr:
386 n_err(_("Synopsis: dotmove: up <-> or down <+> by one message\n"));
387 rv = 1;
388 } else switch (args[0]) {
389 case '-':
390 case '+':
391 if (msgCount == 0) {
392 printf(_("At EOF\n"));
393 rv = 0;
394 } else if (getmsglist(n_UNCONST(/*TODO*/ args), msgvec, 0) > 0) {
395 setdot(message + msgvec[0] - 1);
396 msgvec[1] = 0;
397 rv = c_headers(msgvec);
398 } else
399 rv = 1;
400 break;
401 default:
402 goto jerr;
404 NYD_LEAVE;
405 return rv;
408 FL int
409 c_save(void *v)
411 char *str = v;
412 int rv;
413 NYD_ENTER;
415 rv = save1(str, 1, "save", n_IGNORE_SAVE, SEND_MBOX, 0, 0);
416 NYD_LEAVE;
417 return rv;
420 FL int
421 c_Save(void *v)
423 char *str = v;
424 int rv;
425 NYD_ENTER;
427 rv = save1(str, 1, "save", n_IGNORE_SAVE, SEND_MBOX, 1, 0);
428 NYD_LEAVE;
429 return rv;
432 FL int
433 c_copy(void *v)
435 char *str = v;
436 int rv;
437 NYD_ENTER;
439 rv = save1(str, 0, "copy", n_IGNORE_SAVE, SEND_MBOX, 0, 0);
440 NYD_LEAVE;
441 return rv;
444 FL int
445 c_Copy(void *v)
447 char *str = v;
448 int rv;
449 NYD_ENTER;
451 rv = save1(str, 0, "copy", n_IGNORE_SAVE, SEND_MBOX, 1, 0);
452 NYD_LEAVE;
453 return rv;
456 FL int
457 c_move(void *v)
459 char *str = v;
460 int rv;
461 NYD_ENTER;
463 rv = save1(str, 0, "move", n_IGNORE_SAVE, SEND_MBOX, 0, 1);
464 NYD_LEAVE;
465 return rv;
468 FL int
469 c_Move(void *v)
471 char *str = v;
472 int rv;
473 NYD_ENTER;
475 rv = save1(str, 0, "move", n_IGNORE_SAVE, SEND_MBOX, 1, 1);
476 NYD_LEAVE;
477 return rv;
480 FL int
481 c_decrypt(void *v)
483 char *str = v;
484 int rv;
485 NYD_ENTER;
487 rv = save1(str, 0, "decrypt", n_IGNORE_SAVE, SEND_DECRYPT, 0, 0);
488 NYD_LEAVE;
489 return rv;
492 FL int
493 c_Decrypt(void *v)
495 char *str = v;
496 int rv;
497 NYD_ENTER;
499 rv = save1(str, 0, "decrypt", n_IGNORE_SAVE, SEND_DECRYPT, 1, 0);
500 NYD_LEAVE;
501 return rv;
504 FL int
505 c_write(void *v)
507 char *str = v;
508 int rv;
509 NYD_ENTER;
511 if (str == NULL || *str == '\0')
512 str = savestr("/dev/null");
513 rv = save1(str, 0, "write", n_IGNORE_ALL, SEND_TOFILE, 0, 0);
514 NYD_LEAVE;
515 return rv;
518 FL int
519 c_delete(void *v)
521 int *msgvec = v;
522 NYD_ENTER;
524 delm(msgvec);
525 NYD_LEAVE;
526 return 0;
529 FL int
530 c_deltype(void *v)
532 int list[2], rv = 0, *msgvec = v, lastdot;
533 NYD_ENTER;
535 lastdot = (int)PTR2SIZE(dot - message + 1);
536 if (delm(msgvec) >= 0) {
537 list[0] = (int)PTR2SIZE(dot - message + 1);
538 if (list[0] > lastdot) {
539 touch(dot);
540 list[1] = 0;
541 rv = c_type(list);
542 goto jleave;
544 printf(_("At EOF\n"));
545 } else
546 printf(_("No more messages\n"));
547 jleave:
548 NYD_LEAVE;
549 return rv;
552 FL int
553 c_undelete(void *v)
555 int *msgvec = v, *ip;
556 struct message *mp;
557 NYD_ENTER;
559 for (ip = msgvec; *ip != 0 && UICMP(z, PTR2SIZE(ip - msgvec), <, msgCount);
560 ++ip) {
561 mp = message + *ip - 1;
562 touch(mp);
563 setdot(mp);
564 if (mp->m_flag & (MDELETED | MSAVED))
565 mp->m_flag &= ~(MDELETED | MSAVED);
566 else
567 mp->m_flag &= ~MDELETED;
569 NYD_LEAVE;
570 return 0;
573 FL int
574 c_stouch(void *v)
576 int *msgvec = v, *ip;
577 NYD_ENTER;
579 for (ip = msgvec; *ip != 0; ++ip) {
580 setdot(message + *ip - 1);
581 dot->m_flag |= MTOUCH;
582 dot->m_flag &= ~MPRESERVE;
583 pstate |= PS_DID_PRINT_DOT;
585 NYD_LEAVE;
586 return 0;
589 FL int
590 c_mboxit(void *v)
592 int *msgvec = v, *ip;
593 NYD_ENTER;
595 if (pstate & PS_EDIT) {
596 n_err(_("`mbox' can only be used in a system mailbox\n")); /* TODO */
597 goto jleave;
600 for (ip = msgvec; *ip != 0; ++ip) {
601 setdot(message + *ip - 1);
602 dot->m_flag |= MTOUCH | MBOX;
603 dot->m_flag &= ~MPRESERVE;
604 pstate |= PS_DID_PRINT_DOT;
606 jleave:
607 NYD_LEAVE;
608 return 0;
611 FL int
612 c_preserve(void *v)
614 int *msgvec = v, *ip, mesg, rv = 1;
615 struct message *mp;
616 NYD_ENTER;
618 if (pstate & PS_EDIT) {
619 printf(_("Cannot `preserve' in a system mailbox\n"));
620 goto jleave;
623 for (ip = msgvec; *ip != 0; ++ip) {
624 mesg = *ip;
625 mp = message + mesg - 1;
626 mp->m_flag |= MPRESERVE;
627 mp->m_flag &= ~MBOX;
628 setdot(mp);
629 pstate |= PS_DID_PRINT_DOT;
631 rv = 0;
632 jleave:
633 NYD_LEAVE;
634 return rv;
637 FL int
638 c_unread(void *v)
640 int *msgvec = v, *ip;
641 NYD_ENTER;
643 for (ip = msgvec; *ip != 0; ++ip) {
644 setdot(message + *ip - 1);
645 dot->m_flag &= ~(MREAD | MTOUCH);
646 dot->m_flag |= MSTATUS;
647 pstate |= PS_DID_PRINT_DOT;
649 NYD_LEAVE;
650 return 0;
653 FL int
654 c_seen(void *v)
656 int *msgvec = v, *ip;
657 NYD_ENTER;
659 for (ip = msgvec; *ip != 0; ++ip) {
660 struct message *mp = message + *ip - 1;
661 setdot(mp);
662 touch(mp);
664 NYD_LEAVE;
665 return 0;
668 FL int
669 c_flag(void *v)
671 struct message *m;
672 int *msgvec = v, *ip;
673 NYD_ENTER;
675 for (ip = msgvec; *ip != 0; ++ip) {
676 m = message + *ip - 1;
677 setdot(m);
678 if (!(m->m_flag & (MFLAG | MFLAGGED)))
679 m->m_flag |= MFLAG | MFLAGGED;
681 NYD_LEAVE;
682 return 0;
685 FL int
686 c_unflag(void *v)
688 struct message *m;
689 int *msgvec = v, *ip;
690 NYD_ENTER;
692 for (ip = msgvec; *ip != 0; ++ip) {
693 m = message + *ip - 1;
694 setdot(m);
695 if (m->m_flag & (MFLAG | MFLAGGED)) {
696 m->m_flag &= ~(MFLAG | MFLAGGED);
697 m->m_flag |= MUNFLAG;
700 NYD_LEAVE;
701 return 0;
704 FL int
705 c_answered(void *v)
707 struct message *m;
708 int *msgvec = v, *ip;
709 NYD_ENTER;
711 for (ip = msgvec; *ip != 0; ++ip) {
712 m = message + *ip - 1;
713 setdot(m);
714 if (!(m->m_flag & (MANSWER | MANSWERED)))
715 m->m_flag |= MANSWER | MANSWERED;
717 NYD_LEAVE;
718 return 0;
721 FL int
722 c_unanswered(void *v)
724 struct message *m;
725 int *msgvec = v, *ip;
726 NYD_ENTER;
728 for (ip = msgvec; *ip != 0; ++ip) {
729 m = message + *ip - 1;
730 setdot(m);
731 if (m->m_flag & (MANSWER | MANSWERED)) {
732 m->m_flag &= ~(MANSWER | MANSWERED);
733 m->m_flag |= MUNANSWER;
736 NYD_LEAVE;
737 return 0;
740 FL int
741 c_draft(void *v)
743 struct message *m;
744 int *msgvec = v, *ip;
745 NYD_ENTER;
747 for (ip = msgvec; *ip != 0; ++ip) {
748 m = message + *ip - 1;
749 setdot(m);
750 if (!(m->m_flag & (MDRAFT | MDRAFTED)))
751 m->m_flag |= MDRAFT | MDRAFTED;
753 NYD_LEAVE;
754 return 0;
757 FL int
758 c_undraft(void *v)
760 struct message *m;
761 int *msgvec = v, *ip;
762 NYD_ENTER;
764 for (ip = msgvec; *ip != 0; ++ip) {
765 m = message + *ip - 1;
766 setdot(m);
767 if (m->m_flag & (MDRAFT | MDRAFTED)) {
768 m->m_flag &= ~(MDRAFT | MDRAFTED);
769 m->m_flag |= MUNDRAFT;
772 NYD_LEAVE;
773 return 0;
776 /* s-it-mode */