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 "inih.h"
15 
16 #define MAX_LINE 1024
17 #define MAX_SECTION 64
18 #define MAX_NAME 64
19 
20 /* Strip whitespace chars off end of given string, in place. Return s. */
rstrip(char * s)21 static char* rstrip(char* s)
22 {
23     char* p = s + strlen(s);
24     while (p > s && isspace(*--p))
25         *p = '\0';
26     return s;
27 }
28 
29 /* Return pointer to first non-whitespace char in given string. */
lskip(const char * s)30 static char* lskip(const char* s)
31 {
32     while (*s && isspace(*s))
33         s++;
34     return (char*)s;
35 }
36 
37 /* Return pointer to first char c or ';' comment in given string, or pointer to
38    null at end of string if neither found. ';' must be prefixed by a whitespace
39    character to register as a comment. */
find_char_or_comment(const char * s,char c)40 static char* find_char_or_comment(const char* s, char c)
41 {
42     int was_whitespace = 0;
43     while (*s && *s != c && !(was_whitespace && *s == ';')) {
44         was_whitespace = isspace(*s);
45         s++;
46     }
47     return (char*)s;
48 }
49 
50 /* Version of strncpy that ensures dest (size bytes) is null-terminated. */
strncpy0(char * dest,const char * src,size_t size)51 static char* strncpy0(char* dest, const char* src, size_t size)
52 {
53     strncpy(dest, src, size);
54     dest[size - 1] = '\0';
55     return dest;
56 }
57 
58 /* See documentation in header file. */
ini_parse_file(FILE * file,int (* handler)(void *,const char *,const char *,const char *),void * user)59 int ini_parse_file(FILE* file,
60                    int (*handler)(void*, const char*, const char*,
61                                   const char*),
62                    void* user)
63 {
64     /* Uses a fair bit of stack (use heap instead if you need to) */
65     char line[MAX_LINE];
66     char section[MAX_SECTION] = "";
67     char prev_name[MAX_NAME] = "";
68 
69     char* start;
70     char* end;
71     char* name;
72     char* value;
73     int lineno = 0;
74     int error = 0;
75 
76     /* Scan through file line by line */
77     while (fgets(line, sizeof(line), file) != NULL) {
78         lineno++;
79         start = lskip(rstrip(line));
80 
81         if (*start == ';' || *start == '#') {
82             /* Per Python ConfigParser, allow '#' comments at start of line */
83         }
84 #if INI_ALLOW_MULTILINE
85         else if (*prev_name && *start && start > line) {
86             /* Non-black line with leading whitespace, treat as continuation
87                of previous name's value (as per Python ConfigParser). */
88             if (!handler(user, section, prev_name, start) && !error)
89                 error = lineno;
90         }
91 #endif
92         else if (*start == '[') {
93             /* A "[section]" line */
94             end = find_char_or_comment(start + 1, ']');
95             if (*end == ']') {
96                 *end = '\0';
97                 strncpy0(section, start + 1, sizeof(section));
98                 *prev_name = '\0';
99             }
100             else if (!error) {
101                 /* No ']' found on section line */
102                 error = lineno;
103             }
104         }
105         else if (*start && *start != ';') {
106             /* Not a comment, must be a name[=:]value pair */
107             end = find_char_or_comment(start, '=');
108             if (*end != '=') {
109                 end = find_char_or_comment(start, ':');
110             }
111             if (*end == '=' || *end == ':') {
112                 *end = '\0';
113                 name = rstrip(start);
114                 value = lskip(end + 1);
115                 end = find_char_or_comment(value, '\0');
116                 if (*end == ';')
117                     *end = '\0';
118                 rstrip(value);
119 
120                 /* Valid name[=:]value pair found, call handler */
121                 strncpy0(prev_name, name, sizeof(prev_name));
122                 if (!handler(user, section, name, value) && !error)
123                     error = lineno;
124             }
125             else if (!error) {
126                 /* No '=' or ':' found on name[=:]value line */
127                 error = lineno;
128             }
129         }
130     }
131 
132     return error;
133 }
134 
135 /* See documentation in header file. */
ini_parse(const char * filename,int (* handler)(void *,const char *,const char *,const char *),void * user)136 int ini_parse(const char* filename,
137               int (*handler)(void*, const char*, const char*, const char*),
138               void* user)
139 {
140     FILE* file;
141     int error;
142 
143     file = fopen(filename, "r");
144     if (!file)
145         return -1;
146     error = ini_parse_file(file, handler, user);
147     fclose(file);
148     return error;
149 }
150