/**
* Copyright 2005-2016 hdiv.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.hdiv.filter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.servlet.FilterChain;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hdiv.config.HDIVConfig;
import org.hdiv.config.multipart.IMultipartConfig;
import org.hdiv.config.multipart.exception.HdivMultipartException;
import org.hdiv.context.RequestContextFactory;
import org.hdiv.context.RequestContextHolder;
import org.hdiv.exception.HDIVException;
import org.hdiv.init.RequestInitializer;
import org.hdiv.logs.IUserData;
import org.hdiv.logs.Logger;
import org.hdiv.util.Constants;
import org.hdiv.util.HDIVErrorCodes;
import org.hdiv.util.HDIVUtil;
import org.springframework.context.ApplicationContext;
import org.springframework.web.filter.OncePerRequestFilter;
/**
* An unique filter exists within HDIV. This filter has two responsibilities: initialize and validate. In fact, the actual validation is not
* implemented in this class, it is delegated to ValidatorHelper.
*
* @author Roberto Velasco
* @author Gorka Vicente
* @see org.hdiv.filter.ValidatorHelperRequest
*/
public class ValidatorFilter extends OncePerRequestFilter {
/**
* Commons Logging instance.
*/
private static final Log log = LogFactory.getLog(ValidatorFilter.class);
/**
* HDIV configuration object.
*/
protected HDIVConfig hdivConfig;
/**
* IValidationHelper object.
*/
protected IValidationHelper validationHelper;
/**
* The multipart configuration.
*/
protected IMultipartConfig multipartConfig;
/**
* Logger to print the possible attacks detected by HDIV.
*/
protected Logger logger;
/**
* Validation error handler.
*/
protected ValidatorErrorHandler errorHandler;
/**
* Request data and wrappers initializer.
*/
protected RequestInitializer requestInitializer;
private RequestContextFactory requestContextFactory;
/**
* Obtains user data from the request
*/
protected IUserData userData;
/**
* Creates ValidationContext
*/
protected ValidationContextFactory validationContextFactory;
/**
* Initialize required dependencies.
*/
protected void initDependencies() {
if (validationContextFactory == null) {
synchronized (this) {
if (hdivConfig == null) {
ServletContext servletContext = getServletContext();
ApplicationContext context = HDIVUtil.findWebApplicationContext(servletContext);
hdivConfig = context.getBean(HDIVConfig.class);
validationHelper = context.getBean(IValidationHelper.class);
String[] names = context.getBeanNamesForType(IMultipartConfig.class);
if (names.length > 1) {
throw new HDIVException("More than one bean of type 'multipartConfig' is defined.");
}
if (names.length == 1) {
multipartConfig = context.getBean(IMultipartConfig.class);
}
else {
// For applications without Multipart requests
multipartConfig = null;
}
requestContextFactory = context.getBean(RequestContextFactory.class);
userData = context.getBean(IUserData.class);
logger = context.getBean(Logger.class);
errorHandler = context.getBean(ValidatorErrorHandler.class);
requestInitializer = context.getBean(RequestInitializer.class);
validationContextFactory = context.getBean(ValidationContextFactory.class);
}
}
}
}
/**
* Called by the container each time a request/response pair is passed through the chain due to a client request for a resource at the
* end of the chain.
*
* @param request request object
* @param response response object
* @param filterChain filter chain
* @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain)
*/
@Override
protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response, final FilterChain filterChain)
throws ServletException, IOException {
// Initialize dependencies
initDependencies();
if (validationHelper.isInternal(request, response)) {
return;
}
RequestContextHolder ctx = requestContextFactory.create(requestInitializer, request, response);
// Initialize request scoped data
requestInitializer.initRequest(ctx);
@SuppressWarnings("deprecation")
RequestWrapper requestWrapper = (RequestWrapper) ctx.getRequest();
ResponseWrapper responseWrapper = (ResponseWrapper) ctx.getResponse();
HttpServletRequest multipartProcessedRequest = requestWrapper;
boolean isMultipartProcessed = false;
try {
boolean legal = false;
boolean isMultipartException = false;
if (isMultipartContent(request)) {
requestWrapper.setMultipart(true);
try {
if (multipartConfig == null) {
throw new RuntimeException("No 'multipartConfig' configured. It is required for multipart requests.");
}
multipartProcessedRequest = multipartConfig.handleMultipartRequest(requestWrapper, super.getServletContext());
isMultipartProcessed = true;
}
catch (HdivMultipartException e) {
request.setAttribute(IMultipartConfig.FILEUPLOAD_EXCEPTION, e);
isMultipartException = true;
legal = true;
}
}
ValidationContext context = validationContextFactory.newInstance(ctx, validationHelper, hdivConfig.isUrlObfuscation());
ctx.setValidationContext(context);
List<ValidatorError> errors = null;
try {
ValidatorHelperResult result = null;
if (!isMultipartException) {
result = validationHelper.validate(context);
legal = result.isValid();
// Store validation result in request
request.setAttribute(Constants.VALIDATOR_HELPER_RESULT_NAME, result);
}
// All errors, integrity and editable validation
errors = result == null ? null : result.getErrors();
}
catch (ValidationErrorException e) {
if (e.getResult() == ValidatorHelperResult.PEN_TESTING) {
return;
}
errors = e.getResult().getErrors();
}
catch (Exception e) {
if (hdivConfig.isDebugMode()) {
errors = findErrors(e, context.getRequestedTarget());
if (errors == null) {
/**
* It is not a HdivException... but it was launched in our code...
*/
if (log.isErrorEnabled()) {
log.error("Exception in request validation in target:" + context.getRequestedTarget(), e);
}
errors = Collections.singletonList(new ValidatorError(e, context.getRequestedTarget()));
}
}
else {
throw e;
}
}
boolean hasEditableError = false;
if (errors != null && !errors.isEmpty()) {
// Complete error data
completeErrorData(multipartProcessedRequest, errors);
// Log the errors
logValidationErrors(errors);
hasEditableError = processEditableValidationErrors(multipartProcessedRequest, errors);
}
if (legal || hdivConfig.isDebugMode() || hasEditableError && !hdivConfig.isShowErrorPageOnEditableValidation()) {
processRequest(ctx, multipartProcessedRequest, responseWrapper, filterChain, context.getRedirect());
}
else {
// Call to ValidatorErrorHandler
errorHandler.handleValidatorError(ctx, errors);
}
}
catch (Exception e) {
List<ValidatorError> errors = findErrors(e, request.getRequestURI());
if (errors != null) {
// Show error page
if (!hdivConfig.isDebugMode()) {
errorHandler.handleValidatorError(ctx, errors);
}
}
else {
/**
* Try to rethrow the same exception if posible
*/
if (e instanceof RuntimeException) {
throw (RuntimeException) e;
}
if (e instanceof ServletException) {
throw (ServletException) e;
}
if (e instanceof IOException) {
throw (IOException) e;
}
throw new RuntimeException(e);
}
}
finally {
if (isMultipartProcessed) {
// Cleanup multipart
multipartConfig.cleanupMultipart(multipartProcessedRequest);
}
// Destroy request scoped data
requestInitializer.endRequest(ctx);
}
}
private List<ValidatorError> findErrors(final Throwable e, final String target) {
Throwable current = e;
do {
if (!(current instanceof HDIVException)) {
current = current.getCause();
}
} while (current != null && !(current instanceof HDIVException));
if (current instanceof HDIVException) {
if (log.isErrorEnabled()) {
log.error("Exception in request validation", current);
}
// Show error page
return Collections.singletonList(new ValidatorError(current, target));
}
return null;
}
/**
* Utility method that determines whether the request contains multipart content.
*
* @param request the request
* @return <code>true</code> if the request is multipart. <code>false</code> otherwise.
*/
protected boolean isMultipartContent(final HttpServletRequest request) {
return HDIVUtil.isMultipartContent(request);
}
/**
* Processes requests for both HTTP <code>GET</code> and <code>POST</code> methods.
*
* @param ctx request context
* @param requestWrapper request wrapper
* @param responseWrapper response wrapper
* @param filterChain filter chain
* @param obfuscated obfuscated
* @throws IOException if there is an error in request process.
* @throws ServletException if there is an error in request process.
*/
protected final void processRequest(final RequestContextHolder ctx, final HttpServletRequest requestWrapper,
final ResponseWrapper responseWrapper, final FilterChain filterChain, final String obfuscated)
throws IOException, ServletException {
validationHelper.startPage(ctx);
try {
if (obfuscated != null) {
requestWrapper.getRequestDispatcher(obfuscated).forward(requestWrapper, responseWrapper);
}
else {
filterChain.doFilter(requestWrapper, responseWrapper);
}
}
finally {
validationHelper.endPage(ctx);
}
}
/**
* Complete {@link ValidatorError} containing data including user related info.
*
* @param request request object
* @param errors all validation errors
*/
protected void completeErrorData(final HttpServletRequest request, final List<ValidatorError> errors) {
String localIp = userData.getLocalIp(request);
String remoteIp = userData.getRemoteIp(request);
String userName = userData.getUsername(request);
String contextPath = request.getContextPath();
for (ValidatorError error : errors) {
error.setLocalIp(localIp);
error.setRemoteIp(remoteIp);
error.setUserName(userName);
// Include context path in the target
String target = error.getTarget();
if (target != null && !target.startsWith(contextPath)) {
target = request.getContextPath() + target;
}
else if (target == null) {
target = request.getRequestURI();
}
error.setTarget(target);
}
}
/**
* Log validation errors
*
* @param errors all validation errors
*/
protected void logValidationErrors(final List<ValidatorError> errors) {
for (ValidatorError error : errors) {
// Log the error
logger.log(error);
}
}
/**
* Process editable validation errors. Add them to the request scope to read later from the web framework.
*
* @param request request object
* @param errors all validation errors
* @return true if there is a editable validation error
*/
protected boolean processEditableValidationErrors(final HttpServletRequest request, final List<ValidatorError> errors) {
List<ValidatorError> editableErrors = new ArrayList<ValidatorError>();
for (ValidatorError error : errors) {
if (HDIVErrorCodes.INVALID_EDITABLE_VALUE.equals(error.getType())) {
editableErrors.add(error);
}
}
if (!editableErrors.isEmpty() && !hdivConfig.isDebugMode()) {
// Put the errors on request to be accessible from the Web framework
request.setAttribute(Constants.EDITABLE_PARAMETER_ERROR, editableErrors);
if (hdivConfig.isShowErrorPageOnEditableValidation()) {
// Redirect to error page
// Put errors in session to be accessible from error page
request.getSession().setAttribute(Constants.EDITABLE_PARAMETER_ERROR, editableErrors);
}
}
return !editableErrors.isEmpty();
}
}