Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Invalid request using Proxy for a multipart/form-data with no filename specified. #1642

Closed
asfimport opened this issue Nov 30, 2005 · 8 comments

Comments

@asfimport
Copy link
Collaborator

Benjamin Francisoud (Bug 37716):
When using the JMmeter proxy (to record pages) on a multipart/form-data whith an
upload file field, if the filename is not specified, an invalid request is send
to the server.

My actual webapp (with cocoon) and the form are too complex to be put as an
attachment, but I'll provide the stacktrace.

I recreated the same problem using a simple form with ruby on rails, I'll put
the webrick stacktrace too.

I think I've spoted the problem in PostWriter.java, I'll provide a patch (as
soon as I'll have checkout sources from svn).

Severity: normal
OS: Windows XP

@asfimport
Copy link
Collaborator Author

Benjamin Francisoud (migrated from Bugzilla):
Created attachment cocoon.html: Cocoon stacktrace

cocoon.html
<html><head><title>Problem in creating the Request</title><style><!--body { background-color: white; color: black; font-family: verdana, helvetica, sanf serif;}h1 {color: #336699; margin: 0px 0px 20px 0px; border-width: 0px 0px 1px 0px; border-style: solid; border-color: #336699;}p.footer { color: #336699; border-width: 1px 0px 0px 0px; border-style: solid; border-color: #336699; }span {color: #336699;}pre {padding-left: 20px;}a:link {font-weight: bold; color: #336699;}a:visited {color: #336699; }a:hover {color: #800000; background-color: #ffff80;}a:active {color: #006666;}--></style></head><body><h1>Problem in creating the Request</h1><p><span>Message:</span> Malformed stream</p><p><span>Description:</span> org.apache.cocoon.servlet.multipart.MultipartException: Malformed stream</p><p><span>Sender:</span> org.apache.cocoon.servlet.CocoonServlet</p><p><span>Source:</span> Cocoon Servlet</p><p><span>cause</span><pre>org.apache.cocoon.servlet.multipart.MultipartException: Malformed stream</pre></p><p><span>request-uri</span><pre>/repons/portal/portal</pre></p><p><span>full exception chain stacktrace</span><pre>org.apache.cocoon.servlet.multipart.MultipartException: Malformed stream
	at org.apache.cocoon.servlet.multipart.MultipartParser.parseMultiPart(MultipartParser.java:160)
	at org.apache.cocoon.servlet.multipart.MultipartParser.parseParts(MultipartParser.java:108)
	at org.apache.cocoon.servlet.multipart.MultipartParser.getParts(MultipartParser.java:134)
	at org.apache.cocoon.servlet.multipart.RequestFactory.getServletRequest(RequestFactory.java:91)
	at org.apache.cocoon.servlet.CocoonServlet.service(CocoonServlet.java:1055)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:802)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:237)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:157)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:214)
	at org.apache.catalina.core.StandardValveContext.invokeNext(StandardValveContext.java:104)
	at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:520)
	at org.apache.catalina.core.StandardContextValve.invokeInternal(StandardContextValve.java:198)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:152)
	at org.apache.catalina.core.StandardValveContext.invokeNext(StandardValveContext.java:104)
	at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:520)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:137)
	at org.apache.catalina.core.StandardValveContext.invokeNext(StandardValveContext.java:104)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:117)
	at org.apache.catalina.core.StandardValveContext.invokeNext(StandardValveContext.java:102)
	at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:520)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
	at org.apache.catalina.core.StandardValveContext.invokeNext(StandardValveContext.java:104)
	at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:520)
	at org.apache.catalina.core.ContainerBase.invoke(ContainerBase.java:929)
	at org.apache.coyote.tomcat5.CoyoteAdapter.service(CoyoteAdapter.java:160)
	at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:799)
	at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.processConnection(Http11Protocol.java:705)
	at org.apache.tomcat.util.net.TcpWorkerThread.runIt(PoolTcpEndpoint.java:577)
	at org.apache.tomcat.util.threads.ThreadPool$ControlRunnable.run(ThreadPool.java:683)
	at java.lang.Thread.run(Thread.java:534)
