/**
 * 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
 * <a href="http://www.owasp.org/index.php/ESAPI">http://www.owasp.org/index.php/ESAPI</a>.
 *
 * 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 <a href="http://www.aspectsecurity.com">Aspect Security</a>
 * @created 2007
 */
package org.owasp.esapi.reference;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;

import org.apache.commons.lang.text.StrTokenizer;
import org.owasp.esapi.ESAPI;
import org.owasp.esapi.Logger;
import org.owasp.esapi.SecurityConfiguration;
import org.owasp.esapi.configuration.EsapiPropertyManager;
import org.owasp.esapi.errors.ConfigurationException;

/**
 * The reference {@code SecurityConfiguration} manages all the settings used by the ESAPI in a single place. In this reference
 * implementation, resources can be put in several locations, which are searched in the following order:
 * <p>
 * 1) Inside a directory set with a call to SecurityConfiguration.setResourceDirectory( "C:\temp\resources" ).
 * <p>
 * 2) Inside the System.getProperty( "org.owasp.esapi.resources" ) directory.
 * You can set this on the java command line as follows (for example):
 * <pre>
 * 		java -Dorg.owasp.esapi.resources="C:\temp\resources"
 * </pre>
 * You may have to add this to the start-up script that starts your web server. For example, for Tomcat,
 * in the "catalina" script that starts Tomcat, you can set the JAVA_OPTS variable to the {@code -D} string above.
 * <p>
 * 3) Inside the {@code System.getProperty( "user.home" ) + "/.esapi"} directory (supported for backward compatibility) or
 * inside the {@code System.getProperty( "user.home" ) + "/esapi"} directory.
 * <p>
 * 4) The first ".esapi" or "esapi" directory on the classpath. (The former for backward compatibility.)
 * <p>
 * Once the Configuration is initialized with a resource directory, you can edit it to set things like master
 * keys and passwords, logging locations, error thresholds, and allowed file extensions.
 * <p>
 * WARNING: Do not forget to update ESAPI.properties to change the master key and other security critical settings.
 *
 * @author Jeff Williams (jeff.williams .at. aspectsecurity.com) <a href="http://www.aspectsecurity.com">Aspect Security</a>
 * @author Jim Manico (jim .at. manico.net) <a href="http://www.manico.net">Manico.net</a>
 * @author Kevin Wall (kevin.w.wall .at. gmail.com)
 */

public class DefaultSecurityConfiguration implements SecurityConfiguration {
    private static volatile SecurityConfiguration instance = null;

    public static SecurityConfiguration getInstance() {
        if ( instance == null ) {
            synchronized (DefaultSecurityConfiguration.class) {
                if ( instance == null ) {
                    instance = new DefaultSecurityConfiguration();
                }
            }
        }
        return instance;
    }
    
    private Properties properties = null;
    private String cipherXformFromESAPIProp = null;	// New in ESAPI 2.0
    private String cipherXformCurrent = null;		// New in ESAPI 2.0

	/** The name of the ESAPI property file */
	public static final String DEFAULT_RESOURCE_FILE = "ESAPI.properties";
	
    public static final String REMEMBER_TOKEN_DURATION = "Authenticator.RememberTokenDuration";
    public static final String IDLE_TIMEOUT_DURATION = "Authenticator.IdleTimeoutDuration";
    public static final String ABSOLUTE_TIMEOUT_DURATION = "Authenticator.AbsoluteTimeoutDuration";
    public static final String ALLOWED_LOGIN_ATTEMPTS = "Authenticator.AllowedLoginAttempts";
    public static final String USERNAME_PARAMETER_NAME = "Authenticator.UsernameParameterName";
    public static final String PASSWORD_PARAMETER_NAME = "Authenticator.PasswordParameterName";
    public static final String MAX_OLD_PASSWORD_HASHES = "Authenticator.MaxOldPasswordHashes";

    public static final String ALLOW_MULTIPLE_ENCODING = "Encoder.AllowMultipleEncoding";
    public static final String ALLOW_MIXED_ENCODING	= "Encoder.AllowMixedEncoding";
    public static final String CANONICALIZATION_CODECS = "Encoder.DefaultCodecList";

    public static final String DISABLE_INTRUSION_DETECTION  = "IntrusionDetector.Disable";
    
    public static final String MASTER_KEY = "Encryptor.MasterKey";
    public static final String MASTER_SALT = "Encryptor.MasterSalt";
    public static final String KEY_LENGTH = "Encryptor.EncryptionKeyLength";
    public static final String ENCRYPTION_ALGORITHM = "Encryptor.EncryptionAlgorithm";
    public static final String HASH_ALGORITHM = "Encryptor.HashAlgorithm";
    public static final String HASH_ITERATIONS = "Encryptor.HashIterations";
    public static final String CHARACTER_ENCODING = "Encryptor.CharacterEncoding";
    public static final String RANDOM_ALGORITHM = "Encryptor.RandomAlgorithm";
    public static final String DIGITAL_SIGNATURE_ALGORITHM = "Encryptor.DigitalSignatureAlgorithm";
    public static final String DIGITAL_SIGNATURE_KEY_LENGTH = "Encryptor.DigitalSignatureKeyLength";
    			// ==================================//
    			//		New in ESAPI Java 2.x		 //
    			// ================================= //
    public static final String PREFERRED_JCE_PROVIDER = "Encryptor.PreferredJCEProvider";
    public static final String CIPHER_TRANSFORMATION_IMPLEMENTATION = "Encryptor.CipherTransformation";
    public static final String CIPHERTEXT_USE_MAC = "Encryptor.CipherText.useMAC";
    public static final String PLAINTEXT_OVERWRITE = "Encryptor.PlainText.overwrite";
    public static final String IV_TYPE = "Encryptor.ChooseIVMethod";

    @Deprecated
    public static final String FIXED_IV = "Encryptor.fixedIV";

    public static final String COMBINED_CIPHER_MODES = "Encryptor.cipher_modes.combined_modes";
    public static final String ADDITIONAL_ALLOWED_CIPHER_MODES = "Encryptor.cipher_modes.additional_allowed";
    public static final String KDF_PRF_ALG = "Encryptor.KDF.PRF";
	public static final String PRINT_PROPERTIES_WHEN_LOADED = "ESAPI.printProperties";

    public static final String WORKING_DIRECTORY = "Executor.WorkingDirectory";
    public static final String APPROVED_EXECUTABLES = "Executor.ApprovedExecutables";

