Coverage Report

Created: 2026-10-17 06:50

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