Coverage Report

Created: 2026-03-12 06:19

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
9.99k
{
52
15.2k
    while (end > s && isspace((unsigned char)(*--end)))
53
5.27k
        *end = '\0';
54
9.99k
    return s;
55
9.99k
}
56
57
/* Return pointer to first non-whitespace char in given string. */
58
static char* ini_lskip(const char* s)
59
8.35k
{
60
12.9k
    while (*s && isspace((unsigned char)(*s)))
61
4.57k
        s++;
62
8.35k
    return (char*)s;
63
8.35k
}
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.08k
{
70
5.08k
#if INI_ALLOW_INLINE_COMMENTS
71
5.08k
    int was_space = 0;
72
27.9k
    while (*s && (!chars || !strchr(chars, *s)) &&
73
22.9k
           !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) {
74
22.8k
        was_space = isspace((unsigned char)(*s));
75
22.8k
        s++;
76
22.8k
    }
77
#else
78
    while (*s && (!chars || !strchr(chars, *s))) {
79
        s++;
80
    }
81
#endif
82
5.08k
    return (char*)s;
83
5.08k
}
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
1.95k
{
89
    /* Could use strncpy internally, but it causes gcc warnings (see issue #91) */
90
1.95k
    size_t i;
91
8.44k
    for (i = 0; i < size - 1 && src[i]; i++)
92
6.49k
        dest[i] = src[i];
93
1.95k
    dest[i] = '\0';
94
1.95k
    return dest;
95
1.95k
}
96
97
/* See documentation in header file. */
98
int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
99
                     void* user)
100
549
{
101
    /* Uses a fair bit of stack (use heap instead if you need to) */
102
549
#if INI_USE_STACK
103
549
    char line[INI_MAX_LINE];
104
549
    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
549
    char section[MAX_SECTION] = "";
113
549
#if INI_ALLOW_MULTILINE
114
549
    char prev_name[MAX_NAME] = "";
115
549
#endif
116
117
549
    size_t offset;
118
549
    char* start;
119
549
    char* end;
120
549
    char* name;
121
549
    char* value;
122
549
    int lineno = 0;
123
549
    int error = 0;
124
549
    char abyss[16];  /* Used to consume input when a line is too long. */
125
549
    size_t abyss_len;
126
127
549
    assert(reader != NULL);
128
549
    assert(stream != NULL);
129
549
    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.29k
#define HANDLER(u, s, n, v) handler(u, s, n, v)
142
549
#endif
143
144
    /* Scan through stream line by line */
145
7.66k
    while (reader(line, (int)max_line, stream) != NULL) {
146
7.11k
        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.11k
        lineno++;
167
168
        /* If line exceeded INI_MAX_LINE bytes, discard till end of line. */
169
7.11k
        if (offset == max_line - 1 && line[offset - 1] != '\n') {
170
216
            while (reader(abyss, sizeof(abyss), stream) != NULL) {
171
172
                if (!error)
172
29
                    error = lineno;
173
172
                abyss_len = strlen(abyss);
174
172
                if (abyss_len > 0 && abyss[abyss_len - 1] == '\n')
175
8
                    break;
176
172
            }
177
52
        }
178
179
7.11k
        start = line;
180
7.11k
#if INI_ALLOW_BOM
181
7.11k
        if (lineno == 1 && (unsigned char)start[0] == 0xEF &&
182
33
                           (unsigned char)start[1] == 0xBB &&
183
15
                           (unsigned char)start[2] == 0xBF) {
184
2
            start += 3;
185
2
        }
186
7.11k
#endif
187
7.11k
        start = ini_rstrip(ini_lskip(start), line + offset);
188
189
7.11k
        if (strchr(INI_START_COMMENT_PREFIXES, *start)) {
190
            /* Start-of-line comment */
191
3.27k
        }
192
3.84k
#if INI_ALLOW_MULTILINE
193
3.84k
        else if (*prev_name && *start && start > line) {
194
407
#if INI_ALLOW_INLINE_COMMENTS
195
407
            end = ini_find_chars_or_comment(start, NULL);
196
407
            *end = '\0';
197
407
            ini_rstrip(start, end);
198
407
#endif
199
            /* Non-blank line with leading whitespace, treat as continuation
200
               of previous name's value (as per Python configparser). */
201
407
            if (!HANDLER(user, section, prev_name, start) && !error)
202
0
                error = lineno;
203
407
        }
204
3.43k
#endif
205
3.43k
        else if (*start == '[') {
206
            /* A "[section]" line */
207
1.46k
            end = ini_find_chars_or_comment(start + 1, "]");
208
1.46k
            if (*end == ']') {
209
710
                *end = '\0';
210
710
                ini_strncpy0(section, start + 1, sizeof(section));
211
710
#if INI_ALLOW_MULTILINE
212
710
                *prev_name = '\0';
213
710
#endif
214
#if INI_CALL_HANDLER_ON_NEW_SECTION
215
                if (!HANDLER(user, section, NULL, NULL) && !error)
216
                    error = lineno;
217
#endif
218
710
            }
219
752
            else if (!error) {
220
                /* No ']' found on section line */
221
46
                error = lineno;
222
46
            }
223
1.46k
        }
224
1.97k
        else if (*start) {
225
            /* Not a comment, must be a name[=:]value pair */
226
1.97k
            end = ini_find_chars_or_comment(start, "=:");
227
1.97k
            if (*end == '=' || *end == ':') {
228
1.24k
                *end = '\0';
229
1.24k
                name = ini_rstrip(start, end);
230
1.24k
                value = end + 1;
231
1.24k
#if INI_ALLOW_INLINE_COMMENTS
232
1.24k
                end = ini_find_chars_or_comment(value, NULL);
233
1.24k
                *end = '\0';
234
1.24k
#endif
235
1.24k
                value = ini_lskip(value);
236
1.24k
                ini_rstrip(value, end);
237
238
1.24k
#if INI_ALLOW_MULTILINE
239
1.24k
                ini_strncpy0(prev_name, name, sizeof(prev_name));
240
1.24k
#endif
241
                /* Valid name[=:]value pair found, call handler */
242
1.24k
                if (!HANDLER(user, section, name, value) && !error)
243
0
                    error = lineno;
244
1.24k
            }
245
733
            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
733
                if (!error)
254
129
                    error = lineno;
255
733
#endif
256
733
            }
257
1.97k
        }
258
259
#if INI_STOP_ON_FIRST_ERROR
260
        if (error)
261
            break;
262
#endif
263
7.11k
    }
