View Javadoc
1   /*
2    * Copyright 2012-2017 CodeLibs Project and the Others.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
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,
13   * either express or implied. See the License for the specific language
14   * governing permissions and limitations under the License.
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.util.Hashtable;
23  import java.util.Iterator;
24  import java.util.List;
25  import java.util.Map;
26  
27  import javax.servlet.ServletException;
28  import javax.servlet.http.HttpServletRequest;
29  
30  import org.apache.commons.fileupload.FileItem;
31  import org.apache.commons.fileupload.FileUploadBase.SizeLimitExceededException;
32  import org.apache.commons.fileupload.FileUploadException;
33  import org.apache.commons.fileupload.disk.DiskFileItemFactory;
34  import org.apache.commons.fileupload.servlet.ServletFileUpload;
35  import org.dbflute.helper.message.ExceptionMessageBuilder;
36  import org.lastaflute.core.message.UserMessages;
37  import org.lastaflute.web.LastaWebKey;
38  import org.lastaflute.web.exception.Forced404NotFoundException;
39  import org.lastaflute.web.ruts.config.ModuleConfig;
40  import org.lastaflute.web.ruts.multipart.MultipartFormFile;
41  import org.lastaflute.web.ruts.multipart.MultipartRequestHandler;
42  import org.lastaflute.web.ruts.multipart.MultipartRequestWrapper;
43  import org.lastaflute.web.ruts.multipart.exception.MultipartExceededException;
44  import org.lastaflute.web.util.LaServletContextUtil;
45  import org.slf4j.Logger;
46  import org.slf4j.LoggerFactory;
47  
48  /**
49   * @author modified by jflute (originated in Seasar)
50   */
51  public class FessMultipartRequestHandler implements MultipartRequestHandler {
52  
53      // ===================================================================================
54      //                                                                          Definition
55      //                                                                          ==========
56      private static final Logger logger = LoggerFactory.getLogger(FessMultipartRequestHandler.class);
57      public static final long DEFAULT_SIZE_MAX = 250 * 1024 * 1024L; // 250MB
58      public static final int DEFAULT_SIZE_THRESHOLD = 256 * 1024; // 250KB
59      protected static final String CONTEXT_TEMPDIR_KEY = "javax.servlet.context.tempdir";
60      protected static final String JAVA_IO_TMPDIR_KEY = "java.io.tmpdir";
61  
62      // ===================================================================================
63      //                                                                           Attribute
64      //                                                                           =========
65      protected Map<String, Object> elementsAll;
66      protected Map<String, MultipartFormFile> elementsFile;
67      protected Map<String, String[]> elementsText;
68  
69      // ===================================================================================
70      //                                                                      Handle Request
71      //                                                                      ==============
72      @Override
73      public void handleRequest(final HttpServletRequest request) throws ServletException {
74          // /- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
75          // copied from super's method and extends it
76          // basically for JVN#14876762
77          // thought not all problems are resolved however the main case is safety
78          // - - - - - - - - - -/
79          final ServletFileUpload upload = createServletFileUpload(request);
80          prepareElementsHash();
81          try {
82              final List<FileItem> items = parseRequest(request, upload);
83              mappingParameter(request, items);
84          } catch (final SizeLimitExceededException e) {
85              handleSizeLimitExceededException(request, e);
86          } catch (final FileUploadException e) {
87              handleFileUploadException(e);
88          }
89      }
90  
91      protected ModuleConfig getModuleConfig(final HttpServletRequest request) {
92          return (ModuleConfig) request.getAttribute(LastaWebKey.MODULE_CONFIG_KEY);
93      }
94  
95      // ===================================================================================
96      //                                                            Create ServletFileUpload
97      //                                                            ========================
98      protected ServletFileUpload createServletFileUpload(final HttpServletRequest request) {
99          final DiskFileItemFactory fileItemFactory = createDiskFileItemFactory();
100         final ServletFileUpload upload = newServletFileUpload(fileItemFactory);
101         upload.setHeaderEncoding(request.getCharacterEncoding());
102         upload.setSizeMax(getSizeMax());
103         return upload;
104     }
105 
106     protected ServletFileUpload newServletFileUpload(final DiskFileItemFactory fileItemFactory) {
107         return new ServletFileUpload(fileItemFactory) {
108             @Override
109             protected byte[] getBoundary(final String contentType) { // for security
110                 final byte[] boundary = super.getBoundary(contentType);
111                 checkBoundarySize(contentType, boundary);
112                 return boundary;
113             }
114         };
115     }
116 
117     protected void checkBoundarySize(final String contentType, final byte[] boundary) {
118         final int boundarySize = boundary.length;
119         final int limitSize = getBoundaryLimitSize();
120         if (boundarySize > getBoundaryLimitSize()) {
121             throwTooLongBoundarySizeException(contentType, boundarySize, limitSize);
122         }
123     }
124 
125     protected int getBoundaryLimitSize() {
126         // one HTTP proxy tool already limits the size (e.g. 3450 bytes)
127         // so specify this size for test
128         return 2000; // you can override as you like it
129     }
130 
131     protected void throwTooLongBoundarySizeException(final String contentType, final int boundarySize, final int limitSize) {
132         final ExceptionMessageBuilder br = new ExceptionMessageBuilder();
133         br.addNotice("Too long boundary size so treats it as 404.");
134         br.addItem("Advice");
135         br.addElement("Against for JVN14876762.");
136         br.addElement("Boundary size is limited by Framework.");
137         br.addElement("Too long boundary is treated as 404 because it's thought of as attack.");
138         br.addElement("");
139         br.addElement("While, you can override the boundary limit size");
140         br.addElement(" in " + FessMultipartRequestHandler.class.getSimpleName() + ".");
141         br.addItem("Content Type");
142         br.addElement(contentType);
143         br.addItem("Boundary Size");
144         br.addElement(boundarySize);
145         br.addItem("Limit Size");
146         br.addElement(limitSize);
147         final String msg = br.buildExceptionMessage();
148         throw new Forced404NotFoundException(msg, UserMessages.empty()); // heavy attack!? so give no page to tell wasted action
149     }
150 
151     protected DiskFileItemFactory createDiskFileItemFactory() {
152         final File repository = createRepositoryFile();
153         return new DiskFileItemFactory((int) getSizeThreshold(), repository);
154     }
155 
156     protected File createRepositoryFile() {
157         return new File(getRepositoryPath());
158     }
159 
160     // ===================================================================================
161     //                                                                      Handling Parts
162     //                                                                      ==============
163     protected void prepareElementsHash() {
164         elementsText = new Hashtable<>();
165         elementsFile = new Hashtable<>();
166         elementsAll = new Hashtable<>();
167     }
168 
169     protected List<FileItem> parseRequest(final HttpServletRequest request, final ServletFileUpload upload) throws FileUploadException {
170         return upload.parseRequest(request);
171     }
172 
173     protected void mappingParameter(final HttpServletRequest request, final List<FileItem> items) {
174         showFieldLoggingTitle();
175         final Iterator<FileItem> iter = items.iterator();
176         while (iter.hasNext()) {
177             final FileItem item = iter.next();
178             if (item.isFormField()) {
179                 showFormFieldParameter(item);
180                 addTextParameter(request, item);
181             } else {
182                 showFileFieldParameter(item);
183                 final String itemName = item.getName();
184                 if (itemName != null && !itemName.isEmpty()) {
185                     addFileParameter(item);
186                 }
187             }
188         }
189     }
190 
191     protected void showFieldLoggingTitle() {
192         // logging filter cannot show the parameters when multi-part so logging here
193         if (logger.isDebugEnabled()) {
194             logger.debug("[Multipart Request Parameter]");
195         }
196     }
197 
198     protected void showFormFieldParameter(final FileItem item) {
199         if (logger.isDebugEnabled()) {
200             logger.debug("[param] {}={}", item.getFieldName(), item.getString());
201         }
202     }
203 
204     protected void showFileFieldParameter(final FileItem item) {
205         if (logger.isDebugEnabled()) {
206             logger.debug("[param] {}:{name={}, size={}}", item.getFieldName(), item.getName(), item.getSize());
207         }
208     }
209 
210     protected void handleSizeLimitExceededException(final HttpServletRequest request, final SizeLimitExceededException e) {
211         final long actual = e.getActualSize();
212         final long permitted = e.getPermittedSize();
213         final String msg = "Exceeded size of the multipart request: actual=" + actual + " permitted=" + permitted;
214         request.setAttribute(MAX_LENGTH_EXCEEDED_KEY, new MultipartExceededException(msg, actual, permitted, e));
215         try {
216             final InputStream is = request.getInputStream();
217             try {
218                 final byte[] buf = new byte[1024];
219                 while ((is.read(buf)) != -1) {}
220             } catch (final Exception ignored) {} finally {
221                 try {
222                     is.close();
223                 } catch (final Exception ignored) {}
224             }
225         } catch (final Exception ignored) {}
226     }
227 
228     protected void handleFileUploadException(final FileUploadException e) throws ServletException {
229         // suppress logging because it can be caught by logging filter
230         //log.error("Failed to parse multipart request", e);
231         throw new ServletException("Failed to upload the file.", e);
232     }
233 
234     // ===================================================================================
235     //                                                                           Roll-back
236     //                                                                           =========
237     @Override
238     public void rollback() {
239         final Iterator<MultipartFormFile> iter = elementsFile.values().iterator();
240         while (iter.hasNext()) {
241             final MultipartFormFile formFile = iter.next();
242             formFile.destroy();
243         }
244     }
245 
246     // ===================================================================================
247     //                                                                            Add Text
248     //                                                                            ========
249     protected void addTextParameter(final HttpServletRequest request, final FileItem item) {
250         final String name = item.getFieldName();
251         final String encoding = request.getCharacterEncoding();
252         String value = null;
253         boolean haveValue = false;
254         if (encoding != null) {
255             try {
256                 value = item.getString(encoding);
257                 haveValue = true;
258             } catch (final Exception e) {}
259         }
260         if (!haveValue) {
261             try {
262                 value = item.getString("ISO-8859-1");
263             } catch (final java.io.UnsupportedEncodingException uee) {
264                 value = item.getString();
265             }
266             haveValue = true;
267         }
268         if (request instanceof MultipartRequestWrapper) {
269             final MultipartRequestWrapper wrapper = (MultipartRequestWrapper) request;
270             wrapper.setParameter(name, value);
271         }
272         final String[] oldArray = elementsText.get(name);
273         final String[] newArray;
274         if (oldArray != null) {
275             newArray = new String[oldArray.length + 1];
276             System.arraycopy(oldArray, 0, newArray, 0, oldArray.length);
277             newArray[oldArray.length] = value;
278         } else {
279             newArray = new String[] { value };
280         }
281         elementsText.put(name, newArray);
282         elementsAll.put(name, newArray);
283     }
284 
285     protected void addFileParameter(final FileItem item) {
286         final MultipartFormFile formFile = newActionMultipartFormFile(item);
287         elementsFile.put(item.getFieldName(), formFile);
288         elementsAll.put(item.getFieldName(), formFile);
289     }
290 
291     protected ActionMultipartFormFile newActionMultipartFormFile(final FileItem item) {
292         return new ActionMultipartFormFile(item);
293     }
294 
295     // ===================================================================================
296     //                                                                              Finish
297     //                                                                              ======
298     @Override
299     public void finish() {
300         rollback();
301     }
302 
303     // ===================================================================================
304     //                                                                        Small Helper
305     //                                                                        ============
306     protected long getSizeMax() {
307         return DEFAULT_SIZE_MAX;
308     }
309 
310     protected long getSizeThreshold() {
311         return DEFAULT_SIZE_THRESHOLD;
312     }
313 
314     protected String getRepositoryPath() {
315         final File tempDirFile = (File) LaServletContextUtil.getServletContext().getAttribute(CONTEXT_TEMPDIR_KEY);
316         String tempDir = tempDirFile.getAbsolutePath();
317         if (tempDir == null || tempDir.length() == 0) {
318             tempDir = System.getProperty(JAVA_IO_TMPDIR_KEY);
319         }
320         return tempDir;
321     }
322 
323     // ===================================================================================
324     //                                                                           Form File
325     //                                                                           =========
326     protected static class ActionMultipartFormFile implements MultipartFormFile, Serializable {
327 
328         private static final long serialVersionUID = 1L;
329 
330         protected final FileItem fileItem;
331 
332         public ActionMultipartFormFile(final FileItem fileItem) {
333             this.fileItem = fileItem;
334         }
335 
336         @Override
337         public byte[] getFileData() throws IOException {
338             return fileItem.get();
339         }
340 
341         @Override
342         public InputStream getInputStream() throws IOException {
343             return fileItem.getInputStream();
344         }
345 
346         @Override
347         public String getContentType() {
348             return fileItem.getContentType();
349         }
350 
351         @Override
352         public int getFileSize() {
353             return (int) fileItem.getSize();
354         }
355 
356         @Override
357         public String getFileName() {
358             return getBaseFileName(fileItem.getName());
359         }
360 
361         protected String getBaseFileName(final String filePath) {
362             final String fileName = new File(filePath).getName();
363             int colonIndex = fileName.indexOf(':');
364             if (colonIndex == -1) {
365                 colonIndex = fileName.indexOf("\\\\"); // Windows SMB
366             }
367             final int backslashIndex = fileName.lastIndexOf('\\');
368             if (colonIndex > -1 && backslashIndex > -1) {
369                 return fileName.substring(backslashIndex + 1);
370             } else {
371                 return fileName;
372             }
373         }
374 
375         @Override
376         public void destroy() {
377             fileItem.delete();
378         }
379 
380         @Override
381         public String toString() {
382             return "formFile:{" + getFileName() + "}";
383         }
384     }
385 
386     // ===================================================================================
387     //                                                                            Accessor
388     //                                                                            ========
389     @Override
390     public Map<String, Object> getAllElements() {
391         return elementsAll;
392     }
393 
394     @Override
395     public Map<String, String[]> getTextElements() {
396         return elementsText;
397     }
398 
399     @Override
400     public Map<String, MultipartFormFile> getFileElements() {
401         return elementsFile;
402     }
403 }