Mercurial Hosting > luan
changeset 1147:30d87b7d1d62
webserver - support multipart/form-data
author | Franklin Schmidt <fschmidt@gmail.com> |
---|---|
date | Thu, 01 Feb 2018 22:06:37 -0700 |
parents | 2dda3c92a473 |
children | 49fb4e83484f |
files | src/luan/webserver/Connection.java src/luan/webserver/Request.java src/luan/webserver/RequestParser.java src/luan/webserver/Util.java src/luan/webserver/examples/post_multipart.html |
diffstat | 5 files changed, 110 insertions(+), 6 deletions(-) [+] |
line wrap: on
line diff
diff -r 2dda3c92a473 -r 30d87b7d1d62 src/luan/webserver/Connection.java --- a/src/luan/webserver/Connection.java Thu Feb 01 03:08:21 2018 -0700 +++ b/src/luan/webserver/Connection.java Thu Feb 01 22:06:37 2018 -0700 @@ -76,8 +76,8 @@ } size += n; } - request.body = new String(body); -//System.out.println(request.body); + request.body = body; +//System.out.println(new String(request.body)); } String contentType = (String)request.headers.get("Content-Type"); @@ -87,8 +87,10 @@ } else { if( "application/x-www-form-urlencoded".equals(contentType) ) { parser.parseUrlencoded(); + } else if( contentType.startsWith("multipart/form-data;") ) { + parser.parseMultipart(); } else { - logger.warn("unknown content type: "+contentType); + logger.error("unknown content type: "+contentType); } } }
diff -r 2dda3c92a473 -r 30d87b7d1d62 src/luan/webserver/Request.java --- a/src/luan/webserver/Request.java Thu Feb 01 03:08:21 2018 -0700 +++ b/src/luan/webserver/Request.java Thu Feb 01 22:06:37 2018 -0700 @@ -14,5 +14,19 @@ public final Map<String,Object> headers = Collections.synchronizedMap(new LinkedHashMap<String,Object>()); public final Map<String,Object> parameters = Collections.synchronizedMap(new LinkedHashMap<String,Object>()); public final Map<String,String> cookies = Collections.synchronizedMap(new LinkedHashMap<String,String>()); - public volatile String body; + public volatile byte[] body; + + public static final class MultipartFile { + public final String filename; + public final Object content; // byte[] or String + + MultipartFile(String filename,Object content) { + this.filename = filename; + this.content = content; + } + + public String toString() { + return "{filename="+filename+", content="+content+"}"; + } + } }
diff -r 2dda3c92a473 -r 30d87b7d1d62 src/luan/webserver/RequestParser.java --- a/src/luan/webserver/RequestParser.java Thu Feb 01 03:08:21 2018 -0700 +++ b/src/luan/webserver/RequestParser.java Thu Feb 01 22:06:37 2018 -0700 @@ -15,7 +15,7 @@ } void parseUrlencoded() throws ParseException { - this.parser = new Parser(request.body); + this.parser = new Parser(Util.toString(request.body)); parseQuery(); require( parser.endOfInput() ); } @@ -195,4 +195,64 @@ } } + + private static final String contentTypeStart = "multipart/form-data; boundary="; + + void parseMultipart() throws ParseException { + String contentType = (String)request.headers.get("Content-Type"); + if( !contentType.startsWith(contentTypeStart) ) + throw new RuntimeException(contentType); + String boundary = "--"+contentType.substring(contentTypeStart.length()); + this.parser = new Parser(Util.toString(request.body)); + require( parser.match(boundary) ); + boundary = "\r\n" + boundary; + while( !parser.match("--\r\n") ) { + require( parser.match("\r\n") ); + require( parser.match("Content-Disposition: form-data; name=") ); + String name = quotedString(); + String filename = null; + boolean isBinary = false; + if( parser.match("; filename=") ) { + filename = quotedString(); + require( parser.match("\r\n") ); + require( parser.match("Content-Type: ") ); + if( parser.match("application/octet-stream") ) { + isBinary = true; + } else if( parser.match("text/plain") ) { + isBinary = false; + } else + throw new ParseException(parser,"bad file content-type"); + } + require( parser.match("\r\n") ); + require( parser.match("\r\n") ); + int start = parser.currentIndex(); + while( !parser.test(boundary) ) { + require( parser.anyChar() ); + } + String value = parser.textFrom(start); + if( filename == null ) { + Util.add(request.parameters,name,value); + } else { + Object content = isBinary ? Util.toBytes(value) : value; + Request.MultipartFile mf = new Request.MultipartFile(filename,content); + Util.add(request.parameters,name,mf); + } + require( parser.match(boundary) ); + } + } + + private String quotedString() throws ParseException { + StringBuilder sb = new StringBuilder(); + require( parser.match('"') ); + while( !parser.match('"') ) { + if( parser.match("\\\"") ) { + sb.append('"'); + } else { + require( parser.anyChar() ); + sb.append( parser.lastChar() ); + } + } + return sb.toString(); + } + }
diff -r 2dda3c92a473 -r 30d87b7d1d62 src/luan/webserver/Util.java --- a/src/luan/webserver/Util.java Thu Feb 01 03:08:21 2018 -0700 +++ b/src/luan/webserver/Util.java Thu Feb 01 22:06:37 2018 -0700 @@ -26,7 +26,7 @@ } } - static void add(Map<String,Object> map,String name,String value) { + static void add(Map<String,Object> map,String name,Object value) { Object current = map.get(name); if( current == null ) { map.put(name,value); @@ -41,5 +41,22 @@ } } + static String toString(byte[] a) { + char[] ac = new char[a.length]; + for( int i=0; i<a.length; i++ ) { + ac[i] = (char)a[i]; + } + return new String(ac); + } + + static byte[] toBytes(String s) { + char[] ac = s.toCharArray(); + byte[] a = new byte[ac.length]; + for( int i=0; i<ac.length; i++ ) { + a[i] = (byte)ac[i]; + } + return a; + } + private Util() {} // never }
diff -r 2dda3c92a473 -r 30d87b7d1d62 src/luan/webserver/examples/post_multipart.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/luan/webserver/examples/post_multipart.html Thu Feb 01 22:06:37 2018 -0700 @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html> + <body> + <form action=/params method=post enctype="multipart/form-data"> + <p>a <input name=a></p> + <p>b <input name=b></p> + <p><input type=file name=file></p> + <p><input type=submit></p> + </form> + </body> +</html>