001: /*
002: * File upload filter request wrapper
003: * Copyright (C) 2004 Rick Knowles
004: *
005: * This program is free software; you can redistribute it and/or
006: * modify it under the terms of the GNU Library General Public License
007: * Version 2 as published by the Free Software Foundation.
008: *
009: * This program is distributed in the hope that it will be useful,
010: * but WITHOUT ANY WARRANTY; without even the implied warranty of
011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
012: * GNU General Public License Version 2 for more details.
013: *
014: * You should have received a copy of the GNU Library General Public License
015: * Version 2 along with this program; if not, write to the Free Software
016: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
017: */
018: package dinamica.upload;
019:
020: import java.io.ByteArrayInputStream;
021: import java.io.ByteArrayOutputStream;
022: import java.io.File;
023: import java.io.FileInputStream;
024: import java.io.FileOutputStream;
025: import java.io.IOException;
026: import java.io.InputStream;
027: import java.io.OutputStream;
028: import java.util.Collections;
029: import java.util.Enumeration;
030: import java.util.HashMap;
031: import java.util.HashSet;
032: import java.util.Iterator;
033: import java.util.Map;
034: import java.util.Set;
035: import javax.activation.DataSource;
036: import javax.mail.MessagingException;
037: import javax.mail.internet.MimeBodyPart;
038: import javax.mail.internet.MimeMultipart;
039: import javax.servlet.ServletInputStream;
040: import javax.servlet.ServletRequest;
041: import javax.servlet.http.HttpServletRequest;
042: import javax.servlet.http.HttpServletRequestWrapper;
043:
044: /**
045: * This class is used to encapsulate the decoding of HTTP POST requests
046: * using the "multipart/form-data" encoding type.
047: * <br/>
048: * <br/>
049: * It uses Javamail for Mime libraries and the JavaBeans Activation
050: * Framework (JAF), so make sure you have activation.jar and mail.jar
051: * in the class path before using this class.
052: * <br/>
053: * <br/>
054: * Note: The servlet input stream is empty after the contructor executes.
055: * This prevents the use of this class on the same request twice.
056: *
057: * @author <a href="mailto:rick_knowles@hotmail.com">Rick Knowles</a>
058: * @version $Id: MultipartRequestWrapper.java,v 1.3 2005/09/08 02:42:02 rickknowles Exp $
059: */
060: public class MultipartRequestWrapper extends HttpServletRequestWrapper {
061: public final static String MPH_ATTRIBUTE = "MultipartRequestWrapper.reference";
062:
063: @SuppressWarnings("unchecked")
064: private Map stringParameters;
065: @SuppressWarnings("unchecked")
066: private Map tempFileNames;
067: @SuppressWarnings("unchecked")
068: private Map mimeTypes;
069: @SuppressWarnings("unchecked")
070: private Map uploadFileNames;
071:
072: /**
073: * Constructor - this uses a servlet request, validating it to make
074: * sure it's a multipart/form-data request, then reads the
075: * ServletInputStream, storing the results after Mime decoding in
076: * a member array. Use getParameter etc to retrieve the contents.
077: * @param request The Servlet's request object.
078: */
079: @SuppressWarnings("unchecked")
080: public MultipartRequestWrapper(ServletRequest request)
081: throws IOException {
082: super ((HttpServletRequest) request);
083: String contentType = request.getContentType();
084:
085: if (!contentType.toLowerCase()
086: .startsWith("multipart/form-data")) {
087: throw new IOException(
088: "The MIME Content-Type of the Request must be "
089: + '"' + "multipart/form-data" + '"'
090: + ", not " + '"' + contentType + '"' + ".");
091: }
092: // If we find a request attribute with an mph already present, copy from that
093: else if (request.getAttribute(MPH_ATTRIBUTE) != null) {
094: MultipartRequestWrapper oldMPH = (MultipartRequestWrapper) request
095: .getAttribute(MPH_ATTRIBUTE);
096:
097: this .stringParameters = oldMPH.stringParameters;
098: this .mimeTypes = oldMPH.mimeTypes;
099: this .tempFileNames = oldMPH.tempFileNames;
100: this .uploadFileNames = oldMPH.uploadFileNames;
101:
102: return;
103: }
104:
105: try {
106: ByteArrayOutputStream byteArray = new ByteArrayOutputStream();
107: InputStream inputServlet = request.getInputStream();
108: byte buffer[] = new byte[2048];
109: int readBytes = inputServlet.read(buffer);
110: while (readBytes != -1) {
111: byteArray.write(buffer, 0, readBytes);
112: readBytes = inputServlet.read(buffer);
113: }
114: inputServlet.close();
115: MimeMultipart parts = new MimeMultipart(
116: new MultipartRequestWrapperDataSource(contentType,
117: byteArray.toByteArray()));
118: byteArray.close();
119:
120: Map stringParameters = new HashMap();
121: Map mimeTypes = new HashMap();
122: Map tempFileNames = new HashMap();
123: Map uploadFileNames = new HashMap();
124: String encoding = request.getCharacterEncoding();
125: if (encoding == null) {
126: encoding = "8859_1";
127: }
128:
129: for (int loopCount = 0; loopCount < parts.getCount(); loopCount++) {
130: MimeBodyPart current = (MimeBodyPart) parts
131: .getBodyPart(loopCount);
132: String headers = current.getHeader(
133: "Content-Disposition", "; ");
134:
135: // Get the name field
136: if (headers.indexOf(" name=" + '"') == -1) {
137: throw new MessagingException(
138: "No name header found in "
139: + "Content-Disposition field.");
140: } else {
141: // Get the name field
142: String namePart = headers.substring(headers
143: .indexOf(" name=\"") + 7);
144: namePart = namePart.substring(0, namePart
145: .indexOf('"'));
146: String nameField = javax.mail.internet.MimeUtility
147: .decodeText(namePart);
148:
149: InputStream inRaw = current.getInputStream();
150:
151: if (headers.indexOf(" filename=" + '"') != -1) {
152: String fileName = headers.substring(headers
153: .indexOf(" filename=" + '"') + 11);
154: fileName = fileName.substring(0, fileName
155: .indexOf('"'));
156: if (fileName.lastIndexOf('/') != -1) {
157: fileName = fileName.substring(fileName
158: .lastIndexOf('/') + 1);
159: }
160: if (fileName.lastIndexOf('\\') != -1) {
161: fileName = fileName.substring(fileName
162: .lastIndexOf('\\') + 1);
163: }
164: uploadFileNames.put(nameField, fileName);
165:
166: if (tempFileNames.containsKey(nameField)) {
167: throw new IOException(
168: "Multiple parameters named "
169: + nameField);
170: }
171:
172: if (current.getContentType() == null) {
173: mimeTypes.put(nameField, "text/plain");
174: } else {
175: mimeTypes.put(nameField, current
176: .getContentType());
177: }
178:
179: // write out a file in temp space and store it
180: File tempFile = File.createTempFile("mph",
181: ".tmp");
182: OutputStream outStream = new FileOutputStream(
183: tempFile, true);
184: while ((readBytes = inRaw.read(buffer)) != -1) {
185: outStream.write(buffer, 0, readBytes);
186: }
187: inRaw.close();
188: outStream.close();
189: tempFileNames.put(nameField, tempFile
190: .getAbsoluteFile());
191: } else {
192: byte[] stash = new byte[inRaw.available()];
193: inRaw.read(stash);
194: inRaw.close();
195:
196: Object oldParam = stringParameters
197: .get(nameField);
198: if (oldParam == null) {
199: stringParameters.put(nameField,
200: new String[] { new String(stash,
201: encoding) });
202: } else {
203: String oldParams[] = (String[]) oldParam;
204: String newParams[] = new String[oldParams.length + 1];
205: System.arraycopy(oldParams, 0, newParams,
206: 0, oldParams.length);
207: newParams[oldParams.length] = new String(
208: stash, encoding);
209: stringParameters.put(nameField, newParams);
210: }
211: }
212: }
213: }
214:
215: parts = null;
216: this .stringParameters = Collections
217: .unmodifiableMap(stringParameters);
218: this .mimeTypes = Collections.unmodifiableMap(mimeTypes);
219: this .tempFileNames = Collections
220: .unmodifiableMap(tempFileNames);
221: this .uploadFileNames = Collections
222: .unmodifiableMap(uploadFileNames);
223:
224: // Set this handler into the request attribute set
225: request.setAttribute(MPH_ATTRIBUTE, this );
226: } catch (MessagingException errMime) {
227: throw new IOException(errMime);
228: }
229: }
230:
231: public String getParameter(String name) {
232: String parameterValues[] = getParameterValues(name);
233: if ((parameterValues == null) || (parameterValues.length == 0)) {
234: return null;
235: } else {
236: return parameterValues[0];
237: }
238: }
239:
240: @SuppressWarnings("unchecked")
241: public Map getParameterMap() {
242: Map paramMap = new HashMap();
243: for (Enumeration names = this .getParameterNames(); names
244: .hasMoreElements();) {
245: String name = (String) names.nextElement();
246: paramMap.put(name, getParameterValues(name));
247: }
248: return Collections.unmodifiableMap(paramMap);
249: }
250:
251: @SuppressWarnings("unchecked")
252: public Enumeration getParameterNames() {
253: Set names = new HashSet(this .stringParameters.keySet());
254: names.addAll(this .tempFileNames.keySet());
255: Enumeration parent = super .getParameterNames();
256: names.addAll(Collections.list(parent));
257: return Collections.enumeration(names);
258: }
259:
260: public ServletInputStream getInputStream() throws IOException {
261: throw new IOException(
262: "InputStream already parsed by the MultipartRequestWrapper class");
263: }
264:
265: public String[] getParameterValues(String name) {
266: // Try parent first - since after a forward we want that to have precendence
267: String parentValue[] = super .getParameterValues(name);
268: if (parentValue != null) {
269: return parentValue;
270: } else if (name == null) {
271: return null;
272: } else if (name.endsWith(".filename")
273: && isFileUploadParameter(name.substring(0, name
274: .length() - 9))) {
275: return new String[] { getUploadFileName(name.substring(0,
276: name.length() - 9)) };
277: } else if (name.endsWith(".content-type")
278: && isFileUploadParameter(name.substring(0, name
279: .length() - 13))) {
280: return new String[] { getContentType(name.substring(0, name
281: .length() - 13)) };
282: } else if (isNonFileUploadParameter(name)) {
283: return (String[]) this .stringParameters.get(name);
284: } else if (this .isFileUploadParameter(name)) {
285: return new String[] { ((File) this .tempFileNames.get(name))
286: .getAbsolutePath() };
287: } else {
288: return null;
289: }
290: }
291:
292: /**
293: * The byte array version of the parameter requested (as an Object).
294: * This always returns a byte array, ignoring the mime type of the
295: * submitted object.
296: * @param name The parameter you wish to retrieve.
297: * @return A byte array representation of the supplied parameter.
298: */
299: public byte[] getRawParameter(String name) throws IOException {
300: if (name == null) {
301: return null;
302: }
303: File tempFile = (File) this .tempFileNames.get(name);
304: if (tempFile == null) {
305: return null;
306: }
307: InputStream inFile = new FileInputStream(tempFile);
308: ByteArrayOutputStream byteArray = new ByteArrayOutputStream();
309: byte buffer[] = new byte[2048];
310: int readBytes = inFile.read(buffer);
311: while (readBytes != -1) {
312: byteArray.write(buffer, 0, readBytes);
313: readBytes = inFile.read(buffer);
314: }
315: inFile.close();
316: byte output[] = byteArray.toByteArray();
317: byteArray.close();
318: return output;
319: }
320:
321: /**
322: * Get the MimeType of a particular parameter.
323: * @param name The parameter you wish to find the Mime type of.
324: * @return The Mime type for the requested parameter (as specified
325: * in the Mime header during the post).
326: */
327: public String getContentType(String name) {
328: return (String) this .mimeTypes.get(name);
329: }
330:
331: /**
332: * The local (client) name of the file submitted if this parameter was
333: * a file.
334: * @param name The parameter you wish to find the file name for.
335: * @return The local name for the requested parameter (as specified
336: * in the Mime header during the post).
337: */
338: public String getUploadFileName(String name) {
339: return (String) this .uploadFileNames.get(name);
340: }
341:
342: public boolean isFileUploadParameter(String name) {
343: return this .tempFileNames.containsKey(name);
344: }
345:
346: public boolean isNonFileUploadParameter(String name) {
347: return this .stringParameters.containsKey(name);
348: }
349:
350: /**
351: * Retrieve a Map of the raw bytes of the parameters supplied in the HTTP POST request.
352: */
353: @SuppressWarnings("unchecked")
354: public Map getRawParameterMap() throws IOException {
355: Map output = new HashMap();
356: for (Iterator i = this .uploadFileNames.keySet().iterator(); i
357: .hasNext();) {
358: String key = (String) i.next();
359: output.put(key, getRawParameter(key));
360: }
361: return output;
362: }
363:
364: /**
365: * Retrieve a Map of the filenames supplied in the HTTP POST request.
366: */
367: @SuppressWarnings("unchecked")
368: public Map getContentTypeMap() {
369: return this .mimeTypes;
370: }
371:
372: /**
373: * Retrieve a Map of the filenames supplied in the HTTP POST request.
374: */
375: @SuppressWarnings("unchecked")
376: public Map getUploadFileNameMap() {
377: return this .uploadFileNames;
378: }
379:
380: private class MultipartRequestWrapperDataSource implements
381: DataSource {
382:
383: private byte mimeByteArray[];
384: private String contentType;
385:
386: private MultipartRequestWrapperDataSource(String contentType,
387: byte mimeByteArray[]) {
388: this .mimeByteArray = mimeByteArray;
389: this .contentType = contentType;
390: }
391:
392: /**
393: * Required for implementation of the DataSource interface.
394: * Internal use only.
395: */
396: public String getName() {
397: return "MultipartHandler";
398: }
399:
400: /**
401: * Required for implementation of the DataSource interface.
402: * Internal use only.
403: */
404: public String getContentType() {
405: return contentType;
406: }
407:
408: /**
409: * Required for implementation of the DataSource interface.
410: * Internal use only.
411: */
412: public java.io.InputStream getInputStream()
413: throws java.io.IOException {
414: return new ByteArrayInputStream(this .mimeByteArray);
415: }
416:
417: /**
418: * Required for implementation of the DataSource interface.
419: * Internal use only.
420: */
421: public java.io.OutputStream getOutputStream()
422: throws java.io.IOException {
423: throw new IOException("This is a read-only datasource.");
424: }
425: }
426: }
|