Skip to content
Permalink
Browse files
Allow unquoted tokens for values in content-disposition header
Use httpcomponents BasicHeaderValueParser instead of our simple parser, as
we were assuming parameters would always be surrounded by quotes.

Bugzilla Id: 65884
  • Loading branch information
FSchumacher committed Feb 17, 2022
1 parent efe50ca commit 33ad9306b2fbe823fd9fd1bd207262cd7a0ec4a6
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 16 deletions.
@@ -20,6 +20,10 @@
import java.io.Serializable;

import org.apache.commons.lang3.StringUtils;
import org.apache.http.HeaderElement;
import org.apache.http.NameValuePair;
import org.apache.http.ParseException;
import org.apache.http.message.BasicHeaderValueParser;
import org.apache.jmeter.config.Arguments;
import org.apache.jmeter.protocol.http.util.HTTPArgument;
import org.apache.jmeter.protocol.http.util.HTTPFileArgs;
@@ -28,6 +32,8 @@
import org.apache.oro.text.regex.Pattern;
import org.apache.oro.text.regex.Perl5Compiler;
import org.apache.oro.text.regex.Perl5Matcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Configuration element which handles HTTP Parameters and files to be uploaded
@@ -46,6 +52,8 @@ public class MultipartUrlConfig implements Serializable {

private final Arguments args;

private static final Logger log = LoggerFactory.getLogger(MultipartUrlConfig.class);

/**
* HTTPFileArgs list to be uploaded with http request.
*/
@@ -129,20 +137,25 @@ public void parseArguments(String queryString) {
// Check if it is form data
if (contentDisposition != null && contentDisposition.contains("form-data")) { //$NON-NLS-1$
// Get the form field name
final String namePrefix = "name=\""; //$NON-NLS-1$
int index = contentDisposition.indexOf(namePrefix) + namePrefix.length();
String name = contentDisposition.substring(index, contentDisposition.indexOf('\"', index)); //$NON-NLS-1$

// Check if it is a file being uploaded
final String filenamePrefix = "filename=\""; //$NON-NLS-1$
if (contentDisposition.contains(filenamePrefix)) {
// Get the filename
index = contentDisposition.indexOf(filenamePrefix) + filenamePrefix.length();
String path = contentDisposition.substring(index, contentDisposition.indexOf('\"', index)); //$NON-NLS-1$
if (path != null && path.length() > 0) {
// Set the values retrieved for the file upload
files.addHTTPFileArg(path, name, contentType);
HeaderElement[] headerElements = null;
try {
headerElements = BasicHeaderValueParser.parseElements(
contentDisposition,
BasicHeaderValueParser.INSTANCE);
} catch (ParseException e) {
log.info("Can't parse header {}", contentDisposition, e);
}
String name = "";
String path = null;
if (headerElements != null) {
for (HeaderElement element : headerElements) {
name = getParameterValue(element, "name", "");
path = getParameterValue(element, "filename", null);
}
}
if (path != null && !path.isEmpty()) {
// Set the values retrieved for the file upload
files.addHTTPFileArg(path, name, contentType);
} else {
// Find the first empty line of the multipart, it signals end of headers for multipart
// Agents are supposed to terminate lines in CRLF:
@@ -160,6 +173,14 @@ public void parseArguments(String queryString) {
}
}

private static String getParameterValue(HeaderElement element, String name, String defaultValue) {
NameValuePair parameter = element.getParameterByName(name);
if (parameter == null) {
return defaultValue;
}
return parameter.getValue();
}

private static String getHeaderValue(String headerName, String multiPart) {
String regularExpression = headerName + "\\s*:\\s*(.*)$"; //$NON-NLS-1$
Perl5Matcher localMatcher = JMeterUtils.getMatcher();
@@ -19,17 +19,24 @@

import static org.junit.Assert.assertEquals;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

import org.apache.jmeter.config.Argument;
import org.apache.jmeter.config.Arguments;
import org.apache.jmeter.protocol.http.util.HTTPFileArg;
import org.apache.jmeter.protocol.http.util.HTTPFileArgs;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;

public class MultipartUrlConfigTest {

@Test
@SuppressWarnings("deprecation")
public void testConstructors() {
void testConstructors() {
MultipartUrlConfig muc = new MultipartUrlConfig();
assertEquals(0, muc.getArguments().getArgumentCount());
assertEquals(0, muc.getHTTPFileArgs().getHTTPFileArgCount());
@@ -41,7 +48,7 @@ public void testConstructors() {

// TODO - should LF-only EOL be allowed?
@Test
public void testParseArgumentsLF() {
void testParseArgumentsLF() {
String queryString
= "Content-Disposition: form-data; name=\"aa\"\n"
+ "Content-Type: text/plain; charset=ISO-8859-1\n"
@@ -90,7 +97,7 @@ public void testParseArgumentsLF() {
}

@Test
public void testParseArgumentsCRLF() {
void testParseArgumentsCRLF() {
String queryString
= "Content-Disposition: form-data; name=\"aa\"\r\n"
+ "Content-Type: text/plain; charset=ISO-8859-1\r\n"
@@ -137,4 +144,73 @@ public void testParseArgumentsCRLF() {
assertEquals("abc", arg.getName());
assertEquals("xyz \r\nxyz ", arg.getValue());
}

private static Stream<org.junit.jupiter.params.provider.Arguments> quotedMultiPartArgs() {
String boundary = "7d159c1302d0y0";
HTTPFileArgs fileArgs = new HTTPFileArgs();
List<Argument> args = new ArrayList<>();
List<String> queryLines = new ArrayList<>();
int counter = 1;
for (boolean quoteName: Arrays.asList(true, false)) {
String paramName = "abc" + counter;
counter++;
String quoteStringName = quoteName ? '"' + paramName + '"' : paramName;
String value = "some value for " + paramName;
queryLines.add(String.format("Content-Disposition: form-data; name=%s", quoteStringName));
queryLines.add("Content-Type: text/plain; charset=ISO-8859-1");
queryLines.add("Content-Transfer-Encoding: 8bit");
queryLines.add("");
queryLines.add(value);
queryLines.add("--" + boundary);
args.add(new Argument(paramName, value));
for (boolean quoteFilename: Arrays.asList(true, false)) {
String filenameName = "def" + counter;
counter++;
String quoteStringFile = quoteFilename ? '"' +filenameName + '"' : filenameName ;
String content = "some value for " + paramName + " and " + filenameName ;
queryLines.add(String.format("Content-Disposition: form-data; name=%s; filename=%s",
quoteStringName,
quoteStringFile
));
queryLines.add("Content-Type: text/plain");
queryLines.add("Content-Transfer-Encoding: binary");
queryLines.add("");
queryLines.add(content);
queryLines.add("");
queryLines.add("--" + boundary);
fileArgs.addHTTPFileArg(filenameName, paramName, "text/plain");
}
}
queryLines.remove(queryLines.size()-1);
queryLines.add("");
return Stream.of(
org.junit.jupiter.params.provider.Arguments.of(
boundary, String.join("\n", queryLines), fileArgs, args),
org.junit.jupiter.params.provider.Arguments.of(
boundary, String.join("\r\n", queryLines), fileArgs, args));
}

@ParameterizedTest()
@MethodSource("quotedMultiPartArgs")
void testParseArgumentsQuotingStyle(String boundary, String queryString, HTTPFileArgs expectedFiles, List<Argument> expectedArgs) {
MultipartUrlConfig muc = new MultipartUrlConfig(boundary);
muc.parseArguments(queryString);
HTTPFileArgs files = muc.getHTTPFileArgs();
assertEquals(expectedFiles.getHTTPFileArgCount(), files.getHTTPFileArgCount());
for (int i=0; i<files.getHTTPFileArgCount(); i++) {
HTTPFileArg got = files.getHTTPFileArg(i);
HTTPFileArg expected = expectedFiles.getHTTPFileArg(i);
assertEquals(expected.getParamName(), got.getParamName());
assertEquals(expected.getPath(), got.getPath());
assertEquals(expected.getMimeType(), got.getMimeType());
}
Arguments args = muc.getArguments();
assertEquals(expectedArgs.size(), args.getArgumentCount());
for (int i=0; i<args.getArgumentCount(); i++) {
Argument got = args.getArgument(i);
Argument expected = expectedArgs.get(i);
assertEquals(expected.getName(), got.getName());
assertEquals(expected.getValue(), got.getValue());
}
}
}
@@ -230,6 +230,7 @@ however, the profile can't be updated while the test is running.
<ul>
<li><bug>64962</bug>Save CSV sub-results recursively from View Results Tree</li>
<li><bug>65784</bug>No Graphs displayed in Aggregate Report/Response Time Graph</li>
<li><bug>65884</bug>GUI doesn't display response for multipart request <em>manually</em> encoded</li>
</ul>

<h3>Timers, Assertions, Config, Pre- &amp; Post-Processors</h3>

0 comments on commit 33ad930

Please sign in to comment.