diff options
| -rw-r--r-- | .gitignore | 4 | ||||
| -rw-r--r-- | LICENSE | 21 | ||||
| -rwxr-xr-x | Makefile | 36 | ||||
| -rw-r--r-- | README.md | 58 | ||||
| -rw-r--r-- | examples/incorrect.ini | 21 | ||||
| -rw-r--r-- | examples/ini_file_create.c | 80 | ||||
| -rw-r--r-- | examples/ini_file_read.c | 41 | ||||
| -rw-r--r-- | examples/ini_file_search.c | 60 | ||||
| -rw-r--r-- | examples/openssl.cnf | 397 | ||||
| -rw-r--r-- | examples/testing.ini | 7 | ||||
| -rw-r--r-- | ini_file.c | 478 | ||||
| -rw-r--r-- | ini_file.h | 123 |
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 @@ -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 = §ion->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. + */ |
