From 6db4f88eba1106ab361fe5d6f2d047b595c5cf51 Mon Sep 17 00:00:00 2001 From: Rocco Rutte Date: Sat, 13 Jun 2009 01:08:01 +0200 Subject: [PATCH] Rewrite header folding We now distinct between sending and display case. For display, we always use tabs for folding for readability; for sending we now correctly fold using whitespace found in the header. Closes #2995. Closes #3080. --- commands.c | 2 +- copy.c | 2 +- copy.h | 1 + handler.c | 7 +- protos.h | 2 +- send.c | 4 + sendlib.c | 364 +++++++++++++++++++++++++++++++++-------------------- 7 files changed, 239 insertions(+), 143 deletions(-) diff --git a/commands.c b/commands.c index b9febd1dc..7d9c90d28 100644 --- a/commands.c +++ b/commands.c @@ -146,7 +146,7 @@ int mutt_display_message (HEADER *cur) } res = mutt_copy_message (fpout, Context, cur, cmflags, - (option (OPTWEED) ? (CH_WEED | CH_REORDER) : 0) | CH_DECODE | CH_FROM); + (option (OPTWEED) ? (CH_WEED | CH_REORDER) : 0) | CH_DECODE | CH_FROM | CH_DISPLAY); if ((safe_fclose (&fpout) != 0 && errno != EPIPE) || res < 0) { mutt_error (_("Could not copy message")); diff --git a/copy.c b/copy.c index 90a65dad9..9ddf411b7 100644 --- a/copy.c +++ b/copy.c @@ -287,7 +287,7 @@ mutt_copy_hdr (FILE *in, FILE *out, LOFF_T off_start, LOFF_T off_end, int flags, if (flags & (CH_DECODE|CH_PREFIX)) { if (mutt_write_one_header (out, 0, headers[x], - flags & CH_PREFIX ? prefix : 0, mutt_term_width (Wrap)) == -1) + flags & CH_PREFIX ? prefix : 0, mutt_term_width (Wrap), flags) == -1) { error = TRUE; break; diff --git a/copy.h b/copy.h index 9aff9da65..5f12a3c1e 100644 --- a/copy.h +++ b/copy.h @@ -52,6 +52,7 @@ #define CH_NOQFROM (1<<15) /* ignore ">From " line */ #define CH_UPDATE_IRT (1<<16) /* update In-Reply-To: */ #define CH_UPDATE_REFS (1<<17) /* update References: */ +#define CH_DISPLAY (1<<18) /* display result to user */ int mutt_copy_hdr (FILE *, FILE *, LOFF_T, LOFF_T, int, const char *); diff --git a/handler.c b/handler.c index 378102212..57dab0f41 100644 --- a/handler.c +++ b/handler.c @@ -1089,7 +1089,8 @@ static int message_handler (BODY *a, STATE *s) { mutt_copy_hdr (s->fpin, s->fpout, off_start, b->parts->offset, (((s->flags & M_WEED) || ((s->flags & (M_DISPLAY|M_PRINTING)) && option (OPTWEED))) ? (CH_WEED | CH_REORDER) : 0) | - (s->prefix ? CH_PREFIX : 0) | CH_DECODE | CH_FROM, s->prefix); + (s->prefix ? CH_PREFIX : 0) | CH_DECODE | CH_FROM | + (s->flags & M_DISPLAY) ? CH_DISPLAY : 0, s->prefix); if (s->prefix) state_puts (s->prefix, s); @@ -1424,7 +1425,7 @@ static int external_body_handler (BODY *b, STATE *s) mutt_copy_hdr(s->fpin, s->fpout, ftello (s->fpin), b->parts->offset, (option (OPTWEED) ? (CH_WEED | CH_REORDER) : 0) | - CH_DECODE, NULL); + CH_DECODE | CH_DISPLAY, NULL); } } else @@ -1441,7 +1442,7 @@ static int external_body_handler (BODY *b, STATE *s) access_type); mutt_copy_hdr (s->fpin, s->fpout, ftello (s->fpin), b->parts->offset, (option (OPTWEED) ? (CH_WEED | CH_REORDER) : 0) | - CH_DECODE , NULL); + CH_DECODE | CH_DISPLAY, NULL); } } diff --git a/protos.h b/protos.h index f35e6d9de..fb9f30474 100644 --- a/protos.h +++ b/protos.h @@ -371,7 +371,7 @@ int mutt_which_case (const char *); int mutt_write_fcc (const char *path, HEADER *hdr, const char *msgid, int, char *); int mutt_write_mime_body (BODY *, FILE *); int mutt_write_mime_header (BODY *, FILE *); -int mutt_write_one_header (FILE *fp, const char *tag, const char *value, const char *pfx, int wraplen); +int mutt_write_one_header (FILE *fp, const char *tag, const char *value, const char *pfx, int wraplen, int flags); int mutt_write_rfc822_header (FILE *, ENVELOPE *, BODY *, int, int); void mutt_write_references (LIST *, FILE *, int); int mutt_yesorno (const char *, int); diff --git a/send.c b/send.c index a04e0157c..c509df176 100644 --- a/send.c +++ b/send.c @@ -388,6 +388,10 @@ static int include_forward (CONTEXT *ctx, HEADER *cur, FILE *out) if (option (OPTFORWQUOTE)) cmflags |= M_CM_PREFIX; + /* wrapping headers for forwarding is considered a display + * rather than send action */ + chflags |= CH_DISPLAY; + mutt_copy_message (out, ctx, cur, cmflags, chflags); mutt_forward_trailer (out); return 0; diff --git a/sendlib.c b/sendlib.c index 2bddbe7e3..6a11d2270 100644 --- a/sendlib.c +++ b/sendlib.c @@ -1548,180 +1548,270 @@ void mutt_write_references (LIST *r, FILE *f, int trim) FREE (&ref); } +static const char *find_word (const char *src) +{ + const char *p = src; + + while (p && *p && strchr (" \t\n", *p)) + p++; + while (p && *p && !strchr (" \t\n", *p)) + p++; + return p; +} -static void foldingstrfcpy (char *d, const char *s, int n) +/* like wcwidth(), but gets const char* not wchar_t* */ +static int my_width (const char *str, int col, int flags) { - while (--n >= 0 && *s) + wchar_t wc; + int l, w = 0, nl = 0; + const char *p = str; + + while (p && *p) { - *d = *s++; - if (*d == '\t') - *d = ' '; - if (!(d[0] == '\n' && (*s == '\t' || *s == ' '))) - d++; + if (mbtowc (&wc, p, MB_CUR_MAX) >= 0) + { + l = wcwidth (wc); + if (l < 0) + l = 1; + /* correctly calc tab stop, even for sending as the + * line should look pretty on the receiving end */ + if (wc == L'\t' || (nl && wc == L' ')) + { + nl = 0; + l = 8 - (col % 8); + } + /* track newlines for display-case: if we have a space + * after a newline, assume 8 spaces as for display we + * always tab-fold */ + else if ((flags & CH_DISPLAY) && wc == '\n') + nl = 1; + } + else + l = 1; + w += l; + p++; } - *d = '\0'; + return w; } -int mutt_write_one_header (FILE *fp, const char *tag, const char *value, const char *pfx, int wraplen) +static int print_val (FILE *fp, const char *pfx, const char *value, int flags) { - int col = 0; - int i, k, n; - const char *cp; - char buf [HUGE_STRING]; - wchar_t w = (wchar_t) -1; - wchar_t last = (wchar_t) '\n'; - int l = 0; - int first = 1; - int wrapped = 0; - int in_encoded_word = 0; - - if (wraplen <= 0) - wraplen = 76; - - if (tag) + while (value && *value) { - if (fprintf (fp, "%s%s: ", NONULL (pfx), tag) < 0) + if (fputc (*value, fp) == EOF) return -1; - - col = mutt_strlen (tag) + 2 + mutt_strlen (pfx); - } - else - col = 0; - - *buf = '\0'; - cp = value; - - while (cp && *cp) - { - if (!col) + if (*value == '\n') { - if (fputs (NONULL (pfx), fp) == EOF) + if (*(value + 1) && pfx && *pfx && fputs (pfx, fp) == EOF) return -1; - col = mutt_strlen (pfx); - - /* Space padding, but only if necessary */ - if (!first && *cp != '\t' && *cp != ' ') + /* for display, turn folding spaces into folding tabs */ + if ((flags & CH_DISPLAY) && (*(value + 1) == ' ' || *(value + 1) == '\t')) { + value++; + while (*value && (*value == ' ' || *value == '\t')) + value++; if (fputc ('\t', fp) == EOF) return -1; - col += 8 - (col % 8); + continue; } } + value++; + } + return 0; +} - if (first) +static int fold_one_header (FILE *fp, const char *tag, const char *value, + const char *pfx, int wraplen, int flags) +{ + const char *p = value, *next, *sp; + char buf[HUGE_STRING] = ""; + int first = 1, enc, col = 0, w, l = 0, fold; + + dprint(4,(debugfile,"mwoh: pfx=[%s], tag=[%s], flags=%d value=[%s]\n", + pfx, tag, flags, value)); + + if (fprintf (fp, "%s%s: ", NONULL (pfx), tag) < 0) + return -1; + col = mutt_strlen (tag) + 2 + mutt_strlen (pfx); + + while (p && *p) + { + fold = 0; + + /* find the next word and place it in `buf'. it may start with + * whitespace we can fold before */ + next = find_word (p); + l = MIN(sizeof (buf), next - p); + memcpy (buf, p, l); + buf[l] = 0; + + /* determine width: character cells for display, bytes for sending + * (we get pure ascii only) */ + w = my_width (buf, col, flags); + enc = mutt_strncmp (buf, "=?", 2) == 0; + + dprint(5,(debugfile,"mwoh: word=[%s], col=%d, w=%d, next=[0x0%x]\n", + buf, col, w, *next)); + + /* insert a folding \n before the current word's lwsp except for + * header name, first word on a line (word longer than wrap width) + * and encoded words */ + if (!first && !enc && col && col + w >= wraplen) { - last = '\n'; - wrapped = 0; - first = 0; + col = mutt_strlen (pfx); + fold = 1; + if (fprintf (fp, "\n%s", NONULL(pfx)) <= 0) + return -1; } - /* - * i is our running pointer, and always points to the *beginning* of an mb character. - * k is the pointer to the beginning of the last white-space character we have seen. - * n is the pointer to the beginning of the first character after white-space. - * - * yuck - */ - - for (i = 0, k = 0, l = 0, n = 0; i + MB_CUR_MAX < sizeof (buf) - && cp[i] != '\0' && (col < wraplen || in_encoded_word); - i += l, last = w) + /* print the actual word; for display, ignore leading ws for word + * and fold with tab for readability */ + if ((flags & CH_DISPLAY) && fold) { - - /* Brief look at the last character we had... */ - if (iswspace (last)) + char *p = buf; + while (*p && (*p == ' ' || *p == '\t')) { - /* ... and if the next thing is an encoded word ... */ - if (strncmp (&cp[i], "=?", 2) == 0) - in_encoded_word = 1; - else - in_encoded_word = 0; + p++; + col--; } - - /* If there is a line break in the header, honor it. */ - if (cp[i] == '\n') - { - in_encoded_word = 0; + if (fputc ('\t', fp) == EOF) + return -1; + if (print_val (fp, pfx, p, flags) < 0) + return -1; + col += 8; + } + else if (print_val (fp, pfx, buf, flags) < 0) + return -1; + col += w; - if (cp[i+1] != ' ' && cp[i+1] != '\t') - first = 1; - - if (first || !wrapped) - { - k = i; - n = k + 1; - l = 1; - w = (wchar_t) '\n'; - break; - } - } + /* if the current word ends in \n, ignore all its trailing spaces + * and reset column; this prevents us from putting only spaces (or + * even none) on a line if the trailing spaces are located at our + * current line width + * XXX this covers ASCII space only, for display we probably + * XXX want something like iswspace() here */ + sp = next; + while (*sp && (*sp == ' ' || *sp == '\t')) + sp++; + if (*sp == '\n') + { + next = sp; + col = 0; + } - /* Eat the current character; cannot be '\0' */ + p = next; + first = 0; + } - if ((l = mbtowc (&w, &cp[i], MB_CUR_MAX)) <= 0) - { - dprint (1, (debugfile, "mutt_write_one_header: encoutered bad multi-byte character at %d.\n", i)); - l = 1; /* if bad, move on by one character */ - w = (wchar_t) -1; - } - else - { - if (wcwidth (w) >= 0) - col += wcwidth (w); + /* if we have printed something but didn't \n-terminate it, do it + * except the last word we printed ended in \n already */ + if (col && buf[l - 1] != '\n') + if (putc ('\n', fp) == EOF) + return -1; - if (iswspace (w) && - (!k || col <= wraplen)) - { - if (!k || i != n) - k = i; - n = i + l; - } - } + return 0; +} - /* - * As long as we haven't seen whitespace, we advance at least by one character. - */ - if (!k) - n = i + l; - } +static int write_one_header (FILE *fp, int pfxw, int max, int wraplen, + const char *pfx, const char *start, const char *end, + int flags) +{ + char *tagbuf, *valbuf, *t; - /* If no whitespace was found, copy as much as we can */ - if (!k) - k = n; - - /* If we're done, we're done. */ - if (!cp[i]) - k = n = i; + /* only pass through folding machinery if necessary for sending */ + if (!(flags & CH_DISPLAY) && pfxw + max <= wraplen) + { + valbuf = mutt_substrdup (start, end); + dprint(4,(debugfile,"mwoh: buf[%s%s] short enough, " + "max width = %d <= %d\n", + NONULL(pfx), valbuf, max, wraplen)); + if (pfx && *pfx) + if (fputs (pfx, fp) == EOF) + return -1; + if (print_val (fp, pfx, valbuf, flags) < 0) + { + FREE(&valbuf); + return -1; + } + FREE(&valbuf); + } + else + { + dprint(4,(debugfile,"mwoh: buf[%s%s] too long, " + "max width = %d > %dn", + NONULL(pfx), valbuf, max, wraplen)); + t = strchr (start, ':'); + tagbuf = mutt_substrdup (start, t); + valbuf = mutt_substrdup (t + 2, end); + if (fold_one_header (fp, tagbuf, valbuf, pfx, wraplen, flags) < 0) + return -1; + FREE (&tagbuf); + FREE (&valbuf); + } + return 0; +} - if (k < i) /* we had to go back to an earlier wrapping point */ - wrapped = 1; - - buf[0] = *cp; - foldingstrfcpy (buf + 1, cp + 1, k - 1); +/* split several headers into individual ones and call write_one_header + * for each one */ +int mutt_write_one_header (FILE *fp, const char *tag, const char *value, + const char *pfx, int wraplen, int flags) +{ + char *p = (char *)value, *last, *line; + int max = 0, w; + int pfxw = mutt_strwidth (pfx); - if (fprintf (fp, "%s\n", buf) < 0) - return -1; - col = 0; - - cp = &cp[n]; + /* when not displaying, use sane wrap value */ + if (!(flags & CH_DISPLAY)) + wraplen = 76; + else if (wraplen <= 0 || wraplen > COLS) + wraplen = COLS; - while (*cp) + if (tag) + { + /* if header is short enough, simply print it */ + if (!(flags & CH_DISPLAY) && mutt_strwidth (tag) + 2 + pfxw + + mutt_strwidth (value) <= wraplen) { - last = w; - if ((l = mbtowc (&w, cp, MB_CUR_MAX)) > 0 && iswspace (w)) - cp += l; - else - break; + dprint(4,(debugfile,"mwoh: buf[%s%s: %s] is short enough\n", + NONULL(pfx), tag, value)); + if (fprintf (fp, "%s%s: %s\n", NONULL(pfx), tag, value) <= 0) + return -1; + return 0; } + else + return fold_one_header (fp, tag, value, pfx, wraplen, flags); } - if (col) + p = last = line = (char *)value; + while (p && *p) { - if (fputc ('\n', fp) == EOF) - return -1; - col = 0; + p = strchr (p, '\n'); + + /* find maximum line width in current header */ + if (p) + *p = 0; + if ((w = my_width (line, 0, flags)) > max) + max = w; + if (p) + *p = '\n'; + + if (!p) + break; + + line = ++p; + if (*p != ' ' && *p != '\t') + { + if (write_one_header (fp, pfxw, max, wraplen, pfx, last, p, flags) < 0) + return -1; + last = p; + max = 0; + } } + if (last && *last) + if (write_one_header (fp, pfxw, max, wraplen, pfx, last, p, flags) < 0) + return -1; + return 0; } @@ -1800,7 +1890,7 @@ int mutt_write_rfc822_header (FILE *fp, ENVELOPE *env, BODY *attach, fputs ("Bcc: \n", fp); if (env->subject) - mutt_write_one_header (fp, "Subject", env->subject, NULL, 0); + mutt_write_one_header (fp, "Subject", env->subject, NULL, 0, 0); else if (mode == 1) fputs ("Subject: \n", fp); @@ -1870,7 +1960,7 @@ int mutt_write_rfc822_header (FILE *fp, ENVELOPE *env, BODY *attach, } } - mutt_write_one_header (fp, tmp->data, p, NULL, 0); + mutt_write_one_header (fp, tmp->data, p, NULL, 0, 0); *q = ':'; } } -- 2.40.0