MIME: support RFC 2331 for name=. Bug 3099
authorJeremy Harris <jgh146exb@wizmail.org>
Tue, 2 Jul 2024 13:41:19 +0000 (14:41 +0100)
committerJeremy Harris <jgh146exb@wizmail.org>
Tue, 2 Jul 2024 13:41:19 +0000 (14:41 +0100)
doc/doc-txt/ChangeLog
src/src/mime.c
test/mail/4000.userx
test/scripts/4000-scanning/4000

index acb7796052b4c8d57cdbd176dfa68d2295d41f22..fad93254e839bceb3ac7b03f784a25abc74521b2 100644 (file)
@@ -164,8 +164,10 @@ JH/33 Bug 2994: A subdir dsearch lookup should permit a directory name that star
 JH/34 Fix delivery ordering for 2-phase queue run combined with
       queue_run_in_order.
 
-JH/35 Bug 3099: fix parsing of MIME filenames split over multiple paramemters.
+JH/35 Bug 3099: fix parsing of MIME filename= split over multiple paramemters.
       Previously the $mime_filename variable would have an incorrect value.
+      While in the code, extend coverage to name= which previously was only
+      supported for single parameters, despite also filling in $mime_filename.
 
 
 Exim version 4.97
index 5f9e1ade720185914b7f845195b21b9dad78101c..8044bb3fda35f4b32513261650642b27ab76bb07 100644 (file)
@@ -30,10 +30,10 @@ static int mime_header_list_size = nelem(mime_header_list);
 
 static mime_parameter mime_parameter_list[] = {
   /*   name    namelen  value */
-  { US"name=",     5, &mime_filename },
-  { US"filename=", 9, &mime_filename },
-  { US"charset=",  8, &mime_charset  },
-  { US"boundary=", 9, &mime_boundary }
+  { US"name",     4, &mime_filename },
+  { US"filename", 8, &mime_filename },
+  { US"charset",  7, &mime_charset  },
+  { US"boundary", 8, &mime_boundary }
 };
 
 
