ini.c (8370B)
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 #if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) 15 #define _CRT_SECURE_NO_WARNINGS 16 #endif 17 18 #include <stdio.h> 19 #include <ctype.h> 20 #include <string.h> 21 22 #include "ini.h" 23 24 #if !INI_USE_STACK 25 #if INI_CUSTOM_ALLOCATOR 26 #include <stddef.h> 27 void* ini_malloc(size_t size); 28 void ini_free(void* ptr); 29 void* ini_realloc(void* ptr, size_t size); 30 #else 31 #include <stdlib.h> 32 #define ini_malloc malloc 33 #define ini_free free 34 #define ini_realloc realloc 35 #endif 36 #endif 37 38 #define MAX_SECTION 50 39 #define MAX_NAME 50 40 41 /* Used by ini_parse_string() to keep track of string parsing state. */ 42 typedef struct { 43 const char* ptr; 44 size_t num_left; 45 } ini_parse_string_ctx; 46 47 /* Strip whitespace chars off end of given string, in place. Return s. */ 48 static char* ini_rstrip(char* s) 49 { 50 char* p = s + strlen(s); 51 while (p > s && isspace((unsigned char)(*--p))) 52 *p = '\0'; 53 return s; 54 } 55 56 /* Return pointer to first non-whitespace char in given string. */ 57 static char* ini_lskip(const char* s) 58 { 59 while (*s && isspace((unsigned char)(*s))) 60 s++; 61 return (char*)s; 62 } 63 64 /* Return pointer to first char (of chars) or inline comment in given string, 65 or pointer to NUL at end of string if neither found. Inline comment must 66 be prefixed by a whitespace character to register as a comment. */ 67 static char* ini_find_chars_or_comment(const char* s, const char* chars) 68 { 69 #if INI_ALLOW_INLINE_COMMENTS 70 int was_space = 0; 71 while (*s && (!chars || !strchr(chars, *s)) && 72 !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) { 73 was_space = isspace((unsigned char)(*s)); 74 s++; 75 } 76 #else 77 while (*s && (!chars || !strchr(chars, *s))) { 78 s++; 79 } 80 #endif 81 return (char*)s; 82 } 83 84 /* Similar to strncpy, but ensures dest (size bytes) is 85 NUL-terminated, and doesn't pad with NULs. */ 86 static char* ini_strncpy0(char* dest, const char* src, size_t size) 87 { 88 /* Could use strncpy internally, but it causes gcc warnings (see issue #91) */ 89 size_t i; 90 for (i = 0; i < size - 1 && src[i]; i++) 91 dest[i] = src[i]; 92 dest[i] = '\0'; 93 return dest; 94 } 95 96 /* See documentation in header file. */ 97 int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, 98 void* user) 99 { 100 /* Uses a fair bit of stack (use heap instead if you need to) */ 101 #if INI_USE_STACK 102 char line[INI_MAX_LINE]; 103 size_t max_line = INI_MAX_LINE; 104 #else 105 char* line; 106 size_t max_line = INI_INITIAL_ALLOC; 107 #endif 108 #if INI_ALLOW_REALLOC && !INI_USE_STACK 109 char* new_line; 110 size_t offset; 111 #endif 112 char section[MAX_SECTION] = ""; 113 char prev_name[MAX_NAME] = ""; 114 115 char* start; 116 char* end; 117 char* name; 118 char* value; 119 int lineno = 0; 120 int error = 0; 121 122 #if !INI_USE_STACK 123 line = (char*)ini_malloc(INI_INITIAL_ALLOC); 124 if (!line) { 125 return -2; 126 } 127 #endif 128 129 #if INI_HANDLER_LINENO 130 #define HANDLER(u, s, n, v) handler(u, s, n, v, lineno) 131 #else 132 #define HANDLER(u, s, n, v) handler(u, s, n, v) 133 #endif 134 135 /* Scan through stream line by line */ 136 while (reader(line, (int)max_line, stream) != NULL) { 137 #if INI_ALLOW_REALLOC && !INI_USE_STACK 138 offset = strlen(line); 139 while (offset == max_line - 1 && line[offset - 1] != '\n') { 140 max_line *= 2; 141 if (max_line > INI_MAX_LINE) 142 max_line = INI_MAX_LINE; 143 new_line = ini_realloc(line, max_line); 144 if (!new_line) { 145 ini_free(line); 146 return -2; 147 } 148 line = new_line; 149 if (reader(line + offset, (int)(max_line - offset), stream) == NULL) 150 break; 151 if (max_line >= INI_MAX_LINE) 152 break; 153 offset += strlen(line + offset); 154 } 155 #endif 156 157 lineno++; 158 159 start = line; 160 #if INI_ALLOW_BOM 161 if (lineno == 1 && (unsigned char)start[0] == 0xEF && 162 (unsigned char)start[1] == 0xBB && 163 (unsigned char)start[2] == 0xBF) { 164 start += 3; 165 } 166 #endif 167 start = ini_lskip(ini_rstrip(start)); 168 169 if (strchr(INI_START_COMMENT_PREFIXES, *start)) { 170 /* Start-of-line comment */ 171 } 172 #if INI_ALLOW_MULTILINE 173 else if (*prev_name && *start && start > line) { 174 #if INI_ALLOW_INLINE_COMMENTS 175 end = ini_find_chars_or_comment(start, NULL); 176 if (*end) 177 *end = '\0'; 178 ini_rstrip(start); 179 #endif 180 /* Non-blank line with leading whitespace, treat as continuation 181 of previous name's value (as per Python configparser). */ 182 if (!HANDLER(user, section, prev_name, start) && !error) 183 error = lineno; 184 } 185 #endif 186 else if (*start == '[') { 187 /* A "[section]" line */ 188 end = ini_find_chars_or_comment(start + 1, "]"); 189 if (*end == ']') { 190 *end = '\0'; 191 ini_strncpy0(section, start + 1, sizeof(section)); 192 *prev_name = '\0'; 193 #if INI_CALL_HANDLER_ON_NEW_SECTION 194 if (!HANDLER(user, section, NULL, NULL) && !error) 195 error = lineno; 196 #endif 197 } 198 else if (!error) { 199 /* No ']' found on section line */ 200 error = lineno; 201 } 202 } 203 else if (*start) { 204 /* Not a comment, must be a name[=:]value pair */ 205 end = ini_find_chars_or_comment(start, "=:"); 206 if (*end == '=' || *end == ':') { 207 *end = '\0'; 208 name = ini_rstrip(start); 209 value = end + 1; 210 #if INI_ALLOW_INLINE_COMMENTS 211 end = ini_find_chars_or_comment(value, NULL); 212 if (*end) 213 *end = '\0'; 214 #endif 215 value = ini_lskip(value); 216 ini_rstrip(value); 217 218 /* Valid name[=:]value pair found, call handler */ 219 ini_strncpy0(prev_name, name, sizeof(prev_name)); 220 if (!HANDLER(user, section, name, value) && !error) 221 error = lineno; 222 } 223 else if (!error) { 224 /* No '=' or ':' found on name[=:]value line */ 225 #if INI_ALLOW_NO_VALUE 226 *end = '\0'; 227 name = ini_rstrip(start); 228 if (!HANDLER(user, section, name, NULL) && !error) 229 error = lineno; 230 #else 231 error = lineno; 232 #endif 233 } 234 } 235 236 #if INI_STOP_ON_FIRST_ERROR 237 if (error) 238 break; 239 #endif 240 } 241 242 #if !INI_USE_STACK 243 ini_free(line); 244 #endif 245 246 return error; 247 } 248 249 /* See documentation in header file. */ 250 int ini_parse_file(FILE* file, ini_handler handler, void* user) 251 { 252 return ini_parse_stream((ini_reader)fgets, file, handler, user); 253 } 254 255 /* See documentation in header file. */ 256 int ini_parse(const char* filename, ini_handler handler, void* user) 257 { 258 FILE* file; 259 int error; 260 261 file = fopen(filename, "r"); 262 if (!file) 263 return -1; 264 error = ini_parse_file(file, handler, user); 265 fclose(file); 266 return error; 267 } 268 269 /* An ini_reader function to read the next line from a string buffer. This 270 is the fgets() equivalent used by ini_parse_string(). */ 271 static char* ini_reader_string(char* str, int num, void* stream) { 272 ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream; 273 const char* ctx_ptr = ctx->ptr; 274 size_t ctx_num_left = ctx->num_left; 275 char* strp = str; 276 char c; 277 278 if (ctx_num_left == 0 || num < 2) 279 return NULL; 280 281 while (num > 1 && ctx_num_left != 0) { 282 c = *ctx_ptr++; 283 ctx_num_left--; 284 *strp++ = c; 285 if (c == '\n') 286 break; 287 num--; 288 } 289 290 *strp = '\0'; 291 ctx->ptr = ctx_ptr; 292 ctx->num_left = ctx_num_left; 293 return str; 294 } 295 296 /* See documentation in header file. */ 297 int ini_parse_string(const char* string, ini_handler handler, void* user) { 298 ini_parse_string_ctx ctx; 299 300 ctx.ptr = string; 301 ctx.num_left = strlen(string); 302 return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler, 303 user); 304 }