2929
3030import java .io .File ;
3131import java .io .InputStream ;
32- import java .nio .CharBuffer ;
3332import java .nio .charset .Charset ;
3433import java .nio .charset .StandardCharsets ;
3534import java .util .ArrayList ;
3635import java .util .Collections ;
3736import java .util .List ;
38- import java .util .concurrent . ThreadLocalRandom ;
37+ import java .util .UUID ;
3938
4039import org .apache .hc .core5 .http .ContentType ;
4140import org .apache .hc .core5 .http .HttpEntity ;
4241import org .apache .hc .core5 .http .NameValuePair ;
4342import org .apache .hc .core5 .http .message .BasicNameValuePair ;
4443import org .apache .hc .core5 .util .Args ;
44+ import org .slf4j .Logger ;
45+ import org .slf4j .LoggerFactory ;
4546
4647/**
4748 * Builder for multipart {@link HttpEntity}s.
49+ * <p>
50+ * This class constructs multipart entities with a boundary determined by either a fixed
51+ * value ("httpclient_boundary_7k9p2m4x8n5j3q6t1r0vwyzabcdefghi") or a random UUID. If no
52+ * boundary is explicitly set via {@link #setBoundary(String)}, it defaults to the fixed
53+ * value unless {@link #withRandomBoundary()} is called to request a random UUID at build
54+ * time. Users can provide a custom boundary with {@link #setBoundary(String)}. A warning
55+ * is logged when no explicit boundary is set via {@link #setBoundary(String)}, encouraging
56+ * deliberate choice.
57+ * </p>
4858 *
4959 * @since 5.0
5060 */
5161 public class MultipartEntityBuilder {
5262
53- /**
54- * The pool of ASCII chars to be used for generating a multipart boundary.
55- */
56- private final static char [] MULTIPART_CHARS =
57- "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
58- .toCharArray ();
59-
6063 private ContentType contentType ;
6164 private HttpMultipartMode mode = HttpMultipartMode .STRICT ;
6265 private String boundary ;
6366 private Charset charset ;
6467 private List <MultipartPart > multipartParts ;
6568
69+
70+ private static final String BOUNDARY_PREFIX = "httpclient_boundary_" ;
71+
72+ private boolean isRandomBoundaryRequested = false ;
73+ /**
74+ * The logger for this class.
75+ */
76+ private static final Logger LOG = LoggerFactory .getLogger (MultipartEntityBuilder .class );
77+
78+
6679 /**
6780 * The preamble of the multipart message.
6881 * This field stores the optional preamble that should be added at the beginning of the multipart message.
@@ -104,6 +117,17 @@ public MultipartEntityBuilder setStrictMode() {
104117 return this ;
105118 }
106119
120+ /**
121+ * Sets a custom boundary string for the multipart entity.
122+ * <p>
123+ * If {@code null} is provided, the builder reverts to its default boundary logic:
124+ * either using a boundary from the {@code contentType} if present, or falling back
125+ * to a fixed or random boundary (depending on {@link #withRandomBoundary()}).
126+ * </p>
127+ *
128+ * @param boundary the boundary string, or {@code null} to use the default boundary logic
129+ * @return this builder instance
130+ */
107131 public MultipartEntityBuilder setBoundary (final String boundary ) {
108132 this .boundary = boundary ;
109133 return this ;
@@ -204,6 +228,20 @@ public MultipartEntityBuilder addBinaryBody(final String name, final InputStream
204228 return addBinaryBody (name , stream , ContentType .DEFAULT_BINARY , null );
205229 }
206230
231+ /**
232+ * Returns the fixed default boundary value.
233+ */
234+ private String getFixedBoundary () {
235+ return BOUNDARY_PREFIX + "7k9p2m4x8n5j3q6t1r0vwyzabcdefghi" ;
236+ }
237+
238+ /**
239+ * Generates a random boundary using UUID.
240+ */
241+ private String getRandomBoundary () {
242+ return BOUNDARY_PREFIX + UUID .randomUUID ();
243+ }
244+
207245 /**
208246 * Adds a preamble to the multipart entity being constructed. The preamble is the text that appears before the first
209247 * boundary delimiter. The preamble is optional and may be null.
@@ -231,15 +269,17 @@ public MultipartEntityBuilder addEpilogue(final String epilogue) {
231269 return this ;
232270 }
233271
234- private String generateBoundary () {
235- final ThreadLocalRandom rand = ThreadLocalRandom .current ();
236- final int count = rand .nextInt (30 , 41 ); // a random size from 30 to 40
237- final CharBuffer buffer = CharBuffer .allocate (count );
238- while (buffer .hasRemaining ()) {
239- buffer .put (MULTIPART_CHARS [rand .nextInt (MULTIPART_CHARS .length )]);
240- }
241- buffer .flip ();
242- return buffer .toString ();
272+ /**
273+ * Configures the builder to request a random boundary generated by UUID.randomUUID()
274+ * at build time if no explicit boundary is set via {@link #setBoundary(String)}.
275+ *
276+ * @return this builder instance
277+ * @since 5.5
278+ */
279+ public MultipartEntityBuilder withRandomBoundary () {
280+ this .isRandomBoundaryRequested = true ;
281+ this .boundary = null ;
282+ return this ;
243283 }
244284
245285 MultipartFormEntity buildEntity () {
@@ -248,7 +288,10 @@ MultipartFormEntity buildEntity() {
248288 boundaryCopy = contentType .getParameter ("boundary" );
249289 }
250290 if (boundaryCopy == null ) {
251- boundaryCopy = generateBoundary ();
291+ boundaryCopy = isRandomBoundaryRequested ? getRandomBoundary () : getFixedBoundary ();
292+ if (LOG .isWarnEnabled ()) {
293+ LOG .warn ("No boundary explicitly set; using generated default: {}" , boundaryCopy );
294+ }
252295 }
253296 Charset charsetCopy = charset ;
254297 if (charsetCopy == null && contentType != null ) {
0 commit comments