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