Coverage Report

Created: 2025-11-11 06:30

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/inih/ini.c
Line
Count
Source
1
/* inih -- simple .INI file parser
2
3
SPDX-License-Identifier: BSD-3-Clause
4
5
Copyright (C) 2009-2025, 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. end must be a
48
   pointer to the NUL terminator at the end of the string. Return s. */
49
static char* ini_rstrip(char* s, char* end)
50
10.1k
{
51
14.8k
    while (end > s && isspace((unsigned char)(*--end)))
52
4.66k
        *end = '\0';
53
10.1k
    return s;
54
10.1k
}
55
56
/* Return pointer to first non-whitespace char in given string. */
57
static char* ini_lskip(const char* s)
58
8.65k
{
59
13.6k
    while (*s && isspace((unsigned char)(*s)))
60
5.01k
        s++;
61
8.65k
    return (char*)s;
62
8.65k
}
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
4.64k
{
69
4.64k
#if INI_ALLOW_INLINE_COMMENTS
70
4.64k
    int was_space = 0;
71
27.9k
    while (*s && (!chars || !strchr(chars, *s)) &&
72
23.3k
           !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) {
73
23.2k
        was_space = isspace((unsigned char)(*s));
74
23.2k
        s++;
75
23.2k
    }
76
#else
77
    while (*s && (!chars || !strchr(chars, *s))) {
78
        s++;
79
    }
80
#endif
81
4.64k
    return (char*)s;
82
4.64k
}
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
1.86k
{
88
    /* Could use strncpy internally, but it causes gcc warnings (see issue #91) */
89
1.86k
    size_t i;
90
8.75k
    for (i = 0; i < size - 1 && src[i]; i++)
91
6.88k
        dest[i] = src[i];
92
1.86k
    dest[i] = '\0';
93
1.86k
    return dest;
94
1.86k
}
95
96
/* See documentation in header file. */
97
int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
98
                     void* user)
