Java tutorial
/* * WebTop Services is a web application framework developed by Sonicle S.r.l. * Copyright (C) 2011 Sonicle S.r.l. * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU Affero General Public License version 3 as published by * the Free Software Foundation with the addition of the following permission * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED * WORK IN WHICH THE COPYRIGHT IS OWNED BY SONICLE, SONICLE DISCLAIMS THE * WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU Affero General Public License * along with this program; if not, see http://www.gnu.org/licenses or write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301 USA. * * You can contact Sonicle S.r.l. at email address sonicle@sonicle.com * * The interactive user interfaces in modified source and object code versions * of this program must display Appropriate Legal Notices, as required under * Section 5 of the GNU Affero General Public License version 3. * * In accordance with Section 7(b) of the GNU Affero General Public License * version 3, these Appropriate Legal Notices must retain the display of the * "Powered by Sonicle WebTop" logo. If the display of the logo is not reasonably * feasible for technical reasons, the Appropriate Legal Notices must display * the words "Powered by Sonicle WebTop". */ package com.sonicle.webtop.mail; import java.io.File; import java.io.IOException; import java.io.BufferedOutputStream; import java.io.FileOutputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import javax.servlet.ServletException; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import org.apache.commons.vfs2.FileObject; /** * The MultipartIterator class is responsible for reading the * input data of a multipart request and splitting it up into * input elements, wrapped inside of a * {@link org.apache.struts.upload.MultipartElement MultipartElement} * for easy definition. To use this class, create a new instance * of MultipartIterator passing it a HttpServletRequest in the * constructor. Then use the {@link #getNextElement() getNextElement} * method until it returns null, then you're finished. Example: <br> * <pre> * MultipartIterator iterator = new MultipartIterator(request); * MultipartElement element; * * while ((element = iterator.getNextElement()) != null) { * //do something with element * } * </pre> * * @see org.apache.struts.upload.MultipartElement * @author Mike Schachter */ public class MultipartIterator { /** * The maximum size in bytes of the buffer used to read lines [4K] */ public static int MAX_LINE_SIZE = 4096; /** * The request instance for this class */ protected HttpServletRequest request; /** * The input stream instance for this class */ protected BufferedMultipartInputStream inputStream; /** * The boundary for this multipart request */ protected String boundary; /** * The byte array representing the boundary for this multipart request */ protected byte[] boundaryBytes; /** * Whether or not the input stream is finished */ protected boolean contentRead = false; /** * The maximum file size in bytes allowed. Ignored if -1 */ protected long maxSize = -1; /** * The total bytes read from this request */ protected long totalLength = 0; /** * The content length of this request */ protected int contentLength; /** * The size in bytes written to the filesystem at a time [20K] */ protected int diskBufferSize = 2 * 10240; /** * The amount of data read from a request at a time. * This also represents the maximum size in bytes of * a line read from the request [4KB] */ protected int bufferSize = 4096; /** * The temporary directory to store files */ protected String tempDir; /** * An output stream to create file */ protected FileObject fileObjectDir = null; /** * Constructs a MultipartIterator with a default buffer size and no file size * limit * * @param request The multipart request to iterate */ public MultipartIterator(HttpServletRequest request) throws ServletException { this(request, -1); } /** * Constructs a MultipartIterator with the specified buffer size and * no file size limit * * @param request The multipart request to iterate * @param bufferSize The size in bytes that should be read from the input * stream at a times */ public MultipartIterator(HttpServletRequest request, int bufferSize) throws ServletException { this(request, bufferSize, -1); } /** * Constructs a MultipartIterator with the specified buffer size and * the specified file size limit in bytes * * @param request The multipart request to iterate * @param bufferSize The size in bytes that should be read from the input * stream at a times * @param maxSize The maximum size in bytes allowed for a multipart element's data */ public MultipartIterator(HttpServletRequest request, int bufferSize, long maxSize) throws ServletException { this(request, bufferSize, maxSize, (String) null); } public MultipartIterator(HttpServletRequest request, int bufferSize, long maxSize, String tempDir) throws ServletException { this.request = request; this.maxSize = maxSize; if (bufferSize > -1) { this.bufferSize = bufferSize; } if (tempDir != null) { this.tempDir = tempDir; } else { //default to system-wide tempdir tempDir = System.getProperty("java.io.tmpdir"); } parseRequest(); } public MultipartIterator(HttpServletRequest request, int bufferSize, long maxSize, FileObject dir) throws ServletException { this.request = request; this.maxSize = maxSize; if (bufferSize > -1) { this.bufferSize = bufferSize; } this.fileObjectDir = dir; parseRequest(); } /** * Retrieves the next element in the iterator if one exists. * * @throws a ServletException if the post size exceeds the maximum file size * passed in the 3 argument constructor * @throws an UnsupportedEncodingException if the "ISO-8859-1" encoding isn't found * @return a {@link org.apache.struts.upload.MultipartElement MultipartElement} * representing the next element in the request data * */ public MultipartElement getNextElement() throws ServletException, UnsupportedEncodingException { //retrieve the "Content-Disposition" header //and parse String disposition = readLine(); if ((disposition != null) && (disposition.startsWith("Content-Disposition"))) { String name = parseDispositionName(disposition); String filename = parseDispositionFilename(disposition); String contentType = null; boolean isFile = (filename != null); if (isFile) { filename = new File(filename).getName(); //check for windows filenames, //from linux jdk's the entire filepath //isn't parsed correctly from File.getName() int colonIndex = filename.indexOf(":"); if (colonIndex == -1) { //check for Window's SMB server file paths colonIndex = filename.indexOf("\\\\"); } int slashIndex = filename.lastIndexOf("\\"); if ((colonIndex > -1) && (slashIndex > -1)) { //then consider this filename to be a full //windows filepath, and parse it accordingly //to retrieve just the file name filename = filename.substring(slashIndex + 1, filename.length()); } //get the content type contentType = readLine(); contentType = parseContentType(contentType); } //ignore next line (whitespace) (unless it's a file //without content-type) if (!((isFile) && contentType == null)) { readLine(); } MultipartElement element = null; //process a file element if (isFile) { try { //create a local file on disk representing the element if (fileObjectDir == null) { File elementFile = createLocalFile(); element = new MultipartElement(name, filename, contentType, elementFile); } else { FileObject elementFile = createVFSFile(filename); filename = elementFile.getName().getBaseName(); element = new MultipartElement(name, filename, contentType, elementFile); } } catch (IOException ioe) { Service.logger.error("Exception", ioe); throw new ServletException("IOException while reading file element: " + ioe.getMessage(), ioe); } } else { //read data into String form, then convert to bytes //for text StringBuffer textData = new StringBuffer(); String line; //parse for text data line = readLine(); while ((line != null) && (!line.startsWith(boundary))) { textData.append(line); line = readLine(); } if (textData.length() > 0) { //cut off "\r" from the end if necessary if (textData.charAt(textData.length() - 1) == '\r') { textData.setLength(textData.length() - 1); } } //create the element element = new MultipartElement(name, textData.toString()); } return element; } //reset stream if (inputStream.markSupported()) { try { inputStream.reset(); } catch (IOException ioe) { throw new ServletException("IOException while resetting input stream: " + ioe.getMessage()); } } return null; } /** * Set the maximum amount of bytes read from a line at one time * * @see javax.servlet.ServletInputStream#readLine(byte[], int, int) */ public void setBufferSize(int bufferSize) { this.bufferSize = bufferSize; } /** * Get the maximum amount of bytes read from a line at one time * * @see javax.servlet.ServletInputStream#readLine(byte[], int, int) */ public int getBufferSize() { return bufferSize; } /** * Set the maximum post data size allowed for a multipart request * @param maxSize The maximum post data size in bytes, set to <code>-1</code> * for no limit */ public void setMaxSize(long maxSize) { this.maxSize = maxSize; } /** * Get the maximum post data size allowed for a multipart request * @return The maximum post data size in bytes */ public long getMaxSize() { return maxSize; } /** * Handles retrieving the boundary and setting the input stream */ protected void parseRequest() throws ServletException { contentLength = request.getContentLength(); //set boundary boundary = parseBoundary(request.getContentType()); boundaryBytes = boundary.getBytes(); try { //set the input stream inputStream = new BufferedMultipartInputStream(request.getInputStream(), bufferSize, contentLength, maxSize); //mark the input stream to allow multiple reads if (inputStream.markSupported()) { inputStream.mark(contentLength + 1); } } catch (IOException ioe) { throw new ServletException("Problem while reading request: " + ioe.getMessage(), ioe); } if ((boundary == null) || (boundary.length() < 1)) { //try retrieving the header through more "normal" means boundary = parseBoundary(request.getHeader("Content-type")); } if ((boundary == null) || (boundary.length() < 1)) { throw new ServletException("MultipartIterator: cannot retrieve boundary " + "for multipart request"); } //read first line try { String firstLine = readLine(); if (firstLine == null) { throw new ServletException("MultipartIterator: no multipart request data " + "sent"); } if (!firstLine.startsWith(boundary)) { throw new ServletException( "MultipartIterator: invalid multipart request " + "data, doesn't start with boundary"); } } catch (UnsupportedEncodingException uee) { throw new ServletException("MultipartIterator: encoding \"ISO-8859-1\" not supported"); } } /** * Parses a content-type String for the boundary. Appends a * "--" to the beginning of the boundary, because thats the * real boundary as opposed to the shortened one in the * content type. */ public static String parseBoundary(String contentType) { if (contentType.lastIndexOf("boundary=") != -1) { String _boundary = "--" + contentType.substring(contentType.lastIndexOf("boundary=") + 9); if (_boundary.endsWith("\n")) { //strip it off return _boundary.substring(0, _boundary.length() - 1); } return _boundary; } return null; } /** * Parses the "Content-Type" line of a multipart form for a content type * * @param contentTypeString A String reprsenting the Content-Type line, * with a trailing "\n" * @return The content type specified, or <code>null</code> if one can't be * found. */ public static String parseContentType(String contentTypeString) { int nameIndex = contentTypeString.indexOf("Content-Type: "); if (nameIndex == -1) nameIndex = contentTypeString.indexOf("\n"); if (nameIndex != -1) { int endLineIndex = contentTypeString.indexOf("\n"); if (endLineIndex == -1) { endLineIndex = contentTypeString.length() - 1; } return contentTypeString.substring(nameIndex + 14, endLineIndex); } return null; } /** * Retrieves the "name" attribute from a content disposition line * * @param dispositionString The entire "Content-disposition" string * @return <code>null</code> if no name could be found, otherwise, * returns the name * @see #parseForAttribute(String, String) */ public static String parseDispositionName(String dispositionString) { return parseForAttribute("name", dispositionString); } /** * Retrieves the "filename" attribute from a content disposition line * * @param dispositionString The entire "Content-disposition" string * @return <code>null</code> if no filename could be found, otherwise, * returns the filename * @see #parseForAttribute(String, String) */ public static String parseDispositionFilename(String dispositionString) { return parseForAttribute("filename", dispositionString); } /** * Parses a string looking for a attribute-value pair, and returns the value. * For example: * <pre> * String parseString = "Content-Disposition: filename=\"bob\" name=\"jack\""; * MultipartIterator.parseForAttribute(parseString, "name"); * </pre> * That will return "bob". * * @param attribute The name of the attribute you're trying to get * @param parseString The string to retrieve the value from * @return The value of the attribute, or <code>null</code> if none could be found */ public static String parseForAttribute(String attribute, String parseString) { int nameIndex = parseString.indexOf(attribute + "=\""); if (nameIndex != -1) { int endQuoteIndex = parseString.indexOf("\"", nameIndex + attribute.length() + 3); if (endQuoteIndex != -1) { return parseString.substring(nameIndex + attribute.length() + 2, endQuoteIndex); } return ""; } return null; } /** * Reads the input stream until it reaches a new line */ protected String readLine() throws ServletException, UnsupportedEncodingException { byte[] bufferByte; int bytesRead; if (totalLength >= contentLength) { return null; } try { bufferByte = inputStream.readLine(); if (bufferByte == null) return null; bytesRead = bufferByte.length; } catch (IOException ioe) { throw new ServletException("IOException while reading multipart request: " + ioe.getMessage()); } if (bytesRead == -1) { return null; } totalLength += bytesRead; return new String(bufferByte, 0, bytesRead, "ISO-8859-1"); } /** * Creates a file on disk from the current mulitpart element * @param fileName the name of the multipart file */ protected File createLocalFile() throws IOException { File tempFile = File.createTempFile("strts", null, new File(tempDir)); BufferedOutputStream fos = new BufferedOutputStream(new FileOutputStream(tempFile), diskBufferSize); try { writeStream(fos); } catch (IOException exc) { tempFile.delete(); } return tempFile; } /** * Creates a file on disk from the current mulitpart element * @param fileName the name of the multipart file */ protected FileObject createVFSFile(String filename) throws IOException { FileObject vfsfile = fileObjectDir.resolveFile(filename); int n = 0; String xfilename = filename; String xext = null; int ix = xfilename.lastIndexOf("."); if (ix > 0) { xext = xfilename.substring(ix + 1); xfilename = xfilename.substring(0, ix); } while (vfsfile.exists()) { ++n; filename = xfilename + " (" + n + ")"; if (xext != null) filename += "." + xext; vfsfile = fileObjectDir.resolveFile(filename); } OutputStream fos = vfsfile.getContent().getOutputStream(); try { writeStream(fos); } catch (IOException exc) { vfsfile.delete(); } return vfsfile; } protected void writeStream(OutputStream fos) throws IOException { byte[] lineBuffer = inputStream.readLine(); if (lineBuffer == null) { throw new IOException("Premature end of stream while reading multipart request"); } int bytesRead = lineBuffer.length; boolean cutCarriage = false; boolean cutNewline = false; try { while ((bytesRead != -1) && (!equals(lineBuffer, 0, boundaryBytes.length, boundaryBytes))) { if (cutCarriage) { fos.write('\r'); } if (cutNewline) { fos.write('\n'); } cutCarriage = false; if (bytesRead > 0) { if (lineBuffer[bytesRead - 1] == '\r') { bytesRead--; cutCarriage = true; } } cutNewline = true; fos.write(lineBuffer, 0, bytesRead); lineBuffer = inputStream.readLine(); if (lineBuffer == null) { //throw new IOException("Premature end of stream while reading multipart request"); break; } bytesRead = lineBuffer.length; } } catch (IOException ioe) { fos.close(); throw ioe; } fos.flush(); fos.close(); } /** * Checks bytes for equality. Two byte arrays are equal if * each of their elements are the same. This method checks * comp[offset] with source[0] to source[length-1] with * comp[offset + length - 1] * @param comp The byte to compare to <code>source</code> * @param offset The offset to start at in <code>comp</code> * @param length The length of <code>comp</code> to compare to * @param source The reference byte array to test for equality */ public static boolean equals(byte[] comp, int offset, int length, byte[] source) { if ((length != source.length) || (comp.length - offset < length)) { return false; } for (int i = 0; i < length; i++) { if (comp[offset + i] != source[i]) { return false; } } return true; } }