0001: /* *************************************************************************
0002:
0003: Millstone(TM)
0004: Open Sourced User Interface Library for
0005: Internet Development with Java
0006:
0007: Millstone is a registered trademark of IT Mill Ltd
0008: Copyright (C) 2000-2005 IT Mill Ltd
0009:
0010: *************************************************************************
0011:
0012: This library is free software; you can redistribute it and/or
0013: modify it under the terms of the GNU Lesser General Public
0014: license version 2.1 as published by the Free Software Foundation.
0015:
0016: This library is distributed in the hope that it will be useful,
0017: but WITHOUT ANY WARRANTY; without even the implied warranty of
0018: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0019: Lesser General Public License for more details.
0020:
0021: You should have received a copy of the GNU Lesser General Public
0022: License along with this library; if not, write to the Free Software
0023: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
0024:
0025: *************************************************************************
0026:
0027: For more information, contact:
0028:
0029: IT Mill Ltd phone: +358 2 4802 7180
0030: Ruukinkatu 2-4 fax: +358 2 4802 7181
0031: 20540, Turku email: info@itmill.com
0032: Finland company www: www.itmill.com
0033:
0034: Primary source for MillStone information and releases: www.millstone.org
0035:
0036: ********************************************************************** */
0037:
0038: package org.millstone.webadapter;
0039:
0040: import java.util.Hashtable;
0041: import java.io.BufferedOutputStream;
0042: import java.io.BufferedInputStream;
0043: import java.io.OutputStream;
0044: import java.io.InputStream;
0045: import java.io.PrintWriter;
0046: import java.io.ByteArrayOutputStream;
0047: import java.io.ByteArrayInputStream;
0048: import java.io.FileOutputStream;
0049: import java.io.UnsupportedEncodingException;
0050: import java.io.IOException;
0051: import java.util.Enumeration;
0052: import java.util.Vector;
0053: import java.io.File;
0054:
0055: /**
0056: A Multipart form data parser. Parses an input stream and writes out any files found,
0057: making available a hashtable of other url parameters. As of version 1.17 the files can
0058: be saved to memory, and optionally written to a database, etc.
0059:
0060: <BR>
0061: <BR>
0062: Copyright (C)2001 Jason Pell.
0063: <BR>
0064:
0065: <PRE>
0066: This library is free software; you can redistribute it and/or
0067: modify it under the terms of the GNU Lesser General Public
0068: License as published by the Free Software Foundation; either
0069: version 2.1 of the License, or (at your option) any later version.
0070: <BR>
0071: This library is distributed in the hope that it will be useful,
0072: but WITHOUT ANY WARRANTY; without even the implied warranty of
0073: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0074: Lesser General Public License for more details.
0075: <BR>
0076: You should have received a copy of the GNU Lesser General Public
0077: License along with this library; if not, write to the Free Software
0078: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
0079: <BR>
0080: Email: jasonpell@hotmail.com
0081: Url: http://www.geocities.com/jasonpell
0082: </PRE>
0083:
0084: @author Jason Pell
0085:
0086: @version 1.18 Fixed some serious bugs. A new method readAndWrite(InputStream in, OutputStream out) which now does
0087: the generic processing in common for readAndWriteFile and readFile. The differences are that now
0088: the two extra bytes at the end of a file upload are processed once, instead of after each line. Also
0089: if an empty file is encountered, an outputstream is opened, but then deleted if no data written to it.
0090: The getCharArray() method has been removed. Replaced by the new String(bytes, encoding) method using
0091: a specific encoding (Defaults to ISO-8859-1) to ensure that extended characters are supported.
0092: All strings are processed using this encoding. The addition of static methods setEncoding(String)
0093: and getEncoding() to allow the use of MultipartRequest with a specific encoding type. All instances
0094: of MultipartRequest will utilise the static charEncoding variable value, that the setEncoding() method
0095: can be used to set. Started to introduce support for multiple file uploads with the same form field
0096: name, but not completed for v1.18. 26/06/2001
0097: @version 1.17 A few _very_ minor fixes. Plus a cool new feature added. The ability to save files into memory.
0098: <b>Thanks to Mark Latham for the idea and some of the code.</b> 11/04/2001
0099: @version 1.16 Added support for multiple parameter values. Also fixed getCharArray(...) method to support
0100: parameters with non-english ascii values (ascii above 127). Thanks to Stefan Schmidt &
0101: Michael Elvers for this. (No fix yet for reported problems with Tomcat 3.2 or a single extra
0102: byte appended to uploads of certain files). By 1.17 hopefully will have a resolution for the
0103: second problem. 14/03/2001
0104: @version 1.15 A new parameter added, intMaxReadBytes, to allow arbitrary length files. Released under
0105: the LGPL (Lesser General Public License). 03/02/2001
0106: @version 1.14 Fix for IE problem with filename being empty. This is because IE includes a default Content-Type
0107: even when no file is uploaded. 16/02/2001
0108: @version 1.13 If an upload directory is not specified, then all file contents are sent into oblivion, but the
0109: rest of the parsing works as normal.
0110: @version 1.12 Fix, was allowing zero length files. Will not even create the output file until there is
0111: something to write. getFile(String) now returns null, if a zero length file was specified. 06/11/2000
0112: @version 1.11 Fix, in case Content-type is not specified.
0113: @version 1.1 Removed dependence on Servlets. Now passes in a generic InputStream instead.
0114: "Borrowed" readLine from Tomcat 3.1 ServletInputStream class,
0115: so we can remove some of the dependencies on ServletInputStream.
0116: Fixed bug where a empty INPUT TYPE="FILE" value, would cause an exception.
0117: @version 1.0 Initial Release.
0118: */
0119:
0120: public class MultipartRequest {
0121: /**
0122: Define Character Encoding method here.
0123: */
0124: private String charEncoding = "UTF-8";
0125:
0126: // If not null, send debugging out here.
0127: private PrintWriter debug = null;
0128:
0129: private Hashtable htParameters = null;
0130: private Hashtable htFiles = null;
0131:
0132: private String strBoundary = null;
0133:
0134: // If this Directory spec remains null, writing of files will be disabled...
0135: private File fileOutPutDirectory = null;
0136: private boolean loadIntoMemory = false;
0137:
0138: private long intContentLength = -1;
0139: private long intTotalRead = -1;
0140:
0141: /**
0142: Prevent a denial of service by defining this, will never read more data.
0143: If Content-Length is specified to be more than this, will throw an exception.
0144:
0145: This limits the maximum number of bytes to the value of an int, which is 2 Gigabytes.
0146: */
0147: public static final int MAX_READ_BYTES = Integer.MAX_VALUE;
0148:
0149: /**
0150: Defines the number of bytes to read per readLine call. 128K
0151: */
0152: public static final int READ_LINE_BLOCK = 1024 * 128;
0153:
0154: /**
0155: Store a read from the input stream here. Global so we do not keep creating new arrays each read.
0156: */
0157: private byte[] blockOfBytes = null;
0158:
0159: /**
0160: Type constant for File FILENAME.
0161: */
0162: public static final int FILENAME = 0;
0163:
0164: /**
0165: Type constant for the File CONTENT_TYPE.
0166: */
0167: public static final int CONTENT_TYPE = 1;
0168:
0169: /**
0170: Type constant for the File SIZE.
0171: */
0172: public static final int SIZE = 2;
0173:
0174: /**
0175: Type constant for the File CONTENTS.
0176:
0177: <b>Note: </b>Only used for file upload to memory.
0178: */
0179: public static final int CONTENTS = 3;
0180:
0181: /**
0182: This method should be called on the MultipartRequest itself, not on any
0183: instances of MultipartRequest, because this sets up the encoding for all
0184: instances of multipartrequest. You can set the encoding to null, in which
0185: case the default encoding will be applied. The default encoding if this method
0186: is not called has been set to ISO-8859-1, which seems to offer the best hope
0187: of support for international characters, such as german "Umlaut" characters.
0188:
0189: <p><b>Warning:</b> In multithreaded environments it is the responsibility of the
0190: implementer to make sure that this method is not called while another instance
0191: is being constructed. When an instance of MultipartRequest is constructed, it parses
0192: the input data, and uses the result of getEncoding() to convert between bytes and
0193: strings. If setEncoding() is called by another thread, while the private parse() is
0194: executing, the method will utilise this new encoding, which may cause serious
0195: problems.</p>
0196: */
0197: public void setEncoding(String enc)
0198: throws UnsupportedEncodingException {
0199: if (enc == null || enc.trim() == "")
0200: charEncoding = System.getProperty("file.encoding");
0201: else {
0202: // This will test the encoding for validity.
0203: new String(new byte[] { '\n' }, enc);
0204:
0205: charEncoding = enc;
0206: }
0207: }
0208:
0209: /**
0210: Returns the current encoding method.
0211: */
0212: public String getEncoding() {
0213: return charEncoding;
0214: }
0215:
0216: /**
0217: * Constructor.
0218: *
0219: * @param strContentTypeText The "Content-Type" HTTP header value.
0220: * @param intContentLength The "Content-Length" HTTP header value.
0221: * @param in The InputStream to read and parse.
0222: * @param strSaveDirectory The temporary directory to save the file from where they can then be moved to wherever by the
0223: * calling process. <b>If you specify <u>null</u> for this parameter, then any files uploaded
0224: * will be silently ignored.</b>
0225: *
0226: * @exception IllegalArgumentException If the strContentTypeText does not contain a Content-Type of "multipart/form-data" or the boundary is not found.
0227: * @exception IOException If the intContentLength is higher than MAX_READ_BYTES or strSaveDirectory is invalid or cannot be written to.
0228: *
0229: * @see #MAX_READ_BYTES
0230: */
0231: public MultipartRequest(String strContentTypeText,
0232: int intContentLength, InputStream in,
0233: String strSaveDirectory) throws IllegalArgumentException,
0234: IOException {
0235: this (null, strContentTypeText, intContentLength, in,
0236: strSaveDirectory, MAX_READ_BYTES);
0237: }
0238:
0239: /**
0240: * Constructor.
0241: *
0242: * @param strContentTypeText The "Content-Type" HTTP header value.
0243: * @param intContentLength The "Content-Length" HTTP header value.
0244: * @param in The InputStream to read and parse.
0245: * @param strSaveDirectory The temporary directory to save the file from where they can then be moved to wherever by the
0246: * calling process. <b>If you specify <u>null</u> for this parameter, then any files uploaded
0247: * will be silently ignored.</B>
0248: * @param intMaxReadBytes Overrides the MAX_BYTES_READ value, to allow arbitrarily long files.
0249: *
0250: * @exception IllegalArgumentException If the strContentTypeText does not contain a Content-Type of "multipart/form-data" or the boundary is not found.
0251: * @exception IOException If the intContentLength is higher than MAX_READ_BYTES or strSaveDirectory is invalid or cannot be written to.
0252: *
0253: * @see #MAX_READ_BYTES
0254: */
0255: public MultipartRequest(String strContentTypeText,
0256: int intContentLength, InputStream in,
0257: String strSaveDirectory, int intMaxReadBytes)
0258: throws IllegalArgumentException, IOException {
0259: this (null, strContentTypeText, intContentLength, in,
0260: strSaveDirectory, intMaxReadBytes);
0261: }
0262:
0263: /**
0264: * Constructor.
0265: *
0266: * @param debug A PrintWriter that can be used for debugging.
0267: * @param strContentTypeText The "Content-Type" HTTP header value.
0268: * @param intContentLength The "Content-Length" HTTP header value.
0269: * @param in The InputStream to read and parse.
0270: * @param strSaveDirectory The temporary directory to save the file from where they can then be moved to wherever by the
0271: * calling process. <b>If you specify <u>null</u> for this parameter, then any files uploaded
0272: * will be silently ignored.</B>
0273: *
0274: * @exception IllegalArgumentException If the strContentTypeText does not contain a Content-Type of "multipart/form-data" or the boundary is not found.
0275: * @exception IOException If the intContentLength is higher than MAX_READ_BYTES or strSaveDirectory is invalid or cannot be written to.
0276: *
0277: * @see #MAX_READ_BYTES
0278: * @deprecated Replaced by MultipartRequest(PrintWriter, String, int, InputStream, int)
0279: * You can specify MultipartRequest.MAX_READ_BYTES for the intMaxReadBytes parameter
0280: */
0281: public MultipartRequest(PrintWriter debug,
0282: String strContentTypeText, int intContentLength,
0283: InputStream in, String strSaveDirectory)
0284: throws IllegalArgumentException, IOException {
0285: this (debug, strContentTypeText, intContentLength, in,
0286: strSaveDirectory, MAX_READ_BYTES);
0287:
0288: }
0289:
0290: /**
0291: * Constructor - load into memory constructor
0292: *
0293: * @param debug A PrintWriter that can be used for debugging.
0294: * @param strContentTypeText The "Content-Type" HTTP header value.
0295: * @param intContentLength The "Content-Length" HTTP header value.
0296: * @param in The InputStream to read and parse.
0297: * @param intMaxReadBytes Overrides the MAX_BYTES_READ value, to allow arbitrarily long files.
0298: *
0299: * @exception IllegalArgumentException If the strContentTypeText does not contain a Content-Type of "multipart/form-data" or the boundary is not found.
0300: * @exception IOException If the intContentLength is higher than MAX_READ_BYTES or strSaveDirectory is invalid or cannot be written to.
0301: *
0302: * @see #MAX_READ_BYTES
0303: */
0304: public MultipartRequest(PrintWriter debug,
0305: String strContentTypeText, int intContentLength,
0306: InputStream in, int intMaxReadBytes)
0307: throws IllegalArgumentException, IOException {
0308: this .loadIntoMemory = true;
0309:
0310: // Now initialise the object, which will actually call the parse method to parse multipart stream.
0311: init(debug, strContentTypeText, intContentLength, in,
0312: intMaxReadBytes);
0313: }
0314:
0315: /**
0316: * Constructor.
0317: *
0318: * @param debug A PrintWriter that can be used for debugging.
0319: * @param strContentTypeText The "Content-Type" HTTP header value.
0320: * @param intContentLength The "Content-Length" HTTP header value.
0321: * @param in The InputStream to read and parse.
0322: * @param strSaveDirectory The temporary directory to save the file from where they can then be moved to wherever by the
0323: * calling process. <b>If you specify <u>null</u> for this parameter, then any files uploaded
0324: * will be silently ignored.</B>
0325: * @param intMaxReadBytes Overrides the MAX_BYTES_READ value, to allow arbitrarily long files.
0326: *
0327: * @exception IllegalArgumentException If the strContentTypeText does not contain a Content-Type of "multipart/form-data" or the boundary is not found.
0328: * @exception IOException If the intContentLength is higher than MAX_READ_BYTES or strSaveDirectory is invalid or cannot be written to.
0329: *
0330: * @see #MAX_READ_BYTES
0331: */
0332: public MultipartRequest(PrintWriter debug,
0333: String strContentTypeText, int intContentLength,
0334: InputStream in, String strSaveDirectory, int intMaxReadBytes)
0335: throws IllegalArgumentException, IOException {
0336: // IF strSaveDirectory == NULL, then we should ignore any files uploaded.
0337: if (strSaveDirectory != null) {
0338: fileOutPutDirectory = new File(strSaveDirectory);
0339: if (!fileOutPutDirectory.exists())
0340: throw new IOException("Directory [" + strSaveDirectory
0341: + "] is invalid.");
0342: else if (!fileOutPutDirectory.canWrite())
0343: throw new IOException("Directory [" + strSaveDirectory
0344: + "] is readonly.");
0345: }
0346:
0347: // Now initialise the object, which will actually call the parse method to parse multipart stream.
0348: init(debug, strContentTypeText, intContentLength, in,
0349: intMaxReadBytes);
0350: }
0351:
0352: /**
0353: * Initialise the parser.
0354: *
0355: * @param debug A PrintWriter that can be used for debugging.
0356: * @param strContentTypeText The "Content-Type" HTTP header value.
0357: * @param intContentLength The "Content-Length" HTTP header value.
0358: * @param in The InputStream to read and parse.
0359: * @param strSaveDirectory The temporary directory to save the file from where they can then be moved to wherever by the
0360: * calling process. <b>If you specify <u>null</u> for this parameter, then any files uploaded
0361: * will be silently ignored.</B>
0362: * @param intMaxReadBytes Overrides the MAX_BYTES_READ value, to allow arbitrarily long files.
0363: *
0364: * @exception IllegalArgumentException If the strContentTypeText does not contain a Content-Type of "multipart/form-data" or the boundary is not found.
0365: * @exception IOException If the intContentLength is higher than MAX_READ_BYTES or strSaveDirectory is invalid or cannot be written to.
0366: *
0367: * @see #MAX_READ_BYTES
0368: */
0369: private void init(PrintWriter debug, String strContentTypeText,
0370: int intContentLength, InputStream in, int intMaxReadBytes)
0371: throws IllegalArgumentException, IOException {
0372: // save reference to debug stream for later.
0373: this .debug = debug;
0374:
0375: if (strContentTypeText != null
0376: && strContentTypeText.startsWith("multipart/form-data")
0377: && strContentTypeText.indexOf("boundary=") != -1)
0378: strBoundary = strContentTypeText.substring(
0379: strContentTypeText.indexOf("boundary=")
0380: + "boundary=".length()).trim();
0381: else {
0382: // <mtl,jpell>
0383: debug("ContentType = " + strContentTypeText);
0384: throw new IllegalArgumentException("Invalid Content Type.");
0385: }
0386:
0387: this .intContentLength = intContentLength;
0388: // FIX: 115
0389: if (intContentLength > intMaxReadBytes)
0390: throw new IOException("Content Length Error ("
0391: + intContentLength + " > " + intMaxReadBytes + ")");
0392:
0393: // Instantiate the hashtable...
0394: htParameters = new Hashtable();
0395: htFiles = new Hashtable();
0396: blockOfBytes = new byte[READ_LINE_BLOCK];
0397:
0398: // Now parse the data.
0399: parse(new BufferedInputStream(in));
0400:
0401: // No need for this once parse is complete.
0402: this .blockOfBytes = null;
0403: this .debug = null;
0404: this .strBoundary = null;
0405: }
0406:
0407: /**
0408: Return the value of the strName URLParameter.
0409: If more than one value for a particular Parameter, will return the first.
0410: If an error occurs will return null.
0411: */
0412: public String getURLParameter(String strName) {
0413: Object value = htParameters.get(strName);
0414: if (value instanceof Vector)
0415: return (String) ((Vector) value).firstElement();
0416: else
0417: return (String) htParameters.get(strName);
0418: }
0419:
0420: /**
0421: Return an enumeration of all values for the strName parameter.
0422: Even if a single value for, will always return an enumeration, although
0423: it may actually be empty if no value was encountered for strName or
0424: it is an invalid parameter name.
0425: */
0426: public Enumeration getURLParameters(String strName) {
0427: Object value = htParameters.get(strName);
0428: if (value instanceof Vector)
0429: return ((Vector) value).elements();
0430: else {
0431: Vector vector = new Vector();
0432: if (value != null)
0433: vector.addElement(value);
0434: return vector.elements();
0435: }
0436: }
0437:
0438: /**
0439: An enumeration of all URL Parameters for the current HTTP Request.
0440: */
0441: public Enumeration getParameterNames() {
0442: return htParameters.keys();
0443: }
0444:
0445: /**
0446: This enumeration will return all INPUT TYPE=FILE parameter NAMES as encountered
0447: during the upload.
0448: */
0449: public Enumeration getFileParameterNames() {
0450: return htFiles.keys();
0451: }
0452:
0453: /**
0454: Returns the Content-Type of a file.
0455:
0456: @see #getFileParameter(java.lang.String, int)
0457: */
0458: public String getContentType(String strName) {
0459: // Can cast null, it will be ignored.
0460: return (String) getFileParameter(strName, CONTENT_TYPE);
0461: }
0462:
0463: /**
0464: If files were uploaded into memory, this method will retrieve the contents
0465: of the file as a InputStream.
0466:
0467: @return the contents of the file as a InputStream, or null if not file uploaded,
0468: or file uploaded to file system directory.
0469:
0470: @see #getFileParameter(java.lang.String, int)
0471: */
0472: public InputStream getFileContents(String strName) {
0473: Object obj = getFileParameter(strName, CONTENTS);
0474: if (obj != null)
0475: return new ByteArrayInputStream((byte[]) obj);
0476: else
0477: return null;
0478: }
0479:
0480: /**
0481: Returns a File reference to the uploaded file. This reference is to the files uploaded location,
0482: and allows you to read/move/delete the file.
0483:
0484: This method is only of use, if files were uploaded to the file system. Will return null if
0485: uploaded to memory, in which case you should use getFileContents(strName) instead.
0486:
0487: @return Returns a null file reference if a call to getFileSize(strName) returns zero or files were
0488: uploaded to memory.
0489:
0490: @see #getFileSize(java.lang.String)
0491: @see #getFileContents(java.lang.String)
0492: @see #getFileSystemName(java.lang.String)
0493: */
0494: public File getFile(String strName) {
0495: String filename = getFileSystemName(strName);
0496: // Fix: If fileOutPutDirectory is null, then we are ignoring any file contents, so we must return null.
0497: if (filename != null && getFileSize(strName) > 0
0498: && fileOutPutDirectory != null)
0499: return new File(fileOutPutDirectory, filename);
0500: else
0501: return null;
0502: }
0503:
0504: /**
0505: Get the file system basename of an uploaded file.
0506:
0507: @return null if strName not found.
0508:
0509: @see #getFileParameter(java.lang.String, int)
0510: */
0511: public String getFileSystemName(String strName) {
0512: // Can cast null, it will be ignored.
0513: return (String) getFileParameter(strName, FILENAME);
0514: }
0515:
0516: /**
0517: Returns the File Size of a uploaded file.
0518:
0519: @return -1 if file size not defined.
0520:
0521: @see #getFileParameter(java.lang.String, int)
0522: */
0523: public long getFileSize(String strName) {
0524: Object obj = getFileParameter(strName, SIZE);
0525: if (obj != null)
0526: return ((Long) obj).longValue();
0527: else
0528: return (long) -1;
0529: }
0530:
0531: /**
0532: Access an attribute of a file upload parameter record.
0533:
0534: @param strName is the form field name, used to upload the file. This identifies
0535: the formfield location in the storage facility.
0536:
0537: @param strFilename This is the FileSystemName of the file
0538: @param type What attribute you want from the File Parameter.
0539: The following types are supported:
0540: MultipartRequest.FILENAME,
0541: MultipartRequest.CONTENT_TYPE,
0542: MultipartRequest.SIZE,
0543: MultipartRequest.CONTENTS
0544:
0545: <p>The getFileSystemName(String strName),getFileSize(String strName),getContentType(String strName),
0546: getContents(String strName) methods all use this method passing in a different type argument.</p>
0547:
0548: <p><b>Note: </b>This class has been changed to provide for future functionality where you
0549: will be able to access all files uploaded, even if they are uploaded using the same
0550: form field name. At this point however, only the first file uploaded via a form
0551: field name is accessible.</p>
0552:
0553: @see #getContentType(java.lang.String)
0554: @see #getFileSize(java.lang.String)
0555: @see #getFileContents(java.lang.String)
0556: @see #getFileSystemName(java.lang.String)
0557: */
0558: public Object getFileParameter(String strName, int type) {
0559: Object[] objArray = null;
0560: Object value = htFiles.get(strName);
0561: if (value instanceof Vector)
0562: objArray = (Object[]) ((Vector) value).firstElement();
0563: else
0564: objArray = (Object[]) htFiles.get(strName);
0565:
0566: // Now ensure valid value.
0567: if (objArray != null && type >= FILENAME && type <= CONTENTS)
0568: return objArray[type];
0569: else
0570: return null;
0571: }
0572:
0573: /**
0574: This is the main parse method.
0575: */
0576: private void parse(InputStream in) throws IOException {
0577: String strContentType = null;
0578: String strName = null;
0579: String strFilename = null;
0580: String strLine = null;
0581: int read = -1;
0582:
0583: // First run through, check that the first line is a boundary, otherwise throw a exception as format incorrect.
0584: read = readLine(in, blockOfBytes);
0585: strLine = read > 0 ? new String(blockOfBytes, 0, read,
0586: charEncoding) : null;
0587:
0588: // Must be boundary at top of loop, otherwise we have finished.
0589: if (strLine == null || strLine.indexOf(this .strBoundary) == -1)
0590: // Just exit. Exception would be overkill
0591: return;
0592: //throw new IOException("Invalid Form Data, no boundary encountered.");
0593:
0594: // At the top of loop, we assume that the Content-Disposition line is next, otherwise we are at the end.
0595: while (true) {
0596: // Get Content-Disposition line.
0597: read = readLine(in, blockOfBytes);
0598: if (read <= 0)
0599: break; // Nothing to do.
0600: else {
0601: strLine = new String(blockOfBytes, 0, read,
0602: charEncoding);
0603:
0604: // Mac IE4 adds extra line after last boundary - 1.21
0605: if (strLine == null || strLine.length() == 0
0606: || strLine.trim().length() == 0)
0607: break;
0608:
0609: strName = trimQuotes(getValue("name", strLine));
0610: // If this is not null, it indicates that we are processing a filename.
0611: strFilename = trimQuotes(getValue("filename", strLine));
0612: // Now if not null, strip it of any directory information.
0613:
0614: if (strFilename != null) {
0615: // Fix: did not check whether filename was empty string indicating FILE contents were not passed.
0616: if (strFilename.length() > 0) {
0617: // Need to get the content type.
0618: read = readLine(in, blockOfBytes);
0619: strLine = read > 0 ? new String(blockOfBytes,
0620: 0, read, charEncoding) : null;
0621:
0622: strContentType = "application/octet-stream";
0623: // Fix 1.11: If not null AND strLine.length() is long enough.
0624: if (strLine != null
0625: && strLine.length() > "Content-Type: "
0626: .length())
0627: strContentType = strLine
0628: .substring("Content-Type: "
0629: .length());// Changed 1.13
0630: } else {
0631: // FIX 1.14: IE problem with empty filename.
0632: read = readLine(in, blockOfBytes);
0633: strLine = read > 0 ? new String(blockOfBytes,
0634: 0, read, charEncoding) : null;
0635:
0636: if (strLine != null
0637: && strLine.startsWith("Content-Type:"))
0638: readLine(in, blockOfBytes);
0639: }
0640: }
0641:
0642: // Ignore next line, as it should be blank.
0643: readLine(in, blockOfBytes);
0644:
0645: // No filename specified at all.
0646: if (strFilename == null) {
0647: String param = readParameter(in);
0648: addParameter(strName, param);
0649: } else {
0650: if (strFilename.length() > 0) {
0651: long filesize = -1;
0652: // Will remain null for read onto file system uploads.
0653: byte[] contentsOfFile = null;
0654:
0655: // Get the BASENAME version of strFilename.
0656: strFilename = getBasename(strFilename);
0657:
0658: // Are we loading files into memory instead of the filesystem?
0659: if (loadIntoMemory) {
0660: contentsOfFile = readFile(in);
0661: if (contentsOfFile != null)
0662: filesize = contentsOfFile.length;
0663: } else
0664: // Read the file onto file system.
0665: filesize = readAndWriteFile(in, strFilename);
0666:
0667: // Fix 1.18 for multiple FILE parameter values.
0668: if (filesize > 0)
0669: addFileParameter(strName,
0670: new Object[] { strFilename,
0671: strContentType,
0672: new Long(filesize),
0673: contentsOfFile });
0674: else
0675: // Zero length file.
0676: addFileParameter(strName, new Object[] {
0677: strFilename, null, new Long(0),
0678: null });
0679: } else // Fix: FILE INPUT TYPE, but no file passed as input...
0680: {
0681: addFileParameter(strName, new Object[] { null,
0682: null, null, null });
0683: readLine(in, blockOfBytes);
0684: }
0685: }
0686: }
0687: }// while
0688: }
0689:
0690: /**
0691: So we can put the logic for supporting multiple parameters with the same
0692: form field name in the one location.
0693: */
0694: private void addParameter(String strName, String value) {
0695: // Fix NPE in case of null name
0696: if (strName == null)
0697: return;
0698:
0699: // Fix 1.16: for multiple parameter values.
0700: Object objParms = htParameters.get(strName);
0701:
0702: // Add an new entry to the param vector.
0703: if (objParms instanceof Vector)
0704: ((Vector) objParms).addElement(value);
0705: else if (objParms instanceof String)// There is only one entry, so we create a vector!
0706: {
0707: Vector vecParms = new Vector();
0708: vecParms.addElement(objParms);
0709: vecParms.addElement(value);
0710:
0711: htParameters.put(strName, vecParms);
0712: } else
0713: // first entry for strName.
0714: htParameters.put(strName, value);
0715: }
0716:
0717: /**
0718: So we can put the logic for supporting multiple files with the same
0719: form field name in the one location.
0720:
0721: Assumes that this method will never be called with a null fileObj or strFilename.
0722: */
0723: private void addFileParameter(String strName, Object[] fileObj) {
0724: Object objParms = htFiles.get(strName);
0725:
0726: // Add an new entry to the param vector.
0727: if (objParms instanceof Vector)
0728: ((Vector) objParms).addElement(fileObj);
0729: else if (objParms instanceof Object[])// There is only one entry, so we create a vector!
0730: {
0731: Vector vecParms = new Vector();
0732: vecParms.addElement(objParms);
0733: vecParms.addElement(fileObj);
0734:
0735: htFiles.put(strName, vecParms);
0736: } else
0737: // first entry for strName.
0738: htFiles.put(strName, fileObj);
0739: }
0740:
0741: /**
0742: Read parameters, assume already passed Content-Disposition and blank line.
0743:
0744: @return the value read in.
0745: */
0746: private String readParameter(InputStream in) throws IOException {
0747: StringBuffer buf = new StringBuffer();
0748: int read = -1;
0749:
0750: String line = null;
0751: while (true) {
0752: read = readLine(in, blockOfBytes);
0753: if (read < 0)
0754: throw new IOException("Stream ended prematurely.");
0755:
0756: // Change v1.18: Only instantiate string once for performance reasons.
0757: line = new String(blockOfBytes, 0, read, charEncoding);
0758: if (read < blockOfBytes.length
0759: && line.indexOf(this .strBoundary) != -1)
0760: break; // Boundary found, we need to finish up.
0761: else
0762: buf.append(line);
0763: }
0764:
0765: if (buf.length() > 0)
0766: buf.setLength(getLengthMinusEnding(buf));
0767: return buf.toString();
0768: }
0769:
0770: /**
0771: Read from in, write to out, minus last two line ending bytes.
0772: */
0773: private long readAndWrite(InputStream in, OutputStream out)
0774: throws IOException {
0775: long fileSize = 0;
0776: int read = -1;
0777:
0778: // This variable will be assigned the bytes actually read.
0779: byte[] secondLineOfBytes = new byte[blockOfBytes.length];
0780: // So we do not have to keep creating the second array.
0781: int sizeOfSecondArray = 0;
0782:
0783: while (true) {
0784: read = readLine(in, blockOfBytes);
0785: if (read < 0)
0786: throw new IOException("Stream ended prematurely.");
0787:
0788: // Found boundary.
0789: if (read < blockOfBytes.length
0790: && new String(blockOfBytes, 0, read, charEncoding)
0791: .indexOf(this .strBoundary) != -1) {
0792: // Write the line, minus any line ending bytes.
0793: //The secondLineOfBytes will NEVER BE NON-NULL if out==null, so there is no need to included this in the test
0794: if (sizeOfSecondArray != 0) {
0795: // Only used once, so declare here.
0796: int actualLength = getLengthMinusEnding(
0797: secondLineOfBytes, sizeOfSecondArray);
0798: if (actualLength > 0 && out != null) {
0799: out.write(secondLineOfBytes, 0, actualLength);
0800: // Update file size.
0801: fileSize += actualLength;
0802: }
0803: }
0804: break;
0805: } else {
0806: // Write out previous line.
0807: //The sizeOfSecondArray will NEVER BE ZERO if out==null, so there is no need to included this in the test
0808: if (sizeOfSecondArray != 0) {
0809: out.write(secondLineOfBytes, 0, sizeOfSecondArray);
0810: // Update file size.
0811: fileSize += sizeOfSecondArray;
0812: }
0813:
0814: // out will always be null, so there is no need to reset sizeOfSecondArray to zero each time.
0815: if (out != null) {
0816: //Copy the read bytes into the array.
0817: System.arraycopy(blockOfBytes, 0,
0818: secondLineOfBytes, 0, read);
0819: // That is how many bytes to read from the secondLineOfBytes
0820: sizeOfSecondArray = read;
0821: }
0822: }
0823: }
0824:
0825: //Return the number of bytes written to outstream.
0826: return fileSize;
0827: }
0828:
0829: /**
0830: Read a Multipart section that is a file type. Assumes that the Content-Disposition/Content-Type and blank line
0831: have already been processed. So we read until we hit a boundary, then close file and return.
0832:
0833: @exception IOException if an error occurs writing the file.
0834:
0835: @return the number of bytes read.
0836: */
0837: private long readAndWriteFile(InputStream in, String strFilename)
0838: throws IOException {
0839: // Store a reference to this, as we may need to delete it later.
0840: File outFile = new File(fileOutPutDirectory, strFilename);
0841:
0842: BufferedOutputStream out = null;
0843: // Do not bother opening a OutputStream, if we cannot even write the file.
0844: if (fileOutPutDirectory != null)
0845: out = new BufferedOutputStream(
0846: new FileOutputStream(outFile));
0847:
0848: long count = readAndWrite(in, out);
0849: // Count would NOT be larger than zero if out was null.
0850: if (count > 0) {
0851: out.flush();
0852: out.close();
0853: } else {
0854: out.close();
0855: // Delete file as empty. We should be able to delete it, if we can open it!
0856: outFile.delete();
0857: }
0858: return count;
0859: }
0860:
0861: /**
0862: * If the fileOutPutDirectory wasn't specified, just read the file to memory.
0863: *
0864: * @param strName - Url parameter this file was loaded under.
0865: * @return contents of file, from which you can garner the size as well.
0866: */
0867: private byte[] readFile(InputStream in) throws IOException {
0868: // In this case, we do not need to worry about a outputdirectory.
0869: ByteArrayOutputStream out = new ByteArrayOutputStream();
0870:
0871: long count = readAndWrite(in, out);
0872: // Count would NOT be larger than zero if out was null.
0873: if (count > 0) {
0874: // Return contents of file to parse method for inclusion in htFiles object.
0875: return out.toByteArray();
0876: } else
0877: return null;
0878: }
0879:
0880: /**
0881: Returns the length of the line minus line ending.
0882:
0883: @param endOfArray This is because in many cases the byteLine will have garbage data at the end, so we
0884: act as though the actual end of the array is this parameter. If you want to process
0885: the complete byteLine, specify byteLine.length as the endOfArray parameter.
0886: */
0887: private static final int getLengthMinusEnding(byte byteLine[],
0888: int endOfArray) {
0889: if (byteLine == null)
0890: return 0;
0891:
0892: if (endOfArray >= 2 && byteLine[endOfArray - 2] == '\r'
0893: && byteLine[endOfArray - 1] == '\n')
0894: return endOfArray - 2;
0895: else if (endOfArray >= 1 && byteLine[endOfArray - 1] == '\n'
0896: || byteLine[endOfArray - 1] == '\r')
0897: return endOfArray - 1;
0898: else
0899: return endOfArray;
0900: }
0901:
0902: private static final int getLengthMinusEnding(StringBuffer buf) {
0903: if (buf.length() >= 2 && buf.charAt(buf.length() - 2) == '\r'
0904: && buf.charAt(buf.length() - 1) == '\n')
0905: return buf.length() - 2;
0906: else if (buf.length() >= 1
0907: && buf.charAt(buf.length() - 1) == '\n'
0908: || buf.charAt(buf.length() - 1) == '\r')
0909: return buf.length() - 1;
0910: else
0911: return buf.length();
0912: }
0913:
0914: /**
0915: Reads at most READ_BLOCK blocks of data, or a single line whichever is smaller.
0916: Returns -1, if nothing to read, or we have reached the specified content-length.
0917:
0918: Assumes that bytToBeRead.length indicates the block size to read.
0919:
0920: @return -1 if stream has ended, before a newline encountered (should never happen) OR
0921: we have read past the Content-Length specified. (Should also not happen). Otherwise
0922: return the number of characters read. You can test whether the number returned is less
0923: than bytesToBeRead.length, which indicates that we have read the last line of a file or parameter or
0924: a border line, or some other formatting stuff.
0925: */
0926: private int readLine(InputStream in, byte[] bytesToBeRead)
0927: throws IOException {
0928: // Ensure that there is still stuff to read...
0929: if (intTotalRead >= intContentLength)
0930: return -1;
0931:
0932: // Get the length of what we are wanting to read.
0933: int length = bytesToBeRead.length;
0934:
0935: // End of content, but some servers (apparently) may not realise this and end the InputStream, so
0936: // we cover ourselves this way.
0937: if (length > (intContentLength - intTotalRead))
0938: length = (int) (intContentLength - intTotalRead); // So we only read the data that is left.
0939:
0940: int result = readLine(in, bytesToBeRead, 0, length);
0941: // Only if we get actually read something, otherwise something weird has happened, such as the end of stream.
0942: if (result > 0)
0943: intTotalRead += result;
0944:
0945: return result;
0946: }
0947:
0948: /**
0949: This needs to support the possibility of a / or a \ separator.
0950:
0951: Returns strFilename after removing all characters before the last
0952: occurence of / or \.
0953: */
0954: private static final String getBasename(String strFilename) {
0955: if (strFilename == null)
0956: return strFilename;
0957:
0958: int intIndex = strFilename.lastIndexOf("/");
0959: if (intIndex == -1 || strFilename.lastIndexOf("\\") > intIndex)
0960: intIndex = strFilename.lastIndexOf("\\");
0961:
0962: if (intIndex != -1)
0963: return strFilename.substring(intIndex + 1);
0964: else
0965: return strFilename;
0966: }
0967:
0968: /**
0969: trimQuotes trims any quotes from the start and end of a string and returns the trimmed string...
0970: */
0971: private static final String trimQuotes(String strItem) {
0972: // Saves having to go any further....
0973: if (strItem == null || strItem.indexOf("\"") == -1)
0974: return strItem;
0975:
0976: // Get rid of any whitespace..
0977: strItem = strItem.trim();
0978:
0979: if (strItem.charAt(0) == '\"')
0980: strItem = strItem.substring(1);
0981:
0982: if (strItem.charAt(strItem.length() - 1) == '\"')
0983: strItem = strItem.substring(0, strItem.length() - 1);
0984:
0985: return strItem;
0986: }
0987:
0988: /**
0989: Format of string name=value; name=value;
0990:
0991: If not found, will return null.
0992: */
0993: private static final String getValue(String strName,
0994: String strToDecode) {
0995: strName = strName + "=";
0996:
0997: int startIndexOf = 0;
0998: while (startIndexOf < strToDecode.length()) {
0999: int indexOf = strToDecode.indexOf(strName, startIndexOf);
1000: // Ensure either first name, or a space or ; precedes it.
1001: if (indexOf != -1) {
1002: if (indexOf == 0
1003: || Character.isWhitespace(strToDecode
1004: .charAt(indexOf - 1))
1005: || strToDecode.charAt(indexOf - 1) == ';') {
1006: int endIndexOf = strToDecode.indexOf(";", indexOf
1007: + strName.length());
1008: if (endIndexOf == -1) // May return an empty string...
1009: return strToDecode.substring(indexOf
1010: + strName.length());
1011: else
1012: return strToDecode.substring(indexOf
1013: + strName.length(), endIndexOf);
1014: } else
1015: startIndexOf = indexOf + strName.length();
1016: } else
1017: return null;
1018: }
1019: return null;
1020: }
1021:
1022: /**
1023: * <I>Tomcat's ServletInputStream.readLine(byte[],int,int) Slightly Modified to utilise in.read()</I>
1024: * <BR>
1025: * Reads the input stream, one line at a time. Starting at an
1026: * offset, reads bytes into an array, until it reads a certain number
1027: * of bytes or reaches a newline character, which it reads into the
1028: * array as well.
1029: *
1030: * <p>This method <u><b>does not</b></u> returns -1 if it reaches the end of the input
1031: * stream before reading the maximum number of bytes, it returns -1, if no bytes read.
1032: *
1033: * @param b an array of bytes into which data is read
1034: *
1035: * @param off an integer specifying the character at which
1036: * this method begins reading
1037: *
1038: * @param len an integer specifying the maximum number of
1039: * bytes to read
1040: *
1041: * @return an integer specifying the actual number of bytes
1042: * read, or -1 if the end of the stream is reached
1043: *
1044: * @exception IOException if an input or output exception has occurred
1045: *
1046:
1047: Note: We have a problem with Tomcat reporting an erroneous number of bytes, so we need to check this.
1048: This is the method where we get an infinite loop, but only with binary files.
1049: */
1050: private int readLine(InputStream in, byte[] b, int off, int len)
1051: throws IOException {
1052: if (len <= 0)
1053: return 0;
1054:
1055: int count = 0, c;
1056:
1057: while ((c = in.read()) != -1) {
1058: b[off++] = (byte) c;
1059: count++;
1060: if (c == '\n' || count == len)
1061: break;
1062: }
1063:
1064: return count > 0 ? count : -1;
1065: }
1066:
1067: /**
1068: Use when debugging this object.
1069: */
1070: protected void debug(String x) {
1071: if (debug != null) {
1072: debug.println(x);
1073: debug.flush();
1074: }
1075: }
1076:
1077: /**
1078: For debugging.
1079: */
1080: public String getHtmlTable() {
1081: StringBuffer sbReturn = new StringBuffer();
1082:
1083: sbReturn.append("<h2>Parameters</h2>");
1084: sbReturn
1085: .append("\n<table border=3><tr><td><b>Name</b></td><td><b>Value</b></td></tr>");
1086: for (Enumeration e = getParameterNames(); e.hasMoreElements();) {
1087: String strName = (String) e.nextElement();
1088: sbReturn.append("\n<tr>" + "<td>" + strName + "</td>");
1089:
1090: sbReturn.append("<td><table border=1><tr>");
1091: for (Enumeration f = getURLParameters(strName); f
1092: .hasMoreElements();) {
1093: String value = (String) f.nextElement();
1094: sbReturn.append("<td>" + value + "</td>");
1095: }
1096: sbReturn.append("</tr></table></td></tr>");
1097: }
1098: sbReturn.append("</table>");
1099:
1100: sbReturn.append("<h2>File Parameters</h2>");
1101:
1102: sbReturn
1103: .append("\n<table border=2><tr><td><b>Name</b></td><td><b>Filename</b></td><td><b>Path</b></td><td><b>Content Type</b></td><td><b>Size</b></td></tr>");
1104: for (Enumeration e = getFileParameterNames(); e
1105: .hasMoreElements();) {
1106: String strName = (String) e.nextElement();
1107:
1108: sbReturn
1109: .append("\n<tr>"
1110: + "<td>"
1111: + strName
1112: + "</td>"
1113: + "<td>"
1114: + (getFileSystemName(strName) != null ? getFileSystemName(strName)
1115: : "") + "</td>");
1116:
1117: if (loadIntoMemory)
1118: sbReturn
1119: .append("<td>"
1120: + (getFileSize(strName) > 0 ? "<i>in memory</i>"
1121: : "") + "</td>");
1122: else
1123: sbReturn.append("<td>"
1124: + (getFile(strName) != null ? getFile(strName)
1125: .getAbsolutePath() : "") + "</td>");
1126:
1127: sbReturn
1128: .append("<td>"
1129: + (getContentType(strName) != null ? getContentType(strName)
1130: : "")
1131: + "</td>"
1132: + "<td>"
1133: + (getFileSize(strName) != -1 ? getFileSize(strName)
1134: + ""
1135: : "") + "</td>" + "</tr>");
1136: }
1137: sbReturn.append("</table>");
1138:
1139: return sbReturn.toString();
1140: }
1141: }
|