Started implementation of http multiform

This commit is contained in:
Ziver Koc 2016-07-06 17:36:57 +02:00
parent 765063ae47
commit 5cffdc49d1
5 changed files with 361 additions and 206 deletions

46
src/zutil/net/http/multipart/MultipartField.java Normal file → Executable file
View file

@ -25,46 +25,22 @@
package zutil.net.http.multipart; package zutil.net.http.multipart;
import java.io.InputStream;
/** /**
* A class for handling multipart field * A interface representing a single field in a multipart message.
* *
* @author Ziver * @author Ziver
*/ */
public class MultipartField{ public interface MultipartField{
protected long received; /**
protected long length; * @return the amount of data received for this field. Might only be available when all data has been processed
protected String contentType; */
public long getLength();
protected String fieldname;
protected String value;
protected MultipartField(){
}
/** /**
* @return the amount of data received for this field * @return the name of the field.
*/ */
public long getReceivedBytes(){ public String getName();
return received;
}
/**
* @return the fieldname
*/
public String getFieldname(){
return fieldname;
}
/**
* @return the value of the field
*/
public String getValue(){
return value;
}
protected void parse(){
}
} }

View file

@ -1,84 +1,102 @@
/* /*
* The MIT License (MIT) * The MIT License (MIT)
* *
* Copyright (c) 2015 Ziver Koc * Copyright (c) 2015 Ziver Koc
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights * in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is * copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions: * furnished to do so, subject to the following conditions:
* *
* The above copyright notice and this permission notice shall be included in * The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software. * all copies or substantial portions of the Software.
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE. * THE SOFTWARE.
*/ */
package zutil.net.http.multipart; package zutil.net.http.multipart;
import java.io.File; import java.io.File;
import java.io.IOException;
/**
* A class for handling multipart files /**
* * A class for handling multipart files
* @author Ziver *
*/ * @author Ziver
public class MultipartFile extends MultipartField{ */
protected String filename; public class MultipartFileField implements MultipartField{
protected File file; /** This is the temporary directory for the received files */
private File tempDir;
protected String filename;
protected MultipartFile(File tempFile){ protected File file;
this.file = tempFile;
}
protected MultipartFileField() throws IOException {
/** this.file = createTempFile();
* @return the amount of data received for this field }
*/
public long getReceivedBytes(){ /**
return received; * @return the amount of data received for this field
} */
public long getLength(){
/** return 0; //TODO:
* @return the value of the field }
*/
public String getValue(){ /**
return null; * @return the specific file name for this field.
} */
public String getName(){
/** return filename;
* @return the filename }
*/
public String getFilename(){ /**
return filename; * @return the File class that points to the received file
} */
public File getFile(){
/** return file;
* @return the File class that points to the received file }
*/
public File getFile(){ /**
return file; * Moves the received file.
} *
* @param newFile is the new location to move the file to
/** * @return if the move was successful
* Moves this file */
* public boolean moveFile(File newFile){
* @param new_file is the new location to move the file to boolean success = file.renameTo(newFile);
* @return if the move was successful if(success)
*/ file = newFile;
public boolean moveFile(File new_file){ return success;
boolean success = file.renameTo(new_file); }
if(success)
file = new_file; /**
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;
}
}

View file

@ -25,14 +25,18 @@
package zutil.net.http.multipart; package zutil.net.http.multipart;
import zutil.ProgressListener; import zutil.ProgressListener;
import zutil.log.LogUtil;
import zutil.net.http.HttpHeader; import zutil.net.http.HttpHeader;
import zutil.net.http.HttpHeaderParser;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader; import java.io.*;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/** /**
* Parses a multipart/form-data http request, * Parses a multipart/form-data http request,
@ -43,103 +47,140 @@ import java.util.List;
* @author Ziver * @author Ziver
* *
*/ */
public class MultipartParser { public class MultipartParser implements Iterable<MultipartField>{
/** This is the temporary directory for the received files */ private static final Logger logger = LogUtil.getLogger();
private File tempDir; private static final String HEADER_CONTENT_DISPOSITION = "Content-Disposition";
private static final String HEADER_CONTENT_TYPE = "Content-Type";
/** This is the delimiter that will separate the fields */ /** This is the delimiter that will separate the fields */
private String delimiter; private String delimiter;
/** The length of the HTTP Body */ /** The length of the HTTP Body */
private long contentLength; private long contentLength;
/** This is the input stream */ /** This is the input stream */
private BufferedReader in; private BufferedReader in;
/** This is the listener that will listen on the progress */ private MultiPartIterator iterator;
private ProgressListener<MultipartParser,MultipartField> listener;
public MultipartParser(BufferedReader in, String delimiter, long length){
this.in = in;
this.delimiter = delimiter;
this.contentLength = length;
}
public MultipartParser(BufferedReader in, HttpHeader header){ public MultipartParser(BufferedReader in, HttpHeader header){
this.in = in; this(in,
parseDelimiter(header.getHeader("Content-type")),
String cotype = header.getHeader("Content-type"); Long.parseLong( header.getHeader("Content-Length")));
cotype = cotype.split(" *; *")[1];
delimiter = cotype.split(" *= *")[1];
contentLength = Long.parseLong( header.getHeader("Content-Length") );
} }
public MultipartParser(HttpServletRequest req) throws IOException {
public MultipartParser(BufferedReader in, HttpServletRequest req){ this(req.getReader(),
this.in = in; parseDelimiter(req.getHeader("Content-type")),
req.getContentLength());
String cotype = req.getHeader("Content-type");
cotype = cotype.split(" *; *")[1];
delimiter = cotype.split(" *= *")[1];
contentLength = req.getContentLength();
}
public MultipartParser(BufferedReader in, String delimiter, long length){
this.in = in;
this.delimiter = delimiter;
this.contentLength = length;
}
/**
* @param listener is the listener that will be called for progress
*/
public void setListener(ProgressListener<MultipartParser,MultipartField> listener){
this.listener = listener;
}
/**
* Parses the HTTP Body and returns a list of fields
*
* @return A list of FormField
*/
public List<MultipartField> parse() throws IOException{
ArrayList<MultipartField> list = new ArrayList<MultipartField>();
parse(list, delimiter);
return list;
} }
private static String parseDelimiter(String contentTypeHeader){
private void parse(List<MultipartField> list, String delimiter) throws IOException{ String delimiter = contentTypeHeader.split(" *; *")[1];
// TODO: delimiter = delimiter.split(" *= *")[1];
} return delimiter;
}
/**
* 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");
}
/**
* Sets the initial delimiter
*
* @param delimiter is the new delimiter
*/
public void setDelimiter(String delimiter){
this.delimiter = delimiter;
}
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 long getContentLength(){ public long getContentLength(){
return contentLength; return contentLength;
} }
@Override
public Iterator<MultipartField> iterator() {
if (iterator == null)
iterator = new MultiPartIterator();
return iterator;
}
protected class MultiPartIterator implements Iterator<MultipartField>{
private String currentLine;
private boolean endOfStream;
private HttpHeaderParser parser;
protected MultiPartIterator(){
this.endOfStream = false;
this.parser = new HttpHeaderParser(in);
this.parser.setReadStatusLine(false);
}
@Override
public boolean hasNext() {
try{
findDelimiter();
if (endOfStream)
return false;
return true;
} 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 {
HttpHeader header = parser.read();
String disposition = header.getHeader(HEADER_CONTENT_DISPOSITION);
if (disposition != null){
HashMap<String,String> map = new HashMap<>();
HttpHeaderParser.parseHeaderValue(map, disposition);
if (map.containsKey("form-data")){
if (map.containsKey("filename")){
MultipartFileField field = new MultipartFileField();
return field;
}
else{
MultipartStringField field = new MultipartStringField();
return field;
}
}
else {
logger.warning("Only form-data is supported");
return this.next(); // find next field
}
}
} catch (IOException e) {
logger.log(Level.SEVERE, null, e);
}
return null;
}
@Override
public void remove() {
throw new UnsupportedOperationException("Remove not supported in read only stream.");
}
}
} }

View file

@ -0,0 +1,24 @@
package zutil.net.http.multipart;
import java.io.InputStream;
/**
* Created by ezivkoc on 2016-07-06.
*/
public class MultipartStringField implements MultipartField {
private String value;
@Override
public long getLength() {
return 0;
}
@Override
public String getName() {
return null;
}
public String getValue() {
return value;
}
}

View file

@ -0,0 +1,96 @@
package zutil.net.http.multipart;
import org.junit.Test;
import java.io.BufferedReader;
import java.io.StringReader;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
/**
* JUnit test for MultipartParser class
*
* Created by Ziver on 2016-07-06.
*/
public class MultipartParserTest {
@Test
public void singleFormDataField(){
String input =
"------------------------------83ff53821b7c\n" +
"Content-Disposition: form-data; name=\"foo\"\n" +
"\n" +
"bar\n" +
"------------------------------83ff53821b7c--";
MultipartParser parser = new MultipartParser(
new BufferedReader(new StringReader(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;
}
}
//@Test
public void singleFileUpload(){
String input =
"------------------------------83ff53821b7c\n" +
"Content-Disposition: form-data; name=\"img\"; filename=\"a.png\"\n" +
"Content-Type: application/octet-stream\n" +
"\n" +
"?PNG\n" +
"\n" +
"IHD?wS??iCCPICC Profilex?T?kA?6n??Zk?x?\"IY?hE?6?bk\n" +
"Y?<ߡ)??????9Nyx?+=?Y\"|@5-?\u007FM?S?%?@?H8??qR>?\u05CB??inf???O?????b??N?????~N??>?!?\n" +
"??V?J?p?8?da?sZHO?Ln?}&???wVQ?y?g????E??0\n" +
" ??\n" +
" IDAc????????-IEND?B`?\n" +
"------------------------------83ff53821b7c--";
MultipartParser parser = new MultipartParser(
new BufferedReader(new StringReader(input)),
"----------------------------83ff53821b7c",
input.length());
// TODO
}
//Test
public void multiFileUpload(){
String input =
" --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" +
"\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" +
"\n" +
" ...contents of file2.gif...\n" +
" --BbC04y--\n" +
" --AaB03x--";
MultipartParser parser = new MultipartParser(
new BufferedReader(new StringReader(input)),
"AaB03x",
input.length());
}
}