    public static final String FORCE_HTTPONLYSESSION = "HttpUtilities.ForceHttpOnlySession";
    public static final String FORCE_SECURESESSION = "HttpUtilities.SecureSession";
    public static final String FORCE_HTTPONLYCOOKIES = "HttpUtilities.ForceHttpOnlyCookies";
    public static final String FORCE_SECURECOOKIES = "HttpUtilities.ForceSecureCookies";
	public static final String MAX_HTTP_HEADER_SIZE = "HttpUtilities.MaxHeaderSize";
    public static final String UPLOAD_DIRECTORY = "HttpUtilities.UploadDir";
    public static final String UPLOAD_TEMP_DIRECTORY = "HttpUtilities.UploadTempDir";
    public static final String APPROVED_UPLOAD_EXTENSIONS = "HttpUtilities.ApprovedUploadExtensions";
    public static final String MAX_UPLOAD_FILE_BYTES = "HttpUtilities.MaxUploadFileBytes";
    public static final String RESPONSE_CONTENT_TYPE = "HttpUtilities.ResponseContentType";
    public static final String HTTP_SESSION_ID_NAME = "HttpUtilities.HttpSessionIdName";

    public static final String APPLICATION_NAME = "Logger.ApplicationName";
    public static final String LOG_ENCODING_REQUIRED = "Logger.LogEncodingRequired";
    public static final String LOG_APPLICATION_NAME = "Logger.LogApplicationName";
    public static final String LOG_SERVER_IP = "Logger.LogServerIP";
    public static final String LOG_USER_INFO = "Logger.UserInfo";
    public static final String LOG_CLIENT_INFO = "Logger.ClientInfo";
    public static final String VALIDATION_PROPERTIES = "Validator.ConfigurationFile";
    public static final String VALIDATION_PROPERTIES_MULTIVALUED = "Validator.ConfigurationFile.MultiValued";
    public static final String ACCEPT_LENIENT_DATES = "Validator.AcceptLenientDates";
    public static final String VALIDATOR_HTML_VALIDATION_ACTION = "Validator.HtmlValidationAction";
	public static final String VALIDATOR_HTML_VALIDATION_CONFIGURATION_FILE = "Validator.HtmlValidationConfigurationFile";

    /**
     * Special {@code System} property that, if set to {@code true}, will
     * disable logging from {@code DefaultSecurityConfiguration.logToStdout()}
     * methods, which is called from various {@code logSpecial()} methods.
     * @see org.owasp.esapi.reference.DefaultSecurityConfiguration#logToStdout(String msg, Throwable t)
     */
    public static final String DISCARD_LOGSPECIAL = "org.owasp.esapi.logSpecial.discard";

    // We assume that this does not change in the middle of processing the
    // ESAPI.properties files and thus only fetch its value once.
    private static final String logSpecialValue = System.getProperty(DISCARD_LOGSPECIAL, "false");


    protected final int MAX_REDIRECT_LOCATION = 1000;
    
    /*
     * Implementation Keys
     */
    public static final String LOG_IMPLEMENTATION = "ESAPI.Logger";
    public static final String AUTHENTICATION_IMPLEMENTATION = "ESAPI.Authenticator";
    public static final String ENCODER_IMPLEMENTATION = "ESAPI.Encoder";
    public static final String ACCESS_CONTROL_IMPLEMENTATION = "ESAPI.AccessControl";
    public static final String ENCRYPTION_IMPLEMENTATION = "ESAPI.Encryptor";
    public static final String INTRUSION_DETECTION_IMPLEMENTATION = "ESAPI.IntrusionDetector";
    public static final String RANDOMIZER_IMPLEMENTATION = "ESAPI.Randomizer";
	public static final String EXECUTOR_IMPLEMENTATION = "ESAPI.Executor";
	public static final String VALIDATOR_IMPLEMENTATION = "ESAPI.Validator";
	public static final String HTTP_UTILITIES_IMPLEMENTATION = "ESAPI.HTTPUtilities";

    /*
     * Default Implementations
     */
    public static final String DEFAULT_LOG_IMPLEMENTATION = "org.owasp.esapi.logging.java.JavaLogFactory";
    public static final String DEFAULT_AUTHENTICATION_IMPLEMENTATION = "org.owasp.esapi.reference.FileBasedAuthenticator";
    public static final String DEFAULT_ENCODER_IMPLEMENTATION = "org.owasp.esapi.reference.DefaultEncoder";
    public static final String DEFAULT_ACCESS_CONTROL_IMPLEMENTATION = "org.owasp.esapi.reference.DefaultAccessController";
    public static final String DEFAULT_ENCRYPTION_IMPLEMENTATION = "org.owasp.esapi.reference.crypto.JavaEncryptor";
    public static final String DEFAULT_INTRUSION_DETECTION_IMPLEMENTATION = "org.owasp.esapi.reference.DefaultIntrusionDetector";
    public static final String DEFAULT_RANDOMIZER_IMPLEMENTATION = "org.owasp.esapi.reference.DefaultRandomizer";
    public static final String DEFAULT_EXECUTOR_IMPLEMENTATION = "org.owasp.esapi.reference.DefaultExecutor";
    public static final String DEFAULT_HTTP_UTILITIES_IMPLEMENTATION = "org.owasp.esapi.reference.DefaultHTTPUtilities";
    public static final String DEFAULT_VALIDATOR_IMPLEMENTATION = "org.owasp.esapi.reference.DefaultValidator";

    private static final Map<String, Pattern> patternCache = new HashMap<String, Pattern>();

    /*
     * Absolute path to the user.home. No longer includes the ESAPI portion as it used to.
     */
    private static final String userHome = System.getProperty("user.home" );
    /*
     * Absolute path to the customDirectory
     */	// DISCUSS: Implicit assumption here that there is no SecurityManager installed enforcing the
        //			prevention of reading system properties. Otherwise this will fail with SecurityException.
    private static String customDirectory = System.getProperty("org.owasp.esapi.resources");
    /*
     * Relative path to the resourceDirectory. Relative to the classpath.
     * Specifically, ClassLoader.getResource(resourceDirectory + filename) will
     * be used to load the file.
     */
    private String resourceDirectory = ".esapi";	// For backward compatibility (vs. "esapi")
	private final String resourceFile;
    private EsapiPropertyManager esapiPropertyManager;

//    private static long lastModified = -1;

