xref: /f-stack/lib/ff_ini_parser.c (revision ebf5cedb)
1 /* inih -- simple .INI file parser
2 
3 inih is released under the New BSD license. Go to the project
4 home page for more info:
5 
6 https://github.com/benhoyt/inih
7 
8 */
9 
10 #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS)
11 #define _CRT_SECURE_NO_WARNINGS
12 #endif
13 
14 #include <stdio.h>
15 #include <ctype.h>
16 #include <string.h>
17 
18 #include "ff_ini_parser.h"
19 
20 #if !INI_USE_STACK
21 #include <stdlib.h>
22 #endif
23 
24 #define MAX_SECTION 50
25 #define MAX_NAME 50
26 
27 /* Strip whitespace chars off end of given string, in place. Return s. */
28 static char* rstrip(char* s)
29 {
30     char* p = s + strlen(s);
31     while (p > s && isspace((unsigned char)(*--p)))
32         *p = '\0';
33     return s;
34 }
35 
36 /* Return pointer to first non-whitespace char in given string. */
37 static char* lskip(const char* s)
38 {
39     while (*s && isspace((unsigned char)(*s)))
40         s++;
41     return (char*)s;
42 }
43 
44 /* Return pointer to first char (of chars) or inline comment in given string,
45    or pointer to null at end of string if neither found. Inline comment must
46    be prefixed by a whitespace character to register as a comment. */
47 static char* find_chars_or_comment(const char* s, const char* chars)
48 {
49 #if INI_ALLOW_INLINE_COMMENTS
50     int was_space = 0;
51     while (*s && (!chars || !strchr(chars, *s)) &&
52            !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) {
53         was_space = isspace((unsigned char)(*s));
54         s++;
55     }
56 #else
57     while (*s && (!chars || !strchr(chars, *s))) {
58         s++;
59     }
60 #endif
61     return (char*)s;
62 }
63 
64 /* Version of strncpy that ensures dest (size bytes) is null-terminated. */
65 static char* strncpy0(char* dest, const char* src, size_t size)
66 {
67     strncpy(dest, src, size - 1);
68     dest[size - 1] = '\0';
69     return dest;
70 }
71 
72 /* See documentation in header file. */
73 int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
74                      void* user)
75 {
76     /* Uses a fair bit of stack (use heap instead if you need to) */
77 #if INI_USE_STACK
78     char line[INI_MAX_LINE];
79 #else
80     char* line;
81 #endif
82     char section[MAX_SECTION] = "";
83     char prev_name[MAX_NAME] = "";
84 
85     char* start;
86     char* end;
87     char* name;
88     char* value;
89     int lineno = 0;
90     int error = 0;
91 
92 #if !INI_USE_STACK
93     line = (char*)malloc(INI_MAX_LINE);
94     if (!line) {
95         return -2;
96     }
97 #endif
98 
99     /* Scan through stream line by line */
100     while (reader(line, INI_MAX_LINE, stream) != NULL) {
101         lineno++;
102 
103         start = line;
104 #if INI_ALLOW_BOM
105         if (lineno == 1 && (unsigned char)start[0] == 0xEF &&
106                            (unsigned char)start[1] == 0xBB &&
107                            (unsigned char)start[2] == 0xBF) {
108             start += 3;
109         }
110 #endif
111         start = lskip(rstrip(start));
112 
113         if (*start == ';' || *start == '#') {
114             /* Per Python configparser, allow both ; and # comments at the
115                start of a line */
116         }
117 #if INI_ALLOW_MULTILINE
118         else if (*prev_name && *start && start > line) {
119             /* Non-blank line with leading whitespace, treat as continuation
120                of previous name's value (as per Python configparser). */
121             if (!handler(user, section, prev_name, start) && !error)
122                 error = lineno;
123         }
124 #endif
125         else if (*start == '[') {
126             /* A "[section]" line */
127             end = find_chars_or_comment(start + 1, "]");
128             if (*end == ']') {
129                 *end = '\0';
130                 strncpy0(section, start + 1, sizeof(section));
131                 *prev_name = '\0';
132             }
133             else if (!error) {
134                 /* No ']' found on section line */
135                 error = lineno;
136             }
137         }
138         else if (*start) {
139             /* Not a comment, must be a name[=:]value pair */
140             end = find_chars_or_comment(start, "=:");
141             if (*end == '=' || *end == ':') {
142                 *end = '\0';
143                 name = rstrip(start);
144                 value = end + 1;
145 #if INI_ALLOW_INLINE_COMMENTS
146                 end = find_chars_or_comment(value, NULL);
147                 if (*end)
148                     *end = '\0';
149 #endif
150                 value = lskip(value);
151                 rstrip(value);
152 
153                 /* Valid name[=:]value pair found, call handler */
154                 strncpy0(prev_name, name, sizeof(prev_name));
155                 if (!handler(user, section, name, value) && !error)
156                     error = lineno;
157             }
158             else if (!error) {
159                 /* No '=' or ':' found on name[=:]value line */
160                 error = lineno;
161             }
162         }
163 
164 #if INI_STOP_ON_FIRST_ERROR
165         if (error)
166             break;
167 #endif
168     }
169 
170 #if !INI_USE_STACK
171     free(line);
172 #endif
173 
174     return error;
175 }
176 
177 /* See documentation in header file. */
178 int ini_parse_file(FILE* file, ini_handler handler, void* user)
179 {
180     return ini_parse_stream((ini_reader)fgets, file, handler, user);
181 }
182 
183 /* See documentation in header file. */
184 int ini_parse(const char* filename, ini_handler handler, void* user)
185 {
186     FILE* file;
187     int error;
188 
189     file = fopen(filename, "r");
190     if (!file)
191         return -1;
192     error = ini_parse_file(file, handler, user);
193     fclose(file);
194     return error;
195 }
196