</pre></p><p><span>stacktrace</span><pre>org.apache.cocoon.servlet.multipart.MultipartException: Malformed stream
	at org.apache.cocoon.servlet.multipart.MultipartParser.parseMultiPart(MultipartParser.java:160)
	at org.apache.cocoon.servlet.multipart.MultipartParser.parseParts(MultipartParser.java:108)
	at org.apache.cocoon.servlet.multipart.MultipartParser.getParts(MultipartParser.java:134)
	at org.apache.cocoon.servlet.multipart.RequestFactory.getServletRequest(RequestFactory.java:91)
	at org.apache.cocoon.servlet.CocoonServlet.service(CocoonServlet.java:1055)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:802)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:237)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:157)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:214)
	at org.apache.catalina.core.StandardValveContext.invokeNext(StandardValveContext.java:104)
	at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:520)
	at org.apache.catalina.core.StandardContextValve.invokeInternal(StandardContextValve.java:198)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:152)
	at org.apache.catalina.core.StandardValveContext.invokeNext(StandardValveContext.java:104)
	at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:520)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:137)
	at org.apache.catalina.core.StandardValveContext.invokeNext(StandardValveContext.java:104)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:117)
	at org.apache.catalina.core.StandardValveContext.invokeNext(StandardValveContext.java:102)
	at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:520)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
	at org.apache.catalina.core.StandardValveContext.invokeNext(StandardValveContext.java:104)
	at org.apache.catalina.core.StandardPipeline.invoke(StandardPipeline.java:520)
	at org.apache.catalina.core.ContainerBase.invoke(ContainerBase.java:929)
	at org.apache.coyote.tomcat5.CoyoteAdapter.service(CoyoteAdapter.java:160)
	at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:799)
	at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.processConnection(Http11Protocol.java:705)
	at org.apache.tomcat.util.net.TcpWorkerThread.runIt(PoolTcpEndpoint.java:577)
	at org.apache.tomcat.util.threads.ThreadPool$ControlRunnable.run(ThreadPool.java:683)
	at java.lang.Thread.run(Thread.java:534)
</pre></p><p class='footer'><a href='http://cocoon.apache.org/'>Apache Cocoon 2.1.8-dev</p></body></html>````

</details>

@asfimport
Copy link
Collaborator Author

Benjamin Francisoud (migrated from Bugzilla):
The simple html page I used in ruby on rails to reproduce the problem with the
jmeter proxy.

Created attachment test.htm: simple multipart form

@asfimport
Copy link
Collaborator Author

Benjamin Francisoud (migrated from Bugzilla):
The stacktrace I get when using jmeter proxy with the simple form with no
filename specified.

Created attachment rails.txt: webrick - ruby on rails - stacktrace

