Coverage Report

Created: 2026-01-13 06:28

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.2k
{
51
14.8k
    while (end > s && isspace((unsigned char)(*--end)))
52
4.60k
        *end = '\0';
53
10.2k
    return s;
54
10.2k
}
55
56
/* Return pointer to first non-whitespace char in given string. */
57
static char* ini_lskip(const char* s)
58
8.72k
{
59
13.5k
    while (*s && isspace((unsigned char)(*s)))
60
4.78k
        s++;
61
8.72k
    return (char*)s;
62
8.72k
}
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.84k
{
69
4.84k
#if INI_ALLOW_INLINE_COMMENTS
70
4.84k
    int was_space = 0;
71
28.8k
    while (*s && (!chars || !strchr(chars, *s)) &&
72
24.1k
           !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) {
73
24.0k
        was_space = isspace((unsigned char)(*s));
74
24.0k
        s++;
75
24.0k
    }
76
#else
77
    while (*s && (!chars || !strchr(chars, *s))) {
78
        s++;
79
    }
80
#endif
81
4.84k
    return (char*)s;
82
4.84k
}
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.85k
{
88
    /* Could use strncpy internally, but it causes gcc warnings (see issue #91) */
89
1.85k
    size_t i;
90
8.75k
    for (i = 0; i < size - 1 && src[i]; i++)
91
6.90k
        dest[i] = src[i];
92
1.85k
    dest[i] = '\0';
93
1.85k
    return dest;
94
1.85k
}
95
96
/* See documentation in header file. */
97
int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
98
                     void* user)
99
555
{
100
    /* Uses a fair bit of stack (use heap instead if you need to) */
101
555
#if INI_USE_STACK
102
555
    char line[INI_MAX_LINE];
103
555
    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
555
    char section[MAX_SECTION] = "";
112
555
#if INI_ALLOW_MULTILINE
113
555
    char prev_name[MAX_NAME] = "";
114
555
#endif
115
116
555
    size_t offset;
117
555
    char* start;
118
555
    char* end;
119
555
    char* name;
120
555
    char* value;
121
555
    int lineno = 0;
122
555
    int error = 0;
123
555
    char abyss[16];  /* Used to consume input when a line is too long. */
124
555
    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
2.98k
#define HANDLER(u, s, n, v) handler(u, s, n, v)
137
555
#endif
138
139
    /* Scan through stream line by line */
140
8.04k
    while (reader(line, (int)max_line, stream) != NULL) {
141
7.48k
        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.48k
        lineno++;
162
163
        /* If line exceeded INI_MAX_LINE bytes, discard till end of line. */
164
7.48k
        if (offset == max_line - 1 && line[offset - 1] != '\n') {
165
250
            while (reader(abyss, sizeof(abyss), stream) != NULL) {
166
199
                if (!error)
167
31
                    error = lineno;
168
199
                abyss_len = strlen(abyss);
169
199
                if (abyss_len > 0 && abyss[abyss_len - 1] == '\n')
170
5
                    break;
171
199
            }
172
56
        }
173
174
7.48k
        start = line;
175
7.48k
#if INI_ALLOW_BOM
176
7.48k
        if (lineno == 1 && (unsigned char)start[0] == 0xEF &&
177
40
                           (unsigned char)start[1] == 0xBB &&
178
15
                           (unsigned char)start[2] == 0xBF) {
179
2
            start += 3;
180
2
        }
181
7.48k
#endif
182
7.48k
        start = ini_rstrip(ini_lskip(start), line + offset);
183
184
7.48k
        if (strchr(INI_START_COMMENT_PREFIXES, *start)) {
185
            /* Start-of-line comment */
186
3.88k
        }
187
3.60k
#if INI_ALLOW_MULTILINE
188
3.60k
        else if (*prev_name && *start && start > line) {
189
252
#if INI_ALLOW_INLINE_COMMENTS
190
252
            end = ini_find_chars_or_comment(start, NULL);
191
252
            *end = '\0';
192
252
            ini_rstrip(start, end);
193
252
#endif
194
            /* Non-blank line with leading whitespace, treat as continuation
195
               of previous name's value (as per Python configparser). */
196
252
            if (!HANDLER(user, section, prev_name, start) && !error)
197
0
                error = lineno;
198
252
        }
199
3.35k
#endif
200
3.35k
        else if (*start == '[') {
201
            /* A "[section]" line */
202
1.32k
            end = ini_find_chars_or_comment(start + 1, "]");
203
1.32k
            if (*end == ']') {
204
613
                *end = '\0';
205
613
                ini_strncpy0(section, start + 1, sizeof(section));
206
613
#if INI_ALLOW_MULTILINE
207
613
                *prev_name = '\0';
208
613
#endif
209
#if INI_CALL_HANDLER_ON_NEW_SECTION
210
                if (!HANDLER(user, section, NULL, NULL) && !error)
211
                    error = lineno;
212
#endif
213
613
            }
214
708
            else if (!error) {
215
                /* No ']' found on section line */
216
48
                error = lineno;
217
48
            }
218
1.32k
        }
219
2.02k
        else if (*start) {
220
            /* Not a comment, must be a name[=:]value pair */
221
2.02k
            end = ini_find_chars_or_comment(start, "=:");
222
2.02k
            if (*end == '=' || *end == ':') {
223
1.24k
                *end = '\0';
224
1.24k
                name = ini_rstrip(start, end);
225
1.24k
                value = end + 1;
226
1.24k
#if INI_ALLOW_INLINE_COMMENTS
227
1.24k
                end = ini_find_chars_or_comment(value, NULL);
228
1.24k
                *end = '\0';
229
1.24k
#endif
230
1.24k
                value = ini_lskip(value);
231
1.24k
                ini_rstrip(value, end);
232
233
1.24k
#if INI_ALLOW_MULTILINE
234
1.24k
                ini_strncpy0(prev_name, name, sizeof(prev_name));
235
1.24k
#endif
236
                /* Valid name[=:]value pair found, call handler */
237
1.24k
                if (!HANDLER(user, section, name, value) && !error)
238
0
                    error = lineno;
239
1.24k
            }
240
789
            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
789
                if (!error)
249
145
                    error = lineno;
250
789
#endif
251
789
            }
252
2.02k
        }
253
254
#if INI_STOP_ON_FIRST_ERROR
255
        if (error)
256
            break;
257
#endif
258
7.48k
    }
