View Javadoc
1   /*
2    * Copyright (c) 2002-2025, City of Paris
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions
7    * are met:
8    *
9    *  1. Redistributions of source code must retain the above copyright notice
10   *     and the following disclaimer.
11   *
12   *  2. Redistributions in binary form must reproduce the above copyright notice
13   *     and the following disclaimer in the documentation and/or other materials
14   *     provided with the distribution.
15   *
16   *  3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
17   *     contributors may be used to endorse or promote products derived from
18   *     this software without specific prior written permission.
19   *
20   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
24   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30   * POSSIBILITY OF SUCH DAMAGE.
31   *
32   * License 1.0
33   */
34  package fr.paris.lutece.util.http;
35  
36  import fr.paris.lutece.portal.service.html.EncodingService;
37  import fr.paris.lutece.portal.service.html.XSSSanitizerException;
38  import fr.paris.lutece.portal.service.html.XSSSanitizerService;
39  import fr.paris.lutece.portal.service.util.AppLogService;
40  import fr.paris.lutece.portal.web.upload.MultipartHttpServletRequest;
41  import fr.paris.lutece.portal.web.upload.NormalizeFileItem;
42  
43  import org.apache.commons.fileupload.FileItem;
44  import org.apache.commons.fileupload.FileItemIterator;
45  import org.apache.commons.fileupload.FileItemStream;
46  import org.apache.commons.fileupload.FileUploadBase.SizeLimitExceededException;
47  import org.apache.commons.fileupload.FileUploadException;
48  import org.apache.commons.fileupload.disk.DiskFileItemFactory;
49  import org.apache.commons.fileupload.servlet.ServletFileUpload;
50  import org.apache.commons.lang3.StringUtils;
51  
52  import java.io.UnsupportedEncodingException;
53  
54  import java.util.ArrayList;
55  import java.util.HashMap;
56  import java.util.List;
57  import java.util.Map;
58  import java.util.Optional;
59  
60  import javax.servlet.http.HttpServletRequest;
61  
62  /**
63   *
64   * MultipartUtil
65   *
66   */
67  public final class MultipartUtil
68  {
69      /**
70       * Private constructor
71       */
72      private MultipartUtil( )
73      {
74      }
75  
76      /**
77       * Check if the given HTTP request has multipart content
78       *
79       * @param request
80       *            the HTTP request
81       * @return true if it has multipart content, false otherwise
82       */
83      public static boolean isMultipart( HttpServletRequest request )
84      {
85          return ServletFileUpload.isMultipartContent( request );
86      }
87  
88      /**
89       * Convert a HTTP request to a {@link MultipartHttpServletRequest}
90       *
91       * @param nSizeThreshold
92       *            the size threshold
93       * @param nRequestSizeMax
94       *            the request size max
95       * @param bActivateNormalizeFileName
96       *            true if the file name must be normalized, false otherwise
97       * @param request
98       *            the HTTP request
99       * @return a {@link MultipartHttpServletRequest}, null if the request does not have a multipart content
100      * @throws SizeLimitExceededException
101      *             exception if the file size is too big
102      * @throws FileUploadException
103      *             exception if an unknown error has occurred
104      */
105     public static MultipartHttpServletRequest convert( int nSizeThreshold, long nRequestSizeMax, boolean bActivateNormalizeFileName,
106             HttpServletRequest request, boolean isXssSanitize ) throws FileUploadException
107     {
108         if ( !isMultipart( request ) )
109         {
110             return null;
111         }
112 
113         // Create a factory for disk-based file items
114         DiskFileItemFactory factory = new DiskFileItemFactory( );
115 
116         // Set factory constraints
117         factory.setSizeThreshold( nSizeThreshold );
118 
119         // Create a new file upload handler
120         ServletFileUpload upload = new ServletFileUpload( factory );
121 
122         // Set overall request size constraint
123         upload.setSizeMax( nRequestSizeMax );
124 
125         // get encoding to be used
126         String strEncoding = Optional.ofNullable( request.getCharacterEncoding( ) ).orElse( EncodingService.getEncoding( ) );
127 
128         Map<String, List<FileItem>> mapFiles = new HashMap<>( );
129         Map<String, String [ ]> mapParameters = new HashMap<>( );
130 
131         List<FileItem> listItems;
132 
133         try {
134             listItems = upload.parseRequest(request);
135         }
136         catch( SizeLimitExceededException e )
137         {
138             closeInputStream( upload, request );
139             throw e;
140         }
141 
142         // Process the uploaded items
143         for ( FileItem item : listItems )
144         {
145             processItem( item, strEncoding, bActivateNormalizeFileName, mapFiles, mapParameters, isXssSanitize );
146         }
147 
148         return new MultipartHttpServletRequest( request, mapFiles, mapParameters );
149     }
150 
151     /**
152      * Close the inputStream of the request
153      * @param upload
154      * @param request
155      */
156     private static void closeInputStream( ServletFileUpload upload, HttpServletRequest request)
157     {
158         try {
159             upload.setSizeMax(-1);
160             FileItemIterator fileiterator = upload.getItemIterator(request);
161 
162             while (fileiterator.hasNext()) 
163             {
164                 FileItemStream item = fileiterator.next();
165                 item.openStream().close();
166             }
167         }
168         catch (Exception e)
169         {
170             AppLogService.error( "error occured during closing the stream" , e);
171         }
172 
173     }
174 
175     private static void processItem( FileItem item, String strEncoding, boolean bActivateNormalizeFileName, Map<String, List<FileItem>> mapFiles,
176             Map<String, String [ ]> mapParameters, boolean isXssSanitize )
177     {
178         if ( item.isFormField( ) )
179         {
180             String strValue = StringUtils.EMPTY;
181 
182             if ( item.getSize( ) > 0 )
183             {
184                 try
185                 {
186                     strValue = item.getString( strEncoding );
187                 }
188                 catch( UnsupportedEncodingException ex )
189                 {
190                     // if encoding problem, try with system encoding
191                     strValue = item.getString( );
192                 }
193             }
194 
195             if ( isXssSanitize ) 
196             {
197         	try
198 		{
199 		    strValue = XSSSanitizerService.sanitize( strValue );
200 		} 
201         	catch ( XSSSanitizerException e )
202 		{
203 		    AppLogService.error( "XSS Sanitize Service Error", e );
204 		} 
205             }
206             
207             // check if item of same name already in map
208             String [ ] curParam = mapParameters.get( item.getFieldName( ) );
209 
210             if ( curParam == null )
211             {
212                 // simple form field
213                 mapParameters.put( item.getFieldName( ), new String [ ] {
214                         strValue
215                 } );
216             }
217             else
218             {
219                 // array of simple form fields
220                 String [ ] newArray = new String [ curParam.length + 1];
221                 System.arraycopy( curParam, 0, newArray, 0, curParam.length );
222                 newArray [curParam.length] = strValue;
223                 mapParameters.put( item.getFieldName( ), newArray );
224             }
225         }
226         else
227         {
228             // multipart file field, if the parameter filter ActivateNormalizeFileName is
229             // set to true
230             // all file name will be normalize
231             FileItem fileItem = bActivateNormalizeFileName ? new NormalizeFileItem( item ) : item;
232             List<FileItem> listFileItem = mapFiles.get( fileItem.getFieldName( ) );
233 
234             if ( listFileItem != null )
235             {
236                 listFileItem.add( fileItem );
237             }
238             else
239             {
240                 listFileItem = new ArrayList<>( 1 );
241                 listFileItem.add( fileItem );
242                 mapFiles.put( fileItem.getFieldName( ), listFileItem );
243             }
244         }
245     }
246 }