1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.codelibs.fess.mylasta.direction.sponsor;
17
18 import java.io.File;
19 import java.io.IOException;
20 import java.io.InputStream;
21 import java.io.Serializable;
22 import java.nio.charset.Charset;
23 import java.util.HashMap;
24 import java.util.List;
25 import java.util.Map;
26
27 import org.apache.commons.fileupload2.core.DiskFileItem;
28 import org.apache.commons.fileupload2.core.DiskFileItemFactory;
29 import org.apache.commons.fileupload2.core.FileUploadByteCountLimitException;
30 import org.apache.commons.fileupload2.core.FileUploadException;
31 import org.apache.commons.fileupload2.jakarta.servlet6.JakartaServletDiskFileUpload;
32 import org.apache.logging.log4j.LogManager;
33 import org.apache.logging.log4j.Logger;
34 import org.codelibs.fess.util.ComponentUtil;
35 import org.dbflute.helper.message.ExceptionMessageBuilder;
36 import org.lastaflute.core.message.UserMessages;
37 import org.lastaflute.web.exception.Forced404NotFoundException;
38 import org.lastaflute.web.ruts.multipart.MultipartFormFile;
39 import org.lastaflute.web.ruts.multipart.MultipartRequestHandler;
40 import org.lastaflute.web.ruts.multipart.MultipartRequestWrapper;
41 import org.lastaflute.web.ruts.multipart.exception.MultipartExceededException;
42 import org.lastaflute.web.util.LaServletContextUtil;
43
44 import jakarta.servlet.ServletContext;
45 import jakarta.servlet.ServletException;
46 import jakarta.servlet.http.HttpServletRequest;
47
48
49
50
51
52
53 public class FessMultipartRequestHandler implements MultipartRequestHandler {
54
55
56
57
58 private static final Logger logger = LogManager.getLogger(FessMultipartRequestHandler.class);
59
60
61
62
63
64 protected static final String CONTEXT_TEMPDIR_KEY = jakarta.servlet.ServletContext.TEMPDIR;
65 protected static final String JAVA_IO_TMPDIR_KEY = "java.io.tmpdir";
66
67
68
69
70
71
72 protected Map<String, Object> elementsAll;
73 protected Map<String, MultipartFormFile> elementsFile;
74 protected Map<String, String[]> elementsText;
75
76
77
78
79 @Override
80 public void handleRequest(final HttpServletRequest request) throws ServletException {
81 final JakartaServletDiskFileUpload upload = createDiskFileUpload(request);
82 prepareElementsHash();
83 try {
84 final List<DiskFileItem> items = parseRequest(request, upload);
85 mappingParameter(request, items);
86 } catch (final FileUploadByteCountLimitException e) {
87 handleSizeLimitExceededException(request, e);
88 } catch (final FileUploadException e) {
89 handleFileUploadException(e);
90 }
91 }
92
93 protected void prepareElementsHash() {
94
95 elementsAll = new HashMap<>();
96 elementsText = new HashMap<>();
97 elementsFile = new HashMap<>();
98 }
99
100 protected List<DiskFileItem> parseRequest(final HttpServletRequest request, final JakartaServletDiskFileUpload upload)
101 throws FileUploadException {
102 return upload.parseRequest(request);
103 }
104
105
106
107
108 protected JakartaServletDiskFileUpload createDiskFileUpload(final HttpServletRequest request) {
109 final DiskFileItemFactory fileItemFactory = createDiskFileItemFactory();
110 final JakartaServletDiskFileUpload upload = newServletFileUpload(fileItemFactory);
111 setupServletFileUpload(upload, request);
112 return upload;
113 }
114
115
116
117
118 protected DiskFileItemFactory createDiskFileItemFactory() {
119 final int sizeThreshold = getSizeThreshold();
120 final File repository = createRepositoryFile();
121 return DiskFileItemFactory.builder().setBufferSize(sizeThreshold).setFile(repository).get();
122 }
123
124 protected int getSizeThreshold() {
125 return ComponentUtil.getFessConfig().getHttpFileuploadThresholdSizeAsInteger();
126 }
127
128 protected File createRepositoryFile() {
129 return new File(getRepositoryPath());
130 }
131
132 protected String getRepositoryPath() {
133 final ServletContext servletContext = LaServletContextUtil.getServletContext();
134 if (servletContext.getAttribute(CONTEXT_TEMPDIR_KEY) instanceof final File tempDirFile) {
135 final String tempDir = tempDirFile.getAbsolutePath();
136 if (tempDir != null && tempDir.length() > 0) {
137 return tempDir;
138 }
139 }
140 return System.getProperty(JAVA_IO_TMPDIR_KEY);
141 }
142
143
144
145
146 protected JakartaServletDiskFileUpload newServletFileUpload(final DiskFileItemFactory fileItemFactory) {
147 return new JakartaServletDiskFileUpload(fileItemFactory) {
148 @Override
149 public byte[] getBoundary(final String contentType) {
150 final byte[] boundary = super.getBoundary(contentType);
151 checkBoundarySize(contentType, boundary);
152 return boundary;
153 }
154 };
155 }
156
157
158
159 protected void checkBoundarySize(final String contentType, final byte[] boundary) {
160 final int boundarySize = boundary.length;
161 final int limitSize = getBoundaryLimitSize();
162 if (boundarySize > getBoundaryLimitSize()) {
163 throwTooLongBoundarySizeException(contentType, boundarySize, limitSize);
164 }
165 }
166
167 protected int getBoundaryLimitSize() {
168
169
170 return 2000;
171 }
172
173 protected void throwTooLongBoundarySizeException(final String contentType, final int boundarySize, final int limitSize) {
174 final ExceptionMessageBuilder br = new ExceptionMessageBuilder();
175 br.addNotice("Too long boundary size so treats it as 404.");
176 br.addItem("Advice");
177 br.addElement("Against for CVE-2014-0050 (JVN14876762).");
178 br.addElement("Boundary size is limited by Framework.");
179 br.addElement("Too long boundary is treated as 404 because it's thought of as attack.");
180 br.addElement("");
181 br.addElement("While, you can override the boundary limit size");
182 br.addElement(" in " + getClass().getSimpleName() + ".");
183 br.addItem("Content Type");
184 br.addElement(contentType);
185 br.addItem("Boundary Size");
186 br.addElement(boundarySize);
187 br.addItem("Limit Size");
188 br.addElement(limitSize);
189 final String msg = br.buildExceptionMessage();
190 throw new Forced404NotFoundException(msg, UserMessages.empty());
191 }
192
193 protected void setupServletFileUpload(final JakartaServletDiskFileUpload upload, final HttpServletRequest request) {
194 upload.setHeaderCharset(Charset.forName(request.getCharacterEncoding()));
195 upload.setMaxSize(getSizeMax());
196 upload.setMaxFileCount(getFileCountMax());
197 }
198
199 protected long getSizeMax() {
200 return ComponentUtil.getFessConfig().getHttpFileuploadMaxSizeAsInteger().longValue();
201 }
202
203 protected long getFileCountMax() {
204 return ComponentUtil.getFessConfig().getHttpFileuploadMaxFileCountAsInteger().longValue();
205 }
206
207
208
209
210 protected void mappingParameter(final HttpServletRequest request, final List<DiskFileItem> items) {
211 showFieldLoggingTitle();
212 for (DiskFileItem item : items) {
213 if (item.isFormField()) {
214 showFormFieldParameter(item);
215 addTextParameter(request, item);
216 } else {
217 showFileFieldParameter(item);
218 final String itemName = item.getName();
219 if (itemName != null && !itemName.isEmpty()) {
220 addFileParameter(item);
221 }
222 }
223 }
224 }
225
226
227
228
229
230 protected void showFieldLoggingTitle() {
231 if (logger.isDebugEnabled()) {
232 logger.debug("[Multipart Request Parameter]");
233 }
234 }
235
236 protected void showFormFieldParameter(final DiskFileItem item) {
237 if (logger.isDebugEnabled()) {
238 try {
239 logger.debug("[param] {}={}", item.getFieldName(), item.getString());
240 } catch (final IOException e) {
241 logger.debug("[param] {}=(failed to read)", item.getFieldName());
242 }
243 }
244 }
245
246 protected void showFileFieldParameter(final DiskFileItem item) {
247 if (logger.isDebugEnabled()) {
248 logger.debug("[param] {}:{name={}, size={}}", item.getFieldName(), item.getName(), item.getSize());
249 }
250 }
251
252
253
254
255 protected void addTextParameter(final HttpServletRequest request, final DiskFileItem item) {
256 final String fieldName = item.getFieldName();
257 final Charset encoding = Charset.forName(request.getCharacterEncoding());
258 String value = null;
259 boolean haveValue = false;
260 if (encoding != null) {
261 try {
262 value = item.getString(encoding);
263 haveValue = true;
264 } catch (final Exception e) {}
265 }
266 if (!haveValue) {
267 try {
268 value = item.getString(Charset.forName("ISO-8859-1"));
269 } catch (final java.io.UnsupportedEncodingException uee) {
270 try {
271 value = item.getString();
272 } catch (final IOException e) {
273 throw new IllegalStateException("Failed to get string from the item: " + item, e);
274 }
275 } catch (final IOException e) {
276 throw new IllegalStateException("Failed to get string from the item: " + item, e);
277 }
278 haveValue = true;
279 }
280 if (request instanceof final MultipartRequestWrapper wrapper) {
281 wrapper.setParameter(fieldName, value);
282 }
283 final String[] oldArray = elementsText.get(fieldName);
284 final String[] newArray;
285 if (oldArray != null) {
286 newArray = new String[oldArray.length + 1];
287 System.arraycopy(oldArray, 0, newArray, 0, oldArray.length);
288 newArray[oldArray.length] = value;
289 } else {
290 newArray = new String[] { value };
291 }
292 elementsAll.put(fieldName, newArray);
293 elementsText.put(fieldName, newArray);
294 }
295
296 protected void addFileParameter(final DiskFileItem item) {
297 final String fieldName = item.getFieldName();
298 final MultipartFormFile formFile = newActionMultipartFormFile(item);
299 elementsAll.put(fieldName, formFile);
300 elementsFile.put(fieldName, formFile);
301 }
302
303 protected ActionMultipartFormFile newActionMultipartFormFile(final DiskFileItem item) {
304 return new ActionMultipartFormFile(item);
305 }
306
307
308
309
310 protected void handleSizeLimitExceededException(final HttpServletRequest request, final FileUploadByteCountLimitException e) {
311 final long actual = e.getActualSize();
312 final long permitted = e.getPermitted();
313 final String msg = "Exceeded size of the multipart request: actual=" + actual + " permitted=" + permitted;
314 request.setAttribute(MAX_LENGTH_EXCEEDED_KEY, new MultipartExceededException(msg, actual, permitted, e));
315 try {
316 final InputStream is = request.getInputStream();
317 try {
318 final byte[] buf = new byte[1024];
319 while ((is.read(buf)) != -1) {}
320 } catch (final Exception ignored) {} finally {
321 try {
322 is.close();
323 } catch (final Exception ignored) {}
324 }
325 } catch (final Exception ignored) {}
326 }
327
328 protected void handleFileUploadException(final FileUploadException e) throws ServletException {
329
330
331 throw new ServletException("Failed to upload the file.", e);
332 }
333
334
335
336
337 @Override
338 public void rollback() {
339 for (MultipartFormFile formFile : elementsFile.values()) {
340 formFile.destroy();
341 }
342 }
343
344
345
346
347 @Override
348 public void finish() {
349 rollback();
350 }
351
352
353
354
355 protected static class ActionMultipartFormFile implements MultipartFormFile, Serializable {
356
357 private static final long serialVersionUID = 1L;
358
359 protected final DiskFileItem fileItem;
360
361 public ActionMultipartFormFile(final DiskFileItem fileItem) {
362 this.fileItem = fileItem;
363 }
364
365 @Override
366 public byte[] getFileData() throws IOException {
367 return fileItem.get();
368 }
369
370 @Override
371 public InputStream getInputStream() throws IOException {
372 return fileItem.getInputStream();
373 }
374
375 @Override
376 public String getContentType() {
377 return fileItem.getContentType();
378 }
379
380 @Override
381 public int getFileSize() {
382 return (int) fileItem.getSize();
383 }
384
385 @Override
386 public String getFileName() {
387 return getBaseFileName(fileItem.getName());
388 }
389
390 protected String getBaseFileName(final String filePath) {
391 final String fileName = new File(filePath).getName();
392 int colonIndex = fileName.indexOf(":");
393 if (colonIndex == -1) {
394 colonIndex = fileName.indexOf("\\\\");
395 }
396 final int backslashIndex = fileName.lastIndexOf("\\");
397 if (colonIndex > -1 && backslashIndex > -1) {
398 return fileName.substring(backslashIndex + 1);
399 }
400 return fileName;
401 }
402
403 @Override
404 public void destroy() {
405 try {
406 fileItem.delete();
407 } catch (final IOException e) {
408 throw new IllegalStateException("Failed to delete the fileItem: " + fileItem, e);
409 }
410 }
411
412 @Override
413 public String toString() {
414 return "formFile:{" + getFileName() + "}";
415 }
416 }
417
418
419
420
421 @Override
422 public Map<String, Object> getAllElements() {
423 return elementsAll;
424 }
425
426 @Override
427 public Map<String, String[]> getTextElements() {
428 return elementsText;
429 }
430
431 @Override
432 public Map<String, MultipartFormFile> getFileElements() {
433 return elementsFile;
434 }
435 }