Coverage Report

Created: 2024-06-18 06:06

/src/inih/ini.c
Line
Count
Source (jump to first uncovered line)
1
/* inih -- simple .INI file parser
2
3
SPDX-License-Identifier: BSD-3-Clause
4
5
Copyright (C) 2009-2020, 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. Return s. */
48
static char* ini_rstrip(char* s)
49
9.60k
{
50
9.60k
    char* p = s + strlen(s);
51
16.8k
    while (p > s && isspace((unsigned char)(*--p)))
52
7.22k
        *p = '\0';
53
9.60k
    return s;
54
9.60k
}
55
56
/* Return pointer to first non-whitespace char in given string. */
57
static char* ini_lskip(const char* s)
58
7.76k
{
59
9.29k
    while (*s && isspace((unsigned char)(*s)))
60
1.53k
        s++;
61
7.76k
    return (char*)s;
62
7.76k
}
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.17k
{
69
5.17k
#if INI_ALLOW_INLINE_COMMENTS
70
5.17k
    int was_space = 0;
71
18.4k
    while (*s && (!chars || !strchr(chars, *s)) &&
72
18.4k
           !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) {
73
13.2k
        was_space = isspace((unsigned char)(*s));
74
13.2k
        s++;
75
13.2k
    }
76
#else
77
    while (*s && (!chars || !strchr(chars, *s))) {
78
        s++;
79
    }
80
#endif
81
5.17k
    return (char*)s;
82
5.17k
}
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.16k
{
88
    /* Could use strncpy internally, but it causes gcc warnings (see issue #91) */
89
2.16k
    size_t i;
90
8.54k
    for (i = 0; i < size - 1 && src[i]; i++)
91
6.38k
        dest[i] = src[i];
92
2.16k
    dest[i] = '\0';
93
2.16k
    return dest;
94
2.16k
}
95
96
/* See documentation in header file. */
97
int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
98
                     void* user)
