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