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);