This is an automated email from the ASF dual-hosted git repository.

vladimirsitnikov pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/jmeter.git


The following commit(s) were added to refs/heads/master by this push:
     new 64e4fcca20 issue#556: Avoid adding "; charset=" for 
multipart/form-data requests
64e4fcca20 is described below

commit 64e4fcca20f6a5f1a60a6f148b9dd188b2233731
Author: Vladimir Sitnikov <[email protected]>
AuthorDate: Mon Mar 18 15:52:09 2024 +0300

    issue#556: Avoid adding "; charset=" for multipart/form-data requests
    
    Fixes https://github.com/apache/jmeter/issues/6250
    
    See https://issues.apache.org/jira/browse/HTTPCLIENT-2325
    See https://github.com/apache/httpcomponents-client/pull/556
    
    Co-authored-by: dongfangtianyu 
<[email protected]>
---
 .../http/entity/mime/MultipartEntityBuilder2.java  | 244 +++++++++++++++++++++
 .../jmeter/protocol/http/sampler/HTTPHC4Impl.java  |   4 +-
 .../protocol/http/sampler/TestHTTPHC4Impl.java     |  19 ++
 3 files changed, 265 insertions(+), 2 deletions(-)

diff --git 
a/src/protocol/http/src/main/java/org/apache/http/entity/mime/MultipartEntityBuilder2.java
 
