all repos — mgba @ 447750dc2e53fa4986a26e5ddcca90f5861be49a

mGBA Game Boy Advance Emulator

src/third-party/inih/ini.c (view raw)

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