From 077963ae28c55a1360b65ce7f69567196a93fb4c Mon Sep 17 00:00:00 2001 From: Ziver Koc Date: Fri, 8 Jul 2016 16:23:29 +0200 Subject: [PATCH] Using buffered boundary stream in multipart parser. But currently not working --- src/zutil/io/BufferedBoundaryInputStream.java | 4 +- src/zutil/io/IOUtil.java | 18 +++- src/zutil/io/NullOutputStream.java | 20 +++++ src/zutil/io/NullWriter.java | 41 +++++++++ .../http/multipart/MultipartFileField.java | 76 ++++++----------- .../net/http/multipart/MultipartParser.java | 61 +++++-------- .../http/multipart/MultipartStringField.java | 12 ++- src/zutil/struct/CircularBuffer.java | 1 + .../io/BufferedBoundaryInputStreamTest.java | 18 +++- .../http/multipart/MultipartParserTest.java | 85 +++++++++---------- test/zutil/struct/CircularBufferTest.java | 14 +-- 11 files changed, 198 insertions(+), 152 deletions(-) create mode 100755 src/zutil/io/NullOutputStream.java create mode 100755 src/zutil/io/NullWriter.java diff --git a/src/zutil/io/BufferedBoundaryInputStream.java b/src/zutil/io/BufferedBoundaryInputStream.java index 47cde39..8d5cc78 100755 --- a/src/zutil/io/BufferedBoundaryInputStream.java +++ b/src/zutil/io/BufferedBoundaryInputStream.java @@ -216,9 +216,9 @@ public class BufferedBoundaryInputStream extends FilterInputStream{ } /** - * @return if current position in the buffer is a boundary + * @return if there is more data to read */ - public boolean isBoundary(){ + public boolean hasNext(){ return bound_pos == buf_pos; } diff --git a/src/zutil/io/IOUtil.java b/src/zutil/io/IOUtil.java index f7e758b..ec2ea6e 100755 --- a/src/zutil/io/IOUtil.java +++ b/src/zutil/io/IOUtil.java @@ -89,11 +89,23 @@ public class IOUtil { } /** - * Copies all data from the input stream to the output stream. - * The input stream will not be closed after method has returned. + * Copies all data from one InputStream to another OutputStream. + * The streams will not be closed after method has returned. */ public static void copyStream(InputStream in, OutputStream out) throws IOException { - byte[] buff = new byte[256]; + byte[] buff = new byte[8192]; // This is the default BufferedInputStream buffer size + int len; + while((len = in.read(buff)) > 0){ + out.write(buff, 0, len); + } + } + + /** + * Copies all data from one Reader to another Writer. + * The streams will not be closed after method has returned. + */ + public static void copyStream(Reader in, Writer out) throws IOException { + char[] buff = new char[8192]; // This is the default BufferedReader buffer size int len; while((len = in.read(buff)) > 0){ out.write(buff, 0, len); diff --git a/src/zutil/io/NullOutputStream.java b/src/zutil/io/NullOutputStream.java new file mode 100755 index 0000000..55f35ad --- /dev/null +++ b/src/zutil/io/NullOutputStream.java @@ -0,0 +1,20 @@ +package zutil.io; + +import java.io.OutputStream; + +/** + * An OutputStream that does nothing but discard data. Similar to /dev/null + * + * Created by Ziver on 2016-07-08. + */ +public class NullOutputStream extends OutputStream { + + @Override + public void write(int b) { } + + @Override + public void write(byte b[]) { } + + @Override + public void write(byte b[], int off, int len) { } +} diff --git a/src/zutil/io/NullWriter.java b/src/zutil/io/NullWriter.java new file mode 100755 index 0000000..329b911 --- /dev/null +++ b/src/zutil/io/NullWriter.java @@ -0,0 +1,41 @@ +package zutil.io; + +import java.io.IOException; +import java.io.Writer; + +/** + * An Writer that does nothing but discard data. Similar to /dev/null + * + * Created by Ziver on 2016-07-08. + */ +public class NullWriter extends Writer{ + + @Override + public void write(int c) { } + @Override + public void write(char cbuf[]) { } + @Override + public void write(char cbuf[], int off, int len) { } + @Override + public void write(String str) throws IOException { } + @Override + public void write(String str, int off, int len) { } + + @Override + public Writer append(CharSequence csq) { + return this; + } + @Override + public Writer append(CharSequence csq, int start, int end) { + return this; + } + @Override + public Writer append(char c) { + return this; + } + + @Override + public void flush() { } + @Override + public void close() { }; +} diff --git a/src/zutil/net/http/multipart/MultipartFileField.java b/src/zutil/net/http/multipart/MultipartFileField.java index 7f584c3..4c86253 100755 --- a/src/zutil/net/http/multipart/MultipartFileField.java +++ b/src/zutil/net/http/multipart/MultipartFileField.java @@ -24,8 +24,11 @@ package zutil.net.http.multipart; -import java.io.File; -import java.io.IOException; +import zutil.io.IOUtil; + +import java.io.*; +import java.util.HashMap; +import java.util.Map; /** @@ -34,14 +37,14 @@ import java.io.IOException; * @author Ziver */ public class MultipartFileField implements MultipartField{ - /** This is the temporary directory for the received files */ - private File tempDir; - protected String filename; - protected File file; - + private String fieldname; + private String filename; + private InputStream in; - protected MultipartFileField() throws IOException { - this.file = createTempFile(); + + protected MultipartFileField(Map header, BufferedReader in) throws IOException { + this.fieldname = header.get("fieldname"); + this.filename = header.get("filename"); } /** @@ -52,51 +55,28 @@ public class MultipartFileField implements MultipartField{ } /** - * @return the specific file name for this field. + * @return the field name */ public String getName(){ - return filename; + return fieldname; + } + + public String getFilename(){ + return filename; + } + + public InputStream getInputStream(){ + return in; } /** - * @return the File class that points to the received file - */ - public File getFile(){ - return file; - } - - /** - * Moves the received file. + * Reads in all data and save it into the specified file * - * @param newFile is the new location to move the file to - * @return if the move was successful + * @param file is the new file where the data will be stored */ - public boolean moveFile(File newFile){ - boolean success = file.renameTo(newFile); - if(success) - file = newFile; - return success; - } - - /** - * Creates a temporary file in either the system - * temporary folder or by the setTempDir() function - * - * @return the temporary file - */ - protected File createTempFile() throws IOException { - if(tempDir != null) - return File.createTempFile("upload", ".part", tempDir.getAbsoluteFile()); - else - return File.createTempFile("upload", ".part"); - } - - - public void setTempDir(File dir){ - if(!dir.isDirectory()) - throw new RuntimeException("\""+dir.getAbsolutePath()+"\" is not a directory!"); - if(!dir.canWrite()) - throw new RuntimeException("\""+dir.getAbsolutePath()+"\" is not writable!"); - tempDir = dir; + public void saveToFile(File file) throws IOException { + BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file)); + IOUtil.copyStream(in, out); + out.close(); } } diff --git a/src/zutil/net/http/multipart/MultipartParser.java b/src/zutil/net/http/multipart/MultipartParser.java index 426c5fc..ac8f7ca 100755 --- a/src/zutil/net/http/multipart/MultipartParser.java +++ b/src/zutil/net/http/multipart/MultipartParser.java @@ -24,17 +24,17 @@ package zutil.net.http.multipart; -import zutil.ProgressListener; +import zutil.io.BufferedBoundaryInputStream; +import zutil.io.IOUtil; +import zutil.io.NullWriter; import zutil.log.LogUtil; import zutil.net.http.HttpHeader; import zutil.net.http.HttpHeaderParser; import javax.servlet.http.HttpServletRequest; import java.io.*; -import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; -import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; @@ -57,23 +57,23 @@ public class MultipartParser implements Iterable{ /** The length of the HTTP Body */ private long contentLength; /** This is the input stream */ - private BufferedReader in; + private InputStream in; private MultiPartIterator iterator; - public MultipartParser(BufferedReader in, String delimiter, long length){ + public MultipartParser(InputStream in, String delimiter, long length){ this.in = in; this.delimiter = delimiter; this.contentLength = length; } - public MultipartParser(BufferedReader in, HttpHeader header){ + public MultipartParser(InputStream in, HttpHeader header){ this(in, parseDelimiter(header.getHeader("Content-type")), Long.parseLong( header.getHeader("Content-Length"))); } public MultipartParser(HttpServletRequest req) throws IOException { - this(req.getReader(), + this(req.getInputStream(), parseDelimiter(req.getHeader("Content-type")), req.getContentLength()); } @@ -101,56 +101,39 @@ public class MultipartParser implements Iterable{ protected class MultiPartIterator implements Iterator{ - private String currentLine; - private boolean endOfStream; + private BufferedBoundaryInputStream boundaryIn; + private BufferedReader buffIn; private HttpHeaderParser parser; protected MultiPartIterator(){ - this.endOfStream = false; - this.parser = new HttpHeaderParser(in); + this.boundaryIn = new BufferedBoundaryInputStream(in); + this.buffIn = new BufferedReader(new InputStreamReader(boundaryIn)); + this.parser = new HttpHeaderParser(buffIn); this.parser.setReadStatusLine(false); + + this.boundaryIn.setBoundary("--"+delimiter); } @Override public boolean hasNext() { - try{ - findDelimiter(); - if (endOfStream) - return false; - return true; + try { + IOUtil.copyStream(buffIn, new NullWriter()); + return boundaryIn.hasNext(); } catch (IOException e){ logger.log(Level.SEVERE, null, e); - endOfStream = true; } return false; } - private void findDelimiter() throws IOException { - if (endOfStream || matchesDelimiter(currentLine)) - return; - while ((currentLine=in.readLine()) != null) { - if (matchesDelimiter(currentLine)) - break; - } - } - private boolean matchesDelimiter(String match){ - if (match == null) - return false; - else if (match.equals("--" + delimiter + "--")) { - endOfStream = true; - return true; - } - else if (match.equals("--" + delimiter)) - return true; - return false; - } + @Override public MultipartField next() { if (!hasNext()) return null; try { + boundaryIn.next(); // Skip current boundary HttpHeader header = parser.read(); String disposition = header.getHeader(HEADER_CONTENT_DISPOSITION); if (disposition != null){ @@ -158,16 +141,16 @@ public class MultipartParser implements Iterable{ HttpHeaderParser.parseHeaderValue(map, disposition); if (map.containsKey("form-data")){ if (map.containsKey("filename")){ - MultipartFileField field = new MultipartFileField(); + MultipartFileField field = new MultipartFileField(map, buffIn); return field; } else{ - MultipartStringField field = new MultipartStringField(map.get("name")); + MultipartStringField field = new MultipartStringField(map, buffIn); return field; } } else { - logger.warning("Only form-data is supported"); + logger.warning("Only multipart form-data is supported"); return this.next(); // find next field } } diff --git a/src/zutil/net/http/multipart/MultipartStringField.java b/src/zutil/net/http/multipart/MultipartStringField.java index 7b1fb7f..6548a1e 100755 --- a/src/zutil/net/http/multipart/MultipartStringField.java +++ b/src/zutil/net/http/multipart/MultipartStringField.java @@ -1,7 +1,12 @@ package zutil.net.http.multipart; -import java.util.HashMap; +import zutil.io.IOUtil; + +import java.io.BufferedReader; +import java.io.IOException; +import java.util.Map; + /** * Created by Ziver on 2016-07-06. @@ -10,8 +15,9 @@ public class MultipartStringField implements MultipartField { private String name; private String value; - protected MultipartStringField(String name){ - this.name = name; + protected MultipartStringField(Map header, BufferedReader in) throws IOException { + this.name = header.get("name"); + value = IOUtil.getContentAsString(in); } @Override diff --git a/src/zutil/struct/CircularBuffer.java b/src/zutil/struct/CircularBuffer.java index c155bc2..a684085 100755 --- a/src/zutil/struct/CircularBuffer.java +++ b/src/zutil/struct/CircularBuffer.java @@ -31,6 +31,7 @@ import java.util.NoSuchElementException; /** * This class is a first in first out circular buffer with a fixed size. * If the size is exceed then the oldest item will be removed. + * * Created by Ziver on 2015-09-22. */ public class CircularBuffer implements Iterable{ diff --git a/test/zutil/io/BufferedBoundaryInputStreamTest.java b/test/zutil/io/BufferedBoundaryInputStreamTest.java index 25a0fea..3867bfc 100755 --- a/test/zutil/io/BufferedBoundaryInputStreamTest.java +++ b/test/zutil/io/BufferedBoundaryInputStreamTest.java @@ -29,13 +29,14 @@ import org.junit.Test; import java.io.IOException; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; @SuppressWarnings("resource") public class BufferedBoundaryInputStreamTest { @Test - public void testReadB1() throws IOException { + public void normal() throws IOException { StringInputStream inin = new StringInputStream("aaa#aaaaaaaaaaaaaaaa#aaaaaaaaaaaaaaa#"); BufferedBoundaryInputStream in = new BufferedBoundaryInputStream(inin); @@ -61,7 +62,16 @@ public class BufferedBoundaryInputStreamTest { } @Test - public void testOnlyBoundaries() throws IOException { + public void startWithBound() throws IOException { + StringInputStream inin = new StringInputStream("#aaa"); + BufferedBoundaryInputStream in = new BufferedBoundaryInputStream(inin); + in.setBoundary("#"); + + assertEquals(-1, in.read()); + } + + @Test + public void onlyBoundaries() throws IOException { StringInputStream inin = new StringInputStream("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); BufferedBoundaryInputStream in = new BufferedBoundaryInputStream(inin); @@ -72,14 +82,14 @@ public class BufferedBoundaryInputStreamTest { assertEquals(-1, in.read()); assertEquals(-1, in.read()); in.next(); - if(!in.isBoundary()) + if(!in.hasNext()) break; } assertEquals(35, n); } @Test - public void testNoBounds() throws IOException { + public void noBounds() throws IOException { String data = "1234567891011121314151617181920"; StringInputStream inin = new StringInputStream(data); BufferedBoundaryInputStream in = new BufferedBoundaryInputStream(inin); diff --git a/test/zutil/net/http/multipart/MultipartParserTest.java b/test/zutil/net/http/multipart/MultipartParserTest.java index 998fbf3..11945b2 100755 --- a/test/zutil/net/http/multipart/MultipartParserTest.java +++ b/test/zutil/net/http/multipart/MultipartParserTest.java @@ -1,48 +1,47 @@ package zutil.net.http.multipart; import org.junit.Test; +import zutil.io.StringInputStream; -import java.io.BufferedReader; -import java.io.StringReader; +import java.util.Iterator; +import static junit.framework.TestCase.assertFalse; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.fail; +import static org.junit.Assert.assertTrue; /** * JUnit test for MultipartParser class - * + *

* Created by Ziver on 2016-07-06. */ public class MultipartParserTest { @Test - public void singleFormDataField(){ + public void singleFormDataField() { String input = - "------------------------------83ff53821b7c\n" + - "Content-Disposition: form-data; name=\"foo\"\n" + - "\n" + - "bar\n" + - "------------------------------83ff53821b7c--"; + "------------------------------83ff53821b7c\n" + + "Content-Disposition: form-data; name=\"foo\"\n" + + "\n" + + "bar\n" + + "------------------------------83ff53821b7c--"; MultipartParser parser = new MultipartParser( - new BufferedReader(new StringReader(input)), + new StringInputStream(input), "----------------------------83ff53821b7c", input.length()); - int count = 0; - for(MultipartField field : parser){ - assertEquals(0, count); - if (field instanceof MultipartStringField) { - MultipartStringField stringField = (MultipartStringField)field; - assertEquals("foo", stringField.getName()); - assertEquals("bar", stringField.getValue()); - } - else fail("Field is not an instance of "+MultipartStringField.class); - ++count; - } + // Assertions + Iterator it = parser.iterator(); + assertTrue(it.hasNext()); + MultipartField field = it.next(); + assertTrue(field instanceof MultipartStringField); + MultipartStringField stringField = (MultipartStringField) field; + assertEquals("foo", stringField.getName()); + assertEquals("bar", stringField.getValue()); + assertFalse(it.hasNext()); } //@Test - public void singleFileUpload(){ + public void singleFileUpload() { String input = "------------------------------83ff53821b7c\n" + "Content-Disposition: form-data; name=\"img\"; filename=\"a.png\"\n" + @@ -57,7 +56,7 @@ public class MultipartParserTest { " IDAc????????-IEND?B`?\n" + "------------------------------83ff53821b7c--"; MultipartParser parser = new MultipartParser( - new BufferedReader(new StringReader(input)), + new StringInputStream(input), "----------------------------83ff53821b7c", input.length()); @@ -65,31 +64,31 @@ public class MultipartParserTest { } //Test - public void multiFileUpload(){ + public void multiFileUpload() { String input = - " --AaB03x\n" + - " Content-Disposition: form-data; name=\"submit-name\"\n" + + "--AaB03x\n" + + "Content-Disposition: form-data; name=\"submit-name\"\n" + "\n" + - " Larry\n" + - " --AaB03x\n" + - " Content-Disposition: form-data; name=\"files\"\n" + - " Content-Type: multipart/mixed; boundary=BbC04y\n" + + "Larry\n" + + "--AaB03x\n" + + "Content-Disposition: form-data; name=\"files\"\n" + + "Content-Type: multipart/mixed; boundary=BbC04y\n" + "\n" + - " --BbC04y\n" + - " Content-Disposition: file; filename=\"file1.txt\"\n" + - " Content-Type: text/plain\n" + + "--BbC04y\n" + + "Content-Disposition: file; filename=\"file1.txt\"\n" + + "Content-Type: text/plain\n" + "\n" + - " ... contents of file1.txt ...\n" + - " --BbC04y\n" + - " Content-Disposition: file; filename=\"file2.gif\"\n" + - " Content-Type: image/gif\n" + - " Content-Transfer-Encoding: binary\n" + + "... contents of file1.txt ...\n" + + "--BbC04y\n" + + "Content-Disposition: file; filename=\"file2.gif\"\n" + + "Content-Type: image/gif\n" + + "Content-Transfer-Encoding: binary\n" + "\n" + - " ...contents of file2.gif...\n" + - " --BbC04y--\n" + - " --AaB03x--"; + "...contents of file2.gif...\n" + + "--BbC04y--\n" + + "--AaB03x--"; MultipartParser parser = new MultipartParser( - new BufferedReader(new StringReader(input)), + new StringInputStream(input), "AaB03x", input.length()); } diff --git a/test/zutil/struct/CircularBufferTest.java b/test/zutil/struct/CircularBufferTest.java index d32ff14..8f6718e 100755 --- a/test/zutil/struct/CircularBufferTest.java +++ b/test/zutil/struct/CircularBufferTest.java @@ -83,19 +83,16 @@ public class CircularBufferTest { } catch (IndexOutOfBoundsException e) {} } - @Test + @Test(expected = NoSuchElementException.class) public void iteratorEmpty() { CircularBuffer buff = new CircularBuffer(10); Iterator it = buff.iterator(); assert (!it.hasNext()); - try { - it.next(); - fail("NoSuchElementException was not thrown"); - } catch (NoSuchElementException e) {} + it.next(); // Exception } - @Test + @Test(expected = NoSuchElementException.class) public void iteratorThreeElements() { CircularBuffer buff = new CircularBuffer(10); buff.add(10); @@ -111,9 +108,6 @@ public class CircularBufferTest { assertEquals((Integer) 10, it.next()); assert (!it.hasNext()); - try { - it.next(); - fail("NoSuchElementException was not thrown"); - } catch (NoSuchElementException e) {} + it.next(); // Exception } }