]> granicus.if.org Git - mutt/commitdiff
Write rfc2231 parameter continuations for long parameters.
authorKevin McCarthy <kevin@8t8.us>
Thu, 2 May 2019 19:31:29 +0000 (12:31 -0700)
committerKevin McCarthy <kevin@8t8.us>
Fri, 3 May 2019 01:02:27 +0000 (18:02 -0700)
Previously, Mutt would truncate long attachment filenames, to avoid
writing an illegal length header line.  This commit is a followup to
4dcb3ba1, where I reverted an incorrect fix for the problem.

rfc2231_encode_string() now returns a list of continuations, with
encoding and continuation number suffixes already appended to the
attribute.  The function tries to keep the line length less than 78
characters, but the code is a bit imprecise as a trade off for
simplicity and readability.

Modify mutt_write_mime_header() to loop through the continuations.

rfc2231.c
rfc2231.h
sendlib.c

index 437fdfa1a65598abfb1766b8b8f0f804b341de5e..cf92c2ff546d5d419fa5b0a438a49d38bb63fe4d 100644 (file)
--- a/rfc2231.c
+++ b/rfc2231.c
@@ -310,72 +310,157 @@ static void rfc2231_join_continuations (PARAMETER **head,
   }
 }
 
-int rfc2231_encode_string (char **pd)
+PARAMETER *rfc2231_encode_string (const char *attribute, char *value)
 {
-  int ext = 0, encode = 0;
-  char *charset, *s, *t, *e, *d = 0;
-  size_t slen, dlen = 0;
+  int encode = 0, add_quotes = 0, free_src_value = 0;
+  int split = 0, continuation_number = 0;
+  size_t dest_value_len = 0, max_value_len = 0, cur_value_len = 0;
+  char *cur, *charset = NULL, *src_value = NULL;
+  PARAMETER *result = NULL, *current, **lastp;
+  BUFFER *cur_attribute, *cur_value;
+
+  cur_attribute = mutt_buffer_pool_get ();
+  cur_value = mutt_buffer_pool_get ();
 
   /*
-   * A shortcut to detect pure 7bit data.
-   *
-   * This should prevent the worst when character set handling
-   * is flawed.
+   * Perform charset conversion
    */
-
-  for (s = *pd; *s; s++)
-    if (*s & 0x80)
+  for (cur = value; *cur; cur++)
+    if (*cur < 0x20 || *cur >= 0x7f)
+    {
+      encode = 1;
       break;
+    }
 
-  if (!*s)
-    return 0;
-
-  if (!Charset || !SendCharset ||
-      !(charset = mutt_choose_charset (Charset, SendCharset,
-                                       *pd, strlen (*pd), &d, &dlen)))
+  if (encode)
   {
-    charset = safe_strdup (Charset ? Charset : "unknown-8bit");
-    d = *pd;
-    dlen = strlen (d);
+    if (Charset && SendCharset)
+      charset = mutt_choose_charset (Charset, SendCharset,
+                                     value, mutt_strlen (value),
+                                     &src_value, NULL);
+    if (src_value)
+      free_src_value = 1;
+    if (!charset)
+      charset = safe_strdup (Charset ? Charset : "unknown-8bit");
   }
+  if (!src_value)
+    src_value = value;
+
+  /*
+   * Count the size the resultant value will need in total.
+   */
+  if (encode)
+    dest_value_len = mutt_strlen (charset) + 2;  /* charset'' prefix */
 
-  if (!mutt_is_us_ascii (charset))
-    encode = 1;
+  for (cur = src_value; *cur; cur++)
+  {
+    dest_value_len++;
+
+    if (encode)
+    {
+      /* These get converted to %xx so need a total of three chars */
+      if (*cur < 0x20 || *cur >= 0x7f ||
+          strchr (MimeSpecials, *cur) ||
+          strchr ("*'%", *cur))
+      {
+        dest_value_len += 2;
+      }
+    }
+    else
+    {
+      /* rfc822_cat() will add outer quotes if it finds MimeSpecials. */
+      if (!add_quotes && strchr (MimeSpecials, *cur))
+        add_quotes = 1;
+      /* rfc822_cat() will add a backslash if it finds '\' or '"'. */
+      if (*cur == '\\' || *cur == '"')
+        dest_value_len++;
+    }
+  }
 
-  for (s = d, slen = dlen; slen; s++, slen--)
-    if (*s < 0x20 || *s >= 0x7f)
-      encode = 1, ++ext;
-    else if (strchr (MimeSpecials, *s) || strchr ("*'%", *s))
-      ++ext;
+  /*
+   * Determine if need to split into parameter value continuations
+   */
+  max_value_len =
+    78                      -    /* rfc suggested line length */
+    1                       -    /* Leading tab on continuation line */
+    mutt_strlen (attribute) -    /* attribute */
+    (encode ? 1 : 0)        -    /* '*' encoding marker */
+    1                       -    /* '=' */
+    (add_quotes ? 2 : 0)    -    /* "...." */
+    1;                           /* ';' */
+
+  if (max_value_len < 30)
+    max_value_len = 30;
+
+  if (dest_value_len > max_value_len)
+  {
+    split = 1;
+    max_value_len -= 4;          /* '*n' continuation number and extra encoding
+                                  * space to keep loop below simpler */
+  }
 
+  /*
+   * Generate list of parameter continuations.
+   */
+  lastp = &result;
+  cur = src_value;
   if (encode)
   {
-    e = safe_malloc (dlen + 2*ext + strlen (charset) + 3);
-    sprintf (e, "%s''", charset);      /* __SPRINTF_CHECKED__ */
-    t = e + strlen (e);
-    for (s = d, slen = dlen; slen; s++, slen--)
-      if (*s < 0x20 || *s >= 0x7f ||
-         strchr (MimeSpecials, *s) || strchr ("*'%", *s))
+    mutt_buffer_printf (cur_value, "%s''", charset);
+    cur_value_len = mutt_buffer_len (cur_value);
+  }
+
+  while (*cur)
+  {
+    *lastp = current = mutt_new_parameter ();
+    lastp = &current->next;
+    mutt_buffer_strcpy (cur_attribute, attribute);
+    if (split)
+      mutt_buffer_add_printf (cur_attribute, "*%d", continuation_number++);
+    if (encode)
+      mutt_buffer_addch (cur_attribute, '*');
+
+    while (*cur && (!split || cur_value_len < max_value_len))
+    {
+      if (encode)
       {
-       sprintf (t, "%%%02X", (unsigned char)*s);
-       t += 3;
+        if (*cur < 0x20 || *cur >= 0x7f ||
+            strchr (MimeSpecials, *cur) ||
+            strchr ("*'%", *cur))
+        {
+          mutt_buffer_add_printf (cur_value, "%%%02X", (unsigned char)*cur);
+          cur_value_len += 3;
+        }
+        else
+        {
+          mutt_buffer_addch (cur_value, *cur);
+          cur_value_len++;
+        }
       }
       else
-       *t++ = *s;
-    *t = '\0';
+      {
+        mutt_buffer_addch (cur_value, *cur);
+        cur_value_len++;
+        if (*cur == '\\' || *cur == '"')
+          cur_value_len++;
+      }
 
-    if (d != *pd)
-      FREE (&d);
-    FREE (pd);         /* __FREE_CHECKED__ */
-    *pd = e;
-  }
-  else if (d != *pd)
-  {
-    FREE (pd);         /* __FREE_CHECKED__ */
-    *pd = d;
+      cur++;
+    }
+
+    current->attribute = safe_strdup (mutt_b2s (cur_attribute));
+    current->value = safe_strdup (mutt_b2s (cur_value));
+
+    mutt_buffer_clear (cur_value);
+    cur_value_len = 0;
   }
 
+  mutt_buffer_pool_release (&cur_attribute);
+  mutt_buffer_pool_release (&cur_value);
+
   FREE (&charset);
+  if (free_src_value)
+    FREE (&src_value);
 
-  return encode;
+  return result;
 }