    /**
     * Instantiates a new configuration, using the provided property file name
     * 
     * @param resourceFile The name of the property file to load
     */
    DefaultSecurityConfiguration(String resourceFile) {
    	this.resourceFile = resourceFile;
    	// load security configuration
    	try {
            this.esapiPropertyManager = new EsapiPropertyManager();
        	loadConfiguration();
        	this.setCipherXProperties();
        } catch( IOException e ) {
	        logSpecial("Failed to load security configuration", e );
	        throw new ConfigurationException("Failed to load security configuration", e);
        }
    }
    
    /**
     * Instantiates a new configuration with the supplied properties.
     * 
     * Warning - if the setResourceDirectory() method is invoked the properties will
     * be re-loaded, replacing the supplied properties.
     * 
     * @param properties
     */
    public DefaultSecurityConfiguration(Properties properties) {
    	resourceFile = DEFAULT_RESOURCE_FILE;
    	this.properties = properties; 
    	this.setCipherXProperties();
    }
    
    /**
     * Instantiates a new configuration.
     */
    public DefaultSecurityConfiguration(){
    	this(DEFAULT_RESOURCE_FILE);
    }
    private void setCipherXProperties() {
		// TODO: FUTURE: Replace by future CryptoControls class???
		// See SecurityConfiguration.setCipherTransformation() for
		// explanation of this.
        // (Propose this in 2.1 via future email to ESAPI-DEV list.)
		cipherXformFromESAPIProp =
			getESAPIProperty(CIPHER_TRANSFORMATION_IMPLEMENTATION,
							 "AES/CBC/PKCS5Padding");
		cipherXformCurrent = cipherXformFromESAPIProp;
    }

    /**
	 * {@inheritDoc}
	 */
    public String getApplicationName() {
    	return getESAPIProperty(APPLICATION_NAME, "DefaultName");
    }

    /**
	 * {@inheritDoc}
	 */
    public String getLogImplementation() {
    	return getESAPIProperty(LOG_IMPLEMENTATION, DEFAULT_LOG_IMPLEMENTATION);
    }

    /**
	 * {@inheritDoc}
	 */
    public String getAuthenticationImplementation() {
    	return getESAPIProperty(AUTHENTICATION_IMPLEMENTATION, DEFAULT_AUTHENTICATION_IMPLEMENTATION);
    }

    /**
	 * {@inheritDoc}
	 */
    public String getEncoderImplementation() {
    	return getESAPIProperty(ENCODER_IMPLEMENTATION, DEFAULT_ENCODER_IMPLEMENTATION);
    }

    /**
	 * {@inheritDoc}
	 */
    public String getAccessControlImplementation() {
    	return getESAPIProperty(ACCESS_CONTROL_IMPLEMENTATION, DEFAULT_ACCESS_CONTROL_IMPLEMENTATION);
    }

    /**
	 * {@inheritDoc}
	 */
    public String getEncryptionImplementation() {
    	return getESAPIProperty(ENCRYPTION_IMPLEMENTATION, DEFAULT_ENCRYPTION_IMPLEMENTATION);
    }

    /**
	 * {@inheritDoc}
	 */
    public String getIntrusionDetectionImplementation() {
    	return getESAPIProperty(INTRUSION_DETECTION_IMPLEMENTATION, DEFAULT_INTRUSION_DETECTION_IMPLEMENTATION);
    }

    /**
	 * {@inheritDoc}
	 */
    public String getRandomizerImplementation() {
    	return getESAPIProperty(RANDOMIZER_IMPLEMENTATION, DEFAULT_RANDOMIZER_IMPLEMENTATION);
    }

    /**
	 * {@inheritDoc}
	 */
    public String getExecutorImplementation() {
    	return getESAPIProperty(EXECUTOR_IMPLEMENTATION, DEFAULT_EXECUTOR_IMPLEMENTATION);
    }

    /**
	 * {@inheritDoc}
	 */
    public String getHTTPUtilitiesImplementation() {
    	return getESAPIProperty(HTTP_UTILITIES_IMPLEMENTATION, DEFAULT_HTTP_UTILITIES_IMPLEMENTATION);
    }

    /**
	 * {@inheritDoc}
	 */
    public String getValidationImplementation() {
    	return getESAPIProperty(VALIDATOR_IMPLEMENTATION, DEFAULT_VALIDATOR_IMPLEMENTATION);
    }


    /**
	 * {@inheritDoc}
	 */
    public byte[] getMasterKey() {
    	byte[] key = getESAPIPropertyEncoded( MASTER_KEY, null );
    	if ( key == null || key.length == 0 ) {
    		throw new ConfigurationException("Property '" + MASTER_KEY +
    							"' missing or empty in ESAPI.properties file.");
    }
    	return key;
    }

    /**
	 * {@inheritDoc}
	 */
    public void setResourceDirectory( String dir ) {
    	resourceDirectory = dir;
        logSpecial( "Reset resource directory to: " + dir, null );

        // reload configuration if necessary
    	try {
    		this.loadConfiguration();
    	} catch( IOException e ) {
	        logSpecial("Failed to load security configuration from " + dir, e);
    	}
    }

    public int getEncryptionKeyLength() {
    	return getESAPIProperty(KEY_LENGTH, 128 );
    }

    /**
	 * {@inheritDoc}
	 */
    public byte[] getMasterSalt() {
    	byte[] salt = getESAPIPropertyEncoded( MASTER_SALT, null );
    	if ( salt == null || salt.length == 0 ) {
    		throw new ConfigurationException("Property '" + MASTER_SALT +
    							"' missing or empty in ESAPI.properties file.");
    }
    	return salt;
    }

    /**
	 * {@inheritDoc}
	 */
	public List<String> getAllowedExecutables() {
    	String def = "";
        String[] exList = getESAPIProperty(APPROVED_EXECUTABLES,def).split(",");
        return Arrays.asList(exList);
    }

    /**
	 * {@inheritDoc}
	 */
	public List<String> getAllowedFileExtensions() {
        String def = ".pdf,.txt,.jpg,.png";
        String[] extList = getESAPIProperty(APPROVED_UPLOAD_EXTENSIONS,def).split(",");
        return Arrays.asList(extList);
    }

    /**
	 * {@inheritDoc}
	 */
    public int getAllowedFileUploadSize() {
        return getESAPIProperty(MAX_UPLOAD_FILE_BYTES, 5000000);
    }


