package org.owasp.esapi.reference.crypto;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.Properties;

import org.owasp.esapi.EncryptedProperties;

/**
 * Command line utilities for reading, writing and creating encrypted properties files.
 * <p>
 * Usage:<br/>
 * <code>
 *    java org.owasp.esapi.reference.crypto.EncryptedPropertiesUtils [--in file] [--out file] 
 *			[--in-encrypted true|false] [--verbose true|false]
 * </code>
 * <p>
 * Command line parameters:<br/>
 * <ul>
 * <li><b>--in</b> (Optional) Encrypted or plaintext file to read from. If no input file is specified, a new properties file will be created.</li>
 * <li><b>--out</b> (Optional) Encrypted file to output to. Default: Overwrite input file</li>
 * <li><b>--in-encrypted</b> (Optional) True if the input file is encrypted. Default: true</li>
 * <li><b>--verbose</b> (Optional) If true, output (potentially unencrypted) information to the terminal. Default: false</li>
 * </ul>
 *
 * @author August Detlefsen (augustd at codemagi dot com)
 *         <a href="http://www.codemagi.com">CodeMagi, Inc.</a>
 * @since October 8, 2010
 * @see org.owasp.esapi.EncryptedProperties
 */
public class EncryptedPropertiesUtils {

	/**
	 * Loads encrypted or plaintext properties file based on the location passed in args
	 * then prompts the user to input key-value pairs.  When the user enters a null or
	 * blank key, the values are stored to the properties file.
	 *
	 * @throws Exception Any exception thrown
	 */
	public static void main(String[] args) throws Exception {

		//command line options
		String inFile = null;
		String outFile = null;
		boolean inFileEncrypted = true;
		boolean verbose = false;

		//parse command line params
		for (int i = 0; i < args.length; i = i + 2) {
			String paramType = args[i];

			if ("--in".equals(paramType) && args.length >= i + 1) {
				inFile = args[i + 1];

			} else if ("--out".equals(paramType) && args.length >= i + 1) {
				outFile = args[i + 1];

			} else if ("--in-encrypted".equals(paramType) && args.length >= i + 1) {
				inFileEncrypted = Boolean.valueOf(args[i + 1]);

			} else if ("--verbose".equals(paramType) && args.length >= i + 1) {
				verbose = Boolean.valueOf(args[i + 1]);
			}

		}

		if (outFile == null) {
			outFile = inFile; //if no output file is specified we will overwrite the input file
		}
		if (outFile == null) {
			//no input or output file specified. Can't continue.
			System.out.println("You must specify an input file or output file");
			System.exit(1);
		}

		//load in existing properties from a file
		Properties props = loadProperties(inFile, inFileEncrypted);

		//read user input and add keys and values to the property file
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		String key = null;
		do {
			System.out.print("Enter key: ");
			key = br.readLine();

			if (props.containsKey(key)) {
				System.out.print("Key already exists. Replace? ");
				String confirm = br.readLine();
				if (!("y".equals(confirm) || "yes".equals(confirm))) {
					continue;
				}
			}

			System.out.print("Enter value: ");
			String value = br.readLine();

			addProperty(props, key, value);

		} while (key != null && key.length() > 0);

		//save output file
		storeProperties(outFile, props,
				"Encrypted Properties File generated by org.owasp.esapi.reference.crypto.EncryptedPropertiesUtils");

		System.out.println("Encrypted Properties file output to " + outFile);

		if (verbose) {
			for (Object oKey : props.keySet()) {
				String sKey = (String) oKey;
				String value = props.getProperty(sKey);
				System.out.println("   " + sKey + "=" + value);
			}
		}

	}

	/**
	 * Loads a Properties file from a filename. If the filename is unspecified
	 * or the file could not be found, a new Properties is returned.
	 *
	 * @param inFile Filename to load Properties from.
	 * @param inFileEncrypted If true, the input file is assumed to be already encrypted. Default true.
	 * @return Either the loaded Properties object or a new one if the file could not be found.
	 * @throws IOException
	 */
	public static Properties loadProperties(String inFile, Boolean inFileEncrypted) throws IOException {

		if (inFileEncrypted == null) inFileEncrypted = true;

		Properties props;

		if (inFile != null) {

			File f = new File(inFile);
			if (!f.exists()) {
				System.out.println("Input properties file not found. Creating new.");
				props = new ReferenceEncryptedProperties();

			} else {
				String encrypted = inFileEncrypted ? "Encrypted" : "Plaintext";
				System.out.println(encrypted + " properties found in " + f.getAbsolutePath());

				Properties inProperties;
				if (inFileEncrypted) {
					inProperties = new ReferenceEncryptedProperties();
				} else {
					inProperties = new Properties();
				}

				InputStream in = null;
				try {
					in = new FileInputStream(f);
					inProperties.load(in);
				} finally {
					try {
						if (in != null) in.close(); //quietly close the InputStream
					} catch (Exception e) {}
				}

				//Use the existing properties
				props = new ReferenceEncryptedProperties(inProperties);
			}
		} else {
			System.out.println("Input properties file not found. Creating new.");
			props = new ReferenceEncryptedProperties();
		}

		return props;
	}

	/**
	 * Stores a Properties object to a file.
	 *
	 * @param outFile Filename to store to
	 * @param props Properties to store
	 * @param message A message to add to the comments in the stored file
	 * @throws Exception
	 */
	public static void storeProperties(String outFile, Properties props, String message) throws Exception {
		OutputStream out = null;
		try {
			out = new FileOutputStream(new File(outFile));
			props.store(out, message);
		} finally {
			try {
				if (out != null) out.close();  //quietly close OutputStream
			} catch (Exception e) {}
		}
	}

	/**
	 * Adds a new key-value property to the passed Properties object
	 *
	 * @param props The Properties object to add to
	 * @param key The key to add
	 * @param value The value to set
	 * @return The previous value of the property, or null if it is newly added.
	 */
	public static Object addProperty(Properties props, String key, String value) {
		if (props != null && key != null && key.length() > 0 && value != null && value.length() > 0) {
			return props.setProperty(key, value);
		}
		return null;
	}
	
}
