You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@wicket.apache.org by mg...@apache.org on 2010/12/05 14:08:00 UTC
svn commit: r1042345 [2/3] - in
/wicket/trunk/wicket-util/src/main/java/org/apache/wicket/util: io/ upload/
Modified: wicket/trunk/wicket-util/src/main/java/org/apache/wicket/util/upload/FileUploadBase.java
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket-util/src/main/java/org/apache/wicket/util/upload/FileUploadBase.java?rev=1042345&r1=1042344&r2=1042345&view=diff
==============================================================================
--- wicket/trunk/wicket-util/src/main/java/org/apache/wicket/util/upload/FileUploadBase.java (original)
+++ wicket/trunk/wicket-util/src/main/java/org/apache/wicket/util/upload/FileUploadBase.java Sun Dec 5 13:08:00 2010
@@ -18,13 +18,18 @@ package org.apache.wicket.util.upload;
import java.io.IOException;
import java.io.InputStream;
-import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.NoSuchElementException;
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.wicket.util.io.Streams;
+import org.apache.wicket.util.upload.MultipartFormInputStream.ItemInputStream;
/**
* <p>
@@ -33,7 +38,10 @@ import java.util.Map;
*
* <p>
* This class handles multiple files per single HTML widget, sent using <code>multipart/mixed</code>
- * encoding type, as specified by <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>.
+ * encoding type, as specified by <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>. Use
+ * {@link #parseRequest(HttpServletRequest)} to acquire a list of
+ * {@link org.apache.wicket.util.upload.FileItem}s associated with a given HTML widget.
+ * </p>
*
* <p>
* How the data for individual parts is stored is determined by the factory used to create them; a
@@ -83,6 +91,24 @@ public abstract class FileUploadBase
return false;
}
+
+ /**
+ * Utility method that determines whether the request contains multipart content.
+ *
+ * @param req
+ * The servlet request to be evaluated. Must be non-null.
+ *
+ * @return <code>true</code> if the request is multipart; <code>false</code> otherwise.
+ *
+ * @deprecated Use the method on <code>ServletFileUpload</code> instead.
+ */
+ @Deprecated
+ public static boolean isMultipartContent(HttpServletRequest req)
+ {
+ return ServletFileUpload.isMultipartContent(req);
+ }
+
+
// ----------------------------------------------------- Manifest constants
@@ -97,6 +123,11 @@ public abstract class FileUploadBase
*/
public static final String CONTENT_DISPOSITION = "Content-disposition";
+ /**
+ * HTTP content length header name.
+ */
+ public static final String CONTENT_LENGTH = "Content-length";
+
/**
* Content-disposition value for form data.
@@ -130,7 +161,12 @@ public abstract class FileUploadBase
/**
* The maximum length of a single header line that will be parsed (1024 bytes).
+ *
+ * @deprecated This constant is no longer used. As of commons-fileupload 1.2, the only
+ * applicable limit is the total size of a parts headers,
+ * {@link MultipartStream#HEADER_PART_SIZE_MAX}.
*/
+ @Deprecated
public static final int MAX_HEADER_SIZE = 1024;
@@ -138,16 +174,26 @@ public abstract class FileUploadBase
/**
- * The maximum size permitted for an uploaded file. A value of -1 indicates no maximum.
+ * The maximum size permitted for the complete request, as opposed to {@link #fileSizeMax}. A
+ * value of -1 indicates no maximum.
*/
private long sizeMax = -1;
+ /**
+ * The maximum size permitted for a single uploaded file, as opposed to {@link #sizeMax}. A
+ * value of -1 indicates no maximum.
+ */
+ private long fileSizeMax = -1;
/**
* The content encoding to use when reading part headers.
*/
private String headerEncoding;
+ /**
+ * The progress listener.
+ */
+ private ProgressListener listener;
// ----------------------------------------------------- Property accessors
@@ -170,9 +216,11 @@ public abstract class FileUploadBase
/**
- * Returns the maximum allowed upload size.
+ * Returns the maximum allowed size of a complete request, as opposed to
+ * {@link #getFileSizeMax()}.
*
- * @return The maximum allowed size, in bytes.
+ * @return The maximum allowed size, in bytes. The default value of -1 indicates, that there is
+ * no limit.
*
* @see #setSizeMax(long)
*
@@ -184,10 +232,12 @@ public abstract class FileUploadBase
/**
- * Sets the maximum allowed upload size. If negative, there is no maximum.
+ * Sets the maximum allowed size of a complete request, as opposed to
+ * {@link #setFileSizeMax(long)}.
*
* @param sizeMax
- * The maximum allowed size, in bytes, or -1 for no maximum.
+ * The maximum allowed size, in bytes. The default value of -1 indicates, that there
+ * is no limit.
*
* @see #getSizeMax()
*
@@ -197,10 +247,34 @@ public abstract class FileUploadBase
this.sizeMax = sizeMax;
}
+ /**
+ * Returns the maximum allowed size of a single uploaded file, as opposed to
+ * {@link #getSizeMax()}.
+ *
+ * @see #setFileSizeMax(long)
+ * @return Maximum size of a single uploaded file.
+ */
+ public long getFileSizeMax()
+ {
+ return fileSizeMax;
+ }
+
+ /**
+ * Sets the maximum allowed size of a single uploaded file, as opposed to {@link #getSizeMax()}.
+ *
+ * @see #getFileSizeMax()
+ * @param fileSizeMax
+ * Maximum size of a single uploaded file.
+ */
+ public void setFileSizeMax(long fileSizeMax)
+ {
+ this.fileSizeMax = fileSizeMax;
+ }
/**
* Retrieves the character encoding used when reading the headers of an individual part. When
- * not specified, or <code>null</code>, the platform default encoding is used.
+ * not specified, or <code>null</code>, the request encoding is used. If that is also not
+ * specified, or <code>null</code>, the platform default encoding is used.
*
* @return The encoding used to read part headers.
*/
@@ -211,8 +285,9 @@ public abstract class FileUploadBase
/**
- * Specifies the character encoding to be used when reading the headers of individual parts.
- * When not specified, or <code>null</code>, the platform default encoding is used.
+ * Specifies the character encoding to be used when reading the headers of individual part. When
+ * not specified, or <code>null</code>, the request encoding is used. If that is also not
+ * specified, or <code>null</code>, the platform default encoding is used.
*
* @param encoding
* The encoding used to read part headers.
@@ -225,6 +300,50 @@ public abstract class FileUploadBase
// --------------------------------------------------------- Public methods
+
+ /**
+ * Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a> compliant
+ * <code>multipart/form-data</code> stream.
+ *
+ * @param req
+ * The servlet request to be parsed.
+ *
+ * @return A list of <code>FileItem</code> instances parsed from the request, in the order that
+ * they were transmitted.
+ *
+ * @throws FileUploadException
+ * if there are problems reading/parsing the request or storing files.
+ *
+ * @deprecated Use the method in <code>ServletFileUpload</code> instead.
+ */
+ @Deprecated
+ public List<FileItem> parseRequest(HttpServletRequest req) throws FileUploadException
+ {
+ return parseRequest(new ServletRequestContext(req));
+ }
+
+ /**
+ * Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a> compliant
+ * <code>multipart/form-data</code> stream.
+ *
+ * @param ctx
+ * The context for the request to be parsed.
+ *
+ * @return An iterator to instances of <code>FileItemStream</code> parsed from the request, in
+ * the order that they were transmitted.
+ *
+ * @throws FileUploadException
+ * if there are problems reading/parsing the request or storing files.
+ * @throws IOException
+ * An I/O error occurred. This may be a network error while communicating with the
+ * client or a problem while storing the uploaded content.
+ */
+ public FileItemIterator getItemIterator(RequestContext ctx) throws FileUploadException,
+ IOException
+ {
+ return new FileItemIteratorImpl(ctx);
+ }
+
/**
* Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a> compliant
* <code>multipart/form-data</code> stream.
@@ -235,132 +354,55 @@ public abstract class FileUploadBase
* @return A list of <code>FileItem</code> instances parsed from the request, in the order that
* they were transmitted.
*
- * @exception FileUploadException
- * if there are problems reading/parsing the request or storing files.
+ * @throws FileUploadException
+ * if there are problems reading/parsing the request or storing files.
*/
public List<FileItem> parseRequest(final RequestContext ctx) throws FileUploadException
{
- if (ctx == null)
- {
- throw new IllegalArgumentException("ctx parameter cannot be null");
- }
-
- List<FileItem> items = new ArrayList<FileItem>();
- String contentType = ctx.getContentType();
-
- if ((null == contentType) || (!contentType.toLowerCase().startsWith(MULTIPART)))
- {
- throw new InvalidContentTypeException("the request doesn't contain a " +
- MULTIPART_FORM_DATA + " or " + MULTIPART_MIXED +
- " stream, content type header is " + contentType);
- }
- int requestSize = ctx.getContentLength();
-
- if ((requestSize == -1) && (getSizeMax() != Long.MAX_VALUE))
- {
- throw new UnknownSizeException("the request was rejected because its size is unknown");
- }
-
- if ((sizeMax >= 0) && (requestSize > sizeMax))
- {
- throw new SizeLimitExceededException("the request was rejected because "
- + "its size exceeds allowed range");
- }
-
try
{
- byte[] boundary = getBoundary(contentType);
- if (boundary == null)
+ FileItemIterator iter = getItemIterator(ctx);
+ List<FileItem> items = new ArrayList<FileItem>();
+ FileItemFactory fac = getFileItemFactory();
+ if (fac == null)
{
- throw new FileUploadException("the request was rejected because "
- + "no multipart boundary was found");
+ throw new NullPointerException("No FileItemFactory has been set.");
}
-
- InputStream input = ctx.getInputStream();
-
- MultipartFormInputStream multi = new MultipartFormInputStream(input, boundary);
- multi.setHeaderEncoding(headerEncoding);
-
- boolean nextPart = multi.skipPreamble();
-
- // Don't allow a header larger than this size (to prevent DOS
- // attacks)
- final int maxHeaderBytes = 65536;
- while (nextPart)
- {
- Map<String, String> headers = parseHeaders(multi.readHeaders(maxHeaderBytes));
- String fieldName = getFieldName(headers);
- if (fieldName != null)
- {
- String subContentType = getHeader(headers, CONTENT_TYPE);
- if ((subContentType != null) &&
- subContentType.toLowerCase().startsWith(MULTIPART_MIXED))
- {
- // Multiple files.
- byte[] subBoundary = getBoundary(subContentType);
- multi.setBoundary(subBoundary);
- boolean nextSubPart = multi.skipPreamble();
- while (nextSubPart)
- {
- headers = parseHeaders(multi.readHeaders(maxHeaderBytes));
- if (getFileName(headers) != null)
- {
- FileItem item = createItem(headers, false);
- items.add(item);
- OutputStream os = item.getOutputStream();
- try
- {
- multi.readBodyData(os);
- }
- finally
- {
- os.close();
- }
- }
- else
- {
- // Ignore anything but files inside
- // multipart/mixed.
- multi.discardBodyData();
- }
- nextSubPart = multi.readBoundary();
- }
- multi.setBoundary(boundary);
- }
- else
- {
- FileItem item = createItem(headers, getFileName(headers) == null);
- items.add(item);
- OutputStream os = item.getOutputStream();
- try
- {
- multi.readBodyData(os);
- }
- finally
- {
- os.close();
- }
- }
+ while (iter.hasNext())
+ {
+ FileItemStream item = iter.next();
+ FileItem fileItem = fac.createItem(item.getFieldName(), item.getContentType(),
+ item.isFormField(), item.getName());
+ try
+ {
+ Streams.copyAndClose(item.openStream(), fileItem.getOutputStream());
}
- else
+ catch (FileUploadIOException e)
+ {
+ throw (FileUploadException)e.getCause();
+ }
+ catch (IOException e)
+ {
+ throw new IOFileUploadException("Processing of " + MULTIPART_FORM_DATA +
+ " request failed. " + e.getMessage(), e);
+ }
+ if (fileItem instanceof FileItemHeadersSupport)
{
- // Skip this part.
- multi.discardBodyData();
+ final FileItemHeaders fih = item.getHeaders();
+ ((FileItemHeadersSupport)fileItem).setHeaders(fih);
}
- nextPart = multi.readBoundary();
+ items.add(fileItem);
}
+ return items;
+ }
+ catch (FileUploadIOException e)
+ {
+ throw (FileUploadException)e.getCause();
}
catch (IOException e)
{
- for (FileItem item : items)
- {
- item.delete();
- }
- throw new FileUploadException("Processing of " + MULTIPART_FORM_DATA +
- " request failed. " + e.getMessage(), e);
+ throw new FileUploadException(e.getMessage(), e);
}
-
- return items;
}
@@ -380,8 +422,8 @@ public abstract class FileUploadBase
ParameterParser parser = new ParameterParser();
parser.setLowerCaseNames(true);
// Parameter parser can handle null input
- Map<String, String> params = parser.parse(contentType, ';');
- String boundaryStr = params.get("boundary");
+ Map params = parser.parse(contentType, new char[] { ';', ',' });
+ String boundaryStr = (String)params.get("boundary");
if (boundaryStr == null)
{
@@ -407,39 +449,61 @@ public abstract class FileUploadBase
* A <code>Map</code> containing the HTTP request headers.
*
* @return The file name for the current <code>encapsulation</code>.
+ * @deprecated Use {@link #getFileName(FileItemHeaders)}.
*/
+ @Deprecated
protected String getFileName(final Map<String, String> headers)
{
+ return getFileName(getHeader(headers, CONTENT_DISPOSITION));
+ }
+
+ /**
+ * Retrieves the file name from the <code>Content-disposition</code> header.
+ *
+ * @param headers
+ * The HTTP headers object.
+ *
+ * @return The file name for the current <code>encapsulation</code>.
+ */
+ protected String getFileName(FileItemHeaders headers)
+ {
+ return getFileName(headers.getHeader(CONTENT_DISPOSITION));
+ }
+
+ /**
+ * Returns the given content-disposition headers file name.
+ *
+ * @param pContentDisposition
+ * The content-disposition headers value.
+ * @return The file name
+ */
+ private String getFileName(String pContentDisposition)
+ {
String fileName = null;
- String cd = getHeader(headers, CONTENT_DISPOSITION);
- if (cd.startsWith(FORM_DATA) || cd.startsWith(ATTACHMENT))
+ if (pContentDisposition != null)
{
- ParameterParser parser = new ParameterParser();
- parser.setLowerCaseNames(true);
- // Parameter parser can handle null input
- Map<String, String> params = parser.parse(cd, ';');
- if (params.containsKey("filename"))
+ String cdl = pContentDisposition.toLowerCase();
+ if (cdl.startsWith(FORM_DATA) || cdl.startsWith(ATTACHMENT))
{
- fileName = params.get("filename");
- if (fileName != null)
+ ParameterParser parser = new ParameterParser();
+ parser.setLowerCaseNames(true);
+ // Parameter parser can handle null input
+ Map params = parser.parse(pContentDisposition, ';');
+ if (params.containsKey("filename"))
{
- fileName = fileName.trim();
- int index = fileName.lastIndexOf('\\');
- if (index == -1)
+ fileName = (String)params.get("filename");
+ if (fileName != null)
{
- index = fileName.lastIndexOf('/');
+ fileName = fileName.trim();
}
- if (index != -1)
+ else
{
- fileName = fileName.substring(index + 1);
+ // Even if there is no value, the parameter is present,
+ // so we return an empty file name rather than no file
+ // name.
+ fileName = "";
}
}
- else
- {
- // Even if there is no value, the parameter is present, so
- // we return an empty file name rather than no file name.
- fileName = "";
- }
}
}
return fileName;
@@ -454,18 +518,28 @@ public abstract class FileUploadBase
*
* @return The field name for the current <code>encapsulation</code>.
*/
- protected String getFieldName(final Map<String, String> headers)
+ protected String getFieldName(final FileItemHeaders headers)
+ {
+ return getFieldName(headers.getHeader(CONTENT_DISPOSITION));
+ }
+
+ /**
+ * Returns the field name, which is given by the content-disposition header.
+ *
+ * @param pContentDisposition
+ * The content-dispositions header value.
+ * @return The field jake
+ */
+ private String getFieldName(String pContentDisposition)
{
String fieldName = null;
- String cd = getHeader(headers, CONTENT_DISPOSITION);
- if ((cd != null) && cd.startsWith(FORM_DATA))
+ if (pContentDisposition != null && pContentDisposition.toLowerCase().startsWith(FORM_DATA))
{
-
ParameterParser parser = new ParameterParser();
parser.setLowerCaseNames(true);
// Parameter parser can handle null input
- Map<String, String> params = parser.parse(cd, ';');
- fieldName = params.get("name");
+ Map params = parser.parse(pContentDisposition, ';');
+ fieldName = (String)params.get("name");
if (fieldName != null)
{
fieldName = fieldName.trim();
@@ -474,6 +548,21 @@ public abstract class FileUploadBase
return fieldName;
}
+ /**
+ * Retrieves the field name from the <code>Content-disposition</code> header.
+ *
+ * @param headers
+ * A <code>Map</code> containing the HTTP request headers.
+ *
+ * @return The field name for the current <code>encapsulation</code>.
+ * @deprecated Use {@link #getFieldName(FileItemHeaders)}.
+ */
+ @Deprecated
+ protected String getFieldName(Map<String, String> headers)
+ {
+ return getFieldName(getHeader(headers, CONTENT_DISPOSITION));
+ }
+
/**
* Creates a new {@link FileItem} instance.
@@ -484,14 +573,20 @@ public abstract class FileUploadBase
* Whether or not this item is a form field, as opposed to a file.
*
* @return A newly created <code>FileItem</code> instance.
+ *
+ * @throws FileUploadException
+ * if an error occurs.
+ * @deprecated This method is no longer used in favour of internally created instances of
+ * {@link FileItem}.
*/
+ @Deprecated
protected FileItem createItem(final Map<String, String> headers, final boolean isFormField)
+ throws FileUploadException
{
return getFileItemFactory().createItem(getFieldName(headers),
getHeader(headers, CONTENT_TYPE), isFormField, getFileName(headers));
}
-
/**
* <p>
* Parses the <code>header-part</code> and returns as key/value pairs.
@@ -505,60 +600,137 @@ public abstract class FileUploadBase
*
* @return A <code>Map</code> containing the parsed HTTP request headers.
*/
- protected Map<String, String> parseHeaders(final String headerPart)
+ protected FileItemHeaders getParsedHeaders(final String headerPart)
{
- Map<String, String> headers = new HashMap<String, String>();
- char[] buffer = new char[MAX_HEADER_SIZE];
- boolean done = false;
- int j = 0;
- int i;
- String header, headerName, headerValue;
- try
+ final int len = headerPart.length();
+ FileItemHeadersImpl headers = newFileItemHeaders();
+ int start = 0;
+ for (;;)
{
- while (!done)
+ int end = parseEndOfLine(headerPart, start);
+ if (start == end)
{
- i = 0;
- // Copy a single line of characters into the buffer,
- // omitting trailing CRLF.
- while ((i < 2) || (buffer[i - 2] != '\r') || (buffer[i - 1] != '\n'))
- {
- buffer[i++] = headerPart.charAt(j++);
- }
- header = new String(buffer, 0, i - 2);
- if (header.equals(""))
- {
- done = true;
- }
- else
+ break;
+ }
+ String header = headerPart.substring(start, end);
+ start = end + 2;
+ while (start < len)
+ {
+ int nonWs = start;
+ while (nonWs < len)
{
- if (header.indexOf(':') == -1)
- {
- // This header line is malformed, skip it.
- continue;
- }
- headerName = header.substring(0, header.indexOf(':')).trim().toLowerCase();
- headerValue = header.substring(header.indexOf(':') + 1).trim();
- if (getHeader(headers, headerName) != null)
- {
- // More that one header of that name exists,
- // append to the list.
- headers.put(headerName, getHeader(headers, headerName) + ',' + headerValue);
- }
- else
+ char c = headerPart.charAt(nonWs);
+ if (c != ' ' && c != '\t')
{
- headers.put(headerName, headerValue);
+ break;
}
+ ++nonWs;
}
+ if (nonWs == start)
+ {
+ break;
+ }
+ // Continuation line found
+ end = parseEndOfLine(headerPart, nonWs);
+ header += " " + headerPart.substring(nonWs, end);
+ start = end + 2;
}
+ parseHeaderLine(headers, header);
}
- catch (IndexOutOfBoundsException e)
+ return headers;
+ }
+
+ /**
+ * Creates a new instance of {@link FileItemHeaders}.
+ *
+ * @return The new instance.
+ */
+ protected FileItemHeadersImpl newFileItemHeaders()
+ {
+ return new FileItemHeadersImpl();
+ }
+
+ /**
+ * <p>
+ * Parses the <code>header-part</code> and returns as key/value pairs.
+ *
+ * <p>
+ * If there are multiple headers of the same names, the name will map to a comma-separated list
+ * containing the values.
+ *
+ * @param headerPart
+ * The <code>header-part</code> of the current <code>encapsulation</code>.
+ *
+ * @return A <code>Map</code> containing the parsed HTTP request headers.
+ * @deprecated Use {@link #getParsedHeaders(String)}
+ */
+ @Deprecated
+ protected Map /* String, String */parseHeaders(String headerPart)
+ {
+ FileItemHeaders headers = getParsedHeaders(headerPart);
+ Map result = new HashMap();
+ for (Iterator iter = headers.getHeaderNames(); iter.hasNext();)
+ {
+ String headerName = (String)iter.next();
+ Iterator iter2 = headers.getHeaders(headerName);
+ String headerValue = (String)iter2.next();
+ while (iter2.hasNext())
+ {
+ headerValue += "," + iter2.next();
+ }
+ result.put(headerName, headerValue);
+ }
+ return result;
+ }
+
+ /**
+ * Skips bytes until the end of the current line.
+ *
+ * @param headerPart
+ * The headers, which are being parsed.
+ * @param end
+ * Index of the last byte, which has yet been processed.
+ * @return Index of the \r\n sequence, which indicates end of line.
+ */
+ private int parseEndOfLine(String headerPart, int end)
+ {
+ int index = end;
+ for (;;)
{
- // Headers were malformed. continue with all that was
- // parsed.
+ int offset = headerPart.indexOf('\r', index);
+ if (offset == -1 || offset + 1 >= headerPart.length())
+ {
+ throw new IllegalStateException(
+ "Expected headers to be terminated by an empty line.");
+ }
+ if (headerPart.charAt(offset + 1) == '\n')
+ {
+ return offset;
+ }
+ index = offset + 1;
}
- return headers;
}
+ /**
+ * Reads the next header line.
+ *
+ * @param headers
+ * String with all headers.
+ * @param header
+ * Map where to store the current header.
+ */
+ private void parseHeaderLine(FileItemHeadersImpl headers, String header)
+ {
+ final int colonOffset = header.indexOf(':');
+ if (colonOffset == -1)
+ {
+ // This header line is malformed, skip it.
+ return;
+ }
+ String headerName = header.substring(0, colonOffset).trim();
+ String headerValue = header.substring(header.indexOf(':') + 1).trim();
+ headers.addHeader(headerName, headerValue);
+ }
/**
* Returns the header with the specified name from the supplied map. The header lookup is
@@ -571,27 +743,506 @@ public abstract class FileUploadBase
*
* @return The value of specified header, or a comma-separated list if there were multiple
* headers of that name.
+ * @deprecated Use {@link FileItemHeaders#getHeader(String)}.
*/
+ @Deprecated
protected final String getHeader(final Map<String, String> headers, final String name)
{
return headers.get(name.toLowerCase());
}
+ /**
+ * The iterator, which is returned by {@link FileUploadBase#getItemIterator(RequestContext)}.
+ */
+ private class FileItemIteratorImpl implements FileItemIterator
+ {
+ /**
+ * Default implementation of {@link FileItemStream}.
+ */
+ private class FileItemStreamImpl implements FileItemStream
+ {
+ /**
+ * The file items content type.
+ */
+ private final String contentType;
+ /**
+ * The file items field name.
+ */
+ private final String fieldName;
+ /**
+ * The file items file name.
+ */
+ private final String name;
+ /**
+ * Whether the file item is a form field.
+ */
+ private final boolean formField;
+ /**
+ * The file items input stream.
+ */
+ private final InputStream stream;
+ /**
+ * Whether the file item was already opened.
+ */
+ private boolean opened;
+ /**
+ * The headers, if any.
+ */
+ private FileItemHeaders headers;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param pName
+ * The items file name, or null.
+ * @param pFieldName
+ * The items field name.
+ * @param pContentType
+ * The items content type, or null.
+ * @param pFormField
+ * Whether the item is a form field.
+ * @param pContentLength
+ * The items content length, if known, or -1
+ * @throws IOException
+ * Creating the file item failed.
+ */
+ FileItemStreamImpl(String pName, String pFieldName, String pContentType,
+ boolean pFormField, long pContentLength) throws IOException
+ {
+ name = pName;
+ fieldName = pFieldName;
+ contentType = pContentType;
+ formField = pFormField;
+ final ItemInputStream itemStream = multi.newInputStream();
+ InputStream istream = itemStream;
+ if (fileSizeMax != -1)
+ {
+ if (pContentLength != -1 && pContentLength > fileSizeMax)
+ {
+ FileUploadException e = new FileSizeLimitExceededException("The field " +
+ fieldName + " exceeds its maximum permitted " + " size of " +
+ fileSizeMax + " characters.", pContentLength, fileSizeMax);
+ throw new FileUploadIOException(e);
+ }
+ istream = new LimitedInputStream(istream, fileSizeMax)
+ {
+ @Override
+ protected void raiseError(long pSizeMax, long pCount) throws IOException
+ {
+ itemStream.close(true);
+ FileUploadException e = new FileSizeLimitExceededException(
+ "The field " + fieldName + " exceeds its maximum permitted " +
+ " size of " + pSizeMax + " characters.", pCount, pSizeMax);
+ throw new FileUploadIOException(e);
+ }
+ };
+ }
+ stream = istream;
+ }
+
+ /**
+ * Returns the items content type, or null.
+ *
+ * @return Content type, if known, or null.
+ */
+ public String getContentType()
+ {
+ return contentType;
+ }
+
+ /**
+ * Returns the items field name.
+ *
+ * @return Field name.
+ */
+ public String getFieldName()
+ {
+ return fieldName;
+ }
+
+ /**
+ * Returns the items file name.
+ *
+ * @return File name, if known, or null.
+ */
+ public String getName()
+ {
+ return name;
+ }
+
+ /**
+ * Returns, whether this is a form field.
+ *
+ * @return True, if the item is a form field, otherwise false.
+ */
+ public boolean isFormField()
+ {
+ return formField;
+ }
+
+ /**
+ * Returns an input stream, which may be used to read the items contents.
+ *
+ * @return Opened input stream.
+ * @throws IOException
+ * An I/O error occurred.
+ */
+ public InputStream openStream() throws IOException
+ {
+ if (opened)
+ {
+ throw new IllegalStateException("The stream was already opened.");
+ }
+ if (((Closeable)stream).isClosed())
+ {
+ throw new FileItemStream.ItemSkippedException();
+ }
+ return stream;
+ }
+
+ /**
+ * Closes the file item.
+ *
+ * @throws IOException
+ * An I/O error occurred.
+ */
+ void close() throws IOException
+ {
+ stream.close();
+ }
+
+ /**
+ * Returns the file item headers.
+ *
+ * @return The items header object
+ */
+ public FileItemHeaders getHeaders()
+ {
+ return headers;
+ }
+
+ /**
+ * Sets the file item headers.
+ *
+ * @param pHeaders
+ * The items header object
+ */
+ public void setHeaders(FileItemHeaders pHeaders)
+ {
+ headers = pHeaders;
+ }
+ }
+
+ /**
+ * The multi part stream to process.
+ */
+ private final MultipartFormInputStream multi;
+ /**
+ * The notifier, which used for triggering the {@link ProgressListener}.
+ */
+ private final MultipartFormInputStream.ProgressNotifier notifier;
+ /**
+ * The boundary, which separates the various parts.
+ */
+ private final byte[] boundary;
+ /**
+ * The item, which we currently process.
+ */
+ private FileItemStreamImpl currentItem;
+ /**
+ * The current items field name.
+ */
+ private String currentFieldName;
+ /**
+ * Whether we are currently skipping the preamble.
+ */
+ private boolean skipPreamble;
+ /**
+ * Whether the current item may still be read.
+ */
+ private boolean itemValid;
+ /**
+ * Whether we have seen the end of the file.
+ */
+ private boolean eof;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param ctx
+ * The request context.
+ * @throws FileUploadException
+ * An error occurred while parsing the request.
+ * @throws IOException
+ * An I/O error occurred.
+ */
+ FileItemIteratorImpl(RequestContext ctx) throws FileUploadException, IOException
+ {
+ if (ctx == null)
+ {
+ throw new NullPointerException("ctx parameter");
+ }
+
+ String contentType = ctx.getContentType();
+ if ((null == contentType) || (!contentType.toLowerCase().startsWith(MULTIPART)))
+ {
+ throw new InvalidContentTypeException("the request doesn't contain a " +
+ MULTIPART_FORM_DATA + " or " + MULTIPART_MIXED +
+ " stream, content type header is " + contentType);
+ }
+
+ InputStream input = ctx.getInputStream();
+
+ if (sizeMax >= 0)
+ {
+ int requestSize = ctx.getContentLength();
+ if (requestSize == -1)
+ {
+ input = new LimitedInputStream(input, sizeMax)
+ {
+ @Override
+ protected void raiseError(long pSizeMax, long pCount) throws IOException
+ {
+ FileUploadException ex = new SizeLimitExceededException(
+ "the request was rejected because" + " its size (" + pCount +
+ ") exceeds the configured maximum" + " (" + pSizeMax + ")",
+ pCount, pSizeMax);
+ throw new FileUploadIOException(ex);
+ }
+ };
+ }
+ else
+ {
+ if (sizeMax >= 0 && requestSize > sizeMax)
+ {
+ throw new SizeLimitExceededException(
+ "the request was rejected because its size (" + requestSize +
+ ") exceeds the configured maximum (" + sizeMax + ")", requestSize,
+ sizeMax);
+ }
+ }
+ }
+
+ String charEncoding = headerEncoding;
+ if (charEncoding == null)
+ {
+ charEncoding = ctx.getCharacterEncoding();
+ }
+
+ boundary = getBoundary(contentType);
+ if (boundary == null)
+ {
+ throw new FileUploadException("the request was rejected because "
+ + "no multipart boundary was found");
+ }
+
+ notifier = new MultipartFormInputStream.ProgressNotifier(listener,
+ ctx.getContentLength());
+ multi = new MultipartFormInputStream(input, boundary, notifier);
+ multi.setHeaderEncoding(charEncoding);
+
+ skipPreamble = true;
+ findNextItem();
+ }
+
+ /**
+ * Called for finding the nex item, if any.
+ *
+ * @return True, if an next item was found, otherwise false.
+ * @throws IOException
+ * An I/O error occurred.
+ */
+ private boolean findNextItem() throws IOException
+ {
+ if (eof)
+ {
+ return false;
+ }
+ if (currentItem != null)
+ {
+ currentItem.close();
+ currentItem = null;
+ }
+ for (;;)
+ {
+ boolean nextPart;
+ if (skipPreamble)
+ {
+ nextPart = multi.skipPreamble();
+ }
+ else
+ {
+ nextPart = multi.readBoundary();
+ }
+ if (!nextPart)
+ {
+ if (currentFieldName == null)
+ {
+ // Outer multipart terminated -> No more data
+ eof = true;
+ return false;
+ }
+ // Inner multipart terminated -> Return to parsing the outer
+ multi.setBoundary(boundary);
+ currentFieldName = null;
+ continue;
+ }
+ FileItemHeaders headers = getParsedHeaders(multi.readHeaders());
+ if (currentFieldName == null)
+ {
+ // We're parsing the outer multipart
+ String fieldName = getFieldName(headers);
+ if (fieldName != null)
+ {
+ String subContentType = headers.getHeader(CONTENT_TYPE);
+ if (subContentType != null &&
+ subContentType.toLowerCase().startsWith(MULTIPART_MIXED))
+ {
+ currentFieldName = fieldName;
+ // Multiple files associated with this field name
+ byte[] subBoundary = getBoundary(subContentType);
+ multi.setBoundary(subBoundary);
+ skipPreamble = true;
+ continue;
+ }
+ String fileName = getFileName(headers);
+ currentItem = new FileItemStreamImpl(fileName, fieldName,
+ headers.getHeader(CONTENT_TYPE), fileName == null,
+ getContentLength(headers));
+ notifier.noteItem();
+ itemValid = true;
+ return true;
+ }
+ }
+ else
+ {
+ String fileName = getFileName(headers);
+ if (fileName != null)
+ {
+ currentItem = new FileItemStreamImpl(fileName, currentFieldName,
+ headers.getHeader(CONTENT_TYPE), false, getContentLength(headers));
+ notifier.noteItem();
+ itemValid = true;
+ return true;
+ }
+ }
+ multi.discardBodyData();
+ }
+ }
+
+ private long getContentLength(FileItemHeaders pHeaders)
+ {
+ try
+ {
+ return Long.parseLong(pHeaders.getHeader(CONTENT_LENGTH));
+ }
+ catch (Exception e)
+ {
+ return -1;
+ }
+ }
+
+ /**
+ * Returns, whether another instance of {@link FileItemStream} is available.
+ *
+ * @throws FileUploadException
+ * Parsing or processing the file item failed.
+ * @throws IOException
+ * Reading the file item failed.
+ * @return True, if one or more additional file items are available, otherwise false.
+ */
+ public boolean hasNext() throws FileUploadException, IOException
+ {
+ if (eof)
+ {
+ return false;
+ }
+ if (itemValid)
+ {
+ return true;
+ }
+ return findNextItem();
+ }
+
+ /**
+ * Returns the next available {@link FileItemStream}.
+ *
+ * @throws java.util.NoSuchElementException
+ * No more items are available. Use {@link #hasNext()} to prevent this
+ * exception.
+ * @throws FileUploadException
+ * Parsing or processing the file item failed.
+ * @throws IOException
+ * Reading the file item failed.
+ * @return FileItemStream instance, which provides access to the next file item.
+ */
+ public FileItemStream next() throws FileUploadException, IOException
+ {
+ if (eof || (!itemValid && !hasNext()))
+ {
+ throw new NoSuchElementException();
+ }
+ itemValid = false;
+ return currentItem;
+ }
+ }
+
+ /**
+ * This exception is thrown for hiding an inner {@link FileUploadException} in an
+ * {@link IOException}.
+ */
+ public static class FileUploadIOException extends IOException
+ {
+ /**
+ * The exceptions UID, for serializing an instance.
+ */
+ private static final long serialVersionUID = -7047616958165584154L;
+ /**
+ * The exceptions cause; we overwrite the parent classes field, which is available since
+ * Java 1.4 only.
+ */
+ private final FileUploadException cause;
+
+ /**
+ * Creates a <code>FileUploadIOException</code> with the given cause.
+ *
+ * @param pCause
+ * The exceptions cause, if any, or null.
+ */
+ public FileUploadIOException(FileUploadException pCause)
+ {
+ // We're not doing super(pCause) cause of 1.3 compatibility.
+ cause = pCause;
+ }
+
+ /**
+ * Returns the exceptions cause.
+ *
+ * @return The exceptions cause, if any, or null.
+ */
+ @Override
+ public Throwable getCause()
+ {
+ return cause;
+ }
+ }
/**
* Thrown to indicate that the request is not a multipart request.
*/
public static class InvalidContentTypeException extends FileUploadException
{
-
- private static final long serialVersionUID = 1L;
+ /**
+ * The exceptions UID, for serializing an instance.
+ */
+ private static final long serialVersionUID = -9073026332015646668L;
/**
* Constructs a <code>InvalidContentTypeException</code> with no detail message.
*/
public InvalidContentTypeException()
{
- super();
+ // Nothing to do.
}
/**
@@ -606,14 +1257,114 @@ public abstract class FileUploadBase
}
}
+ /**
+ * Thrown to indicate an IOException.
+ */
+ public static class IOFileUploadException extends FileUploadException
+ {
+ /**
+ * The exceptions UID, for serializing an instance.
+ */
+ private static final long serialVersionUID = 1749796615868477269L;
+ /**
+ * The exceptions cause; we overwrite the parent classes field, which is available since
+ * Java 1.4 only.
+ */
+ private final IOException cause;
+
+ /**
+ * Creates a new instance with the given cause.
+ *
+ * @param pMsg
+ * The detail message.
+ * @param pException
+ * The exceptions cause.
+ */
+ public IOFileUploadException(String pMsg, IOException pException)
+ {
+ super(pMsg);
+ cause = pException;
+ }
+
+ /**
+ * Returns the exceptions cause.
+ *
+ * @return The exceptions cause, if any, or null.
+ */
+ @Override
+ public Throwable getCause()
+ {
+ return cause;
+ }
+ }
/**
- * Thrown to indicate that the request size is not specified.
+ * This exception is thrown, if a requests permitted size is exceeded.
*/
- public static class UnknownSizeException extends FileUploadException
+ protected abstract static class SizeException extends FileUploadException
{
+ /**
+ * The actual size of the request.
+ */
+ private final long actual;
+
+ /**
+ * The maximum permitted size of the request.
+ */
+ private final long permitted;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param message
+ * The detail message.
+ * @param actual
+ * The actual number of bytes in the request.
+ * @param permitted
+ * The requests size limit, in bytes.
+ */
+ protected SizeException(String message, long actual, long permitted)
+ {
+ super(message);
+ this.actual = actual;
+ this.permitted = permitted;
+ }
+
+ /**
+ * Retrieves the actual size of the request.
+ *
+ * @return The actual size of the request.
+ */
+ public long getActualSize()
+ {
+ return actual;
+ }
+
+ /**
+ * Retrieves the permitted size of the request.
+ *
+ * @return The permitted size of the request.
+ */
+ public long getPermittedSize()
+ {
+ return permitted;
+ }
+ }
- private static final long serialVersionUID = 1L;
+ /**
+ * Thrown to indicate that the request size is not specified. In other words, it is thrown, if
+ * the content-length header is missing or contains the value -1.
+ *
+ * @deprecated As of commons-fileupload 1.2, the presence of a content-length header is no
+ * longer required.
+ */
+ @Deprecated
+ public static class UnknownSizeException extends FileUploadException
+ {
+ /**
+ * The exceptions UID, for serializing an instance.
+ */
+ private static final long serialVersionUID = 7062279004812015273L;
/**
* Constructs a <code>UnknownSizeException</code> with no detail message.
@@ -635,33 +1386,98 @@ public abstract class FileUploadBase
}
}
-
/**
* Thrown to indicate that the request size exceeds the configured maximum.
*/
- public static class SizeLimitExceededException extends FileUploadException
+ public static class SizeLimitExceededException extends SizeException
{
-
- private static final long serialVersionUID = 1L;
+ /**
+ * The exceptions UID, for serializing an instance.
+ */
+ private static final long serialVersionUID = -2474893167098052828L;
/**
- * Constructs a <code>SizeExceededException</code> with no detail message.
+ * @deprecated Replaced by {@link #SizeLimitExceededException(String, long, long)}
*/
+ @Deprecated
public SizeLimitExceededException()
{
- super();
+ this(null, 0, 0);
+ }
+
+ /**
+ * @deprecated Replaced by {@link #SizeLimitExceededException(String, long, long)}
+ * @param message
+ * The exceptions detail message.
+ */
+ @Deprecated
+ public SizeLimitExceededException(String message)
+ {
+ this(message, 0, 0);
}
/**
- * Constructs an <code>SizeExceededException</code> with the specified detail message.
+ * Constructs a <code>SizeExceededException</code> with the specified detail message, and
+ * actual and permitted sizes.
*
* @param message
* The detail message.
+ * @param actual
+ * The actual request size.
+ * @param permitted
+ * The maximum permitted request size.
*/
- public SizeLimitExceededException(final String message)
+ public SizeLimitExceededException(String message, long actual, long permitted)
{
- super(message);
+ super(message, actual, permitted);
+ }
+ }
+
+ /**
+ * Thrown to indicate that A files size exceeds the configured maximum.
+ */
+ public static class FileSizeLimitExceededException extends SizeException
+ {
+ /**
+ * The exceptions UID, for serializing an instance.
+ */
+ private static final long serialVersionUID = 8150776562029630058L;
+
+ /**
+ * Constructs a <code>SizeExceededException</code> with the specified detail message, and
+ * actual and permitted sizes.
+ *
+ * @param message
+ * The detail message.
+ * @param actual
+ * The actual request size.
+ * @param permitted
+ * The maximum permitted request size.
+ */
+ public FileSizeLimitExceededException(String message, long actual, long permitted)
+ {
+ super(message, actual, permitted);
}
}
+ /**
+ * Returns the progress listener.
+ *
+ * @return The progress listener, if any, or null.
+ */
+ public ProgressListener getProgressListener()
+ {
+ return listener;
+ }
+
+ /**
+ * Sets the progress listener.
+ *
+ * @param pListener
+ * The progress listener, if any. Defaults to null.
+ */
+ public void setProgressListener(ProgressListener pListener)
+ {
+ listener = pListener;
+ }
}
Modified: wicket/trunk/wicket-util/src/main/java/org/apache/wicket/util/upload/FileUploadException.java
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket-util/src/main/java/org/apache/wicket/util/upload/FileUploadException.java?rev=1042345&r1=1042344&r2=1042345&view=diff
==============================================================================
--- wicket/trunk/wicket-util/src/main/java/org/apache/wicket/util/upload/FileUploadException.java (original)
+++ wicket/trunk/wicket-util/src/main/java/org/apache/wicket/util/upload/FileUploadException.java Sun Dec 5 13:08:00 2010
@@ -16,22 +16,33 @@
*/
package org.apache.wicket.util.upload;
-import java.io.IOException;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+
/**
* Exception for errors encountered while processing the request.
*
* @author <a href="mailto:jmcnally@collab.net">John McNally</a>
*/
-public class FileUploadException extends IOException
+public class FileUploadException extends Exception
{
- private static final long serialVersionUID = 1L;
+ /**
+ * Serial version UID, being used, if the exception is serialized.
+ */
+ private static final long serialVersionUID = 8881893724388807504L;
+ /**
+ * The exceptions cause. We overwrite the cause of the super class, which isn't available in
+ * Java 1.3.
+ */
+ private final Throwable cause;
/**
* Constructs a new <code>FileUploadException</code> without message.
*/
public FileUploadException()
{
+ this(null, null);
}
/**
@@ -42,32 +53,60 @@ public class FileUploadException extends
*/
public FileUploadException(final String msg)
{
- super(msg);
+ this(msg, null);
}
/**
- * Constructs a new <code>FileUploadException</code> with specified cause.
+ * Creates a new <code>FileUploadException</code> with the given detail message and cause.
*
+ * @param msg
+ * The exceptions detail message.
* @param cause
- * the cause.
+ * The exceptions cause.
*/
- public FileUploadException(final Throwable cause)
+ public FileUploadException(String msg, Throwable cause)
{
- super();
- initCause(cause);
+ super(msg);
+ this.cause = cause;
}
/**
- * Constructs a new <code>FileUploadException</code> with specified detail message and cause
+ * Prints this throwable and its backtrace to the specified print stream.
*
- * @param message
- * the error message.
- * @param cause
- * the cause.
+ * @param stream
+ * <code>PrintStream</code> to use for output
*/
- public FileUploadException(final String message, final Throwable cause)
+ @Override
+ public void printStackTrace(PrintStream stream)
+ {
+ super.printStackTrace(stream);
+ if (cause != null)
+ {
+ stream.println("Caused by:");
+ cause.printStackTrace(stream);
+ }
+}
+
+ /**
+ * Prints this throwable and its backtrace to the specified print writer.
+ *
+ * @param writer
+ * <code>PrintWriter</code> to use for output
+ */
+ @Override
+ public void printStackTrace(PrintWriter writer)
+ {
+ super.printStackTrace(writer);
+ if (cause != null)
+ {
+ writer.println("Caused by:");
+ cause.printStackTrace(writer);
+ }
+ }
+
+ @Override
+ public Throwable getCause()
{
- super(message);
- initCause(cause);
+ return cause;
}
}
Added: wicket/trunk/wicket-util/src/main/java/org/apache/wicket/util/upload/LimitedInputStream.java
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket-util/src/main/java/org/apache/wicket/util/upload/LimitedInputStream.java?rev=1042345&view=auto
==============================================================================
--- wicket/trunk/wicket-util/src/main/java/org/apache/wicket/util/upload/LimitedInputStream.java (added)
+++ wicket/trunk/wicket-util/src/main/java/org/apache/wicket/util/upload/LimitedInputStream.java Sun Dec 5 13:08:00 2010
@@ -0,0 +1,172 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.wicket.util.upload;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+
+/**
+ * An input stream, which limits its data size. This stream is used, if the content length is
+ * unknown.
+ */
+public abstract class LimitedInputStream extends FilterInputStream implements Closeable
+{
+ /**
+ * The maximum size of an item, in bytes.
+ */
+ private final long sizeMax;
+ /**
+ * The current number of bytes.
+ */
+ private long count;
+ /**
+ * Whether this stream is already closed.
+ */
+ private boolean closed;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param pIn
+ * The input stream, which shall be limited.
+ * @param pSizeMax
+ * The limit; no more than this number of bytes shall be returned by the source
+ * stream.
+ */
+ public LimitedInputStream(InputStream pIn, long pSizeMax)
+ {
+ super(pIn);
+ sizeMax = pSizeMax;
+ }
+
+ /**
+ * Called to indicate, that the input streams limit has been exceeded.
+ *
+ * @param pSizeMax
+ * The input streams limit, in bytes.
+ * @param pCount
+ * The actual number of bytes.
+ * @throws IOException
+ * The called method is expected to raise an IOException.
+ */
+ protected abstract void raiseError(long pSizeMax, long pCount) throws IOException;
+
+ /**
+ * Called to check, whether the input streams limit is reached.
+ *
+ * @throws IOException
+ * The given limit is exceeded.
+ */
+ private void checkLimit() throws IOException
+ {
+ if (count > sizeMax)
+ {
+ raiseError(sizeMax, count);
+ }
+ }
+
+ /**
+ * Reads the next byte of data from this input stream. The value byte is returned as an
+ * <code>int</code> in the range <code>0</code> to <code>255</code>. If no byte is available
+ * because the end of the stream has been reached, the value <code>-1</code> is returned. This
+ * method blocks until input data is available, the end of the stream is detected, or an
+ * exception is thrown.
+ * <p>
+ * This method simply performs <code>in.read()</code> and returns the result.
+ *
+ * @return the next byte of data, or <code>-1</code> if the end of the stream is reached.
+ * @exception IOException
+ * if an I/O error occurs.
+ * @see java.io.FilterInputStream#in
+ */
+ @Override
+ public int read() throws IOException
+ {
+ int res = super.read();
+ if (res != -1)
+ {
+ count++;
+ checkLimit();
+ }
+ return res;
+ }
+
+ /**
+ * Reads up to <code>len</code> bytes of data from this input stream into an array of bytes. If
+ * <code>len</code> is not zero, the method blocks until some input is available; otherwise, no
+ * bytes are read and <code>0</code> is returned.
+ * <p>
+ * This method simply performs <code>in.read(b, off, len)</code> and returns the result.
+ *
+ * @param b
+ * the buffer into which the data is read.
+ * @param off
+ * The start offset in the destination array <code>b</code>.
+ * @param len
+ * the maximum number of bytes read.
+ * @return the total number of bytes read into the buffer, or <code>-1</code> if there is no
+ * more data because the end of the stream has been reached.
+ * @exception NullPointerException
+ * If <code>b</code> is <code>null</code>.
+ * @exception IndexOutOfBoundsException
+ * If <code>off</code> is negative, <code>len</code> is negative, or
+ * <code>len</code> is greater than <code>b.length - off</code>
+ * @exception IOException
+ * if an I/O error occurs.
+ * @see java.io.FilterInputStream#in
+ */
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException
+ {
+ int res = super.read(b, off, len);
+ if (res > 0)
+ {
+ count += res;
+ checkLimit();
+ }
+ return res;
+ }
+
+ /**
+ * Returns, whether this stream is already closed.
+ *
+ * @return True, if the stream is closed, otherwise false.
+ * @throws IOException
+ * An I/O error occurred.
+ */
+ public boolean isClosed() throws IOException
+ {
+ return closed;
+ }
+
+ /**
+ * Closes this input stream and releases any system resources associated with the stream. This
+ * method simply performs <code>in.close()</code>.
+ *
+ * @exception IOException
+ * if an I/O error occurs.
+ * @see java.io.FilterInputStream#in
+ */
+ @Override
+ public void close() throws IOException
+ {
+ closed = true;
+ super.close();
+ }
+}
Propchange: wicket/trunk/wicket-util/src/main/java/org/apache/wicket/util/upload/LimitedInputStream.java
------------------------------------------------------------------------------
svn:eol-style = native
Modified: wicket/trunk/wicket-util/src/main/java/org/apache/wicket/util/upload/MultipartFormInputStream.java
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket-util/src/main/java/org/apache/wicket/util/upload/MultipartFormInputStream.java?rev=1042345&r1=1042344&r2=1042345&view=diff
==============================================================================
--- wicket/trunk/wicket-util/src/main/java/org/apache/wicket/util/upload/MultipartFormInputStream.java (original)
+++ wicket/trunk/wicket-util/src/main/java/org/apache/wicket/util/upload/MultipartFormInputStream.java Sun Dec 5 13:08:00 2010
@@ -22,6 +22,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
+import org.apache.wicket.util.io.Streams;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -41,7 +42,7 @@ import org.slf4j.LoggerFactory;
* multipart-body := preamble 1*encapsulation close-delimiter epilogue<br>
* encapsulation := delimiter body CRLF<br>
* delimiter := "--" boundary CRLF<br>
- * close-delimiter := "--" boundary "--"<br>
+ * close-delimiter := "--" boudary "--"<br>
* preamble := <ignore><br>
* epilogue := <ignore><br>
* body := header-part CRLF body-part<br>
@@ -53,8 +54,8 @@ import org.slf4j.LoggerFactory;
* </code>
*
* <p>
- * Note that body-data can contain another multipart entity. There is limited support for single
- * pass processing of such nested streams. The nested stream is <strong>required</strong> to have a
+ * Note that body-data can contain another mulipart entity. There is limited support for single pass
+ * processing of such nested streams. The nested stream is <strong>required</strong> to have a
* boundary token of the same length as the parent stream (see {@link #setBoundary(byte[])}).
*
* <p>
@@ -64,7 +65,7 @@ import org.slf4j.LoggerFactory;
* try {
* MultipartStream multipartStream = new MultipartStream(input,
* boundary);
- * boolean nextPart = malitPartStream.skipPreamble();
+ * boolean nextPart = multipartStream.skipPreamble();
* OutputStream output;
* while(nextPart) {
* header = chunks.readHeader();
@@ -78,96 +79,189 @@ import org.slf4j.LoggerFactory;
* } catch(IOException) {
* // a read or write error occurred
* }
+ *
* </pre>
*
* @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
* @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
* @author Sean C. Sullivan
*
- * @version $Id$
*/
public class MultipartFormInputStream
{
/** Log. */
private static final Logger log = LoggerFactory.getLogger(MultipartFormInputStream.class);
+ /**
+ * Internal class, which is used to invoke the {@link ProgressListener}.
+ */
+ static class ProgressNotifier
+ {
+ /**
+ * The listener to invoke.
+ */
+ private final ProgressListener listener;
+ /**
+ * Number of expected bytes, if known, or -1.
+ */
+ private final long contentLength;
+ /**
+ * Number of bytes, which have been read so far.
+ */
+ private long bytesRead;
+ /**
+ * Number of items, which have been read so far.
+ */
+ private int items;
+
+ /**
+ * Creates a new instance with the given listener and content length.
+ *
+ * @param pListener
+ * The listener to invoke.
+ * @param pContentLength
+ * The expected content length.
+ */
+ ProgressNotifier(ProgressListener pListener, long pContentLength)
+ {
+ listener = pListener;
+ contentLength = pContentLength;
+ }
+
+ /**
+ * Called to indicate that bytes have been read.
+ *
+ * @param pBytes
+ * Number of bytes, which have been read.
+ */
+ void noteBytesRead(int pBytes)
+ {
+ /*
+ * Indicates, that the given number of bytes have been read from the input stream.
+ */
+ bytesRead += pBytes;
+ notifyListener();
+ }
+
+ /**
+ * Called to indicate, that a new file item has been detected.
+ */
+ void noteItem()
+ {
+ ++items;
+ }
+
+ /**
+ * Called for notifying the listener.
+ */
+ private void notifyListener()
+ {
+ if (listener != null)
+ {
+ listener.update(bytesRead, contentLength, items);
+ }
+ }
+ }
+
// ----------------------------------------------------- Manifest constants
+
/**
* The Carriage Return ASCII character value.
*/
public static final byte CR = 0x0D;
+
/**
* The Line Feed ASCII character value.
*/
public static final byte LF = 0x0A;
+
/**
* The dash (-) ASCII character value.
*/
public static final byte DASH = 0x2D;
+
/**
* The maximum length of <code>header-part</code> that will be processed (10 kilobytes = 10240
* bytes.).
*/
public static final int HEADER_PART_SIZE_MAX = 10240;
+
/**
* The default length of the buffer used for processing a request.
*/
protected static final int DEFAULT_BUFSIZE = 4096;
+
/**
* A byte sequence that marks the end of <code>header-part</code> (<code>CRLFCRLF</code>).
*/
protected static final byte[] HEADER_SEPARATOR = { CR, LF, CR, LF };
+
/**
* A byte sequence that that follows a delimiter that will be followed by an encapsulation (
* <code>CRLF</code>).
*/
protected static final byte[] FIELD_SEPARATOR = { CR, LF };
+
/**
* A byte sequence that that follows a delimiter of the last encapsulation in the stream (
* <code>--</code>).
*/
protected static final byte[] STREAM_TERMINATOR = { DASH, DASH };
+
+ /**
+ * A byte sequence that precedes a boundary (<code>CRLF--</code>).
+ */
+ protected static final byte[] BOUNDARY_PREFIX = { CR, LF, DASH, DASH };
+
+
// ----------------------------------------------------------- Data members
+
/**
* The input stream from which data is read.
*/
- private InputStream input;
+ private final InputStream input;
+
/**
* The length of the boundary token plus the leading <code>CRLF--</code>.
*/
private int boundaryLength;
+
/**
* The amount of data, in bytes, that must be kept in the buffer in order to detect delimiters
* reliably.
*/
- private int keepRegion;
+ private final int keepRegion;
+
/**
* The byte sequence that partitions the stream.
*/
- private byte[] boundary;
+ private final byte[] boundary;
+
/**
* The length of the buffer used for processing the request.
*/
- private int bufSize;
+ private final int bufSize;
+
/**
* The buffer used for processing the request.
*/
- private byte[] buffer;
+ private final byte[] buffer;
+
/**
* The index of first valid character in the buffer. <br>
@@ -175,33 +269,44 @@ public class MultipartFormInputStream
*/
private int head;
+
/**
- * The index of last valid character in the buffer + 1. <br>
+ * The index of last valid characer in the buffer + 1. <br>
* 0 <= tail <= bufSize
*/
private int tail;
+
/**
* The content encoding to use when reading headers.
*/
private String headerEncoding;
+
+ /**
+ * The progress notifier, if any, or null.
+ */
+ private final ProgressNotifier notifier;
+
// ----------------------------------------------------------- Constructors
/**
- * Default constructor.
- *
- * @see #MultipartFormInputStream(InputStream, byte[], int)
- * @see #MultipartFormInputStream(InputStream, byte[])
+ * Creates a new instance.
*
+ * @deprecated Use
+ * {@link #MultipartStream(InputStream, byte[], org.apache.wicket.util.uploadMultipartStream.ProgressNotifier)}
+ * , or
+ * {@link #MultipartStream(InputStream, byte[], int, org.apache.wicket.util.uploadMultipartStream.ProgressNotifier)}
*/
+ @Deprecated
public MultipartFormInputStream()
{
+ this(null, null, null);
}
/**
* <p>
- * Constructs a <code>MultipartStream</code> with a custom size buffer.
+ * Constructs a <code>MultipartStream</code> with a custom size buffer and no progress notifier.
*
* <p>
* Note that the buffer must be at least big enough to contain the boundary string, plus 4
@@ -215,28 +320,54 @@ public class MultipartFormInputStream
* @param bufSize
* The size of the buffer to be used, in bytes.
*
+ * @see #MultipartFormInputStream(InputStream, byte[],
+ * MultipartFormInputStream.ProgressNotifier)
+ * @deprecated Use
+ * {@link #MultipartStream(InputStream, byte[], int, org.apache.wicket.util.uploadMultipartStream.ProgressNotifier)}
+ * .
+ */
+ @Deprecated
+ public MultipartFormInputStream(InputStream input, byte[] boundary, int bufSize)
+ {
+ this(input, boundary, bufSize, null);
+ }
+
+ /**
+ * <p>
+ * Constructs a <code>MultipartStream</code> with a custom size buffer.
+ *
+ * <p>
+ * Note that the buffer must be at least big enough to contain the boundary string, plus 4
+ * characters for CR/LF and double dash, plus at least one byte of data. Too small a buffer size
+ * setting will degrade performance.
*
- * @see #MultipartFormInputStream()
- * @see #MultipartFormInputStream(InputStream, byte[])
+ * @param input
+ * The <code>InputStream</code> to serve as a data source.
+ * @param boundary
+ * The token used for dividing the stream into <code>encapsulations</code>.
+ * @param bufSize
+ * The size of the buffer to be used, in bytes.
+ * @param pNotifier
+ * The notifier, which is used for calling the progress listener, if any.
*
+ * @see #MultipartFormInputStream(InputStream, byte[],
+ * MultipartFormInputStream.ProgressNotifier)
*/
- public MultipartFormInputStream(final InputStream input, final byte[] boundary,
- final int bufSize)
+ MultipartFormInputStream(InputStream input, byte[] boundary, int bufSize,
+ ProgressNotifier pNotifier)
{
this.input = input;
this.bufSize = bufSize;
buffer = new byte[bufSize];
+ notifier = pNotifier;
- // We prepend CR/LF to the boundary to chop trailing CR/LF from
+ // We prepend CR/LF to the boundary to chop trailng CR/LF from
// body-data tokens.
- this.boundary = new byte[boundary.length + 4];
- boundaryLength = boundary.length + 4;
- keepRegion = boundary.length + 3;
- this.boundary[0] = CR;
- this.boundary[1] = LF;
- this.boundary[2] = DASH;
- this.boundary[3] = DASH;
- System.arraycopy(boundary, 0, this.boundary, 4, boundary.length);
+ this.boundary = new byte[boundary.length + BOUNDARY_PREFIX.length];
+ boundaryLength = boundary.length + BOUNDARY_PREFIX.length;
+ keepRegion = this.boundary.length;
+ System.arraycopy(BOUNDARY_PREFIX, 0, this.boundary, 0, BOUNDARY_PREFIX.length);
+ System.arraycopy(boundary, 0, this.boundary, BOUNDARY_PREFIX.length, boundary.length);
head = 0;
tail = 0;
@@ -251,17 +382,18 @@ public class MultipartFormInputStream
* The <code>InputStream</code> to serve as a data source.
* @param boundary
* The token used for dividing the stream into <code>encapsulations</code>.
+ * @param pNotifier
+ * An object for calling the progress listener, if any.
*
- * @see #MultipartFormInputStream()
- * @see #MultipartFormInputStream(InputStream, byte[], int)
*
+ * @see #MultipartFormInputStream(InputStream, byte[], int,
+ * MultipartFormInputStream.ProgressNotifier)
*/
- public MultipartFormInputStream(final InputStream input, final byte[] boundary)
+ MultipartFormInputStream(InputStream input, byte[] boundary, ProgressNotifier pNotifier)
{
- this(input, boundary, DEFAULT_BUFSIZE);
+ this(input, boundary, DEFAULT_BUFSIZE, pNotifier);
}
-
// --------------------------------------------------------- Public methods
@@ -296,8 +428,8 @@ public class MultipartFormInputStream
*
* @return The next byte from the input stream.
*
- * @exception IOException
- * if there is no more data available.
+ * @throws IOException
+ * if there is no more data available.
*/
public byte readByte() throws IOException
{
@@ -312,6 +444,10 @@ public class MultipartFormInputStream
// No more data available.
throw new IOException("No more data is available");
}
+ if (notifier != null)
+ {
+ notifier.noteBytesRead(tail);
+ }
}
return buffer[head++];
}
@@ -324,8 +460,8 @@ public class MultipartFormInputStream
* @return <code>true</code> if there are more encapsulations in this stream; <code>false</code>
* otherwise.
*
- * @exception MalformedStreamException
- * if the stream ends unexpectedly or fails to follow required syntax.
+ * @throws MalformedStreamException
+ * if the stream ends unexpecetedly or fails to follow required syntax.
*/
public boolean readBoundary() throws MalformedStreamException
{
@@ -387,59 +523,59 @@ public class MultipartFormInputStream
* @param boundary
* The boundary to be used for parsing of the nested stream.
*
- * @exception IllegalBoundaryException
- * if the <code>boundary</code> has a different length than the one being
- * currently parsed.
+ * @throws IllegalBoundaryException
+ * if the <code>boundary</code> has a different length than the one being currently
+ * parsed.
*/
public void setBoundary(final byte[] boundary) throws IllegalBoundaryException
{
- if (boundary.length != boundaryLength - 4)
+ if (boundary.length != boundaryLength - BOUNDARY_PREFIX.length)
{
throw new IllegalBoundaryException("The length of a boundary token can not be changed");
}
- System.arraycopy(boundary, 0, this.boundary, 4, boundary.length);
+ System.arraycopy(boundary, 0, this.boundary, BOUNDARY_PREFIX.length, boundary.length);
}
+
/**
* <p>
* Reads the <code>header-part</code> of the current <code>encapsulation</code>.
+ *
* <p>
* Headers are returned verbatim to the input stream, including the trailing <code>CRLF</code>
* marker. Parsing is left to the application.
*
- * @param maxSize
- * The maximum amount to read before giving up
+ * <p>
+ * <strong>TODO</strong> allow limiting maximum header size to protect against abuse.
*
* @return The <code>header-part</code> of the current encapsulation.
*
- * @exception MalformedStreamException
- * if the stream ends unexpectedly.
+ * @throws MalformedStreamException
+ * if the stream ends unexpecetedly.
*/
- public String readHeaders(final int maxSize) throws MalformedStreamException
+ public String readHeaders() throws MalformedStreamException
{
int i = 0;
- byte[] b = new byte[1];
+ byte b;
// to support multi-byte characters
ByteArrayOutputStream baos = new ByteArrayOutputStream();
- int sizeMax = HEADER_PART_SIZE_MAX;
int size = 0;
- while (i < 4)
+ while (i < HEADER_SEPARATOR.length)
{
try
{
- b[0] = readByte();
+ b = readByte();
}
catch (IOException e)
{
throw new MalformedStreamException("Stream ended unexpectedly");
}
- size++;
- if (size > maxSize)
+ if (++size > HEADER_PART_SIZE_MAX)
{
- throw new MalformedStreamException("Stream exceeded maximum of " + maxSize +
- " bytes");
+ throw new MalformedStreamException("Header section has more than " +
+ HEADER_PART_SIZE_MAX + " bytes (maybe it is not properly terminated)");
}
- if (b[0] == HEADER_SEPARATOR[i])
+ if (b == HEADER_SEPARATOR[i])
{
i++;
}
@@ -447,10 +583,7 @@ public class MultipartFormInputStream
{
i = 0;
}
- if (size <= sizeMax)
- {
- baos.write(b[0]);
- }
+ baos.write(b);
}
String headers = null;
@@ -483,81 +616,36 @@ public class MultipartFormInputStream
*
* <p>
* Arbitrary large amounts of data can be processed by this method using a constant size buffer.
- * (see {@link #MultipartFormInputStream(InputStream,byte[],int) constructor}).
+ * (see
+ * {@link #MultipartFormInputStream(InputStream,byte[],int, MultipartFormInputStream.ProgressNotifier)
+ * constructor}).
*
* @param output
- * The <code>Stream</code> to write data into.
+ * The <code>Stream</code> to write data into. May be null, in which case this method
+ * is equivalent to {@link #discardBodyData()}.
*
* @return the amount of data written.
*
- * @exception MalformedStreamException
- * if the stream ends unexpectedly.
- * @exception IOException
- * if an i/o error occurs.
+ * @throws MalformedStreamException
+ * if the stream ends unexpectedly.
+ * @throws IOException
+ * if an i/o error occurs.
*/
public int readBodyData(final OutputStream output) throws MalformedStreamException, IOException
{
- boolean done = false;
- int pad;
- int pos;
- int bytesRead;
- int total = 0;
- while (!done)
- {
- // Is boundary token present somewhere in the buffer?
- pos = findSeparator();
- if (pos != -1)
- {
- // Write the rest of the data before the boundary.
- output.write(buffer, head, pos - head);
- total += pos - head;
- head = pos;
- done = true;
- }
- else
- {
- // Determine how much data should be kept in the
- // buffer.
- if (tail - head > keepRegion)
- {
- pad = keepRegion;
- }
- else
- {
- pad = tail - head;
- }
- // Write out the data belonging to the body-data.
- output.write(buffer, head, tail - head - pad);
-
- // Move the data to the beginning of the buffer.
- total += tail - head - pad;
- System.arraycopy(buffer, tail - pad, buffer, 0, pad);
-
- // Refill buffer with new data.
- head = 0;
- bytesRead = input.read(buffer, pad, bufSize - pad);
-
- // [pprrrrrrr]
- if (bytesRead != -1)
- {
- tail = pad + bytesRead;
- }
- else
- {
- // The last pad amount is left in the buffer.
- // Boundary can't be in there so write out the
- // data you have and signal an error condition.
- output.write(buffer, 0, pad);
- output.flush();
- total += pad;
- throw new MalformedStreamException("Stream ended unexpectedly");
- }
- }
- }
- output.flush();
- return total;
+ final InputStream istream = newInputStream();
+ return Streams.copy(istream, output == null ? new NoopOutputStream() : output);
}
+ /**
+ * Creates a new {@link ItemInputStream}.
+ *
+ * @return A new instance of {@link ItemInputStream}.
+ */
+ ItemInputStream newInputStream()
+ {
+ return new ItemInputStream();
+ }
/**
* <p>
@@ -568,75 +656,24 @@ public class MultipartFormInputStream
*
* @return The amount of data discarded.
*
- * @exception MalformedStreamException
- * if the stream ends unexpectedly.
- * @exception IOException
- * if an i/o error occurs.
+ * @throws MalformedStreamException
+ * if the stream ends unexpectedly.
+ * @throws IOException
+ * if an i/o error occurs.
*/
public int discardBodyData() throws MalformedStreamException, IOException
{
- boolean done = false;
- int pad;
- int pos;
- int bytesRead;
- int total = 0;
- while (!done)
- {
- // Is boundary token present somewhere in the buffer?
- pos = findSeparator();
- if (pos != -1)
- {
- // Write the rest of the data before the boundary.
- total += pos - head;
- head = pos;
- done = true;
- }
- else
- {
- // Determine how much data should be kept in the
- // buffer.
- if (tail - head > keepRegion)
- {
- pad = keepRegion;
- }
- else
- {
- pad = tail - head;
- }
- total += tail - head - pad;
-
- // Move the data to the beginning of the buffer.
- System.arraycopy(buffer, tail - pad, buffer, 0, pad);
-
- // Refill buffer with new data.
- head = 0;
- bytesRead = input.read(buffer, pad, bufSize - pad);
-
- // [pprrrrrrr]
- if (bytesRead != -1)
- {
- tail = pad + bytesRead;
- }
- else
- {
- // The last pad amount is left in the buffer.
- // Boundary can't be in there so signal an error
- // condition.
- total += pad;
- throw new MalformedStreamException("Stream ended unexpectedly");
- }
- }
- }
- return total;
+ return readBodyData(null);
}
+
/**
* Finds the beginning of the first <code>encapsulation</code>.
*
* @return <code>true</code> if an <code>encapsulation</code> was found in the stream.
*
- * @exception IOException
- * if an i/o error occurs.
+ * @throws IOException
+ * if an i/o error occurs.
*/
public boolean skipPreamble() throws IOException
{
@@ -648,7 +685,7 @@ public class MultipartFormInputStream
// Discard all data up to the delimiter.
discardBodyData();
- // Read boundary - if succeeded, the stream contains an
+ // Read boundary - if succeded, the stream contains an
// encapsulation.
return readBoundary();
}
@@ -783,6 +820,7 @@ public class MultipartFormInputStream
return sbTemp.toString();
}
+
/**
* Thrown to indicate that the input stream fails to follow the required syntax.
*/
@@ -838,23 +876,342 @@ public class MultipartFormInputStream
}
}
+ /**
+ * An {@link InputStream} for reading an items contents.
+ */
+ public class ItemInputStream extends InputStream implements Closeable
+ {
+ /**
+ * The number of bytes, which have been read so far.
+ */
+ private long total;
+ /**
+ * The number of bytes, which must be hold, because they might be a part of the boundary.
+ */
+ private int pad;
+ /**
+ * The current offset in the buffer.
+ */
+ private int pos;
+ /**
+ * Whether the stream is already closed.
+ */
+ private boolean closed;
+
+ /**
+ * Creates a new instance.
+ */
+ ItemInputStream()
+ {
+ findSeparator();
+ }
+
+ /**
+ * Called for finding the separator.
+ */
+ private void findSeparator()
+ {
+ pos = MultipartFormInputStream.this.findSeparator();
+ if (pos == -1)
+ {
+ if (tail - head > keepRegion)
+ {
+ pad = keepRegion;
+ }
+ else
+ {
+ pad = tail - head;
+ }
+ }
+ }
+
+ /**
+ * Returns the number of bytes, which have been read by the stream.
+ *
+ * @return Number of bytes, which have been read so far.
+ */
+ public long getBytesRead()
+ {
+ return total;
+ }
+
+ /**
+ * Returns the number of bytes, which are currently available, without blocking.
+ *
+ * @throws IOException
+ * An I/O error occurs.
+ * @return Number of bytes in the buffer.
+ */
+ @Override
+ public int available() throws IOException
+ {
+ if (pos == -1)
+ {
+ return tail - head - pad;
+ }
+ return pos - head;
+ }
+
+ /**
+ * Offset when converting negative bytes to integers.
+ */
+ private static final int BYTE_POSITIVE_OFFSET = 256;
+
+ /**
+ * Returns the next byte in the stream.
+ *
+ * @return The next byte in the stream, as a non-negative integer, or -1 for EOF.
+ * @throws IOException
+ * An I/O error occurred.
+ */
+ @Override
+ public int read() throws IOException
+ {
+ if (closed)
+ {
+ throw new FileItemStream.ItemSkippedException();
+ }
+ if (available() == 0)
+ {
+ if (makeAvailable() == 0)
+ {
+ return -1;
+ }
+ }
+ ++total;
+ int b = buffer[head++];
+ if (b >= 0)
+ {
+ return b;
+ }
+ return b + BYTE_POSITIVE_OFFSET;
+ }
+
+ /**
+ * Reads bytes into the given buffer.
+ *
+ * @param b
+ * The destination buffer, where to write to.
+ * @param off
+ * Offset of the first byte in the buffer.
+ * @param len
+ * Maximum number of bytes to read.
+ * @return Number of bytes, which have been actually read, or -1 for EOF.
+ * @throws IOException
+ * An I/O error occurred.
+ */
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException
+ {
+ if (closed)
+ {
+ throw new FileItemStream.ItemSkippedException();
+ }
+ if (len == 0)
+ {
+ return 0;
+ }
+ int res = available();
+ if (res == 0)
+ {
+ res = makeAvailable();
+ if (res == 0)
+ {
+ return -1;
+ }
+ }
+ res = Math.min(res, len);
+ System.arraycopy(buffer, head, b, off, res);
+ head += res;
+ total += res;
+ return res;
+ }
+
+ /**
+ * Closes the input stream.
+ *
+ * @throws IOException
+ * An I/O error occurred.
+ */
+ @Override
+ public void close() throws IOException
+ {
+ close(false);
+ }
+
+ /**
+ * Closes the input stream.
+ *
+ * @param pCloseUnderlying
+ * Whether to close the underlying stream (hard close)
+ * @throws IOException
+ * An I/O error occurred.
+ */
+ public void close(boolean pCloseUnderlying) throws IOException
+ {
+ if (closed)
+ {
+ return;
+ }
+ if (pCloseUnderlying)
+ {
+ closed = true;
+ input.close();
+ }
+ else
+ {
+ for (;;)
+ {
+ int av = available();
+ if (av == 0)
+ {
+ av = makeAvailable();
+ if (av == 0)
+ {
+ break;
+ }
+ }
+ skip(av);
+ }
+ }
+ closed = true;
+ }
+
+ /**
+ * Skips the given number of bytes.
+ *
+ * @param bytes
+ * Number of bytes to skip.
+ * @return The number of bytes, which have actually been skipped.
+ * @throws IOException
+ * An I/O error occurred.
+ */
+ @Override
+ public long skip(long bytes) throws IOException
+ {
+ if (closed)
+ {
+ throw new FileItemStream.ItemSkippedException();
+ }
+ int av = available();
+ if (av == 0)
+ {
+ av = makeAvailable();
+ if (av == 0)
+ {
+ return 0;
+ }
+ }
+ long res = Math.min(av, bytes);
+ head += res;
+ return res;
+ }
+
+ /**
+ * Attempts to read more data.
+ *
+ * @return Number of available bytes
+ * @throws IOException
+ * An I/O error occurred.
+ */
+ private int makeAvailable() throws IOException
+ {
+ if (pos != -1)
+ {
+ return 0;
+ }
+
+ // Move the data to the beginning of the buffer.
+ total += tail - head - pad;
+ System.arraycopy(buffer, tail - pad, buffer, 0, pad);
+
+ // Refill buffer with new data.
+ head = 0;
+ tail = pad;
+
+ for (;;)
+ {
+ int bytesRead = input.read(buffer, tail, bufSize - tail);
+ if (bytesRead == -1)
+ {
+ // The last pad amount is left in the buffer.
+ // Boundary can't be in there so signal an error
+ // condition.
+ final String msg = "Stream ended unexpectedly";
+ throw new MalformedStreamException(msg);
+ }
+ if (notifier != null)
+ {
+ notifier.noteBytesRead(bytesRead);
+ }
+ tail += bytesRead;
+
+ findSeparator();
+ int av = available();
+
+ if (av > 0 || pos != -1)
+ {
+ return av;
+ }
+ }
+ }
+
+ /**
+ * Returns, whether the stream is closed.
+ *
+ * @return True, if the stream is closed, otherwise false.
+ */
+ public boolean isClosed()
+ {
+ return closed;
+ }
+ }
+
+ private final static class NoopOutputStream extends OutputStream
+ {
+ @Override
+ public void close()
+ {
+ }
+
+ @Override
+ public void flush()
+ {
+ }
+
+ @Override
+ public void write(byte[] b)
+ {
+ }
+
+ @Override
+ public void write(byte[] b, int i, int l)
+ {
+ }
+
+ @Override
+ public void write(int b)
+ {
+ }
+ }
// ------------------------------------------------------ Debugging methods
// These are the methods that were used to debug this stuff.
/*
+ *
* // Dump data. protected void dump() { System.out.println("01234567890"); byte[] temp = new
* byte[buffer.length]; for(int i=0; i<buffer.length; i++) { if (buffer[i] == 0x0D || buffer[i]
* == 0x0A) { temp[i] = 0x21; } else { temp[i] = buffer[i]; } } System.out.println(new
* String(temp)); int i; for (i=0; i<head; i++) System.out.print(" "); System.out.println("h");
* for (i=0; i<tail; i++) System.out.print(" "); System.out.println("t"); System.out.flush(); }
+ *
* // Main routine, for testing purposes only. // // @param args A String[] with the command
- * line arguments. // @exception Exception, a generic exception. public static void main(
- * String[] args ) throws Exception { File boundaryFile = new File("boundary.dat"); int
- * boundarySize = (int)boundaryFile.length(); byte[] boundary = new byte[boundarySize];
- * FileInputStream input = new FileInputStream(boundaryFile);
- * input.read(boundary,0,boundarySize);
+ * line arguments. // @throws Exception, a generic exception. public static void main( String[]
+ * args ) throws Exception { File boundaryFile = new File("boundary.dat"); int boundarySize =
+ * (int)boundaryFile.length(); byte[] boundary = new byte[boundarySize]; FileInputStream input =
+ * new FileInputStream(boundaryFile); input.read(boundary,0,boundarySize);
*
* input = new FileInputStream("multipart.dat"); MultipartStream chunks = new
* MultipartStream(input, boundary);
Modified: wicket/trunk/wicket-util/src/main/java/org/apache/wicket/util/upload/ParameterParser.java
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket-util/src/main/java/org/apache/wicket/util/upload/ParameterParser.java?rev=1042345&r1=1042344&r2=1042345&view=diff
==============================================================================
--- wicket/trunk/wicket-util/src/main/java/org/apache/wicket/util/upload/ParameterParser.java (original)
+++ wicket/trunk/wicket-util/src/main/java/org/apache/wicket/util/upload/ParameterParser.java Sun Dec 5 13:08:00 2010
@@ -20,8 +20,8 @@ import java.util.HashMap;
import java.util.Map;
/**
- * A simple parser intended to parse sequences of name/value pairs. Parameter values are expected to
- * be enclosed in quotes if they contain unsafe characters, such as '=' characters or separators.
+ * A simple parser intended to parse sequences of name/value pairs. Parameter values are exptected
+ * to be enclosed in quotes if they contain unsafe characters, such as '=' characters or separators.
* Parameter values are optional and can be omitted.
*
* <p>
@@ -122,7 +122,7 @@ public class ParameterParser
* Tests if the given character is present in the array of characters.
*
* @param ch
- * the character to test for presence in the array of characters
+ * the character to test for presense in the array of characters
* @param charray
* the array of characters to test against
*
@@ -233,6 +233,43 @@ public class ParameterParser
/**
* Extracts a map of name/value pairs from the given string. Names are expected to be unique.
+ * Multiple separators may be specified and the earliest found in the input string is used.
+ *
+ * @param str
+ * the string that contains a sequence of name/value pairs
+ * @param separators
+ * the name/value pairs separators
+ *
+ * @return a map of name/value pairs
+ */
+ public Map<String, String> parse(final String str, char[] separators)
+ {
+ if (separators == null || separators.length == 0)
+ {
+ return new HashMap<String, String>();
+ }
+ char separator = separators[0];
+ if (str != null)
+ {
+ int idx = str.length();
+ for (int i = 0; i < separators.length; i++)
+ {
+ int tmp = str.indexOf(separators[i]);
+ if (tmp != -1)
+ {
+ if (tmp < idx)
+ {
+ idx = tmp;
+ separator = separators[i];
+ }
+ }
+ }
+ }
+ return parse(str, separator);
+ }
+
+ /**
+ * Extracts a map of name/value pairs from the given string. Names are expected to be unique.
*
* @param str
* the string that contains a sequence of name/value pairs
Copied: wicket/trunk/wicket-util/src/main/java/org/apache/wicket/util/upload/ProgressListener.java (from r1042295, wicket/trunk/wicket-util/src/main/java/org/apache/wicket/util/upload/RequestContext.java)
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket-util/src/main/java/org/apache/wicket/util/upload/ProgressListener.java?p2=wicket/trunk/wicket-util/src/main/java/org/apache/wicket/util/upload/ProgressListener.java&p1=wicket/trunk/wicket-util/src/main/java/org/apache/wicket/util/upload/RequestContext.java&r1=1042295&r2=1042345&rev=1042345&view=diff
==============================================================================
--- wicket/trunk/wicket-util/src/main/java/org/apache/wicket/util/upload/RequestContext.java (original)
+++ wicket/trunk/wicket-util/src/main/java/org/apache/wicket/util/upload/ProgressListener.java Sun Dec 5 13:08:00 2010
@@ -16,42 +16,23 @@
*/
package org.apache.wicket.util.upload;
-import java.io.IOException;
-import java.io.InputStream;
/**
- * <p>
- * Abstracts access to the request information needed for file uploads. This interface should be
- * implemented for each type of request that may be handled by FileUpload, such as servlets and
- * portlets.
- * </p>
- *
- * @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
+ * The {@link ProgressListener} may be used to display a progress bar or do stuff like that.
*/
-public interface RequestContext
+public interface ProgressListener
{
-
- /**
- * Retrieve the content type of the request.
- *
- * @return The content type of the request.
- */
- String getContentType();
-
/**
- * Retrieve the content length of the request.
- *
- * @return The content length of the request.
- */
- int getContentLength();
-
- /**
- * Retrieve the input stream for the request.
- *
- * @return The input stream for the request.
+ * Updates the listeners status information.
*
- * @throws IOException
- * if a problem occurs.
+ * @param pBytesRead
+ * The total number of bytes, which have been read so far.
+ * @param pContentLength
+ * The total number of bytes, which are being read. May be -1, if this number is
+ * unknown.
+ * @param pItems
+ * The number of the field, which is currently being read. (0 = no item so far, 1 =
+ * first item is being read, ...)
*/
- InputStream getInputStream() throws IOException;
+ void update(long pBytesRead, long pContentLength, int pItems);
}
Modified: wicket/trunk/wicket-util/src/main/java/org/apache/wicket/util/upload/RequestContext.java
URL: http://svn.apache.org/viewvc/wicket/trunk/wicket-util/src/main/java/org/apache/wicket/util/upload/RequestContext.java?rev=1042345&r1=1042344&r2=1042345&view=diff
==============================================================================
--- wicket/trunk/wicket-util/src/main/java/org/apache/wicket/util/upload/RequestContext.java (original)
+++ wicket/trunk/wicket-util/src/main/java/org/apache/wicket/util/upload/RequestContext.java Sun Dec 5 13:08:00 2010
@@ -21,7 +21,7 @@ import java.io.InputStream;
/**
* <p>
- * Abstracts access to the request information needed for file uploads. This interface should be
+ * Abstracts access to the request information needed for file uploads. This interfsace should be
* implemented for each type of request that may be handled by FileUpload, such as servlets and
* portlets.
* </p>
@@ -32,6 +32,13 @@ public interface RequestContext
{
/**
+ * Retrieve the character encoding for the request.
+ *
+ * @return The character encoding for the request.
+ */
+ String getCharacterEncoding();
+
+ /**
* Retrieve the content type of the request.
*
* @return The content type of the request.