index b5f59c1e85e5f38d8987e4c748328e930f6a4148..90c35c54e0fc6418d4849b6eda40638c27e6c313 100644 (file)
--- a/rfc2231.h
+++ b/rfc2231.h
@@ -20,4 +20,4 @@
  */
 
 void rfc2231_decode_parameters (PARAMETER **);
-int rfc2231_encode_string (char **);
+PARAMETER *rfc2231_encode_string (const char *, char *);
index 4acb64698f18e77acffca3c7e7d9b2b0c1866f43..6f8b556fd69ee98e615ed4ad94f34170f8c67915 100644 (file)
--- a/sendlib.c
+++ b/sendlib.c
@@ -294,12 +294,12 @@ static void encode_8bit (FGETCONV *fc, FILE *fout, int istext)
 int mutt_write_mime_header (BODY *a, FILE *f)
 {
   PARAMETER *p;
+  PARAMETER *param_conts, *cont;
   char buffer[STRING];
   char *t;
   char *fn;
   int len;
   int tmplen;
-  int encode;
 
   fprintf (f, "Content-Type: %s/%s", TYPE (a), a->subtype);
 
@@ -309,43 +309,41 @@ int mutt_write_mime_header (BODY *a, FILE *f)
 
     for (p = a->parameter; p; p = p->next)
     {
-      char *tmp;
-
       if (!p->value)
        continue;
 
-      fputc (';', f);
-
-      buffer[0] = 0;
-      tmp = safe_strdup (p->value);
-      encode = rfc2231_encode_string (&tmp);
-      rfc822_cat (buffer, sizeof (buffer), tmp, MimeSpecials);
-
-      /* Dirty hack to make messages readable by Outlook Express
-       * for the Mac: force quotes around the boundary parameter
-       * even when they aren't needed.
-       */
-
-      if (!ascii_strcasecmp (p->attribute, "boundary") && !strcmp (buffer, tmp))
-       snprintf (buffer, sizeof (buffer), "\"%s\"", tmp);
-
-      FREE (&tmp);
-
-      tmplen = mutt_strlen (buffer) + mutt_strlen (p->attribute) + 1;
-
-      if (len + tmplen + 2 > 76)
+      param_conts = rfc2231_encode_string (p->attribute, p->value);
+      for (cont = param_conts; cont; cont = cont->next)
       {
-       fputs ("\n\t", f);
-       len = tmplen + 8;
-      }
-      else
-      {
-       fputc (' ', f);
-       len += tmplen + 1;
-      }
+        fputc (';', f);
+
+        buffer[0] = 0;
+        rfc822_cat (buffer, sizeof (buffer), cont->value, MimeSpecials);
+
+        /* Dirty hack to make messages readable by Outlook Express
+         * for the Mac: force quotes around the boundary parameter
+         * even when they aren't needed.
+         */
+        if (!ascii_strcasecmp (cont->attribute, "boundary") &&
+            !mutt_strcmp (buffer, cont->value))
+          snprintf (buffer, sizeof (buffer), "\"%s\"", cont->value);
+
+        tmplen = mutt_strlen (buffer) + mutt_strlen (cont->attribute) + 1;
+        if (len + tmplen + 2 > 76)
+        {
+          fputs ("\n\t", f);
+          len = tmplen + 1;
+        }
+        else
+        {
+          fputc (' ', f);
+          len += tmplen + 1;
+        }
 
-      fprintf (f, "%s%s=%s", p->attribute, encode ? "*" : "", buffer);
+        fprintf (f, "%s=%s", cont->attribute, buffer);
+      }
 
+      mutt_free_parameter (&param_conts);
     }
   }
 