@@ -577,8 +577,8 @@ while(1)
       if (*(p = q)) p++;                       /* jump past the ; */
 
        {
-       uschar * mime_fname = NULL;
-       uschar * mime_fname_rfc2231 = NULL;
+       gstring * mime_fname = NULL;
+       gstring * mime_fname_rfc2231 = NULL;
        uschar * mime_filename_charset = NULL;
        BOOL decoding_failed = FALSE;
 
@@ -590,90 +590,92 @@ while(1)
          DEBUG(D_acl)
            debug_printf_indent("MIME:   considering paramlist '%s'\n", p);
 
-         if (  strncmpic(CUS"content-disposition:", header, 20) == 0
-            && strncmpic(CUS"filename*", p, 9) == 0
-            )
-           {                                   /* RFC 2231 filename */
-           uschar * q;
-
-           /* find value of the filename */
-           p += 9;
-           while(*p != '=' && *p) p++;
-           if (*p) p++;                        /* p is filename or NUL */
-           q = mime_param_val(&p);             /* p now trailing ; or NUL */
-
-           if (q && *q)
+         /* look for interesting parameters */
+         for (mime_parameter * mp = mime_parameter_list;
+              mp < mime_parameter_list + nelem(mime_parameter_list);
+              mp++
+             ) if (strncmpic(mp->name, p, mp->namelen) == 0)
+           {
+           p += mp->namelen;
+           if (*p == '*')                      /* RFC 2231 */
              {
-             uschar * temp_string, * err_msg, * fname = q;
-             int slen;
-
-             /* build up an un-decoded filename over successive
-             filename*= parameters (for use when 2047 decode fails) */
-/*XXX could grow a gstring here */
-
-             mime_fname_rfc2231 = string_sprintf("%#s%s",
-               mime_fname_rfc2231, q);
-
-             if (!decoding_failed)
+             while (isdigit(*++p)) ;           /* ignore cont-cnt values */
+             if (*p == '*') p++;               /* step over sep chset mark */
+             if (*p == '=')
                {
-               int size;
-               if (!mime_filename_charset)
+               uschar * q;
+               p++;                            /* step over = */
+               q = mime_param_val(&p);         /* p now trailing ; or NUL */
+
+               if (q && *q)                    /* q is the dequoted value */
                  {
-                 uschar * s = q;
+                 uschar * err_msg, * fname = q;
+                 int slen;
+
+                 /* build up an un-decoded filename over successive
+                 filename*= parameters (for use when 2047 decode fails) */
 
-                 /* look for a ' in the "filename" */
-                 while(*s != '\'' && *s) s++;  /* s is 1st ' or NUL */
+                 mime_fname_rfc2231 = string_cat(mime_fname_rfc2231, q);
 
-                 if (*s)                       /* there was a ' */
+                 if (!decoding_failed)
                    {
-                   if ((size = s-q) > 0)
-                     mime_filename_charset = string_copyn(q, size);
-
-                   if (*(fname = s)) fname++;
-                   while(*fname == '\'') fname++;    /* fname is after 2nd ' */
-                   }
-                 }
-
-               DEBUG(D_acl)
-                 debug_printf_indent("MIME:    charset %s fname '%s'\n",
-                   mime_filename_charset ? mime_filename_charset : US"<NULL>",
-                   fname);
-
-               temp_string = rfc2231_to_2047(fname, mime_filename_charset,
-                                             &slen);
-               DEBUG(D_acl)
-                 debug_printf_indent("MIME:    2047-name %s\n", temp_string);
-
-               temp_string = rfc2047_decode(temp_string, FALSE, NULL, ' ',
-                                             NULL, &err_msg);
-               DEBUG(D_acl)
-                 debug_printf_indent("MIME:    plain-name %s\n", temp_string);
-
-               if (!temp_string || (size = Ustrlen(temp_string)) == slen)
-                 decoding_failed = TRUE;
-               else
-                 /* build up a decoded filename over successive
-                 filename*= parameters */
-
-                 mime_filename = mime_fname = mime_fname
-                   ? string_sprintf("%s%s", mime_fname, temp_string)
-                   : temp_string;
-               }       /*!decoding_failed*/
-             }         /*q*/
-           }           /*2231 filename*/
-
-         else
-           /* look for interesting parameters */
-           for (mime_parameter * mp = mime_parameter_list;
-                mp < mime_parameter_list + nelem(mime_parameter_list);
-                mp++
-               ) if (strncmpic(mp->name, p, mp->namelen) == 0)
-             {
-             uschar * q;
-             uschar * dummy_errstr;
+                   if (!mime_filename_charset)
+                     {                 /* try for RFC 2231 chset/lang */
+                     uschar * s = q;
+
+                     /* look for a ' in the raw paramval */
+                     while(*s != '\'' && *s) s++;      /* s is 1st ' or NUL */
+
+                     if (*s)                           /* there was a ' */
+                       {
+                       int size;
+                       if ((size = s-q) > 0)
+                         mime_filename_charset = string_copyn(q, size);
+
+                       if (*(fname = s)) fname++;
+                       while(*fname == '\'') fname++;    /*fname is after 2nd '*/
+                       }
+                     }
+
+                   DEBUG(D_acl)
+                     debug_printf_indent("MIME:    charset %s fname '%s'\n",
+                       mime_filename_charset ? mime_filename_charset : US"<NULL>",
+                       fname);
+
+                   fname = rfc2231_to_2047(fname, mime_filename_charset,
+                                                 &slen);
+                   DEBUG(D_acl)
+                     debug_printf_indent("MIME:    2047-name %s\n", fname);
+
+                   fname = rfc2047_decode(fname, FALSE, NULL, ' ',
+                                                 NULL, &err_msg);
+                   DEBUG(D_acl) debug_printf_indent(
+                                   "MIME:    plain-name %s\n", fname);
+
+                   if (!fname || Ustrlen(fname) == slen)
+                     decoding_failed = TRUE;
+                   else if (mp->value == &mime_filename)
+                     {
+                     /* build up a decoded filename over successive
+                     filename*= parameters */
+
+                     mime_fname = string_cat(mime_fname, fname);
+                     mime_filename = string_from_gstring(mime_fname);
+                     }
+                   }   /*!decoding_failed*/
+                 }     /*q*/
+
+               if (*p) p++;                    /* p is past ; */
+               goto param_done;                /* done matching param names */
+               }               /*2231 param coding extension*/
+             }
+           else if (*p == '=')
+             {         /* non-2231 param */
+             uschar * q, * dummy_errstr;
 
              /* grab the value and copy to its expansion variable */
-             p += mp->namelen;
+
+             if (*p) p++;                      /* step over = */
              q = mime_param_val(&p);           /* p now trailing ; or NUL */
 
              *mp->value = q && *q
@@ -684,26 +686,31 @@ while(1)
                "MIME:  found %s parameter in %s header, value '%s'\n",
                mp->name, mh->name, *mp->value);
 
