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