/** * 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.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.servlet.http.Cookie; 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.context.RequestContextHolder; import org.hdiv.dataComposer.DataComposerFactory; import org.hdiv.dataComposer.IDataComposer; import org.hdiv.dataValidator.IDataValidator; import org.hdiv.dataValidator.IValidationResult; import org.hdiv.exception.HDIVException; import org.hdiv.session.ISession; import org.hdiv.state.IPage; import org.hdiv.state.IParameter; import org.hdiv.state.IState; import org.hdiv.state.StateUtil; import org.hdiv.state.scope.StateScope; import org.hdiv.state.scope.StateScopeManager; import org.hdiv.urlProcessor.BasicUrlProcessor; import org.hdiv.urlProcessor.UrlData; import org.hdiv.util.Constants; import org.hdiv.util.HDIVErrorCodes; import org.hdiv.util.HDIVUtil; import org.hdiv.util.Method; import org.hdiv.validator.EditableDataValidationResult; import org.springframework.web.util.HtmlUtils; /** * It validates client requests by consuming an object of type IState and validating all the entry data, besides replacing relative values * by its real values. * * @author Roberto Velasco * @author Gorka Vicente * @author Gotzon Illarramendi * @since HDIV 2.0 */ public class ValidatorHelperRequest implements IValidationHelper, StateRestorer { private static final String VALIDATION_ERROR = "validation.error"; /** * Commons Logging instance. */ private static final Log log = LogFactory.getLog(ValidatorHelperRequest.class); /** * HDIV configuration object. */ protected HDIVConfig hdivConfig; /** * Utility methods for state */ protected StateUtil stateUtil; /** * State that represents all the data of a request or a form existing in a page <code>page</code> */ protected ISession session; /** * IDataValidator factory */ protected IDataValidator dataValidator; /** * {@link IDataComposer} factory */ protected DataComposerFactory dataComposerFactory; /** * Compiled numeric <code>Pattern</code> */ protected Pattern numberPattern = Pattern.compile("[0-9]+"); /** * URL String processor. */ protected BasicUrlProcessor urlProcessor; /** * State scope manager. */ protected StateScopeManager stateScopeManager; /** * Initialization of the objects needed for the validation process. * * @throws HDIVException if there is an initialization error. */ public void init() { } private void processPenTesting(final ValidationContext context) { ValidatorHelperResult ptresult = restoreState(context); if (ptresult.isValid()) { List<String> editable = new ArrayList<String>(); context.getRequestContext().getResponse().setContentType("text/html"); if (ptresult.getValue().getParameters() != null) { for (IParameter parameter : ptresult.getValue().getParameters()) { if (parameter.isEditable()) { editable.add(parameter.getName()); } } } for (int i = 0; i < editable.size(); i++) { try { PrintWriter out = context.getRequestContext().getResponse().getWriter(); if (i != 0) { out.write(','); } out.write(editable.get(i)); out.flush(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } throw new ValidationErrorException(ValidatorHelperResult.PEN_TESTING); } } /** * Checks if the values of the parameters received in the request <code>request</code> are valid. These values are valid if and only if * the noneditable parameters haven't been modified.<br> * Validation process is as follows.<br> * 1. If the action to which the request is directed is an init page, then it is a valid request.<br> * 2. if the cookies received in the request are not found in the user session, the validation is incorrect.<br> * 3. if the state recover process has produced an error, incorrect validation.<br> * 4. If the action received in the request is different to the action of the recovered state, incorrect validation. <br> * 5. If not, all the parameter values are checked and if all the received values are valid then the request is valid. <br> * 5.1. If it is an init parameter or a HDIV parameter then it is a valid parameter.<br> * 5.2. If the received parameter is not in the state:<br> * 5.2.1. If it has been defined by the user as a no validation required parameter, then it is a valid parameter. <br> * 5.2.2. otherwise, it is a no valid request.<br> * 5.3. If the parameter is editable, if validations have been defined values are checked.<br> * 5.4. If it is a non editable parameter, all the received values are checked. * * @param context request context * @return valid result If all the parameter values of the request <code>request</code> pass the the HDIV validation. False, otherwise. * @throws HDIVException If the request doesn't pass the HDIV validation an exception is thrown explaining the cause of the error. */ @SuppressWarnings("unused") public ValidatorHelperResult validate(final ValidationContext context) { String target = context.getTarget(); RequestContextHolder ctx = context.getRequestContext(); if (false && target.endsWith(UrlData.PEN_TESTING_ROOT_PATH)) { processPenTesting(context); } // Hook before the validation ValidatorHelperResult result = preValidate(context); if (result != null) { return result; } if (hdivConfig.hasExtensionToExclude(target)) { if (log.isDebugEnabled()) { log.debug("The target [" + target + "] has an extension to exclude from validation"); } return ValidatorHelperResult.VALIDATION_NOT_REQUIRED; } if (!hdivConfig.isValidationInUrlsWithoutParamsActivated()) { boolean requestHasParameters = ctx.getParameterNames() != null && ctx.getParameterNames().hasMoreElements(); if (!requestHasParameters) { if (log.isDebugEnabled()) { log.debug("The url [" + ctx.getRequestURI() + "] is not be validated because it has not got parameters"); } return ValidatorHelperResult.VALIDATION_NOT_REQUIRED; } } if (isStartPage(ctx, target)) { result = validateStartPageParameters(ctx, target); if (result.isValid()) { if (log.isDebugEnabled()) { log.debug("The target [" + target + "] is an start page and parameters are valid."); } return ValidatorHelperResult.VALIDATION_NOT_REQUIRED; } else { if (log.isDebugEnabled()) { log.debug("The target [" + target + "] is an start page and parameters are NOT valid."); } return result; } } if (hdivConfig.isCookiesIntegrityActivated()) { result = validateRequestCookies(context, target); if (!result.isValid()) { if (log.isDebugEnabled()) { log.debug("Invalid cookies found."); } return result; } } // Hdiv parameter name String hdivParameter = context.getRequestContext().getHdivParameterName(); // Restore state from request or memory result = restoreState(context); if (!result.isValid()) { if (log.isDebugEnabled()) { log.debug("Error restoring the state: " + result); } return result; } // Get resultant object, the stored state IState state = result.getValue(); result = isTheSameAction(ctx, target, state); if (!result.isValid()) { return result; } // Extract url params from State Map<String, String[]> stateParams = urlProcessor.getUrlParamsAsMap(hdivParameter, context.getBuffer(), state.getParams()); result = allRequiredParametersReceived(ctx, state, target, stateParams); if (!result.isValid()) { return result; } List<ValidatorError> unauthorizedEditableParameters = new ArrayList<ValidatorError>(); Enumeration<?> parameters = ctx.getParameterNames(); while (parameters.hasMoreElements()) { String parameter = (String) parameters.nextElement(); // Validate parameter result = validateParameter(ctx, stateParams, state.getParameter(parameter), stateParams.get(parameter), unauthorizedEditableParameters, hdivParameter, target, parameter); if (!result.isValid()) { return result; } } if (!unauthorizedEditableParameters.isEmpty()) { return new ValidatorHelperResult(unauthorizedEditableParameters); } return ValidatorHelperResult.VALID; } @Deprecated protected final boolean isStartPage(final HttpServletRequest request, final String target) { throw new UnsupportedOperationException(); } /** * Check if the current request is a start page. * * @param request HttpServletRequest to validate * @param target Part of the url that represents the target action * @return true if it is a start page */ protected boolean isStartPage(final RequestContextHolder request, final String target) { return hdivConfig.isStartPage(target, Method.secureValueOf(request.getMethod())); } @Deprecated protected final ValidatorHelperResult isTheSameAction(final HttpServletRequest request, final String target, final IState state) { return isTheSameAction(request, target, state.getAction()); } @Deprecated protected final ValidatorHelperResult isTheSameAction(final HttpServletRequest request, final String target, final String stateAction) { throw new UnsupportedOperationException(); } /** * Checks if the action received in the request is the same as the one stored in the HDIV state. * * @param context Request context * @param target Part of the url that represents the target action * @param state The restored state for this url * @return valid result if the actions are the same. False otherwise. */ protected ValidatorHelperResult isTheSameAction(final RequestContextHolder context, final String target, final IState state) { return isTheSameAction(context, target, state.getAction()); } /** * Checks if the action received in the request is the same as the one stored in the HDIV state. * * @param context Request context * @param target Part of the url that represents the target action * @param stateAction The restored state for this url * @return valid result if the actions are the same. False otherwise. */ protected ValidatorHelperResult isTheSameAction(final RequestContextHolder context, final String target, String stateAction) { // Remove HTML escaped content from the action, for example, HTML entities like Ñ stateAction = HtmlUtils.htmlUnescape(stateAction); if (stateAction.equalsIgnoreCase(target)) { return ValidatorHelperResult.VALID; } if (target.endsWith("/")) { String actionSlash = stateAction + "/"; if (actionSlash.equalsIgnoreCase(target)) { return ValidatorHelperResult.VALID; } } if (log.isDebugEnabled()) { log.debug("Validation error in the action. Action in state [" + stateAction + "], action in the request [" + target + "]"); } ValidatorError error = new ValidatorError(HDIVErrorCodes.INVALID_ACTION, target); return new ValidatorHelperResult(error); } @Deprecated protected final ValidatorHelperResult validateStartPageParameters(final HttpServletRequest request, final String target) { throw new UnsupportedOperationException(); } /** * It validates the parameters of an init page because our application can receive requests that require validation but don't have any * HDIV state. So, despite being init pages, editable data validation must be done. * * @param request HttpServletRequest to validate * @param target Part of the url that represents the target action * @return valid result if the values of the editable parameters pass the validations defined in hdiv-config.xml. False otherwise. * @since HDIV 1.1.2 */ protected ValidatorHelperResult validateStartPageParameters(final RequestContextHolder request, final String target) { List<ValidatorError> unauthorizedEditableParameters = new ArrayList<ValidatorError>(); Enumeration<?> parameters = request.getParameterNames(); while (parameters.hasMoreElements()) { String parameter = (String) parameters.nextElement(); String[] values = request.getParameterValues(parameter); validateEditableParameter(target, parameter, values, "text", unauthorizedEditableParameters); } if (!unauthorizedEditableParameters.isEmpty()) { return new ValidatorHelperResult(unauthorizedEditableParameters); } return ValidatorHelperResult.VALID; } /** * Checks if the cookies received in the request are correct. For that, it checks if they are in the user session. * * @param context Validation context * @param target Part of the url that represents the target action * @return valid result if all the cookies received in the request are correct. They must have been previously stored in the user * session by HDIV to be correct. False otherwise. * @since HDIV 1.1 */ protected final ValidatorHelperResult validateRequestCookies(final ValidationContext context, final String target) { Cookie[] requestCookies = context.getRequestContext().getCookies(); if (requestCookies == null || requestCookies.length == 0) { return ValidatorHelperResult.VALID; } @SuppressWarnings("unchecked") Map<String, SavedCookie> sessionCookies = session.getAttribute(context.getRequestContext(), // TODO cache // context? Constants.HDIV_COOKIES_KEY, Map.class); if (sessionCookies == null) { return ValidatorHelperResult.VALID; } boolean cookiesConfidentiality = hdivConfig.getConfidentiality() && hdivConfig.isCookiesConfidentialityActivated(); for (int i = 0; i < requestCookies.length; i++) { boolean found = false; if (requestCookies[i].getName().equals(Constants.JSESSIONID)) { continue; } if (requestCookies[i].getName().equals(Constants.HDIV_RANDOM_COOKIE)) { continue; } if (sessionCookies.containsKey(requestCookies[i].getName())) { SavedCookie savedCookie = sessionCookies.get(requestCookies[i].getName()); if (savedCookie.isEqual(requestCookies[i], cookiesConfidentiality)) { found = true; if (cookiesConfidentiality && savedCookie.getValue() != null) { requestCookies[i].setValue(savedCookie.getValue()); } } } if (!found) { ValidatorError error = new ValidatorError(HDIVErrorCodes.INVALID_COOKIE, target, "cookie:" + requestCookies[i].getName(), requestCookies[i].getValue()); return new ValidatorHelperResult(error); } } return ValidatorHelperResult.VALID; } /** * Checks if the values <code>values</code> are valid for the editable parameter <code>parameter</code>. If the values are not valid, an * error message with the parameter and the received values will be log. * * @param target Part of the url that represents the target action * @param parameter parameter name * @param values parameter's values * @param dataType editable data type * @param unauthorizedParameters Unauthorized editable parameters * @since HDIV 1.1 */ protected void validateEditableParameter(final String target, final String parameter, final String[] values, final String dataType, final List<ValidatorError> unauthorizedParameters) { EditableDataValidationResult result = hdivConfig.getEditableDataValidationProvider().validate(target, parameter, values, dataType); if (!result.isValid()) { String value; if ("password".equals(dataType)) { value = Constants.HDIV_EDITABLE_PASSWORD_ERROR_KEY; } else { StringBuilder unauthorizedValues = new StringBuilder(values[0]); // TODO include only unauthorized values, not all values for (int i = 1; i < values.length; i++) { unauthorizedValues.append(',').append(values[i]); } value = unauthorizedValues.toString(); } ValidatorError error = new ValidatorError(HDIVErrorCodes.INVALID_EDITABLE_VALUE, target, parameter, value, null, result.getValidationId()); unauthorizedParameters.add(error); } } @Deprecated protected final ValidatorHelperResult allRequiredParametersReceived(final HttpServletRequest request, final IState state, final String target, final Map<String, String[]> stateParams) { throw new UnsupportedOperationException(); } /** * Check if all required parameters are received in <code>request</code>. * * @param request HttpServletRequest to validate * @param state IState The restored state for this url * @param target Part of the url that represents the target action * @param stateParams Url params from State * @return valid result if all required parameters are received. False in otherwise. */ protected ValidatorHelperResult allRequiredParametersReceived(final RequestContextHolder request, final IState state, final String target, final Map<String, String[]> stateParams) { List<String> requiredParameters = state.getRequiredParams(); List<String> requiredParams = new ArrayList<String>(stateParams.keySet()); Enumeration<?> requestParameters = request.getParameterNames(); List<String> required = new ArrayList<String>(); required.addAll(requiredParameters); required.addAll(requiredParams); while (requestParameters.hasMoreElements()) { String currentParameter = (String) requestParameters.nextElement(); required.remove(currentParameter); // If multiple parameters are received, it is possible to pass this // verification without checking all the request parameters. if (required.isEmpty()) { return ValidatorHelperResult.VALID; } } // Fix for IBM Websphere different behavior with parameters without values. // For example, param1=val1¶m2 // This kind of parameters are excluded from request.getParameterNames() API. // http://www.ibm.com/support/docview.wss?uid=swg1PM35450 if (!required.isEmpty()) { Iterator<String> it = required.iterator(); while (it.hasNext()) { String req = it.next(); if (isNoValueParameter(request, req)) { it.remove(); } } } return validateMissingParameters(request, state, target, stateParams, required); } @Deprecated protected final ValidatorHelperResult validateMissingParameters(final HttpServletRequest request, final IState state, final String target, final Map<String, String[]> stateParams, final List<String> missingParameters) { throw new UnsupportedOperationException(); } /** * Validate required parameters but not received in the request. * * @param request HttpServletRequest to validate * @param state IState The restored state for this url * @param target Part of the url that represents the target action * @param stateParams Url params from State * @param missingParameters Required parameters not received in the request. * @return result with the error */ protected ValidatorHelperResult validateMissingParameters(final RequestContextHolder request, final IState state, final String target, final Map<String, String[]> stateParams, final List<String> missingParameters) { if (missingParameters.isEmpty()) { return ValidatorHelperResult.VALID; } if (log.isDebugEnabled()) { log.debug("Missing some required parameters: " + missingParameters.toString()); } ValidatorError error = new ValidatorError(HDIVErrorCodes.NOT_RECEIVED_ALL_REQUIRED_PARAMETERS, target, missingParameters.toString()); return new ValidatorHelperResult(error); } /** * Check if the given parameter doesn't have values looking in the query string. * * @param request HttpServletRequest instance * @param parameter Parameter name * @return true if the parameter does't have value */ private boolean isNoValueParameter(final RequestContextHolder request, final String parameter) { String queryString = request.getQueryString(); if (queryString == null) { return false; } String[] parts = queryString.split("&"); if (parts.length == 0) { return false; } List<String> partsList = Arrays.asList(parts); return partsList.contains(parameter); } @Deprecated protected final ValidatorHelperResult validateParameter(final HttpServletRequest request, final Map<String, String[]> stateParams, final IParameter stateParameter, final String[] actionParamValues, final List<ValidatorError> unauthorizedEditableParameters, final String hdivParameter, final String target, final String parameter) { throw new UnsupportedOperationException(); } /** * Validate single parameter values. * * @param request HttpServletRequest to validate * @param stateParams parameter and values from the parameters stored in the state * @param stateParameter IParameter The restored state for this url * @param actionParamValues action params values * @param unauthorizedEditableParameters Editable parameters with errors * @param hdivParameter Hdiv state parameter name * @param target Part of the url that represents the target action * @param parameter Parameter name to validate * @return Valid if parameter has not errors * @since HDIV 2.1.5 */ protected ValidatorHelperResult validateParameter(final RequestContextHolder request, final Map<String, String[]> stateParams, final IParameter stateParameter, final String[] actionParamValues, final List<ValidatorError> unauthorizedEditableParameters, final String hdivParameter, final String target, final String parameter) { // If the parameter requires no validation it is considered a valid parameter if (isUserDefinedNonValidationParameter(target, parameter, hdivParameter)) { return ValidatorHelperResult.VALIDATION_NOT_REQUIRED; } if (stateParameter == null && actionParamValues == null) { // The parameter is not defined in the state, it is an extra parameter. return validateExtraParameter(request, stateParams, stateParameter, actionParamValues, unauthorizedEditableParameters, hdivParameter, target, parameter); } // At this point we are processing a noneditable parameter String[] values = request.getParameterValues(parameter); // Check if the parameter is editable if (stateParameter != null && stateParameter.isEditable()) { // Mark parameter as editable request.addEditableParameter(parameter); if (stateParameter.getEditableDataType() != null) { validateEditableParameter(target, parameter, values, stateParameter.getEditableDataType(), unauthorizedEditableParameters); } return ValidatorHelperResult.VALID; } try { return validateParameterValues(request, target, stateParameter, actionParamValues, parameter, values); } catch (final HDIVException e) { String errorMessage = request.getMessage(VALIDATION_ERROR, e.getMessage()); throw new HDIVException(errorMessage, e); } } @Deprecated protected final ValidatorHelperResult validateExtraParameter(final HttpServletRequest request, final Map<String, String[]> stateParams, final IParameter stateParameter, final String[] actionParamValues, final List<ValidatorError> unauthorizedEditableParameters, final String hdivParameter, final String target, final String parameter) { throw new UnsupportedOperationException(); } /** * Validate parameter non present in the state. * * @param request HttpServletRequest to validate * @param stateParams parameter and values from the parameters stored in the state * @param stateParameter IParameter The restored state for this url * @param actionParamValues actio params values * @param unauthorizedEditableParameters Editable parameters with errors * @param hdivParameter Hdiv state parameter name * @param target Part of the url that represents the target action * @param parameter Parameter name to validate * @return Valid if parameter has not errors * @since HDIV 2.1.13 */ protected ValidatorHelperResult validateExtraParameter(final RequestContextHolder request, final Map<String, String[]> stateParams, final IParameter stateParameter, final String[] actionParamValues, final List<ValidatorError> unauthorizedEditableParameters, final String hdivParameter, final String target, final String parameter) { // If the parameter is not defined in the state, it is an error. // With this verification we guarantee that no extra parameters are added. if (log.isDebugEnabled()) { log.debug("Validation Error Detected: Parameter [" + parameter + "] does not exist in the state for action [" + target + "]"); } ValidatorError error = new ValidatorError(HDIVErrorCodes.INVALID_PARAMETER_NAME, target, parameter); return new ValidatorHelperResult(error); } /** * Checks if the parameter <code>parameter</code> is defined by the user as a no required validation parameter for the action * <code>this.target</code>. * * @param target target * @param parameter parameter name * @param hdivParameter Hdiv state parameter name * @return True if the parameter doesn't need validation. False otherwise. */ protected boolean isUserDefinedNonValidationParameter(final String target, final String parameter, final String hdivParameter) { // Check if the HDIV validation must be applied to the parameter if (!hdivConfig.needValidation(parameter, hdivParameter)) { if (log.isDebugEnabled() && !parameter.equals(hdivParameter)) { log.debug("Parameter [" + parameter + "] doesn't need validation. It is configured as 'StartParameter'"); } return true; } if (hdivConfig.isParameterWithoutValidation(target, parameter)) { if (log.isDebugEnabled()) { log.debug("Parameter [" + parameter + "] doesn't need validation. It is configured as 'ParameterWithoutValidation'."); } return true; } return false; } /** * Restore state from session or <code>request</code> with <code>request</code> identifier. Strategy defined by the user determines the * way the state is restored. * * @param context validation context * @return valid result if restored state is valid. False in otherwise. */ public final ValidatorHelperResult restoreState(final ValidationContext context) { return restoreState(context, context.getRequestContext().getHdivState()); } @Deprecated protected final ValidatorHelperResult restoreState(final String hdivParameter, final ValidationContext context) { // checks if the parameter HDIV parameter exists in the parameters of the request return restoreState(hdivParameter, context.getRequestContext(), context.getRequestedTarget(), context.getRequestContext().getParameter(hdivParameter)); } @Deprecated protected final ValidatorHelperResult restoreState(final String hdivParameter, final String hdivState, final ValidationContext context) { // checks if the parameter HDIV parameter exists in the parameters of the requestds return restoreState(hdivParameter, context.getRequestContext(), context.getRequestedTarget(), hdivState); } @Deprecated protected final ValidatorHelperResult restoreState(final String hdivParameter, final HttpServletRequest request, final String target, final String requestState) { return restoreState(hdivParameter, HDIVUtil.getRequestContext(request), target, requestState); } @Deprecated protected final ValidatorHelperResult restoreState(final String hdivParameter, final RequestContextHolder context, final String target, final String requestState) { throw new UnsupportedOperationException(); } /** * Restore state from session or <code>request</code> with <code>request</code> identifier. Strategy defined by the user determines the * way the state is restored. * * @param context Validation context * @param requestState Request state * @return valid result if restored state is valid. False in otherwise. */ public final ValidatorHelperResult restoreState(final ValidationContext context, String requestState) { if (requestState == null) { ValidatorError error = new ValidatorError(HDIVErrorCodes.HDIV_PARAMETER_DOES_NOT_EXIST, context.getRequestedTarget(), context.getRequestContext().getHdivParameterName()); return new ValidatorHelperResult(error); } // In some browsers (eg: IE 6), fragment identifier is sent with the request, it has to be removed from the // requestState if (requestState.contains("#")) { requestState = requestState.split("#")[0]; } try { RequestContextHolder ctx = context.getRequestContext(); int pageId = stateUtil.getPageId(requestState); IState state = doRestoreState(context, requestState); // Save current page id in request ctx.setCurrentPageId(pageId); if (!validateHDIVSuffix(ctx, requestState, state)) { ValidatorError error = new ValidatorError(HDIVErrorCodes.INVALID_HDIV_PARAMETER_VALUE, context.getRequestedTarget(), context.getRequestContext().getHdivParameterName(), requestState); return new ValidatorHelperResult(error); } // return validation OK and resultant state return new ValidatorHelperResult(true, state); } catch (final HDIVException e) { if (log.isDebugEnabled()) { log.debug("Error while restoring state:" + requestState, e); } // HDIVException message contains error code ValidatorError error = new ValidatorError(e.getMessage(), context.getRequestedTarget(), context.getRequestContext().getHdivParameterName(), requestState); return new ValidatorHelperResult(error); } } protected IState doRestoreState(final ValidationContext ctx, final String requestState) { return stateUtil.restoreState(ctx.getRequestContext(), requestState); } /** * Checks if the suffix added in the memory version to all requests in the HDIV parameter is the same as the one stored in session, * which is the original suffix. So any request using the memory version should keep the suffix unchanged. * * @param context Request context holder * @param value value received in the HDIV parameter * @param restoredState restored state * @return True if the received value of the suffix is valid. False otherwise. */ protected boolean validateHDIVSuffix(final RequestContextHolder context, final String value, final IState restoredState) { int firstSeparator = value.indexOf(Constants.STATE_ID_SEPARATOR); int lastSeparator = value.lastIndexOf(Constants.STATE_ID_SEPARATOR); if (firstSeparator == -1) { return false; } if (firstSeparator >= lastSeparator) { return false; } try { // read hdiv's suffix from request String requestSuffix = value.substring(lastSeparator + 1); // read suffix from page stored in session String pId = value.substring(0, firstSeparator); String sId = value.substring(firstSeparator + 1, lastSeparator); int pageId; int stateId = 0; try { stateId = Integer.parseInt(sId); } catch (final NumberFormatException e) { throw new HDIVException(HDIVErrorCodes.INVALID_PAGE_ID, e); } StateScope stateScope = stateScopeManager.getStateScope(value); if (stateScope != null) { String token = stateScope.getStateToken(context, stateId); return requestSuffix.equals(token); } try { pageId = Integer.parseInt(pId); } catch (final NumberFormatException e) { throw new HDIVException(HDIVErrorCodes.INVALID_PAGE_ID, e); } IPage currentPage = restoredState.getPage(); if (currentPage == null) { currentPage = session.getPage(context, pageId); } if (currentPage == null) { if (log.isErrorEnabled()) { log.error("Page with id [" + pageId + "] not found in session."); } throw new HDIVException(HDIVErrorCodes.INVALID_PAGE_ID); } return currentPage.getRandomToken(restoredState.getTokenType()).equals(requestSuffix); } catch (final IndexOutOfBoundsException e) { String errorMessage = context.getMessage(VALIDATION_ERROR, e.getMessage()); if (log.isErrorEnabled()) { log.error(errorMessage); } throw new HDIVException(errorMessage, e); } } @Deprecated protected final ValidatorHelperResult validateParameterValues(final HttpServletRequest request, final String target, final IParameter stateParameter, final String[] actionParamValues, final String parameter, final String[] values) { throw new UnsupportedOperationException(); } /** * Checks if all the received parameter <code>parameter</code> values are valid, that is, are expected values. Received value number is * checked and then these values are validated. * * @param request HttpServletRequest to validate * @param target Part of the url that represents the target action * @param stateParameter Parameter stored in state * @param actionParamValues values of the parameters of the form action * @param parameter Url parameters * @param values parameter <code>parameter</code> values * @return valid result if the validation is correct. False otherwise. * @throws HDIVException if there is an error in parameter validation process. */ protected ValidatorHelperResult validateParameterValues(final RequestContextHolder request, final String target, final IParameter stateParameter, final String[] actionParamValues, final String parameter, final String[] values) { try { // Only for required parameters must be checked if the number of received // values is the same as number of values in the state. If this wasn't // taken into account, this verification will be done for every parameter, // including for example, a multiple combo where hardly ever are all its // values received. if (actionParamValues != null && values.length != actionParamValues.length) { String valueMessage = ""; if (values.length > actionParamValues.length) { if (log.isDebugEnabled()) { log.debug("Received more values than expected for the parameter '" + parameter + "'. Received=" + values + ", Expected=" + actionParamValues); valueMessage = Arrays.toString(values); } else { log.debug("Received fewer values than expected for the parameter '" + parameter + "'. Received=" + values + ", Expected=" + actionParamValues); valueMessage = Arrays.toString(actionParamValues); } } ValidatorError error = new ValidatorError(HDIVErrorCodes.NOT_RECEIVED_ALL_PARAMETER_VALUES, target, parameter, valueMessage); return new ValidatorHelperResult(error); } List<String> stateParamValues; if (stateParameter != null) { stateParamValues = stateParameter.getValues(); } else { stateParamValues = Arrays.asList(actionParamValues); } ValidatorHelperResult result = hasRepeatedOrInvalidValues(request, target, parameter, values, stateParamValues); if (!result.isValid()) { return result; } // At this point, we know that the number of received values is the same // as the number of values sent to the client. Now we have to check if // the received values are all tha ones stored in the state. return validateReceivedValuesInState(request, target, stateParameter, actionParamValues, parameter, values); } catch (final HDIVException e) { String errorMessage = request.getMessage(VALIDATION_ERROR, e.getMessage()); throw new HDIVException(errorMessage, e); } } @Deprecated protected final ValidatorHelperResult hasRepeatedOrInvalidValues(final HttpServletRequest request, final String target, final String parameter, final String[] values, final List<String> stateValues) { throw new UnsupportedOperationException(); } /** * Checks if repeated or no valid values have been received for the parameter <code>parameter</code>. * * @param request HttpServletRequest to validate * @param target Part of the url that represents the target action * @param parameter parameter name * @param values Parameter <code>parameter</code> values * @param stateValues values stored in state for <code>parameter</code> * @return True If repeated or no valid values have been received for the parameter <code>parameter</code>. */ protected ValidatorHelperResult hasRepeatedOrInvalidValues(final RequestContextHolder request, final String target, final String parameter, final String[] values, final List<String> stateValues) { List<String> tempStateValues = new ArrayList<String>(); tempStateValues.addAll(stateValues); if (hdivConfig.getConfidentiality()) { return hasConfidentialIncorrectValues(request, target, parameter, values, tempStateValues); } else { return hasNonConfidentialIncorrectValues(target, parameter, values, tempStateValues); } } @Deprecated protected final ValidatorHelperResult hasConfidentialIncorrectValues(final HttpServletRequest request, final String target, final String parameter, final String[] values, final List<String> stateValues) { throw new UnsupportedOperationException(); } /** * Checks if repeated values have been received for the parameter <code>parameter</code>. * * @param request HttpServletRequest to validate * @param target Part of the url that represents the target action * @param parameter parameter name * @param values Parameter <code>parameter</code> values * @param stateValues real values for <code>parameter</code> * @return True If repeated values have been received for the parameter <code>parameter</code>. */ protected ValidatorHelperResult hasConfidentialIncorrectValues(final RequestContextHolder request, final String target, final String parameter, final String[] values, final List<String> stateValues) { Set<String> receivedValues = new HashSet<String>(); for (int i = 0; i < values.length; i++) { if (hdivConfig.isParameterWithoutConfidentiality(request, parameter)) { return ValidatorHelperResult.VALID; } ValidatorHelperResult result = isInRange(target, parameter, values[i], stateValues); if (!result.isValid()) { return result; } if (receivedValues.contains(values[i])) { String originalValue = stateValues.size() > 1 ? stateValues.toString() : stateValues.get(0); ValidatorError error = new ValidatorError(HDIVErrorCodes.REPEATED_VALUES_FOR_PARAMETER, target, parameter, values[i], originalValue); return new ValidatorHelperResult(error); } receivedValues.add(values[i]); } return ValidatorHelperResult.VALID; } /** * Checks if repeated or no valid values have been received for the parameter <code>parameter</code>. * * @param target Part of the url that represents the target action * @param parameter parameter name * @param values Parameter <code>parameter</code> values * @param tempStateValues values stored in state for <code>parameter</code> * @return True If repeated or no valid values have been received for the parameter <code>parameter</code>. */ protected ValidatorHelperResult hasNonConfidentialIncorrectValues(final String target, final String parameter, final String[] values, final List<String> tempStateValues) { Set<String> receivedValues = new HashSet<String>(); for (int i = 0; i < values.length; i++) { boolean exists = false; for (int j = 0; j < tempStateValues.size() && !exists; j++) { String tempValue = tempStateValues.get(j); if (tempValue.equalsIgnoreCase(values[i]) || HDIVUtil.isTheSameEncodedValue(tempValue, values[i])) { tempStateValues.remove(j); exists = true; } } if (!exists) { String originalValue = ""; if (tempStateValues.size() == 1) { originalValue = tempStateValues.get(0); } else if (tempStateValues.size() > 1) { originalValue = tempStateValues.toString(); } if (receivedValues.contains(values[i])) { ValidatorError error = new ValidatorError(HDIVErrorCodes.REPEATED_VALUES_FOR_PARAMETER, target, parameter, values[i], originalValue); return new ValidatorHelperResult(error); } ValidatorError error = new ValidatorError(HDIVErrorCodes.INVALID_PARAMETER_VALUE, target, parameter, values[i], originalValue); return new ValidatorHelperResult(error); } receivedValues.add(values[i]); } return ValidatorHelperResult.VALID; } /** * Checks if the confidential value received in <code>value</code> is a value lower than the number or values received for the parameter * <code>parameter</code>. * * @param target Part of the url that represents the target action * @param parameter parameter * @param value value * @param stateValues real values for <code>parameter</code> * @return ValidatorHelperResult with the result of the validation. * @since HDIV 2.0 */ protected ValidatorHelperResult isInRange(final String target, final String parameter, final String value, final List<String> stateValues) { Matcher m = numberPattern.matcher(value); try { if (!m.matches() || Integer.parseInt(value) >= stateValues.size()) { String originalValue = stateValues.size() > 1 ? stateValues.toString() : stateValues.get(0); ValidatorError error = new ValidatorError(HDIVErrorCodes.INVALID_CONFIDENTIAL_VALUE, target, parameter, value, originalValue); return new ValidatorHelperResult(error); } } catch (final NumberFormatException e) { // value is not a number or is greater than the length of Integer.MAX_VALUE String originalValue = stateValues.size() > 1 ? stateValues.toString() : stateValues.get(0); ValidatorError error = new ValidatorError(HDIVErrorCodes.INVALID_CONFIDENTIAL_VALUE, target, parameter, value, originalValue); return new ValidatorHelperResult(error); } return ValidatorHelperResult.VALID; } @Deprecated protected final ValidatorHelperResult validateReceivedValuesInState(final HttpServletRequest request, final String target, final IParameter stateParameter, final String[] actionParamValues, final String parameter, final String[] values) { throw new UnsupportedOperationException(); } /** * Checks that values <code>values</code> for the <code>parameter</code> are valid. * * @param request HttpServletRequest to validate * @param target Part of the url that represents the target action * @param stateParameter parameters from the state * @param actionParamValues parameter from the state * @param parameter Parameter name * @param values Parameter <code>parameter</code> values. * @return True If the <code>values</code> validation is correct. False otherwise. */ protected ValidatorHelperResult validateReceivedValuesInState(final RequestContextHolder request, final String target, final IParameter stateParameter, final String[] actionParamValues, final String parameter, final String[] values) { int size = values.length; String[] originalValues = new String[size]; for (int i = 0; i < size; i++) { IValidationResult result = dataValidator.validate(request, values[i], target, parameter, stateParameter, actionParamValues); if (!result.getLegal()) { ValidatorError error = new ValidatorError(HDIVErrorCodes.INVALID_PARAMETER_VALUE, target, parameter, values[i]); return new ValidatorHelperResult(error); } else { originalValues[i] = result.getResult(); } } if (hdivConfig.getConfidentiality()) { request.addParameterToRequest(parameter, originalValues); } return ValidatorHelperResult.VALID; } @Deprecated protected final void addParameterToRequest(final HttpServletRequest request, final String name, final String[] value) { throw new UnsupportedOperationException(); } @Deprecated protected final void addEditableParameter(final HttpServletRequest request, final String name) { throw new UnsupportedOperationException(); } /** * <p> * Method invoked before validation. Designed to change the validation logic beyond the base implementation. * </p> * The response of the method can have three meanings: * <ul> * <li>Valid ValidatorHelperResult: The validation has been completed correctly and is not necessary to run the entire validation * process.</li> * <li>Error ValidatorHelperResult: The validation has encountered an error and terminates the validation process.</li> * <li>null: It should continue with the validation process (default answer).</li> * </ul> * * @param context Validation context * @return ValidatorHelperResult result */ protected ValidatorHelperResult preValidate(final ValidationContext context) { return null; } @Deprecated protected final ValidatorHelperResult preValidate(final HttpServletRequest request, final String target) { throw new UnsupportedOperationException(); } /* * (non-Javadoc) * * @see org.hdiv.filter.IValidationHelper#startPage(javax.servlet.http. HttpServletRequest) */ public void startPage(final RequestContextHolder request) { // Don`t create IDataComposer if it is not necessary boolean exclude = hdivConfig.hasExtensionToExclude(request.getRequestURI()); if (!exclude) { // Init datacomposer request.setDataComposer(dataComposerFactory.newInstance(request)); } } /* * (non-Javadoc) * * @see org.hdiv.filter.IValidationHelper#endPage(javax.servlet.http. HttpServletRequest) */ public void endPage(final RequestContextHolder request) { // End page in datacomposer IDataComposer dataComposer = request.getDataComposer(); if (dataComposer != null) { dataComposer.endPage(); if (!request.isAsync()) { // If this is an Async request, don't remove IDataComposer from request. request.setDataComposer(null); } } } /** * @param stateUtil The state utility to set. */ public void setStateUtil(final StateUtil stateUtil) { this.stateUtil = stateUtil; } /** * @param hdivConfig The HDIV configuration object to set. */ public void setHdivConfig(final HDIVConfig hdivConfig) { this.hdivConfig = hdivConfig; } /** * @param session the session to set */ public void setSession(final ISession session) { this.session = session; } /** * @param dataValidator the dataValidator to set */ public void setDataValidator(final IDataValidator dataValidator) { this.dataValidator = dataValidator; } /** * @param numberPattern the numberPattern to set */ public void setNumberPattern(final Pattern numberPattern) { this.numberPattern = numberPattern; } /** * @param dataComposerFactory the dataComposerFactory to set */ public void setDataComposerFactory(final DataComposerFactory dataComposerFactory) { this.dataComposerFactory = dataComposerFactory; } /** * @param urlProcessor the urlProcessor to set */ public void setUrlProcessor(final BasicUrlProcessor urlProcessor) { this.urlProcessor = urlProcessor; } /** * @param stateScopeManager the stateScopeManager to set */ public void setStateScopeManager(final StateScopeManager stateScopeManager) { this.stateScopeManager = stateScopeManager; } public boolean isInternal(final HttpServletRequest request, final HttpServletResponse response) { return false; } }