001: /*
002: * $Header: /home/cvs/jakarta-commons/fileupload/src/java/org/apache/commons/fileupload/AFileUploadBase.java,v 1.3 2003/06/01 00:18:13 martinc Exp $
003: * $Revision: 1.3 $
004: * $Date: 2003/06/01 00:18:13 $
005: *
006: * ====================================================================
007: *
008: * The Apache Software License, Version 1.1
009: *
010: * Copyright (c) 2001-2003 The Apache Software Foundation. All rights
011: * reserved.
012: *
013: * Redistribution and use in source and binary forms, with or without
014: * modification, are permitted provided that the following conditions
015: * are met:
016: *
017: * 1. Redistributions of source code must retain the above copyright
018: * notice, this list of conditions and the following disclaimer.
019: *
020: * 2. Redistributions in binary form must reproduce the above copyright
021: * notice, this list of conditions and the following disclaimer in
022: * the documentation and/or other materials provided with the
023: * distribution.
024: *
025: * 3. The end-user documentation included with the redistribution, if
026: * any, must include the following acknowlegement:
027: * "This product includes software developed by the
028: * Apache Software Foundation (http://www.apache.org/)."
029: * Alternately, this acknowlegement may appear in the software itself,
030: * if and wherever such third-party acknowlegements normally appear.
031: *
032: * 4. The names "The Jakarta Project", "Commons", and "Apache Software
033: * Foundation" must not be used to endorse or promote products derived
034: * from this software without prior written permission. For written
035: * permission, please contact apache@apache.org.
036: *
037: * 5. Products derived from this software may not be called "Apache"
038: * nor may "Apache" appear in their names without prior written
039: * permission of the Apache Group.
040: *
041: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
042: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
043: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
044: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
045: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
046: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
047: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
048: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
049: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
050: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
051: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
052: * SUCH DAMAGE.
053: * ====================================================================
054: *
055: * This software consists of voluntary contributions made by many
056: * individuals on behalf of the Apache Software Foundation. For more
057: * information on the Apache Software Foundation, please see
058: * <http://www.apache.org/>.
059: *
060: */
061:
062: package de.ug2t.extTools.httpFileUpLoad;
063:
064: import java.io.*;
065: import java.util.*;
066:
067: import javax.servlet.http.*;
068:
069: /**
070: * <p>
071: * High level API for processing file uploads.
072: * </p>
073: *
074: * <p>
075: * This class handles multiple files per single MARKUP widget, sent using
076: * <code>multipart/mixed</code> encoding type, as specified by <a
077: * href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>. Use {@link
078: * #parseRequest(HttpServletRequest)} to acquire a list of {@link
079: * org.apache.commons.fileupload.FileItem}s associated with a given MARKUP
080: * widget.
081: * </p>
082: *
083: * <p>
084: * How the data for individual parts is stored is determined by the factory used
085: * to create them; a given part may be in memory, on disk, or somewhere else.
086: * </p>
087: *
088: * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
089: * @author <a href="mailto:dlr@collab.net">Daniel Rall</a>
090: * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
091: * @author <a href="mailto:jmcnally@collab.net">John McNally</a>
092: * @author <a href="mailto:martinc@apache.org">Martin Cooper</a>
093: * @author Sean C. Sullivan
094: *
095: * @version $Id: AFileUploadBase.java,v 1.3 2003/06/01 00:18:13 martinc Exp $
096: */
097: public abstract class AFileUploadBase {
098:
099: // ---------------------------------------------------------- Class methods
100:
101: /**
102: * Utility method that determines whether the request contains multipart
103: * content.
104: *
105: * @param req
106: * The servlet request to be evaluated. Must be non-null.
107: *
108: * @return <code>true</code> if the request is multipart; <code>false</code>
109: * otherwise.
110: */
111: public static final boolean isMultipartContent(
112: HttpServletRequest req) {
113: String contentType = req.getHeader(CONTENT_TYPE);
114: if (contentType == null) {
115: return false;
116: }
117: if (contentType.startsWith(MULTIPART)) {
118: return true;
119: }
120: return false;
121: }
122:
123: // ----------------------------------------------------- Manifest constants
124:
125: /**
126: * HTTP content type header name.
127: */
128: public static final String CONTENT_TYPE = "Content-type";
129:
130: /**
131: * HTTP content disposition header name.
132: */
133: public static final String CONTENT_DISPOSITION = "Content-disposition";
134:
135: /**
136: * Content-disposition value for form data.
137: */
138: public static final String FORM_DATA = "form-data";
139:
140: /**
141: * Content-disposition value for file attachment.
142: */
143: public static final String ATTACHMENT = "attachment";
144:
145: /**
146: * Part of HTTP content type header.
147: */
148: public static final String MULTIPART = "multipart/";
149:
150: /**
151: * HTTP content type header for multipart forms.
152: */
153: public static final String MULTIPART_FORM_DATA = "multipart/form-data";
154:
155: /**
156: * HTTP content type header for multiple uploads.
157: */
158: public static final String MULTIPART_MIXED = "multipart/mixed";
159:
160: /**
161: * The maximum length of a single header line that will be parsed (1024
162: * bytes).
163: */
164: public static final int MAX_HEADER_SIZE = 1024;
165:
166: // ----------------------------------------------------------- Data members
167:
168: /**
169: * The maximum size permitted for an uploaded file. A value of -1 indicates no
170: * maximum.
171: */
172: private long sizeMax = -1;
173:
174: /**
175: * The content encoding to use when reading part headers.
176: */
177: private String headerEncoding;
178:
179: // ----------------------------------------------------- Property accessors
180:
181: /**
182: * Returns the factory class used when creating file items.
183: *
184: * @return The factory class for new file items.
185: */
186: public abstract FileItemFactory getFileItemFactory();
187:
188: /**
189: * Sets the factory class to use when creating file items.
190: *
191: * @param factory
192: * The factory class for new file items.
193: */
194: public abstract void setFileItemFactory(FileItemFactory factory);
195:
196: /**
197: * Returns the maximum allowed upload size.
198: *
199: * @return The maximum allowed size, in bytes.
200: *
201: * @see #setSizeMax(long)
202: *
203: */
204: public long getSizeMax() {
205: return sizeMax;
206: }
207:
208: /**
209: * Sets the maximum allowed upload size. If negative, there is no maximum.
210: *
211: * @param sizeMax
212: * The maximum allowed size, in bytes, or -1 for no maximum.
213: *
214: * @see #getSizeMax()
215: *
216: */
217: public void setSizeMax(long sizeMax) {
218: this .sizeMax = sizeMax;
219: }
220:
221: /**
222: * Retrieves the character encoding used when reading the headers of an
223: * individual part. When not specified, or <code>null</code>, the platform
224: * default encoding is used.
225: *
226: * @return The encoding used to read part headers.
227: */
228: public String getHeaderEncoding() {
229: return headerEncoding;
230: }
231:
232: /**
233: * Specifies the character encoding to be used when reading the headers of
234: * individual parts. When not specified, or <code>null</code>, the platform
235: * default encoding is used.
236: *
237: * @param encoding
238: * The encoding used to read part headers.
239: */
240: public void setHeaderEncoding(String encoding) {
241: headerEncoding = encoding;
242: }
243:
244: // --------------------------------------------------------- Public methods
245:
246: /**
247: * Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
248: * compliant <code>multipart/form-data</code> stream. If files are stored on
249: * disk, the path is given by <code>getRepository()</code>.
250: *
251: * @param req
252: * The servlet request to be parsed.
253: *
254: * @return A list of <code>FileItem</code> instances parsed from the
255: * request, in the order that they were transmitted.
256: *
257: * @exception FileUploadException
258: * if there are problems reading/parsing the request or storing
259: * files.
260: */
261: public List /* FileItem */parseRequest(HttpServletRequest req)
262: throws FileUploadException {
263: if (null == req) {
264: throw new NullPointerException("req parameter");
265: }
266:
267: ArrayList items = new ArrayList();
268: String contentType = req.getHeader(CONTENT_TYPE);
269:
270: if ((null == contentType)
271: || (!contentType.startsWith(MULTIPART))) {
272: throw new InvalidContentTypeException(
273: "the request doesn't contain a "
274: + MULTIPART_FORM_DATA + " or "
275: + MULTIPART_MIXED
276: + " stream, content type header is "
277: + contentType);
278: }
279: int requestSize = req.getContentLength();
280:
281: if (requestSize == -1) {
282: throw new UnknownSizeException(
283: "the request was rejected because it's size is unknown");
284: }
285:
286: if (sizeMax >= 0 && requestSize > sizeMax) {
287: throw new SizeLimitExceededException(
288: "the request was rejected because "
289: + "it's size exceeds allowed range");
290: }
291:
292: try {
293: int boundaryIndex = contentType.indexOf("boundary=");
294: if (boundaryIndex < 0) {
295: throw new FileUploadException(
296: "the request was rejected because "
297: + "no multipart boundary was found");
298: }
299: byte[] boundary = contentType.substring(boundaryIndex + 9)
300: .getBytes();
301:
302: InputStream input = req.getInputStream();
303:
304: MultipartStream multi = new MultipartStream(input, boundary);
305: multi.setHeaderEncoding(headerEncoding);
306:
307: boolean nextPart = multi.skipPreamble();
308: while (nextPart) {
309: Map headers = parseHeaders(multi.readHeaders());
310: String fieldName = getFieldName(headers);
311: if (fieldName != null) {
312: String subContentType = getHeader(headers,
313: CONTENT_TYPE);
314: if (subContentType != null
315: && subContentType
316: .startsWith(MULTIPART_MIXED)) {
317: // Multiple files.
318: byte[] subBoundary = subContentType
319: .substring(
320: subContentType
321: .indexOf("boundary=") + 9)
322: .getBytes();
323: multi.setBoundary(subBoundary);
324: boolean nextSubPart = multi.skipPreamble();
325: while (nextSubPart) {
326: headers = parseHeaders(multi.readHeaders());
327: if (getFileName(headers) != null) {
328: FileItem item = createItem(headers,
329: false);
330: OutputStream os = item
331: .getOutputStream();
332: try {
333: multi.readBodyData(os);
334: } finally {
335: os.close();
336: }
337: items.add(item);
338: } else {
339: // Ignore anything but files inside
340: // multipart/mixed.
341: multi.discardBodyData();
342: }
343: nextSubPart = multi.readBoundary();
344: }
345: multi.setBoundary(boundary);
346: } else {
347: if (getFileName(headers) != null) {
348: // A single file.
349: FileItem item = createItem(headers, false);
350: OutputStream os = item.getOutputStream();
351: try {
352: multi.readBodyData(os);
353: } finally {
354: os.close();
355: }
356: items.add(item);
357: } else {
358: // A form field.
359: FileItem item = createItem(headers, true);
360: OutputStream os = item.getOutputStream();
361: try {
362: multi.readBodyData(os);
363: } finally {
364: os.close();
365: }
366: items.add(item);
367: }
368: }
369: } else {
370: // Skip this part.
371: multi.discardBodyData();
372: }
373: nextPart = multi.readBoundary();
374: }
375: } catch (IOException e) {
376: throw new FileUploadException("Processing of "
377: + MULTIPART_FORM_DATA + " request failed. "
378: + e.getMessage());
379: }
380:
381: return items;
382: }
383:
384: // ------------------------------------------------------ Protected methods
385:
386: /**
387: * Retrieves the file name from the <code>Content-disposition</code> header.
388: *
389: * @param headers
390: * A <code>Map</code> containing the HTTP request headers.
391: *
392: * @return The file name for the current <code>encapsulation</code>.
393: */
394: protected String getFileName(Map /* String, String */headers) {
395: String fileName = null;
396: String cd = getHeader(headers, CONTENT_DISPOSITION);
397: if (cd.startsWith(FORM_DATA) || cd.startsWith(ATTACHMENT)) {
398: int start = cd.indexOf("filename=\"");
399: int end = cd.indexOf('"', start + 10);
400: if (start != -1 && end != -1) {
401: fileName = cd.substring(start + 10, end).trim();
402: }
403: }
404: return fileName;
405: }
406:
407: /**
408: * Retrieves the field name from the <code>Content-disposition</code>
409: * header.
410: *
411: * @param headers
412: * A <code>Map</code> containing the HTTP request headers.
413: *
414: * @return The field name for the current <code>encapsulation</code>.
415: */
416: protected String getFieldName(Map /* String, String */headers) {
417: String fieldName = null;
418: String cd = getHeader(headers, CONTENT_DISPOSITION);
419: if (cd != null && cd.startsWith(FORM_DATA)) {
420: int start = cd.indexOf("name=\"");
421: int end = cd.indexOf('"', start + 6);
422: if (start != -1 && end != -1) {
423: fieldName = cd.substring(start + 6, end);
424: }
425: }
426: return fieldName;
427: }
428:
429: /**
430: * Creates a new {@link FileItem} instance.
431: *
432: * @param headers
433: * A <code>Map</code> containing the HTTP request headers.
434: * @param isFormField
435: * Whether or not this item is a form field, as opposed to a file.
436: *
437: * @return A newly created <code>FileItem</code> instance.
438: *
439: * @exception FileUploadException
440: * if an error occurs.
441: */
442: protected FileItem createItem(Map /* String, String */headers,
443: boolean isFormField) throws FileUploadException {
444: return getFileItemFactory().createItem(getFieldName(headers),
445: getHeader(headers, CONTENT_TYPE), isFormField,
446: getFileName(headers));
447: }
448:
449: /**
450: * <p>
451: * Parses the <code>header-part</code> and returns as key/value pairs.
452: *
453: * <p>
454: * If there are multiple headers of the same names, the name will map to a
455: * comma-separated list containing the values.
456: *
457: * @param headerPart
458: * The <code>header-part</code> of the current
459: * <code>encapsulation</code>.
460: *
461: * @return A <code>Map</code> containing the parsed HTTP request headers.
462: */
463: protected Map /* String, String */parseHeaders(String headerPart) {
464: Map headers = new HashMap();
465: char buffer[] = new char[MAX_HEADER_SIZE];
466: boolean done = false;
467: int j = 0;
468: int i;
469: String header, headerName, headerValue;
470: try {
471: while (!done) {
472: i = 0;
473: // Copy a single line of characters into the buffer,
474: // omitting trailing CRLF.
475: while (i < 2 || buffer[i - 2] != '\r'
476: || buffer[i - 1] != '\n') {
477: buffer[i++] = headerPart.charAt(j++);
478: }
479: header = new String(buffer, 0, i - 2);
480: if (header.equals("")) {
481: done = true;
482: } else {
483: if (header.indexOf(':') == -1) {
484: // This header line is malformed, skip it.
485: continue;
486: }
487: headerName = header.substring(0,
488: header.indexOf(':')).trim().toLowerCase();
489: headerValue = header.substring(
490: header.indexOf(':') + 1).trim();
491: if (getHeader(headers, headerName) != null) {
492: // More that one heder of that name exists,
493: // append to the list.
494: headers.put(headerName, getHeader(headers,
495: headerName)
496: + ',' + headerValue);
497: } else {
498: headers.put(headerName, headerValue);
499: }
500: }
501: }
502: } catch (IndexOutOfBoundsException e) {
503: // Headers were malformed. continue with all that was
504: // parsed.
505: }
506: return headers;
507: }
508:
509: /**
510: * Returns the header with the specified name from the supplied map. The
511: * header lookup is case-insensitive.
512: *
513: * @param headers
514: * A <code>Map</code> containing the HTTP request headers.
515: * @param name
516: * The name of the header to return.
517: *
518: * @return The value of specified header, or a comma-separated list if there
519: * were multiple headers of that name.
520: */
521: protected final String getHeader(Map /* String, String */headers,
522: String name) {
523: return (String) headers.get(name.toLowerCase());
524: }
525:
526: /**
527: * Thrown to indicate that the request is not a multipart request.
528: */
529: public static class InvalidContentTypeException extends
530: FileUploadException {
531: /**
532: * Constructs a <code>InvalidContentTypeException</code> with no detail
533: * message.
534: */
535: public InvalidContentTypeException() {
536: super ();
537: }
538:
539: /**
540: * Constructs an <code>InvalidContentTypeException</code> with the
541: * specified detail message.
542: *
543: * @param message
544: * The detail message.
545: */
546: public InvalidContentTypeException(String message) {
547: super (message);
548: }
549: }
550:
551: /**
552: * Thrown to indicate that the request size is not specified.
553: */
554: public static class UnknownSizeException extends
555: FileUploadException {
556: /**
557: * Constructs a <code>UnknownSizeException</code> with no detail message.
558: */
559: public UnknownSizeException() {
560: super ();
561: }
562:
563: /**
564: * Constructs an <code>UnknownSizeException</code> with the specified
565: * detail message.
566: *
567: * @param message
568: * The detail message.
569: */
570: public UnknownSizeException(String message) {
571: super (message);
572: }
573: }
574:
575: /**
576: * Thrown to indicate that the request size exceeds the configured maximum.
577: */
578: public static class SizeLimitExceededException extends
579: FileUploadException {
580: /**
581: * Constructs a <code>SizeExceededException</code> with no detail message.
582: */
583: public SizeLimitExceededException() {
584: super ();
585: }
586:
587: /**
588: * Constructs an <code>SizeExceededException</code> with the specified
589: * detail message.
590: *
591: * @param message
592: * The detail message.
593: */
594: public SizeLimitExceededException(String message) {
595: super(message);
596: }
597: }
598:
599: }
|