    private Properties loadPropertiesFromStream( InputStream is, String name ) throws IOException {
    	Properties config = new Properties();
        try {
	        config.load(is);
	        logSpecial("Loaded '" + name + "' properties file", null);
        } finally {
            if ( is != null ) try { is.close(); } catch( Exception e ) {}
        }
        return config;
    }

	/**
	 * Load configuration. Never prints properties.
	 * 
	 * @throws java.io.IOException
	 *             if the file is inaccessible
	 */
	protected void loadConfiguration() throws IOException {
		try {
		    //first attempt file IO loading of properties
			logSpecial("Attempting to load " + resourceFile + " via file I/O.");
			properties = loadPropertiesFromStream(getResourceStream(resourceFile), resourceFile);
			
		} catch (Exception iae) {
		    //if file I/O loading fails, attempt classpath based loading next
		    logSpecial("Loading " + resourceFile + " via file I/O failed. Exception was: " + iae);
			logSpecial("Attempting to load " + resourceFile + " via the classpath.");
			try {
				properties = loadConfigurationFromClasspath(resourceFile);
			} catch (Exception e) {				
				logSpecial(resourceFile + " could not be loaded by any means. Fail.", e);
				throw new ConfigurationException(resourceFile + " could not be loaded by any means. Fail.", e);
			}			
		}
		
		// if properties loaded properly above, get validation properties and merge them into the main properties
		if (properties != null) {
			final Iterator<String> validationPropFileNames;
			
			//defaults to single-valued for backwards compatibility
			final boolean multivalued= getESAPIProperty(VALIDATION_PROPERTIES_MULTIVALUED, false);
			final String validationPropValue = getESAPIProperty(VALIDATION_PROPERTIES, "validation.properties");
			
			if(multivalued){
				// the following cast warning goes away if the apache commons lib is updated to current version				
				validationPropFileNames = StrTokenizer.getCSVInstance(validationPropValue);
			} else {
				validationPropFileNames = Collections.singletonList(validationPropValue).iterator();
			}
			
			//clear any cached validation patterns so they can be reloaded from validation.properties
			patternCache.clear();
			while(validationPropFileNames.hasNext()){
				String validationPropFileName = validationPropFileNames.next();
				Properties validationProperties = null;
				try {
				    //first attempt file IO loading of properties
					logSpecial("Attempting to load " + validationPropFileName + " via file I/O.");
					validationProperties = loadPropertiesFromStream(getResourceStream(validationPropFileName), validationPropFileName);
					
				} catch (Exception iae) {
				    //if file I/O loading fails, attempt classpath based loading next
				    logSpecial("Loading " + validationPropFileName + " via file I/O failed.");
					logSpecial("Attempting to load " + validationPropFileName + " via the classpath.");		
					try {
						validationProperties = loadConfigurationFromClasspath(validationPropFileName);
					} catch (Exception e) {				
						logSpecial(validationPropFileName + " could not be loaded by any means. fail.", e);
					}			
				}
				
				if (validationProperties != null) {
			    	Iterator<?> i = validationProperties.keySet().iterator();
			    	while( i.hasNext() ) {
			    		String key = (String)i.next();
			    		String value = validationProperties.getProperty(key);
			    		properties.put( key, value);
			    	}
				}
				
		        if ( shouldPrintProperties() ) {
		    	
		    	//FIXME - make this chunk configurable
		    	/*
		        logSpecial("  ========Master Configuration========", null);
		        //logSpecial( "  ResourceDirectory: " + DefaultSecurityConfiguration.resourceDirectory );
		        Iterator j = new TreeSet( properties.keySet() ).iterator();
		        while (j.hasNext()) {
		            String key = (String)j.next();
		            // print out properties, but not sensitive ones like MasterKey and MasterSalt
		            if ( !key.contains( "Master" ) ) {
		            		logSpecial("  |   " + key + "=" + properties.get(key), null);
		        	}
		        }
		        */
		        }   	
	        }
		}
	}	
	
	/**
	 * @param filename
	 * @return An {@code InputStream} associated with the specified file name as
	 *         a resource stream.
	 * @throws IOException
	 *             If the file cannot be found or opened for reading.
	 */
	public InputStream getResourceStream(String filename) throws IOException {
		if (filename == null) {
			return null;
		}

		try {
			File f = getResourceFile(filename);
			if (f != null && f.exists()) {
				return new FileInputStream(f);
			}
		} catch (Exception e) {
		}

		throw new FileNotFoundException();
	}
	
	/**
	 * {@inheritDoc}
	 */
	public File getResourceFile(String filename) {
		logSpecial("Attempting to load " + filename + " as resource file via file I/O.");

		if (filename == null) {
			logSpecial("Failed to load properties via FileIO. Filename is null.");
			return null; // not found.
		}

		File f = null;

		// first, allow command line overrides. -Dorg.owasp.esapi.resources
		// directory
		f = new File(customDirectory, filename);
		if (customDirectory != null && f.canRead()) {
			logSpecial("Found in 'org.owasp.esapi.resources' directory: " + f.getAbsolutePath());
			return f;
		} else {
			logSpecial("Not found in 'org.owasp.esapi.resources' directory or file not readable: " + f.getAbsolutePath());
		}

		// if not found, then try the programmatically set resource directory
		// (this defaults to SystemResource directory/resourceFile
		URL fileUrl = ClassLoader.getSystemResource(resourceDirectory + "/" + filename);
        if ( fileUrl == null ) {
            fileUrl = ClassLoader.getSystemResource("esapi/" + filename);
        }

		if (fileUrl != null) {
			try {
				String fileLocation = fileUrl.toURI().getPath();
				f = new File(fileLocation);
				if (f.exists()) {
					logSpecial("Found in SystemResource Directory/resourceDirectory: " + f.getAbsolutePath());
					return f;
				} else {
					logSpecial("Not found in SystemResource Directory/resourceDirectory (this should never happen): " + f.getAbsolutePath());
				}
			} catch (URISyntaxException e) {
				logSpecial("Error while converting URL " + fileUrl + " to file path: " + e.getMessage());
			}
		} else {
			logSpecial("Not found in SystemResource Directory/resourceDirectory: " + resourceDirectory + File.separator + filename);
		}

		// If not found, then try immediately under user's home directory first in
		//		userHome + "/.esapi"		and secondly under
		//		userHome + "/esapi"
		// We look in that order because of backward compatibility issues.
		String homeDir = userHome;
		if ( homeDir == null ) {
			homeDir = "";	// Without this,	homeDir + "/.esapi"	would produce
							// the string		"null/.esapi"		which surely is not intended.
		}
		// First look under ".esapi" (for reasons of backward compatibility).
		f = new File(homeDir + "/.esapi", filename);
		if ( f.canRead() ) {
			logSpecial("[Compatibility] Found in 'user.home' directory: " + f.getAbsolutePath());
			return f;
		} else {
			// Didn't find it under old directory ".esapi" so now look under the "esapi" directory.
			f = new File(homeDir + "/esapi", filename);
			if ( f.canRead() ) {
				logSpecial("Found in 'user.home' directory: " + f.getAbsolutePath());
				return f;
			} else {
				logSpecial("Not found in 'user.home' (" + homeDir + ") directory: " + f.getAbsolutePath());
			}
		}

		// return null if not found
		return null;
	}
	
