QUOTE: Life is a journey, not a destination.

rx

A terminal-based radio player

ini.h (14075B)


      1/* inih -- simple .INI file parser
      2
      3SPDX-License-Identifier: BSD-3-Clause
      4
      5Copyright (C) 2009-2020, Ben Hoyt
      6
      7inih is released under the New BSD license (see LICENSE.txt). Go to the project
      8home page for more info:
      9
     10https://github.com/benhoyt/inih
     11
     12*/
     13
     14#ifndef INI_H
     15#define INI_H
     16
     17/* Make this header file easier to include in C++ code */
     18#ifdef __cplusplus
     19extern "C" {
     20#endif
     21
     22#include <stdio.h>
     23
     24/* Nonzero if ini_handler callback should accept lineno parameter. */
     25#ifndef INI_HANDLER_LINENO
     26#define INI_HANDLER_LINENO 0
     27#endif
     28
     29/* Visibility symbols, required for Windows DLLs */
     30#ifndef INI_API
     31#if defined _WIN32 || defined __CYGWIN__
     32#	ifdef INI_SHARED_LIB
     33#		ifdef INI_SHARED_LIB_BUILDING
     34#			define INI_API __declspec(dllexport)
     35#		else
     36#			define INI_API __declspec(dllimport)
     37#		endif
     38#	else
     39#		define INI_API
     40#	endif
     41#else
     42#	if defined(__GNUC__) && __GNUC__ >= 4
     43#		define INI_API __attribute__ ((visibility ("default")))
     44#	else
     45#		define INI_API
     46#	endif
     47#endif
     48#endif
     49
     50/* Typedef for prototype of handler function. */
     51#if INI_HANDLER_LINENO
     52typedef int (*ini_handler)(void* user, const char* section,
     53                           const char* name, const char* value,
     54                           int lineno);
     55#else
     56typedef int (*ini_handler)(void* user, const char* section,
     57                           const char* name, const char* value);
     58#endif
     59
     60/* Typedef for prototype of fgets-style reader function. */
     61typedef char* (*ini_reader)(char* str, int num, void* stream);
     62
     63/* Parse given INI-style file. May have [section]s, name=value pairs
     64   (whitespace stripped), and comments starting with ';' (semicolon). Section
     65   is "" if name=value pair parsed before any section heading. name:value
     66   pairs are also supported as a concession to Python's configparser.
     67
     68   For each name=value pair parsed, call handler function with given user
     69   pointer as well as section, name, and value (data only valid for duration
     70   of handler call). Handler should return nonzero on success, zero on error.
     71
     72   Returns 0 on success, line number of first error on parse error (doesn't
     73   stop on first error), -1 on file open error, or -2 on memory allocation
     74   error (only when INI_USE_STACK is zero).
     75*/
     76INI_API int ini_parse(const char* filename, ini_handler handler, void* user);
     77
     78/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't
     79   close the file when it's finished -- the caller must do that. */
     80INI_API int ini_parse_file(FILE* file, ini_handler handler, void* user);
     81
     82/* Same as ini_parse(), but takes an ini_reader function pointer instead of
     83   filename. Used for implementing custom or string-based I/O (see also
     84   ini_parse_string). */
     85INI_API int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
     86                     void* user);
     87
     88/* Same as ini_parse(), but takes a zero-terminated string with the INI data
     89instead of a file. Useful for parsing INI data from a network socket or
     90already in memory. */
     91INI_API int ini_parse_string(const char* string, ini_handler handler, void* user);
     92
     93/* Nonzero to allow multi-line value parsing, in the style of Python's
     94   configparser. If allowed, ini_parse() will call the handler with the same
     95   name for each subsequent line parsed. */
     96#ifndef INI_ALLOW_MULTILINE
     97#define INI_ALLOW_MULTILINE 1
     98#endif
     99
    100/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of
    101   the file. See https://github.com/benhoyt/inih/issues/21 */
    102#ifndef INI_ALLOW_BOM
    103#define INI_ALLOW_BOM 1
    104#endif
    105
    106/* Chars that begin a start-of-line comment. Per Python configparser, allow
    107   both ; and # comments at the start of a line by default. */
    108#ifndef INI_START_COMMENT_PREFIXES
    109#define INI_START_COMMENT_PREFIXES ";#"
    110#endif
    111
    112/* Nonzero to allow inline comments (with valid inline comment characters
    113   specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match
    114   Python 3.2+ configparser behaviour. */
    115#ifndef INI_ALLOW_INLINE_COMMENTS
    116#define INI_ALLOW_INLINE_COMMENTS 1
    117#endif
    118#ifndef INI_INLINE_COMMENT_PREFIXES
    119#define INI_INLINE_COMMENT_PREFIXES ";"
    120#endif
    121
    122/* Nonzero to use stack for line buffer, zero to use heap (malloc/free). */
    123#ifndef INI_USE_STACK
    124#define INI_USE_STACK 1
    125#endif
    126
    127/* Maximum line length for any line in INI file (stack or heap). Note that
    128   this must be 3 more than the longest line (due to '\r', '\n', and '\0'). */
    129#ifndef INI_MAX_LINE
    130#define INI_MAX_LINE 200
    131#endif
    132
    133/* Nonzero to allow heap line buffer to grow via realloc(), zero for a
    134   fixed-size buffer of INI_MAX_LINE bytes. Only applies if INI_USE_STACK is
    135   zero. */
    136#ifndef INI_ALLOW_REALLOC
    137#define INI_ALLOW_REALLOC 0
    138#endif
    139
    140/* Initial size in bytes for heap line buffer. Only applies if INI_USE_STACK
    141   is zero. */
    142#ifndef INI_INITIAL_ALLOC
    143#define INI_INITIAL_ALLOC 200
    144#endif
    145
    146/* Stop parsing on first error (default is to keep parsing). */
    147#ifndef INI_STOP_ON_FIRST_ERROR
    148#define INI_STOP_ON_FIRST_ERROR 0
    149#endif
    150
    151/* Nonzero to call the handler at the start of each new section (with
    152   name and value NULL). Default is to only call the handler on
    153   each name=value pair. */
    154#ifndef INI_CALL_HANDLER_ON_NEW_SECTION
    155#define INI_CALL_HANDLER_ON_NEW_SECTION 0
    156#endif
    157
    158/* Nonzero to allow a name without a value (no '=' or ':' on the line) and
    159   call the handler with value NULL in this case. Default is to treat
    160   no-value lines as an error. */
    161#ifndef INI_ALLOW_NO_VALUE
    162#define INI_ALLOW_NO_VALUE 0
    163#endif
    164
    165/* Nonzero to use custom ini_malloc, ini_free, and ini_realloc memory
    166   allocation functions (INI_USE_STACK must also be 0). These functions must
    167   have the same signatures as malloc/free/realloc and behave in a similar
    168   way. ini_realloc is only needed if INI_ALLOW_REALLOC is set. */
    169#ifndef INI_CUSTOM_ALLOCATOR
    170#define INI_CUSTOM_ALLOCATOR 0
    171#endif
    172
    173
    174#ifdef __cplusplus
    175}
    176#endif
    177
    178#ifdef INI_IMPL
    179
    180#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
    181#define _CRT_SECURE_NO_WARNINGS
    182#endif
    183
    184#include <stdio.h>
    185#include <ctype.h>
    186#include <string.h>
    187
    188#include "ini.h"
    189
    190#if !INI_USE_STACK
    191#if INI_CUSTOM_ALLOCATOR
    192#include <stddef.h>
    193void* ini_malloc(size_t size);
    194void ini_free(void* ptr);
    195void* ini_realloc(void* ptr, size_t size);
    196#else
    197#include <stdlib.h>
    198#define ini_malloc malloc
    199#define ini_free free
    200#define ini_realloc realloc
    201#endif
    202#endif
    203
    204#define MAX_SECTION 50
    205#define MAX_NAME 50
    206
    207/* Used by ini_parse_string() to keep track of string parsing state. */
    208typedef struct {
    209    const char* ptr;
    210    size_t num_left;
    211} ini_parse_string_ctx;
    212
    213/* Strip whitespace chars off end of given string, in place. Return s. */
    214static char* ini_rstrip(char* s)
    215{
    216    char* p = s + strlen(s);
    217    while (p > s && isspace((unsigned char)(*--p)))
    218        *p = '\0';
    219    return s;
    220}
    221
    222/* Return pointer to first non-whitespace char in given string. */
    223static char* ini_lskip(const char* s)
    224{
    225    while (*s && isspace((unsigned char)(*s)))
    226        s++;
    227    return (char*)s;
    228}
    229
    230/* Return pointer to first char (of chars) or inline comment in given string,
    231   or pointer to NUL at end of string if neither found. Inline comment must
    232   be prefixed by a whitespace character to register as a comment. */
    233static char* ini_find_chars_or_comment(const char* s, const char* chars)
    234{
    235#if INI_ALLOW_INLINE_COMMENTS
    236    int was_space = 0;
    237    while (*s && (!chars || !strchr(chars, *s)) &&
    238           !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) {
    239        was_space = isspace((unsigned char)(*s));
    240        s++;
    241    }
    242#else
    243    while (*s && (!chars || !strchr(chars, *s))) {
    244        s++;
    245    }
    246#endif
    247    return (char*)s;
    248}
    249
    250/* Similar to strncpy, but ensures dest (size bytes) is
    251   NUL-terminated, and doesn't pad with NULs. */
    252static char* ini_strncpy0(char* dest, const char* src, size_t size)
    253{
    254    /* Could use strncpy internally, but it causes gcc warnings (see issue #91) */
    255    size_t i;
    256    for (i = 0; i < size - 1 && src[i]; i++)
    257        dest[i] = src[i];
    258    dest[i] = '\0';
    259    return dest;
    260}
    261
    262/* See documentation in header file. */
    263int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
    264                     void* user)
    265{
    266    /* Uses a fair bit of stack (use heap instead if you need to) */
    267#if INI_USE_STACK
    268    char line[INI_MAX_LINE];
    269    size_t max_line = INI_MAX_LINE;
    270#else
    271    char* line;
    272    size_t max_line = INI_INITIAL_ALLOC;
    273#endif
    274#if INI_ALLOW_REALLOC && !INI_USE_STACK
    275    char* new_line;
    276    size_t offset;
    277#endif
    278    char section[MAX_SECTION] = "";
    279#if INI_ALLOW_MULTILINE
    280    char prev_name[MAX_NAME] = "";
    281#endif
    282
    283    char* start;
    284    char* end;
    285    char* name;
    286    char* value;
    287    int lineno = 0;
    288    int error = 0;
    289
    290#if !INI_USE_STACK
    291    line = (char*)ini_malloc(INI_INITIAL_ALLOC);
    292    if (!line) {
    293        return -2;
    294    }
    295#endif
    296
    297#if INI_HANDLER_LINENO
    298#define HANDLER(u, s, n, v) handler(u, s, n, v, lineno)
    299#else
    300#define HANDLER(u, s, n, v) handler(u, s, n, v)
    301#endif
    302
    303    /* Scan through stream line by line */
    304    while (reader(line, (int)max_line, stream) != NULL) {
    305#if INI_ALLOW_REALLOC && !INI_USE_STACK
    306        offset = strlen(line);
    307        while (offset == max_line - 1 && line[offset - 1] != '\n') {
    308            max_line *= 2;
    309            if (max_line > INI_MAX_LINE)
    310                max_line = INI_MAX_LINE;
    311            new_line = ini_realloc(line, max_line);
    312            if (!new_line) {
    313                ini_free(line);
    314                return -2;
    315            }
    316            line = new_line;
    317            if (reader(line + offset, (int)(max_line - offset), stream) == NULL)
    318                break;
    319            if (max_line >= INI_MAX_LINE)
    320                break;
    321            offset += strlen(line + offset);
    322        }
    323#endif
    324
    325        lineno++;
    326
    327        start = line;
    328#if INI_ALLOW_BOM
    329        if (lineno == 1 && (unsigned char)start[0] == 0xEF &&
    330                           (unsigned char)start[1] == 0xBB &&
    331                           (unsigned char)start[2] == 0xBF) {
    332            start += 3;
    333        }
    334#endif
    335        start = ini_rstrip(ini_lskip(start));
    336
    337        if (strchr(INI_START_COMMENT_PREFIXES, *start)) {
    338            /* Start-of-line comment */
    339        }
    340#if INI_ALLOW_MULTILINE
    341        else if (*prev_name && *start && start > line) {
    342#if INI_ALLOW_INLINE_COMMENTS
    343            end = ini_find_chars_or_comment(start, NULL);
    344            if (*end)
    345                *end = '\0';
    346            ini_rstrip(start);
    347#endif
    348            /* Non-blank line with leading whitespace, treat as continuation
    349               of previous name's value (as per Python configparser). */
    350            if (!HANDLER(user, section, prev_name, start) && !error)
    351                error = lineno;
    352        }
    353#endif
    354        else if (*start == '[') {
    355            /* A "[section]" line */
    356            end = ini_find_chars_or_comment(start + 1, "]");
    357            if (*end == ']') {
    358                *end = '\0';
    359                ini_strncpy0(section, start + 1, sizeof(section));
    360#if INI_ALLOW_MULTILINE
    361                *prev_name = '\0';
    362#endif
    363#if INI_CALL_HANDLER_ON_NEW_SECTION
    364                if (!HANDLER(user, section, NULL, NULL) && !error)
    365                    error = lineno;
    366#endif
    367            }
    368            else if (!error) {
    369                /* No ']' found on section line */
    370                error = lineno;
    371            }
    372        }
    373        else if (*start) {
    374            /* Not a comment, must be a name[=:]value pair */
    375            end = ini_find_chars_or_comment(start, "=:");
    376            if (*end == '=' || *end == ':') {
    377                *end = '\0';
    378                name = ini_rstrip(start);
    379                value = end + 1;
    380#if INI_ALLOW_INLINE_COMMENTS
    381                end = ini_find_chars_or_comment(value, NULL);
    382                if (*end)
    383                    *end = '\0';
    384#endif
    385                value = ini_lskip(value);
    386                ini_rstrip(value);
    387
    388#if INI_ALLOW_MULTILINE
    389                ini_strncpy0(prev_name, name, sizeof(prev_name));
    390#endif
    391                /* Valid name[=:]value pair found, call handler */
    392                if (!HANDLER(user, section, name, value) && !error)
    393                    error = lineno;
    394            }
    395            else if (!error) {
    396                /* No '=' or ':' found on name[=:]value line */
    397#if INI_ALLOW_NO_VALUE
    398                *end = '\0';
    399                name = ini_rstrip(start);
    400                if (!HANDLER(user, section, name, NULL) && !error)
    401                    error = lineno;
    402#else
    403                error = lineno;
    404#endif
    405            }
    406        }
    407
    408#if INI_STOP_ON_FIRST_ERROR
    409        if (error)
    410            break;
    411#endif
    412    }
    413
    414#if !INI_USE_STACK
    415    ini_free(line);
    416#endif
    417
    418    return error;
    419}
    420
    421/* See documentation in header file. */
    422int ini_parse_file(FILE* file, ini_handler handler, void* user)
    423{
    424    return ini_parse_stream((ini_reader)fgets, file, handler, user);
    425}
    426
    427/* See documentation in header file. */
    428int ini_parse(const char* filename, ini_handler handler, void* user)
    429{
    430    FILE* file;
    431    int error;
    432
    433    file = fopen(filename, "r");
    434    if (!file)
    435        return -1;
    436    error = ini_parse_file(file, handler, user);
    437    fclose(file);
    438    return error;
    439}
    440
    441/* An ini_reader function to read the next line from a string buffer. This
    442   is the fgets() equivalent used by ini_parse_string(). */
    443static char* ini_reader_string(char* str, int num, void* stream) {
    444    ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream;
    445    const char* ctx_ptr = ctx->ptr;
    446    size_t ctx_num_left = ctx->num_left;
    447    char* strp = str;
    448    char c;
    449
    450    if (ctx_num_left == 0 || num < 2)
    451        return NULL;
    452
    453    while (num > 1 && ctx_num_left != 0) {
    454        c = *ctx_ptr++;
    455        ctx_num_left--;
    456        *strp++ = c;
    457        if (c == '\n')
    458            break;
    459        num--;
    460    }
    461
    462    *strp = '\0';
    463    ctx->ptr = ctx_ptr;
    464    ctx->num_left = ctx_num_left;
    465    return str;
    466}
    467
    468/* See documentation in header file. */
    469int ini_parse_string(const char* string, ini_handler handler, void* user) {
    470    ini_parse_string_ctx ctx;
    471
    472    ctx.ptr = string;
    473    ctx.num_left = strlen(string);
    474    return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler,
    475                            user);
    476}
    477
    478#endif /* INI_IMPL */
    479#endif /* INI_H */