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.ArrayList; 021import java.util.Enumeration; 022import java.util.HashMap; 023import java.util.Hashtable; 024import java.util.List; 025import java.util.Map; 026 027import javax.servlet.http.HttpServletRequest; 028 029import org.apache.cocoon.servlet.multipart.MultipartException; 030import org.apache.cocoon.servlet.multipart.MultipartHttpServletRequest; 031import org.apache.cocoon.servlet.multipart.Part; 032import org.apache.cocoon.servlet.multipart.PartOnDisk; 033import org.apache.cocoon.servlet.multipart.RejectedPart; 034 035import org.ametys.runtime.config.ConfigManager; 036import org.ametys.runtime.servlet.AnalyseFileForVirusHelper; 037 038/** 039 * Extension of the cocoon request factory to allow antivirus scanning of uploaded file 040 */ 041public class RequestFactory extends org.apache.cocoon.servlet.multipart.RequestFactory 042{ 043 // check for antivirus activation at instantiation 044 private boolean _antivirusEnabled; 045 046 /** 047 * Default constructor see {@link org.apache.cocoon.servlet.multipart.RequestFactory} for documentation 048 * @param saveUploadedFilesToDisk to save files to disk 049 * @param uploadDirectory where to save files on disk 050 * @param allowOverwrite to allow overwrite 051 * @param silentlyRename to rename silently 052 * @param maxUploadSize the maximum size of an uploaded file 053 * @param defaultCharEncoding the default encoding 054 */ 055 public RequestFactory(boolean saveUploadedFilesToDisk, 056 File uploadDirectory, 057 boolean allowOverwrite, 058 boolean silentlyRename, 059 int maxUploadSize, 060 String defaultCharEncoding) 061 { 062 super(saveUploadedFilesToDisk, uploadDirectory, allowOverwrite, silentlyRename, maxUploadSize, defaultCharEncoding); 063 064 // Check antivirus activation 065 if (ConfigManager.getInstance().isComplete()) 066 { 067 _antivirusEnabled = AnalyseFileForVirusHelper.isAntivirusEnabled(); 068 } 069 } 070 071 @SuppressWarnings("unchecked") 072 @Override 073 public HttpServletRequest getServletRequest(HttpServletRequest request) throws IOException, MultipartException 074 { 075 HttpServletRequest servletRequest = super.getServletRequest(request); 076 // Check the activation of the antivirus to ensure minimal impact if antivirus is disabled 077 if (servletRequest instanceof MultipartHttpServletRequest multipart && _antivirusEnabled) 078 { 079 Map<String, Object> rejectedParam = new HashMap<>(); 080 Enumeration<String> parameterNames = multipart.getParameterNames(); 081 while (parameterNames.hasMoreElements()) 082 { 083 String name = parameterNames.nextElement(); 084 Object value = multipart.get(name); 085 if (value instanceof PartOnDisk diskPart) 086 { 087 if (_analyseAndRejectFile(diskPart, name)) 088 { 089 // Record the name of param to remove 090 rejectedParam.put(name, new RejectedPart(diskPart.getHeaders(), 0, 0, 0)); 091 } 092 } 093 else if (value instanceof List valueList && !valueList.isEmpty() && valueList.get(0) instanceof PartOnDisk) 094 { 095 List<Part> replaceFiles = new ArrayList<>(); 096 for (PartOnDisk diskPart : (List<PartOnDisk>) valueList) 097 { 098 // Analyse the file and reject and delete it if it has a virus 099 if (_analyseAndRejectFile(diskPart, name)) 100 { 101 replaceFiles.add(new RejectedPart(diskPart.getHeaders(), 0, 0, 0)); 102 } 103 else 104 { 105 replaceFiles.add(diskPart); 106 } 107 } 108 109 // Record the name of param to remove 110 rejectedParam.put(name, replaceFiles); 111 } 112 } 113 114 if (!rejectedParam.isEmpty()) 115 { 116 Hashtable<String, Object> params = new Hashtable<>(); 117 // some part were rejected. 118 // We have to copy the request parameters 119 parameterNames = multipart.getParameterNames(); 120 while (parameterNames.hasMoreElements()) 121 { 122 String name = parameterNames.nextElement(); 123 if (rejectedParam.containsKey(name)) 124 { 125 // We represent a contaminated part with a rejected part with max size = 0byte 126 params.put(name, rejectedParam.get(name)); 127 } 128 else 129 { 130 params.put(name, multipart.get(name)); 131 } 132 } 133 134 // and wrap the request with the new parameters including the rejected parts 135 servletRequest = new MultipartHttpServletRequest(servletRequest, params); 136 } 137 } 138 return servletRequest; 139 } 140 141 /** 142 * Analyse the file for viruses, and delete it if a virus has been detected 143 * @param diskPart The part to analyse 144 * @param name The name of the param containing the file 145 * @return a {@link Boolean}, true if a virus has been detected, false otherwise 146 */ 147 private boolean _analyseAndRejectFile(PartOnDisk diskPart, String name) 148 { 149 File file = diskPart.getFile(); 150 // Check file here 151 if (!AnalyseFileForVirusHelper.analysefile(file.getAbsolutePath())) 152 { 153 // Delete dangerous file instantly 154 file.delete(); 155 156 return true; 157 } 158 159 return false; 160 } 161}