b/src/protocol/http/src/main/java/org/apache/http/entity/mime/MultipartEntityBuilder2.java
new file mode 100644
index 0000000000..d549d81a1b
--- /dev/null
+++ 
b/src/protocol/http/src/main/java/org/apache/http/entity/mime/MultipartEntityBuilder2.java
@@ -0,0 +1,244 @@
+/*
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation.  For more
+ * information on the Apache Software Foundation, please see
+ * <http://www.apache.org/>.
+ *
+ */
+
+package org.apache.http.entity.mime;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+
+import org.apache.http.HttpEntity;
+import org.apache.http.NameValuePair;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.mime.content.ByteArrayBody;
+import org.apache.http.entity.mime.content.ContentBody;
+import org.apache.http.entity.mime.content.FileBody;
+import org.apache.http.entity.mime.content.InputStreamBody;
+import org.apache.http.entity.mime.content.StringBody;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.util.Args;
+import org.apiguardian.api.API;
+
+/**
+ * Builder for multipart {@link HttpEntity}s.
+ *
+ * Note: the code was taken from
+ * <a 
href="https://github.com/apache/httpcomponents-client/blob/54900db4653d7f207477e6ee40135b88e9bcf832/httpmime/src/main/java/org/apache/http/entity/mime/MultipartEntityBuilder.java";>MultipartEntityBuilder
 4.5.14</a>
+ *
+ */
+@API(status = API.Status.INTERNAL, since = "5.6.4")
+public class MultipartEntityBuilder2 {
+
+    /**
+     * The pool of ASCII chars to be used for generating a multipart boundary.
+     */
+    private final static char[] MULTIPART_CHARS =
+            "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+                    .toCharArray();
+
+    private final static String DEFAULT_SUBTYPE = "form-data";
+
+    private ContentType contentType;
+    private HttpMultipartMode mode = HttpMultipartMode.STRICT;
+    private String boundary = null;
+    private Charset charset = null;
+    private List<FormBodyPart> bodyParts = null;
+
+    public static MultipartEntityBuilder2 create() {
+        return new MultipartEntityBuilder2();
+    }
+
+    MultipartEntityBuilder2() {
+    }
+
+    public MultipartEntityBuilder2 setMode(final HttpMultipartMode mode) {
+        this.mode = mode;
+        return this;
+    }
+
+    public MultipartEntityBuilder2 setLaxMode() {
+        this.mode = HttpMultipartMode.BROWSER_COMPATIBLE;
+        return this;
+    }
+
+    public MultipartEntityBuilder2 setStrictMode() {
+        this.mode = HttpMultipartMode.STRICT;
+        return this;
+    }
+
+    public MultipartEntityBuilder2 setBoundary(final String boundary) {
+        this.boundary = boundary;
+        return this;
+    }
+
+    /**
+     * @since 4.4
+     */
+    public MultipartEntityBuilder2 setMimeSubtype(final String subType) {
+        Args.notBlank(subType, "MIME subtype");
+        this.contentType = ContentType.create("multipart/" + subType);
+        return this;
+    }
+
+    /**
+     * @since 4.4
+     *
+     * @deprecated (4.5) Use {@link 
#setContentType(org.apache.http.entity.ContentType)}.
+     */
+    @Deprecated
+    public MultipartEntityBuilder2 seContentType(final ContentType 
contentType) {
+        return setContentType(contentType);
+    }
+
+    /**
+     * @since 4.5
+     */
+    public MultipartEntityBuilder2 setContentType(final ContentType 
contentType) {
+        Args.notNull(contentType, "Content type");
+        this.contentType = contentType;
+        return this;
+    }
+
+    public MultipartEntityBuilder2 setCharset(final Charset charset) {
+        this.charset = charset;
+        return this;
+    }
+
+    /**
+     * @since 4.4
+     */
+    public MultipartEntityBuilder2 addPart(final FormBodyPart bodyPart) {
+        if (bodyPart == null) {
+            return this;
+        }
+        if (this.bodyParts == null) {
+            this.bodyParts = new ArrayList<FormBodyPart>();
+        }
+        this.bodyParts.add(bodyPart);
+        return this;
+    }
+
+    public MultipartEntityBuilder2 addPart(final String name, final 
ContentBody contentBody) {
+        Args.notNull(name, "Name");
+        Args.notNull(contentBody, "Content body");
+        return addPart(FormBodyPartBuilder.create(name, contentBody).build());
+    }
+
+    public MultipartEntityBuilder2 addTextBody(
+            final String name, final String text, final ContentType 
contentType) {
+        return addPart(name, new StringBody(text, contentType));
+    }
+
+    public MultipartEntityBuilder2 addTextBody(
+            final String name, final String text) {
+        return addTextBody(name, text, ContentType.DEFAULT_TEXT);
+    }
+
+    public MultipartEntityBuilder2 addBinaryBody(
+            final String name, final byte[] b, final ContentType contentType, 
final String filename) {
+        return addPart(name, new ByteArrayBody(b, contentType, filename));
+    }
+
+    public MultipartEntityBuilder2 addBinaryBody(
+            final String name, final byte[] b) {
+        return addBinaryBody(name, b, ContentType.DEFAULT_BINARY, null);
+    }
+
+    public MultipartEntityBuilder2 addBinaryBody(
+            final String name, final File file, final ContentType contentType, 
final String filename) {
+        return addPart(name, new FileBody(file, contentType, filename));
+    }
+
+    public MultipartEntityBuilder2 addBinaryBody(
+            final String name, final File file) {
+        return addBinaryBody(name, file, ContentType.DEFAULT_BINARY, file != 
null ? file.getName() : null);
+    }
+
+    public MultipartEntityBuilder2 addBinaryBody(
+            final String name, final InputStream stream, final ContentType 
contentType,
+            final String filename) {
+        return addPart(name, new InputStreamBody(stream, contentType, 
filename));
+    }
+
+    public MultipartEntityBuilder2 addBinaryBody(final String name, final 
InputStream stream) {
+        return addBinaryBody(name, stream, ContentType.DEFAULT_BINARY, null);
+    }
+
+    private static String generateBoundary() {
+        final StringBuilder buffer = new StringBuilder();
+        final Random rand = new Random();
+        final int count = rand.nextInt(11) + 30; // a random size from 30 to 40
+        for (int i = 0; i < count; i++) {
+            
buffer.append(MULTIPART_CHARS[rand.nextInt(MULTIPART_CHARS.length)]);
+        }
+        return buffer.toString();
+    }
+
+    MultipartFormEntity buildEntity() {
+        String boundaryCopy = boundary;
+        if (boundaryCopy == null && contentType != null) {
+            boundaryCopy = contentType.getParameter("boundary");
+        }
+        if (boundaryCopy == null) {
+            boundaryCopy = generateBoundary();
+        }
+        Charset charsetCopy = charset;
+        if (charsetCopy == null && contentType != null) {
+            charsetCopy = contentType.getCharset();
+        }
+        // JMeter update: charset is no longer explicitly added
+        // See https://github.com/apache/httpcomponents-client/pull/556
+        final NameValuePair[] params = new NameValuePair[]{new 
BasicNameValuePair("boundary", boundaryCopy)};
+        final ContentType contentTypeCopy = contentType != null ?
+                contentType.withParameters(params) :
+                ContentType.create("multipart/" + DEFAULT_SUBTYPE, params);
+        final List<FormBodyPart> bodyPartsCopy = bodyParts != null ? new 
ArrayList<>(bodyParts) :
+                Collections.emptyList();
+        final HttpMultipartMode modeCopy = mode != null ? mode : 
HttpMultipartMode.STRICT;
+        final AbstractMultipartForm form;
+        switch (modeCopy) {
+            case BROWSER_COMPATIBLE:
+                form = new HttpBrowserCompatibleMultipart(charsetCopy, 
boundaryCopy, bodyPartsCopy);
+                break;
+            case RFC6532:
+                form = new HttpRFC6532Multipart(charsetCopy, boundaryCopy, 
bodyPartsCopy);
+                break;
+            default:
+                form = new HttpStrictMultipart(charsetCopy, boundaryCopy, 
bodyPartsCopy);
+        }
+        return new MultipartFormEntity(form, contentTypeCopy, 
form.getTotalLength());
+    }
+
+    public HttpEntity build() {
+        return buildEntity();
+    }
+
+}
diff --git 
a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/sampler/HTTPHC4Impl.java
 
