1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.fileupload;
18
19 import java.io.IOException;
20 import java.io.InputStream;
21 import java.io.UnsupportedEncodingException;
22 import java.util.ArrayList;
23 import java.util.HashMap;
24 import java.util.Iterator;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.NoSuchElementException;
28
29 import javax.servlet.http.HttpServletRequest;
30
31 import org.apache.commons.fileupload.MultipartStream.ItemInputStream;
32 import org.apache.commons.fileupload.servlet.ServletFileUpload;
33 import org.apache.commons.fileupload.servlet.ServletRequestContext;
34 import org.apache.commons.fileupload.util.Closeable;
35 import org.apache.commons.fileupload.util.FileItemHeadersImpl;
36 import org.apache.commons.fileupload.util.LimitedInputStream;
37 import org.apache.commons.fileupload.util.Streams;
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63 public abstract class FileUploadBase {
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82 public static final boolean isMultipartContent(RequestContext ctx) {
83 String contentType = ctx.getContentType();
84 if (contentType == null) {
85 return false;
86 }
87 if (contentType.toLowerCase().startsWith(MULTIPART)) {
88 return true;
89 }
90 return false;
91 }
92
93
94
95
96
97
98
99
100
101
102
103
104
105 public static boolean isMultipartContent(HttpServletRequest req) {
106 return ServletFileUpload.isMultipartContent(req);
107 }
108
109
110
111
112
113
114
115
116 public static final String CONTENT_TYPE = "Content-type";
117
118
119
120
121
122 public static final String CONTENT_DISPOSITION = "Content-disposition";
123
124
125
126
127 public static final String CONTENT_LENGTH = "Content-length";
128
129
130
131
132
133 public static final String FORM_DATA = "form-data";
134
135
136
137
138
139 public static final String ATTACHMENT = "attachment";
140
141
142
143
144
145 public static final String MULTIPART = "multipart/";
146
147
148
149
150
151 public static final String MULTIPART_FORM_DATA = "multipart/form-data";
152
153
154
155
156
157 public static final String MULTIPART_MIXED = "multipart/mixed";
158
159
160
161
162
163
164
165
166
167 public static final int MAX_HEADER_SIZE = 1024;
168
169
170
171
172
173
174
175
176
177 private long sizeMax = -1;
178
179
180
181
182
183 private long fileSizeMax = -1;
184
185
186
187
188 private String headerEncoding;
189
190
191
192
193 private ProgressListener listener;
194
195
196
197
198
199
200
201
202
203 public abstract FileItemFactory getFileItemFactory();
204
205
206
207
208
209
210
211 public abstract void setFileItemFactory(FileItemFactory factory);
212
213
214
215
216
217
218
219
220
221
222
223
224 public long getSizeMax() {
225 return sizeMax;
226 }
227
228
229
230
231
232
233
234
235
236
237
238
239 public void setSizeMax(long sizeMax) {
240 this.sizeMax = sizeMax;
241 }
242
243
244
245
246
247
248
249
250 public long getFileSizeMax() {
251 return fileSizeMax;
252 }
253
254
255
256
257
258
259
260
261 public void setFileSizeMax(long fileSizeMax) {
262 this.fileSizeMax = fileSizeMax;
263 }
264
265
266
267
268
269
270
271
272
273 public String getHeaderEncoding() {
274 return headerEncoding;
275 }
276
277
278
279
280
281
282
283
284
285
286 public void setHeaderEncoding(String encoding) {
287 headerEncoding = encoding;
288 }
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308 public List
309 throws FileUploadException {
310 return parseRequest(new ServletRequestContext(req));
311 }
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329 public FileItemIterator getItemIterator(RequestContext ctx)
330 throws FileUploadException, IOException {
331 return new FileItemIteratorImpl(ctx);
332 }
333
334
335
336
337
338
339
340
341
342
343
344
345
346 public List
347 throws FileUploadException {
348 try {
349 FileItemIterator iter = getItemIterator(ctx);
350 List items = new ArrayList();
351 FileItemFactory fac = getFileItemFactory();
352 if (fac == null) {
353 throw new NullPointerException(
354 "No FileItemFactory has been set.");
355 }
356 while (iter.hasNext()) {
357 FileItemStream item = iter.next();
358 FileItem fileItem = fac.createItem(item.getFieldName(),
359 item.getContentType(), item.isFormField(),
360 item.getName());
361 try {
362 Streams.copy(item.openStream(), fileItem.getOutputStream(),
363 true);
364 } catch (FileUploadIOException e) {
365 throw (FileUploadException) e.getCause();
366 } catch (IOException e) {
367 throw new IOFileUploadException(
368 "Processing of " + MULTIPART_FORM_DATA
369 + " request failed. " + e.getMessage(), e);
370 }
371 if (fileItem instanceof FileItemHeadersSupport) {
372 final FileItemHeaders fih = item.getHeaders();
373 ((FileItemHeadersSupport) fileItem).setHeaders(fih);
374 }
375 items.add(fileItem);
376 }
377 return items;
378 } catch (FileUploadIOException e) {
379 throw (FileUploadException) e.getCause();
380 } catch (IOException e) {
381 throw new FileUploadException(e.getMessage(), e);
382 }
383 }
384
385
386
387
388
389
390
391
392
393
394
395
396
397 protected byte[] getBoundary(String contentType) {
398 ParameterParser parser = new ParameterParser();
399 parser.setLowerCaseNames(true);
400
401 Map params = parser.parse(contentType, new char[] {';', ','});
402 String boundaryStr = (String) params.get("boundary");
403
404 if (boundaryStr == null) {
405 return null;
406 }
407 byte[] boundary;
408 try {
409 boundary = boundaryStr.getBytes("ISO-8859-1");
410 } catch (UnsupportedEncodingException e) {
411 boundary = boundaryStr.getBytes();
412 }
413 return boundary;
414 }
415
416
417
418
419
420
421
422
423
424
425
426 protected String getFileName(Map
427 return getFileName(getHeader(headers, CONTENT_DISPOSITION));
428 }
429
430
431
432
433
434
435
436
437
438 protected String getFileName(FileItemHeaders headers) {
439 return getFileName(headers.getHeader(CONTENT_DISPOSITION));
440 }
441
442
443
444
445
446
447 private String getFileName(String pContentDisposition) {
448 String fileName = null;
449 if (pContentDisposition != null) {
450 String cdl = pContentDisposition.toLowerCase();
451 if (cdl.startsWith(FORM_DATA) || cdl.startsWith(ATTACHMENT)) {
452 ParameterParser parser = new ParameterParser();
453 parser.setLowerCaseNames(true);
454
455 Map params = parser.parse(pContentDisposition, ';');
456 if (params.containsKey("filename")) {
457 fileName = (String) params.get("filename");
458 if (fileName != null) {
459 fileName = fileName.trim();
460 } else {
461
462
463
464 fileName = "";
465 }
466 }
467 }
468 }
469 return fileName;
470 }
471
472
473
474
475
476
477
478
479
480
481 protected String getFieldName(FileItemHeaders headers) {
482 return getFieldName(headers.getHeader(CONTENT_DISPOSITION));
483 }
484
485
486
487
488
489
490
491 private String getFieldName(String pContentDisposition) {
492 String fieldName = null;
493 if (pContentDisposition != null
494 && pContentDisposition.toLowerCase().startsWith(FORM_DATA)) {
495 ParameterParser parser = new ParameterParser();
496 parser.setLowerCaseNames(true);
497
498 Map params = parser.parse(pContentDisposition, ';');
499 fieldName = (String) params.get("name");
500 if (fieldName != null) {
501 fieldName = fieldName.trim();
502 }
503 }
504 return fieldName;
505 }
506
507
508
509
510
511
512
513
514
515
516 protected String getFieldName(Map
517 return getFieldName(getHeader(headers, CONTENT_DISPOSITION));
518 }
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535 protected FileItem createItem(Map
536 boolean isFormField)
537 throws FileUploadException {
538 return getFileItemFactory().createItem(getFieldName(headers),
539 getHeader(headers, CONTENT_TYPE),
540 isFormField,
541 getFileName(headers));
542 }
543
544
545
546
547
548
549
550
551
552
553
554
555
556 protected FileItemHeaders getParsedHeaders(String headerPart) {
557 final int len = headerPart.length();
558 FileItemHeadersImpl headers = newFileItemHeaders();
559 int start = 0;
560 for (;;) {
561 int end = parseEndOfLine(headerPart, start);
562 if (start == end) {
563 break;
564 }
565 String header = headerPart.substring(start, end);
566 start = end + 2;
567 while (start < len) {
568 int nonWs = start;
569 while (nonWs < len) {
570 char c = headerPart.charAt(nonWs);
571 if (c != ' ' && c != '\t') {
572 break;
573 }
574 ++nonWs;
575 }
576 if (nonWs == start) {
577 break;
578 }
579
580 end = parseEndOfLine(headerPart, nonWs);
581 header += " " + headerPart.substring(nonWs, end);
582 start = end + 2;
583 }
584 parseHeaderLine(headers, header);
585 }
586 return headers;
587 }
588
589
590
591
592
593 protected FileItemHeadersImpl newFileItemHeaders() {
594 return new FileItemHeadersImpl();
595 }
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610 protected Map
611 FileItemHeaders headers = getParsedHeaders(headerPart);
612 Map result = new HashMap();
613 for (Iterator iter = headers.getHeaderNames(); iter.hasNext();) {
614 String headerName = (String) iter.next();
615 Iterator iter2 = headers.getHeaders(headerName);
616 String headerValue = (String) iter2.next();
617 while (iter2.hasNext()) {
618 headerValue += "," + iter2.next();
619 }
620 result.put(headerName, headerValue);
621 }
622 return result;
623 }
624
625
626
627
628
629
630
631
632
633 private int parseEndOfLine(String headerPart, int end) {
634 int index = end;
635 for (;;) {
636 int offset = headerPart.indexOf('\r', index);
637 if (offset == -1 || offset + 1 >= headerPart.length()) {
638 throw new IllegalStateException(
639 "Expected headers to be terminated by an empty line.");
640 }
641 if (headerPart.charAt(offset + 1) == '\n') {
642 return offset;
643 }
644 index = offset + 1;
645 }
646 }
647
648
649
650
651
652
653 private void parseHeaderLine(FileItemHeadersImpl headers, String header) {
654 final int colonOffset = header.indexOf(':');
655 if (colonOffset == -1) {
656
657 return;
658 }
659 String headerName = header.substring(0, colonOffset).trim();
660 String headerValue =
661 header.substring(header.indexOf(':') + 1).trim();
662 headers.addHeader(headerName, headerValue);
663 }
664
665
666
667
668
669
670
671
672
673
674
675
676 protected final String getHeader(Map
677 String name) {
678 return (String) headers.get(name.toLowerCase());
679 }
680
681
682
683
684
685 private class FileItemIteratorImpl implements FileItemIterator {
686
687
688
689 private class FileItemStreamImpl implements FileItemStream {
690
691
692 private final String contentType;
693
694
695 private final String fieldName;
696
697
698 private final String name;
699
700
701 private final boolean formField;
702
703
704 private final InputStream stream;
705
706
707 private boolean opened;
708
709
710 private FileItemHeaders headers;
711
712
713
714
715
716
717
718
719
720
721 FileItemStreamImpl(String pName, String pFieldName,
722 String pContentType, boolean pFormField,
723 long pContentLength) throws IOException {
724 name = pName;
725 fieldName = pFieldName;
726 contentType = pContentType;
727 formField = pFormField;
728 final ItemInputStream itemStream = multi.newInputStream();
729 InputStream istream = itemStream;
730 if (fileSizeMax != -1) {
731 if (pContentLength != -1
732 && pContentLength > fileSizeMax) {
733 FileUploadException e =
734 new FileSizeLimitExceededException(
735 "The field " + fieldName
736 + " exceeds its maximum permitted "
737 + " size of " + fileSizeMax
738 + " characters.",
739 pContentLength, fileSizeMax);
740 throw new FileUploadIOException(e);
741 }
742 istream = new LimitedInputStream(istream, fileSizeMax) {
743 protected void raiseError(long pSizeMax, long pCount)
744 throws IOException {
745 itemStream.close(true);
746 FileUploadException e =
747 new FileSizeLimitExceededException(
748 "The field " + fieldName
749 + " exceeds its maximum permitted "
750 + " size of " + pSizeMax
751 + " characters.",
752 pCount, pSizeMax);
753 throw new FileUploadIOException(e);
754 }
755 };
756 }
757 stream = istream;
758 }
759
760
761
762
763
764 public String getContentType() {
765 return contentType;
766 }
767
768
769
770
771
772 public String getFieldName() {
773 return fieldName;
774 }
775
776
777
778
779
780 public String getName() {
781 return name;
782 }
783
784
785
786
787
788
789 public boolean isFormField() {
790 return formField;
791 }
792
793
794
795
796
797
798
799 public InputStream openStream() throws IOException {
800 if (opened) {
801 throw new IllegalStateException(
802 "The stream was already opened.");
803 }
804 if (((Closeable) stream).isClosed()) {
805 throw new FileItemStream.ItemSkippedException();
806 }
807 return stream;
808 }
809
810
811
812
813
814 void close() throws IOException {
815 stream.close();
816 }
817
818
819
820
821
822 public FileItemHeaders getHeaders() {
823 return headers;
824 }
825
826
827
828
829
830 public void setHeaders(FileItemHeaders pHeaders) {
831 headers = pHeaders;
832 }
833 }
834
835
836
837
838 private final MultipartStream multi;
839
840
841
842
843 private final MultipartStream.ProgressNotifier notifier;
844
845
846
847 private final byte[] boundary;
848
849
850
851 private FileItemStreamImpl currentItem;
852
853
854
855 private String currentFieldName;
856
857
858
859 private boolean skipPreamble;
860
861
862
863 private boolean itemValid;
864
865
866
867 private boolean eof;
868
869
870
871
872
873
874
875
876 FileItemIteratorImpl(RequestContext ctx)
877 throws FileUploadException, IOException {
878 if (ctx == null) {
879 throw new NullPointerException("ctx parameter");
880 }
881
882 String contentType = ctx.getContentType();
883 if ((null == contentType)
884 || (!contentType.toLowerCase().startsWith(MULTIPART))) {
885 throw new InvalidContentTypeException(
886 "the request doesn't contain a "
887 + MULTIPART_FORM_DATA
888 + " or "
889 + MULTIPART_MIXED
890 + " stream, content type header is "
891 + contentType);
892 }
893
894 InputStream input = ctx.getInputStream();
895
896 if (sizeMax >= 0) {
897 int requestSize = ctx.getContentLength();
898 if (requestSize == -1) {
899 input = new LimitedInputStream(input, sizeMax) {
900 protected void raiseError(long pSizeMax, long pCount)
901 throws IOException {
902 FileUploadException ex =
903 new SizeLimitExceededException(
904 "the request was rejected because"
905 + " its size (" + pCount
906 + ") exceeds the configured maximum"
907 + " (" + pSizeMax + ")",
908 pCount, pSizeMax);
909 throw new FileUploadIOException(ex);
910 }
911 };
912 } else {
913 if (sizeMax >= 0 && requestSize > sizeMax) {
914 throw new SizeLimitExceededException(
915 "the request was rejected because its size ("
916 + requestSize
917 + ") exceeds the configured maximum ("
918 + sizeMax + ")",
919 requestSize, sizeMax);
920 }
921 }
922 }
923
924 String charEncoding = headerEncoding;
925 if (charEncoding == null) {
926 charEncoding = ctx.getCharacterEncoding();
927 }
928
929 boundary = getBoundary(contentType);
930 if (boundary == null) {
931 throw new FileUploadException(
932 "the request was rejected because "
933 + "no multipart boundary was found");
934 }
935
936 notifier = new MultipartStream.ProgressNotifier(listener,
937 ctx.getContentLength());
938 multi = new MultipartStream(input, boundary, notifier);
939 multi.setHeaderEncoding(charEncoding);
940
941 skipPreamble = true;
942 findNextItem();
943 }
944
945
946
947
948
949
950 private boolean findNextItem() throws IOException {
951 if (eof) {
952 return false;
953 }
954 if (currentItem != null) {
955 currentItem.close();
956 currentItem = null;
957 }
958 for (;;) {
959 boolean nextPart;
960 if (skipPreamble) {
961 nextPart = multi.skipPreamble();
962 } else {
963 nextPart = multi.readBoundary();
964 }
965 if (!nextPart) {
966 if (currentFieldName == null) {
967
968 eof = true;
969 return false;
970 }
971
972 multi.setBoundary(boundary);
973 currentFieldName = null;
974 continue;
975 }
976 FileItemHeaders headers = getParsedHeaders(multi.readHeaders());
977 if (currentFieldName == null) {
978
979 String fieldName = getFieldName(headers);
980 if (fieldName != null) {
981 String subContentType = headers.getHeader(CONTENT_TYPE);
982 if (subContentType != null
983 && subContentType.toLowerCase()
984 .startsWith(MULTIPART_MIXED)) {
985 currentFieldName = fieldName;
986
987 byte[] subBoundary = getBoundary(subContentType);
988 multi.setBoundary(subBoundary);
989 skipPreamble = true;
990 continue;
991 }
992 String fileName = getFileName(headers);
993 currentItem = new FileItemStreamImpl(fileName,
994 fieldName, headers.getHeader(CONTENT_TYPE),
995 fileName == null, getContentLength(headers));
996 notifier.noteItem();
997 itemValid = true;
998 return true;
999 }
1000 } else {
1001 String fileName = getFileName(headers);
1002 if (fileName != null) {
1003 currentItem = new FileItemStreamImpl(fileName,
1004 currentFieldName,
1005 headers.getHeader(CONTENT_TYPE),
1006 false, getContentLength(headers));
1007 notifier.noteItem();
1008 itemValid = true;
1009 return true;
1010 }
1011 }
1012 multi.discardBodyData();
1013 }
1014 }
1015
1016 private long getContentLength(FileItemHeaders pHeaders) {
1017 try {
1018 return Long.parseLong(pHeaders.getHeader(CONTENT_LENGTH));
1019 } catch (Exception e) {
1020 return -1;
1021 }
1022 }
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033 public boolean hasNext() throws FileUploadException, IOException {
1034 if (eof) {
1035 return false;
1036 }
1037 if (itemValid) {
1038 return true;
1039 }
1040 return findNextItem();
1041 }
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053 public FileItemStream next() throws FileUploadException, IOException {
1054 if (eof || (!itemValid && !hasNext())) {
1055 throw new NoSuchElementException();
1056 }
1057 itemValid = false;
1058 return currentItem;
1059 }
1060 }
1061
1062
1063
1064
1065
1066 public static class FileUploadIOException extends IOException {
1067
1068
1069 private static final long serialVersionUID = -7047616958165584154L;
1070
1071
1072
1073
1074 private final FileUploadException cause;
1075
1076
1077
1078
1079
1080
1081 public FileUploadIOException(FileUploadException pCause) {
1082
1083 cause = pCause;
1084 }
1085
1086
1087
1088
1089
1090 public Throwable getCause() {
1091 return cause;
1092 }
1093 }
1094
1095
1096
1097
1098 public static class InvalidContentTypeException
1099 extends FileUploadException {
1100
1101
1102 private static final long serialVersionUID = -9073026332015646668L;
1103
1104
1105
1106
1107
1108 public InvalidContentTypeException() {
1109
1110 }
1111
1112
1113
1114
1115
1116
1117
1118 public InvalidContentTypeException(String message) {
1119 super(message);
1120 }
1121 }
1122
1123
1124
1125
1126 public static class IOFileUploadException extends FileUploadException {
1127
1128
1129 private static final long serialVersionUID = 1749796615868477269L;
1130
1131
1132
1133
1134 private final IOException cause;
1135
1136
1137
1138
1139
1140
1141 public IOFileUploadException(String pMsg, IOException pException) {
1142 super(pMsg);
1143 cause = pException;
1144 }
1145
1146
1147
1148
1149
1150 public Throwable getCause() {
1151 return cause;
1152 }
1153 }
1154
1155
1156
1157
1158 protected abstract static class SizeException extends FileUploadException {
1159
1160
1161
1162 private final long actual;
1163
1164
1165
1166
1167 private final long permitted;
1168
1169
1170
1171
1172
1173
1174
1175 protected SizeException(String message, long actual, long permitted) {
1176 super(message);
1177 this.actual = actual;
1178 this.permitted = permitted;
1179 }
1180
1181
1182
1183
1184
1185
1186 public long getActualSize() {
1187 return actual;
1188 }
1189
1190
1191
1192
1193
1194
1195 public long getPermittedSize() {
1196 return permitted;
1197 }
1198 }
1199
1200
1201
1202
1203
1204
1205
1206
1207 public static class UnknownSizeException
1208 extends FileUploadException {
1209
1210
1211 private static final long serialVersionUID = 7062279004812015273L;
1212
1213
1214
1215
1216
1217 public UnknownSizeException() {
1218 super();
1219 }
1220
1221
1222
1223
1224
1225
1226
1227 public UnknownSizeException(String message) {
1228 super(message);
1229 }
1230 }
1231
1232
1233
1234
1235 public static class SizeLimitExceededException
1236 extends SizeException {
1237
1238
1239 private static final long serialVersionUID = -2474893167098052828L;
1240
1241
1242
1243
1244
1245 public SizeLimitExceededException() {
1246 this(null, 0, 0);
1247 }
1248
1249
1250
1251
1252
1253
1254 public SizeLimitExceededException(String message) {
1255 this(message, 0, 0);
1256 }
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266 public SizeLimitExceededException(String message, long actual,
1267 long permitted) {
1268 super(message, actual, permitted);
1269 }
1270 }
1271
1272
1273
1274
1275 public static class FileSizeLimitExceededException
1276 extends SizeException {
1277
1278
1279 private static final long serialVersionUID = 8150776562029630058L;
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289 public FileSizeLimitExceededException(String message, long actual,
1290 long permitted) {
1291 super(message, actual, permitted);
1292 }
1293 }
1294
1295
1296
1297
1298
1299 public ProgressListener getProgressListener() {
1300 return listener;
1301 }
1302
1303
1304
1305
1306
1307 public void setProgressListener(ProgressListener pListener) {
1308 listener = pListener;
1309 }
1310 }