1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.fileupload2.core;
18
19 import java.io.IOException;
20 import java.io.InputStream;
21 import java.util.Iterator;
22 import java.util.Locale;
23 import java.util.NoSuchElementException;
24 import java.util.Objects;
25
26 import org.apache.commons.io.Charsets;
27 import org.apache.commons.io.IOUtils;
28 import org.apache.commons.io.input.BoundedInputStream;
29
30
31
32
33 class FileItemInputIteratorImpl implements FileItemInputIterator {
34
35
36
37
38
39
40 private final AbstractFileUpload<?, ?, ?> fileUpload;
41
42
43
44
45
46
47 private final RequestContext requestContext;
48
49
50
51
52 private long sizeMax;
53
54
55
56
57 private long fileSizeMax;
58
59
60
61
62 private MultipartInput multiPartInput;
63
64
65
66
67 private MultipartInput.ProgressNotifier progressNotifier;
68
69
70
71
72 private byte[] multiPartBoundary;
73
74
75
76
77 private FileItemInputImpl currentItem;
78
79
80
81
82 private String currentFieldName;
83
84
85
86
87 private boolean skipPreamble;
88
89
90
91
92 private boolean itemValid;
93
94
95
96
97 private boolean eof;
98
99
100
101
102
103
104
105
106
107 FileItemInputIteratorImpl(final AbstractFileUpload<?, ?, ?> fileUploadBase, final RequestContext requestContext) throws FileUploadException, IOException {
108 this.fileUpload = fileUploadBase;
109 this.sizeMax = fileUploadBase.getSizeMax();
110 this.fileSizeMax = fileUploadBase.getFileSizeMax();
111 this.requestContext = Objects.requireNonNull(requestContext, "requestContext");
112 this.skipPreamble = true;
113 findNextItem();
114 }
115
116
117
118
119
120
121
122 private boolean findNextItem() throws FileUploadException, IOException {
123 if (eof) {
124 return false;
125 }
126 if (currentItem != null) {
127 currentItem.close();
128 currentItem = null;
129 }
130 final var multi = getMultiPartInput();
131 for (;;) {
132 final boolean nextPart;
133 if (skipPreamble) {
134 nextPart = multi.skipPreamble();
135 } else {
136 nextPart = multi.readBoundary();
137 }
138 if (!nextPart) {
139 if (currentFieldName == null) {
140
141 eof = true;
142 return false;
143 }
144
145 multi.setBoundary(multiPartBoundary);
146 currentFieldName = null;
147 continue;
148 }
149 final var headers = fileUpload.getParsedHeaders(multi.readHeaders());
150 if (currentFieldName == null) {
151
152 final var fieldName = fileUpload.getFieldName(headers);
153 if (fieldName != null) {
154 final var subContentType = headers.getHeader(AbstractFileUpload.CONTENT_TYPE);
155 if (subContentType != null && subContentType.toLowerCase(Locale.ENGLISH).startsWith(AbstractFileUpload.MULTIPART_MIXED)) {
156 currentFieldName = fieldName;
157
158 final var subBoundary = fileUpload.getBoundary(subContentType);
159 multi.setBoundary(subBoundary);
160 skipPreamble = true;
161 continue;
162 }
163 final var fileName = fileUpload.getFileName(headers);
164 currentItem = new FileItemInputImpl(this, fileName, fieldName, headers.getHeader(AbstractFileUpload.CONTENT_TYPE), fileName == null,
165 getContentLength(headers));
166 currentItem.setHeaders(headers);
167 progressNotifier.noteItem();
168 itemValid = true;
169 return true;
170 }
171 } else {
172 final var fileName = fileUpload.getFileName(headers);
173 if (fileName != null) {
174 currentItem = new FileItemInputImpl(this, fileName, currentFieldName, headers.getHeader(AbstractFileUpload.CONTENT_TYPE), false,
175 getContentLength(headers));
176 currentItem.setHeaders(headers);
177 progressNotifier.noteItem();
178 itemValid = true;
179 return true;
180 }
181 }
182 multi.discardBodyData();
183 }
184 }
185
186 private long getContentLength(final FileItemHeaders headers) {
187 try {
188 return Long.parseLong(headers.getHeader(AbstractFileUpload.CONTENT_LENGTH));
189 } catch (final Exception e) {
190 return -1;
191 }
192 }
193
194 @Override
195 public long getFileSizeMax() {
196 return fileSizeMax;
197 }
198
199 public MultipartInput getMultiPartInput() throws FileUploadException, IOException {
200 if (multiPartInput == null) {
201 init(fileUpload, requestContext);
202 }
203 return multiPartInput;
204 }
205
206 @Override
207 public long getSizeMax() {
208 return sizeMax;
209 }
210
211
212
213
214
215
216
217
218 @Override
219 public boolean hasNext() throws IOException {
220 if (eof) {
221 return false;
222 }
223 if (itemValid) {
224 return true;
225 }
226 return findNextItem();
227 }
228
229 protected void init(final AbstractFileUpload<?, ?, ?> fileUploadBase, final RequestContext initContext) throws FileUploadException, IOException {
230 final var contentType = requestContext.getContentType();
231 if (null == contentType || !contentType.toLowerCase(Locale.ENGLISH).startsWith(AbstractFileUpload.MULTIPART)) {
232 throw new FileUploadContentTypeException(String.format("the request doesn't contain a %s or %s stream, content type header is %s",
233 AbstractFileUpload.MULTIPART_FORM_DATA, AbstractFileUpload.MULTIPART_MIXED, contentType), contentType);
234 }
235 final var contentLengthInt = requestContext.getContentLength();
236
237 final var requestSize = RequestContext.class.isAssignableFrom(requestContext.getClass())
238
239 ? requestContext.getContentLength()
240 : contentLengthInt;
241
242
243 final InputStream inputStream;
244 if (sizeMax >= 0) {
245 if (requestSize != -1 && requestSize > sizeMax) {
246 throw new FileUploadSizeException(
247 String.format("the request was rejected because its size (%s) exceeds the configured maximum (%s)", requestSize, sizeMax), sizeMax,
248 requestSize);
249 }
250
251 inputStream = new BoundedInputStream(requestContext.getInputStream(), sizeMax) {
252 @Override
253 protected void onMaxLength(final long maxLen, final long count) throws IOException {
254 throw new FileUploadSizeException(
255 String.format("The request was rejected because its size (%s) exceeds the configured maximum (%s)", count, maxLen), maxLen, count);
256 }
257 };
258 } else {
259 inputStream = requestContext.getInputStream();
260 }
261
262 final var charset = Charsets.toCharset(fileUploadBase.getHeaderCharset(), requestContext.getCharset());
263 multiPartBoundary = fileUploadBase.getBoundary(contentType);
264 if (multiPartBoundary == null) {
265 IOUtils.closeQuietly(inputStream);
266 throw new FileUploadException("the request was rejected because no multipart boundary was found");
267 }
268
269 progressNotifier = new MultipartInput.ProgressNotifier(fileUploadBase.getProgressListener(), requestSize);
270 try {
271 multiPartInput = MultipartInput.builder().setInputStream(inputStream).setBoundary(multiPartBoundary).setProgressNotifier(progressNotifier).get();
272 } catch (final IllegalArgumentException e) {
273 IOUtils.closeQuietly(inputStream);
274 throw new FileUploadContentTypeException(String.format("The boundary specified in the %s header is too long", AbstractFileUpload.CONTENT_TYPE), e);
275 }
276 multiPartInput.setHeaderCharset(charset);
277 }
278
279
280
281
282
283
284
285
286
287 @Override
288 public FileItemInput next() throws IOException {
289 if (eof || !itemValid && !hasNext()) {
290 throw new NoSuchElementException();
291 }
292 itemValid = false;
293 return currentItem;
294 }
295
296 @Override
297 public void setFileSizeMax(final long fileSizeMax) {
298 this.fileSizeMax = fileSizeMax;
299 }
300
301 @Override
302 public void setSizeMax(final long sizeMax) {
303 this.sizeMax = sizeMax;
304 }
305
306 @Override
307 public Iterator<FileItemInput> unwrap() {
308
309 return (Iterator<FileItemInput>) this;
310 }
311
312 }