From a0bce049c7b035456cb3e1eb825a06f52689c06f Mon Sep 17 00:00:00 2001 From: Steffen Nurpmeso Date: Thu, 24 May 2018 15:50:54 +0200 Subject: [PATCH] Add *forward-inject-tail*, *quote-inject-{head,tail}*, `~Q'.. and extend the meaning of *quote*, too: if that yet would produce the string "FROMHEADER wrote:\n\n" we will look for the newly introduced latter two variables. And as below. All of *{forward,quote}-inject-{head,tail}* will be passed through a newly introduced formatter function, a_coll_quote_message(). This will introduce a compose-mode specific set of formats, getting more and better as time goes by. The generated output honours *quote-fold*. This changes the meaning of *forward-inject-head* which has been introduced in v14.9.0 in that it will also be passed through the formatter. While here, introduce the new command escape `~Q' that performs full *quote* cycles on the given message list. --- cc-test.sh | 52 ++++++-- cmd-headers.c | 137 +++------------------ cmd-resend.c | 6 +- collect.c | 250 +++++++++++++++++++++++++++++---------- config.h | 6 +- head.c | 372 +++++++++++++++++++++++++++++++++++++++++++++------------- nail.1 | 162 +++++++++++++++++-------- nail.h | 5 +- nailfuns.h | 37 ++++-- send.c | 3 +- sendout.c | 13 +- 11 files changed, 707 insertions(+), 336 deletions(-) diff --git a/cc-test.sh b/cc-test.sh index 0a8ad2e8..1eb9e679 100755 --- a/cc-test.sh +++ b/cc-test.sh @@ -4200,9 +4200,16 @@ __EOT__ # Reply, forward, resend, Resend ${rm} "${MBOX}" - printf 'set from=f1@z\nm t1@z\nb1\n!.\nset from=f2@z\nm t2@z\nb2\n!.\n' | - ${MAILX} ${ARGS} -Snomemdebug -Sescape=! \ - -Smta=./.tmta.sh + printf '# + set from="f1@z + m t1@z +b1 +!. + set from="du " stealthmua=noagent + m t2@z +b2 +!. + ' | ${MAILX} ${ARGS} -Smta=./.tmta.sh -Snomemdebug -Sescape=! printf ' echo start: $? $! $^ERRNAME @@ -4220,12 +4227,25 @@ this is content of Reply 1 2 this is content of forward 1 !. echo forward 1: $? $! $^ERRNAME;echo;echo + wysh set forward-inject-head=$'"'"'-- \\ + forward (%%a)(%%d)(%%f)(%%i)(%%n)(%%r) --\\n'"'"' + wysh set forward-inject-tail=$'"'"'-- \\ + end of forward (%%i) --\\n'"'"' + forward 2 fwdex@am.ple +this is content of forward 2 +!. + echo forward 2: $? $! $^ERRNAME;echo;echo + set showname + forward 2 fwdex2@am.ple +this is content of forward 2, 2nd, with showname set +!. + echo forward 2, 2nd: $? $! $^ERRNAME;echo;echo resend 1 2 resendex@am.ple echo resend 1 2: $? $! $^ERRNAME;echo;echo Resend 1 2 Resendex@am.ple echo Resend 1 2: $? $! $^ERRNAME;echo;echo ' "${MBOX}" | - ${MAILX} ${ARGS} -Snomemdebug -Sescape=! \ + ${MAILX} ${ARGS} -Snomemdebug -Sescape=! -Sfullnames \ -Smta=./.tmta.sh \ -X' define bail { @@ -4308,7 +4328,7 @@ this is content of forward 1 ' > ./.tnotes 2>&1 check_ex0 4-estat ${cat} ./.tnotes >> "${MBOX}" - check 4 - "${MBOX}" '3038884027 7516' + check 4 - "${MBOX}" '2151712038 11184' t_epilog } @@ -4460,6 +4480,22 @@ t_quote_a_cmd_escapes() { !m 21: ~m 3 !m 3 +28-32: ~Q; 28: ~Q +!Q +29: ~Q 1 3 +!Q 1 3 +set quote +!:set quote +30: ~Q +!Q +31: ~Q 1 3 +!Q 1 3 +set quote-inject-head quote-inject-tail indentprefix +!:wysh set quote-inject-head=%%a quote-inject-tail=--%%r +32: ~Q +!Q +unset quote stuff +!:unset quote quote-inject-head quote-inject-tail 22: ~R ./.ttxt !R ./.ttxt 23: ~r ./.ttxt @@ -4482,8 +4518,8 @@ and i ~w rite this out to ./.tmsg ./.tmbox >./.tall 2>&1 check_ex0 2-estat ${cat} ./.tall >> "${MBOX}" - check 2 0 "${MBOX}" '639836485 3604' - check 3 - ./.tmsg '2112542907 2789' + check 2 0 "${MBOX}" '2613898218 4090' + check 3 - ./.tmsg '2771314896 3186' t_epilog } @@ -4606,7 +4642,7 @@ t_mime_types_load_control() { t_epilog } -t_lreply_futh_rth_etc() { +t_lreply_futh_rth_etc() { # TODO *fullnames*! t_prolog lreply_futh_rth_etc TRAP_EXIT_ADDONS="./.t*" diff --git a/cmd-headers.c b/cmd-headers.c index bbd3161d..b4a9cb49 100644 --- a/cmd-headers.c +++ b/cmd-headers.c @@ -41,10 +41,8 @@ static int _screen; -/* ... And place the extracted date in `date' */ -static void _parse_from_(struct message *mp, char date[n_FROM_DATEBUF]); - -/* Print out the header of a specific message +/* Print out the header of a specific message. + * time_current must be up-to-date when this is called. * a_cmd__hprf: handle *headline* * a_cmd__subject: -1 if Subject: yet seen, otherwise n_alloc()d Subject: * a_cmd__putindent: print out the indenting in threaded display @@ -69,23 +67,6 @@ static int a_cmd_scroll(char const *arg, bool_t onlynew); static int _headers(int msgspec); static void -_parse_from_(struct message *mp, char date[n_FROM_DATEBUF]) /* TODO line pool */ -{ - FILE *ibuf; - int hlen; - char *hline = NULL; - size_t hsize = 0; - NYD_ENTER; - - if ((ibuf = setinput(&mb, mp, NEED_HEADER)) != NULL && - (hlen = readline_restart(ibuf, &hline, &hsize, 0)) > 0) - extract_date_from_from_(hline, hlen, date); - if (hline != NULL) - n_free(hline); - NYD_LEAVE; -} - -static void a_cmd_print_head(size_t yetprinted, size_t msgno, FILE *f, bool_t threaded, bool_t subject_thread_compress){ enum {attrlen = 14}; @@ -133,18 +114,16 @@ static void a_cmd__hprf(size_t yetprinted, char const *fmt, size_t msgno, FILE *f, bool_t threaded, bool_t subject_thread_compress, char const *attrlist) { - char buf[16], datebuf[n_FROM_DATEBUF], cbuf[8], *cp, *subjline; - char const *datefmt, *date, *name, *fp n_COLOUR( COMMA *colo_tag ); + char buf[16], cbuf[8], *cp, *subjline; + char const *date, *name, *fp, *color_tag; int i, n, s, wleft, subjlen; struct message *mp; - time_t datet; n_COLOUR( struct n_colour_pen *cpen_new COMMA *cpen_cur COMMA *cpen_bas; ) enum { _NONE = 0, _ISDOT = 1<<0, - _ISADDR = 1<<1, - _ISTO = 1<<2, - _IFMT = 1<<3, + _ISTO = 1<<1, + _IFMT = 1<<2, _LOOP_MASK = (1<<4) - 1, _SFMT = 1<<4, /* It is 'S' */ /* For the simple byte-based counts in wleft and n we sometimes need @@ -157,85 +136,17 @@ a_cmd__hprf(size_t yetprinted, char const *fmt, size_t msgno, FILE *f, if ((mp = message + msgno - 1) == dot) flags = _ISDOT; - datet = mp->m_time; - date = NULL; - n_COLOUR( colo_tag = NULL; ) - - datefmt = ok_vlook(datefield); -jredo: - if (datefmt != NULL) { - fp = hfield1("date", mp);/* TODO use m_date field! */ - if (fp == NULL) { - datefmt = NULL; - goto jredo; - } - datet = rfctime(fp); - date = n_time_ctime(datet, NULL); - fp = ok_vlook(datefield_markout_older); - i = (*datefmt != '\0'); - if (fp != NULL) - i |= (*fp != '\0') ? 2 | 4 : 2; /* XXX no magics */ - - /* May we strftime(3)? */ - if (i & (1 | 4)){ - /* This localtime(3) should not fail since rfctime(3).. but .. */ - struct tm *tmp; - time_t datet2; - - /* TODO the datetime stuff is horror: mails should be parsed into - * TODO an object tree, and date: etc. have a datetime object, which - * TODO verifies upon parse time; then ALL occurrences of datetime are - * TODO valid all through the program; and: to_wire, to_user! */ - datet2 = datet; -jredo_localtime: - if((tmp = localtime(&datet2)) == NULL){ - datet2 = 0; - goto jredo_localtime; - } - memcpy(&time_current.tc_local, tmp, sizeof(*tmp)); - } - if ((i & 2) && - (UICMP(64,datet, >, time_current.tc_time + n_DATE_SECSDAY) || -#define _6M ((n_DATE_DAYSYEAR / 2) * n_DATE_SECSDAY) - UICMP(64,datet + _6M, <, time_current.tc_time))) { -#undef _6M - if ((datefmt = (i & 4) ? fp : NULL) == NULL) { - memset(datebuf, ' ', n_FROM_DATEBUF); /* xxx ur */ - memcpy(datebuf + 4, date + 4, 7); - datebuf[4 + 7] = ' '; - memcpy(datebuf + 4 + 7 + 1, date + 20, 4); - datebuf[4 + 7 + 1 + 4] = '\0'; - date = datebuf; - } - n_COLOUR( colo_tag = n_COLOUR_TAG_SUM_OLDER; ) - } else if ((i & 1) == 0) - datefmt = NULL; - } else if (datet == (time_t)0 && !(mp->m_flag & MNOFROM)) { - /* TODO eliminate this path, query the FROM_ date in setptr(), - * TODO all other codepaths do so by themselves ALREADY ????? - * TODO assert(mp->m_time != 0);, then - * TODO ALSO changes behaviour of datefield_markout_older */ - _parse_from_(mp, datebuf); - date = datebuf; - } else - date = n_time_ctime(datet, NULL); - - flags |= _ISADDR; - /* TODO This is wrong since it is not lextract()ed! */ - name = n_header_senderfield_of(mp); - if (name != NULL && ok_blook(showto) && n_is_myname(skin(name))) { - if ((cp = hfield1("to", mp)) != NULL) { - name = cp; + color_tag = NULL; + date = n_header_textual_date_info(mp, &color_tag); + /* C99 */{ + bool_t isto; + + n_header_textual_sender_info(mp, &cp, NULL, NULL, NULL, &isto); + name = cp; + if(isto) flags |= _ISTO; - } - } - if (name == NULL) { - name = n_empty; - flags &= ~_ISADDR; } - if (flags & _ISADDR) - name = ok_blook(showname) ? realname(name) : prstr(skin(name)); subjline = NULL; @@ -284,8 +195,8 @@ jredo_localtime: #ifdef HAVE_COLOUR if(n_COLOUR_IS_ACTIVE()){ if(flags & _ISDOT) - colo_tag = n_COLOUR_TAG_SUM_DOT; - cpen_bas = n_colour_pen_create(n_COLOUR_ID_SUM_HEADER, colo_tag); + color_tag = n_COLOUR_TAG_SUM_DOT; + cpen_bas = n_colour_pen_create(n_COLOUR_ID_SUM_HEADER, color_tag); n_colour_pen_put(cpen_new = cpen_cur = cpen_bas); }else cpen_new = cpen_bas = cpen_cur = NULL; @@ -331,7 +242,7 @@ jredo_localtime: n_COLOUR( if(n_COLOUR_IS_ACTIVE()) cpen_new = n_colour_pen_create(n_COLOUR_ID_SUM_DOTMARK, - colo_tag); + color_tag); ); if((n_psonce & n_PSO_UNICODE) && !ok_blook(headline_plain)){ if (c == '>') @@ -392,17 +303,6 @@ jputcb: #endif break; case 'd': - if (datefmt != NULL) { - i = strftime(datebuf, sizeof datebuf, datefmt, - &time_current.tc_local); - if (i != 0) - date = datebuf; - else - n_err(_("Ignoring date format, it is either empty or " - "excesses buffer size (%lu bytes)\n"), - (ul_i)sizeof(datebuf)); - datefmt = NULL; - } if (n == 0) n = 16; if (UICMP(32, n_ABS(n), >, wleft)) @@ -442,7 +342,8 @@ jputcb: if (threaded) { #ifdef HAVE_COLOUR if(n_COLOUR_IS_ACTIVE()){ - cpen_new = n_colour_pen_create(n_COLOUR_ID_SUM_THREAD, colo_tag); + cpen_new = n_colour_pen_create(n_COLOUR_ID_SUM_THREAD, + color_tag); if(cpen_new != cpen_cur) n_colour_pen_put(cpen_cur = cpen_new); } diff --git a/cmd-resend.c b/cmd-resend.c index 79c92ca2..c3db3a30 100644 --- a/cmd-resend.c +++ b/cmd-resend.c @@ -534,7 +534,7 @@ j_lt_redo: head.h_attach->a_content_description = _("Original message content"); } - if(mail1(&head, 1, mp, NULL, !!(hf & HF_RECIPIENT_RECORD), 0) != OKAY){ + if(mail1(&head, 1, mp, NULL, !!(hf & HF_RECIPIENT_RECORD), FAL0) != OKAY){ msgvec = NULL; goto jleave; } @@ -629,7 +629,7 @@ a_crese_Reply(int *msgvec, bool_t recipient_record){ head.h_attach->a_content_description = _("Original message content"); } - if(mail1(&head, 1, mp, NULL, recipient_record, 0) != OKAY){ + if(mail1(&head, 1, mp, NULL, recipient_record, FAL0) != OKAY){ msgvec = NULL; goto jleave; } @@ -704,7 +704,7 @@ a_crese_fwd(char *str, int recipient_record){ } rv = (mail1(&head, 1, (forward_as_attachment ? NULL : mp), NULL, - recipient_record, 1) != OKAY); /* reverse! */ + recipient_record, TRU1) != OKAY); /* reverse! */ jleave: NYD2_LEAVE; return rv; diff --git a/collect.c b/collect.c index 0dbcf9be..579bb932 100644 --- a/collect.c +++ b/collect.c @@ -40,6 +40,18 @@ # include "nail.h" #endif +struct a_coll_fmt_ctx{ /* xxx This is temporary until v15 has objects */ + char const *cfc_fmt; + FILE *cfc_fp; + struct message *cfc_mp; + char *cfc_cumul; + char *cfc_addr; + char *cfc_real; + char *cfc_full; + char *cfc_date; + char *cfc_msgid; /* Or NULL */ +}; + struct a_coll_ocs_arg{ sighandler_type coa_opipe; sighandler_type coa_oint; @@ -81,6 +93,13 @@ static si32_t a_coll_write(char const *name, FILE *fp, int f); /* *message-inject-head* */ static bool_t a_coll_message_inject_head(FILE *fp); +/* With bells and whistles */ +static bool_t a_coll_quote_message(FILE *fp, struct message *mp, bool_t isfwd); + +/* *{forward,quote}-inject-{head,tail}*. + * fmt may be NULL or the empty string, in which case no output is produced */ +static bool_t a_coll__fmt_inj(struct a_coll_fmt_ctx const *cfcp); + /* Parse off the message header from fp and store relevant fields in hp, * replace _coll_fp with a shiny new version without any header */ static bool_t a_coll_makeheader(FILE *fp, struct header *hp, @@ -435,6 +454,149 @@ a_coll_message_inject_head(FILE *fp){ } static bool_t +a_coll_quote_message(FILE *fp, struct message *mp, bool_t isfwd){ + struct a_coll_fmt_ctx cfc; + char const *cp; + struct n_ignore const *quoteitp; + enum sendaction action; + bool_t rv; + NYD_ENTER; + + rv = FAL0; + + if(isfwd || (cp = ok_vlook(quote)) != NULL){ + quoteitp = n_IGNORE_ALL; + action = SEND_QUOTE; + + if(isfwd){ + char const *cp_v15compat; + + if((cp_v15compat = ok_vlook(fwdheading)) != NULL) + n_OBSOLETE(_("please use *forward-inject-head* instead of " + "*fwdheading*")); + if((cp = ok_vlook(forward_inject_head)) == NULL && + (cp = cp_v15compat) == NULL) + cp = n_FORWARD_INJECT_HEAD; + quoteitp = n_IGNORE_FWD; + }else{ + if(!strcmp(cp, "noheading")){ + cp = NULL; + }else if(!strcmp(cp, "headers")){ + quoteitp = n_IGNORE_TYPE; + cp = NULL; + }else if(!strcmp(cp, "allheaders")){ + quoteitp = NULL; + action = SEND_QUOTE_ALL; + cp = NULL; + }else if((cp = ok_vlook(quote_inject_head)) == NULL) + cp = n_QUOTE_INJECT_HEAD; + } + /* We we pass through our formatter? */ + if((cfc.cfc_fmt = cp) != NULL){ + /* TODO In v15 [-textual_-]sender_info() should only create a list + * TODO of matching header objects, and the formatter should simply + * TODO iterate over this list and call OBJ->to_ui_str(FLAGS) or so. + * TODO For now fully initialize this thing once (grrrr!!) */ + cfc.cfc_fp = fp; + cfc.cfc_mp = mp; + n_header_textual_sender_info(cfc.cfc_mp = mp, &cfc.cfc_cumul, + &cfc.cfc_addr, &cfc.cfc_real, &cfc.cfc_full, NULL); + cfc.cfc_date = n_header_textual_date_info(mp, NULL); + /* C99 */{ + struct name *np; + + if((cp = hfield1("message-id", mp)) != NULL && + (np = lextract(cp, GREF)) != NULL) + cfc.cfc_msgid = np->n_name; + else + cp = (char*)-1; + } + + if(!a_coll__fmt_inj(&cfc) || fflush(fp)) + goto jleave; + } + + if(sendmp(mp, fp, quoteitp, (isfwd ? NULL : ok_vlook(indentprefix)), + action, NULL) < 0) + goto jleave; + + if(isfwd){ + if((cp = ok_vlook(forward_inject_tail)) == NULL) + cp = n_FORWARD_INJECT_TAIL; + }else if(cp != NULL && (cp = ok_vlook(quote_inject_tail)) == NULL) + cp = n_QUOTE_INJECT_TAIL; + if((cfc.cfc_fmt = cp) != NULL && (!a_coll__fmt_inj(&cfc) || fflush(fp))) + goto jleave; + } + + rv = TRU1; +jleave: + NYD_LEAVE; + return rv; +} + +static bool_t +a_coll__fmt_inj(struct a_coll_fmt_ctx const *cfcp){ + struct quoteflt qf; + struct n_string s_b, *sp; + char c; + char const *fmt; + NYD_ENTER; + + if((fmt = cfcp->cfc_fmt) == NULL || *fmt == '\0') + goto jleave; + + sp = n_string_book(n_string_creat_auto(&s_b), 127); + + while((c = *fmt++) != '\0'){ + if(c != '%' || (c = *fmt++) == '%'){ +jwrite_char: + sp = n_string_push_c(sp, c); + }else switch(c){ + case 'a': + sp = n_string_push_cp(sp, cfcp->cfc_addr); + break; + case 'd': + sp = n_string_push_cp(sp, cfcp->cfc_date); + break; + case 'f': + sp = n_string_push_cp(sp, cfcp->cfc_full); + break; + case 'i': + if(cfcp->cfc_msgid != NULL) + sp = n_string_push_cp(sp, cfcp->cfc_msgid); + break; + case 'n': + sp = n_string_push_cp(sp, cfcp->cfc_cumul); + break; + case 'r': + sp = n_string_push_cp(sp, cfcp->cfc_real); + break; + case '\0': + --fmt; + c = '%'; + goto jwrite_char; + default: + n_err(_("*{forward,quote}-inject-{head,tail}*: " + "unknown format: %c (in: %s)\n"), + c, n_shexp_quote_cp(cfcp->cfc_fmt, FAL0)); + goto jwrite_char; + } + } + + quoteflt_init(&qf, NULL, FAL0); + quoteflt_reset(&qf, cfcp->cfc_fp); + if(quoteflt_push(&qf, sp->s_dat, sp->s_len) < 0 || quoteflt_flush(&qf) < 0) + cfcp = NULL; + quoteflt_destroy(&qf); + + /*n_string_gut(sp);*/ +jleave: + NYD_LEAVE; + return (cfcp != NULL); +} + +static bool_t a_coll_makeheader(FILE *fp, struct header *hp, si8_t *checkaddr_err, bool_t do_delayed_due_t) { @@ -631,13 +793,20 @@ a_coll_forward(char const *ms, FILE *fp, int f) fprintf(n_stdout, _("Interpolating:")); srelax_hold(); - for (; *msgvec != 0; ++msgvec) { - struct message *mp = message + *msgvec - 1; + for(; *msgvec != 0; ++msgvec){ + struct message *mp; + mp = &message[*msgvec - 1]; touch(mp); + fprintf(n_stdout, " %d", *msgvec); fflush(n_stdout); - if (sendmp(mp, fp, itp, tabst, action, NULL) < 0) { + if(f == 'Q'){ + if(!a_coll_quote_message(fp, mp, FAL0)){ + rv = n_ERR_IO; + break; + } + }else if(sendmp(mp, fp, itp, tabst, action, NULL) < 0){ n_perr(_("forward: temporary mail file"), 0); rv = n_ERR_IO; break; @@ -1775,11 +1944,10 @@ n_temporary_compose_hook_varset(void *arg){ /* TODO v15: drop */ } FL FILE * -collect(struct header *hp, int printheaders, struct message *mp, - char const *quotefile, int doprefix, si8_t *checkaddr_err) +n_collect(struct header *hp, int printheaders, struct message *mp, + char const *quotefile, bool_t is_fwding, si8_t *checkaddr_err) { struct n_string s, * volatile sp; - struct n_ignore const *quoteitp; struct a_coll_ocs_arg *coap; int c; int volatile t, eofcnt, getfields; @@ -1795,7 +1963,6 @@ collect(struct header *hp, int printheaders, struct message *mp, char const *cp, *cp_base, * volatile coapm, * volatile ifs_saved; size_t i, linesize; /* TODO line pool */ long cnt; - enum sendaction action; sigset_t oset, nset; FILE * volatile sigfp; NYD_ENTER; @@ -1878,46 +2045,8 @@ collect(struct header *hp, int printheaders, struct message *mp, goto jerr; /* Quote an original message */ - if (mp != NULL && (doprefix || (cp = ok_vlook(quote)) != NULL)) { - quoteitp = n_IGNORE_ALL; - action = SEND_QUOTE; - if (doprefix) { - char const *cp_v15compat; - - quoteitp = n_IGNORE_FWD; - - if((cp_v15compat = ok_vlook(fwdheading)) != NULL) - n_OBSOLETE(_("please use *forward-inject-head*, " - "not *fwdheading*")); - cp = ok_vlook(forward_inject_head); - if(cp == NULL && (cp = cp_v15compat) == NULL) - cp = "-------- Original Message --------"; - if (*cp != '\0' && fprintf(_coll_fp, "%s\n", cp) < 0) - goto jerr; - } else if (!strcmp(cp, "noheading")) { - /*EMPTY*/; - } else if (!strcmp(cp, "headers")) { - quoteitp = n_IGNORE_TYPE; - } else if (!strcmp(cp, "allheaders")) { - quoteitp = NULL; - action = SEND_QUOTE_ALL; - } else { - cp = hfield1("from", mp); - if (cp != NULL && (cnt = (long)strlen(cp)) > 0) { - if (xmime_write(cp, cnt, _coll_fp, CONV_FROMHDR, TD_NONE, - NULL, NULL) < 0) - goto jerr; - if (fprintf(_coll_fp, _(" wrote:\n\n")) < 0) - goto jerr; - } - } - if (fflush(_coll_fp)) - goto jerr; - if (sendmp(mp, _coll_fp, quoteitp, - (doprefix ? NULL : ok_vlook(indentprefix)), - action, NULL) < 0) - goto jerr; - } + if(mp != NULL && !a_coll_quote_message(_coll_fp, mp, is_fwding)) + goto jerr; } if (quotefile != NULL) { @@ -2165,28 +2294,27 @@ jearg: "COMMAND ESCAPES (to be placed after a newline) excerpt:\n" "~. Commit and send message\n" "~: Execute an internal command\n" -"~< Insert (\"~\" inserts shell command)\n" -"~@ [] Edit[/Add] attachments (file[=input-charset[#output-charset]])\n" +"~< Insert (\"~\": insert shell command)\n" +"~@ [] Edit [Add] attachments (file[=in-charset[#out-charset]])\n" "~c Add users to Cc: list (`~b': to Bcc:)\n" -"~d Read in $DEAD (dead.letter)\n" -"~e Edit message via $EDITOR\n" -"~F Read in with headers, do not *indentprefix* lines\n" +"~e, ~v Edit message via $EDITOR / $VISUAL\n" ), n_stdout); fputs(_( -"~f Like ~F, but honour `ignore' / `retain' configuration\n" +"~F Read in with headers, do not *indentprefix* lines\n" +"~f Like `~F', but honour `headerpick' configuration\n" "~H Edit From:, Reply-To: and Sender:\n" -"~h Prompt for Subject:, To:, Cc: and \"blind\" Bcc:\n" -"~i Insert a value and a newline\n" -"~M Read in with headers, *indentprefix* (`~m': `retain' etc.)\n" +"~h Prompt for Subject:, To:, Cc: and Bcc:\n" +"~i Insert a value and a newline (`~I': insert value)\n" +"~M Read in with headers, *indentprefix* (`~m': use `headerpick')\n" "~p Show current message compose buffer\n" -"~r Insert (`~R': likewise, but *indentprefix* lines)\n" -" may also be <- [HERE-DELIMITER]>\n" +"~Q Read in using normal *quote* algorithm\n" ), n_stdout); fputs(_( +"~r Insert (`~R': *indentprefix* lines)\n" +" may also be <- [HERE-DELIMITER]>\n" "~s Set Subject:\n" "~t Add users to To: list\n" -"~u Read in message(s) without headers (`~U': indent lines)\n" -"~v Edit message via $VISUAL\n" +"~u Read in without headers (`~U': *indentprefix* lines)\n" "~w Write message onto file\n" "~x Abort composition, discard message (`~q': save in $DEAD)\n" "~| Pipe message through shell filter\n" @@ -2369,6 +2497,7 @@ jearg: case 'f': case 'M': case 'm': + case 'Q': case 'U': case 'u': /* Interpolate the named messages, if we are in receiving mail mode. @@ -2434,6 +2563,7 @@ jIi_putesc: n_pstate_err_no = n_ERR_NONE; /* XXX */ n_pstate_ex_no = 0; /* XXX */ break; + /* case 'Q': <> 'F' */ case 'q': case 'x': /* Force a quit, act like an interrupt had happened */ diff --git a/config.h b/config.h index c3dbfd76..ccb86113 100644 --- a/config.h +++ b/config.h @@ -44,12 +44,16 @@ #define ERRORS_MAX 1000 /* Maximum error ring entries TODO configable*/ #define n_ESCAPE "~" /* Default escape for sending (POSIX) */ #define FTMP_OPEN_TRIES 10 /* Maximum number of Ftmp() open(2) tries */ +#define n_FORWARD_INJECT_HEAD "-------- Original Message --------\n" /* DOC! */ +#define n_FORWARD_INJECT_TAIL NULL /* DOC! */ #define HSHSIZE 23 /* Hash prime TODO make dynamic, obsolete */ #define n_IMAP_DELIM "/." /* Directory separator ([0] == replacer, too) */ #define n_MAILDIR_SEPARATOR ':' /* Flag separator character */ #define n_MAXARGC 512 /* Maximum list of raw strings TODO dyn vector! */ #define n_ALIAS_MAXEXP 25 /* Maximum expansion of aliases */ #define n_PATH_DEVNULL "/dev/null" /* Note: manual uses /dev/null as such */ +#define n_QUOTE_INJECT_HEAD "%f wrote:\n\n" /* DOC! */ +#define n_QUOTE_INJECT_TAIL NULL /* DOC! */ #define REFERENCES_MAX 20 /* Maximum entries in References: */ #define n_SIGSUSPEND_NOT_WAITPID 1 /* Not waitpid(2), but sigsuspend(2) */ #define n_UNIREPL "\xEF\xBF\xBD" /* Unicode replacement 0xFFFD in UTF-8 */ @@ -84,7 +88,7 @@ /* Supported IDNA implementations */ #define n_IDNA_IMPL_LIBIDN2 0 #define n_IDNA_IMPL_LIBIDN 1 -#define n_IDNA_IMPL_IDNKIT 2 +#define n_IDNA_IMPL_IDNKIT 2 /* 1 + 2 */ /* Max readable line width TODO simply use BUFSIZ? */ #if BUFSIZ + 0 > 2560 diff --git a/head.c b/head.c index dd553e96..b3852bde 100644 --- a/head.c +++ b/head.c @@ -72,8 +72,15 @@ static struct cmatch_data const _cmatch_data[] = { }; #define a_HEAD_DATE_MINLEN 21 +/* Savage extract date field from From_ line. linelen is convenience as line + * must be terminated (but it may end in a newline [sequence]). + * Return whether the From_ line was parsed successfully (-1 if the From_ line + * wasn't really RFC 4155 compliant) */ +static int a_head_extract_date_from_from_(char const *line, size_t linelen, + char datebuf[n_FROM_DATEBUF]); + /* Skip over "word" as found in From_ line */ -static char const * _from__skipword(char const *wp); +static char const *a_head__from_skipword(char const *wp); /* Match the date string against the date template (tp), return if match. * See _cmatch_data[] for template character description */ @@ -91,6 +98,9 @@ static void a_head_jdn_to_gregorian(size_t jdn, ui32_t *yp, ui32_t *mp, ui32_t *dp); #endif +/* ... And place the extracted date in `date' */ +static void a_head_parse_from_(struct message *mp, char date[n_FROM_DATEBUF]); + /* Convert the domain part of a skinned address to IDNA. * If an error occurs before Unicode information is available, revert the IDNA * error to a normal CHAR one so that the error message doesn't talk Unicode */ @@ -117,8 +127,81 @@ static int msgidnextc(char const **cp, int *status); static char const * nexttoken(char const *cp); +static int +a_head_extract_date_from_from_(char const *line, size_t linelen, + char datebuf[n_FROM_DATEBUF]) +{ + int rv; + char const *cp = line; + NYD_ENTER; + + rv = 1; + + /* "From " */ + cp = a_head__from_skipword(cp); + if (cp == NULL) + goto jerr; + /* "addr-spec " */ + cp = a_head__from_skipword(cp); + if (cp == NULL) + goto jerr; + if((cp[0] == 't' || cp[0] == 'T') && (cp[1] == 't' || cp[1] == 'T') && + (cp[2] == 'y' || cp[2] == 'Y')){ + cp = a_head__from_skipword(cp); + if (cp == NULL) + goto jerr; + } + /* It seems there are invalid MBOX archives in the wild, compare + * . http://bugs.debian.org/624111 + * . [Mutt] #3868: mutt should error if the imported mailbox is invalid + * What they do is that they obfuscate the address to "name at host", + * and even "name at host dot dom dot dom. + * The [Aa][Tt] is also RFC 733, so be tolerant */ + else if((cp[0] == 'a' || cp[0] == 'A') && (cp[1] == 't' || cp[1] == 'T') && + cp[2] == ' '){ + rv = -1; + cp += 3; +jat_dot: + cp = a_head__from_skipword(cp); + if (cp == NULL) + goto jerr; + if((cp[0] == 'd' || cp[0] == 'D') && (cp[1] == 'o' || cp[1] == 'O') && + (cp[2] == 't' || cp[2] == 'T') && cp[3] == ' '){ + cp += 4; + goto jat_dot; + } + } + + linelen -= PTR2SIZE(cp - line); + if (linelen < a_HEAD_DATE_MINLEN) + goto jerr; + if (cp[linelen - 1] == '\n') { + --linelen; + /* (Rather IMAP/POP3 only) */ + if (cp[linelen - 1] == '\r') + --linelen; + if (linelen < a_HEAD_DATE_MINLEN) + goto jerr; + } + if (linelen >= n_FROM_DATEBUF) + goto jerr; + +jleave: + memcpy(datebuf, cp, linelen); + datebuf[linelen] = '\0'; + NYD_LEAVE; + return rv; +jerr: + cp = _(""); + linelen = strlen(cp); + if (linelen >= n_FROM_DATEBUF) + linelen = n_FROM_DATEBUF; + rv = 0; + goto jleave; +} + static char const * -_from__skipword(char const *wp) +a_head__from_skipword(char const *wp) { char c = 0; NYD2_ENTER; @@ -283,6 +366,22 @@ a_head_jdn_to_gregorian(size_t jdn, ui32_t *yp, ui32_t *mp, ui32_t *dp){ } #endif /* 0 */ +static void +a_head_parse_from_(struct message *mp, char date[n_FROM_DATEBUF]){ + FILE *ibuf; + int hlen; + char *hline = NULL; /* TODO line pool */ + size_t hsize = 0; + NYD2_ENTER; + + if((ibuf = setinput(&mb, mp, NEED_HEADER)) != NULL && + (hlen = readline_restart(ibuf, &hline, &hsize, 0)) > 0) + a_head_extract_date_from_from_(hline, hlen, date); + if(hline != NULL) + n_free(hline); + NYD2_LEAVE; +} + #ifdef HAVE_IDNA static struct n_addrguts * a_head_idna_apply(struct n_addrguts *agp){ @@ -1191,86 +1290,13 @@ is_head(char const *linebuf, size_t linelen, bool_t check_rfc4155) NYD2_ENTER; if ((rv = (linelen >= 5 && !memcmp(linebuf, "From ", 5))) && check_rfc4155 && - (extract_date_from_from_(linebuf, linelen, date) <= 0 || + (a_head_extract_date_from_from_(linebuf, linelen, date) <= 0 || !_is_date(date))) rv = TRUM1; NYD2_LEAVE; return rv; } -FL int -extract_date_from_from_(char const *line, size_t linelen, - char datebuf[n_FROM_DATEBUF]) -{ - int rv; - char const *cp = line; - NYD_ENTER; - - rv = 1; - - /* "From " */ - cp = _from__skipword(cp); - if (cp == NULL) - goto jerr; - /* "addr-spec " */ - cp = _from__skipword(cp); - if (cp == NULL) - goto jerr; - if((cp[0] == 't' || cp[0] == 'T') && (cp[1] == 't' || cp[1] == 'T') && - (cp[2] == 'y' || cp[2] == 'Y')){ - cp = _from__skipword(cp); - if (cp == NULL) - goto jerr; - } - /* It seems there are invalid MBOX archives in the wild, compare - * . http://bugs.debian.org/624111 - * . [Mutt] #3868: mutt should error if the imported mailbox is invalid - * What they do is that they obfuscate the address to "name at host", - * and even "name at host dot dom dot dom. - * The [Aa][Tt] is also RFC 733, so be tolerant */ - else if((cp[0] == 'a' || cp[0] == 'A') && (cp[1] == 't' || cp[1] == 'T') && - cp[2] == ' '){ - rv = -1; - cp += 3; -jat_dot: - cp = _from__skipword(cp); - if (cp == NULL) - goto jerr; - if((cp[0] == 'd' || cp[0] == 'D') && (cp[1] == 'o' || cp[1] == 'O') && - (cp[2] == 't' || cp[2] == 'T') && cp[3] == ' '){ - cp += 4; - goto jat_dot; - } - } - - linelen -= PTR2SIZE(cp - line); - if (linelen < a_HEAD_DATE_MINLEN) - goto jerr; - if (cp[linelen - 1] == '\n') { - --linelen; - /* (Rather IMAP/POP3 only) */ - if (cp[linelen - 1] == '\r') - --linelen; - if (linelen < a_HEAD_DATE_MINLEN) - goto jerr; - } - if (linelen >= n_FROM_DATEBUF) - goto jerr; - -jleave: - memcpy(datebuf, cp, linelen); - datebuf[linelen] = '\0'; - NYD_LEAVE; - return rv; -jerr: - cp = _(""); - linelen = strlen(cp); - if (linelen >= n_FROM_DATEBUF) - linelen = n_FROM_DATEBUF; - rv = 0; - goto jleave; -} - FL void n_header_extract(FILE *fp, struct header *hp, bool_t extended_list_of, si8_t *checkaddr_err) @@ -2456,12 +2482,12 @@ jerr: FL void substdate(struct message *m) { + /* The Date: of faked From_ lines is traditionally the date the message was + * written to the mail file. Try to determine this using RFC message header + * fields, or fall back to current time */ char const *cp; NYD_ENTER; - /* Determine the date to print in faked 'From ' lines. This is traditionally - * the date the message was written to the mail file. Try to determine this - * using RFC message header fields, or fall back to current time */ m->m_time = 0; if ((cp = hfield1("received", m)) != NULL) { while ((cp = nexttoken(cp)) != NULL && *cp != ';') { @@ -2481,6 +2507,194 @@ substdate(struct message *m) NYD_LEAVE; } +FL char * +n_header_textual_date_info(struct message *mp, char const **color_tag_or_null){ + struct tm tmlocal; + char *rv; + char const *fmt, *cp; + time_t t; + NYD_ENTER; + n_UNUSED(color_tag_or_null); + + t = mp->m_time; + fmt = ok_vlook(datefield); + +jredo: + if(fmt != NULL){ + ui8_t i; + + cp = hfield1("date", mp);/* TODO use m_date field! */ + if(cp == NULL){ + fmt = NULL; + goto jredo; + } + + t = rfctime(cp); + rv = n_time_ctime(t, NULL); + cp = ok_vlook(datefield_markout_older); + i = (*fmt != '\0'); + if(cp != NULL) + i |= (*cp != '\0') ? 2 | 4 : 2; /* XXX no magics */ + + /* May we strftime(3)? */ + if(i & (1 | 4)){ + /* This localtime(3) should not fail since rfctime(3).. but .. */ + struct tm *tmp; + time_t t2; + + /* TODO the datetime stuff is horror: mails should be parsed into + * TODO an object tree, and date: etc. have a datetime object, which + * TODO verifies upon parse time; then ALL occurrences of datetime are + * TODO valid all through the program; and: to_wire, to_user! */ + t2 = t; +jredo_localtime: + if((tmp = localtime(&t2)) == NULL){ + t2 = 0; + goto jredo_localtime; + } + memcpy(&tmlocal, tmp, sizeof *tmp); + } + + if((i & 2) && + (UICMP(64, t, >, time_current.tc_time + n_DATE_SECSDAY) || +#define _6M ((n_DATE_DAYSYEAR / 2) * n_DATE_SECSDAY) + UICMP(64, t + _6M, <, time_current.tc_time))){ +#undef _6M + if((fmt = (i & 4) ? cp : NULL) == NULL){ + char *x; + n_LCTA(n_FROM_DATEBUF >= 4 + 7 + 1 + 4, "buffer too small"); + + x = n_autorec_alloc(n_FROM_DATEBUF); + memset(x, ' ', 4 + 7 + 1 + 4); + memcpy(&x[4], &rv[4], 7); + x[4 + 7] = ' '; + memcpy(&x[4 + 7 + 1], &rv[20], 4); + x[4 + 7 + 1 + 4] = '\0'; + rv = x; + } + n_COLOUR( + if(color_tag_or_null != NULL) + *color_tag_or_null = n_COLOUR_TAG_SUM_OLDER; + ) + }else if((i & 1) == 0) + fmt = NULL; + + if(fmt != NULL){ + size_t j; + + for(j = n_FROM_DATEBUF;; j <<= 1){ + i = strftime(rv = n_autorec_alloc(j), j, fmt, &tmlocal); + if(i != 0) + break; + if(j > 128){ + n_err(_("Ignoring this date format: %s\n"), + n_shexp_quote_cp(fmt, FAL0)); + n_strscpy(rv, n_time_ctime(t, NULL), j); + } + } + } + }else if(t == (time_t)0 && !(mp->m_flag & MNOFROM)){ + /* TODO eliminate this path, query the FROM_ date in setptr(), + * TODO all other codepaths do so by themselves ALREADY ????? + * TODO assert(mp->m_time != 0);, then + * TODO ALSO changes behaviour of datefield_markout_older */ + a_head_parse_from_(mp, rv = n_autorec_alloc(n_FROM_DATEBUF)); + }else + rv = savestr(n_time_ctime(t, NULL)); + NYD_LEAVE; + return rv; +} + +FL struct name * +n_header_textual_sender_info(struct message *mp, char **cumulation_or_null, + char **addr_or_null, char **name_real_or_null, char **name_full_or_null, + bool_t *is_to_or_null){ + struct n_string s_b1, s_b2, *sp1, *sp2; + struct name *np, *np2; + bool_t isto, b; + char *cp; + NYD_ENTER; + + cp = n_header_senderfield_of(mp); + isto = FAL0; + + if((np = lextract(cp, GFULL | GSKIN)) != NULL){ + if(is_to_or_null != NULL && ok_blook(showto) && + np->n_flink == NULL && n_is_myname(np->n_name)){ + if((cp = hfield1("to", mp)) != NULL && + (np2 = lextract(cp, GFULL | GSKIN)) != NULL){ + np = np2; + isto = TRU1; + } + } + + if(((b = ok_blook(showname)) && cumulation_or_null != NULL) || + name_real_or_null != NULL || name_full_or_null != NULL){ + size_t i; + + for(i = 0, np2 = np; np2 != NULL; np2 = np2->n_flink) + i += strlen(np2->n_fullname) +3; + + sp1 = n_string_book(n_string_creat_auto(&s_b1), i); + sp2 = (name_full_or_null == NULL) ? NULL + : n_string_book(n_string_creat_auto(&s_b2), i); + + for(np2 = np; np2 != NULL; np2 = np2->n_flink){ + if(sp1->s_len > 0){ + sp1 = n_string_push_c(sp1, ','); + sp1 = n_string_push_c(sp1, ' '); + if(sp2 != NULL){ + sp2 = n_string_push_c(sp2, ','); + sp2 = n_string_push_c(sp2, ' '); + } + } + + if((cp = realname(np2->n_fullname)) == NULL) + cp = np2->n_name; + sp1 = n_string_push_cp(sp1, cp); + if(sp2 != NULL) + sp2 = n_string_push_cp(sp2, np2->n_fullname); + } + + n_string_cp(sp1); + if(b && cumulation_or_null != NULL) + *cumulation_or_null = sp1->s_dat; + if(name_real_or_null != NULL) + *name_real_or_null = sp1->s_dat; + if(name_full_or_null != NULL) + *name_full_or_null = n_string_cp(sp2); + + /* n_string_gut(n_string_drop_ownership(sp2)); */ + /* n_string_gut(n_string_drop_ownership(sp1)); */ + } + + if((b = (!b && cumulation_or_null != NULL)) || addr_or_null != NULL){ + cp = detract(np, GCOMMA | GNAMEONLY); + if(b) + *cumulation_or_null = cp; + if(addr_or_null != NULL) + *addr_or_null = cp; + } + }else if(cumulation_or_null != NULL || addr_or_null != NULL || + name_real_or_null != NULL || name_full_or_null != NULL){ + cp = savestr(n_empty); + + if(cumulation_or_null != NULL) + *cumulation_or_null = cp; + if(addr_or_null != NULL) + *addr_or_null = cp; + if(name_real_or_null != NULL) + *name_real_or_null = cp; + if(name_full_or_null != NULL) + *name_full_or_null = cp; + } + + if(is_to_or_null != NULL) + *is_to_or_null = isto; + NYD_LEAVE; + return np; +} + FL void setup_from_and_sender(struct header *hp) { diff --git a/nail.1 b/nail.1 index afcb1907..20acb36d 100644 --- a/nail.1 +++ b/nail.1 @@ -5272,7 +5272,9 @@ and forwards the message to him. The text of the original message is included in the new one, with the value of the .Va forward-inject-head -variable preceding it. +variable preceding, and the value of +.Va forward-inject-tail +succeeding it. To filter the included header fields to the desired subset use the .Ql forward slot of the white- and blacklisting command @@ -8019,6 +8021,11 @@ prefaced by the message header fields and followed by the attachment list, if any. . .Mx +.It Ic ~Q +Read in the given / current message(s) according to the algorithm of +.Va quote . +. +.Mx .It Ic ~q Abort the message being sent, copying it to the file specified by the @@ -9146,13 +9153,19 @@ With this setting enabled messages are sent as unmodified MIME attachments with all of their parts included. . .Mx -.It Va forward-inject-head -The string to put before the text of a message with the +.Mx +.It Va forward-inject-head , forward-inject-tail +The strings to put before and after the text of a message with the .Ic forward -command instead of the default -.Dq -------- Original Message -------- . -No heading is put if it is set to the empty string. -This variable is ignored if the +command, respectively. +The former defaults to +.Ql -------- Original Message --------\en . +Special format directives in these strings will be expanded if possible, +and if so configured the output will be folded according to +.Va quote-fold ; +for more please refer to +.Va quote-inject-head . +These variables are ignored if the .Va forward-as-attachment variable is set. . @@ -9234,14 +9247,15 @@ can be used to set .Mx .It Va headline A format string to use for the summary of -.Ic headers , -similar to the ones used for -.Xr printf 3 -formats. +.Ic headers . Format specifiers in the given string start with a percent sign .Ql % and may be followed by an optional decimal number indicating the field width \(em if that is negative, the field is to be left-aligned. +Names and addresses are subject to modifications according to +.Va showname +and +.Va showto . Valid format specifiers are: . .Pp @@ -9275,14 +9289,16 @@ adjusted by setting .Va attrlist . .It Ql %d The date found in the -.Ql From: +.Ql Date: header of the message when .Va datefield is set (the default), otherwise the date when the message was received. Formatting can be controlled by assigning a .Xr strftime 3 format string to -.Va datefield . +.Va datefield +(and +.Va datefield-markout-older ) . .It Ql %e The indenting level in .Ql thread Ns @@ -10700,38 +10716,37 @@ The default is . .Mx .It Va quote -If set, \*(UA starts a replying message with the original message -prefixed by the value of the variable -.Va indentprefix . -Normally, a heading consisting of -.Dq Fromheaderfield wrote: -is put before the quotation. -If the string -.Ql noheading -is assigned to the -.Va \&\"e -variable, this heading is omitted. -If the string -.Ql headers -is assigned, only the headers selected by the +If set a +.Ic reply +message is started with the quoted original message, +the lines of which are prefixed by the value of the variable +.Va indentprefix , +taking into account +.Va quote-chars +and +.Va quote-fold . +If set to the empty value, the quoted message will be preceded and +followed by the expansions of the values of +.Va quote-inject-head +and +.Va quote-inject-tail , +respectively. +None of the headers of the quoted message is included in the quote if +the value equals +.Ql noheading , +and only the headers selected by the .Ql type .Ic headerpick -selection are put above the message body, -thus -.Va \&\"e -acts like an automatic -.Pf ` Ic ~m Ns ' -.Sx "COMMAND ESCAPES" -command, then. -If the string -.Ql allheaders -is assigned, all headers are put above the message body and all MIME -parts are included, making -.Va \&\"e -act like an automatic -.Pf ` Ic ~M Ns ' -command; also see -.Va quote-as-attachment . +selection are put above the message body for +.Ql headers , +whereas all headers and all MIME parts are included for +.Ql allheaders . +Also see +.Va quote-as-attachment +and +.Ic ~Q , +one of the +.Sx "COMMAND ESCAPES" . . .Mx .It Va quote-as-attachment @@ -10750,9 +10765,8 @@ which shall be treated as quotation leaders, the default being .Mx .It Va quote-fold \*(OP Can be set in addition to -.Va indentprefix . -Setting this turns on a more fancy quotation algorithm in that leading -quotation characters +.Va indentprefix , +and creates a more fancy quotation in that leading quotation characters .Pf ( Va quote-chars ) are compressed and overlong lines are folded. .Va \&\"e-fold @@ -10760,12 +10774,62 @@ can be set to either one or two (space separated) numeric values, which are interpreted as the maximum (goal) and the minimum line length, respectively, in a spirit rather equal to the .Xr fmt 1 -program, but line-, not paragraph-based. +program, but line- instead of paragraph-based. If not set explicitly the minimum will reflect the goal algorithmically. The goal cannot be smaller than the length of .Va indentprefix -plus some additional pad. -Necessary adjustments take place silently. +plus some additional pad; necessary adjustments take place silently. +. +. +.Mx +.Mx +.It Va quote-inject-head , quote-inject-tail +The strings to put before and after the text of a +.Va quote Ns +d message, respectively. +The former defaults to +.Ql %f wrote:\en\en . +Special format directives will be expanded if possible, and if so +configured the output will be folded according to +.Va quote-fold . +Format specifiers in the given strings start with a percent sign +.Ql % +and expand values of the original message, unless noted otherwise. +Note that names and addresses are not subject to the setting of +.Va showto . +Valid format specifiers are: +. +.Pp +.Bl -tag -compact -width ".It Ql _%%_" +.It Ql %% +A plain percent sign. +.It Ql %a +The address(es) of the sender(s). +.It Ql %d +The date found in the +.Ql Date: +header of the message when +.Va datefield +is set (the default), otherwise the date when the message was received. +Formatting can be controlled by assigning a +.Xr strftime 3 +format string to +.Va datefield +(and +.Va datefield-markout-older ) . +.It Ql %f +The full name(s) (name and address, as given) of the sender(s). +.It Ql %i +The +.Ql Message-ID: . +.It Ql %n +The real name(s) of the sender(s) if there is one and +.Va showname +allows usage, the address(es) otherwise. +.It Ql %r +The senders real name(s) if there is one, the address(es) otherwise. +.El +. . .Mx .It Va r-option-implicit diff --git a/nail.h b/nail.h index f8ff329f..51736e69 100644 --- a/nail.h +++ b/nail.h @@ -586,7 +586,7 @@ enum expand_addr_flags { EAF_FAIL = 1<<1, /* "fail" */ EAF_FAILINVADDR = 1<<2, /* "failinvaddr" */ /* TODO HACK! In pre-v15 we have a control flow problem (it is a general - * TODO design problem): if collect() calls makeheader(), e.g., for -t or + * TODO design problem): if n_collect() calls makeheader(), e.g., for -t or * TODO because of ~e diting, then that will checkaddr() and that will * TODO remove invalid headers. However, this code path does not know * TODO about keeping track of senderrors unless a pointer has been passed, @@ -1597,6 +1597,7 @@ ok_v_encoding, /* {obsolete=1} */ ok_v_followup_to_honour, ok_b_forward_as_attachment, ok_v_forward_inject_head, + ok_v_forward_inject_tail, ok_v_from, /* {vip=1} */ ok_b_fullnames, ok_v_fwdheading, /* {obsolete=1} */ @@ -1719,6 +1720,8 @@ ok_v_NAIL_TAIL, /* {name=NAIL_TAIL,obsolete=1} */ ok_b_quote_as_attachment, ok_v_quote_chars, /* {vip=1,notempty=1,defval=">|}:"} */ ok_v_quote_fold, + ok_v_quote_inject_head, + ok_v_quote_inject_tail, ok_b_r_option_implicit, ok_b_recipients_in_cc, diff --git a/nailfuns.h b/nailfuns.h index 652241d6..e1afb657 100644 --- a/nailfuns.h +++ b/nailfuns.h @@ -725,8 +725,8 @@ FL void n_temporary_compose_hook_varset(void *arg); /* If quotefile is (char*)-1, stdin will be used, caller has to verify that * we're not running in interactive mode */ -FL FILE * collect(struct header *hp, int printheaders, struct message *mp, - char const *quotefile, int doprefix, si8_t *checkaddr_err); +FL FILE *n_collect(struct header *hp, int printheaders, struct message *mp, + char const *quotefile, bool_t is_fwding, si8_t *checkaddr_err); /* * colour.c @@ -1027,13 +1027,6 @@ FL char const * myorigin(struct header *hp); FL bool_t is_head(char const *linebuf, size_t linelen, bool_t check_rfc4155); -/* Savage extract date field from From_ line. linelen is convenience as line - * must be terminated (but it may end in a newline [sequence]). - * Return whether the From_ line was parsed successfully (-1 if the From_ line - * wasn't really RFC 4155 compliant) */ -FL int extract_date_from_from_(char const *line, size_t linelen, - char datebuf[n_FROM_DATEBUF]); - /* Extract some header fields (see e.g. -t documentation) from a message. * If extended_list_of is set a number of additional header fields are * understood and address joining is performed as necessary, and the subject @@ -1124,8 +1117,32 @@ FL time_t rfctime(char const *date); FL time_t combinetime(int year, int month, int day, int hour, int minute, int second); +/* Determine the date to print in faked 'From ' lines */ FL void substdate(struct message *m); +/* Create ready-to-go environment taking into account *datefield* etc., + * and return a result in auto-reclaimed storage. + * TODO hack *color_tag_or_null could be set to n_COLOUR_TAG_SUM_OLDER. + * time_current is used for comparison and must thus be up-to-date */ +FL char *n_header_textual_date_info(struct message *mp, + char const **color_tag_or_null); + +/* Create ready-to-go sender name of a message in *cumulation_or_null, the + * addresses only in *addr_or_null, the real names only in *name_real_or_null, + * and the full names in *name_full_or_null, taking acount for *showname*. + * If *is_to_or_null is set, *showto* and n_is_myname() are taken into account + * when choosing which names to use. + * The list as such is returned, or NULL if there is really none (empty strings + * will be stored, then). + * All results are in auto-reclaimed storage, but may point to the same string. + * TODO *is_to_or_null could be set to whether we actually used To:, or not. + * TODO n_header_textual_sender_info(): should only create a list of matching + * TODO name objects, which the user can iterate over and o->to_str().. */ +FL struct name *n_header_textual_sender_info(struct message *mp, + char **cumulation_or_null, char **addr_or_null, + char **name_real_or_null, char **name_full_or_null, + bool_t *is_to_or_null); + /* TODO Weird thing that tries to fill in From: and Sender: */ FL void setup_from_and_sender(struct header *hp); @@ -1888,7 +1905,7 @@ FL int c_Sendmail(void *v); * header. (Internal interface) */ FL enum okay mail1(struct header *hp, int printheaders, struct message *quote, char const *quotefile, - int recipient_record, int doprefix); + int recipient_record, bool_t is_fwding); /* Create a Date: header field. * We compare the localtime() and gmtime() results to get the timezone, because diff --git a/send.c b/send.c index 394461ab..9c3a42a1 100644 --- a/send.c +++ b/send.c @@ -1215,7 +1215,8 @@ joutln: * TODO doesn't end in a newline, because of our input/output 1:1. * TODO This should be handled automatically by a display filter, then */ if(rv >= 0 && !qf->qf_nl_last && - (action == SEND_TODISP || action == SEND_TODISP_ALL)) + (action == SEND_TODISP || action == SEND_TODISP_ALL || + action == SEND_QUOTE || action == SEND_QUOTE_ALL)) rv = quoteflt_push(qf, "\n", 1); quoteflt_flush(qf); diff --git a/sendout.c b/sendout.c index 96f55fd0..79fc60c2 100644 --- a/sendout.c +++ b/sendout.c @@ -786,7 +786,7 @@ sendmail_internal(void *v, int recipient_record) if((head.h_to = lextract(str, GTO | (ok_blook(fullnames) ? GFULL | GSKIN : GSKIN))) != NULL) head.h_mailx_raw_to = namelist_dup(head.h_to, head.h_to->n_type); - rv = mail1(&head, 0, NULL, NULL, recipient_record, 0); + rv = mail1(&head, 0, NULL, NULL, recipient_record, FAL0); NYD_LEAVE; return (rv != OKAY); /* reverse! */ } @@ -1705,7 +1705,7 @@ mail(struct name *to, struct name *cc, struct name *bcc, char const *subject, head.h_attach = attach; - mail1(&head, 0, NULL, quotefile, recipient_record, 0); + mail1(&head, 0, NULL, quotefile, recipient_record, FAL0); if (subject != NULL) n_free(out.s); @@ -1737,7 +1737,7 @@ c_Sendmail(void *v) FL enum okay mail1(struct header *hp, int printheaders, struct message *quote, - char const *quotefile, int recipient_record, int doprefix) + char const *quotefile, int recipient_record, bool_t is_fwding) { struct n_sigman sm; struct sendbundle sb; @@ -1764,7 +1764,8 @@ mail1(struct header *hp, int printheaders, struct message *quote, time_current_update(&time_current, TRU1); /* Collect user's mail from standard input. Get the result as mtf */ - mtf = collect(hp, printheaders, quote, quotefile, doprefix, &_sendout_error); + mtf = n_collect(hp, printheaders, quote, quotefile, is_fwding, + &_sendout_error); if (mtf == NULL) goto jleave; /* TODO All custom headers should be joined here at latest @@ -1802,9 +1803,9 @@ mail1(struct header *hp, int printheaders, struct message *quote, } #endif - /* XXX Update time_current again; once collect() offers editing of more + /* XXX Update time_current again; once n_collect() offers editing of more * XXX headers, including Date:, this must only happen if Date: is the - * XXX same that it was before collect() (e.g., postponing etc.). + * XXX same that it was before n_collect() (e.g., postponing etc.). * XXX But *do* update otherwise because the mail seems to be backdated * XXX if the user edited some time, which looks odd and it happened * XXX to me that i got mis-dated response mails due to that... */ -- 2.11.4.GIT