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