diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/MultiPartFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/MultiPartFilter.java index 2da7f8c1d58..e54d4b26877 100644 --- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/MultiPartFilter.java +++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/MultiPartFilter.java @@ -53,6 +53,7 @@ import org.eclipse.jetty.util.B64Code; import org.eclipse.jetty.util.LazyList; import org.eclipse.jetty.util.MultiMap; import org.eclipse.jetty.util.QuotedStringTokenizer; +import org.eclipse.jetty.util.ReadLineInputStream; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.TypeUtil; @@ -118,7 +119,7 @@ public class MultiPartFilter implements Filter return; } - InputStream in = new BufferedInputStream(request.getInputStream()); + InputStream in = new ReadLineInputStream(request.getInputStream()); String content_type=srequest.getContentType(); // TODO - handle encodings @@ -144,8 +145,7 @@ public class MultiPartFilter implements Filter try { // Get first boundary - byte[] bytes=TypeUtil.readLine(in); - String line=bytes==null?null:new String(bytes,"UTF-8"); + String line=((ReadLineInputStream)in).readLine(); if(line==null || !line.equals(boundary)) { throw new IOException("Missing initial multi part boundary"); @@ -164,14 +164,15 @@ public class MultiPartFilter implements Filter while(true) { // read a line - bytes=TypeUtil.readLine(in); - if (bytes==null) + line=((ReadLineInputStream)in).readLine(); + + //No more input + if (line==null) break outer; // If blank line, end of part headers - if(bytes.length==0) + if("".equals(line)) break; - line=new String(bytes,"UTF-8"); // place part header key and value in map int c=line.indexOf(':',0); @@ -301,7 +302,14 @@ public class MultiPartFilter implements Filter if(c==13||c==10) { if(c==13) - state=in.read(); + { + in.mark(1); + int tmp=in.read(); + if (tmp!=10) + in.reset(); + else + state=tmp; + } break; } // look for boundary @@ -359,7 +367,7 @@ public class MultiPartFilter implements Filter if (file==null) { - bytes = ((ByteArrayOutputStream)out).toByteArray(); + byte[] bytes = ((ByteArrayOutputStream)out).toByteArray(); params.add(name,bytes); if (type_content != null) params.add(name+CONTENT_TYPE_SUFFIX, type_content); diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/MultipartFilterTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/MultipartFilterTest.java index 903fe316f95..a9be27229b5 100644 --- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/MultipartFilterTest.java +++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/MultipartFilterTest.java @@ -53,11 +53,11 @@ public class MultipartFilterTest protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { assertNotNull(req.getParameter("fileName")); - assertEquals("abc", req.getParameter("fileName")); + assertEquals(getServletContext().getAttribute("fileName"), req.getParameter("fileName")); assertNotNull(req.getParameter("desc")); - assertEquals("123", req.getParameter("desc")); + assertEquals(getServletContext().getAttribute("desc"), req.getParameter("desc")); assertNotNull(req.getParameter("title")); - assertEquals("ttt", req.getParameter("title")); + assertEquals(getServletContext().getAttribute("title"), req.getParameter("title")); super.doPost(req, resp); } } @@ -277,6 +277,9 @@ public class MultipartFilterTest HttpTester request = new HttpTester(); HttpTester response = new HttpTester(); tester.addServlet(BoundaryServlet.class,"/testb"); + tester.setAttribute("fileName", "abc"); + tester.setAttribute("desc", "123"); + tester.setAttribute("title", "ttt"); request.setMethod("POST"); request.setVersion("HTTP/1.0"); request.setHeader("Host","tester"); @@ -315,6 +318,160 @@ public class MultipartFilterTest assertTrue(response.getMethod()==null); assertEquals(HttpServletResponse.SC_OK,response.getStatus()); } + + + @Test + public void testLFOnlyRequest() throws Exception + { + String boundary="XyXyXy"; + // generated and parsed test + HttpTester request = new HttpTester(); + HttpTester response = new HttpTester(); + tester.addServlet(BoundaryServlet.class,"/testb"); + tester.setAttribute("fileName", "abc"); + tester.setAttribute("desc", "123"); + tester.setAttribute("title", "ttt"); + request.setMethod("POST"); + request.setVersion("HTTP/1.0"); + request.setHeader("Host","tester"); + request.setURI("/context/testb"); + request.setHeader("Content-Type","multipart/form-data; boundary="+boundary); + + String content = "--XyXyXy\n"+ + "Content-Disposition: form-data; name=\"fileName\"\n"+ + "Content-Type: text/plain; charset=US-ASCII\n"+ + "Content-Transfer-Encoding: 8bit\n"+ + "\n"+ + "abc\n"+ + "--XyXyXy\n"+ + "Content-Disposition: form-data; name=\"desc\"\n"+ + "Content-Type: text/plain; charset=US-ASCII\n"+ + "Content-Transfer-Encoding: 8bit\n"+ + "\n"+ + "123\n"+ + "--XyXyXy\n"+ + "Content-Disposition: form-data; name=\"title\"\n"+ + "Content-Type: text/plain; charset=US-ASCII\n"+ + "Content-Transfer-Encoding: 8bit\n"+ + "\n"+ + "ttt\n"+ + "--XyXyXy\n"+ + "Content-Disposition: form-data; name=\"fileup\"; filename=\"test.upload\"\n"+ + "Content-Type: application/octet-stream\n"+ + "Content-Transfer-Encoding: binary\n"+ + "\n"+ + "000\n"+ + "--XyXyXy--\n"; + request.setContent(content); + + response.parse(tester.getResponses(request.generate())); + assertTrue(response.getMethod()==null); + assertEquals(HttpServletResponse.SC_OK,response.getStatus()); + } + + + @Test + public void testCROnlyRequest() throws Exception + { + String boundary="XyXyXy"; + // generated and parsed test + HttpTester request = new HttpTester(); + HttpTester response = new HttpTester(); + tester.addServlet(BoundaryServlet.class,"/testb"); + tester.setAttribute("fileName", "abc"); + tester.setAttribute("desc", "123"); + tester.setAttribute("title", "ttt"); + request.setMethod("POST"); + request.setVersion("HTTP/1.0"); + request.setHeader("Host","tester"); + request.setURI("/context/testb"); + request.setHeader("Content-Type","multipart/form-data; boundary="+boundary); + + String content = "--XyXyXy\r"+ + "Content-Disposition: form-data; name=\"fileName\"\r"+ + "Content-Type: text/plain; charset=US-ASCII\r"+ + "Content-Transfer-Encoding: 8bit\r"+ + "\r"+ + "abc\r"+ + "--XyXyXy\r"+ + "Content-Disposition: form-data; name=\"desc\"\r"+ + "Content-Type: text/plain; charset=US-ASCII\r"+ + "Content-Transfer-Encoding: 8bit\r"+ + "\r"+ + "123\r"+ + "--XyXyXy\r"+ + "Content-Disposition: form-data; name=\"title\"\r"+ + "Content-Type: text/plain; charset=US-ASCII\r"+ + "Content-Transfer-Encoding: 8bit\r"+ + "\r"+ + "ttt\r"+ + "--XyXyXy\r"+ + "Content-Disposition: form-data; name=\"fileup\"; filename=\"test.upload\"\r"+ + "Content-Type: application/octet-stream\r"+ + "Content-Transfer-Encoding: binary\r"+ + "\r"+ + "000\r"+ + "--XyXyXy--\r"; + request.setContent(content); + + response.parse(tester.getResponses(request.generate())); + assertTrue(response.getMethod()==null); + assertEquals(HttpServletResponse.SC_OK,response.getStatus()); + } + + + @Test + public void testCROnlyWithEmbeddedLFRequest() throws Exception + { + String boundary="XyXyXy"; + // generated and parsed test + HttpTester request = new HttpTester(); + HttpTester response = new HttpTester(); + tester.addServlet(BoundaryServlet.class,"/testb"); + tester.setAttribute("fileName", "\nabc\n"); + tester.setAttribute("desc", "\n123\n"); + tester.setAttribute("title", "\nttt\n"); + request.setMethod("POST"); + request.setVersion("HTTP/1.0"); + request.setHeader("Host","tester"); + request.setURI("/context/testb"); + request.setHeader("Content-Type","multipart/form-data; boundary="+boundary); + + String content = "--XyXyXy\r"+ + "Content-Disposition: form-data; name=\"fileName\"\r"+ + "Content-Type: text/plain; charset=US-ASCII\r"+ + "Content-Transfer-Encoding: 8bit\r"+ + "\r"+ + "\nabc\n"+ + "\r"+ + "--XyXyXy\r"+ + "Content-Disposition: form-data; name=\"desc\"\r"+ + "Content-Type: text/plain; charset=US-ASCII\r"+ + "Content-Transfer-Encoding: 8bit\r"+ + "\r"+ + "\n123\n"+ + "\r"+ + "--XyXyXy\r"+ + "Content-Disposition: form-data; name=\"title\"\r"+ + "Content-Type: text/plain; charset=US-ASCII\r"+ + "Content-Transfer-Encoding: 8bit\r"+ + "\r"+ + "\nttt\n"+ + "\r"+ + "--XyXyXy\r"+ + "Content-Disposition: form-data; name=\"fileup\"; filename=\"test.upload\"\r"+ + "Content-Type: application/octet-stream\r"+ + "Content-Transfer-Encoding: binary\r"+ + "\r"+ + "000\r"+ + "--XyXyXy--\r"; + request.setContent(content); + + response.parse(tester.getResponses(request.generate())); + assertTrue(response.getMethod()==null); + assertEquals(HttpServletResponse.SC_OK,response.getStatus()); + } + /* * see the testParameterMap test diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ReadLineInputStream.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ReadLineInputStream.java new file mode 100644 index 00000000000..3946ca07483 --- /dev/null +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ReadLineInputStream.java @@ -0,0 +1,128 @@ +// +// ======================================================================== +// Copyright (c) 1995-2012 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.util; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * ReadLineInputStream + * + * Read from an input stream, accepting CR/LF, LF or just CR. + */ +public class ReadLineInputStream extends BufferedInputStream +{ + boolean _seenCRLF; + boolean _skipLF; + + public ReadLineInputStream(InputStream in) + { + super(in); + } + + public ReadLineInputStream(InputStream in, int size) + { + super(in,size); + } + + public String readLine() throws IOException + { + mark(buf.length); + + while (true) + { + int b=super.read(); + if (b==-1) + { + markpos=-1; + return null; + } + + if (b=='\r') + { + int p=pos; + + // if we have seen CRLF before, hungrily consume LF + if (_seenCRLF && pos0) + { + _skipLF=false; + if (_seenCRLF) + { + int b = super.read(); + if (b==-1) + return -1; + + if (b!='\n') + { + buf[off]=(byte)(0xff&b); + return 1+super.read(buf,off+1,len-1); + } + } + } + + return super.read(buf,off,len); + } + + +}