Index: providers/multipart/src/main/java/org/jboss/resteasy/plugins/providers/multipart/InputPart.java =================================================================== --- providers/multipart/src/main/java/org/jboss/resteasy/plugins/providers/multipart/InputPart.java (revision 656) +++ providers/multipart/src/main/java/org/jboss/resteasy/plugins/providers/multipart/InputPart.java (working copy) @@ -15,7 +15,7 @@ { MultivaluedMap getHeaders(); - String getBodyAsString(); + String getBodyAsString() throws IOException; T getBody(Class type, Type genericType) throws IOException; Index: providers/multipart/src/main/java/org/jboss/resteasy/plugins/providers/multipart/ListMultipartReader.java =================================================================== --- providers/multipart/src/main/java/org/jboss/resteasy/plugins/providers/multipart/ListMultipartReader.java (revision 656) +++ providers/multipart/src/main/java/org/jboss/resteasy/plugins/providers/multipart/ListMultipartReader.java (working copy) @@ -45,7 +45,7 @@ Type baseType = param.getActualTypeArguments()[0]; Class rawType = Types.getRawType(baseType); - MultipartInputImpl input = new MultipartInputImpl(boundary, workers); + MultipartInputImpl input = new MultipartInputImpl(mediaType, workers); input.parse(entityStream); ArrayList list = new ArrayList(); Index: providers/multipart/src/main/java/org/jboss/resteasy/plugins/providers/multipart/MapMultipartFormDataReader.java =================================================================== --- providers/multipart/src/main/java/org/jboss/resteasy/plugins/providers/multipart/MapMultipartFormDataReader.java (revision 656) +++ providers/multipart/src/main/java/org/jboss/resteasy/plugins/providers/multipart/MapMultipartFormDataReader.java (working copy) @@ -45,7 +45,7 @@ Type baseType = param.getActualTypeArguments()[1]; Class rawType = Types.getRawType(baseType); - MultipartFormDataInputImpl input = new MultipartFormDataInputImpl(boundary, workers); + MultipartFormDataInputImpl input = new MultipartFormDataInputImpl(mediaType, workers); input.parse(entityStream); HashMap map = new HashMap(); Index: providers/multipart/src/main/java/org/jboss/resteasy/plugins/providers/multipart/MultipartFormAnnotationReader.java =================================================================== --- providers/multipart/src/main/java/org/jboss/resteasy/plugins/providers/multipart/MultipartFormAnnotationReader.java (revision 656) +++ providers/multipart/src/main/java/org/jboss/resteasy/plugins/providers/multipart/MultipartFormAnnotationReader.java (working copy) @@ -42,7 +42,7 @@ { String boundary = mediaType.getParameters().get("boundary"); if (boundary == null) throw new IOException("Unable to get boundary for multipart"); - MultipartFormDataInputImpl input = new MultipartFormDataInputImpl(boundary, workers); + MultipartFormDataInputImpl input = new MultipartFormDataInputImpl(mediaType, workers); input.parse(entityStream); Object obj; Index: providers/multipart/src/main/java/org/jboss/resteasy/plugins/providers/multipart/MultipartFormDataInputImpl.java =================================================================== --- providers/multipart/src/main/java/org/jboss/resteasy/plugins/providers/multipart/MultipartFormDataInputImpl.java (revision 656) +++ providers/multipart/src/main/java/org/jboss/resteasy/plugins/providers/multipart/MultipartFormDataInputImpl.java (working copy) @@ -1,16 +1,18 @@ package org.jboss.resteasy.plugins.providers.multipart; -import org.jboss.resteasy.util.GenericType; - -import javax.ws.rs.ext.Providers; import java.io.IOException; -import java.io.InputStream; import java.lang.reflect.Type; import java.util.HashMap; import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.ext.Providers; + +import org.apache.james.mime4j.field.ContentDispositionField; +import org.apache.james.mime4j.message.BodyPart; +import org.apache.james.mime4j.parser.Field; +import org.jboss.resteasy.util.GenericType; + /** * @author Bill Burke * @version $Revision: 1 $ @@ -18,11 +20,10 @@ public class MultipartFormDataInputImpl extends MultipartInputImpl implements MultipartFormDataInput { protected Map formData = new HashMap(); - protected static final Pattern DISPOSITION = Pattern.compile(";\\s*name=\"?([^\";]*)\"?"); - public MultipartFormDataInputImpl(String boundary, Providers workers) + public MultipartFormDataInputImpl(MediaType contentType, Providers workers) { - super(boundary, workers); + super(contentType, workers); } public Map getFormData() @@ -45,20 +46,21 @@ } @Override - protected void extractPart(InputStream is) throws IOException + protected InputPart extractPart(BodyPart bodyPart) throws IOException { - super.extractPart(is); - String disposition = currPart.getHeaders().getFirst("Content-Disposition"); + InputPart currPart = super.extractPart(bodyPart); + Field disposition = bodyPart.getHeader().getField("Content-Disposition"); if (disposition == null) throw new RuntimeException("Could find no Content-Disposition header within part"); - Matcher matcher = DISPOSITION.matcher(disposition); - if (matcher.find()) + if (disposition instanceof ContentDispositionField) { - formData.put(matcher.group(1).trim(), currPart); + formData.put(((ContentDispositionField) disposition).getParameter("name"), currPart); } else { throw new RuntimeException("Could not parse Content-Disposition for MultipartFormData: " + disposition); } + + return currPart; } } Index: providers/multipart/src/main/java/org/jboss/resteasy/plugins/providers/multipart/MultipartFormDataReader.java =================================================================== --- providers/multipart/src/main/java/org/jboss/resteasy/plugins/providers/multipart/MultipartFormDataReader.java (revision 656) +++ providers/multipart/src/main/java/org/jboss/resteasy/plugins/providers/multipart/MultipartFormDataReader.java (working copy) @@ -48,7 +48,7 @@ String boundary = mediaType.getParameters().get("boundary"); if (boundary == null) throw new IOException("Unable to get boundary for multipart"); - MultipartFormDataInputImpl input = new MultipartFormDataInputImpl(boundary, workers); + MultipartFormDataInputImpl input = new MultipartFormDataInputImpl(mediaType, workers); input.parse(entityStream); return input; } Index: providers/multipart/src/main/java/org/jboss/resteasy/plugins/providers/multipart/MultipartInputImpl.java =================================================================== --- providers/multipart/src/main/java/org/jboss/resteasy/plugins/providers/multipart/MultipartInputImpl.java (revision 656) +++ providers/multipart/src/main/java/org/jboss/resteasy/plugins/providers/multipart/MultipartInputImpl.java (working copy) @@ -1,265 +1,204 @@ package org.jboss.resteasy.plugins.providers.multipart; -import org.jboss.resteasy.util.CaseInsensitiveMap; -import org.jboss.resteasy.util.GenericType; - -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.MultivaluedMap; -import javax.ws.rs.ext.MessageBodyReader; -import javax.ws.rs.ext.Providers; import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.Reader; +import java.io.SequenceInputStream; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; import java.lang.annotation.Annotation; import java.lang.reflect.Type; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.ext.MessageBodyReader; +import javax.ws.rs.ext.Providers; + +import org.apache.james.mime4j.field.ContentTypeField; +import org.apache.james.mime4j.message.BinaryBody; +import org.apache.james.mime4j.message.Body; +import org.apache.james.mime4j.message.BodyPart; +import org.apache.james.mime4j.message.Message; +import org.apache.james.mime4j.message.Multipart; +import org.apache.james.mime4j.message.TextBody; +import org.apache.james.mime4j.parser.Field; +import org.jboss.resteasy.util.CaseInsensitiveMap; +import org.jboss.resteasy.util.GenericType; + /** * @author Bill Burke * @version $Revision: 1 $ */ -public class MultipartInputImpl implements MultipartInput -{ - protected String boundary; - protected byte[] boundaryBytes; - protected int pointer; - protected List parts = new ArrayList(); - protected PartImpl currPart; - protected int preambleEnd; - protected Providers workers; - protected static final Annotation[] empty = {}; +public class MultipartInputImpl implements MultipartInput { + protected MediaType contentType; + protected Providers workers; + protected Message mimeMessage; + protected List parts = new ArrayList(); + protected static final Annotation[] empty = {}; - protected ByteArrayOutputStream baos = new ByteArrayOutputStream(); - protected byte[] buffer; + public MultipartInputImpl(MediaType contentType, Providers workers) { + this.contentType = contentType; + this.workers = workers; + } - public MultipartInputImpl(String boundary, Providers workers) - { - this.boundary = "--" + boundary; - boundaryBytes = this.boundary.getBytes(); - this.workers = workers; - } + public void parse(InputStream is) throws IOException { + mimeMessage = new Message(addHeaderToHeadlessStream(is)); + extractParts(); + } - public List getParts() - { - return parts; - } + protected InputStream addHeaderToHeadlessStream(InputStream is) + throws UnsupportedEncodingException { + return new SequenceInputStream(createHeaderInputStream(), is); + } - public class PartImpl implements InputPart - { - private int start; - private int end; - private MultivaluedMap headers = new CaseInsensitiveMap(); - private MediaType mediaType; + protected InputStream createHeaderInputStream() + throws UnsupportedEncodingException { + String header = HttpHeaders.CONTENT_TYPE + ": " + contentType + + "\r\n\r\n"; + return new ByteArrayInputStream(header.getBytes("utf-8")); + } - public MultivaluedMap getHeaders() - { - return headers; - } + public String getPreamble() { + return ((Multipart) mimeMessage.getBody()).getPreamble(); + } - public void startBody(int index) - { - start = index; - } + public List getParts() { + return parts; + } - public void endBody(int index) - { - end = index; - String mime = headers.getFirst("content-type"); - if (mime == null) mediaType = MediaType.TEXT_PLAIN_TYPE; - else mediaType = MediaType.valueOf(mime); - } + protected void extractParts() throws IOException { + Multipart multipart = (Multipart) mimeMessage.getBody(); + for (BodyPart bodyPart : multipart.getBodyParts()) + parts.add(extractPart(bodyPart)); + } - public void addHeader(String header) - { - int colon = header.indexOf(':'); - String name = header.substring(0, colon); - String value = header.substring(colon + 1); - if (value.charAt(0) == '"') value = value.substring(1); - if (value.endsWith("\"")) value = value.substring(0, value.length() - 1); - headers.add(name.trim(), value.trim()); - } + protected InputPart extractPart(BodyPart bodyPart) throws IOException { + return new PartImpl(bodyPart); + } - public InputStream getBody() - { - return new ByteArrayInputStream(buffer, start, end - start); - } + public class PartImpl implements InputPart { + private BodyPart bodyPart; + private MediaType contentType; + private MultivaluedMap headers = new CaseInsensitiveMap(); - public String getBodyAsString() - { - return new String(buffer, start, end - start); - } + public PartImpl(BodyPart bodyPart) { + this.bodyPart = bodyPart; + for (Field field : bodyPart.getHeader()) { + headers.add(field.getName(), field.getBody()); + if (field instanceof ContentTypeField) + contentType = MediaType.valueOf(field.getBody()); + } + if (contentType == null) + contentType = MediaType.TEXT_PLAIN_TYPE; + } - public T getBody(Class type, Type genericType) throws IOException - { - MessageBodyReader reader = workers.getMessageBodyReader(type, genericType, empty, mediaType); - return reader.readFrom(type, genericType, empty, mediaType, headers, getBody()); - } + public T getBody(Class type, Type genericType) + throws IOException { + MessageBodyReader reader = workers.getMessageBodyReader(type, + genericType, empty, contentType); + return reader.readFrom(type, genericType, empty, contentType, + headers, getBody()); + } - public T getBody(GenericType type) throws IOException - { - return getBody(type.getType(), type.getGenericType()); - } + public T getBody(GenericType type) throws IOException { + return getBody(type.getType(), type.getGenericType()); + } - public MediaType getMediaType() - { - return mediaType; - } - } + public InputStream getBody() throws IOException { + Body body = bodyPart.getBody(); + InputStream result = null; + if (body instanceof TextBody) { + final Reader reader = ((TextBody) body).getReader(); + result = new InputStream() { + @Override + public int read() throws IOException { + int c = reader.read(); + return c; + } + }; + } else if (body instanceof BinaryBody) + result = ((BinaryBody) body).getInputStream(); + return result; + } - protected PartImpl createPart() - { - return new PartImpl(); - } + public String getBodyAsString() throws IOException { + Body body = bodyPart.getBody(); + String result = null; + if (body instanceof TextBody) { + Reader reader = ((TextBody) body).getReader(); + StringWriter writer = new StringWriter(); + char[] buffer = new char[4048]; + int n = 0; + while ((n = reader.read(buffer)) != -1) + writer.write(buffer, 0, n); + result = writer.toString(); + } else if (body instanceof BinaryBody) { + InputStream inputStream = ((BinaryBody) body).getInputStream(); + String charset = contentType.getParameters().get("charset"); + StringWriter writer = new StringWriter(); + byte[] buffer = new byte[4048]; + int n = 0; + while ((n = inputStream.read(buffer)) != -1) + if (charset == null) + writer.write(new String(buffer, 0, n)); + else + writer.write(new String(buffer, 0, n, charset)); + result = writer.toString(); + } - public void parse(InputStream is) throws IOException - { - int index = 0; - while (true) - { - int b = read(is); - if (b == boundaryBytes[index]) - { - index++; - if (index == boundaryBytes.length) - { - int b1 = read(is); - if (b1 == -1) throw new RuntimeException("Unexpected end of request, read bounder then EOF"); - int b2 = read(is); - if (b2 == -1) throw new RuntimeException("Unexpected end of request, read bounder then EOF"); + return result; + } - if (b1 == '\r' && b2 == '\n') - { - if (currPart != null) currPart.endBody(pointer - 4 - boundaryBytes.length); - else - { - preambleEnd = pointer - 4 - boundaryBytes.length; - } - currPart = createPart(); - extractPart(is); - } - else if (b1 == '-' && b2 == '-') - { - if (currPart != null) currPart.endBody(pointer - 4 - boundaryBytes.length); - else - { - preambleEnd = pointer - 4 - boundaryBytes.length; - } - break; - } - else - { - throw new RuntimeException("Found boundary but no trailing \\r\\n or --"); - } - index = 0; + public MultivaluedMap getHeaders() { + return headers; + } - } - } - else - { - index = 0; - } - } - buffer = baos.toByteArray(); - } + public MediaType getMediaType() { + return contentType; + } + } - protected void extractPart(InputStream is) - throws IOException - { - parts.add(currPart); - String line = null; - do - { - line = readLine(is); - if (!"".equals(line)) - { - currPart.addHeader(line); - } + public static void main(String[] args) throws Exception { + String input = "URLSTR: file:/Users/billburke/jboss/resteasy-jaxrs/resteasy-jaxrs/src/test/test-data/data.txt\r\n" + + "--B98hgCmKsQ-B5AUFnm2FnDRCgHPDE3\r\n" + + "Content-Disposition: form-data; name=\"part1\"\r\n" + + "Content-Type: text/plain; charset=US-ASCII\r\n" + + "Content-Transfer-Encoding: 8bit\r\n" + + "\r\n" + + "This is Value 1\r\n" + + "--B98hgCmKsQ-B5AUFnm2FnDRCgHPDE3\r\n" + + "Content-Disposition: form-data; name=\"part2\"\r\n" + + "Content-Type: text/plain; charset=US-ASCII\r\n" + + "Content-Transfer-Encoding: 8bit\r\n" + + "\r\n" + + "This is Value 2\r\n" + + "--B98hgCmKsQ-B5AUFnm2FnDRCgHPDE3\r\n" + + "Content-Disposition: form-data; name=\"data.txt\"; filename=\"data.txt\"\r\n" + + "Content-Type: application/octet-stream; charset=ISO-8859-1\r\n" + + "Content-Transfer-Encoding: binary\r\n" + + "\r\n" + + "hello world\r\n" + "--B98hgCmKsQ-B5AUFnm2FnDRCgHPDE3--"; + ByteArrayInputStream bais = new ByteArrayInputStream(input.getBytes()); + Map parameters = new HashMap(); + parameters.put("boundary", "B98hgCmKsQ-B5AUFnm2FnDRCgHPDE3"); + MediaType contentType = new MediaType("multipart", "form-data", + parameters); + MultipartInputImpl multipart = new MultipartInputImpl(contentType, null); + multipart.parse(bais); - } while (line.length() > 0); - currPart.startBody(pointer); - } + System.out.println(multipart.getPreamble()); + System.out.println("**********"); + for (InputPart part : multipart.getParts()) { + System.out.println("--"); + System.out.println("\"" + part.getBodyAsString() + "\""); + } + System.out.println("done"); - public String getPreamble() - { - if (preambleEnd < 0) return null; - return new String(buffer, 0, preambleEnd); - } - - public String getBufferAsString() - { - return new String(buffer); - } - - protected String readLine(InputStream is) throws IOException - { - StringBuffer buf = new StringBuffer(); - while (true) - { - int b = read(is); - if (b == -1) throw new RuntimeException("Unexpected end of buffer"); - if (b == '\r') - { - b = read(is); - if (b == '\n') return buf.toString(); - else - { - buf.append('\r').append((char) b); - } - } - else - { - buf.append((char) b); - } - - } - } - - protected int read(InputStream is) - throws IOException - { - int b = is.read(); - if (b == -1) return -1; - baos.write(b); - pointer++; - return b; - } - - public static void main(String[] args) throws Exception - { - String input = "URLSTR: file:/Users/billburke/jboss/resteasy-jaxrs/resteasy-jaxrs/src/test/test-data/data.txt\r\n" + - "--B98hgCmKsQ-B5AUFnm2FnDRCgHPDE3\r\n" + - "Content-Disposition: form-data; name=\"part1\"\r\n" + - "Content-Type: text/plain; charset=US-ASCII\r\n" + - "Content-Transfer-Encoding: 8bit\r\n" + - "\r\n" + - "This is Value 1\r\n" + - "--B98hgCmKsQ-B5AUFnm2FnDRCgHPDE3\r\n" + - "Content-Disposition: form-data; name=\"part2\"\r\n" + - "Content-Type: text/plain; charset=US-ASCII\r\n" + - "Content-Transfer-Encoding: 8bit\r\n" + - "\r\n" + - "This is Value 2\r\n" + - "--B98hgCmKsQ-B5AUFnm2FnDRCgHPDE3\r\n" + - "Content-Disposition: form-data; name=\"data.txt\"; filename=\"data.txt\"\r\n" + - "Content-Type: application/octet-stream; charset=ISO-8859-1\r\n" + - "Content-Transfer-Encoding: binary\r\n" + - "\r\n" + - "hello world\r\n" + - "--B98hgCmKsQ-B5AUFnm2FnDRCgHPDE3--"; - ByteArrayInputStream bais = new ByteArrayInputStream(input.getBytes()); - MultipartInputImpl multipart = new MultipartInputImpl("B98hgCmKsQ-B5AUFnm2FnDRCgHPDE3", null); - multipart.parse(bais); - - System.out.println(multipart.getPreamble()); - System.out.println("**********"); - for (InputPart part : multipart.getParts()) - { - System.out.println("--"); - System.out.println("\"" + part.getBodyAsString() + "\""); - } - System.out.println("done"); - - } + } } Index: providers/multipart/src/main/java/org/jboss/resteasy/plugins/providers/multipart/MultipartReader.java =================================================================== --- providers/multipart/src/main/java/org/jboss/resteasy/plugins/providers/multipart/MultipartReader.java (revision 656) +++ providers/multipart/src/main/java/org/jboss/resteasy/plugins/providers/multipart/MultipartReader.java (working copy) @@ -35,7 +35,7 @@ { String boundary = mediaType.getParameters().get("boundary"); if (boundary == null) throw new IOException("Unable to get boundary for multipart"); - MultipartInputImpl input = new MultipartInputImpl(boundary, workers); + MultipartInputImpl input = new MultipartInputImpl(mediaType, workers); /* StringWriter writer = new StringWriter(); int b;