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