1 /**
2 * Copyright (c) 2016 rxi
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20 * SOFTWARE.
21 */
22
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <ctype.h>
27
28 #include "rkaiq_ini.h"
29
30 struct ini_t {
31 char *data;
32 char *end;
33 };
34
35
36 /* Case insensitive string compare */
strcmpci(const char * a,const char * b)37 static int strcmpci(const char *a, const char *b) {
38 for (;;) {
39 int d = tolower(*a) - tolower(*b);
40 if (d != 0 || !*a) {
41 return d;
42 }
43 a++, b++;
44 }
45 }
46
47 /* Returns the next string in the split data */
next(ini_t * ini,char * p)48 static char* next(ini_t *ini, char *p) {
49 p += strlen(p);
50 while (p < ini->end && *p == '\0') {
51 p++;
52 }
53 return p;
54 }
55
trim_back(ini_t * ini,char * p)56 static void trim_back(ini_t *ini, char *p) {
57 while (p >= ini->data && (*p == ' ' || *p == '\t' || *p == '\r')) {
58 *p-- = '\0';
59 }
60 }
61
discard_line(ini_t * ini,char * p)62 static char* discard_line(ini_t *ini, char *p) {
63 while (p < ini->end && *p != '\n') {
64 *p++ = '\0';
65 }
66 return p;
67 }
68
69
unescape_quoted_value(ini_t * ini,char * p)70 static char *unescape_quoted_value(ini_t *ini, char *p) {
71 /* Use `q` as write-head and `p` as read-head, `p` is always ahead of `q`
72 * as escape sequences are always larger than their resultant data */
73 char *q = p;
74 p++;
75 while (p < ini->end && *p != '"' && *p != '\r' && *p != '\n') {
76 if (*p == '\\') {
77 /* Handle escaped char */
78 p++;
79 switch (*p) {
80 default : *q = *p; break;
81 case 'r' : *q = '\r'; break;
82 case 'n' : *q = '\n'; break;
83 case 't' : *q = '\t'; break;
84 case '\r' :
85 case '\n' :
86 case '\0' : goto end;
87 }
88
89 } else {
90 /* Handle normal char */
91 *q = *p;
92 }
93 q++, p++;
94 }
95 end:
96 return q;
97 }
98
99
100 /* Splits data in place into strings containing section-headers, keys and
101 * values using one or more '\0' as a delimiter. Unescapes quoted values */
split_data(ini_t * ini)102 static void split_data(ini_t *ini) {
103 char *value_start, *line_start;
104 char *p = ini->data;
105
106 while (p < ini->end) {
107 switch (*p) {
108 case '\r':
109 case '\n':
110 case '\t':
111 case ' ':
112 *p = '\0';
113 p++;
114 break;
115 case '\0':
116 p++;
117 break;
118
119 case '[':
120 p += strcspn(p, "]\n");
121 *p = '\0';
122 break;
123
124 case ';':
125 p = discard_line(ini, p);
126 break;
127
128 default:
129 line_start = p;
130 p += strcspn(p, "=\n");
131
132 /* Is line missing a '='? */
133 if (*p != '=') {
134 p = discard_line(ini, line_start);
135 break;
136 }
137 trim_back(ini, p - 1);
138
139 /* Replace '=' and whitespace after it with '\0' */
140 do {
141 *p++ = '\0';
142 } while (*p == ' ' || *p == '\r' || *p == '\t');
143
144 /* Is a value after '=' missing? */
145 if (*p == '\n' || *p == '\0') {
146 p = discard_line(ini, line_start);
147 break;
148 }
149
150 if (*p == '"') {
151 /* Handle quoted string value */
152 value_start = p;
153 p = unescape_quoted_value(ini, p);
154
155 /* Was the string empty? */
156 if (p == value_start) {
157 p = discard_line(ini, line_start);
158 break;
159 }
160
161 /* Discard the rest of the line after the string value */
162 p = discard_line(ini, p);
163
164 } else {
165 /* Handle normal value */
166 p += strcspn(p, "\n");
167 trim_back(ini, p - 1);
168 }
169 break;
170 }
171 }
172 }
173
174
175
rkaiq_ini_load(const char * filename)176 ini_t* rkaiq_ini_load(const char *filename) {
177 ini_t *ini = NULL;
178 FILE *fp = NULL;
179 int n, sz;
180
181 /* Init ini struct */
182 ini = (ini_t*)malloc(sizeof(*ini));
183 if (!ini) {
184 goto fail;
185 }
186 memset(ini, 0, sizeof(*ini));
187
188 /* Open file */
189 fp = fopen(filename, "rb");
190 if (!fp) {
191 goto fail;
192 }
193
194 /* Get file size */
195 fseek(fp, 0, SEEK_END);
196 sz = ftell(fp);
197 rewind(fp);
198
199 /* Load file content into memory, null terminate, init end var */
200 ini->data = (char*)malloc(sz + 1);
201 ini->data[sz] = '\0';
202 ini->end = ini->data + sz;
203 n = fread(ini->data, 1, sz, fp);
204 if (n != sz) {
205 goto fail;
206 }
207
208 /* Prepare data */
209 split_data(ini);
210
211 /* Clean up and return */
212 fclose(fp);
213 return ini;
214
215 fail:
216 if (fp) fclose(fp);
217 if (ini) rkaiq_ini_free(ini);
218 return NULL;
219 }
220
221
rkaiq_ini_free(ini_t * ini)222 void rkaiq_ini_free(ini_t *ini) {
223 free(ini->data);
224 free(ini);
225 }
226
227
rkaiq_ini_get(ini_t * ini,const char * section,const char * key)228 const char* rkaiq_ini_get(ini_t *ini, const char *section, const char *key) {
229 char *current_section = "";
230 char *val;
231 char *p = ini->data;
232
233 if (*p == '\0') {
234 p = next(ini, p);
235 }
236
237 while (p < ini->end) {
238 if (*p == '[') {
239 /* Handle section */
240 current_section = p + 1;
241
242 } else {
243 /* Handle key */
244 val = next(ini, p);
245 if (!section || !strcmpci(section, current_section)) {
246 if (!strcmpci(p, key)) {
247 return val;
248 }
249 }
250 p = val;
251 }
252
253 p = next(ini, p);
254 }
255
256 return NULL;
257 }
258
259
rkaiq_ini_sget(ini_t * ini,const char * section,const char * key,const char * scanfmt,void * dst)260 int rkaiq_ini_sget(
261 ini_t *ini, const char *section, const char *key,
262 const char *scanfmt, void *dst
263 ) {
264 const char *val = rkaiq_ini_get(ini, section, key);
265 if (!val) {
266 return 0;
267 }
268 if (scanfmt) {
269 sscanf(val, scanfmt, dst);
270 } else {
271 *((const char**) dst) = val;
272 }
273 return 1;
274 }
275