    /**
     * Used to load ESAPI.properties from a variety of different classpath locations.
     *
     * @param fileName The properties file filename.
     */
	private Properties loadConfigurationFromClasspath(String fileName) throws IllegalArgumentException {
		Properties result = null;
		InputStream in = null;

		ClassLoader[] loaders = new ClassLoader[] {
				Thread.currentThread().getContextClassLoader(),
				ClassLoader.getSystemClassLoader(),
				getClass().getClassLoader() 
		};
		String[] classLoaderNames = {
				"current thread context class loader",
				"system class loader",
				"class loader for DefaultSecurityConfiguration class"
		};

		ClassLoader currentLoader = null;
		for (int i = 0; i < loaders.length; i++) {
			if (loaders[i] != null) {
				currentLoader = loaders[i];
				try {
					// try root
					String currentClasspathSearchLocation = "/ (root)";
                        // Note: do NOT add '/' anywhere here even though root value is empty string!
                        // Note that since DefaultSearchPath.ROOT.value() is now "" (the empty string),
                        // then this is logically equivalent to what we used to have, which was:
                        //
						//      in = loaders[i].getResourceAsStream(fileName);
                        //
					in = loaders[i].getResourceAsStream(DefaultSearchPath.ROOT.value() + fileName);
					
					// try resourceDirectory folder
					if (in == null) {
						currentClasspathSearchLocation = resourceDirectory + "/";
						in = currentLoader.getResourceAsStream(DefaultSearchPath.RESOURCE_DIRECTORY.value() + fileName);
					}

					// try .esapi folder. Look here first for backward compatibility.
					if (in == null) {
						currentClasspathSearchLocation = ".esapi/";
						in = currentLoader.getResourceAsStream(DefaultSearchPath.DOT_ESAPI.value() + fileName);
					} 
					
					// try esapi folder (new directory)
					if (in == null) {
						currentClasspathSearchLocation = "esapi/";
						in = currentLoader.getResourceAsStream(DefaultSearchPath.ESAPI.value() + fileName);
					} 
					
					// try resources folder
					if (in == null) {
						currentClasspathSearchLocation = "resources/";
						in = currentLoader.getResourceAsStream(DefaultSearchPath.RESOURCES.value() + fileName);
					}
					
					// try src/main/resources folder
					if (in == null) {
						currentClasspathSearchLocation = "src/main/resources/";
						in = currentLoader.getResourceAsStream(DefaultSearchPath.SRC_MAIN_RESOURCES.value() + fileName);
					}
		
					// now load the properties
					if (in != null) {
						result = new Properties();
						result.load(in); // Can throw IOException
						logSpecial("SUCCESSFULLY LOADED " + fileName + " via the CLASSPATH from '" +
								currentClasspathSearchLocation + "' using " + classLoaderNames[i] + "!");
						break;	// Outta here since we've found and loaded it.
					}
				} catch (Exception e) {
					result = null;
		
				} finally {
					try {
						in.close();
					} catch (Exception e) {
					}
				}
			}
		}

		if (result == null) {
			// CHECKME: This is odd...why not ConfigurationException?
		    throw new IllegalArgumentException("Failed to load " + resourceFile + " as a classloader resource.");
		}

		return result;
	}

    /**
     * Log to standard output (i.e., {@code System.out}. This method is
     * synchronized to reduce the possibility of interleaving the message
     * output (since the {@code System.out} {@code PrintStream} is buffered)
     * it invoked from multiple threads. Output is discarded if the
     * {@code System} property "org.owasp.esapi.logSpecial.discard" is set to
     * {@code true}.
     *
     * @param msg   Message to be logged.
     * @param t     Associated exception that was caught. The class name and
     *              exception message is also logged.
     */
    public final synchronized static void logToStdout(String msg, Throwable t) {
     // Note that this class was made final because it is called from this class'
     // CTOR and we want to prohibit someone from <i>easily</i> doing sneaky
     // things like subclassing this class and inserting a malicious code as a
     // shim. Of course, really in hindsight, this entire class should have been
     // declared 'final', but doing so at this point would likely break someone's
     // code, including possibly some of our own test code. But since this is a
     // new method, we can get away with it here.
        boolean discard = logSpecialValue.trim().equalsIgnoreCase("true");
        if ( discard ) {
            return; // Output is discarded!
        }
        if ( t == null ) {
            System.out.println("ESAPI: " + msg);
        } else {
            System.out.println("ESAPI: " + msg +
                               ". Caught " + t.getClass().getName() +
                               "; exception message was: " + t);
        }
    }
    
    /**
     * Used to log errors to the console during the loading of the properties file itself. Can't use
     * standard logging in this case, since the Logger may not be initialized yet. Output is sent to
     * {@code PrintStream} {@code System.out}.  Output is discarded if the {@code System} property
     * "org.owasp.esapi.logSpecial.discard" is set to {@code true}.
     *
     * @param message The message to send to the console.
     * @param e The error that occurred. (This value printed via {@code e.toString()}.)
     */
    private void logSpecial(String message, Throwable e) {
        logToStdout(message, e);
    }

    /**
     * Used to log errors to the console during the loading of the properties file itself. Can't use
     * standard logging in this case, since the Logger may not be initialized yet. Output is sent to
     * {@code PrintStream} {@code System.out}.  Output is discarded if the {@code System} property
     * "org.owasp.esapi.logSpecial.discard" is set to {@code true}.
     *
     * @param message The message to send to the console.
     */
    private void logSpecial(String message) {
		logToStdout(message, null);
    }
    /**
	 * {@inheritDoc}
	 */
    public String getPasswordParameterName() {
        return getESAPIProperty(PASSWORD_PARAMETER_NAME, "password");
    }

