aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore4
-rw-r--r--LICENSE21
-rwxr-xr-xMakefile36
-rw-r--r--README.md58
-rw-r--r--examples/incorrect.ini21
-rw-r--r--examples/ini_file_create.c80
-rw-r--r--examples/ini_file_read.c41
-rw-r--r--examples/ini_file_search.c60
-rw-r--r--examples/openssl.cnf397
-rw-r--r--examples/testing.ini7
-rw-r--r--ini_file.c478
-rw-r--r--ini_file.h123
12 files changed, 1326 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b04d881
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+.vscode/
+ini_file_read
+ini_file_search
+ini_file_create \ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..0a6ed88
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 CLECIO JUNG <clecio.jung@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100755
index 0000000..134d617
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,36 @@
+# ----------------------------------------
+# Compiler and linker options
+# ----------------------------------------
+
+# Name of the executables to be generated
+EXEC := examples/ini_file_read \
+ examples/ini_file_search \
+ examples/ini_file_create
+
+# Library files
+LIB_FILES := ini_file.c ini_file.h
+
+# Flags for compiler
+CFLAGS := -W -Wall -Wextra -pedantic -Wconversion -Werror -flto -std=c89 -O2
+
+# ----------------------------------------
+# Compilation and linking rules
+# ----------------------------------------
+
+all: $(EXEC)
+
+%: %.c $(LIB_FILES) Makefile
+ $(CC) $(filter %.c,$^) -o $@ $(CFLAGS)
+
+# ----------------------------------------
+# Script rules
+# ----------------------------------------
+
+clean:
+ $(RM) $(EXEC)
+
+remade: clean all
+
+.PHONY: all clean remade
+
+# ----------------------------------------
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..76abf6d
--- /dev/null
+++ b/README.md
@@ -0,0 +1,58 @@
+# ini
+
+## Overview
+
+**ini** is a simple C library for parsing and creating [INI files](https://en.wikipedia.org/wiki/INI_file). It possesses the following characteristics:
+
+- Compatible with C89
+- Has no external dependencies
+
+INI files are commonly used to store configuration data, and our library makes it easy to read and write these files in C programs.
+
+## Table of Contents
+
+- [Installation](#installation)
+- [Usage](#usage)
+- [Examples](#examples)
+- [License](#license)
+
+## Installation
+
+To use this library in your own project, simply download the source code from the repository and link the `ini.c` and `ini.h` files with your project.
+
+## Usage
+
+This library provides a set of functions for reading and writing INI files. Here's an example of how to read an INI file:
+
+```c
+#include <stdio.h>
+#include <stdlib.h>
+#include "ini_file.h"
+
+int main(const int argc, const char **const argv) {
+ struct Ini_File *ini_file;
+ if (argc < 2) {
+ fprintf(stderr, "Usage: %s ini_file_name\n", argv[0]);
+ return EXIT_FAILURE;
+ }
+ ini_file = ini_file_parse(argv[1], NULL);
+ if (ini_file == NULL) {
+ fprintf(stderr, "Was not possible to parse the ini_file \"%s\"\n", argv[1]);
+ return EXIT_FAILURE;
+ }
+ printf("\nThe properties retrieved from the the ini file \"%s\" are:\n\n", argv[1]);
+ ini_file_print_to(ini_file, stdout);
+ ini_file_free(ini_file);
+ return EXIT_SUCCESS;
+}
+```
+
+For a complete list of functions and their documentation, see the `ini.h` header file.
+
+## Examples
+
+In the examples folder, you can find complete examples of how to use the library. To compile them, simply type `make` at your terminal. Run the executables and follow the instructions provided.
+
+## License
+
+This project is released under the MIT license. See the LICENSE file for more information.
diff --git a/examples/incorrect.ini b/examples/incorrect.ini
new file mode 100644
index 0000000..317ce8a
--- /dev/null
+++ b/examples/incorrect.ini
@@ -0,0 +1,21 @@
+ # Test a comment
+ # a = 5
+a = 1
+ ; Test another type of comment
+ ; b = 3
+ b = 2 ; comment
+ c = a+b # comment
+
+[ section 1 ]
+test=2
+beta=96
+
+[ section 2 ]
+test2 = 65
+a = ; 15
+
+alpha = beta
+
+[section 3
+
+[section 4] dfg \ No newline at end of file
diff --git a/examples/ini_file_create.c b/examples/ini_file_create.c
new file mode 100644
index 0000000..8910e9f
--- /dev/null
+++ b/examples/ini_file_create.c
@@ -0,0 +1,80 @@
+/*------------------------------------------------------------------------------
+ * SOURCE
+ *------------------------------------------------------------------------------
+ */
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "../ini_file.h"
+
+#define MAX_STRING_SIZE 1024
+
+/* This function reads a string of size MAX_STRING_SIZE from stdin.
+ * It returns 0 if no valid string was retrieved. */
+ int get_string_from_stdin(const char *const prompt, char *string) {
+ int i, empty = 0;
+ fputs(prompt, stdout);
+ if (fgets(string, MAX_STRING_SIZE, stdin) == NULL) {
+ return 0;
+ }
+ /* Replace \n characters for \0 and check if string is empty (only composed of spaces) */
+ for (i = 0; string[i] != '\0'; i++) {
+ if (string[i] == '\n') {
+ string[i] = '\0';
+ break;
+ }
+ if (!isspace(string[i])) {
+ empty = 1;
+ }
+ }
+ return empty;
+ }
+
+/*------------------------------------------------------------------------------
+ * MAIN
+ *------------------------------------------------------------------------------
+ */
+
+int main(void) {
+ char filename[MAX_STRING_SIZE], key[MAX_STRING_SIZE], value[MAX_STRING_SIZE];
+ /* The first section is global to the file */
+ char section[MAX_STRING_SIZE] = "global";
+ struct Ini_File *ini_file = ini_file_new();
+ /* Instruction on how to use this application */
+ printf("Following, type the requested fields of keys, values and section names.\n");
+ printf("If you wish to create a new section, enter a empty key.\n");
+ printf("If you wish to end the file, enter a empty section name.\n\n");
+ while (1) {
+ while (get_string_from_stdin("key: ", key)) {
+ if (!get_string_from_stdin("value: ", value)) {
+ continue;
+ }
+ ini_file_add_property(ini_file, key, value);
+ }
+ if (!get_string_from_stdin("\nsection: ", section)) {
+ break;
+ }
+ ini_file_add_section(ini_file, section);
+ }
+ if (get_string_from_stdin("\nPlease type the filename: ", filename)) {
+ if (ini_file_save(ini_file, filename) == 0) {
+ printf("The typed properties were saved to the file %s\n", filename);
+ } else {
+ fprintf(stderr, "It was not possible to save the typed properties to the file %s\n", filename);
+ ini_file_free(ini_file);
+ return EXIT_FAILURE;
+ }
+ } else {
+ printf("The typed properties are:\n\n");
+ ini_file_print_to(ini_file, stdout);
+ }
+ ini_file_free(ini_file);
+ return EXIT_SUCCESS;
+}
+
+/*------------------------------------------------------------------------------
+ * END
+ *------------------------------------------------------------------------------
+ */
diff --git a/examples/ini_file_read.c b/examples/ini_file_read.c
new file mode 100644
index 0000000..7752f36
--- /dev/null
+++ b/examples/ini_file_read.c
@@ -0,0 +1,41 @@
+/*------------------------------------------------------------------------------
+ * SOURCE
+ *------------------------------------------------------------------------------
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "../ini_file.h"
+
+int error_callback(const char *const filename, const size_t line_number, const char *const line, const enum Ini_File_Errors error) {
+ fprintf(stderr, "%s:%lu %s:\n%s\n", filename, line_number, ini_file_error_to_string(error), line);
+ return 0;
+}
+
+/*------------------------------------------------------------------------------
+ * MAIN
+ *------------------------------------------------------------------------------
+ */
+
+int main(const int argc, const char **const argv) {
+ struct Ini_File *ini_file;
+ if (argc < 2) {
+ fprintf(stderr, "Usage: %s ini_file_name\n", argv[0]);
+ return EXIT_FAILURE;
+ }
+ ini_file = ini_file_parse(argv[1], error_callback);
+ if (ini_file == NULL) {
+ fprintf(stderr, "Was not possible to parse the ini_file \"%s\"\n", argv[1]);
+ return EXIT_FAILURE;
+ }
+ printf("\nThe properties retrieved from the the ini file \"%s\" are:\n\n", argv[1]);
+ ini_file_print_to(ini_file, stdout);
+ ini_file_free(ini_file);
+ return EXIT_SUCCESS;
+}
+
+/*------------------------------------------------------------------------------
+ * END
+ *------------------------------------------------------------------------------
+ */
diff --git a/examples/ini_file_search.c b/examples/ini_file_search.c
new file mode 100644
index 0000000..43d3ccc
--- /dev/null
+++ b/examples/ini_file_search.c
@@ -0,0 +1,60 @@
+/*------------------------------------------------------------------------------
+ * SOURCE
+ *------------------------------------------------------------------------------
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "../ini_file.h"
+
+/*------------------------------------------------------------------------------
+ * MAIN
+ *------------------------------------------------------------------------------
+ */
+
+int main(const int argc, const char **const argv) {
+ enum Ini_File_Errors error;
+ struct Ini_File *ini_file;
+ struct Ini_Section *ini_section;
+ char *value;
+ if ((argc < 2) || (argc > 4)) {
+ fprintf(stderr, "Usage: %s ini_file_name [section] [key]\n", argv[0]);
+ return EXIT_FAILURE;
+ }
+ ini_file = ini_file_parse(argv[1], NULL);
+ if (ini_file == NULL) {
+ fprintf(stderr, "It was not possible to parse the ini_file \"%s\"\n", argv[1]);
+ return EXIT_FAILURE;
+ }
+ switch (argc) {
+ case 2:
+ ini_file_print_to(ini_file, stdout);
+ break;
+ case 3:
+ error = ini_file_find_section(ini_file, argv[2], &ini_section);
+ if (error != ini_no_error) {
+ fprintf(stderr, "%s\n", ini_file_error_to_string(error));
+ ini_file_free(ini_file);
+ return EXIT_FAILURE;
+ }
+ ini_section_print_to(ini_section, stdout);
+ break;
+ case 4:
+ error = ini_file_find_property(ini_file, argv[2], argv[3], &value);
+ if (error != ini_no_error) {
+ fprintf(stderr, "%s\n", ini_file_error_to_string(error));
+ ini_file_free(ini_file);
+ return EXIT_FAILURE;
+ }
+ puts(value);
+ break;
+ }
+ ini_file_free(ini_file);
+ return EXIT_SUCCESS;
+}
+
+/*------------------------------------------------------------------------------
+ * END
+ *------------------------------------------------------------------------------
+ */
diff --git a/examples/openssl.cnf b/examples/openssl.cnf
new file mode 100644
index 0000000..179dc1f
--- /dev/null
+++ b/examples/openssl.cnf
@@ -0,0 +1,397 @@
+#
+# OpenSSL example configuration file.
+# See doc/man5/config.pod for more info.
+#
+# This is mostly being used for generation of certificate requests,
+# but may be used for auto loading of providers
+
+# Note that you can include other files from the main configuration
+# file using the .include directive.
+#.include filename
+
+# This definition stops the following lines choking if HOME isn't
+# defined.
+HOME = .
+
+# Use this in order to automatically load providers.
+openssl_conf = openssl_init
+
+# Comment out the next line to ignore configuration errors
+config_diagnostics = 1
+
+# Extra OBJECT IDENTIFIER info:
+# oid_file = $ENV::HOME/.oid
+oid_section = new_oids
+
+# To use this configuration file with the "-extfile" option of the
+# "openssl x509" utility, name here the section containing the
+# X.509v3 extensions to use:
+# extensions =
+# (Alternatively, use a configuration file that has only
+# X.509v3 extensions in its main [= default] section.)
+
+[ new_oids ]
+# We can add new OIDs in here for use by 'ca', 'req' and 'ts'.
+# Add a simple OID like this:
+# testoid1=1.2.3.4
+# Or use config file substitution like this:
+# testoid2=${testoid1}.5.6
+
+# Policies used by the TSA examples.
+tsa_policy1 = 1.2.3.4.1
+tsa_policy2 = 1.2.3.4.5.6
+tsa_policy3 = 1.2.3.4.5.7
+
+# For FIPS
+# Optionally include a file that is generated by the OpenSSL fipsinstall
+# application. This file contains configuration data required by the OpenSSL
+# fips provider. It contains a named section e.g. [fips_sect] which is
+# referenced from the [provider_sect] below.
+# Refer to the OpenSSL security policy for more information.
+# .include fipsmodule.cnf
+
+[openssl_init]
+providers = provider_sect
+
+# List of providers to load
+[provider_sect]
+default = default_sect
+# The fips section name should match the section name inside the
+# included fipsmodule.cnf.
+# fips = fips_sect
+
+# If no providers are activated explicitly, the default one is activated implicitly.
+# See man 7 OSSL_PROVIDER-default for more details.
+#
+# If you add a section explicitly activating any other provider(s), you most
+# probably need to explicitly activate the default provider, otherwise it
+# becomes unavailable in openssl. As a consequence applications depending on
+# OpenSSL may not work correctly which could lead to significant system
+# problems including inability to remotely access the system.
+[default_sect]
+# activate = 1
+
+
+####################################################################
+[ ca ]
+default_ca = CA_default # The default ca section
+
+####################################################################
+[ CA_default ]
+
+dir = ./demoCA # Where everything is kept
+certs = $dir/certs # Where the issued certs are kept
+crl_dir = $dir/crl # Where the issued crl are kept
+database = $dir/index.txt # database index file.
+#unique_subject = no # Set to 'no' to allow creation of
+ # several certs with same subject.
+new_certs_dir = $dir/newcerts # default place for new certs.
+
+certificate = $dir/cacert.pem # The CA certificate
+serial = $dir/serial # The current serial number
+crlnumber = $dir/crlnumber # the current crl number
+ # must be commented out to leave a V1 CRL
+crl = $dir/crl.pem # The current CRL
+private_key = $dir/private/cakey.pem # The private key
+
+x509_extensions = usr_cert # The extensions to add to the cert
+
+# Comment out the following two lines for the "traditional"
+# (and highly broken) format.
+name_opt = ca_default # Subject Name options
+cert_opt = ca_default # Certificate field options
+
+# Extension copying option: use with caution.
+# copy_extensions = copy
+
+# Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs
+# so this is commented out by default to leave a V1 CRL.
+# crlnumber must also be commented out to leave a V1 CRL.
+# crl_extensions = crl_ext
+
+default_days = 365 # how long to certify for
+default_crl_days= 30 # how long before next CRL
+default_md = default # use public key default MD
+preserve = no # keep passed DN ordering
+
+# A few difference way of specifying how similar the request should look
+# For type CA, the listed attributes must be the same, and the optional
+# and supplied fields are just that :-)
+policy = policy_match
+
+# For the CA policy
+[ policy_match ]
+countryName = match
+stateOrProvinceName = match
+organizationName = match
+organizationalUnitName = optional
+commonName = supplied
+emailAddress = optional
+
+# For the 'anything' policy
+# At this point in time, you must list all acceptable 'object'
+# types.
+[ policy_anything ]
+countryName = optional
+stateOrProvinceName = optional
+localityName = optional
+organizationName = optional
+organizationalUnitName = optional
+commonName = supplied
+emailAddress = optional
+
+####################################################################
+[ req ]
+default_bits = 2048
+default_keyfile = privkey.pem
+distinguished_name = req_distinguished_name
+attributes = req_attributes
+x509_extensions = v3_ca # The extensions to add to the self signed cert
+
+# Passwords for private keys if not present they will be prompted for
+# input_password = secret
+# output_password = secret
+
+# This sets a mask for permitted string types. There are several options.
+# default: PrintableString, T61String, BMPString.
+# pkix : PrintableString, BMPString (PKIX recommendation before 2004)
+# utf8only: only UTF8Strings (PKIX recommendation after 2004).
+# nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings).
+# MASK:XXXX a literal mask value.
+# WARNING: ancient versions of Netscape crash on BMPStrings or UTF8Strings.
+string_mask = utf8only
+
+# req_extensions = v3_req # The extensions to add to a certificate request
+
+[ req_distinguished_name ]
+countryName = Country Name (2 letter code)
+countryName_default = AU
+countryName_min = 2
+countryName_max = 2
+
+stateOrProvinceName = State or Province Name (full name)
+stateOrProvinceName_default = Some-State
+
+localityName = Locality Name (eg, city)
+
+0.organizationName = Organization Name (eg, company)
+0.organizationName_default = Internet Widgits Pty Ltd
+
+# we can do this but it is not needed normally :-)
+#1.organizationName = Second Organization Name (eg, company)
+#1.organizationName_default = World Wide Web Pty Ltd
+
+organizationalUnitName = Organizational Unit Name (eg, section)
+#organizationalUnitName_default =
+
+commonName = Common Name (e.g. server FQDN or YOUR name)
+commonName_max = 64
+
+emailAddress = Email Address
+emailAddress_max = 64
+
+# SET-ex3 = SET extension number 3
+
+[ req_attributes ]
+challengePassword = A challenge password
+challengePassword_min = 4
+challengePassword_max = 20
+
+unstructuredName = An optional company name
+
+[ usr_cert ]
+
+# These extensions are added when 'ca' signs a request.
+
+# This goes against PKIX guidelines but some CAs do it and some software
+# requires this to avoid interpreting an end user certificate as a CA.
+
+basicConstraints=CA:FALSE
+
+# This is typical in keyUsage for a client certificate.
+# keyUsage = nonRepudiation, digitalSignature, keyEncipherment
+
+# PKIX recommendations harmless if included in all certificates.
+subjectKeyIdentifier=hash
+authorityKeyIdentifier=keyid,issuer
+
+# This stuff is for subjectAltName and issuerAltname.
+# Import the email address.
+# subjectAltName=email:copy
+# An alternative to produce certificates that aren't
+# deprecated according to PKIX.
+# subjectAltName=email:move
+
+# Copy subject details
+# issuerAltName=issuer:copy
+
+# This is required for TSA certificates.
+# extendedKeyUsage = critical,timeStamping
+
+[ v3_req ]
+
+# Extensions to add to a certificate request
+
+basicConstraints = CA:FALSE
+keyUsage = nonRepudiation, digitalSignature, keyEncipherment
+
+[ v3_ca ]
+
+
+# Extensions for a typical CA
+
+
+# PKIX recommendation.
+
+subjectKeyIdentifier=hash
+
+authorityKeyIdentifier=keyid:always,issuer
+
+basicConstraints = critical,CA:true
+
+# Key usage: this is typical for a CA certificate. However since it will
+# prevent it being used as an test self-signed certificate it is best
+# left out by default.
+# keyUsage = cRLSign, keyCertSign
+
+# Include email address in subject alt name: another PKIX recommendation
+# subjectAltName=email:copy
+# Copy issuer details
+# issuerAltName=issuer:copy
+
+# DER hex encoding of an extension: beware experts only!
+# obj=DER:02:03
+# Where 'obj' is a standard or added object
+# You can even override a supported extension:
+# basicConstraints= critical, DER:30:03:01:01:FF
+
+[ crl_ext ]
+
+# CRL extensions.
+# Only issuerAltName and authorityKeyIdentifier make any sense in a CRL.
+
+# issuerAltName=issuer:copy
+authorityKeyIdentifier=keyid:always
+
+[ proxy_cert_ext ]
+# These extensions should be added when creating a proxy certificate
+
+# This goes against PKIX guidelines but some CAs do it and some software
+# requires this to avoid interpreting an end user certificate as a CA.
+
+basicConstraints=CA:FALSE
+
+# This is typical in keyUsage for a client certificate.
+# keyUsage = nonRepudiation, digitalSignature, keyEncipherment
+
+# PKIX recommendations harmless if included in all certificates.
+subjectKeyIdentifier=hash
+authorityKeyIdentifier=keyid,issuer
+
+# This stuff is for subjectAltName and issuerAltname.
+# Import the email address.
+# subjectAltName=email:copy
+# An alternative to produce certificates that aren't
+# deprecated according to PKIX.
+# subjectAltName=email:move
+
+# Copy subject details
+# issuerAltName=issuer:copy
+
+# This really needs to be in place for it to be a proxy certificate.
+proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo
+
+####################################################################
+[ tsa ]
+
+default_tsa = tsa_config1 # the default TSA section
+
+[ tsa_config1 ]
+
+# These are used by the TSA reply generation only.
+dir = ./demoCA # TSA root directory
+serial = $dir/tsaserial # The current serial number (mandatory)
+crypto_device = builtin # OpenSSL engine to use for signing
+signer_cert = $dir/tsacert.pem # The TSA signing certificate
+ # (optional)
+certs = $dir/cacert.pem # Certificate chain to include in reply
+ # (optional)
+signer_key = $dir/private/tsakey.pem # The TSA private key (optional)
+signer_digest = sha256 # Signing digest to use. (Optional)
+default_policy = tsa_policy1 # Policy if request did not specify it
+ # (optional)
+other_policies = tsa_policy2, tsa_policy3 # acceptable policies (optional)
+digests = sha1, sha256, sha384, sha512 # Acceptable message digests (mandatory)
+accuracy = secs:1, millisecs:500, microsecs:100 # (optional)
+clock_precision_digits = 0 # number of digits after dot. (optional)
+ordering = yes # Is ordering defined for timestamps?
+ # (optional, default: no)
+tsa_name = yes # Must the TSA name be included in the reply?
+ # (optional, default: no)
+ess_cert_id_chain = no # Must the ESS cert id chain be included?
+ # (optional, default: no)
+ess_cert_id_alg = sha1 # algorithm to compute certificate
+ # identifier (optional, default: sha1)
+
+[insta] # CMP using Insta Demo CA
+# Message transfer
+server = pki.certificate.fi:8700
+# proxy = # set this as far as needed, e.g., http://192.168.1.1:8080
+# tls_use = 0
+path = pkix/
+
+# Server authentication
+recipient = "/C=FI/O=Insta Demo/CN=Insta Demo CA" # or set srvcert or issuer
+ignore_keyusage = 1 # potentially needed quirk
+unprotected_errors = 1 # potentially needed quirk
+extracertsout = insta.extracerts.pem
+
+# Client authentication
+ref = 3078 # user identification
+secret = pass:insta # can be used for both client and server side
+
+# Generic message options
+cmd = ir # default operation, can be overridden on cmd line with, e.g., kur
+
+# Certificate enrollment
+subject = "/CN=openssl-cmp-test"
+newkey = insta.priv.pem
+out_trusted = insta.ca.crt
+certout = insta.cert.pem
+
+[pbm] # Password-based protection for Insta CA
+# Server and client authentication
+ref = $insta::ref # 3078
+secret = $insta::secret # pass:insta
+
+[signature] # Signature-based protection for Insta CA
+# Server authentication
+trusted = insta.ca.crt # does not include keyUsage digitalSignature
+
+# Client authentication
+secret = # disable PBM
+key = $insta::newkey # insta.priv.pem
+cert = $insta::certout # insta.cert.pem
+
+[ir]
+cmd = ir
+
+[cr]
+cmd = cr
+
+[kur]
+# Certificate update
+cmd = kur
+oldcert = $insta::certout # insta.cert.pem
+
+[rr]
+# Certificate revocation
+cmd = rr
+oldcert = $insta::certout # insta.cert.pem
+
+[pkcs12]
+certBagAttr = cb_attr
+
+# Uncomment this if you need Java compatible PKCS12 files
+[cb_attr]
+#jdkTrustedKeyUsage = anyExtendedKeyUsage \ No newline at end of file
diff --git a/examples/testing.ini b/examples/testing.ini
new file mode 100644
index 0000000..0b07bcc
--- /dev/null
+++ b/examples/testing.ini
@@ -0,0 +1,7 @@
+alpha = 89
+beta = 48
+gamma = 25
+
+[section]
+a = 12
+b = 15
diff --git a/ini_file.c b/ini_file.c
new file mode 100644
index 0000000..f844ec6
--- /dev/null
+++ b/ini_file.c
@@ -0,0 +1,478 @@
+
+/*------------------------------------------------------------------------------
+ * SOURCE
+ *------------------------------------------------------------------------------
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "ini_file.h"
+
+/* Most systems do not allow for a line greather than 4 kbytes */
+#define MAX_LINE_SIZE 4096
+
+#define INITIAL_NUMBER_OF_SECTIONS 10
+#define INITIAL_NUMBER_OF_PROPERTIES 10
+
+size_t get_file_size(FILE *const file) {
+ long file_size;
+ fseek(file, 0, SEEK_END);
+ file_size = ftell(file);
+ fseek(file, 0, SEEK_SET);
+ return (size_t)file_size;
+}
+
+/* Remember to free the memory allocated for the returned string */
+char *get_content_from_file(const char *const file_name) {
+ char *buffer = NULL;
+ FILE *const file = fopen(file_name, "rb");
+ if (file != NULL) {
+ const size_t file_size = get_file_size(file);
+ /* Allocate memory to store the entire file */
+ buffer = (char *)malloc((file_size + 1)*sizeof(char));
+ if (buffer != NULL) {
+ /* Copy the contents of the file to the buffer */
+ const size_t result = fread(buffer, sizeof(char), file_size, file);
+ buffer[file_size] = '\0';
+ if (result != file_size) {
+ /* Reading file error, free dinamically allocated memory */
+ free((void *)buffer);
+ buffer = NULL;
+ }
+ }
+ fclose(file);
+ }
+ return buffer;
+}
+
+struct Ini_File *ini_file_new(void) {
+ struct Ini_File *ini_file = malloc(sizeof(struct Ini_File));
+ if (ini_file != NULL) {
+ ini_file->sections_size = 0;
+ ini_file->sections_capacity = INITIAL_NUMBER_OF_SECTIONS;
+ ini_file->sections = malloc(ini_file->sections_capacity * sizeof(struct Ini_Section));
+ if (ini_file->sections == NULL) {
+ free(ini_file);
+ return NULL;
+ }
+ /* The first section is global to the file */
+ if (ini_file_add_section(ini_file, "global") != 0) {
+ ini_file_free(ini_file);
+ return NULL;
+ }
+ }
+ return ini_file;
+}
+
+void ini_file_free(struct Ini_File *const ini_file) {
+ size_t i, j;
+ if (ini_file == NULL) {
+ return;
+ }
+ for (i = 0; i < ini_file->sections_size; i++) {
+ free((void *)ini_file->sections[i].name);
+ for (j = 0; j < ini_file->sections[i].properties_size; j++) {
+ free((void *)ini_file->sections[i].properties[j].key);
+ free((void *)ini_file->sections[i].properties[j].value);
+ }
+ free(ini_file->sections[i].properties);
+ ini_file->sections[i].properties_capacity = 0;
+ ini_file->sections[i].properties_size = 0;
+ }
+ free(ini_file->sections);
+ ini_file->sections_capacity = 0;
+ ini_file->sections_size = 0;
+ free((void *)ini_file);
+}
+
+void ini_section_print_to(const struct Ini_Section *const ini_section, FILE *const sink) {
+ size_t property_index;
+ if (ini_section == NULL) {
+ return;
+ }
+ fprintf(sink, "[%s]\n", ini_section->name);
+ for (property_index = 0; property_index < ini_section->properties_size; property_index++) {
+ fprintf(sink, "%s = %s\n", ini_section->properties[property_index].key, ini_section->properties[property_index].value);
+ }
+}
+
+void ini_file_print_to(const struct Ini_File *const ini_file, FILE *const sink) {
+ size_t section_index;
+ if (ini_file == NULL) {
+ return;
+ }
+ for (section_index = 0; section_index < ini_file->sections_size; section_index++) {
+ ini_section_print_to(&ini_file->sections[section_index], sink);
+ putchar('\n');
+ }
+}
+
+char *ini_file_error_to_string(const enum Ini_File_Errors error) {
+ static char *const error_messages[] = {
+ "No error has occured",
+ "Couldn't allocate more memory",
+ "Invalid parameters passed to the function",
+ "Couldn't open file",
+ "Expected closing square bracket ']'",
+ "Expected equals sign '='",
+ "Expected a value, but found a comment",
+ "Didn't found the requested section",
+ "Didn't found the requested property",
+ "The requested property is not a valid integer number",
+ "The requested property is not a valid floating point number",
+ };
+#ifdef _Static_assert
+ _Static_assert((NUMBER_OF_INI_FILE_ERRORS == (sizeof(error_messages)/sizeof(error_messages[0]))),
+ "Don't forget to add the messages to this table");
+#endif
+ return error_messages[error];
+}
+
+static void advance_white_spaces(char **const str) {
+ while (isspace((unsigned char) **str)) {
+ (*str)++;
+ }
+}
+
+static void advance_string_until(char **const str, const char *const chars) {
+ while (strchr(chars, **str) == NULL) {
+ (*str)++;
+ }
+}
+
+/* TODO: We can have a large buffer of memory and store each new string in there */
+static char *copy_sized_string(const char *const sized_str, const size_t len) {
+ char *str = malloc(len + 1);
+ if (str != NULL) {
+ strncpy(str, sized_str, len);
+ str[len] = '\0';
+ }
+ return str;
+}
+
+static int ini_file_parse_handle_error(Ini_File_Error_Callback callback, const char *const filename, const size_t line_number, const char *const line, const enum Ini_File_Errors error) {
+ /* This function is called when we found an error in the parsing.
+ * So, we report it to the user using the callback provided.
+ * If the callback returns an integer different from zero,
+ * we end the parsing and return NULL. */
+ if (callback != NULL) {
+ return callback(filename, line_number, line, error);
+ }
+ return 0;
+}
+
+/* TODO: Check for repeated section and key names? */
+/* TODO: Check for spaces inside of section and key names? */
+/* TODO: Sort the sections and keys? This would allow us to use binary search */
+/* TODO: we could allow keys and values to be strings delimited by "" or '',
+ * which would allow us to use the characters "=,#,;" inside keys and values */
+
+/* Remember to free the memory allocated for the returned ini file structure */
+struct Ini_File *ini_file_parse(const char *const filename, Ini_File_Error_Callback callback) {
+ enum Ini_File_Errors error;
+ char line[MAX_LINE_SIZE];
+ size_t line_number;
+ FILE *file;
+ struct Ini_File *ini_file = ini_file_new();
+ if (ini_file == NULL) {
+ ini_file_parse_handle_error(callback, filename, 0, NULL, ini_allocation);
+ return NULL;
+ }
+ file = fopen(filename, "rb");
+ if (file == NULL) {
+ ini_file_parse_handle_error(callback, filename, 0, NULL, ini_couldnt_open_file);
+ ini_file_free(ini_file);
+ return NULL;
+ }
+ for (line_number = 1; fgets(line, sizeof(line), file) != NULL; line_number++) {
+ char *cursor = line;
+ char *key, *value;
+ size_t key_len, value_len;
+ advance_white_spaces(&cursor);
+ /* Discards commments */
+ if (strchr("#;", *cursor) != NULL) {
+ continue;
+ }
+ /* Check if is a new section */
+ if (*cursor == '[') {
+ size_t name_len;
+ char *name;
+ cursor++;
+ advance_white_spaces(&cursor);
+ name = cursor;
+ advance_string_until(&cursor, "]#;\n");
+ if (*cursor != ']') {
+ if (ini_file_parse_handle_error(callback, filename, line_number, line, ini_expected_clocing_bracket) != 0) {
+ goto ini_file_parse_error;
+ }
+ continue;
+ }
+ /* Compute length of the key string and remove trailing whitespaces */
+ name_len = (size_t)(cursor - name);
+ while ((name_len > 0) && (isspace((unsigned char)name[name_len - 1]))) {
+ name_len--;
+ }
+ error = ini_file_add_section_sized(ini_file, name, name_len);
+ if (error != ini_no_error) {
+ if (ini_file_parse_handle_error(callback, filename, line_number, line, error) != 0) {
+ goto ini_file_parse_error;
+ }
+ }
+ /* We just ignore the possible characters after the end of the declaration of the section */
+ continue;
+ }
+ key = cursor;
+ advance_string_until(&cursor, "=#;\n");
+ if (*cursor != '=') {
+ /* Found an error, so we report it to the user using the callback provided.
+ * In case of an error, if the callback returns an integer different from zero,
+ * we end the parsing and return NULL. */
+ if (ini_file_parse_handle_error(callback, filename, line_number, line, ini_expected_equals) != 0) {
+ goto ini_file_parse_error;
+ }
+ continue;
+ }
+ /* Compute length of the key string and remove trailing whitespaces */
+ key_len = (size_t)(cursor - key);
+ while ((key_len > 0) && (isspace((unsigned char)key[key_len - 1]))) {
+ key_len--;
+ }
+ cursor++;
+ advance_white_spaces(&cursor);
+ /* If it is a commment, we found an error */
+ if (strchr("#;", *cursor) != NULL) {
+ /* Found an error, so we report it to the user using the callback provided.
+ * In case of an error, if the callback returns an integer different from zero,
+ * we end the parsing and return NULL. */
+ if (ini_file_parse_handle_error(callback, filename, line_number, line, ini_expected_value_got_comment) != 0) {
+ goto ini_file_parse_error;
+ }
+ continue;
+ }
+ value = cursor;
+ advance_string_until(&cursor, "#;\n");
+ /* Compute length of the value string and remove trailing whitespaces */
+ value_len = (size_t)(cursor - value);
+ while ((value_len > 0) && (isspace((unsigned char)value[value_len - 1]))) {
+ value_len--;
+ }
+ error = ini_file_add_property_sized(ini_file, key, key_len, value, value_len);
+ if (error != ini_no_error) {
+ if (ini_file_parse_handle_error(callback, filename, line_number, line, error) != 0) {
+ goto ini_file_parse_error;
+ }
+ }
+ }
+ fclose(file);
+ return ini_file;
+ini_file_parse_error:
+ ini_file_free(ini_file);
+ fclose(file);
+ return NULL;
+}
+
+enum Ini_File_Errors ini_file_add_section_sized(struct Ini_File *const ini_file, const char *const name, const size_t name_len) {
+ struct Ini_Section *section;
+ if ((ini_file == NULL) || (name == NULL)) {
+ return ini_invalid_parameters;
+ }
+ if (name_len == 0) {
+ return ini_invalid_parameters;
+ }
+ if ((ini_file->sections_size + 1) >= ini_file->sections_capacity) {
+ const size_t new_cap = 2 * ini_file->sections_capacity;
+ struct Ini_Section *const new_sections = realloc(ini_file->sections, new_cap * sizeof(struct Ini_Section));
+ if (new_sections == NULL) {
+ return ini_allocation;
+ }
+ ini_file->sections = new_sections;
+ ini_file->sections_capacity = new_cap;
+ }
+ section = &ini_file->sections[ini_file->sections_size];
+ section->name = copy_sized_string(name, name_len);
+ if (section->name == NULL) {
+ return ini_allocation;
+ }
+ section->properties_size = 0;
+ section->properties_capacity = INITIAL_NUMBER_OF_PROPERTIES;
+ section->properties = malloc(section->properties_capacity * sizeof(struct Key_Value_Pair));
+ if (section->properties == NULL) {
+ free(section->name);
+ return ini_allocation;
+ }
+ ini_file->sections_size++;
+ return ini_no_error;
+}
+
+enum Ini_File_Errors ini_file_add_section(struct Ini_File *const ini_file, const char *const name) {
+ if (name == NULL) {
+ return ini_invalid_parameters;
+ }
+ return ini_file_add_section_sized(ini_file, name, strlen(name));
+}
+
+enum Ini_File_Errors ini_file_add_property_sized(struct Ini_File *const ini_file, const char *const key, const size_t key_len, const char *const value, const size_t value_len) {
+ struct Ini_Section *section;
+ struct Key_Value_Pair *property;
+ if ((ini_file == NULL) || (key == NULL) || (value == NULL)) {
+ return ini_invalid_parameters;
+ }
+ if ((key_len == 0) || (value_len == 0)) {
+ return ini_invalid_parameters;
+ }
+ if (ini_file->sections_size == 0) {
+ return ini_allocation;
+ }
+ /* Insert the new property at the last section */
+ section = &ini_file->sections[ini_file->sections_size - 1];
+ if ((section->properties_size + 1) >= section->properties_capacity) {
+ const size_t new_cap = 2 * section->properties_capacity;
+ struct Key_Value_Pair *const new_properties = realloc(section->properties, new_cap * sizeof(struct Key_Value_Pair));
+ if (new_properties == NULL) {
+ return ini_allocation;
+ }
+ section->properties = new_properties;
+ section->properties_capacity = new_cap;
+ }
+ property = &section->properties[section->properties_size];
+ property->key = copy_sized_string(key, key_len);
+ if (property->key == NULL) {
+ return ini_allocation;
+ }
+ property->value = copy_sized_string(value, value_len);
+ if (property->value == NULL) {
+ free(property->key);
+ return ini_allocation;
+ }
+ section->properties_size++;
+ return ini_no_error;
+}
+
+enum Ini_File_Errors ini_file_add_property(struct Ini_File *const ini_file, const char *const key, const char *const value) {
+ if ((key == NULL) || (value == NULL)) {
+ return ini_invalid_parameters;
+ }
+ return ini_file_add_property_sized(ini_file, key, strlen(key), value, strlen(value));
+}
+
+enum Ini_File_Errors ini_file_save(const struct Ini_File *const ini_file, const char *const filename) {
+ FILE *file;
+ if (ini_file == NULL) {
+ return ini_invalid_parameters;
+ }
+ file = fopen(filename, "wb");
+ if (file == NULL) {
+ return ini_couldnt_open_file;
+ }
+ ini_file_print_to(ini_file, file);
+ fclose(file);
+ return ini_no_error;
+}
+
+enum Ini_File_Errors ini_file_find_section(struct Ini_File *const ini_file, const char *const section, struct Ini_Section **ini_section) {
+ size_t section_index;
+ if ((ini_file == NULL) || (section == NULL) || (ini_section == NULL)) {
+ return ini_invalid_parameters;
+ }
+ if (strlen(section) == 0) {
+ return ini_invalid_parameters;
+ }
+ for (section_index = 0; section_index < ini_file->sections_size; section_index++) {
+ if (strcmp(ini_file->sections[section_index].name, section) == 0) {
+ *ini_section = &ini_file->sections[section_index];
+ return ini_no_error;
+ }
+ }
+ /* Didn't found the requested section */
+ return ini_no_such_section;
+}
+
+enum Ini_File_Errors ini_file_find_property(struct Ini_File *const ini_file, const char *const section, const char *const key, char **value) {
+ struct Ini_Section *ini_section;
+ size_t property_index;
+ enum Ini_File_Errors error;
+ if ((ini_file == NULL) || (section == NULL) || (key == NULL) || (value == NULL)) {
+ return ini_invalid_parameters;
+ }
+ if (strlen(key) == 0) {
+ return ini_invalid_parameters;
+ }
+ error = ini_file_find_section(ini_file, section, &ini_section);
+ if (error != ini_no_error) {
+ return error;
+ }
+ for (property_index = 0; property_index < ini_section->properties_size; property_index++) {
+ if (strcmp(ini_section->properties[property_index].key, key) == 0) {
+ *value = ini_section->properties[property_index].value;
+ return ini_no_error;
+ }
+ }
+ /* Didn't found the requested property */
+ return ini_no_such_property;
+}
+
+enum Ini_File_Errors ini_file_find_integer(struct Ini_File *const ini_file, const char *const section, const char *const key, long *integer) {
+ char *value, *end;
+ enum Ini_File_Errors error;
+ if (integer == NULL) {
+ return ini_invalid_parameters;
+ }
+ error = ini_file_find_property(ini_file, section, key, &value);
+ if (error != ini_no_error) {
+ return error;
+ }
+ *integer = strtol(value, &end, 10);
+ if (*end != '\0') {
+ return ini_not_integer;
+ }
+ return ini_no_error;
+}
+
+enum Ini_File_Errors ini_file_find_float(struct Ini_File *const ini_file, const char *const section, const char *const key, double *real) {
+ char *value, *end;
+ enum Ini_File_Errors error;
+ if (real == NULL) {
+ return ini_invalid_parameters;
+ }
+ error = ini_file_find_property(ini_file, section, key, &value);
+ if (error != ini_no_error) {
+ return error;
+ }
+ *real = strtod(value, &end);
+ if (*end != '\0') {
+ return ini_not_float;
+ }
+ return ini_no_error;
+}
+
+/*------------------------------------------------------------------------------
+ * END
+ *------------------------------------------------------------------------------
+ */
+
+/* MIT License
+ *
+ * Copyright (c) 2023 CLECIO JUNG <clecio.jung@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
diff --git a/ini_file.h b/ini_file.h
new file mode 100644
index 0000000..1790254
--- /dev/null
+++ b/ini_file.h
@@ -0,0 +1,123 @@
+/* MIT License
+ *
+ * Copyright (c) 2023 CLECIO JUNG <clecio.jung@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+/*------------------------------------------------------------------------------
+ * HEADER
+ *------------------------------------------------------------------------------
+ */
+
+#ifndef __INI_FILE
+#define __INI_FILE
+
+struct Key_Value_Pair {
+ char *key;
+ char *value;
+};
+
+struct Ini_Section {
+ char *name;
+ size_t properties_size;
+ size_t properties_capacity;
+ struct Key_Value_Pair *properties;
+};
+
+struct Ini_File {
+ size_t sections_size;
+ size_t sections_capacity;
+ struct Ini_Section *sections;
+};
+
+enum Ini_File_Errors {
+ ini_no_error = 0,
+ ini_allocation,
+ ini_invalid_parameters,
+ ini_couldnt_open_file,
+ ini_expected_clocing_bracket,
+ ini_expected_equals,
+ ini_expected_value_got_comment,
+ ini_no_such_section,
+ ini_no_such_property,
+ ini_not_integer,
+ ini_not_float,
+
+ NUMBER_OF_INI_FILE_ERRORS
+};
+
+/* Callback used to handle errors and warnings in the parsing of INI files (function ini_file_parse).
+ * In case of an error, this callback is called, and if it returns an integer different from zero,
+ * we end the parsing and return NULL. */
+typedef int (*Ini_File_Error_Callback)(const char *const filename, const size_t line_number, const char *const line, const enum Ini_File_Errors error);
+
+size_t get_file_size(FILE *const file);
+/* Remember to free the memory allocated for the returned string */
+char *get_content_from_file(const char *const file_name);
+
+struct Ini_File *ini_file_new(void);
+void ini_file_free(struct Ini_File *const ini_file);
+void ini_section_print_to(const struct Ini_Section *const ini_section, FILE *const sink);
+void ini_file_print_to(const struct Ini_File *const ini_file, FILE *const sink);
+char *ini_file_error_to_string(const enum Ini_File_Errors error);
+
+/* Remember to free the memory allocated for the returned ini file structure */
+struct Ini_File *ini_file_parse(const char *const filename, Ini_File_Error_Callback callback);
+
+/* These functions returns (ini_no_error = 0) if everything worked correctly */
+enum Ini_File_Errors ini_file_add_section_sized(struct Ini_File *const ini_file, const char *const name, const size_t name_len);
+enum Ini_File_Errors ini_file_add_section(struct Ini_File *const ini_file, const char *const name);
+enum Ini_File_Errors ini_file_add_property_sized(struct Ini_File *const ini_file, const char *const key, const size_t key_len, const char *const value, const size_t value_len);
+enum Ini_File_Errors ini_file_add_property(struct Ini_File *const ini_file, const char *const key, const char *const value);
+enum Ini_File_Errors ini_file_save(const struct Ini_File *const ini_file, const char *const filename);
+enum Ini_File_Errors ini_file_find_section(struct Ini_File *const ini_file, const char *const section, struct Ini_Section **ini_section);
+enum Ini_File_Errors ini_file_find_property(struct Ini_File *const ini_file, const char *const section, const char *const key, char **value);
+enum Ini_File_Errors ini_file_find_integer(struct Ini_File *const ini_file, const char *const section, const char *const key, long *integer);
+enum Ini_File_Errors ini_file_find_float(struct Ini_File *const ini_file, const char *const section, const char *const key, double *real);
+
+#endif /* __INI_FILE */
+
+/*------------------------------------------------------------------------------
+ * END
+ *------------------------------------------------------------------------------
+ */
+
+/* MIT License
+ *
+ * Copyright (c) 2023 CLECIO JUNG <clecio.jung@gmail.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */