@@ -384,4 +384,69 @@ private NameValuePair extractBoundary(final String contentType, final String exp
384384 return elem .getParameterByName ("boundary" );
385385 }
386386
387+ @ Test
388+ void testMultipartWriteToRFC7578ModeWithFilenameStarPreEncodedPassThrough () throws Exception {
389+ final String body = "hi" ;
390+ // Pre-encoded RFC 5987 value (as produced by a previous stage)
391+ final String preEncoded = "UTF-8''%F0%9F%90%99_inline-%E5%9B%BE%E5%83%8F_%E6%96%87%E4%BB%B6.png" ;
392+
393+ final List <NameValuePair > parameters = new ArrayList <>();
394+ parameters .add (new BasicNameValuePair (MimeConsts .FIELD_PARAM_NAME , "test" ));
395+ // Provide pre-encoded value directly to filename* param
396+ parameters .add (new BasicNameValuePair (MimeConsts .FIELD_PARAM_FILENAME_START , preEncoded ));
397+
398+ final MultipartFormEntity entity = MultipartEntityBuilder .create ()
399+ .setMode (HttpMultipartMode .EXTENDED )
400+ .setBoundary ("xxxxxxxxxxxxxxxxxxxxxxxx" )
401+ .addPart (new FormBodyPartBuilder ()
402+ .setName ("test" )
403+ .setBody (new StringBody (body , ContentType .TEXT_PLAIN .withCharset (StandardCharsets .UTF_8 )))
404+ .addField ("Content-Disposition" , "multipart/form-data" , parameters )
405+ .build ())
406+ .buildEntity ();
407+
408+ final ByteArrayOutputStream out = new ByteArrayOutputStream ();
409+ entity .writeTo (out );
410+ out .close ();
411+ final String wire = out .toString (StandardCharsets .ISO_8859_1 .name ());
412+
413+ // Pass-through EXACTLY the given value (no second prefix, no %25-escaping)
414+ Assertions .assertTrue (wire .contains ("filename*=\" " + preEncoded + "\" " ));
415+ Assertions .assertFalse (wire .contains ("UTF-8''UTF-8%27%27" ));
416+ Assertions .assertFalse (wire .contains ("%25F0%9F%90%99" )); // octopus emoji must not be re-escaped as %25F0...
417+ }
418+
419+ @ Test
420+ void testExtendedModeAddBinaryBodyAddsFilenameAndFilenameStar_NoDoubleEncoding () throws Exception {
421+ // Non-ASCII filename to trigger RFC 5987 behavior
422+ final String filename = "🐙_图像_文件.png" ;
423+ // Expected percent-encoded for both filename and filename*
424+ final String pct = "%F0%9F%90%99_%E5%9B%BE%E5%83%8F_%E6%96%87%E4%BB%B6.png" ;
425+
426+ final MultipartFormEntity entity = MultipartEntityBuilder .create ()
427+ .setMode (HttpMultipartMode .EXTENDED )
428+ .setBoundary ("xxxxxxxxxxxxxxxxxxxxxxxx" )
429+ .addBinaryBody ("attachments" , new byte []{1 , 2 }, ContentType .IMAGE_PNG , filename )
430+ .buildEntity ();
431+
432+ final ByteArrayOutputStream out = new ByteArrayOutputStream ();
433+ entity .writeTo (out );
434+ out .close ();
435+ final String wire = out .toString (StandardCharsets .ISO_8859_1 .name ());
436+
437+ // Base header
438+ Assertions .assertTrue (wire .contains ("Content-Disposition: form-data; name=\" attachments\" " ));
439+
440+ // filename param (percent-encoded for ASCII transport)
441+ Assertions .assertTrue (wire .contains ("filename=\" " + pct + "\" " ));
442+
443+ // filename* param (single RFC 5987 value, no double prefix / no %25-escaping)
444+ Assertions .assertTrue (wire .contains ("filename*=\" UTF-8''" + pct + "\" " ));
445+
446+ // Guard against regressions
447+ Assertions .assertFalse (wire .contains ("UTF-8''UTF-8%27%27" ));
448+ Assertions .assertFalse (wire .contains ("%25F0%9F%90%99" ));
449+ }
450+
451+
387452}
0 commit comments