View Javadoc
1   /*
2    * Copyright (c) 2002-2021, 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.plugins.asynchronousupload.service;
35  
36  import java.util.ArrayList;
37  import java.util.Arrays;
38  import java.util.Collections;
39  import java.util.Enumeration;
40  import java.util.HashMap;
41  import java.util.List;
42  import java.util.Map;
43  import java.util.UUID;
44  import java.util.regex.Matcher;
45  import java.util.regex.Pattern;
46  
47  import javax.servlet.http.HttpServletRequest;
48  import javax.servlet.http.HttpServletResponse;
49  import javax.servlet.http.HttpSession;
50  
51  import org.apache.commons.collections.CollectionUtils;
52  import org.apache.commons.fileupload.FileItem;
53  import org.apache.commons.lang3.ArrayUtils;
54  import org.apache.commons.lang3.StringUtils;
55  
56  import fr.paris.lutece.plugins.asynchronousupload.util.JSONUtils;
57  import fr.paris.lutece.portal.service.util.AppException;
58  import fr.paris.lutece.portal.service.util.AppLogService;
59  import fr.paris.lutece.portal.web.upload.MultipartHttpServletRequest;
60  import net.sf.json.JSON;
61  import net.sf.json.JSONArray;
62  import net.sf.json.JSONObject;
63  import net.sf.json.JSONSerializer;
64  
65  /**
66   * AbstractAsynchronousUploadHandler.
67   */
68  public abstract class AbstractAsynchronousUploadHandler implements IAsyncUploadHandler
69  {
70      protected static final String PARAM_CUSTOM_SESSION_ID = "CUSTOM_SESSION";
71      private static final String PARAMETER_FIELD_NAME = "fieldname";
72      private static final String PARAMETER_FIELD_INDEX = "field_index";
73      private static final String PARAMETER_HANDLER = "asynchronousupload.handler";
74      private static final String UPLOAD_SUBMIT_PREFIX = "_upload_submit_";
75      private static final String UPLOAD_DELETE_PREFIX = "_upload_delete_";
76      private static final String UPLOAD_CHECKBOX_PREFIX = "_upload_checkbox_";
77      private static final String KEY_FORM_ERROR = "form_error";
78      private static final String KEY_FILE_SIZE = "fileSize";
79      private static final String KEY_FILE_NAME = "fileName";
80      private static final String KEY_FIELD_NAME = "field_name";
81      private static final String KEY_FILES = "files";
82      private static final String HEADER_CONTENT_RANGE = "Content-Range";
83      private static final String REGEXP_CONTENT_RANGE_HEADER = "bytes (\\d*)-(\\d*)\\/(\\d*)";
84  
85      @Override
86      public void process( HttpServletRequest request, HttpServletResponse response, Map<String, Object> map, List<FileItem> listFileItemsToUpload )
87      {
88          map.clear( );
89          String strFieldName = request.getParameter( PARAMETER_FIELD_NAME );
90  
91          if ( StringUtils.isBlank( strFieldName ) )
92          {
93              throw new AppException( "id entry is not provided for the current file upload" );
94          }
95  
96          if ( CollectionUtils.isNotEmpty( listFileItemsToUpload ) )
97          {
98              String strError = canUploadFiles( request, strFieldName, listFileItemsToUpload, request.getLocale( ) );
99              if ( strError == null )
100             {
101                 // manage chunk file if there is not multiple file
102                 if ( isManagePartialContent( ) && isRequestContainsPartialContent( request ) )
103                 {
104                     if ( listFileItemsToUpload.size( ) == 1 )
105                     {
106                         addFileItemToPartialUploadedFilesList( listFileItemsToUpload.get( 0 ), strFieldName, request );
107                         if ( isRequestContainsLastPartialContent( request ) )
108                         {
109                             PartialFileItemGroup/PartialFileItemGroup.html#PartialFileItemGroup">PartialFileItemGroup partialFileItemGroup = new PartialFileItemGroup(
110                                     getListPartialUploadedFiles( strFieldName, request.getSession( ) ) );
111                             addFileItemToUploadedFilesList( partialFileItemGroup, strFieldName, request );
112                         }
113                     }
114                     else
115                     {
116                         AppLogService.error( "AbstractAsynchronousUploadHandler.process : -Chunk files with  multiple file selected do not deal" );
117                         map.put( KEY_FORM_ERROR, "Chunk files with  multiple file selected do not deal" );
118                     }
119                 }
120                 else
121                 {
122                     for ( FileItem fileItem : listFileItemsToUpload )
123                     {
124                         addFileItemToUploadedFilesList( fileItem, strFieldName, request );
125                     }
126                 }
127             }
128             else
129             {
130                 map.put( KEY_FORM_ERROR, strError );
131             }
132         }
133         map.put( KEY_FIELD_NAME, strFieldName );
134 
135         List<FileItem> fileItemsSession = getListUploadedFiles( strFieldName, request.getSession( ) );
136         List<Map<String, Object>> listJsonFileMap = new ArrayList<>( );
137         map.put( KEY_FILES, listJsonFileMap );
138 
139         for ( FileItem fileItem : fileItemsSession )
140         {
141             Map<String, Object> jsonFileMap = new HashMap<>( );
142             jsonFileMap.put( KEY_FILE_NAME, fileItem.getName( ) );
143             jsonFileMap.put( KEY_FILE_SIZE, fileItem.getSize( ) );
144             listJsonFileMap.add( jsonFileMap );
145         }
146 
147     }
148 
149     /**
150      * Checks the request parameters to see if an upload submit has been called.
151      *
152      * @param request
153      *            the HTTP request
154      * @return the name of the upload action, if any. Null otherwise.
155      */
156     public String getUploadAction( HttpServletRequest request )
157     {
158         Enumeration<String> enumParamNames = request.getParameterNames( );
159 
160         while ( enumParamNames.hasMoreElements( ) )
161         {
162             String paramName = enumParamNames.nextElement( );
163 
164             if ( paramName.startsWith( getUploadSubmitPrefix( ) ) || paramName.startsWith( getUploadDeletePrefix( ) ) )
165             {
166                 return paramName;
167             }
168         }
169 
170         return null;
171     }
172 
173     /**
174      * {@inheritDoc}
175      */
176     @Override
177     public void doRemoveFile( HttpServletRequest request, String strFieldName )
178     {
179         if ( hasRemoveFlag( request, strFieldName ) )
180         {
181             HttpSession session = request.getSession( false );
182 
183             if ( session != null )
184             {
185                 // Some previously uploaded files were deleted
186                 // Build the prefix of the associated checkboxes
187                 String strPrefix = getUploadCheckboxPrefix( ) + strFieldName;
188 
189                 // Look for the checkboxes in the request
190                 Enumeration<String> enumParamNames = request.getParameterNames( );
191                 List<Integer> listIndexes = new ArrayList<>( );
192 
193                 while ( enumParamNames.hasMoreElements( ) )
194                 {
195                     String strParamName = enumParamNames.nextElement( );
196                     String strParamValue = request.getParameter( strParamName );
197 
198                     if ( strParamValue.startsWith( strPrefix ) )
199                     {
200                         // Get the index from the name of the checkbox
201                         listIndexes.add( Integer.parseInt( strParamValue.substring( strPrefix.length( ) ) ) );
202                     }
203                 }
204 
205                 Collections.sort( listIndexes );
206                 Collections.reverse( listIndexes );
207 
208                 for ( int nIndex : listIndexes )
209                 {
210                     removeFileItem( strFieldName, session, nIndex );
211                 }
212             }
213         }
214     }
215 
216     /**
217      * {@inheritDoc}
218      */
219     @Override
220     public String doRemoveUploadedFile( HttpServletRequest request, String strFieldName, List<Integer> listIndexesFilesToRemove )
221     {
222         if ( StringUtils.isBlank( strFieldName ) )
223         {
224             return JSONUtils.buildJsonErrorRemovingFile( request ).toString( );
225         }
226 
227         if ( CollectionUtils.isNotEmpty( listIndexesFilesToRemove ) )
228         {
229             // parse json
230             JSON jsonFieldIndexes = JSONSerializer.toJSON( listIndexesFilesToRemove );
231 
232             if ( !jsonFieldIndexes.isArray( ) )
233             {
234                 return JSONUtils.buildJsonErrorRemovingFile( request ).toString( );
235             }
236 
237             JSONArray jsonArrayFieldIndexers = (JSONArray) jsonFieldIndexes;
238             int [ ] tabFieldIndex = new int [ jsonArrayFieldIndexers.size( )];
239 
240             for ( int nIndex = 0; nIndex < jsonArrayFieldIndexers.size( ); nIndex++ )
241             {
242                 try
243                 {
244                     tabFieldIndex [nIndex] = Integer.parseInt( jsonArrayFieldIndexers.getString( nIndex ) );
245                 }
246                 catch( NumberFormatException nfe )
247                 {
248                     return JSONUtils.buildJsonErrorRemovingFile( request ).toString( );
249                 }
250             }
251 
252             // inverse order (removing using index - remove greater first to keep order)
253             Arrays.sort( tabFieldIndex );
254             ArrayUtils.reverse( tabFieldIndex );
255 
256             List<FileItem> fileItemsSession = getListUploadedFiles( strFieldName, request.getSession( ) );
257 
258             List<FileItem> listItemsToRemove = new ArrayList<>( listIndexesFilesToRemove.size( ) );
259 
260             for ( int nFieldIndex : tabFieldIndex )
261             {
262                 if ( fileItemsSession.size( ) == 1 && nFieldIndex > 0 )
263                 {
264                     nFieldIndex = nFieldIndex - 1;
265                 }
266                 listItemsToRemove.add( fileItemsSession.get( nFieldIndex ) );
267                 removeFileItem( strFieldName, request.getSession( ), nFieldIndex );
268             }
269         }
270 
271         JSONObject json = new JSONObject( );
272         json.element( JSONUtils.JSON_KEY_SUCCESS, JSONUtils.JSON_KEY_SUCCESS );
273 
274         json.accumulateAll( JSONUtils.getUploadedFileJSON( getListUploadedFiles( strFieldName, request.getSession( ) ) ) );
275         json.element( JSONUtils.JSON_KEY_FIELD_NAME, strFieldName );
276 
277         return json.toString( );
278     }
279 
280     /**
281      * {@inheritDoc}
282      */
283     @Override
284     public boolean hasRemoveFlag( HttpServletRequest request, String strFieldName )
285     {
286         return StringUtils.isNotEmpty( request.getParameter( getUploadDeletePrefix( ) + strFieldName ) );
287     }
288 
289     /**
290      * {@inheritDoc}
291      */
292     @Override
293     public boolean isInvoked( HttpServletRequest request )
294     {
295         return StringUtils.equals( getHandlerName( ), request.getParameter( PARAMETER_HANDLER ) );
296     }
297 
298     /**
299      * {@inheritDoc}
300      */
301     @Override
302     public boolean hasAddFileFlag( HttpServletRequest request, String strFieldName )
303     {
304         return StringUtils.isNotEmpty( request.getParameter( getUploadSubmitPrefix( ) + strFieldName ) );
305     }
306 
307     /**
308      * {@inheritDoc}
309      */
310     @Override
311     public void addFilesUploadedSynchronously( HttpServletRequest request, String strFieldName )
312     {
313         if ( request instanceof MultipartHttpServletRequest && hasAddFileFlag( request, strFieldName ) )
314         {
315             MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;
316             List<FileItem> listFileItem = multipartRequest.getFileList( strFieldName );
317 
318             if ( CollectionUtils.isNotEmpty( listFileItem ) )
319             {
320                 for ( FileItem fileItem : listFileItem )
321                 {
322                     if ( ( fileItem.getSize( ) > 0L ) && StringUtils.isNotEmpty( fileItem.getName( ) ) )
323                     {
324                         addFileItemToUploadedFilesList( fileItem, strFieldName, request );
325                     }
326                 }
327             }
328         }
329     }
330 
331     /**
332      * {@inheritDoc}
333      */
334     @Override
335     public String getUploadSubmitPrefix( )
336     {
337         return getHandlerName( ) + UPLOAD_SUBMIT_PREFIX;
338     }
339 
340     /**
341      * {@inheritDoc}
342      */
343     @Override
344     public String getUploadDeletePrefix( )
345     {
346         return getHandlerName( ) + UPLOAD_DELETE_PREFIX;
347     }
348 
349     /**
350      * {@inheritDoc}
351      */
352     @Override
353     public String getUploadCheckboxPrefix( )
354     {
355         return getHandlerName( ) + UPLOAD_CHECKBOX_PREFIX;
356     }
357 
358     /**
359      * {@inheritDoc}
360      */
361     @Override
362     public byte [ ] doRetrieveUploadedFile( HttpServletRequest request )
363     {
364         String strFieldName = request.getParameter( PARAMETER_FIELD_NAME );
365         String strFieldIndex = request.getParameter( PARAMETER_FIELD_INDEX );
366         int intFieldIndex;
367         FileItem itemToDownload = null;
368         if ( StringUtils.isNotEmpty( strFieldIndex ) && StringUtils.isNumeric( strFieldIndex ) )
369         {
370             intFieldIndex = Integer.parseInt( request.getParameter( PARAMETER_FIELD_INDEX ) );
371             List<FileItem> fileItemsSession = getListUploadedFiles( strFieldName, request.getSession( ) );
372             itemToDownload = fileItemsSession.get( intFieldIndex );
373         }
374         if ( itemToDownload == null )
375         {
376             return new byte [ 0];
377         }
378         return itemToDownload.get( );
379     }
380 
381     /**
382      * {@inheritDoc}
383      */
384     @Override
385     public List<FileItem> getListPartialUploadedFiles( String strFieldName, HttpSession session )
386     {
387         AppLogService.error( "the Upload Handler do not manage partial content files " );
388         return new ArrayList<>( );
389     }
390 
391     /**
392      * {@inheritDoc}
393      */
394     @Override
395     public void addFileItemToPartialUploadedFilesList( FileItem fileItem, String strFieldName, HttpServletRequest request )
396     {
397         AppLogService.error( "the Upload Handler do not manage partial content files " );
398     }
399 
400     /**
401      * {@inheritDoc}
402      */
403 
404     @Override
405     public boolean isManagePartialContent( )
406     {
407 
408         return false;
409     }
410 
411     /**
412      * {@inheritDoc}
413      */
414     @Override
415     public void removeAllFileItem( HttpSession session )
416     {
417     }
418 
419     /**
420      * return true if the content of the request is partial
421      * 
422      * @param request
423      *            the request
424      * @return true if the content of the request is partial
425      */
426     private boolean isRequestContainsPartialContent( HttpServletRequest request )
427     {
428         return request.getHeader( HEADER_CONTENT_RANGE ) != null;
429 
430     }
431 
432     /**
433      * return true if the request contain the last partial content of the file
434      * 
435      * @param request
436      * @return
437      */
438     private boolean isRequestContainsLastPartialContent( HttpServletRequest request )
439     {
440 
441         boolean bLastPartialContent = false;
442         String strContentRange = request.getHeader( HEADER_CONTENT_RANGE );
443         Pattern r = Pattern.compile( REGEXP_CONTENT_RANGE_HEADER );
444         Matcher m = r.matcher( strContentRange );
445 
446         if ( m.find( ) )
447         {
448 
449             String strLatsBytes = m.group( 2 );
450             String strTotalBytes = m.group( 3 );
451             int nTotalBytes = Integer.parseInt( strTotalBytes );
452             int nLastBytes = Integer.parseInt( strLatsBytes );
453             if ( nTotalBytes - nLastBytes == 1 )
454             {
455                 bLastPartialContent = true;
456 
457             }
458 
459         }
460         return bLastPartialContent;
461     }
462 
463     protected String getCustomSessionId( HttpSession session )
464     {
465         String sessionId = (String) session.getAttribute( PARAM_CUSTOM_SESSION_ID );
466         if ( sessionId == null )
467         {
468             sessionId = UUID.randomUUID( ).toString( );
469             session.setAttribute( PARAM_CUSTOM_SESSION_ID, sessionId );
470         }
471         return sessionId;
472     }
473 
474     @Override
475     public void removeFileItem( String strFieldName, HttpSession session, int nIndex )
476     {
477         // Remove the file (this will also delete the file physically)
478         List<FileItem> uploadedFiles = getListUploadedFiles( strFieldName, session );
479 
480         if ( ( uploadedFiles != null ) && !uploadedFiles.isEmpty( ) && ( uploadedFiles.size( ) > nIndex ) )
481         {
482             // Remove the object from the Hashmap
483             FileItem fileItem = uploadedFiles.remove( nIndex );
484             fileItem.delete( );
485         }
486     }
487 }