##### Coverage Report - org.apache.commons.fileupload.FileUploadBase

Classes in this File | Line Coverage | Branch Coverage | Complexity
FileUploadBase |
76% | 127/167
62% | 36/58
| 2.606060606060606;2.606
FileUploadBase$FileItemIteratorImpl |
60% | 60/100
62% | 31/50
| 2.606060606060606;2.606
FileUploadBase$FileItemIteratorImpl$1 |
100% | 5/5
N/A | N/A
| 2.606060606060606;2.606
FileUploadBase$FileItemIteratorImpl$FileItemStreamImpl |
94% | 34/36
83% | 10/12
| 2.606060606060606;2.606
FileUploadBase$FileItemIteratorImpl$FileItemStreamImpl$1 |
100% | 8/8
N/A | N/A
| 2.606060606060606;2.606
FileUploadBase$FileSizeLimitExceededException |
77% | 7/9
N/A | N/A
| 2.606060606060606;2.606
FileUploadBase$FileUploadIOException |
100% | 4/4
N/A | N/A
| 2.606060606060606;2.606
FileUploadBase$IOFileUploadException |
100% | 4/4
N/A | N/A
| 2.606060606060606;2.606
FileUploadBase$InvalidContentTypeException |
33% | 2/6
N/A | N/A
| 2.606060606060606;2.606
FileUploadBase$SizeException |
83% | 5/6
N/A | N/A
| 2.606060606060606;2.606
FileUploadBase$SizeLimitExceededException |
42% | 3/7
N/A | N/A
| 2.606060606060606;2.606
FileUploadBase$UnknownSizeException |
0% | 0/4
N/A | N/A
| 2.606060606060606;2.606

1 |  |
     /*
2 |  |
      * Licensed to the Apache Software Foundation (ASF) under one or more
3 |  |
      * contributor license agreements.  See the NOTICE file distributed with
4 |  |
      * this work for additional information regarding copyright ownership.
5 |  |
      * The ASF licenses this file to You under the Apache License, Version 2.0
6 |  |
      * (the "License"); you may not use this file except in compliance with
7 |  |
      * the License.  You may obtain a copy of the License at
8 |  |
      *
9 |  |
      *      http://www.apache.org/licenses/LICENSE-2.0
10 |  |
      *
11 |  |
      * Unless required by applicable law or agreed to in writing, software
12 |  |
      * distributed under the License is distributed on an "AS IS" BASIS,
13 |  |
      * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 |  |
      * See the License for the specific language governing permissions and
15 |  |
      * limitations under the License.
16 |  |
      */
17 |  |
     package org.apache.commons.fileupload;
18 |  |
19 |  |
     import static java.lang.String.format;
20 |  |
21 |  |
     import java.io.IOException;
22 |  |
     import java.io.InputStream;
23 |  |
     import java.io.UnsupportedEncodingException;
24 |  |
     import java.util.ArrayList;
25 |  |
     import java.util.HashMap;
26 |  |
     import java.util.Iterator;
27 |  |
     import java.util.List;
28 |  |
     import java.util.Locale;
29 |  |
     import java.util.Map;
30 |  |
     import java.util.NoSuchElementException;
31 |  |
32 |  |
     import javax.servlet.http.HttpServletRequest;
33 |  |
34 |  |
     import org.apache.commons.fileupload.MultipartStream.ItemInputStream;
35 |  |
     import org.apache.commons.fileupload.servlet.ServletFileUpload;
36 |  |
     import org.apache.commons.fileupload.servlet.ServletRequestContext;
37 |  |
     import org.apache.commons.fileupload.util.Closeable;
38 |  |
     import org.apache.commons.fileupload.util.FileItemHeadersImpl;
39 |  |
     import org.apache.commons.fileupload.util.LimitedInputStream;
40 |  |
     import org.apache.commons.fileupload.util.Streams;
41 |  |
     import org.apache.commons.io.IOUtils;
42 |  |
43 |  |
     /**
44 |  |
      * <p>High level API for processing file uploads.</p>
45 |  |
      *
46 |  |
      * <p>This class handles multiple files per single HTML widget, sent using
47 |  |
      * <code>multipart/mixed</code> encoding type, as specified by
48 |  |
      * <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>.  Use {@link
49 |  |
      * #parseRequest(RequestContext)} to acquire a list of {@link
50 |  |
      * org.apache.commons.fileupload.FileItem}s associated with a given HTML
51 |  |
      * widget.</p>
52 |  |
      *
53 |  |
      * <p>How the data for individual parts is stored is determined by the factory
54 |  |
      * used to create them; a given part may be in memory, on disk, or somewhere
55 |  |
      * else.</p>
56 |  |
      */
