Some: stop adding $TMPDIR to ENV, automatic now with new var-handling
[s-mailx.git] / collect.c
blob33f795a1986b8c88002598461f610f717a762bbd
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 - 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 collect
38 #ifndef HAVE_AMALGAMATION
39 # include "nail.h"
40 #endif
42 /* The following hookiness with global variables is so that on receipt of an
43 * interrupt signal, the partial message can be salted away on *DEAD* */
45 static sighandler_type _coll_saveint; /* Previous SIGINT value */
46 static sighandler_type _coll_savehup; /* Previous SIGHUP value */
47 static FILE *_coll_fp; /* File for saving away */
48 static int volatile _coll_hadintr; /* Have seen one SIGINT so far */
49 static sigjmp_buf _coll_jmp; /* To get back to work */
50 static sigjmp_buf _coll_abort; /* To end collection with error */
51 static sigjmp_buf _coll_pipejmp; /* On broken pipe */
53 /* Handle `~:', `~_' and some hooks; hp may be NULL */
54 static void _execute_command(struct header *hp, char const *linebuf,
55 size_t linesize);
57 /* If *interactive* is set and *doecho* is, too, also dump to *stdout* */
58 static int _include_file(char const *name, int *linecount,
59 int *charcount, bool_t doecho, bool_t indent);
61 static void _collect_onpipe(int signo);
63 /* Execute cmd and insert its standard output into fp */
64 static void insertcommand(FILE *fp, char const *cmd);
66 /* ~p command */
67 static void print_collf(FILE *collf, struct header *hp);
69 /* Write a file, ex-like if f set */
70 static int exwrite(char const *name, FILE *fp, int f);
72 /* Parse off the message header from fp and store relevant fields in hp,
73 * replace _coll_fp with a shiny new version without any header */
74 static enum okay makeheader(FILE *fp, struct header *hp, si8_t *checkaddr_err);
76 /* Edit the message being collected on fp. On return, make the edit file the
77 * new temp file */
78 static void mesedit(int c, struct header *hp);
80 /* Pipe the message through the command. Old message is on stdin of command,
81 * new message collected from stdout. Shell must return 0 to accept new msg */
82 static void mespipe(char *cmd);
84 /* Interpolate the named messages into the current message, possibly doing
85 * indent stuff. The flag argument is one of the command escapes: [mMfFuU].
86 * Return a count of the number of characters now in the message, or -1 if an
87 * error is encountered writing the message temporary */
88 static int forward(char *ms, FILE *fp, int f);
90 /* On interrupt, come here to save the partial message in ~/dead.letter.
91 * Then jump out of the collection loop */
92 static void _collint(int s);
94 static void collhup(int s);
96 static int putesc(char const *s, FILE *stream); /* TODO wysh set! */
98 /* call_compose_mode_hook() setter hook */
99 static void a_coll__hook_setter(void *arg);
101 static void
102 _execute_command(struct header *hp, char const *linebuf, size_t linesize){
103 /* The problem arises if there are rfc822 message attachments and the
104 * user uses `~:' to change the current file. TODO Unfortunately we
105 * TODO cannot simply keep a pointer to, or increment a reference count
106 * TODO of the current `file' (mailbox that is) object, because the
107 * TODO codebase doesn't deal with that at all; so, until some far
108 * TODO later time, copy the name of the path, and warn the user if it
109 * TODO changed; we COULD use the AC_TMPFILE attachment type, i.e.,
110 * TODO copy the message attachments over to temporary files, but that
111 * TODO would require more changes so that the user still can recognize
112 * TODO in `~@' etc. that its a rfc822 message attachment; see below */
113 struct n_sigman sm;
114 struct attachment *ap;
115 char * volatile mnbuf;
116 NYD_ENTER;
118 n_UNUSED(linesize);
119 mnbuf = NULL;
121 n_SIGMAN_ENTER_SWITCH(&sm, n_SIGMAN_ALL){
122 case 0:
123 break;
124 default:
125 goto jleave;
128 /* If the above todo is worked, remove or outsource to attachments.c! */
129 if(hp != NULL && (ap = hp->h_attach) != NULL) do
130 if(ap->a_msgno){
131 mnbuf = sstrdup(mailname);
132 break;
134 while((ap = ap->a_flink) != NULL);
136 n_source_command(n_LEXINPUT_CTX_COMPOSE, linebuf);
138 n_sigman_cleanup_ping(&sm);
139 jleave:
140 if(mnbuf != NULL){
141 if(strcmp(mnbuf, mailname))
142 n_err(_("Mailbox changed: it is likely that existing "
143 "rfc822 attachments became invalid!\n"));
144 free(mnbuf);
146 NYD_LEAVE;
147 n_sigman_leave(&sm, n_SIGMAN_VIPSIGS_NTTYOUT);
150 static int
151 _include_file(char const *name, int *linecount, int *charcount,
152 bool_t doecho, bool_t indent)
154 FILE *fbuf;
155 char const *indb;
156 int ret = -1;
157 char *linebuf = NULL; /* TODO line pool */
158 size_t linesize = 0, indl, linelen, cnt;
159 NYD_ENTER;
161 if (name == (char*)-1)
162 fbuf = stdin;
163 else if ((fbuf = Fopen(name, "r")) == NULL) {
164 n_perr(name, 0);
165 goto jleave;
168 if (!indent)
169 indb = NULL, indl = 0;
170 else {
171 if ((indb = ok_vlook(indentprefix)) == NULL)
172 indb = INDENT_DEFAULT;
173 indl = strlen(indb);
176 *linecount = *charcount = 0;
177 cnt = fsize(fbuf);
178 while (fgetline(&linebuf, &linesize, &cnt, &linelen, fbuf, 0) != NULL) {
179 if (indl > 0 && fwrite(indb, sizeof *indb, indl, _coll_fp) != indl)
180 goto jleave;
181 if (fwrite(linebuf, sizeof *linebuf, linelen, _coll_fp) != linelen)
182 goto jleave;
183 ++(*linecount);
184 (*charcount) += linelen + indl;
185 if ((options & OPT_INTERACTIVE) && doecho) {
186 if (indl > 0)
187 fwrite(indb, sizeof *indb, indl, stdout);
188 fwrite(linebuf, sizeof *linebuf, linelen, stdout);
191 if (fflush(_coll_fp))
192 goto jleave;
193 if ((options & OPT_INTERACTIVE) && doecho)
194 fflush(stdout);
196 ret = 0;
197 jleave:
198 if (linebuf != NULL)
199 free(linebuf);
200 if (fbuf != NULL && fbuf != stdin)
201 Fclose(fbuf);
202 NYD_LEAVE;
203 return ret;
206 static void
207 _collect_onpipe(int signo)
209 NYD_X; /* Signal handler */
210 n_UNUSED(signo);
211 siglongjmp(_coll_pipejmp, 1);
214 static void
215 insertcommand(FILE *fp, char const *cmd)
217 FILE *ibuf = NULL;
218 int c;
219 NYD_ENTER;
221 if ((ibuf = Popen(cmd, "r", ok_vlook(SHELL), NULL, 0)) != NULL) {
222 while ((c = getc(ibuf)) != EOF) /* XXX bytewise, yuck! */
223 putc(c, fp);
224 Pclose(ibuf, TRU1);
225 } else
226 n_perr(cmd, 0);
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 = (size_t)fsize(cf);
245 sigint = safe_signal(SIGINT, SIG_IGN);
247 if ((options & OPT_INTERACTIVE) && (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);
265 l = (*cp == '\0') ? (size_t)screensize() : strtoul(cp, NULL, 0);
266 if (m > l)
267 goto jpager;
268 l -= m;
270 for (m = 0; fgetline(&lbuf, &linesize, &cnt2, NULL, cf, 0); ++m)
272 rewind(cf);
273 if (l < m) {
274 jpager:
275 if (sigsetjmp(_coll_pipejmp, 1))
276 goto jendpipe;
277 if ((obuf = n_pager_open()) == NULL)
278 obuf = stdout;
279 else
280 safe_signal(SIGPIPE, &_collect_onpipe);
284 fprintf(obuf, _("-------\nMessage contains:\n"));
285 gf = GIDENT | GTO | GSUBJECT | GCC | GBCC | GNL | GFILES | GCOMMA;
286 puthead(TRU1, hp, obuf, gf, SEND_TODISP, CONV_NONE, NULL, NULL);
287 while (fgetline(&lbuf, &linesize, &cnt, &linelen, cf, 1))
288 prout(lbuf, linelen, obuf);
289 if (hp->h_attach != NULL) {
290 fputs(_("-------\nAttachments:\n"), obuf);
291 for (ap = hp->h_attach; ap != NULL; ap = ap->a_flink) {
292 if (ap->a_msgno)
293 fprintf(obuf, " - message %u\n", ap->a_msgno);
294 else {
295 /* TODO after MIME/send layer rewrite we *know*
296 * TODO the details of the attachment here,
297 * TODO so adjust this again, then */
298 char const *cs, *csi = "-> ";
300 if ((cs = ap->a_charset) == NULL &&
301 (csi = "<- ", cs = ap->a_input_charset) == NULL)
302 cs = charset_get_lc();
303 if ((cp = ap->a_content_type) == NULL)
304 cp = "?";
305 else if (ascncasecmp(cp, "text/", 5))
306 csi = n_empty;
307 fprintf(obuf, " - [%s, %s%s] %s\n", cp, csi, cs,
308 n_shexp_quote_cp(ap->a_name, FAL0));
313 jendpipe:
314 if (obuf != stdout)
315 n_pager_close(obuf);
316 if (lbuf != NULL)
317 free(lbuf);
318 safe_signal(SIGINT, sigint);
319 NYD_LEAVE;
322 static int
323 exwrite(char const *name, FILE *fp, int f)
325 FILE *of;
326 int c, lc, rv = -1;
327 long cc;
328 NYD_ENTER;
330 if (f) {
331 printf("%s ", n_shexp_quote_cp(name, FAL0));
332 fflush(stdout);
334 if ((of = Fopen(name, "a")) == NULL) {
335 n_perr(NULL, 0);
336 goto jleave;
339 lc = 0;
340 cc = 0;
341 while ((c = getc(fp)) != EOF) {
342 ++cc;
343 if (c == '\n')
344 ++lc;
345 putc(c, of);
346 if (ferror(of)) {
347 n_perr(name, 0);
348 Fclose(of);
349 goto jleave;
352 Fclose(of);
353 printf(_("%d/%ld\n"), lc, cc);
354 fflush(stdout);
355 rv = 0;
356 jleave:
357 NYD_LEAVE;
358 return rv;
361 static enum okay
362 makeheader(FILE *fp, struct header *hp, si8_t *checkaddr_err)
364 FILE *nf;
365 int c;
366 enum okay rv = STOP;
367 NYD_ENTER;
369 if ((nf = Ftmp(NULL, "colhead", OF_RDWR | OF_UNLINK | OF_REGISTER)) ==NULL) {
370 n_perr(_("temporary mail edit file"), 0);
371 goto jleave;
374 extract_header(fp, hp, checkaddr_err);
375 if (checkaddr_err != NULL && *checkaddr_err != 0)
376 goto jleave;
378 while ((c = getc(fp)) != EOF) /* XXX bytewise, yuck! */
379 putc(c, nf);
380 if (fp != _coll_fp)
381 Fclose(_coll_fp);
382 Fclose(fp);
383 _coll_fp = nf;
384 if (check_from_and_sender(hp->h_from, hp->h_sender) == NULL)
385 goto jleave;
386 rv = OKAY;
387 jleave:
388 NYD_LEAVE;
389 return rv;
392 static void
393 mesedit(int c, struct header *hp)
395 bool_t saved;
396 sighandler_type sigint;
397 FILE *nf;
398 NYD_ENTER;
400 if(!(saved = ok_blook(add_file_recipients)))
401 ok_bset(add_file_recipients);
403 sigint = safe_signal(SIGINT, SIG_IGN);
404 nf = run_editor(_coll_fp, (off_t)-1, c, FAL0, hp, NULL, SEND_MBOX, sigint);
405 if (nf != NULL) {
406 if (hp) {
407 rewind(nf);
408 makeheader(nf, hp, NULL);
409 } else {
410 fseek(nf, 0L, SEEK_END);
411 Fclose(_coll_fp);
412 _coll_fp = nf;
415 safe_signal(SIGINT, sigint);
417 if(!saved)
418 ok_bclear(add_file_recipients);
419 NYD_LEAVE;
422 static void
423 mespipe(char *cmd)
425 FILE *nf;
426 sighandler_type sigint;
427 NYD_ENTER;
429 sigint = safe_signal(SIGINT, SIG_IGN);
431 if ((nf = Ftmp(NULL, "colpipe", OF_RDWR | OF_UNLINK | OF_REGISTER)) ==NULL) {
432 n_perr(_("temporary mail edit file"), 0);
433 goto jout;
436 /* stdin = current message. stdout = new message */
437 fflush(_coll_fp);
438 if (run_command(ok_vlook(SHELL), 0, fileno(_coll_fp), fileno(nf), "-c",
439 cmd, NULL, NULL) < 0) {
440 Fclose(nf);
441 goto jout;
444 if (fsize(nf) == 0) {
445 n_err(_("No bytes from %s !?\n"), n_shexp_quote_cp(cmd, FAL0));
446 Fclose(nf);
447 goto jout;
450 /* Take new files */
451 fseek(nf, 0L, SEEK_END);
452 Fclose(_coll_fp);
453 _coll_fp = nf;
454 jout:
455 safe_signal(SIGINT, sigint);
456 NYD_LEAVE;
459 static int
460 forward(char *ms, FILE *fp, int f)
462 int *msgvec, rv = 0;
463 struct n_ignore const *itp;
464 char const *tabst;
465 enum sendaction action;
466 NYD_ENTER;
468 msgvec = salloc((size_t)(msgCount + 1) * sizeof *msgvec);
469 if (getmsglist(ms, msgvec, 0) < 0)
470 goto jleave;
471 if (*msgvec == 0) {
472 *msgvec = first(0, MMNORM);
473 if (*msgvec == 0) {
474 n_err(_("No appropriate messages\n"));
475 goto jleave;
477 msgvec[1] = 0;
480 if (f == 'f' || f == 'F' || f == 'u')
481 tabst = NULL;
482 else if ((tabst = ok_vlook(indentprefix)) == NULL)
483 tabst = INDENT_DEFAULT;
484 if (f == 'u' || f == 'U')
485 itp = n_IGNORE_ALL;
486 else
487 itp = upperchar(f) ? NULL : n_IGNORE_TYPE;
488 action = (upperchar(f) && f != 'U') ? SEND_QUOTE_ALL : SEND_QUOTE;
490 printf(_("Interpolating:"));
491 for (; *msgvec != 0; ++msgvec) {
492 struct message *mp = message + *msgvec - 1;
494 touch(mp);
495 printf(" %d", *msgvec);
496 fflush(stdout);
497 if (sendmp(mp, fp, itp, tabst, action, NULL) < 0) {
498 n_perr(_("temporary mail file"), 0);
499 rv = -1;
500 break;
503 printf("\n");
504 jleave:
505 NYD_LEAVE;
506 return rv;
509 static void
510 _collint(int s)
512 NYD_X; /* Signal handler */
514 /* the control flow is subtle, because we can be called from ~q */
515 if (_coll_hadintr == 0) {
516 if (ok_blook(ignore)) {
517 puts("@");
518 fflush(stdout);
519 clearerr(stdin);
520 } else
521 _coll_hadintr = 1;
522 siglongjmp(_coll_jmp, 1);
524 exit_status |= EXIT_SEND_ERROR;
525 if (s != 0)
526 savedeadletter(_coll_fp, TRU1);
527 /* Aborting message, no need to fflush() .. */
528 siglongjmp(_coll_abort, 1);
531 static void
532 collhup(int s)
534 NYD_X; /* Signal handler */
535 n_UNUSED(s);
537 savedeadletter(_coll_fp, TRU1);
538 /* Let's pretend nobody else wants to clean up, a true statement at
539 * this time */
540 exit(EXIT_ERR);
543 static int
544 putesc(char const *s, FILE *stream)
546 int n = 0, rv = -1;
547 NYD_ENTER;
549 while (s[0] != '\0') {
550 if (s[0] == '\\') {
551 if (s[1] == 't') {
552 if (putc('\t', stream) == EOF)
553 goto jleave;
554 ++n;
555 s += 2;
556 continue;
558 if (s[1] == 'n') {
559 if (putc('\n', stream) == EOF)
560 goto jleave;
561 ++n;
562 s += 2;
563 continue;
566 if (putc(s[0], stream) == EOF)
567 goto jleave;
568 ++n;
569 ++s;
571 if (putc('\n', stream) == EOF)
572 goto jleave;
573 rv = ++n;
574 jleave:
575 NYD_LEAVE;
576 return rv;
579 static void
580 a_coll__hook_setter(void *arg){ /* TODO v15: drop */
581 struct header *hp;
582 char const *val;
583 NYD2_ENTER;
585 hp = arg;
587 if((val = detract(hp->h_from, GNAMEONLY)) == NULL)
588 val = n_empty;
589 ok_vset(compose_from, val);
590 if((val = detract(hp->h_sender, 0)) == NULL)
591 val = n_empty;
592 ok_vset(compose_sender, val);
593 if((val = detract(hp->h_to, GNAMEONLY)) == NULL)
594 val = n_empty;
595 ok_vset(compose_to, val);
596 if((val = detract(hp->h_cc, GNAMEONLY)) == NULL)
597 val = n_empty;
598 ok_vset(compose_cc, val);
599 if((val = detract(hp->h_bcc, GNAMEONLY)) == NULL)
600 val = n_empty;
601 ok_vset(compose_bcc, val);
602 if((val = hp->h_subject) == NULL)
603 val = n_empty;
604 ok_vset(compose_subject, val);
605 NYD2_LEAVE;
608 FL FILE *
609 collect(struct header *hp, int printheaders, struct message *mp,
610 char *quotefile, int doprefix, si8_t *checkaddr_err)
612 struct n_ignore const *quoteitp;
613 int lc, cc, c;
614 int volatile t, eofcnt;
615 int volatile escape, getfields;
616 char *linebuf;
617 char const *cp;
618 size_t i, linesize; /* TODO line pool */
619 long cnt;
620 enum sendaction action;
621 sigset_t oset, nset;
622 FILE * volatile sigfp;
623 NYD_ENTER;
625 _coll_fp = NULL;
626 sigfp = NULL;
627 linesize = 0;
628 linebuf = NULL;
629 eofcnt = 0;
631 /* Start catching signals from here, but we're still die on interrupts
632 * until we're in the main loop */
633 sigfillset(&nset);
634 sigprocmask(SIG_BLOCK, &nset, &oset);
635 /* FIXME have dropped handlerpush() and localized onintr() in lex.c! */
636 if ((_coll_saveint = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN)
637 safe_signal(SIGINT, &_collint);
638 if ((_coll_savehup = safe_signal(SIGHUP, SIG_IGN)) != SIG_IGN)
639 safe_signal(SIGHUP, collhup);
640 if (sigsetjmp(_coll_abort, 1))
641 goto jerr;
642 if (sigsetjmp(_coll_jmp, 1))
643 goto jerr;
644 pstate |= PS_RECURSED;
645 sigprocmask(SIG_SETMASK, &oset, (sigset_t*)NULL);
647 if ((_coll_fp = Ftmp(NULL, "collect", OF_RDWR | OF_UNLINK | OF_REGISTER)) ==
648 NULL) {
649 n_perr(_("temporary mail file"), 0);
650 goto jerr;
653 /* If we are going to prompt for a subject, refrain from printing a newline
654 * after the headers (since some people mind) */
655 getfields = 0;
656 if (!(options & OPT_t_FLAG)) {
657 t = GTO | GSUBJECT | GCC | GNL;
658 if (ok_blook(fullnames))
659 t |= GCOMMA;
661 if (options & OPT_INTERACTIVE) {
662 if (hp->h_subject == NULL && (ok_blook(ask) || ok_blook(asksub)))
663 t &= ~GNL, getfields |= GSUBJECT;
665 if (hp->h_to == NULL)
666 t &= ~GNL, getfields |= GTO;
668 if (!ok_blook(bsdcompat) && !ok_blook(askatend)) {
669 if (hp->h_bcc == NULL && ok_blook(askbcc))
670 t &= ~GNL, getfields |= GBCC;
671 if (hp->h_cc == NULL && ok_blook(askcc))
672 t &= ~GNL, getfields |= GCC;
675 } else {
676 n_UNINIT(t, 0);
679 escape = ((cp = ok_vlook(escape)) != NULL) ? *cp : ESCAPE;
680 _coll_hadintr = 0;
682 if (!sigsetjmp(_coll_jmp, 1)) {
683 /* Ask for some headers first, as necessary */
684 if (getfields)
685 grab_headers(n_LEXINPUT_CTX_COMPOSE, hp, getfields, 1);
687 /* Execute compose-enter TODO completely v15-compat intermediate!! */
688 if((cp = ok_vlook(on_compose_enter)) != NULL){
689 setup_from_and_sender(hp);
690 call_compose_mode_hook(cp, &a_coll__hook_setter, hp);
693 if(!(options & OPT_Mm_FLAG)){
694 char const *cp_obsolete = ok_vlook(NAIL_HEAD);
695 if(cp_obsolete != NULL)
696 OBSOLETE(_("please use *message-inject-head* "
697 "instead of *NAIL_HEAD*"));
699 if(((cp = ok_vlook(message_inject_head)) != NULL ||
700 (cp = cp_obsolete) != NULL) && putesc(cp, _coll_fp) < 0)
701 goto jerr;
703 /* Quote an original message */
704 if (mp != NULL && (doprefix || (cp = ok_vlook(quote)) != NULL)) {
705 quoteitp = n_IGNORE_ALL;
706 action = SEND_QUOTE;
707 if (doprefix) {
708 quoteitp = n_IGNORE_FWD;
709 if ((cp = ok_vlook(fwdheading)) == NULL)
710 cp = "-------- Original Message --------";
711 if (*cp != '\0' && fprintf(_coll_fp, "%s\n", cp) < 0)
712 goto jerr;
713 } else if (!strcmp(cp, "noheading")) {
714 /*EMPTY*/;
715 } else if (!strcmp(cp, "headers")) {
716 quoteitp = n_IGNORE_TYPE;
717 } else if (!strcmp(cp, "allheaders")) {
718 quoteitp = NULL;
719 action = SEND_QUOTE_ALL;
720 } else {
721 cp = hfield1("from", mp);
722 if (cp != NULL && (cnt = (long)strlen(cp)) > 0) {
723 if (xmime_write(cp, cnt, _coll_fp, CONV_FROMHDR, TD_NONE) < 0)
724 goto jerr;
725 if (fprintf(_coll_fp, _(" wrote:\n\n")) < 0)
726 goto jerr;
729 if (fflush(_coll_fp))
730 goto jerr;
731 if (doprefix)
732 cp = NULL;
733 else if ((cp = ok_vlook(indentprefix)) == NULL)
734 cp = INDENT_DEFAULT;
735 if (sendmp(mp, _coll_fp, quoteitp, cp, action, NULL) < 0)
736 goto jerr;
740 if (quotefile != NULL) {
741 if (_include_file(quotefile, &lc, &cc,
742 !(options & OPT_Mm_FLAG), FAL0) != 0)
743 goto jerr;
746 if ((options & (OPT_Mm_FLAG | OPT_INTERACTIVE)) == OPT_INTERACTIVE) {
747 /* Print what we have sofar also on the terminal (if useful) */
748 if (!ok_blook(editalong)) {
749 if (printheaders)
750 puthead(TRU1, hp, stdout, t, SEND_TODISP, CONV_NONE, NULL, NULL);
752 rewind(_coll_fp);
753 while ((c = getc(_coll_fp)) != EOF) /* XXX bytewise, yuck! */
754 putc(c, stdout);
755 if (fseek(_coll_fp, 0, SEEK_END))
756 goto jerr;
758 /* Ensure this is clean xxx not really necessary? */
759 fflush(stdout);
760 } else {
761 rewind(_coll_fp);
762 mesedit('e', hp);
763 /* As mandated by the Mail Reference Manual, print "(continue)" */
764 jcont:
765 if(options & OPT_INTERACTIVE){
766 printf(_("(continue)\n"));
767 fflush(stdout);
771 } else {
772 /* Come here for printing the after-signal message. Duplicate messages
773 * won't be printed because the write is aborted if we get a SIGTTOU */
774 if (_coll_hadintr)
775 n_err(_("\n(Interrupt -- one more to kill letter)\n"));
778 /* We're done with -M or -m */
779 if(options & OPT_Mm_FLAG)
780 goto jout;
781 /* No command escapes, interrupts not expected. Simply copy STDIN */
782 if (!(options & (OPT_INTERACTIVE | OPT_t_FLAG | OPT_TILDE_FLAG))) {
783 linebuf = srealloc(linebuf, linesize = LINESIZE);
784 while ((i = fread(linebuf, sizeof *linebuf, linesize, stdin)) > 0) {
785 if (i != fwrite(linebuf, sizeof *linebuf, i, _coll_fp))
786 goto jerr;
788 goto jout;
791 /* The interactive collect loop.
792 * All commands which come here are forbidden when sourcing! */
793 assert(_coll_hadintr || !(pstate & PS_SOURCING));
794 for(;;){
795 /* C99 */{
796 enum n_lexinput_flags lif;
798 lif = n_LEXINPUT_CTX_COMPOSE;
799 if(options & (OPT_INTERACTIVE | OPT_TILDE_FLAG)){
800 if(!(options & OPT_t_FLAG))
801 lif |= n_LEXINPUT_NL_ESC;
803 cnt = n_lex_input(lif, n_empty, &linebuf, &linesize, NULL);
806 if (cnt < 0) {
807 if (options & OPT_t_FLAG) {
808 fflush_rewind(_coll_fp);
809 /* It is important to set PS_t_FLAG before extract_header() *and*
810 * keep OPT_t_FLAG for the first parse of the message, too! */
811 pstate |= PS_t_FLAG;
812 if (makeheader(_coll_fp, hp, checkaddr_err) != OKAY)
813 goto jerr;
814 options &= ~OPT_t_FLAG;
815 continue;
816 } else if ((options & OPT_INTERACTIVE) && ok_blook(ignoreeof) &&
817 ++eofcnt < 4) {
818 printf(_("*ignoreeof* set, use `~.' to terminate letter\n"));
819 continue;
821 break;
824 _coll_hadintr = 0;
826 cp = linebuf;
827 if(cnt == 0)
828 goto jputnl;
829 else if(!(options & (OPT_INTERACTIVE | OPT_TILDE_FLAG)))
830 goto jputline;
831 else if(cp[0] == '.'){
832 if(cnt == 1 && (ok_blook(dot) || ok_blook(ignoreeof)))
833 break;
835 if(cp[0] != escape){
836 jputline:
837 if(fwrite(cp, sizeof *cp, cnt, _coll_fp) != (size_t)cnt)
838 goto jerr;
839 /* TODO PS_READLINE_NL is a terrible hack to ensure that _in_all_-
840 * TODO _code_paths_ a file without trailing newline isn't modified
841 * TODO to continue one; the "saw-newline" needs to be part of an
842 * TODO I/O input machinery object */
843 jputnl:
844 if(pstate & PS_READLINE_NL){
845 if(putc('\n', _coll_fp) == EOF)
846 goto jerr;
848 continue;
851 /* Cleanup the input string: like this we can perform a little bit of
852 * usage testing and also have somewhat normalized history entries */
853 for(cp = &linebuf[2]; (c = *cp) != '\0' && blankspacechar(c); ++cp)
854 continue;
855 if(c == '\0'){
856 linebuf[2] = '\0';
857 cnt = 2;
858 }else{
859 i = PTR2SIZE(cp - linebuf) - 3;
860 memmove(&linebuf[3], cp, (cnt -= i));
861 linebuf[2] = ' ';
862 linebuf[cnt] = '\0';
864 if(cnt > 0){ /* TODO v15 no more trailing WS from lex_input please */
865 cp = &linebuf[cnt];
867 for(;; --cp){
868 c = cp[-1];
869 if(!blankspacechar(c))
870 break;
872 ((char*)n_UNCONST(cp))[0] = '\0';
873 cnt = PTR2SIZE(cp - linebuf);
877 switch((c = linebuf[1])){
878 default:
879 /* On double escape, send a single one. Otherwise, it's an error */
880 if(c == escape){
881 cp = &linebuf[1];
882 --cnt;
883 goto jputline;
884 }else{
885 char buf[sizeof(n_UNIREPL)];
887 if(asciichar(c))
888 buf[0] = c, buf[1] = '\0';
889 else if(options & OPT_UNICODE)
890 memcpy(buf, n_unirepl, sizeof n_unirepl);
891 else
892 buf[0] = '?', buf[1] = '\0';
893 n_err(_("Unknown command escape: ~%s\n"), buf);
894 continue;
896 jearg:
897 n_err(_("Invalid command escape usage: %s\n"), linebuf);
898 continue;
899 case '!':
900 /* Shell escape, send the balance of line to sh -c */
901 if(cnt == 2)
902 goto jearg;
903 c_shell(&linebuf[3]);
904 goto jhistcont;
905 case ':':
906 /* FALLTHRU */
907 case '_':
908 /* Escape to command mode, but be nice! *//* TODO command expansion
909 * TODO should be handled here so that we have unique history! */
910 if(cnt == 2)
911 goto jearg;
912 _execute_command(hp, &linebuf[3], cnt -= 3);
913 break;
914 case '.':
915 /* Simulate end of file on input */
916 if(cnt != 2)
917 goto jearg;
918 goto jout;
919 case 'x':
920 /* Same as 'q', but no *DEAD* saving */
921 /* FALLTHRU */
922 case 'q':
923 /* Force a quit, act like an interrupt had happened */
924 if(cnt != 2)
925 goto jearg;
926 ++_coll_hadintr;
927 _collint((c == 'x') ? 0 : SIGINT);
928 exit(EXIT_ERR);
929 /*NOTREACHED*/
930 case 'h':
931 /* Grab a bunch of headers */
932 if(cnt != 2)
933 goto jearg;
935 grab_headers(n_LEXINPUT_CTX_COMPOSE, hp,
936 (GTO | GSUBJECT | GCC | GBCC),
937 (ok_blook(bsdcompat) && ok_blook(bsdorder)));
938 while(hp->h_to == NULL);
939 break;
940 case 'H':
941 /* Grab extra headers */
942 if(cnt != 2)
943 goto jearg;
945 grab_headers(n_LEXINPUT_CTX_COMPOSE, hp, GEXTRA, 0);
946 while(check_from_and_sender(hp->h_from, hp->h_sender) == NULL);
947 break;
948 case 't':
949 /* Add to the To: list */
950 if(cnt == 2)
951 goto jearg;
952 hp->h_to = cat(hp->h_to,
953 checkaddrs(lextract(&linebuf[3], GTO | GFULL), EACM_NORMAL,
954 NULL));
955 break;
956 case 's':
957 /* Set the Subject list */
958 if(cnt == 2)
959 goto jearg;
960 /* Subject:; take care for Debian #419840 and strip any \r and \n */
961 if(n_anyof_cp("\n\r", hp->h_subject = savestr(&linebuf[3]))){
962 char *xp;
964 n_err(_("-s: normalizing away invalid ASCII NL / CR bytes\n"));
965 for(xp = hp->h_subject; *xp != '\0'; ++xp)
966 if(*xp == '\n' || *xp == '\r')
967 *xp = ' ';
969 break;
970 case '@':
971 /* Edit the attachment list */
972 if(cnt != 2)
973 append_attachments(n_LEXINPUT_CTX_COMPOSE, &hp->h_attach,
974 &linebuf[3]);
975 else
976 edit_attachments(n_LEXINPUT_CTX_COMPOSE, &hp->h_attach);
977 break;
978 case 'c':
979 /* Add to the CC list */
980 if(cnt == 2)
981 goto jearg;
982 hp->h_cc = cat(hp->h_cc,
983 checkaddrs(lextract(&linebuf[3], GCC | GFULL), EACM_NORMAL,
984 NULL));
985 break;
986 case 'b':
987 /* Add stuff to blind carbon copies list */
988 if(cnt == 2)
989 goto jearg;
990 hp->h_bcc = cat(hp->h_bcc,
991 checkaddrs(lextract(&linebuf[3], GBCC | GFULL), EACM_NORMAL,
992 NULL));
993 break;
994 case 'd':
995 if(cnt != 2)
996 goto jearg;
997 cp = n_getdeadletter();
998 if(0){
999 /*FALLTHRU*/
1000 case 'R':
1001 case 'r':
1002 case '<':
1003 /* Invoke a file: Search for the file name, then open it and copy
1004 * the contents to _coll_fp */
1005 if(cnt == 2){
1006 n_err(_("Interpolate what file?\n"));
1007 break;
1009 if(*(cp = &linebuf[3]) == '!'){
1010 insertcommand(_coll_fp, ++cp);
1011 goto jhistcont;
1013 if((cp = fexpand(cp, FEXP_LOCAL | FEXP_NOPROTO)) == NULL)
1014 break;
1016 if(is_dir(cp)){
1017 n_err(_("%s: is a directory\n"), n_shexp_quote_cp(cp, FAL0));
1018 break;
1020 if(_include_file(cp, &lc, &cc, FAL0, (c == 'R')) != 0){
1021 if(ferror(_coll_fp))
1022 goto jerr;
1023 break;
1025 printf(_("%s %d/%d\n"), n_shexp_quote_cp(cp, FAL0), lc, cc);
1026 break;
1027 case 'i':
1028 /* Insert a variable into the file */
1029 if(cnt == 2)
1030 goto jearg;
1031 if((cp = vok_vlook(&linebuf[3])) == NULL || *cp == '\0')
1032 break;
1033 if(putesc(cp, _coll_fp) < 0) /* TODO v15: user resp upon `set' time */
1034 goto jerr;
1035 if((options & OPT_INTERACTIVE) && putesc(cp, stdout) < 0)
1036 goto jerr;
1037 break;
1038 case 'a':
1039 case 'A':
1040 /* Insert the contents of a signature variable */
1041 if(cnt != 2)
1042 goto jearg;
1043 cp = (c == 'a') ? ok_vlook(sign) : ok_vlook(Sign);
1044 if(cp != NULL && *cp != '\0'){
1045 if(putesc(cp, _coll_fp) < 0) /* TODO v15: user upon `set' time */
1046 goto jerr;
1047 if((options & OPT_INTERACTIVE) && putesc(cp, stdout) < 0)
1048 goto jerr;
1050 break;
1051 case 'w':
1052 /* Write the message on a file */
1053 if(cnt == 2)
1054 goto jearg;
1055 if((cp = fexpand(&linebuf[3], FEXP_LOCAL | FEXP_NOPROTO)) == NULL){
1056 n_err(_("Write what file!?\n"));
1057 break;
1059 rewind(_coll_fp);
1060 if(exwrite(cp, _coll_fp, 1) < 0)
1061 goto jerr;
1062 break;
1063 case 'm':
1064 case 'M':
1065 case 'f':
1066 case 'F':
1067 case 'u':
1068 case 'U':
1069 /* Interpolate the named messages, if we are in receiving mail mode.
1070 * Does the standard list processing garbage. If ~f is given, we
1071 * don't shift over */
1072 if(cnt == 2)
1073 goto jearg;
1074 if(forward(&linebuf[3], _coll_fp, c) < 0)
1075 break;
1076 break;
1077 case 'p':
1078 /* Print current state of the message without altering anything */
1079 if(cnt != 2)
1080 goto jearg;
1081 print_collf(_coll_fp, hp);
1082 break;
1083 case '|':
1084 /* Pipe message through command. Collect output as new message */
1085 if(cnt == 2)
1086 goto jearg;
1087 rewind(_coll_fp);
1088 mespipe(&linebuf[3]);
1089 goto jhistcont;
1090 case 'v':
1091 case 'e':
1092 /* Edit the current message. 'e' -> use EDITOR, 'v' -> use VISUAL */
1093 if(cnt != 2)
1094 goto jearg;
1095 rewind(_coll_fp);
1096 mesedit(c, ok_blook(editheaders) ? hp : NULL);
1097 goto jhistcont;
1098 case '?':
1099 /* Last the lengthy help string. (Very ugly, but take care for
1100 * compiler supported string lengths :() */
1101 puts(_(
1102 "COMMAND ESCAPES (to be placed after a newline) excerpt:\n"
1103 "~. Commit and send message\n"
1104 "~: <command> Execute a mail command\n"
1105 "~<! <command> Insert output of command\n"
1106 "~@ [<files>] Edit attachment list\n"
1107 "~A Insert *Sign* variable (`~a': insert *sign*)\n"
1108 "~c <users> Add users to Cc: list (`~b': to Bcc:)\n"
1109 "~d Read in $DEAD (dead.letter)\n"
1110 "~e Edit message via $EDITOR"
1112 puts(_(
1113 "~F <msglist> Read in with headers, don't *indentprefix* lines\n"
1114 "~f <msglist> Like ~F, but honour `ignore' / `retain' configuration\n"
1115 "~H Edit From:, Reply-To: and Sender:\n"
1116 "~h Prompt for Subject:, To:, Cc: and \"blind\" Bcc:\n"
1117 "~i <variable> Insert a value and a newline\n"
1118 "~M <msglist> Read in with headers, *indentprefix* (`~m': `retain' etc.)\n"
1119 "~p Show current message compose buffer\n"
1120 "~r <file> Read in a file (`~R': *indentprefix* lines)"
1122 puts(_(
1123 "~s <subject> Set Subject:\n"
1124 "~t <users> Add users to To: list\n"
1125 "~u <msglist> Read in message(s) without headers (`~U': indent lines)\n"
1126 "~v Edit message via $VISUAL\n"
1127 "~w <file> Write message onto file\n"
1128 "~x Abort composition, discard message (`~q': save in $DEAD)\n"
1129 "~| <command> Pipe message through shell filter"
1131 if(cnt != 2)
1132 goto jearg;
1133 break;
1136 /* Finally place an entry in history as applicable */
1137 if(0){
1138 jhistcont:
1139 c = '\1';
1140 }else
1141 c = '\0';
1142 if(!(options & OPT_t_FLAG))
1143 n_tty_addhist(linebuf, TRU1);
1144 if(c != '\0')
1145 goto jcont;
1148 jout:
1149 /* Execute compose-leave TODO completely v15-compat intermediate!! */
1150 if((cp = ok_vlook(on_compose_leave)) != NULL){
1151 setup_from_and_sender(hp);
1152 call_compose_mode_hook(cp, &a_coll__hook_setter, hp);
1155 /* Final change to edit headers, if not already above */
1156 if (ok_blook(bsdcompat) || ok_blook(askatend)) {
1157 if (hp->h_cc == NULL && ok_blook(askcc))
1158 grab_headers(n_LEXINPUT_CTX_COMPOSE, hp, GCC, 1);
1159 if (hp->h_bcc == NULL && ok_blook(askbcc))
1160 grab_headers(n_LEXINPUT_CTX_COMPOSE, hp, GBCC, 1);
1162 if (hp->h_attach == NULL && ok_blook(askattach))
1163 edit_attachments(n_LEXINPUT_CTX_COMPOSE, &hp->h_attach);
1165 /* Add automatic receivers */
1166 if ((cp = ok_vlook(autocc)) != NULL && *cp != '\0')
1167 hp->h_cc = cat(hp->h_cc, checkaddrs(lextract(cp, GCC | GFULL),
1168 EACM_NORMAL, checkaddr_err));
1169 if ((cp = ok_vlook(autobcc)) != NULL && *cp != '\0')
1170 hp->h_bcc = cat(hp->h_bcc, checkaddrs(lextract(cp, GBCC | GFULL),
1171 EACM_NORMAL, checkaddr_err));
1172 if (*checkaddr_err != 0)
1173 goto jerr;
1175 if(options & OPT_Mm_FLAG)
1176 goto jskiptails;
1178 /* Place signature? */
1179 if((cp = ok_vlook(signature)) != NULL && *cp != '\0'){
1180 char const *cpq;
1182 if((cpq = fexpand(cp, FEXP_LOCAL | FEXP_NOPROTO)) == NULL){
1183 n_err(_("*signature* expands to invalid file: %s\n"),
1184 n_shexp_quote_cp(cp, FAL0));
1185 goto jerr;
1187 cpq = n_shexp_quote_cp(cp = cpq, FAL0);
1189 if((sigfp = Fopen(cp, "r")) == NULL){
1190 n_err(_("Can't open *signature* %s: %s\n"), cpq, strerror(errno));
1191 goto jerr;
1194 if(linebuf == NULL)
1195 linebuf = smalloc(linesize = LINESIZE);
1196 c = '\0';
1197 while((i = fread(linebuf, sizeof *linebuf, linesize, n_UNVOLATILE(sigfp)))
1198 > 0){
1199 c = linebuf[i - 1];
1200 if(i != fwrite(linebuf, sizeof *linebuf, i, _coll_fp))
1201 goto jerr;
1204 /* C99 */{
1205 FILE *x = n_UNVOLATILE(sigfp);
1206 int e = errno, ise = ferror(x);
1208 sigfp = NULL;
1209 Fclose(x);
1211 if(ise){
1212 n_err(_("Errors while reading *signature* %s: %s\n"),
1213 cpq, strerror(e));
1214 goto jerr;
1218 if(c != '\0' && c != '\n')
1219 putc('\n', _coll_fp);
1222 { char const *cp_obsolete = ok_vlook(NAIL_TAIL);
1224 if(cp_obsolete != NULL)
1225 OBSOLETE(_("please use *message-inject-tail* instead of *NAIL_TAIL*"));
1227 if((cp = ok_vlook(message_inject_tail)) != NULL ||
1228 (cp = cp_obsolete) != NULL){
1229 if(putesc(cp, _coll_fp) < 0)
1230 goto jerr;
1231 if((options & OPT_INTERACTIVE) && putesc(cp, stdout) < 0)
1232 goto jerr;
1236 jskiptails:
1237 if(fflush(_coll_fp))
1238 goto jerr;
1239 rewind(_coll_fp);
1241 jleave:
1242 if (linebuf != NULL)
1243 free(linebuf);
1244 sigfillset(&nset);
1245 sigprocmask(SIG_BLOCK, &nset, NULL);
1246 pstate &= ~PS_RECURSED;
1247 safe_signal(SIGINT, _coll_saveint);
1248 safe_signal(SIGHUP, _coll_savehup);
1249 sigprocmask(SIG_SETMASK, &oset, NULL);
1250 NYD_LEAVE;
1251 return _coll_fp;
1253 jerr:
1254 if(sigfp != NULL)
1255 Fclose(n_UNVOLATILE(sigfp));
1256 if (_coll_fp != NULL) {
1257 Fclose(_coll_fp);
1258 _coll_fp = NULL;
1260 assert(checkaddr_err != NULL);
1261 if(*checkaddr_err != 0)
1262 n_err(_("Some addressees were classified as \"hard error\"\n"));
1263 else if(*checkaddr_err != 0) /* TODO ugly: "sendout_error" now.. */
1264 n_err(_("Failed to prepare composed message (I/O error, disk full?)\n"));
1265 goto jleave;
1268 /* s-it-mode */