ini.h (14075B)
1 /* inih -- simple .INI file parser 2 3 SPDX-License-Identifier: BSD-3-Clause 4 5 Copyright (C) 2009-2020, Ben Hoyt 6 7 inih is released under the New BSD license (see LICENSE.txt). Go to the project 8 home page for more info: 9 10 https://github.com/benhoyt/inih 11 12 */ 13 14 #ifndef INI_H 15 #define INI_H 16 17 /* Make this header file easier to include in C++ code */ 18 #ifdef __cplusplus 19 extern "C" { 20 #endif 21 22 #include <stdio.h> 23 24 /* Nonzero if ini_handler callback should accept lineno parameter. */ 25 #ifndef INI_HANDLER_LINENO 26 #define INI_HANDLER_LINENO 0 27 #endif 28 29 /* Visibility symbols, required for Windows DLLs */ 30 #ifndef INI_API 31 #if defined _WIN32 || defined __CYGWIN__ 32 # ifdef INI_SHARED_LIB 33 # ifdef INI_SHARED_LIB_BUILDING 34 # define INI_API __declspec(dllexport) 35 # else 36 # define INI_API __declspec(dllimport) 37 # endif 38 # else 39 # define INI_API 40 # endif 41 #else 42 # if defined(__GNUC__) && __GNUC__ >= 4 43 # define INI_API __attribute__ ((visibility ("default"))) 44 # else 45 # define INI_API 46 # endif 47 #endif 48 #endif 49 50 /* Typedef for prototype of handler function. */ 51 #if INI_HANDLER_LINENO 52 typedef int (*ini_handler)(void* user, const char* section, 53 const char* name, const char* value, 54 int lineno); 55 #else 56 typedef int (*ini_handler)(void* user, const char* section, 57 const char* name, const char* value); 58 #endif 59 60 /* Typedef for prototype of fgets-style reader function. */ 61 typedef char* (*ini_reader)(char* str, int num, void* stream); 62 63 /* Parse given INI-style file. May have [section]s, name=value pairs 64 (whitespace stripped), and comments starting with ';' (semicolon). Section 65 is "" if name=value pair parsed before any section heading. name:value 66 pairs are also supported as a concession to Python's configparser. 67 68 For each name=value pair parsed, call handler function with given user 69 pointer as well as section, name, and value (data only valid for duration 70 of handler call). Handler should return nonzero on success, zero on error. 71 72 Returns 0 on success, line number of first error on parse error (doesn't 73 stop on first error), -1 on file open error, or -2 on memory allocation 74 error (only when INI_USE_STACK is zero). 75 */ 76 INI_API int ini_parse(const char* filename, ini_handler handler, void* user); 77 78 /* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't 79 close the file when it's finished -- the caller must do that. */ 80 INI_API int ini_parse_file(FILE* file, ini_handler handler, void* user); 81 82 /* Same as ini_parse(), but takes an ini_reader function pointer instead of 83 filename. Used for implementing custom or string-based I/O (see also 84 ini_parse_string). */ 85 INI_API int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, 86 void* user); 87 88 /* Same as ini_parse(), but takes a zero-terminated string with the INI data 89 instead of a file. Useful for parsing INI data from a network socket or 90 already in memory. */ 91 INI_API int ini_parse_string(const char* string, ini_handler handler, void* user); 92 93 /* Nonzero to allow multi-line value parsing, in the style of Python's 94 configparser. If allowed, ini_parse() will call the handler with the same 95 name for each subsequent line parsed. */ 96 #ifndef INI_ALLOW_MULTILINE 97 #define INI_ALLOW_MULTILINE 1 98 #endif 99 100 /* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of 101 the file. See https://github.com/benhoyt/inih/issues/21 */ 102 #ifndef INI_ALLOW_BOM 103 #define INI_ALLOW_BOM 1 104 #endif 105 106 /* Chars that begin a start-of-line comment. Per Python configparser, allow 107 both ; and # comments at the start of a line by default. */ 108 #ifndef INI_START_COMMENT_PREFIXES 109 #define INI_START_COMMENT_PREFIXES ";#" 110 #endif 111 112 /* Nonzero to allow inline comments (with valid inline comment characters 113 specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match 114 Python 3.2+ configparser behaviour. */ 115 #ifndef INI_ALLOW_INLINE_COMMENTS 116 #define INI_ALLOW_INLINE_COMMENTS 1 117 #endif 118 #ifndef INI_INLINE_COMMENT_PREFIXES 119 #define INI_INLINE_COMMENT_PREFIXES ";" 120 #endif 121 122 /* Nonzero to use stack for line buffer, zero to use heap (malloc/free). */ 123 #ifndef INI_USE_STACK 124 #define INI_USE_STACK 1 125 #endif 126 127 /* Maximum line length for any line in INI file (stack or heap). Note that 128 this must be 3 more than the longest line (due to '\r', '\n', and '\0'). */ 129 #ifndef INI_MAX_LINE 130 #define INI_MAX_LINE 200 131 #endif 132 133 /* Nonzero to allow heap line buffer to grow via realloc(), zero for a 134 fixed-size buffer of INI_MAX_LINE bytes. Only applies if INI_USE_STACK is 135 zero. */ 136 #ifndef INI_ALLOW_REALLOC 137 #define INI_ALLOW_REALLOC 0 138 #endif 139 140 /* Initial size in bytes for heap line buffer. Only applies if INI_USE_STACK 141 is zero. */ 142 #ifndef INI_INITIAL_ALLOC 143 #define INI_INITIAL_ALLOC 200 144 #endif 145 146 /* Stop parsing on first error (default is to keep parsing). */ 147 #ifndef INI_STOP_ON_FIRST_ERROR 148 #define INI_STOP_ON_FIRST_ERROR 0 149 #endif 150 151 /* Nonzero to call the handler at the start of each new section (with 152 name and value NULL). Default is to only call the handler on 153 each name=value pair. */ 154 #ifndef INI_CALL_HANDLER_ON_NEW_SECTION 155 #define INI_CALL_HANDLER_ON_NEW_SECTION 0 156 #endif 157 158 /* Nonzero to allow a name without a value (no '=' or ':' on the line) and 159 call the handler with value NULL in this case. Default is to treat 160 no-value lines as an error. */ 161 #ifndef INI_ALLOW_NO_VALUE 162 #define INI_ALLOW_NO_VALUE 0 163 #endif 164 165 /* Nonzero to use custom ini_malloc, ini_free, and ini_realloc memory 166 allocation functions (INI_USE_STACK must also be 0). These functions must 167 have the same signatures as malloc/free/realloc and behave in a similar 168 way. ini_realloc is only needed if INI_ALLOW_REALLOC is set. */ 169 #ifndef INI_CUSTOM_ALLOCATOR 170 #define INI_CUSTOM_ALLOCATOR 0 171 #endif 172 173 174 #ifdef __cplusplus 175 } 176 #endif 177 178 #ifdef INI_IMPL 179 180 #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) 181 #define _CRT_SECURE_NO_WARNINGS 182 #endif 183 184 #include <stdio.h> 185 #include <ctype.h> 186 #include <string.h> 187 188 #include "ini.h" 189 190 #if !INI_USE_STACK 191 #if INI_CUSTOM_ALLOCATOR 192 #include <stddef.h> 193 void* ini_malloc(size_t size); 194 void ini_free(void* ptr); 195 void* ini_realloc(void* ptr, size_t size); 196 #else 197 #include <stdlib.h> 198 #define ini_malloc malloc 199 #define ini_free free 200 #define ini_realloc realloc 201 #endif 202 #endif 203 204 #define MAX_SECTION 50 205 #define MAX_NAME 50 206 207 /* Used by ini_parse_string() to keep track of string parsing state. */ 208 typedef struct { 209 const char* ptr; 210 size_t num_left; 211 } ini_parse_string_ctx; 212 213 /* Strip whitespace chars off end of given string, in place. Return s. */ 214 static char* ini_rstrip(char* s) 215 { 216 char* p = s + strlen(s); 217 while (p > s && isspace((unsigned char)(*--p))) 218 *p = '\0'; 219 return s; 220 } 221 222 /* Return pointer to first non-whitespace char in given string. */ 223 static char* ini_lskip(const char* s) 224 { 225 while (*s && isspace((unsigned char)(*s))) 226 s++; 227 return (char*)s; 228 } 229 230 /* Return pointer to first char (of chars) or inline comment in given string, 231 or pointer to NUL at end of string if neither found. Inline comment must 232 be prefixed by a whitespace character to register as a comment. */ 233 static char* ini_find_chars_or_comment(const char* s, const char* chars) 234 { 235 #if INI_ALLOW_INLINE_COMMENTS 236 int was_space = 0; 237 while (*s && (!chars || !strchr(chars, *s)) && 238 !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) { 239 was_space = isspace((unsigned char)(*s)); 240 s++; 241 } 242 #else 243 while (*s && (!chars || !strchr(chars, *s))) { 244 s++; 245 } 246 #endif 247 return (char*)s; 248 } 249 250 /* Similar to strncpy, but ensures dest (size bytes) is 251 NUL-terminated, and doesn't pad with NULs. */ 252 static char* ini_strncpy0(char* dest, const char* src, size_t size) 253 { 254 /* Could use strncpy internally, but it causes gcc warnings (see issue #91) */ 255 size_t i; 256 for (i = 0; i < size - 1 && src[i]; i++) 257 dest[i] = src[i]; 258 dest[i] = '\0'; 259 return dest; 260 } 261 262 /* See documentation in header file. */ 263 int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, 264 void* user) 265 { 266 /* Uses a fair bit of stack (use heap instead if you need to) */ 267 #if INI_USE_STACK 268 char line[INI_MAX_LINE]; 269 size_t max_line = INI_MAX_LINE; 270 #else 271 char* line; 272 size_t max_line = INI_INITIAL_ALLOC; 273 #endif 274 #if INI_ALLOW_REALLOC && !INI_USE_STACK 275 char* new_line; 276 size_t offset; 277 #endif 278 char section[MAX_SECTION] = ""; 279 #if INI_ALLOW_MULTILINE 280 char prev_name[MAX_NAME] = ""; 281 #endif 282 283 char* start; 284 char* end; 285 char* name; 286 char* value; 287 int lineno = 0; 288 int error = 0; 289 290 #if !INI_USE_STACK 291 line = (char*)ini_malloc(INI_INITIAL_ALLOC); 292 if (!line) { 293 return -2; 294 } 295 #endif 296 297 #if INI_HANDLER_LINENO 298 #define HANDLER(u, s, n, v) handler(u, s, n, v, lineno) 299 #else 300 #define HANDLER(u, s, n, v) handler(u, s, n, v) 301 #endif 302 303 /* Scan through stream line by line */ 304 while (reader(line, (int)max_line, stream) != NULL) { 305 #if INI_ALLOW_REALLOC && !INI_USE_STACK 306 offset = strlen(line); 307 while (offset == max_line - 1 && line[offset - 1] != '\n') { 308 max_line *= 2; 309 if (max_line > INI_MAX_LINE) 310 max_line = INI_MAX_LINE; 311 new_line = ini_realloc(line, max_line); 312 if (!new_line) { 313 ini_free(line); 314 return -2; 315 } 316 line = new_line; 317 if (reader(line + offset, (int)(max_line - offset), stream) == NULL) 318 break; 319 if (max_line >= INI_MAX_LINE) 320 break; 321 offset += strlen(line + offset); 322 } 323 #endif 324 325 lineno++; 326 327 start = line; 328 #if INI_ALLOW_BOM 329 if (lineno == 1 && (unsigned char)start[0] == 0xEF && 330 (unsigned char)start[1] == 0xBB && 331 (unsigned char)start[2] == 0xBF) { 332 start += 3; 333 } 334 #endif 335 start = ini_rstrip(ini_lskip(start)); 336 337 if (strchr(INI_START_COMMENT_PREFIXES, *start)) { 338 /* Start-of-line comment */ 339 } 340 #if INI_ALLOW_MULTILINE 341 else if (*prev_name && *start && start > line) { 342 #if INI_ALLOW_INLINE_COMMENTS 343 end = ini_find_chars_or_comment(start, NULL); 344 if (*end) 345 *end = '\0'; 346 ini_rstrip(start); 347 #endif 348 /* Non-blank line with leading whitespace, treat as continuation 349 of previous name's value (as per Python configparser). */ 350 if (!HANDLER(user, section, prev_name, start) && !error) 351 error = lineno; 352 } 353 #endif 354 else if (*start == '[') { 355 /* A "[section]" line */ 356 end = ini_find_chars_or_comment(start + 1, "]"); 357 if (*end == ']') { 358 *end = '\0'; 359 ini_strncpy0(section, start + 1, sizeof(section)); 360 #if INI_ALLOW_MULTILINE 361 *prev_name = '\0'; 362 #endif 363 #if INI_CALL_HANDLER_ON_NEW_SECTION 364 if (!HANDLER(user, section, NULL, NULL) && !error) 365 error = lineno; 366 #endif 367 } 368 else if (!error) { 369 /* No ']' found on section line */ 370 error = lineno; 371 } 372 } 373 else if (*start) { 374 /* Not a comment, must be a name[=:]value pair */ 375 end = ini_find_chars_or_comment(start, "=:"); 376 if (*end == '=' || *end == ':') { 377 *end = '\0'; 378 name = ini_rstrip(start); 379 value = end + 1; 380 #if INI_ALLOW_INLINE_COMMENTS 381 end = ini_find_chars_or_comment(value, NULL); 382 if (*end) 383 *end = '\0'; 384 #endif 385 value = ini_lskip(value); 386 ini_rstrip(value); 387 388 #if INI_ALLOW_MULTILINE 389 ini_strncpy0(prev_name, name, sizeof(prev_name)); 390 #endif 391 /* Valid name[=:]value pair found, call handler */ 392 if (!HANDLER(user, section, name, value) && !error) 393 error = lineno; 394 } 395 else if (!error) { 396 /* No '=' or ':' found on name[=:]value line */ 397 #if INI_ALLOW_NO_VALUE 398 *end = '\0'; 399 name = ini_rstrip(start); 400 if (!HANDLER(user, section, name, NULL) && !error) 401 error = lineno; 402 #else 403 error = lineno; 404 #endif 405 } 406 } 407 408 #if INI_STOP_ON_FIRST_ERROR 409 if (error) 410 break; 411 #endif 412 } 413 414 #if !INI_USE_STACK 415 ini_free(line); 416 #endif 417 418 return error; 419 } 420 421 /* See documentation in header file. */ 422 int ini_parse_file(FILE* file, ini_handler handler, void* user) 423 { 424 return ini_parse_stream((ini_reader)fgets, file, handler, user); 425 } 426 427 /* See documentation in header file. */ 428 int ini_parse(const char* filename, ini_handler handler, void* user) 429 { 430 FILE* file; 431 int error; 432 433 file = fopen(filename, "r"); 434 if (!file) 435 return -1; 436 error = ini_parse_file(file, handler, user); 437 fclose(file); 438 return error; 439 } 440 441 /* An ini_reader function to read the next line from a string buffer. This 442 is the fgets() equivalent used by ini_parse_string(). */ 443 static char* ini_reader_string(char* str, int num, void* stream) { 444 ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream; 445 const char* ctx_ptr = ctx->ptr; 446 size_t ctx_num_left = ctx->num_left; 447 char* strp = str; 448 char c; 449 450 if (ctx_num_left == 0 || num < 2) 451 return NULL; 452 453 while (num > 1 && ctx_num_left != 0) { 454 c = *ctx_ptr++; 455 ctx_num_left--; 456 *strp++ = c; 457 if (c == '\n') 458 break; 459 num--; 460 } 461 462 *strp = '\0'; 463 ctx->ptr = ctx_ptr; 464 ctx->num_left = ctx_num_left; 465 return str; 466 } 467 468 /* See documentation in header file. */ 469 int ini_parse_string(const char* string, ini_handler handler, void* user) { 470 ini_parse_string_ctx ctx; 471 472 ctx.ptr = string; 473 ctx.num_left = strlen(string); 474 return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler, 475 user); 476 } 477 478 #endif /* INI_IMPL */ 479 #endif /* INI_H */