diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java index dd2476e0dd8..a194754dcf0 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java @@ -65,6 +65,7 @@ import org.eclipse.jetty.http.HttpVersions; import org.eclipse.jetty.http.MimeTypes; import org.eclipse.jetty.io.Buffer; import org.eclipse.jetty.io.BufferUtil; +import org.eclipse.jetty.io.ByteArrayBuffer; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.nio.DirectNIOBuffer; import org.eclipse.jetty.io.nio.IndirectNIOBuffer; @@ -1943,12 +1944,28 @@ public class Request implements HttpServletRequest { if (getContentType() == null || !getContentType().startsWith("multipart/form-data")) return null; - + if (_multiPartInputStream == null) { _multiPartInputStream = new MultiPartInputStream(getInputStream(), getContentType(),(MultipartConfigElement)getAttribute(__MULTIPART_CONFIG_ELEMENT), (_context != null?(File)_context.getAttribute("javax.servlet.context.tempdir"):null)); + Collection parts = _multiPartInputStream.getParts(); //causes parsing + for (Part p:parts) + { + MultiPartInputStream.MultiPart mp = (MultiPartInputStream.MultiPart)p; + if (mp.getContentDispositionFilename() == null && mp.getFile() == null) + { + //Servlet Spec 3.0 pg 23, parts without filenames must be put into init params + String charset = null; + if (mp.getContentType() != null) + charset = MimeTypes.getCharsetFromContentType(new ByteArrayBuffer(mp.getContentType())); + + String content=new String(mp.getBytes(),charset==null?StringUtil.__UTF8:charset); + getParameter(""); //cause params to be evaluated + getParameters().add(mp.getName(), content); + } + } } return _multiPartInputStream.getPart(name); } @@ -1964,6 +1981,22 @@ public class Request implements HttpServletRequest _multiPartInputStream = new MultiPartInputStream(getInputStream(), getContentType(),(MultipartConfigElement)getAttribute(__MULTIPART_CONFIG_ELEMENT), (_context != null?(File)_context.getAttribute("javax.servlet.context.tempdir"):null)); + Collection parts = _multiPartInputStream.getParts(); //causes parsing + for (Part p:parts) + { + MultiPartInputStream.MultiPart mp = (MultiPartInputStream.MultiPart)p; + if (mp.getContentDispositionFilename() == null && mp.getFile() == null) + { + //Servlet Spec 3.0 pg 23, parts without filenames must be put into init params + String charset = null; + if (mp.getContentType() != null) + charset = MimeTypes.getCharsetFromContentType(new ByteArrayBuffer(mp.getContentType())); + + String content=new String(mp.getBytes(),charset==null?StringUtil.__UTF8:charset); + getParameter(""); //cause params to be evaluated + getParameters().add(mp.getName(), content); + } + } } return _multiPartInputStream.getParts(); } diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java index 5496c028dce..89f74605563 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/RequestTest.java @@ -18,6 +18,7 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertNotNull; import java.io.BufferedReader; import java.io.File; @@ -35,6 +36,7 @@ import javax.servlet.ServletException; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.Part; import junit.framework.Assert; @@ -122,6 +124,51 @@ public class RequestTest assertTrue(responses.startsWith("HTTP/1.1 200")); } + + @Test + public void testMultiPart() throws Exception + { + _handler._checker = new RequestTester() + { + public boolean check(HttpServletRequest request,HttpServletResponse response) + { + try + { + Part foo = request.getPart("stuff"); + assertNotNull(foo); + String value = request.getParameter("stuff"); + byte[] expected = "000000000000000000000000000000000000000000000000000".getBytes("ISO-8859-1"); + return value.equals(new String(expected, "ISO-8859-1")); + } + catch (Exception e) + { + e.printStackTrace(); + return false; + } + } + }; + + String multipart = "--AaB03x\r\n"+ + "content-disposition: form-data; name=\"field1\"\r\n"+ + "\r\n"+ + "Joe Blow\r\n"+ + "--AaB03x\r\n"+ + "content-disposition: form-data; name=\"stuff\"\r\n"+ + "Content-Type: text/plain;charset=ISO-8859-1\r\n"+ + "\r\n"+ + "000000000000000000000000000000000000000000000000000\r\n"+ + "--AaB03x--\r\n"; + + String request="GET / HTTP/1.1\r\n"+ + "Host: whatever\r\n"+ + "Content-Type: multipart/form-data; boundary=\"AaB03x\"\r\n"+ + "Content-Length: "+multipart.getBytes().length+"\r\n"+ + "\r\n"+ + multipart; + + String responses=_connector.getResponses(request); + assertTrue(responses.startsWith("HTTP/1.1 200")); + } @Test public void testBadUtf8ParamExtraction() throws Exception @@ -833,7 +880,9 @@ public class RequestTest { ((Request)request).setHandled(true); - if (request.getContentLength()>0 && !MimeTypes.FORM_ENCODED.equals(request.getContentType())) + if (request.getContentLength()>0 + && !MimeTypes.FORM_ENCODED.equals(request.getContentType()) + && !request.getContentType().startsWith("multipart/form-data")) _content=IO.toString(request.getInputStream()); if (_checker!=null && _checker.check(request,response)) diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartInputStream.java b/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartInputStream.java index a8b87de377b..00ceb5d5754 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartInputStream.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/MultiPartInputStream.java @@ -64,6 +64,7 @@ public class MultiPartInputStream protected String _filename; protected File _file; protected OutputStream _out; + protected ByteArrayOutputStream2 _bout; protected String _contentType; protected MultiMap _headers; protected long _size = 0; @@ -95,7 +96,7 @@ public class MultiPartInputStream { //Write to a buffer in memory until we discover we've exceed the //MultipartConfig fileSizeThreshold - _out = new ByteArrayOutputStream(); + _out = _bout= new ByteArrayOutputStream2(); } } @@ -142,8 +143,9 @@ public class MultiPartInputStream { //already written some bytes, so need to copy them into the file _out.flush(); - ((ByteArrayOutputStream)_out).writeTo(bos); + _bout.writeTo(bos); _out.close(); + _bout = null; } _out = bos; } @@ -199,10 +201,17 @@ public class MultiPartInputStream else { //part content is in a ByteArrayOutputStream - return new ByteArrayInputStream(((ByteArrayOutputStream)_out).toByteArray()); + return new ByteArrayInputStream(_bout.getBuf(),0,_bout.size()); } } + public byte[] getBytes() + { + if (_bout!=null) + return _bout.toByteArray(); + return null; + } + /** * @see javax.servlet.http.Part#getName() */ @@ -232,19 +241,22 @@ public class MultiPartInputStream try { bos = new BufferedOutputStream(new FileOutputStream(_file)); - ((ByteArrayOutputStream)_out).writeTo(bos); + _bout.writeTo(bos); bos.flush(); } finally { if (bos != null) bos.close(); + _bout = null; } } else { //the part data is already written to a temporary file, just rename it - _file.renameTo(new File(_tmpDir, fileName)); + File f = new File(_tmpDir, fileName); + if (_file.renameTo(f)) + _file = f; } } diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/MultiPartInputStreamTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/MultiPartInputStreamTest.java index c66a14eac11..826dbcb1bba 100644 --- a/jetty-util/src/test/java/org/eclipse/jetty/util/MultiPartInputStreamTest.java +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/MultiPartInputStreamTest.java @@ -131,11 +131,19 @@ public class MultiPartInputStreamTest extends TestCase IO.copy(is, os); assertEquals("Joe Blow", new String(os.toByteArray())); assertEquals(8, field1.getSize()); + + assertNotNull(((MultiPartInputStream.MultiPart)field1).getBytes()); //in internal buffer field1.write("field1.txt"); + assertNull(((MultiPartInputStream.MultiPart)field1).getBytes()); //no longer in internal buffer File f = new File (_dirname+File.separator+"field1.txt"); assertTrue(f.exists()); - field1.delete(); - assertFalse(f.exists()); + field1.write("another_field1.txt"); + File f2 = new File(_dirname+File.separator+"another_field1.txt"); + assertTrue(f2.exists()); + assertFalse(f.exists()); //should have been renamed + field1.delete(); //file should be deleted + assertFalse(f2.exists()); + Part stuff = mpis.getPart("stuff"); assertEquals("text/plain", stuff.getContentType()); @@ -146,6 +154,7 @@ public class MultiPartInputStreamTest extends TestCase assertEquals(51, stuff.getSize()); f = ((MultiPartInputStream.MultiPart)stuff).getFile(); assertNotNull(f); // longer than 100 bytes, should already be a file + assertNull(((MultiPartInputStream.MultiPart)stuff).getBytes()); //not in internal buffer any more assertTrue(f.exists()); assertNotSame("stuff.txt", f.getName()); stuff.write("stuff.txt");