1 /*
2 * $Id$
3 *
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
19 * under the License.
20 */
21 package org.apache.struts.upload;
22
23 import java.io.File;
24 import java.io.FileNotFoundException;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.io.Serializable;
28 import java.lang.reflect.Array;
29 import java.nio.charset.Charset;
30 import java.nio.charset.StandardCharsets;
31 import java.nio.charset.UnsupportedCharsetException;
32 import java.util.Arrays;
33 import java.util.HashMap;
34 import java.util.List;
35 import java.util.Locale;
36
37 import org.apache.commons.fileupload2.core.DiskFileItem;
38 import org.apache.commons.fileupload2.core.DiskFileItemFactory;
39 import org.apache.commons.fileupload2.core.FileItem;
40 import org.apache.commons.fileupload2.core.FileUploadByteCountLimitException;
41 import org.apache.commons.fileupload2.core.FileUploadException;
42 import org.apache.commons.fileupload2.core.FileUploadFileCountLimitException;
43 import org.apache.commons.fileupload2.core.FileUploadSizeException;
44 import org.apache.commons.fileupload2.jakarta.JakartaServletFileUpload;
45 import org.apache.struts.Globals;
46 import org.apache.struts.action.ActionMapping;
47 import org.apache.struts.action.ActionServlet;
48 import org.apache.struts.config.ModuleConfig;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
51
52 import jakarta.servlet.ServletContext;
53 import jakarta.servlet.ServletException;
54 import jakarta.servlet.ServletInputStream;
55 import jakarta.servlet.http.HttpServletRequest;
56
57 /**
58 * This class implements the {@code MultipartRequestHandler} interface by
59 * providing a wrapper around the Jakarta Commons FileUpload library.
60 *
61 * @since Struts 1.1
62 */
63 public class CommonsMultipartRequestHandler implements MultipartRequestHandler {
64 // ----------------------------------------------------- Manifest Constants
65
66 /**
67 * The default value for the maximum allowable size, in bytes, of an
68 * uploaded file. The value is equivalent to 250MB.
69 */
70 public static final long DEFAULT_SIZE_MAX = 250 * 1024 * 1024;
71
72 /**
73 * The default value for the maximum allowable size, in bytes, of an
74 * uploaded file. The value is equivalent to 250MB.
75 */
76 public static final long DEFAULT_FILE_SIZE_MAX = 250 * 1024 * 1024;
77
78 /**
79 * The default value for the maximum length of a string parameter, in
80 * bytes, in a multipart request. The value is equivalent to 4KB.
81 */
82 public static final long DEFAULT_MAX_STRING_LEN = 4 * 1024;
83
84 /**
85 * The default value for the threshold which determines whether an uploaded
86 * file will be written to disk or cached in memory. The value is
87 * equivalent to 250KB.
88 */
89 public static final int DEFAULT_SIZE_THRESHOLD = 256 * 1024;
90
91 // ----------------------------------------------------- Instance Variables
92
93 /**
94 * The {@code Log} instance for this class.
95 */
96 private final Logger log =
97 LoggerFactory.getLogger(CommonsMultipartRequestHandler.class);
98
99 /**
100 * The combined text and file request parameters.
101 */
102 private HashMap<String, Object> elementsAll;
103
104 /**
105 * The file request parameters.
106 */
107 private HashMap<String, FormFile[]> elementsFile;
108
109 /**
110 * The text request parameters.
111 */
112 private HashMap<String, String[]> elementsText;
113
114 /**
115 * The action mapping with which this handler is associated.
116 */
117 private ActionMapping mapping;
118
119 /**
120 * The servlet with which this handler is associated.
121 */
122 private ActionServlet servlet;
123
124 /**
125 * Indicator if we run on a windows-system.
126 */
127 private final static boolean WIN_SYSTEM =
128 System.getProperty("os.name").toLowerCase(Locale.ROOT).contains("windows");
129
130 // ---------------------------------------- MultipartRequestHandler Methods
131
132 /**
133 * Retrieves the servlet with which this handler is associated.
134 *
135 * @return The associated servlet.
136 */
137 public ActionServlet getServlet() {
138 return this.servlet;
139 }
140
141 /**
142 * Sets the servlet with which this handler is associated.
143 *
144 * @param servlet The associated servlet.
145 */
146 public void setServlet(ActionServlet servlet) {
147 this.servlet = servlet;
148 }
149
150 /**
151 * Retrieves the action mapping with which this handler is associated.
152 *
153 * @return The associated action mapping.
154 */
155 public ActionMapping getMapping() {
156 return this.mapping;
157 }
158
159 /**
160 * Sets the action mapping with which this handler is associated.
161 *
162 * @param mapping The associated action mapping.
163 */
164 public void setMapping(ActionMapping mapping) {
165 this.mapping = mapping;
166 }
167
168 /**
169 * Parses the input stream and partitions the parsed items into a set of
170 * form fields and a set of file items. In the process, the parsed items
171 * are translated from Commons FileUpload {@code FileItem} instances to
172 * Struts {@code FormFile} instances.
173 *
174 * @param request The multipart request to be processed.
175 *
176 * @throws ServletException if an unrecoverable error occurs.
177 */
178 public void handleRequest(HttpServletRequest request)
179 throws ServletException {
180 // Get the app config for the current request.
181 ModuleConfig ac =
182 (ModuleConfig) request.getAttribute(Globals.MODULE_KEY);
183
184 // Create a factory for disk-based file items
185 DiskFileItemFactory.Builder factory = DiskFileItemFactory.builder();
186
187 // Set the maximum size that will be stored in memory.
188 factory.setBufferSize((int) getSizeThreshold(ac));
189
190 // Set the location for saving data on disk.
191 factory.setFile(getRepositoryFile(ac));
192
193 // Create a new file upload handler
194 JakartaServletFileUpload<DiskFileItem, DiskFileItemFactory> upload = new JakartaServletFileUpload<>(factory.get());
195
196 // The following line is to support an "EncodingFilter"
197 // see http://issues.apache.org/bugzilla/show_bug.cgi?id=23255
198 upload.setHeaderCharset(Charset.forName(request.getCharacterEncoding()));
199
200 // Sets the maximum allowed size of a complete request before a FileUploadException will be thrown.
201 upload.setSizeMax(getSizeMax(ac));
202
203 // Sets the maximum file size before a FileUploadException will be thrown.
204 upload.setFileSizeMax(getFileSizeMax(ac));
205
206 // Sets the maximum number of files allowed per request.
207 upload.setFileCountMax(getFileCountMax(ac));
208
209 // Create the hash maps to be populated.
210 elementsText = new HashMap<>();
211 elementsFile = new HashMap<>();
212 elementsAll = new HashMap<>();
213
214 // Parse the request into file items.
215 List<DiskFileItem> items = null;
216
217 try {
218 items = upload.parseRequest(request);
219 } catch (FileUploadSizeException e) {
220 // Special handling for uploads that are too big or too much file-uploads.
221 request.setAttribute(MultipartRequestHandler.ATTRIBUTE_MAX_LENGTH_EXCEEDED,
222 Boolean.TRUE);
223
224 if (e instanceof FileUploadByteCountLimitException) {
225 final FileUploadByteCountLimitException e2 = (FileUploadByteCountLimitException) e;
226 request.setAttribute(MultipartRequestHandler.ATTRIBUTE_MAX_BYTE_LENGTH_EXCEEDED,
227 Boolean.TRUE);
228
229 log.warn("Byte-Count-Limit-Exception: FieldName: {}, FileName: {}, MaxSize: {}, "
230 + "CurrentSize: {}", e2.getFieldName(), e2.getFieldName(),
231 e2.getPermitted(), e2.getActualSize(), e2);
232 } else if (e instanceof FileUploadFileCountLimitException) {
233 final FileUploadFileCountLimitException e2 = (FileUploadFileCountLimitException) e;
234 request.setAttribute(MultipartRequestHandler.ATTRIBUTE_MAX_FILE_COUNT_EXCEEDED,
235 Boolean.TRUE);
236
237 log.warn("File-Count-Limit-Exception: MaxSize: {}, CurrentSize: {}",
238 e2.getPermitted(), e2.getActualSize(), e2);
239 } else {
240 log.warn("Byte-Count-Limit-Exception: MaxSize: {}, CurrentSize: {}",
241 e.getPermitted(), e.getActualSize(), e);
242 }
243
244 clearInputStream(request);
245 return;
246 } catch (FileUploadException e) {
247 log.error("Failed to parse multipart request", e);
248 clearInputStream(request);
249 throw new ServletException(e);
250 }
251
252 // Get the maximum allowable length of a string parameter in a
253 // multipart request. CVE-2023-34396
254 final long maxStringLen = getMaxStringLen(ac);
255
256 // Partition the items into form fields and files.
257 for (DiskFileItem item : items) {
258 if (item.isFormField()) {
259 addTextParameter(request, maxStringLen, item);
260 } else {
261 addFileParameter(item);
262 }
263 }
264 }
265
266 /**
267 * Returns a hash map containing the text (that is, non-file) request
268 * parameters.
269 *
270 * @return The text request parameters.
271 */
272 public HashMap<String, String[]> getTextElements() {
273 return this.elementsText;
274 }
275
276 /**
277 * Returns a hash map containing the file (that is, non-text) request
278 * parameters.
279 *
280 * @return The file request parameters.
281 */
282 public HashMap<String, FormFile[]> getFileElements() {
283 return this.elementsFile;
284 }
285
286 /**
287 * Returns a hash map containing both text and file request parameters.
288 *
289 * @return The text and file request parameters.
290 */
291 public HashMap<String, Object> getAllElements() {
292 return this.elementsAll;
293 }
294
295 /**
296 * Cleans up when a problem occurs during request processing.
297 */
298 public void rollback() {
299 for (FormFile[] files : elementsFile.values()) {
300 for (FormFile formFile : files) {
301 try {
302 formFile.destroy();
303 } catch (IOException e) {
304 log.atWarn()
305 .setMessage("Failed to destroy FormFile {}")
306 .addArgument(formFile.getFileName())
307 .setCause(e)
308 .log();
309 }
310 }
311 }
312 }
313
314 /**
315 * Cleans up at the end of a request.
316 */
317 public void finish() {
318 rollback();
319 }
320
321 // -------------------------------------------------------- Support Methods
322
323 /**
324 * Finishes reading the input stream from an aborted upload. Fix for
325 * STR-2700 to prevent Window machines from hanging.
326 */
327 protected void clearInputStream(HttpServletRequest request) {
328 if (WIN_SYSTEM) {
329 try {
330 ServletInputStream is = request.getInputStream();
331 byte[] data = new byte[DEFAULT_SIZE_THRESHOLD];
332 int bytesRead = 0;
333 do {
334 bytesRead = is.read(data);
335 } while (bytesRead > -1);
336 } catch (IOException e) {
337 log.error(e.getMessage(), e);
338 }
339 }
340 }
341
342 /**
343 * Returns the maximum allowed size of a complete request. The value is
344 * obtained from the current module's controller configuration.
345 *
346 * @param mc The current module's configuration.
347 *
348 * @return The maximum allowable size of a complete request, in bytes.
349 */
350 protected long getSizeMax(ModuleConfig mc) {
351 return convertSizeToBytes("maximal request size",
352 mc.getControllerConfig().getMaxSize(), DEFAULT_SIZE_MAX);
353 }
354
355 /**
356 * Returns the maximum allowable file-size, in bytes, of an uploaded file.
357 * The value is obtained from the current module's controller
358 * configuration.
359 *
360 * @param mc The current module's configuration.
361 *
362 * @return The maximum allowable file size, in bytes.
363 */
364 protected long getFileSizeMax(ModuleConfig mc) {
365 return convertSizeToBytes("maximal file size",
366 mc.getControllerConfig().getMaxFileSize(), DEFAULT_FILE_SIZE_MAX);
367 }
368
369 /**
370 * Returns the maximum allowable length, in bytes, of a string parameter in
371 * a multipart request. The value is obtained from the current module's
372 * controller configuration.
373 *
374 * @param mc The current module's configuration.
375 *
376 * @return The maximum allowable length of a string parameter, in bytes.
377 */
378 protected long getMaxStringLen(ModuleConfig mc) {
379 return convertSizeToBytes("maximal string length",
380 mc.getControllerConfig().getMaxStringLen(), DEFAULT_MAX_STRING_LEN);
381 }
382
383 /**
384 * Returns the size threshold which determines whether an uploaded file
385 * will be written to disk or cached in memory.
386 *
387 * @param mc The current module's configuration.
388 *
389 * @return The size threshold, in bytes.
390 */
391 protected long getSizeThreshold(ModuleConfig mc) {
392 return convertSizeToBytes("threshold size",
393 mc.getControllerConfig().getMemFileSize(), DEFAULT_SIZE_THRESHOLD);
394 }
395
396 /**
397 * Converts a size value from a string representation to its numeric value.
398 * The string must be of the form nnnm, where nnn is an arbitrary decimal
399 * value, and m is a multiplier. The multiplier must be one of 'K', 'M' and
400 * 'G', representing kilobytes, megabytes and gigabytes respectively.
401 *
402 * <p>If the size value cannot be converted, for example due to invalid
403 * syntax, the supplied default is returned instead.</p>
404 *
405 * @param sizeType The type of the size. Is's used for logging-message.
406 * @param sizeString The string representation of the size to be converted.
407 * @param defaultSize The value to be returned if the string is invalid.
408 *
409 * @return The actual size in bytes.
410 */
411 protected long convertSizeToBytes(String sizeType, String sizeString, long defaultSize) {
412 int multiplier = 1;
413
414 if (sizeString.endsWith("K")) {
415 multiplier = 1024;
416 } else if (sizeString.endsWith("M")) {
417 multiplier = 1024 * 1024;
418 } else if (sizeString.endsWith("G")) {
419 multiplier = 1024 * 1024 * 1024;
420 }
421
422 if (multiplier != 1) {
423 sizeString = sizeString.substring(0, sizeString.length() - 1);
424 }
425
426 long size = 0;
427
428 try {
429 size = Long.parseLong(sizeString);
430 } catch (NumberFormatException nfe) {
431 log.warn("Invalid format for {} ('{}'). Using default.", sizeType, sizeString);
432 size = defaultSize;
433 multiplier = 1;
434 }
435
436 return size * multiplier;
437 }
438
439 /**
440 * Returns the maximum permitted number of files that may be uploaded in a
441 * single request. A value of -1 indicates no maximum. The value is
442 * obtained from the current module's controller configuration.
443 *
444 * @param mc The current module's configuration.
445 *
446 * @return The maximum allowable file size, in bytes.
447 */
448 protected long getFileCountMax(ModuleConfig mc) {
449 return mc.getControllerConfig().getFileCountMax();
450 }
451
452 /**
453 * Returns the path to the temporary directory to be used for uploaded
454 * files which are written to disk. The directory used is determined from
455 * the first of the following to be non-empty.
456 *
457 * <ol>
458 * <li>A temp dir explicitly defined either using the {@code tempDir}
459 * servlet init param, or the {@code tempDir} attribute of the
460 * <controller> element in the Struts config file.</li>
461 * <li>The container-specified temp dir, obtained from the
462 * {@code jakarta.servlet.context.tempdir} servlet context attribute.</li>
463 * <li>The temp dir specified by the {@code java.io.tmpdir} system
464 * property.</li>
465 * </ol>
466 *
467 * @param mc The module config instance for which the path should be
468 * determined.
469 *
470 * @return The path to the directory to be used to store uploaded files.
471 */
472 protected File getRepositoryFile(ModuleConfig mc) {
473 File tempDirFile = null;
474
475 // First, look for an explicitly defined temp dir.
476 String tempDir = mc.getControllerConfig().getTempDir();
477
478 // If none, look for a container specified temp dir.
479 if (tempDir == null || tempDir.isEmpty()) {
480 if (servlet != null) {
481 ServletContext context = servlet.getServletContext();
482 tempDirFile =
483 (File) context.getAttribute(ServletContext.TEMPDIR);
484
485 if (tempDirFile != null) {
486 tempDirFile = tempDirFile.getAbsoluteFile();
487 }
488 }
489
490 // If none, pick up the system temp dir.
491 if (tempDirFile == null) {
492 tempDir = System.getProperty("java.io.tmpdir");
493 }
494 }
495
496 if (tempDirFile == null && tempDir != null && !tempDir.isEmpty()) {
497 tempDirFile = new File(tempDir);
498 }
499
500 log.trace("File upload temp dir: {}", tempDirFile);
501
502 return tempDirFile;
503 }
504
505 /**
506 * Adds a regular text parameter to the set of text parameters for this
507 * request and also to the list of all parameters. Handles the case of
508 * multiple values for the same parameter by using an array for the
509 * parameter value.
510 *
511 * @param request The request in which the parameter was specified.
512 * @param maxStringLen The maximum allowable length of a string parameter.
513 * @param item The file item for the parameter to add.
514 */
515 protected void addTextParameter(HttpServletRequest request, long maxStringLen, FileItem<?> item) {
516 final String name = item.getFieldName();
517 final String value;
518
519 // CVE-2023-34396
520 if (item.getSize() > maxStringLen) {
521 request.setAttribute(MultipartRequestHandler.ATTRIBUTE_MAX_LENGTH_EXCEEDED,
522 Boolean.TRUE);
523 request.setAttribute(MultipartRequestHandler.ATTRIBUTE_MAX_STRING_LENGTH_EXCEEDED,
524 Boolean.TRUE);
525
526 log.warn("Max-String-Length: FieldName: {}, FileName: {}, MaxSize: {}, "
527 + "CurrentSize: {}", item.getFieldName(), item.getFieldName(),
528 maxStringLen, item.getSize());
529
530 value = "";
531 } else {
532 value = getTextValue(request, item);
533 }
534
535 if (request instanceof MultipartRequestWrapper) {
536 MultipartRequestWrapper wrapper = (MultipartRequestWrapper) request;
537
538 wrapper.setParameter(name, value);
539 }
540
541 elementsAll.put(name, addElement(elementsText, name, value));
542 }
543
544 /**
545 * Gets the regular text parameter of the item.
546 *
547 * @param request The request in which the parameter was specified.
548 * @param item The file item for the parameter.
549 *
550 * @return the value of the {@code item}
551 */
552 protected String getTextValue(HttpServletRequest request, FileItem<?> item) {
553 Charset encoding = null;
554
555 if (item instanceof DiskFileItem) {
556 encoding = ((DiskFileItem)item).getCharset();
557 log.debug("DiskFileItem.getCharset=[{}]", encoding);
558 }
559
560 if (encoding == null) {
561 try {
562 encoding = Charset.forName(request.getCharacterEncoding());
563 log.debug("request.getCharacterEncoding=[{}]", encoding);
564 } catch (UnsupportedCharsetException e) {
565 log.warn("Unknown request.getCharacterEncoding", e);
566 }
567 }
568
569 if (encoding != null) {
570 try {
571 return item.getString(encoding);
572 } catch (Exception e) {
573 // Handled below
574 }
575 }
576
577 try {
578 return item.getString(StandardCharsets.ISO_8859_1);
579 } catch (IOException e) {
580 log.info("FileItem-getString", e);
581 return item.getString();
582 }
583 }
584
585 /**
586 * Adds a file parameter to the set of file parameters for this request and
587 * also to the list of all parameters.
588 *
589 * @param item The file item for the parameter to add.
590 */
591 protected void addFileParameter(FileItem<?> item) {
592 final String name = item.getFieldName();
593 final FormFile value = new CommonsFormFile(item);
594
595 elementsAll.put(name, addElement(elementsFile, name, value));
596 }
597
598 /**
599 * Appends a new element to an array, which is in a map. If no array exists with the name,
600 * a new array with the name will be created.
601 *
602 * @param <T> the elements of the array
603 * @param elements the map with the arrays
604 * @param name the name within the map to find the array
605 * @param value the new element
606 *
607 * @return the new array with the append element
608 */
609 private static <T> T[] addElement(final HashMap<String, T[]> elements, final String name, final T value) {
610 final T[] oldArray = elements.get(name);
611 final T[] newArray;
612
613 if (oldArray != null) {
614 newArray = Arrays.copyOf(oldArray, oldArray.length + 1);
615 } else {
616 @SuppressWarnings("unchecked")
617 final T[] array = (T[]) Array.newInstance(value.getClass(), 1);
618 newArray = array;
619 }
620 newArray[newArray.length - 1] = value;
621
622 elements.put(name, newArray);
623 return newArray;
624 }
625
626 // ---------------------------------------------------------- Inner Classes
627
628 /**
629 * This class implements the Struts {@code FormFile} interface by wrapping
630 * the Commons FileUpload {@code FileItem} interface. This implementation
631 * is <i>read-only</i>; any attempt to modify an instance of this class
632 * will result in an {@code UnsupportedOperationException}.
633 */
634 static class CommonsFormFile implements FormFile, Serializable {
635 private static final long serialVersionUID = -6784594200973351263L;
636
637 /**
638 * The {@code FileItem} instance wrapped by this object.
639 */
640 FileItem<?> fileItem;
641
642 /**
643 * Constructs an instance of this class which wraps the supplied file
644 * item.
645 *
646 * @param fileItem The Commons file item to be wrapped.
647 */
648 public CommonsFormFile(FileItem<?> fileItem) {
649 this.fileItem = fileItem;
650 }
651
652 /**
653 * Returns the content type for this file.
654 *
655 * @return A String representing content type.
656 */
657 public String getContentType() {
658 return fileItem.getContentType();
659 }
660
661 /**
662 * Sets the content type for this file.
663 *
664 * <p>NOTE: This method is not supported in this implementation.</p>
665 *
666 * @param contentType A string representing the content type.
667 */
668 public void setContentType(String contentType) {
669 throw new UnsupportedOperationException(
670 "The setContentType() method is not supported.");
671 }
672
673 /**
674 * Returns the size, in bytes, of this file.
675 *
676 * @return The size of the file, in bytes.
677 *
678 * @deprecated Use {@link #getFileLength()}
679 */
680 @Deprecated
681 public int getFileSize() {
682 long size = fileItem.getSize();
683 if (size > Integer.MAX_VALUE) {
684 throw new IllegalStateException("Size is greater than 2 GB; use getFileLength()");
685 }
686 return (int) size;
687 }
688
689 /**
690 * Sets the size, in bytes, for this file.
691 *
692 * <p>NOTE: This method is not supported in this implementation.</p>
693 *
694 * @param filesize The size of the file, in bytes.
695 *
696 * @deprecated Use {@link #setFileLength(long)}
697 */
698 @Deprecated
699 public void setFileSize(int filesize) {
700 throw new UnsupportedOperationException(
701 "The setFileSize() method is not supported.");
702 }
703
704 /**
705 * Returns the length of this file.
706 *
707 * @return The length of the file, in bytes.
708 *
709 * @throws IllegalStateException if size is greater than 2GB
710 */
711 public long getFileLength() {
712 return fileItem.getSize();
713 }
714
715 /**
716 * Sets the length, in bytes, for this file.
717 *
718 * <p>NOTE: This method is not supported in this implementation.</p>
719 *
720 * @param fileLength The length of the file, in bytes.
721 */
722 public void setFileLength(long fileLength) {
723 throw new UnsupportedOperationException(
724 "The setFileLength() method is not supported.");
725 }
726
727 /**
728 * Returns the (client-side) file name for this file.
729 *
730 * @return The client-size file name.
731 */
732 public String getFileName() {
733 return getBaseFileName(fileItem.getName());
734 }
735
736 /**
737 * Sets the (client-side) file name for this file.
738 *
739 * <p>NOTE: This method is not supported in this implementation.</p>
740 *
741 * @param fileName The client-side name for the file.
742 */
743 public void setFileName(String fileName) {
744 throw new UnsupportedOperationException(
745 "The setFileName() method is not supported.");
746 }
747
748 /**
749 * Returns the data for this file as a byte array. Note that this may
750 * result in excessive memory usage for large uploads. The use of the
751 * {@link #getInputStream() getInputStream} method is encouraged as an
752 * alternative.
753 *
754 * @return An array of bytes representing the data contained in this
755 * form file.
756 *
757 * @throws FileNotFoundException If some sort of file representation
758 * cannot be found for the FormFile
759 * @throws IOException If there is some sort of IOException
760 */
761 public byte[] getFileData()
762 throws FileNotFoundException, IOException {
763 return fileItem.get();
764 }
765
766 /**
767 * Get an InputStream that represents this file. This is the preferred
768 * method of getting file data.
769 *
770 * @throws FileNotFoundException If some sort of file representation
771 * cannot be found for the FormFile
772 * @throws IOException If there is some sort of IOException
773 */
774 public InputStream getInputStream()
775 throws FileNotFoundException, IOException {
776 return fileItem.getInputStream();
777 }
778
779 /**
780 * Destroy all content for this form file. Implementations should
781 * remove any temporary files or any temporary file data stored
782 * somewhere.
783 *
784 * @throws IOException if an error occurs.
785 */
786 public void destroy() throws IOException {
787 fileItem.delete();
788 }
789
790 /**
791 * Returns the base file name from the supplied file path. On the
792 * surface, this would appear to be a trivial task. Apparently,
793 * however, some Linux JDKs do not implement {@code File.getName()}
794 * correctly for Windows paths, so we attempt to take care of that
795 * here.
796 *
797 * @param filePath The full path to the file.
798 *
799 * @return The base file name, from the end of the path.
800 */
801 protected String getBaseFileName(String filePath) {
802 // First, ask the JDK for the base file name.
803 String fileName = new File(filePath).getName();
804
805 // Now check for a Windows file name parsed incorrectly.
806 int colonIndex = fileName.indexOf(":");
807
808 if (colonIndex == -1) {
809 // Check for a Windows SMB file path.
810 colonIndex = fileName.indexOf("\\\\");
811 }
812
813 int backslashIndex = fileName.lastIndexOf("\\");
814
815 if ((colonIndex > -1) && (backslashIndex > -1)) {
816 // Consider this filename to be a full Windows path, and parse
817 // it accordingly to retrieve just the base file name.
818 fileName = fileName.substring(backslashIndex + 1);
819 }
820
821 return fileName;
822 }
823
824 /**
825 * Returns the (client-side) file name for this file.
826 *
827 * @return The client-size file name.
828 */
829 public String toString() {
830 return getFileName();
831 }
832 }
833 }