View Javadoc
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      * &lt;controller&gt; 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 }