org.owasp.esapi.reference.DefaultValidator Maven / Gradle / Ivy
Show all versions of esapi Show documentation
/**
* OWASP Enterprise Security API (ESAPI)
*
* This file is part of the Open Web Application Security Project (OWASP)
* Enterprise Security API (ESAPI) project. For details, please see
* http://www.owasp.org/index.php/ESAPI.
*
* Copyright (c) 2007 - The OWASP Foundation
*
* The ESAPI is published by OWASP under the BSD license. You should read and accept the
* LICENSE before you use, modify, and/or redistribute this software.
*
* @author Jeff Williams Aspect Security
* @author Jim Manico ([email protected]) Manico.net
*
* @created 2007
*/
package org.owasp.esapi.reference;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.text.DateFormat;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;
import org.owasp.esapi.ESAPI;
import org.owasp.esapi.Encoder;
import org.owasp.esapi.Logger;
import org.owasp.esapi.SecurityConfiguration;
import org.owasp.esapi.ValidationErrorList;
import org.owasp.esapi.ValidationRule;
import org.owasp.esapi.Validator;
import org.owasp.esapi.errors.IntrusionException;
import org.owasp.esapi.errors.ValidationAvailabilityException;
import org.owasp.esapi.errors.ValidationException;
import org.owasp.esapi.reference.validation.CreditCardValidationRule;
import org.owasp.esapi.reference.validation.DateValidationRule;
import org.owasp.esapi.reference.validation.HTMLValidationRule;
import org.owasp.esapi.reference.validation.IntegerValidationRule;
import org.owasp.esapi.reference.validation.NumberValidationRule;
import org.owasp.esapi.reference.validation.StringValidationRule;
/**
* Reference implementation of the Validator interface. This implementation
* relies on the ESAPI Encoder, Java Pattern (regex), Date,
* and several other classes to provide basic validation functions. This library
* has a heavy emphasis on whitelist validation and canonicalization.
*
* @author Jeff Williams (jeff.williams .at. aspectsecurity.com) Aspect Security
* @author Jim Manico ([email protected]) Manico.net
* @author Matt Seil (mseil .at. acm.org)
*
* @since June 1, 2007
* @see org.owasp.esapi.Validator
*/
public class DefaultValidator implements org.owasp.esapi.Validator {
private static Logger logger = ESAPI.log();
private static volatile Validator instance = null;
public static Validator getInstance() {
if ( instance == null ) {
synchronized ( Validator.class ) {
if ( instance == null ) {
instance = new DefaultValidator();
}
}
}
return instance;
}
/** A map of validation rules */
private Map rules = new HashMap();
/** The encoder to use for canonicalization */
private Encoder encoder = null;
/** The encoder to use for file system */
private static Validator fileValidator = null;
/** Initialize file validator with an appropriate set of codecs */
static {
List list = new ArrayList();
list.add( "HTMLEntityCodec" );
list.add( "PercentCodec" );
Encoder fileEncoder = new DefaultEncoder( list );
fileValidator = new DefaultValidator( fileEncoder );
}
/**
* Default constructor uses the ESAPI standard encoder for canonicalization.
*/
public DefaultValidator() {
this.encoder = ESAPI.encoder();
}
/**
* Construct a new DefaultValidator that will use the specified
* Encoder for canonicalization.
*
* @param encoder
*/
public DefaultValidator( Encoder encoder ) {
this.encoder = encoder;
}
/**
* Add a validation rule to the registry using the "type name" of the rule as the key.
*/
public void addRule( ValidationRule rule ) {
rules.put( rule.getTypeName(), rule );
}
/**
* Get a validation rule from the registry with the "type name" of the rule as the key.
*/
public ValidationRule getRule( String name ) {
return rules.get( name );
}
/**
* Returns true if data received from browser is valid. Double encoding is treated as an attack. The
* default encoder supports html encoding, URL encoding, and javascript escaping. Input is canonicalized
* by default before validation.
*
* @param context A descriptive name for the field to validate. This is used for error facing validation messages and element identification.
* @param input The actual user input data to validate.
* @param type The regular expression name while maps to the actual regular expression from "ESAPI.properties".
* @param maxLength The maximum post-canonicalized String length allowed.
* @param allowNull If allowNull is true then a input that is NULL or an empty string will be legal. If allowNull is false then NULL or an empty String will throw a ValidationException.
* @return The canonicalized user input.
* @throws IntrusionException
*/
public boolean isValidInput(String context, String input, String type, int maxLength, boolean allowNull) throws IntrusionException {
return isValidInput(context, input, type, maxLength, allowNull, true);
}
public boolean isValidInput(String context, String input, String type, int maxLength, boolean allowNull, ValidationErrorList errors) throws IntrusionException {
return isValidInput(context, input, type, maxLength, allowNull, true, errors);
}
public boolean isValidInput(String context, String input, String type, int maxLength, boolean allowNull, boolean canonicalize) throws IntrusionException {
try {
getValidInput( context, input, type, maxLength, allowNull, canonicalize);
return true;
} catch( Exception e ) {
return false;
}
}
public boolean isValidInput(String context, String input, String type, int maxLength, boolean allowNull, boolean canonicalize, ValidationErrorList errors) throws IntrusionException {
try {
getValidInput( context, input, type, maxLength, allowNull, canonicalize);
return true;
} catch( ValidationException e ) {
errors.addError( context, e );
return false;
}
}
/**
* Validates data received from the browser and returns a safe version.
* Double encoding is treated as an attack. The default encoder supports
* html encoding, URL encoding, and javascript escaping. Input is
* canonicalized by default before validation.
*
* @param context A descriptive name for the field to validate. This is used for error facing validation messages and element identification.
* @param input The actual user input data to validate.
* @param type The regular expression name which maps to the actual regular expression from "ESAPI.properties".
* @param maxLength The maximum post-canonicalized String length allowed.
* @param allowNull If allowNull is true then a input that is NULL or an empty string will be legal. If allowNull is false then NULL or an empty String will throw a ValidationException.
* @return The canonicalized user input.
* @throws ValidationException
* @throws IntrusionException
*/
public String getValidInput(String context, String input, String type, int maxLength, boolean allowNull) throws ValidationException {
return getValidInput(context, input, type, maxLength, allowNull, true);
}
/**
* Validates data received from the browser and returns a safe version. Only
* URL encoding is supported. Double encoding is treated as an attack.
*
* @param context A descriptive name for the field to validate. This is used for error facing validation messages and element identification.
* @param input The actual user input data to validate.
* @param type The regular expression name which maps to the actual regular expression in the ESAPI validation configuration file
* @param maxLength The maximum String length allowed. If input is canonicalized per the canonicalize argument, then maxLength must be verified after canonicalization
* @param allowNull If allowNull is true then a input that is NULL or an empty string will be legal. If allowNull is false then NULL or an empty String will throw a ValidationException.
* @param canonicalize If canonicalize is true then input will be canonicalized before validation
* @return The user input, may be canonicalized if canonicalize argument is true
* @throws ValidationException
* @throws IntrusionException
*/
public String getValidInput(String context, String input, String type, int maxLength, boolean allowNull, boolean canonicalize) throws ValidationException {
StringValidationRule rvr = new StringValidationRule( type, encoder );
Pattern p = ESAPI.securityConfiguration().getValidationPattern( type );
if ( p != null ) {
rvr.addWhitelistPattern( p );
} else {
// Issue 232 - Specify requested type in exception message - CS
throw new IllegalArgumentException("The selected type [" + type + "] was not set via the ESAPI validation configuration");
}
rvr.setMaximumLength(maxLength);
rvr.setAllowNull(allowNull);
rvr.setCanonicalize(canonicalize);
return rvr.getValid(context, input);
}
/**
* Validates data received from the browser and returns a safe version. Only
* URL encoding is supported. Double encoding is treated as an attack. Input
* is canonicalized by default before validation.
*
* @param context A descriptive name for the field to validate. This is used for error facing validation messages and element identification.
* @param input The actual user input data to validate.
* @param type The regular expression name while maps to the actual regular expression from "ESAPI.properties".
* @param maxLength The maximum String length allowed. If input is canonicalized per the canonicalize argument, then maxLength must be verified after canonicalization
* @param allowNull If allowNull is true then a input that is NULL or an empty string will be legal. If allowNull is false then NULL or an empty String will throw a ValidationException.
* @param errors If ValidationException is thrown, then add to error list instead of throwing out to caller
* @return The canonicalized user input.
* @throws IntrusionException
*/
public String getValidInput(String context, String input, String type, int maxLength, boolean allowNull, ValidationErrorList errors) throws IntrusionException {
return getValidInput(context, input, type, maxLength, allowNull, true, errors);
}
/**
* Validates data received from the browser and returns a safe version. Only
* URL encoding is supported. Double encoding is treated as an attack.
*
* @param context A descriptive name for the field to validate. This is used for error facing validation messages and element identification.
* @param input The actual user input data to validate.
* @param type The regular expression name while maps to the actual regular expression from "ESAPI.properties".
* @param maxLength The maximum post-canonicalized String length allowed
* @param allowNull If allowNull is true then a input that is NULL or an empty string will be legal. If allowNull is false then NULL or an empty String will throw a ValidationException.
* @param canonicalize If canonicalize is true then input will be canonicalized before validation
* @param errors If ValidationException is thrown, then add to error list instead of throwing out to caller
* @return The user input, may be canonicalized if canonicalize argument is true
* @throws IntrusionException
*/
public String getValidInput(String context, String input, String type, int maxLength, boolean allowNull, boolean canonicalize, ValidationErrorList errors) throws IntrusionException {
try {
return getValidInput(context, input, type, maxLength, allowNull, canonicalize);
} catch (ValidationException e) {
errors.addError(context, e);
}
return "";
}
/**
* {@inheritDoc}
*/
public boolean isValidDate(String context, String input, DateFormat format, boolean allowNull) throws IntrusionException {
try {
getValidDate( context, input, format, allowNull);
return true;
} catch( Exception e ) {
return false;
}
}
/**
* {@inheritDoc}
*/
public boolean isValidDate(String context, String input, DateFormat format, boolean allowNull, ValidationErrorList errors) throws IntrusionException {
getValidDate( context, input, format, allowNull, errors);
return errors.isEmpty();
}
/**
* {@inheritDoc}
*/
public Date getValidDate(String context, String input, DateFormat format, boolean allowNull) throws ValidationException, IntrusionException {
ValidationErrorList vel = new ValidationErrorList();
Date validDate = getValidDate(context, input, format, allowNull, vel);
if (vel.isEmpty()) {
return validDate;
}
throw vel.errors().get(0);
}
/**
* {@inheritDoc}
*/
public Date getValidDate(String context, String input, DateFormat format, boolean allowNull, ValidationErrorList errors) throws IntrusionException {
Date safeDate = null;
DateValidationRule dvr = new DateValidationRule( "SimpleDate", encoder, format);
dvr.setAllowNull(allowNull);
safeDate = dvr.sanitize(context, input, errors);
if (!errors.isEmpty()) {
safeDate = null;
}
// error has been added to list, so return null
return safeDate;
}
/**
* {@inheritDoc}
*/
public boolean isValidSafeHTML(String context, String input, int maxLength, boolean allowNull) throws IntrusionException {
try {
getValidSafeHTML( context, input, maxLength, allowNull);
return true;
} catch( Exception e ) {
return false;
}
}
/**
* {@inheritDoc}
*/
public boolean isValidSafeHTML(String context, String input, int maxLength, boolean allowNull, ValidationErrorList errors) throws IntrusionException {
try {
getValidSafeHTML( context, input, maxLength, allowNull);
return true;
} catch( ValidationException e ) {
errors.addError(context, e);
return false;
}
}
/**
* {@inheritDoc}
*
* This implementation relies on the OWASP AntiSamy project.
*/
public String getValidSafeHTML( String context, String input, int maxLength, boolean allowNull ) throws ValidationException, IntrusionException {
HTMLValidationRule hvr = new HTMLValidationRule( "safehtml", encoder );
hvr.setMaximumLength(maxLength);
hvr.setAllowNull(allowNull);
return hvr.getValid(context, input);
}
/**
* {@inheritDoc}
*/
public String getValidSafeHTML(String context, String input, int maxLength, boolean allowNull, ValidationErrorList errors) throws IntrusionException {
try {
return getValidSafeHTML(context, input, maxLength, allowNull);
} catch (ValidationException e) {
errors.addError(context, e);
}
return "";
}
/**
* {@inheritDoc}
*/
public boolean isValidCreditCard(String context, String input, boolean allowNull) throws IntrusionException {
try {
getValidCreditCard( context, input, allowNull);
return true;
} catch( Exception e ) {
return false;
}
}
/**
* {@inheritDoc}
*/
public boolean isValidCreditCard(String context, String input, boolean allowNull, ValidationErrorList errors) throws IntrusionException {
try {
getValidCreditCard( context, input, allowNull);
return true;
} catch( ValidationException e ) {
errors.addError(context, e);
return false;
}
}
/**
* {@inheritDoc}
*/
public String getValidCreditCard(String context, String input, boolean allowNull) throws ValidationException, IntrusionException {
CreditCardValidationRule ccvr = new CreditCardValidationRule( "creditcard", encoder );
ccvr.setAllowNull(allowNull);
return ccvr.getValid(context, input);
}
/**
* {@inheritDoc}
*/
public String getValidCreditCard(String context, String input, boolean allowNull, ValidationErrorList errors) throws IntrusionException {
try {
return getValidCreditCard(context, input, allowNull);
} catch (ValidationException e) {
errors.addError(context, e);
}
return "";
}
/**
* {@inheritDoc}
*
* Note: On platforms that support symlinks, this function will fail canonicalization if directorypath
* is a symlink. For example, on MacOS X, /etc is actually /private/etc. If you mean to use /etc, use its real
* path (/private/etc), not the symlink (/etc).
*/
public boolean isValidDirectoryPath(String context, String input, File parent, boolean allowNull) throws IntrusionException {
try {
getValidDirectoryPath( context, input, parent, allowNull);
return true;
} catch( Exception e ) {
return false;
}
}
/**
* {@inheritDoc}
*
* Note: On platforms that support symlinks, this function will fail canonicalization if directorypath
* is a symlink. For example, on MacOS X, /etc is actually /private/etc. If you mean to use /etc, use its real
* path (/private/etc), not the symlink (/etc).
*/
public boolean isValidDirectoryPath(String context, String input, File parent, boolean allowNull, ValidationErrorList errors) throws IntrusionException {
try {
getValidDirectoryPath( context, input, parent, allowNull);
return true;
} catch( ValidationException e ) {
errors.addError(context, e);
return false;
}
}
/**
* {@inheritDoc}
*/
public String getValidDirectoryPath(String context, String input, File parent, boolean allowNull) throws ValidationException, IntrusionException {
try {
if (isEmpty(input)) {
if (allowNull) return null;
throw new ValidationException( context + ": Input directory path required", "Input directory path required: context=" + context + ", input=" + input, context );
}
File dir = new File( input );
// check dir exists and parent exists and dir is inside parent
if ( !dir.exists() ) {
throw new ValidationException( context + ": Invalid directory name", "Invalid directory, does not exist: context=" + context + ", input=" + input );
}
if ( !dir.isDirectory() ) {
throw new ValidationException( context + ": Invalid directory name", "Invalid directory, not a directory: context=" + context + ", input=" + input );
}
if ( !parent.exists() ) {
throw new ValidationException( context + ": Invalid directory name", "Invalid directory, specified parent does not exist: context=" + context + ", input=" + input + ", parent=" + parent );
}
if ( !parent.isDirectory() ) {
throw new ValidationException( context + ": Invalid directory name", "Invalid directory, specified parent is not a directory: context=" + context + ", input=" + input + ", parent=" + parent );
}
if ( !dir.getCanonicalFile().toPath().startsWith( parent.getCanonicalFile().toPath() ) ) { // Fixes GHSL-2022-008
throw new ValidationException( context + ": Invalid directory name", "Invalid directory, not inside specified parent: context=" + context + ", input=" + input + ", parent=" + parent );
}
// check canonical form matches input
String canonicalPath = dir.getCanonicalPath();
String canonical = fileValidator.getValidInput( context, canonicalPath, "DirectoryName", 255, false);
if ( !canonical.equals( input ) ) {
throw new ValidationException( context + ": Invalid directory name", "Invalid directory name does not match the canonical path: context=" + context + ", input=" + input + ", canonical=" + canonical, context );
}
return canonical;
} catch (Exception e) {
throw new ValidationException( context + ": Invalid directory name", "Failure to validate directory path: context=" + context + ", input=" + input, e, context );
}
}
/**
* {@inheritDoc}
*/
public String getValidDirectoryPath(String context, String input, File parent, boolean allowNull, ValidationErrorList errors) throws IntrusionException {
try {
return getValidDirectoryPath(context, input, parent, allowNull);
} catch (ValidationException e) {
errors.addError(context, e);
}
return "";
}
/**
* {@inheritDoc}
*/
public boolean isValidFileName(String context, String input, boolean allowNull) throws IntrusionException {
return isValidFileName( context, input, ESAPI.securityConfiguration().getAllowedFileExtensions(), allowNull );
}
/**
* {@inheritDoc}
*/
public boolean isValidFileName(String context, String input, boolean allowNull, ValidationErrorList errors) throws IntrusionException {
return isValidFileName( context, input, ESAPI.securityConfiguration().getAllowedFileExtensions(), allowNull, errors );
}
/**
* {@inheritDoc}
*/
public boolean isValidFileName(String context, String input, List allowedExtensions, boolean allowNull) throws IntrusionException {
try {
getValidFileName( context, input, allowedExtensions, allowNull);
return true;
} catch( Exception e ) {
return false;
}
}
/**
* {@inheritDoc}
*/
public boolean isValidFileName(String context, String input, List allowedExtensions, boolean allowNull, ValidationErrorList errors) throws IntrusionException {
try {
getValidFileName( context, input, allowedExtensions, allowNull);
return true;
} catch( ValidationException e ) {
errors.addError(context, e);
return false;
}
}
/**
* {@inheritDoc}
*/
public String getValidFileName(String context, String input, List allowedExtensions, boolean allowNull) throws ValidationException, IntrusionException {
if ((allowedExtensions == null) || (allowedExtensions.isEmpty())) {
throw new ValidationException( "Internal Error", "getValidFileName called with an empty or null list of allowed Extensions, therefore no files can be uploaded" );
}
String canonical = "";
// detect path manipulation
try {
if (isEmpty(input)) {
if (allowNull) return null;
throw new ValidationException( context + ": Input file name required", "Input required: context=" + context + ", input=" + input, context );
}
// do basic validation
canonical = new File(input).getCanonicalFile().getName();
getValidInput( context, input, "FileName", 255, true );
File f = new File(canonical);
String c = f.getCanonicalPath();
String cpath = c.substring(c.lastIndexOf(File.separator) + 1);
// the path is valid if the input matches the canonical path
if (!input.equals(cpath)) {
throw new ValidationException( context + ": Invalid file name", "Invalid directory name does not match the canonical path: context=" + context + ", input=" + input + ", canonical=" + canonical, context );
}
} catch (IOException e) {
throw new ValidationException( context + ": Invalid file name", "Invalid file name does not exist: context=" + context + ", canonical=" + canonical, e, context );
}
// verify extensions
Iterator i = allowedExtensions.iterator();
while (i.hasNext()) {
String ext = i.next();
if (input.toLowerCase().endsWith(ext.toLowerCase())) {
return canonical;
}
}
throw new ValidationException( context + ": Invalid file name does not have valid extension ( "+allowedExtensions+")", "Invalid file name does not have valid extension ( "+allowedExtensions+"): context=" + context+", input=" + input, context );
}
/**
* {@inheritDoc}
*/
public String getValidFileName(String context, String input, List allowedParameters, boolean allowNull, ValidationErrorList errors) throws IntrusionException {
try {
return getValidFileName(context, input, allowedParameters, allowNull);
} catch (ValidationException e) {
errors.addError(context, e);
}
return "";
}
/**
* {@inheritDoc}
*/
public boolean isValidNumber(String context, String input, long minValue, long maxValue, boolean allowNull) throws IntrusionException {
try {
getValidNumber(context, input, minValue, maxValue, allowNull);
return true;
} catch( Exception e ) {
return false;
}
}
/**
* {@inheritDoc}
*/
public boolean isValidNumber(String context, String input, long minValue, long maxValue, boolean allowNull, ValidationErrorList errors) throws IntrusionException {
try {
getValidNumber(context, input, minValue, maxValue, allowNull);
return true;
} catch( ValidationException e ) {
errors.addError(context, e);
return false;
}
}
/**
* {@inheritDoc}
*/
public Double getValidNumber(String context, String input, long minValue, long maxValue, boolean allowNull) throws ValidationException, IntrusionException {
Double minDoubleValue = new Double(minValue);
Double maxDoubleValue = new Double(maxValue);
return getValidDouble(context, input, minDoubleValue.doubleValue(), maxDoubleValue.doubleValue(), allowNull);
}
/**
* {@inheritDoc}
*/
public Double getValidNumber(String context, String input, long minValue, long maxValue, boolean allowNull, ValidationErrorList errors) throws IntrusionException {
try {
return getValidNumber(context, input, minValue, maxValue, allowNull);
} catch (ValidationException e) {
errors.addError(context, e);
}
return null;
}
/**
* {@inheritDoc}
*/
public boolean isValidDouble(String context, String input, double minValue, double maxValue, boolean allowNull) throws IntrusionException {
try {
getValidDouble( context, input, minValue, maxValue, allowNull );
return true;
} catch( Exception e ) {
return false;
}
}
/**
* {@inheritDoc}
*/
public boolean isValidDouble(String context, String input, double minValue, double maxValue, boolean allowNull, ValidationErrorList errors) throws IntrusionException {
try {
getValidDouble( context, input, minValue, maxValue, allowNull );
return true;
} catch( ValidationException e ) {
errors.addError(context, e);
return false;
}
}
/**
* {@inheritDoc}
*/
public Double getValidDouble(String context, String input, double minValue, double maxValue, boolean allowNull) throws ValidationException, IntrusionException {
NumberValidationRule nvr = new NumberValidationRule( "number", encoder, minValue, maxValue );
nvr.setAllowNull(allowNull);
return nvr.getValid(context, input);
}
/**
* {@inheritDoc}
*/
public Double getValidDouble(String context, String input, double minValue, double maxValue, boolean allowNull, ValidationErrorList errors) throws IntrusionException {
try {
return getValidDouble(context, input, minValue, maxValue, allowNull);
} catch (ValidationException e) {
errors.addError(context, e);
}
return new Double(Double.NaN);
}
/**
* {@inheritDoc}
*/
public boolean isValidInteger(String context, String input, int minValue, int maxValue, boolean allowNull) throws IntrusionException {
try {
getValidInteger( context, input, minValue, maxValue, allowNull);
return true;
} catch( ValidationException e ) {
return false;
}
}
/**
* {@inheritDoc}
*/
public boolean isValidInteger(String context, String input, int minValue, int maxValue, boolean allowNull, ValidationErrorList errors) throws IntrusionException {
try {
getValidInteger( context, input, minValue, maxValue, allowNull);
return true;
} catch( ValidationException e ) {
errors.addError(context, e);
return false;
}
}
/**
* {@inheritDoc}
*/
public Integer getValidInteger(String context, String input, int minValue, int maxValue, boolean allowNull) throws ValidationException, IntrusionException {
IntegerValidationRule ivr = new IntegerValidationRule( "number", encoder, minValue, maxValue );
ivr.setAllowNull(allowNull);
return ivr.getValid(context, input);
}
/**
* {@inheritDoc}
*/
public Integer getValidInteger(String context, String input, int minValue, int maxValue, boolean allowNull, ValidationErrorList errors) throws IntrusionException {
try {
return getValidInteger(context, input, minValue, maxValue, allowNull);
} catch (ValidationException e) {
errors.addError(context, e);
}
// error has been added to list, so return original input
return null;
}
/**
* {@inheritDoc}
*/
public boolean isValidFileContent(String context, byte[] input, int maxBytes, boolean allowNull) throws IntrusionException {
try {
getValidFileContent( context, input, maxBytes, allowNull);
return true;
} catch( Exception e ) {
return false;
}
}
/**
* {@inheritDoc}
*/
public boolean isValidFileContent(String context, byte[] input, int maxBytes, boolean allowNull, ValidationErrorList errors) throws IntrusionException {
try {
getValidFileContent( context, input, maxBytes, allowNull);
return true;
} catch( ValidationException e ) {
errors.addError(context, e);
return false;
}
}
/**
* {@inheritDoc}
*/
public byte[] getValidFileContent(String context, byte[] input, int maxBytes, boolean allowNull) throws ValidationException, IntrusionException {
if (isEmpty(input)) {
if (allowNull) return null;
throw new ValidationException( context + ": Input required", "Input required: context=" + context + ", input=" + Arrays.toString(input), context );
}
long esapiMaxBytes = ESAPI.securityConfiguration().getAllowedFileUploadSize();
if (input.length > esapiMaxBytes ) throw new ValidationException( context + ": Invalid file content can not exceed " + esapiMaxBytes + " bytes", "Exceeded ESAPI max length", context );
if (input.length > maxBytes ) throw new ValidationException( context + ": Invalid file content can not exceed " + maxBytes + " bytes", "Exceeded maxBytes ( " + input.length + ")", context );
return input;
}
/**
* {@inheritDoc}
*/
public byte[] getValidFileContent(String context, byte[] input, int maxBytes, boolean allowNull, ValidationErrorList errors) throws IntrusionException {
try {
return getValidFileContent(context, input, maxBytes, allowNull);
} catch (ValidationException e) {
errors.addError(context, e);
}
// return empty byte array on error
return new byte[0];
}
/**
* {@inheritDoc}
*
* Note: On platforms that support symlinks, this function will fail canonicalization if directorypath
* is a symlink. For example, on MacOS X, /etc is actually /private/etc. If you mean to use /etc, use its real
* path (/private/etc), not the symlink (/etc).
*/
public boolean isValidFileUpload(String context, String directorypath, String filename, File parent, byte[] content, int maxBytes, boolean allowNull) throws IntrusionException {
return( isValidFileName( context, filename, allowNull ) &&
isValidDirectoryPath( context, directorypath, parent, allowNull ) &&
isValidFileContent( context, content, maxBytes, allowNull ) );
}
/**
* {@inheritDoc}
*
* Note: On platforms that support symlinks, this function will fail canonicalization if directorypath
* is a symlink. For example, on MacOS X, /etc is actually /private/etc. If you mean to use /etc, use its real
* path (/private/etc), not the symlink (/etc).
*/
public boolean isValidFileUpload(String context, String directorypath, String filename, File parent, byte[] content, int maxBytes, boolean allowNull, ValidationErrorList errors) throws IntrusionException {
return( isValidFileName( context, filename, allowNull, errors ) &&
isValidDirectoryPath( context, directorypath, parent, allowNull, errors ) &&
isValidFileContent( context, content, maxBytes, allowNull, errors ) );
}
/**
* {@inheritDoc}
*/
public void assertValidFileUpload(String context, String directorypath, String filename, File parent, byte[] content, int maxBytes, List allowedExtensions, boolean allowNull) throws ValidationException, IntrusionException {
getValidFileName( context, filename, allowedExtensions, allowNull );
getValidDirectoryPath( context, directorypath, parent, allowNull );
getValidFileContent( context, content, maxBytes, allowNull );
}
/**
* {@inheritDoc}
*/
public void assertValidFileUpload(String context, String filepath, String filename, File parent, byte[] content, int maxBytes, List allowedExtensions, boolean allowNull, ValidationErrorList errors)
throws IntrusionException {
try {
assertValidFileUpload(context, filepath, filename, parent, content, maxBytes, allowedExtensions, allowNull);
} catch (ValidationException e) {
errors.addError(context, e);
}
}
/**
* {@inheritDoc}
*
* Returns true if input is a valid list item.
*/
public boolean isValidListItem(String context, String input, List list) {
try {
getValidListItem( context, input, list);
return true;
} catch( Exception e ) {
return false;
}
}
/**
* {@inheritDoc}
*
* Returns true if input is a valid list item.
*/
public boolean isValidListItem(String context, String input, List list, ValidationErrorList errors) {
try {
getValidListItem( context, input, list);
return true;
} catch( ValidationException e ) {
errors.addError(context, e);
return false;
}
}
/**
* Returns the list item that exactly matches the canonicalized input. Invalid or non-matching input
* will generate a descriptive ValidationException, and input that is clearly an attack
* will generate a descriptive IntrusionException.
*/
public String getValidListItem(String context, String input, List list) throws ValidationException, IntrusionException {
if (list.contains(input)) return input;
throw new ValidationException( context + ": Invalid list item", "Invalid list item: context=" + context + ", input=" + input, context );
}
/**
* ValidationErrorList variant of getValidListItem
*
* @param errors
*/
public String getValidListItem(String context, String input, List list, ValidationErrorList errors) throws IntrusionException {
try {
return getValidListItem(context, input, list);
} catch (ValidationException e) {
errors.addError(context, e);
}
// error has been added to list, so return original input
return input;
}
/**
* {@inheritDoc}
*/
public boolean isValidHTTPRequestParameterSet(String context, HttpServletRequest request, Set requiredNames, Set optionalNames) {
try {
assertValidHTTPRequestParameterSet( context, request, requiredNames, optionalNames);
return true;
} catch( Exception e ) {
return false;
}
}
/**
* {@inheritDoc}
*/
public boolean isValidHTTPRequestParameterSet(String context, HttpServletRequest request, Set requiredNames, Set optionalNames, ValidationErrorList errors) {
try {
assertValidHTTPRequestParameterSet( context, request, requiredNames, optionalNames);
return true;
} catch( ValidationException e ) {
errors.addError(context, e);
return false;
}
}
/**
* Validates that the parameters in the current request contain all required parameters and only optional ones in
* addition. Invalid input will generate a descriptive ValidationException, and input that is clearly an attack
* will generate a descriptive IntrusionException.
*
* Uses current HTTPRequest
*/
public void assertValidHTTPRequestParameterSet(String context, HttpServletRequest request, Set required, Set optional) throws ValidationException, IntrusionException {
Set actualNames = request.getParameterMap().keySet();
// verify ALL required parameters are present
Set missing = new HashSet(required);
missing.removeAll(actualNames);
if (missing.size() > 0) {
throw new ValidationException( context + ": Invalid HTTP request missing parameters", "Invalid HTTP request missing parameters " + missing + ": context=" + context, context );
}
// verify ONLY optional + required parameters are present
Set extra = new HashSet(actualNames);
extra.removeAll(required);
extra.removeAll(optional);
if (extra.size() > 0) {
throw new ValidationException( context + ": Invalid HTTP request extra parameters " + extra, "Invalid HTTP request extra parameters " + extra + ": context=" + context, context );
}
}
/**
* ValidationErrorList variant of assertIsValidHTTPRequestParameterSet
*
* Uses current HTTPRequest saved in ESAPI Authenticator
* @param errors
*/
public void assertValidHTTPRequestParameterSet(String context, HttpServletRequest request, Set required, Set optional, ValidationErrorList errors) throws IntrusionException {
try {
assertValidHTTPRequestParameterSet(context, request, required, optional);
} catch (ValidationException e) {
errors.addError(context, e);
}
}
/**
* {@inheritDoc}
*
* Checks that all bytes are valid ASCII characters (between 33 and 126
* inclusive). This implementation does no decoding. http://en.wikipedia.org/wiki/ASCII.
*/
public boolean isValidPrintable(String context, char[] input, int maxLength, boolean allowNull) throws IntrusionException {
try {
getValidPrintable( context, input, maxLength, allowNull);
return true;
} catch( Exception e ) {
return false;
}
}
/**
* {@inheritDoc}
*
* Checks that all bytes are valid ASCII characters (between 33 and 126
* inclusive). This implementation does no decoding. http://en.wikipedia.org/wiki/ASCII.
*/
public boolean isValidPrintable(String context, char[] input, int maxLength, boolean allowNull, ValidationErrorList errors) throws IntrusionException {
try {
getValidPrintable( context, input, maxLength, allowNull);
return true;
} catch( ValidationException e ) {
errors.addError(context, e);
return false;
}
}
/**
* Returns canonicalized and validated printable characters as a byte array. Invalid input will generate a descriptive ValidationException, and input that is clearly an attack
* will generate a descriptive IntrusionException.
*
* @throws IntrusionException
*/
public char[] getValidPrintable(String context, char[] input, int maxLength, boolean allowNull) throws ValidationException, IntrusionException {
if (isEmpty(input)) {
if (allowNull) return null;
throw new ValidationException(context + ": Input bytes required", "Input bytes required: HTTP request is null", context );
}
if (input.length > maxLength) {
throw new ValidationException(context + ": Input bytes can not exceed " + maxLength + " bytes", "Input exceeds maximum allowed length of " + maxLength + " by " + (input.length-maxLength) + " bytes: context=" + context + ", input=" + new String( input ), context);
}
for (int i = 0; i < input.length; i++) {
if (input[i] <= 0x20 || input[i] >= 0x7E ) {
throw new ValidationException(context + ": Invalid input bytes: context=" + context, "Invalid non-ASCII input bytes, context=" + context + ", input=" + new String( input ), context);
}
}
return input;
}
/**
* ValidationErrorList variant of getValidPrintable
*
* @param errors
*/
public char[] getValidPrintable(String context, char[] input,int maxLength, boolean allowNull, ValidationErrorList errors)
throws IntrusionException {
try {
return getValidPrintable(context, input, maxLength, allowNull);
} catch (ValidationException e) {
errors.addError(context, e);
}
// error has been added to list, so return original input
return input;
}
/**
* {@inheritDoc}
*
* Returns true if input is valid printable ASCII characters (32-126).
*/
public boolean isValidPrintable(String context, String input, int maxLength, boolean allowNull) throws IntrusionException {
try {
getValidPrintable( context, input, maxLength, allowNull);
return true;
} catch( Exception e ) {
return false;
}
}
/**
* {@inheritDoc}
*
* Returns true if input is valid printable ASCII characters (32-126).
*/
public boolean isValidPrintable(String context, String input, int maxLength, boolean allowNull, ValidationErrorList errors) throws IntrusionException {
try {
getValidPrintable( context, input, maxLength, allowNull);
return true;
} catch( ValidationException e ) {
errors.addError(context, e);
return false;
}
}
/**
* Returns canonicalized and validated printable characters as a String. Invalid input will generate a descriptive ValidationException, and input that is clearly an attack
* will generate a descriptive IntrusionException.
*
* @throws IntrusionException
*/
public String getValidPrintable(String context, String input, int maxLength, boolean allowNull) throws ValidationException, IntrusionException {
try {
String canonical = encoder.canonicalize(input);
return new String( getValidPrintable( context, canonical.toCharArray(), maxLength, allowNull) );
//TODO - changed this to base Exception since we no longer need EncodingException
//TODO - this is a bit lame: we need to re-think this function.
} catch (Exception e) {
throw new ValidationException( context + ": Invalid printable input", "Invalid encoding of printable input, context=" + context + ", input=" + input, e, context);
}
}
/**
* ValidationErrorList variant of getValidPrintable
*
* @param errors
*/
public String getValidPrintable(String context, String input,int maxLength, boolean allowNull, ValidationErrorList errors) throws IntrusionException {
try {
return getValidPrintable(context, input, maxLength, allowNull);
} catch (ValidationException e) {
errors.addError(context, e);
}
// error has been added to list, so return original input
return input;
}
/**
* Returns true if input is a valid redirect location.
*/
public boolean isValidRedirectLocation(String context, String input, boolean allowNull) throws IntrusionException {
SecurityConfiguration sc = ESAPI.securityConfiguration();
return ESAPI.validator().isValidInput( context, input, "Redirect", sc.getIntProp("HttpUtilities.maxRedirectLength"), allowNull);
}
/**
* Returns true if input is a valid redirect location.
*/
public boolean isValidRedirectLocation(String context, String input, boolean allowNull, ValidationErrorList errors) throws IntrusionException {
SecurityConfiguration sc = ESAPI.securityConfiguration();
return ESAPI.validator().isValidInput( context, input, "Redirect", sc.getIntProp("HttpUtilities.maxRedirectLength"), allowNull, errors);
}
/**
* Returns a canonicalized and validated redirect location as a String. Invalid input will generate a descriptive ValidationException, and input that is clearly an attack
* will generate a descriptive IntrusionException.
*/
public String getValidRedirectLocation(String context, String input, boolean allowNull) throws ValidationException, IntrusionException {
SecurityConfiguration sc = ESAPI.securityConfiguration();
return ESAPI.validator().getValidInput( context, input, "Redirect", sc.getIntProp("HttpUtilities.maxRedirectLength"), allowNull);
}
/**
* ValidationErrorList variant of getValidRedirectLocation
*
* @param errors
*/
public String getValidRedirectLocation(String context, String input, boolean allowNull, ValidationErrorList errors) throws IntrusionException {
try {
return getValidRedirectLocation(context, input, allowNull);
} catch (ValidationException e) {
errors.addError(context, e);
}
// error has been added to list, so return original input
return input;
}
/**
* {@inheritDoc}
*
* This implementation reads until a newline or the specified number of
* characters.
*
* @param in
* @param max
*/
public String safeReadLine(InputStream in, int max) throws ValidationException {
if (max <= 0) {
throw new ValidationAvailabilityException( "Invalid input", "Invalid readline. Must read a positive number of bytes from the stream");
}
StringBuilder sb = new StringBuilder();
int count = 0;
int c;
try {
while (true) {
c = in.read();
if ( c == -1 ) {
if (sb.length() == 0) {
return null;
}
break;
}
if (c == '\n' || c == '\r') {
break;
}
count++;
if (count > max) {
throw new ValidationAvailabilityException( "Invalid input", "Invalid readLine. Read more than maximum characters allowed (" + max + ")");
}
sb.append((char) c);
}
return sb.toString();
} catch (IOException e) {
throw new ValidationAvailabilityException( "Invalid input", "Invalid readLine. Problem reading from input stream", e);
}
}
/**
* Helper function to check if a String is empty
*
* @param input string input value
* @return boolean response if input is empty or not
*/
private final boolean isEmpty(String input) {
return (input==null || input.trim().length() == 0);
}
/**
* Helper function to check if a byte array is empty
*
* @param input string input value
* @return boolean response if input is empty or not
*/
private final boolean isEmpty(byte[] input) {
return (input==null || input.length == 0);
}
/**
* Helper function to check if a char array is empty
*
* @param input string input value
* @return boolean response if input is empty or not
*/
private final boolean isEmpty(char[] input) {
return (input==null || input.length == 0);
}
/**
* {@inheritDoc}
*/
public boolean isValidURI(String context, String input, boolean allowNull) {
boolean isValid = false;
boolean inputIsNullOrEmpty = input == null || "".equals(input);
Encoder encoder = ESAPI.encoder();
try{
URI compliantURI = null == input ? new URI("") : this.getRfcCompliantURI(input);
if(null != compliantURI && input != null){
String canonicalizedURI = encoder.getCanonicalizedURI(compliantURI);
//if getCanonicalizedURI doesn't throw an IntrusionException, then the URI contains no mixed or
//double-encoding attacks.
logger.debug(Logger.SECURITY_SUCCESS, "We did not detect any mixed or multiple encoding in the uri:[" + input + "]");
Validator v = ESAPI.validator();
//This part will use the regex from validation.properties. This regex should be super-simple, and
//used mainly to restrict certain parts of a URL.
Pattern p = ESAPI.securityConfiguration().getValidationPattern( "URL" );
if(p != null){
//We're doing this instead of using the normal validator API, because it will canonicalize the input again
//and if the URI has any queries that also happen to match HTML entities, like ¶
//it will cease conforming to the regex we now specify for a URL.
isValid = p.matcher(canonicalizedURI).matches();
}else{
logger.error(Logger.EVENT_FAILURE, "Invalid regex pulled from configuration. Check the regex for URL and correct.");
}
}else{
if(allowNull && inputIsNullOrEmpty ){
isValid = true;
}
}
}catch (IntrusionException e){
logger.error(Logger.SECURITY_FAILURE, e.getMessage());
isValid = false;
} catch (URISyntaxException e) {
logger.error(Logger.EVENT_FAILURE, e.getMessage());
}
return isValid;
}
/**
* {@inheritDoc}
*/
public URI getRfcCompliantURI(String input){
URI rval = null;
try {
rval = new URI(input);
} catch (URISyntaxException e) {
logger.error(Logger.EVENT_FAILURE, e.getMessage());
}
return rval;
}
}