diff --git a/src/main/core/org/kissweb/IniFile.java b/src/main/core/org/kissweb/IniFile.java
new file mode 100644
index 0000000..f7fddde
--- /dev/null
+++ b/src/main/core/org/kissweb/IniFile.java
@@ -0,0 +1,584 @@
+package org.kissweb;
+
+import java.io.*;
+import java.util.Date;
+import java.util.HashMap;
+
+/**
+ * Class to deal with ini files. These are text-based property files broken into sections. Each section may have any number of key/value pairs. You can also dispense with the sections if you only have one.
+ *
+ * The file can also have blank and comment lines. Comment lines start with a semicolon, colon, hash, dash, or astrix. Keys and values may be quoted with either single or double quotes.
+ *
+ */
+public class IniFile {
+
+ private final HashMap> sections = new HashMap<>();
+ private String filename;
+
+ /**
+ * Create a new ini file in memory. If it is to be saved, the file name to be used can be specified later.
+ */
+ public IniFile() { }
+
+ /**
+ * Create a new ini file in memory that will be saved to the specified file.
+ *
+ * @param fname
+ */
+ public IniFile(String fname) {
+ filename = fname;
+ }
+
+ /**
+ * Load an ini file from a disk file.
+ *
+ * @param fname
+ * @return
+ * @throws IOException
+ */
+ public static IniFile load(String fname) throws IOException {
+ if (!(new java.io.File(fname)).exists()) {
+ return null;
+ }
+ IniFile ini = new IniFile();
+ ini.filename = fname;
+ ini.parse(fname);
+ return ini;
+ }
+
+ private static String unquote(String s) {
+ if (s == null)
+ return null;
+ s = s.trim();
+ if (s.isEmpty())
+ return null;
+ if (s.length() > 1 && (s.startsWith("\"") && s.endsWith("\"") || s.startsWith("'") && s.endsWith("'")))
+ return s.substring(1, s.length() - 1);
+ return s;
+ }
+
+ private void parse(String fname) throws IOException {
+ HashMap section = new HashMap<>();
+ String sectionName = null;
+ try (BufferedReader br = new BufferedReader(new FileReader(fname))) {
+ String line;
+ while ((line = br.readLine()) != null) {
+ line = line.trim();
+ if (line.isEmpty() || line.startsWith("#") || line.startsWith(";") || line.startsWith("-") || line.startsWith("*") || line.startsWith(":"))
+ continue; // comment or blank
+ if (line.startsWith("[")) {
+ sections.put(sectionName, section);
+ sectionName = unquote(line.substring(1, line.length() - 1));
+ section = sections.get(sectionName);
+ if (section == null)
+ section = new HashMap<>();
+ continue;
+ }
+ String[] parts = line.split("=", 2);
+ if (parts.length != 2)
+ continue;
+ String key = unquote(parts[0]);
+ String value = unquote(parts[1]);
+ //System.out.println(key + " = " + value);
+ section.put(key, value);
+ }
+ sections.put(sectionName, section);
+ }
+ }
+
+ /**
+ * Returns the filename of the ini file.
+ */
+ public String getFilename() {
+ return filename;
+ }
+
+ /**
+ * Returns the value of a key from a specified section in the ini file.
+ *
+ * @param section the section to search for the key
+ * @param key the key to search for
+ * @return the value of the key if found, null otherwise
+ */
+ public synchronized String get(String section, String key) {
+ final HashMap s = sections.get(section);
+ if (s == null)
+ return null;
+ return s.get(key);
+ }
+
+ /**
+ * Retrieves the integer value of a key from a specified section in the ini file.
+ *
+ * @param section the section to search for the key
+ * @param key the key to search for
+ * @return the integer value of the key if found, null otherwise
+ */
+ public Integer getInt(String section, String key) {
+ final String s = get(section, key);
+ if (s == null)
+ return null;
+ return Integer.parseInt(s);
+ }
+
+ /**
+ * Retrieves the first character of a key from a specified section in the ini file.
+ *
+ * @param section the section to search for the key
+ * @param key the key to search for
+ * @return the first character of the key if found, null otherwise
+ */
+ public Character getChar(String section, String key) {
+ final String s = get(section, key);
+ if (s == null)
+ return null;
+ return s.charAt(0);
+ }
+
+ /**
+ * Retrieves the boolean value of a key from a specified section in the ini file.
+ *
+ * @param section the section to search for the key
+ * @param key the key to search for
+ * @return true if the value starts with 't', '1', or 'y', false otherwise
+ */
+ public boolean getBoolean(String section, String key) {
+ final String s = get(section, key);
+ if (s == null || s.isEmpty())
+ return false;
+ char c = Character.toLowerCase(s.charAt(0));
+ return c == 't' || c == '1' || c == 'y';
+ }
+
+ /**
+ * Retrieves the double value of a key from a specified section in the ini file.
+ *
+ * @param section the section to search for the key
+ * @param key the key to search for
+ * @return the double value of the key if found, null otherwise
+ */
+ public Double getDouble(String section, String key) {
+ final String s = get(section, key);
+ if (s == null)
+ return null;
+ return Double.parseDouble(s);
+ }
+
+ /**
+ * Retrieves the integer value of a date key from a specified section in the ini file.
+ *
+ * @param section the section to search for the key
+ * @param key the key to search for
+ * @return the integer value of the key if found (YYYYMMDD), 0 otherwise
+ */
+ public int getDateInt(String section, String key) {
+ final String s = get(section, key);
+ if (s == null)
+ return 0;
+ return DateUtils.parse(s);
+ }
+
+ /**
+ * Retrieves the integer value of a time key from a specified section in the ini file.
+ *
+ * @param section the section to search for the key
+ * @param key the key to search for
+ * @return the integer value of the key if found (HHMM), 0 otherwise
+ */
+ public int getTimeInt(String section, String key) {
+ final String s = get(section, key);
+ if (s == null)
+ return 0;
+ return TimeUtils.parse(s);
+ }
+
+ /**
+ * Puts a key-value pair into a specified section of the ini file.
+ *
+ * @param section the section to put the key-value pair into
+ * @param key the key to put into the section
+ * @param value the value to put into the section
+ */
+ public synchronized void put(String section, String key, String value) {
+ HashMap s = sections.get(section);
+ if (s == null)
+ s = new HashMap<>();
+ s.put(key, value);
+ sections.put(section, s);
+ }
+
+ /**
+ * Puts a key-value pair into a specified section of the ini file.
+ *
+ * @param section the section to put the key-value pair into
+ * @param key the key to put into the section
+ * @param value the value to put into the section
+ */
+ public void put(String section, String key, int value) {
+ put(section, key, Integer.toString(value));
+ }
+
+ /**
+ * Puts a key-value pair into a specified section of the ini file.
+ *
+ * @param section the section to put the key-value pair into
+ * @param key the key to put into the section
+ * @param value the value to put into the section
+ */
+ public void put(String section, String key, double value) {
+ put(section, key, Double.toString(value));
+ }
+
+ /**
+ * Puts a key-value pair into a specified section of the ini file.
+ *
+ * @param section the section to put the key-value pair into
+ * @param key the key to put into the section
+ * @param value the value to put into the section
+ */
+ public void put(String section, String key, boolean value) {
+ put(section, key, Boolean.toString(value));
+ }
+
+ /**
+ * Puts a key-value pair into a specified section of the ini file.
+ *
+ * @param section the section to put the key-value pair into
+ * @param key the key to put into the section
+ * @param value the value to put into the section
+ */
+ public void put(String section, String key, char value) {
+ put(section, key, Character.toString(value));
+ }
+
+ /**
+ * Puts a key-value pair into a specified section of the ini file.
+ *
+ * @param section the section to put the key-value pair into
+ * @param key the key to put into the section
+ * @param value the value to put into the section
+ */
+ public void put(String section, String key, long value) {
+ put(section, key, Long.toString(value));
+ }
+
+ /**
+ * Puts a key-value pair into a specified section of the ini file.
+ *
+ * @param section the section to put the key-value pair into
+ * @param key the key to put into the section
+ * @param value the value to put into the section
+ */
+ public void put(String section, String key, Date value) {
+ put(section, key, DateUtils.format4(DateUtils.toInt(value)));
+ }
+
+ /**
+ * Puts a key-value pair into a specified section of the ini file.
+ *
+ * @param section the section to put the key-value pair into
+ * @param key the key to put into the section
+ * @param value the value to put into the section
+ */
+ public void put(String section, String key, Object value) {
+ put(section, key, value);
+ }
+
+ /**
+ * Save the in-memory ini file to the specified file.
+ *
+ * @param fname
+ * @throws IOException
+ */
+ public synchronized void save(String fname) throws IOException {
+ try (BufferedWriter bw = new BufferedWriter(new FileWriter(fname))) {
+ for (String section : sections.keySet()) {
+ if (section == null && sections.get(section).isEmpty())
+ continue;
+ bw.write("[" + (section == null ? "" : section) + "]\n\n");
+ for (String key : sections.get(section).keySet()) {
+ String val = sections.get(section).get(key);
+ bw.write(key + "=" + (val == null ? "" : val) + "\n");
+ }
+ bw.write("\n");
+ }
+ }
+ }
+
+ /**
+ * Save the in-memory ini file to file it was read from.
+ *
+ * @throws IOException
+ */
+ public void save() throws IOException {
+ save(filename);
+ }
+
+ /**
+ * Removes the specified key from the specified section of the ini file.
+ *
+ * @param section the section to remove the key from
+ * @param key the key to remove
+ */
+ public synchronized void removeValue(String section, String key) {
+ HashMap s = sections.get(section);
+ if (s == null)
+ return;
+ s.put(key, null);
+ }
+
+ /**
+ * Removes the specified key/value pair from the specified section of the ini file.
+ *
+ * @param section the section to remove the key from
+ * @param key the key to remove
+ */
+ public synchronized void removeKey(String section, String key) {
+ HashMap s = sections.get(section);
+ if (s == null)
+ return;
+ s.remove(key);
+ }
+
+ /**
+ * Retrieves the string value of a key from the ini file from the null section.
+ *
+ * @param key the key to search for
+ * @return the value of the key if found, null otherwise
+ */
+ public String get(String key) {
+ return get(null, key);
+ }
+
+ /**
+ * Retrieves the Integer value of a key from the ini file from the null section.
+ *
+ * @param key the key to search for
+ * @return the value of the key if found, null otherwise
+ */
+ public Integer getInt(String key) {
+ return getInt(null, key);
+ }
+
+ /**
+ * Retrieves the Character value of a key from the ini file from the null section.
+ *
+ * @param key the key to search for
+ * @return the value of the key if found, null otherwise
+ */
+ public Character getChar(String key) {
+ return getChar(null, key);
+ }
+
+ /**
+ * Retrieves the Boolean value of a key from the ini file from the null section.
+ *
+ * @param key the key to search for
+ * @return the value of the key if found, null otherwise
+ */
+ public boolean getBoolean(String key) {
+ return getBoolean(null, key);
+ }
+
+ /**
+ * Retrieves the Double value of a key from the ini file from the null section.
+ *
+ * @param key the key to search for
+ * @return the value of the key if found, null otherwise
+ */
+ public Double getDouble(String key) {
+ return getDouble(null, key);
+ }
+
+ /**
+ * Retrieves the integer value representing a date as YYYYMMDD of a key from the ini file from the null section.
+ *
+ * @param key the key to search for
+ * @return the value of the key if found, null otherwise
+ */
+ public int getDateInt(String key) {
+ return getDateInt(null, key);
+ }
+
+ /**
+ * Retrieves the integer value representing a date as HHMM of a key from the ini file from the null section.
+ *
+ * @param key the key to search for
+ * @return the value of the key if found, null otherwise
+ */
+ public int getTimeInt(String key) {
+ return getTimeInt(null, key);
+ }
+
+ /**
+ * Puts a key-value pair into the ini file in the null section.
+ *
+ * @param key the key to put into the section
+ * @param value the value to put into the section
+ * @return
+ */
+ public void put(String key, String value) {
+ put(null, key, value);
+ }
+
+ /**
+ * Puts a key-value pair into the ini file in the null section.
+ *
+ * @param key the key to put into the section
+ * @param value the value to put into the section
+ * @return
+ */
+ public void put(String key, int value) {
+ put(null, key, value);
+ }
+
+ /**
+ * Puts a key-value pair into the ini file in the null section.
+ *
+ * @param key the key to put into the section
+ * @param value the value to put into the section
+ * @return
+ */
+ public void put(String key, double value) {
+ put(null, key, value);
+ }
+
+ /**
+ * Puts a key-value pair into the ini file in the null section.
+ *
+ * @param key the key to put into the section
+ * @param value the value to put into the section
+ * @return
+ */
+ public void put(String key, boolean value) {
+ put(null, key, value);
+ }
+
+ /**
+ * Puts a key-value pair into the ini file in the null section.
+ *
+ * @param key the key to put into the section
+ * @param value the value to put into the section
+ * @return
+ */
+ public void put(String key, char value) {
+ put(null, key, value);
+ }
+
+ /**
+ * Puts a key-value pair into the ini file in the null section.
+ *
+ * @param key the key to put into the section
+ * @param value the value to put into the section
+ * @return
+ */
+ public void put(String key, long value) {
+ put(null, key, value);
+ }
+
+ /**
+ * Puts a key-value pair into the ini file in the null section.
+ *
+ * @param key the key to put into the section
+ * @param value the value to put into the section
+ * @return
+ */
+ public void put(String key, Date value) {
+ put(null, key, value);
+ }
+
+ /**
+ * Puts a key-value pair into the ini file in the null section.
+ *
+ * @param key the key to put into the section
+ * @param value the value to put into the section
+ * @return
+ */
+ public void put(String key, Object value) {
+ put(null, key, value);
+ }
+
+ /**
+ * Removes the value associated with the given key from the ini file in the null section.
+ *
+ * @param key the key to remove the value for
+ * @return
+ */
+ public void removeValue(String key) {
+ removeValue(null, key);
+ }
+
+ /**
+ * Removes the key-value pair associated with the given key from the ini file in the null section.
+ *
+ * @param key the key to remove the value for
+ * @return
+ */
+ public void removeKey(String key) {
+ removeKey(null, key);
+ }
+
+ /**
+ * This test program can be run from the command-line as follows: