1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2  *@ Message content preparation (sendmp()).
3  *
4  * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5  * Copyright (c) 2012 - 2020 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
6  * SPDX-License-Identifier: BSD-3-Clause
7  */
8 /*
9  * Copyright (c) 1980, 1993
10  *      The Regents of the University of California.  All rights reserved.
11  *
12  * Redistribution and use in source and binary forms, with or without
13  * modification, are permitted provided that the following conditions
14  * are met:
15  * 1. Redistributions of source code must retain the above copyright
16  *    notice, this list of conditions and the following disclaimer.
17  * 2. Redistributions in binary form must reproduce the above copyright
18  *    notice, this list of conditions and the following disclaimer in the
19  *    documentation and/or other materials provided with the distribution.
20  * 3. Neither the name of the University nor the names of its contributors
21  *    may be used to endorse or promote products derived from this software
22  *    without specific prior written permission.
23  *
24  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34  * SUCH DAMAGE.
35  */
36 #undef su_FILE
37 #define su_FILE send
38 #define mx_SOURCE
39 
40 #ifndef mx_HAVE_AMALGAMATION
41 # include "mx/nail.h"
42 #endif
43 
44 #include <su/cs.h>
45 #include <su/mem.h>
46 
47 #include "mx/child.h"
48 #include "mx/colour.h"
49 #include "mx/file-streams.h"
50 /* TODO but only for creating chain! */
51 #include "mx/filter-quote.h"
52 #include "mx/iconv.h"
53 #include "mx/mime-type.h"
54 #include "mx/random.h"
55 #include "mx/sigs.h"
56 #include "mx/tty.h"
57 #include "mx/ui-str.h"
58 
59 /* TODO fake */
60 #include "su/code-in.h"
61 
62 static sigjmp_buf _send_pipejmp;
63 
64 /* Going for user display, print Part: info string */
65 static void          _print_part_info(FILE *obuf, struct mimepart const *mpp,
66                         struct n_ignore const *doitp, int level,
67                         struct quoteflt *qf, u64 *stats);
68 
69 /* Create a pipe; if mpp is not NULL, place some n_PIPEENV_* environment
70  * variables accordingly */
71 static FILE *a_send_pipefile(enum sendaction action,
72       struct mx_mimetype_handler *mhp, struct mimepart const *mpp, FILE **qbuf,
73       char const *tmpname, int term_infd);
74 
75 /* Call mime_write() as appropriate and adjust statistics */
76 su_SINLINE sz _out(char const *buf, uz len, FILE *fp,
77       enum conversion convert, enum sendaction action, struct quoteflt *qf,
78       u64 *stats, struct str *outrest, struct str *inrest);
79 
80 /* Simply (!) print out a LF (via qf if not NIL) */
81 static boole a_send_out_nl(FILE *fp, struct quoteflt *qf, u64 *stats);
82 
83 /* SIGPIPE handler */
84 static void          _send_onpipe(int signo);
85 
86 /* Send one part */
87 static int           sendpart(struct message *zmp, struct mimepart *ip,
88                         FILE *obuf, struct n_ignore const *doitp,
89                         struct quoteflt *qf, enum sendaction action,
90                         char **linedat, uz *linesize,
91                         u64 *stats, int level, boole *anyoutput/* XXX fake*/);
92 
93 /* Dependent on *mime-alternative-favour-rich* (favour_rich) do a tree walk
94  * and check whether there are any such down mpp, which is a .m_multipart of
95  * an /alternative container.. */
96 static boole        _send_al7ive_have_better(struct mimepart *mpp,
97                         enum sendaction action, boole want_rich);
98 
99 /* Get a file for an attachment */
100 static FILE *        newfile(struct mimepart *ip, boole volatile *ispipe);
101 
102 static boole a_send_pipecpy(FILE *pipebuf, FILE *outbuf, FILE *origobuf,
103       struct quoteflt *qf, u64 *stats);
104 
105 /* Output a reasonable looking status field */
106 static void          statusput(const struct message *mp, FILE *obuf,
107                         struct quoteflt *qf, u64 *stats);
108 static void          xstatusput(const struct message *mp, FILE *obuf,
109                         struct quoteflt *qf, u64 *stats);
110 
111 static void          put_from_(FILE *fp, struct mimepart *ip, u64 *stats);
112 
113 su_SINLINE sz
_out(char const * buf,uz len,FILE * fp,enum conversion convert,enum sendaction action,struct quoteflt * qf,u64 * stats,struct str * outrest,struct str * inrest)114 _out(char const *buf, uz len, FILE *fp, enum conversion convert, enum
115    sendaction action, struct quoteflt *qf, u64 *stats, struct str *outrest,
116    struct str *inrest)
117 {
118    sz size = 0, n;
119    int flags;
120    NYD_IN;
121 
122    /* TODO We should not need is_head() here, i think in v15 the actual Mailbox
123     * TODO subclass should detect From_ cases and either re-encode the part
124     * TODO in question, or perform From_ quoting as necessary!?!?!?  How?!? */
125    /* C99 */{
126       boole from_;
127 
128       if((action == SEND_MBOX || action == SEND_DECRYPT) &&
129             (from_ = is_head(buf, len, TRU1))){
130          if(from_ != TRUM1 || (mb.mb_active & MB_BAD_FROM_) ||
131                ok_blook(mbox_rfc4155)){
132             putc('>', fp);
133             ++size;
134          }
135       }
136    }
137 
138    flags = ((int)action & _TD_EOF);
139    action &= ~_TD_EOF;
140    n = mime_write(buf, len, fp,
141          action == SEND_MBOX ? CONV_NONE : convert,
142          flags | ((action == SEND_TODISP || action == SEND_TODISP_ALL ||
143             action == SEND_TODISP_PARTS ||
144             action == SEND_QUOTE || action == SEND_QUOTE_ALL)
145          ?  TD_ISPR | TD_ICONV
146          : (action == SEND_TOSRCH || action == SEND_TOPIPE ||
147                action == SEND_TOFILE)
148             ? TD_ICONV : (action == SEND_SHOW ? TD_ISPR : TD_NONE)),
149          qf, outrest, inrest);
150    if (n < 0)
151       size = n;
152    else if (n > 0) {
153       size += n;
154       if (stats != NULL)
155          *stats += size;
156    }
157    NYD_OU;
158    return size;
159 }
160 
161 static void
_print_part_info(FILE * obuf,struct mimepart const * mpp,struct n_ignore const * doitp,int level,struct quoteflt * qf,u64 * stats)162 _print_part_info(FILE *obuf, struct mimepart const *mpp, /* TODO strtofmt.. */
163    struct n_ignore const *doitp, int level, struct quoteflt *qf, u64 *stats)
164 {
165    char buf[64];
166    struct str ti, to;
167    boole want_ct, needsep;
168    struct str const *cpre, *csuf;
169    char const *cp;
170    NYD2_IN;
171 
172    cpre = csuf = NULL;
173 #ifdef mx_HAVE_COLOUR
174    if(mx_COLOUR_IS_ACTIVE()){
175       struct mx_colour_pen *cpen;
176 
177       cpen = mx_colour_pen_create(mx_COLOUR_ID_VIEW_PARTINFO, NULL);
178       if((cpre = mx_colour_pen_to_str(cpen)) != NIL)
179          csuf = mx_colour_reset_to_str();
180    }
181 #endif
182 
183    /* Take care of "99.99", i.e., 5 */
184    if ((cp = mpp->m_partstring) == NULL || cp[0] == '\0')
185       cp = n_qm;
186    if (level || (cp[0] != '1' && cp[1] == '\0') || (cp[0] == '1' && /* TODO */
187          cp[1] == '.' && cp[2] != '1')) /* TODO code should not look like so */
188       _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL, NULL);
189 
190    /* Part id, content-type, encoding, charset */
191    if (cpre != NULL)
192       _out(cpre->s, cpre->l, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL,NULL);
193    _out("[-- #", 5, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL,NULL);
194    _out(cp, su_cs_len(cp), obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL,NULL);
195 
196    to.l = snprintf(buf, sizeof buf, " %" PRIuZ "/%" PRIuZ " ",
197          (uz)mpp->m_lines, (uz)mpp->m_size);
198    _out(buf, to.l, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL,NULL);
199 
200    needsep = FAL0;
201 
202     if((cp = mpp->m_ct_type_usr_ovwr) != NULL){
203       _out("+", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL,NULL);
204       want_ct = TRU1;
205    }else if((want_ct = n_ignore_is_ign(doitp,
206          "content-type", sizeof("content-type") -1)))
207       cp = mpp->m_ct_type_plain;
208    if (want_ct && (to.l = su_cs_len(cp)) > 30 &&
209             su_cs_starts_with_case(cp, "application/")) {
210       uz const al = sizeof("appl../") -1, fl = sizeof("application/") -1;
211       uz i = to.l - fl;
212       char *x = n_autorec_alloc(al + i +1);
213 
214       su_mem_copy(x, "appl../", al);
215       su_mem_copy(x + al, cp + fl, i +1);
216       cp = x;
217       to.l = al + i;
218    }
219    if(cp != NULL){
220       _out(cp, to.l, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL,NULL);
221       needsep = TRU1;
222    }
223 
224    if(mpp->m_multipart == NULL/* TODO */ && (cp = mpp->m_ct_enc) != NULL &&
225          (!su_cs_cmp_case(cp, "7bit") ||
226           n_ignore_is_ign(doitp, "content-transfer-encoding",
227             sizeof("content-transfer-encoding") -1))){
228       if(needsep)
229          _out(", ", 2, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL,NULL);
230       if (to.l > 25 && !su_cs_cmp_case(cp, "quoted-printable"))
231          cp = "qu.-pr.";
232       _out(cp, su_cs_len(cp), obuf, CONV_NONE, SEND_MBOX, qf, stats,
233          NULL,NULL);
234       needsep = TRU1;
235    }
236 
237    if (want_ct && mpp->m_multipart == NULL/* TODO */ &&
238          (cp = mpp->m_charset) != NULL) {
239       if(needsep)
240          _out(", ", 2, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL,NULL);
241       _out(cp, su_cs_len(cp), obuf, CONV_NONE, SEND_MBOX, qf, stats,
242          NULL,NULL);
243    }
244 
245    needsep = !needsep;
246    _out(&" --]"[su_S(su_u8,needsep)], 4 - needsep,
247       obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL,NULL);
248    if (csuf != NULL)
249       _out(csuf->s, csuf->l, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL,NULL);
250    _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL,NULL);
251 
252    /* */
253    if (mpp->m_content_info & CI_MIME_ERRORS) {
254       if (cpre != NULL)
255          _out(cpre->s, cpre->l, obuf, CONV_NONE, SEND_MBOX, qf, stats,
256             NULL, NULL);
257       _out("[-- ", 4, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL, NULL);
258 
259       ti.l = su_cs_len(ti.s = n_UNCONST(_("Defective MIME structure")));
260       makeprint(&ti, &to);
261       to.l = delctrl(to.s, to.l);
262       _out(to.s, to.l, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL, NULL);
263       n_free(to.s);
264 
265       _out(" --]", 4, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL, NULL);
266       if (csuf != NULL)
267          _out(csuf->s, csuf->l, obuf, CONV_NONE, SEND_MBOX, qf, stats,
268             NULL, NULL);
269       _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL, NULL);
270    }
271 
272    /* Content-Description */
273    if (n_ignore_is_ign(doitp, "content-description", 19) &&
274          (cp = mpp->m_content_description) != NULL && *cp != '\0') {
275       if (cpre != NULL)
276          _out(cpre->s, cpre->l, obuf, CONV_NONE, SEND_MBOX, qf, stats,
277             NULL, NULL);
278       _out("[-- ", 4, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL, NULL);
279 
280       ti.l = su_cs_len(ti.s = n_UNCONST(mpp->m_content_description));
281       mime_fromhdr(&ti, &to, TD_ISPR | TD_ICONV);
282       _out(to.s, to.l, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL, NULL);
283       n_free(to.s);
284 
285       _out(" --]", 4, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL, NULL);
286       if (csuf != NULL)
287          _out(csuf->s, csuf->l, obuf, CONV_NONE, SEND_MBOX, qf, stats,
288             NULL, NULL);
289       _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL, NULL);
290    }
291 
292    /* Filename */
293    if (n_ignore_is_ign(doitp, "content-disposition", 19) &&
294          mpp->m_filename != NULL && *mpp->m_filename != '\0') {
295       if (cpre != NULL)
296          _out(cpre->s, cpre->l, obuf, CONV_NONE, SEND_MBOX, qf, stats,
297             NULL, NULL);
298       _out("[-- ", 4, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL, NULL);
299 
300       ti.l = su_cs_len(ti.s = mpp->m_filename);
301       makeprint(&ti, &to);
302       to.l = delctrl(to.s, to.l);
303       _out(to.s, to.l, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL, NULL);
304       n_free(to.s);
305 
306       _out(" --]", 4, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL, NULL);
307       if (csuf != NULL)
308          _out(csuf->s, csuf->l, obuf, CONV_NONE, SEND_MBOX, qf, stats,
309             NULL, NULL);
310       _out("\n", 1, obuf, CONV_NONE, SEND_MBOX, qf, stats, NULL, NULL);
311    }
312    NYD2_OU;
313 }
314 
315 static FILE *
a_send_pipefile(enum sendaction action,struct mx_mimetype_handler * mthp,struct mimepart const * mpp,FILE ** qbuf,char const * tmpname,int term_infd)316 a_send_pipefile(enum sendaction action, struct mx_mimetype_handler *mthp,
317       struct mimepart const *mpp, FILE **qbuf, char const *tmpname,
318       int term_infd)
319 {
320    static u32 reprocnt;
321    struct str s;
322    char const *env_addon[9 +8/*v15*/], *cp, *sh;
323    uz i;
324    FILE *rbuf;
325    NYD_IN;
326 
327    rbuf = *qbuf;
328 
329    if(action == SEND_QUOTE || action == SEND_QUOTE_ALL){
330       if((*qbuf = mx_fs_tmp_open("sendp", (mx_FS_O_RDWR | mx_FS_O_UNLINK |
331                mx_FS_O_REGISTER), NIL)) == NIL){
332          n_perr(_("tmpfile"), 0);
333          *qbuf = rbuf;
334       }
335    }
336 
337    if((mthp->mth_flags & mx_MIMETYPE_HDL_TYPE_MASK) == mx_MIMETYPE_HDL_PTF){
338       union {int (*ptf)(void); char const *sh;} u;
339 
340       fflush(*qbuf);
341       if (*qbuf != n_stdout) /* xxx never?  v15: it'll be a filter anyway */
342          fflush(n_stdout);
343 
344       u.ptf = mthp->mth_ptf;
345       if((rbuf = mx_fs_pipe_open(R(char*,-1), "W", u.sh, NIL, fileno(*qbuf))
346             ) == NIL)
347          goto jerror;
348       goto jleave;
349    }
350 
351    i = 0;
352 
353    /* MAILX_FILENAME */
354    if (mpp == NULL || (cp = mpp->m_filename) == NULL)
355       cp = n_empty;
356    env_addon[i++] = str_concat_csvl(&s, n_PIPEENV_FILENAME, "=", cp, NULL)->s;
357 env_addon[i++] = str_concat_csvl(&s, "NAIL_FILENAME", "=", cp, NULL)->s;/*v15*/
358 
359    /* MAILX_FILENAME_GENERATED *//* TODO pathconf NAME_MAX; but user can create
360     * TODO a file wherever he wants!  *Do* create a zero-size temporary file
361     * TODO and give *that* path as MAILX_FILENAME_TEMPORARY, clean it up once
362     * TODO the pipe returns?  Like this we *can* verify path/name issues! */
363    cp = mx_random_create_cp(MIN(NAME_MAX - 3, 16), &reprocnt);
364    env_addon[i++] = str_concat_csvl(&s, n_PIPEENV_FILENAME_GENERATED, "=", cp,
365          NULL)->s;
366 env_addon[i++] = str_concat_csvl(&s, "NAIL_FILENAME_GENERATED", "=", cp,/*v15*/
367       NULL)->s;
368 
369    /* MAILX_CONTENT{,_EVIDENCE} */
370    if (mpp == NULL || (cp = mpp->m_ct_type_plain) == NULL)
371       cp = n_empty;
372    env_addon[i++] = str_concat_csvl(&s, n_PIPEENV_CONTENT, "=", cp, NULL)->s;
373 env_addon[i++] = str_concat_csvl(&s, "NAIL_CONTENT", "=", cp, NULL)->s;/*v15*/
374 
375    if (mpp != NULL && mpp->m_ct_type_usr_ovwr != NULL)
376       cp = mpp->m_ct_type_usr_ovwr;
377    env_addon[i++] = str_concat_csvl(&s, n_PIPEENV_CONTENT_EVIDENCE, "=", cp,
378          NULL)->s;
379 env_addon[i++] = str_concat_csvl(&s, "NAIL_CONTENT_EVIDENCE", "=", cp,/* v15 */
380       NULL)->s;
381 
382    /* message/external-body, access-type=url */
383    env_addon[i++] = str_concat_csvl(&s, n_PIPEENV_EXTERNAL_BODY_URL, "=",
384          ((mpp != NULL && (cp = mpp->m_external_body_url) != NULL
385             ) ? cp : n_empty), NULL)->s;
386 
387    /* MAILX_FILENAME_TEMPORARY? */
388    if (tmpname != NULL) {
389       env_addon[i++] = str_concat_csvl(&s,
390             n_PIPEENV_FILENAME_TEMPORARY, "=", tmpname, NULL)->s;
391 env_addon[i++] = str_concat_csvl(&s,
392          "NAIL_FILENAME_TEMPORARY", "=", tmpname, NULL)->s;/* v15 */
393    }
394 
395    /* TODO we should include header information, especially From:, so
396     * TODO that same-origin can be tested for e.g. external-body!!! */
397 
398    env_addon[i] = NULL;
399    sh = ok_vlook(SHELL);
400 
401    if(mthp->mth_flags & mx_MIMETYPE_HDL_NEEDSTERM){
402       struct mx_child_ctx cc;
403       sigset_t nset;
404 
405       sigemptyset(&nset);
406       mx_child_ctx_setup(&cc);
407       cc.cc_flags = mx_CHILD_RUN_WAIT_LIFE;
408       cc.cc_mask = &nset;
409       cc.cc_fds[mx_CHILD_FD_IN] = term_infd;
410       cc.cc_cmd = sh;
411       cc.cc_args[0] = "-c";
412       cc.cc_args[1] = mthp->mth_shell_cmd;
413       cc.cc_env_addon = env_addon;
414 
415       rbuf = !mx_child_run(&cc) ? NIL : R(FILE*,-1);
416    }else{
417       rbuf = mx_fs_pipe_open(mthp->mth_shell_cmd, "W", sh, env_addon,
418             (mthp->mth_flags & mx_MIMETYPE_HDL_ASYNC ? mx_CHILD_FD_NULL
419              : fileno(*qbuf)));
420 jerror:
421       if(rbuf == NIL)
422          n_err(_("Cannot run MIME type handler: %s: %s\n"),
423             mthp->mth_msg, su_err_doc(su_err_no()));
424       else{
425          fflush(*qbuf);
426          if(*qbuf != n_stdout)
427             fflush(n_stdout);
428       }
429    }
430 jleave:
431    NYD_OU;
432    return rbuf;
433 }
434 
435 static boole
a_send_out_nl(FILE * fp,struct quoteflt * qf,u64 * stats)436 a_send_out_nl(FILE *fp, struct quoteflt *qf, u64 *stats){
437    boole rv;
438    NYD2_IN;
439 
440    if(qf == NIL)
441       qf = quoteflt_dummy();
442 
443    quoteflt_reset(qf, fp);
444    rv = (_out("\n", 1, fp, CONV_NONE, SEND_MBOX, qf, stats, NIL,NIL) > 0);
445    quoteflt_flush(qf);
446    NYD2_OU;
447    return rv;
448 }
449 
450 static void
_send_onpipe(int signo)451 _send_onpipe(int signo)
452 {
453    NYD; /* Signal handler */
454    UNUSED(signo);
455    siglongjmp(_send_pipejmp, 1);
456 }
457 
458 static sigjmp_buf       __sendp_actjmp; /* TODO someday.. */
459 static int              __sendp_sig; /* TODO someday.. */
460 static n_sighdl_t  __sendp_opipe;
461 static void
__sendp_onsig(int sig)462 __sendp_onsig(int sig) /* TODO someday, we won't need it no more */
463 {
464    NYD; /* Signal handler */
465    __sendp_sig = sig;
466    siglongjmp(__sendp_actjmp, 1);
467 }
468 
469 static int
sendpart(struct message * zmp,struct mimepart * ip,FILE * volatile obuf,struct n_ignore const * doitp,struct quoteflt * qf,enum sendaction volatile action,char ** linedat,uz * linesize,u64 * volatile stats,int volatile level,boole * anyoutput)470 sendpart(struct message *zmp, struct mimepart *ip, FILE * volatile obuf,
471    struct n_ignore const *doitp, struct quoteflt *qf,
472    enum sendaction volatile action,
473    char **linedat, uz *linesize, u64 * volatile stats, int volatile level,
474    boole *anyoutput)
475 {
476    int volatile rv = 0;
477    struct mx_mimetype_handler mth_stack, * volatile mthp;
478    struct str outrest, inrest;
479    boole hany, hign;
480    enum sendaction oaction;
481    char *cp;
482    char const * volatile tmpname = NULL;
483    uz linelen, cnt;
484    int volatile dostat, term_infd;
485    int c;
486    struct mimepart * volatile np;
487    FILE * volatile ibuf = NULL, * volatile pbuf = obuf,
488       * volatile qbuf = obuf, *origobuf = obuf;
489    enum conversion volatile convert;
490    n_sighdl_t volatile oldpipe = SIG_DFL;
491    NYD_IN;
492 
493    UNINIT(term_infd, 0);
494    UNINIT(cnt, 0);
495    oaction = action;
496    hany = hign = FAL0;
497 
498    quoteflt_reset(qf, obuf);
499 
500    if((ibuf = setinput(&mb, R(struct message*,ip), NEED_BODY)) == NIL){
501       rv = -1;
502       goto jleave;
503    }
504 
505    cnt = ip->m_size;
506    dostat = 0;
507 
508    if(action == SEND_TODISP || action == SEND_TODISP_ALL ||
509          action == SEND_TODISP_PARTS ||
510          action == SEND_QUOTE || action == SEND_QUOTE_ALL ||
511          action == SEND_TOSRCH){
512       dostat |= 4;
513 
514       if(ip->m_mimetype != mx_MIMETYPE_DISCARD && level != 0 &&
515             action != SEND_QUOTE && action != SEND_QUOTE_ALL){
516          _print_part_info(obuf, ip, doitp, level, qf, stats);
517          hany = TRU1;
518       }
519       if(ip->m_parent != NIL && ip->m_parent->m_mimetype == mx_MIMETYPE_822){
520          ASSERT(ip->m_flag & MNOFROM);
521          hign = TRU1;
522       }
523    }
524 
525    if(ip->m_mimetype == mx_MIMETYPE_DISCARD)
526       goto jheaders_skip;
527 
528    if (ip->m_mimetype == mx_MIMETYPE_PKCS7) {
529       if (ip->m_multipart &&
530             action != SEND_MBOX && action != SEND_RFC822 && action != SEND_SHOW)
531          goto jheaders_skip;
532    }
533 
534    if(!(ip->m_flag & MNOFROM))
535       while(cnt > 0 && (c = getc(ibuf)) != EOF){
536          --cnt;
537          if(c == '\n')
538             break;
539       }
540 
541    if(!hign && level == 0 && action != SEND_TODISP_PARTS){
542       if(doitp != NIL){
543          if(!n_ignore_is_ign(doitp, "status", 6))
544             dostat |= 1;
545          if(!n_ignore_is_ign(doitp, "x-status", 8))
546             dostat |= 2;
547       }else
548          dostat |= 3;
549    }
550 
551 jhdr_redo:
552    convert = (dostat & 4) ? CONV_FROMHDR : CONV_NONE;
553 
554    /* Work the headers */
555    /* C99 */{
556    struct n_string hl, *hlp;
557    uz lineno;
558    boole hstop;
559 
560    hlp = n_string_creat_auto(&hl); /* TODO pool [or, v15: filter!] */
561    /* Reserve three lines, still not enough for references and DKIM etc. */
562    hlp = n_string_reserve(hlp, MAX(MIME_LINELEN, MIME_LINELEN_RFC2047) * 3);
563    lineno = 0;
564 
565    for(hstop = FAL0; !hstop;){
566       uz lcnt;
567 
568       lcnt = cnt;
569       if(fgetline(linedat, linesize, &cnt, &linelen, ibuf, FAL0) == NIL)
570          break;
571       ++lineno;
572       if (linelen == 0 || (cp = *linedat)[0] == '\n')
573          /* If line is blank, we've reached end of headers */
574          break;
575       if(cp[linelen - 1] == '\n'){
576          cp[--linelen] = '\0';
577          if(linelen == 0)
578             break;
579       }
580 
581       /* Are we in a header? */
582       if(hlp->s_len > 0){
583          if(!su_cs_is_blank(*cp)){
584             fseek(ibuf, -(off_t)(lcnt - cnt), SEEK_CUR);
585             cnt = lcnt;
586             goto jhdrput;
587          }
588          goto jhdrpush;
589       }else{
590          /* Pick up the header field if we have one */
591          while((c = *cp) != ':' && !su_cs_is_space(c) && c != '\0')
592             ++cp;
593          for(;;){
594             if(!su_cs_is_space(c) || c == '\0')
595                break;
596             c = *++cp;
597          }
598          if(c != ':'){
599             /* That won't work with MIME when saving etc., before v15 */
600             if (lineno != 1)
601                /* XXX This disturbs, and may happen multiple times, and we
602                 * XXX cannot heal it for multipart except for display <v15 */
603                n_err(_("Malformed message: headers and body not separated "
604                   "(with empty line)\n"));
605             if(level != 0)
606                dostat &= ~(1 | 2);
607             fseek(ibuf, -(off_t)(lcnt - cnt), SEEK_CUR);
608             cnt = lcnt;
609             break;
610          }
611 
612          cp = *linedat;
613 jhdrpush:
614          if(!(dostat & 4)){
615             hlp = n_string_push_buf(hlp, cp, (u32)linelen);
616             hlp = n_string_push_c(hlp, '\n');
617          }else{
618             boole lblank, xblank;
619 
620             for(lblank = FAL0, lcnt = 0; lcnt < linelen; ++cp, ++lcnt){
621                char c8;
622 
623                c8 = *cp;
624                if(!(xblank = su_cs_is_blank(c8)) || !lblank){
625                   if((lblank = xblank))
626                      c8 = ' ';
627                   hlp = n_string_push_c(hlp, c8);
628                }
629             }
630          }
631          continue;
632       }
633 
634 jhdrput:
635       /* If it is an ignored header, skip it */
636       *(cp = su_mem_find(hlp->s_dat, ':', hlp->s_len)) = '\0';
637       /* C99 */{
638          uz i;
639 
640          i = P2UZ(cp - hlp->s_dat);
641          if(hign || (doitp != NULL && n_ignore_is_ign(doitp, hlp->s_dat, i)) ||
642                !su_cs_cmp_case(hlp->s_dat, "status") ||
643                !su_cs_cmp_case(hlp->s_dat, "x-status") ||
644                (action == SEND_MBOX &&
645                   (!su_cs_cmp_case(hlp->s_dat, "content-length") ||
646                    !su_cs_cmp_case(hlp->s_dat, "lines")) &&
647                 !ok_blook(keep_content_length)))
648             goto jhdrtrunc;
649       }
650 
651       /* Dump it */
652       if(!hany && (dostat & 4) && level > 0)
653          a_send_out_nl(obuf, NIL, stats);
654       mx_COLOUR(
655          if(mx_COLOUR_IS_ACTIVE())
656             mx_colour_put(mx_COLOUR_ID_VIEW_HEADER, hlp->s_dat);
657       )
658       *cp = ':';
659       _out(hlp->s_dat, hlp->s_len, obuf, convert, action, qf, stats, NIL,NIL);
660       mx_COLOUR(
661          if(mx_COLOUR_IS_ACTIVE())
662             mx_colour_reset();
663       )
664       if(dostat & 4)
665          a_send_out_nl(obuf, qf, stats);
666       hany = TRU1;
667 
668 jhdrtrunc:
669       hlp = n_string_trunc(hlp, 0);
670    }
671    hstop = TRU1;
672    if(hlp->s_len > 0)
673       goto jhdrput;
674 
675    if(hign /*|| (!hany && (dostat & (1 | 2)))*/){
676       a_send_out_nl(obuf, qf, stats);
677       if(hign)
678          goto jheaders_skip;
679    }
680 
681    /* We have reached end of headers, so eventually force out status: field
682     * and note that we are no longer in header fields */
683    if(dostat & 1){
684       statusput(zmp, obuf, qf, stats);
685       hany = TRU1;
686    }
687    if(dostat & 2){
688       xstatusput(zmp, obuf, qf, stats);
689       hany = TRU1;
690    }
691    if((hany /*&& doitp != n_IGNORE_ALL*/) ||
692          (oaction == SEND_DECRYPT && ip->m_parent != NIL &&
693           ip != ip->m_multipart) ||
694          ((oaction == SEND_QUOTE || oaction == SEND_QUOTE_ALL) &&
695           level != 0 && *anyoutput &&
696            (ip->m_mimetype != mx_MIMETYPE_DISCARD &&
697             ip->m_mimetype != mx_MIMETYPE_PKCS7 &&
698             ip->m_mimetype != mx_MIMETYPE_PKCS7 &&
699             ip->m_mimetype != mx_MIMETYPE_ALTERNATIVE &&
700             ip->m_mimetype != mx_MIMETYPE_RELATED &&
701             ip->m_mimetype != mx_MIMETYPE_DIGEST &&
702             ip->m_mimetype != mx_MIMETYPE_MULTI &&
703             ip->m_mimetype != mx_MIMETYPE_SIGNED &&
704             ip->m_mimetype != mx_MIMETYPE_ENCRYPTED))){
705          if(ip->m_mimetype != mx_MIMETYPE_822 || (dostat & 16)){
706             /*XXX (void)*/a_send_out_nl(obuf, NIL, stats);
707             hany = TRU1;
708          }
709       }
710    } /* C99 */
711 
712    quoteflt_flush(qf);
713 
714    if(ferror(ibuf) || ferror(obuf)){
715       rv = -1;
716       goto jleave;
717    }
718 
719    *anyoutput = hany;
720 jheaders_skip:
721    su_mem_set(mthp = &mth_stack, 0, sizeof mth_stack);
722 
723    if(oaction == SEND_MBOX){
724       convert = CONV_NONE;
725       goto jsend;
726    }
727 
728    switch (ip->m_mimetype) {
729    case mx_MIMETYPE_822:
730       switch (action) {
731       case SEND_TODISP_PARTS:
732          goto jleave;
733       case SEND_TODISP:
734       case SEND_TODISP_ALL:
735       case SEND_QUOTE:
736       case SEND_QUOTE_ALL:
737          if(!(dostat & 16)){ /* XXX */
738             dostat |= 16;
739             a_send_out_nl(obuf, qf, stats);
740             if(ok_blook(rfc822_body_from_)){
741                if(!qf->qf_bypass){
742                   uz i;
743 
744                   i = fwrite(qf->qf_pfix, sizeof *qf->qf_pfix, qf->qf_pfix_len,
745                         obuf);
746                   if(i == qf->qf_pfix_len && stats != NIL)
747                      *stats += i;
748                }
749                put_from_(obuf, ip->m_multipart, stats);
750                hany = TRU1;
751             }
752             goto jhdr_redo;
753          }
754          goto jmulti;
755       case SEND_TOSRCH:
756          goto jmulti;
757       case SEND_DECRYPT:
758          goto jmulti;
759       case SEND_TOFILE:
760       case SEND_TOPIPE:
761          put_from_(obuf, ip->m_multipart, stats);
762          /* FALLTHRU */
763       default:
764          break;
765       }
766       break;
767    case mx_MIMETYPE_TEXT_HTML:
768    case mx_MIMETYPE_TEXT:
769    case mx_MIMETYPE_TEXT_PLAIN:
770       switch (action) {
771       case SEND_TODISP:
772       case SEND_TODISP_ALL:
773       case SEND_TODISP_PARTS:
774       case SEND_QUOTE:
775       case SEND_QUOTE_ALL:
776          if((mthp = ip->m_handler) == NIL)
777             mx_mimetype_handler(mthp =
778                ip->m_handler = n_autorec_alloc(sizeof(*mthp)), ip, oaction);
779          switch(mthp->mth_flags & mx_MIMETYPE_HDL_TYPE_MASK){
780          case mx_MIMETYPE_HDL_NIL:
781             if(oaction != SEND_TODISP_PARTS)
782                break;
783             /* FALLTHRU */
784          case mx_MIMETYPE_HDL_MSG:/* TODO these should be part of partinfo! */
785             if(mthp->mth_msg.l > 0)
786                _out(mthp->mth_msg.s, mthp->mth_msg.l, obuf, CONV_NONE,
787                   SEND_MBOX, qf, stats, NULL, NULL);
788             /* We would print this as plain text, so better force going home */
789             goto jleave;
790          case mx_MIMETYPE_HDL_CMD:
791             if(oaction == SEND_TODISP_PARTS){
792                if(mthp->mth_flags & mx_MIMETYPE_HDL_COPIOUSOUTPUT)
793                   goto jleave;
794                else{
795                   /* Because: interactive OR batch mode, so */
796                   if(!mx_tty_yesorno(_("Run MIME handler for this part?"),
797                         su_state_has(su_STATE_REPRODUCIBLE)))
798                      goto jleave;
799                }
800             }
801             break;
802          case mx_MIMETYPE_HDL_TEXT:
803          case mx_MIMETYPE_HDL_PTF:
804             if(oaction == SEND_TODISP_PARTS)
805                goto jleave;
806             break;
807          default:
808             break;
809          }
810          /* FALLTRHU */
811       default:
812          break;
813       }
814       break;
815    case mx_MIMETYPE_DISCARD:
816       if(oaction != SEND_DECRYPT)
817          goto jleave;
818       break;
819    case mx_MIMETYPE_PKCS7:
820       if(oaction != SEND_RFC822 && oaction != SEND_SHOW &&
821             ip->m_multipart != NIL)
822          goto jmulti;
823       /* FALLTHRU */
824    default:
825       switch (action) {
826       case SEND_TODISP:
827       case SEND_TODISP_ALL:
828       case SEND_TODISP_PARTS:
829       case SEND_QUOTE:
830       case SEND_QUOTE_ALL:
831          if((mthp = ip->m_handler) == NIL)
832             mx_mimetype_handler(mthp = ip->m_handler =
833                n_autorec_alloc(sizeof(*mthp)), ip, oaction);
834          switch(mthp->mth_flags & mx_MIMETYPE_HDL_TYPE_MASK){
835          default:
836          case mx_MIMETYPE_HDL_NIL:
837             if (oaction != SEND_TODISP && oaction != SEND_TODISP_ALL &&
838                   (level != 0 || cnt))
839                goto jleave;
840             /* FALLTHRU */
841          case mx_MIMETYPE_HDL_MSG:/* TODO these should be part of partinfo! */
842             if(mthp->mth_msg.l > 0)
843                _out(mthp->mth_msg.s, mthp->mth_msg.l, obuf, CONV_NONE,
844                   SEND_MBOX, qf, stats, NULL, NULL);
845             /* We would print this as plain text, so better force going home */
846             goto jleave;
847          case mx_MIMETYPE_HDL_CMD:
848             if(oaction == SEND_TODISP_PARTS){
849                if(mthp->mth_flags & mx_MIMETYPE_HDL_COPIOUSOUTPUT)
850                   goto jleave;
851                else{
852                   /* Because: interactive OR batch mode, so */
853                   if(!mx_tty_yesorno(_("Run MIME handler for this part?"),
854                         su_state_has(su_STATE_REPRODUCIBLE)))
855                      goto jleave;
856                }
857             }
858             break;
859          case mx_MIMETYPE_HDL_TEXT:
860          case mx_MIMETYPE_HDL_PTF:
861             if(oaction == SEND_TODISP_PARTS)
862                goto jleave;
863             break;
864          }
865          break;
866       default:
867          break;
868       }
869       break;
870    case mx_MIMETYPE_ALTERNATIVE:
871       if ((oaction == SEND_TODISP || oaction == SEND_QUOTE) &&
872             !ok_blook(print_alternatives)) {
873          /* XXX This (a) should not remain (b) should be own fun
874           * TODO (despite the fact that v15 will do this completely differently
875           * TODO by having an action-specific "manager" that will traverse the
876           * TODO parsed MIME tree and decide for each part whether it'll be
877           * TODO displayed or not *before* we walk the tree for doing action */
878          struct mpstack {
879             struct mpstack *outer;
880             struct mimepart *mp;
881          } outermost, * volatile curr, * volatile mpsp;
882          enum {
883             _NONE,
884             _DORICH  = 1<<0,  /* We are looking for rich parts */
885             _HADPART = 1<<1,  /* Did print a part already */
886             _NEEDNL  = 1<<3   /* Need a visual separator */
887          } flags;
888          struct n_sigman smalter;
889 
890          (curr = &outermost)->outer = NULL;
891          curr->mp = ip;
892          flags = ok_blook(mime_alternative_favour_rich) ? _DORICH : _NONE;
893          if(!_send_al7ive_have_better(ip->m_multipart, action,
894                ((flags & _DORICH) != 0)))
895             flags ^= _DORICH;
896 
897          n_SIGMAN_ENTER_SWITCH(&smalter, n_SIGMAN_ALL) {
898          case 0:
899             break;
900          default:
901             rv = -1;
902             goto jalter_leave;
903          }
904 
905          for(np = ip->m_multipart;;){
906 jalter_redo:
907             level = -ABS(level);
908             for(; np != NIL; np = np->m_nextpart){
909                level = -ABS(level);
910                flags |= _NEEDNL;
911 
912                switch(np->m_mimetype){
913                case mx_MIMETYPE_ALTERNATIVE:
914                case mx_MIMETYPE_RELATED:
915                case mx_MIMETYPE_DIGEST:
916                case mx_MIMETYPE_SIGNED:
917                case mx_MIMETYPE_ENCRYPTED:
918                case mx_MIMETYPE_MULTI:
919                   np->m_flag &= ~MDISPLAY;
920                   mpsp = n_autorec_alloc(sizeof *mpsp);
921                   mpsp->outer = curr;
922                   mpsp->mp = np->m_multipart;
923                   curr->mp = np;
924                   curr = mpsp;
925                   np = mpsp->mp;
926                   flags &= ~_NEEDNL;
927                   goto jalter_redo;
928                default:
929                   if(!(np->m_flag & MDISPLAY)){
930                      if(np->m_mimetype != mx_MIMETYPE_DISCARD &&
931                            (action == SEND_TODISP ||
932                             action == SEND_TODISP_ALL ||
933                             action == SEND_TODISP_PARTS))
934                         _print_part_info(obuf, np, doitp, level, qf, stats);
935                      break;
936                   }
937 
938                   /* This thing we are going to do */
939                   quoteflt_flush(qf);
940                   flags |= _HADPART;
941                   flags &= ~_NEEDNL;
942                   rv = ABS(level) + 1;
943                   if(level < 0){
944                      level = -level;
945                      rv = -rv;
946                   }
947                   rv = sendpart(zmp, np, obuf, doitp, qf, oaction,
948                         linedat, linesize, stats, rv, anyoutput);
949                   quoteflt_reset(qf, origobuf);
950                   if (rv < 0)
951                      curr = &outermost; /* Cause overall loop termination */
952                   break;
953                }
954             }
955 
956             mpsp = curr->outer;
957             if (mpsp == NULL)
958                break;
959             curr = mpsp;
960             np = curr->mp->m_nextpart;
961          }
962 jalter_leave:
963          n_sigman_leave(&smalter, n_SIGMAN_VIPSIGS_NTTYOUT);
964          goto jleave;
965       }
966       /* FALLTHRU */
967    case mx_MIMETYPE_RELATED:
968    case mx_MIMETYPE_DIGEST:
969    case mx_MIMETYPE_SIGNED:
970    case mx_MIMETYPE_ENCRYPTED:
971    case mx_MIMETYPE_MULTI:
972       switch(action){
973       case SEND_TODISP:
974       case SEND_TODISP_ALL:
975       case SEND_TODISP_PARTS:
976       case SEND_QUOTE:
977       case SEND_QUOTE_ALL:
978       case SEND_TOFILE:
979       case SEND_TOPIPE:
980       case SEND_TOSRCH:
981       case SEND_DECRYPT:
982 jmulti:
983          if((oaction == SEND_TODISP || oaction == SEND_TODISP_ALL) &&
984              ip->m_multipart != NIL &&
985              ip->m_multipart->m_mimetype == mx_MIMETYPE_DISCARD &&
986              ip->m_multipart->m_nextpart == NULL) {
987             char const *x = _("[Missing multipart boundary - use `show' "
988                   "to display the raw message]\n");
989             _out(x, su_cs_len(x), obuf, CONV_NONE, SEND_MBOX, qf, stats,
990                NULL,NULL);
991          }
992 
993          level = -ABS(level);
994          for (np = ip->m_multipart; np != NULL; np = np->m_nextpart) {
995             boole volatile ispipe;
996 
997             if(np->m_mimetype == mx_MIMETYPE_DISCARD &&
998                   oaction != SEND_DECRYPT)
999                continue;
1000 
1001             ispipe = FAL0;
1002             switch(oaction){
1003             case SEND_TOFILE:
1004                if (np->m_partstring &&
1005                      np->m_partstring[0] == '1' && np->m_partstring[1] == '\0')
1006                   break;
1007                stats = NULL;
1008                /* TODO Always open multipart on /dev/null, it's a hack to be
1009                 * TODO able to dive into that structure, and still better
1010                 * TODO than asking the user for something stupid.
1011                 * TODO oh, wait, we did ask for a filename for this MIME mail,
1012                 * TODO and that outer container is useless anyway ;-P */
1013                if(np->m_multipart != NULL &&
1014                      np->m_mimetype != mx_MIMETYPE_822){
1015                   if((obuf = mx_fs_open(n_path_devnull, "w")) == NIL)
1016                      continue;
1017                }else if((obuf = newfile(np, &ispipe)) == NIL)
1018                   continue;
1019                if(ispipe){
1020                   oldpipe = safe_signal(SIGPIPE, &_send_onpipe);
1021                   if(sigsetjmp(_send_pipejmp, 1)){
1022                      rv = -1;
1023                      goto jpipe_close;
1024                   }
1025                }
1026                break;
1027             default:
1028                break;
1029             }
1030 
1031             quoteflt_flush(qf);
1032             {
1033                int nlvl = ABS(level) + 1;
1034                if(level < 0){
1035                   level = -level;
1036                   nlvl = -nlvl;
1037                }
1038                if(sendpart(zmp, np, obuf, doitp, qf, oaction, linedat,
1039                      linesize, stats, nlvl, anyoutput) < 0)
1040                   rv = -1;
1041             }
1042             quoteflt_reset(qf, origobuf);
1043 
1044             if(oaction == SEND_QUOTE){
1045                if(ip->m_mimetype != mx_MIMETYPE_RELATED)
1046                   break;
1047             }
1048             if(oaction == SEND_TOFILE && obuf != origobuf){
1049                if(!ispipe)
1050                   mx_fs_close(obuf);
1051                else {
1052 jpipe_close:
1053                   mx_fs_pipe_close(obuf, TRU1);
1054                   safe_signal(SIGPIPE, oldpipe);
1055                }
1056             }
1057          }
1058          goto jleave;
1059       default:
1060          break;
1061       }
1062       break;
1063    }
1064 
1065    /* Copy out message body */
1066    if (doitp == n_IGNORE_ALL && level == 0) /* skip final blank line */
1067       --cnt;
1068    switch (ip->m_mime_enc) {
1069    case MIMEE_BIN:
1070    case MIMEE_7B:
1071    case MIMEE_8B:
1072       convert = CONV_NONE;
1073       break;
1074    case MIMEE_QP:
1075       convert = CONV_FROMQP;
1076       break;
1077    case MIMEE_B64:
1078       switch(ip->m_mimetype){
1079       case mx_MIMETYPE_TEXT:
1080       case mx_MIMETYPE_TEXT_PLAIN:
1081       case mx_MIMETYPE_TEXT_HTML:
1082          convert = CONV_FROMB64_T;
1083          break;
1084       default:
1085          switch (mthp->mth_flags & mx_MIMETYPE_HDL_TYPE_MASK) {
1086          case mx_MIMETYPE_HDL_TEXT:
1087          case mx_MIMETYPE_HDL_PTF:
1088             convert = CONV_FROMB64_T;
1089             break;
1090          default:
1091             convert = CONV_FROMB64;
1092             break;
1093          }
1094          break;
1095       }
1096       break;
1097    default:
1098       convert = CONV_NONE;
1099    }
1100 
1101    /* TODO Unless we have filters, ensure iconvd==-1 so that mime.c:fwrite_td()
1102     * TODO cannot mess things up misusing outrest as line buffer */
1103 #ifdef mx_HAVE_ICONV
1104    if (iconvd != (iconv_t)-1) {
1105       n_iconv_close(iconvd);
1106       iconvd = (iconv_t)-1;
1107    }
1108 #endif
1109 
1110    if(oaction == SEND_DECRYPT || oaction == SEND_MBOX ||
1111          oaction == SEND_RFC822 || oaction == SEND_SHOW)
1112       convert = CONV_NONE;
1113 #ifdef mx_HAVE_ICONV
1114    else if((oaction == SEND_TODISP || oaction == SEND_TODISP_ALL ||
1115             oaction == SEND_TODISP_PARTS ||
1116             oaction == SEND_QUOTE || oaction == SEND_QUOTE_ALL ||
1117             oaction == SEND_TOSRCH || oaction == SEND_TOFILE) &&
1118          (ip->m_mimetype == mx_MIMETYPE_TEXT_PLAIN ||
1119             ip->m_mimetype == mx_MIMETYPE_TEXT_HTML ||
1120             ip->m_mimetype == mx_MIMETYPE_TEXT ||
1121             (mthp->mth_flags & mx_MIMETYPE_HDL_TYPE_MASK
1122                ) == mx_MIMETYPE_HDL_TEXT ||
1123             (mthp->mth_flags & mx_MIMETYPE_HDL_TYPE_MASK
1124                ) == mx_MIMETYPE_HDL_PTF)) {
1125       char const *tcs;
1126 
1127       tcs = ok_vlook(ttycharset);
1128       if (su_cs_cmp_case(tcs, ip->m_charset) &&
1129             su_cs_cmp_case(ok_vlook(charset_7bit), ip->m_charset)) {
1130          iconvd = n_iconv_open(tcs, ip->m_charset);
1131          if (iconvd == (iconv_t)-1 && su_err_no() == su_ERR_INVAL) {
1132             n_err(_("Cannot convert from %s to %s\n"), ip->m_charset, tcs);
1133             /*rv = 1; goto jleave;*/
1134          }
1135       }
1136    }
1137 #endif
1138 
1139    switch (mthp->mth_flags & mx_MIMETYPE_HDL_TYPE_MASK) {
1140    case mx_MIMETYPE_HDL_CMD:
1141       if(!(mthp->mth_flags & mx_MIMETYPE_HDL_COPIOUSOUTPUT)){
1142          if(oaction != SEND_TODISP_PARTS)
1143             goto jmthp_default;
1144          /* FIXME Ach, what a hack!  We need filters.. v15! */
1145          if(convert != CONV_FROMB64_T)
1146             action = SEND_TOPIPE;
1147       }
1148       /* FALLTHRU */
1149    case mx_MIMETYPE_HDL_PTF:
1150       tmpname = NULL;
1151       qbuf = obuf;
1152 
1153       term_infd = mx_CHILD_FD_PASS;
1154       if(mthp->mth_flags & (mx_MIMETYPE_HDL_TMPF | mx_MIMETYPE_HDL_NEEDSTERM)){
1155          struct mx_fs_tmp_ctx *fstcp;
1156          char const *pref;
1157          BITENUM_IS(u32,mx_fs_oflags) of;
1158 
1159          of = mx_FS_O_RDWR | mx_FS_O_REGISTER;
1160 
1161          if(!(mthp->mth_flags & mx_MIMETYPE_HDL_TMPF)){
1162             term_infd = 0;
1163             mthp->mth_flags |= mx_MIMETYPE_HDL_TMPF_FILL;
1164             of |= mx_FS_O_UNLINK;
1165             pref = "mtanonfill";
1166          }else{
1167             /* (async and unlink are mutual exclusive) */
1168             if(mthp->mth_flags & mx_MIMETYPE_HDL_TMPF_UNLINK)
1169                of |= mx_FS_O_REGISTER_UNLINK;
1170 
1171             if(mthp->mth_flags & mx_MIMETYPE_HDL_TMPF_NAMETMPL){
1172                pref = mthp->mth_tmpf_nametmpl;
1173                if(mthp->mth_flags & mx_MIMETYPE_HDL_TMPF_NAMETMPL_SUFFIX)
1174                   of |= mx_FS_O_SUFFIX;
1175             }else if(mthp->mth_flags & mx_MIMETYPE_HDL_TMPF_FILL)
1176                pref = "mimetypefill";
1177             else
1178                pref = "mimetype";
1179          }
1180 
1181          if((pbuf = mx_fs_tmp_open(pref, of,
1182                   (mthp->mth_flags & mx_MIMETYPE_HDL_TMPF ? &fstcp : NIL))
1183                ) == NIL)
1184             goto jesend;
1185 
1186          if(mthp->mth_flags & mx_MIMETYPE_HDL_TMPF)
1187             tmpname = fstcp->fstc_filename; /* In autorec storage! */
1188 
1189          if(mthp->mth_flags & mx_MIMETYPE_HDL_TMPF_FILL){
1190             action = SEND_TOPIPE;
1191             if(term_infd == 0)
1192                term_infd = fileno(pbuf);
1193             goto jsend;
1194          }
1195       }
1196 
1197 jpipe_for_real:
1198       pbuf = a_send_pipefile(oaction, mthp, ip, UNVOLATILE(FILE**,&qbuf),
1199             tmpname, term_infd);
1200       if(pbuf == NIL){
1201 jesend:
1202          pbuf = qbuf = NIL;
1203          rv = -1;
1204          goto jend;
1205       }else if((mthp->mth_flags & mx_MIMETYPE_HDL_NEEDSTERM) &&
1206             pbuf == R(FILE*,-1)){
1207          pbuf = qbuf = NIL;
1208          goto jend;
1209       }
1210       tmpname = NIL;
1211 
1212       action = SEND_TOPIPE;
1213       if (pbuf != qbuf) {
1214          oldpipe = safe_signal(SIGPIPE, &_send_onpipe);
1215          if (sigsetjmp(_send_pipejmp, 1))
1216             goto jend;
1217       }
1218       break;
1219 
1220    default:
1221 jmthp_default:
1222       mthp->mth_flags = mx_MIMETYPE_HDL_NIL;
1223       pbuf = qbuf = obuf;
1224       break;
1225    }
1226 
1227 jsend:
1228    {
1229    boole volatile eof;
1230    boole save_qf_bypass = qf->qf_bypass;
1231    u64 *save_stats = stats;
1232 
1233    if (pbuf != origobuf) {
1234       qf->qf_bypass = TRU1;/* XXX legacy (remove filter instead) */
1235       stats = NULL;
1236    }
1237    eof = FAL0;
1238    outrest.s = inrest.s = NULL;
1239    outrest.l = inrest.l = 0;
1240 
1241    if (pbuf == qbuf) {
1242       __sendp_sig = 0;
1243       __sendp_opipe = safe_signal(SIGPIPE, &__sendp_onsig);
1244       if (sigsetjmp(__sendp_actjmp, 1)) {
1245          n_pstate &= ~n_PS_BASE64_STRIP_CR;/* (but outer sigman protected) */
1246          if (outrest.s != NULL)
1247             n_free(outrest.s);
1248          if (inrest.s != NULL)
1249             n_free(inrest.s);
1250 #ifdef mx_HAVE_ICONV
1251          if (iconvd != (iconv_t)-1)
1252             n_iconv_close(iconvd);
1253 #endif
1254          safe_signal(SIGPIPE, __sendp_opipe);
1255          n_raise(__sendp_sig);
1256       }
1257    }
1258 
1259    quoteflt_reset(qf, pbuf);
1260    if(dostat & 4){
1261       if(pbuf == origobuf) /* TODO */
1262          n_pstate |= n_PS_BASE64_STRIP_CR;
1263    }
1264    while(!eof && fgetline(linedat, linesize, &cnt, &linelen, ibuf, FAL0)){
1265 joutln:
1266       if (_out(*linedat, linelen, pbuf, convert, action, qf, stats, &outrest,
1267             (action & _TD_EOF ? NULL : &inrest)) < 0 || ferror(pbuf)) {
1268          rv = -1; /* XXX Should bail away?! */
1269          break;
1270       }
1271    }
1272    if(ferror(ibuf))
1273       rv = -1;
1274    if(eof <= FAL0 && rv >= 0 && (outrest.l != 0 || inrest.l != 0)){
1275       linelen = 0;
1276       if(eof || inrest.l == 0)
1277          action |= _TD_EOF;
1278       eof = eof ? TRU1 : TRUM1;
1279       goto joutln;
1280    }
1281    n_pstate &= ~n_PS_BASE64_STRIP_CR;
1282    action &= ~_TD_EOF;
1283 
1284    /* TODO HACK: when sending to the display we yet get fooled if a message
1285     * TODO doesn't end in a newline, because of our input/output 1:1.
1286     * TODO This should be handled automatically by a display filter, then */
1287    if(rv >= 0 && !qf->qf_nl_last &&
1288          (action == SEND_TODISP || action == SEND_TODISP_ALL ||
1289           action == SEND_QUOTE || action == SEND_QUOTE_ALL))
1290       rv = quoteflt_push(qf, "\n", 1);
1291 
1292    quoteflt_flush(qf);
1293 
1294    if(!(qf->qf_bypass = save_qf_bypass))
1295       *anyoutput = TRU1;
1296    stats = save_stats;
1297 
1298    if (rv >= 0 && (mthp->mth_flags & mx_MIMETYPE_HDL_TMPF_FILL)) {
1299       mthp->mth_flags &= ~mx_MIMETYPE_HDL_TMPF_FILL;
1300       fflush(pbuf);
1301       really_rewind(pbuf);
1302       /* Don't fs_close() a tmp_open() thing due to FS_O_UNREGISTER_UNLINK++ */
1303       goto jpipe_for_real;
1304    }
1305 
1306    if (pbuf == qbuf)
1307       safe_signal(SIGPIPE, __sendp_opipe);
1308 
1309    if (outrest.s != NULL)
1310       n_free(outrest.s);
1311    if (inrest.s != NULL)
1312       n_free(inrest.s);
1313 
1314    }
1315 
1316 jend:
1317    if(pbuf != qbuf){
1318       mx_fs_pipe_close(pbuf, !(mthp->mth_flags & mx_MIMETYPE_HDL_ASYNC));
1319       safe_signal(SIGPIPE, oldpipe);
1320       if (rv >= 0 && qbuf != NULL && qbuf != obuf){
1321          *anyoutput = TRU1;
1322          if(!a_send_pipecpy(qbuf, obuf, origobuf, qf, stats))
1323             rv = -1;
1324       }
1325    }
1326 
1327 #ifdef mx_HAVE_ICONV
1328    if (iconvd != (iconv_t)-1)
1329       n_iconv_close(iconvd);
1330 #endif
1331 
1332 jleave:
1333    NYD_OU;
1334    return rv;
1335 }
1336 
1337 static boole
_send_al7ive_have_better(struct mimepart * mpp,enum sendaction action,boole want_rich)1338 _send_al7ive_have_better(struct mimepart *mpp, enum sendaction action,
1339       boole want_rich){
1340    struct mimepart *plain, *rich;
1341    boole rv;
1342    NYD_IN;
1343 
1344    rv = FAL0;
1345    plain = rich = NIL;
1346 
1347    for(; mpp != NIL; mpp = mpp->m_nextpart){
1348       switch(mpp->m_mimetype){
1349       case mx_MIMETYPE_TEXT_PLAIN:
1350          plain = mpp;
1351          if(!want_rich)
1352             goto jfound;
1353          continue;
1354       case mx_MIMETYPE_ALTERNATIVE:
1355       case mx_MIMETYPE_RELATED:
1356       case mx_MIMETYPE_DIGEST:
1357       case mx_MIMETYPE_SIGNED:
1358       case mx_MIMETYPE_ENCRYPTED:
1359       case mx_MIMETYPE_MULTI:
1360          /* Be simple and recurse */
1361          if(_send_al7ive_have_better(mpp->m_multipart, action, want_rich))
1362             goto jleave;
1363          continue;
1364       default:
1365          break;
1366       }
1367 
1368       if(mpp->m_handler == NIL)
1369          mx_mimetype_handler(mpp->m_handler =
1370             su_AUTO_ALLOC(sizeof(*mpp->m_handler)), mpp, action);
1371 
1372       switch(mpp->m_handler->mth_flags & mx_MIMETYPE_HDL_TYPE_MASK){
1373       case mx_MIMETYPE_HDL_TEXT:
1374          if(!want_rich)
1375             goto jfound;
1376          if(plain == NIL)
1377             plain = mpp;
1378          break;
1379       case mx_MIMETYPE_HDL_PTF:
1380          if(want_rich)
1381             goto jfound;
1382          if(rich == NIL ||
1383                (rich->m_handler->mth_flags & mx_MIMETYPE_HDL_TYPE_MASK
1384                   ) != mx_MIMETYPE_HDL_PTF)
1385             rich = mpp;
1386          break;
1387       case mx_MIMETYPE_HDL_CMD:
1388          if(mpp->m_handler->mth_flags & mx_MIMETYPE_HDL_COPIOUSOUTPUT){
1389             if(want_rich)
1390                goto jfound;
1391             if(rich == NIL)
1392                rich = mpp;
1393          }
1394          /* FALLTHRU */
1395       default:
1396          break;
1397       }
1398    }
1399 
1400    /* Without plain part at all, choose an existing rich no matter what */
1401    if((mpp = plain) != NIL || (mpp = rich) != NIL){
1402 jfound:
1403       mpp->m_flag |= MDISPLAY;
1404       ASSERT(mpp->m_parent != NIL);
1405       mpp->m_parent->m_flag |= MDISPLAY;
1406       rv = TRU1;
1407    }
1408 
1409 jleave:
1410    NYD_OU;
1411    return rv;
1412 }
1413 
1414 static FILE *
newfile(struct mimepart * ip,boole volatile * ispipe)1415 newfile(struct mimepart *ip, boole volatile *ispipe)
1416 {
1417    struct str in, out;
1418    char *f;
1419    FILE *fp;
1420    NYD_IN;
1421 
1422    f = ip->m_filename;
1423    *ispipe = FAL0;
1424 
1425    if (f != NULL && f != (char*)-1) {
1426       in.s = f;
1427       in.l = su_cs_len(f);
1428       makeprint(&in, &out);
1429       out.l = delctrl(out.s, out.l);
1430       f = savestrbuf(out.s, out.l);
1431       n_free(out.s);
1432    }
1433 
1434    /* In interactive mode, let user perform all kind of expansions as desired,
1435     * and offer |SHELL-SPEC pipe targets, too */
1436    if (n_psonce & n_PSO_INTERACTIVE) {
1437       struct str prompt;
1438       struct n_string shou, *shoup;
1439       char *f2, *f3;
1440 
1441       shoup = n_string_creat_auto(&shou);
1442 
1443       /* TODO If the current part is the first textpart the target
1444        * TODO is implicit from outer `write' etc! */
1445       /* I18N: Filename input prompt with file type indication */
1446       str_concat_csvl(&prompt, _("Enter filename for part "),
1447          (ip->m_partstring != NULL ? ip->m_partstring : n_qm),
1448          " (", ip->m_ct_type_plain, "): ", NULL);
1449 jgetname:
1450       while(mx_tty_getfilename(shoup,
1451             (n_GO_INPUT_CTX_DEFAULT | n_GO_INPUT_HIST_ADD), prompt.s,
1452             ((f != R(char*,-1) && f != NIL) ? n_shexp_quote_cp(f, FAL0) : NIL
1453             )) < TRU1){
1454       }
1455 
1456       f2 = n_string_cp(shoup);
1457       if(*f2 == '\0') {
1458          if(n_poption & n_PO_D_V)
1459             n_err(_("... skipping this\n"));
1460          n_string_gut(shoup);
1461          fp = NIL;
1462          goto jleave;
1463       }
1464 
1465       if(*f2 == '|')
1466          /* Pipes are expanded by the shell */
1467          f = f2;
1468       else if((f3 = fexpand(f2, (FEXP_LOCAL_FILE | FEXP_NVAR))) == NIL)
1469          /* (Error message written by fexpand()) */
1470          goto jgetname;
1471       else
1472          f = f3;
1473 
1474       n_string_gut(shoup);
1475    }
1476 
1477    if (f == NULL || f == (char*)-1 || *f == '\0')
1478       fp = NULL;
1479    else if(n_psonce & n_PSO_INTERACTIVE){
1480       if(*f == '|'){
1481          fp = mx_fs_pipe_open(&f[1], "w", ok_vlook(SHELL), NIL, -1);
1482          if(!(*ispipe = (fp != NIL)))
1483             n_perr(f, 0);
1484       }else if((fp = mx_fs_open(f, "w")) == NIL)
1485          n_err(_("Cannot open %s\n"), n_shexp_quote_cp(f, FAL0));
1486    }else{
1487       /* Be very picky in non-interactive mode: actively disallow pipes,
1488        * prevent directory separators, and any filename member that would
1489        * become expanded by the shell if the name would be echo(1)ed */
1490       if(su_cs_first_of(f, "/" n_SHEXP_MAGIC_PATH_CHARS) != su_UZ_MAX){
1491          char c;
1492 
1493          for(out.s = n_autorec_alloc((su_cs_len(f) * 3) +1), out.l = 0;
1494                (c = *f++) != '\0';)
1495             if(su_cs_find_c("/" n_SHEXP_MAGIC_PATH_CHARS, c)){
1496                out.s[out.l++] = '%';
1497                n_c_to_hex_base16(&out.s[out.l], c);
1498                out.l += 2;
1499             }else
1500                out.s[out.l++] = c;
1501          out.s[out.l] = '\0';
1502          f = out.s;
1503       }
1504 
1505       /* Avoid overwriting of existing files */
1506       while((fp = mx_fs_open(f, "wx")) == NIL){
1507          int e;
1508 
1509          if((e = su_err_no()) != su_ERR_EXIST){
1510             n_err(_("Cannot open %s: %s\n"),
1511                n_shexp_quote_cp(f, FAL0), su_err_doc(e));
1512             break;
1513          }
1514 
1515          if(ip->m_partstring != NULL)
1516             f = savecatsep(f, '#', ip->m_partstring);
1517          else
1518             f = savecat(f, "#.");
1519       }
1520    }
1521 jleave:
1522    NYD_OU;
1523    return fp;
1524 }
1525 
1526 static boole
a_send_pipecpy(FILE * pipebuf,FILE * outbuf,FILE * origobuf,struct quoteflt * qf,u64 * stats)1527 a_send_pipecpy(FILE *pipebuf, FILE *outbuf, FILE *origobuf,
1528       struct quoteflt *qf, u64 *stats){
1529    sz all_sz, i;
1530    uz linesize, linelen, cnt;
1531    char *line;
1532    boole rv;
1533    NYD_IN;
1534 
1535    rv = TRU1;
1536    mx_fs_linepool_aquire(&line, &linesize);
1537    quoteflt_reset(qf, outbuf);
1538 
1539    fflush_rewind(pipebuf);
1540    cnt = S(uz,fsize(pipebuf));
1541    all_sz = 0;
1542    while(fgetline(&line, &linesize, &cnt, &linelen, pipebuf, FAL0) != NIL){
1543       if((i = quoteflt_push(qf, line, linelen)) == -1){
1544          rv = FAL0;
1545          break;
1546       }
1547       all_sz += i;
1548    }
1549    if((i = quoteflt_flush(qf)) != -1){
1550       all_sz += i;
1551       if(all_sz > 0 && outbuf == origobuf && stats != NIL)
1552          *stats += all_sz;
1553    }else
1554       rv = FAL0;
1555 
1556    mx_fs_linepool_release(line, linesize);
1557 
1558    if(ferror(pipebuf))
1559       rv = FAL0;
1560    if(!mx_fs_close(pipebuf))
1561       rv = FAL0;
1562 
1563    NYD_OU;
1564    return rv;
1565 }
1566 
1567 static void
statusput(const struct message * mp,FILE * obuf,struct quoteflt * qf,u64 * stats)1568 statusput(const struct message *mp, FILE *obuf, struct quoteflt *qf,
1569    u64 *stats)
1570 {
1571    char statout[3], *cp = statout;
1572    NYD_IN;
1573 
1574    if (mp->m_flag & MREAD)
1575       *cp++ = 'R';
1576    if (!(mp->m_flag & MNEW))
1577       *cp++ = 'O';
1578    *cp = 0;
1579    if (statout[0]) {
1580       int i = fprintf(obuf, "%.*sStatus: %s\n", (int)qf->qf_pfix_len,
1581             (qf->qf_bypass ? NULL : qf->qf_pfix), statout);
1582       if (i > 0 && stats != NULL)
1583          *stats += i;
1584    }
1585    NYD_OU;
1586 }
1587 
1588 static void
xstatusput(const struct message * mp,FILE * obuf,struct quoteflt * qf,u64 * stats)1589 xstatusput(const struct message *mp, FILE *obuf, struct quoteflt *qf,
1590    u64 *stats)
1591 {
1592    char xstatout[4];
1593    char *xp = xstatout;
1594    NYD_IN;
1595 
1596    if (mp->m_flag & MFLAGGED)
1597       *xp++ = 'F';
1598    if (mp->m_flag & MANSWERED)
1599       *xp++ = 'A';
1600    if (mp->m_flag & MDRAFTED)
1601       *xp++ = 'T';
1602    *xp = 0;
1603    if (xstatout[0]) {
1604       int i = fprintf(obuf, "%.*sX-Status: %s\n", (int)qf->qf_pfix_len,
1605             (qf->qf_bypass ? NULL : qf->qf_pfix), xstatout);
1606       if (i > 0 && stats != NULL)
1607          *stats += i;
1608    }
1609    NYD_OU;
1610 }
1611 
1612 static void
put_from_(FILE * fp,struct mimepart * ip,u64 * stats)1613 put_from_(FILE *fp, struct mimepart *ip, u64 *stats)
1614 {
1615    char const *froma, *date, *nl;
1616    int i;
1617    NYD_IN;
1618 
1619    if (ip != NULL && ip->m_from != NULL) {
1620       froma = ip->m_from;
1621       date = n_time_ctime(ip->m_time, NULL);
1622       nl = "\n";
1623    } else {
1624       froma = ok_vlook(LOGNAME);
1625       date = time_current.tc_ctime;
1626       nl = n_empty;
1627    }
1628 
1629    mx_COLOUR(
1630       if(mx_COLOUR_IS_ACTIVE())
1631          mx_colour_put(mx_COLOUR_ID_VIEW_FROM_, NULL);
1632    )
1633    i = fprintf(fp, "From %s %s%s", froma, date, nl);
1634    mx_COLOUR(
1635       if(mx_COLOUR_IS_ACTIVE())
1636          mx_colour_reset();
1637    )
1638    if (i > 0 && stats != NULL)
1639       *stats += i;
1640    NYD_OU;
1641 }
1642 
1643 FL int
sendmp(struct message * mp,FILE * obuf,struct n_ignore const * doitp,char const * prefix,enum sendaction action,u64 * stats)1644 sendmp(struct message *mp, FILE *obuf, struct n_ignore const *doitp,
1645    char const *prefix, enum sendaction action, u64 *stats)
1646 {
1647    struct n_sigman linedat_protect;
1648    struct quoteflt qf;
1649    boole anyoutput;
1650    FILE *ibuf;
1651    enum mime_parse_flags mpf;
1652    struct mimepart *ip;
1653    uz linesize, cnt, size, i;
1654    char *linedat;
1655    int rv, c;
1656    NYD_IN;
1657 
1658    time_current_update(&time_current, TRU1);
1659    rv = -1;
1660    linedat = NULL;
1661    linesize = 0;
1662    quoteflt_init(&qf, prefix, (prefix == NULL));
1663 
1664    n_SIGMAN_ENTER_SWITCH(&linedat_protect, n_SIGMAN_ALL){
1665    case 0:
1666       break;
1667    default:
1668       goto jleave;
1669    }
1670 
1671    if (mp == dot && action != SEND_TOSRCH)
1672       n_pstate |= n_PS_DID_PRINT_DOT;
1673    if (stats != NULL)
1674       *stats = 0;
1675 
1676    /* First line is the From_ line, so no headers there to worry about */
1677    if ((ibuf = setinput(&mb, mp, NEED_BODY)) == NULL)
1678       goto jleave;
1679 
1680    cnt = mp->m_size;
1681    size = 0;
1682    {
1683    boole nozap;
1684    char const *cpre = n_empty, *csuf = n_empty;
1685 
1686 #ifdef mx_HAVE_COLOUR
1687    if(mx_COLOUR_IS_ACTIVE()){
1688       struct mx_colour_pen *cpen;
1689       struct str const *s;
1690 
1691       cpen = mx_colour_pen_create(mx_COLOUR_ID_VIEW_FROM_,NULL);
1692       if((s = mx_colour_pen_to_str(cpen)) != NIL){
1693          cpre = s->s;
1694          s = mx_colour_reset_to_str();
1695          if(s != NIL)
1696             csuf = s->s;
1697       }
1698    }
1699 #endif
1700 
1701    nozap = (doitp != n_IGNORE_ALL && doitp != n_IGNORE_FWD &&
1702          action != SEND_RFC822 &&
1703          !n_ignore_is_ign(doitp, "from_", sizeof("from_") -1));
1704    if (mp->m_flag & (MNOFROM | MBADFROM_)) {
1705       if (nozap)
1706          size = fprintf(obuf, "%s%.*sFrom %s %s%s\n",
1707                cpre, (int)qf.qf_pfix_len,
1708                (qf.qf_bypass ? n_empty : qf.qf_pfix), fakefrom(mp),
1709                n_time_ctime(mp->m_time, NULL), csuf);
1710    } else if (nozap) {
1711       if (!qf.qf_bypass) {
1712          i = fwrite(qf.qf_pfix, sizeof *qf.qf_pfix, qf.qf_pfix_len, obuf);
1713          if (i != qf.qf_pfix_len)
1714             goto jleave;
1715          size += i;
1716       }
1717 #ifdef mx_HAVE_COLOUR
1718       if(*cpre != '\0'){
1719          fputs(cpre, obuf);
1720          cpre = (char const*)0x1;
1721       }
1722 #endif
1723 
1724       while (cnt > 0 && (c = getc(ibuf)) != EOF) {
1725 #ifdef mx_HAVE_COLOUR
1726          if(c == '\n' && *csuf != '\0'){
1727             cpre = (char const*)0x1;
1728             fputs(csuf, obuf);
1729          }
1730 #endif
1731          putc(c, obuf);
1732          ++size;
1733          --cnt;
1734          if (c == '\n')
1735             break;
1736       }
1737 
1738 #ifdef mx_HAVE_COLOUR
1739       if(*csuf != '\0' && cpre != (char const*)0x1 && *cpre != '\0')
1740          fputs(csuf, obuf);
1741 #endif
1742    } else {
1743       while (cnt > 0 && (c = getc(ibuf)) != EOF) {
1744          --cnt;
1745          if (c == '\n')
1746             break;
1747       }
1748    }
1749    }
1750    if (size > 0 && stats != NULL)
1751       *stats += size;
1752 
1753    mpf = MIME_PARSE_NONE;
1754    if (action != SEND_MBOX && action != SEND_RFC822 && action != SEND_SHOW)
1755       mpf |= MIME_PARSE_PARTS | MIME_PARSE_DECRYPT;
1756    if(action == SEND_TODISP || action == SEND_TODISP_ALL ||
1757          action == SEND_QUOTE || action == SEND_QUOTE_ALL)
1758       mpf |= MIME_PARSE_FOR_USER_CONTEXT;
1759    if ((ip = mime_parse_msg(mp, mpf)) == NULL)
1760       goto jleave;
1761 
1762    anyoutput = FAL0;
1763    rv = sendpart(mp, ip, obuf, doitp, &qf, action, &linedat, &linesize,
1764          stats, 0, &anyoutput);
1765 
1766    n_sigman_cleanup_ping(&linedat_protect);
1767 jleave:
1768    n_pstate &= ~n_PS_BASE64_STRIP_CR;
1769    quoteflt_destroy(&qf);
1770    if(linedat != NULL)
1771       n_free(linedat);
1772    NYD_OU;
1773    n_sigman_leave(&linedat_protect, n_SIGMAN_VIPSIGS_NTTYOUT);
1774    return rv;
1775 }
1776 
1777 #include "su/code-ou.h"
1778 /* s-it-mode */
1779