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