259
260
#if !INI_USE_STACK
261
    ini_free(line);
262
#endif
263
264
555
    return error;
265
555
}
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.29k
static char* ini_reader_string(char* str, int num, void* stream) {
290
8.29k
    ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream;
291
8.29k
    const char* ctx_ptr = ctx->ptr;
292
8.29k
    size_t ctx_num_left = ctx->num_left;
293
8.29k
    char* strp = str;
294
8.29k
    char c;
295
296
8.29k
    if (ctx_num_left == 0 || num < 2)
297
606
        return NULL;
298
299
38.7k
    while (num > 1 && ctx_num_left != 0) {
300
38.0k
        c = *ctx_ptr++;
301
38.0k
        ctx_num_left--;
302
38.0k
        *strp++ = c;
303
38.0k
        if (c == '\n')
304
6.95k
            break;
305
31.1k
        num--;
306
31.1k
    }
307
308
7.68k
    *strp = '\0';
309
7.68k
    ctx->ptr = ctx_ptr;
310
7.68k
    ctx->num_left = ctx_num_left;
311
7.68k
    return str;
312
8.29k
}
313
314
/* See documentation in header file. */
315
555
int ini_parse_string(const char* string, ini_handler handler, void* user) {
316
555
    return ini_parse_string_length(string, strlen(string), handler, user);
317
555
}
318
319
/* See documentation in header file. */
320
int ini_parse_string_length(const char* string, size_t length,
321
555
                            ini_handler handler, void* user) {
322
555
    ini_parse_string_ctx ctx;
323
324
555
    ctx.ptr = string;
325
555
    ctx.num_left = length;
326
555
    return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler,
327
555
                            user);
328
555
}