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