mpc-bar

macOS menu bar client for the Music Player Daemon
Log | Files | Refs | README | LICENSE

ini.c (8370B)


      1 /* inih -- simple .INI file parser
      2 
      3 SPDX-License-Identifier: BSD-3-Clause
      4 
      5 Copyright (C) 2009-2020, Ben Hoyt
      6 
      7 inih is released under the New BSD license (see LICENSE.txt). Go to the project
      8 home page for more info:
      9 
     10 https://github.com/benhoyt/inih
     11 
     12 */
     13 
     14 #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
     15 #define _CRT_SECURE_NO_WARNINGS
     16 #endif
     17 
     18 #include <stdio.h>
     19 #include <ctype.h>
     20 #include <string.h>
     21 
     22 #include "ini.h"
     23 
     24 #if !INI_USE_STACK
     25 #if INI_CUSTOM_ALLOCATOR
     26 #include <stddef.h>
     27 void* ini_malloc(size_t size);
     28 void ini_free(void* ptr);
     29 void* ini_realloc(void* ptr, size_t size);
     30 #else
     31 #include <stdlib.h>
     32 #define ini_malloc malloc
     33 #define ini_free free
     34 #define ini_realloc realloc
     35 #endif
     36 #endif
     37 
     38 #define MAX_SECTION 50
     39 #define MAX_NAME 50
     40 
     41 /* Used by ini_parse_string() to keep track of string parsing state. */
     42 typedef struct {
     43     const char* ptr;
     44     size_t num_left;
     45 } ini_parse_string_ctx;
     46 
     47 /* Strip whitespace chars off end of given string, in place. Return s. */
     48 static char* ini_rstrip(char* s)
     49 {
     50     char* p = s + strlen(s);
     51     while (p > s && isspace((unsigned char)(*--p)))
     52         *p = '\0';
     53     return s;
     54 }
     55 
     56 /* Return pointer to first non-whitespace char in given string. */
     57 static char* ini_lskip(const char* s)
     58 {
     59     while (*s && isspace((unsigned char)(*s)))
     60         s++;
     61     return (char*)s;
     62 }
     63 
     64 /* Return pointer to first char (of chars) or inline comment in given string,
     65    or pointer to NUL at end of string if neither found. Inline comment must
     66    be prefixed by a whitespace character to register as a comment. */
     67 static char* ini_find_chars_or_comment(const char* s, const char* chars)
     68 {
     69 #if INI_ALLOW_INLINE_COMMENTS
     70     int was_space = 0;
     71     while (*s && (!chars || !strchr(chars, *s)) &&
     72            !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) {
     73         was_space = isspace((unsigned char)(*s));
     74         s++;
     75     }
     76 #else
     77     while (*s && (!chars || !strchr(chars, *s))) {
     78         s++;
     79     }
     80 #endif
     81     return (char*)s;
     82 }
     83 
     84 /* Similar to strncpy, but ensures dest (size bytes) is
     85    NUL-terminated, and doesn't pad with NULs. */
     86 static char* ini_strncpy0(char* dest, const char* src, size_t size)
     87 {
     88     /* Could use strncpy internally, but it causes gcc warnings (see issue #91) */
     89     size_t i;
     90     for (i = 0; i < size - 1 && src[i]; i++)
     91         dest[i] = src[i];
     92     dest[i] = '\0';
     93     return dest;
     94 }
     95 
     96 /* See documentation in header file. */
     97 int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
     98                      void* user)
     99 {
    100     /* Uses a fair bit of stack (use heap instead if you need to) */
    101 #if INI_USE_STACK
    102     char line[INI_MAX_LINE];
    103     size_t max_line = INI_MAX_LINE;
    104 #else
    105     char* line;
    106     size_t max_line = INI_INITIAL_ALLOC;
    107 #endif
    108 #if INI_ALLOW_REALLOC && !INI_USE_STACK
    109     char* new_line;
    110     size_t offset;
    111 #endif
    112     char section[MAX_SECTION] = "";
    113     char prev_name[MAX_NAME] = "";
    114 
    115     char* start;
    116     char* end;
    117     char* name;
    118     char* value;
    119     int lineno = 0;
    120     int error = 0;
    121 
    122 #if !INI_USE_STACK
    123     line = (char*)ini_malloc(INI_INITIAL_ALLOC);
    124     if (!line) {
    125         return -2;
    126     }
    127 #endif
    128 
    129 #if INI_HANDLER_LINENO
    130 #define HANDLER(u, s, n, v) handler(u, s, n, v, lineno)
    131 #else
    132 #define HANDLER(u, s, n, v) handler(u, s, n, v)
    133 #endif
    134 
    135     /* Scan through stream line by line */
    136     while (reader(line, (int)max_line, stream) != NULL) {
    137 #if INI_ALLOW_REALLOC && !INI_USE_STACK
    138         offset = strlen(line);
    139         while (offset == max_line - 1 && line[offset - 1] != '\n') {
    140             max_line *= 2;
    141             if (max_line > INI_MAX_LINE)
    142                 max_line = INI_MAX_LINE;
    143             new_line = ini_realloc(line, max_line);
    144             if (!new_line) {
    145                 ini_free(line);
    146                 return -2;
    147             }
    148             line = new_line;
    149             if (reader(line + offset, (int)(max_line - offset), stream) == NULL)
    150                 break;
    151             if (max_line >= INI_MAX_LINE)
    152                 break;
    153             offset += strlen(line + offset);
    154         }
    155 #endif
    156 
    157         lineno++;
    158 
    159         start = line;
    160 #if INI_ALLOW_BOM
    161         if (lineno == 1 && (unsigned char)start[0] == 0xEF &&
    162                            (unsigned char)start[1] == 0xBB &&
    163                            (unsigned char)start[2] == 0xBF) {
    164             start += 3;
    165         }
    166 #endif
    167         start = ini_lskip(ini_rstrip(start));
    168 
    169         if (strchr(INI_START_COMMENT_PREFIXES, *start)) {
    170             /* Start-of-line comment */
    171         }
    172 #if INI_ALLOW_MULTILINE
    173         else if (*prev_name && *start && start > line) {
    174 #if INI_ALLOW_INLINE_COMMENTS
    175             end = ini_find_chars_or_comment(start, NULL);
    176             if (*end)
    177                 *end = '\0';
    178             ini_rstrip(start);
    179 #endif
    180             /* Non-blank line with leading whitespace, treat as continuation
    181                of previous name's value (as per Python configparser). */
    182             if (!HANDLER(user, section, prev_name, start) && !error)
    183                 error = lineno;
    184         }
    185 #endif
    186         else if (*start == '[') {
    187             /* A "[section]" line */
    188             end = ini_find_chars_or_comment(start + 1, "]");
    189             if (*end == ']') {
    190                 *end = '\0';
    191                 ini_strncpy0(section, start + 1, sizeof(section));
    192                 *prev_name = '\0';
    193 #if INI_CALL_HANDLER_ON_NEW_SECTION
    194                 if (!HANDLER(user, section, NULL, NULL) && !error)
    195                     error = lineno;
    196 #endif
    197             }
    198             else if (!error) {
    199                 /* No ']' found on section line */
    200                 error = lineno;
    201             }
    202         }
    203         else if (*start) {
    204             /* Not a comment, must be a name[=:]value pair */
    205             end = ini_find_chars_or_comment(start, "=:");
    206             if (*end == '=' || *end == ':') {
    207                 *end = '\0';
    208                 name = ini_rstrip(start);
    209                 value = end + 1;
    210 #if INI_ALLOW_INLINE_COMMENTS
    211                 end = ini_find_chars_or_comment(value, NULL);
    212                 if (*end)
    213                     *end = '\0';
    214 #endif
    215                 value = ini_lskip(value);
    216                 ini_rstrip(value);
    217 
    218                 /* Valid name[=:]value pair found, call handler */
    219                 ini_strncpy0(prev_name, name, sizeof(prev_name));
    220                 if (!HANDLER(user, section, name, value) && !error)
    221                     error = lineno;
    222             }
    223             else if (!error) {
    224                 /* No '=' or ':' found on name[=:]value line */
    225 #if INI_ALLOW_NO_VALUE
    226                 *end = '\0';
    227                 name = ini_rstrip(start);
    228                 if (!HANDLER(user, section, name, NULL) && !error)
    229                     error = lineno;
    230 #else
    231                 error = lineno;
    232 #endif
    233             }
    234         }
    235 
    236 #if INI_STOP_ON_FIRST_ERROR
    237         if (error)
    238             break;
    239 #endif
    240     }
    241 
    242 #if !INI_USE_STACK
    243     ini_free(line);
    244 #endif
    245 
    246     return error;
    247 }
    248 
    249 /* See documentation in header file. */
    250 int ini_parse_file(FILE* file, ini_handler handler, void* user)
    251 {
    252     return ini_parse_stream((ini_reader)fgets, file, handler, user);
    253 }
    254 
    255 /* See documentation in header file. */
    256 int ini_parse(const char* filename, ini_handler handler, void* user)
    257 {
    258     FILE* file;
    259     int error;
    260 
    261     file = fopen(filename, "r");
    262     if (!file)
    263         return -1;
    264     error = ini_parse_file(file, handler, user);
    265     fclose(file);
    266     return error;
    267 }
    268 
    269 /* An ini_reader function to read the next line from a string buffer. This
    270    is the fgets() equivalent used by ini_parse_string(). */
    271 static char* ini_reader_string(char* str, int num, void* stream) {
    272     ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream;
    273     const char* ctx_ptr = ctx->ptr;
    274     size_t ctx_num_left = ctx->num_left;
    275     char* strp = str;
    276     char c;
    277 
    278     if (ctx_num_left == 0 || num < 2)
    279         return NULL;
    280 
    281     while (num > 1 && ctx_num_left != 0) {
    282         c = *ctx_ptr++;
    283         ctx_num_left--;
    284         *strp++ = c;
    285         if (c == '\n')
    286             break;
    287         num--;
    288     }
    289 
    290     *strp = '\0';
    291     ctx->ptr = ctx_ptr;
    292     ctx->num_left = ctx_num_left;
    293     return str;
    294 }
    295 
    296 /* See documentation in header file. */
    297 int ini_parse_string(const char* string, ini_handler handler, void* user) {
    298     ini_parse_string_ctx ctx;
    299 
    300     ctx.ptr = string;
    301     ctx.num_left = strlen(string);
    302     return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler,
    303                             user);
    304 }