1 package org.apache.turbine.services.upload;
2
3 /* ====================================================================
4 * The Apache Software License, Version 1.1
5 *
6 * Copyright (c) 2001 The Apache Software Foundation. All rights
7 * reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 *
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 *
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in
18 * the documentation and/or other materials provided with the
19 * distribution.
20 *
21 * 3. The end-user documentation included with the redistribution,
22 * if any, must include the following acknowledgment:
23 * "This product includes software developed by the
24 * Apache Software Foundation (http://www.apache.org/)."
25 * Alternately, this acknowledgment may appear in the software itself,
26 * if and wherever such third-party acknowledgments normally appear.
27 *
28 * 4. The names "Apache" and "Apache Software Foundation" and
29 * "Apache Turbine" must not be used to endorse or promote products
30 * derived from this software without prior written permission. For
31 * written permission, please contact apache@apache.org.
32 *
33 * 5. Products derived from this software may not be called "Apache",
34 * "Apache Turbine", nor may "Apache" appear in their name, without
35 * prior written permission of the Apache Software Foundation.
36 *
37 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
38 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
39 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
40 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
41 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
42 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
43 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
44 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
46 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
47 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
48 * SUCH DAMAGE.
49 * ====================================================================
50 *
51 * This software consists of voluntary contributions made by many
52 * individuals on behalf of the Apache Software Foundation. For more
53 * information on the Apache Software Foundation, please see
54 * <http://www.apache.org/>.
55 */
56
57 import java.io.IOException;
58 import java.io.InputStream;
59 import java.io.OutputStream;
60 import java.util.HashMap;
61 import java.util.Map;
62 import javax.servlet.http.HttpServletRequest;
63 import org.apache.turbine.util.ParameterParser;
64 import org.apache.turbine.util.TurbineException;
65 import org.apache.turbine.util.upload.FileItem;
66 import org.apache.turbine.util.upload.MultipartStream;
67
68 /***
69 * <p> This class is an implementation of {@link UploadService}.
70 *
71 * <p> Files will be stored in temporary disk storage on in memory,
72 * depending on request size, and will be available from the {@link
73 * org.apache.turbine.util.ParameterParser} as {@link
74 * org.apache.turbine.util.upload.FileItem}s.
75 *
76 * <p>This implementation of {@link UploadService} handles multiple
77 * files per single html widget, sent using multipar/mixed encoding
78 * type, as specified by RFC 1867. Use {@link
79 * org.apache.turbine.util.ParameterParser#getFileItems(String)} to
80 * acquire an array of {@link
81 * org.apache.turbine.util.upload.FileItem}s associated with given
82 * html widget.
83 *
84 * @author <a href="mailto:Rafal.Krzewski@e-point.pl">Rafal Krzewski</a>
85 * @author <a href="mailto:dlr@collab.net">Daniel Rall</a>
86 * @version $Id: TurbineUploadService.java,v 1.4 2002/07/11 16:53:23 mpoeschl Exp $
87 */
88 public class TurbineUploadService
89 extends BaseUploadService
90 {
91 /***
92 * <p> Processes an <a href="http://rf.cx/rfc1867.html">RFC
93 * 1867</a> compliant <code>multipart/form-data</code> stream.
94 *
95 * @param req The servlet request to be parsed.
96 * @param params The ParameterParser instance to insert form
97 * fields into.
98 * @param path The location where the files should be stored.
99 * @exception TurbineException If there are problems reading/parsing
100 * the request or storing files.
101 */
102 public void parseRequest( HttpServletRequest req,
103 ParameterParser params,
104 String path )
105 throws TurbineException
106 {
107 String contentType = req.getHeader(CONTENT_TYPE);
108 if(!contentType.startsWith(MULTIPART_FORM_DATA))
109 {
110 throw new TurbineException("the request doesn't contain a " +
111 MULTIPART_FORM_DATA + " stream");
112 }
113 int requestSize = req.getContentLength();
114 if(requestSize == -1)
115 {
116 throw new TurbineException("the request was rejected because " +
117 "it's size is unknown");
118 }
119 if(requestSize > TurbineUpload.getSizeMax())
120 {
121 throw new TurbineException("the request was rejected because " +
122 "it's size exceeds allowed range");
123 }
124
125 try
126 {
127 byte[] boundary = contentType.substring(
128 contentType.indexOf("boundary=")+9).getBytes();
129 InputStream input = (InputStream)req.getInputStream();
130
131 MultipartStream multi = new MultipartStream(input, boundary);
132 boolean nextPart = multi.skipPreamble();
133 while(nextPart)
134 {
135 Map headers = parseHeaders(multi.readHeaders());
136 String fieldName = getFieldName(headers);
137 if (fieldName != null)
138 {
139 String subContentType = getHeader(headers, CONTENT_TYPE);
140 if (subContentType != null && subContentType
141 .startsWith(MULTIPART_MIXED))
142 {
143 // Multiple files.
144 byte[] subBoundary =
145 subContentType.substring(
146 subContentType
147 .indexOf("boundary=")+9).getBytes();
148 multi.setBoundary(subBoundary);
149 boolean nextSubPart = multi.skipPreamble();
150 while (nextSubPart)
151 {
152 headers = parseHeaders(multi.readHeaders());
153 if (getFileName(headers) != null)
154 {
155 FileItem item = createItem(path, headers,
156 requestSize);
157 OutputStream os = item.getOutputStream();
158 try
159 {
160 multi.readBodyData(os);
161 }
162 finally
163 {
164 os.close();
165 }
166 params.append(getFieldName(headers), item);
167 }
168 else
169 {
170 // Ignore anything but files inside
171 // multipart/mixed.
172 multi.discardBodyData();
173 }
174 nextSubPart = multi.readBoundary();
175 }
176 multi.setBoundary(boundary);
177 }
178 else
179 {
180 if (getFileName(headers) != null)
181 {
182 // A single file.
183 FileItem item = createItem(path, headers,
184 requestSize);
185 OutputStream os = item.getOutputStream();
186 try
187 {
188 multi.readBodyData(os);
189 }
190 finally
191 {
192 os.close();
193 }
194 params.append(getFieldName(headers), item);
195 }
196 else
197 {
198 // A form field.
199 FileItem item = createItem(path, headers,
200 requestSize);
201 OutputStream os = item.getOutputStream();
202 try
203 {
204 multi.readBodyData(os);
205 }
206 finally
207 {
208 os.close();
209 }
210 params.append(getFieldName(headers),
211 new String(item.get()));
212 }
213 }
214 }
215 else
216 {
217 // Skip this part.
218 multi.discardBodyData();
219 }
220 nextPart = multi.readBoundary();
221 }
222 }
223 catch(IOException e)
224 {
225 throw new TurbineException("Processing of " + MULTIPART_FORM_DATA
226 + " request failed", e);
227 }
228 }
229
230 /***
231 * <p> Retrieves file name from <code>Content-disposition</code> header.
232 *
233 * @param headers The HTTP request headers.
234 * @return A the file name for the current <code>encapsulation</code>.
235 */
236 protected String getFileName(Map headers)
237 {
238 String fileName = null;
239 String cd = getHeader(headers, CONTENT_DISPOSITION);
240 if(cd.startsWith(FORM_DATA) || cd.startsWith("attachment"))
241 {
242 int start = cd.indexOf("filename=\"");
243 int end = cd.indexOf('"', start + 10);
244 if(start != -1 && end != -1 && (start + 10) != end)
245 {
246 String str = cd.substring(start + 10, end).trim();
247 if (str.length() > 0)
248 {
249 fileName = str;
250 }
251 }
252 }
253 return fileName;
254 }
255
256 /***
257 * <p> Retrieves field name from <code>Content-disposition</code> header.
258 *
259 * @param headers The HTTP request headers.
260 * @return The field name for the current <code>encapsulation</code>.
261 */
262 protected String getFieldName(Map headers)
263 {
264 String fieldName = null;
265 String cd = getHeader(headers, CONTENT_DISPOSITION);
266 if(cd != null && cd.startsWith(FORM_DATA))
267 {
268 int start = cd.indexOf("name=\"");
269 int end = cd.indexOf('"', start + 6);
270 if(start != -1 && end != -1)
271 {
272 fieldName = cd.substring(start + 6, end);
273 }
274 }
275 return fieldName;
276 }
277
278 /***
279 * <p> Creates a new instance of {@link
280 * org.apache.turbine.util.upload.FileItem}.
281 *
282 * @param path The path for the FileItem.
283 * @param headers The HTTP request headers.
284 * @param requestSize The size of the request.
285 * @return A newly created <code>FileItem</code>.
286 */
287 protected FileItem createItem( String path,
288 Map headers,
289 int requestSize )
290 {
291 return FileItem.newInstance(path,
292 getFileName(headers),
293 getHeader(headers, CONTENT_TYPE),
294 requestSize);
295 }
296
297 /***
298 * <p> Parses the <code>header-part</code> and returns as key/value
299 * pairs.
300 *
301 * <p> If there are multiple headers of the same names, the name
302 * will map to a comma-separated list containing the values.
303 *
304 * @param headerPart The <code>header-part</code> of the current
305 * <code>encapsulation</code>.
306 * @return The parsed HTTP request headers.
307 */
308 protected Map parseHeaders( String headerPart )
309 {
310 Map headers = new HashMap();
311 char buffer[] = new char[MAX_HEADER_SIZE];
312 boolean done = false;
313 int j = 0;
314 int i;
315 String header, headerName, headerValue;
316 try
317 {
318 while (!done)
319 {
320 i=0;
321 // Copy a single line of characters into the buffer,
322 // omitting trailing CRLF.
323 while (i<2 || buffer[i-2] != '\r' || buffer[i-1] != '\n')
324 {
325 buffer[i++] = headerPart.charAt(j++);
326 }
327 header = new String(buffer, 0, i-2);
328 if (header.equals(""))
329 {
330 done = true;
331 }
332 else
333 {
334 if (header.indexOf(':') == -1)
335 {
336 // This header line is malformed, skip it.
337 continue;
338 }
339 headerName = header.substring(0, header.indexOf(':'))
340 .trim().toLowerCase();
341 headerValue =
342 header.substring(header.indexOf(':') + 1).trim();
343 if (getHeader(headers, headerName) != null)
344 {
345 // More that one heder of that name exists,
346 // append to the list.
347 headers.put(headerName,
348 getHeader(headers, headerName) + ',' +
349 headerValue);
350 }
351 else
352 {
353 headers.put(headerName, headerValue);
354 }
355 }
356 }
357 }
358 catch(IndexOutOfBoundsException e)
359 {
360 // Headers were malformed. continue with all that was
361 // parsed.
362 }
363 return headers;
364 }
365
366 /***
367 * <p> Returns a header with specified name.
368 *
369 * @param headers The HTTP request headers.
370 * @param name The name of the header to fetch.
371 * @return The value of specified header, or a comma-separated
372 * list if there were multiple headers of that name.
373 */
374 protected final String getHeader( Map headers, String name )
375 {
376 return (String)headers.get(name.toLowerCase());
377 }
378 }
This page was automatically generated by Maven