You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jmeter.apache.org by pm...@apache.org on 2018/12/19 09:54:42 UTC

svn commit: r1849285 - in /jmeter/trunk: src/core/org/apache/jmeter/gui/action/ src/core/org/apache/jmeter/resources/ src/protocol/http/org/apache/jmeter/protocol/http/curl/ src/protocol/http/org/apache/jmeter/protocol/http/gui/action/ test/src/org/apa...

Author: pmouawad
Date: Wed Dec 19 09:54:42 2018
New Revision: 1849285

URL: http://svn.apache.org/viewvc?rev=1849285&view=rev
Log:
Bug 62959 - Ability to create a Test plan from a Curl command

Contributed by UbikLoadPack

This closes #436
Bugzilla Id: 62959

Added:
    jmeter/trunk/src/protocol/http/org/apache/jmeter/protocol/http/curl/
    jmeter/trunk/src/protocol/http/org/apache/jmeter/protocol/http/curl/BasicCurlParser.java   (with props)
    jmeter/trunk/src/protocol/http/org/apache/jmeter/protocol/http/gui/action/
    jmeter/trunk/src/protocol/http/org/apache/jmeter/protocol/http/gui/action/ParseCurlCommandAction.java   (with props)
    jmeter/trunk/test/src/org/apache/jmeter/curl/
    jmeter/trunk/test/src/org/apache/jmeter/curl/BasicCurlParserTest.java   (with props)
Modified:
    jmeter/trunk/src/core/org/apache/jmeter/gui/action/ActionNames.java
    jmeter/trunk/src/core/org/apache/jmeter/resources/messages.properties
    jmeter/trunk/src/core/org/apache/jmeter/resources/messages_fr.properties
    jmeter/trunk/xdocs/changes.xml

