Java tutorial
/* * ConcourseConnect * Copyright 2009 Concursive Corporation * http://www.concursive.com * * This file is part of ConcourseConnect, an open source social business * software and community platform. * * Concursive ConcourseConnect is free software: you can redistribute it and/or * modify it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, version 3 of the License. * * Under the terms of the GNU Affero General Public License you must release the * complete source code for any application that uses any part of ConcourseConnect * (system header files and libraries used by the operating system are excluded). * These terms must be included in any work that has ConcourseConnect components. * If you are developing and distributing open source applications under the * GNU Affero General Public License, then you are free to use ConcourseConnect * under the GNU Affero General Public License. * * If you are deploying a web site in which users interact with any portion of * ConcourseConnect over a network, the complete source code changes must be made * available. For example, include a link to the source archive directly from * your web site. * * For OEMs, ISVs, SIs and VARs who distribute ConcourseConnect with their * products, and do not license and distribute their source code under the GNU * Affero General Public License, Concursive provides a flexible commercial * license. * * To anyone in doubt, we recommend the commercial license. Our commercial license * is competitively priced and will eliminate any confusion about how * ConcourseConnect can be used and distributed. * * ConcourseConnect 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 Affero General Public License for more * details. * * You should have received a copy of the GNU Affero General Public License * along with ConcourseConnect. If not, see <http://www.gnu.org/licenses/>. * * Attribution Notice: ConcourseConnect is an Original Work of software created * by Concursive Corporation */ package com.concursive.connect.web.modules.documents.utils; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import java.io.*; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import java.text.SimpleDateFormat; import java.util.HashMap; import java.util.StringTokenizer; /** * This class provides methods for parsing a HTML multi-part form. Each method * returns a HashMap which contains keys for all parameters sent from the web * browser. The corresponding values are either type "String" or "FileInfo" * depending on the type of data in the corresponding part. <P> * <p/> * Refer to http://www.ietf.org/rfc/rfc1867.txt<P> * <p/> * The following is a sample InputStream expected by the methods in this class: * <PRE> * -----------------------------7ce23a18680 * Content-Disposition: form-data; name="SomeTextField1" * on * -----------------------------7ce23a18680 * Content-Disposition: form-data; name="LocalFile1"; filename="C:\temp\testit.c" * Content-Type: text/plain * #include <stdlib.h> * int main(int argc, char **argv) * { * printf("Testing\n"); * return 0; * } * -----------------------------7ce23a18680-- * </PRE> * NOTE: One known bug: if a file is not selected, then no form elements are * processed after the file tag * <p/> * Copyright(c) 2001 iSavvix Corporation (http://www.isavvix.com/) All rights * reserved Permission to use, copy, modify and distribute this material for * any purpose and without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies, and that * the name of iSavvix Corporation not be used in advertising or publicity * pertaining to this material without the specific, prior written permission * of an authorized representative of iSavvix Corporation. ISAVVIX CORPORATION * MAKES NO REPRESENTATIONS AND EXTENDS NO WARRANTIES, EXPRESS OR IMPLIED, WITH * RESPECT TO THE SOFTWARE, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR ANY PARTICULAR PURPOSE, AND * THE WARRANTY AGAINST INFRINGEMENT OF PATENTS OR OTHER INTELLECTUAL PROPERTY * RIGHTS. THE SOFTWARE IS PROVIDED "AS IS", AND IN NO EVENT SHALL ISAVVIX * CORPORATION OR ANY OF ITS AFFILIATES BE LIABLE FOR ANY DAMAGES, INCLUDING * ANY LOST PROFITS OR OTHER INCIDENTAL OR CONSEQUENTIAL DAMAGES RELATING TO * THE SOFTWARE. * * @author Anil Hemrajani * @version $Id$ * @created December 6, 2001 */ public class HttpMultiPartParser { protected static final Log LOG = LogFactory.getLog(HttpMultiPartParser.class); private final String fs = System.getProperty("file.separator"); private final int ONE_MB = 1024 * 1024 * 1; private boolean useUniqueName = false; private boolean usePathParam = false; private boolean useDateForFolder = false; private double version = -1; private int extensionId = -1; /** * Sets the useUniqueName attribute of the HttpMultiPartParser object * * @param tmp The new useUniqueName value */ public void setUseUniqueName(boolean tmp) { this.useUniqueName = tmp; } /** * Sets the UsePathParam attribute of the HttpMultiPartParser object * * @param tmp The new UsePathParam value */ public void setUsePathParam(boolean tmp) { this.usePathParam = tmp; } /** * Sets the useDateForFolder attribute of the HttpMultiPartParser object * * @param tmp The new useDateForFolder value */ public void setUseDateForFolder(boolean tmp) { this.useDateForFolder = tmp; } /** * Sets the Version attribute of the HttpMultiPartParser object * * @param tmp The new Version value */ public void setVersion(double tmp) { this.version = tmp; } /** * Sets the extensionId attribute of the HttpMultiPartParser object * * @param tmp The new extensionId value */ public void setExtensionId(int tmp) { this.extensionId = tmp; } /** * Gets the useUniqueName attribute of the HttpMultiPartParser object * * @return The useUniqueName value */ public boolean getUseUniqueName() { return useUniqueName; } /** * Gets the UsePathParam attribute of the HttpMultiPartParser object * * @return The UsePathParam value */ public boolean getUsePathParam() { return usePathParam; } /** * Gets the useDateForFolder attribute of the HttpMultiPartParser object * * @return The useDateForFolder value */ public boolean getUseDateForFolder() { return useDateForFolder; } /** * Gets the Version attribute of the HttpMultiPartParser object * * @return The Version value */ public double getVersion() { return version; } /** * Gets the extensionId attribute of the HttpMultiPartParser object * * @return The extensionId value */ public int getExtensionId() { return extensionId; } /** * Parses the InputStream, separates the various parts and returns them as * key=value pairs in a HashMap. Any incoming files are saved in directory * "saveInDir" using the client's file name; the file information is stored * as java.io.File object in the HashMap ("value" part). * * @param saveInDir Description of Parameter * @param request Description of the Parameter * @return Description of the Returned Value * @throws IllegalArgumentException Description of Exception * @throws IOException Description of Exception */ public HashMap parseData(HttpServletRequest request, String saveInDir) throws IllegalArgumentException, IOException { return processData(request, saveInDir); } /** * Parses the InputStream, separates the various parts and returns them as * key=value pairs in a HashMap. Any incoming files are saved as byte arrays; * the file information is stored as java.io.File object in the HashMap * ("value" part). * * @param request Description of the Parameter * @return Description of the Returned Value * @throws IllegalArgumentException Description of Exception * @throws IOException Description of Exception */ public HashMap parseData(HttpServletRequest request) throws IllegalArgumentException, IOException { return processData(request, null); } /** * Convenience method to read HTTP header lines * * @param sis Description of Parameter * @return The Line value * @throws IOException Description of Exception */ private synchronized String getLine(ServletInputStream sis) throws IOException { byte b[] = new byte[1024]; int read = sis.readLine(b, 0, b.length); int index; String line = null; if (read != -1) { line = new String(b, 0, read); if ((index = line.indexOf('\n')) >= 0) { line = line.substring(0, index - 1); } } b = null; return line; } /** * Concats the directory and file names. * * @param dir Description of Parameter * @param fileName Description of Parameter * @return The FileName value * @throws IllegalArgumentException Description of Exception */ private String getFileName(String dir, String fileName) throws IllegalArgumentException { String path = null; if (dir == null || fileName == null) { throw new IllegalArgumentException("dir or fileName is null"); } int index = fileName.lastIndexOf('/'); String name = null; if (index >= 0) { name = fileName.substring(index + 1); } else { name = fileName; } index = name.lastIndexOf('\\'); if (index >= 0) { fileName = name.substring(index + 1); } path = dir + File.separator + fileName; if (File.separatorChar == '/') { return path.replace('\\', File.separatorChar); } else { return path.replace('/', File.separatorChar); } } /** * Gets the fileName attribute of the HttpMultiPartParser object * * @param fileName Description of the Parameter * @return The fileName value * @throws IllegalArgumentException Description of the Exception */ private String getFileName(String fileName) throws IllegalArgumentException { if (fileName == null) { throw new IllegalArgumentException("dir or fileName is null"); } int index = fileName.lastIndexOf('/'); String name = null; if (index >= 0) { name = fileName.substring(index + 1); } else { name = fileName; } index = name.lastIndexOf('\\'); if (index >= 0) { fileName = name.substring(index + 1); } return fileName; } /** * Description of the Method * * @param saveInDir Description of Parameter * @param request Description of the Parameter * @return Description of the Returned Value * @throws IllegalArgumentException Description of Exception * @throws IOException Description of Exception */ private HashMap processData(HttpServletRequest request, String saveInDir) throws IllegalArgumentException, IOException { String contentType = request.getHeader("Content-type"); //TODO: use the contentLength for a progress bar int contentLength = request.getContentLength(); LOG.debug("HttpMultiPartParser Length: " + contentLength); if ((contentType == null) || (!contentType.startsWith("multipart/"))) { throw new IllegalArgumentException("Not a multipart message"); } int boundaryIndex = contentType.indexOf("boundary="); String boundary = contentType.substring(boundaryIndex + 9); LOG.debug("Request boundary: " + boundary); ServletInputStream is = request.getInputStream(); if (is == null) { throw new IllegalArgumentException("InputStream"); } if (boundary == null || boundary.trim().length() < 1) { throw new IllegalArgumentException("boundary"); } //Each content will begin with two dashes "--" plus the actual boundary string boundary = "--" + boundary; //Prepare to read in from request StringTokenizer stLine = null; StringTokenizer stFields = null; FileInfo fileInfo = null; HashMap dataTable = new HashMap(5); String line = null; String field = null; String paramName = null; boolean saveFiles = (saveInDir != null && saveInDir.trim().length() > 0); boolean isFile = false; boolean validFile = false; int fileCount = 0; //First line should be the boundary line = getLine(is); if (line == null || !line.startsWith(boundary)) { throw new IOException("Boundary not found;" + " boundary = " + boundary + ", line = " + line); } //Continue with the rest of the lines while (line != null) { LOG.trace(line); // Process boundary line ---------------------------------------- if (line == null || !line.startsWith(boundary)) { return dataTable; } // Process "Content-Disposition: " line -------------------------- line = getLine(is); if (line == null) { return dataTable; } // Split line into the following 3 tokens (or 2 if not a file): // 1. Content-Disposition: form-data // 2. name="LocalFile1" // 3. filename="C:\autoexec.bat" (only present if this is part of a HTML file INPUT tag) */ stLine = new StringTokenizer(line, ";\r\n"); if (stLine.countTokens() < 2) { throw new IllegalArgumentException("Bad data in second line"); } // Confirm that this is "form-data" line = stLine.nextToken().toLowerCase(); if (line.indexOf("form-data") < 0) { throw new IllegalArgumentException("Bad data in second line"); } // Now split token 2 from above into field "name" and it's "value" // e.g. name="LocalFile1" stFields = new StringTokenizer(stLine.nextToken(), "=\""); if (stFields.countTokens() < 2) { throw new IllegalArgumentException("Bad data in second line"); } // Get field name fileInfo = new FileInfo(); fileInfo.setVersion(version); fileInfo.setExtensionId(extensionId); stFields.nextToken(); paramName = stFields.nextToken(); // Now split token 3 from above into file "name" and it's "value" // e.g. filename="C:\autoexec.bat" isFile = false; if (stLine.hasMoreTokens()) { field = stLine.nextToken(); stFields = new StringTokenizer(field, "=\""); if (stFields.countTokens() > 1) { if (stFields.nextToken().trim().equalsIgnoreCase("filename")) { fileInfo.setName(paramName); String value = stFields.nextToken(); if (value != null && value.trim().length() > 0) { fileInfo.setClientFileName(value); isFile = true; ++fileCount; } else { // An error condition occurred, skip to next boundary line = getLine(is); // Skip "Content-Type:" line line = getLine(is); // Skip blank line line = getLine(is); // Skip blank line line = getLine(is); // Position to boundary line continue; } } } else if (field.toLowerCase().indexOf("filename") >= 0) { // An error condition occurred, skip to next boundary line = getLine(is); // Skip "Content-Type:" line line = getLine(is); // Skip blank line line = getLine(is); // Skip blank line line = getLine(is); // Position to boundary line continue; } } // Process "Content-Type: " line ---------------------------------- // e.g. Content-Type: text/plain boolean skipBlankLine = true; if (isFile) { line = getLine(is); if (line == null) { return dataTable; } // "Content-type" line not guaranteed to be sent by the browser if (line.trim().length() < 1) { skipBlankLine = false; } else { // Prevent re-skipping below stLine = new StringTokenizer(line, ": "); if (stLine.countTokens() < 2) { throw new IllegalArgumentException("Bad data in third line"); } stLine.nextToken(); // Content-Type fileInfo.setFileContentType(stLine.nextToken()); } } // Skip blank line ----------------------------------------------- if (skipBlankLine) { // Blank line already skipped above? line = getLine(is); if (line == null) { return dataTable; } } // Process data: If not a file, add to hashmap and continue if (!isFile) { line = getLine(is); if (line == null) { return dataTable; } StringBuffer sb = new StringBuffer(); sb.append(line); while (line != null && !line.startsWith(boundary)) { line = getLine(is); if (line != null && !line.startsWith(boundary)) { sb.append(System.getProperty("line.separator")); sb.append(line); } } LOG.debug(" HttpMultiPartParser ->Adding param: " + paramName); dataTable.put(paramName, sb.toString()); continue; } // Either save contents in memory or to a local file OutputStream os = null; String path = null; try { String tmpPath = null; String filenameToUse = null; if (saveFiles) { if (usePathParam) { tmpPath = saveInDir + fileInfo.getName() + fs; } else { tmpPath = saveInDir; } if (useDateForFolder) { SimpleDateFormat formatter1 = new SimpleDateFormat("yyyy"); String datePathToUse1 = formatter1.format(new java.util.Date()); SimpleDateFormat formatter2 = new SimpleDateFormat("MMdd"); String datePathToUse2 = formatter2.format(new java.util.Date()); tmpPath += datePathToUse1 + fs + datePathToUse2 + fs; } // Create output directory in case it doesn't exist File f = new File(tmpPath); f.mkdirs(); //If specified, store files using a unique name, based on date if (useUniqueName) { SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMddHHmmss"); filenameToUse = formatter.format(new java.util.Date()); if (fileCount > 1) { filenameToUse += String.valueOf(fileCount); } } else { filenameToUse = fileInfo.getClientFileName(); } fileInfo.setClientFileName(getFileName(fileInfo.getClientFileName())); //Append a version id for record keeping and uniqueness, prevents //multiple uploads from overwriting each other filenameToUse += (version == -1 ? "" : "^" + version) + (extensionId == -1 ? "" : "-" + extensionId); //Create the file to a file os = new FileOutputStream(path = getFileName(tmpPath, filenameToUse)); } else { //Store the file in memory os = new ByteArrayOutputStream(ONE_MB); } //Begin reading in the request boolean readingContent = true; byte previousLine[] = new byte[2 * ONE_MB]; byte temp[] = null; byte currentLine[] = new byte[2 * ONE_MB]; int read; int read3; //read in the first line, break out if eof if ((read = is.readLine(previousLine, 0, previousLine.length)) == -1) { line = null; break; } //read until next boundary and write the contents to OutputStream while (readingContent) { if ((read3 = is.readLine(currentLine, 0, currentLine.length)) == -1) { line = null; break; } //check if current line is a boundary if (compareBoundary(boundary, currentLine)) { if (read - 2 > 0) { validFile = true; } os.write(previousLine, 0, read - 2); os.flush(); line = new String(currentLine, 0, read3); break; } else { //current line is not a boundary, write previous line os.write(previousLine, 0, read); validFile = true; os.flush(); //reposition previousLine to be currentLine temp = currentLine; currentLine = previousLine; previousLine = temp; read = read3; } } os.close(); temp = null; previousLine = null; currentLine = null; //Store the completed file somewhere if (!saveFiles) { ByteArrayOutputStream baos = (ByteArrayOutputStream) os; fileInfo.setFileContents(baos.toByteArray()); } else { File thisFile = new File(path); if (validFile) { fileInfo.setLocalFile(thisFile); fileInfo.setSize((int) thisFile.length()); os = null; } else { thisFile.delete(); } } if (validFile) { LOG.debug("Adding file param: " + fileInfo.getName()); dataTable.put(paramName, fileInfo); } } catch (Exception e) { LOG.error("HttpMultiPartParser-> error: " + e.getMessage(), e); if (os != null) { os.close(); } if (saveFiles && path != null) { File thisFile = new File(path); if (thisFile.exists()) { thisFile.delete(); LOG.warn("HttpMultiPartParser-> Temporary file deleted"); } } } } return dataTable; } /** * Compares boundary string to byte array * * @param boundary Description of Parameter * @param ba Description of Parameter * @return Description of the Returned Value */ private boolean compareBoundary(String boundary, byte ba[]) { if (boundary == null || ba == null) { return false; } for (int i = 0; i < boundary.length(); i++) { if ((byte) boundary.charAt(i) != ba[i]) { return false; } } return true; } }