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

fschumacher 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 f045cf5  Sending mime type with parameter throws 
IllegalArgumentException
f045cf5 is described below

commit f045cf5e1604c68b7d43986fe9bd82a102fa2b76
Author: Felix Schumacher <felix.schumac...@internetallee.de>
AuthorDate: Wed Dec 23 10:45:44 2020 +0100

    Sending mime type with parameter throws IllegalArgumentException
    
    More stuff from that bugzilla entry:
    
    * Better distinguishing between a quoted filename and a quoted name, that 
contains an @ char
    * Allow more than one header and parameter with the same name to be added
    * Simplify the code in ParseCurlCommandAction by moving some of the logic 
in extra classes
    
    What is still missing are curl specialities like
    
     --form file=@"file with; semicolon or space";type=text/something
     --form param="red green blue";headers="X-Something: \"yeah; something\""
    
    Bugzilla Id: 65024
---
 src/protocol/build.gradle.kts                      |  8 +++
 .../jmeter/protocol/http/curl/ArgumentHolder.java  | 55 +++++++++++++++
 .../jmeter/protocol/http/curl/BasicCurlParser.java | 51 +++++++++-----
 .../protocol/http/curl/FileArgumentHolder.java     | 80 ++++++++++++++++++++++
 .../protocol/http/curl/StringArgumentHolder.java   | 74 ++++++++++++++++++++
 .../http/gui/action/ParseCurlCommandAction.java    | 57 +++++++--------
 .../apache/jmeter/curl/BasicCurlParserTest.java    | 72 ++++++++++++++-----
 7 files changed, 334 insertions(+), 63 deletions(-)

diff --git a/src/protocol/build.gradle.kts b/src/protocol/build.gradle.kts
index d0855cf..263eec8 100644
--- a/src/protocol/build.gradle.kts
+++ b/src/protocol/build.gradle.kts
@@ -89,6 +89,14 @@ project("http") {
         implementation("com.fasterxml.jackson.core:jackson-databind")
         testImplementation(testFixtures(project(":src:testkit-wiremock")))
         testImplementation("com.github.tomakehurst:wiremock-jre8")
+        // For some reason JMeter bundles just tika-core and tika-parsers 
without transitive
+        // dependencies. So we exclude those
+        implementation("org.apache.tika:tika-core") {
+            isTransitive = false
+        }
+        runtimeOnly("org.apache.tika:tika-parsers") {
+            isTransitive = false
+        }
     }
 }
 
diff --git 
a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/curl/ArgumentHolder.java
 
b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/curl/ArgumentHolder.java
new file mode 100644
index 0000000..ea9294a
--- /dev/null
+++ 
b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/curl/ArgumentHolder.java
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+
+package org.apache.jmeter.protocol.http.curl;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.commons.lang3.tuple.Pair;
+
+public interface ArgumentHolder {
+
+    String getName();
+
+    Map<String, String> getMetadata();
+
+    default String getContentType() {
+        return getMetadata().get("type");
+    }
+
+    default boolean hasContenType() {
+        return getMetadata().containsKey("type");
+    }
+
+    static Pair<String, Map<String, String>> parse(String name) {
+        if (name.contains(";")) {
+            String[] parts = name.split(";");
+            String realName = parts[0];
+            Map<String, String> metadata = new HashMap<>();
+            for (int i = 1; i< parts.length; i++) {
+                String[] typeParts = parts[i].split("\\s*=\\s*", 2);
+                metadata.put(typeParts[0].toLowerCase(Locale.US), 
typeParts[1]);
+            }
+            return Pair.of(realName, metadata);
+        } else {
+            return Pair.of(name, Collections.emptyMap());
+        }
+    }
+}
diff --git 
a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/curl/BasicCurlParser.java
 
b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/curl/BasicCurlParser.java
index 9471130..4fb6581 100644
--- 
a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/curl/BasicCurlParser.java
+++ 
b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/curl/BasicCurlParser.java
@@ -32,6 +32,7 @@ import java.util.Collections;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
@@ -43,6 +44,8 @@ import org.apache.commons.cli.avalon.CLArgsParser;
 import org.apache.commons.cli.avalon.CLOption;
 import org.apache.commons.cli.avalon.CLOptionDescriptor;
 import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.tuple.Pair;
 import org.apache.jmeter.protocol.http.control.AuthManager.Mechanism;
 import org.apache.jmeter.protocol.http.control.Authorization;
 import org.apache.jmeter.protocol.http.control.Cookie;
