001: // Copyright (c) 2003-2007, Jodd Team (jodd.sf.net). All Rights Reserved.
002:
003: package jodd.servlet.upload;
004:
005: import jodd.servlet.ServletUtil;
006: import jodd.servlet.upload.impl.MemoryFileUpload;
007:
008: import javax.servlet.http.HttpServletRequest;
009: import java.io.IOException;
010: import java.io.UnsupportedEncodingException;
011: import java.util.ArrayList;
012: import java.util.Enumeration;
013: import java.util.HashMap;
014: import java.util.List;
015: import java.util.Map;
016: import java.util.Set;
017:
018: /**
019: * Handles multipart requests and extract uploaded files and parameters from
020: * it. Multipart forms should be defined as:
021: * <p>
022: *
023: * <code>
024: * <form method="post" enctype="multipart/form-data" accept-charset="<i>charset</i>"...
025: * </code>
026: *
027: * <p>
028: * "accept-charset" may be used in case when jsp page uses specific
029: * encoding. If default encoding is used, this attribute is not required.
030: *
031: * <p>
032: * MultipleRequest class may be created in two ways:<br>
033: * 1) with the constructors, when user must prevent instatiating more than once;<br>
034: * 2) using staic factory methods, which always return valid MultipleRequest instance.
035: *
036: * <p>
037: * This class loads complete request. To prevent big uploads (and potential
038: * DoS attacks) check content length <b>before</b> loading.
039: */
040: public class MultipartRequest {
041:
042: // ---------------------------------------------------------------- properties
043:
044: private Map<String, String[]> requestParameters;
045: private Map<String, FileUpload[]> requestFiles;
046: private HttpServletRequest request;
047: private String characterEncoding;
048: private FileUploadFactory fileUploadFactory;
049:
050: /**
051: * Returns actual http servlet request instance.
052: */
053: public HttpServletRequest getServletRequest() {
054: return request;
055: }
056:
057: /**
058: * Returns request content length. Usually used before loading, to check the upload size.
059: */
060: public int getContentLength() {
061: return request.getContentLength();
062: }
063:
064: /**
065: * Returns current encoding.
066: */
067: public String getCharacterEncoding() {
068: return characterEncoding;
069: }
070:
071: // ---------------------------------------------------------------- constructors
072:
073: /**
074: * @see #MultipartRequest(javax.servlet.http.HttpServletRequest, FileUploadFactory, String)
075: */
076: public MultipartRequest(HttpServletRequest request) {
077: this (request, null, null);
078: }
079:
080: /**
081: * @see #MultipartRequest(javax.servlet.http.HttpServletRequest, FileUploadFactory, String)
082: */
083: public MultipartRequest(HttpServletRequest request,
084: FileUploadFactory fileUploadFactory) {
085: this (request, fileUploadFactory, null);
086: }
087:
088: /**
089: * @see #MultipartRequest(javax.servlet.http.HttpServletRequest, FileUploadFactory, String)
090: */
091: public MultipartRequest(HttpServletRequest request, String encoding) {
092: this (request, null, encoding);
093: }
094:
095: /**
096: * Creates new Multipart request with form encoding and file upload factory.
097: * After construction stream is <b>not</b> yet parsed! Use {@link #parseMultipartRequest()} or
098: * {@link #parseRequest()} to parse before further usage.
099: *
100: * <p>
101: * If not specified, character encoding is read from the request.
102: *
103: * <p>
104: * Multiple instantiation doesn't work, since input stream can be parsed just once.
105: * Still, if it is needed, use {@link #getInstance(javax.servlet.http.HttpServletRequest, FileUploadFactory, String)}
106: * instead.
107: *
108: * @param request http request
109: * @param encoding form encoding or <code>null</code>
110: * @param fileUploadFactory file factory, or <code>null</code> for default factory
111: */
112: public MultipartRequest(HttpServletRequest request,
113: FileUploadFactory fileUploadFactory, String encoding) {
114: this .request = request;
115: if (encoding != null) {
116: this .characterEncoding = encoding;
117: } else {
118: this .characterEncoding = request.getCharacterEncoding();
119: }
120: this .fileUploadFactory = (fileUploadFactory == null ? new MemoryFileUpload.Factory()
121: : fileUploadFactory);
122: }
123:
124: // ---------------------------------------------------------------- factories
125:
126: private static String MREQ_ATTR_NAME = "jodd.servlet.upload.MultipartRequest";
127:
128: /**
129: * Returns a new instance of MultipleRequest if it was not created before during current request.
130: */
131: public static MultipartRequest getInstance(
132: HttpServletRequest request,
133: FileUploadFactory fileUploadFactory, String encoding) {
134: MultipartRequest mreq = (MultipartRequest) request
135: .getAttribute(MREQ_ATTR_NAME);
136: if (mreq == null) {
137: mreq = new MultipartRequest(request, fileUploadFactory,
138: encoding);
139: request.setAttribute(MREQ_ATTR_NAME, mreq);
140: }
141: return mreq;
142: }
143:
144: /**
145: * Returns parsed instance of MultipartRequest.
146: */
147: public static MultipartRequest getParsedInstance(
148: HttpServletRequest request,
149: FileUploadFactory fileUploadFactory, String encoding)
150: throws IOException {
151: MultipartRequest mreq = getInstance(request, fileUploadFactory,
152: encoding);
153: if (mreq.isLoaded() == false) {
154: mreq.parseRequest();
155: }
156: return mreq;
157: }
158:
159: public static MultipartRequest getInstance(
160: HttpServletRequest request, String encoding) {
161: return getInstance(request, null, encoding);
162: }
163:
164: public static MultipartRequest getParsedInstance(
165: HttpServletRequest request, String encoding)
166: throws IOException {
167: MultipartRequest mreq = getInstance(request, null, encoding);
168: if (mreq.isLoaded() == false) {
169: mreq.parseRequest();
170: }
171: return mreq;
172: }
173:
174: public static MultipartRequest getInstance(
175: HttpServletRequest request,
176: FileUploadFactory fileUploadFactory) {
177: return getInstance(request, fileUploadFactory, null);
178: }
179:
180: public static MultipartRequest getParsedInstance(
181: HttpServletRequest request,
182: FileUploadFactory fileUploadFactory) throws IOException {
183: MultipartRequest mreq = getInstance(request, fileUploadFactory,
184: null);
185: if (mreq.isLoaded() == false) {
186: mreq.parseRequest();
187: }
188: return mreq;
189: }
190:
191: public static MultipartRequest getInstance(
192: HttpServletRequest request) {
193: return getInstance(request, null, null);
194: }
195:
196: public static MultipartRequest getParsedInstance(
197: HttpServletRequest request) throws IOException {
198: MultipartRequest mreq = getInstance(request, null, null);
199: if (mreq.isLoaded() == false) {
200: mreq.parseRequest();
201: }
202: return mreq;
203: }
204:
205: // ---------------------------------------------------------------- load
206:
207: private boolean loaded;
208:
209: /**
210: * Returns <code>true</code> if multipart request is already loaded.
211: */
212: public boolean isLoaded() {
213: return loaded;
214: }
215:
216: /**
217: * Loads and parse multipart request. It <b>doesn't</b> check if request is multipart.
218: * Must be called on same request at most <b>once</b>.
219: */
220: public void parseMultipartRequest() throws IOException {
221: if (loaded == true) {
222: throw new IOException(
223: "Multipart request already loaded and parsed.");
224: }
225: loaded = true;
226: parseRequestStream(request, characterEncoding);
227: }
228:
229: /**
230: * Checks if request if multipart and parse it. If request is not multipart it
231: * copies all parameters, to make usage the same in both cases.
232: *
233: * @see MultipartRequestWrapper
234: */
235: public void parseRequest() throws IOException {
236: if (loaded == true) {
237: throw new IOException(
238: "Multipart request already loaded and parsed.");
239: }
240: loaded = true;
241: if (ServletUtil.isMultipartRequest(request) == true) {
242: parseRequestStream(request, characterEncoding);
243: } else {
244: requestParameters = new HashMap<String, String[]>();
245: requestFiles = new HashMap<String, FileUpload[]>();
246: Enumeration names = request.getParameterNames();
247: while (names.hasMoreElements()) {
248: String paramName = (String) names.nextElement();
249: String[] values = request.getParameterValues(paramName);
250: requestParameters.put(paramName, values);
251: }
252: }
253: }
254:
255: // ---------------------------------------------------------------- parameters
256:
257: /**
258: * Returns single value of a parameter. If parameter name is used for
259: * more then one parameter, only the first one will be returned.
260: *
261: * @return parameter value, or <code>null</code> if not found
262: */
263: public String getParameter(String paramName) {
264: String[] values = requestParameters.get(paramName);
265: if ((values != null) && (values.length > 0)) {
266: return values[0];
267: }
268: return null;
269: }
270:
271: /**
272: * Returns the names of the parameters contained in this request.
273: */
274: public Set<String> getParameterNames() {
275: return requestParameters.keySet();
276: }
277:
278: /**
279: * Returns all values all of the values the given request parameter has.
280: */
281: public String[] getParameterValues(String paramName) {
282: return requestParameters.get(paramName);
283: }
284:
285: /**
286: * Returns uploaded file.
287: * @param paramName parameter name of the uploaded file
288: * @return uploaded file or <code>null</code> if parameter name not found
289: */
290: public FileUpload getFile(String paramName) {
291: FileUpload[] values = requestFiles.get(paramName);
292: if ((values != null) && (values.length > 0)) {
293: return values[0];
294: }
295: return null;
296: }
297:
298: /**
299: * Returns all uploaded files the given request parameter has.
300: */
301: public FileUpload[] getFiles(String paramName) {
302: return requestFiles.get(paramName);
303: }
304:
305: /**
306: * Returns parameter names of all uploaded files.
307: */
308: public Set<String> getFileParameterNames() {
309: return requestFiles.keySet();
310: }
311:
312: // ---------------------------------------------------------------- load and extract
313:
314: private void putFile(Map<String, List<FileUpload>> destination,
315: String name, FileUpload value) {
316: List<FileUpload> valuesList = destination.get(name);
317: if (valuesList == null) {
318: valuesList = new ArrayList<FileUpload>();
319: destination.put(name, valuesList);
320: }
321: valuesList.add(value);
322: }
323:
324: private void putString(Map<String, List<String>> destination,
325: String name, String value) {
326: List<String> valuesList = destination.get(name);
327: if (valuesList == null) {
328: valuesList = new ArrayList<String>();
329: destination.put(name, valuesList);
330: }
331: valuesList.add(value);
332: }
333:
334: /**
335: * Extracts uploaded files and parameters from the request data.
336: */
337: protected void parseRequestStream(HttpServletRequest request,
338: String encoding) throws IOException {
339: HashMap<String, List<String>> reqParam = new HashMap<String, List<String>>();
340: HashMap<String, List<FileUpload>> reqFiles = new HashMap<String, List<FileUpload>>();
341:
342: MultipartRequestInputStream input = new MultipartRequestInputStream(
343: request.getInputStream());
344: input.readBoundary();
345: while (true) {
346: FileUploadHeader header = input.readDataHeader(encoding);
347: if (header == null) {
348: break;
349: }
350:
351: if (header.isFile && (header.fileName.length() > 0)) {
352: if (header.contentType
353: .indexOf("application/x-macbinary") > 0) {
354: input.skipBytes(128);
355: }
356: FileUpload newFile = fileUploadFactory.create(input);
357: newFile.processStream();
358: putFile(reqFiles, header.formFieldName, newFile);
359: } else {
360: MemoryFileUpload newFile = new MemoryFileUpload(input);
361: newFile.processStream();
362: if (header.isFile == true) {
363: // file was specified, but no name was provided, therefore it was not uploaded.
364: newFile.uploaded = false;
365: putFile(reqFiles, header.formFieldName, newFile);
366: } else {
367: // no file, therefore it is regular form parameter.
368: String value = null;
369: if (encoding != null) {
370: try {
371: value = new String(newFile.getFileData(),
372: encoding);
373: } catch (UnsupportedEncodingException ucex) {
374: value = null;
375: }
376: }
377: if (value == null) {
378: value = new String(newFile.getFileData());
379: }
380: putString(reqParam, header.formFieldName, value);
381: }
382: }
383: input.skipBytes(1);
384: input.mark(1);
385: if (input.readByte() == '-') {
386: input.reset();
387: break;
388: }
389: input.reset();
390: }
391:
392: // convert lists into arrays
393: for (String paramName : reqParam.keySet()) {
394: List valuesList = reqParam.get(paramName);
395: if (valuesList != null) {
396: String[] result = new String[valuesList.size()];
397: for (int i = 0; i < result.length; i++) {
398: result[i] = (String) valuesList.get(i);
399: }
400: requestParameters.put(paramName, result);
401: }
402: }
403: for (String paramName : reqFiles.keySet()) {
404: List<FileUpload> valuesList = reqFiles.get(paramName);
405: if (valuesList != null) {
406: FileUpload[] result = new FileUpload[valuesList.size()];
407: for (int i = 0; i < result.length; i++) {
408: result[i] = valuesList.get(i);
409: }
410: requestFiles.put(paramName, result);
411: }
412: }
413: }
414: }
|