rails.txt
127.0.0.1 - - [29/Nov/2005:17:14:55 Paris, Madrid] "GET /test HTTP/1.1" 200 566
- -> /test
#<EOFError: bad content body>
["c:/ruby/lib/ruby/1.8/cgi.rb:980:in `read_multipart'", "c:/ruby/lib/ruby/gems/1.8/gems/actionpack-1.11.0/lib/action_controller/cgi_ext/raw_post_data_
fix.rb:20:in `initialize_query'", "c:/ruby/lib/ruby/gems/1.8/gems/rails-0.14.3/lib/webrick_server.rb:36:in `initialize'", "c:/ruby/lib/ruby/gems/1.8/g
ems/rails-0.14.3/lib/webrick_server.rb:118:in `new'", "c:/ruby/lib/ruby/gems/1.8/gems/rails-0.14.3/lib/webrick_server.rb:118:in `handle_dispatch'", "c
:/ruby/lib/ruby/gems/1.8/gems/rails-0.14.3/lib/webrick_server.rb:83:in `service'", "c:/ruby/lib/ruby/1.8/webrick/httpserver.rb:104:in `service'", "c:/
ruby/lib/ruby/1.8/webrick/httpserver.rb:65:in `run'", "c:/ruby/lib/ruby/1.8/webrick/server.rb:155:in `start_thread'", "c:/ruby/lib/ruby/1.8/webrick/se
rver.rb:144:in `start'", "c:/ruby/lib/ruby/1.8/webrick/server.rb:144:in `start_thread'", "c:/ruby/lib/ruby/1.8/webrick/server.rb:94:in `start'", "c:/r
uby/lib/ruby/1.8/webrick/server.rb:89:in `each'", "c:/ruby/lib/ruby/1.8/webrick/server.rb:89:in `start'", "c:/ruby/lib/ruby/1.8/webrick/server.rb:79:i
n `start'", "c:/ruby/lib/ruby/1.8/webrick/server.rb:79:in `start'", "c:/ruby/lib/ruby/gems/1.8/gems/rails-0.14.3/lib/webrick_server.rb:69:in `dispatch
'", "c:/ruby/lib/ruby/gems/1.8/gems/rails-0.14.3/lib/commands/servers/webrick.rb:59", "c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:18:in
 `require__'", "c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:18:in `require'", "c:/ruby/lib/ruby/gems/1.8/gems/activesupport-1.2.3/lib/ac
tive_support/dependencies.rb:214:in `require'", "c:/ruby/lib/ruby/gems/1.8/gems/rails-0.14.3/lib/commands/server.rb:28", "c:/ruby/lib/ruby/site_ruby/1
.8/rubygems/custom_require.rb:18:in `require__'", "c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:18:in `require'", "c:/ruby/lib/ruby/gems/
1.8/gems/activesupport-1.2.3/lib/active_support/dependencies.rb:214:in `require'", "script/server:5"]
[2005-11-29 17:15:04] ERROR `/test' not found.
127.0.0.1 - - [29/Nov/2005:17:15:04 Paris, Madrid] "POST /test HTTP/1.1" 404 274
http://localhost:3000/test -> /test````

</details>

@asfimport
Copy link
Collaborator Author

Benjamin Francisoud (migrated from Bugzilla):
I spotted the problem in org.apache.jmeter.protocol.http.sampler.PostWriter.java

In sendPostData(), the filename is tested with those lines:
String filename = sampler.getFilename();
if ((filename != null) && (filename.trim().length() > 0)) {

But in setHeaders(), the filename is not use:
String filename = sampler.getFileField();
if ((filename != null) && (filename.trim().length() > 0)) {

Where sampler.getFileField() is the "name" attribut of the html input ("upload"
in my simple multipart form attachment file), therefore wrong headers a used.

See patch-bug-37716.txt

@asfimport
Copy link
Collaborator Author

Benjamin Francisoud (migrated from Bugzilla):
Created attachment patch-bug-37716.txt: replace sampler.getFileField() with sampler.getFilename()

patch-bug-37716.txt
Index: C:/Documents and Settings/Benjamin/Mes documents/workspaces/workspaceApache/jakarta-jmeter-rel2.1/src/protocol/http/org/apache/jmeter/protocol/http/sampler/PostWriter.java
===================================================================
--- C:/Documents and Settings/Benjamin/Mes documents/workspaces/workspaceApache/jakarta-jmeter-rel2.1/src/protocol/http/org/apache/jmeter/protocol/http/sampler/PostWriter.java	(revision 349940)
+++ C:/Documents and Settings/Benjamin/Mes documents/workspaces/workspaceApache/jakarta-jmeter-rel2.1/src/protocol/http/org/apache/jmeter/protocol/http/sampler/PostWriter.java	(working copy)
@@ -78,7 +78,7 @@
 		((HttpURLConnection) connection).setRequestMethod("POST");
 
 		// If filename was specified then send the post using multipart syntax
-		String filename = sampler.getFileField();
+		String filename = sampler.getFilename();
 		if ((filename != null) && (filename.trim().length() > 0)) {
 			connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY);
 			connection.setDoOutput(true);

@asfimport
Copy link
Collaborator Author

Benjamin Francisoud (migrated from Bugzilla):
Test all public methodes of PostWriter.
Place it under:
[jmeter-workspace]/test/src/org/apache/jmeter/protocol/http/sampler

Done under windows XP and eclipse 3.1.

Launch from eclipse: green bar.
Launch from ant: green bar.

Created attachment PostWriterTest.java: junit test case for PostWriter

PostWriterTest.java
package org.apache.jmeter.protocol.http.sampler;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.HashMap;
import java.util.Map;

import junit.framework.TestCase;

import org.apache.jmeter.config.Arguments;

public class PostWriterTest extends TestCase {
	
	// TODO: put PostWriter.CRLF public an use it instead of this one
	private final static byte[] CRLF = { 0x0d, 0x0A };
	
	private PostWriter postWriter;
	private URLConnection connection;
	private HTTPSampler sampler;
	private File temporaryFile;
	
	protected void setUp() throws Exception {
		postWriter = new PostWriter();
		connection = new StubURLConnection("http://fake_url/test");
		sampler = new HTTPSampler();
		
		// create a temporary file to make sure we always have a file to give to the PostWriter 
		// Whereever we are or Whatever the current path is.
		temporaryFile = File.createTempFile("foo", "txt");
		OutputStream output = new FileOutputStream(temporaryFile);
		output.write("foo content".getBytes());
		output.flush();
		output.close();
	}

	protected void tearDown() throws Exception {
		// delete temporay file
		temporaryFile.delete();
	}

	/*
	 * Test method for 'org.apache.jmeter.protocol.http.sampler.PostWriter.sendPostData(URLConnection, HTTPSampler)'
	 */
	public void testSendPostData() throws IOException {
		setupFilename(sampler);
		setupCommons(sampler);
		
		postWriter.sendPostData(connection, sampler);
		
		assertEquals(createExpectedOutputStream().toString(), connection.getOutputStream().toString());
	}

	/*
	 * Test method for 'org.apache.jmeter.protocol.http.sampler.PostWriter.sendPostData(URLConnection, HTTPSampler)'
	 */
	public void testSendPostData_NoFilename() throws IOException {
		setupNoFilename(sampler);
		setupCommons(sampler);

		postWriter.sendPostData(connection, sampler);
		
		assertEquals("title=mytitle&description=mydescription", connection.getOutputStream().toString());
	}

	/*
	 * Test method for 'org.apache.jmeter.protocol.http.sampler.PostWriter.setHeaders(URLConnection, HTTPSampler)'
	 */
	public void testSetHeaders() throws IOException {
		setupFilename(sampler);
		setupCommons(sampler);
		
		postWriter.setHeaders(connection, sampler);
		
		assertEquals("multipart/form-data; boundary=" + PostWriter.BOUNDARY, connection.getRequestProperty("Content-Type"));
	}

	/*
	 * Test method for 'org.apache.jmeter.protocol.http.sampler.PostWriter.setHeaders(URLConnection, HTTPSampler)'
	 */
	public void testSetHeaders_NoFilename() throws IOException {
		setupNoFilename(sampler);
		setupCommons(sampler);
		
		postWriter.setHeaders(connection, sampler);
		
		assertEquals("application/x-www-form-urlencoded", connection.getRequestProperty("Content-Type"));
		assertEquals("39", connection.getRequestProperty("Content-Length"));
	}

	/**
	 * setup commons parts of HTTPSampler with a no filename.
	 *  
	 * @param httpSampler
	 * @throws IOException
	 */
	private void setupNoFilename(HTTPSampler httpSampler) throws IOException {
		httpSampler.setFilename("");
		httpSampler.setMimetype("application/octet-stream");
	}

	/**
	 * setup commons parts of HTTPSampler with a filename.
	 * 
	 * @param httpSampler
	 * @throws IOException
	 */
	private void setupFilename(HTTPSampler httpSampler) throws IOException {
		// httpSampler.setFilename("test/src/org/apache/jmeter/protocol/http/sampler/foo.txt");
		httpSampler.setFilename(temporaryFile.getAbsolutePath());
		httpSampler.setMimetype("text/plain");
	}

	/**
	 * setup commons parts of HTTPSampler form test* methods.
	 * 
	 * @param httpSampler
	 * @throws IOException
	 */
	private void setupCommons(HTTPSampler httpSampler) throws IOException {
		httpSampler.setFileField("upload");
		Arguments args = new Arguments();
		args.addArgument("title", "mytitle");
		args.addArgument("description", "mydescription");
		httpSampler.setArguments(args);
	}
	
	/**
	 * Create the expected output with CRLF. 
	 */
	private OutputStream createExpectedOutputStream() throws IOException {
		/*
		-----------------------------7d159c1302d0y0
		Content-Disposition: form-data; name="title"

		mytitle
		-----------------------------7d159c1302d0y0
		Content-Disposition: form-data; name="description"

		mydescription
		-----------------------------7d159c1302d0y0
		Content-Disposition: form-data; name="upload"; filename="test/src/org/apache/jmeter/protocol/http/sampler/foo.txt"
		Content-Type: plain/text

		foo content
		-----------------------------7d159c1302d0y0--
		*/

		final OutputStream output = new ByteArrayOutputStream();
		output.write("-----------------------------7d159c1302d0y0".getBytes());
		output.write(CRLF);
		output.write("Content-Disposition: form-data; name=\"title\"".getBytes());
		output.write(CRLF);
		output.write(CRLF);
		output.write("mytitle".getBytes());
		output.write(CRLF);
		output.write("-----------------------------7d159c1302d0y0".getBytes());
		output.write(CRLF);
		output.write("Content-Disposition: form-data; name=\"description\"".getBytes());
		output.write(CRLF);
		output.write(CRLF);
		output.write("mydescription".getBytes());
		output.write(CRLF);
		output.write("-----------------------------7d159c1302d0y0".getBytes());
		output.write(CRLF);
		// replace all backslash with double backslash
		String filename = temporaryFile.getAbsolutePath().replaceAll("\\\\","\\\\\\\\");
		output.write(("Content-Disposition: form-data; name=\"upload\"; filename=\"" + filename + "\"").getBytes());
		output.write(CRLF);
		output.write("Content-Type: text/plain".getBytes());
		output.write(CRLF);
		output.write(CRLF);
		output.write("foo content".getBytes());
		output.write(CRLF);
		output.write("-----------------------------7d159c1302d0y0--".getBytes());
		output.write(CRLF);
		output.flush();
		output.close();
		return output;
	}

	/**
	 * Mock an HttpURLConnection.
	 * extends HttpURLConnection instead of just URLConnection because there is a cast in PostWriter.
	 */
	private class StubURLConnection extends HttpURLConnection {
		private OutputStream output = new ByteArrayOutputStream();
		private Map properties = new HashMap();
		
		public StubURLConnection(String url) throws MalformedURLException {
			super(new URL(url));
		}

		public void connect() throws IOException {
		}
		
		public OutputStream getOutputStream() throws IOException {
			return output;
		}

		public void disconnect() {
		}

		public boolean usingProxy() {
			return false;
		}

		public String getRequestProperty(String key) {
			return (String) properties.get(key);
		}

		public void setRequestProperty(String key, String value) {
			properties.put(key, value);
		}
	}
}

@asfimport
Copy link
Collaborator Author

Sebb (migrated from Bugzilla):
Thanks very much - it's particularly useful to have the JUnit testcase.

I've applied the patch to the 2.1 branch.

BTW, PostWriter.CRLF and PostWriterTest.CRLF need to remain private - final
arrays are mutable (unless they have zero entries), so it's not completely safe
to share them.

@asfimport
Copy link
Collaborator Author

Benjamin Francisoud (migrated from Bugzilla):
solve with the latest svn sources :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant