Author: konstantin.mishin
Date: 2007-11-29 14:03:15 -0500 (Thu, 29 Nov 2007)
New Revision: 4362
Added:
trunk/sandbox/ui/fileUpload/src/main/config/component/fileUpload.xml
trunk/sandbox/ui/fileUpload/src/main/java/
trunk/sandbox/ui/fileUpload/src/main/java/org/
trunk/sandbox/ui/fileUpload/src/main/java/org/richfaces/
trunk/sandbox/ui/fileUpload/src/main/java/org/richfaces/org/
trunk/sandbox/ui/fileUpload/src/main/java/org/richfaces/org/jboss/
trunk/sandbox/ui/fileUpload/src/main/java/org/richfaces/org/jboss/seam/
trunk/sandbox/ui/fileUpload/src/main/java/org/richfaces/org/jboss/seam/ui/
trunk/sandbox/ui/fileUpload/src/main/java/org/richfaces/org/jboss/seam/ui/component/
trunk/sandbox/ui/fileUpload/src/main/java/org/richfaces/org/jboss/seam/ui/component/UIFileUpload.java
trunk/sandbox/ui/fileUpload/src/main/java/org/richfaces/org/jboss/seam/ui/renderkit/
trunk/sandbox/ui/fileUpload/src/main/java/org/richfaces/org/jboss/seam/ui/renderkit/FileUploadRendererBase.java
trunk/sandbox/ui/fileUpload/src/main/java/org/richfaces/org/jboss/seam/web/
trunk/sandbox/ui/fileUpload/src/main/java/org/richfaces/org/jboss/seam/web/FileUploadException.java
trunk/sandbox/ui/fileUpload/src/main/java/org/richfaces/org/jboss/seam/web/MultipartFilter.java
trunk/sandbox/ui/fileUpload/src/main/java/org/richfaces/org/jboss/seam/web/MultipartRequest.java
Modified:
trunk/sandbox/ui/fileUpload/pom.xml
Log:
RF-1209
Modified: trunk/sandbox/ui/fileUpload/pom.xml
===================================================================
--- trunk/sandbox/ui/fileUpload/pom.xml 2007-11-29 18:44:54 UTC (rev 4361)
+++ trunk/sandbox/ui/fileUpload/pom.xml 2007-11-29 19:03:15 UTC (rev 4362)
@@ -22,6 +22,13 @@
<goal>generate</goal>
</goals>
</execution>
+ <execution>
+ <id>generate-test-sources</id>
+ <phase>generate-test-sources</phase>
+ <goals>
+ <goal>generate-tests</goal>
+ </goals>
+ </execution>
</executions>
<configuration>
<library>
Added: trunk/sandbox/ui/fileUpload/src/main/config/component/fileUpload.xml
===================================================================
--- trunk/sandbox/ui/fileUpload/src/main/config/component/fileUpload.xml
(rev 0)
+++ trunk/sandbox/ui/fileUpload/src/main/config/component/fileUpload.xml 2007-11-29
19:03:15 UTC (rev 4362)
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE components PUBLIC "-//AJAX4JSF//CDK Generator config/EN"
"https://ajax4jsf.dev.java.net/nonav/dtds/component-config.dtd" >
+<components>
+ <component>
+ <name>org.richfaces.component.FileUpload</name>
+ <family>org.richfaces.component.FileUpload</family>
+ <classname>org.richfaces.component.html.HtmlFileUpload</classname>
+ <superclass>org.richfaces.org.jboss.seam.ui.component.UIFileUpload</superclass>
+ <description>
+ <![CDATA[Renders a file upload control. This control must be used within a form
with an encoding type of multipart/form-data, i.e:
+
+<h:form enctype="multipart/form-data">
+
+For multipart requests, the Seam Multipart servlet filter must also be configured in
web.xml:
+
+<filter>
+ <filter-name>Seam Filter</filter-name>
+ <filter-class>org.jboss.seam.servlet.SeamFilter</filter-class>
+</filter>
+
+<filter-mapping>
+ <filter-name>Seam Filter</filter-name>
+ <url-pattern>/*</url-pattern>
+</filter-mapping>
+
+The following configuration options for multipart requests may be configured in
components.xml:
+
+* createTempFiles - if this option is set to true, uploaded files are streamed to a
temporary file instead of in memory.
+* maxRequestSize - the maximum size of a file upload request, in bytes.
+
+Here's an example:
+
+<component class="org.jboss.seam.servlet.MultipartConfig">
+ <property name="createTempFiles">true</property>
+ <property name="maxRequestSize">1000000</property>
+</component>]]>
+ </description>
+ <renderer generate="false" override="false">
+ <name>org.richfaces.renderkit.html.FileUploadRenderer</name>
+ <classname>org.richfaces.org.jboss.seam.ui.renderkit.FileUploadRendererBase</classname>
+ <!--<template>org/jboss/seam/ui/htmlFileUpload.jspx</template>-->
+ </renderer>
+ <tag>
+ <name>fileUpload</name>
+ <classname>org.richfaces.taglib.FileUploadTag</classname>
+ <superclass>
+ org.ajax4jsf.webapp.taglib.HtmlComponentTagBase
+ </superclass>
+ </tag>
+ &ui_component_attributes;
+ &ui_input_attributes;
+ &html_style_attributes;
+ &html_input_attributes;
+ &html_events;
+ &html_input_events;
+ <property transient="true">
+ <name>accept</name>
+ <classname>java.lang.String</classname>
+ <description>a comma-separated list of content types to accept, may not be
supported by the browser. E.g. "images/png,images/jpg",
"images/*".</description>
+ </property>
+ <property elonly="true">
+ <name>data</name>
+ <classname>java.lang.Object</classname>
+ <description>this value binding receives the file's content type
(optional).</description>
+ </property>
+ <property elonly="true">
+ <name>contentType</name>
+ <classname>java.lang.String</classname>
+ <description>the property to receive the contentType</description>
+ </property>
+ <property elonly="true">
+ <name>fileName</name>
+ <classname>java.lang.String</classname>
+ <description>this value binding receives the filename
(optional).</description>
+ </property>
+ <property elonly="true">
+ <name>fileSize</name>
+ <classname>java.lang.Integer</classname>
+ <description>this value binding receives the file size
(optional).</description>
+ </property>
+ </component>
+</components>
Added:
trunk/sandbox/ui/fileUpload/src/main/java/org/richfaces/org/jboss/seam/ui/component/UIFileUpload.java
===================================================================
---
trunk/sandbox/ui/fileUpload/src/main/java/org/richfaces/org/jboss/seam/ui/component/UIFileUpload.java
(rev 0)
+++
trunk/sandbox/ui/fileUpload/src/main/java/org/richfaces/org/jboss/seam/ui/component/UIFileUpload.java 2007-11-29
19:03:15 UTC (rev 4362)
@@ -0,0 +1,144 @@
+package org.richfaces.org.jboss.seam.ui.component;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+
+import javax.el.ValueExpression;
+import javax.faces.component.UIInput;
+import javax.faces.context.FacesContext;
+
+/**
+ * JSF component class
+ *
+ */
+public abstract class UIFileUpload extends UIInput
+{
+
+ private String localContentType;
+
+ private String localFileName;
+
+ private Integer localFileSize;
+
+ private InputStream localInputStream;
+
+ @Override
+ public void processUpdates(FacesContext context)
+ {
+ ValueExpression dataBinding = getValueExpression("data");
+ if (dataBinding != null)
+ {
+ Class clazz = dataBinding.getType(context.getELContext());
+ if (clazz.isAssignableFrom(InputStream.class))
+ {
+ dataBinding.setValue(context.getELContext(), getLocalInputStream());
+ }
+ else if (clazz.isAssignableFrom(byte[].class))
+ {
+ byte[] bytes = null;
+ if (getLocalInputStream() != null)
+ {
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ try
+ {
+ byte[] buffer = new byte[512];
+ int read = getLocalInputStream().read(buffer);
+ while (read != -1)
+ {
+ bos.write(buffer, 0, read);
+ read = getLocalInputStream().read(buffer);
+ }
+ bytes = bos.toByteArray();
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+ dataBinding.setValue(context.getELContext(), bytes);
+ }
+
+ if (getLocalContentType() != null)
+ {
+ ValueExpression valueExpression =
getValueExpression("contentType");
+ if (valueExpression != null)
+ {
+ valueExpression.setValue(context.getELContext(), getLocalContentType());
+ }
+ }
+
+ if (getLocalFileName() != null)
+ {
+ ValueExpression valueExpression = getValueExpression("fileName");
+ if (valueExpression != null)
+ {
+ valueExpression.setValue(context.getELContext(), getLocalFileName());
+ }
+ }
+
+ if (getLocalFileSize() != null)
+ {
+ ValueExpression valueExpression = getValueExpression("fileSize");
+ if (valueExpression != null)
+ {
+ valueExpression.setValue(context.getELContext(), getLocalFileSize());
+ }
+ }
+ }
+ }
+
+ public String getLocalContentType()
+ {
+ return localContentType;
+ }
+
+ public void setLocalContentType(String localContentType)
+ {
+ this.localContentType = localContentType;
+ }
+
+ public String getLocalFileName()
+ {
+ return localFileName;
+ }
+
+ public void setLocalFileName(String localFileName)
+ {
+ this.localFileName = localFileName;
+ }
+
+ public Integer getLocalFileSize()
+ {
+ return localFileSize;
+ }
+
+ public void setLocalFileSize(Integer localFileSize)
+ {
+ this.localFileSize = localFileSize;
+ }
+
+ public InputStream getLocalInputStream()
+ {
+ return localInputStream;
+ }
+
+ public void setLocalInputStream(InputStream localInputStream)
+ {
+ this.localInputStream = localInputStream;
+ }
+
+ public abstract void setAccept(String accept);
+
+ public abstract String getAccept();
+
+ public abstract String getStyleClass();
+
+ public abstract String getStyle();
+
+ public abstract void setStyleClass(String styleClass);
+
+ public abstract void setStyle(String style);
+
+}
Added:
trunk/sandbox/ui/fileUpload/src/main/java/org/richfaces/org/jboss/seam/ui/renderkit/FileUploadRendererBase.java
===================================================================
---
trunk/sandbox/ui/fileUpload/src/main/java/org/richfaces/org/jboss/seam/ui/renderkit/FileUploadRendererBase.java
(rev 0)
+++
trunk/sandbox/ui/fileUpload/src/main/java/org/richfaces/org/jboss/seam/ui/renderkit/FileUploadRendererBase.java 2007-11-29
19:03:15 UTC (rev 4362)
@@ -0,0 +1,131 @@
+package org.richfaces.org.jboss.seam.ui.renderkit;
+
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+import javax.faces.component.UIComponent;
+import javax.faces.context.FacesContext;
+import javax.faces.context.ResponseWriter;
+import javax.servlet.ServletRequest;
+
+import org.ajax4jsf.renderkit.RendererBase;
+import org.ajax4jsf.renderkit.RendererUtils.HTML;
+import org.richfaces.org.jboss.seam.ui.component.UIFileUpload;
+import org.richfaces.org.jboss.seam.web.MultipartRequest;
+
+public class FileUploadRendererBase extends RendererBase
+{
+
+ @Override
+ protected Class getComponentClass()
+ {
+ return UIFileUpload.class;
+ }
+
+ @Override
+ protected void doEncodeEnd(ResponseWriter writer, FacesContext context, UIComponent
component) throws IOException
+ {
+ UIFileUpload fileUpload = (UIFileUpload) component;
+
+ writer.startElement(HTML.INPUT_ELEM, fileUpload);
+ writer.writeAttribute(HTML.TYPE_ATTR, "file", null);
+
+ String clientId = fileUpload.getClientId(context);
+ writer.writeAttribute(HTML.id_ATTRIBUTE, clientId, null);
+ writer.writeAttribute(HTML.NAME_ATTRIBUTE, clientId, null);
+
+
+ /*if (fileUpload.getAccept() != null)
+ {
+ writer.writeAttribute(HTML.ACCEPT_ATTR, fileUpload.getAccept(),
"accept");
+ }
+
+ if (fileUpload.getStyleClass() != null)
+ {
+ writer.writeAttribute(HTML.CLASS_ATTR, fileUpload.getStyleClass(),
JSF.STYLE_CLASS_ATTR);
+ }
+
+ if (fileUpload.getStyle() != null)
+ {
+ writer.writeAttribute(HTML.STYLE_ATTR, fileUpload.getStyle(),
"style");
+ }*/
+ getUtils().encodePassThruWithExclusions(context, component, HTML.DISABLED_ATTR);
+ writer.endElement(HTML.INPUT_ELEM);
+ }
+
+ @Override
+ protected void doDecode(FacesContext context, UIComponent component)
+ {
+ UIFileUpload fileUpload = (UIFileUpload) component;
+ ServletRequest request = (ServletRequest)
context.getExternalContext().getRequest();
+
+ if (!(request instanceof MultipartRequest))
+ {
+ request = unwrapMultipartRequest(request);
+ }
+
+ if (request instanceof MultipartRequest)
+ {
+ MultipartRequest multipartRequest = (MultipartRequest) request;
+
+ String clientId = component.getClientId(context);
+ fileUpload.setLocalInputStream(multipartRequest.getFileInputStream(clientId));
+ fileUpload.setLocalContentType(multipartRequest.getFileContentType(clientId));
+ fileUpload.setLocalFileName(multipartRequest.getFileName(clientId));
+ fileUpload.setLocalFileSize(multipartRequest.getFileSize(clientId));
+ }
+ }
+
+ /**
+ * Finds an instance of MultipartRequest wrapped within a request or its
+ * (recursively) wrapped requests.
+ */
+ private static ServletRequest unwrapMultipartRequest(ServletRequest request)
+ {
+ while (!(request instanceof MultipartRequest))
+ {
+ boolean found = false;
+
+ for (Method m : request.getClass().getMethods())
+ {
+ if (ServletRequest.class.isAssignableFrom(m.getReturnType())
+ && m.getParameterTypes().length == 0)
+ {
+ try
+ {
+ request = (ServletRequest) m.invoke(request);
+ found = true;
+ break;
+ }
+ catch (Exception ex)
+ { /* Ignore, try the next one */
+ }
+ }
+ }
+
+ if (!found)
+ {
+ for (Field f : request.getClass().getDeclaredFields())
+ {
+ if (ServletRequest.class.isAssignableFrom(f.getType()))
+ {
+ try
+ {
+ request = (ServletRequest) f.get(request);
+ }
+ catch (Exception ex)
+ { /* Ignore */
+ }
+ }
+ }
+ }
+
+ if (!found) break;
+ }
+
+ return request;
+ }
+
+}
Added:
trunk/sandbox/ui/fileUpload/src/main/java/org/richfaces/org/jboss/seam/web/FileUploadException.java
===================================================================
---
trunk/sandbox/ui/fileUpload/src/main/java/org/richfaces/org/jboss/seam/web/FileUploadException.java
(rev 0)
+++
trunk/sandbox/ui/fileUpload/src/main/java/org/richfaces/org/jboss/seam/web/FileUploadException.java 2007-11-29
19:03:15 UTC (rev 4362)
@@ -0,0 +1,24 @@
+package org.richfaces.org.jboss.seam.web;
+
+/**
+ * Thrown when an exception occurs while uploading a file.
+ *
+ * @author Shane Bryzak
+ */
+public class FileUploadException extends RuntimeException
+{
+ public FileUploadException()
+ {
+ this(null, null);
+ }
+
+ public FileUploadException(String message)
+ {
+ this(message, null);
+ }
+
+ public FileUploadException(String message, Throwable cause)
+ {
+ super(message, cause);
+ }
+}
Added:
trunk/sandbox/ui/fileUpload/src/main/java/org/richfaces/org/jboss/seam/web/MultipartFilter.java
===================================================================
---
trunk/sandbox/ui/fileUpload/src/main/java/org/richfaces/org/jboss/seam/web/MultipartFilter.java
(rev 0)
+++
trunk/sandbox/ui/fileUpload/src/main/java/org/richfaces/org/jboss/seam/web/MultipartFilter.java 2007-11-29
19:03:15 UTC (rev 4362)
@@ -0,0 +1,112 @@
+package org.richfaces.org.jboss.seam.web;
+
+import java.io.IOException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * A filter for decoding multipart requests, for
+ * use with the file upload control.
+ *
+ * @author Shane Bryzak
+ *
+ */
+//@Scope(APPLICATION)
+//(a)Name("org.jboss.seam.web.multipartFilter")
+//@Install(precedence = BUILT_IN)
+//@BypassInterceptors
+//(a)Filter(within={"org.jboss.seam.web.ajax4jsfFilter",
"org.jboss.seam.web.exceptionFilter"})
+public class MultipartFilter implements Filter
+{
+ public static final String MULTIPART = "multipart/";
+
+ /**
+ * Flag indicating whether a temporary file should be used to cache the uploaded file
+ */
+ private boolean createTempFiles = false;
+
+ /**
+ * The maximum size of a file upload request. 0 means no limit.
+ */
+ private int maxRequestSize = 0;
+
+ public boolean getCreateTempFiles()
+ {
+ return createTempFiles;
+ }
+
+ public void setCreateTempFiles(boolean createTempFiles)
+ {
+ this.createTempFiles = createTempFiles;
+ }
+
+ public int getMaxRequestSize()
+ {
+ return maxRequestSize;
+ }
+
+ public void setMaxRequestSize(int maxFileSize)
+ {
+ this.maxRequestSize = maxFileSize;
+ }
+
+ public void doFilter(ServletRequest request, ServletResponse response, FilterChain
chain)
+ throws IOException, ServletException
+ {
+ if (!(response instanceof HttpServletResponse))
+ {
+ chain.doFilter(request, response);
+ return;
+ }
+
+ HttpServletRequest httpRequest = (HttpServletRequest) request;
+
+ if (isMultipartRequest(httpRequest))
+ {
+ chain.doFilter(new MultipartRequest(httpRequest, createTempFiles,
+ maxRequestSize), response);
+ }
+ else
+ {
+ chain.doFilter(request, response);
+ }
+ }
+
+ private boolean isMultipartRequest(HttpServletRequest request)
+ {
+ if (!"post".equals(request.getMethod().toLowerCase()))
+ {
+ return false;
+ }
+
+ String contentType = request.getContentType();
+ if (contentType == null)
+ {
+ return false;
+ }
+
+ if (contentType.toLowerCase().startsWith(MULTIPART))
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+public void destroy() {
+ // TODO Auto-generated method stub
+
+}
+
+public void init(FilterConfig filterConfig) throws ServletException {
+ // TODO Auto-generated method stub
+
+}
+}
Added:
trunk/sandbox/ui/fileUpload/src/main/java/org/richfaces/org/jboss/seam/web/MultipartRequest.java
===================================================================
---
trunk/sandbox/ui/fileUpload/src/main/java/org/richfaces/org/jboss/seam/web/MultipartRequest.java
(rev 0)
+++
trunk/sandbox/ui/fileUpload/src/main/java/org/richfaces/org/jboss/seam/web/MultipartRequest.java 2007-11-29
19:03:15 UTC (rev 4362)
@@ -0,0 +1,633 @@
+package org.richfaces.org.jboss.seam.web;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.rmi.server.UID;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+
+/**
+ * Request wrapper for supporting multipart requests, used for file uploading.
+ *
+ * @author Shane Bryzak
+ */
+public class MultipartRequest extends HttpServletRequestWrapper
+{
+ private static final String PARAM_NAME = "name";
+ private static final String PARAM_FILENAME = "filename";
+ private static final String PARAM_CONTENT_TYPE = "Content-Type";
+
+ private static final int BUFFER_SIZE = 2048;
+ private static final int CHUNK_SIZE = 512;
+
+ private boolean createTempFiles;
+
+ private String encoding = null;
+
+ private Map<String,Param> parameters = null;
+
+ private enum ReadState { BOUNDARY, HEADERS, DATA }
+
+ private static final byte CR = 0x0d;
+ private static final byte LF = 0x0a;
+ private static final byte[] CR_LF = {CR,LF};
+
+ private abstract class Param
+ {
+ private String name;
+
+ public Param(String name)
+ {
+ this.name = name;
+ }
+
+ public String getName()
+ {
+ return name;
+ }
+
+ public abstract void appendData(byte[] data, int start, int length)
+ throws IOException;
+ }
+
+ private class ValueParam extends Param
+ {
+ private Object value = null;
+ private ByteArrayOutputStream buf = new ByteArrayOutputStream();
+
+ public ValueParam(String name)
+ {
+ super(name);
+ }
+
+ @Override
+ public void appendData(byte[] data, int start, int length)
+ throws IOException
+ {
+ buf.write(data, start, length);
+ }
+
+ public void complete()
+ throws UnsupportedEncodingException
+ {
+ String val = encoding == null ? new String(buf.toByteArray()) :
+ new String(buf.toByteArray(), encoding);
+ if (value == null)
+ {
+ value = val;
+ }
+ else
+ {
+ if (!(value instanceof List))
+ {
+ List<String> v = new ArrayList<String>();
+ v.add((String) value);
+ value = v;
+ }
+
+ ((List) value).add(val);
+ }
+ buf.reset();
+ }
+
+ public Object getValue()
+ {
+ return value;
+ }
+ }
+
+ private class FileParam extends Param
+ {
+ private String filename;
+ private String contentType;
+ private int fileSize;
+
+ private ByteArrayOutputStream bOut = null;
+ private FileOutputStream fOut = null;
+ private File tempFile = null;
+
+ public FileParam(String name)
+ {
+ super(name);
+ }
+
+ public String getFilename()
+ {
+ return filename;
+ }
+
+ public void setFilename(String filename)
+ {
+ this.filename = filename;
+ }
+
+ public String getContentType()
+ {
+ return contentType;
+ }
+
+ public void setContentType(String contentType)
+ {
+ this.contentType = contentType;
+ }
+
+ public int getFileSize()
+ {
+ return fileSize;
+ }
+
+ public void createTempFile()
+ {
+ try
+ {
+ tempFile = File.createTempFile(new UID().toString().replace(":",
"-"), ".upload");
+ tempFile.deleteOnExit();
+ fOut = new FileOutputStream(tempFile);
+ }
+ catch (IOException ex)
+ {
+ throw new FileUploadException("Could not create temporary file");
+ }
+ }
+
+ @Override
+ public void appendData(byte[] data, int start, int length)
+ throws IOException
+ {
+ if (fOut != null)
+ {
+ fOut.write(data, start, length);
+ fOut.flush();
+ }
+ else
+ {
+ if (bOut == null) bOut = new ByteArrayOutputStream();
+ bOut.write(data, start, length);
+ }
+
+ fileSize += length;
+ }
+
+ public byte[] getData()
+ {
+ if (fOut != null)
+ {
+ try
+ {
+ fOut.close();
+ }
+ catch (IOException ex) {}
+ fOut = null;
+ }
+
+ if (bOut != null)
+ {
+ return bOut.toByteArray();
+ }
+ else if (tempFile != null)
+ {
+ if (tempFile.exists())
+ {
+ try
+ {
+ FileInputStream fIn = new FileInputStream(tempFile);
+ ByteArrayOutputStream bOut = new ByteArrayOutputStream();
+ byte[] buf = new byte[512];
+ int read = fIn.read(buf);
+ while (read != -1)
+ {
+ bOut.write(buf, 0, read);
+ read = fIn.read(buf);
+ }
+ bOut.flush();
+
+ fIn.close();
+ tempFile.delete();
+ return bOut.toByteArray();
+ }
+ catch (IOException ex) { /* too bad? */}
+ }
+ }
+
+ return null;
+ }
+
+ public InputStream getInputStream()
+ {
+ if (fOut != null)
+ {
+ try
+ {
+ fOut.close();
+ }
+ catch (IOException ex) {}
+ fOut = null;
+ }
+
+ if (bOut!=null)
+ {
+ return new ByteArrayInputStream(bOut.toByteArray());
+ }
+ else if (tempFile!=null)
+ {
+ try
+ {
+ return new FileInputStream(tempFile) {
+ @Override
+ public void close() throws IOException
+ {
+ super.close();
+ tempFile.delete();
+ }
+ };
+ }
+ catch (FileNotFoundException ex) { }
+ }
+
+ return null;
+ }
+ }
+
+ private HttpServletRequest request;
+
+ public MultipartRequest(HttpServletRequest request, boolean createTempFiles,
+ int maxRequestSize)
+ {
+ super(request);
+ this.request = request;
+ this.createTempFiles = createTempFiles;
+
+ String contentLength = request.getHeader("Content-Length");
+ if (contentLength != null && maxRequestSize > 0 &&
+ Integer.parseInt(contentLength) > maxRequestSize)
+ {
+ throw new FileUploadException("Multipart request is larger than allowed
size");
+ }
+ }
+
+ private void parseRequest()
+ {
+ byte[] boundaryMarker = getBoundaryMarker(request.getContentType());
+ if (boundaryMarker == null)
+ {
+ throw new FileUploadException("The request was rejected because "
+ + "no multipart boundary was found");
+ }
+
+ encoding = request.getCharacterEncoding();
+
+ parameters = new HashMap<String,Param>();
+
+ try
+ {
+ byte[] buffer = new byte[BUFFER_SIZE];
+ Map<String,String> headers = new HashMap<String,String>();
+
+ ReadState readState = ReadState.BOUNDARY;
+
+ InputStream input = request.getInputStream();
+ int read = input.read(buffer);
+ int pos = 0;
+
+ Param p = null;
+
+ while (read != -1)
+ {
+ for (int i = 0; i < read; i++)
+ {
+ switch (readState)
+ {
+ case BOUNDARY:
+ {
+ if (checkSequence(buffer, i, boundaryMarker) &&
checkSequence(buffer, i + 2, CR_LF))
+ {
+ readState = ReadState.HEADERS;
+ i += 2;
+ pos = i + 1;
+ }
+ break;
+ }
+ case HEADERS:
+ {
+ if (checkSequence(buffer, i, CR_LF))
+ {
+ String param = (encoding == null) ?
+ new String(buffer, pos, i - pos - 1) :
+ new String(buffer, pos, i - pos - 1, encoding);
+ parseParams(param, ";", headers);
+
+ if (checkSequence(buffer, i + CR_LF.length, CR_LF))
+ {
+ readState = ReadState.DATA;
+ i += CR_LF.length;
+ pos = i + 1;
+
+ String paramName = headers.get(PARAM_NAME);
+ if (paramName != null)
+ {
+ if (headers.containsKey(PARAM_FILENAME))
+ {
+ FileParam fp = new FileParam(paramName);
+ if (createTempFiles) fp.createTempFile();
+ fp.setContentType(headers.get(PARAM_CONTENT_TYPE));
+ fp.setFilename(headers.get(PARAM_FILENAME));
+ p = fp;
+ }
+ else
+ {
+ if (parameters.containsKey(paramName))
+ {
+ p = parameters.get(paramName);
+ }
+ else
+ {
+ p = new ValueParam(paramName);
+ }
+ }
+
+ if (!parameters.containsKey(paramName))
+ {
+ parameters.put(paramName, p);
+ }
+ }
+
+ headers.clear();
+ }
+ else
+ {
+ pos = i + 1;
+ }
+ }
+ break;
+ }
+ case DATA:
+ {
+ // If we've encountered another boundary...
+ if (checkSequence(buffer, i - boundaryMarker.length - CR_LF.length,
CR_LF) &&
+ checkSequence(buffer, i, boundaryMarker))
+ {
+ // Write any data before the boundary (that hasn't already
been written) to the param
+ if (pos < i - boundaryMarker.length - CR_LF.length - 1)
+ {
+ p.appendData(buffer, pos, i - pos - boundaryMarker.length -
CR_LF.length - 1);
+ }
+
+ if (p instanceof ValueParam) ((ValueParam) p).complete();
+
+ if (checkSequence(buffer, i + CR_LF.length, CR_LF))
+ {
+ i += CR_LF.length;
+ pos = i + 1;
+ }
+ else
+ {
+ pos = i;
+ }
+
+ readState = ReadState.HEADERS;
+ }
+ // Otherwise write whatever data we have to the param
+ else if (i > (pos + boundaryMarker.length + CHUNK_SIZE +
CR_LF.length))
+ {
+ p.appendData(buffer, pos, CHUNK_SIZE);
+ pos += CHUNK_SIZE;
+ }
+ break;
+ }
+ }
+ }
+
+ if (pos < read)
+ {
+ // move the bytes that weren't read to the start of the buffer
+ int bytesNotRead = read - pos;
+ System.arraycopy(buffer, pos, buffer, 0, bytesNotRead);
+ read = input.read(buffer, bytesNotRead, buffer.length - bytesNotRead);
+ read += bytesNotRead;
+ }
+ else
+ {
+ read = input.read(buffer);
+ }
+
+ pos = 0;
+ }
+ }
+ catch (IOException ex)
+ {
+ throw new FileUploadException("IO Error parsing multipart request",
ex);
+ }
+ }
+
+ private byte[] getBoundaryMarker(String contentType)
+ {
+ Map<String, Object> params = parseParams(contentType, ";");
+ String boundaryStr = (String) params.get("boundary");
+
+ if (boundaryStr == null) return null;
+
+ try
+ {
+ return boundaryStr.getBytes("ISO-8859-1");
+ }
+ catch (UnsupportedEncodingException e)
+ {
+ return boundaryStr.getBytes();
+ }
+ }
+
+ /**
+ * Checks if a specified sequence of bytes ends at a specific position
+ * within a byte array.
+ *
+ * @param data
+ * @param pos
+ * @param seq
+ * @return boolean indicating if the sequence was found at the specified position
+ */
+ private boolean checkSequence(byte[] data, int pos, byte[] seq)
+ {
+ if (pos - seq.length < -1 || pos >= data.length)
+ return false;
+
+ for (int i = 0; i < seq.length; i++)
+ {
+ if (data[(pos - seq.length) + i + 1] != seq[i])
+ return false;
+ }
+
+ return true;
+ }
+
+ private static final Pattern PARAM_VALUE_PATTERN = Pattern
+ .compile("^\\s*([^\\s=]+)\\s*[=:]\\s*(.+)\\s*$");
+
+ private Map parseParams(String paramStr, String separator)
+ {
+ Map<String,String> paramMap = new HashMap<String, String>();
+ parseParams(paramStr, separator, paramMap);
+ return paramMap;
+ }
+
+ private void parseParams(String paramStr, String separator, Map paramMap)
+ {
+ String[] parts = paramStr.split("[" + separator + "]");
+
+ for (String part : parts)
+ {
+ Matcher m = PARAM_VALUE_PATTERN.matcher(part);
+ if (m.matches())
+ {
+ String key = m.group(1);
+ String value = m.group(2);
+
+ // Strip double quotes
+ if (value.startsWith("\"") &&
value.endsWith("\""))
+ value = value.substring(1, value.length() - 1);
+
+ paramMap.put(key, value);
+ }
+ }
+ }
+
+ private Param getParam(String name)
+ {
+ if (parameters == null)
+ parseRequest();
+ return parameters.get(name);
+ }
+
+ @Override
+ public Enumeration getParameterNames()
+ {
+ if (parameters == null)
+ parseRequest();
+
+ return Collections.enumeration(parameters.keySet());
+ }
+
+ public byte[] getFileBytes(String name)
+ {
+ Param p = getParam(name);
+ return (p != null && p instanceof FileParam) ?
+ ((FileParam) p).getData() : null;
+ }
+
+ public InputStream getFileInputStream(String name)
+ {
+ Param p = getParam(name);
+ return (p != null && p instanceof FileParam) ?
+ ((FileParam) p).getInputStream() : null;
+ }
+
+ public String getFileContentType(String name)
+ {
+ Param p = getParam(name);
+ return (p != null && p instanceof FileParam) ?
+ ((FileParam) p).getContentType() : null;
+ }
+
+ public String getFileName(String name)
+ {
+ Param p = getParam(name);
+ return (p != null && p instanceof FileParam) ?
+ ((FileParam) p).getFilename() : null;
+ }
+
+ public int getFileSize(String name)
+ {
+ Param p = getParam(name);
+ return (p != null && p instanceof FileParam) ?
+ ((FileParam) p).getFileSize() : -1;
+ }
+
+ @Override
+ public String getParameter(String name)
+ {
+ Param p = getParam(name);
+ if (p != null && p instanceof ValueParam)
+ {
+ ValueParam vp = (ValueParam) p;
+ if (vp.getValue() instanceof String) return (String) vp.getValue();
+ }
+ else if (p != null && p instanceof FileParam)
+ {
+ return "---BINARY DATA---";
+ }
+ else
+ {
+ return super.getParameter(name);
+ }
+
+ return null;
+ }
+
+ @Override
+ public String[] getParameterValues(String name)
+ {
+ Param p = getParam(name);
+ if (p != null && p instanceof ValueParam)
+ {
+ ValueParam vp = (ValueParam) p;
+ if (vp.getValue() instanceof List)
+ {
+ List vals = (List) vp.getValue();
+ String[] values = new String[vals.size()];
+ vals.toArray(values);
+ return values;
+ }
+ else
+ {
+ return new String[] {(String) vp.getValue()};
+ }
+ }
+ else
+ {
+ return super.getParameterValues(name);
+ }
+ }
+
+ @Override
+ public Map getParameterMap()
+ {
+ if (parameters == null)
+ parseRequest();
+
+ Map<String,Object> params = new
HashMap<String,Object>(super.getParameterMap());
+
+ for (String name : parameters.keySet())
+ {
+ Param p = parameters.get(name);
+ if (p instanceof ValueParam)
+ {
+ ValueParam vp = (ValueParam) p;
+ if (vp.getValue() instanceof String)
+ {
+ params.put(name, vp.getValue());
+ }
+ else if (vp.getValue() instanceof List)
+ {
+ params.put(name, getParameterValues(name));
+ }
+ }
+ }
+
+ return params;
+ }
+}