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