@@ -123,7 +126,7 @@ public class BasicCurlParser {
     public static final class Request {
         private boolean compressed;
         private String url;
-        private Map<String, String> headers = new LinkedHashMap<>();
+        private List<Pair<String, String>> headers = new ArrayList<>();
         private String method = "GET";
         private String postData;
         private String interfaceName;
@@ -133,8 +136,8 @@ public class BasicCurlParser {
         private String filepathCookie="";
         private Authorization authorization = new Authorization();
         private String caCert = "";
-        private Map<String, String> formData = new LinkedHashMap<>();
-        private Map<String, String> formStringData = new LinkedHashMap<>();
+        private List<Pair<String, ArgumentHolder>> formData = new 
ArrayList<>();
+        private List<Pair<String, String>> formStringData = new ArrayList<>();
         private Set<String> dnsServers = new HashSet<>();
         private boolean isKeepAlive = true;
         private double maxTime = -1;
@@ -146,6 +149,7 @@ public class BasicCurlParser {
         private int limitRate = 0;
         private String noproxy;
         private static final List<String> HEADERS_TO_IGNORE = 
Arrays.asList("Connection", "Host");// $NON-NLS-1$
+        private static final List<String> UNIQUE_HEADERS = 
Arrays.asList("user-agent"); // $NON-NLS-1$
         private static final int ONE_KILOBYTE_IN_CPS = 1024;
         public Request() {
             super();
@@ -169,7 +173,11 @@ public class BasicCurlParser {
          * @param value the post data
          */
         public void setPostData(String value) {
-            this.postData = value;
+            if (StringUtils.isBlank(this.postData)) {
+                this.postData = value;
+            } else {
+                this.postData = this.postData + "&" + value;
+            }
         }
 
         /**
@@ -200,8 +208,13 @@ public class BasicCurlParser {
         public void addHeader(String name, String value) {
             if ("COOKIE".equalsIgnoreCase(name)) {
                 this.cookieInHeaders = value;
-            } else if (!HEADERS_TO_IGNORE.contains(name)) {
-                headers.put(name, value);
+            } else if (HEADERS_TO_IGNORE.contains(name)) {
+                return;
+            } else {
+                if (UNIQUE_HEADERS.contains(name.toLowerCase(Locale.US))) {
+                    headers.removeIf(p -> p.getLeft().equalsIgnoreCase(name));
+                }
+                headers.add(Pair.of(name, value));
             }
         }
 
@@ -239,8 +252,8 @@ public class BasicCurlParser {
         /**
          * @return the headers
          */
-        public Map<String, String> getHeaders() {
-            return Collections.unmodifiableMap(this.headers);
+        public List<Pair<String, String>> getHeaders() {
+            return Collections.unmodifiableList(this.headers);
         }
 
         /**
@@ -406,8 +419,8 @@ public class BasicCurlParser {
         /**
          * @return the map of form data
          */
-        public Map<String, String> getFormStringData() {
-            return Collections.unmodifiableMap(this.formStringData);
+        public List<Pair<String,String>> getFormStringData() {
+            return Collections.unmodifiableList(this.formStringData);
         }
 
         /**
@@ -415,22 +428,22 @@ public class BasicCurlParser {
          * @param value the value of form data
          */
         public void addFormStringData(String key, String value) {
-            formStringData.put(key, value);
+            formStringData.add(Pair.of(key, value));
         }
 
         /**
          * @return the map of form data
          */
-        public Map<String, String> getFormData() {
-            return Collections.unmodifiableMap(this.formData);
+        public List<Pair<String,ArgumentHolder>> getFormData() {
+            return Collections.unmodifiableList(this.formData);
         }
 
         /**
          * @param key   the key of form data
          * @param value the value of form data
          */
-        public void addFormData(String key, String value) {
-            formData.put(key, value);
+        public void addFormData(String key, ArgumentHolder value) {
+            formData.add(Pair.of(key, value));
         }
 
         /**
@@ -541,7 +554,7 @@ public class BasicCurlParser {
             new CLOptionDescriptor("request", 
CLOptionDescriptor.ARGUMENT_REQUIRED, METHOD_OPT,
                     "Pass custom header LINE to server");
     private static final CLOptionDescriptor D_DATA_OPT =
-            new CLOptionDescriptor("data", 
CLOptionDescriptor.ARGUMENT_REQUIRED, DATA_OPT,
+            new CLOptionDescriptor("data", 
CLOptionDescriptor.ARGUMENT_REQUIRED | CLOptionDescriptor.DUPLICATES_ALLOWED, 
DATA_OPT,
                     "HTTP POST data");
     private static final CLOptionDescriptor D_DATA_ASCII_OPT = new 
CLOptionDescriptor("data-ascii",
             CLOptionDescriptor.ARGUMENT_REQUIRED, DATA_ASCII_OPT, "HTTP POST 
ascii data ");
@@ -712,7 +725,11 @@ public class BasicCurlParser {
                     if 
("form-string".equals(option.getDescriptor().getName())) {
                         request.addFormStringData(key, unquote(value));
                     } else {
-                        request.addFormData(key, unquote(value));
+                        if (value.charAt(0) == '@') {
+                            request.addFormData(key, 
FileArgumentHolder.of(unquote(value.substring(1))));
+                        } else {
+                            request.addFormData(key, 
StringArgumentHolder.of(unquote(value)));
+                        }
                     }
                     request.setMethod("POST");
                 } else if (option.getDescriptor().getId() == USER_AGENT_OPT) {
diff --git 
a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/curl/FileArgumentHolder.java
 
b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/curl/FileArgumentHolder.java
new file mode 100644
index 0000000..a11c652
--- /dev/null
+++ 
b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/curl/FileArgumentHolder.java
@@ -0,0 +1,80 @@
+/*
+ * 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.
+ */
+
+package org.apache.jmeter.protocol.http.curl;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Objects;
+
+import org.apache.commons.lang3.tuple.Pair;
+
+public class FileArgumentHolder implements ArgumentHolder {
+    private String name;
+    private Map<String, String> metadata;
+
+    private FileArgumentHolder(String name, Map<String, String> metadata) {
+        this.name = name;
+        this.metadata = metadata;
+    };
+
+    public static FileArgumentHolder of(String name) {
+        if (name == null) {
+            return new FileArgumentHolder("", Collections.emptyMap());
+        }
+        Pair<String, Map<String, String>> parsed = ArgumentHolder.parse(name);
+        return new FileArgumentHolder(parsed.getLeft(), parsed.getRight());
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((name == null) ? 0 : name.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        FileArgumentHolder other = (FileArgumentHolder) obj;
+        return Objects.equals(name, other.name);
+    }
+
+    @Override
+    public String getName() {
+        return this.name;
+    }
+
+    @Override
+    public String toString() {
+        return "FileArgumentHolder(" + name + ", " + metadata + ")";
+    }
+
+    @Override
+    public Map<String, String> getMetadata() {
+        return Collections.unmodifiableMap(metadata);
+    }
+}
diff --git 
a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/curl/StringArgumentHolder.java
 
b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/curl/StringArgumentHolder.java
new file mode 100644
index 0000000..3c1db0e
--- /dev/null
+++ 
b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/curl/StringArgumentHolder.java
@@ -0,0 +1,74 @@
+/*
+ * 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.
+ */
+
+package org.apache.jmeter.protocol.http.curl;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Objects;
+
+import org.apache.commons.lang3.tuple.Pair;
+
+public class StringArgumentHolder implements ArgumentHolder {
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(metadata, name);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        StringArgumentHolder other = (StringArgumentHolder) obj;
+        return Objects.equals(metadata, other.metadata) && 
Objects.equals(name, other.name);
+    }
+
+    private String name;
+    private Map<String, String> metadata;
+
+    private StringArgumentHolder(String name, Map<String, String> metadata) {
+        this.name = name;
+        this.metadata = metadata;
+    }
+
+    public static StringArgumentHolder of(String name) {
+        Pair<String, Map<String, String>> argdata = ArgumentHolder.parse(name);
+        return new StringArgumentHolder(argdata.getLeft(), argdata.getRight());
+    }
+
+    @Override
+    public String getName() {
+        return this.name;
+    }
+
+    public Map<String, String> getMetadata() {
+        return Collections.unmodifiableMap(metadata);
+    }
+
+    @Override
+    public String toString() {
+        return "StringArgumentHolder(" + name + ", " + metadata + ")";
+    }
+}
diff --git 
a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/gui/action/ParseCurlCommandAction.java
 
b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/gui/action/ParseCurlCommandAction.java
index e03ae17..d610b52 100644
--- 
a/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/gui/action/ParseCurlCommandAction.java
+++ 
b/src/protocol/http/src/main/java/org/apache/jmeter/protocol/http/gui/action/ParseCurlCommandAction.java
@@ -38,7 +38,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
-import javax.activation.MimetypesFileTypeMap;
 import javax.swing.JButton;
 import javax.swing.JCheckBox;
 import javax.swing.JLabel;
@@ -52,6 +51,7 @@ import javax.swing.tree.TreePath;
 
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.tuple.Pair;
 import org.apache.jmeter.config.Arguments;
 import org.apache.jmeter.config.KeystoreConfig;
 import org.apache.jmeter.control.Controller;
@@ -79,8 +79,10 @@ import org.apache.jmeter.protocol.http.control.Header;
 import org.apache.jmeter.protocol.http.control.HeaderManager;
 import org.apache.jmeter.protocol.http.control.StaticHost;
 import org.apache.jmeter.protocol.http.control.gui.HttpTestSampleGui;
+import org.apache.jmeter.protocol.http.curl.ArgumentHolder;
 import org.apache.jmeter.protocol.http.curl.BasicCurlParser;
 import org.apache.jmeter.protocol.http.curl.BasicCurlParser.Request;
+import org.apache.jmeter.protocol.http.curl.FileArgumentHolder;
 import org.apache.jmeter.protocol.http.gui.AuthPanel;
 import org.apache.jmeter.protocol.http.gui.CookiePanel;
 import org.apache.jmeter.protocol.http.gui.DNSCachePanel;
@@ -101,6 +103,7 @@ import 
org.apache.jmeter.visualizers.ViewResultsFullVisualizer;
 import org.apache.jorphan.collections.HashTree;
 import org.apache.jorphan.gui.ComponentUtil;
 import org.apache.jorphan.gui.JMeterUIDefaults;
+import org.apache.tika.Tika;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -118,6 +121,7 @@ public class ParseCurlCommandAction extends AbstractAction 
implements MenuCreato
     private static final String CREATE_REQUEST = "CREATE_REQUEST";
     private static final String TYPE_FORM = ";type=";
     private static final String CERT = "cert";
+    private Logger log = LoggerFactory.getLogger(getClass());
     /** A panel allowing results to be saved. */
     private FilePanel filePanel = null;
     static {
@@ -126,6 +130,7 @@ public class ParseCurlCommandAction extends AbstractAction 
implements MenuCreato
     private JSyntaxTextArea cURLCommandTA;
     private JLabel statusText;
     private JCheckBox uploadCookiesCheckBox;
+    private final Tika tika = new Tika();
     public ParseCurlCommandAction() {
         super();
     }
@@ -344,9 +349,8 @@ public class ParseCurlCommandAction extends AbstractAction 
implements MenuCreato
         headerManager.setProperty(TestElement.GUI_CLASS, 
HeaderPanel.class.getName());
         headerManager.setProperty(TestElement.NAME, "HTTP HeaderManager");
         headerManager.setProperty(TestElement.COMMENTS, getDefaultComment());
-        Map<String, String> map = request.getHeaders();
         boolean hasAcceptEncoding = false;
-        for (Map.Entry<String, String> header : map.entrySet()) {
+        for (Pair<String, String> header : request.getHeaders()) {
             String key = header.getKey();
             hasAcceptEncoding = hasAcceptEncoding || 
key.equalsIgnoreCase(ACCEPT_ENCODING);
             headerManager.getHeaders().addItem(new Header(key, 
header.getValue()));
@@ -515,33 +519,39 @@ public class ParseCurlCommandAction extends 
AbstractAction implements MenuCreato
             throw new IllegalArgumentException("--form and --data can't appear 
in the same command");
         }
         List<HTTPFileArg> httpFileArgs = new ArrayList<>();
-        for (Map.Entry<String, String> entry : 
request.getFormStringData().entrySet()) {
+        for (Pair<String, String> entry : request.getFormStringData()) {
             String formName = entry.getKey();
             String formValue = entry.getValue();
             httpSampler.addNonEncodedArgument(formName, formValue, "");
         }
-        for (Map.Entry<String, String> entry : 
request.getFormData().entrySet()) {
+        for (Pair<String, ArgumentHolder> entry : request.getFormData()) {
             String formName = entry.getKey();
-            String formValue = entry.getValue();
-            boolean isContainsFile = "@".equals(formValue.substring(0, 1));
-            boolean isContainsContentType = 
formValue.toLowerCase().contains(TYPE_FORM);
-            if (isContainsFile) {
-                formValue = unquote(formValue.substring(1, 
formValue.length()));
+            ArgumentHolder formValueObject = entry.getValue();
+            String formValue = formValueObject.getName();
+            if (formValueObject instanceof FileArgumentHolder) {
                 String contentType;
-                if (isContainsContentType) {
-                    String[] formValueWithType = formValue.split(TYPE_FORM);
-                    formValue = formValueWithType[0];
-                    contentType = formValueWithType[1];
+                if (formValueObject.hasContenType()) {
+                    contentType = formValueObject.getContentType();
                 } else {
-                    contentType = new 
MimetypesFileTypeMap().getContentType(formValue);
+                    try {
+                        final File contentFile = new File(formValue);
+                        if (contentFile.canRead()) {
+                            contentType = tika.detect(contentFile);
+                        } else {
+                            log.info("Can not read file {}, so guessing 
contentType by extension.", formValue);
+                            contentType = tika.detect(formValue);
+                        }
+                    } catch (IOException e) {
+                        log.info(
+                                "Could not detect contentType for file {} by 
content, so falling back to detection by filename",
+                                formValue);
+                        contentType = tika.detect(formValue);
+                    }
                 }
                 httpFileArgs.add(new HTTPFileArg(formValue, formName, 
contentType));
             } else {
-                if (isContainsContentType) {
-                    String[] formValueWithType = formValue.split(TYPE_FORM);
-                    formValue = formValueWithType[0];
-                    String contentType = formValueWithType[1];
-                    httpSampler.addNonEncodedArgument(formName, formValue, "", 
contentType);
+                if (formValueObject.hasContenType()) {
+                    httpSampler.addNonEncodedArgument(formName, formValue, "", 
formValueObject.getContentType());
                 } else {
                     httpSampler.addNonEncodedArgument(formName, formValue, "");
                 }
@@ -552,13 +562,6 @@ public class ParseCurlCommandAction extends AbstractAction 
implements MenuCreato
         }
     }
 
-    private String unquote(String substring) {
-        if (substring.charAt(0) == '"') {
-            return substring.substring(1, substring.length() - 
1).replaceAll("\\\\(.)", "$1");
-        }
-        return substring;
-    }
-
     private void createProxyServer(Request request, HTTPSamplerProxy 
httpSampler) {
         Map<String, String> proxyServer = request.getProxyServer();
         for (Map.Entry<String, String> proxyPara : proxyServer.entrySet()) {
diff --git 
a/src/protocol/http/src/test/java/org/apache/jmeter/curl/BasicCurlParserTest.java
 
b/src/protocol/http/src/test/java/org/apache/jmeter/curl/BasicCurlParserTest.java
index adfa954..11bb1ef 100644
--- 
a/src/protocol/http/src/test/java/org/apache/jmeter/curl/BasicCurlParserTest.java
+++ 
b/src/protocol/http/src/test/java/org/apache/jmeter/curl/BasicCurlParserTest.java
@@ -27,11 +27,14 @@ import java.io.IOException;
 import java.nio.charset.StandardCharsets;
 import java.nio.file.Path;
 import java.util.List;
-import java.util.Map;
 
 import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang3.tuple.Pair;
 import org.apache.jmeter.protocol.http.control.Cookie;
+import org.apache.jmeter.protocol.http.curl.ArgumentHolder;
 import org.apache.jmeter.protocol.http.curl.BasicCurlParser;
+import org.apache.jmeter.protocol.http.curl.FileArgumentHolder;
+import org.apache.jmeter.protocol.http.curl.StringArgumentHolder;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.io.TempDir;
@@ -57,10 +60,10 @@ public class BasicCurlParserTest {
         assertEquals(5, request.getHeaders().size());
         assertTrue(request.isCompressed());
         assertEquals("GET", request.getMethod());
-        String resParser = "Request [compressed=true, 
url=http://jmeter.apache.org/, method=GET, headers={User-Agent=Mozilla/5.0 "
-                + "(Macintosh; Intel Mac OS X 10.11; rv:63.0) Gecko/20100101 
Firefox/63.0, Accept=text/html,application/xhtml+xml,"
-                + "application/xml;q=0.9,*/*;q=0.8, 
Accept-Language=en-US,en;q=0.5, DNT=1, "
-                + "Upgrade-Insecure-Requests=1}]";
+        String resParser = "Request [compressed=true, 
url=http://jmeter.apache.org/, method=GET, headers=[(User-Agent,Mozilla/5.0 "
+                +"(Macintosh; Intel Mac OS X 10.11; rv:63.0) Gecko/20100101 
Firefox/63.0), (Accept,text/html,application/xhtml+xml,"
+                + "application/xml;q=0.9,*/*;q=0.8), 
(Accept-Language,en-US,en;q=0.5), (DNT,1), "
+                + "(Upgrade-Insecure-Requests,1)]]";
         assertEquals(resParser, request.toString(),
                 "The method 'toString' should get all parameters correctly");
     }
@@ -139,7 +142,7 @@ public class BasicCurlParserTest {
         String cmdLine = "";
         BasicCurlParser basicCurlParser = new BasicCurlParser();
         BasicCurlParser.Request request = basicCurlParser.parse(cmdLine);
-        assertEquals("Request [compressed=false, url=null, method=GET, 
headers={}]", request.toString(),
+        assertEquals("Request [compressed=false, url=null, method=GET, 
headers=[]]", request.toString(),
                 "The method 'translateCommandline' should return 'null' when 
command is empty, ");
     }
 
@@ -208,7 +211,7 @@ public class BasicCurlParserTest {
         BasicCurlParser.Request request = basicCurlParser.parse(cmdLine);
         assertEquals(5, request.getHeaders().size(),
                 "With method 'parser', the quantity of Headers should be 5'");
-        assertEquals("Mozilla/5.0", request.getHeaders().get("User-Agent"),
+        assertTrue(request.getHeaders().contains(Pair.of("User-Agent", 
"Mozilla/5.0")),
                 "With method 'parser', Headers need to add 'user-agent' with 
value 'Mozilla/5.0' ");
     }
 
@@ -319,6 +322,15 @@ public class BasicCurlParserTest {
     }
 
     @Test
+    public void testDuplicatedKeyInData() {
+        String cmdLine = "curl 'https://example.invalid' "
+                + "-H 'cache-control: no-cache' --data 'name=one' --data 
'name=two' ";
+        BasicCurlParser basicCurlParser = new BasicCurlParser();
+        BasicCurlParser.Request request = basicCurlParser.parse(cmdLine);
+        assertEquals("name=one&name=two", request.getPostData());
+    }
+
+    @Test
     public void testDataReadFromFile() throws IOException {
         String encoding = StandardCharsets.UTF_8.name();
         FileUtils.writeStringToFile(tempFile, "name=test" + 
System.lineSeparator(), encoding, true);
@@ -417,19 +429,29 @@ public class BasicCurlParserTest {
                 + "-H 'cache-control: no-cache' -F 'test=name' -F 
'test1=name1' ";
         BasicCurlParser basicCurlParser = new BasicCurlParser();
         BasicCurlParser.Request request = basicCurlParser.parse(cmdLine);
-        Map<String, String> res = request.getFormData();
-        assertEquals("name1", res.get("test1"),
+        List<Pair<String,ArgumentHolder>> res = request.getFormData();
+        assertTrue(res.contains(Pair.of("test1", 
StringArgumentHolder.of("name1"))),
                 "With method 'parser', we should post form data");
     }
 
     @Test
+    public void testFormWithEmptyValue() {
+        String cmdLine = "curl 'https://example.invalid' -F 'test=\"\"' ";
+        BasicCurlParser basicCurlParser = new BasicCurlParser();
+        BasicCurlParser.Request request = basicCurlParser.parse(cmdLine);
+        List<Pair<String,ArgumentHolder>> res = request.getFormData();
+        assertTrue(res.contains(Pair.of("test", StringArgumentHolder.of(""))),
+                "With method 'parser', we should post form data: " + 
request.getFormData());
+    }
+
+    @Test
     public void testFormWithQuotedValue() {
         String cmdLine = "curl 'https://www.exaple.invalid/' "
                 + "--form 'test=\"something quoted\"'";
         BasicCurlParser basicCurlParser = new BasicCurlParser();
         BasicCurlParser.Request request = basicCurlParser.parse(cmdLine);
-        Map<String, String> res = request.getFormData();
-        assertEquals("something quoted", res.get("test"),
+        List<Pair<String,ArgumentHolder>> res = request.getFormData();
+        assertTrue(res.contains(Pair.of("test", 
StringArgumentHolder.of("something quoted"))),
                 "With method 'form', we should post form data");
     }
 
@@ -439,8 +461,8 @@ public class BasicCurlParserTest {
                 + "--form 'test=\"something \\\"quoted\\\"\"'";
         BasicCurlParser basicCurlParser = new BasicCurlParser();
         BasicCurlParser.Request request = basicCurlParser.parse(cmdLine);
-        Map<String, String> res = request.getFormData();
-        assertEquals("something \"quoted\"", res.get("test"),
+        List<Pair<String,ArgumentHolder>> res = request.getFormData();
+        assertTrue(res.contains(Pair.of("test", 
StringArgumentHolder.of("something \"quoted\""))),
                 "With method 'form', we should post form data");
     }
 
@@ -451,8 +473,20 @@ public class BasicCurlParserTest {
                 + "--form 'image=@\"/some/file.jpg\"'";
         BasicCurlParser basicCurlParser = new BasicCurlParser();
         BasicCurlParser.Request request = basicCurlParser.parse(cmdLine);
-        Map<String, String> res = request.getFormData();
-        assertEquals("@\"/some/file.jpg\"", res.get("image"),
+        List<Pair<String,ArgumentHolder>> res = request.getFormData();
+        assertTrue(res.contains(Pair.of("image", 
FileArgumentHolder.of("/some/file.jpg"))),
+                "With method 'form', we should post form data: " + 
request.getFormData());
+    }
+
+    @Test
+    public void testFormWithQuotedNotFilename() {
+        // The quotes will be removed later by the consumer, which is 
ParseCurlCommandAction
+        String cmdLine = "curl 'https://www.exaple.invalid/' "
+                + "--form 'image=\"@/some/file.jpg\"'";
+        BasicCurlParser basicCurlParser = new BasicCurlParser();
+        BasicCurlParser.Request request = basicCurlParser.parse(cmdLine);
+        List<Pair<String,ArgumentHolder>> res = request.getFormData();
+        assertTrue(res.contains(Pair.of("image", 
StringArgumentHolder.of("@/some/file.jpg"))),
                 "With method 'form', we should post form data");
     }
 
@@ -462,9 +496,9 @@ public class BasicCurlParserTest {
                 + "-H 'cache-control: no-cache' --form-string 
'image=@C:\\Test\\test.jpg' ";
         BasicCurlParser basicCurlParser = new BasicCurlParser();
         BasicCurlParser.Request request = basicCurlParser.parse(cmdLine);
-        Map<String, String> res = request.getFormStringData();
-        assertEquals("@C:\\Test\\test.jpg", res.get("image"),
-                "With method 'parser', we should post form data");
+        List<Pair<String,String>> res = request.getFormStringData();
+        assertTrue(res.contains(Pair.of("image", "@C:\\Test\\test.jpg")),
+                "With method 'parser', we should post form data: " + 
request.getFormStringData());
     }
 
     @Test
@@ -556,7 +590,7 @@ public class BasicCurlParserTest {
         String cmdLine = "curl 'http://jmeter.apache.org/' --referer 
'www.baidu.com'";
         BasicCurlParser basicCurlParser = new BasicCurlParser();
         BasicCurlParser.Request request = basicCurlParser.parse(cmdLine);
-        assertEquals("www.baidu.com", request.getHeaders().get("Referer"));
+        assertTrue(request.getHeaders().contains(Pair.of("Referer", 
"www.baidu.com")));
     }
 
     @Test

Reply via email to