Modified: jmeter/trunk/src/core/org/apache/jmeter/gui/action/ActionNames.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/core/org/apache/jmeter/gui/action/ActionNames.java?rev=1849285&r1=1849284&r2=1849285&view=diff
==============================================================================
--- jmeter/trunk/src/core/org/apache/jmeter/gui/action/ActionNames.java (original)
+++ jmeter/trunk/src/core/org/apache/jmeter/gui/action/ActionNames.java Wed Dec 19 09:54:42 2018
@@ -115,6 +115,7 @@ public final class ActionNames {
     public static final String VALIDATE_TG      = "validate_tg"; //$NON-NLS-1$
     public static final String ZOOM_IN          = "zoom_in"; //$NON-NLS-1$
     public static final String ZOOM_OUT         = "zoom_out"; //$NON-NLS-1$
+    public static final String PARSE_CURL       = "parse_curl"; ////$NON-NLS-1$
 
     // Prevent instantiation
     private ActionNames() {}

Modified: jmeter/trunk/src/core/org/apache/jmeter/resources/messages.properties
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/core/org/apache/jmeter/resources/messages.properties?rev=1849285&r1=1849284&r2=1849285&view=diff
==============================================================================
--- jmeter/trunk/src/core/org/apache/jmeter/resources/messages.properties (original)
+++ jmeter/trunk/src/core/org/apache/jmeter/resources/messages.properties Wed Dec 19 09:54:42 2018
@@ -243,6 +243,11 @@ cssjquery_tester_button_test=Test
 cssjquery_tester_field=Selector\:
 cssjquery_tester_title=CSS Selector Tester
 csvread_file_file_name=CSV file to get values from | *alias
+curl_import_menu=Import from cURL (alpha)
+curl_import=Create Test plan from cURL
+curl_create_request=Create Test plan
+curl_create_success=Successfully created test plan
+curl_create_failure=Error creating test plan, error:{0}
 cut=Cut
 cut_paste_function=Function syntax\:
 database_conn_pool_max_usage=Max Usage For Each Connection\:

Modified: jmeter/trunk/src/core/org/apache/jmeter/resources/messages_fr.properties
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/core/org/apache/jmeter/resources/messages_fr.properties?rev=1849285&r1=1849284&r2=1849285&view=diff
==============================================================================
--- jmeter/trunk/src/core/org/apache/jmeter/resources/messages_fr.properties (original)
+++ jmeter/trunk/src/core/org/apache/jmeter/resources/messages_fr.properties Wed Dec 19 09:54:42 2018
@@ -238,6 +238,11 @@ cssjquery_tester_error=Une erreur s''est
 cssjquery_tester_field=S\u00E9lecteur\:
 cssjquery_tester_title=Testeur Selecteur CSS
 csvread_file_file_name=Fichier CSV pour obtenir les valeurs de | *alias
+curl_import_menu_import=Import depuis cURL (alpha)
+curl_import=Cr\u00e9er un plan de test \u00e0 partir de cURL
+curl_create_request=Cr\u00e9er plan de test
+curl_create_success=Plan de test cr\u00e9\u00e9 avec succ\u00e8s
+curl_create_failure=Erreur de cr\u00e9ation de plan de test, erreur:{0}.
 cut=Couper
 cut_paste_function=Syntaxe de la fonction \:
 database_conn_pool_max_usage=Utilisation max pour chaque connexion\:

Added: jmeter/trunk/src/protocol/http/org/apache/jmeter/protocol/http/curl/BasicCurlParser.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/protocol/http/org/apache/jmeter/protocol/http/curl/BasicCurlParser.java?rev=1849285&view=auto
==============================================================================
--- jmeter/trunk/src/protocol/http/org/apache/jmeter/protocol/http/curl/BasicCurlParser.java (added)
+++ jmeter/trunk/src/protocol/http/org/apache/jmeter/protocol/http/curl/BasicCurlParser.java Wed Dec 19 09:54:42 2018
@@ -0,0 +1,255 @@
+/*
+ * 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.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+import org.apache.commons.cli.avalon.CLArgsParser;
+import org.apache.commons.cli.avalon.CLOption;
+import org.apache.commons.cli.avalon.CLOptionDescriptor;
+
+/**
+ * Basic cURL command parser that handles:
+ * -X
+ * -H
+ * --compressed
+ * --data POST with Body data
+ * 
+ * @since 5.1
+ */
+public class BasicCurlParser {
+    private static final int METHOD_OPT = 'X';
+    private static final int COMPRESSED_OPT      = 'c';// $NON-NLS-1$
+    private static final int HEADER_OPT      = 'H';// $NON-NLS-1$
+    private static final int DATA_OPT      = 'd';// $NON-NLS-1$
+    
+    public static final class Request {
+        private boolean compressed;
+        private String url;
+        private Map<String, String> headers = new LinkedHashMap<>();
+        private String method = "GET";
+        private String postData;
+        /**
+         */
+        public Request() {
+            super();
+        }
+        /**
+         * @return the compressed
+         */
+        public boolean isCompressed() {
+            return compressed;
+        }
+        /**
+         * @param compressed the compressed to set
+         */
+        public void setCompressed(boolean compressed) {
+            this.compressed = compressed;
+        }
+        
+        public void addHeader(String name, String value) {
+            headers.put(name, value);
+        }
+        /**
+         * @return the url
+         */
+        public String getUrl() {
+            return url;
+        }
+        /**
+         * @param url the url to set
+         */
+        public void setUrl(String url) {
+            this.url = url;
+        }
+        /**
+         * @return the headers
+         */
+        public Map<String, String> getHeaders() {
+            return headers;
+        }
+        /* (non-Javadoc)
+         * @see java.lang.Object#toString()
+         */
+        @Override
+        public String toString() {
+            StringBuilder builder = new StringBuilder();
+            builder.append("Request [compressed=");
+            builder.append(compressed);
+            builder.append(", url=");
+            builder.append(url);
+            builder.append(", method=");
+            builder.append(method);
+            builder.append(", headers=");
+            builder.append(headers);
+            builder.append("]");
+            return builder.toString();
+        }
+        public String getMethod() {
+            return method;
+        }
+        /**
+         * @param method the method to set
+         */
+        public void setMethod(String method) {
+            this.method = method;
+        }
+        public void setPostData(String value) {
+            this.postData = value;
+        }
+        /**
+         * @return the postData
+         */
+        public String getPostData() {
+            return postData;
+        }
+    }
+    private static final CLOptionDescriptor D_COMPRESSED_OPT =
+            new CLOptionDescriptor("compressed", CLOptionDescriptor.ARGUMENT_DISALLOWED, COMPRESSED_OPT,
+                    "Request compressed response (using deflate or gzip)");
+    private static final CLOptionDescriptor D_HEADER_OPT =
+            new CLOptionDescriptor("header", CLOptionDescriptor.ARGUMENT_REQUIRED | CLOptionDescriptor.DUPLICATES_ALLOWED, HEADER_OPT,
+                    "Pass custom header LINE to server");
+    private static final CLOptionDescriptor D_METHOD_OPT =
+            new CLOptionDescriptor("command", 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,
+                    "HTTP POST data");
+
+
+    private static final CLOptionDescriptor[] OPTIONS = new CLOptionDescriptor[] {
+            D_COMPRESSED_OPT,
+            D_HEADER_OPT,
+            D_METHOD_OPT,
+            D_DATA_OPT
+    };
+    
+    public BasicCurlParser() {
+        super();
+    }
+    
+    public Request parse(String commandLine) {
+        String[] args = translateCommandline(commandLine);
+        CLArgsParser parser = new CLArgsParser(args, OPTIONS);
+        String error = parser.getErrorString();
+        if(error == null) {
+            List<CLOption> clOptions = parser.getArguments();
+            Request request = new Request();
+            for (CLOption option : clOptions) {
+                if (option.getDescriptor().getId() == CLOption.TEXT_ARGUMENT) {
+                    // Curl or URL
+                    if(!"CURL".equalsIgnoreCase(option.getArgument())) {
+                        request.setUrl(option.getArgument());
+                        continue;
+                    }
+                } else if (option.getDescriptor().getId() == COMPRESSED_OPT) {
+                    request.setCompressed(true);
+                } else if (option.getDescriptor().getId() == HEADER_OPT) {
+                    String nameAndValue = option.getArgument(0);
+                    int indexOfSemicolon = nameAndValue.indexOf(':');
+                    String name = nameAndValue.substring(0, indexOfSemicolon).trim();
+                    String value = nameAndValue.substring(indexOfSemicolon+1).trim();
+                    request.addHeader(name, value);
+                } else if (option.getDescriptor().getId() == METHOD_OPT) {
+                    String value = option.getArgument(0);
+                    request.setMethod(value);
+                } else if (option.getDescriptor().getId() == DATA_OPT) {
+                    String value = option.getArgument(0);
+                    request.setMethod("POST");
+                    request.setPostData(value);
+                }
+            }
+            return request;
+        } else {
+            throw new IllegalArgumentException("Unexpected format for command line:"+commandLine+", error:"+error);
+        }
+    }
+    
+    /**
+     * Crack a command line.
+     * @param toProcess the command line to process.
+     * @return the command line broken into strings.
+     * An empty or null toProcess parameter results in a zero sized array.
+     */
+    public static String[] translateCommandline(String toProcess) {
+        if (toProcess == null || toProcess.isEmpty()) {
+            //no command? no string
+            return new String[0];
+        }
+        // parse with a simple finite state machine
+
+        final int normal = 0;
+        final int inQuote = 1;
+        final int inDoubleQuote = 2;
+        int state = normal;
+        final StringTokenizer tok = new StringTokenizer(toProcess, "\"\' ", true);
+        final ArrayList<String> result = new ArrayList<>();
+        final StringBuilder current = new StringBuilder();
+        boolean lastTokenHasBeenQuoted = false;
+
+        while (tok.hasMoreTokens()) {
+            String nextTok = tok.nextToken();
+            switch (state) {
+            case inQuote:
+                if ("\'".equals(nextTok)) {
+                    lastTokenHasBeenQuoted = true;
+                    state = normal;
+                } else {
+                    current.append(nextTok);
+                }
+                break;
+            case inDoubleQuote:
+                if ("\"".equals(nextTok)) {
+                    lastTokenHasBeenQuoted = true;
+                    state = normal;
+                } else {
+                    current.append(nextTok);
+                }
+                break;
+            default:
+                if ("\'".equals(nextTok)) {
+                    state = inQuote;
+                } else if ("\"".equals(nextTok)) {
+                    state = inDoubleQuote;
+                } else if (" ".equals(nextTok)) {
+                    if (lastTokenHasBeenQuoted || current.length() > 0) {
+                        result.add(current.toString());
+                        current.setLength(0);
+                    }
+                } else {
+                    current.append(nextTok);
+                }
+                lastTokenHasBeenQuoted = false;
+                break;
+            }
+        }
+        if (lastTokenHasBeenQuoted || current.length() > 0) {
+            result.add(current.toString());
+        }
+        if (state == inQuote || state == inDoubleQuote) {
+            throw new IllegalArgumentException("unbalanced quotes in " + toProcess);
+        }
+        return result.toArray(new String[result.size()]);
+    }
+}

Propchange: jmeter/trunk/src/protocol/http/org/apache/jmeter/protocol/http/curl/BasicCurlParser.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jmeter/trunk/src/protocol/http/org/apache/jmeter/protocol/http/curl/BasicCurlParser.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: jmeter/trunk/src/protocol/http/org/apache/jmeter/protocol/http/gui/action/ParseCurlCommandAction.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/protocol/http/org/apache/jmeter/protocol/http/gui/action/ParseCurlCommandAction.java?rev=1849285&view=auto
==============================================================================
--- jmeter/trunk/src/protocol/http/org/apache/jmeter/protocol/http/gui/action/ParseCurlCommandAction.java (added)
+++ jmeter/trunk/src/protocol/http/org/apache/jmeter/protocol/http/gui/action/ParseCurlCommandAction.java Wed Dec 19 09:54:42 2018
@@ -0,0 +1,272 @@
+/*
+ * 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.gui.action;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Container;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.text.MessageFormat;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+import javax.swing.JPanel;
+import javax.swing.MenuElement;
+import javax.swing.SwingUtilities;
+import javax.swing.tree.TreePath;
+
+import org.apache.jmeter.config.Arguments;
+import org.apache.jmeter.control.LoopController;
+import org.apache.jmeter.control.gui.TestPlanGui;
+import org.apache.jmeter.exceptions.IllegalUserActionException;
+import org.apache.jmeter.gui.GuiPackage;
+import org.apache.jmeter.gui.action.AbstractAction;
+import org.apache.jmeter.gui.action.ActionNames;
+import org.apache.jmeter.gui.action.ActionRouter;
+import org.apache.jmeter.gui.plugin.MenuCreator;
+import org.apache.jmeter.gui.tree.JMeterTreeNode;
+import org.apache.jmeter.gui.util.EscapeDialog;
+import org.apache.jmeter.gui.util.JSyntaxTextArea;
+import org.apache.jmeter.gui.util.JTextScrollPane;
+import org.apache.jmeter.protocol.http.control.Header;
+import org.apache.jmeter.protocol.http.control.HeaderManager;
+import org.apache.jmeter.protocol.http.control.gui.HttpTestSampleGui;
+import org.apache.jmeter.protocol.http.curl.BasicCurlParser;
+import org.apache.jmeter.protocol.http.curl.BasicCurlParser.Request;
+import org.apache.jmeter.protocol.http.gui.HeaderPanel;
+import org.apache.jmeter.protocol.http.sampler.HTTPSamplerFactory;
+import org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy;
+import org.apache.jmeter.reporters.ResultCollector;
+import org.apache.jmeter.services.FileServer;
+import org.apache.jmeter.testelement.TestElement;
+import org.apache.jmeter.testelement.TestPlan;
+import org.apache.jmeter.threads.ThreadGroup;
+import org.apache.jmeter.threads.gui.ThreadGroupGui;
+import org.apache.jmeter.util.JMeterUtils;
+import org.apache.jmeter.visualizers.ViewResultsFullVisualizer;
+import org.apache.jorphan.collections.HashTree;
+import org.apache.jorphan.gui.ComponentUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Opens a popup where user can enter a cURL command line and create a test plan from it
+ * @since 5.1
+ */
+public class ParseCurlCommandAction extends AbstractAction implements MenuCreator, ActionListener { // NOSONAR 
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(ParseCurlCommandAction.class);
+    private static final String ACCEPT_ENCODING = "Accept-Encoding";
+    private static final Set<String> commands = new HashSet<>();
+    public static final String IMPORT_CURL       = "import_curl";
+    private static final String CREATE_REQUEST = "CREATE_REQUEST";
+    
+    static {
+        commands.add(IMPORT_CURL);
+    }
+
+    private JSyntaxTextArea cURLCommandTA;
+    private JLabel statusText;
+
+    /**
+     * 
+     */
+    public ParseCurlCommandAction() {
+        super();
+    }
+
+    @Override
+    public void doAction(ActionEvent e) {
+        ActionRouter.getInstance().doActionNow(new ActionEvent(e.getSource(), e.getID(), ActionNames.CLOSE));
+        showInputDialog();
+    }
+    
+    /**
+     * Show popup where user can import cURL command
+     */
+    private final void showInputDialog() {
+        EscapeDialog messageDialog = new EscapeDialog(GuiPackage.getInstance().getMainFrame(),
+                JMeterUtils.getResString("curl_import"), true); //$NON-NLS-1$
+        Container contentPane = messageDialog.getContentPane();
+        contentPane.setLayout(new BorderLayout());
+        statusText = new JLabel("", JLabel.CENTER);
+        statusText.setForeground(Color.RED);
+        contentPane.add(statusText, BorderLayout.NORTH);
+        
+        cURLCommandTA = JSyntaxTextArea.getInstance(10, 80, false);
+        cURLCommandTA.setCaretPosition(0);
+        contentPane.add(JTextScrollPane.getInstance(cURLCommandTA), BorderLayout.CENTER);
+        
+        JPanel buttonPanel = new JPanel(new GridLayout(1, 1));
+        JButton button = new JButton(JMeterUtils.getResString("curl_create_request"));
+        button.setActionCommand(CREATE_REQUEST);
+        button.addActionListener(this);
+        buttonPanel.add(button);
+        contentPane.add(buttonPanel, BorderLayout.SOUTH);
+        messageDialog.pack();
+        ComponentUtil.centerComponentInComponent(GuiPackage.getInstance().getMainFrame(), messageDialog);
+        SwingUtilities.invokeLater(() -> messageDialog.setVisible(true));
+    }
+
+    private void createTestPlan(ActionEvent e, Request request) throws MalformedURLException, IllegalUserActionException {
+        GuiPackage guiPackage = GuiPackage.getInstance();
+
+        guiPackage.clearTestPlan();
+        FileServer.getFileServer().setScriptName(null);
+
+        ThreadGroup threadGroup = new ThreadGroup();
+        threadGroup.setProperty(TestElement.GUI_CLASS, ThreadGroupGui.class.getName());
+        threadGroup.setProperty(TestElement.NAME, "Thread Group");
+        threadGroup.setNumThreads(1);
+        threadGroup.setRampUp(1);
+
+        LoopController loopCtrl = new LoopController();
+        loopCtrl.setLoops(1);
+        loopCtrl.setContinueForever(false);
+        threadGroup.setSamplerController(loopCtrl);
+
+        TestPlan testPlan = new TestPlan();
+        testPlan.setProperty(TestElement.NAME, "Test Plan");
+        testPlan.setProperty(TestElement.GUI_CLASS, TestPlanGui.class.getName());
+
+        HashTree tree = new HashTree();
+        HashTree testPlanHT = tree.add(testPlan);
+        HashTree threadGroupHT = testPlanHT.add(threadGroup);
+
+        createHttpRequest(request, threadGroupHT);
+
+        ResultCollector resultCollector = new ResultCollector();
+        resultCollector.setProperty(TestElement.NAME, "View Results Tree");
+        resultCollector.setProperty(TestElement.GUI_CLASS, ViewResultsFullVisualizer.class.getName());
+        tree.add(tree.getArray()[0], resultCollector);
+
+        final HashTree newTree = guiPackage.addSubTree(tree);
+        guiPackage.updateCurrentGui();
+        guiPackage.getMainFrame().getTree().setSelectionPath(
+                new TreePath(((JMeterTreeNode) newTree.getArray()[0]).getPath()));
+        final HashTree subTree = guiPackage.getCurrentSubTree();
+        // Send different event wether we are merging a test plan into another test plan,
+        // or loading a testplan from scratch
+        ActionEvent actionEvent =
+            new ActionEvent(subTree.get(subTree.getArray()[subTree.size() - 1]), e.getID(), ActionNames.SUB_TREE_LOADED);
+        ActionRouter.getInstance().actionPerformed(actionEvent);
+        ActionRouter.getInstance().doActionNow(new ActionEvent(e.getSource(), e.getID(), ActionNames.EXPAND_ALL));
+    }
+    
+    private HTTPSamplerProxy createHttpRequest(Request request, HashTree parentHT) throws MalformedURLException {
+        HTTPSamplerProxy httpSampler = (HTTPSamplerProxy) HTTPSamplerFactory.newInstance(HTTPSamplerFactory.DEFAULT_CLASSNAME);
+        httpSampler.setProperty(TestElement.GUI_CLASS, HttpTestSampleGui.class.getName());
+        httpSampler.setProperty(TestElement.NAME, "HTTP Request");
+        httpSampler.setProtocol(new URL(request.getUrl()).getProtocol());
+        httpSampler.setPath(request.getUrl());
+        httpSampler.setMethod(request.getMethod());
+        
+        HashTree samplerHT = parentHT.add(httpSampler);
+        
+        HeaderManager headerManager = new HeaderManager();
+        headerManager.setProperty(TestElement.GUI_CLASS, HeaderPanel.class.getName());
+        headerManager.setProperty(TestElement.NAME, "HTTP HeaderManager");
+        Map<String, String> map = request.getHeaders();
+        
+        boolean hasAcceptEncoding = false;
+        for (Map.Entry<String, String> header : map.entrySet()) {
+            String key = header.getKey();
+            hasAcceptEncoding = hasAcceptEncoding || key.equalsIgnoreCase(ACCEPT_ENCODING);
+            headerManager.getHeaders().addItem(new Header(key, header.getValue()));
+        }
+        if(!hasAcceptEncoding) {
+            headerManager.getHeaders().addItem(new Header(ACCEPT_ENCODING, "gzip, deflate"));
+        }
+        if (!"GET".equals(request.getMethod())) {
+            Arguments arguments = new Arguments();
+            httpSampler.setArguments(arguments);
+            httpSampler.addNonEncodedArgument("", request.getPostData(), "");
+        }
+        httpSampler.addTestElement(headerManager);
+        samplerHT.add(headerManager);
+        return httpSampler;
+    }
+
+    @Override
+    public Set<String> getActionNames() {
+        return commands;
+    }
+
+    @Override
+    public JMenuItem[] getMenuItemsAtLocation(MENU_LOCATION location) {
+        if(location == MENU_LOCATION.HELP) {
+            JMenuItem menuItemIC = new JMenuItem(
+                    JMeterUtils.getResString("curl_import_menu"), KeyEvent.VK_UNDEFINED);
+            menuItemIC.setName(IMPORT_CURL);
+            menuItemIC.setActionCommand(IMPORT_CURL);
+            menuItemIC.setAccelerator(null);
+            menuItemIC.addActionListener(ActionRouter.getInstance());
+            return new JMenuItem[]{menuItemIC};
+        }
+        return new JMenuItem[0];
+    }
+
+    @Override
+    public JMenu[] getTopLevelMenus() {
+        return new JMenu[0];
+    }
+
+    @Override
+    public boolean localeChanged(MenuElement menu) {
+        return false;
+    }
+
+    @Override
+    public void localeChanged() {
+        // NOOP
+    }
+
+    @Override
+    public void actionPerformed(ActionEvent e) {
+        statusText.setText("");
+        statusText.setForeground(Color.GREEN);
+        if(e.getActionCommand().equals(CREATE_REQUEST)) {
+            String curlCommand = cURLCommandTA.getText();
+            try {
+                LOGGER.info("Transforming CURL command {}", curlCommand);
+                BasicCurlParser basicCurlParser = new BasicCurlParser();
+                BasicCurlParser.Request request = basicCurlParser.parse(curlCommand);
+                LOGGER.info("Parsed CURL command {} into {}", curlCommand, request);
+                GuiPackage guiPackage = GuiPackage.getInstance();
+                guiPackage.updateCurrentNode();
+                createTestPlan(e, request);
+                statusText.setText(JMeterUtils.getResString("curl_create_success"));
+            } catch (Exception ex) {
+                LOGGER.error("Error creating test plan from cURL command:{}, error:{}", curlCommand, ex.getMessage(), ex);
+                statusText.setText(MessageFormat.format(JMeterUtils.getResString("curl_create_failure"), ex.getMessage()));
+                statusText.setForeground(Color.RED);
+            }
+        }
+    }
+}

Propchange: jmeter/trunk/src/protocol/http/org/apache/jmeter/protocol/http/gui/action/ParseCurlCommandAction.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jmeter/trunk/src/protocol/http/org/apache/jmeter/protocol/http/gui/action/ParseCurlCommandAction.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: jmeter/trunk/test/src/org/apache/jmeter/curl/BasicCurlParserTest.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/test/src/org/apache/jmeter/curl/BasicCurlParserTest.java?rev=1849285&view=auto
==============================================================================
--- jmeter/trunk/test/src/org/apache/jmeter/curl/BasicCurlParserTest.java (added)
+++ jmeter/trunk/test/src/org/apache/jmeter/curl/BasicCurlParserTest.java Wed Dec 19 09:54:42 2018
@@ -0,0 +1,98 @@
+/*
+ * 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.curl;
+
+import org.apache.jmeter.protocol.http.curl.BasicCurlParser;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * @since 5.1
+ */
+public class BasicCurlParserTest {
+
+    /**
+     * 
+     */
+    public BasicCurlParserTest() {
+        super();
+    }
+    
+    @Test
+    public void testFFParsing() {
+        String cmdLine = "curl 'http://jmeter.apache.org/' -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:63.0) Gecko/20100101 Firefox/63.0' -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' -H 'Accept-Language: en-US,en;q=0.5' --compressed -H 'DNT: 1' -H 'Connection: keep-alive' -H 'Upgrade-Insecure-Requests: 1'";
+        BasicCurlParser basicCurlParser = new BasicCurlParser();
+        BasicCurlParser.Request request = basicCurlParser.parse(cmdLine);
+        Assert.assertEquals("http://jmeter.apache.org/", request.getUrl());
+        Assert.assertEquals(6, request.getHeaders().size());
+        Assert.assertTrue(request.isCompressed());
+        Assert.assertEquals("GET", request.getMethod());
+    }   
+    
+    @Test
+    public void testChromeParsing() {
+        String cmdLine = "curl 'https://jmeter.apache.org/' -H 'Proxy-Connection: keep-alive' -H 'Proxy-Authorization: Basic XXXXXXXXX/' -H 'Upgrade-Insecure-Requests: 1' -H 'User-Agent: Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Mobile Safari/537.36' -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8' -H 'Accept-Encoding: gzip, deflate' -H 'Accept-Language: en-US,en;q=0.9,fr;q=0.8' --compressed";
+        BasicCurlParser basicCurlParser = new BasicCurlParser();
+        BasicCurlParser.Request request = basicCurlParser.parse(cmdLine);
+        Assert.assertEquals("https://jmeter.apache.org/", request.getUrl());
+        Assert.assertEquals(7, request.getHeaders().size());
+        Assert.assertTrue(request.isCompressed());
+        Assert.assertEquals("GET", request.getMethod());
+    }
+    
+    @Test
+    public void testChromeParsingNotCompressed() {
+        String cmdLine = "curl 'https://jmeter.apache.org/' -H 'Proxy-Connection: keep-alive' -H 'Proxy-Authorization: Basic XXXXXXXXX/' -H 'Upgrade-Insecure-Requests: 1' -H 'User-Agent: Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Mobile Safari/537.36' -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8' -H 'Accept-Encoding: gzip, deflate' -H 'Accept-Language: en-US,en;q=0.9,fr;q=0.8'";
+        BasicCurlParser basicCurlParser = new BasicCurlParser();
+        BasicCurlParser.Request request = basicCurlParser.parse(cmdLine);
+        Assert.assertEquals("https://jmeter.apache.org/", request.getUrl());
+        Assert.assertEquals(7, request.getHeaders().size());
+        Assert.assertFalse(request.isCompressed());
+        Assert.assertEquals("GET", request.getMethod());
+    }
+    
+    @Test
+    public void testChromeParsingNoHeaders() {
+        String cmdLine = "curl 'https://jmeter.apache.org/'";
+        BasicCurlParser basicCurlParser = new BasicCurlParser();
+        BasicCurlParser.Request request = basicCurlParser.parse(cmdLine);
+        Assert.assertEquals("https://jmeter.apache.org/", request.getUrl());
+        Assert.assertTrue(request.getHeaders().isEmpty());
+        Assert.assertFalse(request.isCompressed());
+        Assert.assertEquals("GET", request.getMethod());
+    }
+    
+    @Test
+    public void testPost() {
+        String cmdLine = "curl 'https://jmeter.apache.org/test' -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.11; rv:63.0) Gecko/20100101 Firefox/63.0' -H 'Accept: */*' -H 'Accept-Language: en-US,en;q=0.5' --compressed -H 'Referer: https://www.example.com/' -H 'content-type: application/json;charset=UTF-8' -H 'Origin: https://www.example.com' -H 'DNT: 1' -H 'Connection: keep-alive' -H 'TE: Trailers' --data '{\"abc\":\"123\",\"no\":\"matter on sunshine\"}'";
+        BasicCurlParser basicCurlParser = new BasicCurlParser();
+        BasicCurlParser.Request request = basicCurlParser.parse(cmdLine);
+        Assert.assertEquals("https://jmeter.apache.org/test", request.getUrl());
+        Assert.assertEquals(9, request.getHeaders().size());
+        Assert.assertTrue(request.isCompressed());
+        Assert.assertEquals("POST", request.getMethod());
+    }
+    
+    @Test(expected=IllegalArgumentException.class)
+    public void testError() {
+        String cmdLine = "curl 'https://jmeter.apache.org/' -u -H 'Proxy-Connection: keep-alive' -H 'Proxy-Authorization: Basic XXXXXXXXX/' -H 'Upgrade-Insecure-Requests: 1' -H 'User-Agent: Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Mobile Safari/537.36' -H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8' -H 'Accept-Encoding: gzip, deflate' -H 'Accept-Language: en-US,en;q=0.9,fr;q=0.8'";
+        BasicCurlParser basicCurlParser = new BasicCurlParser();
+        basicCurlParser.parse(cmdLine);
+    }
+}

Propchange: jmeter/trunk/test/src/org/apache/jmeter/curl/BasicCurlParserTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jmeter/trunk/test/src/org/apache/jmeter/curl/BasicCurlParserTest.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: jmeter/trunk/xdocs/changes.xml
URL: http://svn.apache.org/viewvc/jmeter/trunk/xdocs/changes.xml?rev=1849285&r1=1849284&r2=1849285&view=diff
==============================================================================
--- jmeter/trunk/xdocs/changes.xml [utf-8] (original)
+++ jmeter/trunk/xdocs/changes.xml [utf-8] Wed Dec 19 09:54:42 2018
@@ -117,16 +117,17 @@ of previous time slot as a base. Startin
 
 <h3>General</h3>
 <ul>
-  <li><pr>394</pr>Allow <code>null</code> values in <code>FieldStringEditor</code>. Based on patch by Mingun (alexander_sergey at mail.ru)</li>
-  <li><bug>62826</bug>When changing LAF, make JMeter restart if user clicks yes to popup</li>
-  <li><bug>62744</bug>Upgrade jquery to version 3.3.1, jquery-ui to 1.12.1, bootstrap to 3.3.7</li>
-   <li><bug>62257</bug><pr>401</pr>Expand/Collapse short key <keysym>-</keysym> (minus sign) on numpad doesn't work. Contributed by Ori Marko (orimarko at gmail.com)</li>
-   <li><bug>62752</bug>Add to Documentation: <code>ctx.getThreadNum()</code> is zero-based while <code>${__threadNum}</code> is one-based</li>
-   <li><pr>411</pr>Use <code>SHA-1</code> instead of <code>SHA1</code> in <code>org.apache.jmeter.save.SaveService</code>. Contributed by Paco (paco.xu at daocloud.io)</li>
-   <li><bug>62914</bug>Add a hint in Thread Group UI about duration of test</li>
-   <li><bug>62925</bug>Add support for ThreadDump to the JMeter non-GUI</li>
-   <li><bug>62870</bug>Templates : Add ability to provide parameters. Contributed by Ubik Load Pack (support at ubikloadpack.com)</li>
-   <li><bug>62829</bug>Allow specifying Proxy server scheme for HTTP request sampler, Advanced tab and command line option. Contributed by Hitesh Patel (hitesh.h.patel at gmail.com)</li>
+    <li><bug>62959</bug>Ability to create a Test plan from a cURL command. Contributed by Ubik Load Pack (support at ubikloadpack.com)</li>
+    <li><pr>394</pr>Allow <code>null</code> values in <code>FieldStringEditor</code>. Based on patch by Mingun (alexander_sergey at mail.ru)</li>
+    <li><bug>62826</bug>When changing LAF, make JMeter restart if user clicks yes to popup</li>
+    <li><bug>62744</bug>Upgrade jquery to version 3.3.1, jquery-ui to 1.12.1, bootstrap to 3.3.7</li>
+    <li><bug>62257</bug><pr>401</pr>Expand/Collapse short key <keysym>-</keysym> (minus sign) on numpad doesn't work. Contributed by Ori Marko (orimarko at gmail.com)</li>
+    <li><bug>62752</bug>Add to Documentation: <code>ctx.getThreadNum()</code> is zero-based while <code>${__threadNum}</code> is one-based</li>
+    <li><pr>411</pr>Use <code>SHA-1</code> instead of <code>SHA1</code> in <code>org.apache.jmeter.save.SaveService</code>. Contributed by Paco (paco.xu at daocloud.io)</li>
+    <li><bug>62914</bug>Add a hint in Thread Group UI about duration of test</li>
+    <li><bug>62925</bug>Add support for ThreadDump to the JMeter non-GUI</li>
+    <li><bug>62870</bug>Templates : Add ability to provide parameters. Contributed by Ubik Load Pack (support at ubikloadpack.com)</li>
+    <li><bug>62829</bug>Allow specifying Proxy server scheme for HTTP request sampler, Advanced tab and command line option. Contributed by Hitesh Patel (hitesh.h.patel at gmail.com)</li>
 </ul>
 
 <ch_section>Non-functional changes</ch_section>