@@ -365,6 +363,7 @@ int mutt_write_mime_header (BODY *a, FILE *f)
     if (a->disposition < sizeof(dispstr)/sizeof(char*))
     {
       fprintf (f, "Content-Disposition: %s", dispstr[a->disposition]);
+      len = 21 + mutt_strlen (dispstr[a->disposition]);
 
       if (a->use_disp)
       {
@@ -373,20 +372,35 @@ int mutt_write_mime_header (BODY *a, FILE *f)
 
        if (fn)
        {
-         char *tmp;
-
          /* Strip off the leading path... */
          if ((t = strrchr (fn, '/')))
            t++;
          else
            t = fn;
 
-         buffer[0] = 0;
-         tmp = safe_strdup (t);
-         encode = rfc2231_encode_string (&tmp);
-         rfc822_cat (buffer, sizeof (buffer), tmp, MimeSpecials);
-         FREE (&tmp);
-         fprintf (f, "; filename%s=%s", encode ? "*" : "", buffer);
+          param_conts = rfc2231_encode_string ("filename", t);
+          for (cont = param_conts; cont; cont = cont->next)
+          {
+            fputc (';', f);
+            buffer[0] = 0;
+            rfc822_cat (buffer, sizeof (buffer), cont->value, MimeSpecials);
+
+            tmplen = mutt_strlen (buffer) + mutt_strlen (cont->attribute) + 1;
+            if (len + tmplen + 2 > 76)
+            {
+              fputs ("\n\t", f);
+              len = tmplen + 1;
+            }
+            else
+            {
+              fputc (' ', f);
+              len += tmplen + 1;
+            }
+
+            fprintf (f, "%s=%s", cont->attribute, buffer);
+          }
+
+          mutt_free_parameter (&param_conts);
        }
       }