1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
59
60
61
62
63 public class CommonsMultipartRequestHandler implements MultipartRequestHandler {
64
65
66
67
68
69
70 public static final long DEFAULT_SIZE_MAX = 250 * 1024 * 1024;
71
72
73
74
75
76 public static final long DEFAULT_FILE_SIZE_MAX = 250 * 1024 * 1024;
77
78
79
80
81
82 public static final long DEFAULT_MAX_STRING_LEN = 4 * 1024;
83
84
85
86
87
88
89 public static final int DEFAULT_SIZE_THRESHOLD = 256 * 1024;
90
91
92
93
94
95
96 private final Logger log =
97 LoggerFactory.getLogger(CommonsMultipartRequestHandler.class);
98
99
100
101
102 private HashMap<String, Object> elementsAll;
103
104
105
106
107 private HashMap<String, FormFile[]> elementsFile;
108
109
110
111
112 private HashMap<String, String[]> elementsText;
113
114
115
116
117 private ActionMapping mapping;
118
119
120
121
122 private ActionServlet servlet;
123
124
125
126
127 private final static boolean WIN_SYSTEM =
128 System.getProperty("os.name").toLowerCase(Locale.ROOT).contains("windows");
129
130
131
132
133
134
135
136
137 public ActionServlet getServlet() {
138 return this.servlet;
139 }
140
141
142
143
144
145
146 public void setServlet(ActionServlet servlet) {
147 this.servlet = servlet;
148 }
149
150
151
152
153
154
155 public ActionMapping getMapping() {
156 return this.mapping;
157 }
158
159
160
161
162
163
164 public void setMapping(ActionMapping mapping) {
165 this.mapping = mapping;
166 }
167
168
169
170
171
172
173
174
175
176
177
178 public void handleRequest(HttpServletRequest request)
179 throws ServletException {
180
181 ModuleConfig ac =
182 (ModuleConfig) request.getAttribute(Globals.MODULE_KEY);
183
184
185 DiskFileItemFactory.Builder factory = DiskFileItemFactory.builder();
186
187
188 factory.setBufferSize((int) getSizeThreshold(ac));
189
190
191 factory.setFile(getRepositoryFile(ac));
192
193
194 JakartaServletFileUpload<DiskFileItem, DiskFileItemFactory> upload = new JakartaServletFileUpload<>(factory.get());
195
196
197
198 upload.setHeaderCharset(Charset.forName(request.getCharacterEncoding()));
199
200
201 upload.setSizeMax(getSizeMax(ac));
202
203
204 upload.setFileSizeMax(getFileSizeMax(ac));
205
206
207 upload.setFileCountMax(getFileCountMax(ac));
208
209
210 elementsText = new HashMap<>();
211 elementsFile = new HashMap<>();
212 elementsAll = new HashMap<>();
213
214
215 List<DiskFileItem> items = null;
216
217 try {
218 items = upload.parseRequest(request);
219 } catch (FileUploadSizeException e) {
220
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
253
254 final long maxStringLen = getMaxStringLen(ac);
255
256
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
268
269
270
271
272 public HashMap<String, String[]> getTextElements() {
273 return this.elementsText;
274 }
275
276
277
278
279
280
281
282 public HashMap<String, FormFile[]> getFileElements() {
283 return this.elementsFile;
284 }
285
286
287
288
289
290
291 public HashMap<String, Object> getAllElements() {
292 return this.elementsAll;
293 }
294
295
296
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
316
317 public void finish() {
318 rollback();
319 }
320
321
322
323
324
325
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
344
345
346
347
348
349
350 protected long getSizeMax(ModuleConfig mc) {
351 return convertSizeToBytes("maximal request size",
352 mc.getControllerConfig().getMaxSize(), DEFAULT_SIZE_MAX);
353 }
354
355
356
357
358
359
360
361
362
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
371
372
373
374
375
376
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
385
386
387
388
389
390
391 protected long getSizeThreshold(ModuleConfig mc) {
392 return convertSizeToBytes("threshold size",
393 mc.getControllerConfig().getMemFileSize(), DEFAULT_SIZE_THRESHOLD);
394 }
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
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
441
442
443
444
445
446
447
448 protected long getFileCountMax(ModuleConfig mc) {
449 return mc.getControllerConfig().getFileCountMax();
450 }
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472 protected File getRepositoryFile(ModuleConfig mc) {
473 File tempDirFile = null;
474
475
476 String tempDir = mc.getControllerConfig().getTempDir();
477
478
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
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
507
508
509
510
511
512
513
514
515 protected void addTextParameter(HttpServletRequest request, long maxStringLen, FileItem<?> item) {
516 final String name = item.getFieldName();
517 final String value;
518
519
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
546
547
548
549
550
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
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
587
588
589
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
600
601
602
603
604
605
606
607
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
627
628
629
630
631
632
633
634 static class CommonsFormFile implements FormFile, Serializable {
635 private static final long serialVersionUID = -6784594200973351263L;
636
637
638
639
640 FileItem<?> fileItem;
641
642
643
644
645
646
647
648 public CommonsFormFile(FileItem<?> fileItem) {
649 this.fileItem = fileItem;
650 }
651
652
653
654
655
656
657 public String getContentType() {
658 return fileItem.getContentType();
659 }
660
661
662
663
664
665
666
667
668 public void setContentType(String contentType) {
669 throw new UnsupportedOperationException(
670 "The setContentType() method is not supported.");
671 }
672
673
674
675
676
677
678
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
691
692
693
694
695
696
697
698 @Deprecated
699 public void setFileSize(int filesize) {
700 throw new UnsupportedOperationException(
701 "The setFileSize() method is not supported.");
702 }
703
704
705
706
707
708
709
710
711 public long getFileLength() {
712 return fileItem.getSize();
713 }
714
715
716
717
718
719
720
721
722 public void setFileLength(long fileLength) {
723 throw new UnsupportedOperationException(
724 "The setFileLength() method is not supported.");
725 }
726
727
728
729
730
731
732 public String getFileName() {
733 return getBaseFileName(fileItem.getName());
734 }
735
736
737
738
739
740
741
742
743 public void setFileName(String fileName) {
744 throw new UnsupportedOperationException(
745 "The setFileName() method is not supported.");
746 }
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761 public byte[] getFileData()
762 throws FileNotFoundException, IOException {
763 return fileItem.get();
764 }
765
766
767
768
769
770
771
772
773
774 public InputStream getInputStream()
775 throws FileNotFoundException, IOException {
776 return fileItem.getInputStream();
777 }
778
779
780
781
782
783
784
785
786 public void destroy() throws IOException {
787 fileItem.delete();
788 }
789
790
791
792
793
794
795
796
797
798
799
800
801 protected String getBaseFileName(String filePath) {
802
803 String fileName = new File(filePath).getName();
804
805
806 int colonIndex = fileName.indexOf(":");
807
808 if (colonIndex == -1) {
809
810 colonIndex = fileName.indexOf("\\\\");
811 }
812
813 int backslashIndex = fileName.lastIndexOf("\\");
814
815 if ((colonIndex > -1) && (backslashIndex > -1)) {
816
817
818 fileName = fileName.substring(backslashIndex + 1);
819 }
820
821 return fileName;
822 }
823
824
825
826
827
828
829 public String toString() {
830 return getFileName();
831 }
832 }
833 }