Started implementation of http multiform
This commit is contained in:
parent
765063ae47
commit
5cffdc49d1
5 changed files with 361 additions and 206 deletions
44
src/zutil/net/http/multipart/MultipartField.java
Normal file → Executable file
44
src/zutil/net/http/multipart/MultipartField.java
Normal file → Executable 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(){
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
|
||||||
56
src/zutil/net/http/multipart/MultipartFile.java → src/zutil/net/http/multipart/MultipartFileField.java
Normal file → Executable file
56
src/zutil/net/http/multipart/MultipartFile.java → src/zutil/net/http/multipart/MultipartFileField.java
Normal file → Executable file
|
|
@ -25,6 +25,7 @@
|
||||||
package zutil.net.http.multipart;
|
package zutil.net.http.multipart;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -32,33 +33,28 @@ import java.io.File;
|
||||||
*
|
*
|
||||||
* @author Ziver
|
* @author Ziver
|
||||||
*/
|
*/
|
||||||
public class MultipartFile extends MultipartField{
|
public class MultipartFileField implements MultipartField{
|
||||||
|
/** This is the temporary directory for the received files */
|
||||||
|
private File tempDir;
|
||||||
protected String filename;
|
protected String filename;
|
||||||
protected File file;
|
protected File file;
|
||||||
|
|
||||||
|
|
||||||
protected MultipartFile(File tempFile){
|
protected MultipartFileField() throws IOException {
|
||||||
this.file = tempFile;
|
this.file = createTempFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the amount of data received for this field
|
* @return the amount of data received for this field
|
||||||
*/
|
*/
|
||||||
public long getReceivedBytes(){
|
public long getLength(){
|
||||||
return received;
|
return 0; //TODO:
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the value of the field
|
* @return the specific file name for this field.
|
||||||
*/
|
*/
|
||||||
public String getValue(){
|
public String getName(){
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the filename
|
|
||||||
*/
|
|
||||||
public String getFilename(){
|
|
||||||
return filename;
|
return filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -70,15 +66,37 @@ public class MultipartFile extends MultipartField{
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Moves this file
|
* Moves the received file.
|
||||||
*
|
*
|
||||||
* @param new_file is the new location to move the file to
|
* @param newFile is the new location to move the file to
|
||||||
* @return if the move was successful
|
* @return if the move was successful
|
||||||
*/
|
*/
|
||||||
public boolean moveFile(File new_file){
|
public boolean moveFile(File newFile){
|
||||||
boolean success = file.renameTo(new_file);
|
boolean success = file.renameTo(newFile);
|
||||||
if(success)
|
if(success)
|
||||||
file = new_file;
|
file = newFile;
|
||||||
return success;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -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,9 +47,11 @@ 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 */
|
||||||
|
|
@ -53,93 +59,128 @@ public class MultipartParser {
|
||||||
/** 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, HttpHeader header){
|
|
||||||
this.in = in;
|
|
||||||
|
|
||||||
String cotype = header.getHeader("Content-type");
|
|
||||||
cotype = cotype.split(" *; *")[1];
|
|
||||||
delimiter = cotype.split(" *= *")[1];
|
|
||||||
|
|
||||||
contentLength = Long.parseLong( header.getHeader("Content-Length") );
|
|
||||||
}
|
|
||||||
|
|
||||||
public MultipartParser(BufferedReader in, HttpServletRequest req){
|
|
||||||
this.in = in;
|
|
||||||
|
|
||||||
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){
|
public MultipartParser(BufferedReader in, String delimiter, long length){
|
||||||
this.in = in;
|
this.in = in;
|
||||||
this.delimiter = delimiter;
|
this.delimiter = delimiter;
|
||||||
this.contentLength = length;
|
this.contentLength = length;
|
||||||
}
|
}
|
||||||
|
public MultipartParser(BufferedReader in, HttpHeader header){
|
||||||
|
this(in,
|
||||||
/**
|
parseDelimiter(header.getHeader("Content-type")),
|
||||||
* @param listener is the listener that will be called for progress
|
Long.parseLong( header.getHeader("Content-Length")));
|
||||||
*/
|
}
|
||||||
public void setListener(ProgressListener<MultipartParser,MultipartField> listener){
|
public MultipartParser(HttpServletRequest req) throws IOException {
|
||||||
this.listener = listener;
|
this(req.getReader(),
|
||||||
|
parseDelimiter(req.getHeader("Content-type")),
|
||||||
|
req.getContentLength());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private static String parseDelimiter(String contentTypeHeader){
|
||||||
* Parses the HTTP Body and returns a list of fields
|
String delimiter = contentTypeHeader.split(" *; *")[1];
|
||||||
*
|
delimiter = delimiter.split(" *= *")[1];
|
||||||
* @return A list of FormField
|
return delimiter;
|
||||||
*/
|
|
||||||
public List<MultipartField> parse() throws IOException{
|
|
||||||
ArrayList<MultipartField> list = new ArrayList<MultipartField>();
|
|
||||||
parse(list, delimiter);
|
|
||||||
return list;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void parse(List<MultipartField> list, String delimiter) throws IOException{
|
|
||||||
// TODO:
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
24
src/zutil/net/http/multipart/MultipartStringField.java
Executable file
24
src/zutil/net/http/multipart/MultipartStringField.java
Executable 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
96
test/zutil/net/http/multipart/MultipartParserTest.java
Executable file
96
test/zutil/net/http/multipart/MultipartParserTest.java
Executable 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue