1+ /*
2+ * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved.
3+ * <p>
4+ * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0;
5+ * you may not use this file except in compliance with the License.
6+ * You may obtain a copy of the License at
7+ * <p>
8+ * http://www.gnu.org/licenses/lgpl.html
9+ * <p>
10+ * Unless required by applicable law or agreed to in writing, software
11+ * distributed under the License is distributed on an "AS IS" BASIS,
12+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+ * See the License for the specific language governing permissions and
14+ * limitations under the License.
15+ */
16+
17+ package top .continew .starter .core .util .multipart ;
18+
19+ import org .apache .commons .fileupload .FileItem ;
20+ import org .apache .commons .fileupload .FileUploadException ;
21+ import org .apache .commons .fileupload .disk .DiskFileItem ;
22+ import org .apache .commons .logging .Log ;
23+ import org .apache .commons .logging .LogFactory ;
24+ import org .springframework .core .log .LogFormatUtils ;
25+ import org .springframework .util .FileCopyUtils ;
26+ import org .springframework .web .multipart .MultipartFile ;
27+
28+ import java .io .File ;
29+ import java .io .IOException ;
30+ import java .io .InputStream ;
31+ import java .io .Serializable ;
32+ import java .nio .file .Files ;
33+ import java .nio .file .Path ;
34+
35+ /**
36+ * {@link MultipartFile} implementation for Apache Commons FileUpload.
37+ * <p>
38+ * Spring Boot3(Spring 6)移除了 CommonsMultipartFile 和 CommonsMultipartResolver,彻底废弃了对 Apache Commons FileUpload 的依赖,只保留基于
39+ * Servlet 3.1+ 的标准上传机制 StandardMultipartFile。
40+ * <br>为了方便在项目中进行 MultipartFile 格式转换,特备份该实现。
41+ * </p>
42+ *
43+ * @author Trevor D. Cook
44+ * @author Juergen Hoeller
45+ * @author Charles7c
46+ * @since 2.15.0
47+ */
48+ @ SuppressWarnings ("serial" )
49+ public class CommonsMultipartFile implements MultipartFile , Serializable {
50+
51+ protected static final Log logger = LogFactory .getLog (CommonsMultipartFile .class );
52+
53+ private final FileItem fileItem ;
54+
55+ private final long size ;
56+
57+ private boolean preserveFilename = false ;
58+
59+ /**
60+ * Create an instance wrapping the given FileItem.
61+ *
62+ * @param fileItem the FileItem to wrap
63+ */
64+ public CommonsMultipartFile (FileItem fileItem ) {
65+ this .fileItem = fileItem ;
66+ this .size = this .fileItem .getSize ();
67+ }
68+
69+ /**
70+ * Return the underlying {@code org.apache.commons.fileupload.FileItem}
71+ * instance. There is hardly any need to access this.
72+ */
73+ public final FileItem getFileItem () {
74+ return this .fileItem ;
75+ }
76+
77+ /**
78+ * Set whether to preserve the filename as sent by the client, not stripping off
79+ * path information in {@link CommonsMultipartFile#getOriginalFilename()}.
80+ * <p>Default is "false", stripping off path information that may prefix the
81+ * actual filename e.g. from Opera. Switch this to "true" for preserving the
82+ * client-specified filename as-is, including potential path separators.
83+ *
84+ * @since 4.3.5
85+ * @see #getOriginalFilename()
86+ */
87+ public void setPreserveFilename (boolean preserveFilename ) {
88+ this .preserveFilename = preserveFilename ;
89+ }
90+
91+ @ Override
92+ public String getName () {
93+ return this .fileItem .getFieldName ();
94+ }
95+
96+ @ Override
97+ public String getOriginalFilename () {
98+ String filename = this .fileItem .getName ();
99+ if (filename == null ) {
100+ // Should never happen.
101+ return "" ;
102+ }
103+ if (this .preserveFilename ) {
104+ // Do not try to strip off a path...
105+ return filename ;
106+ }
107+
108+ // Check for Unix-style path
109+ int unixSep = filename .lastIndexOf ('/' );
110+ // Check for Windows-style path
111+ int winSep = filename .lastIndexOf ('\\' );
112+ // Cut off at latest possible point
113+ int pos = Math .max (winSep , unixSep );
114+ if (pos != -1 ) {
115+ // Any sort of path separator found...
116+ return filename .substring (pos + 1 );
117+ } else {
118+ // A plain name
119+ return filename ;
120+ }
121+ }
122+
123+ @ Override
124+ public String getContentType () {
125+ return this .fileItem .getContentType ();
126+ }
127+
128+ @ Override
129+ public boolean isEmpty () {
130+ return (this .size == 0 );
131+ }
132+
133+ @ Override
134+ public long getSize () {
135+ return this .size ;
136+ }
137+
138+ @ Override
139+ public byte [] getBytes () {
140+ if (!isAvailable ()) {
141+ throw new IllegalStateException ("File has been moved - cannot be read again" );
142+ }
143+ byte [] bytes = this .fileItem .get ();
144+ return (bytes != null ? bytes : new byte [0 ]);
145+ }
146+
147+ @ Override
148+ public InputStream getInputStream () throws IOException {
149+ if (!isAvailable ()) {
150+ throw new IllegalStateException ("File has been moved - cannot be read again" );
151+ }
152+ InputStream inputStream = this .fileItem .getInputStream ();
153+ return (inputStream != null ? inputStream : InputStream .nullInputStream ());
154+ }
155+
156+ @ Override
157+ public void transferTo (File dest ) throws IOException , IllegalStateException {
158+ if (!isAvailable ()) {
159+ throw new IllegalStateException ("File has already been moved - cannot be transferred again" );
160+ }
161+
162+ if (dest .exists () && !dest .delete ()) {
163+ throw new IOException ("Destination file [" + dest
164+ .getAbsolutePath () + "] already exists and could not be deleted" );
165+ }
166+
167+ try {
168+ this .fileItem .write (dest );
169+ LogFormatUtils .traceDebug (logger , traceOn -> {
170+ String action = "transferred" ;
171+ if (!this .fileItem .isInMemory ()) {
172+ action = (isAvailable () ? "copied" : "moved" );
173+ }
174+ return "Part '" + getName () + "', filename '" + getOriginalFilename () + "'" + (Boolean .TRUE
175+ .equals (traceOn ) ? ", stored " + getStorageDescription () : "" ) + ": " + action + " to [" + dest
176+ .getAbsolutePath () + "]" ;
177+ });
178+ } catch (FileUploadException ex ) {
179+ throw new IllegalStateException (ex .getMessage (), ex );
180+ } catch (IllegalStateException | IOException ex ) {
181+ // Pass through IllegalStateException when coming from FileItem directly,
182+ // or propagate an exception from I/O operations within FileItem.write
183+ throw ex ;
184+ } catch (Exception ex ) {
185+ throw new IOException ("File transfer failed" , ex );
186+ }
187+ }
188+
189+ @ Override
190+ public void transferTo (Path dest ) throws IOException , IllegalStateException {
191+ if (!isAvailable ()) {
192+ throw new IllegalStateException ("File has already been moved - cannot be transferred again" );
193+ }
194+
195+ FileCopyUtils .copy (this .fileItem .getInputStream (), Files .newOutputStream (dest ));
196+ }
197+
198+ /**
199+ * Determine whether the multipart content is still available.
200+ * If a temporary file has been moved, the content is no longer available.
201+ */
202+ protected boolean isAvailable () {
203+ // If in memory, it's available.
204+ if (this .fileItem .isInMemory ()) {
205+ return true ;
206+ }
207+ // Check actual existence of temporary file.
208+ if (this .fileItem instanceof DiskFileItem df ) {
209+ return df .getStoreLocation ().exists ();
210+ }
211+ // Check whether current file size is different than original one.
212+ return (this .fileItem .getSize () == this .size );
213+ }
214+
215+ /**
216+ * Return a description for the storage location of the multipart content.
217+ * Tries to be as specific as possible: mentions the file location in case
218+ * of a temporary file.
219+ */
220+ public String getStorageDescription () {
221+ if (this .fileItem .isInMemory ()) {
222+ return "in memory" ;
223+ } else if (this .fileItem instanceof DiskFileItem df ) {
224+ return "at [" + df .getStoreLocation ().getAbsolutePath () + "]" ;
225+ } else {
226+ return "on disk" ;
227+ }
228+ }
229+
230+ @ Override
231+ public String toString () {
232+ return "MultipartFile[field=\" " + this .fileItem .getFieldName () + "\" " + (this .fileItem .getName () != null
233+ ? ", filename=" + this .fileItem .getName ()
234+ : "" ) + (this .fileItem .getContentType () != null
235+ ? ", contentType=" + this .fileItem .getContentType ()
236+ : "" ) + ", size=" + this .fileItem .getSize () + "]" ;
237+ }
238+ }
0 commit comments