From 1b3209b0577a9327ebb076f3b32b8a159c253f7b Mon Sep 17 00:00:00 2001
From: Jeremy Harris <jgh146exb@wizmail.org>
Date: Tue, 2 Jul 2024 14:41:19 +0100
Subject: [PATCH] MIME: support RFC 2331 for name=.  Bug 3099

---
 doc/ChangeLog |   2 +
 src/mime.c    | 188 ++++++++++++++++++++++++++------------------------
 2 files changed, 100 insertions(+), 90 deletions(-)

diff --git a/doc/ChangeLog b/doc/ChangeLog
index 095bdb4..c1f0499 100644
--- a/doc/ChangeLog
+++ b/doc/ChangeLog
@@ -150,6 +150,8 @@ JH/33 Fis ${srs_encode ...} to handle an empty sender address, now returning
 
 JH/34 Bug 3099: fix parsing of MIME filenames 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.
 
 HS/01 Bug 2855: Handle a v4mapped sender address given us by a frontending
       proxy.  Previously these were misparsed, leading to paniclog entries.
diff --git a/src/mime.c b/src/mime.c
index e7af413..f36823f 100644
--- a/src/mime.c
+++ b/src/mime.c
@@ -29,10 +29,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 }
 };
 
 
@@ -576,8 +576,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;
 
@@ -589,90 +589,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;
 
-		  /* look for a ' in the "filename" */
-		  while(*s != '\'' && *s) s++;	/* s is 1st ' or NUL */
+		  /* build up an un-decoded filename over successive
+		  filename*= parameters (for use when 2047 decode fails) */
 
-		  if (*s)			/* there was a ' */
+		  mime_fname_rfc2231 = string_cat(mime_fname_rfc2231, q);
+
+		  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
@@ -683,26 +685,32 @@ 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 */
 
-	  /* 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 */
+             p = mime_next_semicolon(p);
+	     if (*p) p++;
+      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
-- 
2.33.0