99
535
{
100
    /* Uses a fair bit of stack (use heap instead if you need to) */
101
535
#if INI_USE_STACK
102
535
    char line[INI_MAX_LINE];
103
535
    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
#endif
111
535
    char section[MAX_SECTION] = "";
112
535
#if INI_ALLOW_MULTILINE
113
535
    char prev_name[MAX_NAME] = "";
114
535
#endif
115
116
535
    size_t offset;
117
535
    char* start;
118
535
    char* end;
119
535
    char* name;
120
535
    char* value;
121
535
    int lineno = 0;
122
535
    int error = 0;
123
535
    char abyss[16];  /* Used to consume input when a line is too long. */
124
535
    size_t abyss_len;
125
126
#if !INI_USE_STACK
127
    line = (char*)ini_malloc(INI_INITIAL_ALLOC);
128
    if (!line) {
129
        return -2;
130
    }
131
#endif
132
133
#if INI_HANDLER_LINENO
134
#define HANDLER(u, s, n, v) handler(u, s, n, v, lineno)
135
#else
136
3.04k
#define HANDLER(u, s, n, v) handler(u, s, n, v)
137
535
#endif
138
139
    /* Scan through stream line by line */
140
7.92k
    while (reader(line, (int)max_line, stream) != NULL) {
141
7.38k
        offset = strlen(line);
142
143
#if INI_ALLOW_REALLOC && !INI_USE_STACK
144
        while (max_line < INI_MAX_LINE &&
145
               offset == max_line - 1 && line[offset - 1] != '\n') {
146
            max_line *= 2;
147
            if (max_line > INI_MAX_LINE)
148
                max_line = INI_MAX_LINE;
149
            new_line = ini_realloc(line, max_line);
150
            if (!new_line) {
151
                ini_free(line);
152
                return -2;
153
            }
154
            line = new_line;
155
            if (reader(line + offset, (int)(max_line - offset), stream) == NULL)
156
                break;
157
            offset += strlen(line + offset);
158
        }
159
#endif
160
161
7.38k
        lineno++;
162
163
        /* If line exceeded INI_MAX_LINE bytes, discard till end of line. */
164
7.38k
        if (offset == max_line - 1 && line[offset - 1] != '\n') {
165
277
            while (reader(abyss, sizeof(abyss), stream) != NULL) {
166
224
                if (!error)
167
32
                    error = lineno;
168
224
                abyss_len = strlen(abyss);
169
224
                if (abyss_len > 0 && abyss[abyss_len - 1] == '\n')
170
6
                    break;
171
224
            }
172
59
        }
173
174
7.38k
        start = line;
175
7.38k
#if INI_ALLOW_BOM
176
7.38k
        if (lineno == 1 && (unsigned char)start[0] == 0xEF &&
177
35
                           (unsigned char)start[1] == 0xBB &&
178
15
                           (unsigned char)start[2] == 0xBF) {
179
2
            start += 3;
180
2
        }
181
7.38k
#endif
182
7.38k
        start = ini_rstrip(ini_lskip(start), line + offset);
183
184
7.38k
        if (strchr(INI_START_COMMENT_PREFIXES, *start)) {
185
            /* Start-of-line comment */
186
4.00k
        }
187
3.37k
#if INI_ALLOW_MULTILINE
188
3.37k
        else if (*prev_name && *start && start > line) {
189
259
#if INI_ALLOW_INLINE_COMMENTS
190
259
            end = ini_find_chars_or_comment(start, NULL);
191
259
            *end = '\0';
192
259
            ini_rstrip(start, end);
193
259
#endif
194
            /* Non-blank line with leading whitespace, treat as continuation
195
               of previous name's value (as per Python configparser). */
196
259
            if (!HANDLER(user, section, prev_name, start) && !error)
197
0
                error = lineno;
198
259
        }
199
3.12k
#endif
200
3.12k
        else if (*start == '[') {
201
            /* A "[section]" line */
202
1.06k
            end = ini_find_chars_or_comment(start + 1, "]");
203
1.06k
            if (*end == ']') {
204
596
                *end = '\0';
205
596
                ini_strncpy0(section, start + 1, sizeof(section));
206
596
#if INI_ALLOW_MULTILINE
207
596
                *prev_name = '\0';
208
596
#endif
209
#if INI_CALL_HANDLER_ON_NEW_SECTION
210
                if (!HANDLER(user, section, NULL, NULL) && !error)
211
                    error = lineno;
212
#endif
213
596
            }
214
473
            else if (!error) {
215
                /* No ']' found on section line */
216
46
                error = lineno;
217
46
            }
218
1.06k
        }
219
2.05k
        else if (*start) {
220
            /* Not a comment, must be a name[=:]value pair */
221
2.05k
            end = ini_find_chars_or_comment(start, "=:");
222
2.05k
            if (*end == '=' || *end == ':') {
223
1.26k
                *end = '\0';
224
1.26k
                name = ini_rstrip(start, end);
225
1.26k
                value = end + 1;
226
1.26k
#if INI_ALLOW_INLINE_COMMENTS
227
1.26k
                end = ini_find_chars_or_comment(value, NULL);
228
1.26k
                *end = '\0';
229
1.26k
#endif
230
1.26k
                value = ini_lskip(value);
231
1.26k
                ini_rstrip(value, end);
232
233
1.26k
#if INI_ALLOW_MULTILINE
234
1.26k
                ini_strncpy0(prev_name, name, sizeof(prev_name));
235
1.26k
#endif
236
                /* Valid name[=:]value pair found, call handler */
237
1.26k
                if (!HANDLER(user, section, name, value) && !error)
238
0
                    error = lineno;
239
1.26k
            }
240
786
            else {
241
                /* No '=' or ':' found on name[=:]value line */
242
#if INI_ALLOW_NO_VALUE
243
                *end = '\0';
244
                name = ini_rstrip(start, end);
245
                if (!HANDLER(user, section, name, NULL) && !error)
246
                    error = lineno;
247
#else
248
786
                if (!error)
249
121
                    error = lineno;
250
786
#endif
251
786
            }
252
2.05k
        }
253
254
#if INI_STOP_ON_FIRST_ERROR
255
        if (error)
256
            break;
257
#endif
258
7.38k
    }
259
260
#if !INI_USE_STACK
261
    ini_free(line);
262
#endif
263
264
535
    return error;
265
535
}
266
267
/* See documentation in header file. */
268
int ini_parse_file(FILE* file, ini_handler handler, void* user)
269
0
{
270
0
    return ini_parse_stream((ini_reader)fgets, file, handler, user);
271
0
}
272
273
/* See documentation in header file. */
274
int ini_parse(const char* filename, ini_handler handler, void* user)
275
0
{
276
0
    FILE* file;
277
0
    int error;
278
279
0
    file = fopen(filename, "r");
280
0
    if (!file)
281
0
        return -1;
282
0
    error = ini_parse_file(file, handler, user);
283
0
    fclose(file);
284
0
    return error;
285
0
}
286
287
/* An ini_reader function to read the next line from a string buffer. This
288
   is the fgets() equivalent used by ini_parse_string(). */
289
8.19k
static char* ini_reader_string(char* str, int num, void* stream) {
290
8.19k
    ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream;
291
8.19k
    const char* ctx_ptr = ctx->ptr;
292
8.19k
    size_t ctx_num_left = ctx->num_left;
293
8.19k
    char* strp = str;
294
8.19k
    char c;
295
296
8.19k
    if (ctx_num_left == 0 || num < 2)
297
588
        return NULL;
298
299
38.2k
    while (num > 1 && ctx_num_left != 0) {
300
37.5k
        c = *ctx_ptr++;
301
37.5k
        ctx_num_left--;
302
37.5k
        *strp++ = c;
303
37.5k
        if (c == '\n')
304
6.87k
            break;
305
30.6k
        num--;
306
30.6k
    }
307
308
7.61k
    *strp = '\0';
309
7.61k
    ctx->ptr = ctx_ptr;
310
7.61k
    ctx->num_left = ctx_num_left;
311
7.61k
    return str;
312
8.19k
}
313
314
/* See documentation in header file. */
315
535
int ini_parse_string(const char* string, ini_handler handler, void* user) {
316
535
    return ini_parse_string_length(string, strlen(string), handler, user);
317
535
}
318
319
/* See documentation in header file. */
320
int ini_parse_string_length(const char* string, size_t length,
321
535
                            ini_handler handler, void* user) {
322
535
    ini_parse_string_ctx ctx;
323
324
535
    ctx.ptr = string;
325
535
    ctx.num_left = length;
326
535
    return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler,
327
535
                            user);
328
535
}