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