57 | 6530 |
     public abstract class FileUploadBase {
58 |  |
59 |  |
         // ---------------------------------------------------------- Class methods
60 |  |
61 |  |
         /**
62 |  |
          * <p>Utility method that determines whether the request contains multipart
63 |  |
          * content.</p>
64 |  |
          *
65 |  |
          * <p><strong>NOTE:</strong>This method will be moved to the
66 |  |
          * <code>ServletFileUpload</code> class after the FileUpload 1.1 release.
67 |  |
          * Unfortunately, since this method is static, it is not possible to
68 |  |
          * provide its replacement until this method is removed.</p>
69 |  |
          *
70 |  |
          * @param ctx The request context to be evaluated. Must be non-null.
71 |  |
          *
72 |  |
          * @return <code>true</code> if the request is multipart;
73 |  |
          *         <code>false</code> otherwise.
74 |  |
          */
75 |  |
         public static final boolean isMultipartContent(RequestContext ctx) {
76 | 0 |
             String contentType = ctx.getContentType();
77 | 0 |
             if (contentType == null) {
78 | 0 |
                 return false;
79 |  |
             }
80 | 0 |
             if (contentType.toLowerCase(Locale.ENGLISH).startsWith(MULTIPART)) {
81 | 0 |
                 return true;
82 |  |
             }
83 | 0 |
             return false;
84 |  |
         }
85 |  |
86 |  |
         /**
87 |  |
          * Utility method that determines whether the request contains multipart
88 |  |
          * content.
89 |  |
          *
90 |  |
          * @param req The servlet request to be evaluated. Must be non-null.
91 |  |
          *
92 |  |
          * @return <code>true</code> if the request is multipart;
93 |  |
          *         <code>false</code> otherwise.
94 |  |
          *
95 |  |
          * @deprecated 1.1 Use the method on <code>ServletFileUpload</code> instead.
96 |  |
          */
97 |  |
         @Deprecated
98 |  |
         public static boolean isMultipartContent(HttpServletRequest req) {
99 | 0 |
             return ServletFileUpload.isMultipartContent(req);
100 |  |
         }
101 |  |
102 |  |
         // ----------------------------------------------------- Manifest constants
103 |  |
104 |  |
         /**
105 |  |
          * HTTP content type header name.
106 |  |
          */
107 |  |
         public static final String CONTENT_TYPE = "Content-type";
108 |  |
109 |  |
         /**
110 |  |
          * HTTP content disposition header name.
111 |  |
          */
112 |  |
         public static final String CONTENT_DISPOSITION = "Content-disposition";
113 |  |
114 |  |
         /**
115 |  |
          * HTTP content length header name.
116 |  |
          */
117 |  |
         public static final String CONTENT_LENGTH = "Content-length";
118 |  |
119 |  |
         /**
120 |  |
          * Content-disposition value for form data.
121 |  |
          */
122 |  |
         public static final String FORM_DATA = "form-data";
123 |  |
124 |  |
         /**
125 |  |
          * Content-disposition value for file attachment.
126 |  |
          */
127 |  |
         public static final String ATTACHMENT = "attachment";
128 |  |
129 |  |
         /**
130 |  |
          * Part of HTTP content type header.
131 |  |
          */
132 |  |
         public static final String MULTIPART = "multipart/";
133 |  |
134 |  |
         /**
135 |  |
          * HTTP content type header for multipart forms.
136 |  |
          */
137 |  |
         public static final String MULTIPART_FORM_DATA = "multipart/form-data";
138 |  |
139 |  |
         /**
140 |  |
          * HTTP content type header for multiple uploads.
141 |  |
          */
142 |  |
         public static final String MULTIPART_MIXED = "multipart/mixed";
143 |  |
144 |  |
         /**
145 |  |
          * The maximum length of a single header line that will be parsed
146 |  |
          * (1024 bytes).
147 |  |
          * @deprecated This constant is no longer used. As of commons-fileupload
148 |  |
          *   1.2, the only applicable limit is the total size of a parts headers,
149 |  |
          *   {@link MultipartStream#HEADER_PART_SIZE_MAX}.
150 |  |
          */
151 |  |
         @Deprecated
152 |  |
         public static final int MAX_HEADER_SIZE = 1024;
153 |  |
154 |  |
         // ----------------------------------------------------------- Data members
155 |  |
156 |  |
         /**
157 |  |
          * The maximum size permitted for the complete request, as opposed to
158 |  |
          * {@link #fileSizeMax}. A value of -1 indicates no maximum.
159 |  |
          */
160 | 25 |
         private long sizeMax = -1;
161 |  |
162 |  |
         /**
163 |  |
          * The maximum size permitted for a single uploaded file, as opposed
164 |  |
          * to {@link #sizeMax}. A value of -1 indicates no maximum.
165 |  |
          */
166 | 25 |
         private long fileSizeMax = -1;
167 |  |
168 |  |
         /**
169 |  |
          * The content encoding to use when reading part headers.
170 |  |
          */
171 |  |
         private String headerEncoding;
172 |  |
173 |  |
         /**
174 |  |
          * The progress listener.
175 |  |
          */
176 |  |
         private ProgressListener listener;
177 |  |
178 |  |
         // ----------------------------------------------------- Property accessors
179 |  |
180 |  |
         /**
181 |  |
          * Returns the factory class used when creating file items.
182 |  |
          *
183 |  |
          * @return The factory class for new file items.
184 |  |
          */
185 |  |
         public abstract FileItemFactory getFileItemFactory();
186 |  |
187 |  |
         /**
188 |  |
          * Sets the factory class to use when creating file items.
189 |  |
          *
190 |  |
          * @param factory The factory class for new file items.
191 |  |
          */
192 |  |
         public abstract void setFileItemFactory(FileItemFactory factory);
193 |  |
194 |  |
         /**
195 |  |
          * Returns the maximum allowed size of a complete request, as opposed
196 |  |
          * to {@link #getFileSizeMax()}.
197 |  |
          *
198 |  |
          * @return The maximum allowed size, in bytes. The default value of
199 |  |
          *   -1 indicates, that there is no limit.
200 |  |
          *
201 |  |
          * @see #setSizeMax(long)
202 |  |
          *
203 |  |
          */
204 |  |
         public long getSizeMax() {
205 | 0 |
             return sizeMax;
206 |  |
         }
207 |  |
208 |  |
         /**
209 |  |
          * Sets the maximum allowed size of a complete request, as opposed
210 |  |
          * to {@link #setFileSizeMax(long)}.
211 |  |
          *
212 |  |
          * @param sizeMax The maximum allowed size, in bytes. The default value of
213 |  |
          *   -1 indicates, that there is no limit.
214 |  |
          *
215 |  |
          * @see #getSizeMax()
216 |  |
          *
217 |  |
          */
218 |  |
         public void setSizeMax(long sizeMax) {
219 | 2 |
             this.sizeMax = sizeMax;
220 | 2 |
         }
221 |  |
222 |  |
         /**
223 |  |
          * Returns the maximum allowed size of a single uploaded file,
224 |  |
          * as opposed to {@link #getSizeMax()}.
225 |  |
          *
226 |  |
          * @see #setFileSizeMax(long)
227 |  |
          * @return Maximum size of a single uploaded file.
228 |  |
          */
229 |  |
         public long getFileSizeMax() {
230 | 0 |
             return fileSizeMax;
231 |  |
         }
232 |  |
233 |  |
         /**
234 |  |
          * Sets the maximum allowed size of a single uploaded file,
235 |  |
          * as opposed to {@link #getSizeMax()}.
236 |  |
          *
237 |  |
          * @see #getFileSizeMax()
238 |  |
          * @param fileSizeMax Maximum size of a single uploaded file.
239 |  |
          */
240 |  |
         public void setFileSizeMax(long fileSizeMax) {
241 | 9 |
             this.fileSizeMax = fileSizeMax;
242 | 9 |
         }
243 |  |
244 |  |
         /**
245 |  |
          * Retrieves the character encoding used when reading the headers of an
246 |  |
          * individual part. When not specified, or <code>null</code>, the request
247 |  |
          * encoding is used. If that is also not specified, or <code>null</code>,
248 |  |
          * the platform default encoding is used.
249 |  |
          *
250 |  |
          * @return The encoding used to read part headers.
251 |  |
          */
252 |  |
         public String getHeaderEncoding() {
253 | 0 |
             return headerEncoding;
254 |  |
         }
255 |  |
256 |  |
         /**
257 |  |
          * Specifies the character encoding to be used when reading the headers of
258 |  |
          * individual part. When not specified, or <code>null</code>, the request
259 |  |
          * encoding is used. If that is also not specified, or <code>null</code>,
260 |  |
          * the platform default encoding is used.
261 |  |
          *
262 |  |
          * @param encoding The encoding used to read part headers.
263 |  |
          */
264 |  |
         public void setHeaderEncoding(String encoding) {
265 | 0 |
             headerEncoding = encoding;
266 | 0 |
         }
267 |  |
268 |  |
         // --------------------------------------------------------- Public methods
269 |  |
270 |  |
         /**
271 |  |
          * Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
272 |  |
          * compliant <code>multipart/form-data</code> stream.
273 |  |
          *
274 |  |
          * @param req The servlet request to be parsed.
275 |  |
          *
276 |  |
          * @return A list of <code>FileItem</code> instances parsed from the
277 |  |
          *         request, in the order that they were transmitted.
278 |  |
          *
279 |  |
          * @throws FileUploadException if there are problems reading/parsing
280 |  |
          *                             the request or storing files.
281 |  |
          *
282 |  |
          * @deprecated 1.1 Use {@link ServletFileUpload#parseRequest(HttpServletRequest)} instead.
283 |  |
          */
284 |  |
         @Deprecated
285 |  |
         public List<FileItem> parseRequest(HttpServletRequest req)
286 |  |
         throws FileUploadException {
287 | 2 |
             return parseRequest(new ServletRequestContext(req));
288 |  |
         }
289 |  |
290 |  |
         /**
291 |  |
          * Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
292 |  |
          * compliant <code>multipart/form-data</code> stream.
293 |  |
          *
294 |  |
          * @param ctx The context for the request to be parsed.
295 |  |
          *
296 |  |
          * @return An iterator to instances of <code>FileItemStream</code>
297 |  |
          *         parsed from the request, in the order that they were
298 |  |
          *         transmitted.
299 |  |
          *
300 |  |
          * @throws FileUploadException if there are problems reading/parsing
301 |  |
          *                             the request or storing files.
302 |  |
          * @throws IOException An I/O error occurred. This may be a network
303 |  |
          *   error while communicating with the client or a problem while
304 |  |
          *   storing the uploaded content.
305 |  |
          */
306 |  |
         public FileItemIterator getItemIterator(RequestContext ctx)
307 |  |
         throws FileUploadException, IOException {
308 |  |
             try {
309 | 39 |
                 return new FileItemIteratorImpl(ctx);
310 | 1 |
             } catch (FileUploadIOException e) {
311 |  |
                 // unwrap encapsulated SizeException
312 | 1 |
                 throw (FileUploadException) e.getCause();
313 |  |
             }
314 |  |
         }
315 |  |
316 |  |
         /**
317 |  |
          * Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
318 |  |
          * compliant <code>multipart/form-data</code> stream.
319 |  |
          *
320 |  |
          * @param ctx The context for the request to be parsed.
321 |  |
          *
322 |  |
          * @return A list of <code>FileItem</code> instances parsed from the
323 |  |
          *         request, in the order that they were transmitted.
324 |  |
          *
325 |  |
          * @throws FileUploadException if there are problems reading/parsing
326 |  |
          *                             the request or storing files.
327 |  |
          */
328 |  |
         public List<FileItem> parseRequest(RequestContext ctx)
329 |  |
                 throws FileUploadException {
330 | 35 |
             List<FileItem> items = new ArrayList<FileItem>();
331 | 35 |
             boolean successful = false;
332 |  |
             try {
333 | 35 |
                 FileItemIterator iter = getItemIterator(ctx);
334 | 30 |
                 FileItemFactory fac = getFileItemFactory();
335 | 30 |
                 if (fac == null) {
336 | 0 |
                     throw new NullPointerException("No FileItemFactory has been set.");
337 |  |
                 }
338 | 2190 |
                 while (iter.hasNext()) {
339 | 2163 |
                     final FileItemStream item = iter.next();
340 |  |
                     // Don't use getName() here to prevent an InvalidFileNameException.
341 | 2163 |
                     final String fileName = ((FileItemIteratorImpl.FileItemStreamImpl) item).name;
342 | 4326 |
                     FileItem fileItem = fac.createItem(item.getFieldName(), item.getContentType(),
343 | 2163 |
                                                        item.isFormField(), fileName);
344 | 2163 |
                     items.add(fileItem);
345 |  |
                     try {
346 | 2163 |
                         Streams.copy(item.openStream(), fileItem.getOutputStream(), true);
347 | 2 |
                     } catch (FileUploadIOException e) {
348 | 2 |
                         throw (FileUploadException) e.getCause();
349 | 1 |
                     } catch (IOException e) {
350 | 2 |
                         throw new IOFileUploadException(format("Processing of %s request failed. %s",
351 | 1 |
                                                                MULTIPART_FORM_DATA, e.getMessage()), e);
352 | 2160 |
                     }
353 | 2160 |
                     final FileItemHeaders fih = item.getHeaders();
354 | 2160 |
                     fileItem.setHeaders(fih);
355 | 2160 |
                 }
356 | 27 |
                 successful = true;
357 | 54 |
                 return items;
358 | 0 |
             } catch (FileUploadIOException e) {
359 | 0 |
                 throw (FileUploadException) e.getCause();
360 | 1 |
             } catch (IOException e) {
361 | 1 |
                 throw new FileUploadException(e.getMessage(), e);
362 |  |
             } finally {
363 | 35 |
                 if (!successful) {
364 | 8 |
                     for (FileItem fileItem : items) {
365 |  |
                         try {
366 | 701 |
                             fileItem.delete();
367 | 0 |
                         } catch (Exception ignored) {
368 |  |
                             // ignored TODO perhaps add to tracker delete failure list somehow?
369 | 701 |
                         }
370 | 701 |
                     }
371 |  |
                 }
372 | 8 |
             }
373 |  |
         }
374 |  |
375 |  |
         /**
376 |  |
          * Processes an <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>
377 |  |
          * compliant <code>multipart/form-data</code> stream.
378 |  |
          *
379 |  |
          * @param ctx The context for the request to be parsed.
380 |  |
          *
381 |  |
          * @return A map of <code>FileItem</code> instances parsed from the request.
382 |  |
          *
383 |  |
          * @throws FileUploadException if there are problems reading/parsing
384 |  |
          *                             the request or storing files.
385 |  |
          *
386 |  |
          * @since 1.3
387 |  |
          */
388 |  |
         public Map<String, List<FileItem>> parseParameterMap(RequestContext ctx)
389 |  |
                 throws FileUploadException {
390 | 2 |
             final List<FileItem> items = parseRequest(ctx);
391 | 2 |
             final Map<String, List<FileItem>> itemsMap = new HashMap<String, List<FileItem>>(items.size());
392 |  |
393 | 2 |
             for (FileItem fileItem : items) {
394 | 8 |
                 String fieldName = fileItem.getFieldName();
395 | 8 |
                 List<FileItem> mappedItems = itemsMap.get(fieldName);
396 |  |
397 | 8 |
                 if (mappedItems == null) {
398 | 6 |
                     mappedItems = new ArrayList<FileItem>();
399 | 6 |
                     itemsMap.put(fieldName, mappedItems);
400 |  |
                 }
401 |  |
402 | 8 |
                 mappedItems.add(fileItem);
403 | 8 |
             }
404 |  |
405 | 2 |
             return itemsMap;
406 |  |
         }
407 |  |
408 |  |
         // ------------------------------------------------------ Protected methods
409 |  |
410 |  |
         /**
411 |  |
          * Retrieves the boundary from the <code>Content-type</code> header.
412 |  |
          *
413 |  |
          * @param contentType The value of the content type header from which to
414 |  |
          *                    extract the boundary value.
415 |  |
          *
416 |  |
          * @return The boundary, as a byte array.
417 |  |
          */
418 |  |
         protected byte[] getBoundary(String contentType) {
419 | 41 |
             ParameterParser parser = new ParameterParser();
420 | 41 |
             parser.setLowerCaseNames(true);
421 |  |
             // Parameter parser can handle null input
422 | 41 |
             Map<String, String> params = parser.parse(contentType, new char[] {';', ','});
423 | 41 |
             String boundaryStr = params.get("boundary");
424 |  |
425 | 41 |
             if (boundaryStr == null) {
426 | 1 |
                 return null;
427 |  |
             }
428 |  |
             byte[] boundary;
429 |  |
             try {
430 | 40 |
                 boundary = boundaryStr.getBytes("ISO-8859-1");
431 | 0 |
             } catch (UnsupportedEncodingException e) {
432 | 0 |
                 boundary = boundaryStr.getBytes(); // Intentionally falls back to default charset
433 | 40 |
             }
434 | 40 |
             return boundary;
435 |  |
         }
436 |  |
437 |  |
         /**
438 |  |
          * Retrieves the file name from the <code>Content-disposition</code>
439 |  |
          * header.
440 |  |
          *
441 |  |
          * @param headers A <code>Map</code> containing the HTTP request headers.
442 |  |
          *
443 |  |
          * @return The file name for the current <code>encapsulation</code>.
444 |  |
          * @deprecated 1.2.1 Use {@link #getFileName(FileItemHeaders)}.
445 |  |
          */
446 |  |
         @Deprecated
447 |  |
         protected String getFileName(Map<String, String> headers) {
448 | 0 |
             return getFileName(getHeader(headers, CONTENT_DISPOSITION));
449 |  |
         }
450 |  |
451 |  |
         /**
452 |  |
          * Retrieves the file name from the <code>Content-disposition</code>
453 |  |
          * header.
454 |  |
          *
455 |  |
          * @param headers The HTTP headers object.
456 |  |
          *
457 |  |
          * @return The file name for the current <code>encapsulation</code>.
458 |  |
          */
459 |  |
         protected String getFileName(FileItemHeaders headers) {
460 | 3191 |
             return getFileName(headers.getHeader(CONTENT_DISPOSITION));
461 |  |
         }
462 |  |
463 |  |
         /**
464 |  |
          * Returns the given content-disposition headers file name.
465 |  |
          * @param pContentDisposition The content-disposition headers value.
466 |  |
          * @return The file name
467 |  |
          */
468 |  |
         private String getFileName(String pContentDisposition) {
469 | 3191 |
             String fileName = null;
470 | 3191 |
             if (pContentDisposition != null) {
471 | 3191 |
                 String cdl = pContentDisposition.toLowerCase(Locale.ENGLISH);
472 | 3191 |
                 if (cdl.startsWith(FORM_DATA) || cdl.startsWith(ATTACHMENT)) {
473 | 3191 |
                     ParameterParser parser = new ParameterParser();
474 | 3191 |
                     parser.setLowerCaseNames(true);
475 |  |
                     // Parameter parser can handle null input
476 | 3191 |
                     Map<String, String> params = parser.parse(pContentDisposition, ';');
477 | 3191 |
                     if (params.containsKey("filename")) {
478 | 29 |
                         fileName = params.get("filename");
479 | 29 |
                         if (fileName != null) {
480 | 27 |
                             fileName = fileName.trim();
481 |  |
                         } else {
482 |  |
                             // Even if there is no value, the parameter is present,
483 |  |
                             // so we return an empty file name rather than no file
484 |  |
                             // name.
485 | 2 |
                             fileName = "";
486 |  |
                         }
487 |  |
                     }
488 |  |
                 }
489 |  |
             }
490 | 3191 |
             return fileName;
491 |  |
         }
492 |  |
493 |  |
         /**
494 |  |
          * Retrieves the field name from the <code>Content-disposition</code>
495 |  |
          * header.
496 |  |
          *
497 |  |
          * @param headers A <code>Map</code> containing the HTTP request headers.
498 |  |
          *
499 |  |
          * @return The field name for the current <code>encapsulation</code>.
500 |  |
          */
501 |  |
         protected String getFieldName(FileItemHeaders headers) {
502 | 3189 |
             return getFieldName(headers.getHeader(CONTENT_DISPOSITION));
503 |  |
         }
504 |  |
505 |  |
         /**
506 |  |
          * Returns the field name, which is given by the content-disposition
507 |  |
          * header.
508 |  |
          * @param pContentDisposition The content-dispositions header value.
509 |  |
          * @return The field jake
510 |  |
          */
511 |  |
         private String getFieldName(String pContentDisposition) {
512 | 3189 |
             String fieldName = null;
513 | 3189 |
             if (pContentDisposition != null
514 | 3189 |
                     && pContentDisposition.toLowerCase(Locale.ENGLISH).startsWith(FORM_DATA)) {
515 | 3189 |
                 ParameterParser parser = new ParameterParser();
516 | 3189 |
                 parser.setLowerCaseNames(true);
517 |  |
                 // Parameter parser can handle null input
518 | 3189 |
                 Map<String, String> params = parser.parse(pContentDisposition, ';');
519 | 3189 |
                 fieldName = params.get("name");
520 | 3189 |
                 if (fieldName != null) {
521 | 3189 |
                     fieldName = fieldName.trim();
522 |  |
                 }
523 |  |
             }
524 | 3189 |
             return fieldName;
525 |  |
         }
526 |  |
527 |  |
         /**
528 |  |
          * Retrieves the field name from the <code>Content-disposition</code>
529 |  |
          * header.
530 |  |
          *
531 |  |
          * @param headers A <code>Map</code> containing the HTTP request headers.
532 |  |
          *
533 |  |
          * @return The field name for the current <code>encapsulation</code>.
534 |  |
          * @deprecated 1.2.1 Use {@link #getFieldName(FileItemHeaders)}.
535 |  |
          */
536 |  |
         @Deprecated
537 |  |
         protected String getFieldName(Map<String, String> headers) {
538 | 0 |
             return getFieldName(getHeader(headers, CONTENT_DISPOSITION));
539 |  |
         }
540 |  |
541 |  |
         /**
542 |  |
          * Creates a new {@link FileItem} instance.
543 |  |
          *
544 |  |
          * @param headers       A <code>Map</code> containing the HTTP request
545 |  |
          *                      headers.
546 |  |
          * @param isFormField   Whether or not this item is a form field, as
547 |  |
          *                      opposed to a file.
548 |  |
          *
549 |  |
          * @return A newly created <code>FileItem</code> instance.
550 |  |
          *
551 |  |
          * @throws FileUploadException if an error occurs.
552 |  |
          * @deprecated 1.2 This method is no longer used in favour of
553 |  |
          *   internally created instances of {@link FileItem}.
554 |  |
          */
555 |  |
         @Deprecated
556 |  |
         protected FileItem createItem(Map<String, String> headers,
557 |  |
                                       boolean isFormField)
558 |  |
             throws FileUploadException {
559 | 0 |
             return getFileItemFactory().createItem(getFieldName(headers),
560 | 0 |
                     getHeader(headers, CONTENT_TYPE),
561 |  |
                     isFormField,
562 | 0 |
                     getFileName(headers));
563 |  |
         }
564 |  |
565 |  |
         /**
566 |  |
          * <p> Parses the <code>header-part</code> and returns as key/value
567 |  |
          * pairs.
568 |  |
          *
569 |  |
          * <p> If there are multiple headers of the same names, the name
570 |  |
          * will map to a comma-separated list containing the values.
571 |  |
          *
572 |  |
          * @param headerPart The <code>header-part</code> of the current
573 |  |
          *                   <code>encapsulation</code>.
574 |  |
          *
575 |  |
          * @return A <code>Map</code> containing the parsed HTTP request headers.
576 |  |
          */
577 |  |
         protected FileItemHeaders getParsedHeaders(String headerPart) {
578 | 3195 |
             final int len = headerPart.length();
579 | 3195 |
             FileItemHeadersImpl headers = newFileItemHeaders();
580 | 3195 |
             int start = 0;
581 |  |
             for (;;) {
582 | 6436 |
                 int end = parseEndOfLine(headerPart, start);
583 | 6436 |
                 if (start == end) {
584 | 3195 |
                     break;
585 |  |
                 }
586 | 3241 |
                 StringBuilder header = new StringBuilder(headerPart.substring(start, end));
587 | 3241 |
                 start = end + 2;
588 | 3249 |
                 while (start < len) {
589 | 3249 |
                     int nonWs = start;
590 | 3273 |
                     while (nonWs < len) {
591 | 3273 |
                         char c = headerPart.charAt(nonWs);
592 | 3273 |
                         if (c != ' '  &&  c != '\t') {
593 | 3249 |
                             break;
594 |  |
                         }
595 | 24 |
                         ++nonWs;
596 | 24 |
                     }
597 | 3249 |
                     if (nonWs == start) {
598 | 3241 |
                         break;
599 |  |
                     }
600 |  |
                     // Continuation line found
601 | 8 |
                     end = parseEndOfLine(headerPart, nonWs);
602 | 8 |
                     header.append(" ").append(headerPart.substring(nonWs, end));
603 | 8 |
                     start = end + 2;
604 | 8 |
                 }
605 | 3241 |
                 parseHeaderLine(headers, header.toString());
606 | 3241 |
             }
607 | 3195 |
             return headers;
608 |  |
         }
609 |  |
610 |  |
         /**
611 |  |
          * Creates a new instance of {@link FileItemHeaders}.
612 |  |
          * @return The new instance.
613 |  |
          */
614 |  |
         protected FileItemHeadersImpl newFileItemHeaders() {
615 | 3195 |
             return new FileItemHeadersImpl();
616 |  |
         }
617 |  |
618 |  |
         /**
619 |  |
          * <p> Parses the <code>header-part</code> and returns as key/value
620 |  |
          * pairs.
621 |  |
          *
622 |  |
          * <p> If there are multiple headers of the same names, the name
623 |  |
          * will map to a comma-separated list containing the values.
624 |  |
          *
625 |  |
          * @param headerPart The <code>header-part</code> of the current
626 |  |
          *                   <code>encapsulation</code>.
627 |  |
          *
628 |  |
          * @return A <code>Map</code> containing the parsed HTTP request headers.
629 |  |
          * @deprecated 1.2.1 Use {@link #getParsedHeaders(String)}
630 |  |
          */
631 |  |
         @Deprecated
632 |  |
         protected Map<String, String> parseHeaders(String headerPart) {
633 | 0 |
             FileItemHeaders headers = getParsedHeaders(headerPart);
634 | 0 |
             Map<String, String> result = new HashMap<String, String>();
635 | 0 |
             for (Iterator<String> iter = headers.getHeaderNames();  iter.hasNext();) {
636 | 0 |
                 String headerName = iter.next();
637 | 0 |
                 Iterator<String> iter2 = headers.getHeaders(headerName);
638 | 0 |
                 StringBuilder headerValue = new StringBuilder(iter2.next());
639 | 0 |
                 while (iter2.hasNext()) {
640 | 0 |
                     headerValue.append(",").append(iter2.next());
641 |  |
                 }
642 | 0 |
                 result.put(headerName, headerValue.toString());
643 | 0 |
             }
644 | 0 |
             return result;
645 |  |
         }
646 |  |
647 |  |
         /**
648 |  |
          * Skips bytes until the end of the current line.
649 |  |
          * @param headerPart The headers, which are being parsed.
650 |  |
          * @param end Index of the last byte, which has yet been
651 |  |
          *   processed.
652 |  |
          * @return Index of the \r\n sequence, which indicates
653 |  |
          *   end of line.
654 |  |
          */
655 |  |
         private int parseEndOfLine(String headerPart, int end) {
656 | 6444 |
             int index = end;
657 |  |
             for (;;) {
658 | 6444 |
                 int offset = headerPart.indexOf('\r', index);
659 | 6444 |
                 if (offset == -1  ||  offset + 1 >= headerPart.length()) {
660 | 0 |
                     throw new IllegalStateException(
661 |  |
                         "Expected headers to be terminated by an empty line.");
662 |  |
                 }
663 | 6444 |
                 if (headerPart.charAt(offset + 1) == '\n') {
664 | 6444 |
                     return offset;
665 |  |
                 }
666 | 0 |
                 index = offset + 1;
667 | 0 |
             }
668 |  |
         }
669 |  |
670 |  |
         /**
671 |  |
          * Reads the next header line.
672 |  |
          * @param headers String with all headers.
673 |  |
          * @param header Map where to store the current header.
674 |  |
          */
675 |  |
         private void parseHeaderLine(FileItemHeadersImpl headers, String header) {
676 | 3241 |
             final int colonOffset = header.indexOf(':');
677 | 3241 |
             if (colonOffset == -1) {
678 |  |
                 // This header line is malformed, skip it.
679 | 0 |
                 return;
680 |  |
             }
681 | 3241 |
             String headerName = header.substring(0, colonOffset).trim();
682 | 3241 |
             String headerValue =
683 | 3241 |
                 header.substring(header.indexOf(':') + 1).trim();
684 | 3241 |
             headers.addHeader(headerName, headerValue);
685 | 3241 |
         }
686 |  |
687 |  |
         /**
688 |  |
          * Returns the header with the specified name from the supplied map. The
689 |  |
          * header lookup is case-insensitive.
690 |  |
          *
691 |  |
          * @param headers A <code>Map</code> containing the HTTP request headers.
692 |  |
          * @param name    The name of the header to return.
693 |  |
          *
694 |  |
          * @return The value of specified header, or a comma-separated list if
695 |  |
          *         there were multiple headers of that name.
696 |  |
          * @deprecated 1.2.1 Use {@link FileItemHeaders#getHeader(String)}.
697 |  |
          */
698 |  |
         @Deprecated
699 |  |
         protected final String getHeader(Map<String, String> headers,
700 |  |
                 String name) {
701 | 0 |
             return headers.get(name.toLowerCase(Locale.ENGLISH));
702 |  |
         }
703 |  |
704 |  |
         /**
705 |  |
          * The iterator, which is returned by
706 |  |
          * {@link FileUploadBase#getItemIterator(RequestContext)}.
707 |  |
          */
708 | 0 |
         private class FileItemIteratorImpl implements FileItemIterator {
709 |  |
710 |  |
             /**
711 |  |
              * Default implementation of {@link FileItemStream}.
712 |  |
              */
713 | 2169 |
             class FileItemStreamImpl implements FileItemStream {
714 |  |
715 |  |
                 /**
716 |  |
                  * The file items content type.
717 |  |
                  */
718 |  |
                 private final String contentType;
719 |  |
720 |  |
                 /**
721 |  |
                  * The file items field name.
722 |  |
                  */
723 |  |
                 private final String fieldName;
724 |  |
725 |  |
                 /**
726 |  |
                  * The file items file name.
727 |  |
                  */
728 |  |
                 private final String name;
729 |  |
730 |  |
                 /**
731 |  |
                  * Whether the file item is a form field.
732 |  |
                  */
733 |  |
                 private final boolean formField;
734 |  |
735 |  |
                 /**
736 |  |
                  * The file items input stream.
737 |  |
                  */
738 |  |
                 private final InputStream stream;
739 |  |
740 |  |
                 /**
741 |  |
                  * Whether the file item was already opened.
742 |  |
                  */
743 |  |
                 private boolean opened;
744 |  |
745 |  |
                 /**
746 |  |
                  * The headers, if any.
747 |  |
                  */
748 |  |
                 private FileItemHeaders headers;
749 |  |
750 |  |
                 /**
751 |  |
                  * Creates a new instance.
752 |  |
                  *
753 |  |
                  * @param pName The items file name, or null.
754 |  |
                  * @param pFieldName The items field name.
755 |  |
                  * @param pContentType The items content type, or null.
756 |  |
                  * @param pFormField Whether the item is a form field.
757 |  |
                  * @param pContentLength The items content length, if known, or -1
758 |  |
                  * @throws IOException Creating the file item failed.
759 |  |
                  */
760 |  |
                 FileItemStreamImpl(String pName, String pFieldName,
761 |  |
                         String pContentType, boolean pFormField,
762 | 3191 |
                         long pContentLength) throws IOException {
763 | 3191 |
                     name = pName;
764 | 3191 |
                     fieldName = pFieldName;
765 | 3191 |
                     contentType = pContentType;
766 | 3191 |
                     formField = pFormField;
767 | 3191 |
                     if (fileSizeMax != -1) { // Check if limit is already exceeded
768 | 5 |
                         if (pContentLength != -1
769 | 3 |
                                 && pContentLength > fileSizeMax) {
770 | 1 |
                             FileSizeLimitExceededException e =
771 |  |
                                     new FileSizeLimitExceededException(
772 | 1 |
                                             format("The field %s exceeds its maximum permitted size of %s bytes.",
773 | 1 |
                                                     fieldName, Long.valueOf(fileSizeMax)),
774 | 1 |
                                             pContentLength, fileSizeMax);
775 | 1 |
                             e.setFileName(pName);
776 | 1 |
                             e.setFieldName(pFieldName);
777 | 1 |
                             throw new FileUploadIOException(e);
778 |  |
                         }
779 |  |
                     }
780 |  |
                     // OK to construct stream now
781 | 3190 |
                     final ItemInputStream itemStream = multi.newInputStream();
782 | 3190 |
                     InputStream istream = itemStream;
783 | 3190 |
                     if (fileSizeMax != -1) {
784 | 4 |
                         istream = new LimitedInputStream(istream, fileSizeMax) {
785 |  |
                             @Override
786 |  |
                             protected void raiseError(long pSizeMax, long pCount)
787 |  |
                                     throws IOException {
788 | 2 |
                                 itemStream.close(true);
789 | 2 |
                                 FileSizeLimitExceededException e =
790 |  |
                                     new FileSizeLimitExceededException(
791 | 2 |
                                         format("The field %s exceeds its maximum permitted size of %s bytes.",
792 | 2 |
                                                fieldName, Long.valueOf(pSizeMax)),
793 |  |
                                         pCount, pSizeMax);
794 | 2 |
                                 e.setFieldName(fieldName);
795 | 2 |
                                 e.setFileName(name);
796 | 2 |
                                 throw new FileUploadIOException(e);
797 |  |
                             }
798 |  |
                         };
799 |  |
                     }
800 | 3190 |
                     stream = istream;
801 | 3190 |
                 }
802 |  |
803 |  |
                 /**
804 |  |
                  * Returns the items content type, or null.
805 |  |
                  *
806 |  |
                  * @return Content type, if known, or null.
807 |  |
                  */
808 |  |
                 @Override
809 |  |
                 public String getContentType() {
810 | 2163 |
                     return contentType;
811 |  |
                 }
812 |  |
813 |  |
                 /**
814 |  |
                  * Returns the items field name.
815 |  |
                  *
816 |  |
                  * @return Field name.
817 |  |
                  */
818 |  |
                 @Override
819 |  |
                 public String getFieldName() {
820 | 2164 |
                     return fieldName;
821 |  |
                 }
822 |  |
823 |  |
                 /**
824 |  |
                  * Returns the items file name.
825 |  |
                  *
826 |  |
                  * @return File name, if known, or null.
827 |  |
                  * @throws InvalidFileNameException The file name contains a NUL character,
828 |  |
                  *   which might be an indicator of a security attack. If you intend to
829 |  |
                  *   use the file name anyways, catch the exception and use
830 |  |
                  *   InvalidFileNameException#getName().
831 |  |
                  */
832 |  |
                 @Override
833 |  |
                 public String getName() {
834 | 2 |
                     return Streams.checkFileName(name);
835 |  |
                 }
836 |  |
837 |  |
                 /**
838 |  |
                  * Returns, whether this is a form field.
839 |  |
                  *
840 |  |
                  * @return True, if the item is a form field,
841 |  |
                  *   otherwise false.
842 |  |
                  */
843 |  |
                 @Override
844 |  |
                 public boolean isFormField() {
845 | 2164 |
                     return formField;
846 |  |
                 }
847 |  |
848 |  |
                 /**
849 |  |
                  * Returns an input stream, which may be used to
850 |  |
                  * read the items contents.
851 |  |
                  *
852 |  |
                  * @return Opened input stream.
853 |  |
                  * @throws IOException An I/O error occurred.
854 |  |
                  */
855 |  |
                 @Override
856 |  |
                 public InputStream openStream() throws IOException {
857 | 3189 |
                     if (opened) {
858 | 0 |
                         throw new IllegalStateException(
859 |  |
                                 "The stream was already opened.");
860 |  |
                     }
861 | 3189 |
                     if (((Closeable) stream).isClosed()) {
862 | 0 |
                         throw new FileItemStream.ItemSkippedException();
863 |  |
                     }
864 | 3189 |
                     return stream;
865 |  |
                 }
866 |  |
867 |  |
                 /**
868 |  |
                  * Closes the file item.
869 |  |
                  *
870 |  |
                  * @throws IOException An I/O error occurred.
871 |  |
                  */
872 |  |
                 void close() throws IOException {
873 | 3185 |
                     stream.close();
874 | 3185 |
                 }
875 |  |
876 |  |
                 /**
877 |  |
                  * Returns the file item headers.
878 |  |
                  *
879 |  |
                  * @return The items header object
880 |  |
                  */
881 |  |
                 @Override
882 |  |
                 public FileItemHeaders getHeaders() {
883 | 2160 |
                     return headers;
884 |  |
                 }
885 |  |
886 |  |
                 /**
887 |  |
                  * Sets the file item headers.
888 |  |
                  *
889 |  |
                  * @param pHeaders The items header object
890 |  |
                  */
891 |  |
                 @Override
892 |  |
                 public void setHeaders(FileItemHeaders pHeaders) {
893 | 3190 |
                     headers = pHeaders;
894 | 3190 |
                 }
895 |  |
896 |  |
             }
897 |  |
898 |  |
             /**
899 |  |
              * The multi part stream to process.
900 |  |
              */
901 |  |
             private final MultipartStream multi;
902 |  |
903 |  |
             /**
904 |  |
              * The notifier, which used for triggering the
905 |  |
              * {@link ProgressListener}.
906 |  |
              */
907 |  |
             private final MultipartStream.ProgressNotifier notifier;
908 |  |
909 |  |
             /**
910 |  |
              * The boundary, which separates the various parts.
911 |  |
              */
912 |  |
             private final byte[] boundary;
913 |  |
914 |  |
             /**
915 |  |
              * The item, which we currently process.
916 |  |
              */
917 |  |
             private FileItemStreamImpl currentItem;
918 |  |
919 |  |
             /**
920 |  |
              * The current items field name.
921 |  |
              */
922 |  |
             private String currentFieldName;
923 |  |
924 |  |
             /**
925 |  |
              * Whether we are currently skipping the preamble.
926 |  |
              */
927 |  |
             private boolean skipPreamble;
928 |  |
929 |  |
             /**
930 |  |
              * Whether the current item may still be read.
931 |  |
              */
932 |  |
             private boolean itemValid;
933 |  |
934 |  |
             /**
935 |  |
              * Whether we have seen the end of the file.
936 |  |
              */
937 |  |
             private boolean eof;
938 |  |
939 |  |
             /**
940 |  |
              * Creates a new instance.
941 |  |
              *
942 |  |
              * @param ctx The request context.
943 |  |
              * @throws FileUploadException An error occurred while
944 |  |
              *   parsing the request.
945 |  |
              * @throws IOException An I/O error occurred.
946 |  |
              */
947 |  |
             FileItemIteratorImpl(RequestContext ctx)
948 | 39 |
                     throws FileUploadException, IOException {
949 | 39 |
                 if (ctx == null) {
950 | 0 |
                     throw new NullPointerException("ctx parameter");
951 |  |
                 }
952 |  |
953 | 39 |
                 String contentType = ctx.getContentType();
954 | 39 |
                 if ((null == contentType)
955 | 38 |
                         || (!contentType.toLowerCase(Locale.ENGLISH).startsWith(MULTIPART))) {
956 | 1 |
                     throw new InvalidContentTypeException(
957 | 1 |
                             format("the request doesn't contain a %s or %s stream, content type header is %s",
958 |  |
                                    MULTIPART_FORM_DATA, MULTIPART_MIXED, contentType));
959 |  |
                 }
960 |  |
961 |  |
962 |  |
                 @SuppressWarnings("deprecation") // still has to be backward compatible
963 | 38 |
                 final int contentLengthInt = ctx.getContentLength();
964 |  |
965 | 38 |
                 final long requestSize = UploadContext.class.isAssignableFrom(ctx.getClass())
966 |  |
                                          // Inline conditional is OK here CHECKSTYLE:OFF
967 | 38 |
                                          ? ((UploadContext) ctx).contentLength()
968 |  |
                                          : contentLengthInt;
969 |  |
                                          // CHECKSTYLE:ON
970 |  |
971 |  |
                 InputStream input; // N.B. this is eventually closed in MultipartStream processing
972 | 38 |
                 if (sizeMax >= 0) {
973 | 2 |
                     if (requestSize != -1 && requestSize > sizeMax) {
974 | 1 |
                         throw new SizeLimitExceededException(
975 | 1 |
                             format("the request was rejected because its size (%s) exceeds the configured maximum (%s)",
976 | 1 |
                                     Long.valueOf(requestSize), Long.valueOf(sizeMax)),
977 | 1 |
                                    requestSize, sizeMax);
978 |  |
                     }
979 |  |
                     // N.B. this is eventually closed in MultipartStream processing
980 | 1 |
                     input = new LimitedInputStream(ctx.getInputStream(), sizeMax) {
981 |  |
                         @Override
982 |  |
                         protected void raiseError(long pSizeMax, long pCount)
983 |  |
                                 throws IOException {
984 | 2 |
                             FileUploadException ex = new SizeLimitExceededException(
985 | 2 |
                             format("the request was rejected because its size (%s) exceeds the configured maximum (%s)",
986 | 2 |
                                     Long.valueOf(pCount), Long.valueOf(pSizeMax)),
987 |  |
                                    pCount, pSizeMax);
988 | 2 |
                             throw new FileUploadIOException(ex);
989 |  |
                         }
990 |  |
                     };
991 |  |
                 } else {
992 | 36 |
                     input = ctx.getInputStream();
993 |  |
                 }
994 |  |
995 | 37 |
                 String charEncoding = headerEncoding;
996 | 37 |
                 if (charEncoding == null) {
997 | 37 |
                     charEncoding = ctx.getCharacterEncoding();
998 |  |
                 }
999 |  |
1000 | 37 |
                 boundary = getBoundary(contentType);
1001 | 37 |
                 if (boundary == null) {
1002 | 1 |
                     IOUtils.closeQuietly(input); // avoid possible resource leak
1003 | 1 |
                     throw new FileUploadException("the request was rejected because no multipart boundary was found");
1004 |  |
                 }
1005 |  |
1006 | 36 |
                 notifier = new MultipartStream.ProgressNotifier(listener, requestSize);
1007 |  |
                 try {
1008 | 36 |
                     multi = new MultipartStream(input, boundary, notifier);
1009 | 0 |
                 } catch (IllegalArgumentException iae) {
1010 | 0 |
                     IOUtils.closeQuietly(input); // avoid possible resource leak
1011 | 0 |
                     throw new InvalidContentTypeException(
1012 | 0 |
                             format("The boundary specified in the %s header is too long", CONTENT_TYPE), iae);
1013 | 36 |
                 }
1014 | 36 |
                 multi.setHeaderEncoding(charEncoding);
1015 |  |
1016 | 36 |
                 skipPreamble = true;
1017 | 36 |
                 findNextItem();
1018 | 34 |
             }
1019 |  |
1020 |  |
             /**
1021 |  |
              * Called for finding the next item, if any.
1022 |  |
              *
1023 |  |
              * @return True, if an next item was found, otherwise false.
1024 |  |
              * @throws IOException An I/O error occurred.
1025 |  |
              */
1026 |  |
             private boolean findNextItem() throws IOException {
1027 | 3221 |
                 if (eof) {
1028 | 0 |
                     return false;
1029 |  |
                 }
1030 | 3221 |
                 if (currentItem != null) {
1031 | 3185 |
                     currentItem.close();
1032 | 3185 |
                     currentItem = null;
1033 |  |
                 }
1034 |  |
                 for (;;) {
1035 |  |
                     boolean nextPart;
1036 | 3229 |
                     if (skipPreamble) {
1037 | 3229 |
                         nextPart = multi.skipPreamble();
1038 |  |
                     } else {
1039 | 0 |
                         nextPart = multi.readBoundary();
1040 |  |
                     }
1041 | 3228 |
                     if (!nextPart) {
1042 | 33 |
                         if (currentFieldName == null) {
1043 |  |
                             // Outer multipart terminated -> No more data
1044 | 29 |
                             eof = true;
1045 | 29 |
                             return false;
1046 |  |
                         }
1047 |  |
                         // Inner multipart terminated -> Return to parsing the outer
1048 | 4 |
                         multi.setBoundary(boundary);
1049 | 4 |
                         currentFieldName = null;
1050 | 4 |
                         continue;
1051 |  |
                     }
1052 | 3195 |
                     FileItemHeaders headers = getParsedHeaders(multi.readHeaders());
1053 | 3195 |
                     if (currentFieldName == null) {
1054 |  |
                         // We're parsing the outer multipart
1055 | 3189 |
                         String fieldName = getFieldName(headers);
1056 | 3189 |
                         if (fieldName != null) {
1057 | 3189 |
                             String subContentType = headers.getHeader(CONTENT_TYPE);
1058 | 3189 |
                             if (subContentType != null
1059 | 25 |
                                     &&  subContentType.toLowerCase(Locale.ENGLISH)
1060 | 25 |
                                             .startsWith(MULTIPART_MIXED)) {
1061 | 4 |
                                 currentFieldName = fieldName;
1062 |  |
                                 // Multiple files associated with this field name
1063 | 4 |
                                 byte[] subBoundary = getBoundary(subContentType);
1064 | 4 |
                                 multi.setBoundary(subBoundary);
1065 | 4 |
                                 skipPreamble = true;
1066 | 4 |
                                 continue;
1067 |  |
                             }
1068 | 3185 |
                             String fileName = getFileName(headers);
1069 | 3185 |
                             currentItem = new FileItemStreamImpl(fileName,
1070 | 0 |
                                     fieldName, headers.getHeader(CONTENT_TYPE),
1071 | 0 |
                                     fileName == null, getContentLength(headers));
1072 | 0 |
                             currentItem.setHeaders(headers);
1073 | 0 |
                             notifier.noteItem();
1074 | 0 |
                             itemValid = true;
1075 | 0 |
                             return true;
1076 |  |
                         }
1077 | 0 |
                     } else {
1078 | 0 |
                         String fileName = getFileName(headers);
1079 | 0 |
                         if (fileName != null) {
1080 | 0 |
                             currentItem = new FileItemStreamImpl(fileName,
1081 |  |
                                     currentFieldName,
1082 | 0 |
                                     headers.getHeader(CONTENT_TYPE),
1083 | 0 |
                                     false, getContentLength(headers));
1084 | 0 |
                             currentItem.setHeaders(headers);
1085 | 0 |
                             notifier.noteItem();
1086 | 0 |
                             itemValid = true;
1087 | 0 |
                             return true;
1088 |  |
                         }
1089 |  |
                     }
1090 | 0 |
                     multi.discardBodyData();
1091 | 0 |
                 }
1092 |  |
             }
1093 |  |
1094 |  |
             private long getContentLength(FileItemHeaders pHeaders) {
1095 |  |
                 try {
1096 | 0 |
                     return Long.parseLong(pHeaders.getHeader(CONTENT_LENGTH));
1097 | 0 |
                 } catch (Exception e) {
1098 | 0 |
                     return -1;
1099 |  |
                 }
1100 |  |
             }
1101 |  |
1102 |  |
             /**
1103 |  |
              * Returns, whether another instance of {@link FileItemStream}
1104 |  |
              * is available.
1105 |  |
              *
1106 |  |
              * @throws FileUploadException Parsing or processing the
1107 |  |
              *   file item failed.
1108 |  |
              * @throws IOException Reading the file item failed.
1109 |  |
              * @return True, if one or more additional file items
1110 |  |
              *   are available, otherwise false.
1111 |  |
              */
1112 |  |
             @Override
1113 |  |
             public boolean hasNext() throws FileUploadException, IOException {
1114 | 0 |
                 if (eof) {
1115 | 0 |
                     return false;
1116 |  |
                 }
1117 | 0 |
                 if (itemValid) {
1118 | 0 |
                     return true;
1119 |  |
                 }
1120 |  |
                 try {
1121 | 0 |
                     return findNextItem();
1122 | 0 |
                 } catch (FileUploadIOException e) {
1123 |  |
                     // unwrap encapsulated SizeException
1124 | 0 |
                     throw (FileUploadException) e.getCause();
1125 |  |
                 }
1126 |  |
             }
1127 |  |
1128 |  |
             /**
1129 |  |
              * Returns the next available {@link FileItemStream}.
1130 |  |
              *
1131 |  |
              * @throws java.util.NoSuchElementException No more items are
1132 |  |
              *   available. Use {@link #hasNext()} to prevent this exception.
1133 |  |
              * @throws FileUploadException Parsing or processing the
1134 |  |
              *   file item failed.
1135 |  |
              * @throws IOException Reading the file item failed.
1136 |  |
              * @return FileItemStream instance, which provides
1137 |  |
              *   access to the next file item.
1138 |  |
              */
1139 |  |
             @Override
1140 |  |
             public FileItemStream next() throws FileUploadException, IOException {
1141 | 0 |
                 if (eof  ||  (!itemValid && !hasNext())) {
1142 | 0 |
                     throw new NoSuchElementException();
1143 |  |
                 }
1144 | 0 |
                 itemValid = false;
1145 | 0 |
                 return currentItem;
1146 |  |
             }
1147 |  |
1148 |  |
         }
1149 |  |
1150 |  |
         /**
1151 |  |
          * This exception is thrown for hiding an inner
1152 |  |
          * {@link FileUploadException} in an {@link IOException}.
1153 |  |
          */
1154 |  |
         public static class FileUploadIOException extends IOException {
1155 |  |
1156 |  |
             /**
1157 |  |
              * The exceptions UID, for serializing an instance.
1158 |  |
              */
1159 |  |
             private static final long serialVersionUID = -7047616958165584154L;
1160 |  |
1161 |  |
             /**
1162 |  |
              * The exceptions cause; we overwrite the parent
1163 |  |
              * classes field, which is available since Java
1164 |  |
              * 1.4 only.
1165 |  |
              */
1166 |  |
             private final FileUploadException cause;
1167 |  |
1168 |  |
             /**
1169 |  |
              * Creates a <code>FileUploadIOException</code> with the
1170 |  |
              * given cause.
1171 |  |
              *
1172 |  |
              * @param pCause The exceptions cause, if any, or null.
1173 |  |
              */
1174 | 5 |
             public FileUploadIOException(FileUploadException pCause) {
1175 |  |
                 // We're not doing super(pCause) cause of 1.3 compatibility.
1176 | 5 |
                 cause = pCause;
1177 | 5 |
             }
1178 |  |
1179 |  |
             /**
1180 |  |
              * Returns the exceptions cause.
1181 |  |
              *
1182 |  |
              * @return The exceptions cause, if any, or null.
1183 |  |
              */
1184 |  |
             @Override
1185 |  |
             public Throwable getCause() {
1186 | 3 |
                 return cause;
1187 |  |
             }
1188 |  |
1189 |  |
         }
1190 |  |
1191 |  |
         /**
1192 |  |
          * Thrown to indicate that the request is not a multipart request.
1193 |  |
          */
1194 |  |
         public static class InvalidContentTypeException
1195 |  |
                 extends FileUploadException {
1196 |  |
1197 |  |
             /**
1198 |  |
              * The exceptions UID, for serializing an instance.
1199 |  |
              */
1200 |  |
             private static final long serialVersionUID = -9073026332015646668L;
1201 |  |
1202 |  |
             /**
1203 |  |
              * Constructs a <code>InvalidContentTypeException</code> with no
1204 |  |
              * detail message.
1205 |  |
              */
1206 |  |
             public InvalidContentTypeException() {
1207 | 0 |
                 super();
1208 | 0 |
             }
1209 |  |
1210 |  |
             /**
1211 |  |
              * Constructs an <code>InvalidContentTypeException</code> with
1212 |  |
              * the specified detail message.
1213 |  |
              *
1214 |  |
              * @param message The detail message.
1215 |  |
              */
1216 |  |
             public InvalidContentTypeException(String message) {
1217 | 1 |
                 super(message);
1218 | 1 |
             }
1219 |  |
1220 |  |
             /**
1221 |  |
              * Constructs an <code>InvalidContentTypeException</code> with
1222 |  |
              * the specified detail message and cause.
1223 |  |
              *
1224 |  |
              * @param msg The detail message.
1225 |  |
              * @param cause the original cause
1226 |  |
              *
1227 |  |
              * @since 1.3.1
1228 |  |
              */
1229 |  |
             public InvalidContentTypeException(String msg, Throwable cause) {
1230 | 0 |
                 super(msg, cause);
1231 | 0 |
             }
1232 |  |
         }
1233 |  |
1234 |  |
         /**
1235 |  |
          * Thrown to indicate an IOException.
1236 |  |
          */
1237 |  |
         public static class IOFileUploadException extends FileUploadException {
1238 |  |
1239 |  |
             /**
1240 |  |
              * The exceptions UID, for serializing an instance.
1241 |  |
              */
1242 |  |
             private static final long serialVersionUID = 1749796615868477269L;
1243 |  |
1244 |  |
             /**
1245 |  |
              * The exceptions cause; we overwrite the parent
1246 |  |
              * classes field, which is available since Java
1247 |  |
              * 1.4 only.
1248 |  |
              */
1249 |  |
             private final IOException cause;
1250 |  |
1251 |  |
             /**
1252 |  |
              * Creates a new instance with the given cause.
1253 |  |
              *
1254 |  |
              * @param pMsg The detail message.
1255 |  |
              * @param pException The exceptions cause.
1256 |  |
              */
1257 |  |
             public IOFileUploadException(String pMsg, IOException pException) {
1258 | 1 |
                 super(pMsg);
1259 | 1 |
                 cause = pException;
1260 | 1 |
             }
1261 |  |
1262 |  |
             /**
1263 |  |
              * Returns the exceptions cause.
1264 |  |
              *
1265 |  |
              * @return The exceptions cause, if any, or null.
1266 |  |
              */
1267 |  |
             @Override
1268 |  |
             public Throwable getCause() {
1269 | 1 |
                 return cause;
1270 |  |
             }
1271 |  |
1272 |  |
         }
1273 |  |
1274 |  |
         /**
1275 |  |
          * This exception is thrown, if a requests permitted size
1276 |  |
          * is exceeded.
1277 |  |
          */
1278 |  |
         protected abstract static class SizeException extends FileUploadException {
1279 |  |
1280 |  |
             /**
1281 |  |
              * Serial version UID, being used, if serialized.
1282 |  |
              */
1283 |  |
             private static final long serialVersionUID = -8776225574705254126L;
1284 |  |
1285 |  |
             /**
1286 |  |
              * The actual size of the request.
1287 |  |
              */
1288 |  |
             private final long actual;
1289 |  |
1290 |  |
             /**
1291 |  |
              * The maximum permitted size of the request.
1292 |  |
              */
1293 |  |
             private final long permitted;
1294 |  |
1295 |  |
             /**
1296 |  |
              * Creates a new instance.
1297 |  |
              *
1298 |  |
              * @param message The detail message.
1299 |  |
              * @param actual The actual number of bytes in the request.
1300 |  |
              * @param permitted The requests size limit, in bytes.
1301 |  |
              */
1302 |  |
             protected SizeException(String message, long actual, long permitted) {
1303 | 6 |
                 super(message);
1304 | 6 |
                 this.actual = actual;
1305 | 6 |
                 this.permitted = permitted;
1306 | 6 |
             }
1307 |  |
1308 |  |
             /**
1309 |  |
              * Retrieves the actual size of the request.
1310 |  |
              *
1311 |  |
              * @return The actual size of the request.
1312 |  |
              * @since 1.3
1313 |  |
              */
1314 |  |
             public long getActualSize() {
1315 | 0 |
                 return actual;
1316 |  |
             }
1317 |  |
1318 |  |
             /**
1319 |  |
              * Retrieves the permitted size of the request.
1320 |  |
              *
1321 |  |
              * @return The permitted size of the request.
1322 |  |
              * @since 1.3
1323 |  |
              */
1324 |  |
             public long getPermittedSize() {
1325 | 4 |
                 return permitted;
1326 |  |
             }
1327 |  |
1328 |  |
         }
1329 |  |
1330 |  |
         /**
1331 |  |
          * Thrown to indicate that the request size is not specified. In other
1332 |  |
          * words, it is thrown, if the content-length header is missing or
1333 |  |
          * contains the value -1.
1334 |  |
          *
1335 |  |
          * @deprecated 1.2 As of commons-fileupload 1.2, the presence of a
1336 |  |
          *   content-length header is no longer required.
1337 |  |
          */
1338 |  |
         @Deprecated
1339 |  |
         public static class UnknownSizeException
1340 |  |
             extends FileUploadException {
1341 |  |
1342 |  |
             /**
1343 |  |
              * The exceptions UID, for serializing an instance.
1344 |  |
              */
1345 |  |
             private static final long serialVersionUID = 7062279004812015273L;
1346 |  |
1347 |  |
             /**
1348 |  |
              * Constructs a <code>UnknownSizeException</code> with no
1349 |  |
              * detail message.
1350 |  |
              */
1351 |  |
             public UnknownSizeException() {
1352 | 0 |
                 super();
1353 | 0 |
             }
1354 |  |
1355 |  |
             /**
1356 |  |
              * Constructs an <code>UnknownSizeException</code> with
1357 |  |
              * the specified detail message.
1358 |  |
              *
1359 |  |
              * @param message The detail message.
1360 |  |
              */
1361 |  |
             public UnknownSizeException(String message) {
1362 | 0 |
                 super(message);
1363 | 0 |
             }
1364 |  |
1365 |  |
         }
1366 |  |
1367 |  |
         /**
1368 |  |
          * Thrown to indicate that the request size exceeds the configured maximum.
1369 |  |
          */
1370 | 1 |
         public static class SizeLimitExceededException
1371 |  |
                 extends SizeException {
1372 |  |
1373 |  |
             /**
1374 |  |
              * The exceptions UID, for serializing an instance.
1375 |  |
              */
1376 |  |
             private static final long serialVersionUID = -2474893167098052828L;
1377 |  |
1378 |  |
             /**
1379 |  |
              * @deprecated 1.2 Replaced by
1380 |  |
              * {@link #SizeLimitExceededException(String, long, long)}
1381 |  |
              */
1382 |  |
             @Deprecated
1383 |  |
             public SizeLimitExceededException() {
1384 | 0 |
                 this(null, 0, 0);
1385 | 0 |
             }
1386 |  |
1387 |  |
             /**
1388 |  |
              * @deprecated 1.2 Replaced by
1389 |  |
              * {@link #SizeLimitExceededException(String, long, long)}
1390 |  |
              * @param message The exceptions detail message.
1391 |  |
              */
1392 |  |
             @Deprecated
1393 |  |
             public SizeLimitExceededException(String message) {
1394 | 0 |
                 this(message, 0, 0);
1395 | 0 |
             }
1396 |  |
1397 |  |
             /**
1398 |  |
              * Constructs a <code>SizeExceededException</code> with
1399 |  |
              * the specified detail message, and actual and permitted sizes.
1400 |  |
              *
1401 |  |
              * @param message   The detail message.
1402 |  |
              * @param actual    The actual request size.
1403 |  |
              * @param permitted The maximum permitted request size.
1404 |  |
              */
1405 |  |
             public SizeLimitExceededException(String message, long actual,
1406 |  |
                     long permitted) {
1407 | 3 |
                 super(message, actual, permitted);
1408 | 3 |
             }
1409 |  |
1410 |  |
         }
1411 |  |
1412 |  |
         /**
1413 |  |
          * Thrown to indicate that A files size exceeds the configured maximum.
1414 |  |
          */
1415 | 3 |
         public static class FileSizeLimitExceededException
1416 |  |
                 extends SizeException {
1417 |  |
1418 |  |
             /**
1419 |  |
              * The exceptions UID, for serializing an instance.
1420 |  |
              */
1421 |  |
             private static final long serialVersionUID = 8150776562029630058L;
1422 |  |
1423 |  |
             /**
1424 |  |
              * File name of the item, which caused the exception.
1425 |  |
              */
1426 |  |
             private String fileName;
1427 |  |
1428 |  |
             /**
1429 |  |
              * Field name of the item, which caused the exception.
1430 |  |
              */
1431 |  |
             private String fieldName;
1432 |  |
1433 |  |
             /**
1434 |  |
              * Constructs a <code>SizeExceededException</code> with
1435 |  |
              * the specified detail message, and actual and permitted sizes.
1436 |  |
              *
1437 |  |
              * @param message   The detail message.
1438 |  |
              * @param actual    The actual request size.
1439 |  |
              * @param permitted The maximum permitted request size.
1440 |  |
              */
1441 |  |
             public FileSizeLimitExceededException(String message, long actual,
1442 |  |
                     long permitted) {
1443 | 3 |
                 super(message, actual, permitted);
1444 | 3 |
             }
1445 |  |
1446 |  |
             /**
1447 |  |
              * Returns the file name of the item, which caused the
1448 |  |
              * exception.
1449 |  |
              *
1450 |  |
              * @return File name, if known, or null.
1451 |  |
              */
1452 |  |
             public String getFileName() {
1453 | 0 |
                 return fileName;
1454 |  |
             }
1455 |  |
1456 |  |
             /**
1457 |  |
              * Sets the file name of the item, which caused the
1458 |  |
              * exception.
1459 |  |
              *
1460 |  |
              * @param pFileName the file name of the item, which caused the exception.
1461 |  |
              */
1462 |  |
             public void setFileName(String pFileName) {
1463 | 3 |
                 fileName = pFileName;
1464 | 3 |
             }
1465 |  |
1466 |  |
             /**
1467 |  |
              * Returns the field name of the item, which caused the
1468 |  |
              * exception.
1469 |  |
              *
1470 |  |
              * @return Field name, if known, or null.
1471 |  |
              */
1472 |  |
             public String getFieldName() {
1473 | 0 |
                 return fieldName;
1474 |  |
             }
1475 |  |
1476 |  |
             /**
1477 |  |
              * Sets the field name of the item, which caused the
1478 |  |
              * exception.
1479 |  |
              *
1480 |  |
              * @param pFieldName the field name of the item,
1481 |  |
              *        which caused the exception.
1482 |  |
              */
1483 |  |
             public void setFieldName(String pFieldName) {
1484 | 3 |
                 fieldName = pFieldName;
1485 | 3 |
             }
1486 |  |
1487 |  |
         }
1488 |  |
1489 |  |
         /**
1490 |  |
          * Returns the progress listener.
1491 |  |
          *
1492 |  |
          * @return The progress listener, if any, or null.
1493 |  |
          */
1494 |  |
         public ProgressListener getProgressListener() {
1495 | 0 |
             return listener;
1496 |  |
         }
1497 |  |
1498 |  |
         /**
1499 |  |
          * Sets the progress listener.
1500 |  |
          *
1501 |  |
          * @param pListener The progress listener, if any. Defaults to null.
1502 |  |
          */
1503 |  |
         public void setProgressListener(ProgressListener pListener) {
1504 | 2 |
             listener = pListener;
1505 | 2 |
         }
1506 |  |
1507 |  |
     }