Save This Page
Home » struts-2.1.8.1-src » org.apache » struts2 » interceptor » [javadoc | source]
    1   /*
    2    * $Id: FileUploadInterceptor.java 781032 2009-06-02 13:50:54Z wesw $
    3    *
    4    * Licensed to the Apache Software Foundation (ASF) under one
    5    * or more contributor license agreements.  See the NOTICE file
    6    * distributed with this work for additional information
    7    * regarding copyright ownership.  The ASF licenses this file
    8    * to you under the Apache License, Version 2.0 (the
    9    * "License"); you may not use this file except in compliance
   10    * with the License.  You may obtain a copy of the License at
   11    *
   12    *  http://www.apache.org/licenses/LICENSE-2.0
   13    *
   14    * Unless required by applicable law or agreed to in writing,
   15    * software distributed under the License is distributed on an
   16    * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   17    * KIND, either express or implied.  See the License for the
   18    * specific language governing permissions and limitations
   19    * under the License.
   20    */
   21   
   22   package org.apache.struts2.interceptor;
   23   
   24   import java.io.File;
   25   import java.util;
   26   
   27   import javax.servlet.http.HttpServletRequest;
   28   
   29   import org.apache.struts2.ServletActionContext;
   30   import org.apache.struts2.dispatcher.multipart.MultiPartRequestWrapper;
   31   
   32   import com.opensymphony.xwork2.ActionContext;
   33   import com.opensymphony.xwork2.ActionInvocation;
   34   import com.opensymphony.xwork2.ActionProxy;
   35   import com.opensymphony.xwork2.ValidationAware;
   36   import com.opensymphony.xwork2.interceptor.AbstractInterceptor;
   37   import com.opensymphony.xwork2.util.LocalizedTextUtil;
   38   import com.opensymphony.xwork2.util.TextParseUtil;
   39   import com.opensymphony.xwork2.util.logging.Logger;
   40   import com.opensymphony.xwork2.util.logging.LoggerFactory;
   41   
   42   /**
   43    * <!-- START SNIPPET: description -->
   44    * <p/>
   45    * Interceptor that is based off of {@link MultiPartRequestWrapper}, which is automatically applied for any request that
   46    * includes a file. It adds the following parameters, where [File Name] is the name given to the file uploaded by the
   47    * HTML form:
   48    * <p/>
   49    * <ul>
   50    * <p/>
   51    * <li>[File Name] : File - the actual File</li>
   52    * <p/>
   53    * <li>[File Name]ContentType : String - the content type of the file</li>
   54    * <p/>
   55    * <li>[File Name]FileName : String - the actual name of the file uploaded (not the HTML name)</li>
   56    * <p/>
   57    * </ul>
   58    * <p/>
   59    * <p/> You can get access to these files by merely providing setters in your action that correspond to any of the three
   60    * patterns above, such as setDocument(File document), setDocumentContentType(String contentType), etc.
   61    * <br/>See the example code section.
   62    * <p/>
   63    * <p/> This interceptor will add several field errors, assuming that the action implements {@link ValidationAware}.
   64    * These error messages are based on several i18n values stored in struts-messages.properties, a default i18n file
   65    * processed for all i18n requests. You can override the text of these messages by providing text for the following
   66    * keys:
   67    * <p/>
   68    * <ul>
   69    * <p/>
   70    * <li>struts.messages.error.uploading - a general error that occurs when the file could not be uploaded</li>
   71    * <p/>
   72    * <li>struts.messages.error.file.too.large - occurs when the uploaded file is too large</li>
   73    * <p/>
   74    * <li>struts.messages.error.content.type.not.allowed - occurs when the uploaded file does not match the expected
   75    * content types specified</li>
   76    * <p/>
   77    * <li>struts.messages.error.file.extension.not.allowed - occurs when the uploaded file does not match the expected
   78    * file extensions specified</li>
   79    * <p/>
   80    * </ul>
   81    * <p/>
   82    * <!-- END SNIPPET: description -->
   83    * <p/>
   84    * <p/> <u>Interceptor parameters:</u>
   85    * <p/>
   86    * <!-- START SNIPPET: parameters -->
   87    * <p/>
   88    * <ul>
   89    * <p/>
   90    * <li>maximumSize (optional) - the maximum size (in bytes) that the interceptor will allow a file reference to be set
   91    * on the action. Note, this is <b>not</b> related to the various properties found in struts.properties.
   92    * Default to approximately 2MB.</li>
   93    * <p/>
   94    * <li>allowedTypes (optional) - a comma separated list of content types (ie: text/html) that the interceptor will allow
   95    * a file reference to be set on the action. If none is specified allow all types to be uploaded.</li>
   96    * <p/>
   97    * <li>allowedExtensions (optional) - a comma separated list of file extensions (ie: .html) that the interceptor will allow
   98    * a file reference to be set on the action. If none is specified allow all extensions to be uploaded.</li>
   99    * </ul>
  100    * <p/>
  101    * <p/>
  102    * <!-- END SNIPPET: parameters -->
  103    * <p/>
  104    * <p/> <u>Extending the interceptor:</u>
  105    * <p/>
  106    * <p/>
  107    * <p/>
  108    * <!-- START SNIPPET: extending -->
  109    * <p/>
  110    * You can extend this interceptor and override the acceptFile method to provide more control over which files
  111    * are supported and which are not.
  112    * <p/>
  113    * <!-- END SNIPPET: extending -->
  114    * <p/>
  115    * <p/> <u>Example code:</u>
  116    * <p/>
  117    * <pre>
  118    * <!-- START SNIPPET: example-configuration -->
  119    * &lt;action name="doUpload" class="com.example.UploadAction"&gt;
  120    *     &lt;interceptor-ref name="fileUpload"/&gt;
  121    *     &lt;interceptor-ref name="basicStack"/&gt;
  122    *     &lt;result name="success"&gt;good_result.jsp&lt;/result&gt;
  123    * &lt;/action&gt;
  124    * <!-- END SNIPPET: example-configuration -->
  125    * </pre>
  126    * <p/>
  127    * <!-- START SNIPPET: multipart-note -->
  128    * <p/>
  129    * You must set the encoding to <code>multipart/form-data</code> in the form where the user selects the file to upload.
  130    * <p/>
  131    * <!-- END SNIPPET: multipart-note -->
  132    * <p/>
  133    * <pre>
  134    * <!-- START SNIPPET: example-form -->
  135    *   &lt;s:form action="doUpload" method="post" enctype="multipart/form-data"&gt;
  136    *       &lt;s:file name="upload" label="File"/&gt;
  137    *       &lt;s:submit/&gt;
  138    *   &lt;/s:form&gt;
  139    * <!-- END SNIPPET: example-form -->
  140    * </pre>
  141    * <p/>
  142    * And then in your action code you'll have access to the File object if you provide setters according to the
  143    * naming convention documented in the start.
  144    * <p/>
  145    * <pre>
  146    * <!-- START SNIPPET: example-action -->
  147    *    package com.example;
  148    * <p/>
  149    *    import java.io.File;
  150    *    import com.opensymphony.xwork2.ActionSupport;
  151    * <p/>
  152    *    public UploadAction extends ActionSupport {
  153    *       private File file;
  154    *       private String contentType;
  155    *       private String filename;
  156    * <p/>
  157    *       public void setUpload(File file) {
  158    *          this.file = file;
  159    *       }
  160    * <p/>
  161    *       public void setUploadContentType(String contentType) {
  162    *          this.contentType = contentType;
  163    *       }
  164    * <p/>
  165    *       public void setUploadFileName(String filename) {
  166    *          this.filename = filename;
  167    *       }
  168    * <p/>
  169    *       public String execute() {
  170    *          //...
  171    *          return SUCCESS;
  172    *       }
  173    *  }
  174    * <!-- END SNIPPET: example-action -->
  175    * </pre>
  176    */
  177   public class FileUploadInterceptor extends AbstractInterceptor {
  178   
  179       private static final long serialVersionUID = -4764627478894962478L;
  180   
  181       protected static final Logger LOG = LoggerFactory.getLogger(FileUploadInterceptor.class);
  182       private static final String DEFAULT_MESSAGE = "no.message.found";
  183   
  184       protected boolean useActionMessageBundle;
  185   
  186       protected Long maximumSize;
  187       protected Set<String> allowedTypesSet = Collections.emptySet();
  188       protected Set<String> allowedExtensionsSet = Collections.emptySet();
  189   
  190       public void setUseActionMessageBundle(String value) {
  191           this.useActionMessageBundle = Boolean.valueOf(value);
  192       }
  193   
  194       /**
  195        * Sets the allowed extensions
  196        *
  197        * @param allowedExtensions A comma-delimited list of extensions
  198        */
  199       public void setAllowedExtensions(String allowedExtensions) {
  200           allowedExtensionsSet = TextParseUtil.commaDelimitedStringToSet(allowedExtensions);
  201       }
  202   
  203       /**
  204        * Sets the allowed mimetypes
  205        *
  206        * @param allowedTypes A comma-delimited list of types
  207        */
  208       public void setAllowedTypes(String allowedTypes) {
  209           allowedTypesSet = TextParseUtil.commaDelimitedStringToSet(allowedTypes);
  210       }
  211   
  212       /**
  213        * Sets the maximum size of an uploaded file
  214        *
  215        * @param maximumSize The maximum size in bytes
  216        */
  217       public void setMaximumSize(Long maximumSize) {
  218           this.maximumSize = maximumSize;
  219       }
  220   
  221       /* (non-Javadoc)
  222        * @see com.opensymphony.xwork2.interceptor.Interceptor#intercept(com.opensymphony.xwork2.ActionInvocation)
  223        */
  224       public String intercept(ActionInvocation invocation) throws Exception {
  225           ActionContext ac = invocation.getInvocationContext();
  226   
  227           HttpServletRequest request = (HttpServletRequest) ac.get(ServletActionContext.HTTP_REQUEST);
  228   
  229           if (!(request instanceof MultiPartRequestWrapper)) {
  230               if (LOG.isDebugEnabled()) {
  231                   ActionProxy proxy = invocation.getProxy();
  232                   LOG.debug(getTextMessage("struts.messages.bypass.request", new Object[]{proxy.getNamespace(), proxy.getActionName()}, ac.getLocale()));
  233               }
  234   
  235               return invocation.invoke();
  236           }
  237   
  238           ValidationAware validation = null;
  239   
  240           Object action = invocation.getAction();
  241           
  242           if (action instanceof ValidationAware) {
  243               validation = (ValidationAware) action;
  244           }
  245   
  246           MultiPartRequestWrapper multiWrapper = (MultiPartRequestWrapper) request;
  247   
  248           if (multiWrapper.hasErrors()) {
  249               for (String error : multiWrapper.getErrors()) {
  250                   if (validation != null) {
  251                       validation.addActionError(error);
  252                   }
  253   
  254                   LOG.warn(error);
  255               }
  256           }
  257   
  258           // bind allowed Files
  259           Enumeration fileParameterNames = multiWrapper.getFileParameterNames();
  260           while (fileParameterNames != null && fileParameterNames.hasMoreElements()) {
  261               // get the value of this input tag
  262               String inputName = (String) fileParameterNames.nextElement();
  263   
  264               // get the content type
  265               String[] contentType = multiWrapper.getContentTypes(inputName);
  266   
  267               if (isNonEmpty(contentType)) {
  268                   // get the name of the file from the input tag
  269                   String[] fileName = multiWrapper.getFileNames(inputName);
  270   
  271                   if (isNonEmpty(fileName)) {
  272                       // get a File object for the uploaded File
  273                       File[] files = multiWrapper.getFiles(inputName);
  274                       if (files != null && files.length > 0) {
  275                           List<File> acceptedFiles = new ArrayList<File>(files.length);
  276                           List<String> acceptedContentTypes = new ArrayList<String>(files.length);
  277                           List<String> acceptedFileNames = new ArrayList<String>(files.length);
  278                           String contentTypeName = inputName + "ContentType";
  279                           String fileNameName = inputName + "FileName";
  280   
  281                           for (int index = 0; index < files.length; index++) {
  282                               if (acceptFile(action, files[index], fileName[index], contentType[index], inputName, validation, ac.getLocale())) {
  283                                   acceptedFiles.add(files[index]);
  284                                   acceptedContentTypes.add(contentType[index]);
  285                                   acceptedFileNames.add(fileName[index]);
  286                               }
  287                           }
  288   
  289                           if (!acceptedFiles.isEmpty()) {
  290                               Map<String, Object> params = ac.getParameters();
  291   
  292                               params.put(inputName, acceptedFiles.toArray(new File[acceptedFiles.size()]));
  293                               params.put(contentTypeName, acceptedContentTypes.toArray(new String[acceptedContentTypes.size()]));
  294                               params.put(fileNameName, acceptedFileNames.toArray(new String[acceptedFileNames.size()]));
  295                           }
  296                       }
  297                   } else {
  298                       LOG.warn(getTextMessage(action, "struts.messages.invalid.file", new Object[]{inputName}, ac.getLocale()));
  299                   }
  300               } else {
  301                   LOG.warn(getTextMessage(action, "struts.messages.invalid.content.type", new Object[]{inputName}, ac.getLocale()));
  302               }
  303           }
  304   
  305           // invoke action
  306           String result = invocation.invoke();
  307   
  308           // cleanup
  309           fileParameterNames = multiWrapper.getFileParameterNames();
  310           while (fileParameterNames != null && fileParameterNames.hasMoreElements()) {
  311               String inputValue = (String) fileParameterNames.nextElement();
  312               File[] files = multiWrapper.getFiles(inputValue);
  313   
  314               for (File currentFile : files) {
  315                   if (LOG.isInfoEnabled()) {
  316                       LOG.info(getTextMessage(action, "struts.messages.removing.file", new Object[]{inputValue, currentFile}, ac.getLocale()));
  317                   }
  318   
  319                   if ((currentFile != null) && currentFile.isFile()) {
  320                       if (currentFile.delete() == false) {
  321                           LOG.warn("Resource Leaking:  Could not remove uploaded file '"+currentFile.getCanonicalPath()+"'.");
  322                       }
  323                   }
  324               }
  325           }
  326   
  327           return result;
  328       }
  329   
  330       /**
  331        * Override for added functionality. Checks if the proposed file is acceptable based on contentType and size.
  332        *
  333        * @param action      - uploading action for message retrieval.
  334        * @param file        - proposed upload file.
  335        * @param contentType - contentType of the file.
  336        * @param inputName   - inputName of the file.
  337        * @param validation  - Non-null ValidationAware if the action implements ValidationAware, allowing for better
  338        *                    logging.
  339        * @param locale
  340        * @return true if the proposed file is acceptable by contentType and size.
  341        */
  342       protected boolean acceptFile(Object action, File file, String filename, String contentType, String inputName, ValidationAware validation, Locale locale) {
  343           boolean fileIsAcceptable = false;
  344   
  345           // If it's null the upload failed
  346           if (file == null) {
  347               String errMsg = getTextMessage(action, "struts.messages.error.uploading", new Object[]{inputName}, locale);
  348               if (validation != null) {
  349                   validation.addFieldError(inputName, errMsg);
  350               }
  351   
  352               LOG.warn(errMsg);
  353           } else if (maximumSize != null && maximumSize < file.length()) {
  354               String errMsg = getTextMessage(action, "struts.messages.error.file.too.large", new Object[]{inputName, filename, file.getName(), "" + file.length()}, locale);
  355               if (validation != null) {
  356                   validation.addFieldError(inputName, errMsg);
  357               }
  358   
  359               LOG.warn(errMsg);
  360           } else if ((!allowedTypesSet.isEmpty()) && (!containsItem(allowedTypesSet, contentType))) {
  361               String errMsg = getTextMessage(action, "struts.messages.error.content.type.not.allowed", new Object[]{inputName, filename, file.getName(), contentType}, locale);
  362               if (validation != null) {
  363                   validation.addFieldError(inputName, errMsg);
  364               }
  365   
  366               LOG.warn(errMsg);
  367           } else if ((! allowedExtensionsSet.isEmpty()) && (!hasAllowedExtension(allowedExtensionsSet, filename))) {
  368               String errMsg = getTextMessage(action, "struts.messages.error.file.extension.not.allowed", new Object[]{inputName, filename, file.getName(), contentType}, locale);
  369               if (validation != null) {
  370                   validation.addFieldError(inputName, errMsg);
  371               }
  372   
  373               LOG.warn(errMsg);
  374           } else {
  375               fileIsAcceptable = true;
  376           }
  377   
  378           return fileIsAcceptable;
  379       }
  380   
  381       /**
  382        * @param extensionCollection - Collection of extensions (all lowercase).
  383        * @param filename            - filename to check.
  384        * @return true if the filename has an allowed extension, false otherwise.
  385        */
  386       private static boolean hasAllowedExtension(Collection<String> extensionCollection, String filename) {
  387           if (filename == null) {
  388               return false;
  389           }
  390   
  391           String lowercaseFilename = filename.toLowerCase();
  392           for (String extension : extensionCollection) {
  393               if (lowercaseFilename.endsWith(extension)) {
  394                   return true;
  395               }
  396           }
  397   
  398           return false;
  399       }
  400   
  401       /**
  402        * @param itemCollection - Collection of string items (all lowercase).
  403        * @param item           - Item to search for.
  404        * @return true if itemCollection contains the item, false otherwise.
  405        */
  406       private static boolean containsItem(Collection<String> itemCollection, String item) {
  407           return itemCollection.contains(item.toLowerCase());
  408       }
  409   
  410       private static boolean isNonEmpty(Object[] objArray) {
  411           boolean result = false;
  412           for (int index = 0; index < objArray.length && !result; index++) {
  413               if (objArray[index] != null) {
  414                   result = true;
  415               }
  416           }
  417           return result;
  418       }
  419   
  420       private String getTextMessage(String messageKey, Object[] args, Locale locale) {
  421           return getTextMessage(null, messageKey, args, locale);
  422       }
  423       
  424       private String getTextMessage(Object action, String messageKey, Object[] args, Locale locale) {
  425           if (args == null || args.length == 0) {
  426               if ( action != null && useActionMessageBundle) {
  427                   return LocalizedTextUtil.findText(action.getClass(), messageKey, locale);
  428               }
  429               return LocalizedTextUtil.findText(this.getClass(), messageKey, locale);                        
  430           } else {
  431               if ( action != null && useActionMessageBundle) {
  432                   return LocalizedTextUtil.findText(action.getClass(), messageKey, locale, DEFAULT_MESSAGE, args);
  433               }
  434               return LocalizedTextUtil.findText(this.getClass(), messageKey, locale, DEFAULT_MESSAGE, args);
  435           }
  436       }
  437   }

Save This Page
Home » struts-2.1.8.1-src » org.apache » struts2 » interceptor » [javadoc | source]