Coverage Report

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