    /**
	 * {@inheritDoc}
	 */
    public String getUsernameParameterName() {
        return getESAPIProperty(USERNAME_PARAMETER_NAME, "username");
    }

    /**
	 * {@inheritDoc}
	 */
    public String getEncryptionAlgorithm() {
        return getESAPIProperty(ENCRYPTION_ALGORITHM, "AES");
    }

    /**
	 * {@inheritDoc}
	 */
    public String getCipherTransformation() {
        // Assertion should be okay here. An NPE is likely at runtime if disabled.
    	assert cipherXformCurrent != null : "Current cipher transformation is null";
    	return cipherXformCurrent;
    }

    /**
     * {@inheritDoc}
     */
    public String setCipherTransformation(String cipherXform) {
        String previous = getCipherTransformation();
        if ( cipherXform == null ) {
            // Special case... means set it to original value from ESAPI.properties
            cipherXformCurrent = cipherXformFromESAPIProp;
        } else {
            if ( cipherXform.trim().equals("") ) {
                throw new ConfigurationException("Cipher transformation cannot be just white space or empty string");
            }
            cipherXformCurrent = cipherXform;   // Note: No other sanity checks!!!
        }
        return previous;
    }

    /**
     * {@inheritDoc}
     */
    public boolean useMACforCipherText() {
    	return getESAPIProperty(CIPHERTEXT_USE_MAC, true);
    }

    /**
     * {@inheritDoc}
     */
    public boolean overwritePlainText() {
    	return getESAPIProperty(PLAINTEXT_OVERWRITE, true);
    }
    
    /**
	 * {@inheritDoc}
	 */
    public String getIVType() {
    	String value = getESAPIProperty(IV_TYPE, "random");
    	if ( value.equalsIgnoreCase("random") ) {
            return value;
        } else if ( value.equalsIgnoreCase("fixed") ) {
            logSpecial("WARNING: Property '" + IV_TYPE + "=fixed' is DEPRECATED. It was intended to support legacy applications, but is inherently insecure, especially with any streaming mode. Support for this will be completed dropped next ESAPI minor release (probably 2.3");
    		return value;
    	} else if ( value.equalsIgnoreCase("specified") ) {
    		// This is planned for future implementation where setting
    		// Encryptor.ChooseIVMethod=specified   will require setting some
    		// other TBD property that will specify an implementation class that
    		// will generate appropriate IVs. The intent of this would be to use
    		// such a class with various feedback modes where it is imperative
    		// that for a given key, any particular IV is *NEVER* reused. For
    		// now, we will assume that generating a random IV is usually going
    		// to be sufficient to prevent this.
    		throw new ConfigurationException("'" + IV_TYPE + "=specified' is not yet implemented. Use 'random' for now.");
    	} else {
    		// TODO: Once 'specified' is legal, adjust exception msg, below.
    		// DISCUSS: Could just log this and then silently return "random" instead.
    		throw new ConfigurationException(value + " is illegal value for " + IV_TYPE +
    										 ". Use 'random'.");
    	}
    }

    /**
	 * {@inheritDoc}
	 */
    @Deprecated
    public String getFixedIV() {
    	if ( getIVType().equalsIgnoreCase("fixed") ) {
    		String ivAsHex = getESAPIProperty(FIXED_IV, ""); // No default
    		if ( ivAsHex == null || ivAsHex.trim().equals("") ) {
    			throw new ConfigurationException("Fixed IV requires property " +
    						FIXED_IV + " to be set, but it is not.");
    		}
    		return ivAsHex;		// We do no further checks here as we have no context.
    	} else {
    		// DISCUSS: Should we just log a warning here and return null instead?
    		//			If so, may cause NullPointException somewhere later.
    		throw new ConfigurationException("IV type not 'fixed' [which is DEPRECATED!] (set to '" +
    										 getIVType() + "'), so no fixed IV applicable.");
    	}
    }

    /**
	 * {@inheritDoc}
	 */
    public String getHashAlgorithm() {
        return getESAPIProperty(HASH_ALGORITHM, "SHA-512");
    }

    /**
	 * {@inheritDoc}
	 */
    public int getHashIterations() {
    	return getESAPIProperty(HASH_ITERATIONS, 1024);
    }

    /**
     * {@inheritDoc}
     */
	public String getKDFPseudoRandomFunction() {
		return getESAPIProperty(KDF_PRF_ALG, "HmacSHA256");  // NSA recommended SHA2 or better.
	}

    /**
	 * {@inheritDoc}
	 */
    public String getCharacterEncoding() {
        return getESAPIProperty(CHARACTER_ENCODING, "UTF-8");
    }

    /**
	 * {@inheritDoc}
	 */
	public boolean getAllowMultipleEncoding() {
		return getESAPIProperty( ALLOW_MULTIPLE_ENCODING, false );
	}

    /**
	 * {@inheritDoc}
	 */
	public boolean getAllowMixedEncoding() {
		return getESAPIProperty( ALLOW_MIXED_ENCODING, false );
	}

    /**
	 * {@inheritDoc}
	 */
	public List<String> getDefaultCanonicalizationCodecs() {
		List<String> def = new ArrayList<String>();
		def.add( "org.owasp.esapi.codecs.HTMLEntityCodec" );
		def.add( "org.owasp.esapi.codecs.PercentCodec" );
		def.add( "org.owasp.esapi.codecs.JavaScriptCodec" );
		return getESAPIProperty( CANONICALIZATION_CODECS, def );
	}

    /**
	 * {@inheritDoc}
	 */
    public String getDigitalSignatureAlgorithm() {
        return getESAPIProperty(DIGITAL_SIGNATURE_ALGORITHM, "SHAwithDSA");
    }

    /**
	 * {@inheritDoc}
	 */
    public int getDigitalSignatureKeyLength() {
        return getESAPIProperty(DIGITAL_SIGNATURE_KEY_LENGTH, 1024);
    }

    /**
	 * {@inheritDoc}
	 */
    public String getRandomAlgorithm() {
        return getESAPIProperty(RANDOM_ALGORITHM, "SHA1PRNG");
    }

    /**
	 * {@inheritDoc}
	 */
    public int getAllowedLoginAttempts() {
        return getESAPIProperty(ALLOWED_LOGIN_ATTEMPTS, 5);
    }

    /**
	 * {@inheritDoc}
	 */
    public int getMaxOldPasswordHashes() {
        return getESAPIProperty(MAX_OLD_PASSWORD_HASHES, 12);
    }