264
265
#if !INI_USE_STACK
266
    ini_free(line);
267
#endif
268
269
549
    return error;
270
549
}
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
7.87k
static char* ini_reader_string(char* str, int num, void* stream) {
295
7.87k
    ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream;
296
7.87k
    const char* ctx_ptr = ctx->ptr;
297
7.87k
    size_t ctx_num_left = ctx->num_left;
298
7.87k
    char* strp = str;
299
7.87k
    char c;
300
301
7.87k
    if (ctx_num_left == 0 || num < 2)
302
593
        return NULL;
303
304
38.0k
    while (num > 1 && ctx_num_left != 0) {
305
37.3k
        c = *ctx_ptr++;
306
37.3k
        ctx_num_left--;
307
37.3k
        *strp++ = c;
308
37.3k
        if (c == '\n')
309
6.59k
            break;
310
30.7k
        num--;
311
30.7k
    }
312
313
7.28k
    *strp = '\0';
314
7.28k
    ctx->ptr = ctx_ptr;
315
7.28k
    ctx->num_left = ctx_num_left;
316
7.28k
    return str;
317
7.87k
}
318
319
/* See documentation in header file. */
320
549
int ini_parse_string(const char* string, ini_handler handler, void* user) {
321
549
    return ini_parse_string_length(string, strlen(string), handler, user);
322
549
}
323
324
/* See documentation in header file. */
325
int ini_parse_string_length(const char* string, size_t length,
326
549
                            ini_handler handler, void* user) {
327
549
    ini_parse_string_ctx ctx;
328
329
549
    ctx.ptr = string;
330
549
    ctx.num_left = length;
331
549
    return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler,
332
549
                            user);
333
549
}