1 /* inih -- simple .INI file parser
2 
3 The "inih" library is distributed under the New BSD license:
4 
5 Copyright (c) 2009, Brush Technology
6 All rights reserved.
7 
8 Redistribution and use in source and binary forms, with or without
9 modification, are permitted provided that the following conditions are met:
10     * Redistributions of source code must retain the above copyright
11       notice, this list of conditions and the following disclaimer.
12     * Redistributions in binary form must reproduce the above copyright
13       notice, this list of conditions and the following disclaimer in the
14       documentation and/or other materials provided with the distribution.
15     * Neither the name of Brush Technology nor the names of its contributors
16       may be used to endorse or promote products derived from this software
17       without specific prior written permission.
18 
19 THIS SOFTWARE IS PROVIDED BY BRUSH TECHNOLOGY ''AS IS'' AND ANY
20 EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 DISCLAIMED. IN NO EVENT SHALL BRUSH TECHNOLOGY BE LIABLE FOR ANY
23 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 
30 Go to the project home page for more info:
31 http://code.google.com/p/inih/
32 
33 */
34 
35 #include "global.h"
36 
37 #include <stdio.h>
38 #include <ctype.h>
39 #include <string.h>
40 
41 #include "ini.h"
42 
43 #define MAX_LINE 5000
44 #define MAX_SECTION MAX_SECTION_NAME
45 #define MAX_NAME MAX_PROPERTY_NAME
46 
47 /* Strip whitespace chars off end of given string, in place. Return s. */
rstrip(char * s)48 static char* rstrip(char* s)
49 {
50     char* p = s + strlen(s);
51     while (p > s && isspace(*--p))
52         *p = '\0';
53     return s;
54 }
55 
56 /* Return pointer to first non-whitespace char in given string. */
lskip(const char * s)57 static char* lskip(const char* s)
58 {
59     while (*s && isspace(*s))
60         s++;
61     return (char*)s;
62 }
63 
64 /* Return pointer to first char c or ';' comment in given string, or pointer to
65    null at end of string if neither found. ';' must be prefixed by a whitespace
66    character to register as a comment. */
find_char_or_comment(const char * s,char c)67 static char* find_char_or_comment(const char* s, char c)
68 {
69     int was_whitespace = 0;
70     while (*s && *s != c && !(was_whitespace && (*s == ';' || *s == '#'))) {
71         was_whitespace = isspace(*s);
72         s++;
73     }
74     return (char*)s;
75 }
76 
find_last_char_or_comment(const char * s,char c)77 static char* find_last_char_or_comment(const char* s, char c)
78 {
79     const char* last_char = s;
80     int was_whitespace = 0;
81     while (*s && !(was_whitespace && (*s == ';' || *s == '#'))) {
82         if (*s == c)
83             last_char = s;
84         was_whitespace = isspace(*s);
85         s++;
86     }
87     return (char*)last_char;
88 }
89 
90 /* Version of strncpy that ensures dest (size bytes) is null-terminated. */
strncpy0(char * dest,const char * src,size_t size)91 static char* strncpy0(char* dest, const char* src, size_t size)
92 {
93     strncpy(dest, src, size);
94     dest[size - 1] = '\0';
95     return dest;
96 }
97 
98 /* See documentation in header file. */
99 EDITORCONFIG_LOCAL
ini_parse_file(FILE * file,int (* handler)(void *,const char *,const char *,const char *),void * user)100 int ini_parse_file(FILE* file,
101                    int (*handler)(void*, const char*, const char*,
102                                   const char*),
103                    void* user)
104 {
105     /* Uses a fair bit of stack (use heap instead if you need to) */
106     char line[MAX_LINE];
107     char section[MAX_SECTION+1] = "";
108     char prev_name[MAX_NAME+1] = "";
109 
110     char* start;
111     char* end;
112     char* name;
113     char* value;
114     int lineno = 0;
115     int error = 0;
116 
117     /* Scan through file line by line */
118     while (fgets(line, sizeof(line), file) != NULL) {
119         lineno++;
120 
121         start = line;
122 #if INI_ALLOW_BOM
123         if (lineno == 1 && (unsigned char)start[0] == 0xEF &&
124                            (unsigned char)start[1] == 0xBB &&
125                            (unsigned char)start[2] == 0xBF) {
126             start += 3;
127         }
128 #endif
129         start = lskip(rstrip(start));
130 
131         if (*start == ';' || *start == '#') {
132             /* Per Python ConfigParser, allow '#' comments at start of line */
133         }
134 #if INI_ALLOW_MULTILINE
135         else if (*prev_name && *start && start > line) {
136             /* Non-black line with leading whitespace, treat as continuation
137                of previous name's value (as per Python ConfigParser). */
138             if (!handler(user, section, prev_name, start) && !error)
139                 error = lineno;
140         }
141 #endif
142         else if (*start == '[') {
143             /* A "[section]" line */
144             end = find_last_char_or_comment(start + 1, ']');
145             if (*end == ']') {
146                 *end = '\0';
147                 /* Section name too long. Skipped. */
148                 if (end - start - 1 > MAX_SECTION_NAME)
149                     continue;
150                 strncpy0(section, start + 1, sizeof(section));
151                 *prev_name = '\0';
152             }
153             else if (!error) {
154                 /* No ']' found on section line */
155                 error = lineno;
156             }
157         }
158         else if (*start && (*start != ';' || *start == '#')) {
159             /* Not a comment, must be a name[=:]value pair */
160             end = find_char_or_comment(start, '=');
161             if (*end != '=') {
162                 end = find_char_or_comment(start, ':');
163             }
164             if (*end == '=' || *end == ':') {
165                 *end = '\0';
166                 name = rstrip(start);
167                 value = lskip(end + 1);
168                 end = find_char_or_comment(value, '\0');
169                 if (*end == ';' || *end == '#')
170                     *end = '\0';
171                 rstrip(value);
172 
173                 /* Either name or value is too long. Skip it. */
174                 if (strlen(name) > MAX_PROPERTY_NAME ||
175                     strlen(value) > MAX_PROPERTY_VALUE)
176                     continue;
177 
178                 /* Valid name[=:]value pair found, call handler */
179                 strncpy0(prev_name, name, sizeof(prev_name));
180                 if (!handler(user, section, name, value) && !error)
181                     error = lineno;
182             }
183             else if (!error) {
184                 /* No '=' or ':' found on name[=:]value line */
185                 error = lineno;
186             }
187         }
188     }
189 
190     return error;
191 }
192 
193 /* See documentation in header file. */
194 EDITORCONFIG_LOCAL
ini_parse(const char * filename,int (* handler)(void *,const char *,const char *,const char *),void * user)195 int ini_parse(const char* filename,
196               int (*handler)(void*, const char*, const char*, const char*),
197               void* user)
198 {
199     FILE* file;
200     int error;
201 
202     file = fopen(filename, "r");
203     if (!file)
204         return -1;
205     error = ini_parse_file(file, handler, user);
206     fclose(file);
207     return error;
208 }
209