99
507
{
100
    /* Uses a fair bit of stack (use heap instead if you need to) */
101
507
#if INI_USE_STACK
102
507
    char line[INI_MAX_LINE];
103
507
    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
    size_t offset;
111
#endif
112
507
    char section[MAX_SECTION] = "";
113
507
    char prev_name[MAX_NAME] = "";
114
115
507
    char* start;
116
507
    char* end;
117
507
    char* name;
118
507
    char* value;
119
507
    int lineno = 0;
120
507
    int error = 0;
121
122
#if !INI_USE_STACK
123
    line = (char*)ini_malloc(INI_INITIAL_ALLOC);
124
    if (!line) {
125
        return -2;
126
    }
127
#endif
128
129
#if INI_HANDLER_LINENO
130
#define HANDLER(u, s, n, v) handler(u, s, n, v, lineno)
131
#else
132
3.68k
#define HANDLER(u, s, n, v) handler(u, s, n, v)
133
507
#endif
134
135
    /* Scan through stream line by line */
136
6.72k
    while (reader(line, (int)max_line, stream) != NULL) {
137
#if INI_ALLOW_REALLOC && !INI_USE_STACK
138
        offset = strlen(line);
139
        while (offset == max_line - 1 && line[offset - 1] != '\n') {
140
            max_line *= 2;
141
            if (max_line > INI_MAX_LINE)
142
                max_line = INI_MAX_LINE;
143
            new_line = ini_realloc(line, max_line);
144
            if (!new_line) {
145
                ini_free(line);
146
                return -2;
147
            }
148
            line = new_line;
149
            if (reader(line + offset, (int)(max_line - offset), stream) == NULL)
150
                break;
151
            if (max_line >= INI_MAX_LINE)
152
                break;
153
            offset += strlen(line + offset);
154
        }
155
#endif
156
157
6.21k
        lineno++;
158
159
6.21k
        start = line;
160
6.21k
#if INI_ALLOW_BOM
161
6.21k
        if (lineno == 1 && (unsigned char)start[0] == 0xEF &&
162
6.21k
                           (unsigned char)start[1] == 0xBB &&
163
6.21k
                           (unsigned char)start[2] == 0xBF) {
164
1
            start += 3;
165
1
        }
166
6.21k
#endif
167
6.21k
        start = ini_lskip(ini_rstrip(start));
168
169
6.21k
        if (strchr(INI_START_COMMENT_PREFIXES, *start)) {
170
            /* Start-of-line comment */
171
2.59k
        }
172
3.61k
#if INI_ALLOW_MULTILINE
173
3.61k
        else if (*prev_name && *start && start > line) {
174
290
#if INI_ALLOW_INLINE_COMMENTS
175
290
            end = ini_find_chars_or_comment(start, NULL);
176
290
            if (*end)
177
79
                *end = '\0';
178
290
            ini_rstrip(start);
179
290
#endif
180
            /* Non-blank line with leading whitespace, treat as continuation
181
               of previous name's value (as per Python configparser). */
182
290
            if (!HANDLER(user, section, prev_name, start) && !error)
183
0
                error = lineno;
184
290
        }
185
3.32k
#endif
186
3.32k
        else if (*start == '[') {
187
            /* A "[section]" line */
188
1.18k
            end = ini_find_chars_or_comment(start + 1, "]");
189
1.18k
            if (*end == ']') {
190
608
                *end = '\0';
191
608
                ini_strncpy0(section, start + 1, sizeof(section));
192
608
                *prev_name = '\0';
193
#if INI_CALL_HANDLER_ON_NEW_SECTION
194
                if (!HANDLER(user, section, NULL, NULL) && !error)
195
                    error = lineno;
196
#endif
197
608
            }
198
572
            else if (!error) {
199
                /* No ']' found on section line */
200
35
                error = lineno;
201
35
            }
202
1.18k
        }
203
2.14k
        else if (*start) {
204
            /* Not a comment, must be a name[=:]value pair */
205
2.14k
            end = ini_find_chars_or_comment(start, "=:");
206
2.14k
            if (*end == '=' || *end == ':') {
207
1.55k
                *end = '\0';
208
1.55k
                name = ini_rstrip(start);
209
1.55k
                value = end + 1;
210
1.55k
#if INI_ALLOW_INLINE_COMMENTS
211
1.55k
                end = ini_find_chars_or_comment(value, NULL);
212
1.55k
                if (*end)
213
455
                    *end = '\0';
214
1.55k
#endif
215
1.55k
                value = ini_lskip(value);
216
1.55k
                ini_rstrip(value);
217
218
                /* Valid name[=:]value pair found, call handler */
219
1.55k
                ini_strncpy0(prev_name, name, sizeof(prev_name));
220
1.55k
                if (!HANDLER(user, section, name, value) && !error)
221
0
                    error = lineno;
222
1.55k
            }
223
597
            else if (!error) {
224
                /* No '=' or ':' found on name[=:]value line */
225
#if INI_ALLOW_NO_VALUE
226
                *end = '\0';
227
                name = ini_rstrip(start);
228
                if (!HANDLER(user, section, name, NULL) && !error)
229
                    error = lineno;
230
#else
231
120
                error = lineno;
232
120
#endif
233
120
            }
234
2.14k
        }
235
236
#if INI_STOP_ON_FIRST_ERROR
237
        if (error)
238
            break;
239
#endif
240
6.21k
    }
241
242
#if !INI_USE_STACK
243
    ini_free(line);
244
#endif
245
246
507
    return error;
247
507
}
248
249
/* See documentation in header file. */
250
int ini_parse_file(FILE* file, ini_handler handler, void* user)
251
0
{
252
0
    return ini_parse_stream((ini_reader)fgets, file, handler, user);
253
0
}
254
255
/* See documentation in header file. */
256
int ini_parse(const char* filename, ini_handler handler, void* user)
257
0
{
258
0
    FILE* file;
259
0
    int error;
260
261
0
    file = fopen(filename, "r");
262
0
    if (!file)
263
0
        return -1;
264
0
    error = ini_parse_file(file, handler, user);
265
0
    fclose(file);
266
0
    return error;
267
0
}
268
269
/* An ini_reader function to read the next line from a string buffer. This
270
   is the fgets() equivalent used by ini_parse_string(). */
271
6.72k
static char* ini_reader_string(char* str, int num, void* stream) {
272
6.72k
    ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream;
273
6.72k
    const char* ctx_ptr = ctx->ptr;
274
6.72k
    size_t ctx_num_left = ctx->num_left;
275
6.72k
    char* strp = str;
276
6.72k
    char c;
277
278
6.72k
    if (ctx_num_left == 0 || num < 2)
279
507
        return NULL;
280
281
24.4k
    while (num > 1 && ctx_num_left != 0) {
282
23.9k
        c = *ctx_ptr++;
283
23.9k
        ctx_num_left--;
284
23.9k
        *strp++ = c;
285
23.9k
        if (c == '\n')
286
5.71k
            break;
287
18.2k
        num--;
288
18.2k
    }
289
290
6.21k
    *strp = '\0';
291
6.21k
    ctx->ptr = ctx_ptr;
292
6.21k
    ctx->num_left = ctx_num_left;
293
6.21k
    return str;
294
6.72k
}
295
296
/* See documentation in header file. */
297
507
int ini_parse_string(const char* string, ini_handler handler, void* user) {
298
507
    ini_parse_string_ctx ctx;
299
300
507
    ctx.ptr = string;
301
507
    ctx.num_left = strlen(string);
302
507
    return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler,
303
507
                            user);
304
507
}