    /**
	 * {@inheritDoc}
	 */
    public File getUploadDirectory() {
    	String dir = getESAPIProperty( UPLOAD_DIRECTORY, "UploadDir");
    	return new File( dir );
    }

    /**
	 * {@inheritDoc}
	 */
    public File getUploadTempDirectory() {
    	String dir = getESAPIProperty(UPLOAD_TEMP_DIRECTORY,
            System.getProperty("java.io.tmpdir","UploadTempDir"));
    	return new File( dir );
    }
    
    /**
	 * {@inheritDoc}
	 */
	public boolean getDisableIntrusionDetection() {
    	String value = properties.getProperty( DISABLE_INTRUSION_DETECTION );
    	if ("true".equalsIgnoreCase(value)) return true;
    	return false;	// Default result
	}

    /**
	 * {@inheritDoc}
	 */
	public Threshold getQuota(String eventName) {
        int count = getESAPIProperty("IntrusionDetector." + eventName + ".count", 0);
        int interval =  getESAPIProperty("IntrusionDetector." + eventName + ".interval", 0);
        List<String> actions = new ArrayList<String>();
        String actionString = getESAPIProperty("IntrusionDetector." + eventName + ".actions", "");
        if (actionString != null) {
            String[] actionList = actionString.split(",");
            actions = Arrays.asList(actionList);
        }
        if ( count > 0 && interval > 0 && actions.size() > 0 ) {
        	return new Threshold(eventName, count, interval, actions);
        }
        return null;
    }

    /**
	 * {@inheritDoc}
	 */
    public boolean getLogEncodingRequired() {
    	return getESAPIProperty( LOG_ENCODING_REQUIRED, false );
	}


    /**
	 * {@inheritDoc}
	 */
    public boolean getLogApplicationName() {
    	return getESAPIProperty( LOG_APPLICATION_NAME, true );
	}


    /**
	 * {@inheritDoc}
	 */
    public boolean getLogServerIP() {
    	return getESAPIProperty( LOG_SERVER_IP, true );
	}

    /**
	 * {@inheritDoc}
	 */
    public boolean getForceHttpOnlySession() {
    	return getESAPIProperty( FORCE_HTTPONLYSESSION, true );
    }

    /**
	 * {@inheritDoc}
	 */
    public boolean getForceSecureSession() {
    	return getESAPIProperty( FORCE_SECURESESSION, true );
    }

    /**
	 * {@inheritDoc}
	 */
    public boolean getForceHttpOnlyCookies() {
    	return getESAPIProperty( FORCE_HTTPONLYCOOKIES, true );
    }

    /**
	 * {@inheritDoc}
	 */
    public boolean getForceSecureCookies() {
    	return getESAPIProperty( FORCE_SECURECOOKIES, true );
    }

    /**
	 * {@inheritDoc}
	 */
	public int getMaxHttpHeaderSize() {
        return getESAPIProperty( MAX_HTTP_HEADER_SIZE, 4096);
	}

    /**
	 * {@inheritDoc}
	 */
	public String getResponseContentType() {
        return getESAPIProperty( RESPONSE_CONTENT_TYPE, "text/html; charset=UTF-8" );
    }

	/**
	 * {@inheritDoc}
	 */
	public String getHttpSessionIdName() {
        return getESAPIProperty( HTTP_SESSION_ID_NAME, "JSESSIONID" );
    }
	
	/**
	 * {@inheritDoc}
	 */
    public long getRememberTokenDuration() {
        int days = getESAPIProperty( REMEMBER_TOKEN_DURATION, 14 );
        return (long) (1000 * 60 * 60 * 24 * days);
    }

    /**
	 * {@inheritDoc}
	 */
	public int getSessionIdleTimeoutLength() {
        int minutes = getESAPIProperty( IDLE_TIMEOUT_DURATION, 20 );
        return 1000 * 60 * minutes;
	}

	/**
	 * {@inheritDoc}
	 */
	public int getSessionAbsoluteTimeoutLength() {
        int minutes = getESAPIProperty(ABSOLUTE_TIMEOUT_DURATION, 20 );
        return 1000 * 60 * minutes;
	}

   /**
    * getValidationPattern returns a single pattern based upon key
    *
    *  @param key
    *  			validation pattern name you'd like
    *  @return
    *  			if key exists, the associated validation pattern, null otherwise
	*/
    public Pattern getValidationPattern( String key ) {
    	String value = getESAPIProperty( "Validator." + key, "" );
    	// check cache
    	Pattern p = patternCache.get( value );
    	if ( p != null ) return p;

    	// compile a new pattern
    	if ( value == null || value.equals( "" ) ) return null;
    	try {
    		Pattern q = Pattern.compile(value);
    		patternCache.put( value, q );
    		return q;
    	} catch ( PatternSyntaxException e ) {
    		logSpecial( "SecurityConfiguration for " + key + " not a valid regex in ESAPI.properties. Returning null", null );
    		return null;
    	}
    }

    /**
     * getWorkingDirectory returns the default directory where processes will be executed
     * by the Executor.
     */
	public File getWorkingDirectory() {
		String dir = getESAPIProperty( WORKING_DIRECTORY, System.getProperty( "user.dir") );
		if ( dir != null ) {
			return new File( dir );
		}
		return null;
	}
	
	/**
	 * {@inheritDoc}
	 */
	public String getPreferredJCEProvider() {
	    return properties.getProperty(PREFERRED_JCE_PROVIDER); // No default!
	}  

	/**
	 * {@inheritDoc}
	 */
	public List<String> getCombinedCipherModes()
	{
	    List<String> empty = new ArrayList<String>();     // Default is empty list
	    return getESAPIProperty(COMBINED_CIPHER_MODES, empty);
	}

	/**
	 * {@inheritDoc}
	 */
	public List<String> getAdditionalAllowedCipherModes()
	{
	    List<String> empty = new ArrayList<String>();     // Default is empty list
	    return getESAPIProperty(ADDITIONAL_ALLOWED_CIPHER_MODES, empty);
	}
	
	/**
	 * {@inheritDoc}
	 */
	public boolean getLenientDatesAccepted() {
		return getESAPIProperty( ACCEPT_LENIENT_DATES, false);
	}

	protected String getESAPIProperty( String key, String def ) {
		String value = properties.getProperty(key);
		if ( value == null ) {
    		logSpecial( "SecurityConfiguration for " + key + " not found in ESAPI.properties. Using default: " + def, null );
    		return def;
		}
		return value;
	}

