• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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