INSTALL: more details on s-nail.dat creation (Bob Tennent)
[s-mailx.git] / collect.c
blob53adc4d35a73113ea448cd53385d1f058c892108
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Collect input from standard input, handling ~ escapes.
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. 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.
39 #undef n_FILE
40 #define n_FILE collect
42 #ifndef HAVE_AMALGAMATION
43 # include "nail.h"
44 #endif
46 /* The following hookiness with global variables is so that on receipt of an
47 * interrupt signal, the partial message can be salted away on *DEAD* */
49 static sighandler_type _coll_saveint; /* Previous SIGINT value */
50 static sighandler_type _coll_savehup; /* Previous SIGHUP value */
51 static sighandler_type _coll_savetstp; /* Previous SIGTSTP value */
52 static sighandler_type _coll_savettou; /* Previous SIGTTOU value */
53 static sighandler_type _coll_savettin; /* Previous SIGTTIN value */
54 static FILE *_coll_fp; /* File for saving away */
55 static int volatile _coll_hadintr; /* Have seen one SIGINT so far */
56 static sigjmp_buf _coll_jmp; /* To get back to work */
57 static int _coll_jmp_p; /* whether to long jump */
58 static sigjmp_buf _coll_abort; /* To end collection with error */
59 static sigjmp_buf _coll_pipejmp; /* On broken pipe */
61 /* Handle `~:', `~_' */
62 static void _execute_command(struct header *hp, char *linebuf,
63 size_t linesize);
65 /* If *interactive* is set and *doecho* is, too, also dump to *stdout* */
66 static int _include_file(char const *name, int *linecount,
67 int *charcount, bool_t doecho, bool_t indent);
69 static void _collect_onpipe(int signo);
71 /* Execute cmd and insert its standard output into fp */
72 static void insertcommand(FILE *fp, char const *cmd);
74 /* ~p command */
75 static void print_collf(FILE *collf, struct header *hp);
77 /* Write a file, ex-like if f set */
78 static int exwrite(char const *name, FILE *fp, int f);
80 static enum okay makeheader(FILE *fp, struct header *hp);
82 /* Edit the message being collected on fp. On return, make the edit file the
83 * new temp file */
84 static void mesedit(int c, struct header *hp);
86 /* Pipe the message through the command. Old message is on stdin of command,
87 * new message collected from stdout. Shell must return 0 to accept new msg */
88 static void mespipe(char *cmd);
90 /* Interpolate the named messages into the current message, possibly doing
91 * indent stuff. The flag argument is one of the tilde escapes: [mMfFuU].
92 * Return a count of the number of characters now in the message, or -1 if an
93 * error is encountered writing the message temporary */
94 static int forward(char *ms, FILE *fp, int f);
96 /* Print (continue) when continued after ^Z */
97 static void collstop(int s);
99 /* On interrupt, come here to save the partial message in ~/dead.letter.
100 * Then jump out of the collection loop */
101 static void _collint(int s);
103 static void collhup(int s);
105 static int putesc(char const *s, FILE *stream);
107 static void
108 _execute_command(struct header *hp, char *linebuf, size_t linesize)
110 /* The problem arises if there are rfc822 message attachments and the
111 * user uses `~:' to change the current file. TODO Unfortunately we
112 * TODO cannot simply keep a pointer to, or increment a reference count
113 * TODO of the current `file' (mailbox that is) object, because the
114 * TODO codebase doesn't deal with that at all; so, until some far
115 * TODO later time, copy the name of the path, and warn the user if it
116 * TODO changed; we COULD use the AC_TMPFILE attachment type, i.e.,
117 * TODO copy the message attachments over to temporary files, but that
118 * TODO would require more changes so that the user still can recognize
119 * TODO in `~@' etc. that its a rfc822 message attachment; see below */
120 char *mnbuf = NULL;
121 size_t mnlen = 0 /* silence CC */;
122 struct attachment *ap;
123 NYD_ENTER;
125 /* If the above todo is worked, remove or outsource to attachments.c! */
126 if ((ap = hp->h_attach) != NULL) do
127 if (ap->a_msgno) {
128 mnlen = strlen(mailname) +1;
129 mnbuf = ac_alloc(mnlen);
130 memcpy(mnbuf, mailname, mnlen);
131 break;
133 while ((ap = ap->a_flink) != NULL);
135 pstate &= ~PS_HOOK_MASK;
136 execute(linebuf, TRU1, linesize);
138 if (mnbuf != NULL) {
139 if (strncmp(mnbuf, mailname, mnlen))
140 fputs(_("Mailbox changed: it seems existing rfc822 attachments "
141 "became invalid!\n"), stderr);
142 ac_free(mnbuf);
144 NYD_LEAVE;
147 static int
148 _include_file(char const *name, int *linecount, int *charcount,
149 bool_t doecho, bool_t indent)
151 FILE *fbuf;
152 char const *indb;
153 int ret = -1;
154 char *linebuf = NULL; /* TODO line pool */
155 size_t linesize = 0, indl, linelen, cnt;
156 NYD_ENTER;
158 if ((fbuf = Fopen(name, "r")) == NULL) {
159 perror(name);
160 goto jleave;
163 if (!indent)
164 indb = NULL, indl = 0;
165 else {
166 if ((indb = ok_vlook(indentprefix)) == NULL)
167 indb = INDENT_DEFAULT;
168 indl = strlen(indb);
171 *linecount = *charcount = 0;
172 cnt = fsize(fbuf);
173 while (fgetline(&linebuf, &linesize, &cnt, &linelen, fbuf, 0) != NULL) {
174 if (indl > 0 && fwrite(indb, sizeof *indb, indl, _coll_fp) != indl)
175 goto jleave;
176 if (fwrite(linebuf, sizeof *linebuf, linelen, _coll_fp) != linelen)
177 goto jleave;
178 ++(*linecount);
179 (*charcount) += linelen + indl;
180 if ((options & OPT_INTERACTIVE) && doecho) {
181 if (indl > 0)
182 fwrite(indb, sizeof *indb, indl, stdout);
183 fwrite(linebuf, sizeof *linebuf, linelen, stdout);
186 if (fflush(_coll_fp))
187 goto jleave;
188 if ((options & OPT_INTERACTIVE) && doecho)
189 fflush(stdout);
191 ret = 0;
192 jleave:
193 if (linebuf != NULL)
194 free(linebuf);
195 if (fbuf != NULL)
196 Fclose(fbuf);
197 NYD_LEAVE;
198 return ret;
201 static void
202 _collect_onpipe(int signo)
204 NYD_X; /* Signal handler */
205 UNUSED(signo);
206 siglongjmp(_coll_pipejmp, 1);
209 static void
210 insertcommand(FILE *fp, char const *cmd)
212 FILE *ibuf = NULL;
213 char const *cp;
214 int c;
215 NYD_ENTER;
217 cp = ok_vlook(SHELL);
218 if (cp == NULL)
219 cp = XSHELL;
221 if ((ibuf = Popen(cmd, "r", cp, NULL, 0)) != NULL) {
222 while ((c = getc(ibuf)) != EOF) /* XXX bytewise, yuck! */
223 putc(c, fp);
224 Pclose(ibuf, TRU1);
225 } else
226 perror(cmd);
227 NYD_LEAVE;
230 static void
231 print_collf(FILE *cf, struct header *hp)
233 char *lbuf = NULL; /* TODO line pool */
234 sighandler_type sigint;
235 FILE *volatile obuf = stdout;
236 struct attachment *ap;
237 char const *cp;
238 enum gfield gf;
239 size_t linesize = 0, linelen, cnt, cnt2;
240 NYD_ENTER;
242 fflush_rewind(cf);
243 cnt = cnt2 = fsize(cf);
245 sigint = safe_signal(SIGINT, SIG_IGN);
247 if (IS_TTY_SESSION() && (cp = ok_vlook(crt)) != NULL) {
248 size_t l, m;
250 m = 4;
251 if (hp->h_to != NULL)
252 ++m;
253 if (hp->h_subject != NULL)
254 ++m;
255 if (hp->h_cc != NULL)
256 ++m;
257 if (hp->h_bcc != NULL)
258 ++m;
259 if (hp->h_attach != NULL)
260 ++m;
261 m += (hp->h_from != NULL || myaddrs(hp) != NULL);
262 m += (hp->h_sender != NULL || ok_vlook(sender) != NULL);
263 m += (hp->h_replyto != NULL || ok_vlook(replyto) != NULL);
264 m += (hp->h_organization != NULL || ok_vlook(ORGANIZATION) != NULL);
266 l = (*cp == '\0') ? screensize() : atoi(cp);
267 if (m > l)
268 goto jpager;
269 l -= m;
271 for (m = 0; fgetline(&lbuf, &linesize, &cnt2, NULL, cf, 0); ++m)
273 rewind(cf);
274 if (l < m) {
275 jpager:
276 cp = get_pager(NULL);
277 if (sigsetjmp(_coll_pipejmp, 1))
278 goto jendpipe;
279 obuf = Popen(cp, "w", NULL, NULL, 1);
280 if (obuf == NULL) {
281 perror(cp);
282 obuf = stdout;
283 } else
284 safe_signal(SIGPIPE, &_collect_onpipe);
288 fprintf(obuf, _("-------\nMessage contains:\n"));
289 gf = GIDENT | GTO | GSUBJECT | GCC | GBCC | GNL | GFILES | GCOMMA;
290 puthead(hp, obuf, gf, SEND_TODISP, CONV_NONE, NULL, NULL);
291 while (fgetline(&lbuf, &linesize, &cnt, &linelen, cf, 1))
292 prout(lbuf, linelen, obuf);
293 if (hp->h_attach != NULL) {
294 fputs(_("-------\nAttachments:\n"), obuf);
295 for (ap = hp->h_attach; ap != NULL; ap = ap->a_flink) {
296 if (ap->a_msgno)
297 fprintf(obuf, " - message %u\n", ap->a_msgno);
298 else {
299 /* TODO after MIME/send layer rewrite we *know*
300 * TODO the details of the attachment here,
301 * TODO so adjust this again, then */
302 char const *cs, *csi = "-> ";
304 if ((cs = ap->a_charset) == NULL &&
305 (csi = "<- ", cs = ap->a_input_charset) == NULL)
306 cs = charset_get_lc();
307 if ((cp = ap->a_content_type) == NULL)
308 cp = "?";
309 else if (ascncasecmp(cp, "text/", 5))
310 csi = "";
311 fprintf(obuf, " - [%s, %s%s] %s\n", cp, csi, cs, ap->a_name);
316 jendpipe:
317 if (obuf != stdout) {
318 safe_signal(SIGPIPE, SIG_IGN);
319 Pclose(obuf, TRU1);
320 safe_signal(SIGPIPE, dflpipe);
322 if (lbuf != NULL)
323 free(lbuf);
324 safe_signal(SIGINT, sigint);
325 NYD_LEAVE;
328 static int
329 exwrite(char const *name, FILE *fp, int f)
331 FILE *of;
332 int c, lc, rv = -1;
333 long cc;
334 NYD_ENTER;
336 if (f) {
337 printf("\"%s\" ", name);
338 fflush(stdout);
340 if ((of = Fopen(name, "a")) == NULL) {
341 perror(NULL);
342 goto jleave;
345 lc = 0;
346 cc = 0;
347 while ((c = getc(fp)) != EOF) {
348 ++cc;
349 if (c == '\n')
350 ++lc;
351 putc(c, of);
352 if (ferror(of)) {
353 perror(name);
354 Fclose(of);
355 goto jleave;
358 Fclose(of);
359 printf(_("%d/%ld\n"), lc, cc);
360 fflush(stdout);
361 rv = 0;
362 jleave:
363 NYD_LEAVE;
364 return rv;
367 static enum okay
368 makeheader(FILE *fp, struct header *hp)
370 FILE *nf;
371 int c;
372 enum okay rv = STOP;
373 NYD_ENTER;
375 if ((nf = Ftmp(NULL, "colhead", OF_RDWR | OF_UNLINK | OF_REGISTER, 0600)) ==
376 NULL) {
377 perror(_("temporary mail edit file"));
378 goto jleave;
381 extract_header(fp, hp);
382 while ((c = getc(fp)) != EOF) /* XXX bytewise, yuck! */
383 putc(c, nf);
384 if (fp != _coll_fp)
385 Fclose(_coll_fp);
386 Fclose(fp);
387 _coll_fp = nf;
388 if (check_from_and_sender(hp->h_from, hp->h_sender) == NULL)
389 goto jleave;
390 rv = OKAY;
391 jleave:
392 NYD_LEAVE;
393 return rv;
396 static void
397 mesedit(int c, struct header *hp)
399 bool_t saved;
400 sighandler_type sigint;
401 FILE *nf;
402 NYD_ENTER;
404 saved = ok_blook(add_file_recipients);
405 ok_bset(add_file_recipients, TRU1);
407 sigint = safe_signal(SIGINT, SIG_IGN);
408 nf = run_editor(_coll_fp, (off_t)-1, c, 0, hp, NULL, SEND_MBOX, sigint);
409 if (nf != NULL) {
410 if (hp) {
411 rewind(nf);
412 makeheader(nf, hp);
413 } else {
414 fseek(nf, 0L, SEEK_END);
415 Fclose(_coll_fp);
416 _coll_fp = nf;
419 safe_signal(SIGINT, sigint);
421 ok_bset(add_file_recipients, saved);
422 NYD_LEAVE;
425 static void
426 mespipe(char *cmd)
428 FILE *nf;
429 sighandler_type sigint;
430 char const *sh;
431 NYD_ENTER;
433 sigint = safe_signal(SIGINT, SIG_IGN);
435 if ((nf = Ftmp(NULL, "colpipe", OF_RDWR | OF_UNLINK | OF_REGISTER, 0600)) ==
436 NULL) {
437 perror(_("temporary mail edit file"));
438 goto jout;
441 /* stdin = current message. stdout = new message */
442 if ((sh = ok_vlook(SHELL)) == NULL)
443 sh = XSHELL;
444 fflush(_coll_fp);
445 if (run_command(sh, 0, fileno(_coll_fp), fileno(nf), "-c", cmd, NULL) < 0) {
446 Fclose(nf);
447 goto jout;
450 if (fsize(nf) == 0) {
451 fprintf(stderr, _("No bytes from \"%s\" !?\n"), cmd);
452 Fclose(nf);
453 goto jout;
456 /* Take new files */
457 fseek(nf, 0L, SEEK_END);
458 Fclose(_coll_fp);
459 _coll_fp = nf;
460 jout:
461 safe_signal(SIGINT, sigint);
462 NYD_LEAVE;
465 static int
466 forward(char *ms, FILE *fp, int f)
468 int *msgvec, rv = 0;
469 struct ignoretab *ig;
470 char const *tabst;
471 enum sendaction action;
472 NYD_ENTER;
474 msgvec = salloc((size_t)(msgCount + 1) * sizeof *msgvec);
475 if (getmsglist(ms, msgvec, 0) < 0)
476 goto jleave;
477 if (*msgvec == 0) {
478 *msgvec = first(0, MMNORM);
479 if (*msgvec == 0) {
480 fputs(_("No appropriate messages\n"), stderr);
481 goto jleave;
483 msgvec[1] = 0;
486 if (f == 'f' || f == 'F' || f == 'u')
487 tabst = NULL;
488 else if ((tabst = ok_vlook(indentprefix)) == NULL)
489 tabst = INDENT_DEFAULT;
490 if (f == 'u' || f == 'U')
491 ig = allignore;
492 else
493 ig = upperchar(f) ? NULL : ignore;
494 action = (upperchar(f) && f != 'U') ? SEND_QUOTE_ALL : SEND_QUOTE;
496 printf(_("Interpolating:"));
497 for (; *msgvec != 0; ++msgvec) {
498 struct message *mp = message + *msgvec - 1;
500 touch(mp);
501 printf(" %d", *msgvec);
502 fflush(stdout);
503 if (sendmp(mp, fp, ig, tabst, action, NULL) < 0) {
504 perror(_("temporary mail file"));
505 rv = -1;
506 break;
509 printf("\n");
510 jleave:
511 NYD_LEAVE;
512 return rv;
515 static void
516 collstop(int s)
518 sighandler_type old_action;
519 sigset_t nset;
520 NYD_X; /* Signal handler */
522 old_action = safe_signal(s, SIG_DFL);
524 sigemptyset(&nset);
525 sigaddset(&nset, s);
526 sigprocmask(SIG_UNBLOCK, &nset, NULL);
527 n_raise(s);
528 sigprocmask(SIG_BLOCK, &nset, NULL);
530 safe_signal(s, old_action);
531 if (_coll_jmp_p) {
532 _coll_jmp_p = 0;
533 _coll_hadintr = 0;
534 siglongjmp(_coll_jmp, 1);
538 static void
539 _collint(int s)
541 NYD_X; /* Signal handler */
543 /* the control flow is subtle, because we can be called from ~q */
544 if (_coll_hadintr == 0) {
545 if (ok_blook(ignore)) {
546 puts("@");
547 fflush(stdout);
548 clearerr(stdin);
549 } else
550 _coll_hadintr = 1;
551 siglongjmp(_coll_jmp, 1);
553 exit_status |= EXIT_SEND_ERROR;
554 if (ok_blook(save) && s != 0)
555 savedeadletter(_coll_fp, 1);
556 /* Aborting message, no need to fflush() .. */
557 siglongjmp(_coll_abort, 1);
560 static void
561 collhup(int s)
563 NYD_X; /* Signal handler */
564 UNUSED(s);
566 savedeadletter(_coll_fp, 1);
567 /* Let's pretend nobody else wants to clean up, a true statement at
568 * this time */
569 exit(1);
572 static int
573 putesc(char const *s, FILE *stream)
575 int n = 0, rv = -1;
576 NYD_ENTER;
578 while (s[0] != '\0') {
579 if (s[0] == '\\') {
580 if (s[1] == 't') {
581 if (putc('\t', stream) == EOF)
582 goto jleave;
583 ++n;
584 s += 2;
585 continue;
587 if (s[1] == 'n') {
588 if (putc('\n', stream) == EOF)
589 goto jleave;
590 ++n;
591 s += 2;
592 continue;
595 if (putc(s[0], stream) == EOF)
596 goto jleave;
597 ++n;
598 ++s;
600 if (putc('\n', stream) == EOF)
601 goto jleave;
602 rv = ++n;
603 jleave:
604 NYD_LEAVE;
605 return rv;
608 FL FILE *
609 collect(struct header *hp, int printheaders, struct message *mp,
610 char *quotefile, int doprefix)
612 struct ignoretab *quoteig;
613 int lc, cc, c, t;
614 int volatile escape, getfields;
615 char *linebuf = NULL, *quote = NULL;
616 char const *cp;
617 size_t linesize = 0; /* TODO line pool */
618 long cnt;
619 enum sendaction action;
620 sigset_t oset, nset;
621 sighandler_type savedtop;
622 NYD_ENTER;
624 _coll_fp = NULL;
625 /* Start catching signals from here, but we're still die on interrupts
626 * until we're in the main loop */
627 sigemptyset(&nset);
628 sigaddset(&nset, SIGINT);
629 sigaddset(&nset, SIGHUP);
630 sigprocmask(SIG_BLOCK, &nset, &oset);
631 handlerpush(&_collint);
632 if ((_coll_saveint = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN)
633 safe_signal(SIGINT, &_collint);
634 if ((_coll_savehup = safe_signal(SIGHUP, SIG_IGN)) != SIG_IGN)
635 safe_signal(SIGHUP, collhup);
636 /* TODO We do a lot of redundant signal handling, especially
637 * TODO with the command line editor(s); try to merge this */
638 _coll_savetstp = safe_signal(SIGTSTP, collstop);
639 _coll_savettou = safe_signal(SIGTTOU, collstop);
640 _coll_savettin = safe_signal(SIGTTIN, collstop);
641 if (sigsetjmp(_coll_abort, 1))
642 goto jerr;
643 if (sigsetjmp(_coll_jmp, 1))
644 goto jerr;
645 sigprocmask(SIG_SETMASK, &oset, (sigset_t*)NULL);
647 ++noreset;
648 if ((_coll_fp = Ftmp(NULL, "collect", OF_RDWR | OF_UNLINK | OF_REGISTER,
649 0600)) == NULL) {
650 perror(_("temporary mail file"));
651 goto jerr;
654 if ((cp = ok_vlook(NAIL_HEAD)) != NULL && putesc(cp, _coll_fp) < 0)
655 goto jerr;
657 /* If we are going to prompt for a subject, refrain from printing a newline
658 * after the headers (since some people mind) */
659 getfields = 0;
660 if (!(options & OPT_t_FLAG)) {
661 t = GTO | GSUBJECT | GCC | GNL;
662 if (ok_blook(fullnames))
663 t |= GCOMMA;
665 if (options & OPT_INTERACTIVE) {
666 if (hp->h_subject == NULL && (ok_blook(ask) || ok_blook(asksub)))
667 t &= ~GNL, getfields |= GSUBJECT;
669 if (hp->h_to == NULL)
670 t &= ~GNL, getfields |= GTO;
672 if (!ok_blook(bsdcompat) && !ok_blook(askatend)) {
673 if (hp->h_bcc == NULL && ok_blook(askbcc))
674 t &= ~GNL, getfields |= GBCC;
675 if (hp->h_cc == NULL && ok_blook(askcc))
676 t &= ~GNL, getfields |= GCC;
680 if (printheaders && !ok_blook(editalong)) {
681 puthead(hp, stdout, t, SEND_TODISP, CONV_NONE, NULL, NULL);
682 fflush(stdout);
686 /* Quote an original message */
687 if (mp != NULL && (doprefix || (quote = ok_vlook(quote)) != NULL)) {
688 quoteig = allignore;
689 action = SEND_QUOTE;
690 if (doprefix) {
691 quoteig = fwdignore;
692 if ((cp = ok_vlook(fwdheading)) == NULL)
693 cp = "-------- Original Message --------";
694 if (*cp != '\0' && fprintf(_coll_fp, "%s\n", cp) < 0)
695 goto jerr;
696 } else if (!strcmp(quote, "noheading")) {
697 /*EMPTY*/;
698 } else if (!strcmp(quote, "headers")) {
699 quoteig = ignore;
700 } else if (!strcmp(quote, "allheaders")) {
701 quoteig = NULL;
702 action = SEND_QUOTE_ALL;
703 } else {
704 cp = hfield1("from", mp);
705 if (cp != NULL && (cnt = (long)strlen(cp)) > 0) {
706 if (xmime_write(cp, cnt, _coll_fp, CONV_FROMHDR, TD_NONE) < 0)
707 goto jerr;
708 if (fprintf(_coll_fp, _(" wrote:\n\n")) < 0)
709 goto jerr;
712 if (fflush(_coll_fp))
713 goto jerr;
714 if (doprefix)
715 cp = NULL;
716 else if ((cp = ok_vlook(indentprefix)) == NULL)
717 cp = INDENT_DEFAULT;
718 if (sendmp(mp, _coll_fp, quoteig, cp, action, NULL) < 0)
719 goto jerr;
722 /* Print what we have sofar also on the terminal (if useful) */
723 if ((options & OPT_INTERACTIVE) && !ok_blook(editalong)) {
724 rewind(_coll_fp);
725 while ((c = getc(_coll_fp)) != EOF) /* XXX bytewise, yuck! */
726 putc(c, stdout);
727 if (fseek(_coll_fp, 0, SEEK_END))
728 goto jerr;
729 /* Ensure this is clean xxx not really necessary? */
730 fflush(stdout);
733 escape = ((cp = ok_vlook(escape)) != NULL) ? *cp : ESCAPE;
734 _coll_hadintr = 0;
736 if (!sigsetjmp(_coll_jmp, 1)) {
737 if (getfields)
738 grab_headers(hp, getfields, 1);
739 if (quotefile != NULL) {
740 if (_include_file(quotefile, &lc, &cc, TRU1, FAL0) != 0)
741 goto jerr;
743 if ((options & OPT_INTERACTIVE) && ok_blook(editalong)) {
744 rewind(_coll_fp);
745 mesedit('e', hp);
746 goto jcont;
748 } else {
749 /* Come here for printing the after-signal message. Duplicate messages
750 * won't be printed because the write is aborted if we get a SIGTTOU */
751 if (_coll_hadintr) {
752 fprintf(stderr, _("\n(Interrupt -- one more to kill letter)\n"));
753 } else {
754 jcont:
755 printf(_("(continue)\n"));
756 fflush(stdout);
760 /* No tilde escapes, interrupts not expected. Simply copy STDIN */
761 if (!(options & (OPT_INTERACTIVE | OPT_t_FLAG | OPT_TILDE_FLAG))) {
762 linebuf = srealloc(linebuf, linesize = LINESIZE);
763 while ((cnt = fread(linebuf, sizeof *linebuf, linesize, stdin)) > 0) {
764 if ((size_t)cnt != fwrite(linebuf, sizeof *linebuf, cnt, _coll_fp))
765 goto jerr;
767 if (fflush(_coll_fp))
768 goto jerr;
769 goto jout;
772 /* The interactive collect loop.
773 * All commands which come here are forbidden when sourcing! */
774 assert(_coll_hadintr || !(pstate & PS_SOURCING));
775 for (;;) {
776 _coll_jmp_p = 1;
777 cnt = readline_input("", FAL0, &linebuf, &linesize, NULL);
778 _coll_jmp_p = 0;
780 if (cnt < 0) {
781 /* Since readline_input() transparently switches to `source'd files
782 * and `~:source FILE' may enter this, ensure we quit again! */
783 if (pstate & PS_SOURCING) {
784 unstack();
785 continue;
787 if ((options & OPT_INTERACTIVE) && ok_blook(ignoreeof)) {
788 printf(_("*ignoreeof* set, use \".\" to terminate letter\n"));
789 continue;
791 break;
793 if ((options & OPT_t_FLAG) && cnt == 0) {
794 rewind(_coll_fp);
795 if (makeheader(_coll_fp, hp) != OKAY)
796 goto jerr;
797 rewind(_coll_fp);
798 options &= ~OPT_t_FLAG;
799 continue;
802 _coll_hadintr = 0;
803 if (linebuf[0] == '.' && linebuf[1] == '\0' &&
804 (options & (OPT_INTERACTIVE | OPT_TILDE_FLAG)) &&
805 (ok_blook(dot) || ok_blook(ignoreeof)))
806 break;
807 if (cnt == 0 || linebuf[0] != escape ||
808 !(options & (OPT_INTERACTIVE | OPT_TILDE_FLAG))) {
809 /* TODO calls putline(), which *always* appends LF;
810 * TODO thus, STDIN with -t will ALWAYS end with LF,
811 * TODO even if no trailing LF and QP encoding.
812 * TODO when finally changed, update cc-test.sh */
813 if (putline(_coll_fp, linebuf, cnt) < 0)
814 goto jerr;
815 continue;
818 tty_addhist(linebuf, TRU1);
820 c = linebuf[1];
821 switch (c) {
822 default:
823 /* On double escape, send a single one. Otherwise, it's an error */
824 if (c == escape) {
825 if (putline(_coll_fp, linebuf + 1, cnt - 1) < 0)
826 goto jerr;
827 else
828 break;
830 fputs(_("Unknown tilde escape.\n"), stderr);
831 break;
832 case '!':
833 /* Shell escape, send the balance of line to sh -c */
834 c_shell(linebuf + 2);
835 break;
836 case ':':
837 /* FALLTHRU */
838 case '_':
839 /* Escape to command mode, but be nice! */
840 _execute_command(hp, linebuf + 2, cnt - 2);
841 goto jcont;
842 case '.':
843 /* Simulate end of file on input */
844 goto jout;
845 case 'x':
846 /* Same as 'q', but no *DEAD* saving */
847 /* FALLTHRU */
848 case 'q':
849 /* Force a quit, act like an interrupt had happened */
850 ++_coll_hadintr;
851 _collint((c == 'x') ? 0 : SIGINT);
852 exit(1);
853 /*NOTREACHED*/
854 case 'h':
855 /* Grab a bunch of headers */
857 grab_headers(hp, GTO | GSUBJECT | GCC | GBCC,
858 (ok_blook(bsdcompat) && ok_blook(bsdorder)));
859 while (hp->h_to == NULL);
860 goto jcont;
861 case 'H':
862 /* Grab extra headers */
864 grab_headers(hp, GEXTRA, 0);
865 while (check_from_and_sender(hp->h_from, hp->h_sender) == NULL);
866 goto jcont;
867 case 't':
868 /* Add to the To list */
869 hp->h_to = cat(hp->h_to,
870 checkaddrs(lextract(linebuf + 2, GTO | GFULL), EACM_NORMAL,
871 NULL));
872 break;
873 case 's':
874 /* Set the Subject list */
875 cp = linebuf + 2;
876 while (whitechar(*cp))
877 ++cp;
878 hp->h_subject = savestr(cp);
879 break;
880 #ifdef HAVE_DEBUG
881 case 'S':
882 c_sstats(NULL);
883 break;
884 #endif
885 case '@':
886 /* Edit the attachment list */
887 if (linebuf[2] != '\0')
888 append_attachments(&hp->h_attach, linebuf + 2);
889 else
890 edit_attachments(&hp->h_attach);
891 break;
892 case 'c':
893 /* Add to the CC list */
894 hp->h_cc = cat(hp->h_cc,
895 checkaddrs(lextract(linebuf + 2, GCC | GFULL), EACM_NORMAL,
896 NULL));
897 break;
898 case 'b':
899 /* Add stuff to blind carbon copies list */
900 hp->h_bcc = cat(hp->h_bcc,
901 checkaddrs(lextract(linebuf + 2, GBCC | GFULL), EACM_NORMAL,
902 NULL));
903 break;
904 case 'd':
905 strncpy(linebuf + 2, getdeadletter(), linesize - 2);
906 linebuf[linesize - 1] = '\0';
907 /*FALLTHRU*/
908 case 'R':
909 case 'r':
910 case '<':
911 /* Invoke a file: Search for the file name, then open it and copy the
912 * contents to _coll_fp */
913 cp = linebuf + 2;
914 while (whitechar(*cp))
915 ++cp;
916 if (*cp == '\0') {
917 fputs(_("Interpolate what file?\n"), stderr);
918 break;
920 if (*cp == '!') {
921 insertcommand(_coll_fp, cp + 1);
922 break;
924 if ((cp = file_expand(cp)) == NULL)
925 break;
926 if (is_dir(cp)) {
927 fprintf(stderr, _("%s: Directory\n"), cp);
928 break;
930 printf(_("\"%s\" "), cp);
931 fflush(stdout);
932 if (_include_file(cp, &lc, &cc, FAL0, (c == 'R')) != 0)
933 goto jerr;
934 printf(_("%d/%d\n"), lc, cc);
935 break;
936 case 'i':
937 /* Insert a variable into the file */
938 cp = linebuf + 2;
939 while (whitechar(*cp))
940 ++cp;
941 if ((cp = vok_vlook(cp)) == NULL || *cp == '\0')
942 break;
943 if (putesc(cp, _coll_fp) < 0)
944 goto jerr;
945 if ((options & OPT_INTERACTIVE) && putesc(cp, stdout) < 0)
946 goto jerr;
947 break;
948 case 'a':
949 case 'A':
950 /* Insert the contents of a signature variable */
951 cp = (c == 'a') ? ok_vlook(sign) : ok_vlook(Sign);
952 if (cp != NULL && *cp != '\0') {
953 if (putesc(cp, _coll_fp) < 0)
954 goto jerr;
955 if ((options & OPT_INTERACTIVE) && putesc(cp, stdout) < 0)
956 goto jerr;
958 break;
959 case 'w':
960 /* Write the message on a file */
961 cp = linebuf + 2;
962 while (blankchar(*cp))
963 ++cp;
964 if (*cp == '\0' || (cp = file_expand(cp)) == NULL) {
965 fputs(_("Write what file!?\n"), stderr);
966 break;
968 rewind(_coll_fp);
969 if (exwrite(cp, _coll_fp, 1) < 0)
970 goto jerr;
971 break;
972 case 'm':
973 case 'M':
974 case 'f':
975 case 'F':
976 case 'u':
977 case 'U':
978 /* Interpolate the named messages, if we are in receiving mail mode.
979 * Does the standard list processing garbage. If ~f is given, we
980 * don't shift over */
981 if (forward(linebuf + 2, _coll_fp, c) < 0)
982 goto jerr;
983 goto jcont;
984 case 'p':
985 /* Print current state of the message without altering anything */
986 print_collf(_coll_fp, hp);
987 goto jcont;
988 case '|':
989 /* Pipe message through command. Collect output as new message */
990 rewind(_coll_fp);
991 mespipe(linebuf + 2);
992 goto jcont;
993 case 'v':
994 case 'e':
995 /* Edit the current message. 'e' -> use EDITOR, 'v' -> use VISUAL */
996 rewind(_coll_fp);
997 mesedit(c, ok_blook(editheaders) ? hp : NULL);
998 goto jcont;
999 case '?':
1000 /* Last the lengthy help string. (Very ugly, but take care for
1001 * compiler supported string lengths :() */
1002 puts(_(
1003 "-------------------- ~ ESCAPES ----------------------------\n"
1004 "~~ Quote a single tilde\n"
1005 "~@ [file ...] Edit attachment list\n"
1006 "~b users Add users to \"blind\" Bcc: list\n"
1007 "~c users Add users to Cc: list\n"
1008 "~d Read in dead.letter\n"
1009 "~e Edit the message buffer\n"
1010 "~F messages Read in messages including all headers, don't indent lines\n"
1011 "~f messages Like ~F, but honour the `ignore' / `retain' configuration\n"
1012 "~h Prompt for Subject:, To:, Cc: and \"blind\" Bcc:\n"));
1013 puts(_(
1014 "~R file Read in a file, indent lines\n"
1015 "~r file Read in a file\n"
1016 "~p Print the message buffer\n"
1017 "~q Abort message composition and save text to DEAD\n"
1018 "~M messages Read in messages, keep all header lines, indent lines\n"
1019 "~m messages Like ~M, but honour the `ignore' / `retain' configuration\n"
1020 "~s subject Set Subject:\n"
1021 "~t users Add users to To: list\n"));
1022 puts(_(
1023 "~U messages Read in message(s) without any headers, indent lines\n"
1024 "~u messages Read in message(s) without any headers\n"
1025 "~v Invoke alternate editor ($VISUAL) on message\n"
1026 "~w file Write message onto file\n"
1027 "~x Abort message composition and discard message\n"
1028 "~!command Invoke the shell\n"
1029 "~:command Execute a regular command\n"
1030 "-----------------------------------------------------------\n"));
1031 break;
1035 jout:
1036 if (_coll_fp != NULL) {
1037 if ((cp = ok_vlook(NAIL_TAIL)) != NULL) {
1038 if (putesc(cp, _coll_fp) < 0)
1039 goto jerr;
1040 if ((options & OPT_INTERACTIVE) && putesc(cp, stdout) < 0)
1041 goto jerr;
1043 rewind(_coll_fp);
1045 if (linebuf != NULL)
1046 free(linebuf);
1047 handlerpop();
1048 --noreset;
1049 sigemptyset(&nset);
1050 sigaddset(&nset, SIGINT);
1051 sigaddset(&nset, SIGHUP);
1052 sigprocmask(SIG_BLOCK, &nset, NULL);
1053 safe_signal(SIGINT, _coll_saveint);
1054 safe_signal(SIGHUP, _coll_savehup);
1055 safe_signal(SIGTSTP, _coll_savetstp);
1056 safe_signal(SIGTTOU, _coll_savettou);
1057 safe_signal(SIGTTIN, _coll_savettin);
1058 sigprocmask(SIG_SETMASK, &oset, NULL);
1059 NYD_LEAVE;
1060 return _coll_fp;
1062 jerr:
1063 if (_coll_fp != NULL) {
1064 Fclose(_coll_fp);
1065 _coll_fp = NULL;
1067 goto jout;
1070 FL void
1071 savedeadletter(FILE *fp, int fflush_rewind_first)
1073 char const *cp;
1074 int c;
1075 FILE *dbuf;
1076 ul_i lines, bytes;
1077 NYD_ENTER;
1079 if (fflush_rewind_first) {
1080 fflush(fp);
1081 rewind(fp);
1083 if (fsize(fp) == 0)
1084 goto jleave;
1086 cp = getdeadletter();
1087 c = umask(077);
1088 dbuf = Fopen(cp, "a");
1089 umask(c);
1090 if (dbuf == NULL)
1091 goto jleave;
1093 really_rewind(fp);
1095 printf("\"%s\" ", cp);
1096 for (lines = bytes = 0; (c = getc(fp)) != EOF; ++bytes) {
1097 putc(c, dbuf);
1098 if (c == '\n')
1099 ++lines;
1101 printf("%lu/%lu\n", lines, bytes);
1103 Fclose(dbuf);
1104 rewind(fp);
1105 jleave:
1106 NYD_LEAVE;
1109 /* s-it-mode */