src/third-party/inih/ini.c (view raw)
1/* inih -- simple .INI file parser
2
3SPDX-License-Identifier: BSD-3-Clause
4
5Copyright (C) 2009-2020, Ben Hoyt
6
7inih is released under the New BSD license (see LICENSE.txt). Go to the project
8home page for more info:
9
10https://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#include <stdlib.h>
26#endif
27
28#define MAX_SECTION 128
29#define MAX_NAME 128
30
31/* Used by ini_parse_string() to keep track of string parsing state. */
32typedef struct {
33 const char* ptr;
34 size_t num_left;
35} ini_parse_string_ctx;
36
37/* Strip whitespace chars off end of given string, in place. Return s. */
38static char* rstrip(char* s)
39{
40 char* p = s + strlen(s);
41 while (p > s && isspace((unsigned char)(*--p)))
42 *p = '\0';
43 return s;
44}
45
46/* Return pointer to first non-whitespace char in given string. */
47static char* lskip(const char* s)
48{
49 while (*s && isspace((unsigned char)(*s)))
50 s++;
51 return (char*)s;
52}
53
54/* Return pointer to first char (of chars) or inline comment in given string,
55 or pointer to NUL at end of string if neither found. Inline comment must
56 be prefixed by a whitespace character to register as a comment. */
57static char* find_chars_or_comment(const char* s, const char* chars)
58{
59#if INI_ALLOW_INLINE_COMMENTS
60 int was_space = 0;
61 while (*s && (!chars || !strchr(chars, *s)) &&
62 !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) {
63 was_space = isspace((unsigned char)(*s));
64 s++;
65 }
66#else
67 while (*s && (!chars || !strchr(chars, *s))) {
68 s++;
69 }
70#endif
71 return (char*)s;
72}
73
74/* Similar to strncpy, but ensures dest (size bytes) is
75 NUL-terminated, and doesn't pad with NULs. */
76static char* strncpy0(char* dest, const char* src, size_t size)
77{
78 /* Could use strncpy internally, but it causes gcc warnings (see issue #91) */
79 size_t i;
80 for (i = 0; i < size - 1 && src[i]; i++)
81 dest[i] = src[i];
82 dest[i] = '\0';
83 return dest;
84}
85
86/* See documentation in header file. */
87int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
88 void* user)
89{
90 /* Uses a fair bit of stack (use heap instead if you need to) */
91#if INI_USE_STACK
92 char line[INI_MAX_LINE];
93 int max_line = INI_MAX_LINE;
94#else
95 char* line;
96 size_t max_line = INI_INITIAL_ALLOC;
97#endif
98#if INI_ALLOW_REALLOC && !INI_USE_STACK
99 char* new_line;
100 size_t offset;
101#endif
102 char section[MAX_SECTION] = "";
103 char prev_name[MAX_NAME] = "";
104
105 char* start;
106 char* end;
107 char* name;
108 char* value;
109 int lineno = 0;
110 int error = 0;
111
112#if !INI_USE_STACK
113 line = (char*)malloc(INI_INITIAL_ALLOC);
114 if (!line) {
115 return -2;
116 }
117#endif
118
119#if INI_HANDLER_LINENO
120#define HANDLER(u, s, n, v) handler(u, s, n, v, lineno)
121#else
122#define HANDLER(u, s, n, v) handler(u, s, n, v)
123#endif
124
125 /* Scan through stream line by line */
126 while (reader(line, (int)max_line, stream) != NULL) {
127#if INI_ALLOW_REALLOC && !INI_USE_STACK
128 offset = strlen(line);
129 while (offset == max_line - 1 && line[offset - 1] != '\n') {
130 max_line *= 2;
131 if (max_line > INI_MAX_LINE)
132 max_line = INI_MAX_LINE;
133 new_line = realloc(line, max_line);
134 if (!new_line) {
135 free(line);
136 return -2;
137 }
138 line = new_line;
139 if (reader(line + offset, (int)(max_line - offset), stream) == NULL)
140 break;
141 if (max_line >= INI_MAX_LINE)
142 break;
143 offset += strlen(line + offset);
144 }
145#endif
146
147 lineno++;
148
149 start = line;
150#if INI_ALLOW_BOM
151 if (lineno == 1 && (unsigned char)start[0] == 0xEF &&
152 (unsigned char)start[1] == 0xBB &&
153 (unsigned char)start[2] == 0xBF) {
154 start += 3;
155 }
156#endif
157 start = lskip(rstrip(start));
158
159 if (strchr(INI_START_COMMENT_PREFIXES, *start)) {
160 /* Start-of-line comment */
161 }
162#if INI_ALLOW_MULTILINE
163 else if (*prev_name && *start && start > line) {
164 /* Non-blank line with leading whitespace, treat as continuation
165 of previous name's value (as per Python configparser). */
166 if (!HANDLER(user, section, prev_name, start) && !error)
167 error = lineno;
168 }
169#endif
170 else if (*start == '[') {
171 /* A "[section]" line */
172 end = find_chars_or_comment(start + 1, "]");
173 if (*end == ']') {
174 *end = '\0';
175 strncpy0(section, start + 1, sizeof(section));
176 *prev_name = '\0';
177#if INI_CALL_HANDLER_ON_NEW_SECTION
178 if (!HANDLER(user, section, NULL, NULL) && !error)
179 error = lineno;
180#endif
181 }
182 else if (!error) {
183 /* No ']' found on section line */
184 error = lineno;
185 }
186 }
187 else if (*start) {
188 /* Not a comment, must be a name[=:]value pair */
189 end = find_chars_or_comment(start, "=:");
190 if (*end == '=' || *end == ':') {
191 *end = '\0';
192 name = rstrip(start);
193 value = end + 1;
194#if INI_ALLOW_INLINE_COMMENTS
195 end = find_chars_or_comment(value, NULL);
196 if (*end)
197 *end = '\0';
198#endif
199 value = lskip(value);
200 rstrip(value);
201
202 /* Valid name[=:]value pair found, call handler */
203 strncpy0(prev_name, name, sizeof(prev_name));
204 if (!HANDLER(user, section, name, value) && !error)
205 error = lineno;
206 }
207 else if (!error) {
208 /* No '=' or ':' found on name[=:]value line */
209#if INI_ALLOW_NO_VALUE
210 *end = '\0';
211 name = rstrip(start);
212 if (!HANDLER(user, section, name, NULL) && !error)
213 error = lineno;
214#else
215 error = lineno;
216#endif
217 }
218 }
219
220#if INI_STOP_ON_FIRST_ERROR
221 if (error)
222 break;
223#endif
224 }
225
226#if !INI_USE_STACK
227 free(line);
228#endif
229
230 return error;
231}
232
233/* See documentation in header file. */
234int ini_parse_file(FILE* file, ini_handler handler, void* user)
235{
236 return ini_parse_stream((ini_reader)fgets, file, handler, user);
237}
238
239/* See documentation in header file. */
240int ini_parse(const char* filename, ini_handler handler, void* user)
241{
242 FILE* file;
243 int error;
244
245 file = fopen(filename, "r");
246 if (!file)
247 return -1;
248 error = ini_parse_file(file, handler, user);
249 fclose(file);
250 return error;
251}
252
253/* An ini_reader function to read the next line from a string buffer. This
254 is the fgets() equivalent used by ini_parse_string(). */
255static char* ini_reader_string(char* str, int num, void* stream) {
256 ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream;
257 const char* ctx_ptr = ctx->ptr;
258 size_t ctx_num_left = ctx->num_left;
259 char* strp = str;
260 char c;
261
262 if (ctx_num_left == 0 || num < 2)
263 return NULL;
264
265 while (num > 1 && ctx_num_left != 0) {
266 c = *ctx_ptr++;
267 ctx_num_left--;
268 *strp++ = c;
269 if (c == '\n')
270 break;
271 num--;
272 }
273
274 *strp = '\0';
275 ctx->ptr = ctx_ptr;
276 ctx->num_left = ctx_num_left;
277 return str;
278}
279
280/* See documentation in header file. */
281int ini_parse_string(const char* string, ini_handler handler, void* user) {
282 ini_parse_string_ctx ctx;
283
284 ctx.ptr = string;
285 ctx.num_left = strlen(string);
286 return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler,
287 user);
288}