b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/sampler/HTTPHC4Impl.java
index e9d21660a1..2e68cb2226 100644
--- 
a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/sampler/HTTPHC4Impl.java
+++ 
b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/sampler/HTTPHC4Impl.java
@@ -102,7 +102,7 @@ import org.apache.http.entity.StringEntity;
 import org.apache.http.entity.mime.FormBodyPart;
 import org.apache.http.entity.mime.FormBodyPartBuilder;
 import org.apache.http.entity.mime.HttpMultipartMode;
-import org.apache.http.entity.mime.MultipartEntityBuilder;
+import org.apache.http.entity.mime.MultipartEntityBuilder2;
 import org.apache.http.entity.mime.content.FileBody;
 import org.apache.http.entity.mime.content.StringBody;
 import org.apache.http.impl.auth.BasicScheme;
@@ -1552,7 +1552,7 @@ public class HTTPHC4Impl extends HTTPHCAbstractImpl {
                         doBrowserCompatibleMultipart, charset, 
haveContentEncoding);
             }
             // Write the request to our own stream
-            MultipartEntityBuilder multipartEntityBuilder = 
MultipartEntityBuilder.create();
+            MultipartEntityBuilder2 multipartEntityBuilder = 
MultipartEntityBuilder2.create();
             multipartEntityBuilder.setCharset(charset);
             if (doBrowserCompatibleMultipart) {
                 multipartEntityBuilder.setLaxMode();
diff --git 
a/src/protocol/http/src/test/java/org/apache/jmeter/protocol/http/sampler/TestHTTPHC4Impl.java
 
b/src/protocol/http/src/test/java/org/apache/jmeter/protocol/http/sampler/TestHTTPHC4Impl.java
index 315806d1b8..0f6525e2bb 100644
--- 
a/src/protocol/http/src/test/java/org/apache/jmeter/protocol/http/sampler/TestHTTPHC4Impl.java
+++ 
b/src/protocol/http/src/test/java/org/apache/jmeter/protocol/http/sampler/TestHTTPHC4Impl.java
@@ -97,6 +97,25 @@ public class TestHTTPHC4Impl {
         Assertions.assertTrue(requestData.contains("charset=utf-8"));
     }
 
+    @Test
+    void testMultipartFormHeaderWithoutCharset() throws Exception {
+        HTTPSamplerBase sampler = (HTTPSamplerBase) new 
HttpTestSampleGui().createTestElement();
+        sampler.setThreadContext(jmctx);
+        sampler.setDoMultipart(true);
+        sampler.setDoBrowserCompatibleMultipart(true);
+        sampler.setHTTPFiles(new HTTPFileArg[] {new HTTPFileArg("filename", 
"file", "application/octect; charset=utf-8")});
+        sampler.getArguments().addArgument(new HTTPArgument("param", "value"));
+        HTTPHC4Impl hc = new HTTPHC4Impl(sampler);
+
+        HttpEntityEnclosingRequestBase post = new HttpPost();
+        hc.setupHttpEntityEnclosingRequestData(post);
+        String contentTypeWithEntity = 
post.getEntity().getContentType().getValue();
+        Assertions.assertFalse(
+                contentTypeWithEntity.contains("charset"),
+                () -> "multipart/form-data's Content-Type should not contain 
charset=.... Got " + contentTypeWithEntity
+        );
+    }
+
     @Test
     public void 
testNotifyFirstSampleAfterLoopRestartWhenThreadIterationIsSameUser() {
         jmvars.putObject(SAME_USER, true);

Reply via email to