001/*
002 *  Copyright 2023 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.ametys.runtime.request;
017
018import java.io.File;
019import java.io.IOException;
020import java.util.Enumeration;
021import java.util.HashMap;
022import java.util.Hashtable;
023import java.util.Map;
024
025import javax.servlet.http.HttpServletRequest;
026
027import org.apache.cocoon.servlet.multipart.MultipartException;
028import org.apache.cocoon.servlet.multipart.MultipartHttpServletRequest;
029import org.apache.cocoon.servlet.multipart.PartOnDisk;
030import org.apache.cocoon.servlet.multipart.RejectedPart;
031
032import org.ametys.runtime.config.ConfigManager;
033import org.ametys.runtime.servlet.AnalyseFileForVirusHelper;
034
035/**
036 * Extension of the cocoon request factory to allow antivirus scanning of uploaded file
037 */
038public class RequestFactory extends org.apache.cocoon.servlet.multipart.RequestFactory
039{
040    // check for antivirus activation at instantiation
041    private boolean _antivirusEnabled;
042
043    /**
044     * Default constructor see {@link org.apache.cocoon.servlet.multipart.RequestFactory} for documentation
045     * @param saveUploadedFilesToDisk to save files to disk
046     * @param uploadDirectory where to save files on disk
047     * @param allowOverwrite to allow overwrite
048     * @param silentlyRename to rename silently
049     * @param maxUploadSize the maximum size of an uploaded file
050     * @param defaultCharEncoding the default encoding
051     */
052    public RequestFactory(boolean saveUploadedFilesToDisk,
053            File uploadDirectory,
054            boolean allowOverwrite,
055            boolean silentlyRename,
056            int maxUploadSize,
057            String defaultCharEncoding)
058    {
059        super(saveUploadedFilesToDisk, uploadDirectory, allowOverwrite, silentlyRename, maxUploadSize, defaultCharEncoding);
060        
061        // Check antivirus activation
062        if (ConfigManager.getInstance().isComplete())
063        {
064            _antivirusEnabled = AnalyseFileForVirusHelper.isAntivirusEnabled();
065        }
066    }
067    
068    @Override
069    public HttpServletRequest getServletRequest(HttpServletRequest request) throws IOException, MultipartException
070    {
071        HttpServletRequest servletRequest = super.getServletRequest(request);
072        // Check the activation of the antivirus to ensure minimal impact if antivirus is disabled
073        if (servletRequest instanceof MultipartHttpServletRequest multipart && _antivirusEnabled)
074        {
075            Map<String, RejectedPart> rejectedParam = new HashMap<>();
076            Enumeration<String> parameterNames = multipart.getParameterNames();
077            while (parameterNames.hasMoreElements())
078            {
079                String name = parameterNames.nextElement();
080                Object value = multipart.get(name);
081                if (value instanceof PartOnDisk diskPart)
082                {
083                    File file = diskPart.getFile();
084                    // Check file here
085                    if (!AnalyseFileForVirusHelper.analysefile(file.getAbsolutePath()))
086                    {
087                        // Delete dangerous file instantly
088                        file.delete();
089                        // Record the name of param to remove
090                        rejectedParam.put(name, new RejectedPart(diskPart.getHeaders(), 0, 0, 0));
091                    }
092                }
093            }
094            
095            if (!rejectedParam.isEmpty())
096            {
097                Hashtable<String, Object> params = new Hashtable<>();
098                // some part were rejected. 
099                // We have to copy the request parameters
100                parameterNames = multipart.getParameterNames();
101                while (parameterNames.hasMoreElements())
102                {
103                    String name = parameterNames.nextElement();
104                    if (rejectedParam.containsKey(name))
105                    {
106                        // We represent a contaminated part with a rejected part with max size = 0byte
107                        params.put(name, rejectedParam.get(name));
108                    }
109                    else
110                    {
111                        params.put(name, multipart.get(name));
112                    }
113                }
114                
115                // and wrap the request with the new parameters including the rejected parts
116                servletRequest = new MultipartHttpServletRequest(servletRequest, params);
117            }
118        }
119        return servletRequest;
120    }
121}