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