-             break;                    /* done matching param names */
+             if (*p) p++;                      /* p is past ; */
+             goto param_done;                  /* done matching param names */
              }
-
+           }                                   /* interesting parameters */
 
          /* There is something, but not one of our interesting parameters.
          Advance past the next semicolon */
+
          p = mime_next_semicolon(p);
          if (*p) p++;
-         }                             /* param scan on line */
+  param_done:
+         }                                     /* param scan on line */
 
        if (strncmpic(CUS"content-disposition:", header, 20) == 0)
          {
-         if (decoding_failed) mime_filename = mime_fname_rfc2231;
+         if (decoding_failed)
+           mime_filename = string_from_gstring(mime_fname_rfc2231);
 
          DEBUG(D_acl) debug_printf_indent(
            "MIME:  found %s parameter in %s header, value is '%s'\n",
            "filename", mh->name, mime_filename);
          }
        }
-      }
+      break;
+      }        /* interesting headers */
 
   /* set additional flag variables (easier access) */
   if (  mime_content_type
index 242be66273eca8aa5c327facf58e721d486d75ac..99f71010c5ca9ffdd0d9c06990fc3361c8fe2167 100644 (file)
@@ -460,6 +460,19 @@ X-2-is-coverletter: 0
 X-2-is-rfc822: 0
 X-2-decode-filename: TESTSUITE/spool/scan/10HmbF-000000005vi-0000/10HmbF-000000005vi-0000-00002
 X-2-content-size: 1
+X-3-content-type: application/octet-stream
+X-3-filename: example4.txt
+X-3-charset: 
+X-3-boundary: 
+X-3-content-disposition: attachment
+X-3-content-transfer-encoding: base64
+X-3-content-id: 
+X-3-content-description: 
+X-3-is-multipart: 0
+X-3-is-coverletter: 0
+X-3-is-rfc822: 0
+X-3-decode-filename: TESTSUITE/spool/scan/10HmbF-000000005vi-0000/10HmbF-000000005vi-0000-00003
+X-3-content-size: 1
 
 ------=_MIME_BOUNDARY_000_695039
 Content-Type: text/plain
@@ -474,5 +487,14 @@ Content-Transfer-Encoding: BASE64
 
 QmVpc3BpZWwK
 
+------=_MIME_BOUNDARY_000_695039
+Content-Type: application/octet-stream
+Content-Disposition: attachment;
+    name*0="example4";
+    name*1=".txt"
+Content-Transfer-Encoding: BASE64
+
+QmVpc3BpZWwK
+
 ------=_MIME_BOUNDARY_000_695039--
 
index bb2835ed3f2b81cb920dff702d4bd82d1a1466cb..b10f13d00c5d8d8e130c96c9f557e5175962f728 100644 (file)
@@ -275,6 +275,7 @@ quit
 #
 #
 # Filename using parameter value continuation (RFC 2231 sec. 3)
+#
 exim -odi -bs
 ehlo test.ex
 mail from:<>
@@ -299,6 +300,15 @@ Content-Transfer-Encoding: BASE64
 
 QmVpc3BpZWwK
 
+------=_MIME_BOUNDARY_000_695039
+Content-Type: application/octet-stream
+Content-Disposition: attachment;
+    name*0="example4";
+    name*1=".txt"
+Content-Transfer-Encoding: BASE64
+
+QmVpc3BpZWwK
+
 ------=_MIME_BOUNDARY_000_695039--
 .
 quit