	protected boolean getESAPIProperty( String key, boolean def ) {
		String property = properties.getProperty(key);
		if ( property == null ) {
    		logSpecial( "SecurityConfiguration for " + key + " not found in ESAPI.properties. Using default: " + def, null );
    		return def;
		}
		if ( property.equalsIgnoreCase("true") || property.equalsIgnoreCase("yes" ) ) {
			return true;
		}
		if ( property.equalsIgnoreCase("false") || property.equalsIgnoreCase( "no" ) ) {
			return false;
		}
		logSpecial( "SecurityConfiguration for " + key + " not either \"true\" or \"false\" in ESAPI.properties. Using default: " + def, null );
		return def;
	}

	protected byte[] getESAPIPropertyEncoded( String key, byte[] def ) {
		String property = properties.getProperty(key);
		if ( property == null ) {
    		logSpecial( "SecurityConfiguration for " + key + " not found in ESAPI.properties. Using default: " + def, null );
    		return def;
		}
        try {
            return ESAPI.encoder().decodeFromBase64(property);
        } catch( IOException e ) {
    		logSpecial( "SecurityConfiguration for " + key + " not properly Base64 encoded in ESAPI.properties. Using default: " + def, null );
            return null;
        }
	}

	protected int getESAPIProperty( String key, int def ) {
		String property = properties.getProperty(key);
		if ( property == null ) {
    		logSpecial( "SecurityConfiguration for " + key + " not found in ESAPI.properties. Using default: " + def, null );
    		return def;
		}
		try {
            return Integer.parseInt( property );
		} catch( NumberFormatException e ) {
    		logSpecial( "SecurityConfiguration for " + key + " not an integer in ESAPI.properties. Using default: " + def, null );
			return def;
		}
	}

	/**
     * Returns a {@code List} representing the parsed, comma-separated property.
     * 
	 * @param key  The specified property name
	 * @param def  A default value for the property name to return if the property
	 *             is not set.
	 * @return A list of strings.
	 */
	protected List<String> getESAPIProperty( String key, List<String> def ) {
	    String property = properties.getProperty( key );
	    if ( property == null ) {
	        logSpecial( "SecurityConfiguration for " + key + " not found in ESAPI.properties. Using default: " + def, null );
	        return def;
	    }
	    String[] parts = property.split(",");
	    return Arrays.asList( parts );
	}

    /**
     * {@inheritDoc}
     * Looks for property in three configuration files in following order:
     * 1.) In file defined as org.owasp.esapi.opsteam system property 
     * 2.) In file defined as org.owasp.esapi.devteam system property 
     * 3.) In ESAPI.properties* 
     */
    @Override
    public int getIntProp(String propertyName) throws ConfigurationException {
        try {
            return esapiPropertyManager.getIntProp(propertyName);
        } catch (ConfigurationException ex) {
            String property = properties.getProperty(propertyName);
            try {
                return Integer.parseInt(property);
            } catch (NumberFormatException e) {
                throw new ConfigurationException( "SecurityConfiguration for " + propertyName + " has incorrect " +
                        "type");
            }
        }
    }

    /**
     * {@inheritDoc}
     * Looks for property in three configuration files in following order:
     * 1.) In file defined as org.owasp.esapi.opsteam system property 
     * 2.) In file defined as org.owasp.esapi.devteam system property 
     * 3.) In ESAPI.properties
     */
    @Override
    public byte[] getByteArrayProp(String propertyName) throws ConfigurationException {
        try {
            return esapiPropertyManager.getByteArrayProp(propertyName);
        } catch (ConfigurationException ex) {
            String property = properties.getProperty(propertyName);
            if ( property == null ) {
                throw new ConfigurationException( "SecurityConfiguration for " + propertyName + " not found in ESAPI.properties");
            }
            try {
                return ESAPI.encoder().decodeFromBase64(property);
            } catch( IOException e ) {
                throw new ConfigurationException( "SecurityConfiguration for " + propertyName + " has incorrect " +
                        "type");
            }
        }
    }

    /**
     * {@inheritDoc}  
     * Looks for property in three configuration files in following order:
     * 1.) In file defined as org.owasp.esapi.opsteam system property 
     * 2.) In file defined as org.owasp.esapi.devteam system property 
     * 3.) In ESAPI.properties
     */
    @Override
    public Boolean getBooleanProp(String propertyName) throws ConfigurationException {
        try {
            return esapiPropertyManager.getBooleanProp(propertyName);
        } catch (ConfigurationException ex) {
            String property = properties.getProperty( propertyName );
            if ( property == null ) {
                throw new ConfigurationException( "SecurityConfiguration for " + propertyName + " not found in ESAPI.properties");
            }
            if ( property.equalsIgnoreCase("true") || property.equalsIgnoreCase("yes" ) ) {
                return true;
            }
            if ( property.equalsIgnoreCase("false") || property.equalsIgnoreCase( "no" ) ) {
                return false;
            }
            throw new ConfigurationException( "SecurityConfiguration for " + propertyName + " has incorrect " +
                    "type");
        }
    }

    /**
     * {@inheritDoc}
     * Looks for property in three configuration files in following order:
     * 1.) In file defined as org.owasp.esapi.opsteam system property
     * 2.) In file defined as org.owasp.esapi.devteam system property
     * 3.) In ESAPI.properties
     */
    @Override
    public String getStringProp(String propertyName) throws ConfigurationException {
        try {
            return esapiPropertyManager.getStringProp(propertyName);
        } catch (ConfigurationException ex) {
            String property = properties.getProperty( propertyName );
            if ( property == null ) {
                throw new ConfigurationException( "SecurityConfiguration for " + propertyName + " not found in ESAPI.properties");
            }
            return property;
        }
    }


    protected boolean shouldPrintProperties() {
        return getESAPIProperty(PRINT_PROPERTIES_WHEN_LOADED, false);
	}

    protected Properties getESAPIProperties() {
        return properties;
    }
    
    public enum DefaultSearchPath {
    	
    	RESOURCE_DIRECTORY("resourceDirectory/"),
    	SRC_MAIN_RESOURCES("src/main/resources/"),
    	ROOT(""),
    	DOT_ESAPI(".esapi/"),
    	ESAPI("esapi/"),
    	RESOURCES("resources/");
    	
    	private final String path;
    	
    	private DefaultSearchPath(String s){
    		this.path = s;
    	}
    	
    	public String value(){
    		return path;
    	}
    }
}
