Coverage Report

Created: 2026-08-13 06:37

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